Skip to main content

dvb_ci/objects/
mmi_display.rs

1//! Low-Level / Display / Keypad / Subtitle / Download MMI objects —
2//! ETSI EN 50221 §8.6.2-§8.6.4, Tables 34-45 (PDF pp. 36-45).
3//!
4//! - `display_control` (`9F 88 01`, Table 34) — cmd + optional MMI_mode.
5//! - `display_reply` (`9F 88 02`, Table 35) — branch-dependent reply body.
6//! - `keypad_control` (`9F 88 05`, Table 36) — cmd + optional key_code list.
7//! - `keypress` (`9F 88 06`, Table 37) — one key_code.
8//! - `subtitle_segment` (`9F 88 0E` last / `9F 88 0F` more, Table 38).
9//! - `display_message` (`9F 88 10`, Table 39) — display_message_id.
10//! - `scene_end_mark` (`9F 88 11`, Table 40) — flags + scene_tag.
11//! - `scene_done_message` (`9F 88 12`, Table 41) — flags + scene_tag.
12//! - `scene_control` (`9F 88 13`, Table 42) — flags + scene_tag.
13//! - `subtitle_download` (`9F 88 14` last / `9F 88 15` more, Table 43).
14//! - `flush_download` (`9F 88 16`, Table 44) — header only.
15//! - `download_reply` (`9F 88 17`, Table 45) — object_id + download_reply_id.
16
17use crate::error::{Error, Result};
18use crate::tag::{self, ApduTag};
19use crate::traits::ApduDef;
20use alloc::vec::Vec;
21use dvb_common::{Parse, Serialize};
22
23/// `display_control_cmd` values (Table 34, p. 37).
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[non_exhaustive]
27pub enum DisplayControlCmd {
28    /// `01` — enter the MMI mode indicated by the MMI_mode byte.
29    SetMmiMode,
30    /// `02` — request the display character-table list.
31    GetDisplayCharacterTableList,
32    /// `03` — request the input character-table list.
33    GetInputCharacterTableList,
34    /// `04` — request the overlay-graphics profile.
35    GetOverlayGraphicsCharacteristics,
36    /// `05` — request the full-screen-graphics profile.
37    GetFullScreenGraphicsCharacteristics,
38    /// Any other value (reserved).
39    Reserved(u8),
40}
41
42impl DisplayControlCmd {
43    /// Decode a `display_control_cmd` byte.
44    #[must_use]
45    pub fn from_u8(v: u8) -> Self {
46        match v {
47            0x01 => Self::SetMmiMode,
48            0x02 => Self::GetDisplayCharacterTableList,
49            0x03 => Self::GetInputCharacterTableList,
50            0x04 => Self::GetOverlayGraphicsCharacteristics,
51            0x05 => Self::GetFullScreenGraphicsCharacteristics,
52            other => Self::Reserved(other),
53        }
54    }
55    /// Wire byte.
56    #[must_use]
57    pub const fn to_u8(self) -> u8 {
58        match self {
59            Self::SetMmiMode => 0x01,
60            Self::GetDisplayCharacterTableList => 0x02,
61            Self::GetInputCharacterTableList => 0x03,
62            Self::GetOverlayGraphicsCharacteristics => 0x04,
63            Self::GetFullScreenGraphicsCharacteristics => 0x05,
64            Self::Reserved(v) => v,
65        }
66    }
67    /// Spec token, or `"reserved"`.
68    #[must_use]
69    pub fn name(&self) -> &'static str {
70        match self {
71            Self::SetMmiMode => "set_mmi_mode",
72            Self::GetDisplayCharacterTableList => "get_display_character_table_list",
73            Self::GetInputCharacterTableList => "get_input_character_table_list",
74            Self::GetOverlayGraphicsCharacteristics => "get_overlay_graphics_characteristics",
75            Self::GetFullScreenGraphicsCharacteristics => {
76                "get_full-screen_graphics_characteristics"
77            }
78            Self::Reserved(_) => "reserved",
79        }
80    }
81}
82dvb_common::impl_spec_display!(DisplayControlCmd, Reserved);
83
84/// `mmi_mode` values (Table 34, p. 37).
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87#[non_exhaustive]
88pub enum MmiMode {
89    /// `01` — open a high-level MMI session.
90    HighLevel,
91    /// `02` — open a low-level overlay-graphics session.
92    LowLevelOverlayGraphics,
93    /// `03` — open a low-level full-screen-graphics session.
94    LowLevelFullScreenGraphics,
95    /// Any other value (reserved).
96    Reserved(u8),
97}
98
99impl MmiMode {
100    /// Decode an `mmi_mode` byte.
101    #[must_use]
102    pub fn from_u8(v: u8) -> Self {
103        match v {
104            0x01 => Self::HighLevel,
105            0x02 => Self::LowLevelOverlayGraphics,
106            0x03 => Self::LowLevelFullScreenGraphics,
107            other => Self::Reserved(other),
108        }
109    }
110    /// Wire byte.
111    #[must_use]
112    pub const fn to_u8(self) -> u8 {
113        match self {
114            Self::HighLevel => 0x01,
115            Self::LowLevelOverlayGraphics => 0x02,
116            Self::LowLevelFullScreenGraphics => 0x03,
117            Self::Reserved(v) => v,
118        }
119    }
120    /// Spec token, or `"reserved"`.
121    #[must_use]
122    pub fn name(&self) -> &'static str {
123        match self {
124            Self::HighLevel => "high_level",
125            Self::LowLevelOverlayGraphics => "low_level_overlay_graphics",
126            Self::LowLevelFullScreenGraphics => "low_level_full_screen_graphics",
127            Self::Reserved(_) => "reserved",
128        }
129    }
130}
131dvb_common::impl_spec_display!(MmiMode, Reserved);
132
133/// `display_control()` object (Table 34).
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize))]
136pub struct DisplayControl {
137    /// `display_control_cmd`.
138    pub cmd: DisplayControlCmd,
139    /// `MMI_mode` — present only when `cmd == set_mmi_mode`.
140    pub mmi_mode: Option<MmiMode>,
141}
142
143impl<'a> Parse<'a> for DisplayControl {
144    type Error = Error;
145    fn parse(bytes: &'a [u8]) -> Result<Self> {
146        let body = super::parse_apdu_header(bytes, tag::DISPLAY_CONTROL, "display_control")?;
147        let cmd_byte = *body.first().ok_or(Error::BufferTooShort {
148            need: 1,
149            have: 0,
150            what: "display_control cmd",
151        })?;
152        let cmd = DisplayControlCmd::from_u8(cmd_byte);
153        let mmi_mode = if cmd == DisplayControlCmd::SetMmiMode {
154            if body.len() < 2 {
155                return Err(Error::BufferTooShort {
156                    need: 2,
157                    have: body.len(),
158                    what: "display_control mmi_mode",
159                });
160            }
161            Some(MmiMode::from_u8(body[1]))
162        } else {
163            None
164        };
165        Ok(Self { cmd, mmi_mode })
166    }
167}
168
169impl Serialize for DisplayControl {
170    type Error = Error;
171    fn serialized_len(&self) -> usize {
172        super::apdu_len(1 + usize::from(self.mmi_mode.is_some()))
173    }
174    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
175        let body_len = 1 + usize::from(self.mmi_mode.is_some());
176        let mut pos = super::write_apdu_header(tag::DISPLAY_CONTROL, body_len, buf)?;
177        buf[pos] = self.cmd.to_u8();
178        pos += 1;
179        if let Some(m) = self.mmi_mode {
180            buf[pos] = m.to_u8();
181            pos += 1;
182        }
183        Ok(pos)
184    }
185}
186
187impl<'a> ApduDef<'a> for DisplayControl {
188    const TAG: ApduTag = tag::DISPLAY_CONTROL;
189    const NAME: &'static str = "DISPLAY_CONTROL";
190}
191
192/// `display_reply_id` values (Table 35, p. 38).
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize))]
195#[non_exhaustive]
196pub enum DisplayReplyId {
197    /// `01` — acknowledge of the selected MMI mode.
198    MmiModeAck,
199    /// `02` — list of display character tables.
200    ListDisplayCharacterTables,
201    /// `03` — list of input character tables.
202    ListInputCharacterTables,
203    /// `04` — overlay-graphics characteristics.
204    ListGraphicOverlayCharacteristics,
205    /// `05` — full-screen-graphics characteristics.
206    ListFullScreenGraphicCharacteristics,
207    /// `F0` — unknown display_control_cmd.
208    UnknownDisplayControlCmd,
209    /// `F1` — unknown mmi_mode.
210    UnknownMmiMode,
211    /// `F2` — unknown character table.
212    UnknownCharacterTable,
213    /// Any other value (reserved).
214    Reserved(u8),
215}
216
217impl DisplayReplyId {
218    /// Decode a `display_reply_id` byte.
219    #[must_use]
220    pub fn from_u8(v: u8) -> Self {
221        match v {
222            0x01 => Self::MmiModeAck,
223            0x02 => Self::ListDisplayCharacterTables,
224            0x03 => Self::ListInputCharacterTables,
225            0x04 => Self::ListGraphicOverlayCharacteristics,
226            0x05 => Self::ListFullScreenGraphicCharacteristics,
227            0xF0 => Self::UnknownDisplayControlCmd,
228            0xF1 => Self::UnknownMmiMode,
229            0xF2 => Self::UnknownCharacterTable,
230            other => Self::Reserved(other),
231        }
232    }
233    /// Wire byte.
234    #[must_use]
235    pub const fn to_u8(self) -> u8 {
236        match self {
237            Self::MmiModeAck => 0x01,
238            Self::ListDisplayCharacterTables => 0x02,
239            Self::ListInputCharacterTables => 0x03,
240            Self::ListGraphicOverlayCharacteristics => 0x04,
241            Self::ListFullScreenGraphicCharacteristics => 0x05,
242            Self::UnknownDisplayControlCmd => 0xF0,
243            Self::UnknownMmiMode => 0xF1,
244            Self::UnknownCharacterTable => 0xF2,
245            Self::Reserved(v) => v,
246        }
247    }
248    /// Spec token, or `"reserved"`.
249    #[must_use]
250    pub fn name(&self) -> &'static str {
251        match self {
252            Self::MmiModeAck => "mmi_mode_ack",
253            Self::ListDisplayCharacterTables => "list_display_character_tables",
254            Self::ListInputCharacterTables => "list_input_character_tables",
255            Self::ListGraphicOverlayCharacteristics => "list_graphic_overlay_characteristics",
256            Self::ListFullScreenGraphicCharacteristics => {
257                "list_full_screen_graphic_characteristics"
258            }
259            Self::UnknownDisplayControlCmd => "unknown display_control_cmd",
260            Self::UnknownMmiMode => "unknown_mmi_mode",
261            Self::UnknownCharacterTable => "unknown_character_table",
262            Self::Reserved(_) => "reserved",
263        }
264    }
265}
266dvb_common::impl_spec_display!(DisplayReplyId, Reserved);
267
268/// One pixel-depth entry in a graphics-characteristics display reply (Table 35).
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270#[cfg_attr(feature = "serde", derive(serde::Serialize))]
271pub struct PixelDepth {
272    /// 3-bit `display_depth` (coded as region_level_of_compatibility in `[9]`).
273    pub display_depth: u8,
274    /// 3-bit `pixels_per_byte` (0 = 8-bit-deep display special case).
275    pub pixels_per_byte: u8,
276    /// 8-bit `region_overhead` (×16 = pixel reduction per added region).
277    pub region_overhead: u8,
278}
279
280/// The graphics-characteristics profile body of a [`DisplayReply`] (Table 35,
281/// for `list_graphic_overlay_characteristics` / `list_full_screen_…`).
282#[derive(Debug, Clone, PartialEq, Eq)]
283#[cfg_attr(feature = "serde", derive(serde::Serialize))]
284pub struct GraphicsCharacteristics {
285    /// 16-bit `display_horizontal_size`.
286    pub display_horizontal_size: u16,
287    /// 16-bit `display_vertical_size`.
288    pub display_vertical_size: u16,
289    /// 4-bit `aspect_ratio_information` (ISO/IEC 13818-2 coding).
290    pub aspect_ratio_information: u8,
291    /// 3-bit `graphics_relation_to_video`.
292    pub graphics_relation_to_video: u8,
293    /// `multiple_depths` flag.
294    pub multiple_depths: bool,
295    /// 12-bit `display_bytes` (×256 = display memory bytes).
296    pub display_bytes: u16,
297    /// 8-bit `composition_buffer_bytes` (×256).
298    pub composition_buffer_bytes: u8,
299    /// 8-bit `object_cache_bytes` (×4096).
300    pub object_cache_bytes: u8,
301    /// The per-pixel-depth entries (`number_pixel_depths` = `depths.len()`).
302    pub depths: Vec<PixelDepth>,
303}
304
305// Fixed part of the graphics body: h(2)+v(2)+packed(1)+display/comp/obj/n(4).
306const GFX_FIXED: usize = 9;
307
308/// The branch-dependent body of a [`DisplayReply`] (Table 35).
309#[derive(Debug, Clone, PartialEq, Eq)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize))]
311#[non_exhaustive]
312pub enum DisplayReplyBody {
313    /// `list_graphic_overlay_characteristics` / `list_full_screen_…`.
314    Graphics(GraphicsCharacteristics),
315    /// `list_display_character_tables` / `list_input_character_tables` — the
316    /// `character_table_byte` list.
317    CharacterTables(Vec<u8>),
318    /// `mmi_mode_ack` — the acknowledged mode.
319    MmiModeAck(MmiMode),
320    /// No branch body (`unknown_*` replies and reserved ids).
321    None,
322}
323
324/// `display_reply()` object (Table 35).
325#[derive(Debug, Clone, PartialEq, Eq)]
326#[cfg_attr(feature = "serde", derive(serde::Serialize))]
327pub struct DisplayReply {
328    /// `display_reply_id`.
329    pub reply_id: DisplayReplyId,
330    /// The branch-dependent body keyed by `reply_id`.
331    pub body: DisplayReplyBody,
332}
333
334impl DisplayReply {
335    fn parse_graphics(b: &[u8]) -> Result<GraphicsCharacteristics> {
336        if b.len() < GFX_FIXED {
337            return Err(Error::BufferTooShort {
338                need: GFX_FIXED,
339                have: b.len(),
340                what: "display_reply graphics",
341            });
342        }
343        let display_horizontal_size = u16::from_be_bytes([b[0], b[1]]);
344        let display_vertical_size = u16::from_be_bytes([b[2], b[3]]);
345        let aspect_ratio_information = (b[4] >> 4) & 0x0F;
346        let graphics_relation_to_video = (b[4] >> 1) & 0x07;
347        let multiple_depths = (b[4] & 0x01) != 0;
348        // display_bytes(12) | composition_buffer_bytes(8) | object_cache_bytes(8)
349        // | number_pixel_depths(4), packed across b[5..9].
350        let display_bytes = (((b[5] as u16) << 4) | ((b[6] >> 4) as u16)) & 0x0FFF;
351        let composition_buffer_bytes = ((b[6] & 0x0F) << 4) | (b[7] >> 4);
352        let object_cache_bytes = ((b[7] & 0x0F) << 4) | (b[8] >> 4);
353        let number_pixel_depths = (b[8] & 0x0F) as usize;
354        let mut depths = Vec::with_capacity(number_pixel_depths);
355        let mut pos = GFX_FIXED;
356        for _ in 0..number_pixel_depths {
357            if pos + 2 > b.len() {
358                return Err(Error::BufferTooShort {
359                    need: pos + 2,
360                    have: b.len(),
361                    what: "display_reply pixel_depth",
362                });
363            }
364            depths.push(PixelDepth {
365                display_depth: (b[pos] >> 5) & 0x07,
366                pixels_per_byte: (b[pos] >> 2) & 0x07,
367                region_overhead: b[pos + 1],
368            });
369            pos += 2;
370        }
371        Ok(GraphicsCharacteristics {
372            display_horizontal_size,
373            display_vertical_size,
374            aspect_ratio_information,
375            graphics_relation_to_video,
376            multiple_depths,
377            display_bytes,
378            composition_buffer_bytes,
379            object_cache_bytes,
380            depths,
381        })
382    }
383
384    fn write_graphics(g: &GraphicsCharacteristics, buf: &mut [u8]) -> usize {
385        buf[0..2].copy_from_slice(&g.display_horizontal_size.to_be_bytes());
386        buf[2..4].copy_from_slice(&g.display_vertical_size.to_be_bytes());
387        buf[4] = ((g.aspect_ratio_information & 0x0F) << 4)
388            | ((g.graphics_relation_to_video & 0x07) << 1)
389            | u8::from(g.multiple_depths);
390        let n = g.depths.len() as u8 & 0x0F;
391        buf[5] = (g.display_bytes >> 4) as u8;
392        buf[6] = (((g.display_bytes & 0x0F) as u8) << 4) | (g.composition_buffer_bytes >> 4);
393        buf[7] = ((g.composition_buffer_bytes & 0x0F) << 4) | (g.object_cache_bytes >> 4);
394        buf[8] = ((g.object_cache_bytes & 0x0F) << 4) | n;
395        let mut pos = GFX_FIXED;
396        for d in &g.depths {
397            buf[pos] = ((d.display_depth & 0x07) << 5) | ((d.pixels_per_byte & 0x07) << 2);
398            buf[pos + 1] = d.region_overhead;
399            pos += 2;
400        }
401        pos
402    }
403
404    fn body_len(&self) -> usize {
405        1 + match &self.body {
406            DisplayReplyBody::Graphics(g) => GFX_FIXED + g.depths.len() * 2,
407            DisplayReplyBody::CharacterTables(t) => t.len(),
408            DisplayReplyBody::MmiModeAck(_) => 1,
409            DisplayReplyBody::None => 0,
410        }
411    }
412}
413
414impl<'a> Parse<'a> for DisplayReply {
415    type Error = Error;
416    fn parse(bytes: &'a [u8]) -> Result<Self> {
417        let body = super::parse_apdu_header(bytes, tag::DISPLAY_REPLY, "display_reply")?;
418        let id_byte = *body.first().ok_or(Error::BufferTooShort {
419            need: 1,
420            have: 0,
421            what: "display_reply id",
422        })?;
423        let reply_id = DisplayReplyId::from_u8(id_byte);
424        let rest = &body[1..];
425        let body = match reply_id {
426            DisplayReplyId::ListGraphicOverlayCharacteristics
427            | DisplayReplyId::ListFullScreenGraphicCharacteristics => {
428                DisplayReplyBody::Graphics(Self::parse_graphics(rest)?)
429            }
430            DisplayReplyId::ListDisplayCharacterTables
431            | DisplayReplyId::ListInputCharacterTables => {
432                DisplayReplyBody::CharacterTables(rest.to_vec())
433            }
434            DisplayReplyId::MmiModeAck => {
435                let m = *rest.first().ok_or(Error::BufferTooShort {
436                    need: 1,
437                    have: 0,
438                    what: "display_reply mmi_mode_ack",
439                })?;
440                DisplayReplyBody::MmiModeAck(MmiMode::from_u8(m))
441            }
442            _ => DisplayReplyBody::None,
443        };
444        Ok(Self { reply_id, body })
445    }
446}
447
448impl Serialize for DisplayReply {
449    type Error = Error;
450    fn serialized_len(&self) -> usize {
451        super::apdu_len(self.body_len())
452    }
453    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
454        let body_len = self.body_len();
455        let mut pos = super::write_apdu_header(tag::DISPLAY_REPLY, body_len, buf)?;
456        buf[pos] = self.reply_id.to_u8();
457        pos += 1;
458        match &self.body {
459            DisplayReplyBody::Graphics(g) => {
460                pos += Self::write_graphics(g, &mut buf[pos..]);
461            }
462            DisplayReplyBody::CharacterTables(t) => {
463                buf[pos..pos + t.len()].copy_from_slice(t);
464                pos += t.len();
465            }
466            DisplayReplyBody::MmiModeAck(m) => {
467                buf[pos] = m.to_u8();
468                pos += 1;
469            }
470            DisplayReplyBody::None => {}
471        }
472        Ok(pos)
473    }
474}
475
476impl<'a> ApduDef<'a> for DisplayReply {
477    const TAG: ApduTag = tag::DISPLAY_REPLY;
478    const NAME: &'static str = "DISPLAY_REPLY";
479}
480
481/// `keypad_control_cmd` values (Table 36, p. 41).
482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
483#[cfg_attr(feature = "serde", derive(serde::Serialize))]
484#[non_exhaustive]
485pub enum KeypadControlCmd {
486    /// `01` — intercept all keypresses.
487    InterceptAllKeypresses,
488    /// `02` — ignore all keypresses.
489    IgnoreAllKeypresses,
490    /// `03` — intercept the listed keypresses.
491    InterceptSelectedKeypress,
492    /// `04` — ignore the listed keypresses.
493    IgnoreSelectedKeypress,
494    /// `05` — reject one keypress.
495    RejectKeypress,
496    /// Any other value (reserved).
497    Reserved(u8),
498}
499
500impl KeypadControlCmd {
501    /// Decode a `keypad_control_cmd` byte.
502    #[must_use]
503    pub fn from_u8(v: u8) -> Self {
504        match v {
505            0x01 => Self::InterceptAllKeypresses,
506            0x02 => Self::IgnoreAllKeypresses,
507            0x03 => Self::InterceptSelectedKeypress,
508            0x04 => Self::IgnoreSelectedKeypress,
509            0x05 => Self::RejectKeypress,
510            other => Self::Reserved(other),
511        }
512    }
513    /// Wire byte.
514    #[must_use]
515    pub const fn to_u8(self) -> u8 {
516        match self {
517            Self::InterceptAllKeypresses => 0x01,
518            Self::IgnoreAllKeypresses => 0x02,
519            Self::InterceptSelectedKeypress => 0x03,
520            Self::IgnoreSelectedKeypress => 0x04,
521            Self::RejectKeypress => 0x05,
522            Self::Reserved(v) => v,
523        }
524    }
525    /// Spec token, or `"reserved"`.
526    #[must_use]
527    pub fn name(&self) -> &'static str {
528        match self {
529            Self::InterceptAllKeypresses => "intercept_all_keypresses",
530            Self::IgnoreAllKeypresses => "ignore_all_keypresses",
531            Self::InterceptSelectedKeypress => "intercept_selected_keypress",
532            Self::IgnoreSelectedKeypress => "ignore_selected_keypress",
533            Self::RejectKeypress => "reject_keypress",
534            Self::Reserved(_) => "reserved",
535        }
536    }
537    /// `true` for the commands that carry a `key_code` list/value.
538    fn carries_key_codes(self) -> bool {
539        matches!(
540            self,
541            Self::InterceptSelectedKeypress | Self::IgnoreSelectedKeypress | Self::RejectKeypress
542        )
543    }
544}
545dvb_common::impl_spec_display!(KeypadControlCmd, Reserved);
546
547/// `keypad_control()` object (Table 36).
548#[derive(Debug, Clone, PartialEq, Eq)]
549#[cfg_attr(feature = "serde", derive(serde::Serialize))]
550pub struct KeypadControl {
551    /// `keypad_control_cmd`.
552    pub cmd: KeypadControlCmd,
553    /// The `key_code` list — non-empty only for the select/reject commands (a
554    /// single code for `reject_keypress`).
555    pub key_codes: Vec<u8>,
556}
557
558impl<'a> Parse<'a> for KeypadControl {
559    type Error = Error;
560    fn parse(bytes: &'a [u8]) -> Result<Self> {
561        let body = super::parse_apdu_header(bytes, tag::KEYPAD_CONTROL, "keypad_control")?;
562        let cmd_byte = *body.first().ok_or(Error::BufferTooShort {
563            need: 1,
564            have: 0,
565            what: "keypad_control cmd",
566        })?;
567        let cmd = KeypadControlCmd::from_u8(cmd_byte);
568        let key_codes = if cmd.carries_key_codes() {
569            body[1..].to_vec()
570        } else {
571            Vec::new()
572        };
573        Ok(Self { cmd, key_codes })
574    }
575}
576
577impl Serialize for KeypadControl {
578    type Error = Error;
579    fn serialized_len(&self) -> usize {
580        let codes = if self.cmd.carries_key_codes() {
581            self.key_codes.len()
582        } else {
583            0
584        };
585        super::apdu_len(1 + codes)
586    }
587    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
588        let with_codes = self.cmd.carries_key_codes();
589        let codes = if with_codes { self.key_codes.len() } else { 0 };
590        let mut pos = super::write_apdu_header(tag::KEYPAD_CONTROL, 1 + codes, buf)?;
591        buf[pos] = self.cmd.to_u8();
592        pos += 1;
593        if with_codes {
594            buf[pos..pos + self.key_codes.len()].copy_from_slice(&self.key_codes);
595            pos += self.key_codes.len();
596        }
597        Ok(pos)
598    }
599}
600
601impl<'a> ApduDef<'a> for KeypadControl {
602    const TAG: ApduTag = tag::KEYPAD_CONTROL;
603    const NAME: &'static str = "KEYPAD_CONTROL";
604}
605
606/// `keypress()` object (Table 37): one `key_code`.
607#[derive(Debug, Clone, Copy, PartialEq, Eq)]
608#[cfg_attr(feature = "serde", derive(serde::Serialize))]
609pub struct Keypress {
610    /// `key_code` (Table of key codes, p. 41).
611    pub key_code: u8,
612}
613
614impl<'a> Parse<'a> for Keypress {
615    type Error = Error;
616    fn parse(bytes: &'a [u8]) -> Result<Self> {
617        let body = super::parse_apdu_header(bytes, tag::KEYPRESS, "keypress")?;
618        let key_code = *body.first().ok_or(Error::BufferTooShort {
619            need: 1,
620            have: 0,
621            what: "keypress key_code",
622        })?;
623        Ok(Self { key_code })
624    }
625}
626
627impl Serialize for Keypress {
628    type Error = Error;
629    fn serialized_len(&self) -> usize {
630        super::apdu_len(1)
631    }
632    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
633        let mut pos = super::write_apdu_header(tag::KEYPRESS, 1, buf)?;
634        buf[pos] = self.key_code;
635        pos += 1;
636        Ok(pos)
637    }
638}
639
640impl<'a> ApduDef<'a> for Keypress {
641    const TAG: ApduTag = tag::KEYPRESS;
642    const NAME: &'static str = "KEYPRESS";
643}
644
645/// `subtitle_segment()` object (Table 38): a verbatim DVB_Subtitling_segment.
646///
647/// The `_last` (`9F 88 0E`) / `_more` (`9F 88 0F`) tags share this body;
648/// [`SubtitleSegment::more`] selects which tag is written and is set from the tag.
649#[derive(Debug, Clone, PartialEq, Eq)]
650#[cfg_attr(feature = "serde", derive(serde::Serialize))]
651pub struct SubtitleSegment<'a> {
652    /// `true` = `subtitle_segment_more`; `false` = `subtitle_segment_last`.
653    pub more: bool,
654    /// The DVB_Subtitling_segment bytes (reference `[9]`), verbatim.
655    #[cfg_attr(feature = "serde", serde(borrow, with = "super::bytes_serde"))]
656    pub segment: &'a [u8],
657}
658
659impl<'a> SubtitleSegment<'a> {
660    /// The `apdu_tag` for this object given [`SubtitleSegment::more`].
661    #[must_use]
662    pub fn tag(&self) -> ApduTag {
663        if self.more {
664            tag::SUBTITLE_SEGMENT_MORE
665        } else {
666            tag::SUBTITLE_SEGMENT_LAST
667        }
668    }
669}
670
671impl<'a> Parse<'a> for SubtitleSegment<'a> {
672    type Error = Error;
673    fn parse(bytes: &'a [u8]) -> Result<Self> {
674        if bytes.len() < 3 {
675            return Err(Error::BufferTooShort {
676                need: 3,
677                have: bytes.len(),
678                what: "subtitle_segment tag",
679            });
680        }
681        let t = ApduTag::from_bytes(bytes[0], bytes[1], bytes[2]);
682        let (expected, more) = match t {
683            tag::SUBTITLE_SEGMENT_MORE => (tag::SUBTITLE_SEGMENT_MORE, true),
684            _ => (tag::SUBTITLE_SEGMENT_LAST, false),
685        };
686        let body = super::parse_apdu_header(bytes, expected, "subtitle_segment")?;
687        Ok(Self {
688            more,
689            segment: body,
690        })
691    }
692}
693
694impl Serialize for SubtitleSegment<'_> {
695    type Error = Error;
696    fn serialized_len(&self) -> usize {
697        super::apdu_len(self.segment.len())
698    }
699    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
700        let mut pos = super::write_apdu_header(self.tag(), self.segment.len(), buf)?;
701        buf[pos..pos + self.segment.len()].copy_from_slice(self.segment);
702        pos += self.segment.len();
703        Ok(pos)
704    }
705}
706
707impl<'a> ApduDef<'a> for SubtitleSegment<'a> {
708    const TAG: ApduTag = tag::SUBTITLE_SEGMENT_LAST;
709    const NAME: &'static str = "SUBTITLE_SEGMENT";
710}
711
712/// `display_message_id` values (Table 39, p. 42).
713#[derive(Debug, Clone, Copy, PartialEq, Eq)]
714#[cfg_attr(feature = "serde", derive(serde::Serialize))]
715#[non_exhaustive]
716pub enum DisplayMessageId {
717    /// `00` — Display OK.
718    DisplayOk,
719    /// `01` — Display Error.
720    DisplayError,
721    /// `02` — Display out of memory.
722    DisplayOutOfMemory,
723    /// `03` — DVB Subtitling syntax error.
724    DvbSubtitlingSyntaxError,
725    /// `04` — Undefined region referenced.
726    UndefinedRegionReferenced,
727    /// `05` — Undefined CLUT referenced.
728    UndefinedClutReferenced,
729    /// `06` — Undefined object referenced.
730    UndefinedObjectReferenced,
731    /// `07` — Object incompatible with region.
732    ObjectIncompatibleWithRegion,
733    /// `08` — Unknown character referenced.
734    UnknownCharacterReferenced,
735    /// `09` — Display characteristics changed.
736    DisplayCharacteristicsChanged,
737    /// Any other value (reserved).
738    Reserved(u8),
739}
740
741impl DisplayMessageId {
742    /// Decode a `display_message_id` byte.
743    #[must_use]
744    pub fn from_u8(v: u8) -> Self {
745        match v {
746            0x00 => Self::DisplayOk,
747            0x01 => Self::DisplayError,
748            0x02 => Self::DisplayOutOfMemory,
749            0x03 => Self::DvbSubtitlingSyntaxError,
750            0x04 => Self::UndefinedRegionReferenced,
751            0x05 => Self::UndefinedClutReferenced,
752            0x06 => Self::UndefinedObjectReferenced,
753            0x07 => Self::ObjectIncompatibleWithRegion,
754            0x08 => Self::UnknownCharacterReferenced,
755            0x09 => Self::DisplayCharacteristicsChanged,
756            other => Self::Reserved(other),
757        }
758    }
759    /// Wire byte.
760    #[must_use]
761    pub const fn to_u8(self) -> u8 {
762        match self {
763            Self::DisplayOk => 0x00,
764            Self::DisplayError => 0x01,
765            Self::DisplayOutOfMemory => 0x02,
766            Self::DvbSubtitlingSyntaxError => 0x03,
767            Self::UndefinedRegionReferenced => 0x04,
768            Self::UndefinedClutReferenced => 0x05,
769            Self::UndefinedObjectReferenced => 0x06,
770            Self::ObjectIncompatibleWithRegion => 0x07,
771            Self::UnknownCharacterReferenced => 0x08,
772            Self::DisplayCharacteristicsChanged => 0x09,
773            Self::Reserved(v) => v,
774        }
775    }
776    /// Spec token, or `"reserved"`.
777    #[must_use]
778    pub fn name(&self) -> &'static str {
779        match self {
780            Self::DisplayOk => "Display OK",
781            Self::DisplayError => "Display Error",
782            Self::DisplayOutOfMemory => "Display out of memory",
783            Self::DvbSubtitlingSyntaxError => "DVB Subtitling syntax error",
784            Self::UndefinedRegionReferenced => "Undefined region referenced",
785            Self::UndefinedClutReferenced => "Undefined CLUT referenced",
786            Self::UndefinedObjectReferenced => "Undefined object referenced",
787            Self::ObjectIncompatibleWithRegion => "Object incompatible with region",
788            Self::UnknownCharacterReferenced => "Unknown character referenced",
789            Self::DisplayCharacteristicsChanged => "Display characteristics changed",
790            Self::Reserved(_) => "reserved",
791        }
792    }
793}
794dvb_common::impl_spec_display!(DisplayMessageId, Reserved);
795
796/// `display_message()` object (Table 39).
797#[derive(Debug, Clone, Copy, PartialEq, Eq)]
798#[cfg_attr(feature = "serde", derive(serde::Serialize))]
799pub struct DisplayMessage {
800    /// `display_message_id`.
801    pub message_id: DisplayMessageId,
802}
803
804impl<'a> Parse<'a> for DisplayMessage {
805    type Error = Error;
806    fn parse(bytes: &'a [u8]) -> Result<Self> {
807        let body = super::parse_apdu_header(bytes, tag::DISPLAY_MESSAGE, "display_message")?;
808        let message_id = DisplayMessageId::from_u8(*body.first().ok_or(Error::BufferTooShort {
809            need: 1,
810            have: 0,
811            what: "display_message id",
812        })?);
813        Ok(Self { message_id })
814    }
815}
816
817impl Serialize for DisplayMessage {
818    type Error = Error;
819    fn serialized_len(&self) -> usize {
820        super::apdu_len(1)
821    }
822    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
823        let mut pos = super::write_apdu_header(tag::DISPLAY_MESSAGE, 1, buf)?;
824        buf[pos] = self.message_id.to_u8();
825        pos += 1;
826        Ok(pos)
827    }
828}
829
830impl<'a> ApduDef<'a> for DisplayMessage {
831    const TAG: ApduTag = tag::DISPLAY_MESSAGE;
832    const NAME: &'static str = "DISPLAY_MESSAGE";
833}
834
835/// `scene_end_mark()` object (Table 40): subtitle display-set delimiter.
836#[derive(Debug, Clone, Copy, PartialEq, Eq)]
837#[cfg_attr(feature = "serde", derive(serde::Serialize))]
838pub struct SceneEndMark {
839    /// `decoder_continue_flag`.
840    pub decoder_continue_flag: bool,
841    /// `scene_reveal_flag`.
842    pub scene_reveal_flag: bool,
843    /// `send_scene_done` — instruct the decoder to send a scene done APDU.
844    pub send_scene_done: bool,
845    /// 4-bit `scene_tag`.
846    pub scene_tag: u8,
847}
848
849impl<'a> Parse<'a> for SceneEndMark {
850    type Error = Error;
851    fn parse(bytes: &'a [u8]) -> Result<Self> {
852        let body = super::parse_apdu_header(bytes, tag::SCENE_END_MARK, "scene_end_mark")?;
853        let b = *body.first().ok_or(Error::BufferTooShort {
854            need: 1,
855            have: 0,
856            what: "scene_end_mark",
857        })?;
858        Ok(Self {
859            decoder_continue_flag: (b & 0x80) != 0,
860            scene_reveal_flag: (b & 0x40) != 0,
861            send_scene_done: (b & 0x20) != 0,
862            // bit `[4]` reserved, scene_tag in `[3:0]`.
863            scene_tag: b & 0x0F,
864        })
865    }
866}
867
868impl Serialize for SceneEndMark {
869    type Error = Error;
870    fn serialized_len(&self) -> usize {
871        super::apdu_len(1)
872    }
873    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
874        let mut pos = super::write_apdu_header(tag::SCENE_END_MARK, 1, buf)?;
875        // decoder_continue(1) reveal(1) send_done(1) reserved(1)=1 scene_tag(4).
876        buf[pos] = (u8::from(self.decoder_continue_flag) << 7)
877            | (u8::from(self.scene_reveal_flag) << 6)
878            | (u8::from(self.send_scene_done) << 5)
879            | 0x10
880            | (self.scene_tag & 0x0F);
881        pos += 1;
882        Ok(pos)
883    }
884}
885
886impl<'a> ApduDef<'a> for SceneEndMark {
887    const TAG: ApduTag = tag::SCENE_END_MARK;
888    const NAME: &'static str = "SCENE_END_MARK";
889}
890
891/// `scene_done_message()` object (Table 41): decoder's display-set completion.
892///
893/// Per Table 41 the reserved field here is **2 bits** (vs. 1 bit in
894/// `scene_end_mark`), keeping the body byte-aligned without `send_scene_done`.
895#[derive(Debug, Clone, Copy, PartialEq, Eq)]
896#[cfg_attr(feature = "serde", derive(serde::Serialize))]
897pub struct SceneDoneMessage {
898    /// `decoder_continue_flag` — duplicates the causing scene end mark.
899    pub decoder_continue_flag: bool,
900    /// `scene_reveal_flag` — duplicates the causing scene end mark.
901    pub scene_reveal_flag: bool,
902    /// 4-bit `scene_tag` — duplicates the causing scene end mark.
903    pub scene_tag: u8,
904}
905
906impl<'a> Parse<'a> for SceneDoneMessage {
907    type Error = Error;
908    fn parse(bytes: &'a [u8]) -> Result<Self> {
909        let body = super::parse_apdu_header(bytes, tag::SCENE_DONE, "scene_done_message")?;
910        let b = *body.first().ok_or(Error::BufferTooShort {
911            need: 1,
912            have: 0,
913            what: "scene_done_message",
914        })?;
915        Ok(Self {
916            decoder_continue_flag: (b & 0x80) != 0,
917            scene_reveal_flag: (b & 0x40) != 0,
918            // bits `[5:4]` reserved, scene_tag in `[3:0]`.
919            scene_tag: b & 0x0F,
920        })
921    }
922}
923
924impl Serialize for SceneDoneMessage {
925    type Error = Error;
926    fn serialized_len(&self) -> usize {
927        super::apdu_len(1)
928    }
929    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
930        let mut pos = super::write_apdu_header(tag::SCENE_DONE, 1, buf)?;
931        // decoder_continue(1) reveal(1) reserved(2)=11 scene_tag(4).
932        buf[pos] = (u8::from(self.decoder_continue_flag) << 7)
933            | (u8::from(self.scene_reveal_flag) << 6)
934            | 0x30
935            | (self.scene_tag & 0x0F);
936        pos += 1;
937        Ok(pos)
938    }
939}
940
941impl<'a> ApduDef<'a> for SceneDoneMessage {
942    const TAG: ApduTag = tag::SCENE_DONE;
943    const NAME: &'static str = "SCENE_DONE";
944}
945
946/// `scene_control()` object (Table 42): same layout as `scene_done_message`.
947#[derive(Debug, Clone, Copy, PartialEq, Eq)]
948#[cfg_attr(feature = "serde", derive(serde::Serialize))]
949pub struct SceneControl {
950    /// `decoder_continue_flag`.
951    pub decoder_continue_flag: bool,
952    /// `scene_reveal_flag`.
953    pub scene_reveal_flag: bool,
954    /// 4-bit `scene_tag` — the scene end mark being operated upon.
955    pub scene_tag: u8,
956}
957
958impl<'a> Parse<'a> for SceneControl {
959    type Error = Error;
960    fn parse(bytes: &'a [u8]) -> Result<Self> {
961        let body = super::parse_apdu_header(bytes, tag::SCENE_CONTROL, "scene_control")?;
962        let b = *body.first().ok_or(Error::BufferTooShort {
963            need: 1,
964            have: 0,
965            what: "scene_control",
966        })?;
967        Ok(Self {
968            decoder_continue_flag: (b & 0x80) != 0,
969            scene_reveal_flag: (b & 0x40) != 0,
970            scene_tag: b & 0x0F,
971        })
972    }
973}
974
975impl Serialize for SceneControl {
976    type Error = Error;
977    fn serialized_len(&self) -> usize {
978        super::apdu_len(1)
979    }
980    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
981        let mut pos = super::write_apdu_header(tag::SCENE_CONTROL, 1, buf)?;
982        // decoder_continue(1) reveal(1) reserved(2)=11 scene_tag(4).
983        buf[pos] = (u8::from(self.decoder_continue_flag) << 7)
984            | (u8::from(self.scene_reveal_flag) << 6)
985            | 0x30
986            | (self.scene_tag & 0x0F);
987        pos += 1;
988        Ok(pos)
989    }
990}
991
992impl<'a> ApduDef<'a> for SceneControl {
993    const TAG: ApduTag = tag::SCENE_CONTROL;
994    const NAME: &'static str = "SCENE_CONTROL";
995}
996
997/// `subtitle_download()` object (Table 43): identical format to
998/// `subtitle_segment` but constrained to object-data segments.
999///
1000/// The `_last` (`9F 88 14`) / `_more` (`9F 88 15`) tags share this body;
1001/// [`SubtitleDownload::more`] selects which tag is written and is set from the tag.
1002#[derive(Debug, Clone, PartialEq, Eq)]
1003#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1004pub struct SubtitleDownload<'a> {
1005    /// `true` = `subtitle_download_more`; `false` = `subtitle_download_last`.
1006    pub more: bool,
1007    /// The DVB_Subtitling_segment (object-data) bytes, verbatim.
1008    #[cfg_attr(feature = "serde", serde(borrow, with = "super::bytes_serde"))]
1009    pub segment: &'a [u8],
1010}
1011
1012impl<'a> SubtitleDownload<'a> {
1013    /// The `apdu_tag` for this object given [`SubtitleDownload::more`].
1014    #[must_use]
1015    pub fn tag(&self) -> ApduTag {
1016        if self.more {
1017            tag::SUBTITLE_DOWNLOAD_MORE
1018        } else {
1019            tag::SUBTITLE_DOWNLOAD_LAST
1020        }
1021    }
1022}
1023
1024impl<'a> Parse<'a> for SubtitleDownload<'a> {
1025    type Error = Error;
1026    fn parse(bytes: &'a [u8]) -> Result<Self> {
1027        if bytes.len() < 3 {
1028            return Err(Error::BufferTooShort {
1029                need: 3,
1030                have: bytes.len(),
1031                what: "subtitle_download tag",
1032            });
1033        }
1034        let t = ApduTag::from_bytes(bytes[0], bytes[1], bytes[2]);
1035        let (expected, more) = match t {
1036            tag::SUBTITLE_DOWNLOAD_MORE => (tag::SUBTITLE_DOWNLOAD_MORE, true),
1037            _ => (tag::SUBTITLE_DOWNLOAD_LAST, false),
1038        };
1039        let body = super::parse_apdu_header(bytes, expected, "subtitle_download")?;
1040        Ok(Self {
1041            more,
1042            segment: body,
1043        })
1044    }
1045}
1046
1047impl Serialize for SubtitleDownload<'_> {
1048    type Error = Error;
1049    fn serialized_len(&self) -> usize {
1050        super::apdu_len(self.segment.len())
1051    }
1052    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1053        let mut pos = super::write_apdu_header(self.tag(), self.segment.len(), buf)?;
1054        buf[pos..pos + self.segment.len()].copy_from_slice(self.segment);
1055        pos += self.segment.len();
1056        Ok(pos)
1057    }
1058}
1059
1060impl<'a> ApduDef<'a> for SubtitleDownload<'a> {
1061    const TAG: ApduTag = tag::SUBTITLE_DOWNLOAD_LAST;
1062    const NAME: &'static str = "SUBTITLE_DOWNLOAD";
1063}
1064
1065/// `flush_download()` object (Table 44): header-only cache-purge request.
1066#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1067#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1068pub struct FlushDownload;
1069
1070impl<'a> Parse<'a> for FlushDownload {
1071    type Error = Error;
1072    fn parse(bytes: &'a [u8]) -> Result<Self> {
1073        super::parse_empty_apdu(bytes, tag::FLUSH_DOWNLOAD, "flush_download")?;
1074        Ok(Self)
1075    }
1076}
1077
1078impl Serialize for FlushDownload {
1079    type Error = Error;
1080    fn serialized_len(&self) -> usize {
1081        super::empty_apdu_len()
1082    }
1083    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1084        super::serialize_empty_apdu(tag::FLUSH_DOWNLOAD, buf)
1085    }
1086}
1087
1088impl<'a> ApduDef<'a> for FlushDownload {
1089    const TAG: ApduTag = tag::FLUSH_DOWNLOAD;
1090    const NAME: &'static str = "FLUSH_DOWNLOAD";
1091}
1092
1093/// `download_reply_id` values (Table 45, p. 45).
1094#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1095#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1096#[non_exhaustive]
1097pub enum DownloadReplyId {
1098    /// `00` — Download OK.
1099    DownloadOk,
1100    /// `01` — Not an object data segment (`object_id` should be `0xFFFF`).
1101    NotAnObjectDataSegment,
1102    /// `02` — Memory exhausted.
1103    MemoryExhausted,
1104    /// Any other value (reserved).
1105    Reserved(u8),
1106}
1107
1108impl DownloadReplyId {
1109    /// Decode a `download_reply_id` byte.
1110    #[must_use]
1111    pub fn from_u8(v: u8) -> Self {
1112        match v {
1113            0x00 => Self::DownloadOk,
1114            0x01 => Self::NotAnObjectDataSegment,
1115            0x02 => Self::MemoryExhausted,
1116            other => Self::Reserved(other),
1117        }
1118    }
1119    /// Wire byte.
1120    #[must_use]
1121    pub const fn to_u8(self) -> u8 {
1122        match self {
1123            Self::DownloadOk => 0x00,
1124            Self::NotAnObjectDataSegment => 0x01,
1125            Self::MemoryExhausted => 0x02,
1126            Self::Reserved(v) => v,
1127        }
1128    }
1129    /// Spec token, or `"reserved"`.
1130    #[must_use]
1131    pub fn name(&self) -> &'static str {
1132        match self {
1133            Self::DownloadOk => "Download OK",
1134            Self::NotAnObjectDataSegment => "Not an object data segment",
1135            Self::MemoryExhausted => "Memory exhausted",
1136            Self::Reserved(_) => "reserved",
1137        }
1138    }
1139}
1140dvb_common::impl_spec_display!(DownloadReplyId, Reserved);
1141
1142/// `download_reply()` object (Table 45): object_id + download_reply_id.
1143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1144#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1145pub struct DownloadReply {
1146    /// `object_id` — the offending object (`0xFFFF` for "not an object segment").
1147    pub object_id: u16,
1148    /// `download_reply_id`.
1149    pub reply_id: DownloadReplyId,
1150}
1151
1152// object_id(2) + download_reply_id(1).
1153const DOWNLOAD_REPLY_BODY: usize = 3;
1154
1155impl<'a> Parse<'a> for DownloadReply {
1156    type Error = Error;
1157    fn parse(bytes: &'a [u8]) -> Result<Self> {
1158        let body = super::parse_apdu_header(bytes, tag::DOWNLOAD_REPLY, "download_reply")?;
1159        if body.len() < DOWNLOAD_REPLY_BODY {
1160            return Err(Error::BufferTooShort {
1161                need: DOWNLOAD_REPLY_BODY,
1162                have: body.len(),
1163                what: "download_reply",
1164            });
1165        }
1166        Ok(Self {
1167            object_id: u16::from_be_bytes([body[0], body[1]]),
1168            reply_id: DownloadReplyId::from_u8(body[2]),
1169        })
1170    }
1171}
1172
1173impl Serialize for DownloadReply {
1174    type Error = Error;
1175    fn serialized_len(&self) -> usize {
1176        super::apdu_len(DOWNLOAD_REPLY_BODY)
1177    }
1178    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1179        let mut pos = super::write_apdu_header(tag::DOWNLOAD_REPLY, DOWNLOAD_REPLY_BODY, buf)?;
1180        buf[pos..pos + 2].copy_from_slice(&self.object_id.to_be_bytes());
1181        buf[pos + 2] = self.reply_id.to_u8();
1182        pos += DOWNLOAD_REPLY_BODY;
1183        Ok(pos)
1184    }
1185}
1186
1187impl<'a> ApduDef<'a> for DownloadReply {
1188    const TAG: ApduTag = tag::DOWNLOAD_REPLY;
1189    const NAME: &'static str = "DOWNLOAD_REPLY";
1190}
1191
1192#[cfg(test)]
1193mod tests {
1194    use super::*;
1195
1196    #[test]
1197    fn display_control_set_mmi_mode_and_query() {
1198        let dc = DisplayControl {
1199            cmd: DisplayControlCmd::SetMmiMode,
1200            mmi_mode: Some(MmiMode::HighLevel),
1201        };
1202        let bytes = dc.to_bytes();
1203        assert_eq!(bytes, [0x9F, 0x88, 0x01, 0x02, 0x01, 0x01]);
1204        assert_eq!(DisplayControl::parse(&bytes).unwrap(), dc);
1205
1206        let q = DisplayControl {
1207            cmd: DisplayControlCmd::GetDisplayCharacterTableList,
1208            mmi_mode: None,
1209        };
1210        let qb = q.to_bytes();
1211        assert_eq!(qb, [0x9F, 0x88, 0x01, 0x01, 0x02]);
1212        assert_eq!(DisplayControl::parse(&qb).unwrap(), q);
1213
1214        let mut other = dc;
1215        other.mmi_mode = Some(MmiMode::LowLevelFullScreenGraphics);
1216        assert_ne!(bytes, other.to_bytes());
1217    }
1218
1219    #[test]
1220    fn display_reply_graphics_round_trips_and_bites() {
1221        let g = GraphicsCharacteristics {
1222            display_horizontal_size: 720,
1223            display_vertical_size: 576,
1224            aspect_ratio_information: 3,
1225            graphics_relation_to_video: 0b111,
1226            multiple_depths: true,
1227            display_bytes: 0x0ABC,
1228            composition_buffer_bytes: 0x12,
1229            object_cache_bytes: 0x34,
1230            depths: alloc::vec![
1231                PixelDepth {
1232                    display_depth: 2,
1233                    pixels_per_byte: 4,
1234                    region_overhead: 0x10,
1235                },
1236                PixelDepth {
1237                    display_depth: 7,
1238                    pixels_per_byte: 1,
1239                    region_overhead: 0x20,
1240                },
1241            ],
1242        };
1243        let dr = DisplayReply {
1244            reply_id: DisplayReplyId::ListGraphicOverlayCharacteristics,
1245            body: DisplayReplyBody::Graphics(g),
1246        };
1247        let bytes = dr.to_bytes();
1248        let parsed = DisplayReply::parse(&bytes).unwrap();
1249        assert_eq!(parsed, dr);
1250        if let DisplayReplyBody::Graphics(gg) = &parsed.body {
1251            assert_eq!(gg.depths.len(), 2);
1252            assert_eq!(gg.display_bytes, 0x0ABC);
1253            assert_eq!(gg.composition_buffer_bytes, 0x12);
1254            assert_eq!(gg.object_cache_bytes, 0x34);
1255        } else {
1256            panic!("expected graphics");
1257        }
1258
1259        // bite: change one depth entry.
1260        let mut other = dr.clone();
1261        if let DisplayReplyBody::Graphics(gg) = &mut other.body {
1262            gg.depths[0].region_overhead = 0xFF;
1263        }
1264        assert_ne!(bytes, other.to_bytes());
1265    }
1266
1267    #[test]
1268    fn display_reply_char_tables_and_ack() {
1269        let ct = DisplayReply {
1270            reply_id: DisplayReplyId::ListDisplayCharacterTables,
1271            body: DisplayReplyBody::CharacterTables(alloc::vec![0x00, 0x01, 0x02]),
1272        };
1273        let bytes = ct.to_bytes();
1274        assert_eq!(bytes, [0x9F, 0x88, 0x02, 0x04, 0x02, 0x00, 0x01, 0x02]);
1275        assert_eq!(DisplayReply::parse(&bytes).unwrap(), ct);
1276
1277        let ack = DisplayReply {
1278            reply_id: DisplayReplyId::MmiModeAck,
1279            body: DisplayReplyBody::MmiModeAck(MmiMode::HighLevel),
1280        };
1281        let ab = ack.to_bytes();
1282        assert_eq!(ab, [0x9F, 0x88, 0x02, 0x02, 0x01, 0x01]);
1283        assert_eq!(DisplayReply::parse(&ab).unwrap(), ack);
1284
1285        let unknown = DisplayReply {
1286            reply_id: DisplayReplyId::UnknownMmiMode,
1287            body: DisplayReplyBody::None,
1288        };
1289        let ub = unknown.to_bytes();
1290        assert_eq!(ub, [0x9F, 0x88, 0x02, 0x01, 0xF1]);
1291        assert_eq!(DisplayReply::parse(&ub).unwrap(), unknown);
1292    }
1293
1294    #[test]
1295    fn keypad_control_multi_key_round_trips_and_bites() {
1296        let kc = KeypadControl {
1297            cmd: KeypadControlCmd::InterceptSelectedKeypress,
1298            key_codes: alloc::vec![0x00, 0x01, 0x0A], // 0, 1, menu
1299        };
1300        let bytes = kc.to_bytes();
1301        assert_eq!(bytes, [0x9F, 0x88, 0x05, 0x04, 0x03, 0x00, 0x01, 0x0A]);
1302        let parsed = KeypadControl::parse(&bytes).unwrap();
1303        assert_eq!(parsed, kc);
1304        assert_eq!(parsed.key_codes.len(), 3);
1305
1306        // intercept_all carries no codes.
1307        let all = KeypadControl {
1308            cmd: KeypadControlCmd::InterceptAllKeypresses,
1309            key_codes: Vec::new(),
1310        };
1311        let ab = all.to_bytes();
1312        assert_eq!(ab, [0x9F, 0x88, 0x05, 0x01, 0x01]);
1313        assert_eq!(KeypadControl::parse(&ab).unwrap(), all);
1314
1315        // bite.
1316        let mut other = kc.clone();
1317        other.key_codes[1] = 0x09;
1318        assert_ne!(bytes, other.to_bytes());
1319    }
1320
1321    #[test]
1322    fn keypress_round_trips_and_bites() {
1323        let k = Keypress { key_code: 0x0A };
1324        let bytes = k.to_bytes();
1325        assert_eq!(bytes, [0x9F, 0x88, 0x06, 0x01, 0x0A]);
1326        assert_eq!(Keypress::parse(&bytes).unwrap(), k);
1327        let other = Keypress { key_code: 0x0B };
1328        assert_ne!(bytes, other.to_bytes());
1329    }
1330
1331    #[test]
1332    fn subtitle_segment_more_bites() {
1333        let s = SubtitleSegment {
1334            more: false,
1335            segment: &[0x0F, 0x10, 0x00, 0x01, 0x00, 0x05],
1336        };
1337        let bytes = s.to_bytes();
1338        assert_eq!(bytes[2], 0x0E);
1339        assert_eq!(SubtitleSegment::parse(&bytes).unwrap(), s);
1340        let mut more = s.clone();
1341        more.more = true;
1342        let mb = more.to_bytes();
1343        assert_eq!(mb[2], 0x0F);
1344        assert_ne!(bytes, mb);
1345        assert_eq!(SubtitleSegment::parse(&mb).unwrap(), more);
1346    }
1347
1348    #[test]
1349    fn display_message_round_trips_and_bites() {
1350        let m = DisplayMessage {
1351            message_id: DisplayMessageId::DvbSubtitlingSyntaxError,
1352        };
1353        let bytes = m.to_bytes();
1354        assert_eq!(bytes, [0x9F, 0x88, 0x10, 0x01, 0x03]);
1355        assert_eq!(DisplayMessage::parse(&bytes).unwrap(), m);
1356        assert_eq!(m.message_id.name(), "DVB Subtitling syntax error");
1357        let other = DisplayMessage {
1358            message_id: DisplayMessageId::DisplayOk,
1359        };
1360        assert_ne!(bytes, other.to_bytes());
1361    }
1362
1363    #[test]
1364    fn scene_end_mark_round_trips_and_bites() {
1365        let s = SceneEndMark {
1366            decoder_continue_flag: true,
1367            scene_reveal_flag: false,
1368            send_scene_done: true,
1369            scene_tag: 0x05,
1370        };
1371        let bytes = s.to_bytes();
1372        // 1_0_1_1_0101 = 0xB5
1373        assert_eq!(bytes, [0x9F, 0x88, 0x11, 0x01, 0xB5]);
1374        assert_eq!(SceneEndMark::parse(&bytes).unwrap(), s);
1375        let mut other = s;
1376        other.scene_tag = 0x06;
1377        assert_ne!(bytes, other.to_bytes());
1378    }
1379
1380    #[test]
1381    fn scene_done_message_two_bit_reserved() {
1382        let s = SceneDoneMessage {
1383            decoder_continue_flag: false,
1384            scene_reveal_flag: true,
1385            scene_tag: 0x0A,
1386        };
1387        let bytes = s.to_bytes();
1388        // 0_1_11_1010 = 0x7A
1389        assert_eq!(bytes, [0x9F, 0x88, 0x12, 0x01, 0x7A]);
1390        assert_eq!(SceneDoneMessage::parse(&bytes).unwrap(), s);
1391        let mut other = s;
1392        other.decoder_continue_flag = true;
1393        assert_ne!(bytes, other.to_bytes());
1394    }
1395
1396    #[test]
1397    fn scene_control_round_trips() {
1398        let s = SceneControl {
1399            decoder_continue_flag: true,
1400            scene_reveal_flag: true,
1401            scene_tag: 0x03,
1402        };
1403        let bytes = s.to_bytes();
1404        // 1_1_11_0011 = 0xF3
1405        assert_eq!(bytes, [0x9F, 0x88, 0x13, 0x01, 0xF3]);
1406        assert_eq!(SceneControl::parse(&bytes).unwrap(), s);
1407    }
1408
1409    #[test]
1410    fn subtitle_download_more_bites() {
1411        let s = SubtitleDownload {
1412            more: true,
1413            segment: &[0x0F, 0x13, 0x00, 0x01],
1414        };
1415        let bytes = s.to_bytes();
1416        assert_eq!(bytes[2], 0x15);
1417        assert_eq!(SubtitleDownload::parse(&bytes).unwrap(), s);
1418        let mut last = s.clone();
1419        last.more = false;
1420        let lb = last.to_bytes();
1421        assert_eq!(lb[2], 0x14);
1422        assert_ne!(bytes, lb);
1423    }
1424
1425    #[test]
1426    fn flush_download_round_trips() {
1427        let bytes = FlushDownload.to_bytes();
1428        assert_eq!(bytes, [0x9F, 0x88, 0x16, 0x00]);
1429        assert_eq!(FlushDownload::parse(&bytes).unwrap(), FlushDownload);
1430    }
1431
1432    #[test]
1433    fn download_reply_round_trips_and_bites() {
1434        let d = DownloadReply {
1435            object_id: 0xFFFF,
1436            reply_id: DownloadReplyId::NotAnObjectDataSegment,
1437        };
1438        let bytes = d.to_bytes();
1439        assert_eq!(bytes, [0x9F, 0x88, 0x17, 0x03, 0xFF, 0xFF, 0x01]);
1440        assert_eq!(DownloadReply::parse(&bytes).unwrap(), d);
1441        let mut other = d;
1442        other.object_id = 0x0001;
1443        assert_ne!(bytes, other.to_bytes());
1444    }
1445}