Skip to content

Commit

Permalink
Support application-defined packet
Browse files Browse the repository at this point in the history
  • Loading branch information
samitAtsyna committed Jun 28, 2024
1 parent 878e0b0 commit d6e313b
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
117 changes: 117 additions & 0 deletions application_packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package rtcp

import (
"encoding/binary"
)

// ApplicationDefined represents an RTCP application-defined packet.
type ApplicationDefined struct {
SSRC uint32
Name [4]byte
Data []byte
}

// DestinationSSRC returns the SSRC value for this packet.
func (a ApplicationDefined) DestinationSSRC() []uint32 {
return []uint32{a.SSRC}

Check warning on line 16 in application_packet.go

View check run for this annotation

Codecov / codecov/patch

application_packet.go#L15-L16

Added lines #L15 - L16 were not covered by tests
}

// Marshal serializes the AppPacket into a byte slice with padding.
func (a ApplicationDefined) Marshal() ([]byte, error) {
dataLength := len(a.Data)
if dataLength > 0xFFFF-12 {
return nil, errAppDefinedDataTooLarge
}

// Calculate the padding size to be added to make the packet length a multiple of 4 bytes.
paddingSize := 4 - (dataLength % 4)
if paddingSize == 4 {
paddingSize = 0
}

packetSize := a.MarshalSize()
header := Header{
Type: TypeApplicationDefined,
Length: uint16((packetSize / 4) - 1),
Padding: paddingSize != 0,
}

headerBytes, err := header.Marshal()
if err != nil {
return nil, err

Check warning on line 41 in application_packet.go

View check run for this annotation

Codecov / codecov/patch

application_packet.go#L41

Added line #L41 was not covered by tests
}

rawPacket := make([]byte, packetSize)
copy(rawPacket[:], headerBytes)

Check failure on line 45 in application_packet.go

View workflow job for this annotation

GitHub Actions / lint / Go

unslice: could simplify rawPacket[:] to rawPacket (gocritic)
binary.BigEndian.PutUint32(rawPacket[4:8], a.SSRC)
copy(rawPacket[8:12], a.Name[:])
copy(rawPacket[12:], a.Data)

// Add padding if necessary.
if paddingSize > 0 {
for i := 0; i < paddingSize; i++ {
rawPacket[12+dataLength+i] = byte(paddingSize)
}
}

return rawPacket, nil
}

// Unmarshal parses the given raw packet into an AppPacket, handling padding.
func (a *ApplicationDefined) Unmarshal(rawPacket []byte) error {

Check failure on line 61 in application_packet.go

View workflow job for this annotation

GitHub Actions / lint / Go

unnecessary leading newline (whitespace)

Check failure on line 62 in application_packet.go

View workflow job for this annotation

GitHub Actions / lint / Go

File is not `gofumpt`-ed (gofumpt)
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| subtype | PT=APP=204 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC/CSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| name (ASCII) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| application-dependent data ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < 12 {
return errPacketTooShort
}

header := Header{}
err := header.Unmarshal(rawPacket)
if err != nil {
return err

Check warning on line 83 in application_packet.go

View check run for this annotation

Codecov / codecov/patch

application_packet.go#L83

Added line #L83 was not covered by tests
}

if int(header.Length+1)*4 != len(rawPacket) {
return errAppDefinedInvalidLength
}

a.SSRC = binary.BigEndian.Uint32(rawPacket[4:8])
copy(a.Name[:], rawPacket[8:12])

// Check for padding.
paddingSize := 0
if header.Padding {
paddingSize = int(rawPacket[len(rawPacket)-1])
if paddingSize > len(rawPacket)-12 {
return errWrongPadding
}
}

a.Data = rawPacket[12 : len(rawPacket)-paddingSize]

return nil
}

