use crate::error::{Error, Result};
use crate::tag::ApduTag;
use broadcast_common::{Parse, Serialize};
macro_rules! declare_apdus {
(
$lt:lifetime;
$( $variant:ident = $($path:ident)::+ $(<$plt:lifetime>)?
$( [ $( $alt:path ),+ ] )? ),+ $(,)?
) => {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum AnyApdu<$lt> {
$(
#[allow(missing_docs)]
$variant($($path)::+ $(<$plt>)?),
)+
Unknown {
tag: ApduTag,
#[cfg_attr(feature = "serde", serde(with = "crate::objects::bytes_serde"))]
body: &$lt [u8],
},
}
$(
impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyApdu<$lt> {
fn from(v: $($path)::+ $(<$plt>)?) -> Self {
Self::$variant(v)
}
}
)+
impl<$lt> AnyApdu<$lt> {
pub const DISPATCHED_TAGS: &'static [ApduTag] = &[
$( <$($path)::+ as crate::traits::ApduDef>::TAG
$(, $( $alt ),+ )? ),+
];
#[must_use]
pub fn name(&self) -> &'static str {
match self {
$( Self::$variant(_) =>
<$($path)::+ as crate::traits::ApduDef>::NAME, )+
Self::Unknown { .. } => "UNKNOWN",
}
}
#[must_use]
pub fn tag(&self) -> ApduTag {
match self {
$( Self::$variant(_) =>
<$($path)::+ as crate::traits::ApduDef>::TAG, )+
Self::Unknown { tag, .. } => *tag,
}
}
pub fn parse(bytes: &$lt [u8]) -> Result<Self> {
if bytes.len() < 3 {
return Err(Error::BufferTooShort { need: 3, have: bytes.len(), what: "apdu_tag" });
}
let tag = ApduTag::from_bytes(bytes[0], bytes[1], bytes[2]);
match tag {
$( t if t == <$($path)::+ as crate::traits::ApduDef>::TAG
$( || $( t == $alt )||+ )? =>
<$($path)::+>::parse(bytes).map(Self::$variant), )+
_ => {
let body = crate::objects::parse_apdu_header(bytes, tag, "unknown apdu")?;
Ok(Self::Unknown { tag, body })
}
}
}
}
impl Serialize for AnyApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
$( Self::$variant(v) => v.serialized_len(), )+
Self::Unknown { body, .. } => crate::objects::apdu_len(body.len()),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
$( Self::$variant(v) => v.serialize_into(buf), )+
Self::Unknown { tag, body } => {
let mut pos = crate::objects::write_apdu_header(*tag, body.len(), buf)?;
buf[pos..pos + body.len()].copy_from_slice(body);
pos += body.len();
Ok(pos)
}
}
}
}
#[cfg(test)]
mod macro_drift {
#[test]
fn tag_literals_match_apdu_def() {
use crate::traits::ApduDef;
$(
assert!(
!<$($path)::+ as ApduDef>::NAME.is_empty(),
concat!("empty NAME for ", stringify!($variant)),
);
assert_eq!(
<$($path)::+ as ApduDef>::TAG.to_bytes()[0],
crate::tag::APDU_TAG_PREFIX,
concat!("non-0x9F apdu_tag for ", stringify!($variant)),
);
)+
}
}
};
}
declare_apdus! {'a;
ProfileEnq = crate::objects::resource_manager::ProfileEnq,
Profile = crate::objects::resource_manager::Profile,
ProfileChange = crate::objects::resource_manager::ProfileChange,
ApplicationInfoEnq = crate::objects::application_info::ApplicationInfoEnq,
ApplicationInfo = crate::objects::application_info::ApplicationInfo<'a>,
EnterMenu = crate::objects::application_info::EnterMenu,
CaInfoEnq = crate::objects::ca_info::CaInfoEnq,
CaInfo = crate::objects::ca_info::CaInfo,
CaPmt = crate::objects::ca_pmt::CaPmt<'a>,
CaPmtReply = crate::objects::ca_pmt_reply::CaPmtReply,
DateTimeEnq = crate::objects::date_time::DateTimeEnq,
DateTime = crate::objects::date_time::DateTime,
CloseMmi = crate::objects::mmi_close::CloseMmi,
Tune = crate::objects::host_control::Tune,
Replace = crate::objects::host_control::Replace,
ClearReplace = crate::objects::host_control::ClearReplace,
AskRelease = crate::objects::host_control::AskRelease,
Text = crate::objects::mmi_high::Text<'a> [crate::tag::TEXT_MORE],
Enq = crate::objects::mmi_high::Enq<'a>,
Answ = crate::objects::mmi_high::Answ<'a>,
Menu = crate::objects::mmi_high::Menu<'a> [crate::tag::MENU_MORE],
MenuAnsw = crate::objects::mmi_high::MenuAnsw,
List = crate::objects::mmi_high::List<'a> [crate::tag::LIST_MORE],
DisplayControl = crate::objects::mmi_display::DisplayControl,
DisplayReply = crate::objects::mmi_display::DisplayReply,
KeypadControl = crate::objects::mmi_display::KeypadControl,
Keypress = crate::objects::mmi_display::Keypress,
SubtitleSegment = crate::objects::mmi_display::SubtitleSegment<'a> [crate::tag::SUBTITLE_SEGMENT_MORE],
DisplayMessage = crate::objects::mmi_display::DisplayMessage,
SceneEndMark = crate::objects::mmi_display::SceneEndMark,
SceneDoneMessage = crate::objects::mmi_display::SceneDoneMessage,
SceneControl = crate::objects::mmi_display::SceneControl,
SubtitleDownload = crate::objects::mmi_display::SubtitleDownload<'a> [crate::tag::SUBTITLE_DOWNLOAD_MORE],
FlushDownload = crate::objects::mmi_display::FlushDownload,
DownloadReply = crate::objects::mmi_display::DownloadReply,
CommsCmd = crate::objects::low_speed_comms::CommsCmd<'a>,
ConnectionDescriptor = crate::objects::low_speed_comms::ConnectionDescriptor<'a>,
CommsReply = crate::objects::low_speed_comms::CommsReply,
CommsSend = crate::objects::low_speed_comms::CommsSend<'a> [crate::tag::COMMS_SEND_MORE],
CommsRcv = crate::objects::low_speed_comms::CommsRcv<'a> [crate::tag::COMMS_RCV_MORE],
}
#[cfg(test)]
mod tests {
use super::*;
use crate::objects::ca_pmt::{CaPmt, CaPmtListManagement};
#[test]
fn dispatch_ca_pmt() {
let pmt = CaPmt {
list_management: CaPmtListManagement::Only,
program_number: 1,
version_number: 0,
current_next_indicator: true,
cmd_id: None,
program_ca_descriptors: &[],
streams: alloc::vec::Vec::new(),
};
let bytes = pmt.to_bytes();
let any = AnyApdu::parse(&bytes).unwrap();
assert_eq!(any.name(), "CA_PMT");
assert_eq!(any.tag(), crate::tag::CA_PMT);
assert_eq!(any.to_bytes(), bytes);
}
#[test]
fn unknown_tag_round_trips() {
let bytes = [0x9F, 0x90, 0x01, 0x02, 0xAA, 0xBB];
let any = AnyApdu::parse(&bytes).unwrap();
assert!(matches!(any, AnyApdu::Unknown { .. }));
assert_eq!(any.name(), "UNKNOWN");
assert_eq!(any.to_bytes(), bytes);
}
#[test]
fn dispatches_more_tag_to_same_variant() {
use crate::objects::mmi_high::Text;
let t = Text {
more: true,
text_chars: b"HI",
};
let bytes = t.to_bytes();
assert_eq!(bytes[2], 0x04);
let any = AnyApdu::parse(&bytes).unwrap();
assert_eq!(any.name(), "TEXT");
assert_eq!(any.to_bytes(), bytes);
}
#[test]
fn dispatched_tags_listed() {
assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::CA_PMT));
assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::PROFILE_ENQ));
assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::TEXT_MORE));
assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::COMMS_SEND_MORE));
assert_eq!(AnyApdu::DISPATCHED_TAGS.len(), 40 + 7);
}
}