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 (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PacketType specifies the type of an RTCP packet
|
// PacketType specifies the type of an RTCP packet
|
||||||
@@ -55,15 +53,6 @@ type Header struct {
|
|||||||
Length uint16
|
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 (
|
const (
|
||||||
headerLength = 4
|
headerLength = 4
|
||||||
versionShift = 6
|
versionShift = 6
|
||||||
|
|||||||
@@ -107,4 +107,20 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
if got, want := sdes, wantSdes; !reflect.DeepEqual(got, want) {
|
if got, want := sdes, wantSdes; !reflect.DeepEqual(got, want) {
|
||||||
t.Errorf("Unmarshal sdes: got %#v, want %#v", 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 (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SDESType is the item type used in the RTCP SDES control packet.
|
// 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 (
|
const (
|
||||||
sdesSourceLen = 4
|
sdesSourceLen = 4
|
||||||
sdesTypeLen = 1
|
sdesTypeLen = 1
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ func TestSourceDescriptionRoundTrip(t *testing.T) {
|
|||||||
Chunks: []SourceDescriptionChunk{{
|
Chunks: []SourceDescriptionChunk{{
|
||||||
Items: []SourceDescriptionItem{{
|
Items: []SourceDescriptionItem{{
|
||||||
Type: SDESCNAME,
|
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
|
// a slice with enough SourceDescriptionChunks to overflow an 5-bit int
|
||||||
var tooManyChunks []SourceDescriptionChunk
|
var tooManyChunks []SourceDescriptionChunk
|
||||||
|
var tooLongText string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for i := 0; i < (1 << 5); i++ {
|
for i := 0; i < (1 << 5); i++ {
|
||||||
tooManyChunks = append(tooManyChunks, SourceDescriptionChunk{})
|
tooManyChunks = append(tooManyChunks, SourceDescriptionChunk{})
|
||||||
}
|
}
|
||||||
|
for i := 0; i < (1 << 8); i++ {
|
||||||
|
tooLongText += "x"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user