162 lines
3.7 KiB
Go
162 lines
3.7 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package rtcp
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// A CompoundPacket is a collection of RTCP packets transmitted as a single packet with
|
|
// the underlying protocol (for example UDP).
|
|
//
|
|
// To maximize the resolution of receiption statistics, the first Packet in a CompoundPacket
|
|
// must always be either a SenderReport or a ReceiverReport. This is true even if no data
|
|
// has been sent or received, in which case an empty ReceiverReport must be sent, and even
|
|
// if the only other RTCP packet in the compound packet is a Goodbye.
|
|
//
|
|
// Next, a SourceDescription containing a CNAME item must be included in each CompoundPacket
|
|
// to identify the source and to begin associating media for purposes such as lip-sync.
|
|
//
|
|
// Other RTCP packet types may follow in any order. Packet types may appear more than once.
|
|
type CompoundPacket []Packet
|
|
|
|
// Validate returns an error if this is not an RFC-compliant CompoundPacket.
|
|
func (c CompoundPacket) Validate() error {
|
|
if len(c) == 0 {
|
|
return errEmptyCompound
|
|
}
|
|
|
|
// SenderReport and ReceiverReport are the only types that
|
|
// are allowed to be the first packet in a compound datagram
|
|
switch c[0].(type) {
|
|
case *SenderReport, *ReceiverReport:
|
|
// ok
|
|
default:
|
|
return errBadFirstPacket
|
|
}
|
|
|
|
for _, pkt := range c[1:] {
|
|
switch p := pkt.(type) {
|
|
// If the number of RecetpionReports exceeds 31 additional ReceiverReports
|
|
// can be included here.
|
|
case *ReceiverReport:
|
|
continue
|
|
|
|
// A SourceDescription containing a CNAME must be included in every
|
|
// CompoundPacket.
|
|
case *SourceDescription:
|
|
var hasCNAME bool
|
|
for _, c := range p.Chunks {
|
|
for _, it := range c.Items {
|
|
if it.Type == SDESCNAME {
|
|
hasCNAME = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasCNAME {
|
|
return errMissingCNAME
|
|
}
|
|
|
|
return nil
|
|
|
|
// Other packets are not permitted before the CNAME
|
|
default:
|
|
return errPacketBeforeCNAME
|
|
}
|
|
}
|
|
|
|
// CNAME never reached
|
|
return errMissingCNAME
|
|
}
|
|
|
|
// CNAME returns the CNAME that *must* be present in every CompoundPacket
|
|
func (c CompoundPacket) CNAME() (string, error) {
|
|
var err error
|
|
|
|
if len(c) < 1 {
|
|
return "", errEmptyCompound
|
|
}
|
|
|
|
for _, pkt := range c[1:] {
|
|
sdes, ok := pkt.(*SourceDescription)
|
|
if ok {
|
|
for _, c := range sdes.Chunks {
|
|
for _, it := range c.Items {
|
|
if it.Type == SDESCNAME {
|
|
return it.Text, err
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
_, ok := pkt.(*ReceiverReport)
|
|
if !ok {
|
|
err = errPacketBeforeCNAME
|
|
}
|
|
}
|
|
}
|
|
return "", errMissingCNAME
|
|
}
|
|
|
|
// Marshal encodes the CompoundPacket as binary.
|
|
func (c CompoundPacket) Marshal() ([]byte, error) {
|
|
if err := c.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := []Packet(c)
|
|
return Marshal(p)
|
|
}
|
|
|
|
// MarshalSize returns the size of the packet once marshaled
|
|
func (c CompoundPacket) MarshalSize() int {
|
|
l := 0
|
|
for _, p := range c {
|
|
l += p.MarshalSize()
|
|
}
|
|
return l
|
|
}
|
|
|
|
// Unmarshal decodes a CompoundPacket from binary.
|
|
func (c *CompoundPacket) Unmarshal(rawData []byte) error {
|
|
out := make(CompoundPacket, 0)
|
|
for len(rawData) != 0 {
|
|
p, processed, err := unmarshal(rawData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
out = append(out, p)
|
|
rawData = rawData[processed:]
|
|
}
|
|
*c = out
|
|
|
|
return c.Validate()
|
|
}
|
|
|
|
// DestinationSSRC returns the synchronization sources associated with this
|
|
// CompoundPacket's reception report.
|
|
func (c CompoundPacket) DestinationSSRC() []uint32 {
|
|
if len(c) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return c[0].DestinationSSRC()
|
|
}
|
|
|
|
func (c CompoundPacket) String() string {
|
|
out := "CompoundPacket\n"
|
|
for _, p := range c {
|
|
stringer, canString := p.(fmt.Stringer)
|
|
if canString {
|
|
out += stringer.String()
|
|
} else {
|
|
out += stringify(p)
|
|
}
|
|
}
|
|
out = strings.TrimSuffix(strings.ReplaceAll(out, "\n", "\n\t"), "\t")
|
|
return out
|
|
}
|