Skip to main content

dvb_ci/ci_ext/
software_download.rs

1//! Software Download (CAM firmware) objects + DSM-CC download messages — ETSI
2//! TS 101 699 V1.1.1 §6.7, Tables 74-83 (PDF pp. 64-74). See
3//! `docs/ci_plus/software-download.md`.
4//!
5//! Resource ID `0x00051041` (fixed). A CI module acts as a firmware source for a
6//! host; the host is the DSM-CC client and the module the download server, using
7//! the DSM-CC (ISO/IEC 13818-6) User-Network Download protocol.
8//!
9//! APDU objects (§6.7.4):
10//! - `download_enq` (`9F 80 00`, Table 75) — host → app: encapsulates a DSM-CC
11//!   U-N message (DownloadInfoRequest / DownloadDataRequest / DownloadCancel).
12//! - `download_reply` (`9F 80 01`, Table 76) — app → host: encapsulates a DSM-CC
13//!   U-N message (DownloadInfoResponse / DownloadDataBlock / DownloadCancel).
14//! - `user_authorization_initiate` (`9F 80 02`, Table 77) — host → app: the
15//!   7-byte [`BinaryId`] + opaque `data_byte`s.
16//! - `user_authorization_result` (`9F 80 03`, Table 78) — app → host: the
17//!   7-byte [`BinaryId`] + opaque `result_byte`s.
18//!
19//! The encapsulated DSM-CC message itself is carried **opaque** by the
20//! `download_enq` / `download_reply` objects (the `DSMCC_descriptor()` loop);
21//! the DSM-CC message structures (Tables 79-83) are provided as separate
22//! `Parse`/`Serialize` types ([`DownloadInfoRequest`], [`DownloadInfoResponse`],
23//! [`DownloadCancel`], [`DownloadDataRequest`], [`DownloadDataBlock`]) that
24//! decode those encapsulated bytes. Firmware payload, compatibility-descriptor
25//! bodies, module info and private data are opaque borrowed `&[u8]` per §6.7.5.
26
27use crate::error::{Error, Result};
28use crate::objects;
29use crate::tag::ApduTag;
30use broadcast_common::{Parse, Serialize};
31
32/// Resource-scoped `apdu_tag`s for the Download resource (Tables 75-78).
33pub mod tag {
34    use crate::tag::ApduTag;
35    /// `download_enq_tag` = `9F 80 00`.
36    pub const DOWNLOAD_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
37    /// `download_rep_tag` = `9F 80 01`.
38    pub const DOWNLOAD_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
39    /// `user_authorization_initiate_tag` = `9F 80 02`.
40    pub const USER_AUTH_INITIATE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
41    /// `user_authorization_result_tag` = `9F 80 03`.
42    pub const USER_AUTH_RESULT: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
43}
44
45/// `BinaryId` (Table 74, §6.7.3.1) — the 7-byte identification of a manufacturer
46/// binary: a 24-bit IEEE OUI `specifier` + 16-bit `model` + 16-bit `version`.
47pub const BINARY_ID_LEN: usize = 7;
48
49/// Manufacturer-binary identification (Table 74).
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52pub struct BinaryId {
53    /// `specifier` — 24-bit IEEE OUI (low 24 bits).
54    pub specifier: u32,
55    /// `model` — 16-bit, semantics defined by the `specifier`.
56    pub model: u16,
57    /// `version` — 16-bit, semantics defined by the `specifier`.
58    pub version: u16,
59}
60
61impl BinaryId {
62    fn read(b: &[u8]) -> Self {
63        Self {
64            specifier: ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32,
65            model: u16::from_be_bytes([b[3], b[4]]),
66            version: u16::from_be_bytes([b[5], b[6]]),
67        }
68    }
69    fn write(self, buf: &mut [u8]) {
70        buf[0] = (self.specifier >> 16) as u8;
71        buf[1] = (self.specifier >> 8) as u8;
72        buf[2] = self.specifier as u8;
73        buf[3..5].copy_from_slice(&self.model.to_be_bytes());
74        buf[5..7].copy_from_slice(&self.version.to_be_bytes());
75    }
76}
77
78// =================== APDU objects ===================
79
80/// `download_enq()` (Table 75): host → app — encapsulates one DSM-CC U-N
81/// message, carried verbatim as a borrowed byte string.
82#[derive(Debug, Clone, PartialEq, Eq, Default)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize))]
84pub struct DownloadEnquiry<'a> {
85    /// The encapsulated DSM-CC message bytes (the `DSMCC_descriptor()` loop).
86    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
87    pub dsmcc_message: &'a [u8],
88}
89
90/// `download_reply()` (Table 76): app → host — encapsulates one DSM-CC U-N
91/// message, carried verbatim.
92#[derive(Debug, Clone, PartialEq, Eq, Default)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize))]
94pub struct DownloadReply<'a> {
95    /// The encapsulated DSM-CC message bytes (the `DSMCC_descriptor()` loop).
96    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
97    pub dsmcc_message: &'a [u8],
98}
99
100/// `user_authorization_initiate()` (Table 77): host → app.
101#[derive(Debug, Clone, PartialEq, Eq, Default)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103pub struct UserAuthInitiate<'a> {
104    /// The 7-byte [`BinaryId`].
105    pub binary_id: BinaryId,
106    /// `data_byte`s — optional, meaning defined by the specifier.
107    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
108    pub data: &'a [u8],
109}
110
111/// `user_authorization_result()` (Table 78): app → host.
112#[derive(Debug, Clone, PartialEq, Eq, Default)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114pub struct UserAuthResult<'a> {
115    /// The 7-byte [`BinaryId`].
116    pub binary_id: BinaryId,
117    /// `result_byte`s — conveys the user response; meaning defined by the specifier.
118    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
119    pub result: &'a [u8],
120}
121
122macro_rules! opaque_dsmcc_object {
123    ($ty:ident, $tag:expr, $what:literal) => {
124        impl<'a> Parse<'a> for $ty<'a> {
125            type Error = Error;
126            fn parse(bytes: &'a [u8]) -> Result<Self> {
127                let body = objects::parse_apdu_header(bytes, $tag, $what)?;
128                Ok(Self {
129                    dsmcc_message: body,
130                })
131            }
132        }
133        impl Serialize for $ty<'_> {
134            type Error = Error;
135            fn serialized_len(&self) -> usize {
136                objects::apdu_len(self.dsmcc_message.len())
137            }
138            fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
139                let body_len = self.dsmcc_message.len();
140                let pos = objects::write_apdu_header($tag, body_len, buf)?;
141                buf[pos..pos + body_len].copy_from_slice(self.dsmcc_message);
142                Ok(pos + body_len)
143            }
144        }
145    };
146}
147
148opaque_dsmcc_object!(DownloadEnquiry, tag::DOWNLOAD_ENQ, "download_enq");
149opaque_dsmcc_object!(DownloadReply, tag::DOWNLOAD_REPLY, "download_reply");
150
151macro_rules! user_auth_object {
152    ($ty:ident, $tag:expr, $what:literal, $field:ident) => {
153        impl<'a> Parse<'a> for $ty<'a> {
154            type Error = Error;
155            fn parse(bytes: &'a [u8]) -> Result<Self> {
156                let body = objects::parse_apdu_header(bytes, $tag, $what)?;
157                if body.len() < BINARY_ID_LEN {
158                    return Err(Error::BufferTooShort {
159                        need: BINARY_ID_LEN,
160                        have: body.len(),
161                        what: $what,
162                    });
163                }
164                Ok(Self {
165                    binary_id: BinaryId::read(body),
166                    $field: &body[BINARY_ID_LEN..],
167                })
168            }
169        }
170        impl Serialize for $ty<'_> {
171            type Error = Error;
172            fn serialized_len(&self) -> usize {
173                objects::apdu_len(BINARY_ID_LEN + self.$field.len())
174            }
175            fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
176                let body_len = BINARY_ID_LEN + self.$field.len();
177                let mut pos = objects::write_apdu_header($tag, body_len, buf)?;
178                self.binary_id.write(&mut buf[pos..]);
179                pos += BINARY_ID_LEN;
180                buf[pos..pos + self.$field.len()].copy_from_slice(self.$field);
181                Ok(pos + self.$field.len())
182            }
183        }
184    };
185}
186
187user_auth_object!(
188    UserAuthInitiate,
189    tag::USER_AUTH_INITIATE,
190    "user_authorization_initiate",
191    data
192);
193user_auth_object!(
194    UserAuthResult,
195    tag::USER_AUTH_RESULT,
196    "user_authorization_result",
197    result
198);
199
200// =================== DSM-CC U-N Download messages (Tables 79-83) ===================
201//
202// Reproduced from ISO/IEC 13818-6 as carried inside the DSMCC_descriptor() loop
203// of the download_enq / download_reply objects. These have no apdu_tag; their
204// Parse consumes the whole input slice and Serialize emits exactly the wire
205// bytes. Variable-length nested blocks (compatibility descriptor, module loop,
206// adaptation, private data, block payload) are opaque borrowed &[u8].
207
208/// DSM-CC `protocolDiscriminator` for MPEG-2 DSM-CC (`0x11`).
209pub const DSMCC_PROTOCOL_DISCRIMINATOR: u8 = 0x11;
210/// DSM-CC `dsmccType` for U-N Download messages (`0x03`).
211pub const DSMCC_TYPE_DOWNLOAD: u8 = 0x03;
212/// `messageId` of DownloadInfoRequest (`0x1001`).
213pub const MSG_ID_DOWNLOAD_INFO_REQUEST: u16 = 0x1001;
214/// `messageId` of DownloadInfoResponse (`0x1002`).
215pub const MSG_ID_DOWNLOAD_INFO_RESPONSE: u16 = 0x1002;
216/// `messageId` of DownloadDataBlock (`0x1003`).
217pub const MSG_ID_DOWNLOAD_DATA_BLOCK: u16 = 0x1003;
218/// `messageId` of DownloadDataRequest (`0x1004`).
219pub const MSG_ID_DOWNLOAD_DATA_REQUEST: u16 = 0x1004;
220/// `messageId` of DownloadCancel (`0x1005`).
221pub const MSG_ID_DOWNLOAD_CANCEL: u16 = 0x1005;
222
223/// The DSM-CC message header common to the "info" / cancel messages (Tables
224/// 79/80/81): protocolDiscriminator + dsmccType + messageId + transactionId +
225/// reserved + adaptationLength + messageLength, plus the optional adaptation
226/// bytes (`adaptationType` + data, carried opaque).
227fn parse_dsmcc_header<'a>(
228    body: &'a [u8],
229    what: &'static str,
230) -> Result<(u32, u8, &'a [u8], &'a [u8])> {
231    // protocolDiscriminator(1) dsmccType(1) messageId(2) transactionId(4)
232    // reserved(1) adaptationLength(1) messageLength(2) = 12.
233    const HDR: usize = 12;
234    if body.len() < HDR {
235        return Err(Error::BufferTooShort {
236            need: HDR,
237            have: body.len(),
238            what,
239        });
240    }
241    let transaction_id = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
242    let adaptation_length = body[9] as usize;
243    if body.len() < HDR + adaptation_length {
244        return Err(Error::BufferTooShort {
245            need: HDR + adaptation_length,
246            have: body.len(),
247            what,
248        });
249    }
250    let adaptation = &body[HDR..HDR + adaptation_length];
251    let rest = &body[HDR + adaptation_length..];
252    Ok((transaction_id, adaptation_length as u8, adaptation, rest))
253}
254
255fn write_dsmcc_header(
256    buf: &mut [u8],
257    message_id: u16,
258    transaction_id: u32,
259    adaptation: &[u8],
260    message_length: usize,
261) -> usize {
262    buf[0] = DSMCC_PROTOCOL_DISCRIMINATOR;
263    buf[1] = DSMCC_TYPE_DOWNLOAD;
264    buf[2..4].copy_from_slice(&message_id.to_be_bytes());
265    buf[4..8].copy_from_slice(&transaction_id.to_be_bytes());
266    buf[8] = 0xFF; // reserved
267    buf[9] = adaptation.len() as u8;
268    buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
269    buf[12..12 + adaptation.len()].copy_from_slice(adaptation);
270    12 + adaptation.len()
271}
272
273/// `DownloadInfoRequest()` (Table 79) — client (host) → server. The
274/// compatibility-descriptor block and private-data block are opaque (their
275/// inner descriptor loops are manufacturer-defined per §6.7.5).
276#[derive(Debug, Clone, PartialEq, Eq, Default)]
277#[cfg_attr(feature = "serde", derive(serde::Serialize))]
278pub struct DownloadInfoRequest<'a> {
279    /// `transactionId` (client assigned; 2 MSBs zero per DSM-CC).
280    pub transaction_id: u32,
281    /// `adaptationType` + `adaptationDataByte`s (opaque CA/private adaptation).
282    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
283    pub adaptation: &'a [u8],
284    /// `bufferSize`.
285    pub buffer_size: u32,
286    /// `maximumBlockSize`.
287    pub maximum_block_size: u16,
288    /// `compatibilityDescriptor()` — `compatibilityDescriptorLength` +
289    /// `descriptorCount` + the descriptor loop, carried verbatim.
290    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
291    pub compatibility_descriptor: &'a [u8],
292    /// `privateDataByte`s (`privateDataLength`-prefixed; per §6.7.5.4 shall be empty).
293    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
294    pub private_data: &'a [u8],
295}
296
297impl<'a> Parse<'a> for DownloadInfoRequest<'a> {
298    type Error = Error;
299    fn parse(body: &'a [u8]) -> Result<Self> {
300        let what = "DownloadInfoRequest";
301        let (transaction_id, _adapt_len, adaptation, rest) = parse_dsmcc_header(body, what)?;
302        // bufferSize(4) maximumBlockSize(2) compatibilityDescriptorLength(2).
303        if rest.len() < 8 {
304            return Err(Error::BufferTooShort {
305                need: 8,
306                have: rest.len(),
307                what,
308            });
309        }
310        let buffer_size = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
311        let maximum_block_size = u16::from_be_bytes([rest[4], rest[5]]);
312        // compatibilityDescriptorLength counts the bytes *after* the length field
313        // (descriptorCount + descriptor loop).
314        let compat_len = u16::from_be_bytes([rest[6], rest[7]]) as usize;
315        let compat_start = 6; // start of compatibilityDescriptor block (incl. its length field)
316        let compat_block_end = compat_start + 2 + compat_len;
317        if rest.len() < compat_block_end + 2 {
318            return Err(Error::BufferTooShort {
319                need: compat_block_end + 2,
320                have: rest.len(),
321                what,
322            });
323        }
324        let compatibility_descriptor = &rest[compat_start..compat_block_end];
325        let priv_len =
326            u16::from_be_bytes([rest[compat_block_end], rest[compat_block_end + 1]]) as usize;
327        let priv_start = compat_block_end + 2;
328        let priv_end = priv_start + priv_len;
329        if rest.len() < priv_end {
330            return Err(Error::BufferTooShort {
331                need: priv_end,
332                have: rest.len(),
333                what,
334            });
335        }
336        Ok(Self {
337            transaction_id,
338            adaptation,
339            buffer_size,
340            maximum_block_size,
341            compatibility_descriptor,
342            private_data: &rest[priv_start..priv_end],
343        })
344    }
345}
346impl Serialize for DownloadInfoRequest<'_> {
347    type Error = Error;
348    fn serialized_len(&self) -> usize {
349        12 + self.adaptation.len()
350            + 6
351            + self.compatibility_descriptor.len()
352            + 2
353            + self.private_data.len()
354    }
355    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
356        let total = self.serialized_len();
357        if buf.len() < total {
358            return Err(Error::OutputBufferTooSmall {
359                need: total,
360                have: buf.len(),
361            });
362        }
363        // messageLength = everything after the messageLength field itself.
364        let message_length = total - 12;
365        let mut pos = write_dsmcc_header(
366            buf,
367            MSG_ID_DOWNLOAD_INFO_REQUEST,
368            self.transaction_id,
369            self.adaptation,
370            message_length,
371        );
372        buf[pos..pos + 4].copy_from_slice(&self.buffer_size.to_be_bytes());
373        pos += 4;
374        buf[pos..pos + 2].copy_from_slice(&self.maximum_block_size.to_be_bytes());
375        pos += 2;
376        buf[pos..pos + self.compatibility_descriptor.len()]
377            .copy_from_slice(self.compatibility_descriptor);
378        pos += self.compatibility_descriptor.len();
379        buf[pos..pos + 2].copy_from_slice(&(self.private_data.len() as u16).to_be_bytes());
380        pos += 2;
381        buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
382        Ok(pos + self.private_data.len())
383    }
384}
385
386/// `DownloadInfoResponse()` (Table 80) — server (module) → client. The
387/// compatibility-descriptor block, the per-module info block and the private
388/// data are opaque borrowed `&[u8]`.
389#[derive(Debug, Clone, PartialEq, Eq, Default)]
390#[cfg_attr(feature = "serde", derive(serde::Serialize))]
391pub struct DownloadInfoResponse<'a> {
392    /// `transactionId` (matches the request).
393    pub transaction_id: u32,
394    /// Opaque adaptation bytes.
395    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
396    pub adaptation: &'a [u8],
397    /// `downloadId`.
398    pub download_id: u32,
399    /// `blockSize`.
400    pub block_size: u16,
401    /// `windowSize`.
402    pub window_size: u8,
403    /// `ackPeriod`.
404    pub ack_period: u8,
405    /// `tCDownloadWindow`.
406    pub tc_download_window: u32,
407    /// `tCDownloadScenario`.
408    pub tc_download_scenario: u32,
409    /// `compatibilityDescriptor()` block, carried verbatim (incl. its length field).
410    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
411    pub compatibility_descriptor: &'a [u8],
412    /// The `numberOfModules` module loop (`numberOfModules` + the per-module
413    /// entries), carried verbatim.
414    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
415    pub modules: &'a [u8],
416    /// `privateDataByte`s.
417    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
418    pub private_data: &'a [u8],
419}
420
421impl<'a> Parse<'a> for DownloadInfoResponse<'a> {
422    type Error = Error;
423    fn parse(body: &'a [u8]) -> Result<Self> {
424        let what = "DownloadInfoResponse";
425        let (transaction_id, _adapt_len, adaptation, rest) = parse_dsmcc_header(body, what)?;
426        // downloadId(4) blockSize(2) windowSize(1) ackPeriod(1)
427        // tCDownloadWindow(4) tCDownloadScenario(4) compatibilityDescriptorLength(2).
428        const FIXED: usize = 4 + 2 + 1 + 1 + 4 + 4 + 2;
429        if rest.len() < FIXED {
430            return Err(Error::BufferTooShort {
431                need: FIXED,
432                have: rest.len(),
433                what,
434            });
435        }
436        let download_id = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
437        let block_size = u16::from_be_bytes([rest[4], rest[5]]);
438        let window_size = rest[6];
439        let ack_period = rest[7];
440        let tc_download_window = u32::from_be_bytes([rest[8], rest[9], rest[10], rest[11]]);
441        let tc_download_scenario = u32::from_be_bytes([rest[12], rest[13], rest[14], rest[15]]);
442        let compat_len_off = 16;
443        let compat_len =
444            u16::from_be_bytes([rest[compat_len_off], rest[compat_len_off + 1]]) as usize;
445        let compat_block_end = compat_len_off + 2 + compat_len;
446        // + numberOfModules(2) at compat_block_end.
447        if rest.len() < compat_block_end + 2 {
448            return Err(Error::BufferTooShort {
449                need: compat_block_end + 2,
450                have: rest.len(),
451                what,
452            });
453        }
454        let compatibility_descriptor = &rest[compat_len_off..compat_block_end];
455        // Walk the module loop to find where private data begins.
456        let number_of_modules =
457            u16::from_be_bytes([rest[compat_block_end], rest[compat_block_end + 1]]) as usize;
458        let mut mpos = compat_block_end + 2;
459        for _ in 0..number_of_modules {
460            // moduleId(2) moduleSize(4) moduleVersion(1) moduleInfoLength(1).
461            if rest.len() < mpos + 8 {
462                return Err(Error::BufferTooShort {
463                    need: mpos + 8,
464                    have: rest.len(),
465                    what,
466                });
467            }
468            let module_info_len = rest[mpos + 7] as usize;
469            mpos += 8 + module_info_len;
470            if rest.len() < mpos {
471                return Err(Error::BufferTooShort {
472                    need: mpos,
473                    have: rest.len(),
474                    what,
475                });
476            }
477        }
478        let modules = &rest[compat_block_end..mpos];
479        if rest.len() < mpos + 2 {
480            return Err(Error::BufferTooShort {
481                need: mpos + 2,
482                have: rest.len(),
483                what,
484            });
485        }
486        let priv_len = u16::from_be_bytes([rest[mpos], rest[mpos + 1]]) as usize;
487        let priv_start = mpos + 2;
488        let priv_end = priv_start + priv_len;
489        if rest.len() < priv_end {
490            return Err(Error::BufferTooShort {
491                need: priv_end,
492                have: rest.len(),
493                what,
494            });
495        }
496        Ok(Self {
497            transaction_id,
498            adaptation,
499            download_id,
500            block_size,
501            window_size,
502            ack_period,
503            tc_download_window,
504            tc_download_scenario,
505            compatibility_descriptor,
506            modules,
507            private_data: &rest[priv_start..priv_end],
508        })
509    }
510}
511impl Serialize for DownloadInfoResponse<'_> {
512    type Error = Error;
513    fn serialized_len(&self) -> usize {
514        12 + self.adaptation.len()
515            + 16
516            + self.compatibility_descriptor.len()
517            + self.modules.len()
518            + 2
519            + self.private_data.len()
520    }
521    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
522        let total = self.serialized_len();
523        if buf.len() < total {
524            return Err(Error::OutputBufferTooSmall {
525                need: total,
526                have: buf.len(),
527            });
528        }
529        let message_length = total - 12;
530        let mut pos = write_dsmcc_header(
531            buf,
532            MSG_ID_DOWNLOAD_INFO_RESPONSE,
533            self.transaction_id,
534            self.adaptation,
535            message_length,
536        );
537        buf[pos..pos + 4].copy_from_slice(&self.download_id.to_be_bytes());
538        pos += 4;
539        buf[pos..pos + 2].copy_from_slice(&self.block_size.to_be_bytes());
540        pos += 2;
541        buf[pos] = self.window_size;
542        buf[pos + 1] = self.ack_period;
543        pos += 2;
544        buf[pos..pos + 4].copy_from_slice(&self.tc_download_window.to_be_bytes());
545        pos += 4;
546        buf[pos..pos + 4].copy_from_slice(&self.tc_download_scenario.to_be_bytes());
547        pos += 4;
548        buf[pos..pos + self.compatibility_descriptor.len()]
549            .copy_from_slice(self.compatibility_descriptor);
550        pos += self.compatibility_descriptor.len();
551        buf[pos..pos + self.modules.len()].copy_from_slice(self.modules);
552        pos += self.modules.len();
553        buf[pos..pos + 2].copy_from_slice(&(self.private_data.len() as u16).to_be_bytes());
554        pos += 2;
555        buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
556        Ok(pos + self.private_data.len())
557    }
558}
559
560/// `DownloadCancel()` (Table 81).
561#[derive(Debug, Clone, PartialEq, Eq, Default)]
562#[cfg_attr(feature = "serde", derive(serde::Serialize))]
563pub struct DownloadCancel<'a> {
564    /// `transactionId` (server assigned).
565    pub transaction_id: u32,
566    /// Opaque adaptation bytes.
567    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
568    pub adaptation: &'a [u8],
569    /// `downloadId`.
570    pub download_id: u32,
571    /// `moduleId`.
572    pub module_id: u16,
573    /// `blockNumber`.
574    pub block_number: u16,
575    /// `downloadCancelReason`.
576    pub download_cancel_reason: u8,
577    /// `privateDataByte`s.
578    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
579    pub private_data: &'a [u8],
580}
581
582impl<'a> Parse<'a> for DownloadCancel<'a> {
583    type Error = Error;
584    fn parse(body: &'a [u8]) -> Result<Self> {
585        let what = "DownloadCancel";
586        let (transaction_id, _adapt_len, adaptation, rest) = parse_dsmcc_header(body, what)?;
587        // downloadId(4) moduleId(2) blockNumber(2) downloadCancelReason(1) privateDataLength(2).
588        const FIXED: usize = 4 + 2 + 2 + 1 + 2;
589        if rest.len() < FIXED {
590            return Err(Error::BufferTooShort {
591                need: FIXED,
592                have: rest.len(),
593                what,
594            });
595        }
596        let download_id = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
597        let module_id = u16::from_be_bytes([rest[4], rest[5]]);
598        let block_number = u16::from_be_bytes([rest[6], rest[7]]);
599        let download_cancel_reason = rest[8];
600        let priv_len = u16::from_be_bytes([rest[9], rest[10]]) as usize;
601        let priv_start = 11;
602        let priv_end = priv_start + priv_len;
603        if rest.len() < priv_end {
604            return Err(Error::BufferTooShort {
605                need: priv_end,
606                have: rest.len(),
607                what,
608            });
609        }
610        Ok(Self {
611            transaction_id,
612            adaptation,
613            download_id,
614            module_id,
615            block_number,
616            download_cancel_reason,
617            private_data: &rest[priv_start..priv_end],
618        })
619    }
620}
621impl Serialize for DownloadCancel<'_> {
622    type Error = Error;
623    fn serialized_len(&self) -> usize {
624        12 + self.adaptation.len() + 9 + 2 + self.private_data.len()
625    }
626    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
627        let total = self.serialized_len();
628        if buf.len() < total {
629            return Err(Error::OutputBufferTooSmall {
630                need: total,
631                have: buf.len(),
632            });
633        }
634        let message_length = total - 12;
635        let mut pos = write_dsmcc_header(
636            buf,
637            MSG_ID_DOWNLOAD_CANCEL,
638            self.transaction_id,
639            self.adaptation,
640            message_length,
641        );
642        buf[pos..pos + 4].copy_from_slice(&self.download_id.to_be_bytes());
643        pos += 4;
644        buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
645        pos += 2;
646        buf[pos..pos + 2].copy_from_slice(&self.block_number.to_be_bytes());
647        pos += 2;
648        buf[pos] = self.download_cancel_reason;
649        pos += 1;
650        buf[pos..pos + 2].copy_from_slice(&(self.private_data.len() as u16).to_be_bytes());
651        pos += 2;
652        buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
653        Ok(pos + self.private_data.len())
654    }
655}
656
657/// The DSM-CC `dsmccDownloadDataHeader` (Tables 82/83) differs from the message
658/// header: the `DownloadId` (4) precedes `reserved`/`adaptationLength`/
659/// `messageLength`. Returns `(download_id, adaptation, rest_after_adaptation)`.
660fn parse_dsmcc_data_header<'a>(
661    body: &'a [u8],
662    what: &'static str,
663) -> Result<(u32, &'a [u8], &'a [u8])> {
664    // protocolDiscriminator(1) dsmccType(1) messageId(2) DownloadId(4)
665    // reserved(1) adaptationLength(1) messageLength(2) = 12.
666    const HDR: usize = 12;
667    if body.len() < HDR {
668        return Err(Error::BufferTooShort {
669            need: HDR,
670            have: body.len(),
671            what,
672        });
673    }
674    let download_id = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
675    let adaptation_length = body[9] as usize;
676    if body.len() < HDR + adaptation_length {
677        return Err(Error::BufferTooShort {
678            need: HDR + adaptation_length,
679            have: body.len(),
680            what,
681        });
682    }
683    let adaptation = &body[HDR..HDR + adaptation_length];
684    Ok((download_id, adaptation, &body[HDR + adaptation_length..]))
685}
686
687fn write_dsmcc_data_header(
688    buf: &mut [u8],
689    message_id: u16,
690    download_id: u32,
691    adaptation: &[u8],
692    message_length: usize,
693) -> usize {
694    buf[0] = DSMCC_PROTOCOL_DISCRIMINATOR;
695    buf[1] = DSMCC_TYPE_DOWNLOAD;
696    buf[2..4].copy_from_slice(&message_id.to_be_bytes());
697    buf[4..8].copy_from_slice(&download_id.to_be_bytes());
698    buf[8] = 0xFF; // reserved
699    buf[9] = adaptation.len() as u8;
700    buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
701    buf[12..12 + adaptation.len()].copy_from_slice(adaptation);
702    12 + adaptation.len()
703}
704
705/// `DownloadDataRequest()` (Table 82) — client (host) → server.
706#[derive(Debug, Clone, PartialEq, Eq, Default)]
707#[cfg_attr(feature = "serde", derive(serde::Serialize))]
708pub struct DownloadDataRequest<'a> {
709    /// `DownloadId`.
710    pub download_id: u32,
711    /// Opaque adaptation bytes.
712    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
713    pub adaptation: &'a [u8],
714    /// `moduleId`.
715    pub module_id: u16,
716    /// `blockNumber`.
717    pub block_number: u16,
718    /// `downloadReason`.
719    pub download_reason: u8,
720}
721
722impl<'a> Parse<'a> for DownloadDataRequest<'a> {
723    type Error = Error;
724    fn parse(body: &'a [u8]) -> Result<Self> {
725        let what = "DownloadDataRequest";
726        let (download_id, adaptation, rest) = parse_dsmcc_data_header(body, what)?;
727        // moduleId(2) blockNumber(2) downloadReason(1).
728        const FIXED: usize = 5;
729        if rest.len() < FIXED {
730            return Err(Error::BufferTooShort {
731                need: FIXED,
732                have: rest.len(),
733                what,
734            });
735        }
736        Ok(Self {
737            download_id,
738            adaptation,
739            module_id: u16::from_be_bytes([rest[0], rest[1]]),
740            block_number: u16::from_be_bytes([rest[2], rest[3]]),
741            download_reason: rest[4],
742        })
743    }
744}
745impl Serialize for DownloadDataRequest<'_> {
746    type Error = Error;
747    fn serialized_len(&self) -> usize {
748        12 + self.adaptation.len() + 5
749    }
750    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
751        let total = self.serialized_len();
752        if buf.len() < total {
753            return Err(Error::OutputBufferTooSmall {
754                need: total,
755                have: buf.len(),
756            });
757        }
758        let message_length = total - 12;
759        let mut pos = write_dsmcc_data_header(
760            buf,
761            MSG_ID_DOWNLOAD_DATA_REQUEST,
762            self.download_id,
763            self.adaptation,
764            message_length,
765        );
766        buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
767        pos += 2;
768        buf[pos..pos + 2].copy_from_slice(&self.block_number.to_be_bytes());
769        pos += 2;
770        buf[pos] = self.download_reason;
771        Ok(pos + 1)
772    }
773}
774
775/// `DownloadDataBlock()` (Table 83) — server (module) → client. The
776/// `blockDataByte`s carry the (opaque) firmware payload.
777#[derive(Debug, Clone, PartialEq, Eq, Default)]
778#[cfg_attr(feature = "serde", derive(serde::Serialize))]
779pub struct DownloadDataBlock<'a> {
780    /// `DownloadId`.
781    pub download_id: u32,
782    /// Opaque adaptation bytes.
783    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
784    pub adaptation: &'a [u8],
785    /// `moduleId`.
786    pub module_id: u16,
787    /// `moduleVersion`.
788    pub module_version: u8,
789    /// `blockNumber`.
790    pub block_number: u16,
791    /// `blockDataByte`s — opaque firmware payload.
792    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
793    pub block_data: &'a [u8],
794}
795
796impl<'a> Parse<'a> for DownloadDataBlock<'a> {
797    type Error = Error;
798    fn parse(body: &'a [u8]) -> Result<Self> {
799        let what = "DownloadDataBlock";
800        let (download_id, adaptation, rest) = parse_dsmcc_data_header(body, what)?;
801        // moduleId(2) moduleVersion(1) reserved(1) blockNumber(2).
802        const FIXED: usize = 6;
803        if rest.len() < FIXED {
804            return Err(Error::BufferTooShort {
805                need: FIXED,
806                have: rest.len(),
807                what,
808            });
809        }
810        Ok(Self {
811            download_id,
812            adaptation,
813            module_id: u16::from_be_bytes([rest[0], rest[1]]),
814            module_version: rest[2],
815            // rest[3] = reserved
816            block_number: u16::from_be_bytes([rest[4], rest[5]]),
817            block_data: &rest[FIXED..],
818        })
819    }
820}
821impl Serialize for DownloadDataBlock<'_> {
822    type Error = Error;
823    fn serialized_len(&self) -> usize {
824        12 + self.adaptation.len() + 6 + self.block_data.len()
825    }
826    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
827        let total = self.serialized_len();
828        if buf.len() < total {
829            return Err(Error::OutputBufferTooSmall {
830                need: total,
831                have: buf.len(),
832            });
833        }
834        let message_length = total - 12;
835        let mut pos = write_dsmcc_data_header(
836            buf,
837            MSG_ID_DOWNLOAD_DATA_BLOCK,
838            self.download_id,
839            self.adaptation,
840            message_length,
841        );
842        buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
843        pos += 2;
844        buf[pos] = self.module_version;
845        buf[pos + 1] = 0xFF; // reserved
846        pos += 2;
847        buf[pos..pos + 2].copy_from_slice(&self.block_number.to_be_bytes());
848        pos += 2;
849        buf[pos..pos + self.block_data.len()].copy_from_slice(self.block_data);
850        Ok(pos + self.block_data.len())
851    }
852}
853
854/// Resource-scoped dispatch over the Download APDU objects (Tables 75-78).
855#[derive(Debug, Clone, PartialEq, Eq)]
856#[cfg_attr(feature = "serde", derive(serde::Serialize))]
857#[non_exhaustive]
858pub enum DownloadApdu<'a> {
859    /// `download_enq` (`9F 80 00`).
860    DownloadEnquiry(DownloadEnquiry<'a>),
861    /// `download_reply` (`9F 80 01`).
862    DownloadReply(DownloadReply<'a>),
863    /// `user_authorization_initiate` (`9F 80 02`).
864    UserAuthInitiate(UserAuthInitiate<'a>),
865    /// `user_authorization_result` (`9F 80 03`).
866    UserAuthResult(UserAuthResult<'a>),
867}
868
869impl<'a> DownloadApdu<'a> {
870    /// Parse a Download APDU, dispatching on the leading `apdu_tag`.
871    pub fn parse(body: &'a [u8]) -> Result<Self> {
872        if body.len() < 3 {
873            return Err(Error::BufferTooShort {
874                need: 3,
875                have: body.len(),
876                what: "download apdu_tag",
877            });
878        }
879        let t = ApduTag::from_bytes(body[0], body[1], body[2]);
880        match t {
881            tag::DOWNLOAD_ENQ => Ok(Self::DownloadEnquiry(DownloadEnquiry::parse(body)?)),
882            tag::DOWNLOAD_REPLY => Ok(Self::DownloadReply(DownloadReply::parse(body)?)),
883            tag::USER_AUTH_INITIATE => Ok(Self::UserAuthInitiate(UserAuthInitiate::parse(body)?)),
884            tag::USER_AUTH_RESULT => Ok(Self::UserAuthResult(UserAuthResult::parse(body)?)),
885            _ => Err(Error::UnexpectedApduTag {
886                got: t.as_u24(),
887                expected: tag::DOWNLOAD_ENQ.as_u24(),
888                what: "download",
889            }),
890        }
891    }
892}
893
894impl Serialize for DownloadApdu<'_> {
895    type Error = Error;
896    fn serialized_len(&self) -> usize {
897        match self {
898            Self::DownloadEnquiry(o) => o.serialized_len(),
899            Self::DownloadReply(o) => o.serialized_len(),
900            Self::UserAuthInitiate(o) => o.serialized_len(),
901            Self::UserAuthResult(o) => o.serialized_len(),
902        }
903    }
904    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
905        match self {
906            Self::DownloadEnquiry(o) => o.serialize_into(buf),
907            Self::DownloadReply(o) => o.serialize_into(buf),
908            Self::UserAuthInitiate(o) => o.serialize_into(buf),
909            Self::UserAuthResult(o) => o.serialize_into(buf),
910        }
911    }
912}
913
914#[cfg(test)]
915mod tests {
916    use super::*;
917
918    #[test]
919    fn download_enquiry_round_trips_and_bites() {
920        let enq = DownloadEnquiry {
921            dsmcc_message: &[0x11, 0x03, 0x10, 0x01],
922        };
923        let bytes = enq.to_bytes();
924        assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x04, 0x11, 0x03, 0x10, 0x01]);
925        assert_eq!(DownloadEnquiry::parse(&bytes).unwrap(), enq);
926        let other = DownloadEnquiry {
927            dsmcc_message: &[0x11, 0x03, 0x10, 0x02],
928        };
929        assert_ne!(bytes, other.to_bytes());
930    }
931
932    #[test]
933    fn download_reply_round_trips() {
934        let rep = DownloadReply {
935            dsmcc_message: &[0xAA],
936        };
937        let bytes = rep.to_bytes();
938        assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0xAA]);
939        assert_eq!(DownloadReply::parse(&bytes).unwrap(), rep);
940    }
941
942    #[test]
943    fn user_auth_initiate_round_trips_and_bites() {
944        let uai = UserAuthInitiate {
945            binary_id: BinaryId {
946                specifier: 0x00_1B_67,
947                model: 0x1234,
948                version: 0x0005,
949            },
950            data: &[0xCA, 0xFE],
951        };
952        let bytes = uai.to_bytes();
953        // tag(3) + len(1=9) + specifier(3) model(2) version(2) data(2).
954        assert_eq!(
955            bytes,
956            [
957                0x9F, 0x80, 0x02, 0x09, 0x00, 0x1B, 0x67, 0x12, 0x34, 0x00, 0x05, 0xCA, 0xFE
958            ]
959        );
960        assert_eq!(UserAuthInitiate::parse(&bytes).unwrap(), uai);
961        let mut other = uai.clone();
962        other.binary_id.version = 0x0006;
963        assert_ne!(bytes, other.to_bytes());
964    }
965
966    #[test]
967    fn user_auth_result_round_trips() {
968        let uar = UserAuthResult {
969            binary_id: BinaryId {
970                specifier: 0xAABBCC & 0x00FF_FFFF,
971                model: 1,
972                version: 2,
973            },
974            result: &[0x01],
975        };
976        let bytes = uar.to_bytes();
977        assert_eq!(
978            bytes,
979            [
980                0x9F, 0x80, 0x03, 0x08, 0xAA, 0xBB, 0xCC, 0x00, 0x01, 0x00, 0x02, 0x01
981            ]
982        );
983        assert_eq!(UserAuthResult::parse(&bytes).unwrap(), uar);
984    }
985
986    #[test]
987    fn download_info_request_round_trips_and_bites() {
988        // compatibilityDescriptor block: length(2)=0x0004 + descriptorCount(2)=0x0000
989        // (the two-byte length counts the bytes after itself = descriptorCount + loop;
990        // here just descriptorCount, 2 bytes -> wait, 0x0004 means 4 bytes follow).
991        // Use compat = [00 04, 00 00, AA BB]  (len 4 counts descriptorCount(2)+2 loop bytes).
992        let compat = [0x00, 0x04, 0x00, 0x00, 0xAA, 0xBB];
993        let req = DownloadInfoRequest {
994            transaction_id: 0x0000_0001,
995            adaptation: &[],
996            buffer_size: 0x0001_0000,
997            maximum_block_size: 0x0200,
998            compatibility_descriptor: &compat,
999            private_data: &[],
1000        };
1001        let bytes = req.to_bytes();
1002        assert_eq!(DownloadInfoRequest::parse(&bytes).unwrap(), req);
1003        // Verify header is well-formed.
1004        assert_eq!(bytes[0], 0x11);
1005        assert_eq!(bytes[1], 0x03);
1006        assert_eq!(&bytes[2..4], &[0x10, 0x01]);
1007        let mut other = req.clone();
1008        other.buffer_size = 0x0002_0000;
1009        assert_ne!(bytes, other.to_bytes());
1010    }
1011
1012    #[test]
1013    fn download_info_request_with_adaptation() {
1014        let compat = [0x00, 0x02, 0x00, 0x00];
1015        let req = DownloadInfoRequest {
1016            transaction_id: 0x12,
1017            adaptation: &[0x01, 0x02, 0x03],
1018            buffer_size: 1,
1019            maximum_block_size: 2,
1020            compatibility_descriptor: &compat,
1021            private_data: &[],
1022        };
1023        let bytes = req.to_bytes();
1024        assert_eq!(bytes[9], 0x03); // adaptationLength
1025        assert_eq!(DownloadInfoRequest::parse(&bytes).unwrap(), req);
1026    }
1027
1028    #[test]
1029    fn download_info_response_multi_module_round_trips_and_bites() {
1030        let compat = [0x00, 0x02, 0x00, 0x00];
1031        // Two modules (>=2 loop): each moduleId(2) moduleSize(4) moduleVersion(1)
1032        // moduleInfoLength(1) [info].
1033        // numberOfModules(2)=0x0002, then mod0 (infoLen 1) + mod1 (infoLen 0).
1034        let modules = [
1035            0x00, 0x02, // numberOfModules = 2
1036            0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x01, 0x01, 0xFF, // module 0 (info=[FF])
1037            0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, // module 1 (info empty)
1038        ];
1039        let resp = DownloadInfoResponse {
1040            transaction_id: 1,
1041            adaptation: &[],
1042            download_id: 0xDEAD_BEEF,
1043            block_size: 0x0100,
1044            window_size: 4,
1045            ack_period: 2,
1046            tc_download_window: 1000,
1047            tc_download_scenario: 2000,
1048            compatibility_descriptor: &compat,
1049            modules: &modules,
1050            private_data: &[],
1051        };
1052        let bytes = resp.to_bytes();
1053        assert_eq!(DownloadInfoResponse::parse(&bytes).unwrap(), resp);
1054        let mut other = resp.clone();
1055        other.window_size = 5;
1056        assert_ne!(bytes, other.to_bytes());
1057    }
1058
1059    #[test]
1060    fn download_cancel_round_trips() {
1061        let cancel = DownloadCancel {
1062            transaction_id: 0x10,
1063            adaptation: &[],
1064            download_id: 0x01,
1065            module_id: 0x02,
1066            block_number: 0x03,
1067            download_cancel_reason: 0x05,
1068            private_data: &[],
1069        };
1070        let bytes = cancel.to_bytes();
1071        assert_eq!(DownloadCancel::parse(&bytes).unwrap(), cancel);
1072        assert_eq!(&bytes[2..4], &[0x10, 0x05]); // messageId DownloadCancel
1073    }
1074
1075    #[test]
1076    fn download_data_request_round_trips_and_bites() {
1077        let req = DownloadDataRequest {
1078            download_id: 0xDEAD_BEEF,
1079            adaptation: &[],
1080            module_id: 0x0001,
1081            block_number: 0x0002,
1082            download_reason: 0x00,
1083        };
1084        let bytes = req.to_bytes();
1085        assert_eq!(DownloadDataRequest::parse(&bytes).unwrap(), req);
1086        // messageId DownloadDataRequest, then DownloadId (the data header puts it
1087        // at offset 4, before reserved/adaptation/messageLength).
1088        assert_eq!(&bytes[2..4], &[0x10, 0x04]);
1089        assert_eq!(&bytes[4..8], &0xDEAD_BEEFu32.to_be_bytes());
1090        let mut other = req.clone();
1091        other.block_number = 0x0003;
1092        assert_ne!(bytes, other.to_bytes());
1093    }
1094
1095    #[test]
1096    fn download_data_block_round_trips_and_bites() {
1097        let block = DownloadDataBlock {
1098            download_id: 0x0000_0001,
1099            adaptation: &[],
1100            module_id: 0x0001,
1101            module_version: 0x02,
1102            block_number: 0x0003,
1103            block_data: &[0xFE, 0xED, 0xFA, 0xCE],
1104        };
1105        let bytes = block.to_bytes();
1106        assert_eq!(DownloadDataBlock::parse(&bytes).unwrap(), block);
1107        assert_eq!(&bytes[2..4], &[0x10, 0x03]); // messageId DownloadDataBlock
1108        let mut other = block.clone();
1109        other.block_data = &[0xFE, 0xED, 0xFA, 0xCF];
1110        assert_ne!(bytes, other.to_bytes());
1111    }
1112
1113    #[test]
1114    fn enquiry_round_trips_a_real_dsmcc_message() {
1115        // Build a DownloadDataRequest, wrap it in a DownloadEnquiry, round-trip both.
1116        let inner = DownloadDataRequest {
1117            download_id: 0x1234_5678,
1118            adaptation: &[],
1119            module_id: 1,
1120            block_number: 1,
1121            download_reason: 0,
1122        };
1123        let inner_bytes = inner.to_bytes();
1124        let enq = DownloadEnquiry {
1125            dsmcc_message: &inner_bytes,
1126        };
1127        let outer = enq.to_bytes();
1128        let parsed = DownloadEnquiry::parse(&outer).unwrap();
1129        assert_eq!(parsed, enq);
1130        // And the encapsulated message parses back.
1131        assert_eq!(
1132            DownloadDataRequest::parse(parsed.dsmcc_message).unwrap(),
1133            inner
1134        );
1135    }
1136
1137    #[test]
1138    fn dispatch_routes_each_tag() {
1139        let enq = DownloadEnquiry {
1140            dsmcc_message: &[0x11],
1141        }
1142        .to_bytes();
1143        assert!(matches!(
1144            DownloadApdu::parse(&enq).unwrap(),
1145            DownloadApdu::DownloadEnquiry(_)
1146        ));
1147        let uar = UserAuthResult {
1148            binary_id: BinaryId::default(),
1149            result: &[0x01],
1150        }
1151        .to_bytes();
1152        let parsed = DownloadApdu::parse(&uar).unwrap();
1153        assert!(matches!(parsed, DownloadApdu::UserAuthResult(_)));
1154        assert_eq!(parsed.to_bytes(), uar);
1155    }
1156}