dvb_ci/objects/
mmi_close.rs1use crate::error::{Error, Result};
7use crate::tag::{self, ApduTag};
8use crate::traits::ApduDef;
9use dvb_common::{Parse, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[non_exhaustive]
15pub enum CloseMmiCmdId {
16 Immediate,
18 Delay,
20 Reserved(u8),
22}
23
24impl CloseMmiCmdId {
25 #[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 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58pub struct CloseMmi {
59 pub cmd_id: CloseMmiCmdId,
61 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}