Skip to content
File

Blob: firmware/vendor/str0m/src/rtp/rtcp/sdes.rs

rust326 lines
1use std::str::from_utf8;
2 
3use super::list::private::WordSized;
4use super::{FeedbackMessageType, RtcpType, Ssrc};
5use super::{ReportList, RtcpHeader, RtcpPacket, pad_bytes_to_word};
6 
7/// Multiple source descriptions (SDES).
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Descriptions {
10 /// The descriptions.
11 pub reports: Box<ReportList<Sdes>>,
12}
13 
14/// A single source description (SDES).
15#[allow(missing_docs)]
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Sdes {
18 pub ssrc: Ssrc,
19 pub values: ReportList<(SdesType, String)>,
20}
21 
22/// Types of SDES values.
23#[allow(clippy::upper_case_acronyms)]
24#[derive(Debug, Clone, Copy, Eq, PartialEq)]
25#[allow(missing_docs)]
26pub enum SdesType {
27 /// End of SDES list
28 END = 0,
29 /// Canonical name.
30 CNAME = 1,
31 /// User name
32 NAME = 2,
33 /// User's electronic mail address
34 EMAIL = 3,
35 /// User's phone number
36 PHONE = 4,
37 /// Geographic user location
38 LOC = 5,
39 /// Name of application or tool
40 TOOL = 6,
41 /// Notice about the source
42 NOTE = 7,
43 /// Private extensions
44 PRIV = 8,
45 /// Rtp stream ID.
46 RtpStreamId = 12,
47 /// Repaired rtp stream ID.
48 RepairedRtpStreamId = 13,
49 /// Who knows
50 Unknown,
51}
52 
53impl RtcpPacket for Descriptions {
54 fn header(&self) -> RtcpHeader {
55 RtcpHeader {
56 rtcp_type: RtcpType::SourceDescription,
57 feedback_message_type: FeedbackMessageType::SourceCount(self.reports.len() as u8),
58 words_less_one: (self.length_words() - 1) as u16,
59 }
60 }
61 
62 fn length_words(&self) -> usize {
63 // * header: 1
64 // * size-per-item * items
65 1 + self.reports.iter().map(|r| r.word_size()).sum::<usize>()
66 }
67 
68 fn write_to(&self, buf: &mut [u8]) -> usize {
69 self.header().write_to(buf);
70 
71 let mut buf = &mut buf[4..];
72 let mut tot = 4;
73 
74 for r in &*self.reports {
75 let n = r.write_to(buf);
76 buf = &mut buf[n..];
77 tot += n;
78 }
79 
80 tot
81 }
82}
83 
84impl Sdes {
85 fn write_to(&self, buf: &mut [u8]) -> usize {
86 buf[..4].copy_from_slice(&self.ssrc.to_be_bytes());
87 let mut tot = 4;
88 
89 let mut buf = &mut buf[4..];
90 for (t, v) in &self.values {
91 let bytes = v.as_bytes();
92 let len = bytes.len();
93 
94 buf[0] = *t as u8;
95 buf[1] = len as u8;
96 
97 buf = &mut buf[2..];
98 buf[..len].copy_from_slice(bytes);
99 
100 buf = &mut buf[len..];
101 tot += 2 + len;
102 }
103 
104 buf[0] = SdesType::END as u8;
105 buf = &mut buf[1..];
106 tot += 1;
107 
108 let pad = 4 - tot % 4;
109 if pad < 4 {
110 #[allow(clippy::needless_range_loop)]
111 for i in 0..pad {
112 buf[i] = SdesType::END as u8;
113 }
114 tot += pad;
115 }
116 
117 assert!(tot % 4 == 0, "Sdes is padded to word boundary");
118 
119 tot
120 }
121}
122 
123impl WordSized for Sdes {
124 fn word_size(&self) -> usize {
125 let byte_size = 4
126 + self
127 .values
128 .iter()
129 // 2 here for 2 byte encoding of type + length
130 .map(|(_, s)| 2 + s.len())
131 .sum::<usize>()
132 + 1; // 1 here for the end byte
133 
134 let padded = pad_bytes_to_word(byte_size);
135 
136 padded / 4
137 }
138}
139 
140impl From<u8> for SdesType {
141 fn from(v: u8) -> Self {
142 use SdesType::*;
143 match v {
144 0 => END,
145 1 => CNAME,
146 2 => NAME,
147 3 => EMAIL,
148 4 => PHONE,
149 5 => LOC,
150 6 => TOOL,
151 7 => NOTE,
152 8 => PRIV,
153 12 => RtpStreamId,
154 13 => RepairedRtpStreamId,
155 _ => Unknown,
156 }
157 }
158}
159 
160impl<'a> TryFrom<&'a [u8]> for Descriptions {
161 type Error = &'static str;
162 
163 fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
164 let mut reports = ReportList::new();
165 
166 let mut buf = buf;
167 
168 loop {
169 if reports.len() == 31 {
170 break;
171 }
172 // For some reason FF sends us a full SDES and then [0,0,0,0] at the end.
173 // This can't be interpreted as SDES, so we just ignore it.
174 if buf.len() < 8 {
175 break;
176 }
177 let report: Sdes = buf.try_into()?;
178 
179 let len = report.word_size() * 4;
180 
181 reports.push(report);
182 
183 // word_size() pads up to a 32-bit boundary and may report more bytes
184 // than remain for a trailing or truncated chunk. Guard against slicing
185 // past the end (which would panic) and against a zero len that would
186 // otherwise spin forever.
187 if len == 0 || len >= buf.len() {
188 break;
189 }
190 
191 buf = &buf[len..];
192 }
193 
194 Ok(Descriptions {
195 reports: Box::new(reports),
196 })
197 }
198}
199 
200impl<'a> TryFrom<&'a [u8]> for Sdes {
201 type Error = &'static str;
202 
203 fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
204 if buf.len() < 8 {
205 return Err("Less than 8 bytes for Sdes");
206 }
207 
208 let ssrc = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]).into();
209 let mut values = ReportList::new();
210 
211 let mut buf = &buf[4..];
212 let mut abs = 0;
213 
214 loop {
215 // SDES items are END-terminated rather than count-bounded, so a
216 // malformed chunk could contain more than the ReportList capacity.
217 // Stop at the cap rather than panicking on the 32nd push.
218 if values.is_full() {
219 break;
220 }
221 
222 // Per RFC 3550 Section 6.5:
223 // "The list of items in each chunk MUST be terminated by one or more null octets,
224 // the first of which is interpreted as an item type of end of list.
225 // No length octet follows the null item type octet, but additional null octets
226 // MUST be included if needed to pad until the next 32-bit boundary."
227 //
228 // This means we need a special case when there's only a single END byte (0x00)
229 // at the end of the buffer - it's a valid terminator despite not having a length byte.
230 if buf.len() == 1 && buf[0] == SdesType::END as u8 {
231 break;
232 }
233 
234 if buf.len() < 2 {
235 return Err("Less than 2 bytes for next Sdes value");
236 }
237 
238 let stype: SdesType = buf[0].into();
239 
240 if matches!(stype, SdesType::END) {
241 // The end of SDES.
242 
243 // Each chunk consists of an SSRC/CSRC identifier followed by a list of
244 // zero or more items, which carry information about the SSRC/CSRC.
245 // Each chunk starts on a 32-bit boundary.
246 //
247 // Items are contiguous, i.e., items are not individually padded to a
248 // 32-bit boundary. Text is not null terminated because some multi-
249 // octet encodings include null octets.
250 //
251 // No length octet follows the null item type octet, but additional null
252 // octets MUST be included if needed to pad until the next 32-bit
253 // boundary.
254 
255 let pad = 4 - abs % 4;
256 if pad < 4 && buf.len() < pad {
257 return Err("Not enough buf.len() for Sdes padding");
258 }
259 
260 break;
261 }
262 
263 let len = buf[1] as usize;
264 
265 if buf.len() < 2 + len {
266 return Err("Not enough buf.len() for Sdes value");
267 }
268 buf = &buf[2..];
269 abs += 2;
270 
271 if let Ok(value) = from_utf8(&buf[..len]) {
272 values.push((stype, value.to_string()));
273 } else {
274 // failed to read as utf-8. skip.
275 }
276 
277 buf = &buf[len..];
278 abs += len;
279 }
280 
281 Ok(Sdes { ssrc, values })
282 }
283}
284 
285#[cfg(test)]
286mod test {
287 use super::*;
288 
289 #[test]
290 fn computed_and_write_to_equal() {
291 let mut buf = vec![0; 1500];
292 
293 for i in 1usize..=255 {
294 let mut r = ReportList::new();
295 r.push((
296 SdesType::CNAME,
297 String::from_utf8_lossy(&vec![b'a'; i][..]).to_string(),
298 ));
299 let sdes = Sdes {
300 ssrc: 1.into(),
301 values: r,
302 };
303 assert_eq!(sdes.write_to(&mut buf), sdes.word_size() * 4);
304 }
305 }
306 
307 #[test]
308 fn cname_serialize_deserialize() {
309 let mut s1 = Sdes {
310 ssrc: 1.into(),
311 values: ReportList::new(),
312 };
313 s1.values.push((SdesType::CNAME, "abc123".into()));
314 
315 let mut buf = vec![0; 50];
316 let n = s1.write_to(&mut buf);
317 buf.truncate(n);
318 
319 println!("{buf:02x?}");
320 
321 let s2: Sdes = buf.as_slice().try_into().unwrap();
322 
323 assert_eq!(s1, s2);
324 }
325}