rtc_interceptor/lib.rs
1//! RTC Interceptor - Sans-IO interceptor framework for RTP/RTCP processing.
2//!
3//! This crate provides a composable interceptor framework built on top of the
4//! [`sansio::Protocol`] trait. Interceptors can process, modify, or generate
5//! RTP/RTCP packets as they flow through the pipeline.
6//!
7//! # Available Interceptors
8//!
9//! ## RTCP Reports
10//!
11//! | Interceptor | Description |
12//! |-------------|-------------|
13//! | [`SenderReportInterceptor`] | Generates RTCP Sender Reports (SR) for local streams and filters hop-by-hop RTCP feedback |
14//! | [`ReceiverReportInterceptor`] | Generates RTCP Receiver Reports (RR) based on incoming RTP statistics |
15//!
16//! ## NACK (Negative Acknowledgement)
17//!
18//! | Interceptor | Description |
19//! |-------------|-------------|
20//! | [`NackGeneratorInterceptor`] | Detects missing RTP packets and generates NACK requests (RFC 4585) |
21//! | [`NackResponderInterceptor`] | Buffers sent packets and retransmits on NACK, with optional RTX support (RFC 4588) |
22//!
23//! ## TWCC (Transport Wide Congestion Control)
24//!
25//! | Interceptor | Description |
26//! |-------------|-------------|
27//! | [`TwccSenderInterceptor`] | Adds transport-wide sequence numbers to outgoing RTP packets |
28//! | [`TwccReceiverInterceptor`] | Tracks incoming packets and generates TransportLayerCC feedback |
29//!
30//! ## Utility
31//!
32//! | Interceptor | Description |
33//! |-------------|-------------|
34//! | [`NoopInterceptor`] | Pass-through terminal for interceptor chains |
35//!
36//! # Design
37//!
38//! Each interceptor wraps an inner `Interceptor` and can:
39//! - Process incoming/outgoing RTP/RTCP packets
40//! - Modify packet contents (headers, payloads)
41//! - Generate new packets (e.g., RTCP Sender/Receiver Reports)
42//! - Handle timeouts for periodic tasks (e.g., report generation)
43//! - Track stream statistics and state
44//!
45//! All interceptors work with [`TaggedPacket`] (RTP or RTCP packets with transport metadata).
46//! The innermost interceptor is typically [`NoopInterceptor`], which serves as the terminal.
47//!
48//! # No Direction Concept
49//!
50//! **Important:** Unlike PeerConnection's pipeline where `read` and `write` have
51//! opposite processing direction orders, interceptors have **no direction concept**.
52//!
53//! In PeerConnection's pipeline:
54//! ```text
55//! Read: Network → HandlerA → HandlerB → HandlerC → Application
56//! Write: Application → HandlerC → HandlerB → HandlerA → Network
57//! (reversed order)
58//! ```
59//!
60//! In Interceptor chains, all operations flow in the **same direction**:
61//! ```text
62//! handle_read: Outer → Inner (A.handle_read calls B.handle_read calls C.handle_read)
63//! handle_write: Outer → Inner (A.handle_write calls B.handle_write calls C.handle_write)
64//! handle_event: Outer → Inner (A.handle_event calls B.handle_event calls C.handle_event)
65//! handle_timeout: Outer → Inner (A.handle_timeout calls B.handle_timeout calls C.handle_timeout)
66//!
67//! poll_read: Outer → Inner (A.poll_read calls B.poll_read calls C.poll_read)
68//! poll_write: Outer → Inner (A.poll_write calls B.poll_write calls C.poll_write)
69//! poll_event: Outer → Inner (A.poll_event calls B.poll_event calls C.poll_event)
70//! poll_timeout: Outer → Inner (A.poll_timeout calls B.poll_timeout calls C.poll_timeout)
71//! ```
72//!
73//! This means interceptors are symmetric - they process `read`, `write`, and `event`
74//! in the same structural order. The distinction between "inbound" and "outbound"
75//! is semantic (based on message content), not structural (based on call order).
76//!
77//! # Quick Start
78//!
79//! ```ignore
80//! use rtc_interceptor::{
81//! Registry, SenderReportBuilder, ReceiverReportBuilder,
82//! NackGeneratorBuilder, NackResponderBuilder,
83//! TwccSenderBuilder, TwccReceiverBuilder,
84//! };
85//! use std::time::Duration;
86//!
87//! // Build a full-featured interceptor chain
88//! let chain = Registry::new()
89//! // RTCP reports
90//! .with(SenderReportBuilder::new()
91//! .with_interval(Duration::from_secs(1))
92//! .build())
93//! .with(ReceiverReportBuilder::new()
94//! .with_interval(Duration::from_secs(1))
95//! .build())
96//! // NACK for packet loss recovery
97//! .with(NackGeneratorBuilder::new()
98//! .with_size(512)
99//! .with_interval(Duration::from_millis(100))
100//! .build())
101//! .with(NackResponderBuilder::new()
102//! .with_size(1024)
103//! .build())
104//! // TWCC for congestion control
105//! .with(TwccSenderBuilder::new().build())
106//! .with(TwccReceiverBuilder::new()
107//! .with_interval(Duration::from_millis(100))
108//! .build())
109//! .build();
110//! ```
111//!
112//! # Stream Binding
113//!
114//! Before interceptors can process packets for a stream, the stream must be bound:
115//!
116//! ```ignore
117//! use rtc_interceptor::{StreamInfo, RTCPFeedback, RTPHeaderExtension};
118//!
119//! // Create stream info with NACK and TWCC support
120//! let stream_info = StreamInfo {
121//! ssrc: 0x12345678,
122//! clock_rate: 90000,
123//! mime_type: "video/VP8".to_string(),
124//! payload_type: 96,
125//! rtcp_feedback: vec![RTCPFeedback {
126//! typ: "nack".to_string(),
127//! parameter: String::new(),
128//! }],
129//! rtp_header_extensions: vec![RTPHeaderExtension {
130//! uri: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01".to_string(),
131//! id: 5,
132//! }],
133//! ..Default::default()
134//! };
135//!
136//! // Bind for outgoing streams (sender side)
137//! chain.bind_local_stream(&stream_info);
138//!
139//! // Bind for incoming streams (receiver side)
140//! chain.bind_remote_stream(&stream_info);
141//! ```
142
143#![warn(rust_2018_idioms)]
144#![allow(dead_code)]
145
146use shared::TransportMessage;
147use std::time::Instant;
148
149mod noop;
150mod registry;
151
152pub(crate) mod nack;
153pub(crate) mod report;
154pub(crate) mod stream_info;
155pub(crate) mod twcc;
156
157pub use nack::{
158 generator::{NackGeneratorBuilder, NackGeneratorInterceptor},
159 responder::{NackResponderBuilder, NackResponderInterceptor},
160};
161pub use noop::NoopInterceptor;
162pub use registry::Registry;
163pub use report::{
164 receiver::{ReceiverReportBuilder, ReceiverReportInterceptor},
165 sender::{SenderReportBuilder, SenderReportInterceptor},
166};
167pub use stream_info::{RTCPFeedback, RTPHeaderExtension, StreamInfo};
168pub use twcc::{
169 receiver::{TwccReceiverBuilder, TwccReceiverInterceptor},
170 sender::{TwccSenderBuilder, TwccSenderInterceptor},
171};
172
173/// RTP/RTCP Packet
174///
175/// An enum representing either an RTP or RTCP packet that can be processed
176/// by interceptors in the chain.
177#[derive(Debug, Clone, PartialEq)]
178pub enum Packet {
179 /// RTP (Real-time Transport Protocol) packet containing media data
180 Rtp(rtp::Packet),
181 /// RTCP (RTP Control Protocol) packets for feedback and statistics
182 Rtcp(Vec<Box<dyn rtcp::Packet>>),
183}
184
185/// Tagged packet with transport metadata.
186///
187/// A [`TransportMessage`] wrapping a [`Packet`], which includes transport-level
188/// context such as source/destination addresses and protocol information.
189/// This is the primary message type passed through interceptor chains.
190pub type TaggedPacket = TransportMessage<Packet>;
191
192/// Trait for RTP/RTCP interceptors with fixed Protocol type parameters.
193///
194/// `Interceptor` is a marker trait that requires implementors to also implement
195/// [`sansio::Protocol`] with specific fixed type parameters for RTP/RTCP processing:
196/// - `Rin`, `Win`, `Rout`, `Wout` = [`TaggedPacket`]
197/// - `Ein`, `Eout` = `()`
198/// - `Time` = [`Instant`]
199/// - `Error` = [`shared::error::Error`]
200///
201/// This trait adds stream binding methods and provides a [`with()`](Interceptor::with)
202/// method for composable chaining of interceptors.
203///
204/// Each interceptor must explicitly implement both `Protocol` and `Interceptor` traits.
205///
206/// # Example
207///
208/// ```ignore
209/// // Define a custom interceptor
210/// pub struct MyInterceptor<P> {
211/// inner: P,
212/// }
213///
214/// impl<P: Interceptor> Protocol<TaggedPacket, TaggedPacket, ()> for MyInterceptor<P> {
215/// type Rout = TaggedPacket;
216/// type Wout = TaggedPacket;
217/// type Eout = ();
218/// type Time = Instant;
219/// type Error = shared::error::Error;
220/// // ... implement Protocol methods
221/// }
222///
223/// impl<P: Interceptor> Interceptor for MyInterceptor<P> {
224/// fn bind_local_stream(&mut self, _info: &StreamInfo) {}
225/// fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
226/// fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
227/// fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
228/// }
229///
230/// // Use with the builder
231/// let chain = Registry::new()
232/// .with(MyInterceptor::new);
233/// ```
234pub trait Interceptor:
235 sansio::Protocol<
236 TaggedPacket,
237 TaggedPacket,
238 (),
239 Rout = TaggedPacket,
240 Wout = TaggedPacket,
241 Eout = (),
242 Time = Instant,
243 Error = shared::error::Error,
244 > + Sized
245{
246 /// Wrap this interceptor with another layer.
247 ///
248 /// The wrapper function receives `self` and returns a new interceptor
249 /// that wraps it.
250 ///
251 /// # Example
252 ///
253 /// ```ignore
254 /// use std::time::Duration;
255 /// use rtc_interceptor::{NoopInterceptor, SenderReportBuilder};
256 ///
257 /// // Using the builder pattern (recommended)
258 /// let chain = NoopInterceptor::new()
259 /// .with(SenderReportBuilder::new().with_interval(Duration::from_secs(1)).build());
260 /// ```
261 fn with<O, F>(self, f: F) -> O
262 where
263 F: FnOnce(Self) -> O,
264 O: Interceptor,
265 {
266 f(self)
267 }
268
269 /// bind_local_stream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
270 /// will be called once per rtp packet.
271 fn bind_local_stream(&mut self, info: &StreamInfo);
272
273 /// unbind_local_stream is called when the Stream is removed. It can be used to clean up any data related to that track.
274 fn unbind_local_stream(&mut self, info: &StreamInfo);
275
276 /// bind_remote_stream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
277 /// will be called once per rtp packet.
278 fn bind_remote_stream(&mut self, info: &StreamInfo);
279
280 /// unbind_remote_stream is called when the Stream is removed. It can be used to clean up any data related to that track.
281 fn unbind_remote_stream(&mut self, info: &StreamInfo);
282}