func (a *ApplicationDefined) MarshalSize() int {

Check warning on line 107 in application_packet.go

View workflow job for this annotation

GitHub Actions / lint / Go

exported: exported method ApplicationDefined.MarshalSize should have comment or be unexported (revive)
dataLength := len(a.Data)
// Calculate the padding size to be added to make the packet length a multiple of 4 bytes.
paddingSize := 4 - (dataLength % 4)
if paddingSize == 4 {
paddingSize = 0
}

return 12 + dataLength + paddingSize

Check failure on line 116 in application_packet.go

View workflow job for this annotation

GitHub Actions / lint / Go

File is not `gofumpt`-ed (gofumpt)
}

Check failure on line 117 in application_packet.go

View workflow job for this annotation

GitHub Actions / lint / Go

unnecessary trailing newline (whitespace)
183 changes: 183 additions & 0 deletions application_packet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package rtcp

import (
"errors"
"reflect"
"testing"
)

func TestTApplicationPacketUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want ApplicationDefined
WantError error
}{
{
Name: "valid",
Data: []byte{
// Application Packet Type + Length(0x0003)
0x80, 0xcc, 0x00, 0x03,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUIT'
0x53, 0x55, 0x49, 0x54,
// data='ABCD'
0x41, 0x42, 0x43, 0x44,
},
Want: ApplicationDefined{
SSRC: 0x4baae1ab,
Name: [4]byte{0x53, 0x55, 0x49, 0x54},
Data: []byte{0x41, 0x42, 0x43, 0x44},
},
}, {

Check failure on line 33 in application_packet_test.go

View workflow job for this annotation

GitHub Actions / lint / Go

File is not `gofumpt`-ed (gofumpt)
Name: "validWithPadding",
Data: []byte{
// Application Packet Type + Length(0x0002) (0xA0 has padding bit set)
0xA0, 0xcc, 0x00, 0x04,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUIT'
0x53, 0x55, 0x49, 0x54,
// data='ABCDE'
0x41, 0x42, 0x43, 0x44, 0x45,
// 3 bytes padding as packet length must be a division of 4
0x03, 0x03, 0x03,
},
Want: ApplicationDefined{
SSRC: 0x4baae1ab,
Name: [4]byte{0x53, 0x55, 0x49, 0x54},
Data: []byte{0x41, 0x42, 0x43, 0x44, 0x45},
},
}, {
Name: "invalidAppPacketLengthField",
Data: []byte{
// Application Packet Type + invalid Length(0x00FF)
0x80, 0xcc, 0x00, 0xFF,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUIT'
0x53, 0x55, 0x49, 0x54,
// data='ABCD'
0x41, 0x42, 0x43, 0x44,
},
WantError: errAppDefinedInvalidLength,
}, {
Name: "invalidPacketLengthTooShort",
Data: []byte{
// Application Packet Type + Length(0x0002). Total packet length is less than 12 bytes
0x80, 0xcc, 0x00, 0x2,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUI'
0x53, 0x55, 0x49,
},
WantError: errPacketTooShort,
},
{
Name: "wrongPaddingSize",
Data: []byte{
// Application Packet Type + Length(0x0002) (0xA0 has padding bit set)
0xA0, 0xcc, 0x00, 0x04,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUIT'
0x53, 0x55, 0x49, 0x54,
// data='ABCDE'
0x41, 0x42, 0x43, 0x44, 0x45,
// 3 bytes padding as packet length must be a division of 4
0x03, 0x03, 0x09, // last byte has padding size 0x09 which is more than the data + padding bytes
},
WantError: errWrongPadding,
},
} {
var apk ApplicationDefined
err := apk.Unmarshal(test.Data)
if got, want := err, test.WantError; !errors.Is(got, want) {
t.Fatalf("Unmarshal %q result: got = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}

if got, want := apk, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q result: got %v, want %v", test.Name, got, want)
}
}
}
func TestTApplicationPacketMarshal(t *testing.T) {
for _, test := range []struct {
Name string
Want []byte
Packet ApplicationDefined
WantError error
}{
{
Name: "valid",
Want: []byte{
// Application Packet Type + Length(0x0003)
0x80, 0xcc, 0x00, 0x03,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUIT'
0x53, 0x55, 0x49, 0x54,
// data='ABCD'
0x41, 0x42, 0x43, 0x44,
},
Packet: ApplicationDefined{
SSRC: 0x4baae1ab,
Name: [4]byte{0x53, 0x55, 0x49, 0x54},
Data: []byte{0x41, 0x42, 0x43, 0x44},
},
}, {
Name: "validWithPadding",
Want: []byte{
// Application Packet Type + Length(0x0002) (0xA0 has padding bit set)
0xA0, 0xcc, 0x00, 0x04,
// sender=0x4baae1ab
0x4b, 0xaa, 0xe1, 0xab,
// name='SUIT'
0x53, 0x55, 0x49, 0x54,
// data='ABCDE'
0x41, 0x42, 0x43, 0x44, 0x45,
// 3 bytes padding as packet length must be a division of 4
0x03, 0x03, 0x03,
},
Packet: ApplicationDefined{
SSRC: 0x4baae1ab,
Name: [4]byte{0x53, 0x55, 0x49, 0x54},
Data: []byte{0x41, 0x42, 0x43, 0x44, 0x45},
},
}, {
Name: "invalidDataTooLarge",
WantError: errAppDefinedDataTooLarge,
Packet: ApplicationDefined{
SSRC: 0x4baae1ab,
Name: [4]byte{0x53, 0x55, 0x49, 0x54},
Data: make([]byte, 0xFFFF-12+1), // total max packet size is 0xFFFF including header and other fields.
},
},
} {
rawPacket, err := test.Packet.Marshal()

// Check for expected errors
if got, want := err, test.WantError; !errors.Is(got, want) {
t.Fatalf("Unmarshal %q result: got = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}

// Check for expected succesful result

Check failure on line 171 in application_packet_test.go

View workflow job for this annotation

GitHub Actions / lint / Go

`succesful` is a misspelling of `successful` (misspell)
if got, want := rawPacket, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q result: got %v, want %v", test.Name, got, want)
}

// Check if MarshalSize() is matching the marshalled bytes

Check failure on line 176 in application_packet_test.go

View workflow job for this annotation

GitHub Actions / lint / Go

`marshalled` is a misspelling of `marshaled` (misspell)
marshalSize := test.Packet.MarshalSize()
if marshalSize != len(rawPacket) {
t.Fatalf("MarshalSize %q result: got %d bytes instead of %d", test.Name, len(rawPacket), marshalSize)
}

}

Check failure on line 182 in application_packet_test.go

View workflow job for this annotation

GitHub Actions / lint / Go

unnecessary trailing newline (whitespace)
}
3 changes: 3 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ var (
errWrongChunkType = errors.New("rtcp: wrong chunk type")
errBadStructMemberType = errors.New("rtcp: struct contains unexpected member type")
errBadReadParameter = errors.New("rtcp: cannot read into non-pointer")
errAppDefinedInvalidLength = errors.New("rtcp: application defined packet type invalid length")
errAppDefinedDataTooLarge = errors.New("rtcp: application defined packet data is too large")

Check failure on line 40 in errors.go

View workflow job for this annotation

GitHub Actions / lint / Go

File is not `gofmt`-ed with `-s` (gofmt)
)
3 changes: 3 additions & 0 deletions packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) {
case TypeExtendedReport:
packet = new(ExtendedReport)

case TypeApplicationDefined:
packet = new(ApplicationDefined)

Check warning on line 118 in packet.go

View check run for this annotation

Codecov / codecov/patch

packet.go#L117-L118

Added lines #L117 - L118 were not covered by tests

default:
packet = new(RawPacket)
}
Expand Down

0 comments on commit d6e313b

Please sign in to comment.