use crate::error::{Error, Result};
use crate::objects;
use crate::reserved::ReservedZero;
use crate::resource::ResourceId;
use alloc::vec::Vec;
use broadcast_common::len::need_in;
use broadcast_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const PROFILE_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
pub const PROFILE_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
pub const PROFILE_CHANGED: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x12);
pub const MODULE_ID_SEND: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x13);
pub const MODULE_ID_COMMAND: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x14);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProfileEnq {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProfileChanged {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProfileReply {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub resources: Vec<ResourceId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ModuleIdSend {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub module_id: u8,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved: ReservedZero<2>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ModuleIdCommandKind {
Acknowledgement,
SetModuleId,
Reserved(u8),
}
impl ModuleIdCommandKind {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x01 => Self::Acknowledgement,
0x02 => Self::SetModuleId,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Acknowledgement => 0x01,
Self::SetModuleId => 0x02,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Acknowledgement => "Acknowledgement",
Self::SetModuleId => "Set_ModuleID",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(ModuleIdCommandKind, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ModuleIdCommand {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub command: ModuleIdCommandKind,
pub module_id: u8,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved: ReservedZero<2>,
}
use crate::objects::resource_manager as v1;
impl<'a> Parse<'a> for ProfileEnq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let v = v1::ProfileEnq::parse(bytes)?;
Ok(Self {
length_form: v.length_form,
})
}
}
impl Serialize for ProfileEnq {
type Error = Error;
fn serialized_len(&self) -> usize {
v1::ProfileEnq {
length_form: self.length_form,
}
.serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
v1::ProfileEnq {
length_form: self.length_form,
}
.serialize_into(buf)
}
}
impl<'a> Parse<'a> for ProfileChanged {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let v = v1::ProfileChange::parse(bytes)?;
Ok(Self {
length_form: v.length_form,
})
}
}
impl Serialize for ProfileChanged {
type Error = Error;
fn serialized_len(&self) -> usize {
v1::ProfileChange {
length_form: self.length_form,
}
.serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
v1::ProfileChange {
length_form: self.length_form,
}
.serialize_into(buf)
}
}
impl<'a> Parse<'a> for ProfileReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let v = v1::Profile::parse(bytes)?;
Ok(Self {
length_form: v.length_form,
resources: v.resources,
})
}
}
impl Serialize for ProfileReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(self.resources.len() * ResourceId::LEN, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
v1::Profile {
length_form: self.length_form,
resources: self.resources.clone(),
}
.serialize_into(buf)
}
}
const MODULE_ID_SEND_BODY: usize = 1;
impl<'a> Parse<'a> for ModuleIdSend {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) =
objects::parse_apdu_header(bytes, tag::MODULE_ID_SEND, "module_id_send")?;
need_in(body, MODULE_ID_SEND_BODY, "module_id_send")?;
crate::objects::reject_trailing_body(body, MODULE_ID_SEND_BODY, "module_id_send")?;
Ok(Self {
length_form,
module_id: body[0] & 0x3F,
reserved: ReservedZero::from_bits(body[0] >> 6),
})
}
}
impl Serialize for ModuleIdSend {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(MODULE_ID_SEND_BODY, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(u64::from(self.module_id), 6, "module_id_send module_id")?;
let pos = objects::write_apdu_header(
tag::MODULE_ID_SEND,
MODULE_ID_SEND_BODY,
self.length_form,
buf,
)?;
buf[pos] = (self.reserved.get() << 6) | self.module_id;
Ok(pos + MODULE_ID_SEND_BODY)
}
}
const MODULE_ID_COMMAND_BODY: usize = 2;
impl<'a> Parse<'a> for ModuleIdCommand {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) =
objects::parse_apdu_header(bytes, tag::MODULE_ID_COMMAND, "module_id_command")?;
need_in(body, MODULE_ID_COMMAND_BODY, "module_id_command")?;
crate::objects::reject_trailing_body(body, MODULE_ID_COMMAND_BODY, "module_id_command")?;
Ok(Self {
length_form,
command: ModuleIdCommandKind::from_u8(body[0]),
module_id: body[1] & 0x3F,
reserved: ReservedZero::from_bits(body[1] >> 6),
})
}
}
impl Serialize for ModuleIdCommand {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(MODULE_ID_COMMAND_BODY, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(u64::from(self.module_id), 6, "module_id_command module_id")?;
let pos = objects::write_apdu_header(
tag::MODULE_ID_COMMAND,
MODULE_ID_COMMAND_BODY,
self.length_form,
buf,
)?;
buf[pos] = self.command.to_u8();
buf[pos + 1] = (self.reserved.get() << 6) | self.module_id;
Ok(pos + MODULE_ID_COMMAND_BODY)
}
}
crate::dispatch::declare_resource_apdus! {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ResourceManagerV2Apdu ("resource_manager_v2") {
ProfileEnq(ProfileEnq) = tag::PROFILE_ENQ,
ProfileReply(ProfileReply) = tag::PROFILE_REPLY,
ProfileChanged(ProfileChanged) = tag::PROFILE_CHANGED,
ModuleIdSend(ModuleIdSend) = tag::MODULE_ID_SEND,
ModuleIdCommand(ModuleIdCommand) = tag::MODULE_ID_COMMAND,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profile_enq_round_trips() {
let bytes = ProfileEnq::default().try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x80, 0x10, 0x00]);
assert_eq!(ProfileEnq::parse(&bytes).unwrap(), ProfileEnq::default());
}
#[test]
fn profile_changed_round_trips() {
let bytes = ProfileChanged::default().try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x80, 0x12, 0x00]);
assert_eq!(
ProfileChanged::parse(&bytes).unwrap(),
ProfileChanged::default()
);
}
#[test]
fn profile_reply_multi_round_trips_and_bites() {
let p = ProfileReply {
length_form: crate::length::LengthForm::Minimal,
resources: alloc::vec![ResourceId(0x0001_0042), ResourceId(0x0002_0042)],
};
let bytes = p.try_to_bytes().unwrap();
assert_eq!(
bytes,
[
0x9F, 0x80, 0x11, 0x08, 0x00, 0x01, 0x00, 0x42, 0x00, 0x02, 0x00, 0x42
]
);
assert_eq!(ProfileReply::parse(&bytes).unwrap(), p);
let mut other = p.clone();
other.resources[1] = ResourceId(0x0022_0041);
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn module_id_send_round_trips_and_bites() {
let m = ModuleIdSend {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
module_id: 0x03,
};
let bytes = m.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x80, 0x13, 0x01, 0x03]);
assert_eq!(ModuleIdSend::parse(&bytes).unwrap(), m);
let parsed = ModuleIdSend::parse(&[0x9F, 0x80, 0x13, 0x01, 0xC3]).unwrap();
assert_eq!(parsed.module_id, 0x03);
let other = ModuleIdSend {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
module_id: 0x04,
};
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn module_id_command_round_trips_and_bites() {
let m = ModuleIdCommand {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
command: ModuleIdCommandKind::SetModuleId,
module_id: 0x05,
};
let bytes = m.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x80, 0x14, 0x02, 0x02, 0x05]);
assert_eq!(ModuleIdCommand::parse(&bytes).unwrap(), m);
assert_eq!(m.command.name(), "Set_ModuleID");
let mut other = m;
other.command = ModuleIdCommandKind::Acknowledgement;
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn dispatch_routes_each_tag() {
let enq = ProfileEnq::default().try_to_bytes().unwrap();
assert!(matches!(
ResourceManagerV2Apdu::parse(&enq).unwrap(),
ResourceManagerV2Apdu::ProfileEnq(_)
));
let mic = ModuleIdCommand {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
command: ModuleIdCommandKind::Acknowledgement,
module_id: 1,
}
.try_to_bytes()
.unwrap();
let parsed = ResourceManagerV2Apdu::parse(&mic).unwrap();
assert!(matches!(parsed, ResourceManagerV2Apdu::ModuleIdCommand(_)));
assert_eq!(parsed.try_to_bytes().unwrap(), mic);
}
#[test]
fn oversized_module_id_send_is_rejected_not_wrapped() {
let m = ModuleIdSend {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
module_id: 0x43,
};
let mut buf = [0u8; 16];
assert!(matches!(
m.serialize_into(&mut buf),
Err(Error::InvalidObject { .. })
));
}
#[test]
fn max_module_id_send_still_serializes_and_round_trips() {
let m = ModuleIdSend {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
module_id: 0x3F,
}; let bytes = m.try_to_bytes().unwrap();
assert_eq!(ModuleIdSend::parse(&bytes).unwrap(), m);
}
#[test]
fn oversized_module_id_command_is_rejected_not_wrapped() {
let m = ModuleIdCommand {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
command: ModuleIdCommandKind::SetModuleId,
module_id: 0x43,
};
let mut buf = [0u8; 16];
assert!(matches!(
m.serialize_into(&mut buf),
Err(Error::InvalidObject { .. })
));
}
}