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