Skip to main content

rtc_interceptor/rfc8888/
sender.rs

1//! Emits RFC 8888 congestion control feedback for the streams being received.
2
3use super::recorder::CcFeedbackRecorder;
4use crate::Interceptor;
5use crate::stream_info::StreamInfo;
6use crate::{AttributedPacket, Packet, TaggedPacket};
7use rtcp::transport_feedbacks::cc_feedback_report::Ecn;
8use sansio::Protocol;
9use shared::TransportContext;
10use shared::error::Error;
11use shared::time::SystemInstant;
12use std::collections::HashSet;
13use std::collections::VecDeque;
14use std::time::{Duration, Instant};
15
16/// How often feedback is sent when no interval is configured.
17pub const DEFAULT_INTERVAL: Duration = Duration::from_millis(100);
18
19/// Byte budget for one report, chosen to sit inside a conservative path MTU.
20pub const DEFAULT_MAX_REPORT_SIZE: usize = 1200;
21
22/// Builder for [`Rfc8888Interceptor`].
23///
24/// # Example
25///
26/// ```
27/// use rtc_interceptor::{Registry, Rfc8888Builder};
28/// use std::time::Duration;
29///
30/// let chain = Registry::new()
31///     .with(Rfc8888Builder::new().with_interval(Duration::from_millis(50)).build())
32///     .build();
33/// ```
34pub struct Rfc8888Builder {
35    interval: Duration,
36    max_report_size: usize,
37    sender_ssrc: u32,
38}
39
40impl Default for Rfc8888Builder {
41    fn default() -> Self {
42        Self {
43            interval: DEFAULT_INTERVAL,
44            max_report_size: DEFAULT_MAX_REPORT_SIZE,
45            sender_ssrc: 0,
46        }
47    }
48}
49
50impl Rfc8888Builder {
51    /// Create a builder with the default interval and report size.
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// How often feedback is sent.
57    ///
58    /// Congestion control wants this often — the estimate is only as fresh as the feedback
59    /// driving it — traded against the RTCP bandwidth it costs.
60    pub fn with_interval(mut self, interval: Duration) -> Self {
61        self.interval = interval;
62        self
63    }
64
65    /// Byte budget for one report.
66    ///
67    /// A report larger than the path MTU is fragmented or dropped, and feedback that does not
68    /// arrive is worth nothing, so a report describes fewer packets rather than growing.
69    pub fn with_max_report_size(mut self, max_report_size: usize) -> Self {
70        self.max_report_size = max_report_size;
71        self
72    }
73
74    /// The SSRC these reports are sent from.
75    pub fn with_sender_ssrc(mut self, sender_ssrc: u32) -> Self {
76        self.sender_ssrc = sender_ssrc;
77        self
78    }
79
80    /// Build the interceptor.
81    pub fn build(self) -> Rfc8888Interceptor {
82        Rfc8888Interceptor::new(self.interval, self.max_report_size, self.sender_ssrc)
83    }
84}
85
86/// Reports when each packet of each bound remote stream arrived ([RFC 8888]).
87///
88/// # Differences from upstream
89///
90/// `pion/interceptor` injects a `SenderTicker` and a `SenderNow` so its tests can control time.
91/// Sans-I/O needs neither: every instant arrives as a parameter, so the tests drive the clock by
92/// passing one. Both are dropped.
93///
94/// Upstream's `Recorder` also never sets its sender SSRC, so every report it builds claims to come
95/// from SSRC 0. Here it is configured.
96///
97/// [RFC 8888]: https://www.rfc-editor.org/rfc/rfc8888
98pub struct Rfc8888Interceptor {
99    interval: Duration,
100    max_report_size: usize,
101    sender_ssrc: u32,
102    recorder: CcFeedbackRecorder,
103    /// Remote streams being reported on.
104    streams: HashSet<u32>,
105    next_timeout: Option<Instant>,
106    /// Wall-clock reference, captured from the first instant handed over, so a monotonic `Instant`
107    /// can be turned into the NTP timestamp a report carries.
108    epoch: Option<SystemInstant>,
109    write_queue: VecDeque<TaggedPacket>,
110    /// Inbound packets ready for the next interceptor.
111    read_queue: VecDeque<TaggedPacket>,
112}
113
114impl Rfc8888Interceptor {
115    fn new(interval: Duration, max_report_size: usize, sender_ssrc: u32) -> Self {
116        Self {
117            read_queue: VecDeque::new(),
118            interval,
119            max_report_size,
120            sender_ssrc,
121            recorder: CcFeedbackRecorder::new(),
122            streams: HashSet::new(),
123            next_timeout: None,
124            epoch: None,
125            write_queue: VecDeque::new(),
126        }
127    }
128
129    /// The middle 32 bits of the NTP timestamp for `now`, which is what a report carries.
130    fn report_timestamp(&mut self, now: Instant) -> u32 {
131        let epoch = self.epoch.get_or_insert_with(|| SystemInstant::now(now));
132        (epoch.ntp(now) >> 16) as u32
133    }
134
135    /// Arm the interval from the first instant this interceptor is given.
136    fn arm(&mut self, now: Instant) {
137        if self.next_timeout.is_none() && !self.streams.is_empty() && !self.interval.is_zero() {
138            self.next_timeout = Some(now + self.interval);
139        }
140    }
141}
142
143impl Protocol<TaggedPacket, TaggedPacket, ()> for Rfc8888Interceptor {
144    type Rout = TaggedPacket;
145    type Wout = TaggedPacket;
146    type Eout = ();
147    type Error = Error;
148    type Time = Instant;
149
150    fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
151        if let Packet::Rtp(rtp_packet) = &msg.message.packet
152            && self.streams.contains(&rtp_packet.header.ssrc)
153        {
154            // ECN lives in the IP header, which a sans-I/O interceptor never sees. Until the
155            // transport surfaces it, every packet is reported as Not-ECT — which is what an
156            // endpoint without ECN support would report anyway.
157            self.recorder.add_packet(
158                msg.now,
159                rtp_packet.header.ssrc,
160                rtp_packet.header.sequence_number,
161                Ecn::NotEct,
162            );
163            self.arm(msg.now);
164        }
165        self.read_queue.push_back(msg);
166        Ok(())
167    }
168
169    fn poll_read(&mut self) -> Option<Self::Rout> {
170        self.read_queue.pop_front()
171    }
172
173    fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
174        self.write_queue.push_back(msg);
175        Ok(())
176    }
177
178    fn poll_write(&mut self) -> Option<TaggedPacket> {
179        // Rejoins the belt and passes every interceptor between here and the wire, so a pacer
180        // meters it and a send history counts its bytes like any other outgoing packet.
181        self.write_queue.pop_front()
182    }
183
184    fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
185        self.arm(now);
186
187        if let Some(next_timeout) = self.next_timeout
188            && now >= next_timeout
189        {
190            self.next_timeout = Some(now + self.interval);
191
192            if !self.recorder.is_empty() {
193                let report_timestamp = self.report_timestamp(now);
194                let report = self.recorder.build_report(
195                    now,
196                    self.sender_ssrc,
197                    report_timestamp,
198                    self.max_report_size,
199                );
200                if !report.report_blocks.is_empty() {
201                    self.write_queue.push_back(TaggedPacket {
202                        now,
203                        transport: TransportContext::default(),
204                        message: AttributedPacket::new(Packet::Rtcp(vec![Box::new(report)])),
205                    });
206                }
207            }
208        }
209        Ok(())
210    }
211
212    fn poll_timeout(&mut self) -> Option<Instant> {
213        self.next_timeout
214    }
215}
216
217impl Interceptor for Rfc8888Interceptor {
218    fn bind_remote_stream(&mut self, info: &StreamInfo) {
219        self.streams.insert(info.ssrc);
220    }
221
222    fn unbind_remote_stream(&mut self, info: &StreamInfo) {
223        self.streams.remove(&info.ssrc);
224        self.recorder.remove_stream(info.ssrc);
225        if self.streams.is_empty() {
226            self.next_timeout = None;
227        }
228    }
229
230    fn bind_local_stream(&mut self, _info: &StreamInfo) {}
231
232    fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
233}