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    /// Set the codec specific bit string for thie RPSI along with how many bits in the last byte
86    /// must be ignored.
87    ///
88    /// This is the owned variant that can leave the calling scope.
89    pub fn native_data_owned(
90        self,
91        data: impl Into<Cow<'a, [u8]>>,
92        bit_overrun: u8,
93    ) -> RpsiBuilder<'static> {
94        RpsiBuilder {
95            payload_type: self.payload_type,
96            native_bit_string: data.into().into_owned().into(),
97            native_bit_overrun: bit_overrun,
98        }
99    }
100}
101
102impl<'a> FciBuilder<'a> for RpsiBuilder<'a> {
103    fn format(&self) -> u8 {
104        Rpsi::FCI_FORMAT
105    }
106
107    fn supports_feedback_type(&self) -> FciFeedbackPacketType {
108        FciFeedbackPacketType::PAYLOAD
109    }
110}
111
112impl RtcpPacketWriter for RpsiBuilder<'_> {
113    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
114        if self.payload_type > 127 {
115            return Err(RtcpWriteError::PayloadTypeInvalid);
116        }
117        if self.native_bit_overrun > 8
118            || self.native_bit_string.is_empty() && self.native_bit_overrun > 0
119        {
120            return Err(RtcpWriteError::PaddingBitsTooLarge);
121        }
122        Ok(pad_to_4bytes(self.native_bit_string.len()))
123    }
124
125    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
126        let end = pad_to_4bytes(2 + self.native_bit_string.len());
127        let trailing_bits =
128            8 * (end - self.native_bit_string.len() - 2) + self.native_bit_overrun as usize;
129        buf[0] = trailing_bits as u8;
130        buf[1] = self.payload_type;
131        let mut idx = 2 + self.native_bit_string.len();
132        buf[2..idx].copy_from_slice(&self.native_bit_string);
133        if !self.native_bit_string.is_empty() {
134            let mut bitmask = 0;
135            let mut trailing_bits = self.native_bit_overrun;
136            while trailing_bits > 0 {
137                bitmask = (bitmask << 1) | 1;
138                trailing_bits -= 1;
139            }
140            buf[idx - 1] &= !bitmask;
141        }
142        while idx < end {
143            buf[idx] = 0;
144            idx += 1;
145        }
146        idx
147    }
148
149    fn get_padding(&self) -> Option<u8> {
150        None
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::feedback::PayloadFeedback;
158
159    #[test]
160    fn rpsi_build_parse() {
161        const REQ_LEN: usize = PayloadFeedback::MIN_PACKET_LEN + 4;
162        let mut data = [0; REQ_LEN];
163        let rpsi = {
164            let data = &[0xf0];
165            let fci = Rpsi::builder()
166                .payload_type(96)
167                .native_data(data.as_ref(), 4);
168            PayloadFeedback::builder_owned(fci)
169                .sender_ssrc(0x98765432)
170                .media_ssrc(0x10fedcba)
171        };
172        assert_eq!(rpsi.calculate_size().unwrap(), REQ_LEN);
173        let len = rpsi.write_into(&mut data).unwrap();
174        assert_eq!(len, REQ_LEN);
175        assert_eq!(
176            data,
177            [
178                0x83, 0xce, 0x00, 0x03, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x0c, 0x60,
179                0xf0, 0x00
180            ]
181        );
182
183        let fb = PayloadFeedback::parse(&data).unwrap();
184
185        assert_eq!(fb.sender_ssrc(), 0x98765432);
186        assert_eq!(fb.media_ssrc(), 0x10fedcba);
187        let rpsi = fb.parse_fci::<Rpsi>().unwrap();
188        assert_eq!(rpsi.payload_type(), 96);
189        assert_eq!(rpsi.bit_string(), ([0xf0].as_ref(), 4));
190    }
191
192    #[test]
193    fn rpsi_build_parse_ref() {
194        const REQ_LEN: usize = PayloadFeedback::MIN_PACKET_LEN + 4;
195        let mut data = [0; REQ_LEN];
196        let data_fci = &[0xf0];
197        let fci = Rpsi::builder()
198            .payload_type(96)
199            .native_data(data_fci.as_ref(), 4);
200        let rpsi = PayloadFeedback::builder(&fci)
201            .sender_ssrc(0x98765432)
202            .media_ssrc(0x10fedcba);
203        assert_eq!(rpsi.calculate_size().unwrap(), REQ_LEN);
204        let len = rpsi.write_into(&mut data).unwrap();
205        assert_eq!(len, REQ_LEN);
206        assert_eq!(
207            data,
208            [
209                0x83, 0xce, 0x00, 0x03, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x0c, 0x60,
210                0xf0, 0x00
211            ]
212        );
213
214        let fb = PayloadFeedback::parse(&data).unwrap();
215
216        assert_eq!(fb.sender_ssrc(), 0x98765432);
217        assert_eq!(fb.media_ssrc(), 0x10fedcba);
218        let rpsi = fb.parse_fci::<Rpsi>().unwrap();
219        assert_eq!(rpsi.payload_type(), 96);
220        assert_eq!(rpsi.bit_string(), ([0xf0].as_ref(), 4));
221    }
222}