use anyhow::{bail, Result};
pub mod command {
pub const ICC_POWER_ON: u8 = 0x62;
pub const ICC_POWER_OFF: u8 = 0x63;
pub const GET_SLOT_STATUS: u8 = 0x65;
pub const XFR_BLOCK: u8 = 0x6F;
pub const GET_PARAMETERS: u8 = 0x6C;
pub const RESET_PARAMETERS: u8 = 0x6D;
pub const SET_PARAMETERS: u8 = 0x61;
pub const ESCAPE: u8 = 0x6B;
pub const ICC_CLOCK: u8 = 0x6E;
pub const T0_APDU: u8 = 0x6A;
pub const ABORT: u8 = 0x72;
pub const SET_DATA_RATE_AND_CLOCK_FREQUENCY: u8 = 0x73;
}
pub mod response {
pub const DATA_BLOCK: u8 = 0x80;
pub const SLOT_STATUS: u8 = 0x81;
pub const PARAMETERS: u8 = 0x82;
pub const ESCAPE: u8 = 0x83;
pub const DATA_RATE_AND_CLOCK_FREQUENCY: u8 = 0x84;
pub const NOTIFY_SLOT_CHANGE: u8 = 0x50;
}
pub mod icc_status {
pub const PRESENT_ACTIVE: u8 = 0;
pub const PRESENT_INACTIVE: u8 = 1;
pub const NOT_PRESENT: u8 = 2;
}
pub mod command_status {
pub const PROCESSED: u8 = 0;
pub const FAILED: u8 = 1;
}
pub mod error {
pub const CMD_NOT_SUPPORTED: u8 = 0x00;
pub const ICC_MUTE: u8 = 0xFE;
pub const NONE: u8 = 0x00;
}
pub const HEADER_LEN: usize = 10;
pub const MAX_MESSAGE_LEN: usize = 271;
pub const MAX_PAYLOAD_LEN: usize = MAX_MESSAGE_LEN - HEADER_LEN;
pub fn status_byte(icc: u8, command: u8) -> u8 {
(icc & 0x03) | ((command & 0x03) << 6)
}
#[derive(Debug, Clone)]
pub struct CcidCommand {
pub message_type: u8,
pub slot: u8,
pub seq: u8,
pub params: [u8; 3],
pub data: Vec<u8>,
}
impl CcidCommand {
pub fn parse(bytes: &[u8]) -> Result<Self> {
if bytes.len() < HEADER_LEN {
bail!(
"CCID message too short: {} byte(s), need at least {}",
bytes.len(),
HEADER_LEN
);
}
let declared = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
if declared > MAX_PAYLOAD_LEN {
bail!(
"CCID dwLength={} exceeds the advertised maximum payload of {} byte(s)",
declared,
MAX_PAYLOAD_LEN
);
}
if bytes.len() < HEADER_LEN + declared {
bail!(
"CCID message truncated: dwLength={} but only {} payload byte(s) present",
declared,
bytes.len() - HEADER_LEN
);
}
Ok(Self {
message_type: bytes[0],
slot: bytes[5],
seq: bytes[6],
params: [bytes[7], bytes[8], bytes[9]],
data: bytes[HEADER_LEN..HEADER_LEN + declared].to_vec(),
})
}
pub fn name(&self) -> &'static str {
match self.message_type {
command::ICC_POWER_ON => "PC_to_RDR_IccPowerOn",
command::ICC_POWER_OFF => "PC_to_RDR_IccPowerOff",
command::GET_SLOT_STATUS => "PC_to_RDR_GetSlotStatus",
command::XFR_BLOCK => "PC_to_RDR_XfrBlock",
command::GET_PARAMETERS => "PC_to_RDR_GetParameters",
command::RESET_PARAMETERS => "PC_to_RDR_ResetParameters",
command::SET_PARAMETERS => "PC_to_RDR_SetParameters",
command::ESCAPE => "PC_to_RDR_Escape",
command::ICC_CLOCK => "PC_to_RDR_IccClock",
command::T0_APDU => "PC_to_RDR_T0APDU",
command::ABORT => "PC_to_RDR_Abort",
command::SET_DATA_RATE_AND_CLOCK_FREQUENCY => "PC_to_RDR_SetDataRateAndClockFrequency",
_ => "PC_to_RDR_Unknown",
}
}
}
fn build(message_type: u8, slot: u8, seq: u8, params: [u8; 3], payload: &[u8]) -> Result<Vec<u8>> {
if payload.len() > MAX_PAYLOAD_LEN {
bail!(
"CCID payload of {} byte(s) exceeds dwMaxCCIDMessageLength ({} byte(s) of payload)",
payload.len(),
MAX_PAYLOAD_LEN
);
}
let mut message = Vec::with_capacity(HEADER_LEN + payload.len());
message.push(message_type);
message.extend_from_slice(&(payload.len() as u32).to_le_bytes());
message.push(slot);
message.push(seq);
message.extend_from_slice(¶ms);
message.extend_from_slice(payload);
Ok(message)
}
pub fn data_block(slot: u8, seq: u8, icc: u8, cmd: u8, err: u8, payload: &[u8]) -> Result<Vec<u8>> {
build(
response::DATA_BLOCK,
slot,
seq,
[status_byte(icc, cmd), err, 0x00],
payload,
)
}
pub fn slot_status(slot: u8, seq: u8, icc: u8, cmd: u8, err: u8) -> Vec<u8> {
build(
response::SLOT_STATUS,
slot,
seq,
[status_byte(icc, cmd), err, 0x00],
&[],
)
.expect("slot status has an empty payload")
}
pub fn parameters_t0(slot: u8, seq: u8, icc: u8) -> Vec<u8> {
const T0_PARAMETERS: [u8; 5] = [0x11, 0x00, 0x00, 0x0A, 0x00];
build(
response::PARAMETERS,
slot,
seq,
[
status_byte(icc, command_status::PROCESSED),
error::NONE,
0x00,
],
&T0_PARAMETERS,
)
.expect("T=0 parameter block is 5 bytes")
}
pub fn escape_unsupported(slot: u8, seq: u8, icc: u8) -> Vec<u8> {
build(
response::ESCAPE,
slot,
seq,
[
status_byte(icc, command_status::FAILED),
error::CMD_NOT_SUPPORTED,
0x00,
],
&[],
)
.expect("escape response has an empty payload")
}
pub fn notify_slot_change(card_present: bool) -> Vec<u8> {
let mut bitmap = 0b10; if card_present {
bitmap |= 0b01;
}
vec![response::NOTIFY_SLOT_CHANGE, bitmap]
}
pub fn class_descriptor() -> Vec<u8> {
let mut descriptor = Vec::with_capacity(54);
descriptor.push(54); descriptor.push(0x21); descriptor.extend_from_slice(&0x0110u16.to_le_bytes()); descriptor.push(0x00); descriptor.push(0x07); descriptor.extend_from_slice(&0x0000_0003u32.to_le_bytes()); descriptor.extend_from_slice(&3_580u32.to_le_bytes()); descriptor.extend_from_slice(&3_580u32.to_le_bytes()); descriptor.push(0x00); descriptor.extend_from_slice(&9_600u32.to_le_bytes()); descriptor.extend_from_slice(&9_600u32.to_le_bytes()); descriptor.push(0x00); descriptor.extend_from_slice(&254u32.to_le_bytes()); descriptor.extend_from_slice(&0u32.to_le_bytes()); descriptor.extend_from_slice(&0u32.to_le_bytes()); descriptor.extend_from_slice(&0x0002_04BAu32.to_le_bytes());
descriptor.extend_from_slice(&(MAX_MESSAGE_LEN as u32).to_le_bytes()); descriptor.push(0x00); descriptor.push(0x00); descriptor.extend_from_slice(&0u16.to_le_bytes()); descriptor.push(0x00); descriptor.push(0x01); descriptor
}