Skip to main content

rtc_interceptor/
noop.rs

1//! Where the inbound RTCP path ends.
2
3use crate::Interceptor;
4use crate::{Packet, StreamInfo, TaggedPacket};
5use sansio::Protocol;
6use shared::error::Error;
7use std::collections::VecDeque;
8use std::time::Instant;
9
10/// Decides what becomes of inbound RTCP once the interceptors have had it, and passes everything
11/// else through.
12///
13/// # Why this exists
14///
15/// Inbound RTCP is **for the interceptors**: a receiver report feeds the sender-side statistics, a
16/// NACK is answered by the responder, transport-wide feedback drives the bandwidth estimate.
17/// Handing it onward by default would give an application a stream of control traffic it did not
18/// ask for and cannot act on, mixed in with its media — so by default it stops here.
19///
20/// An application that *does* read RTCP asks for it when building the chain, with
21/// [`Registry::with_rtcp_readable`](crate::Registry::with_rtcp_readable). See below for why that is
22/// the only way to get it.
23///
24/// # Where this belongs in the chain
25///
26/// **Last**, so every interceptor that reads RTCP has already seen it by the time it is dropped.
27/// Anywhere else it would starve the ones beyond it — a NACK responder placed after this would
28/// never see a NACK. [`Registry::build`](crate::Registry::build) appends it for that reason,
29/// rather than leaving the position to each caller.
30///
31/// # Why an interceptor of your own cannot do it instead
32///
33/// Under the nested chain an application could add an interceptor that kept a copy of each RTCP
34/// packet and returned it from its own `poll_read` ahead of delegating inward. That worked because
35/// a local `poll_read` queue was *terminal*: the copy bypassed everything below and escaped this
36/// one.
37///
38/// On the belt it does not. What an interceptor emits from `poll_read` rejoins the belt **behind
39/// itself**, so it arrives here like any other packet and is dropped like any other packet — the
40/// original *and* the copy, twice over. Since this is always the last interceptor, no position
41/// exists from which to forward past it, which is why the decision belongs to whoever builds the
42/// chain rather than to an interceptor in it.
43///
44/// # Naming
45///
46/// It was a no-op in the nested chain, where its job was to terminate the recursion and hand
47/// packets back. The belt needs no terminator, so all that is left is the one decision it always
48/// quietly made.
49#[derive(Default)]
50pub struct NoopInterceptor {
51    rtcp_readable: bool,
52    read_queue: VecDeque<TaggedPacket>,
53    write_queue: VecDeque<TaggedPacket>,
54}
55
56impl NoopInterceptor {
57    /// A terminus.
58    ///
59    /// `rtcp_readable` decides whether inbound RTCP carries on to the application after every
60    /// interceptor has seen it. [`Registry::build`](crate::Registry::build) supplies it from
61    /// [`Registry::with_rtcp_readable`](crate::Registry::with_rtcp_readable).
62    pub fn new(rtcp_readable: bool) -> Self {
63        Self {
64            rtcp_readable,
65            ..Default::default()
66        }
67    }
68}
69
70impl Protocol<TaggedPacket, TaggedPacket, ()> for NoopInterceptor {
71    type Rout = TaggedPacket;
72    type Wout = TaggedPacket;
73    type Eout = ();
74    type Error = Error;
75    type Time = Instant;
76
77    fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
78        let keep = match msg.message.packet {
79            Packet::Rtp(_) => true,
80            // The end of the line for inbound control traffic, unless the chain asked for it.
81            Packet::Rtcp(_) => self.rtcp_readable,
82        };
83        if keep {
84            self.read_queue.push_back(msg);
85        }
86        Ok(())
87    }
88
89    fn poll_read(&mut self) -> Option<Self::Rout> {
90        self.read_queue.pop_front()
91    }
92
93    fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
94        self.write_queue.push_back(msg);
95        Ok(())
96    }
97
98    fn poll_write(&mut self) -> Option<Self::Wout> {
99        self.write_queue.pop_front()
100    }
101}
102
103impl Interceptor for NoopInterceptor {
104    fn bind_local_stream(&mut self, _info: &StreamInfo) {}
105    fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
106    fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
107    fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::{AttributedPacket, Registry, StreamInfo};
114    use sansio::Protocol;
115    use shared::TransportContext;
116    use shared::error::Error;
117    use std::collections::VecDeque;
118    use std::time::Instant;
119
120    fn packet(message: Packet) -> TaggedPacket {
121        TaggedPacket {
122            now: Instant::now(),
123            transport: TransportContext::default(),
124            message: AttributedPacket::new(message),
125        }
126    }
127
128    #[test]
129    fn inbound_rtcp_does_not_reach_the_application() {
130        let mut chain = Registry::new().build();
131
132        chain.handle_read(packet(Packet::Rtcp(vec![]))).unwrap();
133        assert!(chain.poll_read().is_none());
134    }
135
136    #[test]
137    fn inbound_rtp_passes_through() {
138        let mut chain = Registry::new().build();
139
140        chain
141            .handle_read(packet(Packet::Rtp(rtp::Packet::default())))
142            .unwrap();
143        assert!(chain.poll_read().is_some());
144    }
145
146    /// Outbound RTCP is untouched: this ends the *inbound* path only.
147    #[test]
148    fn outbound_rtcp_is_not_affected() {
149        let mut chain = Registry::new().build();
150
151        chain.handle_write(packet(Packet::Rtcp(vec![]))).unwrap();
152        assert!(chain.poll_write().is_some());
153    }
154
155    /// An interceptor wire-ward of the terminus still sees inbound RTCP — that is the whole point of it
156    /// being application-most.
157    #[test]
158    fn stages_before_it_still_see_inbound_rtcp() {
159        #[derive(Default)]
160        struct Counter {
161            seen: std::sync::Arc<std::sync::atomic::AtomicUsize>,
162            read_queue: VecDeque<TaggedPacket>,
163            write_queue: VecDeque<TaggedPacket>,
164        }
165        impl Protocol<TaggedPacket, TaggedPacket, ()> for Counter {
166            type Rout = TaggedPacket;
167            type Wout = TaggedPacket;
168            type Eout = ();
169            type Error = Error;
170            type Time = Instant;
171
172            fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
173                if matches!(msg.message.packet, Packet::Rtcp(_)) {
174                    self.seen.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
175                }
176                self.read_queue.push_back(msg);
177                Ok(())
178            }
179
180            fn poll_read(&mut self) -> Option<Self::Rout> {
181                self.read_queue.pop_front()
182            }
183
184            fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
185                self.write_queue.push_back(msg);
186                Ok(())
187            }
188
189            fn poll_write(&mut self) -> Option<Self::Wout> {
190                self.write_queue.pop_front()
191            }
192
193            fn handle_timeout(&mut self, _now: Instant) -> Result<(), Self::Error> {
194                Ok(())
195            }
196
197            fn poll_timeout(&mut self) -> Option<Self::Time> {
198                None
199            }
200        }
201        impl Interceptor for Counter {
202            fn bind_local_stream(&mut self, _info: &StreamInfo) {}
203            fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
204            fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
205            fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
206        }
207        let counter = Counter::default();
208        let seen = counter.seen.clone();
209        let mut chain = Registry::new().with(counter).build();
210
211        chain.handle_read(packet(Packet::Rtcp(vec![]))).unwrap();
212
213        assert_eq!(1, seen.load(std::sync::atomic::Ordering::Relaxed));
214        assert!(chain.poll_read().is_none(), "but it stops at the terminus");
215    }
216}