-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
878e0b0
commit e1b695e
Showing
5 changed files
with
408 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
package rtcp | ||
|
||
import ( | ||
"encoding/binary" | ||
) | ||
|
||
// ApplicationDefined represents an RTCP application-defined packet. | ||
type ApplicationDefined struct { | ||
SubType uint8 | ||
SSRC uint32 | ||
Name string | ||
Data []byte | ||
} | ||
|
||
// DestinationSSRC returns the SSRC value for this packet. | ||
func (a ApplicationDefined) DestinationSSRC() []uint32 { | ||
return []uint32{a.SSRC} | ||
} | ||
|
||
// Marshal serializes the application-defined struct into a byte slice with padding. | ||
func (a ApplicationDefined) Marshal() ([]byte, error) { | ||
dataLength := len(a.Data) | ||
if dataLength > 0xFFFF-12 { | ||
return nil, errAppDefinedDataTooLarge | ||
} | ||
if len(a.Name) != 4 { | ||
return nil, errAppDefinedInvalidName | ||
} | ||
// 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, | ||
Count: a.SubType, | ||
} | ||
|
||
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 application-defined struct, 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 ... | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
*/ | ||
header := Header{} | ||
err := header.Unmarshal(rawPacket) | ||
if err != nil { | ||
return err | ||
} | ||
if len(rawPacket) < 12 { | ||
return errPacketTooShort | ||
} | ||
|
||
if int(header.Length+1)*4 != len(rawPacket) { | ||
return errAppDefinedInvalidLength | ||
} | ||
|
||
a.SubType = header.Count | ||
a.SSRC = binary.BigEndian.Uint32(rawPacket[4:8]) | ||
a.Name = string(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 | ||
} | ||
|
||
// MarshalSize returns the size of the packet once marshaled | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
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='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// data='ABCD' | ||
0x41, 0x42, 0x43, 0x44, | ||
}, | ||
Want: ApplicationDefined{ | ||
SubType: 0, | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
Data: []byte{0x41, 0x42, 0x43, 0x44}, | ||
}, | ||
}, | ||
{ | ||
Name: "validCustomSsubType", | ||
Data: []byte{ | ||
// Application Packet Type (SubType 31) + Length(0x0003) | ||
0x9f, 0xcc, 0x00, 0x03, | ||
// sender=0x4baae1ab | ||
0x4b, 0xaa, 0xe1, 0xab, | ||
// name='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// data='ABCD' | ||
0x41, 0x42, 0x43, 0x44, | ||
}, | ||
Want: ApplicationDefined{ | ||
SubType: 31, | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
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='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// data='ABCDE' | ||
0x41, 0x42, 0x43, 0x44, 0x45, | ||
// 3 bytes padding as packet length must be a division of 4 | ||
0x03, 0x03, 0x03, | ||
}, | ||
Want: ApplicationDefined{ | ||
SubType: 0, | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
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='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// 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='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// 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, | ||
}, | ||
{ | ||
Name: "invalidHeader", | ||
Data: []byte{ | ||
// Application Packet Type + invalid Length(0x00FF) | ||
0xFF, | ||
}, | ||
WantError: errPacketTooShort, | ||
}, | ||
} { | ||
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) | ||
} | ||
|
||
// Check SSRC is matching | ||
if apk.SSRC != 0x4baae1ab { | ||
t.Fatalf("SSRC %q result: got packet SSRC %x instead of %x", test.Name, apk.SSRC, 0x4baae1ab) | ||
} | ||
if apk.SSRC != apk.DestinationSSRC()[0] { | ||
t.Fatalf("SSRC %q result: DestinationSSRC() %x doesn't match SSRC field %x", test.Name, apk.DestinationSSRC()[0], apk.SSRC) | ||
} | ||
} | ||
} | ||
|
||
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='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// data='ABCD' | ||
0x41, 0x42, 0x43, 0x44, | ||
}, | ||
Packet: ApplicationDefined{ | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
Data: []byte{0x41, 0x42, 0x43, 0x44}, | ||
}, | ||
}, | ||
{ | ||
Name: "validCustomSubType", | ||
Want: []byte{ | ||
// Application Packet Type (SubType 31) + Length(0x0003) | ||
0x9f, 0xcc, 0x00, 0x03, | ||
// sender=0x4baae1ab | ||
0x4b, 0xaa, 0xe1, 0xab, | ||
// name='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// data='ABCD' | ||
0x41, 0x42, 0x43, 0x44, | ||
}, | ||
Packet: ApplicationDefined{ | ||
SubType: 31, | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
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='NAME' | ||
0x4E, 0x41, 0x4D, 0x45, | ||
// 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: "NAME", | ||
Data: []byte{0x41, 0x42, 0x43, 0x44, 0x45}, | ||
}, | ||
}, | ||
{ | ||
Name: "invalidDataTooLarge", | ||
WantError: errAppDefinedDataTooLarge, | ||
Packet: ApplicationDefined{ | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
Data: make([]byte, 0xFFFF-12+1), // total max packet size is 0xFFFF including header and other fields. | ||
}, | ||
}, | ||
{ | ||
Name: "invalidName", | ||
WantError: errAppDefinedInvalidName, | ||
Packet: ApplicationDefined{ | ||
SSRC: 0x4baae1ab, | ||
Name: "NOT4CHARS", | ||
Data: []byte{0x41, 0x42, 0x43, 0x44}, | ||
}, | ||
}, | ||
{ | ||
Name: "InvalidSubType", | ||
WantError: errInvalidHeader, | ||
Packet: ApplicationDefined{ | ||
SubType: 32, // Must be up to 31 | ||
SSRC: 0x4baae1ab, | ||
Name: "NAME", | ||
Data: []byte{0x41, 0x42, 0x43, 0x44}, | ||
}, | ||
}, | ||
} { | ||
rawPacket, err := test.Packet.Marshal() | ||
|
||
// Check for expected errors | ||
if got, want := err, test.WantError; !errors.Is(got, want) { | ||
t.Fatalf("Marshal %q result: got = %v, want %v", test.Name, got, want) | ||
} | ||
if err != nil { | ||
continue | ||
} | ||
|
||
// Check for expected successful result | ||
if got, want := rawPacket, test.Want; !reflect.DeepEqual(got, want) { | ||
t.Fatalf("Marshal %q result: got %v, want %v", test.Name, got, want) | ||
} | ||
|
||
// Check if MarshalSize() is matching the marshaled 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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.