1use std::cmp;
4
5use crate::feedback::FciFeedbackPacketType;
6use crate::prelude::*;
7use crate::utils::{pad_to_4bytes, u16_from_be_bytes};
8use crate::{RtcpParseError, RtcpWriteError};
9
10pub const TWCC_MAX_REFERENCE_TIME: u32 = (1 << 24) - 1;
14
15#[derive(Debug)]
17pub struct Twcc<'a> {
18 data: &'a [u8],
19}
20
21impl<'a> Twcc<'a> {
22 pub fn builder(
24 base_seq: u16,
25 reference_time: u32,
26 feedback_packet_count: u8,
27 status_list: &[TwccPacketStatus],
28 max_size: Option<usize>,
29 ) -> TwccBuilder {
30 TwccBuilder::new(
31 base_seq,
32 reference_time,
33 feedback_packet_count,
34 status_list,
35 max_size,
36 )
37 }
38
39 pub fn base_sequence_number(&self) -> u16 {
43 u16_from_be_bytes(&self.data[0..2])
44 }
45
46 pub fn reference_time(&self) -> u32 {
56 u32::from_be_bytes([0, self.data[4], self.data[5], self.data[6]])
57 }
58
59 pub fn feedback_packet_count(&self) -> u8 {
61 self.data[7]
62 }
63
64 fn packet_status_count(&self) -> u16 {
65 u16_from_be_bytes(&self.data[2..4])
66 }
67
68 fn packet_chunks(&self) -> impl Iterator<Item = PacketStatusChunk> + 'a {
69 let mut remaining_status_count = self.packet_status_count();
70
71 self.data[8..].chunks_exact(2).map_while(move |chunk| {
72 if remaining_status_count == 0 {
73 return None;
74 }
75
76 let chunk = u16_from_be_bytes(chunk);
77
78 let chunk = if chunk & (1 << 15) == 0 {
79 PacketStatusChunk::RunLength(StatusBits::from_two_bits(chunk >> 13), chunk & 0x1FFF)
80 } else if chunk & (1 << 14) == 0 {
81 PacketStatusChunk::Vector1Bit(chunk & 0x3FFF)
82 } else {
83 PacketStatusChunk::Vector2Bit(chunk & 0x3FFF)
84 };
85
86 remaining_status_count = remaining_status_count.saturating_sub(chunk.max_len());
87
88 Some(chunk)
89 })
90 }
91
92 pub fn packets(
97 &self,
98 ) -> impl Iterator<Item = Result<(u16, TwccPacketStatus), RtcpParseError>> + 'a {
99 let mut remaining_packet_status_count = self.packet_status_count();
100 let states = self.packet_chunks().flat_map(move |chunk| {
101 let packet_status_iter = chunk
102 .packet_status_iter()
103 .take(remaining_packet_status_count.into());
104
105 remaining_packet_status_count =
106 remaining_packet_status_count.saturating_sub(chunk.max_len());
107
108 packet_status_iter
109 });
110
111 let packet_chunks_count = self.packet_chunks().count();
112 let mut deltas = self.data[8 + packet_chunks_count * 2..].iter();
113
114 let mut sequence_number = self.base_sequence_number();
115
116 states.map(move |status_bits| -> Result<_, RtcpParseError> {
117 let packet_sequence_number = sequence_number;
118 sequence_number = sequence_number.wrapping_add(1);
119
120 let packet_status = match status_bits {
121 StatusBits::NotReceived => TwccPacketStatus::NotReceived,
122 StatusBits::ReceivedSmallDelta => {
123 let delta_byte = *deltas.next().ok_or(RtcpParseError::TwccDeltaTruncated)?;
125
126 TwccPacketStatus::Received {
127 delta: i16::from(delta_byte),
128 }
129 }
130 StatusBits::ReceivedLargeOrNegativeDelta => {
131 let delta_byte0 = *deltas.next().ok_or(RtcpParseError::TwccDeltaTruncated)?;
133 let delta_byte1 = *deltas.next().ok_or(RtcpParseError::TwccDeltaTruncated)?;
134
135 TwccPacketStatus::Received {
136 delta: i16::from_be_bytes([delta_byte0, delta_byte1]),
137 }
138 }
139 StatusBits::Reserved => return Err(RtcpParseError::TwccReservedPacketStatus),
140 };
141
142 Ok((packet_sequence_number, packet_status))
143 })
144 }
145}
146
147impl<'a> FciParser<'a> for Twcc<'a> {
148 const PACKET_TYPE: FciFeedbackPacketType = FciFeedbackPacketType::TRANSPORT;
149 const FCI_FORMAT: u8 = 15;
150
151 fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
152 if data.len() < 8 {
153 return Err(RtcpParseError::Truncated {
154 expected: 8,
155 actual: data.len(),
156 });
157 }
158 Ok(Self { data })
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum TwccPacketStatus {
165 NotReceived,
167
168 Received {
170 delta: i16,
174 },
175}
176
177impl TwccPacketStatus {
178 fn to_bits(self) -> StatusBits {
179 match self {
180 TwccPacketStatus::NotReceived => StatusBits::NotReceived,
181 TwccPacketStatus::Received { delta } => {
182 if (0..=255).contains(&delta) {
183 StatusBits::ReceivedSmallDelta
184 } else {
185 StatusBits::ReceivedLargeOrNegativeDelta
186 }
187 }
188 }
189 }
190}
191
192#[derive(Debug)]
194pub struct TwccBuilder {
195 base_seq: u16,
196 reference_time: u32,
197 feedback_packet_count: u8,
198 packet_status_count: u16,
199 chunks: Vec<PacketStatusChunk>,
200 deltas: Vec<u8>,
201}
202
203impl TwccBuilder {
204 pub fn new(
210 base_seq: u16,
211 reference_time: u32,
212 feedback_packet_count: u8,
213 status_list: &[TwccPacketStatus],
214 max_size: Option<usize>,
215 ) -> TwccBuilder {
216 let mut this = TwccBuilder {
217 base_seq,
218 reference_time,
219 feedback_packet_count,
220 packet_status_count: 0,
221 chunks: Vec::new(),
222 deltas: Vec::new(),
223 };
224
225 this.set_status_list(status_list, max_size);
226
227 this
228 }
229
230 fn set_status_list(&mut self, mut status_list: &[TwccPacketStatus], max_size: Option<usize>) {
231 while let Some((mut chunk, mut consumed)) =
232 PacketStatusChunk::from_packet_status_list(status_list)
233 {
234 if let Some(max_size) = max_size {
236 let projected_size = self.calculate_projected_size(status_list, consumed);
237
238 if projected_size > max_size {
239 if let PacketStatusChunk::RunLength(
244 status_bits @ (StatusBits::ReceivedSmallDelta
245 | StatusBits::ReceivedLargeOrNegativeDelta),
246 run_length,
247 ) = &mut chunk
248 {
249 let bytes_per_delta = match status_bits {
250 StatusBits::ReceivedSmallDelta => 1,
251 StatusBits::ReceivedLargeOrNegativeDelta => 2,
252 _ => unreachable!(),
253 };
254
255 let overshoot = pad_to_4bytes(projected_size - max_size);
256 if overshoot / bytes_per_delta > usize::from(*run_length - 1) {
257 return;
258 }
259
260 *run_length -= (overshoot / bytes_per_delta) as u16;
261 consumed -= overshoot / bytes_per_delta;
262 } else {
263 return;
264 }
265 }
266 }
267
268 let packet_status_count = match self.packet_status_count.checked_add(consumed as u16) {
270 Some(packet_status_count) => packet_status_count,
271 _ => {
272 return;
273 }
274 };
275
276 self.packet_status_count = packet_status_count;
277 self.chunks.push(chunk);
278
279 for packet_status in &status_list[..consumed] {
281 match *packet_status {
282 TwccPacketStatus::NotReceived => {
283 }
285 TwccPacketStatus::Received { delta } => {
286 if let Ok(delta) = u8::try_from(delta) {
287 self.deltas.push(delta);
288 } else {
289 self.deltas.extend(delta.to_be_bytes());
290 }
291 }
292 }
293 }
294
295 status_list = &status_list[consumed..];
296 }
297 }
298
299 fn calculate_projected_size(
300 &mut self,
301 status_list: &[TwccPacketStatus],
302 consumed: usize,
303 ) -> usize {
304 let additional_deltas_size: usize = status_list
305 .iter()
306 .take(consumed)
307 .map(|packet_status| match packet_status.to_bits() {
308 StatusBits::ReceivedSmallDelta => 1,
309 StatusBits::ReceivedLargeOrNegativeDelta => 2,
310 _ => 0,
311 })
312 .sum();
313
314 let additional_size = additional_deltas_size + 2;
315 let current_size = self.chunks.len() * 2 + self.deltas.len();
316
317 pad_to_4bytes(8 + current_size + additional_size)
318 }
319
320 pub fn packet_status_count(&self) -> usize {
322 usize::from(self.packet_status_count)
323 }
324}
325
326impl FciBuilder<'_> for TwccBuilder {
327 fn format(&self) -> u8 {
328 Twcc::FCI_FORMAT
329 }
330
331 fn supports_feedback_type(&self) -> FciFeedbackPacketType {
332 Twcc::PACKET_TYPE
333 }
334}
335
336impl RtcpPacketWriter for TwccBuilder {
337 fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
338 if self.reference_time > TWCC_MAX_REFERENCE_TIME {
339 return Err(RtcpWriteError::TwccReferenceTimeTooLarge);
340 }
341
342 let packet_chunks = self.chunks.len() * 2;
343 let deltas = self.deltas.len();
344
345 Ok(pad_to_4bytes(8 + packet_chunks + deltas))
346 }
347
348 fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
349 buf[0..2].copy_from_slice(&self.base_seq.to_be_bytes());
350 buf[2..4].copy_from_slice(&self.packet_status_count.to_be_bytes());
351 buf[4..7].copy_from_slice(&self.reference_time.to_be_bytes()[1..]);
352 buf[7] = self.feedback_packet_count;
353
354 let mut idx = 8;
355
356 for chunk in &self.chunks {
357 buf[idx..(idx + 2)].copy_from_slice(&chunk.to_u16().to_be_bytes());
358 idx += 2;
359 }
360
361 buf[idx..idx + self.deltas.len()].copy_from_slice(&self.deltas);
362
363 pad_to_4bytes(idx + self.deltas.len())
364 }
365
366 fn get_padding(&self) -> Option<u8> {
367 None
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372enum PacketStatusChunk {
373 RunLength(StatusBits, u16),
375
376 Vector1Bit(u16),
378
379 Vector2Bit(u16),
381}
382
383impl PacketStatusChunk {
384 fn from_packet_status_list(
388 status_list: &[TwccPacketStatus],
389 ) -> Option<(PacketStatusChunk, usize)> {
390 const RUN_LENGTH_MINIMUM: usize = 7;
391 const CUTOFF_1BIT: usize = 14;
392 const CUTOFF_2BIT: usize = 7;
393
394 let first_status_bits = status_list.first()?.to_bits();
395 let run_length = status_list
396 .iter()
397 .take_while(|packet_status| packet_status.to_bits() == first_status_bits)
398 .take(0x1FFF)
399 .count();
400
401 let num_one_bit_status = status_list
402 .iter()
403 .take_while(|packet_status| packet_status.to_bits().is_one_bit())
404 .take(14)
405 .count();
406
407 if run_length > RUN_LENGTH_MINIMUM && run_length >= num_one_bit_status {
408 Some((
411 PacketStatusChunk::RunLength(first_status_bits, run_length as u16),
412 run_length,
413 ))
414 } else if (status_list.len() == num_one_bit_status && num_one_bit_status > CUTOFF_2BIT)
415 || num_one_bit_status == CUTOFF_1BIT
416 {
417 let num_one_bit_status = cmp::min(num_one_bit_status, CUTOFF_1BIT);
420
421 let mut bits = 0u16;
422
423 for (i, status) in status_list.iter().take(num_one_bit_status).enumerate() {
424 debug_assert!(status.to_bits().is_one_bit());
425 bits |= (status.to_bits() as u16) << (CUTOFF_1BIT - (i + 1));
426 }
427
428 Some((PacketStatusChunk::Vector1Bit(bits), num_one_bit_status))
429 } else {
430 let mut bits = 0u16;
433
434 for (i, status) in status_list.iter().take(CUTOFF_2BIT).enumerate() {
435 bits |= (status.to_bits() as u16) << ((CUTOFF_2BIT - (i + 1)) * 2);
436 }
437
438 Some((
439 PacketStatusChunk::Vector2Bit(bits),
440 status_list.len().min(CUTOFF_2BIT),
441 ))
442 }
443 }
444
445 fn max_len(&self) -> u16 {
447 match self {
448 PacketStatusChunk::RunLength(.., len) => *len,
449 PacketStatusChunk::Vector1Bit(_) => 14,
450 PacketStatusChunk::Vector2Bit(_) => 7,
451 }
452 }
453
454 fn packet_status_iter(mut self) -> impl Iterator<Item = StatusBits> {
455 (0..self.max_len()).map_while(move |offset| {
456 let status = match &mut self {
457 PacketStatusChunk::RunLength(status, len) => {
458 *len -= 1;
459 *status
460 }
461 PacketStatusChunk::Vector1Bit(bits) => {
462 StatusBits::from_one_bit(*bits >> (13 - offset))
463 }
464 PacketStatusChunk::Vector2Bit(bits) => {
465 StatusBits::from_two_bits(*bits >> (12 - (offset * 2)))
466 }
467 };
468
469 Some(status)
470 })
471 }
472
473 fn to_u16(self) -> u16 {
474 match self {
475 PacketStatusChunk::RunLength(status, run_length) => {
476 ((status as u16) << 13) | (run_length & 0x1F_FF)
483 }
484 PacketStatusChunk::Vector1Bit(bits) => {
485 0x8000 | (bits & 0x3F_FF)
492 }
493 PacketStatusChunk::Vector2Bit(bits) => {
494 0xC000 | (bits & 0x3F_FF)
501 }
502 }
503 }
504}
505
506#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
507#[repr(u16)]
508enum StatusBits {
509 NotReceived = 0,
510 ReceivedSmallDelta = 1,
511 ReceivedLargeOrNegativeDelta = 2,
512 Reserved = 3,
513}
514
515impl StatusBits {
516 fn from_two_bits(bits: u16) -> StatusBits {
517 match bits & 0b11 {
518 0 => StatusBits::NotReceived,
519 1 => StatusBits::ReceivedSmallDelta,
520 2 => StatusBits::ReceivedLargeOrNegativeDelta,
521 3 => StatusBits::Reserved,
522 _ => unreachable!(),
523 }
524 }
525
526 fn from_one_bit(bit: u16) -> StatusBits {
527 match bit & 0b1 {
528 0 => StatusBits::NotReceived,
529 1 => StatusBits::ReceivedSmallDelta,
530 _ => unreachable!(),
531 }
532 }
533
534 fn is_one_bit(&self) -> bool {
535 matches!(
536 self,
537 StatusBits::NotReceived | StatusBits::ReceivedSmallDelta
538 )
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use rand::{random, Rng};
546
547 #[test]
548 fn parse_packet_chunk() {
549 use PacketStatusChunk as C;
550 use StatusBits as B;
551
552 assert!(C::RunLength(B::NotReceived, 64)
553 .packet_status_iter()
554 .all(|c| c == StatusBits::NotReceived));
555 assert_eq!(
556 C::RunLength(B::NotReceived, 64)
557 .packet_status_iter()
558 .count(),
559 64
560 );
561
562 assert_eq!(
563 C::Vector1Bit(0b00_10_01_01_10_00_00_00)
564 .packet_status_iter()
565 .collect::<Vec<_>>(),
566 [
567 B::ReceivedSmallDelta,
568 B::NotReceived,
569 B::NotReceived,
570 B::ReceivedSmallDelta,
571 B::NotReceived,
572 B::ReceivedSmallDelta,
573 B::ReceivedSmallDelta,
574 B::NotReceived,
575 B::NotReceived,
576 B::NotReceived,
577 B::NotReceived,
578 B::NotReceived,
579 B::NotReceived,
580 B::NotReceived,
581 ],
582 );
583
584 assert_eq!(
585 C::Vector2Bit(0b00_10_01_01_10_00_11_00)
586 .packet_status_iter()
587 .take(6)
588 .collect::<Vec<_>>(),
589 [
590 B::ReceivedLargeOrNegativeDelta,
591 B::ReceivedSmallDelta,
592 B::ReceivedSmallDelta,
593 B::ReceivedLargeOrNegativeDelta,
594 B::NotReceived,
595 B::Reserved,
596 ],
597 );
598 }
599
600 #[test]
601 fn serialize_packet_chunk() {
602 use PacketStatusChunk as C;
603 use StatusBits as B;
604
605 assert_eq!(
606 C::RunLength(B::ReceivedSmallDelta, 64).to_u16(),
607 0b0010_0000_0100_0000
608 );
609 assert_eq!(
610 C::RunLength(B::NotReceived, 256).to_u16(),
611 0b0000_0001_0000_0000
612 );
613 assert_eq!(
614 C::RunLength(B::ReceivedLargeOrNegativeDelta, 1024).to_u16(),
615 0b0100_0100_0000_0000
616 );
617
618 assert_eq!(
619 C::Vector1Bit(0b0011_0011_0011_0011).to_u16(),
620 0b1011_0011_0011_0011
621 );
622
623 assert_eq!(
624 C::Vector1Bit(0b0000_1100_1100_1100).to_u16(),
625 0b1000_1100_1100_1100
626 );
627
628 assert_eq!(
629 C::Vector2Bit(0b0011_0011_0011_0011).to_u16(),
630 0b1111_0011_0011_0011
631 );
632 assert_eq!(
633 C::Vector2Bit(0b0000_1100_1100_1100).to_u16(),
634 0b1100_1100_1100_1100
635 );
636 }
637
638 #[test]
639 fn packet_chunk_from_status() {
640 let (chunk, consumed) =
641 PacketStatusChunk::from_packet_status_list(&[TwccPacketStatus::NotReceived]).unwrap();
642 assert_eq!(consumed, 1);
643 assert_eq!(chunk, PacketStatusChunk::Vector2Bit(0));
644
645 let (chunk, consumed) = PacketStatusChunk::from_packet_status_list(&[
646 TwccPacketStatus::Received { delta: 0 },
647 TwccPacketStatus::NotReceived,
648 TwccPacketStatus::Received { delta: -1 },
649 ])
650 .unwrap();
651 assert_eq!(consumed, 3);
652 assert_eq!(
653 chunk,
654 PacketStatusChunk::Vector2Bit(0b00_01_00_10_00_00_00_00)
655 );
656
657 let (chunk, consumed) = PacketStatusChunk::from_packet_status_list(&[
659 TwccPacketStatus::Received { delta: 0 },
660 TwccPacketStatus::NotReceived,
661 TwccPacketStatus::Received { delta: -1 },
662 TwccPacketStatus::NotReceived,
663 TwccPacketStatus::NotReceived,
664 TwccPacketStatus::NotReceived,
665 TwccPacketStatus::Received { delta: 1 },
666 TwccPacketStatus::NotReceived,
667 TwccPacketStatus::NotReceived,
668 ])
669 .unwrap();
670 assert_eq!(consumed, 7);
671 assert_eq!(
672 chunk,
673 PacketStatusChunk::Vector2Bit(0b00_01_00_10_00_00_00_01)
674 );
675
676 let (chunk, consumed) = PacketStatusChunk::from_packet_status_list(&[
678 TwccPacketStatus::Received { delta: 0 },
679 TwccPacketStatus::NotReceived,
680 TwccPacketStatus::Received { delta: 1 },
681 TwccPacketStatus::NotReceived,
682 TwccPacketStatus::NotReceived,
683 TwccPacketStatus::NotReceived,
684 TwccPacketStatus::Received { delta: 0 },
685 TwccPacketStatus::NotReceived,
686 TwccPacketStatus::NotReceived,
687 ])
688 .unwrap();
689 assert_eq!(consumed, 9);
690 assert_eq!(
691 chunk,
692 PacketStatusChunk::Vector1Bit(0b00_10_10_00_10_00_00_00)
693 );
694
695 let mut status = vec![TwccPacketStatus::NotReceived; 26];
697 status.push(TwccPacketStatus::Received { delta: -1 });
698
699 let (chunk, consumed) = PacketStatusChunk::from_packet_status_list(&status).unwrap();
700 assert_eq!(consumed, 26);
701 assert_eq!(
702 chunk,
703 PacketStatusChunk::RunLength(StatusBits::NotReceived, 26)
704 );
705 }
706
707 fn build_and_parse_all(mut status_list: &[TwccPacketStatus], max_size: Option<usize>) {
708 let mut base_seq = rand::random::<u16>();
709
710 while !status_list.is_empty() {
711 let fci = Twcc::builder(base_seq, 0, rand::random(), status_list, max_size);
712
713 let consumed = fci.packet_status_count();
714 assert_ne!(consumed, 0);
715
716 let size = fci.calculate_size().unwrap();
717 if let Some(max_size) = max_size {
718 assert!(size <= max_size, "max_size: {max_size}, size: {size}");
719 }
720
721 let mut buf = vec![0u8; size];
722 fci.write_into(&mut buf).unwrap();
723
724 let twcc = Twcc::parse(&buf).unwrap();
725 assert_eq!(
726 twcc.packets()
727 .enumerate()
728 .map(|(i, result)| {
729 let (seq, p) = result.unwrap();
730 let expected_seq = base_seq.wrapping_add(i.try_into().unwrap());
731 assert_eq!(seq, expected_seq);
732 p
733 })
734 .collect::<Vec<_>>(),
735 status_list[..consumed],
736 );
737
738 base_seq = base_seq.wrapping_add(consumed.try_into().unwrap());
739 status_list = &status_list[consumed..];
740 }
741 }
742
743 #[test]
744 fn random_permutations() {
745 let mut status_list = Vec::new();
746
747 for _ in 0..100 {
748 status_list.clear();
749
750 let len = rand::thread_rng().gen_range(200..1000);
751
752 for _ in 0..len {
753 if rand::thread_rng().gen_bool(0.05) {
754 status_list.extend(std::iter::repeat_n(
755 TwccPacketStatus::NotReceived,
756 rand::thread_rng().gen_range(1..3000),
757 ));
758 } else if rand::thread_rng().gen_bool(0.8) {
759 status_list.push(TwccPacketStatus::Received {
760 delta: rand::thread_rng().gen_range(0..20),
761 });
762 } else {
763 status_list.push(TwccPacketStatus::Received { delta: random() });
764 }
765 }
766
767 build_and_parse_all(&status_list, Some(rand::thread_rng().gen_range(800..1500)));
768 }
769 }
770
771 #[test]
772 fn too_many_deltas_for_max_size() {
773 const MAX_SIZE_FOR_1000_STATUS: usize = 1012;
774
775 let status_list = vec![TwccPacketStatus::Received { delta: 0 }; 2000];
776
777 let builder = TwccBuilder::new(0, 0, 0, &status_list, Some(MAX_SIZE_FOR_1000_STATUS));
778
779 assert_eq!(builder.packet_status_count(), 1000);
780
781 let builder = TwccBuilder::new(
782 0,
783 0,
784 0,
785 &status_list[builder.packet_status_count()..],
786 Some(MAX_SIZE_FOR_1000_STATUS),
787 );
788
789 assert_eq!(builder.packet_status_count(), 1000);
790 }
791
792 #[test]
793 fn missing_deltas() {
794 let status_list = vec![TwccPacketStatus::Received { delta: 123 }; 5];
795 let builder = TwccBuilder::new(100, 0, 0, &status_list, None);
796
797 let mut buffer = vec![0u8; builder.calculate_size().unwrap()];
798 builder.write_into(&mut buffer).unwrap();
799
800 buffer.truncate(buffer.len() - 3);
802
803 let parsed = Twcc::parse(&buffer).unwrap();
804 let packets = parsed.packets().collect::<Vec<_>>();
805
806 assert!(matches!(
807 packets[0],
808 Ok((100, TwccPacketStatus::Received { delta: 123 }))
809 ));
810 assert!(matches!(
811 packets[1],
812 Ok((101, TwccPacketStatus::Received { delta: 123 }))
813 ));
814 assert!(matches!(
815 packets[2],
816 Ok((102, TwccPacketStatus::Received { delta: 123 }))
817 ));
818 assert!(matches!(
819 packets[3],
820 Err(RtcpParseError::TwccDeltaTruncated)
821 ));
822 assert!(matches!(
823 packets[4],
824 Err(RtcpParseError::TwccDeltaTruncated)
825 ));
826 }
827}