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