1use crate::error::{Error, Result};
16use crate::tag::ApduTag;
17use broadcast_common::{Parse, Serialize};
18
19macro_rules! declare_apdus {
21 (
22 $lt:lifetime;
23 $( $variant:ident = $($path:ident)::+ $(<$plt:lifetime>)?
24 $( [ $( $alt:path ),+ ] )? ),+ $(,)?
25 ) => {
26 #[derive(Debug, Clone, PartialEq, Eq)]
31 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
32 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
33 #[non_exhaustive]
34 pub enum AnyApdu<$lt> {
35 $(
36 #[allow(missing_docs)]
37 $variant($($path)::+ $(<$plt>)?),
38 )+
39 Unknown {
43 tag: ApduTag,
45 #[cfg_attr(feature = "serde", serde(with = "crate::objects::bytes_serde"))]
47 body: &$lt [u8],
48 },
49 }
50
51 $(
52 impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyApdu<$lt> {
53 fn from(v: $($path)::+ $(<$plt>)?) -> Self {
54 Self::$variant(v)
55 }
56 }
57 )+
58
59 impl<$lt> AnyApdu<$lt> {
60 pub const DISPATCHED_TAGS: &'static [ApduTag] = &[
63 $( <$($path)::+ as crate::traits::ApduDef>::TAG
64 $(, $( $alt ),+ )? ),+
65 ];
66
67 #[must_use]
71 pub fn name(&self) -> &'static str {
72 match self {
73 $( Self::$variant(_) =>
74 <$($path)::+ as crate::traits::ApduDef>::NAME, )+
75 Self::Unknown { .. } => "UNKNOWN",
76 }
77 }
78
79 #[must_use]
81 pub fn tag(&self) -> ApduTag {
82 match self {
83 $( Self::$variant(_) =>
84 <$($path)::+ as crate::traits::ApduDef>::TAG, )+
85 Self::Unknown { tag, .. } => *tag,
86 }
87 }
88
89 pub fn parse(bytes: &$lt [u8]) -> Result<Self> {
92 if bytes.len() < 3 {
93 return Err(Error::BufferTooShort { need: 3, have: bytes.len(), what: "apdu_tag" });
94 }
95 let tag = ApduTag::from_bytes(bytes[0], bytes[1], bytes[2]);
96 match tag {
97 $( t if t == <$($path)::+ as crate::traits::ApduDef>::TAG
98 $( || $( t == $alt )||+ )? =>
99 <$($path)::+>::parse(bytes).map(Self::$variant), )+
100 _ => {
101 let body = crate::objects::parse_apdu_header(bytes, tag, "unknown apdu")?;
102 Ok(Self::Unknown { tag, body })
103 }
104 }
105 }
106 }
107
108 impl Serialize for AnyApdu<'_> {
109 type Error = Error;
110 fn serialized_len(&self) -> usize {
111 match self {
112 $( Self::$variant(v) => v.serialized_len(), )+
113 Self::Unknown { body, .. } => crate::objects::apdu_len(body.len()),
114 }
115 }
116 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
117 match self {
118 $( Self::$variant(v) => v.serialize_into(buf), )+
119 Self::Unknown { tag, body } => {
120 let mut pos = crate::objects::write_apdu_header(*tag, body.len(), buf)?;
121 buf[pos..pos + body.len()].copy_from_slice(body);
122 pos += body.len();
123 Ok(pos)
124 }
125 }
126 }
127 }
128
129 #[cfg(test)]
130 mod macro_drift {
131 #[test]
132 fn tag_literals_match_apdu_def() {
133 use crate::traits::ApduDef;
134 $(
135 assert!(
136 !<$($path)::+ as ApduDef>::NAME.is_empty(),
137 concat!("empty NAME for ", stringify!($variant)),
138 );
139 assert_eq!(
143 <$($path)::+ as ApduDef>::TAG.to_bytes()[0],
144 crate::tag::APDU_TAG_PREFIX,
145 concat!("non-0x9F apdu_tag for ", stringify!($variant)),
146 );
147 )+
148 }
149 }
150 };
151}
152
153declare_apdus! {'a;
154 ProfileEnq = crate::objects::resource_manager::ProfileEnq,
155 Profile = crate::objects::resource_manager::Profile,
156 ProfileChange = crate::objects::resource_manager::ProfileChange,
157 ApplicationInfoEnq = crate::objects::application_info::ApplicationInfoEnq,
158 ApplicationInfo = crate::objects::application_info::ApplicationInfo<'a>,
159 EnterMenu = crate::objects::application_info::EnterMenu,
160 CaInfoEnq = crate::objects::ca_info::CaInfoEnq,
161 CaInfo = crate::objects::ca_info::CaInfo,
162 CaPmt = crate::objects::ca_pmt::CaPmt<'a>,
163 CaPmtReply = crate::objects::ca_pmt_reply::CaPmtReply,
164 DateTimeEnq = crate::objects::date_time::DateTimeEnq,
165 DateTime = crate::objects::date_time::DateTime,
166 CloseMmi = crate::objects::mmi_close::CloseMmi,
167 Tune = crate::objects::host_control::Tune,
169 Replace = crate::objects::host_control::Replace,
170 ClearReplace = crate::objects::host_control::ClearReplace,
171 AskRelease = crate::objects::host_control::AskRelease,
172 Text = crate::objects::mmi_high::Text<'a> [crate::tag::TEXT_MORE],
174 Enq = crate::objects::mmi_high::Enq<'a>,
175 Answ = crate::objects::mmi_high::Answ<'a>,
176 Menu = crate::objects::mmi_high::Menu<'a> [crate::tag::MENU_MORE],
177 MenuAnsw = crate::objects::mmi_high::MenuAnsw,
178 List = crate::objects::mmi_high::List<'a> [crate::tag::LIST_MORE],
179 DisplayControl = crate::objects::mmi_display::DisplayControl,
181 DisplayReply = crate::objects::mmi_display::DisplayReply,
182 KeypadControl = crate::objects::mmi_display::KeypadControl,
183 Keypress = crate::objects::mmi_display::Keypress,
184 SubtitleSegment = crate::objects::mmi_display::SubtitleSegment<'a> [crate::tag::SUBTITLE_SEGMENT_MORE],
185 DisplayMessage = crate::objects::mmi_display::DisplayMessage,
186 SceneEndMark = crate::objects::mmi_display::SceneEndMark,
187 SceneDoneMessage = crate::objects::mmi_display::SceneDoneMessage,
188 SceneControl = crate::objects::mmi_display::SceneControl,
189 SubtitleDownload = crate::objects::mmi_display::SubtitleDownload<'a> [crate::tag::SUBTITLE_DOWNLOAD_MORE],
190 FlushDownload = crate::objects::mmi_display::FlushDownload,
191 DownloadReply = crate::objects::mmi_display::DownloadReply,
192 CommsCmd = crate::objects::low_speed_comms::CommsCmd<'a>,
194 ConnectionDescriptor = crate::objects::low_speed_comms::ConnectionDescriptor<'a>,
195 CommsReply = crate::objects::low_speed_comms::CommsReply,
196 CommsSend = crate::objects::low_speed_comms::CommsSend<'a> [crate::tag::COMMS_SEND_MORE],
197 CommsRcv = crate::objects::low_speed_comms::CommsRcv<'a> [crate::tag::COMMS_RCV_MORE],
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::objects::ca_pmt::{CaPmt, CaPmtListManagement};
204
205 #[test]
206 fn dispatch_ca_pmt() {
207 let pmt = CaPmt {
208 list_management: CaPmtListManagement::Only,
209 program_number: 1,
210 version_number: 0,
211 current_next_indicator: true,
212 cmd_id: None,
213 program_ca_descriptors: &[],
214 streams: alloc::vec::Vec::new(),
215 };
216 let bytes = pmt.to_bytes();
217 let any = AnyApdu::parse(&bytes).unwrap();
218 assert_eq!(any.name(), "CA_PMT");
219 assert_eq!(any.tag(), crate::tag::CA_PMT);
220 assert_eq!(any.to_bytes(), bytes);
222 }
223
224 #[test]
225 fn unknown_tag_round_trips() {
226 let bytes = [0x9F, 0x90, 0x01, 0x02, 0xAA, 0xBB];
228 let any = AnyApdu::parse(&bytes).unwrap();
229 assert!(matches!(any, AnyApdu::Unknown { .. }));
230 assert_eq!(any.name(), "UNKNOWN");
231 assert_eq!(any.to_bytes(), bytes);
232 }
233
234 #[test]
235 fn dispatches_more_tag_to_same_variant() {
236 use crate::objects::mmi_high::Text;
237 let t = Text {
239 more: true,
240 text_chars: b"HI",
241 };
242 let bytes = t.to_bytes();
243 assert_eq!(bytes[2], 0x04);
244 let any = AnyApdu::parse(&bytes).unwrap();
245 assert_eq!(any.name(), "TEXT");
246 assert_eq!(any.to_bytes(), bytes);
247 }
248
249 #[test]
250 fn dispatched_tags_listed() {
251 assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::CA_PMT));
252 assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::PROFILE_ENQ));
253 assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::TEXT_MORE));
254 assert!(AnyApdu::DISPATCHED_TAGS.contains(&crate::tag::COMMS_SEND_MORE));
255 assert_eq!(AnyApdu::DISPATCHED_TAGS.len(), 40 + 7);
257 }
258}