1use crate::error::{Error, Result};
4
5pub const TS_PACKET_SIZE: usize = 188;
7pub const TS_SYNC_BYTE: u8 = 0x47;
9const MAX_SECTION_SIZE: usize = 4098;
14const SECTION_HEADER_LEN: usize = 3;
18pub(crate) const SECTION_LENGTH_HI_MASK: u8 = 0x0F;
22
23const TEI_MASK: u8 = 0x80;
25const PUSI_MASK: u8 = 0x40;
27pub const PID_MASK_HI: u8 = 0x1F;
29pub const SCRAMBLING_MASK: u8 = 0xC0;
31pub const ADAPTATION_FLAG: u8 = 0x20;
33pub const PAYLOAD_FLAG: u8 = 0x10;
35pub const CC_MASK: u8 = 0x0F;
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize))]
41pub struct TsHeader {
42 pub tei: bool,
45 pub pusi: bool,
48 pub pid: u16,
50 pub scrambling: u8,
52 pub has_adaptation: bool,
54 pub has_payload: bool,
56 pub continuity_counter: u8,
58}
59
60#[derive(Clone, Debug)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67pub struct TsPacket<'a> {
68 pub header: TsHeader,
70 pub payload: Option<&'a [u8]>,
73 #[cfg_attr(feature = "serde", serde(skip))]
76 adaptation: Option<&'a [u8]>,
77 #[cfg_attr(feature = "serde", serde(skip))]
79 pub raw: &'a [u8; TS_PACKET_SIZE],
80}
81
82impl TsHeader {
83 pub fn parse(raw4: &[u8]) -> Result<Self> {
85 if raw4.len() < 4 {
86 return Err(Error::BufferTooShort {
87 need: 4,
88 have: raw4.len(),
89 what: "TsHeader",
90 });
91 }
92 let b1 = raw4[1];
93 let b2 = raw4[2];
94 let b3 = raw4[3];
95
96 let tei = (b1 & TEI_MASK) != 0;
97 let pusi = (b1 & PUSI_MASK) != 0;
98 let pid = (((b1 & PID_MASK_HI) as u16) << 8) | (b2 as u16);
99 let scrambling = (b3 & SCRAMBLING_MASK) >> 6;
100 let has_adaptation = (b3 & ADAPTATION_FLAG) != 0;
101 let has_payload = (b3 & PAYLOAD_FLAG) != 0;
102 let continuity_counter = b3 & CC_MASK;
103
104 Ok(Self {
105 tei,
106 pusi,
107 pid,
108 scrambling,
109 has_adaptation,
110 has_payload,
111 continuity_counter,
112 })
113 }
114
115 pub const fn serialized_len() -> usize {
117 4
118 }
119
120 pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
122 if buf.len() < 4 {
123 return Err(Error::OutputBufferTooSmall {
124 need: 4,
125 have: buf.len(),
126 });
127 }
128 buf[0] = TS_SYNC_BYTE;
129 buf[1] = 0;
130 if self.tei {
131 buf[1] |= TEI_MASK;
132 }
133 if self.pusi {
134 buf[1] |= PUSI_MASK;
135 }
136 buf[1] |= ((self.pid >> 8) as u8) & PID_MASK_HI;
137 buf[2] = (self.pid & 0xFF) as u8;
138 buf[3] = (self.scrambling << 6) & SCRAMBLING_MASK;
139 if self.has_adaptation {
140 buf[3] |= ADAPTATION_FLAG;
141 }
142 if self.has_payload {
143 buf[3] |= PAYLOAD_FLAG;
144 }
145 buf[3] |= self.continuity_counter & CC_MASK;
146 Ok(4)
147 }
148}
149
150impl<'a> dvb_common::Parse<'a> for TsHeader {
151 type Error = Error;
152
153 fn parse(bytes: &'a [u8]) -> Result<Self> {
154 TsHeader::parse(bytes)
155 }
156}
157
158impl dvb_common::Serialize for TsHeader {
159 type Error = Error;
160
161 fn serialized_len(&self) -> usize {
162 TsHeader::serialized_len()
163 }
164
165 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
166 TsHeader::serialize_into(self, buf)
167 }
168}
169
170impl<'a> TsPacket<'a> {
171 pub fn parse(buf: &'a [u8]) -> Result<Self> {
177 if buf.len() < TS_PACKET_SIZE {
178 return Err(Error::BufferTooShort {
179 need: TS_PACKET_SIZE,
180 have: buf.len(),
181 what: "TsPacket",
182 });
183 }
184 if buf[0] != TS_SYNC_BYTE {
185 return Err(Error::InvalidSyncByte { found: buf[0] });
186 }
187
188 let raw: &[u8; TS_PACKET_SIZE] =
189 buf[..TS_PACKET_SIZE]
190 .try_into()
191 .map_err(|_| Error::BufferTooShort {
192 need: TS_PACKET_SIZE,
193 have: buf.len(),
194 what: "TsPacket::parse (array conversion)",
195 })?;
196
197 let header = TsHeader::parse(&raw[..4])?;
198
199 let mut cursor = 4usize;
200 let mut payload = None;
201 let mut adaptation = None;
202
203 if header.has_adaptation && cursor < TS_PACKET_SIZE {
206 let af_len = raw[cursor] as usize;
207 let af_start = cursor + 1;
208 if af_len > 0 && af_start < TS_PACKET_SIZE {
209 let af_end = (af_start + af_len).min(TS_PACKET_SIZE);
210 adaptation = Some(&raw[af_start..af_end]);
211 }
212 cursor += 1 + af_len;
213 }
214
215 if header.has_payload && cursor < TS_PACKET_SIZE {
216 payload = Some(&raw[cursor..]);
217 }
218
219 Ok(TsPacket {
220 header,
221 payload,
222 adaptation,
223 raw,
224 })
225 }
226
227 pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField>> {
233 self.adaptation.map(AdaptationField::parse)
234 }
235}
236
237const AF_DISCONTINUITY: u8 = 0x80;
239const AF_RANDOM_ACCESS: u8 = 0x40;
240const AF_ES_PRIORITY: u8 = 0x20;
241const AF_PCR_FLAG: u8 = 0x10;
242const AF_OPCR_FLAG: u8 = 0x08;
243const AF_SPLICING_FLAG: u8 = 0x04;
244const PCR_FIELD_LEN: usize = 6;
246
247#[derive(Clone, Copy, Debug, PartialEq, Eq)]
250#[cfg_attr(feature = "serde", derive(serde::Serialize))]
251pub struct Pcr {
252 pub base: u64,
254 pub extension: u16,
256}
257
258impl Pcr {
259 #[must_use]
261 pub fn as_27mhz(self) -> u64 {
262 self.base * 300 + self.extension as u64
263 }
264
265 fn parse(af: &[u8], at: usize) -> Result<Self> {
267 let b: &[u8; PCR_FIELD_LEN] = af
268 .get(at..at + PCR_FIELD_LEN)
269 .and_then(|s| s.try_into().ok())
270 .ok_or(Error::BufferTooShort {
271 need: at + PCR_FIELD_LEN,
272 have: af.len(),
273 what: "adaptation_field PCR",
274 })?;
275 let base = ((b[0] as u64) << 25)
276 | ((b[1] as u64) << 17)
277 | ((b[2] as u64) << 9)
278 | ((b[3] as u64) << 1)
279 | ((b[4] as u64) >> 7);
280 let extension = (((b[4] & 0x01) as u16) << 8) | (b[5] as u16);
281 Ok(Self { base, extension })
282 }
283}
284
285#[non_exhaustive]
290#[derive(Clone, Copy, Debug, PartialEq, Eq)]
291#[cfg_attr(feature = "serde", derive(serde::Serialize))]
292pub struct AdaptationField {
293 pub discontinuity_indicator: bool,
295 pub random_access_indicator: bool,
297 pub elementary_stream_priority_indicator: bool,
299 pub pcr: Option<Pcr>,
301 pub opcr: Option<Pcr>,
303 pub splice_countdown: Option<i8>,
305}
306
307impl AdaptationField {
308 fn parse(af: &[u8]) -> Result<Self> {
310 let flags = *af.first().ok_or(Error::BufferTooShort {
311 need: 1,
312 have: 0,
313 what: "adaptation_field flags",
314 })?;
315 let mut cursor = 1usize;
316
317 let pcr = if flags & AF_PCR_FLAG != 0 {
318 let p = Pcr::parse(af, cursor)?;
319 cursor += PCR_FIELD_LEN;
320 Some(p)
321 } else {
322 None
323 };
324 let opcr = if flags & AF_OPCR_FLAG != 0 {
325 let p = Pcr::parse(af, cursor)?;
326 cursor += PCR_FIELD_LEN;
327 Some(p)
328 } else {
329 None
330 };
331 let splice_countdown = if flags & AF_SPLICING_FLAG != 0 {
332 let b = *af.get(cursor).ok_or(Error::BufferTooShort {
333 need: cursor + 1,
334 have: af.len(),
335 what: "adaptation_field splice_countdown",
336 })?;
337 Some(b as i8)
338 } else {
339 None
340 };
341
342 Ok(AdaptationField {
343 discontinuity_indicator: flags & AF_DISCONTINUITY != 0,
344 random_access_indicator: flags & AF_RANDOM_ACCESS != 0,
345 elementary_stream_priority_indicator: flags & AF_ES_PRIORITY != 0,
346 pcr,
347 opcr,
348 splice_countdown,
349 })
350 }
351}
352
353#[derive(Default)]
358pub struct SectionReassembler {
359 buf: bytes::BytesMut,
360 ready: std::collections::VecDeque<bytes::Bytes>,
361}
362
363impl SectionReassembler {
364 pub fn feed(&mut self, payload: &[u8], pusi: bool) {
372 if pusi {
373 if payload.is_empty() {
376 self.buf.clear();
377 return;
378 }
379 let pointer = payload[0] as usize;
380
381 if !self.buf.is_empty() && pointer > 0 {
389 let avail = payload.len() - 1;
390 let tail_len = pointer.min(avail);
391 if self.buf.len() + tail_len > MAX_SECTION_SIZE {
392 self.buf.clear();
393 } else {
394 self.buf.extend_from_slice(&payload[1..1 + tail_len]);
395 self.drain_complete_sections();
396 }
397 }
398
399 self.buf.clear();
402
403 let start = 1 + pointer;
404 if start >= payload.len() {
405 return;
407 }
408 let new_data = &payload[start..];
409 if new_data.len() > MAX_SECTION_SIZE {
410 return;
411 }
412 self.buf.extend_from_slice(new_data);
413 } else {
414 if self.buf.is_empty() {
415 return;
416 }
417 let take = if self.buf.len() >= SECTION_HEADER_LEN {
427 let exp = SECTION_HEADER_LEN
428 + (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8
429 | self.buf[2] as usize);
430 exp.saturating_sub(self.buf.len()).min(payload.len())
431 } else {
432 payload.len().min(MAX_SECTION_SIZE - self.buf.len())
436 };
437 self.buf.extend_from_slice(&payload[..take]);
438 }
439
440 self.drain_complete_sections();
441 }
442
443 fn drain_complete_sections(&mut self) {
454 loop {
455 if self.buf.len() < SECTION_HEADER_LEN {
456 break;
459 }
460 if self.buf[0] == 0xFF {
461 self.buf.clear();
463 break;
464 }
465 let exp = SECTION_HEADER_LEN
466 + (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8 | self.buf[2] as usize);
467 if self.buf.len() >= exp {
468 let section = self.buf.split_to(exp).freeze();
471 self.ready.push_back(section);
472 } else {
473 break;
475 }
476 }
477 }
478
479 pub fn pop_section(&mut self) -> Option<bytes::Bytes> {
481 self.ready.pop_front()
482 }
483
484 pub fn len(&self) -> usize {
486 self.buf.len()
487 }
488
489 pub fn is_empty(&self) -> bool {
491 self.buf.is_empty()
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 fn make_packet(b1: u8, b2: u8, b3: u8, payload_data: &[u8]) -> [u8; TS_PACKET_SIZE] {
501 let mut pkt = [0u8; TS_PACKET_SIZE];
502 pkt[0] = TS_SYNC_BYTE;
503 pkt[1] = b1;
504 pkt[2] = b2;
505 pkt[3] = b3;
506 let payload_start = 4;
507 let end = (payload_start + payload_data.len()).min(TS_PACKET_SIZE);
508 let len = (end - payload_start).min(payload_data.len());
509 pkt[payload_start..payload_start + len].copy_from_slice(&payload_data[..len]);
510 pkt
511 }
512
513 #[test]
514 fn parse_rejects_non_0x47_sync_byte() {
515 let mut pkt = [0u8; TS_PACKET_SIZE];
516 pkt[0] = 0x46; let err = TsPacket::parse(&pkt).unwrap_err();
518 match err {
519 Error::InvalidSyncByte { found } => assert_eq!(found, 0x46),
520 other => panic!("expected InvalidSyncByte, got {other:?}"),
521 }
522 }
523
524 #[test]
525 fn ts_header_round_trip() {
526 let cases = [
529 TsHeader {
530 tei: false,
531 pusi: true,
532 pid: 0x0000,
533 scrambling: 0,
534 has_adaptation: false,
535 has_payload: true,
536 continuity_counter: 0,
537 },
538 TsHeader {
539 tei: true,
540 pusi: false,
541 pid: 0x1FFF,
542 scrambling: 0b11,
543 has_adaptation: true,
544 has_payload: true,
545 continuity_counter: 0x0F,
546 },
547 TsHeader {
548 tei: false,
549 pusi: false,
550 pid: 0x0100,
551 scrambling: 0b10,
552 has_adaptation: true,
553 has_payload: false,
554 continuity_counter: 7,
555 },
556 ];
557 for h in cases {
558 let mut buf = [0u8; 4];
559 assert_eq!(h.serialize_into(&mut buf).unwrap(), 4);
560 assert_eq!(TsHeader::parse(&buf).unwrap(), h, "round-trip mismatch");
561 }
562 }
563
564 #[test]
565 fn parse_extracts_pid_and_continuity_counter() {
566 let pkt = make_packet(0x12, 0x34, 0x05, &[]);
572 let pkt = TsPacket::parse(&pkt).unwrap();
573 assert_eq!(pkt.header.pid, 0x1234);
574 assert_eq!(pkt.header.continuity_counter, 5);
575 }
576
577 #[test]
578 fn payload_unit_start_indicator_flag_extracted() {
579 let pkt1 = make_packet(0x40, 0x00, 0x00, &[]);
581 let pkt1 = TsPacket::parse(&pkt1).unwrap();
582 assert!(pkt1.header.pusi);
583
584 let pkt2 = make_packet(0x00, 0x00, 0x00, &[]);
586 let pkt2 = TsPacket::parse(&pkt2).unwrap();
587 assert!(!pkt2.header.pusi);
588 }
589
590 fn build_pusi_payload(pointer_field: u8, previous_tail: &[u8], section: &[u8]) -> Vec<u8> {
595 assert_eq!(pointer_field as usize, previous_tail.len());
596 let mut v = Vec::with_capacity(1 + previous_tail.len() + section.len());
597 v.push(pointer_field);
598 v.extend_from_slice(previous_tail);
599 v.extend_from_slice(section);
600 v
601 }
602
603 fn build_section(table_id: u8, body_after_length: &[u8]) -> Vec<u8> {
607 let section_length = body_after_length.len() as u16;
608 let mut v = Vec::with_capacity(3 + section_length as usize);
609 v.push(table_id);
610 v.push(0xB0 | ((section_length >> 8) as u8 & 0x0F));
612 v.push((section_length & 0xFF) as u8);
613 v.extend_from_slice(body_after_length);
614 v
615 }
616
617 #[test]
623 fn reassembler_accumulates_multi_packet_section() {
624 let body = vec![0xAAu8; 197];
626 let section = build_section(0x02, &body);
627 assert_eq!(section.len(), 200);
628
629 let first_chunk = 100;
630 let payload1 = build_pusi_payload(0, &[], §ion[..first_chunk]);
631 let payload2 = section[first_chunk..].to_vec();
632
633 let mut reasm = SectionReassembler::default();
634 reasm.feed(&payload1, true);
635 reasm.feed(&payload2, false);
636
637 let out = reasm.pop_section().expect("section should be ready");
638 assert_eq!(out.len(), 200);
639 assert_eq!(out.as_ref(), §ion[..]);
640 }
641
642 #[test]
643 fn reassembler_yields_complete_section_once_length_satisfied() {
644 let section = build_section(0x42, &[0xAA]);
646 assert_eq!(section.len(), 4);
647 let payload = build_pusi_payload(0, &[], §ion);
648
649 let mut reasm = SectionReassembler::default();
650 reasm.feed(&payload, true);
651
652 let out = reasm
653 .pop_section()
654 .expect("single-packet section should pop");
655 assert_eq!(out.as_ref(), §ion[..]);
656 }
657
658 #[test]
659 fn reassembler_extracts_all_concatenated_sections_in_one_payload() {
660 let s1 = build_section(0x42, &[0x11, 0x22]); let s2 = build_section(0x46, &[0x33]); let s3 = build_section(0x4A, &[0x44, 0x55, 0x66]); let mut concat = Vec::new();
669 concat.extend_from_slice(&s1);
670 concat.extend_from_slice(&s2);
671 concat.extend_from_slice(&s3);
672 let payload = build_pusi_payload(0, &[], &concat);
673
674 let mut reasm = SectionReassembler::default();
675 reasm.feed(&payload, true);
676
677 let got: Vec<_> = std::iter::from_fn(|| reasm.pop_section()).collect();
679 assert_eq!(got.len(), 3, "all three concatenated sections must pop");
680 assert_eq!(got[0].as_ref(), &s1[..]);
681 assert_eq!(got[1].as_ref(), &s2[..]);
682 assert_eq!(got[2].as_ref(), &s3[..]);
683 }
684
685 #[test]
686 fn reassembler_stops_at_stuffing_after_concatenated_sections() {
687 let s1 = build_section(0x42, &[0xAA]); let s2 = build_section(0x46, &[0xBB, 0xCC]); let mut concat = Vec::new();
693 concat.extend_from_slice(&s1);
694 concat.extend_from_slice(&s2);
695 concat.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); let payload = build_pusi_payload(0, &[], &concat);
697
698 let mut reasm = SectionReassembler::default();
699 reasm.feed(&payload, true);
700
701 let got: Vec<_> = std::iter::from_fn(|| reasm.pop_section()).collect();
702 assert_eq!(got.len(), 2);
703 assert_eq!(got[0].as_ref(), &s1[..]);
704 assert_eq!(got[1].as_ref(), &s2[..]);
705 assert!(
706 reasm.is_empty(),
707 "stuffing tail must be discarded, not buffered"
708 );
709 }
710
711 #[test]
712 fn reassembler_concatenated_then_spanning_tail() {
713 let s1 = build_section(0x42, &[0x01, 0x02]); let s2 = build_section(0x46, &[0x09u8; 60]); let split = 30;
719
720 let mut head = Vec::new();
721 head.extend_from_slice(&s1);
722 head.extend_from_slice(&s2[..split]);
723 let payload1 = build_pusi_payload(0, &[], &head);
724 let payload2 = s2[split..].to_vec();
725
726 let mut reasm = SectionReassembler::default();
727 reasm.feed(&payload1, true);
728 let first = reasm.pop_section().expect("first section pops at once");
729 assert_eq!(first.as_ref(), &s1[..]);
730 assert!(reasm.pop_section().is_none(), "second is still partial");
731
732 reasm.feed(&payload2, false);
733 let second = reasm.pop_section().expect("second pops after continuation");
734 assert_eq!(second.as_ref(), &s2[..]);
735 }
736
737 #[test]
738 fn reassembler_completes_section_spanning_into_pusi_packet() {
739 let spanning = build_section(0x42, &[0x5Au8; 62]); let head = 41;
747 let tail = &spanning[head..]; assert_eq!(tail.len(), 24);
749
750 let next = build_section(0x46, &[0x77, 0x88]); let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
755 let payload_b = build_pusi_payload(24, tail, &next);
757
758 let mut reasm = SectionReassembler::default();
759 reasm.feed(&payload_a, true);
760 assert!(reasm.pop_section().is_none(), "head alone is incomplete");
761
762 reasm.feed(&payload_b, true);
763 let got: Vec<_> = std::iter::from_fn(|| reasm.pop_section()).collect();
764 assert_eq!(got.len(), 2, "spanning section + new section must both pop");
765 assert_eq!(
766 got[0].as_ref(),
767 &spanning[..],
768 "spanning section completed from B's pointer tail"
769 );
770 assert_eq!(got[1].as_ref(), &next[..]);
771 }
772
773 #[test]
774 fn reassembler_pusi_pointer_spans_whole_payload() {
775 let spanning = build_section(0x42, &[0x33u8; 40]); let head = 20;
780 let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
781 let tail = &spanning[head..]; let mut reasm = SectionReassembler::default();
784 reasm.feed(&payload_a, true);
785 reasm.feed(&build_pusi_payload_pointer_spanning_all(tail), true);
787
788 let out = reasm.pop_section().expect("spanning section completes");
789 assert_eq!(out.as_ref(), &spanning[..]);
790 assert!(reasm.pop_section().is_none());
791 }
792
793 fn build_pusi_payload_pointer_spanning_all(tail: &[u8]) -> Vec<u8> {
796 let mut v = Vec::with_capacity(1 + tail.len());
797 v.push(tail.len() as u8);
798 v.extend_from_slice(tail);
799 v
800 }
801
802 #[test]
803 fn reassembler_completes_max_length_section_and_stays_usable() {
804 let mut section = Vec::with_capacity(MAX_SECTION_SIZE);
810 section.push(0x00); section.push(0xB0 | ((4095u16 >> 8) as u8 & 0x0F));
812 section.push(0xFF); section.resize(MAX_SECTION_SIZE, 0u8);
814 assert_eq!(section.len(), MAX_SECTION_SIZE);
815
816 let mut reasm = SectionReassembler::default();
817 let mut first = vec![0x00u8]; first.extend_from_slice(§ion[..183]);
819 reasm.feed(&first, true);
820 assert!(
821 reasm.pop_section().is_none(),
822 "incomplete until the declared length arrives"
823 );
824
825 for chunk in section[183..].chunks(184) {
826 reasm.feed(chunk, false);
827 }
828 let out = reasm
829 .pop_section()
830 .expect("max-length section completes at its declared length");
831 assert_eq!(out.len(), MAX_SECTION_SIZE);
832 assert_eq!(out.as_ref(), §ion[..]);
833 assert!(reasm.is_empty());
834
835 reasm.feed(&[0u8; 184], false);
839 assert!(reasm.pop_section().is_none());
840
841 let valid_section = build_section(0x00, &[0xAA]);
843 let payload2 = build_pusi_payload(0, &[], &valid_section);
844 reasm.feed(&payload2, true);
845 let out = reasm
846 .pop_section()
847 .expect("fresh section should pop after reset");
848 assert_eq!(out.as_ref(), &valid_section[..]);
849 }
850
851 #[test]
852 fn reassembler_handles_pusi_with_nonzero_pointer_field() {
853 let prior_tail = vec![0x11, 0x22, 0x33];
855 let new_section = build_section(0x02, &[0xBB]);
856 assert_eq!(new_section.len(), 4);
857 let payload = build_pusi_payload(3, &prior_tail, &new_section);
858
859 let mut reasm = SectionReassembler::default();
860 reasm.feed(&payload, true);
861
862 let out = reasm
863 .pop_section()
864 .expect("section after pointer_field skip should pop");
865 assert_eq!(out.as_ref(), &new_section[..]);
866 }
867
868 #[test]
869 fn reassembler_ignores_continuation_before_pusi() {
870 let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA, 0xBB, 0xCC]);
873
874 let mut reasm = SectionReassembler::default();
875 reasm.feed(&pkt[4..], false); assert!(
878 reasm.pop_section().is_none(),
879 "no section should appear without prior PUSI"
880 );
881 assert!(
882 reasm.pop_section().is_none(),
883 "second pop should also be none"
884 );
885 }
886
887 #[test]
890 fn reassembler_empty_pusi_payload_does_not_panic() {
891 let mut reasm = SectionReassembler::default();
892 reasm.feed(&[], true);
893 assert!(reasm.pop_section().is_none());
894 let payload = vec![0x00u8, 0x72, 0x70, 0x01, 0x00];
896 reasm.feed(&payload, true);
897 assert!(reasm.pop_section().is_some());
898 }
899
900 #[test]
904 fn reassembler_accepts_maximal_private_section() {
905 let mut section = vec![0x80u8, 0x7F, 0xFF]; section.resize(3 + 0xFFF, 0xAB);
907
908 let mut reasm = SectionReassembler::default();
909 let mut first = vec![0x00];
911 first.extend_from_slice(§ion[..183]);
912 reasm.feed(&first, true);
913 for chunk in section[183..].chunks(184) {
914 reasm.feed(chunk, false);
915 }
916 let out = reasm.pop_section().expect("4098-byte section should pop");
917 assert_eq!(out.len(), 4098);
918 assert_eq!(out.as_ref(), §ion[..]);
919 }
920
921 #[test]
926 fn reassembler_completes_large_section_with_trailing_stuffing() {
927 let body = vec![0x5Au8; 4096 - 3];
928 let section = build_section(0x50, &body); assert_eq!(section.len(), 4096);
930
931 let mut reasm = SectionReassembler::default();
932 let mut first = vec![0x00u8];
934 first.extend_from_slice(§ion[..183]);
935 reasm.feed(&first, true);
936
937 let mut pos = 183usize;
941 while pos < section.len() {
942 let take = (section.len() - pos).min(184);
943 let mut payload = section[pos..pos + take].to_vec();
944 if take < 184 {
945 payload.resize(184, 0xFF); }
947 reasm.feed(&payload, false);
948 pos += take;
949 }
950
951 let out = reasm
952 .pop_section()
953 .expect("4096-byte section must complete despite trailing stuffing (#148)");
954 assert_eq!(out.len(), 4096);
955 assert_eq!(out.as_ref(), §ion[..]);
956 assert!(reasm.is_empty(), "stuffing tail must be discarded");
957 }
958
959 #[test]
962 fn pcr_as_27mhz_known_value() {
963 assert_eq!(
964 Pcr {
965 base: 10_000,
966 extension: 0
967 }
968 .as_27mhz(),
969 3_000_000
970 );
971 assert_eq!(
973 Pcr {
974 base: 1,
975 extension: 100
976 }
977 .as_27mhz(),
978 400
979 );
980 }
981
982 #[test]
983 fn pcr_decode_from_bytes() {
984 let af = [0x10u8, 0x00, 0x00, 0x13, 0x88, 0x7E, 0x00];
986 let pcr = Pcr::parse(&af, 1).expect("6 bytes present");
987 assert_eq!(
988 pcr,
989 Pcr {
990 base: 10_000,
991 extension: 0
992 }
993 );
994 assert_eq!(pcr.as_27mhz(), 3_000_000);
995 }
996
997 #[test]
998 fn adaptation_field_flags_and_pcr() {
999 let mut raw = [0xAAu8; TS_PACKET_SIZE];
1000 raw[0] = TS_SYNC_BYTE;
1001 raw[1] = 0x01; raw[2] = 0x00;
1003 raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
1004 raw[4] = 7; raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG;
1006 raw[6..12].copy_from_slice(&[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
1007 let pkt = TsPacket::parse(&raw).expect("valid packet");
1010 let af = pkt
1011 .adaptation_field()
1012 .expect("has adaptation field")
1013 .expect("adaptation field parses");
1014 assert!(af.discontinuity_indicator);
1015 assert!(!af.random_access_indicator);
1016 assert_eq!(
1017 af.pcr,
1018 Some(Pcr {
1019 base: 10_000,
1020 extension: 0
1021 })
1022 );
1023 assert_eq!(af.pcr.unwrap().as_27mhz(), 3_000_000);
1024 assert!(af.opcr.is_none());
1025 assert!(af.splice_countdown.is_none());
1026 let payload = pkt.payload.expect("payload present");
1028 assert_eq!(payload.len(), TS_PACKET_SIZE - 12);
1029 assert_eq!(payload[0], 0xAA);
1030 }
1031
1032 #[test]
1033 fn no_adaptation_returns_none() {
1034 let mut raw = [0x00u8; TS_PACKET_SIZE];
1035 raw[0] = TS_SYNC_BYTE;
1036 raw[1] = 0x01;
1037 raw[3] = PAYLOAD_FLAG; let pkt = TsPacket::parse(&raw).expect("valid");
1039 assert!(pkt.adaptation_field().is_none());
1040 assert!(pkt.adaptation.is_none());
1041 }
1042
1043 #[test]
1044 fn adaptation_field_splice_countdown_negative() {
1045 let mut raw = [0xAAu8; TS_PACKET_SIZE];
1046 raw[0] = TS_SYNC_BYTE;
1047 raw[1] = 0x01;
1048 raw[2] = 0x00;
1049 raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
1050 raw[4] = 2; raw[5] = AF_SPLICING_FLAG;
1052 raw[6] = 0xFB; let pkt = TsPacket::parse(&raw).expect("valid");
1054 let af = pkt.adaptation_field().unwrap().unwrap();
1055 assert_eq!(af.splice_countdown, Some(-5));
1056 assert!(af.pcr.is_none());
1057 }
1058}