use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
use tracing::{debug, trace, warn};
use super::ccid::{self, CcidCommand};
#[derive(Debug)]
pub struct CardState {
pub atr: Vec<u8>,
pub card_present: bool,
}
#[derive(Debug)]
pub struct PendingApdu {
pub slot: u8,
pub seq: u8,
pub apdu: Vec<u8>,
}
pub const SLOT: u8 = 0;
#[derive(Debug)]
pub struct UsbCcidHandler {
card: Arc<Mutex<CardState>>,
tx_queue: VecDeque<Vec<u8>>,
apdu_tx: mpsc::UnboundedSender<PendingApdu>,
powered: bool,
slot_change_pending: bool,
}
impl UsbCcidHandler {
pub fn new(card: Arc<Mutex<CardState>>, apdu_tx: mpsc::UnboundedSender<PendingApdu>) -> Self {
Self {
card,
tx_queue: VecDeque::new(),
apdu_tx,
powered: false,
slot_change_pending: true,
}
}
pub fn endpoints() -> Vec<usbip::UsbEndpoint> {
vec![
usbip::UsbEndpoint {
address: 0x81, attributes: usbip::EndpointAttributes::Interrupt as u8,
max_packet_size: 8,
interval: 16,
},
usbip::UsbEndpoint {
address: 0x82, attributes: usbip::EndpointAttributes::Bulk as u8,
max_packet_size: 64,
interval: 0,
},
usbip::UsbEndpoint {
address: 0x02, attributes: usbip::EndpointAttributes::Bulk as u8,
max_packet_size: 64,
interval: 0,
},
]
}
pub fn queue_apdu_response(&mut self, slot: u8, seq: u8, response: &[u8]) {
let icc = self.icc_status();
match ccid::data_block(
slot,
seq,
icc,
ccid::command_status::PROCESSED,
ccid::error::NONE,
response,
) {
Ok(message) => {
trace!(
"CCID queueing RDR_to_PC_DataBlock seq={} with {} response byte(s)",
seq,
response.len()
);
self.tx_queue.push_back(message);
}
Err(e) => {
warn!("CCID response for seq={} cannot be framed: {}", seq, e);
self.tx_queue.push_back(ccid::slot_status(
slot,
seq,
icc,
ccid::command_status::FAILED,
ccid::error::ICC_MUTE,
));
}
}
}
pub fn note_slot_change(&mut self) {
self.slot_change_pending = true;
if !self.card_present() {
self.powered = false;
}
}
#[allow(dead_code)]
pub fn pending_responses(&self) -> usize {
self.tx_queue.len()
}
fn card_present(&self) -> bool {
match self.card.lock() {
Ok(card) => card.card_present,
Err(poisoned) => poisoned.into_inner().card_present,
}
}
fn atr(&self) -> Vec<u8> {
match self.card.lock() {
Ok(card) => card.atr.clone(),
Err(poisoned) => poisoned.into_inner().atr.clone(),
}
}
fn icc_status(&self) -> u8 {
if !self.card_present() {
ccid::icc_status::NOT_PRESENT
} else if self.powered {
ccid::icc_status::PRESENT_ACTIVE
} else {
ccid::icc_status::PRESENT_INACTIVE
}
}
fn fail(&self, seq: u8, slot: u8, error: u8) -> Vec<u8> {
ccid::slot_status(
slot,
seq,
self.icc_status(),
ccid::command_status::FAILED,
error,
)
}
fn handle_ccid_command(&mut self, command: &CcidCommand) -> Option<Vec<u8>> {
let seq = command.seq;
let slot = command.slot;
if slot != SLOT {
warn!("CCID command for slot {} but only slot 0 exists", slot);
return Some(self.fail(seq, slot, ccid::error::ICC_MUTE));
}
match command.message_type {
ccid::command::ICC_POWER_ON => {
if !self.card_present() {
debug!("CCID IccPowerOn refused: no card in the slot");
return Some(self.fail(seq, slot, ccid::error::ICC_MUTE));
}
self.powered = true;
let atr = self.atr();
debug!("CCID IccPowerOn: answering with a {}-byte ATR", atr.len());
Some(
ccid::data_block(
slot,
seq,
ccid::icc_status::PRESENT_ACTIVE,
ccid::command_status::PROCESSED,
ccid::error::NONE,
&atr,
)
.unwrap_or_else(|e| {
warn!("CCID ATR cannot be framed: {}", e);
ccid::slot_status(
slot,
seq,
ccid::icc_status::PRESENT_INACTIVE,
ccid::command_status::FAILED,
ccid::error::ICC_MUTE,
)
}),
)
}
ccid::command::ICC_POWER_OFF => {
self.powered = false;
debug!("CCID IccPowerOff");
Some(ccid::slot_status(
slot,
seq,
self.icc_status(),
ccid::command_status::PROCESSED,
ccid::error::NONE,
))
}
ccid::command::GET_SLOT_STATUS | ccid::command::ABORT | ccid::command::ICC_CLOCK => {
Some(ccid::slot_status(
slot,
seq,
self.icc_status(),
ccid::command_status::PROCESSED,
ccid::error::NONE,
))
}
ccid::command::T0_APDU => Some(ccid::slot_status(
slot,
seq,
self.icc_status(),
ccid::command_status::PROCESSED,
ccid::error::NONE,
)),
ccid::command::GET_PARAMETERS
| ccid::command::SET_PARAMETERS
| ccid::command::RESET_PARAMETERS => {
Some(ccid::parameters_t0(slot, seq, self.icc_status()))
}
ccid::command::ESCAPE => {
debug!("CCID Escape refused: no vendor-specific commands are supported");
Some(ccid::escape_unsupported(slot, seq, self.icc_status()))
}
ccid::command::XFR_BLOCK => {
if !self.card_present() {
debug!("CCID XfrBlock refused: no card in the slot");
return Some(self.fail(seq, slot, ccid::error::ICC_MUTE));
}
if !self.powered {
debug!("CCID XfrBlock refused: card has not been powered on");
return Some(self.fail(seq, slot, ccid::error::ICC_MUTE));
}
if command.data.is_empty() {
debug!("CCID XfrBlock carried no APDU");
return Some(self.fail(seq, slot, ccid::error::ICC_MUTE));
}
if self
.apdu_tx
.send(PendingApdu {
slot,
seq,
apdu: command.data.clone(),
})
.is_err()
{
warn!("CCID XfrBlock dropped: the connection task has exited");
return Some(self.fail(seq, slot, ccid::error::ICC_MUTE));
}
trace!(
"CCID XfrBlock seq={} forwarded ({} APDU byte(s))",
seq,
command.data.len()
);
None
}
other => {
debug!("CCID message type {:#04x} is not supported", other);
Some(self.fail(seq, slot, ccid::error::CMD_NOT_SUPPORTED))
}
}
}
fn handle_control(&mut self, setup: usbip::SetupPacket) -> Vec<u8> {
debug!(
"CCID control request type={:#04x} request={:#04x} value={:#06x}",
setup.request_type, setup.request, setup.value
);
Vec::new()
}
}
impl usbip::UsbInterfaceHandler for UsbCcidHandler {
fn handle_urb(
&mut self,
_interface: &usbip::UsbInterface,
ep: usbip::UsbEndpoint,
transfer_buffer_length: u32,
setup: usbip::SetupPacket,
req: &[u8],
) -> std::result::Result<Vec<u8>, std::io::Error> {
if ep.is_ep0() {
return Ok(self.handle_control(setup));
}
match ep.direction() {
usbip::Direction::Out => {
if req.is_empty() {
return Ok(Vec::new());
}
trace!("CCID <- {} ({} bytes)", hex::encode_upper(req), req.len());
match CcidCommand::parse(req) {
Ok(command) => {
trace!("CCID command {} seq={}", command.name(), command.seq);
if let Some(response) = self.handle_ccid_command(&command) {
self.tx_queue.push_back(response);
}
}
Err(e) => {
warn!("CCID message from the host could not be parsed: {}", e);
}
}
Ok(Vec::new())
}
usbip::Direction::In => {
if ep.attributes == usbip::EndpointAttributes::Interrupt as u8 {
if self.slot_change_pending {
self.slot_change_pending = false;
let present = self.card_present();
debug!("CCID NotifySlotChange: card_present={}", present);
return Ok(ccid::notify_slot_change(present));
}
return Ok(Vec::new());
}
let limit = (ep.max_packet_size as usize).min(transfer_buffer_length as usize);
if limit == 0 {
return Ok(Vec::new());
}
let Some(mut message) = self.tx_queue.pop_front() else {
return Ok(Vec::new());
};
if message.len() > limit {
let remainder = message.split_off(limit);
self.tx_queue.push_front(remainder);
}
trace!(
"CCID -> {} ({} bytes)",
hex::encode_upper(&message),
message.len()
);
Ok(message)
}
}
}
fn get_class_specific_descriptor(&self) -> Vec<u8> {
ccid::class_descriptor()
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
}