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