use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use dvb_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const ACTIVATION_STATE_CHANGE_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
pub const ACTIVATION_STATE_CHANGE_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ActivationState {
StandbyPassive,
Reserved(u8),
}
impl ActivationState {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v & 0x0F {
0 => Self::StandbyPassive,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::StandbyPassive => 0,
Self::Reserved(v) => v & 0x0F,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::StandbyPassive => "Standby-passive",
Self::Reserved(_) => "reserved",
}
}
}
dvb_common::impl_spec_display!(ActivationState, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ReplyCode {
Ok,
Busy,
Reserved(u8),
}
impl ReplyCode {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0 => Self::Ok,
1 => Self::Busy,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Ok => 0,
Self::Busy => 1,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Ok => "OK to change state",
Self::Busy => "Module busy, don't change state",
Self::Reserved(_) => "reserved",
}
}
}
dvb_common::impl_spec_display!(ReplyCode, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ActivationStateChangeRequest {
pub activation_state: ActivationState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ActivationStateChangeAck {
pub reply_code: ReplyCode,
}
const REQUEST_BODY: usize = 1;
const ACK_BODY: usize = 1;
impl<'a> Parse<'a> for ActivationStateChangeRequest {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(
bytes,
tag::ACTIVATION_STATE_CHANGE_REQUEST,
"activation_state_change_request",
)?;
if body.len() < REQUEST_BODY {
return Err(Error::BufferTooShort {
need: REQUEST_BODY,
have: body.len(),
what: "activation_state_change_request",
});
}
Ok(Self {
activation_state: ActivationState::from_u8(body[0] & 0x0F),
})
}
}
impl Serialize for ActivationStateChangeRequest {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(REQUEST_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos =
objects::write_apdu_header(tag::ACTIVATION_STATE_CHANGE_REQUEST, REQUEST_BODY, buf)?;
buf[pos] = self.activation_state.to_u8() & 0x0F;
Ok(pos + REQUEST_BODY)
}
}
impl<'a> Parse<'a> for ActivationStateChangeAck {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(
bytes,
tag::ACTIVATION_STATE_CHANGE_ACK,
"activation_state_change_ack",
)?;
if body.len() < ACK_BODY {
return Err(Error::BufferTooShort {
need: ACK_BODY,
have: body.len(),
what: "activation_state_change_ack",
});
}
Ok(Self {
reply_code: ReplyCode::from_u8(body[0]),
})
}
}
impl Serialize for ActivationStateChangeAck {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(ACK_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::ACTIVATION_STATE_CHANGE_ACK, ACK_BODY, buf)?;
buf[pos] = self.reply_code.to_u8();
Ok(pos + ACK_BODY)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum PowerManagerApdu {
Request(ActivationStateChangeRequest),
Ack(ActivationStateChangeAck),
}
impl PowerManagerApdu {
pub fn parse(body: &[u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "power_manager apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::ACTIVATION_STATE_CHANGE_REQUEST => {
Ok(Self::Request(ActivationStateChangeRequest::parse(body)?))
}
tag::ACTIVATION_STATE_CHANGE_ACK => {
Ok(Self::Ack(ActivationStateChangeAck::parse(body)?))
}
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::ACTIVATION_STATE_CHANGE_REQUEST.as_u24(),
what: "power_manager",
}),
}
}
}
impl Serialize for PowerManagerApdu {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::Request(o) => o.serialized_len(),
Self::Ack(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::Request(o) => o.serialize_into(buf),
Self::Ack(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trips_and_bites() {
let r = ActivationStateChangeRequest {
activation_state: ActivationState::StandbyPassive,
};
let bytes = r.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x01, 0x00]);
assert_eq!(ActivationStateChangeRequest::parse(&bytes).unwrap(), r);
assert_eq!(r.activation_state.name(), "Standby-passive");
let other = ActivationStateChangeRequest {
activation_state: ActivationState::Reserved(5),
};
assert_ne!(bytes, other.to_bytes());
assert_eq!(other.to_bytes()[4], 0x05);
}
#[test]
fn ack_round_trips_and_bites() {
let a = ActivationStateChangeAck {
reply_code: ReplyCode::Busy,
};
let bytes = a.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0x01]);
assert_eq!(ActivationStateChangeAck::parse(&bytes).unwrap(), a);
assert_eq!(a.reply_code.name(), "Module busy, don't change state");
let other = ActivationStateChangeAck {
reply_code: ReplyCode::Ok,
};
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn dispatch_routes_both_tags() {
let req = ActivationStateChangeRequest {
activation_state: ActivationState::StandbyPassive,
}
.to_bytes();
assert!(matches!(
PowerManagerApdu::parse(&req).unwrap(),
PowerManagerApdu::Request(_)
));
let ack = ActivationStateChangeAck {
reply_code: ReplyCode::Ok,
}
.to_bytes();
let parsed = PowerManagerApdu::parse(&ack).unwrap();
assert!(matches!(parsed, PowerManagerApdu::Ack(_)));
assert_eq!(parsed.to_bytes(), ack);
}
}