Skip to main content

dvb_si/descriptors/extension/
image_icon.rs

1//! Image Icon Descriptor — ETSI EN 300 468 §6.4.7 (tag_extension 0x00).
2use super::*;
3
4impl<'a> ExtensionBodyDef<'a> for ImageIcon<'a> {
5    const TAG_EXTENSION: u8 = 0x00;
6    const NAME: &'static str = "IMAGE_ICON";
7}
8
9/// image_icon body (Table 145). One descriptor instance; a full icon
10/// spans `descriptor_number` 0..=`last_descriptor_number`, reassembled
11/// by the caller.
12///
13/// Icon transport mode: Table 146
14/// (`0` = data bytes, `1` = URL, `2`-`3` = reserved).
15/// Coordinate system: Table 147
16/// (`0` = 720×576, `1` = 1280×720, `2` = 1920×1080,
17/// `3`-`6` = reserved, `7` = user defined).
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
21pub struct ImageIcon<'a> {
22    /// `descriptor_number` (4 bits) — `[7:4]` of byte 0.
23    pub descriptor_number: u8,
24    /// `last_descriptor_number` (4 bits) — `[3:0]` of byte 0.
25    pub last_descriptor_number: u8,
26    /// `icon_id` (3 bits) — `[2:0]` of byte 1.
27    pub icon_id: u8,
28    /// First-segment metadata vs. continuation payload (keyed by `descriptor_number == 0`).
29    pub body: ImageIconBody<'a>,
30}
31
32/// First-segment metadata vs. continuation payload (keyed by `descriptor_number == 0`).
33#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
36pub enum ImageIconBody<'a> {
37    /// `descriptor_number == 0`: icon metadata + first payload chunk.
38    First(ImageIconFirst<'a>),
39    /// `descriptor_number != 0`: a continuation icon_data chunk (length-prefixed).
40    Continuation {
41        /// Length-delimited icon_data_byte run.
42        icon_data: &'a [u8],
43    },
44}
45
46/// First-segment metadata (`descriptor_number == 0`).
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
50pub struct ImageIconFirst<'a> {
51    /// `icon_transport_mode` (2 bits) — Table 146.
52    pub icon_transport_mode: u8,
53    /// `position` is `Some` iff `position_flag == 1`.
54    pub position: Option<IconPosition>,
55    /// `icon_type_char` text run (length-delimited in the wire).
56    pub icon_type: crate::text::DvbText<'a>,
57    /// Transport-mode-dependent payload (Table 146):
58    /// `0` → icon data bytes; `1` → URL text; `2`-`3` → empty.
59    pub payload: IconLocation<'a>,
60}
61
62/// Transport-mode-dependent payload for the first segment of an image_icon
63/// descriptor (Table 146, §6.4.8).
64#[derive(Debug, Clone, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize))]
66#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
67#[non_exhaustive]
68pub enum IconLocation<'a> {
69    /// `icon_transport_mode` 0: raw icon data bytes (PNG, JPEG, etc.).
70    Data(&'a [u8]),
71    /// `icon_transport_mode` 1: `url_char` text pointing to the icon resource.
72    Url(crate::text::DvbText<'a>),
73    /// `icon_transport_mode` 2–3: reserved/empty.
74    None,
75}
76
77/// Position data (present iff `position_flag == 1`).
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize))]
80pub struct IconPosition {
81    /// `coordinate_system` (3 bits) — Table 147.
82    pub coordinate_system: u8,
83    /// `icon_horizontal_origin` (12 bits).
84    pub icon_horizontal_origin: u16,
85    /// `icon_vertical_origin` (12 bits).
86    pub icon_vertical_origin: u16,
87}
88
89impl<'a> Parse<'a> for ImageIcon<'a> {
90    type Error = crate::error::Error;
91    fn parse(sel: &'a [u8]) -> Result<Self> {
92        // Need at least byte0 (descriptor_number / last_descriptor_number)
93        // and byte1 (reserved + icon_id).
94        if sel.len() < 2 {
95            return Err(Error::BufferTooShort {
96                need: 2,
97                have: sel.len(),
98                what: "image_icon body",
99            });
100        }
101        let descriptor_number = sel[0] >> 4;
102        let last_descriptor_number = sel[0] & 0x0F;
103        let icon_id = sel[1] & 0x07;
104
105        let body = if descriptor_number == 0x00 {
106            // First segment: metadata + payload.
107            if sel.len() < 3 {
108                return Err(Error::BufferTooShort {
109                    need: 3,
110                    have: sel.len(),
111                    what: "image_icon body",
112                });
113            }
114            let packed = sel[2];
115            let icon_transport_mode = packed >> 6;
116            let position_flag = (packed >> 5) & 0x01;
117            let mut pos = 3usize;
118
119            let position = if position_flag == 1 {
120                let coordinate_system = (packed >> 2) & 0x07;
121                if sel.len() < pos + 3 {
122                    return Err(Error::BufferTooShort {
123                        need: pos + 3,
124                        have: sel.len(),
125                        what: "image_icon body",
126                    });
127                }
128                let b0 = sel[pos];
129                let b1 = sel[pos + 1];
130                let b2 = sel[pos + 2];
131                let icon_horizontal_origin = (u16::from(b0) << 4) | (u16::from(b1) >> 4);
132                let icon_vertical_origin = ((u16::from(b1) & 0x0F) << 8) | u16::from(b2);
133                pos += 3;
134                Some(IconPosition {
135                    coordinate_system,
136                    icon_horizontal_origin,
137                    icon_vertical_origin,
138                })
139            } else {
140                None
141            };
142
143            // icon_type_length + run
144            if sel.len() < pos + 1 {
145                return Err(Error::BufferTooShort {
146                    need: pos + 1,
147                    have: sel.len(),
148                    what: "image_icon body",
149                });
150            }
151            let icon_type_length = sel[pos] as usize;
152            pos += 1;
153            if sel.len() < pos + icon_type_length {
154                return Err(Error::BufferTooShort {
155                    need: pos + icon_type_length,
156                    have: sel.len(),
157                    what: "image_icon body",
158                });
159            }
160            let icon_type = crate::text::DvbText::new(&sel[pos..pos + icon_type_length]);
161            pos += icon_type_length;
162
163            // Transport-mode-dependent payload
164            let payload = match icon_transport_mode {
165                0 => {
166                    if sel.len() < pos + 1 {
167                        return Err(Error::BufferTooShort {
168                            need: pos + 1,
169                            have: sel.len(),
170                            what: "image_icon body",
171                        });
172                    }
173                    let payload_len = sel[pos] as usize;
174                    pos += 1;
175                    if sel.len() < pos + payload_len {
176                        return Err(Error::BufferTooShort {
177                            need: pos + payload_len,
178                            have: sel.len(),
179                            what: "image_icon body",
180                        });
181                    }
182                    let p = &sel[pos..pos + payload_len];
183                    pos += payload_len;
184                    IconLocation::Data(p)
185                }
186                1 => {
187                    if sel.len() < pos + 1 {
188                        return Err(Error::BufferTooShort {
189                            need: pos + 1,
190                            have: sel.len(),
191                            what: "image_icon body",
192                        });
193                    }
194                    let payload_len = sel[pos] as usize;
195                    pos += 1;
196                    if sel.len() < pos + payload_len {
197                        return Err(Error::BufferTooShort {
198                            need: pos + payload_len,
199                            have: sel.len(),
200                            what: "image_icon body",
201                        });
202                    }
203                    let t = crate::text::DvbText::new(&sel[pos..pos + payload_len]);
204                    pos += payload_len;
205                    IconLocation::Url(t)
206                }
207                _ => IconLocation::None,
208            };
209
210            if pos != sel.len() {
211                return Err(invalid("image_icon: trailing bytes"));
212            }
213
214            ImageIconBody::First(ImageIconFirst {
215                icon_transport_mode,
216                position,
217                icon_type,
218                payload,
219            })
220        } else {
221            // Continuation segment.
222            if sel.len() < 3 {
223                return Err(Error::BufferTooShort {
224                    need: 3,
225                    have: sel.len(),
226                    what: "image_icon body",
227                });
228            }
229            let icon_data_length = sel[2] as usize;
230            if sel.len() < 3 + icon_data_length {
231                return Err(Error::BufferTooShort {
232                    need: 3 + icon_data_length,
233                    have: sel.len(),
234                    what: "image_icon body",
235                });
236            }
237            let icon_data = &sel[3..3 + icon_data_length];
238            if 3 + icon_data_length != sel.len() {
239                return Err(invalid("image_icon: trailing bytes"));
240            }
241            ImageIconBody::Continuation { icon_data }
242        };
243
244        Ok(ImageIcon {
245            descriptor_number,
246            last_descriptor_number,
247            icon_id,
248            body,
249        })
250    }
251}
252
253impl Serialize for ImageIcon<'_> {
254    type Error = crate::error::Error;
255    fn serialized_len(&self) -> usize {
256        2 + match &self.body {
257            ImageIconBody::First(f) => {
258                1 // packed byte
259                    + if f.position.is_some() { 3 } else { 0 }
260                    + 1 // icon_type_length
261                    + f.icon_type.len()
262                    + match &f.payload {
263                        IconLocation::Data(d) => 1 + d.len(),
264                        IconLocation::Url(u) => 1 + u.len(),
265                        IconLocation::None => 0,
266                    }
267            }
268            ImageIconBody::Continuation { icon_data } => 1 + icon_data.len(),
269        }
270    }
271    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
272        let len = self.serialized_len();
273        if buf.len() < len {
274            return Err(Error::OutputBufferTooSmall {
275                need: len,
276                have: buf.len(),
277            });
278        }
279        // byte0: descriptor_number(4) | last_descriptor_number(4)
280        buf[0] = (self.descriptor_number << 4) | (self.last_descriptor_number & 0x0F);
281        // byte1: reserved_future_use(5)=1 | icon_id(3)
282        buf[1] = 0xF8 | (self.icon_id & 0x07);
283        let mut p = 2;
284        match &self.body {
285            ImageIconBody::First(f) => {
286                // Packed byte: icon_transport_mode(2) | position_flag(1) | ...
287                let position_flag = u8::from(f.position.is_some());
288                if let Some(pos) = &f.position {
289                    // ... | coordinate_system(3) | reserved_future_use(2)=1
290                    buf[p] = (f.icon_transport_mode << 6)
291                        | (position_flag << 5)
292                        | ((pos.coordinate_system & 0x07) << 2)
293                        | 0x03;
294                    p += 1;
295                    // 3 origin bytes: 12+12 bits packed
296                    let h = pos.icon_horizontal_origin & 0x0FFF;
297                    let v = pos.icon_vertical_origin & 0x0FFF;
298                    buf[p] = (h >> 4) as u8;
299                    buf[p + 1] = (((h & 0x0F) << 4) | ((v >> 8) & 0x0F)) as u8;
300                    buf[p + 2] = v as u8;
301                    p += 3;
302                } else {
303                    // ... | position_flag(1) | reserved_future_use(5)=1
304                    buf[p] = (f.icon_transport_mode << 6) | (position_flag << 5) | 0x1F;
305                    p += 1;
306                }
307                // icon_type_length + run
308                buf[p] = f.icon_type.len() as u8;
309                p += 1;
310                buf[p..p + f.icon_type.len()].copy_from_slice(f.icon_type.raw());
311                p += f.icon_type.len();
312                // Payload
313                match &f.payload {
314                    IconLocation::Data(d) => {
315                        buf[p] = d.len() as u8;
316                        p += 1;
317                        buf[p..p + d.len()].copy_from_slice(d);
318                    }
319                    IconLocation::Url(u) => {
320                        buf[p] = u.len() as u8;
321                        p += 1;
322                        buf[p..p + u.len()].copy_from_slice(u.raw());
323                    }
324                    IconLocation::None => {}
325                }
326            }
327            ImageIconBody::Continuation { icon_data } => {
328                buf[p] = icon_data.len() as u8;
329                p += 1;
330                buf[p..p + icon_data.len()].copy_from_slice(icon_data);
331            }
332        }
333        Ok(len)
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use crate::descriptors::extension::test_support::*;
341    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
342
343    #[test]
344    fn parse_image_icon_first_position_mode0() {
345        // descriptor_number=0, last=3, icon_id=5
346        // position_flag=1, coordinate_system=1, h_origin=100, v_origin=200
347        // icon_type=[0xDE, 0xAD], mode=0 icon_data=[0x01, 0x02, 0x03]
348        // byte0: (0<<4)|3 = 0x03
349        // byte1: icon_id=5, reserved=0 = 0x05
350        // packed: (0<<6)|(1<<5)|(1<<2) = 0x24
351        // origin: h=100=0x0064, v=200=0x00C8 → b0=0x06, b1=0x40, b2=0xC8
352        let sel = [
353            0x03, 0x05, 0x24, 0x06, 0x40, 0xC8, 0x02, 0xDE, 0xAD, 0x03, 0x01, 0x02, 0x03,
354        ];
355        let bytes = wrap(0x00, &sel);
356        let d = ExtensionDescriptor::parse(&bytes).unwrap();
357        assert_eq!(d.kind(), Some(ExtensionTag::ImageIcon));
358        match &d.body {
359            ExtensionBody::ImageIcon(b) => {
360                assert_eq!(b.descriptor_number, 0);
361                assert_eq!(b.last_descriptor_number, 3);
362                assert_eq!(b.icon_id, 5);
363                match &b.body {
364                    ImageIconBody::First(f) => {
365                        assert_eq!(f.icon_transport_mode, 0);
366                        assert!(f.position.is_some());
367                        let pos = f.position.as_ref().unwrap();
368                        assert_eq!(pos.coordinate_system, 1);
369                        assert_eq!(pos.icon_horizontal_origin, 100);
370                        assert_eq!(pos.icon_vertical_origin, 200);
371                        assert_eq!(f.icon_type.raw(), &[0xDE, 0xAD]);
372                        match &f.payload {
373                            IconLocation::Data(d) => assert_eq!(*d, &[0x01, 0x02, 0x03]),
374                            other => panic!("expected Data, got {other:?}"),
375                        }
376                    }
377                    other => panic!("expected First, got {other:?}"),
378                }
379            }
380            other => panic!("expected ImageIcon, got {other:?}"),
381        }
382        round_trip(&d);
383    }
384
385    #[test]
386    fn parse_image_icon_first_no_position_mode1() {
387        // descriptor_number=0, last=1, icon_id=7
388        // position_flag=0, mode=1 (URL), icon_type=[0xAB], url=b"http"
389        // byte0: (0<<4)|1 = 0x01; byte1: 0x07
390        // packed: (1<<6)|(0<<5) = 0x40
391        let sel = [0x01, 0x07, 0x40, 0x01, 0xAB, 0x04, b'h', b't', b't', b'p'];
392        let bytes = wrap(0x00, &sel);
393        let d = ExtensionDescriptor::parse(&bytes).unwrap();
394        match &d.body {
395            ExtensionBody::ImageIcon(b) => match &b.body {
396                ImageIconBody::First(f) => {
397                    assert_eq!(f.icon_transport_mode, 1);
398                    assert!(f.position.is_none());
399                    assert_eq!(f.icon_type.raw(), &[0xAB]);
400                    match &f.payload {
401                        IconLocation::Url(u) => assert_eq!(u.decode(), "http"),
402                        other => panic!("expected Url, got {other:?}"),
403                    }
404                }
405                other => panic!("expected First, got {other:?}"),
406            },
407            other => panic!("expected ImageIcon, got {other:?}"),
408        }
409        round_trip(&d);
410    }
411
412    #[test]
413    fn parse_image_icon_first_mode2_empty_payload() {
414        // descriptor_number=0, last=0, icon_id=0, mode=2 (reserved),
415        // position_flag=0, icon_type=0 bytes, empty payload
416        // byte0: 0x00; byte1: 0x00; packed: (2<<6) = 0x80
417        let sel = [0x00, 0x00, 0x80, 0x00];
418        let bytes = wrap(0x00, &sel);
419        let d = ExtensionDescriptor::parse(&bytes).unwrap();
420        match &d.body {
421            ExtensionBody::ImageIcon(b) => match &b.body {
422                ImageIconBody::First(f) => {
423                    assert_eq!(f.icon_transport_mode, 2);
424                    assert!(f.position.is_none());
425                    assert!(f.icon_type.is_empty());
426                    assert!(matches!(f.payload, IconLocation::None));
427                }
428                other => panic!("expected First, got {other:?}"),
429            },
430            other => panic!("expected ImageIcon, got {other:?}"),
431        }
432        round_trip(&d);
433    }
434
435    #[test]
436    fn parse_image_icon_continuation() {
437        // descriptor_number=2, last=3, icon_id=1
438        // icon_data=[0xAA, 0xBB, 0xCC, 0xDD]
439        // byte0: (2<<4)|3 = 0x23; byte1: 0x01; length=4
440        let sel = [0x23, 0x01, 0x04, 0xAA, 0xBB, 0xCC, 0xDD];
441        let bytes = wrap(0x00, &sel);
442        let d = ExtensionDescriptor::parse(&bytes).unwrap();
443        assert_eq!(d.kind(), Some(ExtensionTag::ImageIcon));
444        match &d.body {
445            ExtensionBody::ImageIcon(b) => {
446                assert_eq!(b.descriptor_number, 2);
447                assert_eq!(b.last_descriptor_number, 3);
448                assert_eq!(b.icon_id, 1);
449                match &b.body {
450                    ImageIconBody::Continuation { icon_data } => {
451                        assert_eq!(icon_data, &[0xAA, 0xBB, 0xCC, 0xDD]);
452                    }
453                    other => panic!("expected Continuation, got {other:?}"),
454                }
455            }
456            other => panic!("expected ImageIcon, got {other:?}"),
457        }
458        round_trip(&d);
459    }
460
461    #[test]
462    fn parse_image_icon_rejects_trailing_bytes() {
463        // First segment with an extra trailing byte.
464        // mode=2, one extra byte 0xFF after the complete parse.
465        let sel = [0x00, 0x00, 0x80, 0x00, 0xFF];
466        let bytes = wrap(0x00, &sel);
467        assert!(matches!(
468            ExtensionDescriptor::parse(&bytes).unwrap_err(),
469            crate::error::Error::InvalidDescriptor {
470                tag: super::TAG,
471                ..
472            }
473        ));
474    }
475
476    #[test]
477    fn parse_image_icon_rejects_truncated_continuation() {
478        // Continuation with length=5 but only 3 data bytes.
479        let sel = [0x23, 0x01, 0x05, 0xAA, 0xBB, 0xCC];
480        let bytes = wrap(0x00, &sel);
481        assert!(matches!(
482            ExtensionDescriptor::parse(&bytes).unwrap_err(),
483            crate::error::Error::BufferTooShort { .. }
484        ));
485    }
486
487    #[cfg(feature = "serde")]
488    #[test]
489    fn serde_serialize_image_icon() {
490        let d = ExtensionDescriptor {
491            tag_extension: 0x00,
492            body: ExtensionBody::ImageIcon(ImageIcon {
493                descriptor_number: 2,
494                last_descriptor_number: 3,
495                icon_id: 1,
496                body: ImageIconBody::Continuation {
497                    icon_data: &[0xAA, 0xBB],
498                },
499            }),
500        };
501        let json = serde_json::to_string(&d).unwrap();
502        assert!(json.contains("\"tag_extension\":0"));
503        assert!(json.contains("\"imageIcon\""));
504    }
505}