use crate::error::{Error, Result};
use crate::objects;
use crate::reserved::ReservedZero;
use broadcast_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",
}
}
}
broadcast_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",
}
}
}
broadcast_common::impl_spec_display!(ReplyCode, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ActivationStateChangeRequest {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub activation_state: ActivationState,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved: ReservedZero<4>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ActivationStateChangeAck {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
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 (length_form, 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",
});
}
crate::objects::reject_trailing_body(
body,
REQUEST_BODY,
"activation_state_change_request",
)?;
Ok(Self {
length_form,
activation_state: ActivationState::from_u8(body[0] & 0x0F),
reserved: ReservedZero::from_bits(body[0] >> 4),
})
}
}
impl Serialize for ActivationStateChangeRequest {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(REQUEST_BODY, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(
tag::ACTIVATION_STATE_CHANGE_REQUEST,
REQUEST_BODY,
self.length_form,
buf,
)?;
buf[pos] = (self.reserved.get() << 4) | (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 (length_form, 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",
});
}
crate::objects::reject_trailing_body(body, ACK_BODY, "activation_state_change_ack")?;
Ok(Self {
length_form,
reply_code: ReplyCode::from_u8(body[0]),
})
}
}
impl Serialize for ActivationStateChangeAck {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(ACK_BODY, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(
tag::ACTIVATION_STATE_CHANGE_ACK,
ACK_BODY,
self.length_form,
buf,
)?;
buf[pos] = self.reply_code.to_u8();
Ok(pos + ACK_BODY)
}
}
crate::dispatch::declare_resource_apdus! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum PowerManagerApdu ("power_manager") {
Request(ActivationStateChangeRequest) = tag::ACTIVATION_STATE_CHANGE_REQUEST,
Ack(ActivationStateChangeAck) = tag::ACTIVATION_STATE_CHANGE_ACK,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trips_and_bites() {
let r = ActivationStateChangeRequest {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
activation_state: ActivationState::StandbyPassive,
};
let bytes = r.try_to_bytes().unwrap();
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 {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
activation_state: ActivationState::Reserved(5),
};
assert_ne!(bytes, other.try_to_bytes().unwrap());
assert_eq!(other.try_to_bytes().unwrap()[4], 0x05);
}
#[test]
fn ack_round_trips_and_bites() {
let a = ActivationStateChangeAck {
length_form: crate::length::LengthForm::Minimal,
reply_code: ReplyCode::Busy,
};
let bytes = a.try_to_bytes().unwrap();
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 {
length_form: crate::length::LengthForm::Minimal,
reply_code: ReplyCode::Ok,
};
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn declared_dispatch_lists_tags_and_rejects_foreign_ones() {
let tags: alloc::vec::Vec<u32> =
PowerManagerApdu::TAGS.iter().map(|t| t.as_u24()).collect();
assert_eq!(tags, [0x9F_8000, 0x9F_8001]);
match PowerManagerApdu::parse(&[0x9F, 0x80, 0x02, 0x00]) {
Err(Error::UnexpectedApduTag {
got,
expected,
what,
}) => {
assert_eq!(got, 0x9F_8002);
assert_eq!(expected, 0x9F_8000);
assert_eq!(what, "power_manager");
}
other => panic!("expected UnexpectedApduTag, got {other:?}"),
}
assert!(matches!(
PowerManagerApdu::parse(&[0x9F, 0x80]),
Err(Error::BufferTooShort {
need: 3,
have: 2,
..
})
));
}
#[test]
fn dispatch_routes_both_tags() {
let req = ActivationStateChangeRequest {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
activation_state: ActivationState::StandbyPassive,
}
.try_to_bytes()
.unwrap();
assert!(matches!(
PowerManagerApdu::parse(&req).unwrap(),
PowerManagerApdu::Request(_)
));
let ack = ActivationStateChangeAck {
length_form: crate::length::LengthForm::Minimal,
reply_code: ReplyCode::Ok,
}
.try_to_bytes()
.unwrap();
let parsed = PowerManagerApdu::parse(&ack).unwrap();
assert!(matches!(parsed, PowerManagerApdu::Ack(_)));
assert_eq!(parsed.try_to_bytes().unwrap(), ack);
}
}