Skip to main content

mpeg_ps/
pack_header.rs

1//! Pack Header — ISO/IEC 13818-1 §2.5.3.3, Table 2-39.
2//!
3//! The pack header opens every pack in an MPEG-1/2 Program Stream.
4//! It carries the 42-bit System Clock Reference (SCR), the
5//! `program_mux_rate`, and optional stuffing bytes.
6
7use crate::error::{Error, Result};
8use crate::scr::{self, Scr};
9use broadcast_common::len::{need_in, need_out};
10use broadcast_common::{Parse, Serialize};
11
12/// `pack_start_code` — `0x000001BA`.
13pub const PACK_START_CODE: u32 = 0x0000_01BA;
14
15/// Fixed overhead of the pack header: start_code(4) + SCR field(6) +
16/// mux_rate+reserved+stuffing(4) = 14 bytes.
17const FIXED_LEN: usize = 14;
18
19/// `program_mux_rate` width in bits (Table 2-39, ISO/IEC 13818-1 §2.5.3.3).
20const PROGRAM_MUX_RATE_BITS: u32 = 22;
21/// `reserved` field width in bits (Table 2-39, byte 13 bits[7:3]).
22const RESERVED_BITS: u32 = 5;
23/// Largest `pack_stuffing_length` (3-bit field, Table 2-39).
24const STUFFING_LENGTH_MAX: u8 = 7;
25
26/// A parsed pack header.
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize))]
29pub struct PackHeader<'a> {
30    /// System Clock Reference (42-bit; 33-bit base + 9-bit extension, 27 MHz).
31    pub scr: Scr,
32    /// Rate at which the P-STD receives the stream during this pack,
33    /// in units of 50 bytes/s. Must be non-zero.
34    pub program_mux_rate: u32,
35    /// The `pack_stuffing_length` field value (≤ 7).
36    pub stuffing_length: u8,
37    /// Stuffing bytes (`0xFF`), zero to seven bytes.
38    #[cfg_attr(feature = "serde", serde(skip))]
39    pub stuffing: &'a [u8],
40    /// Reserved bits (5 bits, preserved for round-trip).
41    pub reserved: u8,
42}
43
44impl PackHeader<'_> {
45    /// Offset to the byte immediately after this pack header (i.e. where PES data starts).
46    #[must_use]
47    pub fn header_len(&self) -> usize {
48        self.serialized_len()
49    }
50}
51
52impl<'a> Parse<'a> for PackHeader<'a> {
53    type Error = Error;
54
55    fn parse(b: &'a [u8]) -> Result<Self> {
56        need_in(b, FIXED_LEN, "pack_header")?;
57
58        if u32::from_be_bytes([b[0], b[1], b[2], b[3]]) != PACK_START_CODE {
59            return Err(Error::BadPackStartCode(u32::from_be_bytes([
60                b[0], b[1], b[2], b[3],
61            ])));
62        }
63
64        let scr = scr::read_scr_field(&b[4..10], "SCR")?;
65
66        // Bytes 10..13 (Table 2-39, ISO/IEC 13818-1 §2.5.3.3): unlike the SCR
67        // field, there is no `'01'` prefix before `program_mux_rate` — the
68        // field is the TOP 22 bits of bytes 10..12, followed by the 2 marker
69        // bits in byte 12 bits[1:0]:
70        // byte 10: mux[21:14](8)
71        // byte 11: mux[13:6](8)
72        // byte 12: mux[5:0](6, bits[7:2]) | marker_bit(2, bits[1:0])
73        // byte 13: reserved(5, bits[7:3]) | stuffing(3, bits[2:0])
74
75        // Validate marker bits at byte12[1:0]
76        if b[12] & 0x03 != 0x03 {
77            return Err(Error::BadMarker("program_mux_rate markers"));
78        }
79
80        let program_mux_rate =
81            (u32::from(b[10]) << 14) | (u32::from(b[11]) << 6) | (u32::from(b[12]) >> 2);
82
83        if program_mux_rate == 0 {
84            return Err(Error::ZeroMuxRate);
85        }
86
87        let reserved = (b[13] >> 3) & 0x1F;
88        let stuffing_length = b[13] & 0x07;
89
90        let total = FIXED_LEN + stuffing_length as usize;
91        need_in(b, total, "pack_header stuffing bytes")?;
92
93        Ok(PackHeader {
94            scr,
95            program_mux_rate,
96            stuffing_length,
97            stuffing: &b[FIXED_LEN..total],
98            reserved,
99        })
100    }
101}
102
103impl Serialize for PackHeader<'_> {
104    type Error = Error;
105
106    fn serialized_len(&self) -> usize {
107        FIXED_LEN + self.stuffing_length as usize
108    }
109
110    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
111        // Range-check every narrowed field BEFORE sizing/writing: a silent
112        // mask would make `serialized_len()` (unmasked stuffing_length)
113        // disagree with the bytes actually framed (#1129).
114        if self.stuffing_length > STUFFING_LENGTH_MAX {
115            return Err(Error::StuffingLengthTooLarge(self.stuffing_length));
116        }
117        let mux = broadcast_common::len::fit_bits(
118            u64::from(self.program_mux_rate),
119            PROGRAM_MUX_RATE_BITS,
120            "pack_header.program_mux_rate",
121        )? as u32;
122        let reserved = broadcast_common::len::fit_bits(
123            u64::from(self.reserved),
124            RESERVED_BITS,
125            "pack_header.reserved",
126        )? as u8;
127        let len = self.serialized_len();
128        need_out(buf, len)?;
129
130        // pack_start_code
131        buf[0..4].copy_from_slice(&PACK_START_CODE.to_be_bytes());
132
133        // SCR field (6 bytes)
134        buf[4..10].copy_from_slice(&scr::encode_scr_field(self.scr)?);
135
136        // program_mux_rate: 22 bits across bytes 10..12, no '01' prefix
137        // (Table 2-39; see the parse-side comment above).
138        // byte 10: mux[21:14]
139        buf[10] = (mux >> 14) as u8;
140        // byte 11: mux[13:6]
141        buf[11] = (mux >> 6) as u8;
142        // byte 12: mux[5:0] + marker bits (always '11') at bits[1:0]
143        buf[12] = (((mux & 0x3F) as u8) << 2) | 0x03;
144        // byte 13: reserved(5) + stuffing(3)
145        buf[13] = reserved << 3 | self.stuffing_length;
146
147        // stuffing bytes: `stuffing_byte` is spec-fixed `0xFF` (Table 2-39),
148        // but parse does not reject a non-conformant value (it stores
149        // whatever bytes are present in `self.stuffing`) — write them back
150        // verbatim rather than always `0xFF`, so a hand-built or
151        // non-strictly-conformant-but-parseable `PackHeader` still
152        // round-trips byte-identically (#1119 W8). `stuffing_length` and
153        // `stuffing` are independently-settable public fields; validate they
154        // agree before indexing by one and copying a slice sized by the
155        // other.
156        if self.stuffing.len() != self.stuffing_length as usize {
157            return Err(Error::StuffingLengthMismatch {
158                declared: self.stuffing_length,
159                actual: self.stuffing.len(),
160            });
161        }
162        buf[FIXED_LEN..len].copy_from_slice(self.stuffing);
163
164        Ok(len)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use alloc::vec;
172
173    #[test]
174    fn pack_header_round_trip_fixture_pattern() {
175        // Byte-for-byte the real fixture's first pack header (fixtures/ps/
176        // ffmpeg-mpeg2-ps.mpg, bytes 0..14): SCR=0, reserved=0x1F, stuffing=0.
177        // mux = (0x43<<14)|(0x36<<6)|(0x3B>>2) = 0x10CD8E (Table 2-39, no '01'
178        // prefix before program_mux_rate — see the parse-side comment), which
179        // matches this same fixture's system_header.rate_bound (0x10CD8E,
180        // cross-checked in `real_fixture_walk` / see mpeg-ps/tests/it/fixture_ps.rs).
181        let bytes = vec![
182            0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, // SCR=0
183            0x43, 0x36, 0x3B, 0xF8, // mux + reserved + stuffing
184        ];
185        let h = PackHeader::parse(&bytes).unwrap();
186        assert_eq!(
187            h.scr,
188            Scr {
189                base: 0,
190                extension: 0,
191            }
192        );
193        assert_eq!(h.program_mux_rate, 0x10CD8E);
194        assert_eq!(h.stuffing_length, 0);
195        assert_eq!(h.reserved, 0x1F);
196        assert!(h.stuffing.is_empty());
197
198        let mut out = vec![0u8; h.serialized_len()];
199        h.serialize_into(&mut out).unwrap();
200        assert_eq!(&out[..], &bytes[..], "round-trip mismatch");
201
202        // Biting mutation: change mux_rate → output differs
203        let h_mut = PackHeader {
204            program_mux_rate: 0x12345,
205            ..h.clone()
206        };
207        let mut out2 = vec![0u8; h_mut.serialized_len()];
208        h_mut.serialize_into(&mut out2).unwrap();
209        assert_ne!(&out[..], &out2[..]);
210    }
211
212    #[test]
213    fn serialize_rejects_out_of_range_fields() {
214        let ok = PackHeader {
215            scr: Scr {
216                base: 0,
217                extension: 0,
218            },
219            program_mux_rate: 1,
220            stuffing_length: 0,
221            stuffing: &[],
222            reserved: 0x1F,
223        };
224        let mut out = vec![0u8; 64];
225        assert!(ok.serialize_into(&mut out).is_ok());
226
227        let long = [0xFFu8; 9];
228        let bad = PackHeader {
229            stuffing_length: 9,
230            stuffing: &long,
231            ..ok.clone()
232        };
233        assert_eq!(
234            bad.serialize_into(&mut out),
235            Err(Error::StuffingLengthTooLarge(9))
236        );
237        let bad = PackHeader {
238            program_mux_rate: 0x40_0000,
239            ..ok.clone()
240        };
241        assert!(matches!(
242            bad.serialize_into(&mut out),
243            Err(Error::FieldOverflow(_))
244        ));
245        let bad = PackHeader {
246            reserved: 0x20,
247            ..ok.clone()
248        };
249        assert!(matches!(
250            bad.serialize_into(&mut out),
251            Err(Error::FieldOverflow(_))
252        ));
253    }
254
255    #[test]
256    fn pack_header_round_trip_with_stuffing() {
257        let bytes = vec![
258            0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x01,
259            0x0F, // mux=0x43: byte10=mux[21:14], byte11=mux[13:6], byte12=mux[5:0]<<2|markers
260            0x03, // reserved=0, stuffing=3
261            0xFF, 0xFF, 0xFF,
262        ];
263        let h = PackHeader::parse(&bytes).unwrap();
264        assert_eq!(h.program_mux_rate, 0x43);
265        assert_eq!(h.stuffing_length, 3);
266        assert_eq!(h.stuffing, &[0xFF, 0xFF, 0xFF]);
267        assert_eq!(h.reserved, 0);
268
269        let mut out = vec![0u8; h.serialized_len()];
270        h.serialize_into(&mut out).unwrap();
271        assert_eq!(&out[..], &bytes[..]);
272
273        // Re-parse equals original
274        let h2 = PackHeader::parse(&out).unwrap();
275        assert_eq!(h, h2);
276
277        // Biting mutation: change reserved → output differs
278        let h_mut = PackHeader {
279            reserved: 0x0A,
280            ..h.clone()
281        };
282        let mut out2 = vec![0u8; h_mut.serialized_len()];
283        h_mut.serialize_into(&mut out2).unwrap();
284        assert_ne!(&out[..], &out2[..]);
285    }
286
287    #[test]
288    fn pack_header_nonzero_scr() {
289        let scr = Scr {
290            base: 0x12345678,
291            extension: 0x0AA,
292        };
293        let scr_enc = scr::write_scr_field(scr);
294        let mut b = vec![0u8; 14];
295        b[0..4].copy_from_slice(&PACK_START_CODE.to_be_bytes());
296        b[4..10].copy_from_slice(&scr_enc);
297        b[10..14].copy_from_slice(&[0x00, 0x01, 0x0F, 0x00]); // mux=0x43
298
299        let h = PackHeader::parse(&b).unwrap();
300        assert_eq!(h.scr, scr);
301        assert_eq!(h.program_mux_rate, 0x43);
302
303        let mut out = vec![0u8; h.serialized_len()];
304        h.serialize_into(&mut out).unwrap();
305        assert_eq!(&out[..], &b[..]);
306    }
307
308    /// W8 (#1119): serialize used to always write stuffing bytes as `0xFF`
309    /// regardless of `self.stuffing`, even though `parse` already captured
310    /// the actual bytes present. Non-`0xFF` stuffing (non-conformant to the
311    /// spec's fixed `stuffing_byte`, but not rejected by `parse` either)
312    /// must still round-trip byte-identically.
313    #[test]
314    fn non_0xff_stuffing_round_trips_byte_identically() {
315        let bytes = vec![
316            0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x01, 0x0F,
317            0x03, // reserved=0, stuffing_length=3
318            0xAB, 0xCD, 0xEF, // non-0xFF stuffing
319        ];
320        let h = PackHeader::parse(&bytes).unwrap();
321        assert_eq!(h.stuffing, &[0xAB, 0xCD, 0xEF]);
322
323        let mut out = vec![0u8; h.serialized_len()];
324        h.serialize_into(&mut out).unwrap();
325        assert_eq!(&out[..], &bytes[..]);
326    }
327
328    /// W8 (#1119): `stuffing_length` and `stuffing` are independently
329    /// settable public fields; serialize must reject a mismatch rather than
330    /// panic on the length-mismatched `copy_from_slice`.
331    #[test]
332    fn serialize_rejects_stuffing_length_mismatch() {
333        let bytes = vec![
334            0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x01, 0x0F, 0x03,
335            0xFF, 0xFF, 0xFF,
336        ];
337        let mut h = PackHeader::parse(&bytes).unwrap();
338        h.stuffing = &[0xFF, 0xFF]; // 2 bytes, but stuffing_length still says 3
339
340        let mut out = vec![0u8; h.serialized_len()];
341        let err = h.serialize_into(&mut out).unwrap_err();
342        assert!(matches!(
343            err,
344            Error::StuffingLengthMismatch {
345                declared: 3,
346                actual: 2
347            }
348        ));
349    }
350}