Skip to main content

rtc_rtcp/transport_feedbacks/cc_feedback_report/
mod.rs

1//! RTCP Congestion Control Feedback ([RFC 8888]).
2//!
3//! A receiver reports, per media stream, whether each packet in a sequence-number range arrived,
4//! when it arrived, and what its ECN marking was. Congestion control on the sender uses that to
5//! estimate available bandwidth.
6//!
7//! This is the feedback format RFC 8888 standardised; [`TransportLayerCc`] is the older
8//! `draft-holmer-rmcat-transport-wide-cc` format that browsers ship today. They occupy the same
9//! packet type (205) and are told apart by FMT — 11 here, 15 there.
10//!
11//! ```text
12//!  0                   1                   2                   3
13//!  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
14//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15//! |V=2|P| FMT=11  |   PT = 205    |          length               |
16//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17//! |                 SSRC of RTCP packet sender                    |
18//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19//! |                   SSRC of 1st RTP Stream                      |
20//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21//! |          begin_seq            |          num_reports          |
22//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23//! |R|ECN|  Arrival time offset    | ...                           .
24//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25//! .                                                               .
26//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27//! |                   SSRC of nth RTP Stream                      |
28//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29//! |          begin_seq            |          num_reports          |
30//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31//! |R|ECN|  Arrival time offset    | ...                           |
32//! .                                                               .
33//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34//! |                 Report Timestamp (32 bits)                    |
35//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36//! ```
37//!
38//! [RFC 8888]: https://www.rfc-editor.org/rfc/rfc8888.html
39//! [`TransportLayerCc`]: crate::transport_feedbacks::transport_layer_cc::TransportLayerCc
40
41#[cfg(test)]
42mod cc_feedback_report_test;
43
44use crate::{header::*, packet::*, util::*};
45use shared::{
46    error::{Error, Result},
47    marshal::{Marshal, MarshalSize, Unmarshal},
48};
49
50use bytes::{Buf, BufMut};
51use std::any::Any;
52use std::fmt;
53
54/// Sender SSRC, then the report blocks: the offset of the first one.
55const REPORT_BLOCK_OFFSET: usize = HEADER_LENGTH + SSRC_LENGTH;
56/// The report timestamp trailing every report.
57const REPORT_TIMESTAMP_LENGTH: usize = 4;
58/// SSRC (4) + begin_seq (2) + num_reports (2), before the metric blocks.
59const REPORT_BLOCK_HEADER_LENGTH: usize = 8;
60/// Every metric block is exactly two bytes.
61const METRIC_BLOCK_LENGTH: usize = 2;
62/// `num_reports` is a `u16`, but a block may not describe more than this many packets.
63const MAX_METRIC_BLOCKS: usize = 16384;
64
65/// The two ECN bits of the IP header, as reported for a received packet ([RFC 3168] §5).
66///
67/// The numeric values are the on-the-wire codepoints, which is what makes the ECT pair look
68/// transposed at a glance: `ECT(1)` is `01` and `ECT(0)` is `10`.
69///
70/// [RFC 3168]: https://www.rfc-editor.org/rfc/rfc3168#section-5
71#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
72#[repr(u8)]
73pub enum Ecn {
74    /// `00` — Not ECN-Capable Transport.
75    #[default]
76    NotEct = 0,
77    /// `01` — ECN Capable Transport, ECT(1).
78    Ect1 = 1,
79    /// `10` — ECN Capable Transport, ECT(0).
80    Ect0 = 2,
81    /// `11` — Congestion Encountered.
82    Ce = 3,
83}
84
85impl Ecn {
86    /// The codepoint for the low two bits of `value`.
87    ///
88    /// Total over two bits, so this cannot fail — which is why there is no `TryFrom`.
89    fn from_bits(value: u8) -> Self {
90        match value & 0x03 {
91            0 => Ecn::NotEct,
92            1 => Ecn::Ect1,
93            2 => Ecn::Ect0,
94            _ => Ecn::Ce,
95        }
96    }
97}
98
99impl fmt::Display for Ecn {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        let s = match self {
102            Ecn::NotEct => "Not-ECT (00)",
103            Ecn::Ect1 => "ECT(1) (01)",
104            Ecn::Ect0 => "ECT(0) (10)",
105            Ecn::Ce => "CE (11)",
106        };
107        write!(f, "{s}")
108    }
109}
110
111/// One packet's fate, in two bytes: whether it arrived, its ECN marking, and when.
112///
113/// A metric block has no sequence number of its own — it is positional. The *i*th block in a
114/// report block describes sequence number `begin_sequence + i`.
115#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
116pub struct CcFeedbackMetricBlock {
117    /// Whether the packet arrived. When `false` the remaining fields carry no information and
118    /// are decoded as zero, per [RFC 8888] §3.1.
119    ///
120    /// [RFC 8888]: https://www.rfc-editor.org/rfc/rfc8888.html#section-3.1
121    pub received: bool,
122    /// The ECN marking the packet carried. Meaningful only when `received`.
123    pub ecn: Ecn,
124    /// Arrival time before the report timestamp, in units of 1/1024 s.
125    ///
126    /// Thirteen bits, so at most 8191 — about 8 s. Meaningful only when `received`.
127    pub arrival_time_offset: u16,
128}
129
130impl CcFeedbackMetricBlock {
131    /// Encode into the two-byte wire form.
132    fn marshal_word(&self) -> Result<u16> {
133        let received = u16::from(self.received);
134        let word = set_nbits_of_uint16(0, 1, 0, received)?;
135        let word = set_nbits_of_uint16(word, 2, 1, self.ecn as u16)?;
136        set_nbits_of_uint16(word, 13, 3, self.arrival_time_offset)
137    }
138
139    /// Decode from the two-byte wire form.
140    ///
141    /// A not-received packet reports nothing else: the other 15 bits are ignored rather than
142    /// decoded, so a peer that leaves stale bits there cannot make a lost packet look like it
143    /// arrived with an ECN marking.
144    fn unmarshal_word(word: u16) -> Self {
145        let received = word & 0x8000 != 0;
146        if !received {
147            return Self::default();
148        }
149        Self {
150            received,
151            ecn: Ecn::from_bits((word >> 13) as u8),
152            arrival_time_offset: word & 0x1FFF,
153        }
154    }
155}
156
157impl fmt::Display for CcFeedbackMetricBlock {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        if self.received {
160            write!(f, "(rx, {}, {}/1024s)", self.ecn, self.arrival_time_offset)
161        } else {
162            write!(f, "(lost)")
163        }
164    }
165}
166
167/// The packets of one media stream that a report describes.
168#[derive(Debug, PartialEq, Eq, Default, Clone)]
169pub struct CcFeedbackReportBlock {
170    /// SSRC of the RTP stream this block reports on.
171    pub media_ssrc: u32,
172    /// Sequence number of the first packet described by `metric_blocks`.
173    pub begin_sequence: u16,
174    /// One entry per sequence number from `begin_sequence`, in order.
175    pub metric_blocks: Vec<CcFeedbackMetricBlock>,
176}
177
178impl CcFeedbackReportBlock {
179    /// Encoded length in bytes.
180    ///
181    /// Metric blocks are two bytes each, so an odd count is padded with one empty block to keep
182    /// the next report block 32-bit aligned. `num_reports` still records the true count, so the
183    /// padding is not mistaken for a reported packet.
184    fn raw_size(&self) -> usize {
185        REPORT_BLOCK_HEADER_LENGTH + METRIC_BLOCK_LENGTH * self.padded_metric_block_count()
186    }
187
188    fn padded_metric_block_count(&self) -> usize {
189        let count = self.metric_blocks.len();
190        if count.is_multiple_of(2) {
191            count
192        } else {
193            count + 1
194        }
195    }
196
197    fn marshal_to(&self, buf: &mut &mut [u8]) -> Result<()> {
198        if self.metric_blocks.len() > MAX_METRIC_BLOCKS {
199            return Err(Error::TooManyReports);
200        }
201
202        buf.put_u32(self.media_ssrc);
203        buf.put_u16(self.begin_sequence);
204        buf.put_u16(self.metric_blocks.len() as u16);
205
206        for block in &self.metric_blocks {
207            buf.put_u16(block.marshal_word()?);
208        }
209        // The alignment block, written explicitly: the caller's buffer is not guaranteed zeroed.
210        for _ in self.metric_blocks.len()..self.padded_metric_block_count() {
211            buf.put_u16(0);
212        }
213
214        Ok(())
215    }
216
217    /// Decode one report block, returning it with the number of bytes it consumed.
218    ///
219    /// `budget` is what remains of the report's block region; a `num_reports` claiming more than
220    /// that is a truncated or malformed packet rather than a short read.
221    fn unmarshal_from<B: Buf>(raw_packet: &mut B, budget: usize) -> Result<(Self, usize)> {
222        if budget < REPORT_BLOCK_HEADER_LENGTH
223            || raw_packet.remaining() < REPORT_BLOCK_HEADER_LENGTH
224        {
225            return Err(Error::PacketTooShort);
226        }
227
228        let media_ssrc = raw_packet.get_u32();
229        let begin_sequence = raw_packet.get_u16();
230        let num_reports = raw_packet.get_u16() as usize;
231
232        let padded = if num_reports.is_multiple_of(2) {
233            num_reports
234        } else {
235            num_reports + 1
236        };
237        let consumed = REPORT_BLOCK_HEADER_LENGTH + METRIC_BLOCK_LENGTH * padded;
238        if budget < consumed || raw_packet.remaining() < METRIC_BLOCK_LENGTH * padded {
239            return Err(Error::PacketTooShort);
240        }
241
242        let mut metric_blocks = Vec::with_capacity(num_reports);
243        for _ in 0..num_reports {
244            metric_blocks.push(CcFeedbackMetricBlock::unmarshal_word(raw_packet.get_u16()));
245        }
246        if padded != num_reports {
247            raw_packet.advance(METRIC_BLOCK_LENGTH);
248        }
249
250        Ok((
251            Self {
252                media_ssrc,
253                begin_sequence,
254                metric_blocks,
255            },
256            consumed,
257        ))
258    }
259}
260
261impl fmt::Display for CcFeedbackReportBlock {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        write!(
264            f,
265            "\tssrc={:x} begin_seq={} reports={}\n\t\t",
266            self.media_ssrc,
267            self.begin_sequence,
268            self.metric_blocks.len()
269        )?;
270        for block in &self.metric_blocks {
271            write!(f, "{block} ")?;
272        }
273        writeln!(f)
274    }
275}
276
277/// An RTCP Congestion Control Feedback report ([RFC 8888]).
278///
279/// [RFC 8888]: https://www.rfc-editor.org/rfc/rfc8888.html
280#[derive(Debug, PartialEq, Eq, Default, Clone)]
281pub struct CcFeedbackReport {
282    /// SSRC of the sender of this report.
283    pub sender_ssrc: u32,
284    /// One block per media stream being reported on.
285    pub report_blocks: Vec<CcFeedbackReportBlock>,
286    /// The instant every `arrival_time_offset` is measured back from, in NTP-style 1/65536 s
287    /// units truncated to 32 bits.
288    pub report_timestamp: u32,
289}
290
291impl fmt::Display for CcFeedbackReport {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        writeln!(
294            f,
295            "CcFeedbackReport sender_ssrc={:x} report_timestamp={}",
296            self.sender_ssrc, self.report_timestamp
297        )?;
298        for block in &self.report_blocks {
299            write!(f, "{block}")?;
300        }
301        Ok(())
302    }
303}
304
305impl Packet for CcFeedbackReport {
306    fn header(&self) -> Header {
307        Header {
308            padding: get_padding_size(self.raw_size()) != 0,
309            count: FORMAT_CCFB,
310            packet_type: PacketType::TransportSpecificFeedback,
311            length: ((self.marshal_size() / 4) - 1) as u16,
312        }
313    }
314
315    /// Every media SSRC this report carries a block for.
316    ///
317    /// One report covers several streams, so unlike most feedback packets this returns more than
318    /// one SSRC — which is what lets an SFU route a single report to several senders.
319    fn destination_ssrc(&self) -> Vec<u32> {
320        self.report_blocks
321            .iter()
322            .map(|block| block.media_ssrc)
323            .collect()
324    }
325
326    fn raw_size(&self) -> usize {
327        let blocks: usize = self.report_blocks.iter().map(|b| b.raw_size()).sum();
328        REPORT_BLOCK_OFFSET + blocks + REPORT_TIMESTAMP_LENGTH
329    }
330
331    fn as_any(&self) -> &dyn Any {
332        self
333    }
334
335    fn equal(&self, other: &dyn Packet) -> bool {
336        other.as_any().downcast_ref::<CcFeedbackReport>() == Some(self)
337    }
338
339    fn cloned(&self) -> Box<dyn Packet> {
340        Box::new(self.clone())
341    }
342}
343
344impl MarshalSize for CcFeedbackReport {
345    fn marshal_size(&self) -> usize {
346        let l = self.raw_size();
347        // Already 32-bit aligned by construction; the term keeps the invariant explicit.
348        l + get_padding_size(l)
349    }
350}
351
352impl Marshal for CcFeedbackReport {
353    fn marshal_to(&self, mut buf: &mut [u8]) -> Result<usize> {
354        if buf.remaining_mut() < self.marshal_size() {
355            return Err(Error::BufferTooShort);
356        }
357
358        let h = self.header();
359        let n = h.marshal_to(buf)?;
360        buf = &mut buf[n..];
361
362        buf.put_u32(self.sender_ssrc);
363        for block in &self.report_blocks {
364            block.marshal_to(&mut buf)?;
365        }
366        buf.put_u32(self.report_timestamp);
367
368        if h.padding {
369            put_padding(buf, self.raw_size());
370        }
371
372        Ok(self.marshal_size())
373    }
374}
375
376impl Unmarshal for CcFeedbackReport {
377    fn unmarshal<B>(raw_packet: &mut B) -> Result<Self>
378    where
379        Self: Sized,
380        B: Buf,
381    {
382        let raw_packet_len = raw_packet.remaining();
383        if raw_packet_len < REPORT_BLOCK_OFFSET + REPORT_TIMESTAMP_LENGTH {
384            return Err(Error::PacketTooShort);
385        }
386
387        let h = Header::unmarshal(raw_packet)?;
388        if h.packet_type != PacketType::TransportSpecificFeedback || h.count != FORMAT_CCFB {
389            return Err(Error::WrongType);
390        }
391
392        // The header's length field, not the buffer's, delimits this packet: in a compound RTCP
393        // datagram the buffer may hold more than this report.
394        let packet_len = 4 * (h.length as usize + 1);
395        if raw_packet_len < packet_len || packet_len < REPORT_BLOCK_OFFSET + REPORT_TIMESTAMP_LENGTH
396        {
397            return Err(Error::PacketTooShort);
398        }
399
400        let sender_ssrc = raw_packet.get_u32();
401
402        let mut remaining_blocks_len = packet_len - REPORT_BLOCK_OFFSET - REPORT_TIMESTAMP_LENGTH;
403        let mut report_blocks = Vec::new();
404        while remaining_blocks_len > 0 {
405            let (block, consumed) =
406                CcFeedbackReportBlock::unmarshal_from(raw_packet, remaining_blocks_len)?;
407            report_blocks.push(block);
408            remaining_blocks_len -= consumed;
409        }
410
411        let report_timestamp = raw_packet.get_u32();
412
413        Ok(CcFeedbackReport {
414            sender_ssrc,
415            report_blocks,
416            report_timestamp,
417        })
418    }
419}