Skip to main content

mpeg_ps/
system_header.rs

1//! System Header — ISO/IEC 13818-1 §2.5.3.5, Table 2-40.
2//!
3//! The (optional) system header follows immediately after the pack header
4//! stuffing bytes, in the first pack of the stream. It constrains the P-STD
5//! model: `rate_bound`, `audio_bound`/`video_bound`, and per-stream P-STD
6//! buffer-size bounds.
7
8use alloc::vec::Vec;
9
10use crate::error::{Error, Result};
11use broadcast_common::{Parse, Serialize};
12
13/// `system_header_start_code` — `0x000001BB`.
14pub const SYSTEM_HEADER_START_CODE: u32 = 0x0000_01BB;
15
16/// `stream_id` value that triggers the extension (extended_stream_id) form.
17const EXT_STREAM_ID: u8 = 0xB7;
18
19/// Fixed bytes before the stream loop: start_code(4) + header_length(2).
20pub(crate) const PREFIX_LEN: usize = 6;
21/// Mask of the 7 `reserved_bits` below `packet_rate_restriction_flag` (Table 2-40).
22const RESERVED_BITS_MASK: u8 = 0x7F;
23
24/// A per-stream P-STD buffer bound entry.
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub struct StdBufferBound {
28    /// The stream's `stream_id` (ISO/IEC 13818-1 Table 2-22).
29    pub stream_id: u8,
30    /// If `stream_id == 0xB7`, this is the extended `stream_id_extension` field.
31    /// Otherwise `None`.
32    pub stream_id_extension: Option<u8>,
33    /// `P-STD_buffer_bound_scale`: `false` = 128 bytes, `true` = 1024 bytes.
34    pub buffer_bound_scale: bool,
35    /// `P-STD_buffer_size_bound` in units of `buffer_bound_scale`.
36    pub buffer_size_bound: u16,
37}
38
39/// A parsed system header.
40#[derive(Debug, Clone, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize))]
42pub struct SystemHeader {
43    /// Upper bound on `program_mux_rate` across all packs (22-bit, 50 B/s units).
44    pub rate_bound: u32,
45    /// Upper bound on simultaneously-active audio streams (6-bit).
46    pub audio_bound: u8,
47    /// Fixed / variable bitrate indicator.
48    pub fixed_flag: bool,
49    /// Constrained system parameters flag.
50    pub csps_flag: bool,
51    /// System audio lock flag.
52    pub system_audio_lock_flag: bool,
53    /// System video lock flag.
54    pub system_video_lock_flag: bool,
55    /// Upper bound on simultaneously-active video streams (5-bit).
56    pub video_bound: u8,
57    /// Packet rate restriction flag.
58    pub packet_rate_restriction_flag: bool,
59    /// The 7 `reserved_bits` after `packet_rate_restriction_flag`
60    /// (Table 2-40), preserved so a header whose encoder wrote something
61    /// other than the conventional all-ones (`0x7F`) re-serializes
62    /// byte-identically (audit r14-MPS-W8).
63    pub reserved_bits: u8,
64    /// Per-stream P-STD buffer bounds (the `while (nextbits() == '1')` loop).
65    pub std_buffer_bounds: Vec<StdBufferBound>,
66    /// Bytes of `header_length` that follow the stream-bound loop. Table
67    /// 2-40 defines none, but a real encoder can pad the header; they are
68    /// preserved verbatim so the header re-serializes byte-identically
69    /// (audit r14-MPS-W8, #1119). Empty for a conformant header.
70    pub trailing_reserved: Vec<u8>,
71}
72
73/// Bytes the stream loop occupies. Accumulated in `usize` (#1129): the
74/// previous `u16` accumulator overflowed for more than 10 921
75/// caller-constructed bounds, wrapping `header_length` and `serialized_len()`
76/// together so they stayed mutually consistent but both silently wrong,
77/// which then indexed past the (too-small) allocated buffer in
78/// `serialize_into`. `serialize_into` separately validates that this fits
79/// the 16-bit `header_length` wire field before writing it.
80fn stream_loop_len(system_header: &SystemHeader) -> usize {
81    let mut len: usize = 0;
82    for b in &system_header.std_buffer_bounds {
83        if b.stream_id_extension.is_some() {
84            len += 6;
85        } else {
86            len += 3;
87        }
88    }
89    len + system_header.trailing_reserved.len()
90}
91
92impl<'a> Parse<'a> for SystemHeader {
93    type Error = Error;
94
95    fn parse(b: &'a [u8]) -> Result<Self> {
96        if b.len() < PREFIX_LEN {
97            return Err(Error::BufferTooShort {
98                need: PREFIX_LEN,
99                have: b.len(),
100                what: "system_header prefix",
101            });
102        }
103
104        if u32::from_be_bytes([b[0], b[1], b[2], b[3]]) != SYSTEM_HEADER_START_CODE {
105            return Err(Error::BadSystemHeaderStartCode(u32::from_be_bytes([
106                b[0], b[1], b[2], b[3],
107            ])));
108        }
109
110        let header_length = u16::from_be_bytes([b[4], b[5]]) as usize;
111        let body_end = PREFIX_LEN + header_length;
112        if b.len() < body_end {
113            return Err(Error::HeaderLengthOverflow {
114                header_length,
115                available: b.len().saturating_sub(PREFIX_LEN),
116            });
117        }
118        let body = &b[PREFIX_LEN..body_end];
119
120        // Wire layout (Table 2-40, after header_length):
121        // byte 0: marker(1)  | rate_bound[21:15](7)
122        // byte 1: rate_bound[14:7](8)
123        // byte 2: rate_bound[6:0](7) | marker(1)
124        // byte 3: audio_bound[5:0](6) | fixed_flag(1) | CSPS_flag(1)
125        // byte 4: system_audio_lock_flag(1) | system_video_lock_flag(1) | marker(1) | video_bound[4:0](5)
126        // byte 5: packet_rate_restriction_flag(1) | reserved(7)
127
128        if body.len() < 6 {
129            return Err(Error::BufferTooShort {
130                need: 6 + PREFIX_LEN,
131                have: b.len(),
132                what: "system_header fixed body",
133            });
134        }
135
136        // Marker bit checks
137        if body[0] & 0x80 == 0 {
138            return Err(Error::BadMarker("system_header rate_bound marker 1"));
139        }
140        if body[2] & 0x01 == 0 {
141            return Err(Error::BadMarker("system_header rate_bound marker 2"));
142        }
143        if body[4] & 0x20 == 0 {
144            return Err(Error::BadMarker("system_header video_bound marker"));
145        }
146
147        // rate_bound: 22 bits
148        let rate_bound = ((u32::from(body[0] & 0x7F) << 15)
149            | (u32::from(body[1]) << 7)
150            | u32::from(body[2] >> 1))
151            & 0x3F_FFFF;
152
153        // byte 3: audio_bound(6) | fixed_flag(1) | CSPS_flag(1)
154        let audio_bound = (body[3] >> 2) & 0x3F;
155        let fixed_flag = body[3] & 0x02 != 0;
156        let csps_flag = body[3] & 0x01 != 0;
157
158        // byte 4: system_audio_lock_flag(1) | system_video_lock_flag(1) | marker(1) | video_bound[4:0](5)
159        let system_audio_lock_flag = body[4] & 0x80 != 0;
160        let system_video_lock_flag = body[4] & 0x40 != 0;
161        // W1 (#1119): `video_bound` is the low 5 bits of byte 4, not 4 —
162        // `& 0x0F` dropped bit 4, so `video_bound` in 16..=31 round-tripped
163        // to `video_bound - 16` (serialize already used the correct `0x1F`,
164        // Table 2-40).
165        let video_bound = body[4] & 0x1F;
166
167        // byte 5: packet_rate_restriction_flag(1) | reserved(7)
168        let packet_rate_restriction_flag = body[5] & 0x80 != 0;
169        let reserved_bits = body[5] & RESERVED_BITS_MASK;
170
171        // Stream loop — each entry starts with MSB=1 (nextbits()=='1')
172        let mut pos = 6;
173        let mut std_buffer_bounds = Vec::new();
174        while pos < body.len() && body[pos] & 0x80 != 0 {
175            let stream_id = body[pos];
176            if stream_id == EXT_STREAM_ID {
177                // Extension form (6 bytes)
178                if pos + 6 > body.len() {
179                    return Err(Error::BufferTooShort {
180                        need: pos + 6,
181                        have: body.len(),
182                        what: "system_header extended stream entry",
183                    });
184                }
185                // byte pos+1: '11' + '000000'(6 bits)
186                if body[pos + 1] & 0xC0 != 0xC0 {
187                    return Err(Error::BadStreamIdExtensionPrefix(body[pos + 1]));
188                }
189                // byte pos+2: '0'(1) + stream_id_extension(7)
190                let stream_id_extension = body[pos + 2] & 0x7F;
191                // byte pos+3: '1011 0110'
192                if body[pos + 3] != 0xB6 {
193                    return Err(Error::BadStreamIdExtensionPrefix(body[pos + 3]));
194                }
195                // byte pos+4: '11' + scale(1) + size[12:7](5)
196                if body[pos + 4] & 0xC0 != 0xC0 {
197                    return Err(Error::BadMarker("P-STD_buffer_bound_scale prefix (ext)"));
198                }
199                let buffer_bound_scale = body[pos + 4] & 0x20 != 0;
200                // bytes pos+4/pos+5: '11'(2) | scale(1) | size[12:8](5), then size[7:0](8):
201                // the 13-bit size has no trailing marker bit (Table 2-40).
202                let buffer_size_bound =
203                    (u16::from(body[pos + 4] & 0x1F) << 8) | u16::from(body[pos + 5]);
204                std_buffer_bounds.push(StdBufferBound {
205                    stream_id,
206                    stream_id_extension: Some(stream_id_extension),
207                    buffer_bound_scale,
208                    buffer_size_bound,
209                });
210                pos += 6;
211            } else {
212                // Normal form (3 bytes)
213                if pos + 3 > body.len() {
214                    return Err(Error::BufferTooShort {
215                        need: pos + 3,
216                        have: body.len(),
217                        what: "system_header stream entry",
218                    });
219                }
220                // byte pos+1: '11' + scale(1) + size[12:7](5)
221                if body[pos + 1] & 0xC0 != 0xC0 {
222                    return Err(Error::BadMarker("P-STD_buffer_bound_scale prefix"));
223                }
224                let buffer_bound_scale = body[pos + 1] & 0x20 != 0;
225                // bytes pos+1/pos+2: '11'(2) | scale(1) | size[12:8](5), then size[7:0](8).
226                let buffer_size_bound =
227                    (u16::from(body[pos + 1] & 0x1F) << 8) | u16::from(body[pos + 2]);
228                std_buffer_bounds.push(StdBufferBound {
229                    stream_id,
230                    stream_id_extension: None,
231                    buffer_bound_scale,
232                    buffer_size_bound,
233                });
234                pos += 3;
235            }
236        }
237
238        Ok(SystemHeader {
239            rate_bound,
240            audio_bound,
241            fixed_flag,
242            csps_flag,
243            system_audio_lock_flag,
244            system_video_lock_flag,
245            video_bound,
246            packet_rate_restriction_flag,
247            reserved_bits,
248            std_buffer_bounds,
249            trailing_reserved: body[pos..].to_vec(),
250        })
251    }
252}
253
254impl SystemHeader {
255    /// Checks that the stream loop and `trailing_reserved` would read back
256    /// unchanged: the parser's loop runs while `nextbits() == '1'` (Table
257    /// 2-40), and `stream_id == 0xB7` selects the 6-byte extension form.
258    fn validate_stream_loop(&self) -> Result<()> {
259        for b in &self.std_buffer_bounds {
260            if b.stream_id & 0x80 == 0 {
261                return Err(Error::InvalidField {
262                    field: "system_header.stream_id",
263                    reason: "stream_id MSB is 0, so the entry would end the stream loop on parse",
264                });
265            }
266            match b.stream_id_extension {
267                Some(ext) => {
268                    if b.stream_id != EXT_STREAM_ID {
269                        return Err(Error::InvalidField {
270                            field: "system_header.stream_id_extension",
271                            reason: "stream_id_extension requires stream_id 0xB7",
272                        });
273                    }
274                    broadcast_common::len::fit_bits(
275                        u64::from(ext),
276                        7,
277                        "system_header.stream_id_extension",
278                    )?;
279                }
280                None => {
281                    if b.stream_id == EXT_STREAM_ID {
282                        return Err(Error::InvalidField {
283                            field: "system_header.stream_id",
284                            reason: "stream_id 0xB7 requires stream_id_extension",
285                        });
286                    }
287                }
288            }
289            broadcast_common::len::fit_bits(
290                u64::from(b.buffer_size_bound),
291                13,
292                "system_header.P-STD_buffer_size_bound",
293            )?;
294        }
295        if self
296            .trailing_reserved
297            .first()
298            .is_some_and(|f| f & 0x80 != 0)
299        {
300            return Err(Error::InvalidField {
301                field: "system_header.trailing_reserved",
302                reason: "first byte has its MSB set, so it would be re-read as a stream-bound entry",
303            });
304        }
305        Ok(())
306    }
307}
308
309impl Serialize for SystemHeader {
310    type Error = Error;
311
312    fn serialized_len(&self) -> usize {
313        PREFIX_LEN + 6 + stream_loop_len(self)
314    }
315
316    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
317        let total = self.serialized_len();
318        if buf.len() < total {
319            return Err(Error::BufferTooShort {
320                need: total,
321                have: buf.len(),
322                what: "system_header serialize output",
323            });
324        }
325
326        // system_header_start_code
327        buf[0..4].copy_from_slice(&SYSTEM_HEADER_START_CODE.to_be_bytes());
328
329        // header_length (bytes after this field)
330        let header_length = broadcast_common::len::fit_u16(
331            6 + stream_loop_len(self),
332            "system_header.header_length",
333        )?;
334        buf[4..6].copy_from_slice(&header_length.to_be_bytes());
335
336        let rate_bound = broadcast_common::len::fit_bits(
337            u64::from(self.rate_bound),
338            22,
339            "system_header.rate_bound",
340        )? as u32;
341        let audio_bound = broadcast_common::len::fit_bits(
342            u64::from(self.audio_bound),
343            6,
344            "system_header.audio_bound",
345        )? as u8;
346        let video_bound = broadcast_common::len::fit_bits(
347            u64::from(self.video_bound),
348            5,
349            "system_header.video_bound",
350        )? as u8;
351        let reserved_bits = broadcast_common::len::fit_mask(
352            self.reserved_bits,
353            RESERVED_BITS_MASK,
354            "system_header.reserved_bits",
355        )?;
356        self.validate_stream_loop()?;
357        // byte 6: marker(1) | rate_bound[21:15](7)
358        buf[6] = 0x80 | ((rate_bound >> 15) & 0x7F) as u8;
359        // byte 7: rate_bound[14:7]
360        buf[7] = ((rate_bound >> 7) & 0xFF) as u8;
361        // byte 8: rate_bound[6:0](7) | marker(1)
362        buf[8] = (((rate_bound & 0x7F) as u8) << 1) | 0x01;
363        // byte 9: audio_bound[5:0](6) | fixed_flag(1) | CSPS_flag(1)
364        buf[9] = audio_bound << 2 | (u8::from(self.fixed_flag) << 1) | u8::from(self.csps_flag);
365        // byte 10: system_audio_lock_flag(1) | system_video_lock_flag(1) | marker(1) | video_bound[4:0](5)
366        buf[10] = (u8::from(self.system_audio_lock_flag) << 7)
367            | (u8::from(self.system_video_lock_flag) << 6)
368            | 0x20 // marker_bit (bit5 of byte 10)
369            | video_bound;
370        // byte 11: packet_rate_restriction_flag(1) | reserved(7)
371        buf[11] = (u8::from(self.packet_rate_restriction_flag) << 7) | reserved_bits;
372
373        // Stream loop
374        let mut pos = 12;
375        for bound in &self.std_buffer_bounds {
376            if let Some(ext) = bound.stream_id_extension {
377                buf[pos] = bound.stream_id;
378                buf[pos + 1] = 0xC0;
379                buf[pos + 2] = ext;
380                buf[pos + 3] = 0xB6;
381                buf[pos + 4] = 0xC0
382                    | (u8::from(bound.buffer_bound_scale) << 5)
383                    | (bound.buffer_size_bound >> 8) as u8;
384                buf[pos + 5] = (bound.buffer_size_bound & 0xFF) as u8;
385                pos += 6;
386            } else {
387                buf[pos] = bound.stream_id;
388                buf[pos + 1] = 0xC0
389                    | (u8::from(bound.buffer_bound_scale) << 5)
390                    | (bound.buffer_size_bound >> 8) as u8;
391                buf[pos + 2] = (bound.buffer_size_bound & 0xFF) as u8;
392                pos += 3;
393            }
394        }
395        buf[pos..pos + self.trailing_reserved.len()].copy_from_slice(&self.trailing_reserved);
396
397        Ok(total)
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404    use alloc::vec;
405
406    #[test]
407    fn system_header_round_trip_no_streams() {
408        let bytes = vec![
409            0x00, 0x00, 0x01, 0xBB, // start_code
410            0x00, 0x06, // header_length = 6 (just the fixed part)
411            0x80, 0x00, 0x01, // rate_bound=0, markers
412            0x04, // audio_bound=1, fixed=0, CSPS=0
413            0x20, // audio_lock=0, video_lock=0, marker=1, video_bound=0
414            0xFF, // packet_rate=1, reserved=0x7F
415        ];
416        let h = SystemHeader::parse(&bytes).unwrap();
417        assert_eq!(h.rate_bound, 0);
418        assert_eq!(h.audio_bound, 1);
419        assert!(!h.fixed_flag);
420        assert!(!h.csps_flag);
421        assert!(!h.system_audio_lock_flag);
422        assert!(!h.system_video_lock_flag);
423        assert_eq!(h.video_bound, 0);
424        assert!(h.packet_rate_restriction_flag);
425        assert!(h.std_buffer_bounds.is_empty());
426
427        let mut out = vec![0u8; h.serialized_len()];
428        h.serialize_into(&mut out).unwrap();
429        assert_eq!(&out[..], &bytes[..]);
430
431        let h2 = SystemHeader::parse(&out).unwrap();
432        assert_eq!(h, h2);
433    }
434
435    /// W1 (#1119): `video_bound` (byte 4 bits `[4:0]`, Table 2-40) was
436    /// masked with `0x0F` (4 bits) on parse but `0x1F` (5 bits) on
437    /// serialize. `video_bound` in `16..=31` sets bit 4, which the old
438    /// parse dropped — `video_bound - 16` came back instead.
439    #[test]
440    fn video_bound_bit_4_round_trips() {
441        let bytes = vec![
442            0x00,
443            0x00,
444            0x01,
445            0xBB, // start_code
446            0x00,
447            0x06, // header_length = 6
448            0x80,
449            0x00,
450            0x01,        // rate_bound=0, markers
451            0x04,        // audio_bound=1, fixed=0, CSPS=0
452            0x20 | 0x1F, // audio_lock=0, video_lock=0, marker=1, video_bound=31 (bit 4 set)
453            0xFF,
454        ];
455        let h = SystemHeader::parse(&bytes).unwrap();
456        assert_eq!(
457            h.video_bound, 31,
458            "bit 4 of video_bound must not be dropped"
459        );
460
461        let mut out = vec![0u8; h.serialized_len()];
462        h.serialize_into(&mut out).unwrap();
463        assert_eq!(&out[..], &bytes[..]);
464    }
465
466    #[test]
467    fn system_header_round_trip_with_streams() {
468        let bytes = vec![
469            0x00, 0x00, 0x01, 0xBB, 0x00,
470            0x0C, // header_length = 12 (6 fixed + 6 for 2 streams)
471            0x80, 0x00, 0x01, 0x04, 0x20, 0xFF,
472            // stream 1: stream_id=0xE0, scale=1, size=0x1FFF
473            0xE0, 0xFF, 0xFF, // stream 2: stream_id=0xC0, scale=0, size=0x0100
474            0xC0, 0xC1, 0x00,
475        ];
476        let h = SystemHeader::parse(&bytes).unwrap();
477        assert_eq!(h.std_buffer_bounds.len(), 2);
478        assert_eq!(h.std_buffer_bounds[0].stream_id, 0xE0);
479        assert!(h.std_buffer_bounds[0].stream_id_extension.is_none());
480        assert!(h.std_buffer_bounds[0].buffer_bound_scale);
481        assert_eq!(h.std_buffer_bounds[0].buffer_size_bound, 0x1FFF);
482
483        assert_eq!(h.std_buffer_bounds[1].stream_id, 0xC0);
484        assert!(h.std_buffer_bounds[1].stream_id_extension.is_none());
485        assert!(!h.std_buffer_bounds[1].buffer_bound_scale);
486        assert_eq!(h.std_buffer_bounds[1].buffer_size_bound, 0x0100);
487
488        let mut out = vec![0u8; h.serialized_len()];
489        h.serialize_into(&mut out).unwrap();
490        assert_eq!(&out[..], &bytes[..]);
491
492        let h2 = SystemHeader::parse(&out).unwrap();
493        assert_eq!(h, h2);
494
495        // Mutation test
496        let h_mut = SystemHeader {
497            rate_bound: 12345,
498            ..h.clone()
499        };
500        let mut out2 = vec![0u8; h_mut.serialized_len()];
501        h_mut.serialize_into(&mut out2).unwrap();
502        assert_ne!(&out[..], &out2[..]);
503    }
504
505    /// r14-MPS-W8 (#1119): padding that `header_length` declares after the
506    /// stream-bound loop is preserved, not dropped (which shrank
507    /// `header_length` on re-serialize).
508    #[test]
509    fn trailing_bytes_after_stream_loop_round_trip() {
510        let bytes = vec![
511            0x00,
512            0x00,
513            0x01,
514            0xBB, // start_code
515            0x00,
516            0x0C, // header_length = 12: 6 fixed + 3 stream entry + 3 trailing
517            0x80,
518            0x00,
519            0x01,
520            0x04,
521            0x20 | 0x01,
522            0xFF, // fixed body
523            0xE0,
524            0xE0,
525            0x80, // one stream entry: stream_id 0xE0, bound
526            0x00,
527            0x7F,
528            0xAB, // trailing, not a stream entry (MSB clear)
529        ];
530        let h = SystemHeader::parse(&bytes).unwrap();
531        assert_eq!(h.std_buffer_bounds.len(), 1);
532        assert_eq!(h.trailing_reserved, vec![0x00, 0x7F, 0xAB]);
533        let mut out = vec![0u8; h.serialized_len()];
534        h.serialize_into(&mut out).unwrap();
535        assert_eq!(out, bytes);
536    }
537
538    /// r14-MPS-W8: an encoder that wrote `reserved_bits` other than the
539    /// conventional `0x7F` must re-serialize byte-identically.
540    #[test]
541    fn non_conventional_reserved_bits_round_trip() {
542        let bytes = vec![
543            0x00, 0x00, 0x01, 0xBB, // start_code
544            0x00, 0x06, // header_length
545            0x80, 0x00, 0x01, // rate_bound=0, markers
546            0x04, // audio_bound=1
547            0x20, // marker=1, video_bound=0
548            0x2A, // packet_rate=0, reserved_bits=0b010_1010
549        ];
550        let h = SystemHeader::parse(&bytes).unwrap();
551        assert_eq!(h.reserved_bits, 0x2A);
552        assert!(!h.packet_rate_restriction_flag);
553        let mut out = vec![0u8; h.serialized_len()];
554        h.serialize_into(&mut out).unwrap();
555        assert_eq!(out, bytes);
556    }
557
558    /// MPS-W7 (#1129): `stream_loop_len` accumulated in `u16`, overflowing
559    /// (debug panic / release wrap) for more than 10 921 extension-form
560    /// bounds (6 bytes each: true total 66 006 bytes > `u16::MAX`). The old
561    /// accumulator's overflow point is the same 65 535 boundary the wire
562    /// field itself has, so the only fixable difference is *how* an
563    /// over-limit count fails: the old code panicked (arithmetic overflow in
564    /// debug; an out-of-bounds buffer write in release, because
565    /// `serialized_len()` used the same wrapped, too-small value the loop
566    /// then wrote past). The fix must return a controlled `Err` here
567    /// instead — never panic. Confirmed pre-fix: this exact case panicked
568    /// with "attempt to add with overflow".
569    #[test]
570    fn many_bounds_past_old_u16_overflow_point_errs_not_panics() {
571        let std_buffer_bounds = (0..11_000)
572            .map(|_| StdBufferBound {
573                stream_id: EXT_STREAM_ID,
574                stream_id_extension: Some(0x05),
575                buffer_bound_scale: false,
576                buffer_size_bound: 0,
577            })
578            .collect::<Vec<_>>();
579        let h = SystemHeader {
580            rate_bound: 0,
581            audio_bound: 0,
582            fixed_flag: false,
583            csps_flag: false,
584            system_audio_lock_flag: false,
585            system_video_lock_flag: false,
586            video_bound: 0,
587            packet_rate_restriction_flag: false,
588            reserved_bits: RESERVED_BITS_MASK,
589            std_buffer_bounds,
590            trailing_reserved: Vec::new(),
591        };
592        // Must not panic building the (correctly-sized-in-usize) buffer or
593        // writing into it; the real total exceeds u16::MAX so this is a
594        // controlled Err, not a silently wrapped Ok.
595        let mut buf = vec![0u8; h.serialized_len()];
596        assert!(h.serialize_into(&mut buf).is_err());
597    }
598
599    /// A bound count whose real `header_length` exceeds the 16-bit wire
600    /// field (`6 + 25_000*3 = 75_006 > 65_535`) must error rather than wrap.
601    #[test]
602    fn header_length_over_u16_is_rejected_not_wrapped() {
603        let std_buffer_bounds = (0..25_000)
604            .map(|_| StdBufferBound {
605                stream_id: 0xC0,
606                stream_id_extension: None,
607                buffer_bound_scale: false,
608                buffer_size_bound: 0,
609            })
610            .collect::<Vec<_>>();
611        let h = SystemHeader {
612            rate_bound: 0,
613            audio_bound: 0,
614            fixed_flag: false,
615            csps_flag: false,
616            system_audio_lock_flag: false,
617            system_video_lock_flag: false,
618            video_bound: 0,
619            packet_rate_restriction_flag: false,
620            reserved_bits: RESERVED_BITS_MASK,
621            std_buffer_bounds,
622            trailing_reserved: Vec::new(),
623        };
624        let mut buf = vec![0u8; h.serialized_len()];
625        assert!(h.serialize_into(&mut buf).is_err());
626    }
627
628    /// The boundary: a bound count whose `header_length` still fits u16
629    /// serializes and round-trips.
630    #[test]
631    fn many_bounds_within_u16_header_length_round_trips() {
632        let std_buffer_bounds = (0..1000)
633            .map(|_| StdBufferBound {
634                stream_id: 0xC0,
635                stream_id_extension: None,
636                buffer_bound_scale: false,
637                buffer_size_bound: 0x0100,
638            })
639            .collect::<Vec<_>>();
640        let h = SystemHeader {
641            rate_bound: 0,
642            audio_bound: 0,
643            fixed_flag: false,
644            csps_flag: false,
645            system_audio_lock_flag: false,
646            system_video_lock_flag: false,
647            video_bound: 0,
648            packet_rate_restriction_flag: false,
649            reserved_bits: RESERVED_BITS_MASK,
650            std_buffer_bounds,
651            trailing_reserved: Vec::new(),
652        };
653        let mut buf = vec![0u8; h.serialized_len()];
654        h.serialize_into(&mut buf).unwrap();
655        let back = SystemHeader::parse(&buf).unwrap();
656        assert_eq!(back.std_buffer_bounds.len(), 1000);
657    }
658
659    #[test]
660    fn system_header_round_trip_extended_stream_id() {
661        let bytes = vec![
662            0x00, 0x00, 0x01, 0xBB, 0x00, 0x0C, // header_length = 12
663            0x80, 0x00, 0x01, 0x04, 0x20, 0xFF,
664            // extended stream: stream_id=0xB7, ext=0x05, scale=1, size=0x0100
665            0xB7, 0xC0, 0x05, 0xB6, 0xE1, 0x00,
666        ];
667        let h = SystemHeader::parse(&bytes).unwrap();
668        assert_eq!(h.std_buffer_bounds.len(), 1);
669        assert_eq!(h.std_buffer_bounds[0].stream_id, 0xB7);
670        assert_eq!(h.std_buffer_bounds[0].stream_id_extension, Some(0x05));
671        assert!(h.std_buffer_bounds[0].buffer_bound_scale);
672        assert_eq!(h.std_buffer_bounds[0].buffer_size_bound, 0x100);
673
674        let mut out = vec![0u8; h.serialized_len()];
675        h.serialize_into(&mut out).unwrap();
676        assert_eq!(&out[..], &bytes[..]);
677    }
678}