Skip to main content

rtc_interceptor/
packet.rs

1use shared::TransportMessage;
2use std::any::Any;
3use std::sync::Arc;
4
5/// RTP/RTCP Packet
6///
7/// An enum representing either an RTP or RTCP packet that can be processed
8/// by interceptors in the chain.
9#[derive(Debug, Clone, PartialEq)]
10#[non_exhaustive]
11pub enum Packet {
12    /// RTP (Real-time Transport Protocol) packet containing media data
13    Rtp(rtp::Packet),
14    /// RTCP (RTP Control Protocol) packets for feedback and statistics.
15    ///
16    /// # An empty vector is reserved
17    ///
18    /// `Packet::Rtcp(vec![])` means **attribute carrier** and nothing else: a packet that exists
19    /// only to carry [`Attribute`]s when no real packet is going the same way. `rtc`'s handler
20    /// reads the attributes off it and then drops it, so it never reaches the wire or the
21    /// application.
22    ///
23    /// An interceptor must therefore never emit an empty compound packet meaning anything else —
24    /// it would be discarded with no error and no trace. Generators that build from a
25    /// variable-length list return early when that list is empty rather than emitting nothing-shaped
26    /// output; `rtc-interceptor/tests/empty_rtcp_is_reserved.rs` holds them to it.
27    Rtcp(Vec<Box<dyn rtcp::Packet>>),
28}
29
30/// A fact about a packet, attached by one interceptor and readable by the rest.
31///
32/// # Why it rides with the packet
33///
34/// Because it has to survive the journey. An interceptor that queues a packet and emits it later
35/// puts it back on the belt *behind* itself, where every interceptor ahead sees it again — so what
36/// was learned about that packet has to travel with it or be worked out twice. A side channel
37/// cannot manage that: it has no way to say *which* packet it refers to once the packet has been
38/// held, reordered or duplicated.
39///
40/// With `Ein`/`Eout` left as `()`, this is the only way information crosses interceptors.
41///
42/// # When there is no packet to ride
43///
44/// A connection-level fact — a bandwidth estimate, a keyframe request from the application — often
45/// needs to travel when no media is going that way. The carrier for those is an RTCP packet with an
46/// empty payload, [`Packet::Rtcp(vec![])`](Packet::Rtcp), which is inert to every interceptor that
47/// does not look for attributes and is dropped at the crate boundary once its attributes are read.
48/// That makes an empty compound RTCP packet **reserved**: see [`Packet::Rtcp`].
49///
50/// # Cost
51///
52/// [`AttributedPacket`] holds a `Vec`, which does not allocate while empty — and most packets carry
53/// nothing. Lookup is a linear scan of a handful of words, cheaper than hashing.
54#[derive(Clone)]
55#[non_exhaustive]
56pub enum Attribute {
57    /// Rebuilt by FEC rather than received: these bytes never arrived on the wire.
58    ///
59    /// Not needed by the NACK generator, despite the obvious guess. The FEC decoder is wire-ward of
60    /// it, so a rebuilt packet reaches the generator on the read walk like any other arrival and
61    /// fills the gap in its receive log — there is nothing left to ask for, by ordering rather than
62    /// by inspection. `tests/flexfec_receive.rs` pins that.
63    ///
64    /// It matters to anything that must distinguish *arrived* from *present*: an arrival recorder
65    /// telling the remote a packet turned up, when in fact it was lost and rebuilt here, overstates
66    /// what the path delivered.
67    RecoveredByFec,
68
69    /// A retransmission answering a NACK, not a first transmission.
70    ///
71    /// Still new bytes on the wire, which is why a send history has to count it — counting it as
72    /// an original tells a bandwidth estimator the path is carrying less than it is.
73    Retransmission,
74
75    /// Inbound RTCP an interceptor has decided the application should see.
76    ///
77    /// [`NoopInterceptor`](crate::NoopInterceptor) ends the inbound RTCP path unless the packet
78    /// carries this, which makes forwarding a per-packet judgement by whichever interceptor is
79    /// qualified to make it, rather than a chain-wide setting. An SFU relaying keyframe requests
80    /// marks those and leaves the receiver reports its own chain is acting on alone; a chain-wide
81    /// switch could only offer all of it or none.
82    ///
83    /// Usually attached by an interceptor the application supplies, since the application is what
84    /// knows which packets it can act on. Attaching it is the only way past the terminus:
85    /// re-emitting a copy does not work, because what an interceptor emits from `poll_read` rejoins
86    /// the belt *behind* itself, where the terminus is still ahead of it.
87    DeliverToApplication,
88
89    /// The congestion controller's estimate, in bits per second.
90    ///
91    /// Rides outbound so the pacer reads it on the way past. A bitrate is connection state rather
92    /// than a property of the packet carrying it — it travels this way because with no event
93    /// channel there is nowhere else for it to go.
94    TargetBitrateChanged {
95        /// The new target, in bits per second.
96        bits_per_second: f64,
97    },
98
99    /// Anything an application defines, so this enum is never a bottleneck on it.
100    ///
101    /// `Arc` rather than `Box` so an attributed packet stays cheap to clone — the NACK responder
102    /// clones into its send buffer and the FEC encoder into its block, and neither should deep-copy
103    /// an application's payload. Reached by type: [`AttributedPacket::custom`].
104    Custom(Arc<dyn Any + Send + Sync>),
105}
106
107impl std::fmt::Debug for Attribute {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self {
110            Self::RecoveredByFec => f.write_str("RecoveredByFec"),
111            Self::Retransmission => f.write_str("Retransmission"),
112            Self::DeliverToApplication => f.write_str("DeliverToApplication"),
113            Self::TargetBitrateChanged { bits_per_second } => f
114                .debug_struct("TargetBitrateChanged")
115                .field("bits_per_second", bits_per_second)
116                .finish(),
117            // The payload is `dyn Any`, which is not `Debug`.
118            Self::Custom(_) => f.write_str("Custom(..)"),
119        }
120    }
121}
122
123/// A packet together with what the interceptors have learned about it.
124#[derive(Clone, Debug)]
125pub struct AttributedPacket {
126    /// What the interceptors have attached on the way, in the order they attached it.
127    pub attributes: Vec<Attribute>,
128    /// The packet itself.
129    pub packet: Packet,
130}
131
132impl AttributedPacket {
133    /// A packet with nothing attached.
134    pub fn new(packet: Packet) -> Self {
135        Self {
136            attributes: Vec::new(),
137            packet,
138        }
139    }
140    /// Attach `attribute`, taking ownership — for building a packet in one expression.
141    pub fn with(mut self, attribute: Attribute) -> Self {
142        self.attributes.push(attribute);
143        self
144    }
145    /// Attach `attribute`.
146    ///
147    /// Attaching the same one twice is allowed and means nothing extra; [`has`](Self::has) answers
148    /// the only question anyone asks of it.
149    pub fn add(&mut self, attribute: Attribute) -> &mut Self {
150        self.attributes.push(attribute);
151        self
152    }
153
154    /// Whether this packet carries `attribute`.
155    ///
156    /// Compares by variant, not by value, so `has(&Attribute::TargetBitrateChanged { .. })` finds
157    /// one whatever the rate is, and [`Attribute::Custom`] never satisfies a query for a built-in.
158    pub fn has(&self, attribute: &Attribute) -> bool {
159        let wanted = std::mem::discriminant(attribute);
160        self.attributes
161            .iter()
162            .any(|held| std::mem::discriminant(held) == wanted)
163    }
164
165    /// The first attribute matching `attribute`'s variant, for reading a value out of it.
166    pub fn get(&self, attribute: &Attribute) -> Option<&Attribute> {
167        let wanted = std::mem::discriminant(attribute);
168        self.attributes
169            .iter()
170            .find(|held| std::mem::discriminant(*held) == wanted)
171    }
172
173    /// The first [`Attribute::Custom`] payload of type `T`, if this packet carries one.
174    pub fn custom<T: Any + Send + Sync>(&self) -> Option<&T> {
175        self.attributes
176            .iter()
177            .find_map(|attribute| match attribute {
178                Attribute::Custom(value) => value.downcast_ref::<T>(),
179                _ => None,
180            })
181    }
182}
183
184impl From<Packet> for AttributedPacket {
185    fn from(packet: Packet) -> Self {
186        Self::new(packet)
187    }
188}
189
190/// Tagged packet with transport metadata.
191///
192/// A [`TransportMessage`] wrapping a [`Packet`], which includes transport-level
193/// context such as source/destination addresses and protocol information.
194/// This is the primary message type passed through interceptor chains.
195pub type TaggedPacket = TransportMessage<AttributedPacket>;