Skip to main content

dvb_subtitle/segments/
object_data.rs

1//! Object Data Segment — ETSI EN 300 743 §7.2.5, Table 17 (segment_type 0x13).
2//!
3//! Contains object data: either interlaced/bitmap pixel-data sub-blocks,
4//! a character string, or a progressive (zlib-compressed) pixel block.
5
6use crate::error::{Error, Result};
7use broadcast_common::{Parse, Serialize};
8
9/// The object_data_segment segment_type.
10pub const SEGMENT_TYPE: u8 = 0x13;
11/// Header: 6 bytes.
12pub const HEADER_LEN: usize = 6;
13/// Fixed after header: object_id(2) + version/coding/flags(1) = 3 bytes.
14pub const FIXED_LEN: usize = 3;
15/// Length fields for interlaced coding: top_field_data_block_length(2) + bottom_field_data_block_length(2).
16pub const INTERLACE_LEN_LEN: usize = 4;
17/// Progressive pixel block header: bitmap_width(2) + bitmap_height(2) + compressed_len(2).
18pub const PROGRESSIVE_HEADER_LEN: usize = 6;
19/// 7-bit `run_length` field mask in the 8-bit pixel-data run codes
20/// (EN 300 743 §7.2.5.2.3, Table 22).
21const RUN_LENGTH_7BIT_MASK: u8 = 0x7F;
22
23/// Object coding method as defined in Table 18.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[repr(u8)]
27#[non_exhaustive]
28pub enum ObjectCodingMethod {
29    /// Coding of pixels (interlaced).
30    Pixels = 0x00,
31    /// Coded as a string of characters.
32    Characters = 0x01,
33    /// Progressive coding of pixels.
34    ProgressivePixels = 0x02,
35    /// Reserved.
36    Reserved(u8),
37}
38
39impl ObjectCodingMethod {
40    /// Human-readable name for this coding method.
41    #[must_use]
42    pub fn name(&self) -> &'static str {
43        match self {
44            Self::Pixels => "pixels",
45            Self::Characters => "characters",
46            Self::ProgressivePixels => "progressive_pixels",
47            Self::Reserved(_) => "reserved",
48        }
49    }
50
51    fn to_bits(self) -> u8 {
52        match self {
53            Self::Pixels => 0x00,
54            Self::Characters => 0x01,
55            Self::ProgressivePixels => 0x02,
56            Self::Reserved(v) => v,
57        }
58    }
59}
60
61broadcast_common::impl_spec_display!(ObjectCodingMethod, Reserved);
62
63/// Data type for pixel-data sub-blocks as defined in Table 21.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize))]
66#[repr(u8)]
67#[non_exhaustive]
68pub enum DataType {
69    /// 2-bit/pixel code string.
70    CodeString2Bit = 0x10,
71    /// 4-bit/pixel code string.
72    CodeString4Bit = 0x11,
73    /// 8-bit/pixel code string.
74    CodeString8Bit = 0x12,
75    /// 2-to-4-bit map table (2 bytes).
76    MapTable2To4 = 0x20,
77    /// 2-to-8-bit map table (4 bytes).
78    MapTable2To8 = 0x21,
79    /// 4-to-8-bit map table (16 bytes).
80    MapTable4To8 = 0x22,
81    /// End of object line code (0 bytes of data).
82    EndOfLine = 0xF0,
83    /// Reserved.
84    Reserved(u8),
85}
86
87impl DataType {
88    /// Human-readable name for this data type.
89    #[must_use]
90    pub fn name(&self) -> &'static str {
91        match self {
92            Self::CodeString2Bit => "2-bit_code_string",
93            Self::CodeString4Bit => "4-bit_code_string",
94            Self::CodeString8Bit => "8-bit_code_string",
95            Self::MapTable2To4 => "2_to_4_map_table",
96            Self::MapTable2To8 => "2_to_8_map_table",
97            Self::MapTable4To8 => "4_to_8_map_table",
98            Self::EndOfLine => "end_of_line",
99            Self::Reserved(_) => "reserved",
100        }
101    }
102}
103
104broadcast_common::impl_spec_display!(DataType, Reserved);
105
106/// Size of a 2-to-4 bit map table in bytes.
107const MAP_TABLE_2TO4_BYTES: usize = 2;
108/// Size of a 2-to-8 bit map table in bytes.
109const MAP_TABLE_2TO8_BYTES: usize = 4;
110/// Size of a 4-to-8 bit map table in bytes.
111const MAP_TABLE_4TO8_BYTES: usize = 16;
112
113/// A pixel-data sub-block (Table 20).
114#[derive(Debug, Clone, PartialEq, Eq)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize))]
116pub struct PixelDataSubBlock<'a> {
117    /// The data_type.
118    pub data_type: DataType,
119    /// The payload data following the data_type byte.
120    /// For code strings this is the entire RLE token stream including
121    /// the end-of-string marker and byte-alignment stuffing.
122    /// For map tables this is the fixed-size table data.
123    #[cfg_attr(feature = "serde", serde(skip))]
124    pub data: &'a [u8],
125}
126
127impl<'a> PixelDataSubBlock<'a> {
128    /// Decode this sub-block's pixel runs (Tables 22-26), if it is a
129    /// 2/4/8-bit pixel-data code string — `None` for a map-table or
130    /// end-of-line sub-block, neither of which is an RLE token stream.
131    ///
132    /// Yields typed [`PixelRun`]s instead of exposing `data` as raw RLE
133    /// bytes for the caller to decode itself (#1108/DS-W5). `data` (the
134    /// undecoded token stream, including any trailing byte-alignment
135    /// stuffing) is kept as-is alongside this, since round-tripping the
136    /// segment byte-exact depends on preserving it verbatim.
137    #[must_use]
138    pub fn runs(&self) -> Option<PixelRunIter<'a>> {
139        let depth = match self.data_type {
140            DataType::CodeString2Bit => CodeStringDepth::Two,
141            DataType::CodeString4Bit => CodeStringDepth::Four,
142            DataType::CodeString8Bit => CodeStringDepth::Eight,
143            DataType::MapTable2To4
144            | DataType::MapTable2To8
145            | DataType::MapTable4To8
146            | DataType::EndOfLine
147            | DataType::Reserved(_) => return None,
148        };
149        Some(PixelRunIter {
150            data: self.data,
151            bitpos: 0,
152            depth,
153            done: false,
154        })
155    }
156
157    fn serialized_len(&self) -> usize {
158        1 + self.data.len()
159    }
160
161    fn serialize_into(&self, buf: &mut [u8]) {
162        buf[0] = match self.data_type {
163            DataType::CodeString2Bit => 0x10,
164            DataType::CodeString4Bit => 0x11,
165            DataType::CodeString8Bit => 0x12,
166            DataType::MapTable2To4 => 0x20,
167            DataType::MapTable2To8 => 0x21,
168            DataType::MapTable4To8 => 0x22,
169            DataType::EndOfLine => 0xF0,
170            DataType::Reserved(v) => v,
171        };
172        buf[1..1 + self.data.len()].copy_from_slice(self.data);
173    }
174}
175
176/// An interlaced-pixels object data payload (coding method 0x00).
177#[derive(Debug, Clone, PartialEq, Eq)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize))]
179pub struct InterlacedPixelsData<'a> {
180    /// Top-field pixel-data sub-blocks.
181    #[cfg_attr(feature = "serde", serde(borrow))]
182    pub top_sub_blocks: alloc::vec::Vec<PixelDataSubBlock<'a>>,
183    /// Bottom-field pixel-data sub-blocks.
184    #[cfg_attr(feature = "serde", serde(borrow))]
185    pub bottom_sub_blocks: alloc::vec::Vec<PixelDataSubBlock<'a>>,
186    /// Trailing `8_stuff_bits` (Table 17/19): normally 0 or 1 byte per the
187    /// spec's own `stuffing_length` formula, but kept as a slice rather than
188    /// `Option<u8>` because the spec itself notes some legacy encoders don't
189    /// follow that (#1108/DS-W6) — 2+ trailing bytes are preserved for a
190    /// byte-exact round-trip instead of being silently dropped.
191    #[cfg_attr(feature = "serde", serde(skip))]
192    pub stuffing: &'a [u8],
193}
194
195/// A progressive pixel block (Table 27, coding method 0x02).
196#[derive(Debug, Clone, PartialEq, Eq)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198pub struct ProgressivePixelBlock<'a> {
199    /// Bitmap width in pixels.
200    pub bitmap_width: u16,
201    /// Bitmap height in pixels.
202    pub bitmap_height: u16,
203    /// Compressed data (zlib/DEFLATE) — opaque.
204    #[cfg_attr(feature = "serde", serde(skip))]
205    pub compressed_data: &'a [u8],
206}
207
208/// Object data payload variants.
209#[derive(Debug, Clone, PartialEq, Eq)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize))]
211#[non_exhaustive]
212pub enum ObjectDataPayload<'a> {
213    /// Interlaced/bitmap pixel data.
214    #[cfg_attr(feature = "serde", serde(borrow))]
215    InterlacedPixels(InterlacedPixelsData<'a>),
216    /// Character string. `number_of_codes` (Table 20) is derived from
217    /// `character_codes.len()` on serialize, rather than stored — a stored
218    /// count could diverge from the data it's supposed to describe (#1108/
219    /// DS-W3).
220    Characters {
221        /// Character codes (16-bit each).
222        character_codes: alloc::vec::Vec<u16>,
223    },
224    /// Progressive pixel block (zlib-compressed).
225    #[cfg_attr(feature = "serde", serde(borrow))]
226    ProgressivePixels(ProgressivePixelBlock<'a>),
227    /// Reserved coding method — raw data preserved.
228    Reserved {
229        /// The coding method value (0x03 or unknown).
230        coding_method: u8,
231        /// Raw payload bytes.
232        #[cfg_attr(feature = "serde", serde(skip))]
233        data: &'a [u8],
234    },
235}
236
237/// Object Data Segment.
238#[derive(Debug, Clone, PartialEq, Eq)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize))]
240pub struct ObjectDataSegment<'a> {
241    /// The page_id from the segment header.
242    pub page_id: u16,
243    /// Object identifier.
244    pub object_id: u16,
245    /// Object version number (modulo 16).
246    pub object_version_number: u8,
247    /// Object coding method.
248    pub object_coding_method: ObjectCodingMethod,
249    /// Non-modifying colour flag.
250    pub non_modifying_colour_flag: bool,
251    /// The payload data.
252    #[cfg_attr(feature = "serde", serde(borrow))]
253    pub payload: ObjectDataPayload<'a>,
254}
255
256/// One decoded run from a 2/4/8-bit pixel-data code string (Tables 22-26):
257/// `run_length` (>= 1) consecutive pixels painted with CLUT entry `pixel_code`.
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub struct PixelRun {
260    /// The CLUT entry index for this run.
261    pub pixel_code: u8,
262    /// How many consecutive pixels carry `pixel_code`.
263    pub run_length: u32,
264}
265
266/// Bit depth of a pixel-data code string (Tables 22, 24, 26).
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268enum CodeStringDepth {
269    Two,
270    Four,
271    Eight,
272}
273
274/// A lazy decoder over one 2/4/8-bit pixel-data code string's RLE pixel runs
275/// (Tables 22-26), returned by [`PixelDataSubBlock::runs`]. Stops at the
276/// string's `end_of_string_signal`, or at the data boundary if that comes
277/// first (never reads past the sub-block's own `data`).
278#[derive(Debug, Clone)]
279pub struct PixelRunIter<'a> {
280    data: &'a [u8],
281    bitpos: usize,
282    depth: CodeStringDepth,
283    done: bool,
284}
285
286impl PixelRunIter<'_> {
287    /// Read `n` (<= 8) bits starting at bit offset `bp`, big-endian,
288    /// zero-padded past the end of `data` (never panics/reads out of bounds).
289    fn bits(&self, bp: usize, n: usize) -> u8 {
290        let byte_idx = bp / 8;
291        let bit_idx = bp % 8;
292        if byte_idx >= self.data.len() {
293            return 0;
294        }
295        if bit_idx + n <= 8 {
296            (self.data[byte_idx] >> (8 - bit_idx - n)) & ((1u16 << n) - 1) as u8
297        } else {
298            let first_bits = 8 - bit_idx;
299            let v1 = ((self.data[byte_idx] & ((1u8 << first_bits) - 1)) as u16) << (n - first_bits);
300            let v2 = if byte_idx + 1 < self.data.len() {
301                (self.data[byte_idx + 1] >> (8 - (n - first_bits))) as u16
302            } else {
303                0
304            };
305            (v1 | v2) as u8
306        }
307    }
308
309    /// Table 22/23.
310    fn next_2bit(&mut self) -> Option<PixelRun> {
311        let b2 = self.bits(self.bitpos, 2);
312        self.bitpos += 2;
313        if b2 != 0 {
314            return Some(PixelRun {
315                pixel_code: b2,
316                run_length: 1,
317            });
318        }
319        let s1 = self.bits(self.bitpos, 1);
320        self.bitpos += 1;
321        if s1 == 1 {
322            let n = self.bits(self.bitpos, 3);
323            self.bitpos += 3;
324            let code = self.bits(self.bitpos, 2);
325            self.bitpos += 2;
326            return Some(PixelRun {
327                pixel_code: code,
328                run_length: u32::from(n) + 3,
329            });
330        }
331        let s2 = self.bits(self.bitpos, 1);
332        self.bitpos += 1;
333        if s2 == 1 {
334            return Some(PixelRun {
335                pixel_code: 0,
336                run_length: 1,
337            });
338        }
339        let s3 = self.bits(self.bitpos, 2);
340        self.bitpos += 2;
341        match s3 {
342            0b01 => Some(PixelRun {
343                pixel_code: 0,
344                run_length: 2,
345            }),
346            0b10 => {
347                let n = self.bits(self.bitpos, 4);
348                self.bitpos += 4;
349                let code = self.bits(self.bitpos, 2);
350                self.bitpos += 2;
351                Some(PixelRun {
352                    pixel_code: code,
353                    run_length: u32::from(n) + 12,
354                })
355            }
356            0b11 => {
357                let n = self.bits(self.bitpos, 8);
358                self.bitpos += 8;
359                let code = self.bits(self.bitpos, 2);
360                self.bitpos += 2;
361                Some(PixelRun {
362                    pixel_code: code,
363                    run_length: u32::from(n) + 29,
364                })
365            }
366            _ => {
367                // 0b00: end_of_string.
368                self.done = true;
369                None
370            }
371        }
372    }
373
374    /// Table 24/25.
375    fn next_4bit(&mut self) -> Option<PixelRun> {
376        let b4 = self.bits(self.bitpos, 4);
377        self.bitpos += 4;
378        if b4 != 0 {
379            return Some(PixelRun {
380                pixel_code: b4,
381                run_length: 1,
382            });
383        }
384        let s1 = self.bits(self.bitpos, 1);
385        self.bitpos += 1;
386        if s1 == 0 {
387            let n3 = self.bits(self.bitpos, 3);
388            self.bitpos += 3;
389            if n3 == 0 {
390                self.done = true;
391                return None;
392            }
393            return Some(PixelRun {
394                pixel_code: 0,
395                run_length: u32::from(n3) + 2,
396            });
397        }
398        let s2 = self.bits(self.bitpos, 1);
399        self.bitpos += 1;
400        if s2 == 0 {
401            let n2 = self.bits(self.bitpos, 2);
402            self.bitpos += 2;
403            let code = self.bits(self.bitpos, 4);
404            self.bitpos += 4;
405            return Some(PixelRun {
406                pixel_code: code,
407                run_length: u32::from(n2) + 4,
408            });
409        }
410        let s3 = self.bits(self.bitpos, 2);
411        self.bitpos += 2;
412        match s3 {
413            0b00 => Some(PixelRun {
414                pixel_code: 0,
415                run_length: 1,
416            }),
417            0b01 => Some(PixelRun {
418                pixel_code: 0,
419                run_length: 2,
420            }),
421            0b10 => {
422                let n = self.bits(self.bitpos, 4);
423                self.bitpos += 4;
424                let code = self.bits(self.bitpos, 4);
425                self.bitpos += 4;
426                Some(PixelRun {
427                    pixel_code: code,
428                    run_length: u32::from(n) + 9,
429                })
430            }
431            _ => {
432                // 0b11
433                let n = self.bits(self.bitpos, 8);
434                self.bitpos += 8;
435                let code = self.bits(self.bitpos, 4);
436                self.bitpos += 4;
437                Some(PixelRun {
438                    pixel_code: code,
439                    run_length: u32::from(n) + 25,
440                })
441            }
442        }
443    }
444
445    /// Table 26. Always byte-aligned, so this indexes `data` directly rather
446    /// than going through the bit reader.
447    fn next_8bit(&mut self) -> Option<PixelRun> {
448        let pos = self.bitpos / 8;
449        if pos >= self.data.len() {
450            self.done = true;
451            return None;
452        }
453        let b0 = self.data[pos];
454        if b0 != 0 {
455            self.bitpos += 8;
456            return Some(PixelRun {
457                pixel_code: b0,
458                run_length: 1,
459            });
460        }
461        if pos + 1 >= self.data.len() {
462            self.done = true;
463            return None;
464        }
465        let b1 = self.data[pos + 1];
466        if (b1 >> 7) & 1 == 0 {
467            let rl = b1 & RUN_LENGTH_7BIT_MASK;
468            self.bitpos += 16;
469            if rl == 0 {
470                self.done = true;
471                return None;
472            }
473            return Some(PixelRun {
474                pixel_code: 0,
475                run_length: u32::from(rl),
476            });
477        }
478        // switch_1 == 1: run_length_3-127 + 8-bit pixel-code.
479        if pos + 2 >= self.data.len() {
480            self.done = true;
481            return None;
482        }
483        let rl = b1 & RUN_LENGTH_7BIT_MASK;
484        let code = self.data[pos + 2];
485        self.bitpos += 24;
486        Some(PixelRun {
487            pixel_code: code,
488            run_length: u32::from(rl),
489        })
490    }
491}
492
493impl Iterator for PixelRunIter<'_> {
494    type Item = PixelRun;
495
496    fn next(&mut self) -> Option<PixelRun> {
497        if self.done {
498            return None;
499        }
500        match self.depth {
501            CodeStringDepth::Two => self.next_2bit(),
502            CodeStringDepth::Four => self.next_4bit(),
503            CodeStringDepth::Eight => self.next_8bit(),
504        }
505    }
506}
507
508/// Scan a 2-bit/pixel_code_string to find its end marker and total byte length.
509/// Returns the number of bytes consumed INCLUDING any trailing 2-bit stuffing.
510/// Table 22, 23.
511fn scan_2bit_code_string(data: &[u8]) -> usize {
512    let mut bitpos: usize = 0;
513    let bits = |bp: usize, n: usize| -> u8 {
514        let byte_idx = bp / 8;
515        let bit_idx = bp % 8;
516        if byte_idx >= data.len() {
517            return 0;
518        }
519        if bit_idx + n <= 8 {
520            (data[byte_idx] >> (8 - bit_idx - n)) & ((1u16 << n) - 1) as u8
521        } else {
522            let first_bits = 8 - bit_idx;
523            let v1 = ((data[byte_idx] & ((1u8 << first_bits) - 1)) as u16) << (n - first_bits);
524            let v2 = if byte_idx + 1 < data.len() {
525                (data[byte_idx + 1] >> (8 - (n - first_bits))) as u16
526            } else {
527                0
528            };
529            (v1 | v2) as u8
530        }
531    };
532    loop {
533        let b2 = bits(bitpos, 2);
534        bitpos += 2;
535        if b2 != 0 {
536            continue;
537        }
538        // 2-bit_zero, check switch_1
539        let s1 = bits(bitpos, 1);
540        bitpos += 1;
541        if s1 == 1 {
542            // run_length_3-10 (3 bits) + 2-bitpixel-code (2 bits)
543            bitpos += 3 + 2;
544            continue;
545        }
546        let s2 = bits(bitpos, 1);
547        bitpos += 1;
548        if s2 == 1 {
549            // 1 pixel in colour 0
550            continue;
551        }
552        let s3 = bits(bitpos, 2);
553        bitpos += 2;
554        match s3 {
555            0b00 => break,    // end of string
556            0b01 => continue, // 2 pixels in colour 0
557            0b10 => {
558                bitpos += 4 + 2; // run_length_12-27 + pixel-code
559            }
560            0b11 => {
561                bitpos += 8 + 2; // run_length_29-284 + pixel-code
562            }
563            _ => {}
564        }
565    }
566    // Byte-align: the sub-block includes 2_stuff_bits if not byte-aligned
567    let byte_len = bitpos.div_ceil(8);
568    byte_len.min(data.len())
569}
570
571/// Scan a 4-bit/pixel_code_string to find its end marker and total byte length.
572/// Returns number of bytes consumed INCLUDING any trailing 4-bit stuffing.
573/// Table 24, 25.
574fn scan_4bit_code_string(data: &[u8]) -> usize {
575    let mut bitpos: usize = 0;
576    let bits = |bp: usize, n: usize| -> u8 {
577        let byte_idx = bp / 8;
578        let bit_idx = bp % 8;
579        if byte_idx >= data.len() {
580            return 0;
581        }
582        if bit_idx + n <= 8 {
583            (data[byte_idx] >> (8 - bit_idx - n)) & ((1u16 << n) - 1) as u8
584        } else {
585            let first_bits = 8 - bit_idx;
586            let v1 = ((data[byte_idx] & ((1u8 << first_bits) - 1)) as u16) << (n - first_bits);
587            let v2 = if byte_idx + 1 < data.len() {
588                (data[byte_idx + 1] >> (8 - (n - first_bits))) as u16
589            } else {
590                0
591            };
592            (v1 | v2) as u8
593        }
594    };
595    loop {
596        let b4 = bits(bitpos, 4);
597        bitpos += 4;
598        if b4 != 0 {
599            continue;
600        }
601        // 4-bit_zero
602        let s1 = bits(bitpos, 1);
603        bitpos += 1;
604        if s1 == 0 {
605            let n3 = bits(bitpos, 3);
606            bitpos += 3;
607            if n3 == 0 {
608                // end_of_string_signal
609                break;
610            }
611            // run_length_3-9 in colour 0
612            continue;
613        }
614        let s2 = bits(bitpos, 1);
615        bitpos += 1;
616        if s2 == 0 {
617            // run_length_4-7 (2 bits) + 4-bit_pixel-code
618            bitpos += 2 + 4;
619            continue;
620        }
621        let s3 = bits(bitpos, 2);
622        bitpos += 2;
623        match s3 {
624            0b00 | 0b01 => continue, // 1 or 2 pixels in colour 0
625            0b10 => {
626                bitpos += 4 + 4; // run_length_9-24 + pixel-code
627            }
628            0b11 => {
629                bitpos += 8 + 4; // run_length_25-280 + pixel-code
630            }
631            _ => {}
632        }
633    }
634    // Byte-align: if not byte-aligned, 4_stuff_bits are implied
635    let byte_len = bitpos.div_ceil(8);
636    byte_len.min(data.len())
637}
638
639/// Scan an 8-bit/pixel_code_string to find its end marker and total byte length.
640/// Table 26.
641fn scan_8bit_code_string(data: &[u8]) -> usize {
642    let mut pos: usize = 0;
643    loop {
644        if pos >= data.len() {
645            break;
646        }
647        if data[pos] != 0x00 {
648            pos += 1;
649            continue;
650        }
651        if pos + 1 >= data.len() {
652            pos += 1;
653            break;
654        }
655        let b = data[pos + 1];
656        let s1 = (b >> 7) & 1;
657        if s1 == 0 {
658            let rl = b & RUN_LENGTH_7BIT_MASK;
659            if rl == 0 {
660                // end_of_string_signal
661                pos += 2;
662                break;
663            }
664            // run_length_1-127 in colour 0
665            pos += 2;
666        } else {
667            // run_length_3-127 + 8-bitpixel-code
668            pos += 3;
669        }
670    }
671    pos.min(data.len())
672}
673
674/// Parse pixel-data sub-blocks from raw bytes, bounded by the given field length.
675///
676/// Walks data_type-delimited sub-blocks: code strings are scanned to their
677/// terminator, map tables have fixed sizes, 0xF0 (end-of-line) has no data.
678fn parse_pixel_sub_blocks<'a>(
679    bytes: &'a [u8],
680    field_len: usize,
681    what: &'static str,
682) -> Result<alloc::vec::Vec<PixelDataSubBlock<'a>>> {
683    let end = field_len.min(bytes.len());
684    let field = &bytes[..end];
685    let mut blocks = alloc::vec::Vec::new();
686    let mut pos: usize = 0;
687    while pos < field.len() {
688        if pos >= field.len() {
689            break;
690        }
691        let data_type_byte = field[pos];
692        let data_type = match data_type_byte {
693            0x10 => DataType::CodeString2Bit,
694            0x11 => DataType::CodeString4Bit,
695            0x12 => DataType::CodeString8Bit,
696            0x20 => DataType::MapTable2To4,
697            0x21 => DataType::MapTable2To8,
698            0x22 => DataType::MapTable4To8,
699            0xF0 => DataType::EndOfLine,
700            v => DataType::Reserved(v),
701        };
702        pos += 1; // consume data_type byte
703
704        let data_len = match data_type_byte {
705            0x10 => scan_2bit_code_string(&field[pos..]),
706            0x11 => scan_4bit_code_string(&field[pos..]),
707            0x12 => scan_8bit_code_string(&field[pos..]),
708            0x20 => MAP_TABLE_2TO4_BYTES,
709            0x21 => MAP_TABLE_2TO8_BYTES,
710            0x22 => MAP_TABLE_4TO8_BYTES,
711            0xF0 => 0,
712            _ => {
713                // Unknown: consume rest of field
714                field.len() - pos
715            }
716        };
717
718        let block_end = (pos + data_len).min(field.len());
719        let block_data = &field[pos..block_end];
720
721        blocks.push(PixelDataSubBlock {
722            data_type,
723            data: block_data,
724        });
725        pos = block_end;
726    }
727    // Validate field length was exactly consumed
728    if pos != end {
729        return Err(Error::BufferTooShort {
730            need: end,
731            have: pos,
732            what,
733        });
734    }
735    Ok(blocks)
736}
737
738impl<'a> Parse<'a> for ObjectDataSegment<'a> {
739    type Error = Error;
740
741    fn parse(bytes: &'a [u8]) -> Result<Self> {
742        if bytes.len() < HEADER_LEN + FIXED_LEN {
743            return Err(Error::BufferTooShort {
744                need: HEADER_LEN + FIXED_LEN,
745                have: bytes.len(),
746                what: "object_data_segment",
747            });
748        }
749        if bytes[0] != crate::pes_data_field::SYNC_BYTE {
750            return Err(Error::BadSyncByte(bytes[0]));
751        }
752        if bytes[1] != SEGMENT_TYPE {
753            return Err(Error::UnknownSegmentType(bytes[1]));
754        }
755        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
756        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
757        let total = HEADER_LEN + segment_length;
758        if bytes.len() < total {
759            return Err(Error::BufferTooShort {
760                need: total,
761                have: bytes.len(),
762                what: "object_data_segment data",
763            });
764        }
765        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
766        if body.len() < FIXED_LEN {
767            return Err(Error::BufferTooShort {
768                need: FIXED_LEN,
769                have: body.len(),
770                what: "object_data_segment body",
771            });
772        }
773        let object_id = u16::from_be_bytes([body[0], body[1]]);
774        let object_version_number = body[2] >> 4;
775        let coding_method_bits = (body[2] >> 2) & 0x03;
776        let non_modifying_colour_flag = (body[2] & 0x02) != 0;
777        let reserved = body[2] & 0x01;
778        // reserved bit tolerated per §7.2.0.2 forward compatibility
779        let _ = reserved;
780
781        let object_coding_method = match coding_method_bits {
782            0x00 => ObjectCodingMethod::Pixels,
783            0x01 => ObjectCodingMethod::Characters,
784            0x02 => ObjectCodingMethod::ProgressivePixels,
785            v => ObjectCodingMethod::Reserved(v),
786        };
787
788        let payload_data = &body[FIXED_LEN..];
789
790        let payload = match coding_method_bits {
791            0x00 => {
792                if payload_data.len() < INTERLACE_LEN_LEN {
793                    return Err(Error::BufferTooShort {
794                        need: INTERLACE_LEN_LEN,
795                        have: payload_data.len(),
796                        what: "object_data interlace lengths",
797                    });
798                }
799                let top_len = u16::from_be_bytes([payload_data[0], payload_data[1]]) as usize;
800                let bottom_len = u16::from_be_bytes([payload_data[2], payload_data[3]]) as usize;
801
802                let top_start = INTERLACE_LEN_LEN;
803                let top_end = top_start + top_len;
804                let bottom_start = top_end;
805                let bottom_end = bottom_start + bottom_len;
806
807                if payload_data.len() < bottom_end {
808                    return Err(Error::BufferTooShort {
809                        need: bottom_end,
810                        have: payload_data.len(),
811                        what: "object_data pixel sub-blocks",
812                    });
813                }
814
815                // stuffing_length (Table 18) = segment_length - 7 - top_len -
816                // bottom_len (7 = FIXED_LEN + INTERLACE_LEN_LEN); this is
817                // exactly `payload_data.len() - bottom_end`, since
818                // `payload_data` was already sliced to `segment_length -
819                // FIXED_LEN`. The spec says "shall be either zero or one",
820                // but notes some legacy encoders don't follow that — so
821                // whatever trailing bytes remain are captured (not just the
822                // first one) to keep the round-trip byte-exact (#1108/DS-W6).
823                let stuffing = &payload_data[bottom_end..];
824                if let Some(&b) = stuffing.first()
825                    && b != 0x00
826                {
827                    return Err(Error::BadStuffingByte(b));
828                }
829
830                let top_sub_blocks = parse_pixel_sub_blocks(
831                    &payload_data[top_start..top_end],
832                    top_len,
833                    "top pixel sub-blocks",
834                )?;
835                let bottom_sub_blocks = if bottom_len > 0 {
836                    parse_pixel_sub_blocks(
837                        &payload_data[bottom_start..bottom_end],
838                        bottom_len,
839                        "bottom pixel sub-blocks",
840                    )?
841                } else {
842                    alloc::vec::Vec::new()
843                };
844
845                ObjectDataPayload::InterlacedPixels(InterlacedPixelsData {
846                    top_sub_blocks,
847                    bottom_sub_blocks,
848                    stuffing,
849                })
850            }
851            0x01 => {
852                if payload_data.is_empty() {
853                    return Err(Error::BufferTooShort {
854                        need: 1,
855                        have: 0,
856                        what: "character count",
857                    });
858                }
859                let number_of_codes = payload_data[0] as usize;
860                let codes_len = 1 + number_of_codes * 2;
861                if payload_data.len() < codes_len {
862                    return Err(Error::BufferTooShort {
863                        need: codes_len,
864                        have: payload_data.len(),
865                        what: "character codes",
866                    });
867                }
868                let mut codes = alloc::vec::Vec::with_capacity(number_of_codes);
869                for i in 0..number_of_codes {
870                    let ci = 1 + i * 2;
871                    codes.push(u16::from_be_bytes([payload_data[ci], payload_data[ci + 1]]));
872                }
873                ObjectDataPayload::Characters {
874                    character_codes: codes,
875                }
876            }
877            0x02 => {
878                if payload_data.len() < PROGRESSIVE_HEADER_LEN {
879                    return Err(Error::BufferTooShort {
880                        need: PROGRESSIVE_HEADER_LEN,
881                        have: payload_data.len(),
882                        what: "progressive pixel block",
883                    });
884                }
885                let bitmap_width = u16::from_be_bytes([payload_data[0], payload_data[1]]);
886                let bitmap_height = u16::from_be_bytes([payload_data[2], payload_data[3]]);
887                let compressed_len =
888                    u16::from_be_bytes([payload_data[4], payload_data[5]]) as usize;
889                if payload_data.len() < PROGRESSIVE_HEADER_LEN + compressed_len {
890                    return Err(Error::BufferTooShort {
891                        need: PROGRESSIVE_HEADER_LEN + compressed_len,
892                        have: payload_data.len(),
893                        what: "compressed bitmap data",
894                    });
895                }
896                ObjectDataPayload::ProgressivePixels(ProgressivePixelBlock {
897                    bitmap_width,
898                    bitmap_height,
899                    compressed_data: &payload_data
900                        [PROGRESSIVE_HEADER_LEN..PROGRESSIVE_HEADER_LEN + compressed_len],
901                })
902            }
903            _ => ObjectDataPayload::Reserved {
904                coding_method: coding_method_bits,
905                data: payload_data,
906            },
907        };
908
909        Ok(ObjectDataSegment {
910            page_id,
911            object_id,
912            object_version_number,
913            object_coding_method,
914            non_modifying_colour_flag,
915            payload,
916        })
917    }
918}
919
920impl Serialize for ObjectDataSegment<'_> {
921    type Error = Error;
922
923    fn serialized_len(&self) -> usize {
924        let body_len = FIXED_LEN
925            + match &self.payload {
926                ObjectDataPayload::InterlacedPixels(ip) => {
927                    let top_len: usize = ip.top_sub_blocks.iter().map(|s| s.serialized_len()).sum();
928                    let bottom_len: usize = ip
929                        .bottom_sub_blocks
930                        .iter()
931                        .map(|s| s.serialized_len())
932                        .sum();
933                    INTERLACE_LEN_LEN + top_len + bottom_len + ip.stuffing.len()
934                }
935                ObjectDataPayload::Characters {
936                    character_codes, ..
937                } => 1 + character_codes.len() * 2,
938                ObjectDataPayload::ProgressivePixels(pp) => {
939                    PROGRESSIVE_HEADER_LEN + pp.compressed_data.len()
940                }
941                ObjectDataPayload::Reserved { data, .. } => data.len(),
942            };
943        HEADER_LEN + body_len
944    }
945
946    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
947        let len = self.serialized_len();
948        if buf.len() < len {
949            return Err(Error::BufferTooShort {
950                need: len,
951                have: buf.len(),
952                what: "object_data_segment serialize",
953            });
954        }
955        buf[0] = 0x0F;
956        buf[1] = SEGMENT_TYPE;
957        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
958        let seg_len = crate::segments::check_segment_length(len - HEADER_LEN)?;
959        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
960
961        buf[6..8].copy_from_slice(&self.object_id.to_be_bytes());
962        buf[8] = (broadcast_common::len::fit_mask(
963            self.object_version_number,
964            0x0F,
965            "object_version_number",
966        )? << 4)
967            | (broadcast_common::len::fit_mask(
968                self.object_coding_method.to_bits(),
969                0x03,
970                "object_coding_method",
971            )? << 2)
972            | (u8::from(self.non_modifying_colour_flag) << 1);
973
974        let mut off = HEADER_LEN + FIXED_LEN;
975        match &self.payload {
976            ObjectDataPayload::InterlacedPixels(ip) => {
977                let top_len: usize = ip.top_sub_blocks.iter().map(|s| s.serialized_len()).sum();
978                let bottom_len: usize = ip
979                    .bottom_sub_blocks
980                    .iter()
981                    .map(|s| s.serialized_len())
982                    .sum();
983                let top_len = broadcast_common::len::fit_u16(top_len, "top_field_data_length")?;
984                let bottom_len =
985                    broadcast_common::len::fit_u16(bottom_len, "bottom_field_data_length")?;
986                buf[off..off + 2].copy_from_slice(&top_len.to_be_bytes());
987                buf[off + 2..off + 4].copy_from_slice(&bottom_len.to_be_bytes());
988                off += INTERLACE_LEN_LEN;
989                for sub in &ip.top_sub_blocks {
990                    sub.serialize_into(&mut buf[off..]);
991                    off += sub.serialized_len();
992                }
993                for sub in &ip.bottom_sub_blocks {
994                    sub.serialize_into(&mut buf[off..]);
995                    off += sub.serialized_len();
996                }
997                buf[off..off + ip.stuffing.len()].copy_from_slice(ip.stuffing);
998                off += ip.stuffing.len();
999            }
1000            ObjectDataPayload::Characters { character_codes } => {
1001                buf[off] = broadcast_common::len::fit_u8(character_codes.len(), "number_of_codes")?;
1002                off += 1;
1003                for code in character_codes {
1004                    buf[off..off + 2].copy_from_slice(&code.to_be_bytes());
1005                    off += 2;
1006                }
1007            }
1008            ObjectDataPayload::ProgressivePixels(pp) => {
1009                buf[off..off + 2].copy_from_slice(&pp.bitmap_width.to_be_bytes());
1010                buf[off + 2..off + 4].copy_from_slice(&pp.bitmap_height.to_be_bytes());
1011                let clen = broadcast_common::len::fit_u16(
1012                    pp.compressed_data.len(),
1013                    "compressed_data_length",
1014                )?;
1015                buf[off + 4..off + 6].copy_from_slice(&clen.to_be_bytes());
1016                off += PROGRESSIVE_HEADER_LEN;
1017                buf[off..off + pp.compressed_data.len()].copy_from_slice(pp.compressed_data);
1018                off += pp.compressed_data.len();
1019            }
1020            ObjectDataPayload::Reserved { data, .. } => {
1021                buf[off..off + data.len()].copy_from_slice(data);
1022                off += data.len();
1023            }
1024        }
1025        debug_assert_eq!(off, len);
1026        Ok(len)
1027    }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032    use super::*;
1033    use broadcast_common::{Parse, Serialize};
1034
1035    #[test]
1036    fn round_trip_pixels_no_stuffing() {
1037        let bytes = [
1038            0x0F, 0x13, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x00, 0x03, 0x00, 0x00, 0xF0,
1039            0x10, 0x0A,
1040        ];
1041        let seg = ObjectDataSegment::parse(&bytes).unwrap();
1042        assert_eq!(seg.object_id, 10);
1043        assert_eq!(seg.object_coding_method, ObjectCodingMethod::Pixels);
1044        match &seg.payload {
1045            ObjectDataPayload::InterlacedPixels(ip) => {
1046                assert_eq!(ip.top_sub_blocks.len(), 2);
1047                assert_eq!(ip.top_sub_blocks[0].data_type, DataType::EndOfLine);
1048                assert_eq!(ip.top_sub_blocks[1].data_type, DataType::CodeString2Bit);
1049            }
1050            _ => panic!("expected InterlacedPixels"),
1051        }
1052        let out = seg.try_to_bytes().unwrap();
1053        assert_eq!(out, bytes);
1054
1055        // Biting test
1056        let mut seg2 = seg.clone();
1057        seg2.object_id = 20;
1058        let out2 = seg2.try_to_bytes().unwrap();
1059        assert_ne!(out2, bytes);
1060        let reparse = ObjectDataSegment::parse(&out2).unwrap();
1061        assert_eq!(reparse.object_id, 20);
1062    }
1063
1064    /// DS-W6 (#1108): stuffing_length (Table 18) is "either zero or one" per
1065    /// spec, but the spec itself notes legacy encoders may not follow that.
1066    /// Pre-fix, only exactly 1 trailing byte was captured (as
1067    /// `stuffing_byte: Option<u8>`); 2+ were silently dropped, so a segment
1068    /// with 2 trailing stuffing bytes didn't round-trip byte-exact.
1069    #[test]
1070    fn preserves_multi_byte_stuffing() {
1071        let bytes = [
1072            0x0F, 0x13, 0x00, 0x01, 0x00, 0x09, // header, segment_length=9
1073            0x00, 0x0A, 0x00, // object_id=10, version/method/flags=0 (Pixels)
1074            0x00, 0x00, 0x00, 0x00, // top_len=0, bottom_len=0
1075            0x00, 0x00, // 2 trailing stuffing bytes
1076        ];
1077        let seg = ObjectDataSegment::parse(&bytes).unwrap();
1078        match &seg.payload {
1079            ObjectDataPayload::InterlacedPixels(ip) => {
1080                assert_eq!(ip.stuffing, &[0x00, 0x00]);
1081            }
1082            _ => panic!("expected InterlacedPixels"),
1083        }
1084        let out = seg.try_to_bytes().unwrap();
1085        assert_eq!(out, bytes, "2 trailing stuffing bytes must round-trip");
1086    }
1087
1088    /// DS-W2 (#1108): sync_byte wasn't checked by any typed segment parser.
1089    #[test]
1090    fn rejects_bad_sync_byte() {
1091        let bytes = [
1092            0x00, 0x13, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x00, 0x03, 0x00, 0x00, 0xF0,
1093            0x10, 0x0A,
1094        ];
1095        assert!(matches!(
1096            ObjectDataSegment::parse(&bytes),
1097            Err(Error::BadSyncByte(0x00))
1098        ));
1099    }
1100
1101    #[test]
1102    fn round_trip_multiple_sub_blocks() {
1103        // Top field: map-table(0x20) + end-of-line(0xF0) + 4-bit code string
1104        let map_table: [u8; 2] = [0x00, 0x00];
1105        // 4-bit code string: 5 pixels of colour 1, then end-of-string
1106        // 0001 0001 0001 0001 0001 0000 000 → 5 ones, zero, then switch_1=0, next 3 bits=000 → end
1107        let four_bit_data = [0x11, 0x11, 0x11, 0x00]; // 4 bytes
1108        let body: alloc::vec::Vec<u8> = [
1109            &[0x00u8, 0x0B, 0x00][..],
1110            &[0x00u8, 0x09, 0x00, 0x00][..], // top_len=9 (1+2+1+1+4)
1111            &[0x20u8][..],
1112            &map_table[..],
1113            &[0xF0u8][..],
1114            &[0x11u8][..],
1115            &four_bit_data[..],
1116        ]
1117        .concat();
1118
1119        let mut bytes = alloc::vec![0x0F, 0x13, 0x00, 0x01]; // header up to segment_length
1120        let body_len = body.len() as u16;
1121        bytes.extend_from_slice(&body_len.to_be_bytes());
1122        bytes.extend_from_slice(&body);
1123
1124        let seg = ObjectDataSegment::parse(&bytes).unwrap();
1125        match &seg.payload {
1126            ObjectDataPayload::InterlacedPixels(ip) => {
1127                assert_eq!(ip.top_sub_blocks.len(), 3, "expected 3 sub-blocks");
1128                assert_eq!(ip.top_sub_blocks[0].data_type, DataType::MapTable2To4);
1129                assert_eq!(ip.top_sub_blocks[0].data.len(), 2);
1130                assert_eq!(ip.top_sub_blocks[1].data_type, DataType::EndOfLine);
1131                assert_eq!(ip.top_sub_blocks[2].data_type, DataType::CodeString4Bit);
1132                assert_eq!(ip.top_sub_blocks[2].data.len(), 4);
1133                // DS-W5 (#1108): the pixel data is also available as typed
1134                // runs, not just the raw RLE bytes. Cross-check that the
1135                // decoder consumes exactly as many bits as the (separately
1136                // tested, framing-critical) scanner does, on the same real
1137                // fixture bytes: the two must never disagree about where the
1138                // code string ends.
1139                let sub = &ip.top_sub_blocks[2];
1140                let scanned_len = scan_4bit_code_string(sub.data);
1141                let mut it = sub.runs().expect("code string sub-block has runs()");
1142                let mut iterations = 0;
1143                while it.next().is_some() {
1144                    iterations += 1;
1145                    assert!(iterations < 1_000, "runaway iterator");
1146                }
1147                assert_eq!(it.bitpos.div_ceil(8), scanned_len);
1148            }
1149            _ => panic!("expected InterlacedPixels"),
1150        }
1151        let out = seg.try_to_bytes().unwrap();
1152        assert_eq!(out, bytes);
1153    }
1154
1155    /// DS-W5 (#1108): map-table and end-of-line sub-blocks aren't RLE token
1156    /// streams, so `runs()` must say so rather than misdecoding fixed-size
1157    /// table bytes as a code string.
1158    #[test]
1159    fn runs_is_none_for_non_code_string_data_types() {
1160        let map = PixelDataSubBlock {
1161            data_type: DataType::MapTable2To4,
1162            data: &[0x00, 0x00],
1163        };
1164        assert!(map.runs().is_none());
1165        let eol = PixelDataSubBlock {
1166            data_type: DataType::EndOfLine,
1167            data: &[],
1168        };
1169        assert!(eol.runs().is_none());
1170    }
1171
1172    /// DS-W5 (#1108): exact decode of a hand-computed 2-bit/pixel_code_string
1173    /// (Table 22/23) covering the non-zero direct code and the
1174    /// run_length_3-10 + switch_3 branches, per §7.2.5.2.1.
1175    #[test]
1176    fn pixel_run_iter_2bit_exact() {
1177        // "10" (direct code 2, run 1) then "00 0 0 00" (2-bit_zero, s1=0,
1178        // s2=0, s3=00 -> end_of_string): 0b1000_0000 = 0x80.
1179        let direct = PixelDataSubBlock {
1180            data_type: DataType::CodeString2Bit,
1181            data: &[0x80],
1182        };
1183        let mut it = direct.runs().unwrap();
1184        assert_eq!(
1185            it.next(),
1186            Some(PixelRun {
1187                pixel_code: 2,
1188                run_length: 1
1189            })
1190        );
1191        assert_eq!(it.next(), None);
1192
1193        // "00" (2-bit_zero) "1" (s1=1) "101" (run_length field = 5 -> 5+3=8)
1194        // "11" (pixel_code=3): 0b0011_0111 = 0x37.
1195        let run = PixelDataSubBlock {
1196            data_type: DataType::CodeString2Bit,
1197            data: &[0x37],
1198        };
1199        let mut it = run.runs().unwrap();
1200        assert_eq!(
1201            it.next(),
1202            Some(PixelRun {
1203                pixel_code: 3,
1204                run_length: 8
1205            })
1206        );
1207    }
1208
1209    /// DS-W5 (#1108): exact decode of a hand-computed 8-bit/pixel_code_string
1210    /// (Table 26): a direct code, a colour-0 run, another direct code, then
1211    /// the end_of_string_signal.
1212    #[test]
1213    fn pixel_run_iter_8bit_exact() {
1214        let data = [0x05, 0x00, 0x03, 0x2A, 0x00, 0x00];
1215        let sub = PixelDataSubBlock {
1216            data_type: DataType::CodeString8Bit,
1217            data: &data,
1218        };
1219        let mut it = sub.runs().unwrap();
1220        assert_eq!(
1221            it.next(),
1222            Some(PixelRun {
1223                pixel_code: 5,
1224                run_length: 1
1225            })
1226        );
1227        assert_eq!(
1228            it.next(),
1229            Some(PixelRun {
1230                pixel_code: 0,
1231                run_length: 3
1232            })
1233        );
1234        assert_eq!(
1235            it.next(),
1236            Some(PixelRun {
1237                pixel_code: 0x2A,
1238                run_length: 1
1239            })
1240        );
1241        assert_eq!(it.next(), None);
1242        // scan_8bit_code_string must agree on the consumed length.
1243        assert_eq!(scan_8bit_code_string(&data), it.bitpos.div_ceil(8));
1244    }
1245
1246    #[test]
1247    fn round_trip_characters() {
1248        let bytes = [
1249            0x0F, 0x13, 0x00, 0x01, 0x00, 0x08, 0x00, 0x0B, 0x04, 0x02, 0x00, 0x41, 0x00, 0x42,
1250        ];
1251        let seg = ObjectDataSegment::parse(&bytes).unwrap();
1252        assert_eq!(seg.object_coding_method, ObjectCodingMethod::Characters);
1253        match &seg.payload {
1254            ObjectDataPayload::Characters { character_codes } => {
1255                assert_eq!(character_codes.len(), 2);
1256                assert_eq!(character_codes[0], 0x0041);
1257                assert_eq!(character_codes[1], 0x0042);
1258            }
1259            _ => panic!("expected Characters"),
1260        }
1261        let out = seg.try_to_bytes().unwrap();
1262        assert_eq!(out, bytes);
1263
1264        // Biting test: `number_of_codes` (Table 20) is derived from
1265        // `character_codes.len()`, not stored (#1108/DS-W3), so appending a
1266        // code must grow the on-wire count byte, and the result must
1267        // re-parse to the same, now-3-code, list.
1268        let mut seg2 = seg.clone();
1269        if let ObjectDataPayload::Characters { character_codes } = &mut seg2.payload {
1270            character_codes.push(0x0043);
1271        }
1272        let out2 = seg2.try_to_bytes().unwrap();
1273        assert_ne!(out2, bytes);
1274        assert_eq!(
1275            out2[HEADER_LEN + FIXED_LEN],
1276            3,
1277            "wire number_of_codes must track the new length"
1278        );
1279        let reparse = ObjectDataSegment::parse(&out2).unwrap();
1280        match &reparse.payload {
1281            ObjectDataPayload::Characters { character_codes } => {
1282                assert_eq!(character_codes.as_slice(), &[0x0041, 0x0042, 0x0043]);
1283            }
1284            _ => panic!("expected Characters"),
1285        }
1286    }
1287
1288    #[test]
1289    fn round_trip_progressive() {
1290        let bytes = [
1291            0x0F, 0x13, 0x00, 0x01, 0x00, 0x0F, 0x00, 0x0C, 0x08, 0x00, 0x64, 0x00, 0x32, 0x00,
1292            0x06, 0x78, 0xDA, 0x63, 0x60, 0x60, 0x60,
1293        ];
1294        let seg = ObjectDataSegment::parse(&bytes).unwrap();
1295        assert_eq!(
1296            seg.object_coding_method,
1297            ObjectCodingMethod::ProgressivePixels
1298        );
1299        match &seg.payload {
1300            ObjectDataPayload::ProgressivePixels(pp) => {
1301                assert_eq!(pp.bitmap_width, 100);
1302                assert_eq!(pp.bitmap_height, 50);
1303                assert_eq!(pp.compressed_data.len(), 6);
1304            }
1305            _ => panic!("expected ProgressivePixels"),
1306        }
1307        let out = seg.try_to_bytes().unwrap();
1308        assert_eq!(out, bytes);
1309
1310        // Biting test
1311        let mut seg2 = seg.clone();
1312        if let ObjectDataPayload::ProgressivePixels(pp) = &mut seg2.payload {
1313            pp.bitmap_width = 200;
1314        }
1315        let out2 = seg2.try_to_bytes().unwrap();
1316        assert_ne!(out2, bytes);
1317        let reparse = ObjectDataSegment::parse(&out2).unwrap();
1318        match &reparse.payload {
1319            ObjectDataPayload::ProgressivePixels(pp) => {
1320                assert_eq!(pp.bitmap_width, 200);
1321            }
1322            _ => panic!(),
1323        }
1324    }
1325
1326    #[test]
1327    fn oversized_segment_is_rejected_not_wrapped() {
1328        // Before the fix, a 64 KiB+ body silently wrapped segment_length and
1329        // returned Ok.
1330        let big = alloc::vec![0u8; 65_536];
1331        let seg = ObjectDataSegment {
1332            page_id: 1,
1333            object_id: 1,
1334            object_version_number: 0,
1335            object_coding_method: ObjectCodingMethod::ProgressivePixels,
1336            non_modifying_colour_flag: false,
1337            payload: ObjectDataPayload::ProgressivePixels(ProgressivePixelBlock {
1338                bitmap_width: 1,
1339                bitmap_height: 1,
1340                compressed_data: &big,
1341            }),
1342        };
1343        let mut buf = alloc::vec![0u8; 65_600];
1344        assert_eq!(seg.serialize_into(&mut buf), Err(Error::SegmentTooLarge));
1345    }
1346}