From d6e313bc2a54270b883ed0c3ab6b64865e0a3922 Mon Sep 17 00:00:00 2001 From: Shlomi Amit Date: Fri, 28 Jun 2024 13:38:24 +0300 Subject: [PATCH] Support application-defined packet --- application_packet.go | 117 ++++++++++++++++++++++++ application_packet_test.go | 183 +++++++++++++++++++++++++++++++++++++ errors.go | 3 + packet.go | 3 + 4 files changed, 306 insertions(+) create mode 100644 application_packet.go create mode 100644 application_packet_test.go diff --git a/application_packet.go b/application_packet.go new file mode 100644 index 0000000..59b058e --- /dev/null +++ b/application_packet.go @@ -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} +} + +// 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 + } + + rawPacket := make([]byte, packetSize) + copy(rawPacket[:], headerBytes) + 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 { + + /* + 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 + } + + 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 { + 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 + +} diff --git a/application_packet_test.go b/application_packet_test.go new file mode 100644 index 0000000..2bedb85 --- /dev/null +++ b/application_packet_test.go @@ -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}, + }, + }, { + 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 + 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 + marshalSize := test.Packet.MarshalSize() + if marshalSize != len(rawPacket) { + t.Fatalf("MarshalSize %q result: got %d bytes instead of %d", test.Name, len(rawPacket), marshalSize) + } + + } +} diff --git a/errors.go b/errors.go index d2477af..4334572 100644 --- a/errors.go +++ b/errors.go @@ -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") + ) diff --git a/packet.go b/packet.go index cdb83c0..fc3c9a3 100644 --- a/packet.go +++ b/packet.go @@ -114,6 +114,9 @@ func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) { case TypeExtendedReport: packet = new(ExtendedReport) + case TypeApplicationDefined: + packet = new(ApplicationDefined) + default: packet = new(RawPacket) }