Skip to main content

rtc_interceptor/
registry.rs

1//! Interceptor Registry - Type-safe builder for constructing interceptor chains.
2//!
3//! The [`Registry`] provides a fluent API for composing interceptor chains. Each call
4//! to [`with()`](Registry::with) wraps the current chain with a new interceptor layer.
5//!
6//! # Chain Construction
7//!
8//! Interceptors are added from innermost to outermost. The first interceptor added
9//! becomes the innermost (closest to [`NoopInterceptor`](crate::NoopInterceptor)),
10//! and the last becomes the outermost (processes packets first).
11//!
12//! ```text
13//! Registry::new()
14//!     .with(InterceptorA)  // Innermost
15//!     .with(InterceptorB)  // Middle
16//!     .with(InterceptorC)  // Outermost
17//!     .build()
18//!
19//! Results in: C wraps B wraps A wraps NoopInterceptor
20//! ```
21
22use crate::noop::NoopInterceptor;
23use crate::{BoxedInterceptor, Interceptor};
24
25/// Registry for constructing interceptor chains.
26///
27/// `Registry` wraps an interceptor chain and allows adding more interceptors
28/// via the [`with`](Registry::with) method. The chain can be extracted with [`build`](Registry::build).
29///
30/// # Example
31///
32/// ```
33/// use rtc_interceptor::{ReceiverReportBuilder, Registry, SenderReportBuilder};
34///
35/// // Each `with` changes the registry's type, so rebind rather than reassign.
36/// let registry = Registry::new()
37///     .with(SenderReportBuilder::new().build())
38///     .with(ReceiverReportBuilder::new().build());
39///
40/// // Build the final chain
41/// let chain = registry.build();
42/// ```
43///
44/// # Helper Function Pattern
45///
46/// ```
47/// use rtc_interceptor::{Interceptor, ReceiverReportBuilder, Registry, SenderReportBuilder};
48///
49/// fn register_default_interceptors<P: Interceptor>(
50///     registry: Registry<P>,
51/// ) -> Registry<impl Interceptor + use<P>> {
52///     registry
53///         .with(SenderReportBuilder::new().build())
54///         .with(ReceiverReportBuilder::new().build())
55/// }
56///
57/// let registry = Registry::new();
58/// let registry = register_default_interceptors(registry);
59/// let chain = registry.build();
60/// ```
61#[derive(Clone)]
62pub struct Registry<P> {
63    inner: P,
64}
65
66impl Registry<NoopInterceptor> {
67    /// Create a new empty registry.
68    ///
69    /// This creates a `NoopInterceptor` as the innermost layer.
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// use rtc_interceptor::Registry;
75    ///
76    /// let registry = Registry::new();
77    /// ```
78    pub fn new() -> Self {
79        Registry {
80            inner: NoopInterceptor::new(),
81        }
82    }
83}
84
85impl Default for Registry<NoopInterceptor> {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl<P: Interceptor> Registry<P> {
92    /// Create a registry from an existing interceptor.
93    ///
94    /// # Example
95    ///
96    /// ```
97    /// use rtc_interceptor::{NoopInterceptor, Registry};
98    ///
99    /// let custom = NoopInterceptor::new();
100    /// let registry = Registry::from(custom);
101    /// ```
102    pub fn from(inner: P) -> Self {
103        Registry { inner }
104    }
105
106    /// Wrap the current chain with another interceptor.
107    ///
108    /// Returns a new `Registry` with the updated chain type.
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// use rtc_interceptor::{ReceiverReportBuilder, Registry, SenderReportBuilder};
114    ///
115    /// let registry = Registry::new()
116    ///     .with(SenderReportBuilder::new().build())
117    ///     .with(ReceiverReportBuilder::new().build());
118    /// ```
119    pub fn with<O, F>(self, f: F) -> Registry<O>
120    where
121        F: FnOnce(P) -> O,
122        O: Interceptor,
123    {
124        Registry {
125            inner: f(self.inner),
126        }
127    }
128
129    /// Build and return the interceptor chain.
130    ///
131    /// Consumes the registry and returns the inner interceptor chain.
132    ///
133    /// # Example
134    ///
135    /// ```
136    /// use rtc_interceptor::{Registry, SenderReportBuilder};
137    ///
138    /// let registry = Registry::new().with(SenderReportBuilder::new().build());
139    /// let chain = registry.build();
140    /// ```
141    pub fn build(self) -> P {
142        self.inner
143    }
144
145    /// Erase the chain's type, turning this into a `Registry<BoxedInterceptor>`.
146    ///
147    /// The chain an application assembles at runtime is a deep nest of generic types
148    /// (`TwccSender<NackResponder<...<NoopInterceptor>>>`), which otherwise leaks into every
149    /// type that holds the peer connection. Boxing it collapses that to one concrete type, so
150    /// a struct can store an `RTCPeerConnection<BoxedInterceptor>` field directly.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// use rtc_interceptor::{BoxedInterceptor, Registry, SenderReportBuilder};
156    ///
157    /// // Whatever the chain was composed of, the result has one concrete type.
158    /// let chain: BoxedInterceptor = Registry::new()
159    ///     .with(SenderReportBuilder::new().build())
160    ///     .boxed()
161    ///     .build();
162    /// ```
163    ///
164    /// The `rtc` crate accepts the erased registry directly, so a peer connection can be stored
165    /// as `RTCPeerConnection<BoxedInterceptor>`:
166    ///
167    /// ```ignore
168    /// let registry = register_default_interceptors(Registry::new(), &mut media_engine)?;
169    /// let pc = RTCPeerConnectionBuilder::new()
170    ///     .with_interceptor_registry(registry.boxed())
171    ///     .build()?;
172    /// ```
173    ///
174    /// `P: 'static` is required because [`BoxedInterceptor`] is `Box<dyn Interceptor + 'static>`,
175    /// so the chain must not borrow anything shorter-lived. This is the only operation that needs
176    /// the bound, which is why [`Interceptor`] itself does not require `'static`.
177    pub fn boxed(self) -> Registry<BoxedInterceptor>
178    where
179        P: 'static,
180    {
181        Registry {
182            inner: Box::new(self.inner),
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::TaggedPacket;
191    use sansio::Protocol;
192    use shared::error::Error;
193    use std::time::Instant;
194
195    fn dummy_rtp_packet() -> TaggedPacket {
196        TaggedPacket {
197            now: Instant::now(),
198            transport: Default::default(),
199            message: crate::Packet::Rtp(rtp::Packet::default()),
200        }
201    }
202
203    // A simple test interceptor that wraps an inner protocol
204    struct TestInterceptor<P> {
205        inner: P,
206        name: &'static str,
207    }
208
209    impl<P> TestInterceptor<P> {
210        fn new(inner: P) -> Self {
211            Self {
212                inner,
213                name: "test",
214            }
215        }
216
217        fn with_name(name: &'static str) -> impl FnOnce(P) -> Self {
218            move |inner| Self { inner, name }
219        }
220    }
221
222    impl<P: Interceptor> Protocol<TaggedPacket, TaggedPacket, ()> for TestInterceptor<P> {
223        type Rout = TaggedPacket;
224        type Wout = TaggedPacket;
225        type Eout = ();
226        type Error = Error;
227        type Time = Instant;
228
229        fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
230            self.inner.handle_read(msg)
231        }
232
233        fn poll_read(&mut self) -> Option<Self::Rout> {
234            self.inner.poll_read()
235        }
236
237        fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
238            self.inner.handle_write(msg)
239        }
240
241        fn poll_write(&mut self) -> Option<Self::Wout> {
242            self.inner.poll_write()
243        }
244    }
245
246    impl<P: Interceptor> Interceptor for TestInterceptor<P> {
247        fn bind_local_stream(&mut self, info: &crate::StreamInfo) {
248            self.inner.bind_local_stream(info);
249        }
250        fn unbind_local_stream(&mut self, info: &crate::StreamInfo) {
251            self.inner.unbind_local_stream(info);
252        }
253        fn bind_remote_stream(&mut self, info: &crate::StreamInfo) {
254            self.inner.bind_remote_stream(info);
255        }
256        fn unbind_remote_stream(&mut self, info: &crate::StreamInfo) {
257            self.inner.unbind_remote_stream(info);
258        }
259    }
260
261    #[test]
262    fn test_registry_new() {
263        let registry = Registry::new();
264        let mut chain = registry.build();
265        let pkt = dummy_rtp_packet();
266        chain.handle_read(pkt).unwrap();
267        assert!(chain.poll_read().is_some());
268    }
269
270    #[test]
271    fn test_registry_with_single_interceptor() {
272        let registry = Registry::new().with(TestInterceptor::new);
273        let mut chain = registry.build();
274
275        let pkt = dummy_rtp_packet();
276        chain.handle_read(pkt).unwrap();
277        assert!(chain.poll_read().is_some());
278        assert_eq!(chain.name, "test");
279    }
280
281    #[test]
282    fn test_registry_with_multiple_interceptors() {
283        let registry = Registry::new()
284            .with(TestInterceptor::with_name("inner"))
285            .with(TestInterceptor::with_name("outer"));
286        let mut chain = registry.build();
287
288        let pkt = dummy_rtp_packet();
289        chain.handle_read(pkt).unwrap();
290        assert!(chain.poll_read().is_some());
291        assert_eq!(chain.name, "outer");
292        assert_eq!(chain.inner.name, "inner");
293    }
294
295    #[test]
296    fn test_registry_from_inner() {
297        let custom = NoopInterceptor::new();
298        let registry = Registry::from(custom).with(TestInterceptor::new);
299        let mut chain = registry.build();
300
301        let pkt = dummy_rtp_packet();
302        let pkt_message = pkt.message.clone();
303        chain.handle_write(pkt).unwrap();
304        assert_eq!(chain.poll_write().unwrap().message, pkt_message);
305    }
306
307    // Test the helper function pattern
308    fn register_test_interceptors<P: Interceptor>(
309        registry: Registry<P>,
310    ) -> Registry<TestInterceptor<TestInterceptor<P>>> {
311        registry
312            .with(TestInterceptor::with_name("first"))
313            .with(TestInterceptor::with_name("second"))
314    }
315
316    #[test]
317    fn test_helper_function_pattern() {
318        let registry = Registry::new();
319        let registry = register_test_interceptors(registry);
320        let mut chain = registry.build();
321
322        let pkt = dummy_rtp_packet();
323        chain.handle_read(pkt).unwrap();
324        assert!(chain.poll_read().is_some());
325        assert_eq!(chain.name, "second");
326        assert_eq!(chain.inner.name, "first");
327    }
328}