rtcp_types/
app.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use crate::{
4    prelude::*,
5    utils::{parser, writer},
6    RtcpPacket, RtcpParseError, RtcpWriteError,
7};
8
9/// A Parsed App packet.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct App<'a> {
12    data: &'a [u8],
13}
14
15impl<'a> RtcpPacket for App<'a> {
16    const MIN_PACKET_LEN: usize = 12;
17    const PACKET_TYPE: u8 = 204;
18}
19
20impl<'a> RtcpPacketParser<'a> for App<'a> {
21    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
22        parser::check_packet::<Self>(data)?;
23
24        Ok(Self { data })
25    }
26
27    #[inline(always)]
28    fn header_data(&self) -> [u8; 4] {
29        self.data[..4].try_into().unwrap()
30    }
31}
32
33impl<'a> App<'a> {
34    const SUBTYPE_MASK: u8 = Self::MAX_COUNT;
35    pub const NAME_LEN: usize = 4;
36
37    /// The (optional) padding used by this [`App`] packet
38    pub fn padding(&self) -> Option<u8> {
39        parser::parse_padding(self.data)
40    }
41
42    /// The SSRC this [`App`] packet refers to
43    pub fn ssrc(&self) -> u32 {
44        parser::parse_ssrc(self.data)
45    }
46
47    /// The `name` for this [`App`] packet.  The `name` should be a sequence of 4 ASCII
48    /// characters.
49    pub fn name(&self) -> [u8; App::NAME_LEN] {
50        self.data[8..8 + Self::NAME_LEN].try_into().unwrap()
51    }
52
53    /// The `name` for this [`App`] as a string.
54    pub fn get_name_string(&self) -> Result<String, std::string::FromUtf8Error> {
55        // name is fixed length potentially zero terminated
56        String::from_utf8(Vec::from_iter(self.name().iter().map_while(|&b| {
57            if b == 0 {
58                None
59            } else {
60                Some(b)
61            }
62        })))
63    }
64
65    /// The application specific data
66    pub fn data(&self) -> &[u8] {
67        &self.data[12..self.data.len() - self.padding().unwrap_or(0) as usize]
68    }
69
70    /// Constructs an [`AppBuilder`].
71    ///
72    /// `name` must be "a sequence of four ASCII characters".
73    pub fn builder(ssrc: u32, name: &'a str) -> AppBuilder<'a> {
74        AppBuilder::new(ssrc, name)
75    }
76}
77
78/// App packet Builder
79#[derive(Debug)]
80#[must_use = "The builder must be built to be used"]
81pub struct AppBuilder<'a> {
82    ssrc: u32,
83    padding: u8,
84    subtype: u8,
85    name: &'a str,
86    data: &'a [u8],
87}
88
89impl<'a> AppBuilder<'a> {
90    fn new(ssrc: u32, name: &'a str) -> Self {
91        AppBuilder {
92            ssrc,
93            padding: 0,
94            subtype: 0,
95            name,
96            data: &[],
97        }
98    }
99
100    /// Sets the number of padding bytes to use for this App.
101    pub fn padding(mut self, padding: u8) -> Self {
102        self.padding = padding;
103        self
104    }
105
106    /// The subtype to use for this [`App`] packet
107    pub fn subtype(mut self, subtype: u8) -> Self {
108        self.subtype = subtype;
109        self
110    }
111
112    /// The data to use for this [`App`] packet
113    pub fn data(mut self, data: &'a [u8]) -> Self {
114        self.data = data;
115        self
116    }
117}
118
119impl<'a> RtcpPacketWriter for AppBuilder<'a> {
120    /// Calculates the size required to write this App packet.
121    ///
122    /// Returns an error if:
123    ///
124    /// * The subtype is out of range.
125    /// * The name is not a sequence of four ASCII characters.
126    /// * The data length is not a multiple of 4.
127    /// * The padding is not a multiple of 4.
128    #[inline]
129    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
130        if self.subtype > App::SUBTYPE_MASK {
131            return Err(RtcpWriteError::AppSubtypeOutOfRange {
132                subtype: self.subtype,
133                max: App::SUBTYPE_MASK,
134            });
135        }
136
137        // Note: RFC 3550 p. 44 is ambiguous whether the name MUST consists in
138        // 4 non-zero ASCII character or if it could be shorter with the rest
139        // of the 4 bytes filled with 0. We decided to allow filling with 0
140        // for flexility reasons.
141        if self.name.len() > App::NAME_LEN || !self.name.is_ascii() {
142            return Err(RtcpWriteError::InvalidName);
143        }
144
145        let mut size = App::MIN_PACKET_LEN + self.padding as usize;
146
147        if self.data.len() % 4 != 0 {
148            return Err(RtcpWriteError::DataLen32bitMultiple(self.data.len()));
149        }
150
151        size += self.data.len();
152
153        writer::check_padding(self.padding)?;
154
155        Ok(size)
156    }
157
158    /// Writes this App packet specific data into `buf` without any validity checks.
159    ///
160    /// Returns the number of bytes written.
161    ///
162    /// # Panic
163    ///
164    /// Panics if the buf is not large enough.
165    #[inline]
166    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
167        writer::write_header_unchecked::<App>(self.padding, self.subtype, buf);
168
169        buf[4..8].copy_from_slice(&self.ssrc.to_be_bytes());
170
171        let name = self.name.as_bytes();
172        let name_len = name.len();
173        let mut end = 8 + name_len;
174        buf[8..end].copy_from_slice(name);
175        // See note in calculate_size()
176        if end < 12 {
177            buf[end..12].fill(0);
178        }
179
180        end = 12 + self.data.len();
181        buf[12..end].copy_from_slice(self.data);
182
183        end += writer::write_padding_unchecked(self.padding, &mut buf[end..]);
184
185        end
186    }
187
188    fn get_padding(&self) -> Option<u8> {
189        if self.padding == 0 {
190            return None;
191        }
192
193        Some(self.padding)
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn parse_empty_app() {
203        let data = [
204            0x80, 0xcc, 0x00, 0x02, 0x91, 0x82, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00,
205        ];
206        let app = App::parse(&data).unwrap();
207        assert_eq!(app.version(), 2);
208        assert_eq!(app.padding(), None);
209        assert_eq!(app.subtype(), 0);
210        assert_eq!(app.name(), [0, 0, 0, 0]);
211        assert!(app.get_name_string().unwrap().is_empty());
212        assert!(app.data().is_empty());
213    }
214
215    #[test]
216    fn build_empty_app() {
217        const REQ_LEN: usize = App::MIN_PACKET_LEN;
218        let appb = App::builder(0x91827364, "name");
219        let req_len = appb.calculate_size().unwrap();
220        assert_eq!(req_len, REQ_LEN);
221
222        let mut data = [0; REQ_LEN];
223        let len = appb.write_into(&mut data).unwrap();
224
225        assert_eq!(len, REQ_LEN);
226        assert_eq!(
227            data,
228            [0x80, 0xcc, 0x00, 0x02, 0x91, 0x82, 0x73, 0x64, 0x6e, 0x61, 0x6d, 0x65,]
229        );
230    }
231
232    #[test]
233    fn parse_app() {
234        let data = [
235            0xbf, 0xcc, 0x00, 0x04, 0x91, 0x82, 0x73, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x02,
236            0x03, 0x00, 0x00, 0x00, 0x00, 0x04,
237        ];
238        let app = App::parse(&data).unwrap();
239        assert_eq!(app.version(), 2);
240        assert_eq!(app.padding(), Some(4));
241        assert_eq!(app.subtype(), 31);
242        assert_eq!(app.name(), "name".as_bytes());
243        assert_eq!(app.get_name_string().unwrap(), "name");
244        assert_eq!(app.data(), [0x01, 0x02, 0x3, 0x0]);
245    }
246
247    #[test]
248    fn build_app() {
249        const REQ_LEN: usize = App::MIN_PACKET_LEN + 4 + 4;
250        let appb = App::builder(0x91827364, "name")
251            .padding(4)
252            .subtype(31)
253            .data(&[0x01, 0x02, 0x3, 0x0]);
254        let req_len = appb.calculate_size().unwrap();
255        assert_eq!(req_len, REQ_LEN);
256
257        let mut data = [0; REQ_LEN];
258        let len = appb.write_into(&mut data).unwrap();
259        assert_eq!(len, REQ_LEN);
260        assert_eq!(
261            data,
262            [
263                0xbf, 0xcc, 0x00, 0x04, 0x91, 0x82, 0x73, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x02,
264                0x03, 0x00, 0x00, 0x00, 0x00, 0x04,
265            ]
266        );
267    }
268
269    #[test]
270    fn build_short_name() {
271        const REQ_LEN: usize = App::MIN_PACKET_LEN;
272        let appb = App::builder(0x91827364, "nam").subtype(31);
273        let req_len = appb.calculate_size().unwrap();
274        assert_eq!(req_len, REQ_LEN);
275
276        let mut data = [0; REQ_LEN];
277        let len = appb.write_into(&mut data).unwrap();
278        assert_eq!(len, REQ_LEN);
279        assert_eq!(
280            data,
281            [0x9f, 0xcc, 0x00, 0x02, 0x91, 0x82, 0x73, 0x64, 0x6e, 0x61, 0x6d, 0x00]
282        );
283    }
284
285    #[test]
286    fn build_subtype_out_of_range() {
287        let b = App::builder(0x91827364, "name").subtype(0x1f + 1);
288        let err = b.calculate_size().unwrap_err();
289        assert_eq!(
290            err,
291            RtcpWriteError::AppSubtypeOutOfRange {
292                subtype: 0x1f + 1,
293                max: App::SUBTYPE_MASK
294            }
295        );
296    }
297
298    #[test]
299    fn build_invalid_name_too_large() {
300        let b = App::builder(0x91827364, "name_");
301        let err = b.calculate_size().unwrap_err();
302        assert_eq!(err, RtcpWriteError::InvalidName);
303    }
304
305    #[test]
306    fn build_invalid_non_ascii_name() {
307        let b = App::builder(0x91827364, "nąm");
308        let err = b.calculate_size().unwrap_err();
309        assert_eq!(err, RtcpWriteError::InvalidName);
310    }
311
312    #[test]
313    fn build_data_len_not_32bits_multiple() {
314        let b = App::builder(0x91827364, "name").data(&[0x01, 0x02, 0x3]);
315        let err = b.calculate_size().unwrap_err();
316        assert_eq!(err, RtcpWriteError::DataLen32bitMultiple(3));
317    }
318
319    #[test]
320    fn build_padding_not_multiple_4() {
321        let b = App::builder(0x91827364, "name").padding(5);
322        let err = b.calculate_size().unwrap_err();
323        assert_eq!(err, RtcpWriteError::InvalidPadding { padding: 5 });
324    }
325}