package rtcp
import (
"encoding/binary"
"errors"
"fmt"
"math"
)
var (
errReportBlockLength = errors .New ("feedback report blocks must be at least 8 bytes" )
errIncorrectNumReports = errors .New ("feedback report block contains less reports than num_reports" )
errMetricBlockLength = errors .New ("feedback report metric blocks must be exactly 2 bytes" )
)
type ECN uint8
const (
ECNNonECT ECN = iota
ECNECT1
ECNECT0
ECNCE
)
const (
reportTimestampLength = 4
reportBlockOffset = 8
)
type CCFeedbackReport struct {
SenderSSRC uint32
ReportBlocks []CCFeedbackReportBlock
ReportTimestamp uint32
}
func (b CCFeedbackReport ) DestinationSSRC () []uint32 {
ssrcs := make ([]uint32 , len (b .ReportBlocks ))
for i , block := range b .ReportBlocks {
ssrcs [i ] = block .MediaSSRC
}
return ssrcs
}
func (b *CCFeedbackReport ) Len () int {
return b .MarshalSize ()
}
func (b *CCFeedbackReport ) MarshalSize () int {
n := 0
for _ , block := range b .ReportBlocks {
n += block .len ()
}
return reportBlockOffset + n + reportTimestampLength
}
func (b *CCFeedbackReport ) Header () Header {
return Header {
Padding : false ,
Count : FormatCCFB ,
Type : TypeTransportSpecificFeedback ,
Length : uint16 (b .MarshalSize ()/4 - 1 ),
}
}
func (b CCFeedbackReport ) Marshal () ([]byte , error ) {
header := b .Header ()
headerBuf , err := header .Marshal ()
if err != nil {
return nil , err
}
length := 4 * (header .Length + 1 )
buf := make ([]byte , length )
copy (buf [:headerLength ], headerBuf )
binary .BigEndian .PutUint32 (buf [headerLength :], b .SenderSSRC )
offset := reportBlockOffset
for _ , block := range b .ReportBlocks {
b , err := block .marshal ()
if err != nil {
return nil , err
}
copy (buf [offset :], b )
offset += block .len ()
}
binary .BigEndian .PutUint32 (buf [offset :], b .ReportTimestamp )
return buf , nil
}
func (b CCFeedbackReport ) String () string {
out := fmt .Sprintf ("CCFB:\n\tHeader %v\n" , b .Header ())
out += fmt .Sprintf ("CCFB:\n\tSender SSRC %d\n" , b .SenderSSRC )
out += fmt .Sprintf ("\tReport Timestamp %d\n" , b .ReportTimestamp )
out += "\tFeedback Reports \n"
for _ , report := range b .ReportBlocks {
out += fmt .Sprintf ("%v " , report )
}
out += "\n"
return out
}
func (b *CCFeedbackReport ) Unmarshal (rawPacket []byte ) error {
if len (rawPacket ) < headerLength +ssrcLength +reportTimestampLength {
return errPacketTooShort
}
var h Header
if err := h .Unmarshal (rawPacket ); err != nil {
return err
}
if h .Type != TypeTransportSpecificFeedback {
return errWrongType
}
b .SenderSSRC = binary .BigEndian .Uint32 (rawPacket [headerLength :])
reportTimestampOffset := len (rawPacket ) - reportTimestampLength
b .ReportTimestamp = binary .BigEndian .Uint32 (rawPacket [reportTimestampOffset :])
offset := reportBlockOffset
b .ReportBlocks = []CCFeedbackReportBlock {}
for offset < reportTimestampOffset {
var block CCFeedbackReportBlock
if err := block .unmarshal (rawPacket [offset :]); err != nil {
return err
}
b .ReportBlocks = append (b .ReportBlocks , block )
offset += block .len ()
}
return nil
}
const (
ssrcOffset = 0
beginSequenceOffset = 4
numReportsOffset = 6
reportsOffset = 8
maxMetricBlocks = 16384
)
type CCFeedbackReportBlock struct {
MediaSSRC uint32
BeginSequence uint16
MetricBlocks []CCFeedbackMetricBlock
}
func (b *CCFeedbackReportBlock ) len () int {
n := len (b .MetricBlocks )
if n %2 != 0 {
n ++
}
return reportsOffset + 2 *n
}
func (b CCFeedbackReportBlock ) String () string {
out := fmt .Sprintf ("\tReport Block Media SSRC %d\n" , b .MediaSSRC )
out += fmt .Sprintf ("\tReport Begin Sequence Nr %d\n" , b .BeginSequence )
out += fmt .Sprintf ("\tReport length %d\n\t" , len (b .MetricBlocks ))
for i , block := range b .MetricBlocks {
out += fmt .Sprintf ("{nr: %d, rx: %v, ts: %v} " , b .BeginSequence +uint16 (i ), block .Received , block .ArrivalTimeOffset )
}
out += "\n"
return out
}
func (b CCFeedbackReportBlock ) marshal () ([]byte , error ) {
if len (b .MetricBlocks ) > maxMetricBlocks {
return nil , errTooManyReports
}
buf := make ([]byte , b .len ())
binary .BigEndian .PutUint32 (buf [ssrcOffset :], b .MediaSSRC )
binary .BigEndian .PutUint16 (buf [beginSequenceOffset :], b .BeginSequence )
length := uint16 (len (b .MetricBlocks ))
if length > 0 {
length --
}
binary .BigEndian .PutUint16 (buf [numReportsOffset :], length )
for i , block := range b .MetricBlocks {
b , err := block .marshal ()
if err != nil {
return nil , err
}
copy (buf [reportsOffset +i *2 :], b )
}
return buf , nil
}
func (b *CCFeedbackReportBlock ) unmarshal (rawPacket []byte ) error {
if len (rawPacket ) < reportsOffset {
return errReportBlockLength
}
b .MediaSSRC = binary .BigEndian .Uint32 (rawPacket [:beginSequenceOffset ])
b .BeginSequence = binary .BigEndian .Uint16 (rawPacket [beginSequenceOffset :numReportsOffset ])
numReportsField := binary .BigEndian .Uint16 (rawPacket [numReportsOffset :])
if numReportsField == 0 {
return nil
}
if int (b .BeginSequence )+int (numReportsField ) > math .MaxUint16 {
return errIncorrectNumReports
}
endSequence := b .BeginSequence + numReportsField
numReports := int (endSequence - b .BeginSequence + 1 )
if len (rawPacket ) < reportsOffset +numReports *2 {
return errIncorrectNumReports
}
b .MetricBlocks = make ([]CCFeedbackMetricBlock , numReports )
for i := int (0 ); i < numReports ; i ++ {
var mb CCFeedbackMetricBlock
offset := reportsOffset + 2 *i
if err := mb .unmarshal (rawPacket [offset : offset +2 ]); err != nil {
return err
}
b .MetricBlocks [i ] = mb
}
return nil
}
const (
metricBlockLength = 2
)
type CCFeedbackMetricBlock struct {
Received bool
ECN ECN
ArrivalTimeOffset uint16
}
func (b CCFeedbackMetricBlock ) marshal () ([]byte , error ) {
buf := make ([]byte , 2 )
r := uint16 (0 )
if b .Received {
r = 1
}
dst , err := setNBitsOfUint16 (0 , 1 , 0 , r )
if err != nil {
return nil , err
}
dst , err = setNBitsOfUint16 (dst , 2 , 1 , uint16 (b .ECN ))
if err != nil {
return nil , err
}
dst , err = setNBitsOfUint16 (dst , 13 , 3 , b .ArrivalTimeOffset )
if err != nil {
return nil , err
}
binary .BigEndian .PutUint16 (buf , dst )
return buf , nil
}
func (b *CCFeedbackMetricBlock ) unmarshal (rawPacket []byte ) error {
if len (rawPacket ) != metricBlockLength {
return errMetricBlockLength
}
b .Received = rawPacket [0 ]&0x80 != 0
if !b .Received {
b .ECN = ECNNonECT
b .ArrivalTimeOffset = 0
return nil
}
b .ECN = ECN (rawPacket [0 ] >> 5 & 0x03 )
b .ArrivalTimeOffset = binary .BigEndian .Uint16 (rawPacket ) & 0x1FFF
return nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .