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