rtp_parse/rtcp/
rtcp_bye.rs

1use std::str::from_utf8;
2
3use anyhow::{Context, Result};
4use bit_cursor::{
5    bit_read::BitRead, bit_read_exts::BitReadExts, bit_write::BitWrite,
6    bit_write_exts::BitWriteExts, byte_order::NetworkOrder,
7};
8
9use super::rtcp_header::{write_rtcp_header, RtcpHeader};
10
11/// https://datatracker.ietf.org/doc/html/rfc3550#section-6.6
12///        0                   1                   2                   3
13///        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
14///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15///       |V=2|P|    SC   |   PT=BYE=203  |             length            |
16///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17///       |                           SSRC/CSRC                           |
18///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19///       :                              ...                              :
20///       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
21/// (opt) |     length    |               reason for leaving            ...
22///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23#[derive(Debug)]
24pub struct RtcpByePacket {
25    pub header: RtcpHeader,
26    pub ssrcs: Vec<u32>,
27    pub reason: Option<String>,
28}
29
30impl RtcpByePacket {
31    pub const PT: u8 = 203;
32}
33
34pub fn read_rtcp_bye<R: BitRead>(buf: &mut R, header: RtcpHeader) -> Result<RtcpByePacket> {
35    let ssrcs = (0u32..header.report_count.into())
36        .map(|i| {
37            buf.read_u32::<NetworkOrder>()
38                .with_context(|| format!("ssrc-{i}"))
39        })
40        .collect::<Result<Vec<u32>>>()?;
41
42    // Try to read a BYE reason length
43    let reason = {
44        if let Ok(reason_length) = buf.read_u8() {
45            let mut reason_bytes = vec![0; reason_length.into()];
46            std::io::Read::read(buf, &mut reason_bytes).context("bye reason bytes")?;
47            Some(
48                from_utf8(&reason_bytes)
49                    .context("convert bye reason from urf8")
50                    .map(|str| str.to_owned())?,
51            )
52        } else {
53            // Reason is optional, so if there was no data there just mark it as None.
54            // TODO: technically I think we should only do this in the 'failed to fill buffer'
55            // error case? Otherwise it could be a real error of some kind.
56            None
57        }
58    };
59    Ok(RtcpByePacket {
60        header,
61        ssrcs,
62        reason,
63    })
64}
65
66pub fn write_rtcp_bye<W: BitWrite>(buf: &mut W, packet: &RtcpByePacket) -> Result<()> {
67    write_rtcp_header(buf, &packet.header).context("header")?;
68    packet
69        .ssrcs
70        .iter()
71        .enumerate()
72        .map(|(i, ssrc)| {
73            buf.write_u32::<NetworkOrder>(*ssrc)
74                .with_context(|| format!("ssrc-{i}"))
75        })
76        .collect::<Result<Vec<()>>>()
77        .context("ssrcs")?;
78
79    if let Some(reason) = &packet.reason {
80        let utf8_bytes = reason.as_bytes();
81        buf.write_u8(utf8_bytes.len() as u8)
82            .context("reason length")?;
83        std::io::Write::write(buf, utf8_bytes).context("reason string")?;
84    }
85
86    Ok(())
87}
88
89#[cfg(test)]
90mod tests {
91    use bit_cursor::{bit_cursor::BitCursor, nsw_types::*};
92    use bitvec::{order::Msb0, vec::BitVec};
93
94    use super::*;
95
96    #[test]
97    fn test_parse_success() {
98        let rtcp_header = RtcpHeader {
99            version: u2::new(2),
100            has_padding: false,
101            report_count: u5::new(2),
102            packet_type: 203,
103            length_field: 2,
104        };
105        let reason_str = "goodbye";
106        let reason_bytes = reason_str.bytes();
107        #[rustfmt::skip]
108        let mut payload = vec![
109            // ssrc 1
110            0x00, 0x00, 0x00, 0x01, 
111            // ssrc 2
112            0x00, 0x00, 0x00, 0x02,
113            // reason length
114            reason_bytes.len() as u8
115        ];
116        // add reason bytes
117        payload.extend(reason_bytes.collect::<Vec<u8>>());
118        let mut buf = BitCursor::new(BitVec::<_, Msb0>::from_vec(payload));
119        let rtcp_bye = read_rtcp_bye(&mut buf, rtcp_header).unwrap();
120        assert!(rtcp_bye.ssrcs.contains(&1u32));
121        assert!(rtcp_bye.ssrcs.contains(&2u32));
122        assert_eq!(rtcp_bye.reason.unwrap(), reason_str);
123    }
124
125    #[test]
126    fn test_parse_success_no_reason() {
127        let rtcp_header = RtcpHeader {
128            version: u2::new(2),
129            has_padding: false,
130            report_count: u5::new(2),
131            packet_type: 203,
132            length_field: 2,
133        };
134        let payload = vec![
135            // ssrc 1
136            0x00, 0x00, 0x00, 0x01, // ssrc 2
137            0x00, 0x00, 0x00, 0x02,
138        ];
139        let mut buf = BitCursor::new(BitVec::<u8, Msb0>::from_vec(payload));
140        let rtcp_bye = read_rtcp_bye(&mut buf, rtcp_header).unwrap();
141        assert!(rtcp_bye.ssrcs.contains(&1u32));
142        assert!(rtcp_bye.ssrcs.contains(&2u32));
143        assert!(rtcp_bye.reason.is_none());
144    }
145
146    #[test]
147    fn test_missing_ssrc() {
148        let rtcp_header = RtcpHeader {
149            version: u2::new(2),
150            has_padding: false,
151            report_count: u5::new(2),
152            packet_type: 203,
153            length_field: 2,
154        };
155
156        // Report count (source count) is 2 in header, but we'll just have 1 SSRC in the payload
157        let mut buf = BitCursor::new(BitVec::<u8, Msb0>::from_vec(vec![1, 2, 3, 4]));
158        let result = read_rtcp_bye(&mut buf, rtcp_header);
159        assert!(result.is_err());
160    }
161
162    #[test]
163    fn test_bad_utf8_reason() {
164        let rtcp_header = RtcpHeader {
165            version: u2::new(2),
166            has_padding: false,
167            report_count: u5::new(2),
168            packet_type: 203,
169            length_field: 2,
170        };
171        #[rustfmt::skip]
172        let payload = vec![
173            // ssrc 1
174            0x00, 0x00, 0x00, 0x01,
175            // ssrc 2
176            0x00, 0x00, 0x00, 0x02,
177            // length 2, invalid utf 8
178            0x02, 0xFF, 0xFF
179        ];
180        let mut buf = BitCursor::new(BitVec::<u8, Msb0>::from_vec(payload));
181        let result = read_rtcp_bye(&mut buf, rtcp_header);
182        assert!(result.is_err());
183    }
184}