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