1use std::borrow::Cow;
4
5use crate::{
6 prelude::*,
7 utils::{pad_to_4bytes, parser, u32_from_be_bytes, writer},
8 RtcpPacket, RtcpPacketParser, RtcpPacketWriter, RtcpParseError, RtcpWriteError,
9};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct Bye<'a> {
14 data: &'a [u8],
15}
16
17impl RtcpPacket for Bye<'_> {
18 const MIN_PACKET_LEN: usize = 4;
19 const PACKET_TYPE: u8 = 203;
20}
21
22impl<'a> RtcpPacketParser<'a> for Bye<'a> {
23 fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
24 parser::check_packet::<Self>(data)?;
25
26 let reason_len_offset = Self::MIN_PACKET_LEN + 4 * parser::parse_count(data) as usize;
27 if reason_len_offset > data.len() {
28 return Err(RtcpParseError::Truncated {
29 expected: reason_len_offset,
30 actual: data.len(),
31 });
32 }
33
34 if reason_len_offset < data.len() {
35 let reason_len = data[reason_len_offset] as usize;
36 if reason_len_offset + 1 + reason_len > data.len() {
37 return Err(RtcpParseError::Truncated {
38 expected: reason_len_offset + 1 + reason_len,
39 actual: data.len(),
40 });
41 }
42 } Ok(Self { data })
45 }
46
47 #[inline(always)]
48 fn header_data(&self) -> [u8; 4] {
49 self.data[..4].try_into().unwrap()
50 }
51}
52
53impl<'a> Bye<'a> {
54 const MAX_SOURCES: u8 = Self::MAX_COUNT;
55 const MAX_REASON_LEN: u8 = 0xff;
56
57 pub fn padding(&self) -> Option<u8> {
59 parser::parse_padding(self.data)
60 }
61
62 pub fn ssrcs(&self) -> impl Iterator<Item = u32> + '_ {
64 self.data[4..4 + self.count() as usize * 4]
65 .chunks_exact(4)
66 .map(u32_from_be_bytes)
67 }
68
69 pub fn reason(&self) -> Option<&[u8]> {
71 let offset = self.count() as usize * 4 + 4;
72 let reason_aligned_len = self
73 .length()
74 .checked_sub(offset + 1 + self.padding().unwrap_or(0) as usize)?;
75
76 if reason_aligned_len == 0 {
77 return None;
78 }
79
80 let end = offset + 1 + self.data[offset] as usize;
81 Some(&self.data[offset + 1..end])
82 }
83
84 pub fn get_reason_string(&self) -> Option<Result<String, std::string::FromUtf8Error>> {
86 self.reason().map(|r| String::from_utf8(r.into()))
87 }
88
89 pub fn builder() -> ByeBuilder<'a> {
91 ByeBuilder::new()
92 }
93}
94
95#[derive(Debug)]
97#[must_use = "The builder must be built to be used"]
98pub struct ByeBuilder<'a> {
99 padding: u8,
100 sources: Vec<u32>,
101 reason: Cow<'a, str>,
102}
103
104impl<'a> ByeBuilder<'a> {
105 fn new() -> Self {
106 ByeBuilder {
107 padding: 0,
108 sources: Vec::with_capacity(Bye::MAX_SOURCES as usize),
109 reason: "".into(),
110 }
111 }
112
113 pub fn padding(mut self, padding: u8) -> Self {
115 self.padding = padding;
116 self
117 }
118
119 pub fn add_source(mut self, source: u32) -> Self {
121 self.sources.push(source);
122 self
123 }
124
125 pub fn reason(mut self, reason: impl Into<Cow<'a, str>>) -> Self {
127 self.reason = reason.into();
128 self
129 }
130
131 pub fn reason_owned(self, reason: impl Into<Cow<'a, str>>) -> ByeBuilder<'static> {
133 ByeBuilder {
134 padding: self.padding,
135 sources: self.sources,
136 reason: reason.into().into_owned().into(),
137 }
138 }
139}
140
141impl RtcpPacketWriter for ByeBuilder<'_> {
142 fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
150 if self.sources.len() > Bye::MAX_SOURCES as usize {
151 return Err(RtcpWriteError::TooManySources {
152 count: self.sources.len(),
153 max: Bye::MAX_SOURCES,
154 });
155 }
156
157 writer::check_padding(self.padding)?;
158
159 let mut size = Bye::MIN_PACKET_LEN + 4 * self.sources.len() + self.padding as usize;
160
161 if !self.reason.is_empty() {
162 let reason_len = self.reason.len();
163 if reason_len > Bye::MAX_REASON_LEN as usize {
164 return Err(RtcpWriteError::ReasonLenTooLarge {
165 len: reason_len,
166 max: Bye::MAX_REASON_LEN,
167 });
168 }
169
170 size += 1 + reason_len;
172 size = pad_to_4bytes(size);
174 }
175
176 Ok(size)
177 }
178
179 #[inline]
187 fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
188 let mut idx =
189 writer::write_header_unchecked::<Bye>(self.padding, self.sources.len() as u8, buf);
190
191 let mut end = idx;
192 for ssrc in self.sources.iter() {
193 end += 4;
194 buf[idx..end].copy_from_slice(&ssrc.to_be_bytes());
195 idx = end;
196 }
197
198 if !self.reason.is_empty() {
199 let reason = self.reason.as_bytes();
200 let reason_len = reason.len();
201
202 buf[idx] = reason_len as u8;
203 idx += 1;
204 end = idx + reason_len;
205 buf[idx..end].copy_from_slice(reason);
206 idx = end;
207 end = pad_to_4bytes(end);
209 if end > idx {
210 buf[idx..end].fill(0);
211 }
212 }
213
214 end += writer::write_padding_unchecked(self.padding, &mut buf[idx..]);
215
216 end
217 }
218
219 fn get_padding(&self) -> Option<u8> {
220 if self.padding == 0 {
221 return None;
222 }
223
224 Some(self.padding)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn parse_bye_empty() {
234 let bye = Bye::parse(&[0x80, 0xcb, 0x00, 0x00]).unwrap();
235 assert_eq!(bye.padding(), None);
236 assert_eq!(bye.count(), 0);
237 assert_eq!(bye.ssrcs().count(), 0);
238 assert!(bye.reason().is_none());
239 assert!(bye.get_reason_string().is_none());
240 }
241
242 #[test]
243 fn build_bye_empty() {
244 const REQ_LEN: usize = Bye::MIN_PACKET_LEN;
245 let byeb = Bye::builder();
246 let req_len = byeb.calculate_size().unwrap();
247 assert_eq!(req_len, REQ_LEN);
248
249 let mut data = [0; REQ_LEN];
250 let len = byeb.write_into(&mut data).unwrap();
251 assert_eq!(len, REQ_LEN);
252 assert_eq!(data, [0x80, 0xcb, 0x00, 0x00]);
253 }
254
255 #[test]
256 fn build_bye_static() {
257 const REQ_LEN: usize = Bye::MIN_PACKET_LEN + 4 + 4;
258
259 let byeb = {
260 let reason = &String::from("Bye");
261 Bye::builder().reason_owned(reason).add_source(0x12345678)
262 };
263 let req_len = byeb.calculate_size().unwrap();
264 assert_eq!(req_len, REQ_LEN);
265
266 let mut data = [0; REQ_LEN];
267 let len = byeb.write_into(&mut data).unwrap();
268 assert_eq!(len, REQ_LEN);
269 assert_eq!(
270 data,
271 [0x81, 0xcb, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x03, 0x42, 0x79, 0x65]
272 );
273 }
274
275 #[test]
276 fn parse_bye_3_sources() {
277 let bye = Bye::parse(&[
278 0x83, 0xcb, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x56, 0x78,
279 0x9a, 0xbc,
280 ])
281 .unwrap();
282 assert_eq!(bye.padding(), None);
283 assert_eq!(bye.count(), 3);
284
285 let mut ssrc_iter = bye.ssrcs();
286 assert_eq!(ssrc_iter.next(), Some(0x12345678));
287 assert_eq!(ssrc_iter.next(), Some(0x3456789a));
288 assert_eq!(ssrc_iter.next(), Some(0x56789abc));
289 assert!(ssrc_iter.next().is_none());
290
291 assert!(bye.reason().is_none());
292 assert!(bye.get_reason_string().is_none());
293 }
294
295 #[test]
296 fn build_bye_3_sources() {
297 const REQ_LEN: usize = Bye::MIN_PACKET_LEN + 3 * 4;
298 let byeb = Bye::builder()
299 .add_source(0x12345678)
300 .add_source(0x3456789a)
301 .add_source(0x56789abc);
302 let req_len = byeb.calculate_size().unwrap();
303 assert_eq!(req_len, REQ_LEN);
304
305 let mut data = [0; REQ_LEN];
306 let len = byeb.write_into(&mut data).unwrap();
307 assert_eq!(len, REQ_LEN);
308 assert_eq!(
309 data,
310 [
311 0x83, 0xcb, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x56, 0x78,
312 0x9a, 0xbc,
313 ]
314 );
315 }
316
317 #[test]
318 fn parse_bye_2_sources_reason() {
319 let bye = Bye::parse(&[
320 0x82, 0xcb, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x08, 0x53,
321 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x00, 0x00, 0x00,
322 ])
323 .unwrap();
324 assert_eq!(bye.padding(), None);
325 assert_eq!(bye.count(), 2);
326
327 let mut ssrc_iter = bye.ssrcs();
328 assert_eq!(ssrc_iter.next(), Some(0x12345678));
329 assert_eq!(ssrc_iter.next(), Some(0x3456789a));
330 assert!(ssrc_iter.next().is_none());
331
332 assert_eq!(String::from_utf8_lossy(bye.reason().unwrap()), "Shutdown");
333 }
334
335 #[test]
336 fn build_bye_2_sources_reason() {
337 const REASON: &str = "Shutdown";
338 const LEN: usize = Bye::MIN_PACKET_LEN + 2 * 4 + 1 + REASON.len();
339 const REQ_LEN: usize = pad_to_4bytes(LEN);
341 let byeb = Bye::builder()
342 .add_source(0x12345678)
343 .add_source(0x3456789a)
344 .reason(REASON);
345 let req_len = byeb.calculate_size().unwrap();
346 assert_eq!(req_len, REQ_LEN);
347
348 let mut data = [0; REQ_LEN];
349 let len = byeb.write_into(&mut data).unwrap();
350 assert_eq!(len, REQ_LEN);
351 assert_eq!(
352 data,
353 [
354 0x82, 0xcb, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x08, 0x53,
355 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x00, 0x00, 0x00
356 ]
357 );
358 }
359
360 #[test]
361 fn build_bye_2_sources_raw_reason() {
362 const REASON: &str = "Shutdown";
363 const LEN: usize = Bye::MIN_PACKET_LEN + 2 * 4 + 1 + REASON.len();
364 const REQ_LEN: usize = pad_to_4bytes(LEN);
366 let byeb = Bye::builder()
367 .add_source(0x12345678)
368 .add_source(0x3456789a)
369 .reason(REASON);
370 let req_len = byeb.calculate_size().unwrap();
371 assert_eq!(req_len, REQ_LEN);
372
373 let mut data = [0; REQ_LEN];
374 let len = byeb.write_into(&mut data).unwrap();
375 assert_eq!(len, REQ_LEN);
376 assert_eq!(
377 data,
378 [
379 0x82, 0xcb, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x34, 0x56, 0x78, 0x9a, 0x08, 0x53,
380 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x00, 0x00, 0x00
381 ]
382 );
383 }
384
385 #[test]
386 fn build_too_many_sources() {
387 let mut b = Bye::builder();
388 for _ in 0..Bye::MAX_SOURCES as usize + 1 {
389 b = b.add_source(0)
390 }
391 let err = b.calculate_size().unwrap_err();
392 assert_eq!(
393 err,
394 RtcpWriteError::TooManySources {
395 count: Bye::MAX_SOURCES as usize + 1,
396 max: Bye::MAX_SOURCES
397 }
398 );
399 }
400
401 #[test]
402 fn build_reason_too_large() {
403 let reason: String =
404 String::from_utf8([b'a'; Bye::MAX_REASON_LEN as usize + 1].into()).unwrap();
405 let b = Bye::builder().reason(&reason);
406 let err = b.calculate_size().unwrap_err();
407 assert_eq!(
408 err,
409 RtcpWriteError::ReasonLenTooLarge {
410 len: Bye::MAX_REASON_LEN as usize + 1,
411 max: Bye::MAX_REASON_LEN
412 }
413 );
414 }
415
416 #[test]
417 fn build_padding_not_multiple_4() {
418 let b = Bye::builder().padding(5);
419 let err = b.calculate_size().unwrap_err();
420 assert_eq!(err, RtcpWriteError::InvalidPadding { padding: 5 });
421 }
422}