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    Rtcp(Vec<Box<dyn rtcp::Packet>>),
16}
17
18/// A fact about a packet, attached by one interceptor and readable by the rest.
19///
20/// # Why it rides with the packet
21///
22/// Because it has to survive the journey. An interceptor that queues a packet and emits it later
23/// puts it back on the belt *behind* itself, where every interceptor ahead sees it again — so what
24/// was learned about that packet has to travel with it or be worked out twice. A side channel
25/// cannot manage that: it has no way to say *which* packet it refers to once the packet has been
26/// held, reordered or duplicated.
27///
28/// With `Ein`/`Eout` left as `()`, this is the only way information crosses interceptors.
29///
30/// # Cost
31///
32/// [`AttributedPacket`] holds a `Vec`, which does not allocate while empty — and most packets carry
33/// nothing. Lookup is a linear scan of a handful of words, cheaper than hashing.
34#[derive(Clone)]
35#[non_exhaustive]
36pub enum Attribute {
37    /// Rebuilt by FEC rather than received: these bytes never arrived on the wire.
38    ///
39    /// A NACK generator that sees this must not ask for the packet again.
40    RecoveredByFec,
41
42    /// A retransmission answering a NACK, not a first transmission.
43    ///
44    /// Still new bytes on the wire, which is why a send history has to count it — counting it as
45    /// an original tells a bandwidth estimator the path is carrying less than it is.
46    Retransmission,
47
48    /// Inbound RTCP an interceptor has decided the application should see.
49    ///
50    /// [`NoopInterceptor`](crate::NoopInterceptor) ends the inbound RTCP path unless the packet
51    /// carries this, which makes forwarding a per-packet judgement by whichever interceptor is
52    /// qualified to make it, rather than a chain-wide setting.
53    DeliverToApplication,
54
55    /// The congestion controller's estimate, in bits per second.
56    ///
57    /// Rides outbound so the pacer reads it on the way past. A bitrate is connection state rather
58    /// than a property of the packet carrying it — it travels this way because with no event
59    /// channel there is nowhere else for it to go.
60    TargetBitrateChanged {
61        /// The new target, in bits per second.
62        bits_per_second: f64,
63    },
64
65    /// Ask the keyframe generator to send a Picture Loss Indication now.
66    ForcePli {
67        /// Which streams to request a keyframe for, or `None` for all of them.
68        ssrcs: Option<Vec<u32>>,
69    },
70
71    /// Anything an application defines, so this enum is never a bottleneck on it.
72    ///
73    /// `Arc` rather than `Box` so an attributed packet stays cheap to clone — the NACK responder
74    /// clones into its send buffer and the FEC encoder into its block, and neither should deep-copy
75    /// an application's payload. Reached by type: [`AttributedPacket::custom`].
76    Custom(Arc<dyn Any + Send + Sync>),
77}
78
79impl std::fmt::Debug for Attribute {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            Self::RecoveredByFec => f.write_str("RecoveredByFec"),
83            Self::Retransmission => f.write_str("Retransmission"),
84            Self::DeliverToApplication => f.write_str("DeliverToApplication"),
85            Self::TargetBitrateChanged { bits_per_second } => f
86                .debug_struct("TargetBitrateChanged")
87                .field("bits_per_second", bits_per_second)
88                .finish(),
89            Self::ForcePli { ssrcs } => f.debug_struct("ForcePli").field("ssrcs", ssrcs).finish(),
90            // The payload is `dyn Any`, which is not `Debug`.
91            Self::Custom(_) => f.write_str("Custom(..)"),
92        }
93    }
94}
95
96/// A packet together with what the interceptors have learned about it.
97#[derive(Clone, Debug)]
98pub struct AttributedPacket {
99    /// What the interceptors have attached on the way, in the order they attached it.
100    pub attributes: Vec<Attribute>,
101    /// The packet itself.
102    pub packet: Packet,
103}
104
105impl AttributedPacket {
106    /// A packet with nothing attached.
107    pub fn new(packet: Packet) -> Self {
108        Self {
109            attributes: Vec::new(),
110            packet,
111        }
112    }
113    /// Attach `attribute`, taking ownership — for building a packet in one expression.
114    pub fn with(mut self, attribute: Attribute) -> Self {
115        self.attributes.push(attribute);
116        self
117    }
118    /// Attach `attribute`.
119    ///
120    /// Attaching the same one twice is allowed and means nothing extra; [`has`](Self::has) answers
121    /// the only question anyone asks of it.
122    pub fn add(&mut self, attribute: Attribute) -> &mut Self {
123        self.attributes.push(attribute);
124        self
125    }
126
127    /// Whether this packet carries `attribute`.
128    ///
129    /// Compares by variant, not by value, so `has(&Attribute::TargetBitrateChanged { .. })` finds
130    /// one whatever the rate is, and [`Attribute::Custom`] never satisfies a query for a built-in.
131    pub fn has(&self, attribute: &Attribute) -> bool {
132        let wanted = std::mem::discriminant(attribute);
133        self.attributes
134            .iter()
135            .any(|held| std::mem::discriminant(held) == wanted)
136    }
137
138    /// The first attribute matching `attribute`'s variant, for reading a value out of it.
139    pub fn get(&self, attribute: &Attribute) -> Option<&Attribute> {
140        let wanted = std::mem::discriminant(attribute);
141        self.attributes
142            .iter()
143            .find(|held| std::mem::discriminant(*held) == wanted)
144    }
145
146    /// The first [`Attribute::Custom`] payload of type `T`, if this packet carries one.
147    pub fn custom<T: Any + Send + Sync>(&self) -> Option<&T> {
148        self.attributes
149            .iter()
150            .find_map(|attribute| match attribute {
151                Attribute::Custom(value) => value.downcast_ref::<T>(),
152                _ => None,
153            })
154    }
155}
156
157impl From<Packet> for AttributedPacket {
158    fn from(packet: Packet) -> Self {
159        Self::new(packet)
160    }
161}
162
163/// Tagged packet with transport metadata.
164///
165/// A [`TransportMessage`] wrapping a [`Packet`], which includes transport-level
166/// context such as source/destination addresses and protocol information.
167/// This is the primary message type passed through interceptor chains.
168pub type TaggedPacket = TransportMessage<AttributedPacket>;