mirror of
https://github.com/pion/webrtc.git
synced 2025-10-23 07:09:27 +08:00
@@ -24,8 +24,8 @@ type Header struct {
|
|||||||
// some additional padding octets at the end which are not part of
|
// some additional padding octets at the end which are not part of
|
||||||
// the control information but are included in the length field.
|
// the control information but are included in the length field.
|
||||||
Padding bool
|
Padding bool
|
||||||
// The number of reception report blocks contained in this packet.
|
// The number of reception reports or sources contained in this packet (depending on the Type)
|
||||||
ReportCount uint8
|
Count uint8
|
||||||
// The RTCP packet type for this packet
|
// The RTCP packet type for this packet
|
||||||
Type uint8
|
Type uint8
|
||||||
// The length of this RTCP packet in 32-bit words minus one,
|
// The length of this RTCP packet in 32-bit words minus one,
|
||||||
@@ -34,20 +34,20 @@ type Header struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidVersion = errors.New("invalid version")
|
errInvalidVersion = errors.New("invalid version")
|
||||||
errInvalidReportCount = errors.New("invalid report count")
|
errInvalidCount = errors.New("invalid count header")
|
||||||
errInvalidTotalLost = errors.New("invalid total lost count")
|
errInvalidTotalLost = errors.New("invalid total lost count")
|
||||||
errPacketTooShort = errors.New("packet too short")
|
errPacketTooShort = errors.New("packet too short")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
headerLength = 4
|
headerLength = 4
|
||||||
versionShift = 6
|
versionShift = 6
|
||||||
versionMask = 0x3
|
versionMask = 0x3
|
||||||
paddingShift = 5
|
paddingShift = 5
|
||||||
paddingMask = 0x1
|
paddingMask = 0x1
|
||||||
reportCountShift = 0
|
countShift = 0
|
||||||
reportCountMask = 0x1f
|
countMask = 0x1f
|
||||||
)
|
)
|
||||||
|
|
||||||
// Marshal encodes the Header in binary
|
// Marshal encodes the Header in binary
|
||||||
@@ -70,10 +70,10 @@ func (h Header) Marshal() ([]byte, error) {
|
|||||||
rawPacket[0] |= 1 << paddingShift
|
rawPacket[0] |= 1 << paddingShift
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.ReportCount > 31 {
|
if h.Count > 31 {
|
||||||
return nil, errInvalidReportCount
|
return nil, errInvalidCount
|
||||||
}
|
}
|
||||||
rawPacket[0] |= h.ReportCount << reportCountShift
|
rawPacket[0] |= h.Count << countShift
|
||||||
|
|
||||||
rawPacket[1] = h.Type
|
rawPacket[1] = h.Type
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ func (h *Header) Unmarshal(rawPacket []byte) error {
|
|||||||
|
|
||||||
h.Version = rawPacket[0] >> versionShift & versionMask
|
h.Version = rawPacket[0] >> versionShift & versionMask
|
||||||
h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
|
h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
|
||||||
h.ReportCount = rawPacket[0] >> reportCountShift & reportCountMask
|
h.Count = rawPacket[0] >> countShift & countMask
|
||||||
|
|
||||||
h.Type = rawPacket[1]
|
h.Type = rawPacket[1]
|
||||||
|
|
||||||
|
@@ -46,21 +46,21 @@ func TestHeaderRoundTrip(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "valid",
|
Name: "valid",
|
||||||
Header: Header{
|
Header: Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Padding: true,
|
Padding: true,
|
||||||
ReportCount: 31,
|
Count: 31,
|
||||||
Type: TypeSenderReport,
|
Type: TypeSenderReport,
|
||||||
Length: 4,
|
Length: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "also valid",
|
Name: "also valid",
|
||||||
Header: Header{
|
Header: Header{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Padding: false,
|
Padding: false,
|
||||||
ReportCount: 28,
|
Count: 28,
|
||||||
Type: TypeReceiverReport,
|
Type: TypeReceiverReport,
|
||||||
Length: 65535,
|
Length: 65535,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -71,11 +71,11 @@ func TestHeaderRoundTrip(t *testing.T) {
|
|||||||
WantError: errInvalidVersion,
|
WantError: errInvalidVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "invalid report count",
|
Name: "invalid count",
|
||||||
Header: Header{
|
Header: Header{
|
||||||
ReportCount: 40,
|
Count: 40,
|
||||||
},
|
},
|
||||||
WantError: errInvalidReportCount,
|
WantError: errInvalidCount,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
data, err := test.Header.Marshal()
|
data, err := test.Header.Marshal()
|
||||||
|
264
pkg/rtcp/source_description.go
Normal file
264
pkg/rtcp/source_description.go
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
package rtcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RTP SDES item types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5
|
||||||
|
const (
|
||||||
|
SDESEnd = iota // end of SDES list RFC 3550, 6.5
|
||||||
|
SDESCNAME // canonical name RFC 3550, 6.5.1
|
||||||
|
SDESName // user name RFC 3550, 6.5.2
|
||||||
|
SDESEmail // user's electronic mail address RFC 3550, 6.5.3
|
||||||
|
SDESPhone // user's phone number RFC 3550, 6.5.4
|
||||||
|
SDESLocation // geographic user location RFC 3550, 6.5.5
|
||||||
|
SDESTool // name of application or tool RFC 3550, 6.5.6
|
||||||
|
SDESNote // notice about the source RFC 3550, 6.5.7
|
||||||
|
SDESPrivate // private extensions RFC 3550, 6.5.8 (not implemented)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errSDESTextTooLong = errors.New("session description must be < 255 octets long")
|
||||||
|
errSDESMissingType = errors.New("session description item missing type")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sdesSourceLen = 4
|
||||||
|
sdesTypeLen = 1
|
||||||
|
sdesTypeOffset = 0
|
||||||
|
sdesOctetCountLen = 1
|
||||||
|
sdesOctetCountOffset = 1
|
||||||
|
sdesMaxOctetCount = (1 << 8) - 1
|
||||||
|
sdesTextOffset = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// A SourceDescription (SDES) packet describes the sources in an RTP stream.
|
||||||
|
type SourceDescription struct {
|
||||||
|
Chunks []SourceDescriptionChunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal encodes the SourceDescription in binary
|
||||||
|
func (s SourceDescription) Marshal() ([]byte, error) {
|
||||||
|
/*
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
* chunk | SSRC/CSRC_1 |
|
||||||
|
* 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | SDES items |
|
||||||
|
* | ... |
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
* chunk | SSRC/CSRC_2 |
|
||||||
|
* 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | SDES items |
|
||||||
|
* | ... |
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
*/
|
||||||
|
|
||||||
|
rawPacket := make([]byte, 0)
|
||||||
|
for _, c := range s.Chunks {
|
||||||
|
data, err := c.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawPacket = append(rawPacket, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawPacket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes the SourceDescription from binary
|
||||||
|
func (s *SourceDescription) Unmarshal(rawPacket []byte) error {
|
||||||
|
/*
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
* chunk | SSRC/CSRC_1 |
|
||||||
|
* 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | SDES items |
|
||||||
|
* | ... |
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
* chunk | SSRC/CSRC_2 |
|
||||||
|
* 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | SDES items |
|
||||||
|
* | ... |
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
*/
|
||||||
|
|
||||||
|
for i := 0; i < len(rawPacket); {
|
||||||
|
var chunk SourceDescriptionChunk
|
||||||
|
if err := chunk.Unmarshal(rawPacket[i:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Chunks = append(s.Chunks, chunk)
|
||||||
|
|
||||||
|
i += chunk.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SourceDescriptionChunk contains items describing a single RTP source
|
||||||
|
type SourceDescriptionChunk struct {
|
||||||
|
// The source (ssrc) or contributing source (csrc) identifier this packet describes
|
||||||
|
Source uint32
|
||||||
|
Items []SourceDescriptionItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal encodes the SourceDescriptionChunk in binary
|
||||||
|
func (s SourceDescriptionChunk) Marshal() ([]byte, error) {
|
||||||
|
/*
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
* | SSRC/CSRC_1 |
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | SDES items |
|
||||||
|
* | ... |
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
*/
|
||||||
|
|
||||||
|
rawPacket := make([]byte, sdesSourceLen)
|
||||||
|
binary.BigEndian.PutUint32(rawPacket, s.Source)
|
||||||
|
|
||||||
|
for _, it := range s.Items {
|
||||||
|
data, err := it.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawPacket = append(rawPacket, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The list of items in each chunk MUST be terminated by one or more null octets
|
||||||
|
rawPacket = append(rawPacket, SDESEnd)
|
||||||
|
|
||||||
|
// additional null octets MUST be included if needed to pad until the next 32-bit boundary
|
||||||
|
if size := len(rawPacket); size%4 != 0 {
|
||||||
|
padding := make([]byte, 4-size%4)
|
||||||
|
rawPacket = append(rawPacket, padding...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawPacket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes the SourceDescriptionChunk from binary
|
||||||
|
func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {
|
||||||
|
/*
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
* | SSRC/CSRC_1 |
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | SDES items |
|
||||||
|
* | ... |
|
||||||
|
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||||
|
*/
|
||||||
|
|
||||||
|
if len(rawPacket) < (sdesSourceLen + sdesTypeLen) {
|
||||||
|
return errPacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Source = binary.BigEndian.Uint32(rawPacket)
|
||||||
|
|
||||||
|
for i := 4; i < len(rawPacket); {
|
||||||
|
if pktType := rawPacket[i]; pktType == SDESEnd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var it SourceDescriptionItem
|
||||||
|
if err := it.Unmarshal(rawPacket[i:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Items = append(s.Items, it)
|
||||||
|
i += it.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
return errPacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SourceDescriptionChunk) len() int {
|
||||||
|
len := sdesSourceLen
|
||||||
|
for _, it := range s.Items {
|
||||||
|
len += it.len()
|
||||||
|
}
|
||||||
|
len += sdesTypeLen // for terminating null octet
|
||||||
|
|
||||||
|
// align to 32-bit boundary
|
||||||
|
if len%4 != 0 {
|
||||||
|
len += 4 - (len % 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.
|
||||||
|
type SourceDescriptionItem struct {
|
||||||
|
// The type identifier for this item. eg, SDESCNAME for canonical name description.
|
||||||
|
//
|
||||||
|
// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.
|
||||||
|
Type uint8
|
||||||
|
// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SourceDescriptionItem) len() int {
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | CNAME=1 | length | user and domain name ...
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
return sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal encodes the SourceDescriptionItem in binary
|
||||||
|
func (s SourceDescriptionItem) Marshal() ([]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
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | CNAME=1 | length | user and domain name ...
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
|
||||||
|
if s.Type == SDESEnd {
|
||||||
|
return nil, errSDESMissingType
|
||||||
|
}
|
||||||
|
|
||||||
|
rawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen)
|
||||||
|
|
||||||
|
rawPacket[sdesTypeOffset] = s.Type
|
||||||
|
|
||||||
|
txtBytes := []byte(s.Text)
|
||||||
|
octetCount := len(txtBytes)
|
||||||
|
if octetCount > sdesMaxOctetCount {
|
||||||
|
return nil, errSDESTextTooLong
|
||||||
|
}
|
||||||
|
rawPacket[sdesOctetCountOffset] = uint8(octetCount)
|
||||||
|
|
||||||
|
rawPacket = append(rawPacket, txtBytes...)
|
||||||
|
|
||||||
|
return rawPacket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes the SourceDescriptionItem from binary
|
||||||
|
func (s *SourceDescriptionItem) 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
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
* | CNAME=1 | length | user and domain name ...
|
||||||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
|
||||||
|
if len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) {
|
||||||
|
return errPacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Type = rawPacket[sdesTypeOffset]
|
||||||
|
|
||||||
|
octetCount := int(rawPacket[sdesOctetCountOffset])
|
||||||
|
if sdesTextOffset+octetCount > len(rawPacket) {
|
||||||
|
return errPacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
txtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount]
|
||||||
|
s.Text = string(txtBytes)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
299
pkg/rtcp/source_description_test.go
Normal file
299
pkg/rtcp/source_description_test.go
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package rtcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSourceDescriptionUnmarshal(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
Name string
|
||||||
|
Data []byte
|
||||||
|
Want SourceDescription
|
||||||
|
WantError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "nil",
|
||||||
|
Data: nil,
|
||||||
|
Want: SourceDescription{
|
||||||
|
Chunks: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "missing type",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
WantError: errPacketTooShort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bad cname length",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
// CNAME, len = 1
|
||||||
|
0x01, 0x01,
|
||||||
|
},
|
||||||
|
WantError: errPacketTooShort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "short cname",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
// CNAME, Missing length
|
||||||
|
0x01,
|
||||||
|
},
|
||||||
|
WantError: errPacketTooShort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "no end",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x00000000
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
// CNAME, len=1, content=A
|
||||||
|
0x01, 0x02, 0x41,
|
||||||
|
// Missing END
|
||||||
|
},
|
||||||
|
WantError: errPacketTooShort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bad octet count",
|
||||||
|
Data: []byte{0, 0, 0, 0, 1, 1},
|
||||||
|
WantError: errPacketTooShort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "zero item chunk",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x01020304
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
// END + padding
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
Want: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Source: 0x01020304,
|
||||||
|
Items: nil,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "empty string",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x01020304
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
// CNAME, len=0
|
||||||
|
0x01, 0x00,
|
||||||
|
// END + padding
|
||||||
|
0x00, 0x00,
|
||||||
|
},
|
||||||
|
Want: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Source: 0x01020304,
|
||||||
|
Items: []SourceDescriptionItem{
|
||||||
|
{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "two items",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x10000000
|
||||||
|
0x10, 0x00, 0x00, 0x00,
|
||||||
|
// CNAME, len=1, content=A
|
||||||
|
0x01, 0x01, 0x41,
|
||||||
|
// PHONE, len=1, content=B
|
||||||
|
0x04, 0x01, 0x42,
|
||||||
|
// END + padding
|
||||||
|
0x00, 0x00,
|
||||||
|
},
|
||||||
|
Want: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{
|
||||||
|
{
|
||||||
|
Source: 0x10000000,
|
||||||
|
Items: []SourceDescriptionItem{
|
||||||
|
{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: SDESPhone,
|
||||||
|
Text: "B",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "two chunks",
|
||||||
|
Data: []byte{
|
||||||
|
// ssrc=0x01020304
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
// Chunk 1
|
||||||
|
// CNAME, len=1, content=A
|
||||||
|
0x01, 0x01, 0x41,
|
||||||
|
// END
|
||||||
|
0x00,
|
||||||
|
// Chunk 2
|
||||||
|
// SSRC 0x05060708
|
||||||
|
0x05, 0x06, 0x07, 0x08,
|
||||||
|
// CNAME, len=3, content=BCD
|
||||||
|
0x01, 0x03, 0x42, 0x43, 0x44,
|
||||||
|
// END
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
Want: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{
|
||||||
|
{
|
||||||
|
Source: 0x01020304,
|
||||||
|
Items: []SourceDescriptionItem{
|
||||||
|
{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "A",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: 0x05060708,
|
||||||
|
Items: []SourceDescriptionItem{
|
||||||
|
{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "BCD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
var sdes SourceDescription
|
||||||
|
err := sdes.Unmarshal(test.Data)
|
||||||
|
if got, want := err, test.WantError; got != want {
|
||||||
|
t.Fatalf("Unmarshal %q: err = %v, want %v", test.Name, got, want)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := sdes, test.Want; !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("Unmarshal %q: got %#v, want %#v", test.Name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSourceDescriptionRoundTrip(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
Name string
|
||||||
|
Desc SourceDescription
|
||||||
|
WantError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "valid",
|
||||||
|
Desc: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{
|
||||||
|
{
|
||||||
|
Source: 1,
|
||||||
|
Items: []SourceDescriptionItem{
|
||||||
|
{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "test@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: 2,
|
||||||
|
Items: []SourceDescriptionItem{
|
||||||
|
{
|
||||||
|
Type: SDESNote,
|
||||||
|
Text: "some note",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: SDESNote,
|
||||||
|
Text: "another note",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "item without type",
|
||||||
|
Desc: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Source: 1,
|
||||||
|
Items: []SourceDescriptionItem{{
|
||||||
|
Text: "test@example.com",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
WantError: errSDESMissingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "zero items",
|
||||||
|
Desc: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Source: 1,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "email item",
|
||||||
|
Desc: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Source: 1,
|
||||||
|
Items: []SourceDescriptionItem{{
|
||||||
|
Type: SDESEmail,
|
||||||
|
Text: "test@example.com",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "empty text",
|
||||||
|
Desc: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Source: 1,
|
||||||
|
Items: []SourceDescriptionItem{{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "text too long",
|
||||||
|
Desc: SourceDescription{
|
||||||
|
Chunks: []SourceDescriptionChunk{{
|
||||||
|
Items: []SourceDescriptionItem{{
|
||||||
|
Type: SDESCNAME,
|
||||||
|
Text: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
WantError: errSDESTextTooLong,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
data, err := test.Desc.Marshal()
|
||||||
|
if got, want := err, test.WantError; got != want {
|
||||||
|
t.Fatalf("Marshal %q: err = %v, want %v", test.Name, got, want)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded SourceDescription
|
||||||
|
if err := decoded.Unmarshal(data); err != nil {
|
||||||
|
t.Fatalf("Unmarshal %q: %v", test.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := decoded, test.Desc; !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("%q sdes round trip: got %#v, want %#v", test.Name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user