1use crate::error::{Error, Result};
16use crate::objects;
17use crate::tag::ApduTag;
18use broadcast_common::{Parse, Serialize};
19
20pub mod tag {
22 use crate::tag::ApduTag;
23 pub const REQUEST_START: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
25 pub const REQUEST_START_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
27 pub const FILE_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
29 pub const FILE_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
31 pub const APP_ABORT_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
33 pub const APP_ABORT_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x05);
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[non_exhaustive]
41pub enum AckCode {
42 Ok,
44 WrongApi,
46 ApiBusy,
48 DomainSpecificApiBusy(u8),
50 Reserved(u8),
52}
53
54impl AckCode {
55 #[must_use]
57 pub fn from_u8(v: u8) -> Self {
58 match v {
59 0x01 => Self::Ok,
60 0x02 => Self::WrongApi,
61 0x03 => Self::ApiBusy,
62 0x80..=0xFF => Self::DomainSpecificApiBusy(v),
63 other => Self::Reserved(other),
64 }
65 }
66 #[must_use]
68 pub const fn to_u8(self) -> u8 {
69 match self {
70 Self::Ok => 0x01,
71 Self::WrongApi => 0x02,
72 Self::ApiBusy => 0x03,
73 Self::DomainSpecificApiBusy(v) | Self::Reserved(v) => v,
74 }
75 }
76 #[must_use]
78 pub fn name(&self) -> &'static str {
79 match self {
80 Self::Ok => "OK",
81 Self::WrongApi => "Wrong API",
82 Self::ApiBusy => "API busy",
83 Self::DomainSpecificApiBusy(_) => "Domain specific API busy",
84 Self::Reserved(_) => "reserved",
85 }
86 }
87}
88broadcast_common::impl_spec_display!(AckCode, DomainSpecificApiBusy, Reserved);
89
90#[derive(Debug, Clone, PartialEq, Eq, Default)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct RequestStart<'a> {
94 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
96 pub app_domain_identifier: &'a [u8],
97 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
99 pub initial_object: &'a [u8],
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize))]
105pub struct RequestStartAck {
106 pub ack_code: AckCode,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Default)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113pub struct FileReq<'a> {
114 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
116 pub file_name: &'a [u8],
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Default)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize))]
123pub struct FileAck<'a> {
124 pub file_ok: bool,
126 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
128 pub file: &'a [u8],
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Default)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize))]
135pub struct AppAbortReq<'a> {
136 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
138 pub abort_req_code: &'a [u8],
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Default)]
144#[cfg_attr(feature = "serde", derive(serde::Serialize))]
145pub struct AppAbortAck<'a> {
146 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
148 pub abort_ack_code: &'a [u8],
149}
150
151const REQUEST_START_FIXED: usize = 2;
155
156impl<'a> Parse<'a> for RequestStart<'a> {
157 type Error = Error;
158 fn parse(bytes: &'a [u8]) -> Result<Self> {
159 let body = objects::parse_apdu_header(bytes, tag::REQUEST_START, "RequestStart")?;
160 if body.len() < REQUEST_START_FIXED {
161 return Err(Error::BufferTooShort {
162 need: REQUEST_START_FIXED,
163 have: body.len(),
164 what: "RequestStart",
165 });
166 }
167 let domain_len = body[0] as usize;
168 let object_len = body[1] as usize;
169 let need = REQUEST_START_FIXED + domain_len + object_len;
170 if body.len() < need {
171 return Err(Error::BufferTooShort {
172 need,
173 have: body.len(),
174 what: "RequestStart",
175 });
176 }
177 let domain_start = REQUEST_START_FIXED;
178 let object_start = domain_start + domain_len;
179 Ok(Self {
180 app_domain_identifier: &body[domain_start..object_start],
181 initial_object: &body[object_start..object_start + object_len],
182 })
183 }
184}
185impl Serialize for RequestStart<'_> {
186 type Error = Error;
187 fn serialized_len(&self) -> usize {
188 objects::apdu_len(
189 REQUEST_START_FIXED + self.app_domain_identifier.len() + self.initial_object.len(),
190 )
191 }
192 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
193 let domain_len = self.app_domain_identifier.len();
194 let object_len = self.initial_object.len();
195 if domain_len > u8::MAX as usize {
196 return Err(Error::InvalidObject {
197 what: "RequestStart",
198 reason: "AppDomainIdentifier longer than 255 bytes",
199 });
200 }
201 if object_len > u8::MAX as usize {
202 return Err(Error::InvalidObject {
203 what: "RequestStart",
204 reason: "InitialObject longer than 255 bytes",
205 });
206 }
207 let body_len = REQUEST_START_FIXED + domain_len + object_len;
208 let mut pos = objects::write_apdu_header(tag::REQUEST_START, body_len, buf)?;
209 buf[pos] = domain_len as u8;
210 buf[pos + 1] = object_len as u8;
211 pos += REQUEST_START_FIXED;
212 buf[pos..pos + domain_len].copy_from_slice(self.app_domain_identifier);
213 pos += domain_len;
214 buf[pos..pos + object_len].copy_from_slice(self.initial_object);
215 Ok(pos + object_len)
216 }
217}
218
219const REQUEST_START_ACK_BODY: usize = 1;
222
223impl<'a> Parse<'a> for RequestStartAck {
224 type Error = Error;
225 fn parse(bytes: &'a [u8]) -> Result<Self> {
226 let body = objects::parse_apdu_header(bytes, tag::REQUEST_START_ACK, "RequestStartAck")?;
227 if body.len() < REQUEST_START_ACK_BODY {
228 return Err(Error::BufferTooShort {
229 need: REQUEST_START_ACK_BODY,
230 have: body.len(),
231 what: "RequestStartAck",
232 });
233 }
234 Ok(Self {
235 ack_code: AckCode::from_u8(body[0]),
236 })
237 }
238}
239impl Serialize for RequestStartAck {
240 type Error = Error;
241 fn serialized_len(&self) -> usize {
242 objects::apdu_len(REQUEST_START_ACK_BODY)
243 }
244 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
245 let pos = objects::write_apdu_header(tag::REQUEST_START_ACK, REQUEST_START_ACK_BODY, buf)?;
246 buf[pos] = self.ack_code.to_u8();
247 Ok(pos + REQUEST_START_ACK_BODY)
248 }
249}
250
251impl<'a> Parse<'a> for FileReq<'a> {
254 type Error = Error;
255 fn parse(bytes: &'a [u8]) -> Result<Self> {
256 let body = objects::parse_apdu_header(bytes, tag::FILE_REQ, "FileReq")?;
257 Ok(Self { file_name: body })
258 }
259}
260impl Serialize for FileReq<'_> {
261 type Error = Error;
262 fn serialized_len(&self) -> usize {
263 objects::apdu_len(self.file_name.len())
264 }
265 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
266 let body_len = self.file_name.len();
267 let pos = objects::write_apdu_header(tag::FILE_REQ, body_len, buf)?;
268 buf[pos..pos + body_len].copy_from_slice(self.file_name);
269 Ok(pos + body_len)
270 }
271}
272
273const FILE_OK_BIT: u8 = 0x01;
278
279impl<'a> Parse<'a> for FileAck<'a> {
280 type Error = Error;
281 fn parse(bytes: &'a [u8]) -> Result<Self> {
282 let body = objects::parse_apdu_header(bytes, tag::FILE_ACK, "FileAck")?;
283 if body.is_empty() {
284 return Err(Error::BufferTooShort {
285 need: 1,
286 have: 0,
287 what: "FileAck",
288 });
289 }
290 Ok(Self {
291 file_ok: body[0] & FILE_OK_BIT != 0,
292 file: &body[1..],
293 })
294 }
295}
296impl Serialize for FileAck<'_> {
297 type Error = Error;
298 fn serialized_len(&self) -> usize {
299 objects::apdu_len(1 + self.file.len())
300 }
301 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
302 let body_len = 1 + self.file.len();
303 let mut pos = objects::write_apdu_header(tag::FILE_ACK, body_len, buf)?;
304 buf[pos] = u8::from(self.file_ok);
305 pos += 1;
306 buf[pos..pos + self.file.len()].copy_from_slice(self.file);
307 Ok(pos + self.file.len())
308 }
309}
310
311impl<'a> Parse<'a> for AppAbortReq<'a> {
314 type Error = Error;
315 fn parse(bytes: &'a [u8]) -> Result<Self> {
316 let body = objects::parse_apdu_header(bytes, tag::APP_ABORT_REQ, "AppAbortReq")?;
317 Ok(Self {
318 abort_req_code: body,
319 })
320 }
321}
322impl Serialize for AppAbortReq<'_> {
323 type Error = Error;
324 fn serialized_len(&self) -> usize {
325 objects::apdu_len(self.abort_req_code.len())
326 }
327 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
328 let body_len = self.abort_req_code.len();
329 let pos = objects::write_apdu_header(tag::APP_ABORT_REQ, body_len, buf)?;
330 buf[pos..pos + body_len].copy_from_slice(self.abort_req_code);
331 Ok(pos + body_len)
332 }
333}
334
335impl<'a> Parse<'a> for AppAbortAck<'a> {
336 type Error = Error;
337 fn parse(bytes: &'a [u8]) -> Result<Self> {
338 let body = objects::parse_apdu_header(bytes, tag::APP_ABORT_ACK, "AppAbortAck")?;
339 Ok(Self {
340 abort_ack_code: body,
341 })
342 }
343}
344impl Serialize for AppAbortAck<'_> {
345 type Error = Error;
346 fn serialized_len(&self) -> usize {
347 objects::apdu_len(self.abort_ack_code.len())
348 }
349 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
350 let body_len = self.abort_ack_code.len();
351 let pos = objects::write_apdu_header(tag::APP_ABORT_ACK, body_len, buf)?;
352 buf[pos..pos + body_len].copy_from_slice(self.abort_ack_code);
353 Ok(pos + body_len)
354 }
355}
356
357#[derive(Debug, Clone, PartialEq, Eq)]
359#[cfg_attr(feature = "serde", derive(serde::Serialize))]
360#[non_exhaustive]
361pub enum ApplicationMmiApdu<'a> {
362 RequestStart(RequestStart<'a>),
364 RequestStartAck(RequestStartAck),
366 FileReq(FileReq<'a>),
368 FileAck(FileAck<'a>),
370 AppAbortReq(AppAbortReq<'a>),
372 AppAbortAck(AppAbortAck<'a>),
374}
375
376impl<'a> ApplicationMmiApdu<'a> {
377 pub fn parse(body: &'a [u8]) -> Result<Self> {
379 if body.len() < 3 {
380 return Err(Error::BufferTooShort {
381 need: 3,
382 have: body.len(),
383 what: "application_mmi apdu_tag",
384 });
385 }
386 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
387 match t {
388 tag::REQUEST_START => Ok(Self::RequestStart(RequestStart::parse(body)?)),
389 tag::REQUEST_START_ACK => Ok(Self::RequestStartAck(RequestStartAck::parse(body)?)),
390 tag::FILE_REQ => Ok(Self::FileReq(FileReq::parse(body)?)),
391 tag::FILE_ACK => Ok(Self::FileAck(FileAck::parse(body)?)),
392 tag::APP_ABORT_REQ => Ok(Self::AppAbortReq(AppAbortReq::parse(body)?)),
393 tag::APP_ABORT_ACK => Ok(Self::AppAbortAck(AppAbortAck::parse(body)?)),
394 _ => Err(Error::UnexpectedApduTag {
395 got: t.as_u24(),
396 expected: tag::REQUEST_START.as_u24(),
397 what: "application_mmi",
398 }),
399 }
400 }
401}
402
403impl Serialize for ApplicationMmiApdu<'_> {
404 type Error = Error;
405 fn serialized_len(&self) -> usize {
406 match self {
407 Self::RequestStart(o) => o.serialized_len(),
408 Self::RequestStartAck(o) => o.serialized_len(),
409 Self::FileReq(o) => o.serialized_len(),
410 Self::FileAck(o) => o.serialized_len(),
411 Self::AppAbortReq(o) => o.serialized_len(),
412 Self::AppAbortAck(o) => o.serialized_len(),
413 }
414 }
415 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
416 match self {
417 Self::RequestStart(o) => o.serialize_into(buf),
418 Self::RequestStartAck(o) => o.serialize_into(buf),
419 Self::FileReq(o) => o.serialize_into(buf),
420 Self::FileAck(o) => o.serialize_into(buf),
421 Self::AppAbortReq(o) => o.serialize_into(buf),
422 Self::AppAbortAck(o) => o.serialize_into(buf),
423 }
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn request_start_round_trips_and_bites() {
433 let req = RequestStart {
434 app_domain_identifier: &[0x01, 0x02, 0x03],
435 initial_object: &[0xAA, 0xBB],
436 };
437 let bytes = req.to_bytes();
438 assert_eq!(
440 bytes,
441 [
442 0x9F, 0x80, 0x00, 0x07, 0x03, 0x02, 0x01, 0x02, 0x03, 0xAA, 0xBB
443 ]
444 );
445 assert_eq!(RequestStart::parse(&bytes).unwrap(), req);
446 let other = RequestStart {
447 app_domain_identifier: &[0x01, 0x02, 0x04],
448 initial_object: &[0xAA, 0xBB],
449 };
450 assert_ne!(bytes, other.to_bytes());
451 }
452
453 #[test]
454 fn request_start_empty_strings() {
455 let req = RequestStart {
456 app_domain_identifier: &[],
457 initial_object: &[],
458 };
459 let bytes = req.to_bytes();
460 assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x02, 0x00, 0x00]);
461 assert_eq!(RequestStart::parse(&bytes).unwrap(), req);
462 }
463
464 #[test]
465 fn request_start_ack_codes() {
466 for (byte, code) in [
467 (0x01u8, AckCode::Ok),
468 (0x02, AckCode::WrongApi),
469 (0x03, AckCode::ApiBusy),
470 (0x90, AckCode::DomainSpecificApiBusy(0x90)),
471 (0x00, AckCode::Reserved(0x00)),
472 (0x40, AckCode::Reserved(0x40)),
473 ] {
474 let ack = RequestStartAck {
475 ack_code: AckCode::from_u8(byte),
476 };
477 assert_eq!(ack.ack_code, code);
478 let bytes = ack.to_bytes();
479 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, byte]);
480 assert_eq!(RequestStartAck::parse(&bytes).unwrap(), ack);
481 }
482 }
483
484 #[test]
485 fn file_req_round_trips() {
486 let req = FileReq {
487 file_name: b"app.bin",
488 };
489 let bytes = req.to_bytes();
490 assert_eq!(bytes[..4], [0x9F, 0x80, 0x02, 0x07]);
491 assert_eq!(&bytes[4..], b"app.bin");
492 assert_eq!(FileReq::parse(&bytes).unwrap(), req);
493 }
494
495 #[test]
496 fn file_ack_round_trips_and_bites() {
497 let ack = FileAck {
498 file_ok: true,
499 file: &[0xDE, 0xAD, 0xBE, 0xEF],
500 };
501 let bytes = ack.to_bytes();
502 assert_eq!(
504 bytes,
505 [0x9F, 0x80, 0x03, 0x05, 0x01, 0xDE, 0xAD, 0xBE, 0xEF]
506 );
507 assert_eq!(FileAck::parse(&bytes).unwrap(), ack);
508 let unavailable = FileAck {
510 file_ok: false,
511 file: &[],
512 };
513 let bytes = unavailable.to_bytes();
514 assert_eq!(bytes, [0x9F, 0x80, 0x03, 0x01, 0x00]);
515 assert_eq!(FileAck::parse(&bytes).unwrap(), unavailable);
516 let parsed = FileAck::parse(&[0x9F, 0x80, 0x03, 0x01, 0xFF]).unwrap();
518 assert!(parsed.file_ok);
519 }
520
521 #[test]
522 fn app_abort_req_and_ack_round_trip() {
523 let req = AppAbortReq {
524 abort_req_code: &[0x11, 0x22],
525 };
526 let bytes = req.to_bytes();
527 assert_eq!(bytes, [0x9F, 0x80, 0x04, 0x02, 0x11, 0x22]);
528 assert_eq!(AppAbortReq::parse(&bytes).unwrap(), req);
529
530 let ack = AppAbortAck {
531 abort_ack_code: &[0x33],
532 };
533 let bytes = ack.to_bytes();
534 assert_eq!(bytes, [0x9F, 0x80, 0x05, 0x01, 0x33]);
535 assert_eq!(AppAbortAck::parse(&bytes).unwrap(), ack);
536 }
537
538 #[test]
539 fn dispatch_routes_each_tag() {
540 let rs = RequestStart {
541 app_domain_identifier: &[0x01],
542 initial_object: &[],
543 }
544 .to_bytes();
545 assert!(matches!(
546 ApplicationMmiApdu::parse(&rs).unwrap(),
547 ApplicationMmiApdu::RequestStart(_)
548 ));
549 let aaa = AppAbortAck {
550 abort_ack_code: &[0x01],
551 }
552 .to_bytes();
553 let parsed = ApplicationMmiApdu::parse(&aaa).unwrap();
554 assert!(matches!(parsed, ApplicationMmiApdu::AppAbortAck(_)));
555 assert_eq!(parsed.to_bytes(), aaa);
556 }
557}