2024-06-05 20:10:03 +00:00
|
|
|
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
2024-05-15 23:15:00 +00:00
|
|
|
package rtcp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-06-05 20:10:03 +00:00
|
|
|
"math"
|
2024-05-15 23:15:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee
|
|
|
|
// 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| FMT=11 | PT = 205 | length |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// | SSRC of RTCP packet sender |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// | SSRC of 1st RTP Stream |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// | begin_seq | num_reports |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// |R|ECN| Arrival time offset | ... .
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// . .
|
|
|
|
// . .
|
|
|
|
// . .
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// | SSRC of nth RTP Stream |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// | begin_seq | num_reports |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// |R|ECN| Arrival time offset | ... |
|
|
|
|
// . .
|
|
|
|
// . .
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// | Report Timestamp (32 bits) |
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
|
|
|
|
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")
|
|
|
|
)
|
|
|
|
|
|
|
|
// ECN represents the two ECN bits
|
|
|
|
type ECN uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
//nolint:misspell
|
|
|
|
// ECNNonECT signals Non ECN-Capable Transport, Non-ECT
|
|
|
|
ECNNonECT ECN = iota // 00
|
|
|
|
|
|
|
|
//nolint:misspell
|
|
|
|
// ECNECT1 signals ECN Capable Transport, ECT(0)
|
|
|
|
ECNECT1 // 01
|
|
|
|
|
|
|
|
//nolint:misspell
|
|
|
|
// ECNECT0 signals ECN Capable Transport, ECT(1)
|
|
|
|
ECNECT0 // 10
|
|
|
|
|
|
|
|
// ECNCE signals ECN Congestion Encountered, CE
|
|
|
|
ECNCE // 11
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
reportTimestampLength = 4
|
|
|
|
reportBlockOffset = 8
|
|
|
|
)
|
|
|
|
|
|
|
|
// CCFeedbackReport is a Congestion Control Feedback Report as defined in
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee
|
|
|
|
type CCFeedbackReport struct {
|
|
|
|
// SSRC of sender
|
|
|
|
SenderSSRC uint32
|
|
|
|
|
|
|
|
// Report Blocks
|
|
|
|
ReportBlocks []CCFeedbackReportBlock
|
|
|
|
|
|
|
|
// Basetime
|
|
|
|
ReportTimestamp uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
// DestinationSSRC returns an array of SSRC values that this packet refers to.
|
|
|
|
func (b CCFeedbackReport) DestinationSSRC() []uint32 {
|
|
|
|
ssrcs := make([]uint32, len(b.ReportBlocks))
|
|
|
|
for i, block := range b.ReportBlocks {
|
|
|
|
ssrcs[i] = block.MediaSSRC
|
|
|
|
}
|
|
|
|
return ssrcs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the length of the report in bytes
|
2024-06-05 20:10:03 +00:00
|
|
|
func (b *CCFeedbackReport) Len() int {
|
|
|
|
return b.MarshalSize()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalSize returns the size of the packet once marshaled
|
|
|
|
func (b *CCFeedbackReport) MarshalSize() int {
|
|
|
|
n := 0
|
2024-05-15 23:15:00 +00:00
|
|
|
for _, block := range b.ReportBlocks {
|
|
|
|
n += block.len()
|
|
|
|
}
|
|
|
|
return reportBlockOffset + n + reportTimestampLength
|
|
|
|
}
|
|
|
|
|
|
|
|
// Header returns the Header associated with this packet.
|
|
|
|
func (b *CCFeedbackReport) Header() Header {
|
|
|
|
return Header{
|
|
|
|
Padding: false,
|
|
|
|
Count: FormatCCFB,
|
|
|
|
Type: TypeTransportSpecificFeedback,
|
2024-06-05 20:10:03 +00:00
|
|
|
Length: uint16(b.MarshalSize()/4 - 1),
|
2024-05-15 23:15:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal encodes the Congestion Control Feedback Report in binary
|
|
|
|
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)
|
2024-06-05 20:10:03 +00:00
|
|
|
offset := reportBlockOffset
|
2024-05-15 23:15:00 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal decodes the Congestion Control Feedback Report from binary
|
|
|
|
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:])
|
|
|
|
|
2024-06-05 20:10:03 +00:00
|
|
|
reportTimestampOffset := len(rawPacket) - reportTimestampLength
|
2024-05-15 23:15:00 +00:00
|
|
|
b.ReportTimestamp = binary.BigEndian.Uint32(rawPacket[reportTimestampOffset:])
|
|
|
|
|
2024-06-05 20:10:03 +00:00
|
|
|
offset := reportBlockOffset
|
2024-05-15 23:15:00 +00:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
|
|
|
// CCFeedbackReportBlock is a Feedback Report Block
|
|
|
|
type CCFeedbackReportBlock struct {
|
|
|
|
// SSRC of the RTP stream on which this block is reporting
|
|
|
|
MediaSSRC uint32
|
|
|
|
BeginSequence uint16
|
|
|
|
MetricBlocks []CCFeedbackMetricBlock
|
|
|
|
}
|
|
|
|
|
|
|
|
// len returns the length of the report block in bytes
|
2024-06-05 20:10:03 +00:00
|
|
|
func (b *CCFeedbackReportBlock) len() int {
|
2024-05-15 23:15:00 +00:00
|
|
|
n := len(b.MetricBlocks)
|
|
|
|
if n%2 != 0 {
|
|
|
|
n++
|
|
|
|
}
|
2024-06-05 20:10:03 +00:00
|
|
|
return reportsOffset + 2*n
|
2024-05-15 23:15:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// marshal encodes the Congestion Control Feedback Report Block in binary
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal decodes the Congestion Control Feedback Report Block from binary
|
|
|
|
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
|
|
|
|
}
|
2024-06-05 20:10:03 +00:00
|
|
|
|
|
|
|
if int(b.BeginSequence)+int(numReportsField) > math.MaxUint16 {
|
|
|
|
return errIncorrectNumReports
|
|
|
|
}
|
|
|
|
|
2024-05-15 23:15:00 +00:00
|
|
|
endSequence := b.BeginSequence + numReportsField
|
2024-06-05 20:10:03 +00:00
|
|
|
numReports := int(endSequence - b.BeginSequence + 1)
|
2024-05-15 23:15:00 +00:00
|
|
|
|
2024-06-05 20:10:03 +00:00
|
|
|
if len(rawPacket) < reportsOffset+numReports*2 {
|
2024-05-15 23:15:00 +00:00
|
|
|
return errIncorrectNumReports
|
|
|
|
}
|
2024-06-05 20:10:03 +00:00
|
|
|
|
2024-05-15 23:15:00 +00:00
|
|
|
b.MetricBlocks = make([]CCFeedbackMetricBlock, numReports)
|
2024-06-05 20:10:03 +00:00
|
|
|
for i := int(0); i < numReports; i++ {
|
2024-05-15 23:15:00 +00:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
|
|
|
// CCFeedbackMetricBlock is a Feedback Metric Block
|
|
|
|
type CCFeedbackMetricBlock struct {
|
|
|
|
Received bool
|
|
|
|
ECN ECN
|
|
|
|
|
|
|
|
// Offset in 1/1024 seconds before Report Timestamp
|
|
|
|
ArrivalTimeOffset uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal encodes the Congestion Control Feedback Metric Block in binary
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal decodes the Congestion Control Feedback Metric Block from binary
|
|
|
|
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
|
|
|
|
}
|