rtcp_types/
bye.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::borrow::Cow;
4
5use crate::{
6    prelude::*,
7    utils::{pad_to_4bytes, parser, u32_from_be_bytes, writer},
8    RtcpPacket, RtcpPacketParser, RtcpPacketWriter, RtcpParseError, RtcpWriteError,
9};
10
11/// A Parsed Bye packet.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct Bye<'a> {
14    data: &'a [u8],
15}
16
17impl RtcpPacket for Bye<'_> {
18    const MIN_PACKET_LEN: usize = 4;
19    const PACKET_TYPE: u8 = 203;
20}
21
22impl<'a> RtcpPacketParser<'a> for Bye<'a> {
23    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
24        parser::check_packet::<Self>(data)?;
25
26        let reason_len_offset = Self::MIN_PACKET_LEN + 4 * parser::parse_count(data) as usize;
27        if reason_len_offset > data.len() {
28            return Err(RtcpParseError::Truncated {
29                expected: reason_len_offset,
30                actual: data.len(),
31            });
32        }
33
34        if reason_len_offset < data.len() {
35            let reason_len = data[reason_len_offset] as usize;
36            if reason_len_offset + 1 + reason_len > data.len() {
37                return Err(RtcpParseError::Truncated {
38                    expected: reason_len_offset + 1 + reason_len,
39                    actual: data.len(),
40                });
41            }
42        } // else no reason in this packet
43
44        Ok(Self { data })
45    }
46
47    #[inline(always)]
48    fn header_data(&self) -> [u8; 4] {
49        self.data[..4].try_into().unwrap()
50    }
51}
52
53impl<'a> Bye<'a> {
54    const MAX_SOURCES: u8 = Self::MAX_COUNT;
55    const MAX_REASON_LEN: u8 = 0xff;
56
57    /// The (optional) padding used by this [`Bye`] packet
58    pub fn padding(&self) -> Option<u8> {
59        parser::parse_padding(self.data)
60    }
61
62    /// The list of ssrcs that this [`Bye`] packet refers to
63    pub fn ssrcs(&self) -> impl Iterator<Item = u32> + '_ {
64        self.data[4..4 + self.count() as usize * 4]
65            .chunks_exact(4)
66            .map(u32_from_be_bytes)
67    }
68
69    /// The (optional) reason for the [`Bye`] as raw data.  The reason should be an ASCII string.
70    pub fn reason(&self) -> Option<&[u8]> {
71        let offset = self.count() as usize * 4 + 4;
72        let reason_aligned_len = self
73            .length()
74            .checked_sub(offset + 1 + self.padding().unwrap_or(0) as usize)?;
75
76        if reason_aligned_len == 0 {
77            return None;
78        }
79
80        let end = offset + 1 + self.data[offset] as usize;
81        Some(&self.data[offset + 1..end])
82    }
83
84    /// The (optional) reason for the [`Bye`] as a string
85    pub fn get_reason_string(&self) -> Option<Result<String, std::string::FromUtf8Error>> {
86        self.reason().map(|r| String::from_utf8(r.into()))
87    }
88
89    /// Create a new [`ByeBuilder`]
90    pub fn builder() -> ByeBuilder<'a> {
91        ByeBuilder::new()
92    }
93}
94
95/// Bye packet Builder
96#[derive(Debug)]
97#[must_use = "The builder must be built to be used"]
98pub struct ByeBuilder<'a> {
99    padding: u8,
100    sources: Vec<u32>,
101    reason: Cow<'a, str>,
102}
103
104impl<'a> ByeBuilder<'a> {
105    fn new() -> Self {
106        ByeBuilder {
107            padding: 0,
108            sources: Vec::with_capacity(Bye::MAX_SOURCES as usize),
109            reason: "".into(),
110        }
111    }
112
113    /// Sets the number of padding bytes to use for this Bye packet.
114    pub fn padding(mut self, padding: u8) -> Self {
115        self.padding = padding;
116        self
117    }
118
119    /// Attempts to add the provided Source.
120    pub fn add_source(mut self, source: u32) -> Self {
121        self.sources.push(source);
122        self
123    }
124
125    /// Sets the reason for this Bye packet.
126    pub fn reason(mut self, reason: impl Into<Cow<'a, str>>) -> Self {
127        self.reason = reason.into();
128        self
129    }
130
131    /// Sets the (owned) reason for this Bye packet.
132    pub fn reason_owned(self, reason: impl Into<Cow<'a, str>>) -> ByeBuilder<'static> {
133        ByeBuilder {
134            padding: self.padding,
135            sources: self.sources,
136            reason: reason.into().into_owned().into(),
137        }
138    }
139}
140
141impl RtcpPacketWriter for ByeBuilder<'_> {
142    /// Calculates the size required to write this Bye packet.
143    ///
144    /// Returns an error if:
145    ///
146    /// * Too many sources where added.
147    /// * The length of the reason is out of range.
148    /// * The padding is not a multiple of 4.
149    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
150        if self.sources.len() > Bye::MAX_SOURCES as usize {
151            return Err(RtcpWriteError::TooManySources {
152                count: self.sources.len(),
153                max: Bye::MAX_SOURCES,
154            });
155        }
156
157        writer::check_padding(self.padding)?;
158
159        let mut size = Bye::MIN_PACKET_LEN + 4 * self.sources.len() + self.padding as usize;
160
161        if !self.reason.is_empty() {
162            let reason_len = self.reason.len();
163            if reason_len > Bye::MAX_REASON_LEN as usize {
164                return Err(RtcpWriteError::ReasonLenTooLarge {
165                    len: reason_len,
166                    max: Bye::MAX_REASON_LEN,
167                });
168            }
169
170            // reason length + data
171            size += 1 + reason_len;
172            // 32bit packet alignment
173            size = pad_to_4bytes(size);
174        }
175
176        Ok(size)
177    }
178
179    /// Write this Bye packet data into `buf` without any validity checks.
180    ///
181    /// Returns the number of bytes written.
182    ///
183    /// # Panic
184    ///
185    /// Panics if the buf is not large enough.
186    #[inline]
187    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
188        let mut idx =
189            writer::write_header_unchecked::<Bye>(self.padding, self.sources.len() as u8, buf);
190
191        let mut end = idx;
192        for ssrc in self.sources.iter() {
193            end += 4;
194            buf[idx..end].copy_from_slice(&ssrc.to_be_bytes());
195            idx = end;
196        }
197
198        if !self.reason.is_empty() {
199            let reason = self.reason.as_bytes();
200            let reason_len = reason.len();
201
202            buf[idx] = reason_len as u8;
203            idx += 1;
204            end = idx + reason_len;
205            buf[idx..end].copy_from_slice(reason);
206            idx = end;
207            // 32bit packet alignmant
208            end = pad_to_4bytes(end);
209            if end > idx {
210                buf[idx..end].fill(0);
211            }
212        }
213
214        end += writer::write_padding_unchecked(self.padding, &mut buf[idx..]);
215
216        end
217    }
218
219    fn get_padding(&self) -> Option<u8> {
220        if self.padding == 0 {
221            return None;
222        }
223
224        Some(self.padding)
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn parse_bye_empty() {
234        let bye = Bye::parse(&[0x80, 0xcb, 0x00, 0x00]).unwrap();
235        assert_eq!(bye.padding(), None);
236        assert_eq!(bye.count(), 0);
237        assert_eq!(bye.ssrcs().count(), 0);
238        assert!(bye.reason().is_none());
239        assert!(bye.get_reason_string().is_none());
240    }
241
242    #[test]
243    fn build_bye_empty() {
244        const REQ_LEN: usize = Bye::MIN_PACKET_LEN;
245        let byeb = Bye::builder();
246        let req_len = byeb.calculate_size().unwrap();
247        assert_eq!(req_len, REQ_LEN);
248
249        let mut data = [0; REQ_LEN];
250        let len = byeb.write_into(&mut data).unwrap();
251        assert_eq!(len, REQ_LEN);
252        assert_eq!(data, [0x80, 0xcb, 0x00, 0x00]);
253    }
254
255    #[test]
256    fn build_bye_static() {
257        const REQ_LEN: usize = Bye::MIN_PACKET_LEN + 4 + 4;
258
259        let byeb = {
260            let reason = &String::from("Bye");
261            Bye::builder().reason_owned(reason).add_source(0x12345678)
262        };
263        let req_len = byeb.calculate_size().unwrap();
264        assert_eq!(req_len, REQ_LEN);
265
266        let mut data = [0; REQ_LEN];
267        let len = byeb.write_into(&mut data).unwrap();
268        assert_eq!(len, REQ_LEN);
269        assert_eq!(
270            data,
271            [0x81, 0xcb, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x03, 0x42, 0x79, 0x65]
272        );
273    }
274
275    #[test]
276    fn parse_bye_3_sources() {
277        let bye = Bye::parse(&[
278            0x83, 0xcb, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x56, 0x78,
279            0x9a, 0xbc,
280        ])
281        .unwrap();
282        assert_eq!(bye.padding(), None);
283        assert_eq!(bye.count(), 3);
284
285        let mut ssrc_iter = bye.ssrcs();
286        assert_eq!(ssrc_iter.next(), Some(0x12345678));
287        assert_eq!(ssrc_iter.next(), Some(0x3456789a));
288        assert_eq!(ssrc_iter.next(), Some(0x56789abc));
289        assert!(ssrc_iter.next().is_none());
290
291        assert!(bye.reason().is_none());
292        assert!(bye.get_reason_string().is_none());
293    }
294
295    #[test]
296    fn build_bye_3_sources() {
297        const REQ_LEN: usize = Bye::MIN_PACKET_LEN + 3 * 4;
298        let byeb = Bye::builder()
299            .add_source(0x12345678)
300            .add_source(0x3456789a)
301            .add_source(0x56789abc);
302        let req_len = byeb.calculate_size().unwrap();
303        assert_eq!(req_len, REQ_LEN);
304
305        let mut data = [0; REQ_LEN];
306        let len = byeb.write_into(&mut data).unwrap();
307        assert_eq!(len, REQ_LEN);
308        assert_eq!(
309            data,
310            [
311                0x83, 0xcb, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x56, 0x78,
312                0x9a, 0xbc,
313            ]
314        );
315    }
316
317    #[test]
318    fn parse_bye_2_sources_reason() {
319        let bye = Bye::parse(&[
320            0x82, 0xcb, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x08, 0x53,
321            0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x00, 0x00, 0x00,
322        ])
323        .unwrap();
324        assert_eq!(bye.padding(), None);
325        assert_eq!(bye.count(), 2);
326
327        let mut ssrc_iter = bye.ssrcs();
328        assert_eq!(ssrc_iter.next(), Some(0x12345678));
329        assert_eq!(ssrc_iter.next(), Some(0x3456789a));
330        assert!(ssrc_iter.next().is_none());
331
332        assert_eq!(String::from_utf8_lossy(bye.reason().unwrap()), "Shutdown");
333    }
334
335    #[test]
336    fn build_bye_2_sources_reason() {
337        const REASON: &str = "Shutdown";
338        const LEN: usize = Bye::MIN_PACKET_LEN + 2 * 4 + 1 + REASON.len();
339        // 32bit packet alignment
340        const REQ_LEN: usize = pad_to_4bytes(LEN);
341        let byeb = Bye::builder()
342            .add_source(0x12345678)
343            .add_source(0x3456789a)
344            .reason(REASON);
345        let req_len = byeb.calculate_size().unwrap();
346        assert_eq!(req_len, REQ_LEN);
347
348        let mut data = [0; REQ_LEN];
349        let len = byeb.write_into(&mut data).unwrap();
350        assert_eq!(len, REQ_LEN);
351        assert_eq!(
352            data,
353            [
354                0x82, 0xcb, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x08, 0x53,
355                0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x00, 0x00, 0x00
356            ]
357        );
358    }
359
360    #[test]
361    fn build_bye_2_sources_raw_reason() {
362        const REASON: &str = "Shutdown";
363        const LEN: usize = Bye::MIN_PACKET_LEN + 2 * 4 + 1 + REASON.len();
364        // 32bit packet alignment
365        const REQ_LEN: usize = pad_to_4bytes(LEN);
366        let byeb = Bye::builder()
367            .add_source(0x12345678)
368            .add_source(0x3456789a)
369            .reason(REASON);
370        let req_len = byeb.calculate_size().unwrap();
371        assert_eq!(req_len, REQ_LEN);
372
373        let mut data = [0; REQ_LEN];
374        let len = byeb.write_into(&mut data).unwrap();
375        assert_eq!(len, REQ_LEN);
376        assert_eq!(
377            data,
378            [
379                0x82, 0xcb, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x08, 0x53,
380                0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x00, 0x00, 0x00
381            ]
382        );
383    }
384
385    #[test]
386    fn build_too_many_sources() {
387        let mut b = Bye::builder();
388        for _ in 0..Bye::MAX_SOURCES as usize + 1 {
389            b = b.add_source(0)
390        }
391        let err = b.calculate_size().unwrap_err();
392        assert_eq!(
393            err,
394            RtcpWriteError::TooManySources {
395                count: Bye::MAX_SOURCES as usize + 1,
396                max: Bye::MAX_SOURCES
397            }
398        );
399    }
400
401    #[test]
402    fn build_reason_too_large() {
403        let reason: String =
404            String::from_utf8([b'a'; Bye::MAX_REASON_LEN as usize + 1].into()).unwrap();
405        let b = Bye::builder().reason(&reason);
406        let err = b.calculate_size().unwrap_err();
407        assert_eq!(
408            err,
409            RtcpWriteError::ReasonLenTooLarge {
410                len: Bye::MAX_REASON_LEN as usize + 1,
411                max: Bye::MAX_REASON_LEN
412            }
413        );
414    }
415
416    #[test]
417    fn build_padding_not_multiple_4() {
418        let b = Bye::builder().padding(5);
419        let err = b.calculate_size().unwrap_err();
420        assert_eq!(err, RtcpWriteError::InvalidPadding { padding: 5 });
421    }
422}