Skip to main content

mpeg_ts/
ts.rs

1//! MPEG-TS packet parser and section reassembler — ITU-T H.222.0 §2.4 (= ISO/IEC 13818-1).
2
3use crate::error::{Error, Result};
4
5/// Size of one MPEG-TS packet (ETSI EN 300 468 §3.2, ISO/IEC 13818-1 §2.4.3.2).
6pub const TS_PACKET_SIZE: usize = 188;
7/// Sync byte that every TS packet starts with (ISO/IEC 13818-1 §2.4.3.2).
8pub const TS_SYNC_BYTE: u8 = 0x47;
9/// Upper bound on a single section: `section_length` is 12 bits (max 4095)
10/// plus the 3-byte header = 4098. (Long-form SI caps `section_length` at
11/// 4093 → total 4096, but maximal short-form private sections may reach
12/// 4098; the reassembler accepts the absolute ceiling.)
13const MAX_SECTION_SIZE: usize = 4098;
14/// Bytes before the `section_length` payload: `table_id` (1) + the two bytes
15/// carrying the syntax/RFU flags and the 12-bit `section_length`
16/// (ISO/IEC 13818-1 §2.4.4.1).
17const SECTION_HEADER_LEN: usize = 3;
18/// Mask for the 4 most-significant `section_length` bits in a section's second
19/// byte (ISO/IEC 13818-1 §2.4.4.1 — `section_length` is 12 bits). Shared with
20/// the packetizer in `mux.rs`.
21pub(crate) const SECTION_LENGTH_HI_MASK: u8 = 0x0F;
22
23/// 2-bit `transport_scrambling_control` field — ITU-T H.222.0 (08/2023) Table 2-4
24/// (defines only `00` = not scrambled); DVB assigns `01`/`10`/`11` in ETSI TS 100 289
25/// V1.1.1 §5.1 Table 1 (reserved, even CW, odd CW).
26///
27/// MPEG-2 leaves `01`/`10`/`11` as user-defined; DVB's common-scrambling convention
28/// assigns `10` = even control word, `11` = odd control word, `01` = reserved for future
29/// DVB use. The field lives in the TS header and is never applied to the header itself —
30/// only to the packet payload.
31#[non_exhaustive]
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34pub enum ScramblingControl {
35    /// `00` — not scrambled. The only MPEG-2-defined value (H.222.0 Table 2-4).
36    NotScrambled,
37    /// `01` — reserved for future DVB use (ETSI TS 100 289 V1.1.1 §5.1 Table 1).
38    Reserved,
39    /// `10` — TS packet payload scrambled with the **even** control word
40    /// (DVB common scrambling, ETSI TS 100 289 V1.1.1 §5.1 Table 1).
41    EvenKey,
42    /// `11` — TS packet payload scrambled with the **odd** control word
43    /// (DVB common scrambling, ETSI TS 100 289 V1.1.1 §5.1 Table 1).
44    OddKey,
45}
46
47impl ScramblingControl {
48    /// Decode from the 2-bit `transport_scrambling_control` value (masked to `[1:0]`).
49    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    /// Encode as the 2-bit `transport_scrambling_control` field value (`[1:0]`).
60    ///
61    /// The returned byte is in the range `0x00`–`0x03`; the caller shifts it
62    /// into position within the TS header byte 3 (H.222.0 Table 2-4).
63    pub fn to_bits(self) -> u8 {
64        match self {
65            Self::NotScrambled => 0b00,
66            Self::Reserved => 0b01,
67            Self::EvenKey => 0b10,
68            Self::OddKey => 0b11,
69        }
70    }
71
72    /// Short label for this value, per the #204 convention.
73    pub fn name(&self) -> &'static str {
74        match self {
75            Self::NotScrambled => "not_scrambled",
76            Self::Reserved => "reserved",
77            Self::EvenKey => "even_key",
78            Self::OddKey => "odd_key",
79        }
80    }
81}
82
83broadcast_common::impl_spec_display!(ScramblingControl);
84
85/// 2-bit `adaptation_field_control` field — ITU-T H.222.0 (08/2023) Table 2-5.
86///
87/// Decoders shall discard packets with value `00` (`Reserved`). Null packets use `01`
88/// (`PayloadOnly`). The two flags `has_adaptation`/`has_payload` on [`TsHeader`] carry
89/// the decoded booleans; this enum provides the typed composite view.
90#[non_exhaustive]
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub enum AdaptationFieldControl {
94    /// `00` — reserved for future use; decoders shall discard (H.222.0 Table 2-5).
95    Reserved,
96    /// `01` — no adaptation_field, payload only (H.222.0 Table 2-5).
97    PayloadOnly,
98    /// `10` — adaptation_field only, no payload (H.222.0 Table 2-5).
99    AdaptationOnly,
100    /// `11` — adaptation_field followed by payload (H.222.0 Table 2-5).
101    AdaptationAndPayload,
102}
103
104impl AdaptationFieldControl {
105    /// Derive from the two decoded boolean flags stored on [`TsHeader`].
106    pub fn from_flags(has_adaptation: bool, has_payload: bool) -> Self {
107        match (has_adaptation, has_payload) {
108            (false, false) => Self::Reserved,
109            (false, true) => Self::PayloadOnly,
110            (true, false) => Self::AdaptationOnly,
111            (true, true) => Self::AdaptationAndPayload,
112        }
113    }
114
115    /// Encode as the 2-bit `adaptation_field_control` field value (`[1:0]`).
116    ///
117    /// Bit 1 = adaptation present, bit 0 = payload present (H.222.0 Table 2-5).
118    /// The returned byte is in the range `0x00`–`0x03`; the caller shifts it
119    /// into bits `[5:4]` of TS header byte 3.
120    pub fn to_bits(self) -> u8 {
121        match self {
122            Self::Reserved => 0b00,
123            Self::PayloadOnly => 0b01,
124            Self::AdaptationOnly => 0b10,
125            Self::AdaptationAndPayload => 0b11,
126        }
127    }
128
129    /// Decode into the `(has_adaptation, has_payload)` flag pair stored on
130    /// [`TsHeader`]. Exact inverse of [`from_flags`](Self::from_flags).
131    pub fn to_flags(self) -> (bool, bool) {
132        match self {
133            Self::Reserved => (false, false),
134            Self::PayloadOnly => (false, true),
135            Self::AdaptationOnly => (true, false),
136            Self::AdaptationAndPayload => (true, true),
137        }
138    }
139
140    /// Short label for this value, per the #204 convention.
141    pub fn name(&self) -> &'static str {
142        match self {
143            Self::Reserved => "reserved",
144            Self::PayloadOnly => "payload_only",
145            Self::AdaptationOnly => "adaptation_only",
146            Self::AdaptationAndPayload => "adaptation_and_payload",
147        }
148    }
149}
150
151broadcast_common::impl_spec_display!(AdaptationFieldControl);
152
153/// ISO/IEC 13818-1 §2.4.3.3: transport header byte 1 bit 7 = tei (Transport Error Indicator).
154const TEI_MASK: u8 = 0x80;
155/// ISO/IEC 13818-1 §2.4.3.3: byte 1 bit 6 = pusi (Payload Unit Start Indicator).
156const PUSI_MASK: u8 = 0x40;
157/// ISO/IEC 13818-1 §2.4.3.3: byte 1 bits `[4:0]` = 13-bit PID (upper 5 bits).
158pub const PID_MASK_HI: u8 = 0x1F;
159/// ISO/IEC 13818-1 §2.4.3.3: byte 3 bits `[7:6]` = 2-bit scrambling control.
160pub const SCRAMBLING_MASK: u8 = 0xC0;
161/// ISO/IEC 13818-1 §2.4.3.3: byte 3 bit 5 = adaptation_field_control (bit 5 = 1 means adaptation present).
162pub const ADAPTATION_FLAG: u8 = 0x20;
163/// ISO/IEC 13818-1 §2.4.3.3: byte 3 bit 4 = adaptation_field_control (bit 4 = 1 means payload present).
164pub const PAYLOAD_FLAG: u8 = 0x10;
165/// ISO/IEC 13818-1 §2.4.3.3: byte 3 bits `[3:0]` = 4-bit continuity_counter.
166pub const CC_MASK: u8 = 0x0F;
167
168/// Parsed TS header — the 4-byte transport header fields.
169#[derive(Clone, Debug, PartialEq, Eq)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize))]
171pub struct TsHeader {
172    /// Transport Error Indicator — set by the demodulator when an
173    /// uncorrectable error is present in the packet.
174    pub tei: bool,
175    /// Payload Unit Start Indicator — first byte of the payload is a new
176    /// PES packet or PSI section header when set.
177    pub pusi: bool,
178    /// 13-bit Packet Identifier.
179    pub pid: u16,
180    /// 2-bit transport_scrambling_control (0 = not scrambled).
181    pub scrambling: u8,
182    /// Adaptation field present flag (adaptation_field_control bit 1).
183    pub has_adaptation: bool,
184    /// Payload present flag (adaptation_field_control bit 0).
185    pub has_payload: bool,
186    /// 4-bit continuity_counter (wraps 0..=15 per PID).
187    pub continuity_counter: u8,
188}
189
190/// Borrowed view into one 188-byte TS packet.
191///
192/// Serde: Serialize-only (re-parse from wire bytes to reconstruct). `raw` is
193/// excluded from the serialized form because it is redundant once the header
194/// has been parsed.
195#[derive(Clone, Debug)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize))]
197pub struct TsPacket<'a> {
198    /// Parsed header fields.
199    pub header: TsHeader,
200    /// Slice into the packet's payload, or `None` when `has_payload == false`
201    /// or the adaptation field consumed the whole packet body.
202    pub payload: Option<&'a [u8]>,
203    /// The adaptation-field bytes (after the length byte). Internal capture
204    /// feeding [`adaptation_field`](Self::adaptation_field); not public.
205    #[cfg_attr(feature = "serde", serde(skip))]
206    adaptation: Option<&'a [u8]>,
207    /// The raw 188 bytes of the packet — kept for cheap forwarding.
208    #[cfg_attr(feature = "serde", serde(skip))]
209    pub raw: &'a [u8; TS_PACKET_SIZE],
210}
211
212impl TsHeader {
213    /// Parse a 4-byte TS transport header.
214    pub fn parse(raw4: &[u8]) -> Result<Self> {
215        if raw4.len() < 4 {
216            return Err(Error::BufferTooShort {
217                need: 4,
218                have: raw4.len(),
219                what: "TsHeader",
220            });
221        }
222        let b1 = raw4[1];
223        let b2 = raw4[2];
224        let b3 = raw4[3];
225
226        let tei = (b1 & TEI_MASK) != 0;
227        let pusi = (b1 & PUSI_MASK) != 0;
228        let pid = (((b1 & PID_MASK_HI) as u16) << 8) | (b2 as u16);
229        let scrambling = (b3 & SCRAMBLING_MASK) >> 6;
230        let has_adaptation = (b3 & ADAPTATION_FLAG) != 0;
231        let has_payload = (b3 & PAYLOAD_FLAG) != 0;
232        let continuity_counter = b3 & CC_MASK;
233
234        Ok(Self {
235            tei,
236            pusi,
237            pid,
238            scrambling,
239            has_adaptation,
240            has_payload,
241            continuity_counter,
242        })
243    }
244
245    /// Number of bytes written by [`serialize_into`](Self::serialize_into).
246    pub const fn serialized_len() -> usize {
247        4
248    }
249
250    /// Serialize this header into the first 4 bytes of `buf`.
251    pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
252        if buf.len() < 4 {
253            return Err(Error::OutputBufferTooSmall {
254                need: 4,
255                have: buf.len(),
256            });
257        }
258        buf[0] = TS_SYNC_BYTE;
259        buf[1] = 0;
260        if self.tei {
261            buf[1] |= TEI_MASK;
262        }
263        if self.pusi {
264            buf[1] |= PUSI_MASK;
265        }
266        buf[1] |= ((self.pid >> 8) as u8) & PID_MASK_HI;
267        buf[2] = (self.pid & 0xFF) as u8;
268        buf[3] = (self.scrambling << 6) & SCRAMBLING_MASK;
269        if self.has_adaptation {
270            buf[3] |= ADAPTATION_FLAG;
271        }
272        if self.has_payload {
273            buf[3] |= PAYLOAD_FLAG;
274        }
275        buf[3] |= self.continuity_counter & CC_MASK;
276        Ok(4)
277    }
278
279    /// Typed view of the 2-bit `transport_scrambling_control` field.
280    ///
281    /// See [`ScramblingControl`] for the spec citation (H.222.0 Table 2-4 +
282    /// ETSI TS 100 289 §5.1 Table 1).
283    pub fn scrambling_control(&self) -> ScramblingControl {
284        ScramblingControl::from_bits(self.scrambling)
285    }
286
287    /// Typed view of the `adaptation_field_control` 2-bit field, derived from the
288    /// `has_adaptation`/`has_payload` flags.
289    ///
290    /// See [`AdaptationFieldControl`] for the spec citation (H.222.0 Table 2-5).
291    pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
292        AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
293    }
294}
295
296impl<'a> broadcast_common::Parse<'a> for TsHeader {
297    type Error = Error;
298
299    fn parse(bytes: &'a [u8]) -> Result<Self> {
300        TsHeader::parse(bytes)
301    }
302}
303
304impl broadcast_common::Serialize for TsHeader {
305    type Error = Error;
306
307    fn serialized_len(&self) -> usize {
308        TsHeader::serialized_len()
309    }
310
311    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
312        TsHeader::serialize_into(self, buf)
313    }
314}
315
316impl<'a> TsPacket<'a> {
317    /// Parse a single 188-byte TS packet from a buffer.
318    ///
319    /// Returns `Err(Error::InvalidSyncByte)` if the first byte is not `0x47`,
320    /// `Err(Error::BufferTooShort)` if fewer than 188 bytes, or `Ok` with
321    /// the parsed packet otherwise.
322    pub fn parse(buf: &'a [u8]) -> Result<Self> {
323        if buf.len() < TS_PACKET_SIZE {
324            return Err(Error::BufferTooShort {
325                need: TS_PACKET_SIZE,
326                have: buf.len(),
327                what: "TsPacket",
328            });
329        }
330        if buf[0] != TS_SYNC_BYTE {
331            return Err(Error::InvalidSyncByte { found: buf[0] });
332        }
333
334        let raw: &[u8; TS_PACKET_SIZE] =
335            buf[..TS_PACKET_SIZE]
336                .try_into()
337                .map_err(|_| Error::BufferTooShort {
338                    need: TS_PACKET_SIZE,
339                    have: buf.len(),
340                    what: "TsPacket::parse (array conversion)",
341                })?;
342
343        let header = TsHeader::parse(&raw[..4])?;
344
345        let mut cursor = 4usize;
346        let mut payload = None;
347        let mut adaptation = None;
348
349        // Capture the adaptation field if present, then skip it (the section
350        // path does not need it; decode lazily via `adaptation_field`).
351        if header.has_adaptation && cursor < TS_PACKET_SIZE {
352            let af_len = raw[cursor] as usize;
353            let af_start = cursor + 1;
354            if af_len > 0 && af_start < TS_PACKET_SIZE {
355                let af_end = (af_start + af_len).min(TS_PACKET_SIZE);
356                adaptation = Some(&raw[af_start..af_end]);
357            }
358            cursor += 1 + af_len;
359        }
360
361        if header.has_payload && cursor < TS_PACKET_SIZE {
362            payload = Some(&raw[cursor..]);
363        }
364
365        Ok(TsPacket {
366            header,
367            payload,
368            adaptation,
369            raw,
370        })
371    }
372
373    /// Decode the adaptation field, if present.
374    ///
375    /// Returns `None` when the packet carries no adaptation field, and
376    /// `Some(Err(..))` when a present field is truncated. Layout per
377    /// ISO/IEC 13818-1:2007 §2.4.3.4 (`docs/iso_13818_1_systems.md`).
378    pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField<'a>>> {
379        self.adaptation.map(AdaptationField::parse)
380    }
381}
382
383// Adaptation-field flag bits, byte 0 (ISO/IEC 13818-1:2007 §2.4.3.4).
384pub(crate) const AF_DISCONTINUITY: u8 = 0x80;
385pub(crate) const AF_RANDOM_ACCESS: u8 = 0x40;
386pub(crate) const AF_ES_PRIORITY: u8 = 0x20;
387/// PCR present flag (bit 4 of adaptation field flags byte — §2.4.3.4).
388pub const AF_PCR_FLAG: u8 = 0x10;
389pub(crate) const AF_OPCR_FLAG: u8 = 0x08;
390pub(crate) const AF_SPLICING_FLAG: u8 = 0x04;
391pub(crate) const AF_TRANSPORT_PRIVATE_DATA_FLAG: u8 = 0x02;
392pub(crate) const AF_EXTENSION_FLAG: u8 = 0x01;
393/// Adaptation-field stuffing byte (ISO/IEC 13818-1:2007 §2.4.3.4 — `0xFF`).
394pub(crate) const AF_STUFFING_BYTE: u8 = 0xFF;
395/// Encoded PCR / OPCR field width: 33-bit base + 6 reserved + 9-bit extension.
396pub(crate) const PCR_FIELD_LEN: usize = 6;
397
398/// Program Clock Reference (ISO/IEC 13818-1:2007 §2.4.3.5): a 33-bit base on a
399/// 90 kHz clock plus a 9-bit extension on a 27 MHz clock.
400#[derive(Clone, Copy, Debug, PartialEq, Eq)]
401#[cfg_attr(feature = "serde", derive(serde::Serialize))]
402pub struct Pcr {
403    /// 33-bit base (90 kHz units).
404    pub base: u64,
405    /// 9-bit extension (27 MHz units).
406    pub extension: u16,
407}
408
409impl Pcr {
410    /// Full PCR value on the 27 MHz clock: `base * 300 + extension`.
411    ///
412    /// ISO/IEC 13818-1:2007 §2.4.3.5: PCR = `PCR_base * 300 + PCR_ext`.
413    #[must_use]
414    pub fn as_27mhz(self) -> u64 {
415        self.base * 300 + self.extension as u64
416    }
417
418    /// Construct a [`Pcr`] from an absolute 27 MHz clock value.
419    ///
420    /// Decomposes `ticks` into `base = ticks / 300` and
421    /// `extension = ticks % 300`, clamping each to its wire width
422    /// (33-bit base, 9-bit extension) — ISO/IEC 13818-1:2007 §2.4.3.5.
423    ///
424    /// Round-trips with [`as_27mhz`](Self::as_27mhz):
425    /// `Pcr::from_27mhz(p.as_27mhz()) == p` for any valid `Pcr`.
426    #[must_use]
427    pub fn from_27mhz(ticks: u64) -> Self {
428        // 33-bit base mask: (1 << 33) - 1 = 0x1_FFFF_FFFF
429        const BASE_MASK: u64 = 0x1_FFFF_FFFF;
430        // 9-bit extension mask.
431        const EXT_MASK: u16 = 0x1FF;
432        Self {
433            base: (ticks / 300) & BASE_MASK,
434            extension: ((ticks % 300) as u16) & EXT_MASK,
435        }
436    }
437
438    /// Encode as the exact 6-byte PCR/OPCR field used in the adaptation field
439    /// (ISO/IEC 13818-1:2007 §2.4.3.5).
440    ///
441    /// Wire layout (big-endian, 48 bits):
442    /// `[base[32:25]][base[24:17]][base[16:9]][base[8:1]][base[0] | 6×reserved(1) | ext[8]][ext[7:0]]`
443    ///
444    /// The 6 reserved bits are set to `1` as per the spec's "reserved" convention.
445    /// Exact inverse of the private `parse` function.
446    #[must_use]
447    pub fn to_field_bytes(self) -> [u8; PCR_FIELD_LEN] {
448        let b = self.base;
449        let e = self.extension as u64;
450        [
451            ((b >> 25) & 0xFF) as u8,
452            ((b >> 17) & 0xFF) as u8,
453            ((b >> 9) & 0xFF) as u8,
454            ((b >> 1) & 0xFF) as u8,
455            // byte 4: base[0] in bit 7, bits 6-1 = reserved (set to 1), ext[8] in bit 0.
456            (((b & 0x01) as u8) << 7) | 0x7E | ((e >> 8) as u8 & 0x01),
457            (e & 0xFF) as u8,
458        ]
459    }
460
461    /// Decode the 6-byte PCR/OPCR field starting at `at` within `af`.
462    pub(crate) fn parse(af: &[u8], at: usize) -> Result<Self> {
463        let b: &[u8; PCR_FIELD_LEN] = af
464            .get(at..at + PCR_FIELD_LEN)
465            .and_then(|s| s.try_into().ok())
466            .ok_or(Error::BufferTooShort {
467                need: at + PCR_FIELD_LEN,
468                have: af.len(),
469                what: "adaptation_field PCR",
470            })?;
471        let base = ((b[0] as u64) << 25)
472            | ((b[1] as u64) << 17)
473            | ((b[2] as u64) << 9)
474            | ((b[3] as u64) << 1)
475            | ((b[4] as u64) >> 7);
476        let extension = (((b[4] & 0x01) as u16) << 8) | (b[5] as u16);
477        Ok(Self { base, extension })
478    }
479}
480
481// ── Adaptation-field extension sub-structures (ISO/IEC 13818-1 §2.4.3.5) ──────
482
483/// `legal_time_window` field within the adaptation-field extension
484/// (ISO/IEC 13818-1:2007 §2.4.3.5, `ltw_flag == 1`).
485///
486/// Wire layout: `ltw_valid_flag(1) | ltw_offset(15)` = 2 bytes.
487#[derive(Clone, Copy, Debug, PartialEq, Eq)]
488#[cfg_attr(feature = "serde", derive(serde::Serialize))]
489pub struct Ltw {
490    /// LTW offset valid flag.
491    pub ltw_valid_flag: bool,
492    /// 15-bit `ltw_offset` (lower bound of the legal time window).
493    pub ltw_offset: u16,
494}
495
496/// `seamless_splice` field within the adaptation-field extension
497/// (ISO/IEC 13818-1:2007 §2.4.3.5, `seamless_splice_flag == 1`).
498///
499/// Wire layout: 5 bytes — `splice_type(4) | DTS_next_AU[32:30](3) | marker(1) |
500/// DTS_next_AU[29:15](15) | marker(1) | DTS_next_AU[14:0](15) | marker(1)`.
501/// The DTS field uses the same marker-bit encoding as PTS/DTS in PES headers.
502#[derive(Clone, Copy, Debug, PartialEq, Eq)]
503#[cfg_attr(feature = "serde", derive(serde::Serialize))]
504pub struct SeamlessSplice {
505    /// 4-bit `splice_type`.
506    pub splice_type: u8,
507    /// 33-bit `DTS_next_AU` (90 kHz decoding time of the next splice unit).
508    pub dts_next_au: u64,
509}
510
511/// Adaptation-field extension (ISO/IEC 13818-1:2007 §2.4.3.5,
512/// `adaptation_field_extension_flag == 1`).
513///
514/// Contains optional sub-fields gated by `ltw_flag`,
515/// `piecewise_rate_flag`, and `seamless_splice_flag`.
516#[derive(Clone, Copy, Debug, PartialEq, Eq)]
517#[cfg_attr(feature = "serde", derive(serde::Serialize))]
518pub struct AdaptationFieldExtension {
519    /// LTW (legal time window), if `ltw_flag` is set.
520    pub ltw: Option<Ltw>,
521    /// 22-bit piecewise rate, if `piecewise_rate_flag` is set.
522    pub piecewise_rate: Option<u32>,
523    /// Seamless splice info, if `seamless_splice_flag` is set.
524    pub seamless_splice: Option<SeamlessSplice>,
525}
526
527impl AdaptationFieldExtension {
528    /// Parse the adaptation-field extension starting at `data[0]`
529    /// (the `adaptation_field_extension_length` byte).
530    fn parse(data: &[u8]) -> Result<Self> {
531        if data.is_empty() {
532            return Err(Error::BufferTooShort {
533                need: 1,
534                have: 0,
535                what: "adaptation_field_extension_length",
536            });
537        }
538        let ext_len = data[0] as usize;
539        // At least 1 byte (the flags byte) must be present inside the extension.
540        if data.len() < 1 + ext_len || ext_len < 1 {
541            return Err(Error::BufferTooShort {
542                need: 2.max(1 + ext_len),
543                have: data.len(),
544                what: "adaptation_field_extension body",
545            });
546        }
547        let ext = &data[1..1 + ext_len]; // extension bytes, starts with flags byte
548        let flags = ext[0];
549        let mut cursor = 1usize;
550
551        let ltw = if flags & 0x80 != 0 {
552            if ext.len() < cursor + 2 {
553                return Err(Error::BufferTooShort {
554                    need: cursor + 2,
555                    have: ext.len(),
556                    what: "ltw_offset",
557                });
558            }
559            let w0 = ext[cursor];
560            let w1 = ext[cursor + 1];
561            cursor += 2;
562            Some(Ltw {
563                ltw_valid_flag: (w0 & 0x80) != 0,
564                ltw_offset: (((w0 & 0x7F) as u16) << 8) | (w1 as u16),
565            })
566        } else {
567            None
568        };
569
570        let piecewise_rate = if flags & 0x40 != 0 {
571            if ext.len() < cursor + 3 {
572                return Err(Error::BufferTooShort {
573                    need: cursor + 3,
574                    have: ext.len(),
575                    what: "piecewise_rate",
576                });
577            }
578            let r = (((ext[cursor] & 0x3F) as u32) << 16)
579                | ((ext[cursor + 1] as u32) << 8)
580                | (ext[cursor + 2] as u32);
581            cursor += 3;
582            Some(r)
583        } else {
584            None
585        };
586
587        // seamless_splice: 5 bytes with splice_type(4) + DTS_next_AU in PTS-field encoding
588        let seamless_splice = if flags & 0x20 != 0 {
589            if ext.len() < cursor + 5 {
590                return Err(Error::BufferTooShort {
591                    need: cursor + 5,
592                    have: ext.len(),
593                    what: "seamless_splice DTS_next_AU",
594                });
595            }
596            let b = &ext[cursor..cursor + 5];
597            let splice_type = (b[0] >> 4) & 0x0F;
598            // DTS_next_AU uses the same 5-byte marker-bit encoding as PTS/DTS.
599            let hi = u64::from((b[0] >> 1) & 0x07); // [32:30]
600            let mid = (u64::from(b[1]) << 7) | u64::from(b[2] >> 1); // [29:15]
601            let lo = (u64::from(b[3]) << 7) | u64::from(b[4] >> 1); // [14:0]
602            let dts_next_au = (hi << 30) | (mid << 15) | lo;
603            cursor += 5;
604            Some(SeamlessSplice {
605                splice_type,
606                dts_next_au,
607            })
608        } else {
609            None
610        };
611        let _ = cursor; // remaining extension bytes (reserved) are skipped
612
613        Ok(AdaptationFieldExtension {
614            ltw,
615            piecewise_rate,
616            seamless_splice,
617        })
618    }
619
620    /// Number of bytes written by [`serialize_into`](Self::serialize_into),
621    /// **including** the leading `adaptation_field_extension_length` byte.
622    #[must_use]
623    pub fn serialized_len(&self) -> usize {
624        let body = 1 // flags byte
625            + self.ltw.map_or(0, |_| 2)
626            + self.piecewise_rate.map_or(0, |_| 3)
627            + self.seamless_splice.map_or(0, |_| 5);
628        1 + body // + length byte itself
629    }
630
631    /// Serialize into `buf` (includes the `adaptation_field_extension_length` byte).
632    pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
633        let need = self.serialized_len();
634        if buf.len() < need {
635            return Err(Error::OutputBufferTooSmall {
636                need,
637                have: buf.len(),
638            });
639        }
640        let body_len = need - 1;
641        buf[0] = body_len as u8;
642
643        let mut flags = 0u8;
644        if self.ltw.is_some() {
645            flags |= 0x80;
646        }
647        if self.piecewise_rate.is_some() {
648            flags |= 0x40;
649        }
650        if self.seamless_splice.is_some() {
651            flags |= 0x20;
652        }
653        buf[1] = flags;
654        let mut cursor = 2usize;
655
656        if let Some(ltw) = self.ltw {
657            let ltw_valid = if ltw.ltw_valid_flag { 0x80u8 } else { 0x00 };
658            buf[cursor] = ltw_valid | ((ltw.ltw_offset >> 8) as u8 & 0x7F);
659            buf[cursor + 1] = (ltw.ltw_offset & 0xFF) as u8;
660            cursor += 2;
661        }
662        if let Some(rate) = self.piecewise_rate {
663            // 2 reserved bits (set to 1) | 22-bit rate (ISO/IEC 13818-1 §2.4.3.5).
664            buf[cursor] = 0xC0 | ((rate >> 16) as u8 & 0x3F);
665            buf[cursor + 1] = (rate >> 8) as u8;
666            buf[cursor + 2] = rate as u8;
667            cursor += 3;
668        }
669        if let Some(ss) = self.seamless_splice {
670            let ts = ss.dts_next_au & 0x1_FFFF_FFFF;
671            let st = ss.splice_type & 0x0F;
672            // byte 0: splice_type(4) | DTS[32:30](3) | marker(1)
673            buf[cursor] = (st << 4) | ((((ts >> 30) & 0x07) as u8) << 1) | 0x01;
674            buf[cursor + 1] = ((ts >> 22) & 0xFF) as u8;
675            buf[cursor + 2] = ((((ts >> 15) & 0x7F) as u8) << 1) | 0x01;
676            buf[cursor + 3] = ((ts >> 7) & 0xFF) as u8;
677            buf[cursor + 4] = (((ts & 0x7F) as u8) << 1) | 0x01;
678            cursor += 5;
679        }
680        Ok(cursor)
681    }
682}
683
684/// Decoded adaptation field — full §2.4.3.4 layout including transport-private
685/// data and the adaptation-field extension sub-structure.
686///
687/// The `transport_private_data` slice borrows from the original packet buffer
688/// (it is genuinely opaque caller-defined bytes per the spec — `&[u8]` is the
689/// correct public type). All other fields are fully typed.
690///
691/// ISO/IEC 13818-1:2007 §2.4.3.4.
692#[non_exhaustive]
693#[derive(Clone, Debug, PartialEq, Eq)]
694#[cfg_attr(feature = "serde", derive(serde::Serialize))]
695pub struct AdaptationField<'a> {
696    /// A timing/continuity discontinuity starts at this packet.
697    pub discontinuity_indicator: bool,
698    /// This packet is a random-access point.
699    pub random_access_indicator: bool,
700    /// Elementary-stream priority hint.
701    pub elementary_stream_priority_indicator: bool,
702    /// Program Clock Reference, present iff the PCR flag is set.
703    pub pcr: Option<Pcr>,
704    /// Original PCR, present iff the OPCR flag is set.
705    pub opcr: Option<Pcr>,
706    /// Splice countdown (packets until the splice point), iff the flag is set.
707    pub splice_countdown: Option<i8>,
708    /// Opaque transport-private data (caller-defined; `&[u8]` is spec-correct here).
709    ///
710    /// Present iff `transport_private_data_flag` is set in the flags byte
711    /// (ISO/IEC 13818-1:2007 §2.4.3.4).
712    pub transport_private_data: Option<&'a [u8]>,
713    /// Typed adaptation-field extension sub-structure, if the flag is set.
714    pub extension: Option<AdaptationFieldExtension>,
715    /// Number of trailing `0xFF` stuffing bytes that pad the adaptation-field
716    /// body out to its declared `adaptation_field_length` (ISO/IEC 13818-1:2007
717    /// §2.4.3.4 — "stuffing_byte: fixed 8-bit value `0xFF`").
718    ///
719    /// Real encoders pad the adaptation field so the packet payload begins at a
720    /// fixed offset; these bytes are part of the wire image. Capturing the count
721    /// lets [`serialize_into`](Self::serialize_into) reproduce the packet
722    /// byte-for-byte. Set to `0` when constructing an adaptation field with no
723    /// stuffing.
724    pub stuffing_len: usize,
725}
726
727impl<'a> AdaptationField<'a> {
728    /// Parse the adaptation-field bytes (those following the length byte).
729    ///
730    /// `af` must be exactly the adaptation-field body bytes: the slice starts
731    /// at the **flags byte** (byte 0 of `af`), i.e. the bytes AFTER the
732    /// `adaptation_field_length` byte itself. This matches how
733    /// `TsPacket::parse` captures and hands them off.
734    pub(crate) fn parse(af: &'a [u8]) -> Result<Self> {
735        let flags = *af.first().ok_or(Error::BufferTooShort {
736            need: 1,
737            have: 0,
738            what: "adaptation_field flags",
739        })?;
740        let mut cursor = 1usize;
741
742        let pcr = if flags & AF_PCR_FLAG != 0 {
743            let p = Pcr::parse(af, cursor)?;
744            cursor += PCR_FIELD_LEN;
745            Some(p)
746        } else {
747            None
748        };
749        let opcr = if flags & AF_OPCR_FLAG != 0 {
750            let p = Pcr::parse(af, cursor)?;
751            cursor += PCR_FIELD_LEN;
752            Some(p)
753        } else {
754            None
755        };
756        let splice_countdown = if flags & AF_SPLICING_FLAG != 0 {
757            let b = *af.get(cursor).ok_or(Error::BufferTooShort {
758                need: cursor + 1,
759                have: af.len(),
760                what: "adaptation_field splice_countdown",
761            })?;
762            cursor += 1;
763            Some(b as i8)
764        } else {
765            None
766        };
767
768        // transport_private_data (ISO/IEC 13818-1 §2.4.3.4):
769        // transport_private_data_length(8) + transport_private_data_byte * N
770        let transport_private_data = if flags & AF_TRANSPORT_PRIVATE_DATA_FLAG != 0 {
771            let tpd_len = *af.get(cursor).ok_or(Error::BufferTooShort {
772                need: cursor + 1,
773                have: af.len(),
774                what: "transport_private_data_length",
775            })? as usize;
776            cursor += 1;
777            let end = cursor + tpd_len;
778            let slice = af.get(cursor..end).ok_or(Error::BufferTooShort {
779                need: end,
780                have: af.len(),
781                what: "transport_private_data",
782            })?;
783            cursor = end;
784            Some(slice)
785        } else {
786            None
787        };
788
789        // adaptation_field_extension (ISO/IEC 13818-1 §2.4.3.5):
790        let extension = if flags & AF_EXTENSION_FLAG != 0 {
791            let ext_data = af.get(cursor..).ok_or(Error::BufferTooShort {
792                need: cursor + 1,
793                have: af.len(),
794                what: "adaptation_field_extension",
795            })?;
796            let ext = AdaptationFieldExtension::parse(ext_data)?;
797            // advance cursor past the extension (ext_data[0] = length byte)
798            if !ext_data.is_empty() {
799                let _ext_len = ext_data[0] as usize;
800                cursor += 1 + _ext_len;
801            }
802            Some(ext)
803        } else {
804            None
805        };
806
807        // Any bytes after the last present field, up to `adaptation_field_length`
808        // (= `af.len()`), are `0xFF` stuffing (ISO/IEC 13818-1:2007 §2.4.3.4).
809        // Record the count so serialization reproduces the body byte-for-byte.
810        let stuffing_len = af.len().saturating_sub(cursor);
811
812        Ok(AdaptationField {
813            discontinuity_indicator: flags & AF_DISCONTINUITY != 0,
814            random_access_indicator: flags & AF_RANDOM_ACCESS != 0,
815            elementary_stream_priority_indicator: flags & AF_ES_PRIORITY != 0,
816            pcr,
817            opcr,
818            splice_countdown,
819            transport_private_data,
820            extension,
821            stuffing_len,
822        })
823    }
824
825    /// Number of bytes written by [`serialize_into`](Self::serialize_into).
826    ///
827    /// This is the body length **excluding** the leading `adaptation_field_length`
828    /// byte — it is the value carried in that length byte itself
829    /// (ISO/IEC 13818-1:2007 §2.4.3.4).
830    #[must_use]
831    pub fn serialized_len(&self) -> usize {
832        let mut n = 1usize; // flags byte
833        if self.pcr.is_some() {
834            n += PCR_FIELD_LEN;
835        }
836        if self.opcr.is_some() {
837            n += PCR_FIELD_LEN;
838        }
839        if self.splice_countdown.is_some() {
840            n += 1;
841        }
842        if let Some(tpd) = self.transport_private_data {
843            n += 1 + tpd.len(); // length byte + data
844        }
845        if let Some(ref ext) = self.extension {
846            n += ext.serialized_len();
847        }
848        n += self.stuffing_len;
849        n
850    }
851
852    /// Serialize the adaptation field into `buf`.
853    ///
854    /// Writes the **body** bytes — the flags byte plus optional fields in the
855    /// order specified by ISO/IEC 13818-1:2007 §2.4.3.4. The
856    /// `adaptation_field_length` byte itself is **not** written here; the caller
857    /// must prepend it (it equals `serialized_len()`).
858    ///
859    /// Returns the number of bytes written on success, or
860    /// [`Error::OutputBufferTooSmall`] if `buf` is shorter than
861    /// `serialized_len()`.
862    pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
863        let need = self.serialized_len();
864        if buf.len() < need {
865            return Err(Error::OutputBufferTooSmall {
866                need,
867                have: buf.len(),
868            });
869        }
870
871        // Byte 0: flags (ISO/IEC 13818-1:2007 §2.4.3.4).
872        let mut flags = 0u8;
873        if self.discontinuity_indicator {
874            flags |= AF_DISCONTINUITY;
875        }
876        if self.random_access_indicator {
877            flags |= AF_RANDOM_ACCESS;
878        }
879        if self.elementary_stream_priority_indicator {
880            flags |= AF_ES_PRIORITY;
881        }
882        if self.pcr.is_some() {
883            flags |= AF_PCR_FLAG;
884        }
885        if self.opcr.is_some() {
886            flags |= AF_OPCR_FLAG;
887        }
888        if self.splice_countdown.is_some() {
889            flags |= AF_SPLICING_FLAG;
890        }
891        if self.transport_private_data.is_some() {
892            flags |= AF_TRANSPORT_PRIVATE_DATA_FLAG;
893        }
894        if self.extension.is_some() {
895            flags |= AF_EXTENSION_FLAG;
896        }
897        buf[0] = flags;
898
899        let mut cursor = 1usize;
900        if let Some(pcr) = self.pcr {
901            buf[cursor..cursor + PCR_FIELD_LEN].copy_from_slice(&pcr.to_field_bytes());
902            cursor += PCR_FIELD_LEN;
903        }
904        if let Some(opcr) = self.opcr {
905            buf[cursor..cursor + PCR_FIELD_LEN].copy_from_slice(&opcr.to_field_bytes());
906            cursor += PCR_FIELD_LEN;
907        }
908        if let Some(sc) = self.splice_countdown {
909            buf[cursor] = sc as u8;
910            cursor += 1;
911        }
912        if let Some(tpd) = self.transport_private_data {
913            buf[cursor] = tpd.len() as u8;
914            cursor += 1;
915            buf[cursor..cursor + tpd.len()].copy_from_slice(tpd);
916            cursor += tpd.len();
917        }
918        if let Some(ref ext) = self.extension {
919            let written = ext.serialize_into(&mut buf[cursor..])?;
920            cursor += written;
921        }
922
923        // Trailing `0xFF` stuffing (ISO/IEC 13818-1:2007 §2.4.3.4), reproducing
924        // the padding the encoder used to fill `adaptation_field_length`.
925        for b in buf[cursor..cursor + self.stuffing_len].iter_mut() {
926            *b = AF_STUFFING_BYTE;
927        }
928        cursor += self.stuffing_len;
929
930        Ok(cursor)
931    }
932}
933
934/// Reassembles PSI/SI sections from TS packets on a single PID.
935///
936/// Feed each TS packet's payload with `feed`. Complete sections are
937/// appended to an internal queue; drain them with `pop_section`.
938#[derive(Default)]
939pub struct SectionReassembler {
940    buf: bytes::BytesMut,
941    ready: alloc::collections::VecDeque<bytes::Bytes>,
942}
943
944impl SectionReassembler {
945    /// Feed a TS payload and whether its packet had PUSI set.
946    ///
947    /// Extracts complete SI sections into the internal queue. A single call
948    /// can produce zero, one, or **several** sections — a payload may
949    /// concatenate multiple complete sections after the `pointer_field`
950    /// (EN 300 468 §5.1.4; common on EMM PIDs). Drain with a
951    /// `while let Some(s) = r.pop_section()` loop, not a single `if let`.
952    pub fn feed(&mut self, payload: &[u8], pusi: bool) {
953        if pusi {
954            // A PUSI packet whose adaptation field consumed the whole body is
955            // malformed but constructible — drop sync rather than panic.
956            if payload.is_empty() {
957                self.buf.clear();
958                return;
959            }
960            let pointer = payload[0] as usize;
961
962            // The `pointer_field` counts bytes that belong to a section still
963            // in progress from a previous packet (ISO/IEC 13818-1 §2.4.4): the
964            // `pointer` bytes immediately after it are that section's tail and
965            // must complete it BEFORE new sections begin at `1 + pointer`.
966            // Skipping them (or clearing `buf` first) drops any section that
967            // spans into a PUSI packet — silent loss biased toward whichever
968            // section happens to straddle a packet boundary.
969            if !self.buf.is_empty() && pointer > 0 {
970                let avail = payload.len() - 1;
971                let tail_len = pointer.min(avail);
972                if self.buf.len() + tail_len > MAX_SECTION_SIZE {
973                    self.buf.clear();
974                } else {
975                    self.buf.extend_from_slice(&payload[1..1 + tail_len]);
976                    self.drain_complete_sections();
977                }
978            }
979
980            // New sections start at `1 + pointer`; anything still buffered is
981            // an incomplete (corrupt / lost-packet) section — discard it.
982            self.buf.clear();
983
984            let start = 1 + pointer;
985            if start >= payload.len() {
986                // Pointer spans to (or past) the end — no new section here.
987                return;
988            }
989            let new_data = &payload[start..];
990            if new_data.len() > MAX_SECTION_SIZE {
991                return;
992            }
993            self.buf.extend_from_slice(new_data);
994        } else {
995            if self.buf.is_empty() {
996                return;
997            }
998            // Append only the bytes the in-progress section still needs. A new
999            // section cannot start in a continuation (non-PUSI) packet
1000            // (ISO/IEC 13818-1 §2.4.4), so once the section's declared length
1001            // is satisfied the remaining payload bytes are 0xFF stuffing and
1002            // are ignored. Counting that stuffing toward `MAX_SECTION_SIZE`
1003            // previously dropped valid near-maximal sections (#148). Because
1004            // the 12-bit `section_length` caps a section at `MAX_SECTION_SIZE`,
1005            // `take` is inherently bounded and the buffer cannot grow without
1006            // limit.
1007            let take = if self.buf.len() >= SECTION_HEADER_LEN {
1008                let exp = SECTION_HEADER_LEN
1009                    + (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8
1010                        | self.buf[2] as usize);
1011                exp.saturating_sub(self.buf.len()).min(payload.len())
1012            } else {
1013                // Header not yet complete (split across the packet boundary) —
1014                // take enough to read `section_length` on the next drain,
1015                // bounded by the maximum possible section size.
1016                payload.len().min(MAX_SECTION_SIZE - self.buf.len())
1017            };
1018            self.buf.extend_from_slice(&payload[..take]);
1019        }
1020
1021        self.drain_complete_sections();
1022    }
1023
1024    /// Queue every complete section the buffer currently holds.
1025    ///
1026    /// A single TS payload may concatenate multiple complete sections after
1027    /// the `pointer_field` (legal per ETSI EN 300 468 §5.1.4 and common on
1028    /// EMM PIDs, which pack several short messages into one payload). We must
1029    /// keep extracting until the buffer holds only a partial (multi-packet
1030    /// spanning) section, whose bytes stay buffered for the next packet to
1031    /// continue (the expected length is recomputed from the section header on
1032    /// each drain). A `0xFF` where a `table_id` is expected marks the rest of
1033    /// the payload as stuffing.
1034    fn drain_complete_sections(&mut self) {
1035        loop {
1036            if self.buf.len() < SECTION_HEADER_LEN {
1037                // Not enough for a section header yet; keep the partial bytes
1038                // and wait for the next packet to complete the header.
1039                break;
1040            }
1041            if self.buf[0] == 0xFF {
1042                // Stuffing where a table_id is expected — payload tail is fill.
1043                self.buf.clear();
1044                break;
1045            }
1046            let exp = SECTION_HEADER_LEN
1047                + (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8 | self.buf[2] as usize);
1048            if self.buf.len() >= exp {
1049                // split_to returns the first `exp` bytes as an owned BytesMut,
1050                // leaving the remainder in self.buf — cheap (shifts pointers).
1051                let section = self.buf.split_to(exp).freeze();
1052                self.ready.push_back(section);
1053            } else {
1054                // Partial section spanning into later packets.
1055                break;
1056            }
1057        }
1058    }
1059
1060    /// Pop one complete section. Returns `None` when the queue is empty.
1061    pub fn pop_section(&mut self) -> Option<bytes::Bytes> {
1062        self.ready.pop_front()
1063    }
1064
1065    /// Number of bytes currently buffered (incomplete section).
1066    pub fn len(&self) -> usize {
1067        self.buf.len()
1068    }
1069
1070    /// True if no bytes are currently buffered.
1071    pub fn is_empty(&self) -> bool {
1072        self.buf.is_empty()
1073    }
1074}
1075
1076/// Iterate over all valid TS packets in a byte buffer.
1077///
1078/// Slices `buf` into 188-byte chunks (using [`slice::chunks_exact`]) and yields
1079/// each chunk for which [`TsPacket::parse`] succeeds. Chunks with a bad sync byte
1080/// (`!= 0x47`) or insufficient length are silently skipped — use
1081/// [`crate::resync::TsResync`] for byte-stream resynchronisation before calling
1082/// this when byte alignment is not guaranteed.
1083///
1084/// # Example
1085///
1086/// ```no_run
1087/// # use mpeg_ts::ts::iter_packets;
1088/// # let data: &[u8] = &[];
1089/// for pkt in iter_packets(data) {
1090///     println!("PID: 0x{:04X}", pkt.header.pid);
1091/// }
1092/// ```
1093pub fn iter_packets(buf: &[u8]) -> impl Iterator<Item = TsPacket<'_>> {
1094    buf.chunks_exact(TS_PACKET_SIZE)
1095        .filter_map(|chunk| TsPacket::parse(chunk).ok())
1096}
1097
1098/// Extract the payload bytes from a raw 188-byte TS packet slice.
1099///
1100/// Returns `None` when:
1101/// - `pkt` is fewer than 4 bytes,
1102/// - `adaptation_field_control` is `00` (reserved) or `10` (adaptation only), or
1103/// - the adaptation field length would place the payload start past the packet end.
1104///
1105/// No sync-byte check is performed — the caller is responsible for ensuring the
1106/// slice is properly aligned. Spec: ITU-T H.222.0 (08/2023) §2.4.3.3 Table 2-5.
1107pub fn extract_ts_payload(pkt: &[u8]) -> Option<&[u8]> {
1108    if pkt.len() < 4 {
1109        return None;
1110    }
1111    let afc = (pkt[3] >> 4) & 0x3;
1112    match afc {
1113        0x1 => {
1114            // payload only: payload starts at byte 4
1115            if pkt.len() > 4 { Some(&pkt[4..]) } else { None }
1116        }
1117        0x3 => {
1118            // adaptation field + payload
1119            if pkt.len() < 5 {
1120                return None;
1121            }
1122            let af_len = pkt[4] as usize;
1123            let start = 5 + af_len;
1124            if start < pkt.len() {
1125                Some(&pkt[start..])
1126            } else {
1127                None
1128            }
1129        }
1130        _ => None,
1131    }
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136    use super::*;
1137    use alloc::string::ToString;
1138    use alloc::vec;
1139    use alloc::vec::Vec;
1140
1141    /// Helper: construct a minimal 188-byte TS packet buffer with given header flags and payload.
1142    fn make_packet(b1: u8, b2: u8, b3: u8, payload_data: &[u8]) -> [u8; TS_PACKET_SIZE] {
1143        let mut pkt = [0u8; TS_PACKET_SIZE];
1144        pkt[0] = TS_SYNC_BYTE;
1145        pkt[1] = b1;
1146        pkt[2] = b2;
1147        pkt[3] = b3;
1148        let payload_start = 4;
1149        let end = (payload_start + payload_data.len()).min(TS_PACKET_SIZE);
1150        let len = (end - payload_start).min(payload_data.len());
1151        pkt[payload_start..payload_start + len].copy_from_slice(&payload_data[..len]);
1152        pkt
1153    }
1154
1155    #[test]
1156    fn parse_rejects_non_0x47_sync_byte() {
1157        let mut pkt = [0u8; TS_PACKET_SIZE];
1158        pkt[0] = 0x46; // wrong sync byte
1159        let err = TsPacket::parse(&pkt).unwrap_err();
1160        match err {
1161            Error::InvalidSyncByte { found } => assert_eq!(found, 0x46),
1162            other => panic!("expected InvalidSyncByte, got {other:?}"),
1163        }
1164    }
1165
1166    #[test]
1167    fn ts_header_round_trip() {
1168        // struct → serialize → parse must reproduce the header (the project's
1169        // symmetric Parse/Serialize invariant) across flag/field combinations.
1170        let cases = [
1171            TsHeader {
1172                tei: false,
1173                pusi: true,
1174                pid: 0x0000,
1175                scrambling: 0,
1176                has_adaptation: false,
1177                has_payload: true,
1178                continuity_counter: 0,
1179            },
1180            TsHeader {
1181                tei: true,
1182                pusi: false,
1183                pid: 0x1FFF,
1184                scrambling: 0b11,
1185                has_adaptation: true,
1186                has_payload: true,
1187                continuity_counter: 0x0F,
1188            },
1189            TsHeader {
1190                tei: false,
1191                pusi: false,
1192                pid: 0x0100,
1193                scrambling: 0b10,
1194                has_adaptation: true,
1195                has_payload: false,
1196                continuity_counter: 7,
1197            },
1198        ];
1199        for h in cases {
1200            let mut buf = [0u8; 4];
1201            assert_eq!(h.serialize_into(&mut buf).unwrap(), 4);
1202            assert_eq!(TsHeader::parse(&buf).unwrap(), h, "round-trip mismatch");
1203        }
1204    }
1205
1206    #[test]
1207    fn parse_extracts_pid_and_continuity_counter() {
1208        // PID = 0x1234 → upper 5 bits = 0x12, lower 8 bits = 0x34
1209        // CC = 5 → 0x05
1210        // b1 bits: [tei:1][pusi:1][pid_hi:5]
1211        // pid_hi = 0x12 = 0b00100_10 → bits 5..=1 = 0x12
1212        // b1 = 0b00_010010 = 0x12 (no tei, no pusi)
1213        let pkt = make_packet(0x12, 0x34, 0x05, &[]);
1214        let pkt = TsPacket::parse(&pkt).unwrap();
1215        assert_eq!(pkt.header.pid, 0x1234);
1216        assert_eq!(pkt.header.continuity_counter, 5);
1217    }
1218
1219    #[test]
1220    fn payload_unit_start_indicator_flag_extracted() {
1221        // b1 = 0x40 → pusi = true (bit 6 set, no tei, no pid bits)
1222        let pkt1 = make_packet(0x40, 0x00, 0x00, &[]);
1223        let pkt1 = TsPacket::parse(&pkt1).unwrap();
1224        assert!(pkt1.header.pusi);
1225
1226        // b1 = 0x00 → pusi = false
1227        let pkt2 = make_packet(0x00, 0x00, 0x00, &[]);
1228        let pkt2 = TsPacket::parse(&pkt2).unwrap();
1229        assert!(!pkt2.header.pusi);
1230    }
1231
1232    /// Build a PSI-carrying TS payload: `pointer_field` byte followed by
1233    /// (optionally) some tail of a previous section, followed by a fresh
1234    /// section. `pointer_field` is the number of bytes of the previous
1235    /// section that precede the new one (per ETSI EN 300 468 §5.1.4).
1236    fn build_pusi_payload(pointer_field: u8, previous_tail: &[u8], section: &[u8]) -> Vec<u8> {
1237        assert_eq!(pointer_field as usize, previous_tail.len());
1238        let mut v = Vec::with_capacity(1 + previous_tail.len() + section.len());
1239        v.push(pointer_field);
1240        v.extend_from_slice(previous_tail);
1241        v.extend_from_slice(section);
1242        v
1243    }
1244
1245    /// Build a long-form section with the given table_id and body bytes.
1246    /// Returns the full section including its 3-byte + 5-byte header and a
1247    /// placeholder CRC — for reassembler testing we don't validate the CRC.
1248    fn build_section(table_id: u8, body_after_length: &[u8]) -> Vec<u8> {
1249        let section_length = body_after_length.len() as u16;
1250        let mut v = Vec::with_capacity(3 + section_length as usize);
1251        v.push(table_id);
1252        // ssi=1, pi=0, reserved=11, length hi 4 bits
1253        v.push(0xB0 | ((section_length >> 8) as u8 & 0x0F));
1254        v.push((section_length & 0xFF) as u8);
1255        v.extend_from_slice(body_after_length);
1256        v
1257    }
1258
1259    // The reassembler tests below feed raw payload slices directly to
1260    // `feed()` rather than wrapping them in 188-byte TS packets. This avoids
1261    // the TS stuffing-byte tail (0xFF padding) bleeding into the reassembled
1262    // section and keeps the assertions exact.
1263
1264    #[test]
1265    fn reassembler_accumulates_multi_packet_section() {
1266        // 200-byte section that spans two payload slices.
1267        let body = vec![0xAAu8; 197];
1268        let section = build_section(0x02, &body);
1269        assert_eq!(section.len(), 200);
1270
1271        let first_chunk = 100;
1272        let payload1 = build_pusi_payload(0, &[], &section[..first_chunk]);
1273        let payload2 = section[first_chunk..].to_vec();
1274
1275        let mut reasm = SectionReassembler::default();
1276        reasm.feed(&payload1, true);
1277        reasm.feed(&payload2, false);
1278
1279        let out = reasm.pop_section().expect("section should be ready");
1280        assert_eq!(out.len(), 200);
1281        assert_eq!(out.as_ref(), &section[..]);
1282    }
1283
1284    #[test]
1285    fn reassembler_yields_complete_section_once_length_satisfied() {
1286        // 1-byte-body section: table_id=0x42, section_length=1, total=4 bytes.
1287        let section = build_section(0x42, &[0xAA]);
1288        assert_eq!(section.len(), 4);
1289        let payload = build_pusi_payload(0, &[], &section);
1290
1291        let mut reasm = SectionReassembler::default();
1292        reasm.feed(&payload, true);
1293
1294        let out = reasm
1295            .pop_section()
1296            .expect("single-packet section should pop");
1297        assert_eq!(out.as_ref(), &section[..]);
1298    }
1299
1300    #[test]
1301    fn reassembler_extracts_all_concatenated_sections_in_one_payload() {
1302        // Issue #29: a single PUSI payload packing three complete short
1303        // sections after the pointer_field. All three must be queued — the
1304        // old `feed` stopped after the first and the rest were silently lost
1305        // (the CAS/EMM data-loss bug: SHARED EMMs landing as the 2nd+ section).
1306        let s1 = build_section(0x42, &[0x11, 0x22]); // 5 bytes
1307        let s2 = build_section(0x46, &[0x33]); // 4 bytes
1308        let s3 = build_section(0x4A, &[0x44, 0x55, 0x66]); // 6 bytes
1309
1310        let mut concat = Vec::new();
1311        concat.extend_from_slice(&s1);
1312        concat.extend_from_slice(&s2);
1313        concat.extend_from_slice(&s3);
1314        let payload = build_pusi_payload(0, &[], &concat);
1315
1316        let mut reasm = SectionReassembler::default();
1317        reasm.feed(&payload, true);
1318
1319        // Consumers must drain with a loop, not a single `if let`.
1320        let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
1321        assert_eq!(got.len(), 3, "all three concatenated sections must pop");
1322        assert_eq!(got[0].as_ref(), &s1[..]);
1323        assert_eq!(got[1].as_ref(), &s2[..]);
1324        assert_eq!(got[2].as_ref(), &s3[..]);
1325    }
1326
1327    #[test]
1328    fn reassembler_stops_at_stuffing_after_concatenated_sections() {
1329        // Two sections then 0xFF stuffing fill — the stuffing must not be
1330        // mistaken for a section header (0xFF table_id) nor leak into a
1331        // section; both real sections still pop.
1332        let s1 = build_section(0x42, &[0xAA]); // 4 bytes
1333        let s2 = build_section(0x46, &[0xBB, 0xCC]); // 5 bytes
1334        let mut concat = Vec::new();
1335        concat.extend_from_slice(&s1);
1336        concat.extend_from_slice(&s2);
1337        concat.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); // stuffing tail
1338        let payload = build_pusi_payload(0, &[], &concat);
1339
1340        let mut reasm = SectionReassembler::default();
1341        reasm.feed(&payload, true);
1342
1343        let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
1344        assert_eq!(got.len(), 2);
1345        assert_eq!(got[0].as_ref(), &s1[..]);
1346        assert_eq!(got[1].as_ref(), &s2[..]);
1347        assert!(
1348            reasm.is_empty(),
1349            "stuffing tail must be discarded, not buffered"
1350        );
1351    }
1352
1353    #[test]
1354    fn reassembler_concatenated_then_spanning_tail() {
1355        // One complete section followed by the head of a second that spans
1356        // into a continuation packet: first pops immediately, second pops
1357        // once the continuation arrives.
1358        let s1 = build_section(0x42, &[0x01, 0x02]); // 5 bytes
1359        let s2 = build_section(0x46, &[0x09u8; 60]); // 63 bytes
1360        let split = 30;
1361
1362        let mut head = Vec::new();
1363        head.extend_from_slice(&s1);
1364        head.extend_from_slice(&s2[..split]);
1365        let payload1 = build_pusi_payload(0, &[], &head);
1366        let payload2 = s2[split..].to_vec();
1367
1368        let mut reasm = SectionReassembler::default();
1369        reasm.feed(&payload1, true);
1370        let first = reasm.pop_section().expect("first section pops at once");
1371        assert_eq!(first.as_ref(), &s1[..]);
1372        assert!(reasm.pop_section().is_none(), "second is still partial");
1373
1374        reasm.feed(&payload2, false);
1375        let second = reasm.pop_section().expect("second pops after continuation");
1376        assert_eq!(second.as_ref(), &s2[..]);
1377    }
1378
1379    #[test]
1380    fn reassembler_completes_section_spanning_into_pusi_packet() {
1381        // Issue #29 (second case): a section starts late in packet A and spills
1382        // into packet B, but B is itself PUSI=1 because new sections begin in it.
1383        // B's pointer_field = the count of leading tail bytes belonging to the
1384        // section from A. Those bytes MUST complete A's section before new
1385        // sections start. 3.1.1 cleared buf + skipped them → the spanning
1386        // section was lost (the SHARED EMM the smartcard needed).
1387        let spanning = build_section(0x42, &[0x5Au8; 62]); // 65 bytes
1388        let head = 41;
1389        let tail = &spanning[head..]; // 24 bytes — lands in packet B
1390        assert_eq!(tail.len(), 24);
1391
1392        // New section that begins in packet B after the spanning tail.
1393        let next = build_section(0x46, &[0x77, 0x88]); // 5 bytes
1394
1395        // Packet A (PUSI): pointer 0, then the 41-byte head (incomplete).
1396        let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
1397        // Packet B (PUSI): pointer = 24 (tail of A's section), then `next`.
1398        let payload_b = build_pusi_payload(24, tail, &next);
1399
1400        let mut reasm = SectionReassembler::default();
1401        reasm.feed(&payload_a, true);
1402        assert!(reasm.pop_section().is_none(), "head alone is incomplete");
1403
1404        reasm.feed(&payload_b, true);
1405        let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
1406        assert_eq!(got.len(), 2, "spanning section + new section must both pop");
1407        assert_eq!(
1408            got[0].as_ref(),
1409            &spanning[..],
1410            "spanning section completed from B's pointer tail"
1411        );
1412        assert_eq!(got[1].as_ref(), &next[..]);
1413    }
1414
1415    #[test]
1416    fn reassembler_pusi_pointer_spans_whole_payload() {
1417        // A section spans into a PUSI packet whose pointer covers the ENTIRE
1418        // remaining payload (no new section starts here) — the tail must be
1419        // appended and the section completed once the count is satisfied.
1420        let spanning = build_section(0x42, &[0x33u8; 40]); // 43 bytes
1421        let head = 20;
1422        let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
1423        let tail = &spanning[head..]; // 23 bytes — exactly the rest of payload B
1424
1425        let mut reasm = SectionReassembler::default();
1426        reasm.feed(&payload_a, true);
1427        // Packet B: pointer = 23 = all remaining bytes; no new section follows.
1428        reasm.feed(&build_pusi_payload_pointer_spanning_all(tail), true);
1429
1430        let out = reasm.pop_section().expect("spanning section completes");
1431        assert_eq!(out.as_ref(), &spanning[..]);
1432        assert!(reasm.pop_section().is_none());
1433    }
1434
1435    /// Build a PUSI payload whose `pointer_field` equals the whole tail (so the
1436    /// pointer spans to the end of the payload and no new section starts).
1437    fn build_pusi_payload_pointer_spanning_all(tail: &[u8]) -> Vec<u8> {
1438        let mut v = Vec::with_capacity(1 + tail.len());
1439        v.push(tail.len() as u8);
1440        v.extend_from_slice(tail);
1441        v
1442    }
1443
1444    #[test]
1445    fn reassembler_completes_max_length_section_and_stays_usable() {
1446        // A section declaring the maximum `section_length` (0xFFF → 4098 bytes
1447        // total). The 12-bit length structurally caps the buffer at
1448        // MAX_SECTION_SIZE, so there is no unbounded growth — and (unlike the
1449        // pre-#148 guard, which discarded once buf+payload crossed the cap) a
1450        // valid max-length section completes at its declared length.
1451        let mut section = Vec::with_capacity(MAX_SECTION_SIZE);
1452        section.push(0x00); // table_id
1453        section.push(0xB0 | ((4095u16 >> 8) as u8 & 0x0F));
1454        section.push(0xFF); // section_length = 0xFFF
1455        section.resize(MAX_SECTION_SIZE, 0u8);
1456        assert_eq!(section.len(), MAX_SECTION_SIZE);
1457
1458        let mut reasm = SectionReassembler::default();
1459        let mut first = vec![0x00u8]; // pointer_field 0
1460        first.extend_from_slice(&section[..183]);
1461        reasm.feed(&first, true);
1462        assert!(
1463            reasm.pop_section().is_none(),
1464            "incomplete until the declared length arrives"
1465        );
1466
1467        for chunk in section[183..].chunks(184) {
1468            reasm.feed(chunk, false);
1469        }
1470        let out = reasm
1471            .pop_section()
1472            .expect("max-length section completes at its declared length");
1473        assert_eq!(out.len(), MAX_SECTION_SIZE);
1474        assert_eq!(out.as_ref(), &section[..]);
1475        assert!(reasm.is_empty());
1476
1477        // Extra trailing continuation data after completion is ignored (the
1478        // buffer is empty, so a non-PUSI payload is dropped) — no panic, no
1479        // spurious section.
1480        reasm.feed(&[0u8; 184], false);
1481        assert!(reasm.pop_section().is_none());
1482
1483        // State must be resettable — a fresh valid PUSI section works.
1484        let valid_section = build_section(0x00, &[0xAA]);
1485        let payload2 = build_pusi_payload(0, &[], &valid_section);
1486        reasm.feed(&payload2, true);
1487        let out = reasm
1488            .pop_section()
1489            .expect("fresh section should pop after reset");
1490        assert_eq!(out.as_ref(), &valid_section[..]);
1491    }
1492
1493    #[test]
1494    fn reassembler_handles_pusi_with_nonzero_pointer_field() {
1495        // payload = pointer_field=3, 3 bytes of prior-section tail, then new section.
1496        let prior_tail = vec![0x11, 0x22, 0x33];
1497        let new_section = build_section(0x02, &[0xBB]);
1498        assert_eq!(new_section.len(), 4);
1499        let payload = build_pusi_payload(3, &prior_tail, &new_section);
1500
1501        let mut reasm = SectionReassembler::default();
1502        reasm.feed(&payload, true);
1503
1504        let out = reasm
1505            .pop_section()
1506            .expect("section after pointer_field skip should pop");
1507        assert_eq!(out.as_ref(), &new_section[..]);
1508    }
1509
1510    #[test]
1511    fn reassembler_ignores_continuation_before_pusi() {
1512        // Feed a non-PUSI payload first (no prior PUSI seen).
1513        // SectionReassembler should discard it and stay empty.
1514        let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA, 0xBB, 0xCC]);
1515
1516        let mut reasm = SectionReassembler::default();
1517        reasm.feed(&pkt[4..], false); // no PUSI
1518
1519        assert!(
1520            reasm.pop_section().is_none(),
1521            "no section should appear without prior PUSI"
1522        );
1523        assert!(
1524            reasm.pop_section().is_none(),
1525            "second pop should also be none"
1526        );
1527    }
1528
1529    /// A PUSI packet with an empty payload (adaptation field ate the body)
1530    /// is malformed but must not panic — it drops sync.
1531    #[test]
1532    fn reassembler_empty_pusi_payload_does_not_panic() {
1533        let mut reasm = SectionReassembler::default();
1534        reasm.feed(&[], true);
1535        assert!(reasm.pop_section().is_none());
1536        // Recovers on the next clean PUSI.
1537        let payload = vec![0x00u8, 0x72, 0x70, 0x01, 0x00];
1538        reasm.feed(&payload, true);
1539        assert!(reasm.pop_section().is_some());
1540    }
1541
1542    /// A maximal short-form private section (section_length 0xFFF, total
1543    /// 4098 bytes) reassembles — the ceiling is 12-bit length + 3-byte
1544    /// header, not 4096.
1545    #[test]
1546    fn reassembler_accepts_maximal_private_section() {
1547        let mut section = vec![0x80u8, 0x7F, 0xFF]; // user-private tid, SSI=0, len 0xFFF
1548        section.resize(3 + 0xFFF, 0xAB);
1549
1550        let mut reasm = SectionReassembler::default();
1551        // First TS payload: pointer_field 0 then the section start.
1552        let mut first = vec![0x00];
1553        first.extend_from_slice(&section[..183]);
1554        reasm.feed(&first, true);
1555        for chunk in section[183..].chunks(184) {
1556            reasm.feed(chunk, false);
1557        }
1558        let out = reasm.pop_section().expect("4098-byte section should pop");
1559        assert_eq!(out.len(), 4098);
1560        assert_eq!(out.as_ref(), &section[..]);
1561    }
1562
1563    /// Issue #148: a near-maximal section whose final continuation packet
1564    /// carries the section tail followed by `0xFF` **stuffing** must still
1565    /// complete. The old overflow guard counted the trailing stuffing toward
1566    /// `MAX_SECTION_SIZE` and dropped the section.
1567    #[test]
1568    fn reassembler_completes_large_section_with_trailing_stuffing() {
1569        let body = vec![0x5Au8; 4096 - 3];
1570        let section = build_section(0x50, &body); // 4096 bytes total
1571        assert_eq!(section.len(), 4096);
1572
1573        let mut reasm = SectionReassembler::default();
1574        // First payload (PUSI): pointer_field 0 + first 183 section bytes.
1575        let mut first = vec![0x00u8];
1576        first.extend_from_slice(&section[..183]);
1577        reasm.feed(&first, true);
1578
1579        // Continuation payloads of a full 184 bytes each; the final one is
1580        // padded with 0xFF stuffing to a complete 184-byte payload, exactly as
1581        // a real TS packet would carry it.
1582        let mut pos = 183usize;
1583        while pos < section.len() {
1584            let take = (section.len() - pos).min(184);
1585            let mut payload = section[pos..pos + take].to_vec();
1586            if take < 184 {
1587                payload.resize(184, 0xFF); // stuffing
1588            }
1589            reasm.feed(&payload, false);
1590            pos += take;
1591        }
1592
1593        let out = reasm
1594            .pop_section()
1595            .expect("4096-byte section must complete despite trailing stuffing (#148)");
1596        assert_eq!(out.len(), 4096);
1597        assert_eq!(out.as_ref(), &section[..]);
1598        assert!(reasm.is_empty(), "stuffing tail must be discarded");
1599    }
1600
1601    // ── adaptation field / PCR (ISO/IEC 13818-1 §2.4.3.4–2.4.3.5) ──
1602
1603    #[test]
1604    fn pcr_as_27mhz_known_value() {
1605        assert_eq!(
1606            Pcr {
1607                base: 10_000,
1608                extension: 0
1609            }
1610            .as_27mhz(),
1611            3_000_000
1612        );
1613        // base*300 + extension: 1*300 + 100 = 400.
1614        assert_eq!(
1615            Pcr {
1616                base: 1,
1617                extension: 100
1618            }
1619            .as_27mhz(),
1620            400
1621        );
1622    }
1623
1624    #[test]
1625    fn pcr_decode_from_bytes() {
1626        // 6-byte PCR encoding base=10000, extension=0 (reserved bits set).
1627        let af = [0x10u8, 0x00, 0x00, 0x13, 0x88, 0x7E, 0x00];
1628        let pcr = Pcr::parse(&af, 1).expect("6 bytes present");
1629        assert_eq!(
1630            pcr,
1631            Pcr {
1632                base: 10_000,
1633                extension: 0
1634            }
1635        );
1636        assert_eq!(pcr.as_27mhz(), 3_000_000);
1637    }
1638
1639    #[test]
1640    fn adaptation_field_flags_and_pcr() {
1641        let mut raw = [0xAAu8; TS_PACKET_SIZE];
1642        raw[0] = TS_SYNC_BYTE;
1643        raw[1] = 0x01; // pid 0x0100
1644        raw[2] = 0x00;
1645        raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
1646        raw[4] = 7; // adaptation_field_length: 1 flags + 6 PCR
1647        raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG;
1648        raw[6..12].copy_from_slice(&[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
1649        // raw[12..] stays 0xAA = payload.
1650
1651        let pkt = TsPacket::parse(&raw).expect("valid packet");
1652        let af = pkt
1653            .adaptation_field()
1654            .expect("has adaptation field")
1655            .expect("adaptation field parses");
1656        assert!(af.discontinuity_indicator);
1657        assert!(!af.random_access_indicator);
1658        assert_eq!(
1659            af.pcr,
1660            Some(Pcr {
1661                base: 10_000,
1662                extension: 0
1663            })
1664        );
1665        assert_eq!(af.pcr.unwrap().as_27mhz(), 3_000_000);
1666        assert!(af.opcr.is_none());
1667        assert!(af.splice_countdown.is_none());
1668        // Payload begins right after the adaptation field (cursor 4+1+7=12).
1669        let payload = pkt.payload.expect("payload present");
1670        assert_eq!(payload.len(), TS_PACKET_SIZE - 12);
1671        assert_eq!(payload[0], 0xAA);
1672    }
1673
1674    #[test]
1675    fn no_adaptation_returns_none() {
1676        let mut raw = [0x00u8; TS_PACKET_SIZE];
1677        raw[0] = TS_SYNC_BYTE;
1678        raw[1] = 0x01;
1679        raw[3] = PAYLOAD_FLAG; // payload only
1680        let pkt = TsPacket::parse(&raw).expect("valid");
1681        assert!(pkt.adaptation_field().is_none());
1682        assert!(pkt.adaptation.is_none());
1683    }
1684
1685    #[test]
1686    fn adaptation_field_splice_countdown_negative() {
1687        let mut raw = [0xAAu8; TS_PACKET_SIZE];
1688        raw[0] = TS_SYNC_BYTE;
1689        raw[1] = 0x01;
1690        raw[2] = 0x00;
1691        raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
1692        raw[4] = 2; // 1 flags + 1 splice_countdown
1693        raw[5] = AF_SPLICING_FLAG;
1694        raw[6] = 0xFB; // -5 as i8
1695        let pkt = TsPacket::parse(&raw).expect("valid");
1696        let af = pkt.adaptation_field().unwrap().unwrap();
1697        assert_eq!(af.splice_countdown, Some(-5));
1698        assert!(af.pcr.is_none());
1699    }
1700
1701    // ── ScramblingControl / AdaptationFieldControl enums ──
1702
1703    #[test]
1704    fn scrambling_control_all_values() {
1705        assert_eq!(
1706            ScramblingControl::from_bits(0b00),
1707            ScramblingControl::NotScrambled
1708        );
1709        assert_eq!(
1710            ScramblingControl::from_bits(0b01),
1711            ScramblingControl::Reserved
1712        );
1713        assert_eq!(
1714            ScramblingControl::from_bits(0b10),
1715            ScramblingControl::EvenKey
1716        );
1717        assert_eq!(
1718            ScramblingControl::from_bits(0b11),
1719            ScramblingControl::OddKey
1720        );
1721        // name() labels
1722        assert_eq!(ScramblingControl::NotScrambled.name(), "not_scrambled");
1723        assert_eq!(ScramblingControl::Reserved.name(), "reserved");
1724        assert_eq!(ScramblingControl::EvenKey.name(), "even_key");
1725        assert_eq!(ScramblingControl::OddKey.name(), "odd_key");
1726        // Display delegates to name()
1727        assert_eq!(ScramblingControl::NotScrambled.to_string(), "not_scrambled");
1728        assert_eq!(ScramblingControl::OddKey.to_string(), "odd_key");
1729        // Masking: only low 2 bits matter
1730        assert_eq!(
1731            ScramblingControl::from_bits(0xFF),
1732            ScramblingControl::OddKey
1733        );
1734    }
1735
1736    #[test]
1737    fn adaptation_field_control_all_values() {
1738        assert_eq!(
1739            AdaptationFieldControl::from_flags(false, false),
1740            AdaptationFieldControl::Reserved
1741        );
1742        assert_eq!(
1743            AdaptationFieldControl::from_flags(false, true),
1744            AdaptationFieldControl::PayloadOnly
1745        );
1746        assert_eq!(
1747            AdaptationFieldControl::from_flags(true, false),
1748            AdaptationFieldControl::AdaptationOnly
1749        );
1750        assert_eq!(
1751            AdaptationFieldControl::from_flags(true, true),
1752            AdaptationFieldControl::AdaptationAndPayload
1753        );
1754        // name()
1755        assert_eq!(AdaptationFieldControl::Reserved.name(), "reserved");
1756        assert_eq!(AdaptationFieldControl::PayloadOnly.name(), "payload_only");
1757        assert_eq!(
1758            AdaptationFieldControl::AdaptationOnly.name(),
1759            "adaptation_only"
1760        );
1761        assert_eq!(
1762            AdaptationFieldControl::AdaptationAndPayload.name(),
1763            "adaptation_and_payload"
1764        );
1765        // Display
1766        assert_eq!(
1767            AdaptationFieldControl::PayloadOnly.to_string(),
1768            "payload_only"
1769        );
1770    }
1771
1772    #[test]
1773    fn ts_header_scrambling_control_accessor() {
1774        let hdr = TsHeader {
1775            tei: false,
1776            pusi: false,
1777            pid: 0x0100,
1778            scrambling: 0b10,
1779            has_adaptation: false,
1780            has_payload: true,
1781            continuity_counter: 0,
1782        };
1783        assert_eq!(hdr.scrambling_control(), ScramblingControl::EvenKey);
1784    }
1785
1786    #[test]
1787    fn ts_header_adaptation_field_control_accessor() {
1788        let hdr_payload_only = TsHeader {
1789            tei: false,
1790            pusi: false,
1791            pid: 0x0100,
1792            scrambling: 0,
1793            has_adaptation: false,
1794            has_payload: true,
1795            continuity_counter: 0,
1796        };
1797        assert_eq!(
1798            hdr_payload_only.adaptation_field_control(),
1799            AdaptationFieldControl::PayloadOnly
1800        );
1801
1802        let hdr_both = TsHeader {
1803            tei: false,
1804            pusi: false,
1805            pid: 0x0100,
1806            scrambling: 0,
1807            has_adaptation: true,
1808            has_payload: true,
1809            continuity_counter: 0,
1810        };
1811        assert_eq!(
1812            hdr_both.adaptation_field_control(),
1813            AdaptationFieldControl::AdaptationAndPayload
1814        );
1815    }
1816
1817    // ── iter_packets / extract_ts_payload helpers ──
1818
1819    #[test]
1820    fn iter_packets_yields_valid_and_skips_bad_sync() {
1821        // Two valid packets back-to-back, then one bad-sync packet.
1822        let pkt1 = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA; 10]);
1823        let pkt2 = make_packet(0x40, 0x64, PAYLOAD_FLAG, &[0xBB; 10]);
1824        let mut bad = [0u8; TS_PACKET_SIZE];
1825        bad[0] = 0x00; // bad sync byte
1826
1827        let mut buf = Vec::new();
1828        buf.extend_from_slice(&pkt1);
1829        buf.extend_from_slice(&pkt2);
1830        buf.extend_from_slice(&bad);
1831
1832        let pkts: Vec<_> = super::iter_packets(&buf).collect();
1833        assert_eq!(pkts.len(), 2, "bad sync packet must be skipped");
1834        assert_eq!(pkts[0].header.pid, 0x0000);
1835        assert_eq!(pkts[1].header.pid, 0x0064);
1836    }
1837
1838    #[test]
1839    fn extract_ts_payload_payload_only() {
1840        let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xABu8; 10]);
1841        let p = super::extract_ts_payload(&pkt).expect("payload present");
1842        assert_eq!(p[0], 0xAB);
1843        assert_eq!(p.len(), TS_PACKET_SIZE - 4);
1844    }
1845
1846    #[test]
1847    fn extract_ts_payload_adaptation_only_returns_none() {
1848        let pkt = make_packet(0x00, 0x00, ADAPTATION_FLAG, &[]);
1849        assert!(super::extract_ts_payload(&pkt).is_none());
1850    }
1851
1852    // ── Pcr write-side ──────────────────────────────────────────────────────
1853
1854    /// `from_27mhz(v).as_27mhz() == v` for representative values
1855    /// (ISO/IEC 13818-1:2007 §2.4.3.5).
1856    #[test]
1857    fn pcr_from_27mhz_round_trips() {
1858        for &ticks in &[0u64, 1, 300, 27_000_000, u64::from(u32::MAX), 8_589_934_591] {
1859            let pcr = Pcr::from_27mhz(ticks);
1860            assert_eq!(pcr.as_27mhz(), ticks, "ticks={ticks}");
1861        }
1862    }
1863
1864    /// `to_field_bytes` → `parse` → same `Pcr` (field-bytes round-trip).
1865    #[test]
1866    fn pcr_to_field_bytes_round_trips_parse() {
1867        let cases = [
1868            Pcr {
1869                base: 0,
1870                extension: 0,
1871            },
1872            Pcr {
1873                base: 10_000,
1874                extension: 0,
1875            },
1876            Pcr {
1877                base: 1,
1878                extension: 100,
1879            },
1880            Pcr {
1881                base: 0x1_FFFF_FFFF,
1882                extension: 0x1FF,
1883            },
1884        ];
1885        for pcr in cases {
1886            let bytes = pcr.to_field_bytes();
1887            // Prefix the 6 bytes with a dummy flags byte so the offset is 1,
1888            // matching the parse() calling convention inside AdaptationField::parse.
1889            let mut af = [0u8; 7];
1890            af[1..7].copy_from_slice(&bytes);
1891            let decoded = Pcr::parse(&af, 1).expect("parse round-trip");
1892            assert_eq!(decoded, pcr, "round-trip failed for {pcr:?}");
1893        }
1894    }
1895
1896    /// Known vector from ts.rs existing test — base=10000, extension=0 produces
1897    /// the 6-byte encoding `[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]`.
1898    #[test]
1899    fn pcr_to_field_bytes_known_vector() {
1900        let pcr = Pcr {
1901            base: 10_000,
1902            extension: 0,
1903        };
1904        let bytes = pcr.to_field_bytes();
1905        assert_eq!(bytes, [0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
1906    }
1907
1908    // ── ScramblingControl to_bits ───────────────────────────────────────────
1909
1910    #[test]
1911    fn scrambling_control_to_bits_inverse_of_from_bits() {
1912        for bits in 0u8..=3 {
1913            let sc = ScramblingControl::from_bits(bits);
1914            assert_eq!(sc.to_bits(), bits, "to_bits() != from_bits() for {bits}");
1915        }
1916    }
1917
1918    // ── AdaptationFieldControl to_bits / to_flags ──────────────────────────
1919
1920    #[test]
1921    fn adaptation_field_control_to_bits_inverse_of_from_flags() {
1922        let cases = [
1923            (false, false, 0b00u8),
1924            (false, true, 0b01),
1925            (true, false, 0b10),
1926            (true, true, 0b11),
1927        ];
1928        for (has_af, has_pl, expected_bits) in cases {
1929            let afc = AdaptationFieldControl::from_flags(has_af, has_pl);
1930            assert_eq!(afc.to_bits(), expected_bits);
1931            assert_eq!(afc.to_flags(), (has_af, has_pl));
1932        }
1933    }
1934
1935    // ── AdaptationField serialize_into ─────────────────────────────────────
1936
1937    /// Build an adaptation field with PCR, serialize → parse → verify equal.
1938    #[test]
1939    fn adaptation_field_serialize_round_trip_with_pcr() {
1940        let original = AdaptationField {
1941            discontinuity_indicator: true,
1942            random_access_indicator: false,
1943            elementary_stream_priority_indicator: false,
1944            pcr: Some(Pcr {
1945                base: 10_000,
1946                extension: 0,
1947            }),
1948            opcr: None,
1949            splice_countdown: None,
1950            transport_private_data: None,
1951            extension: None,
1952            stuffing_len: 0,
1953        };
1954        let len = original.serialized_len();
1955        assert_eq!(len, 7); // 1 flags + 6 PCR
1956        let mut buf = vec![0u8; len];
1957        let written = original.serialize_into(&mut buf).expect("serialize");
1958        assert_eq!(written, len);
1959        let decoded = AdaptationField::parse(&buf).expect("parse round-trip");
1960        assert_eq!(decoded, original);
1961    }
1962
1963    /// Known-bytes test: flags=0x30 (discontinuity + PCR), known PCR vector.
1964    #[test]
1965    fn adaptation_field_serialize_produces_known_bytes() {
1966        // Matches the packet in ts.rs `adaptation_field_flags_and_pcr` test:
1967        // raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG = 0x90
1968        // raw[6..12] = [0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]
1969        let af = AdaptationField {
1970            discontinuity_indicator: true,
1971            random_access_indicator: false,
1972            elementary_stream_priority_indicator: false,
1973            pcr: Some(Pcr {
1974                base: 10_000,
1975                extension: 0,
1976            }),
1977            opcr: None,
1978            splice_countdown: None,
1979            transport_private_data: None,
1980            extension: None,
1981            stuffing_len: 0,
1982        };
1983        let mut buf = [0u8; 7];
1984        af.serialize_into(&mut buf).unwrap();
1985        assert_eq!(buf[0], AF_DISCONTINUITY | AF_PCR_FLAG);
1986        assert_eq!(&buf[1..7], &[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
1987    }
1988
1989    /// Serialize → parse round-trip for AdaptationField with OPCR + splice_countdown.
1990    #[test]
1991    fn adaptation_field_serialize_round_trip_opcr_and_splice() {
1992        let original = AdaptationField {
1993            discontinuity_indicator: false,
1994            random_access_indicator: true,
1995            elementary_stream_priority_indicator: true,
1996            pcr: Some(Pcr {
1997                base: 1,
1998                extension: 100,
1999            }),
2000            opcr: Some(Pcr {
2001                base: 999,
2002                extension: 5,
2003            }),
2004            splice_countdown: Some(-3),
2005            transport_private_data: None,
2006            extension: None,
2007            stuffing_len: 0,
2008        };
2009        let len = original.serialized_len();
2010        assert_eq!(len, 1 + 6 + 6 + 1); // flags + PCR + OPCR + splice
2011        let mut buf = vec![0u8; len];
2012        original.serialize_into(&mut buf).unwrap();
2013        let decoded = AdaptationField::parse(&buf).expect("parse");
2014        assert_eq!(decoded, original);
2015    }
2016
2017    /// Flags-only AdaptationField (no PCR/OPCR/splice).
2018    #[test]
2019    fn adaptation_field_serialize_flags_only() {
2020        let af = AdaptationField {
2021            discontinuity_indicator: false,
2022            random_access_indicator: true,
2023            elementary_stream_priority_indicator: false,
2024            pcr: None,
2025            opcr: None,
2026            splice_countdown: None,
2027            transport_private_data: None,
2028            extension: None,
2029            stuffing_len: 0,
2030        };
2031        assert_eq!(af.serialized_len(), 1);
2032        let mut buf = [0u8; 1];
2033        af.serialize_into(&mut buf).unwrap();
2034        assert_eq!(buf[0], AF_RANDOM_ACCESS);
2035        let decoded = AdaptationField::parse(&buf).unwrap();
2036        assert_eq!(decoded, af);
2037    }
2038
2039    /// OutputBufferTooSmall returned when buffer is too short.
2040    #[test]
2041    fn adaptation_field_serialize_rejects_small_buffer() {
2042        let af = AdaptationField {
2043            discontinuity_indicator: false,
2044            random_access_indicator: false,
2045            elementary_stream_priority_indicator: false,
2046            pcr: Some(Pcr {
2047                base: 0,
2048                extension: 0,
2049            }),
2050            opcr: None,
2051            splice_countdown: None,
2052            transport_private_data: None,
2053            extension: None,
2054            stuffing_len: 0,
2055        };
2056        let mut buf = [0u8; 3]; // need 7
2057        assert!(matches!(
2058            af.serialize_into(&mut buf),
2059            Err(Error::OutputBufferTooSmall { .. })
2060        ));
2061    }
2062
2063    // ── AdaptationFieldExtension (§2.4.3.5) ────────────────────────────────
2064
2065    /// Round-trip LTW field.
2066    #[test]
2067    fn adaptation_field_extension_ltw_round_trip() {
2068        let ext = AdaptationFieldExtension {
2069            ltw: Some(Ltw {
2070                ltw_valid_flag: true,
2071                ltw_offset: 0x1234,
2072            }),
2073            piecewise_rate: None,
2074            seamless_splice: None,
2075        };
2076        let mut buf = vec![0u8; ext.serialized_len()];
2077        ext.serialize_into(&mut buf).unwrap();
2078        // Parse via AdaptationField (extension is the last field)
2079        // Build a full AdaptationField with only the extension set.
2080        let af = AdaptationField {
2081            discontinuity_indicator: false,
2082            random_access_indicator: false,
2083            elementary_stream_priority_indicator: false,
2084            pcr: None,
2085            opcr: None,
2086            splice_countdown: None,
2087            transport_private_data: None,
2088            extension: Some(ext),
2089            stuffing_len: 0,
2090        };
2091        let len = af.serialized_len();
2092        let mut abuf = vec![0u8; len];
2093        af.serialize_into(&mut abuf).unwrap();
2094        let decoded = AdaptationField::parse(&abuf).unwrap();
2095        assert_eq!(decoded.extension, Some(ext));
2096    }
2097
2098    /// Round-trip piecewise_rate field.
2099    #[test]
2100    fn adaptation_field_extension_piecewise_rate_round_trip() {
2101        let af = AdaptationField {
2102            discontinuity_indicator: false,
2103            random_access_indicator: false,
2104            elementary_stream_priority_indicator: false,
2105            pcr: None,
2106            opcr: None,
2107            splice_countdown: None,
2108            transport_private_data: None,
2109            extension: Some(AdaptationFieldExtension {
2110                ltw: None,
2111                piecewise_rate: Some(0x3FFFFF), // max 22-bit
2112                seamless_splice: None,
2113            }),
2114            stuffing_len: 0,
2115        };
2116        let mut buf = vec![0u8; af.serialized_len()];
2117        af.serialize_into(&mut buf).unwrap();
2118        let decoded = AdaptationField::parse(&buf).unwrap();
2119        assert_eq!(decoded.extension.unwrap().piecewise_rate, Some(0x3FFFFF));
2120    }
2121
2122    /// Round-trip seamless_splice field.
2123    #[test]
2124    fn adaptation_field_extension_seamless_splice_round_trip() {
2125        let af = AdaptationField {
2126            discontinuity_indicator: false,
2127            random_access_indicator: false,
2128            elementary_stream_priority_indicator: false,
2129            pcr: None,
2130            opcr: None,
2131            splice_countdown: None,
2132            transport_private_data: None,
2133            extension: Some(AdaptationFieldExtension {
2134                ltw: None,
2135                piecewise_rate: None,
2136                seamless_splice: Some(SeamlessSplice {
2137                    splice_type: 0xA,
2138                    dts_next_au: 0x1_2345_6789,
2139                }),
2140            }),
2141            stuffing_len: 0,
2142        };
2143        let mut buf = vec![0u8; af.serialized_len()];
2144        af.serialize_into(&mut buf).unwrap();
2145        let decoded = AdaptationField::parse(&buf).unwrap();
2146        let ss = decoded.extension.unwrap().seamless_splice.unwrap();
2147        assert_eq!(ss.splice_type, 0xA);
2148        assert_eq!(ss.dts_next_au, 0x1_2345_6789);
2149    }
2150
2151    /// Round-trip with transport_private_data.
2152    #[test]
2153    fn adaptation_field_transport_private_data_round_trip() {
2154        let tpd = [0xDE, 0xAD, 0xBE, 0xEF];
2155        let af = AdaptationField {
2156            discontinuity_indicator: false,
2157            random_access_indicator: false,
2158            elementary_stream_priority_indicator: false,
2159            pcr: None,
2160            opcr: None,
2161            splice_countdown: None,
2162            transport_private_data: Some(&tpd),
2163            extension: None,
2164            stuffing_len: 0,
2165        };
2166        let mut buf = vec![0u8; af.serialized_len()];
2167        af.serialize_into(&mut buf).unwrap();
2168        let decoded = AdaptationField::parse(&buf).unwrap();
2169        assert_eq!(decoded.transport_private_data, Some(tpd.as_slice()));
2170    }
2171
2172    /// PCR + `0xFF` stuffing round-trips byte-identical, and the stuffing is
2173    /// re-emitted as `0xFF` (ISO/IEC 13818-1:2007 §2.4.3.4).
2174    #[test]
2175    fn adaptation_field_stuffing_round_trip() {
2176        let af = AdaptationField {
2177            discontinuity_indicator: false,
2178            random_access_indicator: false,
2179            elementary_stream_priority_indicator: false,
2180            pcr: Some(Pcr {
2181                base: 12_345,
2182                extension: 7,
2183            }),
2184            opcr: None,
2185            splice_countdown: None,
2186            transport_private_data: None,
2187            extension: None,
2188            stuffing_len: 20,
2189        };
2190        // 1 flags + 6 PCR + 20 stuffing.
2191        assert_eq!(af.serialized_len(), 27);
2192        let mut buf = vec![0u8; af.serialized_len()];
2193        af.serialize_into(&mut buf).unwrap();
2194        // Trailing bytes are 0xFF stuffing.
2195        assert!(buf[7..27].iter().all(|&b| b == AF_STUFFING_BYTE));
2196        let decoded = AdaptationField::parse(&buf).unwrap();
2197        assert_eq!(decoded.stuffing_len, 20);
2198        assert_eq!(decoded, af);
2199        // Pure stuffing (flags-only body padded out) also round-trips.
2200        let pure = AdaptationField {
2201            discontinuity_indicator: false,
2202            random_access_indicator: false,
2203            elementary_stream_priority_indicator: false,
2204            pcr: None,
2205            opcr: None,
2206            splice_countdown: None,
2207            transport_private_data: None,
2208            extension: None,
2209            stuffing_len: 5,
2210        };
2211        let mut pbuf = vec![0u8; pure.serialized_len()];
2212        pure.serialize_into(&mut pbuf).unwrap();
2213        assert_eq!(pbuf, vec![0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
2214        assert_eq!(AdaptationField::parse(&pbuf).unwrap(), pure);
2215    }
2216
2217    /// All optional fields together: PCR + splice + TPD + extension.
2218    #[test]
2219    fn adaptation_field_all_fields_round_trip() {
2220        let tpd = [0x01u8, 0x02, 0x03];
2221        let af = AdaptationField {
2222            discontinuity_indicator: true,
2223            random_access_indicator: false,
2224            elementary_stream_priority_indicator: false,
2225            pcr: Some(Pcr {
2226                base: 90_000,
2227                extension: 50,
2228            }),
2229            opcr: None,
2230            splice_countdown: Some(10),
2231            transport_private_data: Some(&tpd),
2232            extension: Some(AdaptationFieldExtension {
2233                ltw: Some(Ltw {
2234                    ltw_valid_flag: false,
2235                    ltw_offset: 500,
2236                }),
2237                piecewise_rate: Some(12345),
2238                seamless_splice: None,
2239            }),
2240            stuffing_len: 0,
2241        };
2242        let len = af.serialized_len();
2243        let mut buf = vec![0u8; len];
2244        af.serialize_into(&mut buf).unwrap();
2245        let decoded = AdaptationField::parse(&buf).unwrap();
2246        assert_eq!(decoded.pcr, af.pcr);
2247        assert_eq!(decoded.splice_countdown, af.splice_countdown);
2248        assert_eq!(decoded.transport_private_data, af.transport_private_data);
2249        assert_eq!(decoded.extension, af.extension);
2250        assert!(decoded.discontinuity_indicator);
2251    }
2252
2253    /// Full round-trip: build a packet with PCR in adaptation field, parse
2254    /// it, re-serialize the adaptation field, re-parse, assert PCR matches.
2255    #[test]
2256    fn adaptation_field_serialize_from_real_packet_bytes() {
2257        // Replicate the raw packet from `adaptation_field_flags_and_pcr`.
2258        let mut raw = [0xAAu8; TS_PACKET_SIZE];
2259        raw[0] = TS_SYNC_BYTE;
2260        raw[1] = 0x01;
2261        raw[2] = 0x00;
2262        raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
2263        raw[4] = 7;
2264        raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG;
2265        raw[6..12].copy_from_slice(&[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
2266
2267        let pkt = TsPacket::parse(&raw).unwrap();
2268        let af = pkt.adaptation_field().unwrap().unwrap();
2269
2270        // Re-serialize.
2271        let mut ser = vec![0u8; af.serialized_len()];
2272        af.serialize_into(&mut ser).unwrap();
2273        let decoded = AdaptationField::parse(&ser).unwrap();
2274        assert_eq!(
2275            decoded.pcr,
2276            Some(Pcr {
2277                base: 10_000,
2278                extension: 0
2279            })
2280        );
2281        assert!(decoded.discontinuity_indicator);
2282    }
2283}