use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use broadcast_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const DOWNLOAD_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
pub const DOWNLOAD_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
pub const USER_AUTH_INITIATE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
pub const USER_AUTH_RESULT: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
}
pub const BINARY_ID_LEN: usize = 7;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BinaryId {
pub specifier: u32,
pub model: u16,
pub version: u16,
}
impl BinaryId {
fn read(b: &[u8]) -> Self {
Self {
specifier: ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32,
model: u16::from_be_bytes([b[3], b[4]]),
version: u16::from_be_bytes([b[5], b[6]]),
}
}
fn write(self, buf: &mut [u8]) {
buf[0] = (self.specifier >> 16) as u8;
buf[1] = (self.specifier >> 8) as u8;
buf[2] = self.specifier as u8;
buf[3..5].copy_from_slice(&self.model.to_be_bytes());
buf[5..7].copy_from_slice(&self.version.to_be_bytes());
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadEnquiry<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub dsmcc_message: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadReply<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub dsmcc_message: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct UserAuthInitiate<'a> {
pub binary_id: BinaryId,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub data: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct UserAuthResult<'a> {
pub binary_id: BinaryId,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub result: &'a [u8],
}
macro_rules! opaque_dsmcc_object {
($ty:ident, $tag:expr, $what:literal) => {
impl<'a> Parse<'a> for $ty<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, $tag, $what)?;
Ok(Self {
dsmcc_message: body,
})
}
}
impl Serialize for $ty<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(self.dsmcc_message.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = self.dsmcc_message.len();
let pos = objects::write_apdu_header($tag, body_len, buf)?;
buf[pos..pos + body_len].copy_from_slice(self.dsmcc_message);
Ok(pos + body_len)
}
}
};
}
opaque_dsmcc_object!(DownloadEnquiry, tag::DOWNLOAD_ENQ, "download_enq");
opaque_dsmcc_object!(DownloadReply, tag::DOWNLOAD_REPLY, "download_reply");
macro_rules! user_auth_object {
($ty:ident, $tag:expr, $what:literal, $field:ident) => {
impl<'a> Parse<'a> for $ty<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, $tag, $what)?;
if body.len() < BINARY_ID_LEN {
return Err(Error::BufferTooShort {
need: BINARY_ID_LEN,
have: body.len(),
what: $what,
});
}
Ok(Self {
binary_id: BinaryId::read(body),
$field: &body[BINARY_ID_LEN..],
})
}
}
impl Serialize for $ty<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(BINARY_ID_LEN + self.$field.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = BINARY_ID_LEN + self.$field.len();
let mut pos = objects::write_apdu_header($tag, body_len, buf)?;
self.binary_id.write(&mut buf[pos..]);
pos += BINARY_ID_LEN;
buf[pos..pos + self.$field.len()].copy_from_slice(self.$field);
Ok(pos + self.$field.len())
}
}
};
}
user_auth_object!(
UserAuthInitiate,
tag::USER_AUTH_INITIATE,
"user_authorization_initiate",
data
);
user_auth_object!(
UserAuthResult,
tag::USER_AUTH_RESULT,
"user_authorization_result",
result
);
pub const DSMCC_PROTOCOL_DISCRIMINATOR: u8 = 0x11;
pub const DSMCC_TYPE_DOWNLOAD: u8 = 0x03;
pub const MSG_ID_DOWNLOAD_INFO_REQUEST: u16 = 0x1001;
pub const MSG_ID_DOWNLOAD_INFO_RESPONSE: u16 = 0x1002;
pub const MSG_ID_DOWNLOAD_DATA_BLOCK: u16 = 0x1003;
pub const MSG_ID_DOWNLOAD_DATA_REQUEST: u16 = 0x1004;
pub const MSG_ID_DOWNLOAD_CANCEL: u16 = 0x1005;
fn parse_dsmcc_header<'a>(
body: &'a [u8],
what: &'static str,
) -> Result<(u32, u8, &'a [u8], &'a [u8])> {
const HDR: usize = 12;
if body.len() < HDR {
return Err(Error::BufferTooShort {
need: HDR,
have: body.len(),
what,
});
}
let transaction_id = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
let adaptation_length = body[9] as usize;
if body.len() < HDR + adaptation_length {
return Err(Error::BufferTooShort {
need: HDR + adaptation_length,
have: body.len(),
what,
});
}
let adaptation = &body[HDR..HDR + adaptation_length];
let rest = &body[HDR + adaptation_length..];
Ok((transaction_id, adaptation_length as u8, adaptation, rest))
}
fn write_dsmcc_header(
buf: &mut [u8],
message_id: u16,
transaction_id: u32,
adaptation: &[u8],
message_length: usize,
) -> usize {
buf[0] = DSMCC_PROTOCOL_DISCRIMINATOR;
buf[1] = DSMCC_TYPE_DOWNLOAD;
buf[2..4].copy_from_slice(&message_id.to_be_bytes());
buf[4..8].copy_from_slice(&transaction_id.to_be_bytes());
buf[8] = 0xFF; buf[9] = adaptation.len() as u8;
buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
buf[12..12 + adaptation.len()].copy_from_slice(adaptation);
12 + adaptation.len()
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadInfoRequest<'a> {
pub transaction_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub adaptation: &'a [u8],
pub buffer_size: u32,
pub maximum_block_size: u16,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub compatibility_descriptor: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub private_data: &'a [u8],
}
impl<'a> Parse<'a> for DownloadInfoRequest<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
let what = "DownloadInfoRequest";
let (transaction_id, _adapt_len, adaptation, rest) = parse_dsmcc_header(body, what)?;
if rest.len() < 8 {
return Err(Error::BufferTooShort {
need: 8,
have: rest.len(),
what,
});
}
let buffer_size = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
let maximum_block_size = u16::from_be_bytes([rest[4], rest[5]]);
let compat_len = u16::from_be_bytes([rest[6], rest[7]]) as usize;
let compat_start = 6; let compat_block_end = compat_start + 2 + compat_len;
if rest.len() < compat_block_end + 2 {
return Err(Error::BufferTooShort {
need: compat_block_end + 2,
have: rest.len(),
what,
});
}
let compatibility_descriptor = &rest[compat_start..compat_block_end];
let priv_len =
u16::from_be_bytes([rest[compat_block_end], rest[compat_block_end + 1]]) as usize;
let priv_start = compat_block_end + 2;
let priv_end = priv_start + priv_len;
if rest.len() < priv_end {
return Err(Error::BufferTooShort {
need: priv_end,
have: rest.len(),
what,
});
}
Ok(Self {
transaction_id,
adaptation,
buffer_size,
maximum_block_size,
compatibility_descriptor,
private_data: &rest[priv_start..priv_end],
})
}
}
impl Serialize for DownloadInfoRequest<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
12 + self.adaptation.len()
+ 6
+ self.compatibility_descriptor.len()
+ 2
+ self.private_data.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
let message_length = total - 12;
let mut pos = write_dsmcc_header(
buf,
MSG_ID_DOWNLOAD_INFO_REQUEST,
self.transaction_id,
self.adaptation,
message_length,
);
buf[pos..pos + 4].copy_from_slice(&self.buffer_size.to_be_bytes());
pos += 4;
buf[pos..pos + 2].copy_from_slice(&self.maximum_block_size.to_be_bytes());
pos += 2;
buf[pos..pos + self.compatibility_descriptor.len()]
.copy_from_slice(self.compatibility_descriptor);
pos += self.compatibility_descriptor.len();
buf[pos..pos + 2].copy_from_slice(&(self.private_data.len() as u16).to_be_bytes());
pos += 2;
buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
Ok(pos + self.private_data.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadInfoResponse<'a> {
pub transaction_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub adaptation: &'a [u8],
pub download_id: u32,
pub block_size: u16,
pub window_size: u8,
pub ack_period: u8,
pub tc_download_window: u32,
pub tc_download_scenario: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub compatibility_descriptor: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub modules: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub private_data: &'a [u8],
}
impl<'a> Parse<'a> for DownloadInfoResponse<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
let what = "DownloadInfoResponse";
let (transaction_id, _adapt_len, adaptation, rest) = parse_dsmcc_header(body, what)?;
const FIXED: usize = 4 + 2 + 1 + 1 + 4 + 4 + 2;
if rest.len() < FIXED {
return Err(Error::BufferTooShort {
need: FIXED,
have: rest.len(),
what,
});
}
let download_id = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
let block_size = u16::from_be_bytes([rest[4], rest[5]]);
let window_size = rest[6];
let ack_period = rest[7];
let tc_download_window = u32::from_be_bytes([rest[8], rest[9], rest[10], rest[11]]);
let tc_download_scenario = u32::from_be_bytes([rest[12], rest[13], rest[14], rest[15]]);
let compat_len_off = 16;
let compat_len =
u16::from_be_bytes([rest[compat_len_off], rest[compat_len_off + 1]]) as usize;
let compat_block_end = compat_len_off + 2 + compat_len;
if rest.len() < compat_block_end + 2 {
return Err(Error::BufferTooShort {
need: compat_block_end + 2,
have: rest.len(),
what,
});
}
let compatibility_descriptor = &rest[compat_len_off..compat_block_end];
let number_of_modules =
u16::from_be_bytes([rest[compat_block_end], rest[compat_block_end + 1]]) as usize;
let mut mpos = compat_block_end + 2;
for _ in 0..number_of_modules {
if rest.len() < mpos + 8 {
return Err(Error::BufferTooShort {
need: mpos + 8,
have: rest.len(),
what,
});
}
let module_info_len = rest[mpos + 7] as usize;
mpos += 8 + module_info_len;
if rest.len() < mpos {
return Err(Error::BufferTooShort {
need: mpos,
have: rest.len(),
what,
});
}
}
let modules = &rest[compat_block_end..mpos];
if rest.len() < mpos + 2 {
return Err(Error::BufferTooShort {
need: mpos + 2,
have: rest.len(),
what,
});
}
let priv_len = u16::from_be_bytes([rest[mpos], rest[mpos + 1]]) as usize;
let priv_start = mpos + 2;
let priv_end = priv_start + priv_len;
if rest.len() < priv_end {
return Err(Error::BufferTooShort {
need: priv_end,
have: rest.len(),
what,
});
}
Ok(Self {
transaction_id,
adaptation,
download_id,
block_size,
window_size,
ack_period,
tc_download_window,
tc_download_scenario,
compatibility_descriptor,
modules,
private_data: &rest[priv_start..priv_end],
})
}
}
impl Serialize for DownloadInfoResponse<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
12 + self.adaptation.len()
+ 16
+ self.compatibility_descriptor.len()
+ self.modules.len()
+ 2
+ self.private_data.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
let message_length = total - 12;
let mut pos = write_dsmcc_header(
buf,
MSG_ID_DOWNLOAD_INFO_RESPONSE,
self.transaction_id,
self.adaptation,
message_length,
);
buf[pos..pos + 4].copy_from_slice(&self.download_id.to_be_bytes());
pos += 4;
buf[pos..pos + 2].copy_from_slice(&self.block_size.to_be_bytes());
pos += 2;
buf[pos] = self.window_size;
buf[pos + 1] = self.ack_period;
pos += 2;
buf[pos..pos + 4].copy_from_slice(&self.tc_download_window.to_be_bytes());
pos += 4;
buf[pos..pos + 4].copy_from_slice(&self.tc_download_scenario.to_be_bytes());
pos += 4;
buf[pos..pos + self.compatibility_descriptor.len()]
.copy_from_slice(self.compatibility_descriptor);
pos += self.compatibility_descriptor.len();
buf[pos..pos + self.modules.len()].copy_from_slice(self.modules);
pos += self.modules.len();
buf[pos..pos + 2].copy_from_slice(&(self.private_data.len() as u16).to_be_bytes());
pos += 2;
buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
Ok(pos + self.private_data.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadCancel<'a> {
pub transaction_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub adaptation: &'a [u8],
pub download_id: u32,
pub module_id: u16,
pub block_number: u16,
pub download_cancel_reason: u8,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub private_data: &'a [u8],
}
impl<'a> Parse<'a> for DownloadCancel<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
let what = "DownloadCancel";
let (transaction_id, _adapt_len, adaptation, rest) = parse_dsmcc_header(body, what)?;
const FIXED: usize = 4 + 2 + 2 + 1 + 2;
if rest.len() < FIXED {
return Err(Error::BufferTooShort {
need: FIXED,
have: rest.len(),
what,
});
}
let download_id = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
let module_id = u16::from_be_bytes([rest[4], rest[5]]);
let block_number = u16::from_be_bytes([rest[6], rest[7]]);
let download_cancel_reason = rest[8];
let priv_len = u16::from_be_bytes([rest[9], rest[10]]) as usize;
let priv_start = 11;
let priv_end = priv_start + priv_len;
if rest.len() < priv_end {
return Err(Error::BufferTooShort {
need: priv_end,
have: rest.len(),
what,
});
}
Ok(Self {
transaction_id,
adaptation,
download_id,
module_id,
block_number,
download_cancel_reason,
private_data: &rest[priv_start..priv_end],
})
}
}
impl Serialize for DownloadCancel<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
12 + self.adaptation.len() + 9 + 2 + self.private_data.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
let message_length = total - 12;
let mut pos = write_dsmcc_header(
buf,
MSG_ID_DOWNLOAD_CANCEL,
self.transaction_id,
self.adaptation,
message_length,
);
buf[pos..pos + 4].copy_from_slice(&self.download_id.to_be_bytes());
pos += 4;
buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
pos += 2;
buf[pos..pos + 2].copy_from_slice(&self.block_number.to_be_bytes());
pos += 2;
buf[pos] = self.download_cancel_reason;
pos += 1;
buf[pos..pos + 2].copy_from_slice(&(self.private_data.len() as u16).to_be_bytes());
pos += 2;
buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
Ok(pos + self.private_data.len())
}
}
fn parse_dsmcc_data_header<'a>(
body: &'a [u8],
what: &'static str,
) -> Result<(u32, &'a [u8], &'a [u8])> {
const HDR: usize = 12;
if body.len() < HDR {
return Err(Error::BufferTooShort {
need: HDR,
have: body.len(),
what,
});
}
let download_id = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
let adaptation_length = body[9] as usize;
if body.len() < HDR + adaptation_length {
return Err(Error::BufferTooShort {
need: HDR + adaptation_length,
have: body.len(),
what,
});
}
let adaptation = &body[HDR..HDR + adaptation_length];
Ok((download_id, adaptation, &body[HDR + adaptation_length..]))
}
fn write_dsmcc_data_header(
buf: &mut [u8],
message_id: u16,
download_id: u32,
adaptation: &[u8],
message_length: usize,
) -> usize {
buf[0] = DSMCC_PROTOCOL_DISCRIMINATOR;
buf[1] = DSMCC_TYPE_DOWNLOAD;
buf[2..4].copy_from_slice(&message_id.to_be_bytes());
buf[4..8].copy_from_slice(&download_id.to_be_bytes());
buf[8] = 0xFF; buf[9] = adaptation.len() as u8;
buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
buf[12..12 + adaptation.len()].copy_from_slice(adaptation);
12 + adaptation.len()
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadDataRequest<'a> {
pub download_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub adaptation: &'a [u8],
pub module_id: u16,
pub block_number: u16,
pub download_reason: u8,
}
impl<'a> Parse<'a> for DownloadDataRequest<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
let what = "DownloadDataRequest";
let (download_id, adaptation, rest) = parse_dsmcc_data_header(body, what)?;
const FIXED: usize = 5;
if rest.len() < FIXED {
return Err(Error::BufferTooShort {
need: FIXED,
have: rest.len(),
what,
});
}
Ok(Self {
download_id,
adaptation,
module_id: u16::from_be_bytes([rest[0], rest[1]]),
block_number: u16::from_be_bytes([rest[2], rest[3]]),
download_reason: rest[4],
})
}
}
impl Serialize for DownloadDataRequest<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
12 + self.adaptation.len() + 5
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
let message_length = total - 12;
let mut pos = write_dsmcc_data_header(
buf,
MSG_ID_DOWNLOAD_DATA_REQUEST,
self.download_id,
self.adaptation,
message_length,
);
buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
pos += 2;
buf[pos..pos + 2].copy_from_slice(&self.block_number.to_be_bytes());
pos += 2;
buf[pos] = self.download_reason;
Ok(pos + 1)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DownloadDataBlock<'a> {
pub download_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub adaptation: &'a [u8],
pub module_id: u16,
pub module_version: u8,
pub block_number: u16,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub block_data: &'a [u8],
}
impl<'a> Parse<'a> for DownloadDataBlock<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
let what = "DownloadDataBlock";
let (download_id, adaptation, rest) = parse_dsmcc_data_header(body, what)?;
const FIXED: usize = 6;
if rest.len() < FIXED {
return Err(Error::BufferTooShort {
need: FIXED,
have: rest.len(),
what,
});
}
Ok(Self {
download_id,
adaptation,
module_id: u16::from_be_bytes([rest[0], rest[1]]),
module_version: rest[2],
block_number: u16::from_be_bytes([rest[4], rest[5]]),
block_data: &rest[FIXED..],
})
}
}
impl Serialize for DownloadDataBlock<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
12 + self.adaptation.len() + 6 + self.block_data.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
let message_length = total - 12;
let mut pos = write_dsmcc_data_header(
buf,
MSG_ID_DOWNLOAD_DATA_BLOCK,
self.download_id,
self.adaptation,
message_length,
);
buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
pos += 2;
buf[pos] = self.module_version;
buf[pos + 1] = 0xFF; pos += 2;
buf[pos..pos + 2].copy_from_slice(&self.block_number.to_be_bytes());
pos += 2;
buf[pos..pos + self.block_data.len()].copy_from_slice(self.block_data);
Ok(pos + self.block_data.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum DownloadApdu<'a> {
DownloadEnquiry(DownloadEnquiry<'a>),
DownloadReply(DownloadReply<'a>),
UserAuthInitiate(UserAuthInitiate<'a>),
UserAuthResult(UserAuthResult<'a>),
}
impl<'a> DownloadApdu<'a> {
pub fn parse(body: &'a [u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "download apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::DOWNLOAD_ENQ => Ok(Self::DownloadEnquiry(DownloadEnquiry::parse(body)?)),
tag::DOWNLOAD_REPLY => Ok(Self::DownloadReply(DownloadReply::parse(body)?)),
tag::USER_AUTH_INITIATE => Ok(Self::UserAuthInitiate(UserAuthInitiate::parse(body)?)),
tag::USER_AUTH_RESULT => Ok(Self::UserAuthResult(UserAuthResult::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::DOWNLOAD_ENQ.as_u24(),
what: "download",
}),
}
}
}
impl Serialize for DownloadApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::DownloadEnquiry(o) => o.serialized_len(),
Self::DownloadReply(o) => o.serialized_len(),
Self::UserAuthInitiate(o) => o.serialized_len(),
Self::UserAuthResult(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::DownloadEnquiry(o) => o.serialize_into(buf),
Self::DownloadReply(o) => o.serialize_into(buf),
Self::UserAuthInitiate(o) => o.serialize_into(buf),
Self::UserAuthResult(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn download_enquiry_round_trips_and_bites() {
let enq = DownloadEnquiry {
dsmcc_message: &[0x11, 0x03, 0x10, 0x01],
};
let bytes = enq.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x04, 0x11, 0x03, 0x10, 0x01]);
assert_eq!(DownloadEnquiry::parse(&bytes).unwrap(), enq);
let other = DownloadEnquiry {
dsmcc_message: &[0x11, 0x03, 0x10, 0x02],
};
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn download_reply_round_trips() {
let rep = DownloadReply {
dsmcc_message: &[0xAA],
};
let bytes = rep.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0xAA]);
assert_eq!(DownloadReply::parse(&bytes).unwrap(), rep);
}
#[test]
fn user_auth_initiate_round_trips_and_bites() {
let uai = UserAuthInitiate {
binary_id: BinaryId {
specifier: 0x00_1B_67,
model: 0x1234,
version: 0x0005,
},
data: &[0xCA, 0xFE],
};
let bytes = uai.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x80, 0x02, 0x09, 0x00, 0x1B, 0x67, 0x12, 0x34, 0x00, 0x05, 0xCA, 0xFE]
);
assert_eq!(UserAuthInitiate::parse(&bytes).unwrap(), uai);
let mut other = uai.clone();
other.binary_id.version = 0x0006;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn user_auth_result_round_trips() {
let uar = UserAuthResult {
binary_id: BinaryId {
specifier: 0xAABBCC & 0x00FF_FFFF,
model: 1,
version: 2,
},
result: &[0x01],
};
let bytes = uar.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x80, 0x03, 0x08, 0xAA, 0xBB, 0xCC, 0x00, 0x01, 0x00, 0x02, 0x01]
);
assert_eq!(UserAuthResult::parse(&bytes).unwrap(), uar);
}
#[test]
fn download_info_request_round_trips_and_bites() {
let compat = [0x00, 0x04, 0x00, 0x00, 0xAA, 0xBB];
let req = DownloadInfoRequest {
transaction_id: 0x0000_0001,
adaptation: &[],
buffer_size: 0x0001_0000,
maximum_block_size: 0x0200,
compatibility_descriptor: &compat,
private_data: &[],
};
let bytes = req.to_bytes();
assert_eq!(DownloadInfoRequest::parse(&bytes).unwrap(), req);
assert_eq!(bytes[0], 0x11);
assert_eq!(bytes[1], 0x03);
assert_eq!(&bytes[2..4], &[0x10, 0x01]);
let mut other = req.clone();
other.buffer_size = 0x0002_0000;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn download_info_request_with_adaptation() {
let compat = [0x00, 0x02, 0x00, 0x00];
let req = DownloadInfoRequest {
transaction_id: 0x12,
adaptation: &[0x01, 0x02, 0x03],
buffer_size: 1,
maximum_block_size: 2,
compatibility_descriptor: &compat,
private_data: &[],
};
let bytes = req.to_bytes();
assert_eq!(bytes[9], 0x03); assert_eq!(DownloadInfoRequest::parse(&bytes).unwrap(), req);
}
#[test]
fn download_info_response_multi_module_round_trips_and_bites() {
let compat = [0x00, 0x02, 0x00, 0x00];
let modules = [
0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x01, 0x01, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, ];
let resp = DownloadInfoResponse {
transaction_id: 1,
adaptation: &[],
download_id: 0xDEAD_BEEF,
block_size: 0x0100,
window_size: 4,
ack_period: 2,
tc_download_window: 1000,
tc_download_scenario: 2000,
compatibility_descriptor: &compat,
modules: &modules,
private_data: &[],
};
let bytes = resp.to_bytes();
assert_eq!(DownloadInfoResponse::parse(&bytes).unwrap(), resp);
let mut other = resp.clone();
other.window_size = 5;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn download_cancel_round_trips() {
let cancel = DownloadCancel {
transaction_id: 0x10,
adaptation: &[],
download_id: 0x01,
module_id: 0x02,
block_number: 0x03,
download_cancel_reason: 0x05,
private_data: &[],
};
let bytes = cancel.to_bytes();
assert_eq!(DownloadCancel::parse(&bytes).unwrap(), cancel);
assert_eq!(&bytes[2..4], &[0x10, 0x05]); }
#[test]
fn download_data_request_round_trips_and_bites() {
let req = DownloadDataRequest {
download_id: 0xDEAD_BEEF,
adaptation: &[],
module_id: 0x0001,
block_number: 0x0002,
download_reason: 0x00,
};
let bytes = req.to_bytes();
assert_eq!(DownloadDataRequest::parse(&bytes).unwrap(), req);
assert_eq!(&bytes[2..4], &[0x10, 0x04]);
assert_eq!(&bytes[4..8], &0xDEAD_BEEFu32.to_be_bytes());
let mut other = req.clone();
other.block_number = 0x0003;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn download_data_block_round_trips_and_bites() {
let block = DownloadDataBlock {
download_id: 0x0000_0001,
adaptation: &[],
module_id: 0x0001,
module_version: 0x02,
block_number: 0x0003,
block_data: &[0xFE, 0xED, 0xFA, 0xCE],
};
let bytes = block.to_bytes();
assert_eq!(DownloadDataBlock::parse(&bytes).unwrap(), block);
assert_eq!(&bytes[2..4], &[0x10, 0x03]); let mut other = block.clone();
other.block_data = &[0xFE, 0xED, 0xFA, 0xCF];
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn enquiry_round_trips_a_real_dsmcc_message() {
let inner = DownloadDataRequest {
download_id: 0x1234_5678,
adaptation: &[],
module_id: 1,
block_number: 1,
download_reason: 0,
};
let inner_bytes = inner.to_bytes();
let enq = DownloadEnquiry {
dsmcc_message: &inner_bytes,
};
let outer = enq.to_bytes();
let parsed = DownloadEnquiry::parse(&outer).unwrap();
assert_eq!(parsed, enq);
assert_eq!(
DownloadDataRequest::parse(parsed.dsmcc_message).unwrap(),
inner
);
}
#[test]
fn dispatch_routes_each_tag() {
let enq = DownloadEnquiry {
dsmcc_message: &[0x11],
}
.to_bytes();
assert!(matches!(
DownloadApdu::parse(&enq).unwrap(),
DownloadApdu::DownloadEnquiry(_)
));
let uar = UserAuthResult {
binary_id: BinaryId::default(),
result: &[0x01],
}
.to_bytes();
let parsed = DownloadApdu::parse(&uar).unwrap();
assert!(matches!(parsed, DownloadApdu::UserAuthResult(_)));
assert_eq!(parsed.to_bytes(), uar);
}
}