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>;