rtcp_types/feedback/
mod.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::borrow::Borrow;
4
5use crate::{
6    prelude::*,
7    utils::{pad_to_4bytes, parser, writer},
8    RtcpPacket, RtcpPacketParser, RtcpParseError, RtcpWriteError,
9};
10
11pub mod fir;
12pub mod nack;
13pub mod pli;
14pub mod rpsi;
15pub mod sli;
16
17/// The type of feedback packet.  There is currently transport and payload values supported for
18/// feedback packets.
19#[derive(Debug, Default, PartialEq, Eq)]
20pub struct FciFeedbackPacketType {
21    transport: bool,
22    payload: bool,
23}
24
25impl FciFeedbackPacketType {
26    pub const NONE: Self = Self {
27        transport: false,
28        payload: false,
29    };
30    pub const TRANSPORT: Self = Self {
31        transport: true,
32        payload: false,
33    };
34    pub const PAYLOAD: Self = Self {
35        transport: false,
36        payload: true,
37    };
38}
39
40impl std::ops::BitOr<FciFeedbackPacketType> for FciFeedbackPacketType {
41    type Output = Self;
42    fn bitor(self, rhs: Self) -> Self::Output {
43        Self {
44            transport: self.transport | rhs.transport,
45            payload: self.payload | rhs.payload,
46        }
47    }
48}
49
50impl std::ops::BitAnd<FciFeedbackPacketType> for FciFeedbackPacketType {
51    type Output = Self;
52    fn bitand(self, rhs: Self) -> Self::Output {
53        Self {
54            transport: self.transport & rhs.transport,
55            payload: self.payload & rhs.payload,
56        }
57    }
58}
59
60/// A parsed (Transport) Feedback packet as specified in RFC 4585.
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct TransportFeedback<'a> {
63    data: &'a [u8],
64}
65
66impl<'a> RtcpPacket for TransportFeedback<'a> {
67    const MIN_PACKET_LEN: usize = 12;
68    const PACKET_TYPE: u8 = 205;
69}
70
71impl<'a> RtcpPacketParser<'a> for TransportFeedback<'a> {
72    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
73        parser::check_packet::<Self>(data)?;
74        Ok(Self { data })
75    }
76
77    #[inline(always)]
78    fn header_data(&self) -> [u8; 4] {
79        self.data[..4].try_into().unwrap()
80    }
81}
82
83impl<'a> TransportFeedback<'a> {
84    /// Constructs a [`TransportFeedbackBuilder`] which refers to the provided [`FciBuilder`].
85    ///
86    /// Use [`TransportFeedback::builder_owned`] to return the [`TransportFeedbackBuilder`]
87    /// from a function.
88    pub fn builder(fci: &'a dyn FciBuilder<'a>) -> TransportFeedbackBuilder<'a> {
89        TransportFeedbackBuilder {
90            padding: 0,
91            sender_ssrc: 0,
92            media_ssrc: 0,
93            fci: FciBuilderWrapper::Borrowed(fci),
94        }
95    }
96
97    /// Constructs a [`TransportFeedbackBuilder`] which owns the provided [`FciBuilder`].
98    ///
99    /// This allows returning it from a function.
100    ///
101    /// **Warning:** this causes the [`FciBuilder`] to be heap-allocated.
102    /// Use [`TransportFeedback::builder`] when possible.
103    pub fn builder_owned(
104        fci: impl FciBuilder<'static> + 'static,
105    ) -> TransportFeedbackBuilder<'static> {
106        TransportFeedbackBuilder {
107            padding: 0,
108            sender_ssrc: 0,
109            media_ssrc: 0,
110            fci: FciBuilderWrapper::Owned(Box::new(fci)),
111        }
112    }
113
114    /// The (optional) padding used by this [`TransportFeedback`] packet
115    pub fn padding(&self) -> Option<u8> {
116        parser::parse_padding(self.data)
117    }
118
119    /// The SSRC of the sender sending this feedback
120    pub fn sender_ssrc(&self) -> u32 {
121        parser::parse_ssrc(self.data)
122    }
123
124    /// The SSRC of the media this packet refers to.  May be unset (0) in some feedback types.
125    pub fn media_ssrc(&self) -> u32 {
126        parser::parse_ssrc(&self.data[4..])
127    }
128
129    /// Parse the Feedback Control Information into a concrete type.
130    ///
131    /// Will fail if:
132    /// * The FCI does not support transport feedback,
133    /// * the feedback type does not match the FCI
134    /// * The FCI implementation fails to parse the contained data
135    pub fn parse_fci<F: FciParser<'a>>(&self) -> Result<F, RtcpParseError> {
136        if F::PACKET_TYPE & FciFeedbackPacketType::TRANSPORT == FciFeedbackPacketType::NONE {
137            return Err(RtcpParseError::WrongImplementation);
138        }
139        if parser::parse_count(self.data) != F::FCI_FORMAT {
140            return Err(RtcpParseError::WrongImplementation);
141        }
142        F::parse(&self.data[12..])
143    }
144}
145
146/// TransportFeedback packet builder
147#[derive(Debug)]
148#[must_use = "The builder must be built to be used"]
149pub struct TransportFeedbackBuilder<'a> {
150    padding: u8,
151    sender_ssrc: u32,
152    media_ssrc: u32,
153    fci: FciBuilderWrapper<'a>,
154}
155
156impl<'a> TransportFeedbackBuilder<'a> {
157    /// Set the SSRC this feedback packet is being sent from
158    pub fn sender_ssrc(mut self, sender_ssrc: u32) -> Self {
159        self.sender_ssrc = sender_ssrc;
160        self
161    }
162
163    /// Set the SSRC this feedback packet is being directed towards.  May be 0 if the specific
164    /// feedback type allows.
165    pub fn media_ssrc(mut self, media_ssrc: u32) -> Self {
166        self.media_ssrc = media_ssrc;
167        self
168    }
169
170    /// Sets the number of padding bytes to use for this TransportFeedback packet.
171    pub fn padding(mut self, padding: u8) -> Self {
172        self.padding = padding;
173        self
174    }
175}
176
177#[inline]
178fn fb_write_into<T: RtcpPacket>(
179    feedback_type: FciFeedbackPacketType,
180    buf: &mut [u8],
181    sender_ssrc: u32,
182    media_ssrc: u32,
183    fci: &dyn FciBuilder,
184    padding: u8,
185) -> usize {
186    if feedback_type & fci.supports_feedback_type() == FciFeedbackPacketType::NONE {
187        return 0;
188    }
189
190    let fmt = fci.format();
191    assert!(fmt <= 0x1f);
192    let mut idx = writer::write_header_unchecked::<T>(padding, fmt, buf);
193
194    let mut end = idx;
195    end += 4;
196    buf[idx..end].copy_from_slice(&sender_ssrc.to_be_bytes());
197    idx = end;
198    end += 4;
199    buf[idx..end].copy_from_slice(&media_ssrc.to_be_bytes());
200    idx = end;
201
202    end += fci.write_into_unchecked(&mut buf[idx..]);
203
204    end += writer::write_padding_unchecked(padding, &mut buf[idx..]);
205
206    end
207}
208
209impl<'a> RtcpPacketWriter for TransportFeedbackBuilder<'a> {
210    /// Calculates the size required to write this TransportFeedback packet.
211    ///
212    /// Returns an error if:
213    ///
214    /// * The FCI data is too large
215    /// * The FCI fails to calculate a valid size
216    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
217        writer::check_padding(self.padding)?;
218
219        if self.fci.supports_feedback_type() & FciFeedbackPacketType::TRANSPORT
220            == FciFeedbackPacketType::NONE
221        {
222            return Err(RtcpWriteError::FciWrongFeedbackPacketType);
223        }
224        let fci_len = self.fci.calculate_size()?;
225
226        Ok(TransportFeedback::MIN_PACKET_LEN + pad_to_4bytes(fci_len))
227    }
228
229    /// Write this TransportFeedback packet data into `buf` without any validity checks.
230    ///
231    /// Returns the number of bytes written.
232    ///
233    /// # Panic
234    ///
235    /// Panics if the buf is not large enough.
236    #[inline]
237    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
238        fb_write_into::<TransportFeedback>(
239            FciFeedbackPacketType::TRANSPORT,
240            buf,
241            self.sender_ssrc,
242            self.media_ssrc,
243            self.fci.as_ref(),
244            self.padding,
245        )
246    }
247
248    fn get_padding(&self) -> Option<u8> {
249        if self.padding == 0 {
250            return None;
251        }
252
253        Some(self.padding)
254    }
255}
256
257/// A parsed (Transport) Feedback packet as specified in RFC 4585.
258#[derive(Clone, Debug, PartialEq, Eq)]
259pub struct PayloadFeedback<'a> {
260    data: &'a [u8],
261}
262
263impl<'a> RtcpPacket for PayloadFeedback<'a> {
264    const MIN_PACKET_LEN: usize = 12;
265    const PACKET_TYPE: u8 = 206;
266}
267
268impl<'a> RtcpPacketParser<'a> for PayloadFeedback<'a> {
269    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
270        parser::check_packet::<Self>(data)?;
271        Ok(Self { data })
272    }
273
274    #[inline(always)]
275    fn header_data(&self) -> [u8; 4] {
276        self.data[..4].try_into().unwrap()
277    }
278}
279
280impl<'a> PayloadFeedback<'a> {
281    /// Constructs a [`PayloadFeedbackBuilder`] which refers to the provided [`FciBuilder`].
282    ///
283    /// Use [`PayloadFeedback::builder_owned`] to return the [`PayloadFeedbackBuilder`] from a function.
284    pub fn builder(fci: &'a dyn FciBuilder<'a>) -> PayloadFeedbackBuilder<'a> {
285        PayloadFeedbackBuilder {
286            padding: 0,
287            sender_ssrc: 0,
288            media_ssrc: 0,
289            fci: FciBuilderWrapper::Borrowed(fci),
290        }
291    }
292
293    /// Constructs a [`PayloadFeedbackBuilder`] which owns the provided [`FciBuilder`].
294    ///
295    /// This allows returning it from a function.
296    ///
297    /// **Warning:** this causes the [`FciBuilder`] to be heap-allocated.
298    /// Use [`PayloadFeedback::builder`] when possible.
299    pub fn builder_owned(
300        fci: impl FciBuilder<'static> + 'static,
301    ) -> PayloadFeedbackBuilder<'static> {
302        PayloadFeedbackBuilder {
303            padding: 0,
304            sender_ssrc: 0,
305            media_ssrc: 0,
306            fci: FciBuilderWrapper::Owned(Box::new(fci)),
307        }
308    }
309
310    /// The (optional) padding used by this [`PayloadFeedback`] packet
311    pub fn padding(&self) -> Option<u8> {
312        parser::parse_padding(self.data)
313    }
314
315    /// The SSRC of the sender sending this feedback
316    pub fn sender_ssrc(&self) -> u32 {
317        parser::parse_ssrc(self.data)
318    }
319
320    /// The SSRC of the media this packet refers to.  May be unset (0) in some feedback types.
321    pub fn media_ssrc(&self) -> u32 {
322        parser::parse_ssrc(&self.data[4..])
323    }
324
325    /// Parse the Feedback Control Information into a concrete type.
326    ///
327    /// Will fail if:
328    /// * The FCI does not support payload feedback,
329    /// * the feedback type does not match the FCI
330    /// * The FCI implementation fails to parse the contained data
331    pub fn parse_fci<F: FciParser<'a>>(&self) -> Result<F, RtcpParseError> {
332        if F::PACKET_TYPE & FciFeedbackPacketType::PAYLOAD == FciFeedbackPacketType::NONE {
333            return Err(RtcpParseError::WrongImplementation);
334        }
335        if parser::parse_count(self.data) != F::FCI_FORMAT {
336            return Err(RtcpParseError::WrongImplementation);
337        }
338        F::parse(&self.data[12..])
339    }
340}
341
342/// TransportFeedback packet builder
343#[derive(Debug)]
344#[must_use = "The builder must be built to be used"]
345pub struct PayloadFeedbackBuilder<'a> {
346    padding: u8,
347    sender_ssrc: u32,
348    media_ssrc: u32,
349    fci: FciBuilderWrapper<'a>,
350}
351
352impl<'a> PayloadFeedbackBuilder<'a> {
353    /// Set the SSRC this feedback packet is being sent from
354    pub fn sender_ssrc(mut self, sender_ssrc: u32) -> Self {
355        self.sender_ssrc = sender_ssrc;
356        self
357    }
358
359    /// Set the SSRC this feedback packet is being directed towards.  May be 0 if the specific
360    /// feedback type allows.
361    pub fn media_ssrc(mut self, media_ssrc: u32) -> Self {
362        self.media_ssrc = media_ssrc;
363        self
364    }
365
366    /// Sets the number of padding bytes to use for this TransportFeedback packet.
367    pub fn padding(mut self, padding: u8) -> Self {
368        self.padding = padding;
369        self
370    }
371}
372
373impl<'a> RtcpPacketWriter for PayloadFeedbackBuilder<'a> {
374    /// Calculates the size required to write this PayloadFeedback packet.
375    ///
376    /// Returns an error if:
377    ///
378    /// * The FCI data is too large
379    /// * The FCI fails to calculate a valid size
380    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
381        writer::check_padding(self.padding)?;
382
383        if self.fci.supports_feedback_type() & FciFeedbackPacketType::PAYLOAD
384            == FciFeedbackPacketType::NONE
385        {
386            return Err(RtcpWriteError::FciWrongFeedbackPacketType);
387        }
388        let fci_len = self.fci.calculate_size()?;
389
390        Ok(PayloadFeedback::MIN_PACKET_LEN + pad_to_4bytes(fci_len))
391    }
392
393    /// Write this TransportFeedback packet data into `buf` without any validity checks.
394    ///
395    /// Returns the number of bytes written.
396    ///
397    /// # Panic
398    ///
399    /// Panics if the buf is not large enough.
400    #[inline]
401    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
402        fb_write_into::<PayloadFeedback>(
403            FciFeedbackPacketType::PAYLOAD,
404            buf,
405            self.sender_ssrc,
406            self.media_ssrc,
407            self.fci.as_ref(),
408            self.padding,
409        )
410    }
411
412    fn get_padding(&self) -> Option<u8> {
413        if self.padding == 0 {
414            return None;
415        }
416
417        Some(self.padding)
418    }
419}
420
421/// Trait for parsing FCI data in [`TransportFeedback`] or [`PayloadFeedback`] packets
422pub trait FciParser<'a>: Sized {
423    const PACKET_TYPE: FciFeedbackPacketType;
424    const FCI_FORMAT: u8;
425
426    /// Parse the provided FCI data
427    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError>;
428}
429
430/// Stack or heap allocated [`FciBuilder`] wrapper.
431///
432/// This wrapper allows borrowing or owning an [`FciBuilder`] without
433/// propagating the concrete type of the [`FciBuilder`] to types such as
434/// [`PayloadFeedbackBuilder`] or [`TransportFeedback`]. This is needed for
435/// these types to be included in collections such as [`crate::CompoundBuilder`].
436///
437/// `Cow` from `std` would not be suitable here because it would require
438/// declaring the `B` type parameter as an implementer of `ToOwned`,
439/// which is not object-safe.
440#[derive(Debug)]
441enum FciBuilderWrapper<'a> {
442    Borrowed(&'a dyn FciBuilder<'a>),
443    // Note: using `'a` and not `'static` here to help with the implementations
444    // of `deref()` and `as_ref()`. This enum is private and the `Owned` variant
445    // can only be constructed from `PayloadFeedbackBuilder::builder_owned` &
446    // `TransportFeedback::builder_owned` which constrain the `FciBuilder` to be `'static`.
447    Owned(Box<dyn FciBuilder<'a>>),
448}
449
450impl<'a, T: FciBuilder<'a>> From<&'a T> for FciBuilderWrapper<'a> {
451    fn from(value: &'a T) -> Self {
452        FciBuilderWrapper::Borrowed(value)
453    }
454}
455
456impl<'a> std::convert::AsRef<dyn FciBuilder<'a> + 'a> for FciBuilderWrapper<'a> {
457    fn as_ref(&self) -> &(dyn FciBuilder<'a> + 'a) {
458        match self {
459            FciBuilderWrapper::Borrowed(this) => *this,
460            FciBuilderWrapper::Owned(this) => this.borrow(),
461        }
462    }
463}
464
465impl<'a> std::ops::Deref for FciBuilderWrapper<'a> {
466    type Target = dyn FciBuilder<'a> + 'a;
467
468    fn deref(&self) -> &(dyn FciBuilder<'a> + 'a) {
469        match self {
470            FciBuilderWrapper::Borrowed(this) => *this,
471            FciBuilderWrapper::Owned(this) => this.borrow(),
472        }
473    }
474}
475
476/// Trait for writing a particular FCI implementation with a [`TransportFeedbackBuilder`] or
477/// [`PayloadFeedbackBuilder`].
478pub trait FciBuilder<'a>: RtcpPacketWriter {
479    /// The format field value to place in the RTCP header
480    fn format(&self) -> u8;
481    /// The type of feedback packet this FCI data supports being placed in
482    fn supports_feedback_type(&self) -> FciFeedbackPacketType;
483}