Skip to main content

rtc_rtcp/source_description/
mod.rs

1#[cfg(test)]
2mod source_description_test;
3
4use crate::{header::*, packet::*, util::*};
5use shared::{
6    error::{Error, Result},
7    marshal::{Marshal, MarshalSize, Unmarshal},
8};
9
10use bytes::{Buf, BufMut, Bytes};
11use std::any::Any;
12use std::fmt;
13
14const SDES_SOURCE_LEN: usize = 4;
15const SDES_TYPE_LEN: usize = 1;
16const SDES_TYPE_OFFSET: usize = 0;
17const SDES_OCTET_COUNT_LEN: usize = 1;
18const SDES_OCTET_COUNT_OFFSET: usize = 1;
19const SDES_MAX_OCTET_COUNT: usize = (1 << 8) - 1;
20const SDES_TEXT_OFFSET: usize = 2;
21
22/// SDESType is the item type used in the RTCP SDES control packet.
23/// RTP SDES item types registered with IANA. See: <https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5>
24#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
25#[repr(u8)]
26pub enum SdesType {
27    #[default]
28    /// End of the SDES item list ([RFC 3550] §6.5).
29    SdesEnd = 0, // end of SDES list                RFC 3550, 6.5
30    /// CNAME: the canonical end-point identifier, which ties an SSRC to a participant.
31    ///
32    /// The one item WebRTC always sends — it is how a receiver associates streams that belong
33    /// together.
34    SdesCname = 1, // canonical name                  RFC 3550, 6.5.1
35    /// NAME: the participant's display name.
36    SdesName = 2, // user name                       RFC 3550, 6.5.2
37    /// EMAIL: the participant's email address.
38    SdesEmail = 3, // user's electronic mail address  RFC 3550, 6.5.3
39    /// PHONE: the participant's phone number.
40    SdesPhone = 4, // user's phone number             RFC 3550, 6.5.4
41    /// LOC: the participant's geographic location.
42    SdesLocation = 5, // geographic user location        RFC 3550, 6.5.5
43    /// TOOL: the name and version of the sending application.
44    SdesTool = 6, // name of application or tool     RFC 3550, 6.5.6
45    /// NOTE: a transient note about the source, such as "on hold".
46    SdesNote = 7, // notice about the source         RFC 3550, 6.5.7
47    /// PRIV: a private extension.
48    SdesPrivate = 8, // private extensions              RFC 3550, 6.5.8  (not implemented)
49}
50
51impl fmt::Display for SdesType {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        let s = match self {
54            SdesType::SdesEnd => "END",
55            SdesType::SdesCname => "CNAME",
56            SdesType::SdesName => "NAME",
57            SdesType::SdesEmail => "EMAIL",
58            SdesType::SdesPhone => "PHONE",
59            SdesType::SdesLocation => "LOC",
60            SdesType::SdesTool => "TOOL",
61            SdesType::SdesNote => "NOTE",
62            SdesType::SdesPrivate => "PRIV",
63        };
64        write!(f, "{s}")
65    }
66}
67
68impl From<u8> for SdesType {
69    fn from(b: u8) -> Self {
70        match b {
71            1 => SdesType::SdesCname,
72            2 => SdesType::SdesName,
73            3 => SdesType::SdesEmail,
74            4 => SdesType::SdesPhone,
75            5 => SdesType::SdesLocation,
76            6 => SdesType::SdesTool,
77            7 => SdesType::SdesNote,
78            8 => SdesType::SdesPrivate,
79            _ => SdesType::SdesEnd,
80        }
81    }
82}
83
84/// A SourceDescriptionChunk contains items describing a single RTP source
85#[derive(Debug, PartialEq, Eq, Default, Clone)]
86pub struct SourceDescriptionChunk {
87    /// The source (ssrc) or contributing source (csrc) identifier this packet describes
88    pub source: u32,
89    /// The items describing this source.
90    pub items: Vec<SourceDescriptionItem>,
91}
92
93impl SourceDescriptionChunk {
94    fn raw_size(&self) -> usize {
95        let mut len = SDES_SOURCE_LEN;
96        for it in &self.items {
97            len += it.marshal_size();
98        }
99        len += SDES_TYPE_LEN; // for terminating null octet
100        len
101    }
102}
103
104impl MarshalSize for SourceDescriptionChunk {
105    fn marshal_size(&self) -> usize {
106        let l = self.raw_size();
107        // align to 32-bit boundary
108        l + get_padding_size(l)
109    }
110}
111
112impl Marshal for SourceDescriptionChunk {
113    /// Marshal encodes the SourceDescriptionChunk in binary
114    fn marshal_to(&self, mut buf: &mut [u8]) -> Result<usize> {
115        if buf.remaining_mut() < self.marshal_size() {
116            return Err(Error::BufferTooShort);
117        }
118        /*
119         *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
120         *  |                          SSRC/CSRC_1                          |
121         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
122         *  |                           SDES items                          |
123         *  |                              ...                              |
124         *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
125         */
126
127        buf.put_u32(self.source);
128
129        for it in &self.items {
130            let n = it.marshal_to(buf)?;
131            buf = &mut buf[n..];
132        }
133
134        // The list of items in each chunk MUST be terminated by one or more null octets
135        buf.put_u8(SdesType::SdesEnd as u8);
136
137        // additional null octets MUST be included if needed to pad until the next 32-bit boundary
138        put_padding(buf, self.raw_size());
139        Ok(self.marshal_size())
140    }
141}
142
143impl Unmarshal for SourceDescriptionChunk {
144    /// Unmarshal decodes the SourceDescriptionChunk from binary
145    fn unmarshal<B>(raw_packet: &mut B) -> Result<Self>
146    where
147        Self: Sized,
148        B: Buf,
149    {
150        /*
151         *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
152         *  |                          SSRC/CSRC_1                          |
153         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
154         *  |                           SDES items                          |
155         *  |                              ...                              |
156         *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
157         */
158        let raw_packet_len = raw_packet.remaining();
159        if raw_packet_len < (SDES_SOURCE_LEN + SDES_TYPE_LEN) {
160            return Err(Error::PacketTooShort);
161        }
162
163        let source = raw_packet.get_u32();
164
165        let mut offset = SDES_SOURCE_LEN;
166        let mut items = vec![];
167        while offset < raw_packet_len {
168            let item = SourceDescriptionItem::unmarshal(raw_packet)?;
169            if item.sdes_type == SdesType::SdesEnd {
170                // offset + 1 (one byte for SdesEnd)
171                let padding_len = get_padding_size(offset + 1);
172                if raw_packet.remaining() >= padding_len {
173                    raw_packet.advance(padding_len);
174                    return Ok(SourceDescriptionChunk { source, items });
175                } else {
176                    return Err(Error::PacketTooShort);
177                }
178            }
179            offset += item.marshal_size();
180            items.push(item);
181        }
182
183        Err(Error::PacketTooShort)
184    }
185}
186
187/// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.
188#[derive(Debug, PartialEq, Eq, Default, Clone)]
189pub struct SourceDescriptionItem {
190    /// The type identifier for this item. eg, SDESCNAME for canonical name description.
191    ///
192    /// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.
193    pub sdes_type: SdesType,
194    /// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.
195    pub text: Bytes,
196}
197
198impl MarshalSize for SourceDescriptionItem {
199    fn marshal_size(&self) -> usize {
200        /*
201         *   0                   1                   2                   3
202         *   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
203         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
204         *  |    CNAME=1    |     length    | user and domain name        ...
205         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
206         */
207        SDES_TYPE_LEN + SDES_OCTET_COUNT_LEN + self.text.len()
208    }
209}
210
211impl Marshal for SourceDescriptionItem {
212    /// Marshal encodes the SourceDescriptionItem in binary
213    fn marshal_to(&self, mut buf: &mut [u8]) -> Result<usize> {
214        /*
215         *   0                   1                   2                   3
216         *   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
217         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
218         *  |    CNAME=1    |     length    | user and domain name        ...
219         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
220         */
221
222        if self.sdes_type == SdesType::SdesEnd {
223            return Err(Error::SdesMissingType);
224        }
225
226        if buf.remaining_mut() < self.marshal_size() {
227            return Err(Error::BufferTooShort);
228        }
229
230        buf.put_u8(self.sdes_type as u8);
231
232        if self.text.len() > SDES_MAX_OCTET_COUNT {
233            return Err(Error::SdesTextTooLong);
234        }
235        buf.put_u8(self.text.len() as u8);
236        buf.put(self.text.clone());
237
238        //no padding for each SourceDescriptionItem
239        Ok(self.marshal_size())
240    }
241}
242
243impl Unmarshal for SourceDescriptionItem {
244    /// Unmarshal decodes the SourceDescriptionItem from binary
245    fn unmarshal<B>(raw_packet: &mut B) -> Result<Self>
246    where
247        Self: Sized,
248        B: Buf,
249    {
250        /*
251         *   0                   1                   2                   3
252         *   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
253         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
254         *  |    CNAME=1    |     length    | user and domain name        ...
255         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
256         */
257        let raw_packet_len = raw_packet.remaining();
258        if raw_packet_len < SDES_TYPE_LEN {
259            return Err(Error::PacketTooShort);
260        }
261
262        let sdes_type = SdesType::from(raw_packet.get_u8());
263        if sdes_type == SdesType::SdesEnd {
264            return Ok(SourceDescriptionItem {
265                sdes_type,
266                text: Bytes::new(),
267            });
268        }
269
270        if raw_packet_len < (SDES_TYPE_LEN + SDES_OCTET_COUNT_LEN) {
271            return Err(Error::PacketTooShort);
272        }
273
274        let octet_count = raw_packet.get_u8() as usize;
275        if SDES_TEXT_OFFSET + octet_count > raw_packet_len {
276            return Err(Error::PacketTooShort);
277        }
278
279        let text = raw_packet.copy_to_bytes(octet_count);
280
281        Ok(SourceDescriptionItem { sdes_type, text })
282    }
283}
284
285/// A SourceDescription (SDES) packet describes the sources in an RTP stream.
286#[derive(Debug, Default, PartialEq, Eq, Clone)]
287pub struct SourceDescription {
288    /// One chunk per source described by this packet.
289    pub chunks: Vec<SourceDescriptionChunk>,
290}
291
292impl fmt::Display for SourceDescription {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        let mut out = "Source Description:\n".to_string();
295        for c in &self.chunks {
296            out += format!("\t{:x}\n", c.source).as_str();
297            for it in &c.items {
298                out += format!("\t\t{it:?}\n").as_str();
299            }
300        }
301        write!(f, "{out}")
302    }
303}
304
305impl Packet for SourceDescription {
306    /// Header returns the Header associated with this packet.
307    fn header(&self) -> Header {
308        Header {
309            padding: get_padding_size(self.raw_size()) != 0,
310            count: self.chunks.len() as u8,
311            packet_type: PacketType::SourceDescription,
312            length: ((self.marshal_size() / 4) - 1) as u16,
313        }
314    }
315
316    /// destination_ssrc returns an array of SSRC values that this packet refers to.
317    fn destination_ssrc(&self) -> Vec<u32> {
318        self.chunks.iter().map(|x| x.source).collect()
319    }
320
321    fn raw_size(&self) -> usize {
322        let mut chunks_length = 0;
323        for c in &self.chunks {
324            chunks_length += c.marshal_size();
325        }
326
327        HEADER_LENGTH + chunks_length
328    }
329
330    fn as_any(&self) -> &dyn Any {
331        self
332    }
333
334    fn equal(&self, other: &dyn Packet) -> bool {
335        other.as_any().downcast_ref::<SourceDescription>() == Some(self)
336    }
337
338    fn cloned(&self) -> Box<dyn Packet> {
339        Box::new(self.clone())
340    }
341}
342
343impl MarshalSize for SourceDescription {
344    fn marshal_size(&self) -> usize {
345        let l = self.raw_size();
346        // align to 32-bit boundary
347        l + get_padding_size(l)
348    }
349}
350
351impl Marshal for SourceDescription {
352    /// Marshal encodes the SourceDescription in binary
353    fn marshal_to(&self, mut buf: &mut [u8]) -> Result<usize> {
354        if self.chunks.len() > COUNT_MAX {
355            return Err(Error::TooManyChunks);
356        }
357
358        if buf.remaining_mut() < self.marshal_size() {
359            return Err(Error::BufferTooShort);
360        }
361
362        /*
363         *         0                   1                   2                   3
364         *         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
365         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
366         * header |V=2|P|    SC   |  PT=SDES=202  |             length            |
367         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
368         * chunk  |                          SSRC/CSRC_1                          |
369         *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
370         *        |                           SDES items                          |
371         *        |                              ...                              |
372         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
373         * chunk  |                          SSRC/CSRC_2                          |
374         *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
375         *        |                           SDES items                          |
376         *        |                              ...                              |
377         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
378         */
379
380        let h = self.header();
381        let n = h.marshal_to(buf)?;
382        buf = &mut buf[n..];
383
384        for c in &self.chunks {
385            let n = c.marshal_to(buf)?;
386            buf = &mut buf[n..];
387        }
388
389        if h.padding {
390            put_padding(buf, self.raw_size());
391        }
392
393        Ok(self.marshal_size())
394    }
395}
396
397impl Unmarshal for SourceDescription {
398    /// Unmarshal decodes the SourceDescription from binary
399    fn unmarshal<B>(raw_packet: &mut B) -> Result<Self>
400    where
401        Self: Sized,
402        B: Buf,
403    {
404        /*
405         *         0                   1                   2                   3
406         *         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
407         *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
408         * header |V=2|P|    SC   |  PT=SDES=202  |             length            |
409         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
410         * chunk  |                          SSRC/CSRC_1                          |
411         *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
412         *        |                           SDES items                          |
413         *        |                              ...                              |
414         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
415         * chunk  |                          SSRC/CSRC_2                          |
416         *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
417         *        |                           SDES items                          |
418         *        |                              ...                              |
419         *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
420         */
421        let raw_packet_len = raw_packet.remaining();
422
423        let h = Header::unmarshal(raw_packet)?;
424        if h.packet_type != PacketType::SourceDescription {
425            return Err(Error::WrongType);
426        }
427
428        let mut offset = HEADER_LENGTH;
429        let mut chunks = vec![];
430        while offset < raw_packet_len {
431            let chunk = SourceDescriptionChunk::unmarshal(raw_packet)?;
432            offset += chunk.marshal_size();
433            chunks.push(chunk);
434        }
435
436        if chunks.len() != h.count as usize {
437            return Err(Error::InvalidHeader);
438        }
439
440        if
441        /*h.padding &&*/
442        raw_packet.has_remaining() {
443            raw_packet.advance(raw_packet.remaining());
444        }
445
446        Ok(SourceDescription { chunks })
447    }
448}