mirror of
https://github.com/pion/webrtc.git
synced 2025-10-25 08:10:37 +08:00
16
pkg/rtcp/errors.go
Normal file
16
pkg/rtcp/errors.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package rtcp
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errInvalidTotalLost = errors.New("rtcp: invalid total lost count")
|
||||
errInvalidHeader = errors.New("rtcp: invalid header")
|
||||
errTooManyReports = errors.New("rtcp: too many reports")
|
||||
errTooManyChunks = errors.New("rtcp: too many chunks")
|
||||
errTooManySources = errors.New("rtcp: too many sources")
|
||||
errPacketTooShort = errors.New("rtcp: packet too short")
|
||||
errWrongType = errors.New("rtcp: wrong packet type")
|
||||
errSDESTextTooLong = errors.New("rtcp: sdes must be < 255 octets long")
|
||||
errSDESMissingType = errors.New("rtcp: sdes item missing type")
|
||||
errReasonTooLong = errors.New("rtcp: reason must be < 255 octets long")
|
||||
)
|
||||
133
pkg/rtcp/goodbye.go
Normal file
133
pkg/rtcp/goodbye.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// The Goodbye packet indicates that one or more sources are no longer active.
|
||||
type Goodbye struct {
|
||||
// The SSRC/CSRC identifiers that are no longer active
|
||||
Sources []uint32
|
||||
// Optional text indicating the reason for leaving, e.g., "camera malfunction" or "RTP loop detected"
|
||||
Reason string
|
||||
}
|
||||
|
||||
// Marshal encodes the Goodbye packet in binary
|
||||
func (g Goodbye) 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
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* |V=2|P| SC | PT=BYE=203 | length |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | SSRC/CSRC |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* : ... :
|
||||
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
* (opt) | length | reason for leaving ...
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
rawPacket := make([]byte, len(g.Sources)*ssrcLength)
|
||||
|
||||
if len(g.Sources) > countMax {
|
||||
return nil, errTooManySources
|
||||
}
|
||||
|
||||
for i, s := range g.Sources {
|
||||
binary.BigEndian.PutUint32(rawPacket[i*ssrcLength:], s)
|
||||
}
|
||||
|
||||
if g.Reason != "" {
|
||||
reason := []byte(g.Reason)
|
||||
|
||||
if len(reason) > sdesMaxOctetCount {
|
||||
return nil, errReasonTooLong
|
||||
}
|
||||
|
||||
rawPacket = append(rawPacket, uint8(len(reason)))
|
||||
rawPacket = append(rawPacket, reason...)
|
||||
|
||||
// align to 32-bit boundary
|
||||
if len(rawPacket)%4 != 0 {
|
||||
padCount := 4 - len(rawPacket)%4
|
||||
rawPacket = append(rawPacket, make([]byte, padCount)...)
|
||||
}
|
||||
}
|
||||
|
||||
h := Header{
|
||||
Version: 2,
|
||||
Padding: false,
|
||||
Count: uint8(len(g.Sources)),
|
||||
Type: TypeGoodbye,
|
||||
Length: uint16(headerLength + len(rawPacket)),
|
||||
}
|
||||
hData, err := h.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawPacket = append(hData, rawPacket...)
|
||||
|
||||
return rawPacket, nil
|
||||
}
|
||||
|
||||
// Unmarshal decodes the Goodbye packet from binary
|
||||
func (g *Goodbye) 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| SC | PT=BYE=203 | length |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | SSRC/CSRC |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* : ... :
|
||||
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
* (opt) | length | reason for leaving ...
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
var header Header
|
||||
if err := header.Unmarshal(rawPacket); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if header.Type != TypeGoodbye {
|
||||
return errWrongType
|
||||
}
|
||||
|
||||
if len(rawPacket)%4 != 0 {
|
||||
return errPacketTooShort
|
||||
}
|
||||
|
||||
g.Sources = make([]uint32, header.Count)
|
||||
|
||||
reasonOffset := int(headerLength + header.Count*ssrcLength)
|
||||
if reasonOffset > len(rawPacket) {
|
||||
return errPacketTooShort
|
||||
}
|
||||
|
||||
for i := 0; i < int(header.Count); i++ {
|
||||
offset := headerLength + i*ssrcLength
|
||||
if offset > len(rawPacket) {
|
||||
return errPacketTooShort
|
||||
}
|
||||
|
||||
g.Sources[i] = binary.BigEndian.Uint32(rawPacket[offset:])
|
||||
}
|
||||
|
||||
if reasonOffset < len(rawPacket) {
|
||||
reasonLen := int(rawPacket[reasonOffset])
|
||||
reasonEnd := reasonOffset + 1 + reasonLen
|
||||
|
||||
if reasonEnd > len(rawPacket) {
|
||||
return errPacketTooShort
|
||||
}
|
||||
|
||||
g.Reason = string(rawPacket[reasonOffset+1 : reasonEnd])
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
207
pkg/rtcp/goodbye_test.go
Normal file
207
pkg/rtcp/goodbye_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGoodbyeUnmarshal(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Data []byte
|
||||
Want Goodbye
|
||||
WantError error
|
||||
}{
|
||||
{
|
||||
Name: "valid",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=1, BYE, len=12
|
||||
0x81, 0xcb, 0x00, 0x0c,
|
||||
// ssrc=0x902f9e2e
|
||||
0x90, 0x2f, 0x9e, 0x2e,
|
||||
// len=3, text=FOO
|
||||
0x03, 0x46, 0x4f, 0x4f,
|
||||
},
|
||||
Want: Goodbye{
|
||||
Sources: []uint32{0x902f9e2e},
|
||||
Reason: "FOO",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "invalid octet count",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=1, BYE, len=12
|
||||
0x81, 0xcb, 0x00, 0x0c,
|
||||
// ssrc=0x902f9e2e
|
||||
0x90, 0x2f, 0x9e, 0x2e,
|
||||
// len=4, text=FOO
|
||||
0x04, 0x46, 0x4f, 0x4f,
|
||||
},
|
||||
WantError: errPacketTooShort,
|
||||
},
|
||||
{
|
||||
Name: "wrong type",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=1, SDES, len=12
|
||||
0x81, 0xca, 0x00, 0x0c,
|
||||
// ssrc=0x902f9e2e
|
||||
0x90, 0x2f, 0x9e, 0x2e,
|
||||
// len=3, text=FOO
|
||||
0x03, 0x46, 0x4f, 0x4f,
|
||||
},
|
||||
WantError: errWrongType,
|
||||
},
|
||||
{
|
||||
Name: "short reason",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=1, BYE, len=12
|
||||
0x81, 0xcb, 0x00, 0x0c,
|
||||
// ssrc=0x902f9e2e
|
||||
0x90, 0x2f, 0x9e, 0x2e,
|
||||
// len=3, text=F + padding
|
||||
0x01, 0x46, 0x00, 0x00,
|
||||
},
|
||||
Want: Goodbye{
|
||||
Sources: []uint32{0x902f9e2e},
|
||||
Reason: "F",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "not byte aligned",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=1, BYE, len=10
|
||||
0x81, 0xcb, 0x00, 0x0a,
|
||||
// ssrc=0x902f9e2e
|
||||
0x90, 0x2f, 0x9e, 0x2e,
|
||||
// len=1, text=F
|
||||
0x01, 0x46,
|
||||
},
|
||||
WantError: errPacketTooShort,
|
||||
},
|
||||
{
|
||||
Name: "bad count in header",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=2, BYE, len=8
|
||||
0x82, 0xcb, 0x00, 0x0c,
|
||||
// ssrc=0x902f9e2e
|
||||
0x90, 0x2f, 0x9e, 0x2e,
|
||||
},
|
||||
WantError: errPacketTooShort,
|
||||
},
|
||||
{
|
||||
Name: "empty packet",
|
||||
Data: []byte{
|
||||
// v=2, p=0, count=0, BYE, len=4
|
||||
0x80, 0xcb, 0x00, 0x04,
|
||||
},
|
||||
Want: Goodbye{
|
||||
Sources: []uint32{},
|
||||
Reason: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nil",
|
||||
Data: nil,
|
||||
WantError: errInvalidHeader,
|
||||
},
|
||||
} {
|
||||
var bye Goodbye
|
||||
err := bye.Unmarshal(test.Data)
|
||||
if got, want := err, test.WantError; got != want {
|
||||
t.Fatalf("Unmarshal %q bye: err = %v, want %v", test.Name, got, want)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if got, want := bye, test.Want; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("Unmarshal %q bye: got %v, want %v", test.Name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestGoodbyeRoundTrip(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Bye Goodbye
|
||||
WantError error
|
||||
}{
|
||||
{
|
||||
Name: "empty",
|
||||
Bye: Goodbye{
|
||||
Sources: []uint32{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid",
|
||||
Bye: Goodbye{
|
||||
Sources: []uint32{
|
||||
0x01020304,
|
||||
0x05060708,
|
||||
},
|
||||
Reason: "because",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "empty reason",
|
||||
Bye: Goodbye{
|
||||
Sources: []uint32{0x01020304},
|
||||
Reason: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "reason no source",
|
||||
Bye: Goodbye{
|
||||
Sources: []uint32{},
|
||||
Reason: "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "short reason",
|
||||
Bye: Goodbye{
|
||||
Sources: []uint32{},
|
||||
Reason: "f",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "count overflow",
|
||||
Bye: Goodbye{
|
||||
Sources: tooManySources,
|
||||
},
|
||||
WantError: errTooManySources,
|
||||
},
|
||||
{
|
||||
Name: "reason too long",
|
||||
Bye: Goodbye{
|
||||
Sources: []uint32{},
|
||||
Reason: tooLongText,
|
||||
},
|
||||
WantError: errReasonTooLong,
|
||||
},
|
||||
} {
|
||||
data, err := test.Bye.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 bye Goodbye
|
||||
if err := bye.Unmarshal(data); err != nil {
|
||||
t.Fatalf("Unmarshal %q: %v", test.Name, err)
|
||||
}
|
||||
|
||||
if got, want := bye, test.Bye; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("%q sdes round trip: got %#v, want %#v", test.Name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a slice with enough sources to overflow an 5-bit int
|
||||
var tooManySources []uint32
|
||||
|
||||
func init() {
|
||||
for i := 0; i < (1 << 5); i++ {
|
||||
tooManySources = append(tooManySources, 0x00)
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package rtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PacketType specifies the type of an RTCP packet
|
||||
@@ -55,15 +53,6 @@ type Header struct {
|
||||
Length uint16
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidTotalLost = errors.New("rtcp: invalid total lost count")
|
||||
errInvalidHeader = errors.New("rtcp: invalid header")
|
||||
errTooManyReports = errors.New("rtcp: too many reports")
|
||||
errTooManyChunks = errors.New("rtcp: too many chunks")
|
||||
errPacketTooShort = errors.New("rtcp: packet too short")
|
||||
errWrongType = errors.New("rtcp: wrong packet type")
|
||||
)
|
||||
|
||||
const (
|
||||
headerLength = 4
|
||||
versionShift = 6
|
||||
|
||||
@@ -107,4 +107,20 @@ func TestUnmarshal(t *testing.T) {
|
||||
if got, want := sdes, wantSdes; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Unmarshal sdes: got %#v, want %#v", got, want)
|
||||
}
|
||||
|
||||
// Goodbye
|
||||
_, packet, err = r.ReadPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("Read bye: %v", err)
|
||||
}
|
||||
var bye Goodbye
|
||||
if err := bye.Unmarshal(packet); err != nil {
|
||||
t.Errorf("Unmarshal bye: %v", err)
|
||||
}
|
||||
wantBye := Goodbye{
|
||||
Sources: []uint32{0x902f9e2e},
|
||||
}
|
||||
if got, want := bye, wantBye; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Unmarshal bye: got %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package rtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SDESType is the item type used in the RTCP SDES control packet.
|
||||
@@ -47,11 +45,6 @@ func (s SDESType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errSDESTextTooLong = errors.New("session description must be < 255 octets long")
|
||||
errSDESMissingType = errors.New("session description item missing type")
|
||||
)
|
||||
|
||||
const (
|
||||
sdesSourceLen = 4
|
||||
sdesTypeLen = 1
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestSourceDescriptionRoundTrip(t *testing.T) {
|
||||
Chunks: []SourceDescriptionChunk{{
|
||||
Items: []SourceDescriptionItem{{
|
||||
Type: SDESCNAME,
|
||||
Text: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
Text: tooLongText,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
@@ -358,9 +358,13 @@ func TestSourceDescriptionRoundTrip(t *testing.T) {
|
||||
|
||||
// a slice with enough SourceDescriptionChunks to overflow an 5-bit int
|
||||
var tooManyChunks []SourceDescriptionChunk
|
||||
var tooLongText string
|
||||
|
||||
func init() {
|
||||
for i := 0; i < (1 << 5); i++ {
|
||||
tooManyChunks = append(tooManyChunks, SourceDescriptionChunk{})
|
||||
}
|
||||
for i := 0; i < (1 << 8); i++ {
|
||||
tooLongText += "x"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user