-
Notifications
You must be signed in to change notification settings - Fork 53
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 d6e313b
Showing
4 changed files
with
306 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,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 | ||
|
||
} | ||
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,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) | ||
} | ||
|
||
} | ||
} |
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
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