Skip to main content

dvb_ci/objects/
mmi_close.rs

1//! Close MMI object — ETSI EN 50221 §8.6.2.1, Table 33 (PDF p. 36).
2//!
3//! `close_mmi` (`9F 88 00`): a `close_mmi_cmd_id` byte, plus a `delay` byte when
4//! the command id is `delay` (`01`).
5
6use crate::error::{Error, Result};
7use crate::tag::{self, ApduTag};
8use crate::traits::ApduDef;
9use dvb_common::{Parse, Serialize};
10
11/// `close_mmi_cmd_id` values (Table, p. 36).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[non_exhaustive]
15pub enum CloseMmiCmdId {
16    /// `00` — return to the previous display immediately.
17    Immediate,
18    /// `01` — delay the return; the `delay` byte gives the delay in seconds.
19    Delay,
20    /// Any other value (reserved).
21    Reserved(u8),
22}
23
24impl CloseMmiCmdId {
25    /// Decode a `close_mmi_cmd_id` byte.
26    #[must_use]
27    pub fn from_u8(v: u8) -> Self {
28        match v {
29            0x00 => Self::Immediate,
30            0x01 => Self::Delay,
31            other => Self::Reserved(other),
32        }
33    }
34    /// Wire byte for this command id.
35    #[must_use]
36    pub const fn to_u8(self) -> u8 {
37        match self {
38            Self::Immediate => 0x00,
39            Self::Delay => 0x01,
40            Self::Reserved(v) => v,
41        }
42    }
43    /// Spec token, or `"reserved"`.
44    #[must_use]
45    pub fn name(&self) -> &'static str {
46        match self {
47            Self::Immediate => "immediate",
48            Self::Delay => "delay",
49            Self::Reserved(_) => "reserved",
50        }
51    }
52}
53dvb_common::impl_spec_display!(CloseMmiCmdId, Reserved);
54
55/// `close_mmi()` object (Table 33).
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58pub struct CloseMmi {
59    /// `close_mmi_cmd_id`.
60    pub cmd_id: CloseMmiCmdId,
61    /// `delay` (seconds) — present only when `cmd_id == delay`.
62    pub delay: Option<u8>,
63}
64
65impl<'a> Parse<'a> for CloseMmi {
66    type Error = Error;
67    fn parse(bytes: &'a [u8]) -> Result<Self> {
68        let body = super::parse_apdu_header(bytes, tag::CLOSE_MMI, "close_mmi")?;
69        let cmd_byte = *body.first().ok_or(Error::BufferTooShort {
70            need: 1,
71            have: 0,
72            what: "close_mmi cmd_id",
73        })?;
74        let cmd_id = CloseMmiCmdId::from_u8(cmd_byte);
75        let delay = if cmd_id == CloseMmiCmdId::Delay {
76            if body.len() < 2 {
77                return Err(Error::InvalidObject {
78                    what: "close_mmi",
79                    reason: "cmd_id=delay requires a delay byte",
80                });
81            }
82            Some(body[1])
83        } else {
84            None
85        };
86        Ok(Self { cmd_id, delay })
87    }
88}
89
90impl Serialize for CloseMmi {
91    type Error = Error;
92    fn serialized_len(&self) -> usize {
93        let body = 1 + usize::from(self.delay.is_some());
94        super::apdu_len(body)
95    }
96    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
97        let body_len = 1 + usize::from(self.delay.is_some());
98        let mut pos = super::write_apdu_header(tag::CLOSE_MMI, body_len, buf)?;
99        buf[pos] = self.cmd_id.to_u8();
100        pos += 1;
101        if let Some(d) = self.delay {
102            buf[pos] = d;
103            pos += 1;
104        }
105        Ok(pos)
106    }
107}
108
109impl<'a> ApduDef<'a> for CloseMmi {
110    const TAG: ApduTag = tag::CLOSE_MMI;
111    const NAME: &'static str = "CLOSE_MMI";
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn immediate_round_trip() {
120        let c = CloseMmi {
121            cmd_id: CloseMmiCmdId::Immediate,
122            delay: None,
123        };
124        let bytes = c.to_bytes();
125        assert_eq!(bytes, [0x9F, 0x88, 0x00, 0x01, 0x00]);
126        assert_eq!(CloseMmi::parse(&bytes).unwrap(), c);
127    }
128
129    #[test]
130    fn delay_round_trip() {
131        let c = CloseMmi {
132            cmd_id: CloseMmiCmdId::Delay,
133            delay: Some(5),
134        };
135        let bytes = c.to_bytes();
136        assert_eq!(bytes, [0x9F, 0x88, 0x00, 0x02, 0x01, 0x05]);
137        let parsed = CloseMmi::parse(&bytes).unwrap();
138        assert_eq!(parsed, c);
139        assert_eq!(parsed.cmd_id.name(), "delay");
140    }
141
142    #[test]
143    fn mutating_changes_bytes() {
144        let c = CloseMmi {
145            cmd_id: CloseMmiCmdId::Delay,
146            delay: Some(5),
147        };
148        let a = c.to_bytes();
149        let mut other = c;
150        other.delay = Some(10);
151        assert_ne!(a, other.to_bytes());
152    }
153}