1use crate::error::{Error, Result};
16use crate::tag::ApduTag;
17use dvb_common::{Parse, Serialize};
18
19macro_rules! declare_apdus {
21 (
22 $lt:lifetime;
23 $( $variant:ident = $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
24 ) => {
25 #[derive(Debug, Clone, PartialEq, Eq)]
30 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
31 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
32 #[non_exhaustive]
33 pub enum AnyApdu<$lt> {
34 $(
35 #[allow(missing_docs)]
36 $variant($($path)::+ $(<$plt>)?),
37 )+
38 Unknown {
42 tag: ApduTag,
44 #[cfg_attr(feature = "serde", serde(with = "crate::objects::bytes_serde"))]
46 body: &$lt [u8],
47 },
48 }
49
50 $(
51 impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyApdu<$lt> {
52 fn from(v: $($path)::+ $(<$plt>)?) -> Self {
53 Self::$variant(v)
54 }
55 }
56 )+
57
58 impl<$lt> AnyApdu<$lt> {
59 pub const DISPATCHED_TAGS: &'static [ApduTag] = &[
62 $( <$($path)::+ as crate::traits::ApduDef>::TAG ),+
63 ];
64
65 #[must_use]
69 pub fn name(&self) -> &'static str {
70 match self {
71 $( Self::$variant(_) =>
72 <$($path)::+ as crate::traits::ApduDef>::NAME, )+
73 Self::Unknown { .. } => "UNKNOWN",
74 }
75 }
76
77 #[must_use]
79 pub fn tag(&self) -> ApduTag {
80 match self {
81 $( Self::$variant(_) =>
82 <$($path)::+ as crate::traits::ApduDef>::TAG, )+
83 Self::Unknown { tag, .. } => *tag,
84 }
85 }
86
87 pub fn parse(bytes: &$lt [u8]) -> Result<Self> {
90 if bytes.len() < 3 {
91 return Err(Error::BufferTooShort { need: 3, have: bytes.len(), what: "apdu_tag" });
92 }
93 let tag = ApduTag::from_bytes(bytes[0], bytes[1], bytes[2]);
94 match tag {
95 $( t if t == <$($path)::+ as crate::traits::ApduDef>::TAG =>
96 <$($path)::+>::parse(bytes).map(Self::$variant), )+
97 _ => {
98 let body = crate::objects::parse_apdu_header(bytes, tag, "unknown apdu")?;
99 Ok(Self::Unknown { tag, body })
100 }
101 }
102 }
103 }
104
105 impl Serialize for AnyApdu<'_> {
106 type Error = Error;
107 fn serialized_len(&self) -> usize {
108 match self {
109 $( Self::$variant(v) => v.serialized_len(), )+
110 Self::Unknown { body, .. } => crate::objects::apdu_len(body.len()),
111 }
112 }
113 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
114 match self {
115 $( Self::$variant(v) => v.serialize_into(buf), )+
116 Self::Unknown { tag, body } => {
117 let mut pos = crate::objects::write_apdu_header(*tag, body.len(), buf)?;
118 buf[pos..pos + body.len()].copy_from_slice(body);
119 pos += body.len();
120 Ok(pos)
121 }
122 }
123 }
124 }
125
126 #[cfg(test)]
127 mod macro_drift {
128 #[test]
129 fn tag_literals_match_apdu_def() {
130 use crate::traits::ApduDef;
131 $(
132 assert!(
133 !<$($path)::+ as ApduDef>::NAME.is_empty(),
134 concat!("empty NAME for ", stringify!($variant)),
135 );
136 assert_eq!(
140 <$($path)::+ as ApduDef>::TAG.to_bytes()[0],
141 crate::tag::APDU_TAG_PREFIX,
142 concat!("non-0x9F apdu_tag for ", stringify!($variant)),
143 );
144 )+
145 }
146 }
147 };
148}
149
150declare_apdus! {'a;
151 ProfileEnq = crate::objects::resource_manager::ProfileEnq,
152 Profile = crate::objects::resource_manager::Profile,
153 ProfileChange = crate::objects::resource_manager::ProfileChange,
154 ApplicationInfoEnq = crate::objects::application_info::ApplicationInfoEnq,
155 ApplicationInfo = crate::objects::application_info::ApplicationInfo<'a>,
156 EnterMenu = crate::objects::application_info::EnterMenu,
157 CaInfoEnq = crate::objects::ca_info::CaInfoEnq,
158 CaInfo = crate::objects::ca_info::CaInfo,
159 CaPmt = crate::objects::ca_pmt::CaPmt<'a>,
160 CaPmtReply = crate::objects::ca_pmt_reply::CaPmtReply,
161 DateTimeEnq = crate::objects::date_time::DateTimeEnq,
162 DateTime = crate::objects::date_time::DateTime,
163 CloseMmi = crate::objects::mmi_close::CloseMmi,
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::objects::ca_pmt::{CaPmt, CaPmtListManagement};
170
171 #[test]
172 fn dispatch_ca_pmt() {
173 let pmt = CaPmt {
174 list_management: CaPmtListManagement::Only,
175 program_number: 1,
176 version_number: 0,
177 current_next_indicator: true,
178 cmd_id: None,
179 program_ca_descriptors: &[],
180 streams: alloc::vec::Vec::new(),
181 };
182 let bytes = pmt.to_bytes();
183 let any = AnyApdu::parse(&bytes).unwrap();
184 assert_eq!(any.name(), "CA_PMT");
185 assert_eq!(any.tag(), crate::tag::CA_PMT);
186 assert_eq!(any.to_bytes(), bytes);
188 }
189
190 #[test]
191 fn unknown_tag_round_trips() {
192 let bytes = [0x9F, 0x88, 0x07, 0x02, 0xAA, 0xBB];
194 let any = AnyApdu::parse(&bytes).unwrap();
195 assert!(matches!(any, AnyApdu::Unknown { .. }));
196 assert_eq!(any.name(), "UNKNOWN");
197 assert_eq!(any.to_bytes(), bytes);
198 }
199
200 #[test]
201 fn dispatched_tags_listed() {
202 assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::CA_PMT));
203 assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::PROFILE_ENQ));
204 assert_eq!(AnyApdu::DISPATCHED_TAGS.len(), 13);
205 }
206}