rtc_interceptor/report/sender.rs
1//! Sender Report Interceptor - Filters hop-by-hop RTCP feedback.
2
3use super::sender_stream::SenderStream;
4use crate::Interceptor;
5use crate::stream_info::StreamInfo;
6use crate::{AttributedPacket, Packet, TaggedPacket};
7use rtcp::header::PacketType;
8use sansio::Protocol;
9use shared::TransportContext;
10use shared::error::Error;
11use std::collections::{HashMap, VecDeque};
12use std::time::{Duration, Instant};
13
14/// Builder for the SenderReportInterceptor.
15///
16/// # Example
17///
18/// ```
19/// use rtc_interceptor::{Slot, Registry, SenderReportBuilder};
20/// use std::time::Duration;
21///
22/// // With default interval (1 second)
23/// let chain = Registry::new()
24/// .with(Slot::SenderReport, SenderReportBuilder::new().build())
25/// .build();
26///
27/// // With custom interval
28/// let chain = Registry::new()
29/// .with(Slot::SenderReport, SenderReportBuilder::new().with_interval(Duration::from_millis(500)).build())
30/// .build();
31///
32/// // With use_latest_packet enabled
33/// let chain = Registry::new()
34/// .with(Slot::SenderReport, SenderReportBuilder::new().with_use_latest_packet().build())
35/// .build();
36/// ```
37pub struct SenderReportBuilder {
38 /// Interval between sender reports.
39 interval: Duration,
40 /// Whether to always use the latest packet, even if out-of-order.
41 use_latest_packet: bool,
42}
43
44impl Default for SenderReportBuilder {
45 fn default() -> Self {
46 Self {
47 interval: Duration::from_secs(1),
48 use_latest_packet: false,
49 }
50 }
51}
52
53impl SenderReportBuilder {
54 /// Create a new builder with default settings.
55 ///
56 /// Default interval is 1 second.
57 pub fn new() -> Self {
58 Self::default()
59 }
60
61 /// Set a custom interval between sender reports.
62 ///
63 /// # Example
64 ///
65 /// ```
66 /// use rtc_interceptor::{Registry, SenderReportBuilder, Slot};
67 /// use std::time::Duration;
68 ///
69 /// let registry = Registry::new().with(
70 /// Slot::SenderReport,
71 /// SenderReportBuilder::new()
72 /// .with_interval(Duration::from_millis(500))
73 /// .build(),
74 /// );
75 /// ```
76 pub fn with_interval(mut self, interval: Duration) -> Self {
77 self.interval = interval;
78 self
79 }
80
81 /// Enable always using the latest packet for timestamp tracking,
82 /// even if it appears to be out-of-order based on sequence numbers.
83 ///
84 /// By default (disabled), only in-order packets update the RTP↔NTP
85 /// timestamp correlation. This prevents out-of-order packets from
86 /// corrupting the timestamp mapping.
87 ///
88 /// Enable this option when:
89 /// - Packets are guaranteed to arrive in order
90 /// - The application reorders packets before the interceptor
91 /// - You want the sender report to always reflect the most recent packet
92 ///
93 /// # Example
94 ///
95 /// ```
96 /// use rtc_interceptor::{Slot, Registry, SenderReportBuilder};
97 ///
98 /// let registry =
99 /// Registry::new().with(Slot::SenderReport, SenderReportBuilder::new().with_use_latest_packet().build());
100 /// ```
101 pub fn with_use_latest_packet(mut self) -> Self {
102 self.use_latest_packet = true;
103 self
104 }
105
106 /// Create a builder function for use with Registry.
107 ///
108 /// This returns a closure that can be passed to `Registry::with()`.
109 ///
110 /// # Example
111 ///
112 /// ```
113 /// use rtc_interceptor::{Slot, Registry, SenderReportBuilder};
114 ///
115 /// let registry = Registry::new()
116 /// .with(Slot::SenderReport, SenderReportBuilder::new().build());
117 /// ```
118 pub fn build(self) -> SenderReportInterceptor {
119 SenderReportInterceptor::new(self.interval, self.use_latest_packet)
120 }
121}
122
123/// Interceptor that filters hop-by-hop RTCP reports.
124///
125/// This interceptor filters out RTCP Receiver Reports and Transport-Specific
126/// Feedback, which are hop-by-hop reports that should not be forwarded
127/// end-to-end.
128///
129/// # Type Parameters
130///
131/// - `P`: The inner protocol being wrapped
132///
133/// # Example
134///
135/// ```
136/// use rtc_interceptor::{Slot, Registry, SenderReportBuilder};
137///
138/// let chain = Registry::new()
139/// .with(Slot::SenderReport, SenderReportBuilder::new().build())
140/// .build();
141/// ```
142pub struct SenderReportInterceptor {
143 interval: Duration,
144 next_timeout: Option<Instant>,
145
146 /// Whether to always use the latest packet, even if out-of-order.
147 use_latest_packet: bool,
148
149 streams: HashMap<u32, SenderStream>,
150
151 read_queue: VecDeque<TaggedPacket>,
152 write_queue: VecDeque<TaggedPacket>,
153}
154
155impl SenderReportInterceptor {
156 /// Create a new SenderReportInterceptor.
157 fn new(interval: Duration, use_latest_packet: bool) -> Self {
158 Self {
159 interval,
160 next_timeout: None,
161
162 use_latest_packet,
163
164 streams: HashMap::new(),
165
166 read_queue: VecDeque::new(),
167 write_queue: VecDeque::new(),
168 }
169 }
170
171 /// Check if an RTCP packet type should be filtered.
172 ///
173 /// Returns `true` for hop-by-hop report types that should not be forwarded:
174 /// - Receiver Report (201)
175 /// - Transport-Specific Feedback (205)
176 fn should_filter(packet_type: PacketType) -> bool {
177 packet_type == PacketType::ReceiverReport
178 || (packet_type == PacketType::TransportSpecificFeedback)
179 }
180}
181
182impl Protocol<TaggedPacket, TaggedPacket, ()> for SenderReportInterceptor {
183 type Rout = TaggedPacket;
184 type Wout = TaggedPacket;
185 type Eout = ();
186 type Error = Error;
187 type Time = Instant;
188
189 fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
190 self.read_queue.push_back(msg);
191 Ok(())
192 }
193
194 fn poll_read(&mut self) -> Option<Self::Rout> {
195 self.read_queue.pop_front()
196 }
197
198 fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
199 if let Packet::Rtp(rtp_packet) = &msg.message.packet
200 && let Some(stream) = self.streams.get_mut(&rtp_packet.header.ssrc)
201 {
202 stream.process_rtp(msg.now, rtp_packet);
203
204 // Arm the report timer from the first packet's instant (see nack::generator).
205 if self.next_timeout.is_none() {
206 self.next_timeout = Some(msg.now + self.interval);
207 }
208 }
209
210 self.write_queue.push_back(msg);
211
212 Ok(())
213 }
214
215 fn poll_write(&mut self) -> Option<TaggedPacket> {
216 // First drain generated RTCP reports
217 if let Some(pkt) = self.write_queue.pop_front() {
218 return Some(pkt);
219 }
220 None
221 }
222
223 fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
224 if let Some(next_timeout) = self.next_timeout
225 && now >= next_timeout
226 {
227 self.next_timeout = Some(now + self.interval);
228
229 for stream in self.streams.values_mut() {
230 if let Some(rr) = stream.generate_report(now) {
231 self.write_queue.push_back(TaggedPacket {
232 now,
233 transport: TransportContext::default(),
234 message: AttributedPacket::new(Packet::Rtcp(vec![Box::new(rr)])),
235 });
236 }
237 }
238 }
239 Ok(())
240 }
241
242 fn poll_timeout(&mut self) -> Option<Instant> {
243 self.next_timeout
244 }
245}
246
247impl Interceptor for SenderReportInterceptor {
248 fn bind_local_stream(&mut self, info: &StreamInfo) {
249 let stream = SenderStream::new(info.ssrc, info.clock_rate, self.use_latest_packet);
250 self.streams.insert(info.ssrc, stream);
251 }
252
253 fn unbind_local_stream(&mut self, info: &StreamInfo) {
254 self.streams.remove(&info.ssrc);
255 }
256
257 fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
258
259 fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
260}