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}