rtp_parse/rtcp/
rtcp_sdes.rs

1use std::str::from_utf8;
2
3use anyhow::{Context, Result};
4use bit_cursor::{
5    bit_read_exts::BitReadExts, bit_write_exts::BitWriteExts, byte_order::NetworkOrder,
6};
7
8use crate::{util::consume_padding, PacketBuffer, PacketBufferMut};
9
10use super::rtcp_header::{write_rtcp_header, RtcpHeader};
11
12/// https://datatracker.ietf.org/doc/html/rfc3550#section-6.5
13///         0                   1                   2                   3
14///         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
15///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16/// header |V=2|P|    SC   |  PT=SDES=202  |             length            |
17///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
18/// chunk  |                          SSRC/CSRC_1                          |
19///   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20///        |                           SDES items                          |
21///        |                              ...                              |
22///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
23/// chunk  |                          SSRC/CSRC_2                          |
24///   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25///        |                           SDES items                          |
26///        |                              ...                              |
27///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
28///   Items are contiguous, i.e., items are not individually padded to a
29///     32-bit boundary.  Text is not null terminated because some multi-
30///     octet encodings include null octets.  The list of items in each chunk
31///     MUST be terminated by one or more null octets, the first of which is
32///     interpreted as an item type of zero to denote the end of the list.
33///     No length octet follows the null item type octet, but additional null
34///     octets MUST be included if needed to pad until the next 32-bit
35///     boundary.  Note that this padding is separate from that indicated by
36///     the P bit in the RTCP header.  A chunk with zero items (four null
37///     octets) is valid but useless.
38#[derive(Debug)]
39pub struct RtcpSdesPacket {
40    pub header: RtcpHeader,
41    pub chunks: Vec<SdesChunk>,
42}
43
44impl RtcpSdesPacket {
45    pub const PT: u8 = 202;
46}
47
48pub fn read_rtcp_sdes<B: PacketBuffer>(buf: &mut B, header: RtcpHeader) -> Result<RtcpSdesPacket> {
49    let num_chunks = header.report_count;
50    let chunks = (0u8..num_chunks.into())
51        .map(|i| read_sdes_chunk(buf).with_context(|| format!("chunk {i}")))
52        .collect::<Result<Vec<SdesChunk>>>()
53        .context("sdes chunks")?;
54
55    Ok(RtcpSdesPacket { header, chunks })
56}
57
58pub fn write_rtcp_sdes<B: PacketBufferMut>(buf: &mut B, rtcp_sdes: &RtcpSdesPacket) -> Result<()> {
59    write_rtcp_header(buf, &rtcp_sdes.header).context("header")?;
60    rtcp_sdes
61        .chunks
62        .iter()
63        .enumerate()
64        .map(|(i, chunk)| write_sdes_chunk(buf, chunk).with_context(|| format!("chunk {i}")))
65        .collect::<Result<Vec<()>>>()
66        .context("chunks")?;
67
68    Ok(())
69}
70
71/// 0                   1                   2                   3
72/// 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
73/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74/// |      ID       |     length    | value                       ...
75/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76#[derive(Debug)]
77pub enum SdesItem {
78    Empty,
79    Cname(String),
80    Unknown { item_type: u8, data: Vec<u8> },
81}
82
83pub fn read_sdes_item<R: PacketBuffer>(buf: &mut R) -> Result<SdesItem> {
84    let id = buf.read_u8().context("id")?;
85    if id == 0 {
86        return Ok(SdesItem::Empty);
87    }
88    let length = buf.read_u8().context("length")? as usize;
89    let mut value_bytes = vec![0u8; length];
90    buf.read_exact(&mut value_bytes).context("value")?;
91    match id {
92        1 => Ok(SdesItem::Cname(from_utf8(&value_bytes)?.to_owned())),
93        t => Ok(SdesItem::Unknown {
94            item_type: t,
95            data: value_bytes.to_vec(),
96        }),
97    }
98}
99
100pub fn write_sdes_item<W: PacketBufferMut>(buf: &mut W, sdes_item: &SdesItem) -> Result<()> {
101    match sdes_item {
102        SdesItem::Empty => {
103            buf.write_u8(0).context("id")?;
104        }
105        SdesItem::Cname(value) => {
106            buf.write_u8(1).context("id")?;
107            let bytes = value.as_bytes();
108            buf.write_u8(bytes.len() as u8).context("length")?;
109            buf.write(bytes).context("value")?;
110        }
111        SdesItem::Unknown { item_type, data } => {
112            buf.write_u8(*item_type).context("id")?;
113            buf.write(data).context("value")?;
114        }
115    }
116
117    Ok(())
118}
119
120#[derive(Debug)]
121pub struct SdesChunk {
122    pub ssrc: u32,
123    pub sdes_items: Vec<SdesItem>,
124}
125
126pub fn read_sdes_chunk<R: PacketBuffer>(buf: &mut R) -> Result<SdesChunk> {
127    let ssrc = buf.read_u32::<NetworkOrder>().context("ssrc")?;
128    let mut sdes_items: Vec<SdesItem> = Vec::new();
129    loop {
130        let sdes_item = read_sdes_item(buf).context("item")?;
131        if matches!(sdes_item, SdesItem::Empty) {
132            break;
133        }
134        sdes_items.push(sdes_item);
135    }
136
137    consume_padding(buf);
138
139    Ok(SdesChunk { ssrc, sdes_items })
140}
141
142pub fn write_sdes_chunk<W: PacketBufferMut>(buf: &mut W, sdes_chunk: &SdesChunk) -> Result<()> {
143    buf.write_u32::<NetworkOrder>(sdes_chunk.ssrc)
144        .context("ssrc")?;
145    sdes_chunk
146        .sdes_items
147        .iter()
148        .enumerate()
149        .map(|(i, sdes_item)| {
150            write_sdes_item(buf, sdes_item).with_context(|| format!("sdes item {i}"))
151        })
152        .collect::<Result<Vec<()>>>()
153        .context("sdes items")?;
154
155    write_sdes_item(buf, &SdesItem::Empty).context("empty item")?;
156
157    // TODO: I'm wondering about doing the padding here: what if the buffer we're given is a slice
158    // which doesn't have the proper context of the overall alignment? Also, we'll need some trait
159    // to be able to even add padding here to some alignment.
160
161    Ok(())
162}
163
164#[cfg(test)]
165mod tests {
166    use bit_cursor::{
167        bit_cursor::BitCursor,
168        nsw_types::{u2, u5},
169    };
170    use bitvec::{order::Msb0, vec::BitVec};
171
172    use super::*;
173
174    fn create_cname_item_bytes(str: &str) -> Vec<u8> {
175        let data = str.bytes();
176        let mut item_data = vec![0x1, data.len() as u8];
177        item_data.extend(data.collect::<Vec<u8>>());
178
179        item_data
180    }
181
182    #[test]
183    fn test_read_sdes_item_success() {
184        let str = "hello, world!";
185        let item_data = create_cname_item_bytes(str);
186
187        let mut buf = BitCursor::new(BitVec::<u8, Msb0>::from_vec(item_data));
188        let sdes_item = read_sdes_item(&mut buf).unwrap();
189        match sdes_item {
190            SdesItem::Cname(v) => assert_eq!(v, str),
191            _ => panic!("Wrong SdesItem type"),
192        }
193    }
194
195    #[test]
196    fn test_read_sdes_item_bad_data() {
197        let data: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
198        let mut item_data = vec![0x1, data.len() as u8];
199        item_data.extend(data);
200
201        let mut buf = BitCursor::new(BitVec::<u8, Msb0>::from_vec(item_data));
202        let res = read_sdes_item(&mut buf);
203        assert!(res.is_err());
204    }
205
206    #[test]
207    fn test_read_sdes() {
208        let header = RtcpHeader {
209            version: u2::new(2),
210            has_padding: false,
211            report_count: u5::new(1),
212            packet_type: 202,
213            length_field: 6,
214        };
215        #[rustfmt::skip]
216        let sdes_chunk = vec![
217            // ssrc
218            0xa8, 0x9c, 0x2a, 0xc5,
219            // Cname, length 16, value 6EENBH+pFqtpT6SF
220            0x01, 0x10, 0x36, 0x45, 0x45, 0x4e, 0x42, 0x48, 0x2b, 0x70, 0x46, 0x71, 0x74, 0x70, 0x54, 0x36, 0x53, 0x46,
221            // Empty sdes item to finish
222            0x00,
223        ];
224        let mut cursor = BitCursor::new(BitVec::<u8, Msb0>::from_vec(sdes_chunk));
225
226        let sdes = read_rtcp_sdes(&mut cursor, header).expect("sdes");
227        assert_eq!(sdes.chunks.len(), 1);
228        let chunk = sdes.chunks.first().expect("sdes chunk");
229        assert_eq!(chunk.ssrc, 2828806853);
230    }
231
232    // TODO:
233    // parse_sdes_chunk success | failure in chunk | failure in item
234    // parse_sdes_chunks
235    // parse_rtcp_sdes success | failure in chunk
236}