rtp_parse/rtcp/
rtcp_sdes.rs

1use std::str::from_utf8;
2
3use anyhow::{Context, Result};
4use parsely_rs::*;
5
6use super::rtcp_header::RtcpHeader;
7
8/// https://datatracker.ietf.org/doc/html/rfc3550#section-6.5
9///         0                   1                   2                   3
10///         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
11///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12/// header |V=2|P|    SC   |  PT=SDES=202  |             length            |
13///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
14/// chunk  |                          SSRC/CSRC_1                          |
15///   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16///        |                           SDES items                          |
17///        |                              ...                              |
18///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
19/// chunk  |                          SSRC/CSRC_2                          |
20///   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21///        |                           SDES items                          |
22///        |                              ...                              |
23///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
24///   Items are contiguous, i.e., items are not individually padded to a
25///     32-bit boundary.  Text is not null terminated because some multi-
26///     octet encodings include null octets.  The list of items in each chunk
27///     MUST be terminated by one or more null octets, the first of which is
28///     interpreted as an item type of zero to denote the end of the list.
29///     No length octet follows the null item type octet, but additional null
30///     octets MUST be included if needed to pad until the next 32-bit
31///     boundary.  Note that this padding is separate from that indicated by
32///     the P bit in the RTCP header.  A chunk with zero items (four null
33///     octets) is valid but useless.
34#[derive(Debug, ParselyRead, ParselyWrite, PartialEq)]
35#[parsely_read(required_context("header: RtcpHeader"))]
36pub struct RtcpSdesPacket {
37    #[parsely_read(assign_from = "header")]
38    #[parsely_write(sync_with("self.payload_length_bytes()", "self.chunks.len().try_into()"))]
39    #[parsely(assertion = "|header: &RtcpHeader| header.packet_type == RtcpSdesPacket::PT")]
40    pub header: RtcpHeader,
41    #[parsely_read(count = "header.report_count.into()")]
42    pub chunks: Vec<SdesChunk>,
43}
44
45impl Default for RtcpSdesPacket {
46    fn default() -> Self {
47        Self {
48            header: RtcpHeader {
49                packet_type: RtcpSdesPacket::PT,
50                ..Default::default()
51            },
52            chunks: Default::default(),
53        }
54    }
55}
56
57impl RtcpSdesPacket {
58    pub const PT: u8 = 202;
59
60    pub fn add_chunk(mut self, chunk: SdesChunk) -> Self {
61        self.chunks.push(chunk);
62        self
63    }
64
65    pub fn payload_length_bytes(&self) -> u16 {
66        self.chunks.iter().map(|i| i.length_bytes()).sum()
67    }
68}
69
70/// https://datatracker.ietf.org/doc/html/rfc3550#section-6.5
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, PartialEq)]
77pub enum SdesItem {
78    Empty,
79    Cname(String),
80    Unknown { item_type: u8, data: Vec<u8> },
81}
82
83impl SdesItem {
84    pub fn cname(cname: &str) -> Self {
85        SdesItem::Cname(cname.to_owned())
86    }
87
88    pub fn length_bytes(&self) -> u16 {
89        // All items (except 'empty') take up:
90        // 1 byte for the type
91        // 1 byte for the length
92        // N bytes for the data
93        match self {
94            SdesItem::Empty => 1,
95            SdesItem::Cname(s) => 1 + 1 + s.len() as u16,
96            SdesItem::Unknown { data, .. } => 1 + 1 + data.len() as u16,
97        }
98    }
99}
100
101impl<B: BitBuf> ParselyRead<B> for SdesItem {
102    type Ctx = ();
103    fn read<T: ByteOrder>(buf: &mut B, _ctx: ()) -> ParselyResult<Self> {
104        let id = buf.get_u8().context("id")?;
105
106        if id == 0 {
107            return Ok(SdesItem::Empty);
108        }
109        let length = buf.get_u8().context("length")? as usize;
110        let mut value_bytes = vec![0u8; length];
111        buf.try_copy_to_slice_bytes(&mut value_bytes)
112            .context("value")?;
113        match id {
114            1 => Ok(SdesItem::Cname(from_utf8(&value_bytes)?.to_owned())),
115            t => Ok(SdesItem::Unknown {
116                item_type: t,
117                data: value_bytes.to_vec(),
118            }),
119        }
120    }
121}
122
123impl_stateless_sync!(SdesItem);
124
125impl<B: BitBufMut> ParselyWrite<B> for SdesItem {
126    type Ctx = ();
127    fn write<T: ByteOrder>(&self, buf: &mut B, _ctx: Self::Ctx) -> ParselyResult<()> {
128        match self {
129            SdesItem::Empty => {
130                buf.put_u8(0).context("id")?;
131            }
132            SdesItem::Cname(value) => {
133                buf.put_u8(1).context("id")?;
134                let bytes = value.as_bytes();
135                buf.put_u8(bytes.len() as u8).context("length")?;
136                buf.try_put_slice_bytes(bytes).context("value")?;
137            }
138            SdesItem::Unknown { item_type, data } => {
139                buf.put_u8(*item_type).context("id")?;
140                buf.put_u8(data.len() as u8).context("length")?;
141                buf.try_put_slice_bytes(&data[..]).context("value")?;
142            }
143        }
144        Ok(())
145    }
146}
147
148/// https://datatracker.ietf.org/doc/html/rfc3550#section-6.5
149///         0                   1                   2                   3
150///         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
151///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
152/// chunk  |                          SSRC/CSRC                            |
153///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
154///        |                           SDES items                          |
155///        |                              ...                              |
156///        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
157#[derive(Debug, PartialEq)]
158pub struct SdesChunk {
159    pub ssrc: u32,
160    /// Note that an empty SdesItem does _not_ need to be explicitly added here: that is handled
161    /// when writing the chunk to a buffer
162    pub sdes_items: Vec<SdesItem>,
163}
164
165impl SdesChunk {
166    pub fn new(ssrc: u32) -> Self {
167        Self {
168            ssrc,
169            sdes_items: Vec::new(),
170        }
171    }
172
173    pub fn new_with_items(ssrc: u32, sdes_items: Vec<SdesItem>) -> Self {
174        Self { ssrc, sdes_items }
175    }
176
177    pub fn add_item(mut self, item: SdesItem) -> Self {
178        self.sdes_items.push(item);
179        self
180    }
181
182    pub fn length_bytes(&self) -> u16 {
183        let mut length_bytes = 4 + self
184            .sdes_items
185            .iter()
186            .map(|i| i.length_bytes())
187            .sum::<u16>();
188
189        while length_bytes % 4 != 0 {
190            length_bytes += 1;
191        }
192
193        length_bytes
194    }
195}
196
197impl<B: BitBuf> ParselyRead<B> for SdesChunk {
198    type Ctx = ();
199    fn read<T: ByteOrder>(buf: &mut B, _ctx: ()) -> ParselyResult<Self> {
200        let remaining_start = buf.remaining_bytes();
201        let ssrc = buf.get_u32::<NetworkOrder>().context("ssrc")?;
202        let mut sdes_items: Vec<SdesItem> = Vec::new();
203        loop {
204            let sdes_item = SdesItem::read::<T>(buf, ()).context("item")?;
205            if matches!(sdes_item, SdesItem::Empty) {
206                break;
207            }
208            sdes_items.push(sdes_item);
209        }
210        let mut consumed = remaining_start - buf.remaining_bytes();
211        while consumed % 4 != 0 {
212            buf.get_u8().unwrap();
213            consumed += 1;
214        }
215
216        Ok(SdesChunk { ssrc, sdes_items })
217    }
218}
219
220impl_stateless_sync!(SdesChunk);
221
222impl<B: BitBufMut> ParselyWrite<B> for SdesChunk {
223    type Ctx = ();
224    fn write<T: ByteOrder>(&self, buf: &mut B, _ctx: Self::Ctx) -> ParselyResult<()> {
225        let remaining_start = buf.remaining_mut_bytes();
226        buf.put_u32::<NetworkOrder>(self.ssrc).context("ssrc")?;
227        self.sdes_items
228            .iter()
229            .enumerate()
230            .map(|(i, sdes_item)| {
231                sdes_item
232                    .write::<T>(buf, ())
233                    .with_context(|| format!("Sdes item {i}"))
234            })
235            .collect::<Result<Vec<()>>>()
236            .context("Sdes items")?;
237
238        SdesItem::Empty
239            .write::<T>(buf, ())
240            .context("Terminating empty sdes item")?;
241
242        let mut amount_written = remaining_start - buf.remaining_mut_bytes();
243        while amount_written % 4 != 0 {
244            buf.put_u8(0).context("padding")?;
245            amount_written += 1;
246        }
247
248        Ok(())
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    fn create_cname_item_bytes(str: &str) -> Vec<u8> {
257        let data = str.bytes();
258        let mut item_data = vec![0x1, data.len() as u8];
259        item_data.extend(data.collect::<Vec<u8>>());
260
261        item_data
262    }
263
264    #[test]
265    fn test_read_sdes_item_success() {
266        let str = "hello, world!";
267        let item_data = create_cname_item_bytes(str);
268
269        let mut bits = Bits::from_owner_bytes(item_data);
270        let sdes_item = SdesItem::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
271        match sdes_item {
272            SdesItem::Cname(v) => assert_eq!(v, str),
273            _ => panic!("Wrong SdesItem type"),
274        }
275    }
276
277    #[test]
278    fn test_read_sdes_item_bad_data() {
279        let data: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
280        let mut item_data = vec![0x1, data.len() as u8];
281        item_data.extend(data);
282
283        let mut bits = Bits::from_owner_bytes(item_data);
284        let res = SdesItem::read::<NetworkOrder>(&mut bits, ());
285        assert!(res.is_err());
286    }
287
288    #[test]
289    fn test_read_sdes_item() {
290        // Cname item
291        let data = create_cname_item_bytes("hello");
292        let mut bits = Bits::from_owner_bytes(data);
293
294        let item = SdesItem::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
295        match item {
296            SdesItem::Cname(s) => assert_eq!("hello", s),
297            _ => panic!("Expected cname item"),
298        }
299        // unknown item
300        let data: Vec<u8> = vec![0x6, 0x4, 0xDE, 0xAD, 0xBE, 0xEF];
301        let mut bits = Bits::from_owner_bytes(data);
302
303        let item = SdesItem::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
304        match item {
305            SdesItem::Unknown { item_type, data } => {
306                assert_eq!(item_type, 6);
307                assert_eq!(&data[..], [0xDE, 0xAD, 0xBE, 0xEF]);
308            }
309            _ => panic!("Expected unknown item"),
310        }
311    }
312
313    #[test]
314    fn test_write_sdes_item() {
315        let item = SdesItem::cname("hello");
316        let mut bits_mut = BitsMut::new();
317
318        item.write::<NetworkOrder>(&mut bits_mut, ())
319            .expect("successful write");
320
321        let mut bits = bits_mut.freeze();
322
323        let read_item = SdesItem::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
324        assert_eq!(item, read_item);
325    }
326
327    #[test]
328    fn test_write_unknown_sdes_item() {
329        let item = SdesItem::Unknown {
330            item_type: 0x5,
331            data: vec![0x42, 0x24],
332        };
333        let mut bits_mut = BitsMut::new();
334
335        item.write::<NetworkOrder>(&mut bits_mut, ())
336            .expect("successful write");
337
338        let mut bits = bits_mut.freeze();
339
340        let read_item = SdesItem::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
341        assert_eq!(item, read_item);
342    }
343
344    #[test]
345    fn test_read_sdes_chunk() {
346        #[rustfmt::skip]
347        let mut bits = Bits::from_static_bytes(&[
348            // ssrc (42)
349            0x00, 0x00, 0x00, 0x2a,
350            // Cname, length 16, value hello
351            0x01, 0x5, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
352            // Empty sdes item to finish
353            0x00,
354        ]);
355
356        let chunk = SdesChunk::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
357        assert_eq!(bits.remaining_bytes(), 0);
358        assert_eq!(chunk.ssrc, 42);
359        assert_eq!(chunk.sdes_items.len(), 1);
360        let item = &chunk.sdes_items[0];
361        assert_eq!(item, &SdesItem::cname("hello"));
362    }
363
364    #[test]
365    fn tesd_read_sdes_chunks() {
366        #[rustfmt::skip]
367        let mut bits = Bits::from_static_bytes(&[
368            // ssrc (42)
369            0x00, 0x00, 0x00, 0x2a,
370            // Cname, length 16, value hello
371            0x01, 0x5, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
372            // Unknown
373            0x04, 0x2, 0x42, 0x24,
374            // Empty sdes item to finish
375            0x00,
376        ]);
377
378        let chunk = SdesChunk::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
379        assert_eq!(bits.remaining_bytes(), 0);
380        assert_eq!(chunk.ssrc, 42);
381        assert_eq!(chunk.sdes_items.len(), 2);
382        let item = &chunk.sdes_items[0];
383        assert_eq!(item, &SdesItem::cname("hello"));
384        let item = &chunk.sdes_items[1];
385        assert_eq!(
386            item,
387            &SdesItem::Unknown {
388                item_type: 0x4,
389                data: vec![0x42, 0x24]
390            }
391        );
392    }
393
394    #[test]
395    fn test_read_sdes_chunks_no_termination() {
396        #[rustfmt::skip]
397        let mut bits = Bits::from_static_bytes(&[
398            // ssrc (42)
399            0x00, 0x00, 0x00, 0x2a,
400            // Cname, length 16, value hello
401            0x01, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
402            // Unknown
403            0x04, 0x2, 0x42, 0x24,
404            // No empty item to finish
405        ]);
406
407        let chunk = SdesChunk::read::<NetworkOrder>(&mut bits, ());
408        assert!(chunk.is_err());
409    }
410
411    #[test]
412    fn test_write_sdes_chunk() {
413        let chunk = SdesChunk::new(42)
414            .add_item(SdesItem::cname("hello"))
415            .add_item(SdesItem::Unknown {
416                item_type: 5,
417                data: vec![0x42, 0x24],
418            });
419
420        let mut bits_mut = BitsMut::new();
421        chunk
422            .write::<NetworkOrder>(&mut bits_mut, ())
423            .expect("successful write");
424        let mut bits = bits_mut.freeze();
425        let read_chunk = SdesChunk::read::<NetworkOrder>(&mut bits, ()).expect("successful read");
426        assert_eq!(chunk, read_chunk);
427    }
428
429    #[test]
430    fn test_read_sdes() {
431        let header = RtcpHeader {
432            version: u2::new(2),
433            has_padding: false,
434            report_count: u5::new(1),
435            packet_type: 202,
436            length_field: 4,
437        };
438        #[rustfmt::skip]
439        let mut sdes_chunk_bits = Bits::from_static_bytes(&[
440            // ssrc (42)
441            0x00, 0x00, 0x00, 0x2a,
442            // Cname, length 16, value hello
443            0x01, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
444            // Unknown
445            0x04, 0x2, 0x42, 0x24,
446            // Empty sdes item to finish
447            0x00,
448        ]);
449
450        let sdes = RtcpSdesPacket::read::<NetworkOrder>(&mut sdes_chunk_bits, (header,))
451            .expect("Successful read");
452        assert_eq!(sdes_chunk_bits.remaining_bytes(), 0);
453        assert_eq!(sdes.chunks.len(), 1);
454        let chunk = &sdes.chunks[0];
455        assert_eq!(chunk.ssrc, 42);
456        assert_eq!(chunk.sdes_items.len(), 2);
457    }
458
459    #[test]
460    fn test_read_sdes_multiple_chunks() {
461        let header = RtcpHeader {
462            version: u2::new(2),
463            has_padding: false,
464            report_count: u5::new(2),
465            packet_type: 202,
466            length_field: 9,
467        };
468        #[rustfmt::skip]
469        let mut sdes_chunks_bits = Bits::from_static_bytes(&[
470            // ssrc (42)
471            0x00, 0x00, 0x00, 0x2a,
472            // Cname, length 16, value hello
473            0x01, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
474            // Unknown
475            0x04, 0x2, 0x42, 0x24,
476            // Empty sdes item to finish
477            0x00,
478            // ssrc (43)
479            0x00, 0x00, 0x00, 0x2b,
480            // Unknown
481            0x04, 0x4, 0x42, 0x24, 0x42, 0x24,
482            // Cname, length 16, value hello
483            0x01, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
484            // Empty item
485            0x00, 
486            // Padding
487            0x00, 0x00
488        ]);
489
490        let sdes = RtcpSdesPacket::read::<NetworkOrder>(&mut sdes_chunks_bits, (header,))
491            .expect("Successful read");
492        assert_eq!(sdes_chunks_bits.remaining_bytes(), 0);
493        assert_eq!(sdes.chunks.len(), 2);
494        let chunk = &sdes.chunks[0];
495        assert_eq!(chunk.ssrc, 42);
496        assert_eq!(chunk.sdes_items.len(), 2);
497        let chunk = &sdes.chunks[1];
498        assert_eq!(chunk.ssrc, 43);
499        assert_eq!(chunk.sdes_items.len(), 2);
500    }
501
502    #[test]
503    fn test_sync_rtcp_sdes() {
504        let mut rtcp_sdes = RtcpSdesPacket::default()
505            .add_chunk(SdesChunk::new(42).add_item(SdesItem::cname("hello")))
506            .add_chunk(SdesChunk::new(43).add_item(SdesItem::cname("world")));
507
508        rtcp_sdes.sync(()).expect("successful sync");
509        assert_eq!(rtcp_sdes.header.packet_type, RtcpSdesPacket::PT);
510        assert_eq!(rtcp_sdes.header.report_count, 2);
511        // payload has 2 chunks.
512        //   Each chunk has one ssrc and one cname item and one empty terminator:
513        //     Ssrc take 4
514        //     Cname item takes 1 (type) + 1 (length) + 5 (hello/world are each 5) = 7 bytes
515        //     Empty is 1
516        //     4 + 7 + 1 = 12 (no padding needed)
517        //   12 * 2 = 24 -> 6 words
518        assert_eq!(rtcp_sdes.header.length_field, 6);
519    }
520
521    #[test]
522    fn test_write_rtcp_sdes() {
523        let mut rtcp_sdes = RtcpSdesPacket::default()
524            .add_chunk(SdesChunk::new(42).add_item(SdesItem::cname("hello")))
525            .add_chunk(SdesChunk::new(43).add_item(SdesItem::cname("world")));
526
527        rtcp_sdes.sync(()).expect("successful sync");
528
529        let mut bits_mut = BitsMut::new();
530
531        rtcp_sdes
532            .write::<NetworkOrder>(&mut bits_mut, ())
533            .expect("successful write");
534
535        let mut bits = bits_mut.freeze();
536        let rtcp_header = RtcpHeader::read::<NetworkOrder>(&mut bits, ()).expect("rtcp header");
537        let read_rtcp_sdes = RtcpSdesPacket::read::<NetworkOrder>(&mut bits, (rtcp_header,))
538            .expect("successful read");
539        assert_eq!(read_rtcp_sdes, rtcp_sdes);
540    }
541}