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
20/// Object coding method as defined in Table 18.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[repr(u8)]
24#[non_exhaustive]
25pub enum ObjectCodingMethod {
26    /// Coding of pixels (interlaced).
27    Pixels = 0x00,
28    /// Coded as a string of characters.
29    Characters = 0x01,
30    /// Progressive coding of pixels.
31    ProgressivePixels = 0x02,
32    /// Reserved.
33    Reserved(u8),
34}
35
36impl ObjectCodingMethod {
37    /// Human-readable name for this coding method.
38    #[must_use]
39    pub fn name(&self) -> &'static str {
40        match self {
41            Self::Pixels => "pixels",
42            Self::Characters => "characters",
43            Self::ProgressivePixels => "progressive_pixels",
44            Self::Reserved(_) => "reserved",
45        }
46    }
47
48    fn to_bits(self) -> u8 {
49        match self {
50            Self::Pixels => 0x00,
51            Self::Characters => 0x01,
52            Self::ProgressivePixels => 0x02,
53            Self::Reserved(v) => v & 0x03,
54        }
55    }
56}
57
58broadcast_common::impl_spec_display!(ObjectCodingMethod, Reserved);
59
60/// Data type for pixel-data sub-blocks as defined in Table 21.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63#[repr(u8)]
64#[non_exhaustive]
65pub enum DataType {
66    /// 2-bit/pixel code string.
67    CodeString2Bit = 0x10,
68    /// 4-bit/pixel code string.
69    CodeString4Bit = 0x11,
70    /// 8-bit/pixel code string.
71    CodeString8Bit = 0x12,
72    /// 2-to-4-bit map table (2 bytes).
73    MapTable2To4 = 0x20,
74    /// 2-to-8-bit map table (4 bytes).
75    MapTable2To8 = 0x21,
76    /// 4-to-8-bit map table (16 bytes).
77    MapTable4To8 = 0x22,
78    /// End of object line code (0 bytes of data).
79    EndOfLine = 0xF0,
80    /// Reserved.
81    Reserved(u8),
82}
83
84impl DataType {
85    /// Human-readable name for this data type.
86    #[must_use]
87    pub fn name(&self) -> &'static str {
88        match self {
89            Self::CodeString2Bit => "2-bit_code_string",
90            Self::CodeString4Bit => "4-bit_code_string",
91            Self::CodeString8Bit => "8-bit_code_string",
92            Self::MapTable2To4 => "2_to_4_map_table",
93            Self::MapTable2To8 => "2_to_8_map_table",
94            Self::MapTable4To8 => "4_to_8_map_table",
95            Self::EndOfLine => "end_of_line",
96            Self::Reserved(_) => "reserved",
97        }
98    }
99}
100
101broadcast_common::impl_spec_display!(DataType, Reserved);
102
103/// Size of a 2-to-4 bit map table in bytes.
104const MAP_TABLE_2TO4_BYTES: usize = 2;
105/// Size of a 2-to-8 bit map table in bytes.
106const MAP_TABLE_2TO8_BYTES: usize = 4;
107/// Size of a 4-to-8 bit map table in bytes.
108const MAP_TABLE_4TO8_BYTES: usize = 16;
109
110/// A pixel-data sub-block (Table 20).
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113pub struct PixelDataSubBlock<'a> {
114    /// The data_type.
115    pub data_type: DataType,
116    /// The payload data following the data_type byte.
117    /// For code strings this is the entire RLE token stream including
118    /// the end-of-string marker and byte-alignment stuffing.
119    /// For map tables this is the fixed-size table data.
120    #[cfg_attr(feature = "serde", serde(skip))]
121    pub data: &'a [u8],
122}
123
124impl PixelDataSubBlock<'_> {
125    fn serialized_len(&self) -> usize {
126        1 + self.data.len()
127    }
128
129    fn serialize_into(&self, buf: &mut [u8]) {
130        buf[0] = match self.data_type {
131            DataType::CodeString2Bit => 0x10,
132            DataType::CodeString4Bit => 0x11,
133            DataType::CodeString8Bit => 0x12,
134            DataType::MapTable2To4 => 0x20,
135            DataType::MapTable2To8 => 0x21,
136            DataType::MapTable4To8 => 0x22,
137            DataType::EndOfLine => 0xF0,
138            DataType::Reserved(v) => v,
139        };
140        buf[1..1 + self.data.len()].copy_from_slice(self.data);
141    }
142}
143
144/// An interlaced-pixels object data payload (coding method 0x00).
145#[derive(Debug, Clone, PartialEq, Eq)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize))]
147pub struct InterlacedPixelsData<'a> {
148    /// Top-field pixel-data sub-blocks.
149    #[cfg_attr(feature = "serde", serde(borrow))]
150    pub top_sub_blocks: alloc::vec::Vec<PixelDataSubBlock<'a>>,
151    /// Bottom-field pixel-data sub-blocks.
152    #[cfg_attr(feature = "serde", serde(borrow))]
153    pub bottom_sub_blocks: alloc::vec::Vec<PixelDataSubBlock<'a>>,
154    /// Stuffing byte if present.
155    pub stuffing_byte: Option<u8>,
156}
157
158/// A progressive pixel block (Table 27, coding method 0x02).
159#[derive(Debug, Clone, PartialEq, Eq)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize))]
161pub struct ProgressivePixelBlock<'a> {
162    /// Bitmap width in pixels.
163    pub bitmap_width: u16,
164    /// Bitmap height in pixels.
165    pub bitmap_height: u16,
166    /// Compressed data (zlib/DEFLATE) — opaque.
167    #[cfg_attr(feature = "serde", serde(skip))]
168    pub compressed_data: &'a [u8],
169}
170
171/// Object data payload variants.
172#[derive(Debug, Clone, PartialEq, Eq)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize))]
174#[non_exhaustive]
175pub enum ObjectDataPayload<'a> {
176    /// Interlaced/bitmap pixel data.
177    #[cfg_attr(feature = "serde", serde(borrow))]
178    InterlacedPixels(InterlacedPixelsData<'a>),
179    /// Character string.
180    Characters {
181        /// Number of character codes.
182        number_of_codes: u8,
183        /// Character codes (16-bit each).
184        character_codes: alloc::vec::Vec<u16>,
185    },
186    /// Progressive pixel block (zlib-compressed).
187    #[cfg_attr(feature = "serde", serde(borrow))]
188    ProgressivePixels(ProgressivePixelBlock<'a>),
189    /// Reserved coding method — raw data preserved.
190    Reserved {
191        /// The coding method value (0x03 or unknown).
192        coding_method: u8,
193        /// Raw payload bytes.
194        #[cfg_attr(feature = "serde", serde(skip))]
195        data: &'a [u8],
196    },
197}
198
199/// Object Data Segment.
200#[derive(Debug, Clone, PartialEq, Eq)]
201#[cfg_attr(feature = "serde", derive(serde::Serialize))]
202pub struct ObjectDataSegment<'a> {
203    /// The page_id from the segment header.
204    pub page_id: u16,
205    /// Object identifier.
206    pub object_id: u16,
207    /// Object version number (modulo 16).
208    pub object_version_number: u8,
209    /// Object coding method.
210    pub object_coding_method: ObjectCodingMethod,
211    /// Non-modifying colour flag.
212    pub non_modifying_colour_flag: bool,
213    /// The payload data.
214    #[cfg_attr(feature = "serde", serde(borrow))]
215    pub payload: ObjectDataPayload<'a>,
216}
217
218/// Scan a 2-bit/pixel_code_string to find its end marker and total byte length.
219/// Returns the number of bytes consumed INCLUDING any trailing 2-bit stuffing.
220/// Table 22, 23.
221fn scan_2bit_code_string(data: &[u8]) -> usize {
222    let mut bitpos: usize = 0;
223    let bits = |bp: usize, n: usize| -> u8 {
224        let byte_idx = bp / 8;
225        let bit_idx = bp % 8;
226        if byte_idx >= data.len() {
227            return 0;
228        }
229        if bit_idx + n <= 8 {
230            (data[byte_idx] >> (8 - bit_idx - n)) & ((1u16 << n) - 1) as u8
231        } else {
232            let first_bits = 8 - bit_idx;
233            let v1 = ((data[byte_idx] & ((1u8 << first_bits) - 1)) as u16) << (n - first_bits);
234            let v2 = if byte_idx + 1 < data.len() {
235                (data[byte_idx + 1] >> (8 - (n - first_bits))) as u16
236            } else {
237                0
238            };
239            (v1 | v2) as u8
240        }
241    };
242    loop {
243        let b2 = bits(bitpos, 2);
244        bitpos += 2;
245        if b2 != 0 {
246            continue;
247        }
248        // 2-bit_zero, check switch_1
249        let s1 = bits(bitpos, 1);
250        bitpos += 1;
251        if s1 == 1 {
252            // run_length_3-10 (3 bits) + 2-bitpixel-code (2 bits)
253            bitpos += 3 + 2;
254            continue;
255        }
256        let s2 = bits(bitpos, 1);
257        bitpos += 1;
258        if s2 == 1 {
259            // 1 pixel in colour 0
260            continue;
261        }
262        let s3 = bits(bitpos, 2);
263        bitpos += 2;
264        match s3 {
265            0b00 => break,    // end of string
266            0b01 => continue, // 2 pixels in colour 0
267            0b10 => {
268                bitpos += 4 + 2; // run_length_12-27 + pixel-code
269            }
270            0b11 => {
271                bitpos += 8 + 2; // run_length_29-284 + pixel-code
272            }
273            _ => {}
274        }
275    }
276    // Byte-align: the sub-block includes 2_stuff_bits if not byte-aligned
277    let byte_len = bitpos.div_ceil(8);
278    byte_len.min(data.len())
279}
280
281/// Scan a 4-bit/pixel_code_string to find its end marker and total byte length.
282/// Returns number of bytes consumed INCLUDING any trailing 4-bit stuffing.
283/// Table 24, 25.
284fn scan_4bit_code_string(data: &[u8]) -> usize {
285    let mut bitpos: usize = 0;
286    let bits = |bp: usize, n: usize| -> u8 {
287        let byte_idx = bp / 8;
288        let bit_idx = bp % 8;
289        if byte_idx >= data.len() {
290            return 0;
291        }
292        if bit_idx + n <= 8 {
293            (data[byte_idx] >> (8 - bit_idx - n)) & ((1u16 << n) - 1) as u8
294        } else {
295            let first_bits = 8 - bit_idx;
296            let v1 = ((data[byte_idx] & ((1u8 << first_bits) - 1)) as u16) << (n - first_bits);
297            let v2 = if byte_idx + 1 < data.len() {
298                (data[byte_idx + 1] >> (8 - (n - first_bits))) as u16
299            } else {
300                0
301            };
302            (v1 | v2) as u8
303        }
304    };
305    loop {
306        let b4 = bits(bitpos, 4);
307        bitpos += 4;
308        if b4 != 0 {
309            continue;
310        }
311        // 4-bit_zero
312        let s1 = bits(bitpos, 1);
313        bitpos += 1;
314        if s1 == 0 {
315            let n3 = bits(bitpos, 3);
316            bitpos += 3;
317            if n3 == 0 {
318                // end_of_string_signal
319                break;
320            }
321            // run_length_3-9 in colour 0
322            continue;
323        }
324        let s2 = bits(bitpos, 1);
325        bitpos += 1;
326        if s2 == 0 {
327            // run_length_4-7 (2 bits) + 4-bit_pixel-code
328            bitpos += 2 + 4;
329            continue;
330        }
331        let s3 = bits(bitpos, 2);
332        bitpos += 2;
333        match s3 {
334            0b00 | 0b01 => continue, // 1 or 2 pixels in colour 0
335            0b10 => {
336                bitpos += 4 + 4; // run_length_9-24 + pixel-code
337            }
338            0b11 => {
339                bitpos += 8 + 4; // run_length_25-280 + pixel-code
340            }
341            _ => {}
342        }
343    }
344    // Byte-align: if not byte-aligned, 4_stuff_bits are implied
345    let byte_len = bitpos.div_ceil(8);
346    byte_len.min(data.len())
347}
348
349/// Scan an 8-bit/pixel_code_string to find its end marker and total byte length.
350/// Table 26.
351fn scan_8bit_code_string(data: &[u8]) -> usize {
352    let mut pos: usize = 0;
353    loop {
354        if pos >= data.len() {
355            break;
356        }
357        if data[pos] != 0x00 {
358            pos += 1;
359            continue;
360        }
361        if pos + 1 >= data.len() {
362            pos += 1;
363            break;
364        }
365        let b = data[pos + 1];
366        let s1 = (b >> 7) & 1;
367        if s1 == 0 {
368            let rl = b & 0x7F;
369            if rl == 0 {
370                // end_of_string_signal
371                pos += 2;
372                break;
373            }
374            // run_length_1-127 in colour 0
375            pos += 2;
376        } else {
377            // run_length_3-127 + 8-bitpixel-code
378            pos += 3;
379        }
380    }
381    pos.min(data.len())
382}
383
384/// Parse pixel-data sub-blocks from raw bytes, bounded by the given field length.
385///
386/// Walks data_type-delimited sub-blocks: code strings are scanned to their
387/// terminator, map tables have fixed sizes, 0xF0 (end-of-line) has no data.
388fn parse_pixel_sub_blocks<'a>(
389    bytes: &'a [u8],
390    field_len: usize,
391    what: &'static str,
392) -> Result<alloc::vec::Vec<PixelDataSubBlock<'a>>> {
393    let end = field_len.min(bytes.len());
394    let field = &bytes[..end];
395    let mut blocks = alloc::vec::Vec::new();
396    let mut pos: usize = 0;
397    while pos < field.len() {
398        if pos >= field.len() {
399            break;
400        }
401        let data_type_byte = field[pos];
402        let data_type = match data_type_byte {
403            0x10 => DataType::CodeString2Bit,
404            0x11 => DataType::CodeString4Bit,
405            0x12 => DataType::CodeString8Bit,
406            0x20 => DataType::MapTable2To4,
407            0x21 => DataType::MapTable2To8,
408            0x22 => DataType::MapTable4To8,
409            0xF0 => DataType::EndOfLine,
410            v => DataType::Reserved(v),
411        };
412        pos += 1; // consume data_type byte
413
414        let data_len = match data_type_byte {
415            0x20 => MAP_TABLE_2TO4_BYTES,
416            0x21 => MAP_TABLE_2TO8_BYTES,
417            0x22 => MAP_TABLE_4TO8_BYTES,
418            0xF0 => 0,
419            0x10 => scan_2bit_code_string(&field[pos..]),
420            0x11 => scan_4bit_code_string(&field[pos..]),
421            0x12 => scan_8bit_code_string(&field[pos..]),
422            _ => {
423                // Unknown: consume rest of field
424                field.len() - pos
425            }
426        };
427
428        let block_end = (pos + data_len).min(field.len());
429        let block_data = &field[pos..block_end];
430
431        blocks.push(PixelDataSubBlock {
432            data_type,
433            data: block_data,
434        });
435        pos = block_end;
436    }
437    // Validate field length was exactly consumed
438    if pos != end {
439        return Err(Error::BufferTooShort {
440            need: end,
441            have: pos,
442            what,
443        });
444    }
445    Ok(blocks)
446}
447
448impl<'a> Parse<'a> for ObjectDataSegment<'a> {
449    type Error = Error;
450
451    fn parse(bytes: &'a [u8]) -> Result<Self> {
452        if bytes.len() < HEADER_LEN + FIXED_LEN {
453            return Err(Error::BufferTooShort {
454                need: HEADER_LEN + FIXED_LEN,
455                have: bytes.len(),
456                what: "object_data_segment",
457            });
458        }
459        if bytes[1] != SEGMENT_TYPE {
460            return Err(Error::UnknownSegmentType(bytes[1]));
461        }
462        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
463        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
464        let total = HEADER_LEN + segment_length;
465        if bytes.len() < total {
466            return Err(Error::BufferTooShort {
467                need: total,
468                have: bytes.len(),
469                what: "object_data_segment data",
470            });
471        }
472        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
473        if body.len() < FIXED_LEN {
474            return Err(Error::BufferTooShort {
475                need: FIXED_LEN,
476                have: body.len(),
477                what: "object_data_segment body",
478            });
479        }
480        let object_id = u16::from_be_bytes([body[0], body[1]]);
481        let object_version_number = body[2] >> 4;
482        let coding_method_bits = (body[2] >> 2) & 0x03;
483        let non_modifying_colour_flag = (body[2] & 0x02) != 0;
484        let reserved = body[2] & 0x01;
485        // reserved bit tolerated per §7.2.0.2 forward compatibility
486        let _ = reserved;
487
488        let object_coding_method = match coding_method_bits {
489            0x00 => ObjectCodingMethod::Pixels,
490            0x01 => ObjectCodingMethod::Characters,
491            0x02 => ObjectCodingMethod::ProgressivePixels,
492            v => ObjectCodingMethod::Reserved(v),
493        };
494
495        let payload_data = &body[FIXED_LEN..];
496
497        let payload = match coding_method_bits {
498            0x00 => {
499                if payload_data.len() < INTERLACE_LEN_LEN {
500                    return Err(Error::BufferTooShort {
501                        need: INTERLACE_LEN_LEN,
502                        have: payload_data.len(),
503                        what: "object_data interlace lengths",
504                    });
505                }
506                let top_len = u16::from_be_bytes([payload_data[0], payload_data[1]]) as usize;
507                let bottom_len = u16::from_be_bytes([payload_data[2], payload_data[3]]) as usize;
508
509                let top_start = INTERLACE_LEN_LEN;
510                let top_end = top_start + top_len;
511                let bottom_start = top_end;
512                let bottom_end = bottom_start + bottom_len;
513
514                if payload_data.len() < bottom_end {
515                    return Err(Error::BufferTooShort {
516                        need: bottom_end,
517                        have: payload_data.len(),
518                        what: "object_data pixel sub-blocks",
519                    });
520                }
521
522                // stuffing_length = segment_length - 7 - top_len - bottom_len
523                // computed as: segment_length - (HEADER_LEN + FIXED_LEN + INTERLACE_LEN_LEN + top_len + bottom_len - HEADER_LEN - FIXED_LEN)?
524                // simpler: stuffing = segment_length - 7 - top_len - bottom_len
525                // 7 = 3 (fixed) + 4 (interlace lens)
526                let stuffing_length = segment_length
527                    .wrapping_sub(7)
528                    .wrapping_sub(top_len)
529                    .wrapping_sub(bottom_len);
530
531                let mut stuffing_byte = None;
532                if stuffing_length == 1 {
533                    if bottom_end + 1 > payload_data.len() {
534                        return Err(Error::BufferTooShort {
535                            need: bottom_end + 1,
536                            have: payload_data.len(),
537                            what: "stuffing byte",
538                        });
539                    }
540                    if payload_data[bottom_end] != 0x00 {
541                        return Err(Error::BadStuffingByte(payload_data[bottom_end]));
542                    }
543                    stuffing_byte = Some(payload_data[bottom_end]);
544                    let _ = stuffing_byte;
545                    // Use the actual field length check
546                }
547
548                let top_sub_blocks = parse_pixel_sub_blocks(
549                    &payload_data[top_start..top_end],
550                    top_len,
551                    "top pixel sub-blocks",
552                )?;
553                let bottom_sub_blocks = if bottom_len > 0 {
554                    parse_pixel_sub_blocks(
555                        &payload_data[bottom_start..bottom_end],
556                        bottom_len,
557                        "bottom pixel sub-blocks",
558                    )?
559                } else {
560                    alloc::vec::Vec::new()
561                };
562
563                ObjectDataPayload::InterlacedPixels(InterlacedPixelsData {
564                    top_sub_blocks,
565                    bottom_sub_blocks,
566                    stuffing_byte,
567                })
568            }
569            0x01 => {
570                if payload_data.is_empty() {
571                    return Err(Error::BufferTooShort {
572                        need: 1,
573                        have: 0,
574                        what: "character count",
575                    });
576                }
577                let number_of_codes = payload_data[0] as usize;
578                let codes_len = 1 + number_of_codes * 2;
579                if payload_data.len() < codes_len {
580                    return Err(Error::BufferTooShort {
581                        need: codes_len,
582                        have: payload_data.len(),
583                        what: "character codes",
584                    });
585                }
586                let mut codes = alloc::vec::Vec::with_capacity(number_of_codes);
587                for i in 0..number_of_codes {
588                    let ci = 1 + i * 2;
589                    codes.push(u16::from_be_bytes([payload_data[ci], payload_data[ci + 1]]));
590                }
591                ObjectDataPayload::Characters {
592                    number_of_codes: number_of_codes as u8,
593                    character_codes: codes,
594                }
595            }
596            0x02 => {
597                if payload_data.len() < PROGRESSIVE_HEADER_LEN {
598                    return Err(Error::BufferTooShort {
599                        need: PROGRESSIVE_HEADER_LEN,
600                        have: payload_data.len(),
601                        what: "progressive pixel block",
602                    });
603                }
604                let bitmap_width = u16::from_be_bytes([payload_data[0], payload_data[1]]);
605                let bitmap_height = u16::from_be_bytes([payload_data[2], payload_data[3]]);
606                let compressed_len =
607                    u16::from_be_bytes([payload_data[4], payload_data[5]]) as usize;
608                if payload_data.len() < PROGRESSIVE_HEADER_LEN + compressed_len {
609                    return Err(Error::BufferTooShort {
610                        need: PROGRESSIVE_HEADER_LEN + compressed_len,
611                        have: payload_data.len(),
612                        what: "compressed bitmap data",
613                    });
614                }
615                ObjectDataPayload::ProgressivePixels(ProgressivePixelBlock {
616                    bitmap_width,
617                    bitmap_height,
618                    compressed_data: &payload_data
619                        [PROGRESSIVE_HEADER_LEN..PROGRESSIVE_HEADER_LEN + compressed_len],
620                })
621            }
622            _ => ObjectDataPayload::Reserved {
623                coding_method: coding_method_bits,
624                data: payload_data,
625            },
626        };
627
628        Ok(ObjectDataSegment {
629            page_id,
630            object_id,
631            object_version_number,
632            object_coding_method,
633            non_modifying_colour_flag,
634            payload,
635        })
636    }
637}
638
639impl Serialize for ObjectDataSegment<'_> {
640    type Error = Error;
641
642    fn serialized_len(&self) -> usize {
643        let body_len = FIXED_LEN
644            + match &self.payload {
645                ObjectDataPayload::InterlacedPixels(ip) => {
646                    let top_len: usize = ip.top_sub_blocks.iter().map(|s| s.serialized_len()).sum();
647                    let bottom_len: usize = ip
648                        .bottom_sub_blocks
649                        .iter()
650                        .map(|s| s.serialized_len())
651                        .sum();
652                    INTERLACE_LEN_LEN
653                        + top_len
654                        + bottom_len
655                        + if ip.stuffing_byte.is_some() { 1 } else { 0 }
656                }
657                ObjectDataPayload::Characters {
658                    character_codes, ..
659                } => 1 + character_codes.len() * 2,
660                ObjectDataPayload::ProgressivePixels(pp) => {
661                    PROGRESSIVE_HEADER_LEN + pp.compressed_data.len()
662                }
663                ObjectDataPayload::Reserved { data, .. } => data.len(),
664            };
665        HEADER_LEN + body_len
666    }
667
668    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
669        let len = self.serialized_len();
670        if buf.len() < len {
671            return Err(Error::BufferTooShort {
672                need: len,
673                have: buf.len(),
674                what: "object_data_segment serialize",
675            });
676        }
677        buf[0] = 0x0F;
678        buf[1] = SEGMENT_TYPE;
679        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
680        let seg_len = (len - HEADER_LEN) as u16;
681        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
682
683        buf[6..8].copy_from_slice(&self.object_id.to_be_bytes());
684        buf[8] = (self.object_version_number << 4)
685            | (self.object_coding_method.to_bits() << 2)
686            | (u8::from(self.non_modifying_colour_flag) << 1);
687
688        let mut off = HEADER_LEN + FIXED_LEN;
689        match &self.payload {
690            ObjectDataPayload::InterlacedPixels(ip) => {
691                let top_len: u16 = ip
692                    .top_sub_blocks
693                    .iter()
694                    .map(|s| s.serialized_len() as u16)
695                    .sum();
696                let bottom_len: u16 = ip
697                    .bottom_sub_blocks
698                    .iter()
699                    .map(|s| s.serialized_len() as u16)
700                    .sum();
701                buf[off..off + 2].copy_from_slice(&top_len.to_be_bytes());
702                buf[off + 2..off + 4].copy_from_slice(&bottom_len.to_be_bytes());
703                off += INTERLACE_LEN_LEN;
704                for sub in &ip.top_sub_blocks {
705                    sub.serialize_into(&mut buf[off..]);
706                    off += sub.serialized_len();
707                }
708                for sub in &ip.bottom_sub_blocks {
709                    sub.serialize_into(&mut buf[off..]);
710                    off += sub.serialized_len();
711                }
712                if ip.stuffing_byte.is_some() {
713                    buf[off] = 0x00;
714                    off += 1;
715                }
716            }
717            ObjectDataPayload::Characters {
718                number_of_codes,
719                character_codes,
720            } => {
721                buf[off] = *number_of_codes;
722                off += 1;
723                for code in character_codes {
724                    buf[off..off + 2].copy_from_slice(&code.to_be_bytes());
725                    off += 2;
726                }
727            }
728            ObjectDataPayload::ProgressivePixels(pp) => {
729                buf[off..off + 2].copy_from_slice(&pp.bitmap_width.to_be_bytes());
730                buf[off + 2..off + 4].copy_from_slice(&pp.bitmap_height.to_be_bytes());
731                let clen = pp.compressed_data.len() as u16;
732                buf[off + 4..off + 6].copy_from_slice(&clen.to_be_bytes());
733                off += PROGRESSIVE_HEADER_LEN;
734                buf[off..off + pp.compressed_data.len()].copy_from_slice(pp.compressed_data);
735                off += pp.compressed_data.len();
736            }
737            ObjectDataPayload::Reserved { data, .. } => {
738                buf[off..off + data.len()].copy_from_slice(data);
739                off += data.len();
740            }
741        }
742        debug_assert_eq!(off, len);
743        Ok(len)
744    }
745}
746
747#[cfg(test)]
748mod tests {
749    use super::*;
750    use broadcast_common::{Parse, Serialize};
751
752    #[test]
753    fn round_trip_pixels_no_stuffing() {
754        let bytes = [
755            0x0F, 0x13, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x00, 0x03, 0x00, 0x00, 0xF0,
756            0x10, 0x0A,
757        ];
758        let seg = ObjectDataSegment::parse(&bytes).unwrap();
759        assert_eq!(seg.object_id, 10);
760        assert_eq!(seg.object_coding_method, ObjectCodingMethod::Pixels);
761        match &seg.payload {
762            ObjectDataPayload::InterlacedPixels(ip) => {
763                assert_eq!(ip.top_sub_blocks.len(), 2);
764                assert_eq!(ip.top_sub_blocks[0].data_type, DataType::EndOfLine);
765                assert_eq!(ip.top_sub_blocks[1].data_type, DataType::CodeString2Bit);
766            }
767            _ => panic!("expected InterlacedPixels"),
768        }
769        let out = seg.to_bytes();
770        assert_eq!(out, bytes);
771
772        // Biting test
773        let mut seg2 = seg.clone();
774        seg2.object_id = 20;
775        let out2 = seg2.to_bytes();
776        assert_ne!(out2, bytes);
777        let reparse = ObjectDataSegment::parse(&out2).unwrap();
778        assert_eq!(reparse.object_id, 20);
779    }
780
781    #[test]
782    fn round_trip_multiple_sub_blocks() {
783        // Top field: map-table(0x20) + end-of-line(0xF0) + 4-bit code string
784        let map_table: [u8; 2] = [0x00, 0x00];
785        // 4-bit code string: 5 pixels of colour 1, then end-of-string
786        // 0001 0001 0001 0001 0001 0000 000 → 5 ones, zero, then switch_1=0, next 3 bits=000 → end
787        let four_bit_data = [0x11, 0x11, 0x11, 0x00]; // 4 bytes
788        let body: alloc::vec::Vec<u8> = [
789            &[0x00u8, 0x0B, 0x00][..],
790            &[0x00u8, 0x09, 0x00, 0x00][..], // top_len=9 (1+2+1+1+4)
791            &[0x20u8][..],
792            &map_table[..],
793            &[0xF0u8][..],
794            &[0x11u8][..],
795            &four_bit_data[..],
796        ]
797        .concat();
798
799        let mut bytes = alloc::vec![0x0F, 0x13, 0x00, 0x01]; // header up to segment_length
800        let body_len = body.len() as u16;
801        bytes.extend_from_slice(&body_len.to_be_bytes());
802        bytes.extend_from_slice(&body);
803
804        let seg = ObjectDataSegment::parse(&bytes).unwrap();
805        match &seg.payload {
806            ObjectDataPayload::InterlacedPixels(ip) => {
807                assert_eq!(ip.top_sub_blocks.len(), 3, "expected 3 sub-blocks");
808                assert_eq!(ip.top_sub_blocks[0].data_type, DataType::MapTable2To4);
809                assert_eq!(ip.top_sub_blocks[0].data.len(), 2);
810                assert_eq!(ip.top_sub_blocks[1].data_type, DataType::EndOfLine);
811                assert_eq!(ip.top_sub_blocks[2].data_type, DataType::CodeString4Bit);
812                assert_eq!(ip.top_sub_blocks[2].data.len(), 4);
813            }
814            _ => panic!("expected InterlacedPixels"),
815        }
816        let out = seg.to_bytes();
817        assert_eq!(out, bytes);
818    }
819
820    #[test]
821    fn round_trip_characters() {
822        let bytes = [
823            0x0F, 0x13, 0x00, 0x01, 0x00, 0x08, 0x00, 0x0B, 0x04, 0x02, 0x00, 0x41, 0x00, 0x42,
824        ];
825        let seg = ObjectDataSegment::parse(&bytes).unwrap();
826        assert_eq!(seg.object_coding_method, ObjectCodingMethod::Characters);
827        match &seg.payload {
828            ObjectDataPayload::Characters {
829                number_of_codes,
830                character_codes,
831                ..
832            } => {
833                assert_eq!(*number_of_codes, 2);
834                assert_eq!(character_codes[0], 0x0041);
835                assert_eq!(character_codes[1], 0x0042);
836            }
837            _ => panic!("expected Characters"),
838        }
839        let out = seg.to_bytes();
840        assert_eq!(out, bytes);
841
842        // Biting test
843        let mut seg2 = seg.clone();
844        if let ObjectDataPayload::Characters {
845            number_of_codes, ..
846        } = &mut seg2.payload
847        {
848            *number_of_codes = 1;
849        }
850        let out2 = seg2.to_bytes();
851        assert_ne!(out2, bytes);
852    }
853
854    #[test]
855    fn round_trip_progressive() {
856        let bytes = [
857            0x0F, 0x13, 0x00, 0x01, 0x00, 0x0F, 0x00, 0x0C, 0x08, 0x00, 0x64, 0x00, 0x32, 0x00,
858            0x06, 0x78, 0xDA, 0x63, 0x60, 0x60, 0x60,
859        ];
860        let seg = ObjectDataSegment::parse(&bytes).unwrap();
861        assert_eq!(
862            seg.object_coding_method,
863            ObjectCodingMethod::ProgressivePixels
864        );
865        match &seg.payload {
866            ObjectDataPayload::ProgressivePixels(pp) => {
867                assert_eq!(pp.bitmap_width, 100);
868                assert_eq!(pp.bitmap_height, 50);
869                assert_eq!(pp.compressed_data.len(), 6);
870            }
871            _ => panic!("expected ProgressivePixels"),
872        }
873        let out = seg.to_bytes();
874        assert_eq!(out, bytes);
875
876        // Biting test
877        let mut seg2 = seg.clone();
878        if let ObjectDataPayload::ProgressivePixels(pp) = &mut seg2.payload {
879            pp.bitmap_width = 200;
880        }
881        let out2 = seg2.to_bytes();
882        assert_ne!(out2, bytes);
883        let reparse = ObjectDataSegment::parse(&out2).unwrap();
884        match &reparse.payload {
885            ObjectDataPayload::ProgressivePixels(pp) => {
886                assert_eq!(pp.bitmap_width, 200);
887            }
888            _ => panic!(),
889        }
890    }
891}