1use crate::error::{Error, Result};
12use crate::objects;
13use crate::tag::ApduTag;
14use dvb_common::{Parse, Serialize};
15
16pub mod tag {
18 use crate::tag::ApduTag;
19 pub const ACTIVATION_STATE_CHANGE_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
21 pub const ACTIVATION_STATE_CHANGE_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28#[non_exhaustive]
29pub enum ActivationState {
30 StandbyPassive,
32 Reserved(u8),
34}
35
36impl ActivationState {
37 #[must_use]
39 pub fn from_u8(v: u8) -> Self {
40 match v & 0x0F {
41 0 => Self::StandbyPassive,
42 other => Self::Reserved(other),
43 }
44 }
45 #[must_use]
47 pub fn to_u8(self) -> u8 {
48 match self {
49 Self::StandbyPassive => 0,
50 Self::Reserved(v) => v & 0x0F,
51 }
52 }
53 #[must_use]
55 pub fn name(&self) -> &'static str {
56 match self {
57 Self::StandbyPassive => "Standby-passive",
58 Self::Reserved(_) => "reserved",
59 }
60 }
61}
62dvb_common::impl_spec_display!(ActivationState, Reserved);
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67#[non_exhaustive]
68pub enum ReplyCode {
69 Ok,
71 Busy,
73 Reserved(u8),
75}
76
77impl ReplyCode {
78 #[must_use]
80 pub fn from_u8(v: u8) -> Self {
81 match v {
82 0 => Self::Ok,
83 1 => Self::Busy,
84 other => Self::Reserved(other),
85 }
86 }
87 #[must_use]
89 pub fn to_u8(self) -> u8 {
90 match self {
91 Self::Ok => 0,
92 Self::Busy => 1,
93 Self::Reserved(v) => v,
94 }
95 }
96 #[must_use]
98 pub fn name(&self) -> &'static str {
99 match self {
100 Self::Ok => "OK to change state",
101 Self::Busy => "Module busy, don't change state",
102 Self::Reserved(_) => "reserved",
103 }
104 }
105}
106dvb_common::impl_spec_display!(ReplyCode, Reserved);
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111pub struct ActivationStateChangeRequest {
112 pub activation_state: ActivationState,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119pub struct ActivationStateChangeAck {
120 pub reply_code: ReplyCode,
122}
123
124const REQUEST_BODY: usize = 1;
126const ACK_BODY: usize = 1;
128
129impl<'a> Parse<'a> for ActivationStateChangeRequest {
130 type Error = Error;
131 fn parse(bytes: &'a [u8]) -> Result<Self> {
132 let body = objects::parse_apdu_header(
133 bytes,
134 tag::ACTIVATION_STATE_CHANGE_REQUEST,
135 "activation_state_change_request",
136 )?;
137 if body.len() < REQUEST_BODY {
138 return Err(Error::BufferTooShort {
139 need: REQUEST_BODY,
140 have: body.len(),
141 what: "activation_state_change_request",
142 });
143 }
144 Ok(Self {
145 activation_state: ActivationState::from_u8(body[0] & 0x0F),
146 })
147 }
148}
149impl Serialize for ActivationStateChangeRequest {
150 type Error = Error;
151 fn serialized_len(&self) -> usize {
152 objects::apdu_len(REQUEST_BODY)
153 }
154 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
155 let pos =
156 objects::write_apdu_header(tag::ACTIVATION_STATE_CHANGE_REQUEST, REQUEST_BODY, buf)?;
157 buf[pos] = self.activation_state.to_u8() & 0x0F;
159 Ok(pos + REQUEST_BODY)
160 }
161}
162
163impl<'a> Parse<'a> for ActivationStateChangeAck {
164 type Error = Error;
165 fn parse(bytes: &'a [u8]) -> Result<Self> {
166 let body = objects::parse_apdu_header(
167 bytes,
168 tag::ACTIVATION_STATE_CHANGE_ACK,
169 "activation_state_change_ack",
170 )?;
171 if body.len() < ACK_BODY {
172 return Err(Error::BufferTooShort {
173 need: ACK_BODY,
174 have: body.len(),
175 what: "activation_state_change_ack",
176 });
177 }
178 Ok(Self {
179 reply_code: ReplyCode::from_u8(body[0]),
180 })
181 }
182}
183impl Serialize for ActivationStateChangeAck {
184 type Error = Error;
185 fn serialized_len(&self) -> usize {
186 objects::apdu_len(ACK_BODY)
187 }
188 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
189 let pos = objects::write_apdu_header(tag::ACTIVATION_STATE_CHANGE_ACK, ACK_BODY, buf)?;
190 buf[pos] = self.reply_code.to_u8();
191 Ok(pos + ACK_BODY)
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198#[non_exhaustive]
199pub enum PowerManagerApdu {
200 Request(ActivationStateChangeRequest),
202 Ack(ActivationStateChangeAck),
204}
205
206impl PowerManagerApdu {
207 pub fn parse(body: &[u8]) -> Result<Self> {
209 if body.len() < 3 {
210 return Err(Error::BufferTooShort {
211 need: 3,
212 have: body.len(),
213 what: "power_manager apdu_tag",
214 });
215 }
216 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
217 match t {
218 tag::ACTIVATION_STATE_CHANGE_REQUEST => {
219 Ok(Self::Request(ActivationStateChangeRequest::parse(body)?))
220 }
221 tag::ACTIVATION_STATE_CHANGE_ACK => {
222 Ok(Self::Ack(ActivationStateChangeAck::parse(body)?))
223 }
224 _ => Err(Error::UnexpectedApduTag {
225 got: t.as_u24(),
226 expected: tag::ACTIVATION_STATE_CHANGE_REQUEST.as_u24(),
227 what: "power_manager",
228 }),
229 }
230 }
231}
232
233impl Serialize for PowerManagerApdu {
234 type Error = Error;
235 fn serialized_len(&self) -> usize {
236 match self {
237 Self::Request(o) => o.serialized_len(),
238 Self::Ack(o) => o.serialized_len(),
239 }
240 }
241 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
242 match self {
243 Self::Request(o) => o.serialize_into(buf),
244 Self::Ack(o) => o.serialize_into(buf),
245 }
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn request_round_trips_and_bites() {
255 let r = ActivationStateChangeRequest {
256 activation_state: ActivationState::StandbyPassive,
257 };
258 let bytes = r.to_bytes();
259 assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x01, 0x00]);
260 assert_eq!(ActivationStateChangeRequest::parse(&bytes).unwrap(), r);
261 assert_eq!(r.activation_state.name(), "Standby-passive");
262 let other = ActivationStateChangeRequest {
263 activation_state: ActivationState::Reserved(5),
264 };
265 assert_ne!(bytes, other.to_bytes());
266 assert_eq!(other.to_bytes()[4], 0x05);
267 }
268
269 #[test]
270 fn ack_round_trips_and_bites() {
271 let a = ActivationStateChangeAck {
272 reply_code: ReplyCode::Busy,
273 };
274 let bytes = a.to_bytes();
275 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0x01]);
276 assert_eq!(ActivationStateChangeAck::parse(&bytes).unwrap(), a);
277 assert_eq!(a.reply_code.name(), "Module busy, don't change state");
278 let other = ActivationStateChangeAck {
279 reply_code: ReplyCode::Ok,
280 };
281 assert_ne!(bytes, other.to_bytes());
282 }
283
284 #[test]
285 fn dispatch_routes_both_tags() {
286 let req = ActivationStateChangeRequest {
287 activation_state: ActivationState::StandbyPassive,
288 }
289 .to_bytes();
290 assert!(matches!(
291 PowerManagerApdu::parse(&req).unwrap(),
292 PowerManagerApdu::Request(_)
293 ));
294 let ack = ActivationStateChangeAck {
295 reply_code: ReplyCode::Ok,
296 }
297 .to_bytes();
298 let parsed = PowerManagerApdu::parse(&ack).unwrap();
299 assert!(matches!(parsed, PowerManagerApdu::Ack(_)));
300 assert_eq!(parsed.to_bytes(), ack);
301 }
302}