1use crate::error::{Error, Result};
16use crate::objects;
17use crate::resource::ResourceId;
18use crate::tag::ApduTag;
19use alloc::vec::Vec;
20use broadcast_common::{Parse, Serialize};
21
22pub mod tag {
24 use crate::tag::ApduTag;
25 pub const PROFILE_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
27 pub const PROFILE_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
29 pub const PROFILE_CHANGED: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x12);
31 pub const MODULE_ID_SEND: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x13);
33 pub const MODULE_ID_COMMAND: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x14);
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40pub struct ProfileEnq;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize))]
45pub struct ProfileChanged;
46
47#[derive(Debug, Clone, PartialEq, Eq, Default)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize))]
50pub struct ProfileReply {
51 pub resources: Vec<ResourceId>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58pub struct ModuleIdSend {
59 pub module_id: u8,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67#[non_exhaustive]
68pub enum ModuleIdCommandKind {
69 Acknowledgement,
71 SetModuleId,
73 Reserved(u8),
75}
76
77impl ModuleIdCommandKind {
78 #[must_use]
80 pub fn from_u8(v: u8) -> Self {
81 match v {
82 0x01 => Self::Acknowledgement,
83 0x02 => Self::SetModuleId,
84 other => Self::Reserved(other),
85 }
86 }
87 #[must_use]
89 pub const fn to_u8(self) -> u8 {
90 match self {
91 Self::Acknowledgement => 0x01,
92 Self::SetModuleId => 0x02,
93 Self::Reserved(v) => v,
94 }
95 }
96 #[must_use]
98 pub fn name(&self) -> &'static str {
99 match self {
100 Self::Acknowledgement => "Acknowledgement",
101 Self::SetModuleId => "Set_ModuleID",
102 Self::Reserved(_) => "reserved",
103 }
104 }
105}
106broadcast_common::impl_spec_display!(ModuleIdCommandKind, Reserved);
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111pub struct ModuleIdCommand {
112 pub command: ModuleIdCommandKind,
114 pub module_id: u8,
116}
117
118impl<'a> Parse<'a> for ProfileEnq {
121 type Error = Error;
122 fn parse(bytes: &'a [u8]) -> Result<Self> {
123 objects::parse_empty_apdu(bytes, tag::PROFILE_ENQ, "profile_enq")?;
124 Ok(Self)
125 }
126}
127impl Serialize for ProfileEnq {
128 type Error = Error;
129 fn serialized_len(&self) -> usize {
130 objects::empty_apdu_len()
131 }
132 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
133 objects::serialize_empty_apdu(tag::PROFILE_ENQ, buf)
134 }
135}
136
137impl<'a> Parse<'a> for ProfileChanged {
138 type Error = Error;
139 fn parse(bytes: &'a [u8]) -> Result<Self> {
140 objects::parse_empty_apdu(bytes, tag::PROFILE_CHANGED, "profile_changed")?;
141 Ok(Self)
142 }
143}
144impl Serialize for ProfileChanged {
145 type Error = Error;
146 fn serialized_len(&self) -> usize {
147 objects::empty_apdu_len()
148 }
149 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
150 objects::serialize_empty_apdu(tag::PROFILE_CHANGED, buf)
151 }
152}
153
154impl<'a> Parse<'a> for ProfileReply {
157 type Error = Error;
158 fn parse(bytes: &'a [u8]) -> Result<Self> {
159 let body = objects::parse_apdu_header(bytes, tag::PROFILE_REPLY, "profile_reply")?;
160 if body.len() % ResourceId::LEN != 0 {
161 return Err(Error::InvalidObject {
162 what: "profile_reply",
163 reason: "body length is not a multiple of 4",
164 });
165 }
166 let mut resources = Vec::with_capacity(body.len() / ResourceId::LEN);
167 for chunk in body.chunks_exact(ResourceId::LEN) {
168 resources.push(ResourceId::parse(chunk)?);
169 }
170 Ok(Self { resources })
171 }
172}
173impl Serialize for ProfileReply {
174 type Error = Error;
175 fn serialized_len(&self) -> usize {
176 objects::apdu_len(self.resources.len() * ResourceId::LEN)
177 }
178 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
179 let body_len = self.resources.len() * ResourceId::LEN;
180 let mut pos = objects::write_apdu_header(tag::PROFILE_REPLY, body_len, buf)?;
181 for r in &self.resources {
182 pos += r.serialize_into(&mut buf[pos..])?;
183 }
184 Ok(pos)
185 }
186}
187
188const MODULE_ID_SEND_BODY: usize = 1;
192
193impl<'a> Parse<'a> for ModuleIdSend {
194 type Error = Error;
195 fn parse(bytes: &'a [u8]) -> Result<Self> {
196 let body = objects::parse_apdu_header(bytes, tag::MODULE_ID_SEND, "module_id_send")?;
197 if body.len() < MODULE_ID_SEND_BODY {
198 return Err(Error::BufferTooShort {
199 need: MODULE_ID_SEND_BODY,
200 have: body.len(),
201 what: "module_id_send",
202 });
203 }
204 Ok(Self {
205 module_id: body[0] & 0x3F,
206 })
207 }
208}
209impl Serialize for ModuleIdSend {
210 type Error = Error;
211 fn serialized_len(&self) -> usize {
212 objects::apdu_len(MODULE_ID_SEND_BODY)
213 }
214 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
215 let pos = objects::write_apdu_header(tag::MODULE_ID_SEND, MODULE_ID_SEND_BODY, buf)?;
216 buf[pos] = self.module_id & 0x3F;
218 Ok(pos + MODULE_ID_SEND_BODY)
219 }
220}
221
222const MODULE_ID_COMMAND_BODY: usize = 2;
226
227impl<'a> Parse<'a> for ModuleIdCommand {
228 type Error = Error;
229 fn parse(bytes: &'a [u8]) -> Result<Self> {
230 let body = objects::parse_apdu_header(bytes, tag::MODULE_ID_COMMAND, "module_id_command")?;
231 if body.len() < MODULE_ID_COMMAND_BODY {
232 return Err(Error::BufferTooShort {
233 need: MODULE_ID_COMMAND_BODY,
234 have: body.len(),
235 what: "module_id_command",
236 });
237 }
238 Ok(Self {
239 command: ModuleIdCommandKind::from_u8(body[0]),
240 module_id: body[1] & 0x3F,
241 })
242 }
243}
244impl Serialize for ModuleIdCommand {
245 type Error = Error;
246 fn serialized_len(&self) -> usize {
247 objects::apdu_len(MODULE_ID_COMMAND_BODY)
248 }
249 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
250 let pos = objects::write_apdu_header(tag::MODULE_ID_COMMAND, MODULE_ID_COMMAND_BODY, buf)?;
251 buf[pos] = self.command.to_u8();
252 buf[pos + 1] = self.module_id & 0x3F;
253 Ok(pos + MODULE_ID_COMMAND_BODY)
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize))]
260#[non_exhaustive]
261pub enum ResourceManagerV2Apdu {
262 ProfileEnq(ProfileEnq),
264 ProfileReply(ProfileReply),
266 ProfileChanged(ProfileChanged),
268 ModuleIdSend(ModuleIdSend),
270 ModuleIdCommand(ModuleIdCommand),
272}
273
274impl ResourceManagerV2Apdu {
275 pub fn parse(body: &[u8]) -> Result<Self> {
277 if body.len() < 3 {
278 return Err(Error::BufferTooShort {
279 need: 3,
280 have: body.len(),
281 what: "resource_manager_v2 apdu_tag",
282 });
283 }
284 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
285 match t {
286 tag::PROFILE_ENQ => Ok(Self::ProfileEnq(ProfileEnq::parse(body)?)),
287 tag::PROFILE_REPLY => Ok(Self::ProfileReply(ProfileReply::parse(body)?)),
288 tag::PROFILE_CHANGED => Ok(Self::ProfileChanged(ProfileChanged::parse(body)?)),
289 tag::MODULE_ID_SEND => Ok(Self::ModuleIdSend(ModuleIdSend::parse(body)?)),
290 tag::MODULE_ID_COMMAND => Ok(Self::ModuleIdCommand(ModuleIdCommand::parse(body)?)),
291 _ => Err(Error::UnexpectedApduTag {
292 got: t.as_u24(),
293 expected: tag::PROFILE_ENQ.as_u24(),
294 what: "resource_manager_v2",
295 }),
296 }
297 }
298}
299
300impl Serialize for ResourceManagerV2Apdu {
301 type Error = Error;
302 fn serialized_len(&self) -> usize {
303 match self {
304 Self::ProfileEnq(o) => o.serialized_len(),
305 Self::ProfileReply(o) => o.serialized_len(),
306 Self::ProfileChanged(o) => o.serialized_len(),
307 Self::ModuleIdSend(o) => o.serialized_len(),
308 Self::ModuleIdCommand(o) => o.serialized_len(),
309 }
310 }
311 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
312 match self {
313 Self::ProfileEnq(o) => o.serialize_into(buf),
314 Self::ProfileReply(o) => o.serialize_into(buf),
315 Self::ProfileChanged(o) => o.serialize_into(buf),
316 Self::ModuleIdSend(o) => o.serialize_into(buf),
317 Self::ModuleIdCommand(o) => o.serialize_into(buf),
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn profile_enq_round_trips() {
328 let bytes = ProfileEnq.to_bytes();
329 assert_eq!(bytes, [0x9F, 0x80, 0x10, 0x00]);
330 assert_eq!(ProfileEnq::parse(&bytes).unwrap(), ProfileEnq);
331 }
332
333 #[test]
334 fn profile_changed_round_trips() {
335 let bytes = ProfileChanged.to_bytes();
336 assert_eq!(bytes, [0x9F, 0x80, 0x12, 0x00]);
337 assert_eq!(ProfileChanged::parse(&bytes).unwrap(), ProfileChanged);
338 }
339
340 #[test]
341 fn profile_reply_multi_round_trips_and_bites() {
342 let p = ProfileReply {
343 resources: alloc::vec![ResourceId(0x0001_0042), ResourceId(0x0002_0042)],
344 };
345 let bytes = p.to_bytes();
346 assert_eq!(
348 bytes,
349 [
350 0x9F, 0x80, 0x11, 0x08, 0x00, 0x01, 0x00, 0x42, 0x00, 0x02, 0x00, 0x42
351 ]
352 );
353 assert_eq!(ProfileReply::parse(&bytes).unwrap(), p);
354 let mut other = p.clone();
355 other.resources[1] = ResourceId(0x0022_0041);
356 assert_ne!(bytes, other.to_bytes());
357 }
358
359 #[test]
360 fn module_id_send_round_trips_and_bites() {
361 let m = ModuleIdSend { module_id: 0x03 };
362 let bytes = m.to_bytes();
363 assert_eq!(bytes, [0x9F, 0x80, 0x13, 0x01, 0x03]);
364 assert_eq!(ModuleIdSend::parse(&bytes).unwrap(), m);
365 let parsed = ModuleIdSend::parse(&[0x9F, 0x80, 0x13, 0x01, 0xC3]).unwrap();
367 assert_eq!(parsed.module_id, 0x03);
368 let other = ModuleIdSend { module_id: 0x04 };
369 assert_ne!(bytes, other.to_bytes());
370 }
371
372 #[test]
373 fn module_id_command_round_trips_and_bites() {
374 let m = ModuleIdCommand {
375 command: ModuleIdCommandKind::SetModuleId,
376 module_id: 0x05,
377 };
378 let bytes = m.to_bytes();
379 assert_eq!(bytes, [0x9F, 0x80, 0x14, 0x02, 0x02, 0x05]);
380 assert_eq!(ModuleIdCommand::parse(&bytes).unwrap(), m);
381 assert_eq!(m.command.name(), "Set_ModuleID");
382 let mut other = m;
383 other.command = ModuleIdCommandKind::Acknowledgement;
384 assert_ne!(bytes, other.to_bytes());
385 }
386
387 #[test]
388 fn dispatch_routes_each_tag() {
389 let enq = ProfileEnq.to_bytes();
390 assert!(matches!(
391 ResourceManagerV2Apdu::parse(&enq).unwrap(),
392 ResourceManagerV2Apdu::ProfileEnq(_)
393 ));
394 let mic = ModuleIdCommand {
395 command: ModuleIdCommandKind::Acknowledgement,
396 module_id: 1,
397 }
398 .to_bytes();
399 let parsed = ResourceManagerV2Apdu::parse(&mic).unwrap();
400 assert!(matches!(parsed, ResourceManagerV2Apdu::ModuleIdCommand(_)));
401 assert_eq!(parsed.to_bytes(), mic);
403 }
404}