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
23#[non_exhaustive]
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34pub enum ScramblingControl {
35 NotScrambled,
37 Reserved,
39 EvenKey,
42 OddKey,
45}
46
47impl ScramblingControl {
48 pub fn from_bits(bits: u8) -> Self {
50 match bits & 0b11 {
51 0b00 => Self::NotScrambled,
52 0b01 => Self::Reserved,
53 0b10 => Self::EvenKey,
54 0b11 => Self::OddKey,
55 _ => unreachable!(),
56 }
57 }
58
59 pub fn name(&self) -> &'static str {
61 match self {
62 Self::NotScrambled => "not_scrambled",
63 Self::Reserved => "reserved",
64 Self::EvenKey => "even_key",
65 Self::OddKey => "odd_key",
66 }
67 }
68}
69
70dvb_common::impl_spec_display!(ScramblingControl);
71
72#[non_exhaustive]
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize))]
80pub enum AdaptationFieldControl {
81 Reserved,
83 PayloadOnly,
85 AdaptationOnly,
87 AdaptationAndPayload,
89}
90
91impl AdaptationFieldControl {
92 pub fn from_flags(has_adaptation: bool, has_payload: bool) -> Self {
94 match (has_adaptation, has_payload) {
95 (false, false) => Self::Reserved,
96 (false, true) => Self::PayloadOnly,
97 (true, false) => Self::AdaptationOnly,
98 (true, true) => Self::AdaptationAndPayload,
99 }
100 }
101
102 pub fn name(&self) -> &'static str {
104 match self {
105 Self::Reserved => "reserved",
106 Self::PayloadOnly => "payload_only",
107 Self::AdaptationOnly => "adaptation_only",
108 Self::AdaptationAndPayload => "adaptation_and_payload",
109 }
110 }
111}
112
113dvb_common::impl_spec_display!(AdaptationFieldControl);
114
115const TEI_MASK: u8 = 0x80;
117const PUSI_MASK: u8 = 0x40;
119pub const PID_MASK_HI: u8 = 0x1F;
121pub const SCRAMBLING_MASK: u8 = 0xC0;
123pub const ADAPTATION_FLAG: u8 = 0x20;
125pub const PAYLOAD_FLAG: u8 = 0x10;
127pub const CC_MASK: u8 = 0x0F;
129
130#[derive(Clone, Debug, PartialEq, Eq)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize))]
133pub struct TsHeader {
134 pub tei: bool,
137 pub pusi: bool,
140 pub pid: u16,
142 pub scrambling: u8,
144 pub has_adaptation: bool,
146 pub has_payload: bool,
148 pub continuity_counter: u8,
150}
151
152#[derive(Clone, Debug)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize))]
159pub struct TsPacket<'a> {
160 pub header: TsHeader,
162 pub payload: Option<&'a [u8]>,
165 #[cfg_attr(feature = "serde", serde(skip))]
168 adaptation: Option<&'a [u8]>,
169 #[cfg_attr(feature = "serde", serde(skip))]
171 pub raw: &'a [u8; TS_PACKET_SIZE],
172}
173
174impl TsHeader {
175 pub fn parse(raw4: &[u8]) -> Result<Self> {
177 if raw4.len() < 4 {
178 return Err(Error::BufferTooShort {
179 need: 4,
180 have: raw4.len(),
181 what: "TsHeader",
182 });
183 }
184 let b1 = raw4[1];
185 let b2 = raw4[2];
186 let b3 = raw4[3];
187
188 let tei = (b1 & TEI_MASK) != 0;
189 let pusi = (b1 & PUSI_MASK) != 0;
190 let pid = (((b1 & PID_MASK_HI) as u16) << 8) | (b2 as u16);
191 let scrambling = (b3 & SCRAMBLING_MASK) >> 6;
192 let has_adaptation = (b3 & ADAPTATION_FLAG) != 0;
193 let has_payload = (b3 & PAYLOAD_FLAG) != 0;
194 let continuity_counter = b3 & CC_MASK;
195
196 Ok(Self {
197 tei,
198 pusi,
199 pid,
200 scrambling,
201 has_adaptation,
202 has_payload,
203 continuity_counter,
204 })
205 }
206
207 pub const fn serialized_len() -> usize {
209 4
210 }
211
212 pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
214 if buf.len() < 4 {
215 return Err(Error::OutputBufferTooSmall {
216 need: 4,
217 have: buf.len(),
218 });
219 }
220 buf[0] = TS_SYNC_BYTE;
221 buf[1] = 0;
222 if self.tei {
223 buf[1] |= TEI_MASK;
224 }
225 if self.pusi {
226 buf[1] |= PUSI_MASK;
227 }
228 buf[1] |= ((self.pid >> 8) as u8) & PID_MASK_HI;
229 buf[2] = (self.pid & 0xFF) as u8;
230 buf[3] = (self.scrambling << 6) & SCRAMBLING_MASK;
231 if self.has_adaptation {
232 buf[3] |= ADAPTATION_FLAG;
233 }
234 if self.has_payload {
235 buf[3] |= PAYLOAD_FLAG;
236 }
237 buf[3] |= self.continuity_counter & CC_MASK;
238 Ok(4)
239 }
240
241 pub fn scrambling_control(&self) -> ScramblingControl {
246 ScramblingControl::from_bits(self.scrambling)
247 }
248
249 pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
254 AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
255 }
256}
257
258impl<'a> dvb_common::Parse<'a> for TsHeader {
259 type Error = Error;
260
261 fn parse(bytes: &'a [u8]) -> Result<Self> {
262 TsHeader::parse(bytes)
263 }
264}
265
266impl dvb_common::Serialize for TsHeader {
267 type Error = Error;
268
269 fn serialized_len(&self) -> usize {
270 TsHeader::serialized_len()
271 }
272
273 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
274 TsHeader::serialize_into(self, buf)
275 }
276}
277
278impl<'a> TsPacket<'a> {
279 pub fn parse(buf: &'a [u8]) -> Result<Self> {
285 if buf.len() < TS_PACKET_SIZE {
286 return Err(Error::BufferTooShort {
287 need: TS_PACKET_SIZE,
288 have: buf.len(),
289 what: "TsPacket",
290 });
291 }
292 if buf[0] != TS_SYNC_BYTE {
293 return Err(Error::InvalidSyncByte { found: buf[0] });
294 }
295
296 let raw: &[u8; TS_PACKET_SIZE] =
297 buf[..TS_PACKET_SIZE]
298 .try_into()
299 .map_err(|_| Error::BufferTooShort {
300 need: TS_PACKET_SIZE,
301 have: buf.len(),
302 what: "TsPacket::parse (array conversion)",
303 })?;
304
305 let header = TsHeader::parse(&raw[..4])?;
306
307 let mut cursor = 4usize;
308 let mut payload = None;
309 let mut adaptation = None;
310
311 if header.has_adaptation && cursor < TS_PACKET_SIZE {
314 let af_len = raw[cursor] as usize;
315 let af_start = cursor + 1;
316 if af_len > 0 && af_start < TS_PACKET_SIZE {
317 let af_end = (af_start + af_len).min(TS_PACKET_SIZE);
318 adaptation = Some(&raw[af_start..af_end]);
319 }
320 cursor += 1 + af_len;
321 }
322
323 if header.has_payload && cursor < TS_PACKET_SIZE {
324 payload = Some(&raw[cursor..]);
325 }
326
327 Ok(TsPacket {
328 header,
329 payload,
330 adaptation,
331 raw,
332 })
333 }
334
335 pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField>> {
341 self.adaptation.map(AdaptationField::parse)
342 }
343}
344
345const AF_DISCONTINUITY: u8 = 0x80;
347const AF_RANDOM_ACCESS: u8 = 0x40;
348const AF_ES_PRIORITY: u8 = 0x20;
349const AF_PCR_FLAG: u8 = 0x10;
350const AF_OPCR_FLAG: u8 = 0x08;
351const AF_SPLICING_FLAG: u8 = 0x04;
352const PCR_FIELD_LEN: usize = 6;
354
355#[derive(Clone, Copy, Debug, PartialEq, Eq)]
358#[cfg_attr(feature = "serde", derive(serde::Serialize))]
359pub struct Pcr {
360 pub base: u64,
362 pub extension: u16,
364}
365
366impl Pcr {
367 #[must_use]
369 pub fn as_27mhz(self) -> u64 {
370 self.base * 300 + self.extension as u64
371 }
372
373 fn parse(af: &[u8], at: usize) -> Result<Self> {
375 let b: &[u8; PCR_FIELD_LEN] = af
376 .get(at..at + PCR_FIELD_LEN)
377 .and_then(|s| s.try_into().ok())
378 .ok_or(Error::BufferTooShort {
379 need: at + PCR_FIELD_LEN,
380 have: af.len(),
381 what: "adaptation_field PCR",
382 })?;
383 let base = ((b[0] as u64) << 25)
384 | ((b[1] as u64) << 17)
385 | ((b[2] as u64) << 9)
386 | ((b[3] as u64) << 1)
387 | ((b[4] as u64) >> 7);
388 let extension = (((b[4] & 0x01) as u16) << 8) | (b[5] as u16);
389 Ok(Self { base, extension })
390 }
391}
392
393#[non_exhaustive]
398#[derive(Clone, Copy, Debug, PartialEq, Eq)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize))]
400pub struct AdaptationField {
401 pub discontinuity_indicator: bool,
403 pub random_access_indicator: bool,
405 pub elementary_stream_priority_indicator: bool,
407 pub pcr: Option<Pcr>,
409 pub opcr: Option<Pcr>,
411 pub splice_countdown: Option<i8>,
413}
414
415impl AdaptationField {
416 fn parse(af: &[u8]) -> Result<Self> {
418 let flags = *af.first().ok_or(Error::BufferTooShort {
419 need: 1,
420 have: 0,
421 what: "adaptation_field flags",
422 })?;
423 let mut cursor = 1usize;
424
425 let pcr = if flags & AF_PCR_FLAG != 0 {
426 let p = Pcr::parse(af, cursor)?;
427 cursor += PCR_FIELD_LEN;
428 Some(p)
429 } else {
430 None
431 };
432 let opcr = if flags & AF_OPCR_FLAG != 0 {
433 let p = Pcr::parse(af, cursor)?;
434 cursor += PCR_FIELD_LEN;
435 Some(p)
436 } else {
437 None
438 };
439 let splice_countdown = if flags & AF_SPLICING_FLAG != 0 {
440 let b = *af.get(cursor).ok_or(Error::BufferTooShort {
441 need: cursor + 1,
442 have: af.len(),
443 what: "adaptation_field splice_countdown",
444 })?;
445 Some(b as i8)
446 } else {
447 None
448 };
449
450 Ok(AdaptationField {
451 discontinuity_indicator: flags & AF_DISCONTINUITY != 0,
452 random_access_indicator: flags & AF_RANDOM_ACCESS != 0,
453 elementary_stream_priority_indicator: flags & AF_ES_PRIORITY != 0,
454 pcr,
455 opcr,
456 splice_countdown,
457 })
458 }
459}
460
461#[derive(Default)]
466pub struct SectionReassembler {
467 buf: bytes::BytesMut,
468 ready: alloc::collections::VecDeque<bytes::Bytes>,
469}
470
471impl SectionReassembler {
472 pub fn feed(&mut self, payload: &[u8], pusi: bool) {
480 if pusi {
481 if payload.is_empty() {
484 self.buf.clear();
485 return;
486 }
487 let pointer = payload[0] as usize;
488
489 if !self.buf.is_empty() && pointer > 0 {
497 let avail = payload.len() - 1;
498 let tail_len = pointer.min(avail);
499 if self.buf.len() + tail_len > MAX_SECTION_SIZE {
500 self.buf.clear();
501 } else {
502 self.buf.extend_from_slice(&payload[1..1 + tail_len]);
503 self.drain_complete_sections();
504 }
505 }
506
507 self.buf.clear();
510
511 let start = 1 + pointer;
512 if start >= payload.len() {
513 return;
515 }
516 let new_data = &payload[start..];
517 if new_data.len() > MAX_SECTION_SIZE {
518 return;
519 }
520 self.buf.extend_from_slice(new_data);
521 } else {
522 if self.buf.is_empty() {
523 return;
524 }
525 let take = if self.buf.len() >= SECTION_HEADER_LEN {
535 let exp = SECTION_HEADER_LEN
536 + (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8
537 | self.buf[2] as usize);
538 exp.saturating_sub(self.buf.len()).min(payload.len())
539 } else {
540 payload.len().min(MAX_SECTION_SIZE - self.buf.len())
544 };
545 self.buf.extend_from_slice(&payload[..take]);
546 }
547
548 self.drain_complete_sections();
549 }
550
551 fn drain_complete_sections(&mut self) {
562 loop {
563 if self.buf.len() < SECTION_HEADER_LEN {
564 break;
567 }
568 if self.buf[0] == 0xFF {
569 self.buf.clear();
571 break;
572 }
573 let exp = SECTION_HEADER_LEN
574 + (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8 | self.buf[2] as usize);
575 if self.buf.len() >= exp {
576 let section = self.buf.split_to(exp).freeze();
579 self.ready.push_back(section);
580 } else {
581 break;
583 }
584 }
585 }
586
587 pub fn pop_section(&mut self) -> Option<bytes::Bytes> {
589 self.ready.pop_front()
590 }
591
592 pub fn len(&self) -> usize {
594 self.buf.len()
595 }
596
597 pub fn is_empty(&self) -> bool {
599 self.buf.is_empty()
600 }
601}
602
603pub fn iter_packets(buf: &[u8]) -> impl Iterator<Item = TsPacket<'_>> {
621 buf.chunks_exact(TS_PACKET_SIZE)
622 .filter_map(|chunk| TsPacket::parse(chunk).ok())
623}
624
625pub fn extract_ts_payload(pkt: &[u8]) -> Option<&[u8]> {
635 if pkt.len() < 4 {
636 return None;
637 }
638 let afc = (pkt[3] >> 4) & 0x3;
639 match afc {
640 0x1 => {
641 if pkt.len() > 4 {
643 Some(&pkt[4..])
644 } else {
645 None
646 }
647 }
648 0x3 => {
649 if pkt.len() < 5 {
651 return None;
652 }
653 let af_len = pkt[4] as usize;
654 let start = 5 + af_len;
655 if start < pkt.len() {
656 Some(&pkt[start..])
657 } else {
658 None
659 }
660 }
661 _ => None,
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use super::*;
668 use alloc::string::ToString;
669 use alloc::vec;
670 use alloc::vec::Vec;
671
672 fn make_packet(b1: u8, b2: u8, b3: u8, payload_data: &[u8]) -> [u8; TS_PACKET_SIZE] {
674 let mut pkt = [0u8; TS_PACKET_SIZE];
675 pkt[0] = TS_SYNC_BYTE;
676 pkt[1] = b1;
677 pkt[2] = b2;
678 pkt[3] = b3;
679 let payload_start = 4;
680 let end = (payload_start + payload_data.len()).min(TS_PACKET_SIZE);
681 let len = (end - payload_start).min(payload_data.len());
682 pkt[payload_start..payload_start + len].copy_from_slice(&payload_data[..len]);
683 pkt
684 }
685
686 #[test]
687 fn parse_rejects_non_0x47_sync_byte() {
688 let mut pkt = [0u8; TS_PACKET_SIZE];
689 pkt[0] = 0x46; let err = TsPacket::parse(&pkt).unwrap_err();
691 match err {
692 Error::InvalidSyncByte { found } => assert_eq!(found, 0x46),
693 other => panic!("expected InvalidSyncByte, got {other:?}"),
694 }
695 }
696
697 #[test]
698 fn ts_header_round_trip() {
699 let cases = [
702 TsHeader {
703 tei: false,
704 pusi: true,
705 pid: 0x0000,
706 scrambling: 0,
707 has_adaptation: false,
708 has_payload: true,
709 continuity_counter: 0,
710 },
711 TsHeader {
712 tei: true,
713 pusi: false,
714 pid: 0x1FFF,
715 scrambling: 0b11,
716 has_adaptation: true,
717 has_payload: true,
718 continuity_counter: 0x0F,
719 },
720 TsHeader {
721 tei: false,
722 pusi: false,
723 pid: 0x0100,
724 scrambling: 0b10,
725 has_adaptation: true,
726 has_payload: false,
727 continuity_counter: 7,
728 },
729 ];
730 for h in cases {
731 let mut buf = [0u8; 4];
732 assert_eq!(h.serialize_into(&mut buf).unwrap(), 4);
733 assert_eq!(TsHeader::parse(&buf).unwrap(), h, "round-trip mismatch");
734 }
735 }
736
737 #[test]
738 fn parse_extracts_pid_and_continuity_counter() {
739 let pkt = make_packet(0x12, 0x34, 0x05, &[]);
745 let pkt = TsPacket::parse(&pkt).unwrap();
746 assert_eq!(pkt.header.pid, 0x1234);
747 assert_eq!(pkt.header.continuity_counter, 5);
748 }
749
750 #[test]
751 fn payload_unit_start_indicator_flag_extracted() {
752 let pkt1 = make_packet(0x40, 0x00, 0x00, &[]);
754 let pkt1 = TsPacket::parse(&pkt1).unwrap();
755 assert!(pkt1.header.pusi);
756
757 let pkt2 = make_packet(0x00, 0x00, 0x00, &[]);
759 let pkt2 = TsPacket::parse(&pkt2).unwrap();
760 assert!(!pkt2.header.pusi);
761 }
762
763 fn build_pusi_payload(pointer_field: u8, previous_tail: &[u8], section: &[u8]) -> Vec<u8> {
768 assert_eq!(pointer_field as usize, previous_tail.len());
769 let mut v = Vec::with_capacity(1 + previous_tail.len() + section.len());
770 v.push(pointer_field);
771 v.extend_from_slice(previous_tail);
772 v.extend_from_slice(section);
773 v
774 }
775
776 fn build_section(table_id: u8, body_after_length: &[u8]) -> Vec<u8> {
780 let section_length = body_after_length.len() as u16;
781 let mut v = Vec::with_capacity(3 + section_length as usize);
782 v.push(table_id);
783 v.push(0xB0 | ((section_length >> 8) as u8 & 0x0F));
785 v.push((section_length & 0xFF) as u8);
786 v.extend_from_slice(body_after_length);
787 v
788 }
789
790 #[test]
796 fn reassembler_accumulates_multi_packet_section() {
797 let body = vec![0xAAu8; 197];
799 let section = build_section(0x02, &body);
800 assert_eq!(section.len(), 200);
801
802 let first_chunk = 100;
803 let payload1 = build_pusi_payload(0, &[], §ion[..first_chunk]);
804 let payload2 = section[first_chunk..].to_vec();
805
806 let mut reasm = SectionReassembler::default();
807 reasm.feed(&payload1, true);
808 reasm.feed(&payload2, false);
809
810 let out = reasm.pop_section().expect("section should be ready");
811 assert_eq!(out.len(), 200);
812 assert_eq!(out.as_ref(), §ion[..]);
813 }
814
815 #[test]
816 fn reassembler_yields_complete_section_once_length_satisfied() {
817 let section = build_section(0x42, &[0xAA]);
819 assert_eq!(section.len(), 4);
820 let payload = build_pusi_payload(0, &[], §ion);
821
822 let mut reasm = SectionReassembler::default();
823 reasm.feed(&payload, true);
824
825 let out = reasm
826 .pop_section()
827 .expect("single-packet section should pop");
828 assert_eq!(out.as_ref(), §ion[..]);
829 }
830
831 #[test]
832 fn reassembler_extracts_all_concatenated_sections_in_one_payload() {
833 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();
842 concat.extend_from_slice(&s1);
843 concat.extend_from_slice(&s2);
844 concat.extend_from_slice(&s3);
845 let payload = build_pusi_payload(0, &[], &concat);
846
847 let mut reasm = SectionReassembler::default();
848 reasm.feed(&payload, true);
849
850 let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
852 assert_eq!(got.len(), 3, "all three concatenated sections must pop");
853 assert_eq!(got[0].as_ref(), &s1[..]);
854 assert_eq!(got[1].as_ref(), &s2[..]);
855 assert_eq!(got[2].as_ref(), &s3[..]);
856 }
857
858 #[test]
859 fn reassembler_stops_at_stuffing_after_concatenated_sections() {
860 let s1 = build_section(0x42, &[0xAA]); let s2 = build_section(0x46, &[0xBB, 0xCC]); let mut concat = Vec::new();
866 concat.extend_from_slice(&s1);
867 concat.extend_from_slice(&s2);
868 concat.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); let payload = build_pusi_payload(0, &[], &concat);
870
871 let mut reasm = SectionReassembler::default();
872 reasm.feed(&payload, true);
873
874 let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
875 assert_eq!(got.len(), 2);
876 assert_eq!(got[0].as_ref(), &s1[..]);
877 assert_eq!(got[1].as_ref(), &s2[..]);
878 assert!(
879 reasm.is_empty(),
880 "stuffing tail must be discarded, not buffered"
881 );
882 }
883
884 #[test]
885 fn reassembler_concatenated_then_spanning_tail() {
886 let s1 = build_section(0x42, &[0x01, 0x02]); let s2 = build_section(0x46, &[0x09u8; 60]); let split = 30;
892
893 let mut head = Vec::new();
894 head.extend_from_slice(&s1);
895 head.extend_from_slice(&s2[..split]);
896 let payload1 = build_pusi_payload(0, &[], &head);
897 let payload2 = s2[split..].to_vec();
898
899 let mut reasm = SectionReassembler::default();
900 reasm.feed(&payload1, true);
901 let first = reasm.pop_section().expect("first section pops at once");
902 assert_eq!(first.as_ref(), &s1[..]);
903 assert!(reasm.pop_section().is_none(), "second is still partial");
904
905 reasm.feed(&payload2, false);
906 let second = reasm.pop_section().expect("second pops after continuation");
907 assert_eq!(second.as_ref(), &s2[..]);
908 }
909
910 #[test]
911 fn reassembler_completes_section_spanning_into_pusi_packet() {
912 let spanning = build_section(0x42, &[0x5Au8; 62]); let head = 41;
920 let tail = &spanning[head..]; assert_eq!(tail.len(), 24);
922
923 let next = build_section(0x46, &[0x77, 0x88]); let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
928 let payload_b = build_pusi_payload(24, tail, &next);
930
931 let mut reasm = SectionReassembler::default();
932 reasm.feed(&payload_a, true);
933 assert!(reasm.pop_section().is_none(), "head alone is incomplete");
934
935 reasm.feed(&payload_b, true);
936 let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
937 assert_eq!(got.len(), 2, "spanning section + new section must both pop");
938 assert_eq!(
939 got[0].as_ref(),
940 &spanning[..],
941 "spanning section completed from B's pointer tail"
942 );
943 assert_eq!(got[1].as_ref(), &next[..]);
944 }
945
946 #[test]
947 fn reassembler_pusi_pointer_spans_whole_payload() {
948 let spanning = build_section(0x42, &[0x33u8; 40]); let head = 20;
953 let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
954 let tail = &spanning[head..]; let mut reasm = SectionReassembler::default();
957 reasm.feed(&payload_a, true);
958 reasm.feed(&build_pusi_payload_pointer_spanning_all(tail), true);
960
961 let out = reasm.pop_section().expect("spanning section completes");
962 assert_eq!(out.as_ref(), &spanning[..]);
963 assert!(reasm.pop_section().is_none());
964 }
965
966 fn build_pusi_payload_pointer_spanning_all(tail: &[u8]) -> Vec<u8> {
969 let mut v = Vec::with_capacity(1 + tail.len());
970 v.push(tail.len() as u8);
971 v.extend_from_slice(tail);
972 v
973 }
974
975 #[test]
976 fn reassembler_completes_max_length_section_and_stays_usable() {
977 let mut section = Vec::with_capacity(MAX_SECTION_SIZE);
983 section.push(0x00); section.push(0xB0 | ((4095u16 >> 8) as u8 & 0x0F));
985 section.push(0xFF); section.resize(MAX_SECTION_SIZE, 0u8);
987 assert_eq!(section.len(), MAX_SECTION_SIZE);
988
989 let mut reasm = SectionReassembler::default();
990 let mut first = vec![0x00u8]; first.extend_from_slice(§ion[..183]);
992 reasm.feed(&first, true);
993 assert!(
994 reasm.pop_section().is_none(),
995 "incomplete until the declared length arrives"
996 );
997
998 for chunk in section[183..].chunks(184) {
999 reasm.feed(chunk, false);
1000 }
1001 let out = reasm
1002 .pop_section()
1003 .expect("max-length section completes at its declared length");
1004 assert_eq!(out.len(), MAX_SECTION_SIZE);
1005 assert_eq!(out.as_ref(), §ion[..]);
1006 assert!(reasm.is_empty());
1007
1008 reasm.feed(&[0u8; 184], false);
1012 assert!(reasm.pop_section().is_none());
1013
1014 let valid_section = build_section(0x00, &[0xAA]);
1016 let payload2 = build_pusi_payload(0, &[], &valid_section);
1017 reasm.feed(&payload2, true);
1018 let out = reasm
1019 .pop_section()
1020 .expect("fresh section should pop after reset");
1021 assert_eq!(out.as_ref(), &valid_section[..]);
1022 }
1023
1024 #[test]
1025 fn reassembler_handles_pusi_with_nonzero_pointer_field() {
1026 let prior_tail = vec![0x11, 0x22, 0x33];
1028 let new_section = build_section(0x02, &[0xBB]);
1029 assert_eq!(new_section.len(), 4);
1030 let payload = build_pusi_payload(3, &prior_tail, &new_section);
1031
1032 let mut reasm = SectionReassembler::default();
1033 reasm.feed(&payload, true);
1034
1035 let out = reasm
1036 .pop_section()
1037 .expect("section after pointer_field skip should pop");
1038 assert_eq!(out.as_ref(), &new_section[..]);
1039 }
1040
1041 #[test]
1042 fn reassembler_ignores_continuation_before_pusi() {
1043 let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA, 0xBB, 0xCC]);
1046
1047 let mut reasm = SectionReassembler::default();
1048 reasm.feed(&pkt[4..], false); assert!(
1051 reasm.pop_section().is_none(),
1052 "no section should appear without prior PUSI"
1053 );
1054 assert!(
1055 reasm.pop_section().is_none(),
1056 "second pop should also be none"
1057 );
1058 }
1059
1060 #[test]
1063 fn reassembler_empty_pusi_payload_does_not_panic() {
1064 let mut reasm = SectionReassembler::default();
1065 reasm.feed(&[], true);
1066 assert!(reasm.pop_section().is_none());
1067 let payload = vec![0x00u8, 0x72, 0x70, 0x01, 0x00];
1069 reasm.feed(&payload, true);
1070 assert!(reasm.pop_section().is_some());
1071 }
1072
1073 #[test]
1077 fn reassembler_accepts_maximal_private_section() {
1078 let mut section = vec![0x80u8, 0x7F, 0xFF]; section.resize(3 + 0xFFF, 0xAB);
1080
1081 let mut reasm = SectionReassembler::default();
1082 let mut first = vec![0x00];
1084 first.extend_from_slice(§ion[..183]);
1085 reasm.feed(&first, true);
1086 for chunk in section[183..].chunks(184) {
1087 reasm.feed(chunk, false);
1088 }
1089 let out = reasm.pop_section().expect("4098-byte section should pop");
1090 assert_eq!(out.len(), 4098);
1091 assert_eq!(out.as_ref(), §ion[..]);
1092 }
1093
1094 #[test]
1099 fn reassembler_completes_large_section_with_trailing_stuffing() {
1100 let body = vec![0x5Au8; 4096 - 3];
1101 let section = build_section(0x50, &body); assert_eq!(section.len(), 4096);
1103
1104 let mut reasm = SectionReassembler::default();
1105 let mut first = vec![0x00u8];
1107 first.extend_from_slice(§ion[..183]);
1108 reasm.feed(&first, true);
1109
1110 let mut pos = 183usize;
1114 while pos < section.len() {
1115 let take = (section.len() - pos).min(184);
1116 let mut payload = section[pos..pos + take].to_vec();
1117 if take < 184 {
1118 payload.resize(184, 0xFF); }
1120 reasm.feed(&payload, false);
1121 pos += take;
1122 }
1123
1124 let out = reasm
1125 .pop_section()
1126 .expect("4096-byte section must complete despite trailing stuffing (#148)");
1127 assert_eq!(out.len(), 4096);
1128 assert_eq!(out.as_ref(), §ion[..]);
1129 assert!(reasm.is_empty(), "stuffing tail must be discarded");
1130 }
1131
1132 #[test]
1135 fn pcr_as_27mhz_known_value() {
1136 assert_eq!(
1137 Pcr {
1138 base: 10_000,
1139 extension: 0
1140 }
1141 .as_27mhz(),
1142 3_000_000
1143 );
1144 assert_eq!(
1146 Pcr {
1147 base: 1,
1148 extension: 100
1149 }
1150 .as_27mhz(),
1151 400
1152 );
1153 }
1154
1155 #[test]
1156 fn pcr_decode_from_bytes() {
1157 let af = [0x10u8, 0x00, 0x00, 0x13, 0x88, 0x7E, 0x00];
1159 let pcr = Pcr::parse(&af, 1).expect("6 bytes present");
1160 assert_eq!(
1161 pcr,
1162 Pcr {
1163 base: 10_000,
1164 extension: 0
1165 }
1166 );
1167 assert_eq!(pcr.as_27mhz(), 3_000_000);
1168 }
1169
1170 #[test]
1171 fn adaptation_field_flags_and_pcr() {
1172 let mut raw = [0xAAu8; TS_PACKET_SIZE];
1173 raw[0] = TS_SYNC_BYTE;
1174 raw[1] = 0x01; raw[2] = 0x00;
1176 raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
1177 raw[4] = 7; raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG;
1179 raw[6..12].copy_from_slice(&[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
1180 let pkt = TsPacket::parse(&raw).expect("valid packet");
1183 let af = pkt
1184 .adaptation_field()
1185 .expect("has adaptation field")
1186 .expect("adaptation field parses");
1187 assert!(af.discontinuity_indicator);
1188 assert!(!af.random_access_indicator);
1189 assert_eq!(
1190 af.pcr,
1191 Some(Pcr {
1192 base: 10_000,
1193 extension: 0
1194 })
1195 );
1196 assert_eq!(af.pcr.unwrap().as_27mhz(), 3_000_000);
1197 assert!(af.opcr.is_none());
1198 assert!(af.splice_countdown.is_none());
1199 let payload = pkt.payload.expect("payload present");
1201 assert_eq!(payload.len(), TS_PACKET_SIZE - 12);
1202 assert_eq!(payload[0], 0xAA);
1203 }
1204
1205 #[test]
1206 fn no_adaptation_returns_none() {
1207 let mut raw = [0x00u8; TS_PACKET_SIZE];
1208 raw[0] = TS_SYNC_BYTE;
1209 raw[1] = 0x01;
1210 raw[3] = PAYLOAD_FLAG; let pkt = TsPacket::parse(&raw).expect("valid");
1212 assert!(pkt.adaptation_field().is_none());
1213 assert!(pkt.adaptation.is_none());
1214 }
1215
1216 #[test]
1217 fn adaptation_field_splice_countdown_negative() {
1218 let mut raw = [0xAAu8; TS_PACKET_SIZE];
1219 raw[0] = TS_SYNC_BYTE;
1220 raw[1] = 0x01;
1221 raw[2] = 0x00;
1222 raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
1223 raw[4] = 2; raw[5] = AF_SPLICING_FLAG;
1225 raw[6] = 0xFB; let pkt = TsPacket::parse(&raw).expect("valid");
1227 let af = pkt.adaptation_field().unwrap().unwrap();
1228 assert_eq!(af.splice_countdown, Some(-5));
1229 assert!(af.pcr.is_none());
1230 }
1231
1232 #[test]
1235 fn scrambling_control_all_values() {
1236 assert_eq!(
1237 ScramblingControl::from_bits(0b00),
1238 ScramblingControl::NotScrambled
1239 );
1240 assert_eq!(
1241 ScramblingControl::from_bits(0b01),
1242 ScramblingControl::Reserved
1243 );
1244 assert_eq!(
1245 ScramblingControl::from_bits(0b10),
1246 ScramblingControl::EvenKey
1247 );
1248 assert_eq!(
1249 ScramblingControl::from_bits(0b11),
1250 ScramblingControl::OddKey
1251 );
1252 assert_eq!(ScramblingControl::NotScrambled.name(), "not_scrambled");
1254 assert_eq!(ScramblingControl::Reserved.name(), "reserved");
1255 assert_eq!(ScramblingControl::EvenKey.name(), "even_key");
1256 assert_eq!(ScramblingControl::OddKey.name(), "odd_key");
1257 assert_eq!(ScramblingControl::NotScrambled.to_string(), "not_scrambled");
1259 assert_eq!(ScramblingControl::OddKey.to_string(), "odd_key");
1260 assert_eq!(
1262 ScramblingControl::from_bits(0xFF),
1263 ScramblingControl::OddKey
1264 );
1265 }
1266
1267 #[test]
1268 fn adaptation_field_control_all_values() {
1269 assert_eq!(
1270 AdaptationFieldControl::from_flags(false, false),
1271 AdaptationFieldControl::Reserved
1272 );
1273 assert_eq!(
1274 AdaptationFieldControl::from_flags(false, true),
1275 AdaptationFieldControl::PayloadOnly
1276 );
1277 assert_eq!(
1278 AdaptationFieldControl::from_flags(true, false),
1279 AdaptationFieldControl::AdaptationOnly
1280 );
1281 assert_eq!(
1282 AdaptationFieldControl::from_flags(true, true),
1283 AdaptationFieldControl::AdaptationAndPayload
1284 );
1285 assert_eq!(AdaptationFieldControl::Reserved.name(), "reserved");
1287 assert_eq!(AdaptationFieldControl::PayloadOnly.name(), "payload_only");
1288 assert_eq!(
1289 AdaptationFieldControl::AdaptationOnly.name(),
1290 "adaptation_only"
1291 );
1292 assert_eq!(
1293 AdaptationFieldControl::AdaptationAndPayload.name(),
1294 "adaptation_and_payload"
1295 );
1296 assert_eq!(
1298 AdaptationFieldControl::PayloadOnly.to_string(),
1299 "payload_only"
1300 );
1301 }
1302
1303 #[test]
1304 fn ts_header_scrambling_control_accessor() {
1305 let hdr = TsHeader {
1306 tei: false,
1307 pusi: false,
1308 pid: 0x0100,
1309 scrambling: 0b10,
1310 has_adaptation: false,
1311 has_payload: true,
1312 continuity_counter: 0,
1313 };
1314 assert_eq!(hdr.scrambling_control(), ScramblingControl::EvenKey);
1315 }
1316
1317 #[test]
1318 fn ts_header_adaptation_field_control_accessor() {
1319 let hdr_payload_only = TsHeader {
1320 tei: false,
1321 pusi: false,
1322 pid: 0x0100,
1323 scrambling: 0,
1324 has_adaptation: false,
1325 has_payload: true,
1326 continuity_counter: 0,
1327 };
1328 assert_eq!(
1329 hdr_payload_only.adaptation_field_control(),
1330 AdaptationFieldControl::PayloadOnly
1331 );
1332
1333 let hdr_both = TsHeader {
1334 tei: false,
1335 pusi: false,
1336 pid: 0x0100,
1337 scrambling: 0,
1338 has_adaptation: true,
1339 has_payload: true,
1340 continuity_counter: 0,
1341 };
1342 assert_eq!(
1343 hdr_both.adaptation_field_control(),
1344 AdaptationFieldControl::AdaptationAndPayload
1345 );
1346 }
1347
1348 #[test]
1351 fn iter_packets_yields_valid_and_skips_bad_sync() {
1352 let pkt1 = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA; 10]);
1354 let pkt2 = make_packet(0x40, 0x64, PAYLOAD_FLAG, &[0xBB; 10]);
1355 let mut bad = [0u8; TS_PACKET_SIZE];
1356 bad[0] = 0x00; let mut buf = Vec::new();
1359 buf.extend_from_slice(&pkt1);
1360 buf.extend_from_slice(&pkt2);
1361 buf.extend_from_slice(&bad);
1362
1363 let pkts: Vec<_> = super::iter_packets(&buf).collect();
1364 assert_eq!(pkts.len(), 2, "bad sync packet must be skipped");
1365 assert_eq!(pkts[0].header.pid, 0x0000);
1366 assert_eq!(pkts[1].header.pid, 0x0064);
1367 }
1368
1369 #[test]
1370 fn extract_ts_payload_payload_only() {
1371 let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xABu8; 10]);
1372 let p = super::extract_ts_payload(&pkt).expect("payload present");
1373 assert_eq!(p[0], 0xAB);
1374 assert_eq!(p.len(), TS_PACKET_SIZE - 4);
1375 }
1376
1377 #[test]
1378 fn extract_ts_payload_adaptation_only_returns_none() {
1379 let pkt = make_packet(0x00, 0x00, ADAPTATION_FLAG, &[]);
1380 assert!(super::extract_ts_payload(&pkt).is_none());
1381 }
1382}