Skip to main content

dvb_subtitle/segments/
clut_definition.rs

1//! CLUT Definition Segment โ€” ETSI EN 300 743 ยง7.2.4, Table 16 (segment_type 0x12).
2
3use crate::error::{Error, Result};
4use broadcast_common::{Parse, Serialize};
5
6/// The CLUT_definition_segment segment_type.
7pub const SEGMENT_TYPE: u8 = 0x12;
8/// Header: 6 bytes.
9pub const HEADER_LEN: usize = 6;
10/// Fixed body: CLUT_id(1) + CLUT_version_number(4b)+reserved(4b) = 2 bytes.
11pub const FIXED_LEN: usize = 2;
12/// Entry header: CLUT_entry_id(1) + flags(1) = 2 bytes.
13pub const ENTRY_HEADER_LEN: usize = 2;
14/// Full-range colour: 4 bytes.
15pub const ENTRY_FULL_LEN: usize = 4;
16/// Reduced-range colour: 2 bytes.
17pub const ENTRY_REDUCED_LEN: usize = 2;
18
19/// A single CLUT entry.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct ClutEntry {
23    /// CLUT entry number.
24    pub clut_entry_id: u8,
25    /// Whether this loads into the 2-bit/entry CLUT.
26    pub flag_2bit: bool,
27    /// Whether this loads into the 4-bit/entry CLUT.
28    pub flag_4bit: bool,
29    /// Whether this loads into the 8-bit/entry CLUT.
30    pub flag_8bit: bool,
31    /// Reserved bits in the flags byte (bits `[4:1]`, must be preserved for round-trip).
32    pub reserved_flags: u8,
33    /// Whether full 8-bit resolution colour values follow.
34    pub full_range_flag: bool,
35    /// Y output value.
36    pub y_value: u8,
37    /// Cr output value.
38    pub cr_value: u8,
39    /// Cb output value.
40    pub cb_value: u8,
41    /// T (transparency) output value.
42    pub t_value: u8,
43}
44
45impl ClutEntry {
46    fn serialized_len(&self) -> usize {
47        ENTRY_HEADER_LEN
48            + if self.full_range_flag {
49                ENTRY_FULL_LEN
50            } else {
51                ENTRY_REDUCED_LEN
52            }
53    }
54
55    fn serialize_into(&self, buf: &mut [u8]) {
56        buf[0] = self.clut_entry_id;
57        let mut flags: u8 = self.reserved_flags;
58        if self.flag_2bit {
59            flags |= 0x80;
60        }
61        if self.flag_4bit {
62            flags |= 0x40;
63        }
64        if self.flag_8bit {
65            flags |= 0x20;
66        }
67        if self.full_range_flag {
68            flags |= 0x01;
69        }
70        buf[1] = flags;
71        if self.full_range_flag {
72            buf[2] = self.y_value;
73            buf[3] = self.cr_value;
74            buf[4] = self.cb_value;
75            buf[5] = self.t_value;
76        } else {
77            buf[2] = (self.y_value << 2) | (self.cr_value >> 2);
78            buf[3] = (self.cr_value << 6) | (self.cb_value << 2) | self.t_value;
79        }
80    }
81
82    fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
83        if bytes.len() < ENTRY_HEADER_LEN {
84            return Err(Error::BufferTooShort {
85                need: ENTRY_HEADER_LEN,
86                have: bytes.len(),
87                what: "CLUT_entry header",
88            });
89        }
90        let clut_entry_id = bytes[0];
91        let flag_2bit = (bytes[1] & 0x80) != 0;
92        let flag_4bit = (bytes[1] & 0x40) != 0;
93        let flag_8bit = (bytes[1] & 0x20) != 0;
94        let reserved_flags = bytes[1] & 0x1E;
95        let full_range_flag = (bytes[1] & 0x01) != 0;
96
97        let total = ENTRY_HEADER_LEN
98            + if full_range_flag {
99                ENTRY_FULL_LEN
100            } else {
101                ENTRY_REDUCED_LEN
102            };
103        if bytes.len() < total {
104            return Err(Error::BufferTooShort {
105                need: total,
106                have: bytes.len(),
107                what: "CLUT_entry data",
108            });
109        }
110        let (y, cr, cb, t) = if full_range_flag {
111            (bytes[2], bytes[3], bytes[4], bytes[5])
112        } else {
113            let y = bytes[2] >> 2;
114            let cr = ((bytes[2] & 0x03) << 2) | (bytes[3] >> 6);
115            let cb = (bytes[3] >> 2) & 0x0F;
116            let t = bytes[3] & 0x03;
117            (y, cr, cb, t)
118        };
119        Ok((
120            ClutEntry {
121                clut_entry_id,
122                flag_2bit,
123                flag_4bit,
124                flag_8bit,
125                reserved_flags,
126                full_range_flag,
127                y_value: y,
128                cr_value: cr,
129                cb_value: cb,
130                t_value: t,
131            },
132            total,
133        ))
134    }
135}
136
137/// CLUT Definition Segment.
138#[derive(Debug, Clone, PartialEq, Eq)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize))]
140pub struct ClutDefinitionSegment {
141    /// The page_id from the segment header.
142    pub page_id: u16,
143    /// CLUT family identifier.
144    pub clut_id: u8,
145    /// CLUT version number (modulo 16).
146    pub clut_version_number: u8,
147    /// Reserved bits in the body byte 1 (bits `[3:0]`).
148    pub reserved: u8,
149    /// CLUT entries.
150    pub entries: alloc::vec::Vec<ClutEntry>,
151    /// Trailing bytes after the last successfully-parsed entry (preserved for round-trip).
152    #[cfg_attr(feature = "serde", serde(skip))]
153    pub(crate) suffix: alloc::vec::Vec<u8>,
154}
155
156impl<'a> Parse<'a> for ClutDefinitionSegment {
157    type Error = Error;
158
159    fn parse(bytes: &'a [u8]) -> Result<Self> {
160        if bytes.len() < HEADER_LEN + FIXED_LEN {
161            return Err(Error::BufferTooShort {
162                need: HEADER_LEN + FIXED_LEN,
163                have: bytes.len(),
164                what: "CLUT_definition_segment",
165            });
166        }
167        if bytes[0] != crate::pes_data_field::SYNC_BYTE {
168            return Err(Error::BadSyncByte(bytes[0]));
169        }
170        if bytes[1] != SEGMENT_TYPE {
171            return Err(Error::UnknownSegmentType(bytes[1]));
172        }
173        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
174        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
175        let total = HEADER_LEN + segment_length;
176        if bytes.len() < total {
177            return Err(Error::BufferTooShort {
178                need: total,
179                have: bytes.len(),
180                what: "CLUT_definition_segment data",
181            });
182        }
183        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
184        if body.len() < FIXED_LEN {
185            return Err(Error::BufferTooShort {
186                need: FIXED_LEN,
187                have: body.len(),
188                what: "CLUT_definition_segment body",
189            });
190        }
191        let clut_id = body[0];
192        let clut_version_number = body[1] >> 4;
193        let reserved = body[1] & 0x0F;
194
195        let rest = &body[FIXED_LEN..];
196        let mut entries = alloc::vec::Vec::new();
197        let mut pos: usize = 0;
198        let mut suffix = alloc::vec::Vec::new();
199        while pos < rest.len() {
200            match ClutEntry::parse(&rest[pos..]) {
201                Ok((entry, entry_len)) => {
202                    entries.push(entry);
203                    pos += entry_len;
204                }
205                Err(_) => {
206                    // Truncated or malformed entry โ€” preserve remainder as raw
207                    suffix.extend_from_slice(&rest[pos..]);
208                    break;
209                }
210            }
211        }
212
213        Ok(ClutDefinitionSegment {
214            page_id,
215            clut_id,
216            clut_version_number,
217            reserved,
218            entries,
219            suffix,
220        })
221    }
222}
223
224impl Serialize for ClutDefinitionSegment {
225    type Error = Error;
226
227    fn serialized_len(&self) -> usize {
228        HEADER_LEN
229            + FIXED_LEN
230            + self
231                .entries
232                .iter()
233                .map(|e| e.serialized_len())
234                .sum::<usize>()
235            + self.suffix.len()
236    }
237
238    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
239        let len = self.serialized_len();
240        if buf.len() < len {
241            return Err(Error::BufferTooShort {
242                need: len,
243                have: buf.len(),
244                what: "CLUT_definition_segment serialize",
245            });
246        }
247        buf[0] = 0x0F;
248        buf[1] = SEGMENT_TYPE;
249        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
250        let seg_len = crate::segments::check_segment_length(len - HEADER_LEN)?;
251        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
252
253        buf[6] = self.clut_id;
254        buf[7] = (self.clut_version_number << 4) | (self.reserved & 0x0F);
255
256        let mut off = HEADER_LEN + FIXED_LEN;
257        for entry in &self.entries {
258            entry.serialize_into(&mut buf[off..]);
259            off += entry.serialized_len();
260        }
261        buf[off..off + self.suffix.len()].copy_from_slice(&self.suffix);
262        Ok(len)
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use broadcast_common::{Parse, Serialize};
270
271    #[test]
272    fn round_trip_full_range() {
273        let bytes = [
274            0x0F, 0x12, 0x00, 0x01, 0x00, 0x0E, 0x03, 0x10, 0x00, 0xA1, 0x80, 0x80, 0x80, 0x80,
275            0x01, 0x61, 0xFF, 0xFF, 0xFF, 0x00,
276        ];
277        let seg = ClutDefinitionSegment::parse(&bytes).unwrap();
278        assert_eq!(seg.clut_id, 3);
279        assert_eq!(seg.entries.len(), 2);
280        assert_eq!(seg.entries[0].clut_entry_id, 0);
281        assert!(seg.entries[0].flag_8bit);
282        assert!(seg.entries[0].full_range_flag);
283        assert_eq!(seg.entries[0].y_value, 128);
284        assert_eq!(seg.entries[1].clut_entry_id, 1);
285        assert!(seg.entries[1].flag_4bit);
286        let out = seg.to_bytes();
287        assert_eq!(out, bytes);
288
289        // Biting test
290        let mut seg2 = seg.clone();
291        seg2.clut_id = 5;
292        let out2 = seg2.to_bytes();
293        assert_ne!(out2, bytes);
294        let reparse = ClutDefinitionSegment::parse(&out2).unwrap();
295        assert_eq!(reparse.clut_id, 5);
296    }
297
298    /// DS-W2 (#1108): sync_byte wasn't checked by any typed segment parser.
299    #[test]
300    fn rejects_bad_sync_byte() {
301        let bytes = [
302            0x00, 0x12, 0x00, 0x01, 0x00, 0x0E, 0x03, 0x10, 0x00, 0xA1, 0x80, 0x80, 0x80, 0x80,
303            0x01, 0x61, 0xFF, 0xFF, 0xFF, 0x00,
304        ];
305        assert!(matches!(
306            ClutDefinitionSegment::parse(&bytes),
307            Err(Error::BadSyncByte(0x00))
308        ));
309    }
310
311    #[test]
312    fn round_trip_reduced_range() {
313        let bytes = [
314            0x0F, 0x12, 0x00, 0x01, 0x00, 0x0A, 0x03, 0x10, 0x00, 0x80, 0x00, 0x00, 0x01, 0x40,
315            0xFD, 0xFC,
316        ];
317        let seg = ClutDefinitionSegment::parse(&bytes).unwrap();
318        assert_eq!(seg.entries.len(), 2);
319        assert!(seg.entries[0].flag_2bit);
320        assert!(!seg.entries[0].full_range_flag);
321        assert_eq!(seg.entries[0].y_value, 0);
322        let out = seg.to_bytes();
323        assert_eq!(out, bytes);
324
325        // Biting test
326        let mut seg2 = seg.clone();
327        seg2.entries[0].flag_2bit = false;
328        seg2.entries[0].flag_8bit = true;
329        let out2 = seg2.to_bytes();
330        assert_ne!(out2, bytes);
331        let reparse = ClutDefinitionSegment::parse(&out2).unwrap();
332        assert!(reparse.entries[0].flag_8bit);
333    }
334
335    #[test]
336    fn tolerates_nonzero_reserved_bits() {
337        let bytes = [
338            0x0F, 0x12, 0x00, 0x01, 0x00,
339            0x06, // seg_len=6 = 2 fixed + 4 entry (2 header + 2 reduced)
340            0x03, 0x1A, // CLUT_id=3, version=1, reserved=0xA
341            0x00, 0x8A, 0x00,
342            0x00, // entry: flag_2bit=1, reserved_flags=0x0A, full=0, Y=Cr=Cb=T=0
343        ];
344        let seg = ClutDefinitionSegment::parse(&bytes).unwrap();
345        assert_eq!(seg.reserved, 0x0A);
346        assert_eq!(seg.entries.len(), 1);
347        assert_eq!(seg.entries[0].reserved_flags, 0x0A);
348        assert!(seg.entries[0].flag_2bit);
349        let out = seg.to_bytes();
350        assert_eq!(out, bytes);
351        let seg2 = ClutDefinitionSegment::parse(&out).unwrap();
352        assert_eq!(seg2.reserved, 0x0A);
353        assert_eq!(seg2.entries[0].reserved_flags, 0x0A);
354    }
355}