1use crate::error::{Error, Result};
16use crate::objects;
17use crate::tag::ApduTag;
18use dvb_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}
88dvb_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 [0x9F, 0x80, 0x00, 0x07, 0x03, 0x02, 0x01, 0x02, 0x03, 0xAA, 0xBB]
442 );
443 assert_eq!(RequestStart::parse(&bytes).unwrap(), req);
444 let other = RequestStart {
445 app_domain_identifier: &[0x01, 0x02, 0x04],
446 initial_object: &[0xAA, 0xBB],
447 };
448 assert_ne!(bytes, other.to_bytes());
449 }
450
451 #[test]
452 fn request_start_empty_strings() {
453 let req = RequestStart {
454 app_domain_identifier: &[],
455 initial_object: &[],
456 };
457 let bytes = req.to_bytes();
458 assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x02, 0x00, 0x00]);
459 assert_eq!(RequestStart::parse(&bytes).unwrap(), req);
460 }
461
462 #[test]
463 fn request_start_ack_codes() {
464 for (byte, code) in [
465 (0x01u8, AckCode::Ok),
466 (0x02, AckCode::WrongApi),
467 (0x03, AckCode::ApiBusy),
468 (0x90, AckCode::DomainSpecificApiBusy(0x90)),
469 (0x00, AckCode::Reserved(0x00)),
470 (0x40, AckCode::Reserved(0x40)),
471 ] {
472 let ack = RequestStartAck {
473 ack_code: AckCode::from_u8(byte),
474 };
475 assert_eq!(ack.ack_code, code);
476 let bytes = ack.to_bytes();
477 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, byte]);
478 assert_eq!(RequestStartAck::parse(&bytes).unwrap(), ack);
479 }
480 }
481
482 #[test]
483 fn file_req_round_trips() {
484 let req = FileReq {
485 file_name: b"app.bin",
486 };
487 let bytes = req.to_bytes();
488 assert_eq!(bytes[..4], [0x9F, 0x80, 0x02, 0x07]);
489 assert_eq!(&bytes[4..], b"app.bin");
490 assert_eq!(FileReq::parse(&bytes).unwrap(), req);
491 }
492
493 #[test]
494 fn file_ack_round_trips_and_bites() {
495 let ack = FileAck {
496 file_ok: true,
497 file: &[0xDE, 0xAD, 0xBE, 0xEF],
498 };
499 let bytes = ack.to_bytes();
500 assert_eq!(
502 bytes,
503 [0x9F, 0x80, 0x03, 0x05, 0x01, 0xDE, 0xAD, 0xBE, 0xEF]
504 );
505 assert_eq!(FileAck::parse(&bytes).unwrap(), ack);
506 let unavailable = FileAck {
508 file_ok: false,
509 file: &[],
510 };
511 let bytes = unavailable.to_bytes();
512 assert_eq!(bytes, [0x9F, 0x80, 0x03, 0x01, 0x00]);
513 assert_eq!(FileAck::parse(&bytes).unwrap(), unavailable);
514 let parsed = FileAck::parse(&[0x9F, 0x80, 0x03, 0x01, 0xFF]).unwrap();
516 assert!(parsed.file_ok);
517 }
518
519 #[test]
520 fn app_abort_req_and_ack_round_trip() {
521 let req = AppAbortReq {
522 abort_req_code: &[0x11, 0x22],
523 };
524 let bytes = req.to_bytes();
525 assert_eq!(bytes, [0x9F, 0x80, 0x04, 0x02, 0x11, 0x22]);
526 assert_eq!(AppAbortReq::parse(&bytes).unwrap(), req);
527
528 let ack = AppAbortAck {
529 abort_ack_code: &[0x33],
530 };
531 let bytes = ack.to_bytes();
532 assert_eq!(bytes, [0x9F, 0x80, 0x05, 0x01, 0x33]);
533 assert_eq!(AppAbortAck::parse(&bytes).unwrap(), ack);
534 }
535
536 #[test]
537 fn dispatch_routes_each_tag() {
538 let rs = RequestStart {
539 app_domain_identifier: &[0x01],
540 initial_object: &[],
541 }
542 .to_bytes();
543 assert!(matches!(
544 ApplicationMmiApdu::parse(&rs).unwrap(),
545 ApplicationMmiApdu::RequestStart(_)
546 ));
547 let aaa = AppAbortAck {
548 abort_ack_code: &[0x01],
549 }
550 .to_bytes();
551 let parsed = ApplicationMmiApdu::parse(&aaa).unwrap();
552 assert!(matches!(parsed, ApplicationMmiApdu::AppAbortAck(_)));
553 assert_eq!(parsed.to_bytes(), aaa);
554 }
555}