rtcp_types/feedback/
rpsi.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::borrow::Cow;
4
5use crate::feedback::FciFeedbackPacketType;
6use crate::utils::pad_to_4bytes;
7use crate::{prelude::*, RtcpParseError, RtcpWriteError};
8
9/// Reference Picture Selection Indication information
10#[derive(Debug)]
11pub struct Rpsi<'a> {
12    data: &'a [u8],
13}
14
15impl<'a> Rpsi<'a> {
16    /// Create a new [`RpsiBuilder`]
17    pub fn builder() -> RpsiBuilder<'a> {
18        RpsiBuilder::default()
19    }
20
21    /// The payload type this RPSI references
22    pub fn payload_type(&self) -> u8 {
23        self.data[1] & 0x7f
24    }
25
26    /// The codec specific bit string, that this RPSI contains.
27    /// Returns that bit string data and how many bits to remove from the last byte
28    pub fn bit_string(&self) -> (&[u8], usize) {
29        let padding_bytes = self.padding_bytes();
30        let padding_bits = self.data[0] as usize - padding_bytes * 8;
31        (&self.data[2..self.data.len() - padding_bytes], padding_bits)
32    }
33
34    fn padding_bytes(&self) -> usize {
35        (self.data[0] / 8) as usize
36    }
37}
38
39impl<'a> FciParser<'a> for Rpsi<'a> {
40    const PACKET_TYPE: FciFeedbackPacketType = FciFeedbackPacketType::PAYLOAD;
41    const FCI_FORMAT: u8 = 3;
42
43    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
44        if data.len() < 4 {
45            return Err(RtcpParseError::Truncated {
46                expected: 4,
47                actual: data.len(),
48            });
49        }
50        let ret = Self { data };
51        if ret.padding_bytes() > data.len() - 2 {
52            return Err(RtcpParseError::Truncated {
53                expected: ret.padding_bytes() + 2,
54                actual: data.len(),
55            });
56        }
57
58        Ok(ret)
59    }
60}
61
62/// Reference Picture Selection Indication builder
63#[derive(Debug, Default)]
64pub struct RpsiBuilder<'a> {
65    payload_type: u8,
66    native_bit_string: Cow<'a, [u8]>,
67    native_bit_overrun: u8,
68}
69
70impl<'a> RpsiBuilder<'a> {
71    /// Set the payload type that this RPSI should reference
72    pub fn payload_type(mut self, payload_type: u8) -> Self {
73        self.payload_type = payload_type;
74        self
75    }
76
77    /// Set the codec specific bit string for thie RPSI along with how many bits in the last byte
78    /// must be ignored.
79    pub fn native_data(mut self, data: impl Into<Cow<'a, [u8]>>, bit_overrun: u8) -> Self {
80        self.native_bit_string = data.into();
81        self.native_bit_overrun = bit_overrun;
82        self
83    }
84
85    pub fn native_data_owned(
86        self,
87        data: impl Into<Cow<'a, [u8]>>,
88        bit_overrun: u8,
89    ) -> RpsiBuilder<'static> {
90        RpsiBuilder {
91            payload_type: self.payload_type,
92            native_bit_string: data.into().into_owned().into(),
93            native_bit_overrun: bit_overrun,
94        }
95    }
96}
97
98impl<'a> FciBuilder<'a> for RpsiBuilder<'a> {
99    fn format(&self) -> u8 {
100        Rpsi::FCI_FORMAT
101    }
102
103    fn supports_feedback_type(&self) -> FciFeedbackPacketType {
104        FciFeedbackPacketType::PAYLOAD
105    }
106}
107
108impl<'a> RtcpPacketWriter for RpsiBuilder<'a> {
109    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
110        if self.payload_type > 127 {
111            return Err(RtcpWriteError::PayloadTypeInvalid);
112        }
113        if self.native_bit_overrun > 8
114            || self.native_bit_string.is_empty() && self.native_bit_overrun > 0
115        {
116            return Err(RtcpWriteError::PaddingBitsTooLarge);
117        }
118        Ok(pad_to_4bytes(self.native_bit_string.len()))
119    }
120
121    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
122        let end = pad_to_4bytes(2 + self.native_bit_string.len());
123        let trailing_bits =
124            8 * (end - self.native_bit_string.len() - 2) + self.native_bit_overrun as usize;
125        buf[0] = trailing_bits as u8;
126        buf[1] = self.payload_type;
127        let mut idx = 2 + self.native_bit_string.len();
128        buf[2..idx].copy_from_slice(&self.native_bit_string);
129        if !self.native_bit_string.is_empty() {
130            let mut bitmask = 0;
131            let mut trailing_bits = self.native_bit_overrun;
132            while trailing_bits > 0 {
133                bitmask = (bitmask << 1) | 1;
134                trailing_bits -= 1;
135            }
136            buf[idx - 1] &= !bitmask;
137        }
138        while idx < end {
139            buf[idx] = 0;
140            idx += 1;
141        }
142        idx
143    }
144
145    fn get_padding(&self) -> Option<u8> {
146        None
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::feedback::PayloadFeedback;
154
155    #[test]
156    fn rpsi_build_parse() {
157        const REQ_LEN: usize = PayloadFeedback::MIN_PACKET_LEN + 4;
158        let mut data = [0; REQ_LEN];
159        let rpsi = {
160            let data = &[0xf0];
161            let fci = Rpsi::builder()
162                .payload_type(96)
163                .native_data(data.as_ref(), 4);
164            PayloadFeedback::builder_owned(fci)
165                .sender_ssrc(0x98765432)
166                .media_ssrc(0x10fedcba)
167        };
168        assert_eq!(rpsi.calculate_size().unwrap(), REQ_LEN);
169        let len = rpsi.write_into(&mut data).unwrap();
170        assert_eq!(len, REQ_LEN);
171        assert_eq!(
172            data,
173            [
174                0x83, 0xce, 0x00, 0x03, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x0c, 0x60,
175                0xf0, 0x00
176            ]
177        );
178
179        let fb = PayloadFeedback::parse(&data).unwrap();
180
181        assert_eq!(fb.sender_ssrc(), 0x98765432);
182        assert_eq!(fb.media_ssrc(), 0x10fedcba);
183        let rpsi = fb.parse_fci::<Rpsi>().unwrap();
184        assert_eq!(rpsi.payload_type(), 96);
185        assert_eq!(rpsi.bit_string(), ([0xf0].as_ref(), 4));
186    }
187
188    #[test]
189    fn rpsi_build_parse_ref() {
190        const REQ_LEN: usize = PayloadFeedback::MIN_PACKET_LEN + 4;
191        let mut data = [0; REQ_LEN];
192        let data_fci = &[0xf0];
193        let fci = Rpsi::builder()
194            .payload_type(96)
195            .native_data(data_fci.as_ref(), 4);
196        let rpsi = PayloadFeedback::builder(&fci)
197            .sender_ssrc(0x98765432)
198            .media_ssrc(0x10fedcba);
199        assert_eq!(rpsi.calculate_size().unwrap(), REQ_LEN);
200        let len = rpsi.write_into(&mut data).unwrap();
201        assert_eq!(len, REQ_LEN);
202        assert_eq!(
203            data,
204            [
205                0x83, 0xce, 0x00, 0x03, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x0c, 0x60,
206                0xf0, 0x00
207            ]
208        );
209
210        let fb = PayloadFeedback::parse(&data).unwrap();
211
212        assert_eq!(fb.sender_ssrc(), 0x98765432);
213        assert_eq!(fb.media_ssrc(), 0x10fedcba);
214        let rpsi = fb.parse_fci::<Rpsi>().unwrap();
215        assert_eq!(rpsi.payload_type(), 96);
216        assert_eq!(rpsi.bit_string(), ([0xf0].as_ref(), 4));
217    }
218}