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}