Skip to main content

dvb_ci/ci_ext/
power_manager.rs

1//! Power Manager objects — ETSI TS 101 699 V1.1.1 §6.3, Tables 52-55
2//! (PDF pp. 51-52). See `docs/ci_plus/power-manager.md`.
3//!
4//! Resource ID `0x00220041`. Lets a module tell the host it is busy with a task
5//! that should complete before the host powers down.
6//!
7//! - `activation_state_change_request` (`9F 80 00`, Table 52) — host asks the
8//!   module to change activation state.
9//! - `activation_state_change_ack` (`9F 80 01`, Table 54) — module's reply.
10
11use crate::error::{Error, Result};
12use crate::objects;
13use crate::tag::ApduTag;
14use dvb_common::{Parse, Serialize};
15
16/// Resource-scoped `apdu_tag`s for the Power Manager (Tables 52, 54).
17pub mod tag {
18    use crate::tag::ApduTag;
19    /// `activation_status_change_request_tag` = `9F 80 00`.
20    pub const ACTIVATION_STATE_CHANGE_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
21    /// `activation_status_change_ack_tag` = `9F 80 01`.
22    pub const ACTIVATION_STATE_CHANGE_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
23}
24
25/// `activation_state` — the requested power mode (Table 53).
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28#[non_exhaustive]
29pub enum ActivationState {
30    /// `0` — Standby-passive (the EACEM-defined power mode).
31    StandbyPassive,
32    /// `1`-`15` — reserved for future use.
33    Reserved(u8),
34}
35
36impl ActivationState {
37    /// Decode the 4-bit `activation_state`.
38    #[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    /// 4-bit wire value.
46    #[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    /// Spec token, or `"reserved"`.
54    #[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/// `reply_code` — the module's response to a state-change request (Table 55).
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67#[non_exhaustive]
68pub enum ReplyCode {
69    /// `0` — OK to change state.
70    Ok,
71    /// `1` — Module busy, don't change state.
72    Busy,
73    /// `2`-`255` — reserved for future use.
74    Reserved(u8),
75}
76
77impl ReplyCode {
78    /// Decode the `reply_code` byte.
79    #[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    /// Wire byte.
88    #[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    /// Spec token, or `"reserved"`.
97    #[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/// `activation_state_change_request()` (Table 52): host → module.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111pub struct ActivationStateChangeRequest {
112    /// The requested new activation state (low 4 bits; top 4 bits reserved).
113    pub activation_state: ActivationState,
114}
115
116/// `activation_state_change_ack()` (Table 54): module → host.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119pub struct ActivationStateChangeAck {
120    /// The module's response.
121    pub reply_code: ReplyCode,
122}
123
124// reserved(4) + activation_state(4).
125const REQUEST_BODY: usize = 1;
126// reply_code(8).
127const 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        // reserved(4)='0000', activation_state(4).
158        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/// Resource-scoped dispatch over the Power Manager objects.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198#[non_exhaustive]
199pub enum PowerManagerApdu {
200    /// `activation_state_change_request` (`9F 80 00`).
201    Request(ActivationStateChangeRequest),
202    /// `activation_state_change_ack` (`9F 80 01`).
203    Ack(ActivationStateChangeAck),
204}
205
206impl PowerManagerApdu {
207    /// Parse a Power Manager APDU, dispatching on the `apdu_tag`.
208    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}