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