mirror of
https://github.com/trussed-dev/usbd-ccid.git
synced 2026-06-20 04:16:20 -07:00
Basics for CCID from Zissou (ATR)
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "usbd-ccid"
|
||||
version = "0.0.0-unreleased"
|
||||
authors = ["Nicolas Stalder <n@stalder.io>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cortex-m-semihosting = { version = "0.3.5", optional = true}
|
||||
usb-device = { version = "0.2.3", features = ["control-buffer-256"] }
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"cortex-m-semihosting",
|
||||
]
|
||||
highspeed-usb = []
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use cortex_m_semihosting::hprintln;
|
||||
|
||||
use crate::{
|
||||
constants::*,
|
||||
pipe::Pipe,
|
||||
};
|
||||
|
||||
use usb_device::class_prelude::*;
|
||||
|
||||
type Result<T> = core::result::Result<T, UsbError>;
|
||||
|
||||
pub struct Ccid<Bus>
|
||||
where
|
||||
Bus: 'static + UsbBus,
|
||||
{
|
||||
interface_number: InterfaceNumber,
|
||||
pipe: Pipe<Bus>,
|
||||
must_send_zlp: bool,
|
||||
}
|
||||
|
||||
impl<Bus> Ccid<Bus>
|
||||
where
|
||||
Bus: 'static + UsbBus,
|
||||
{
|
||||
pub fn new(allocator: &'static UsbBusAllocator<Bus>) -> Self {
|
||||
let read_endpoint = allocator.bulk(PACKET_SIZE as _);
|
||||
let write_endpoint = allocator.bulk(PACKET_SIZE as _);
|
||||
let pipe = Pipe::new(read_endpoint, write_endpoint);
|
||||
let interface_number = allocator.interface();
|
||||
Self { interface_number, pipe, must_send_zlp: false }
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ClassRequests {
|
||||
}
|
||||
|
||||
impl<Bus> UsbClass<Bus> for Ccid<Bus>
|
||||
where
|
||||
Bus: 'static + UsbBus,
|
||||
{
|
||||
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter)
|
||||
-> Result<()>
|
||||
{
|
||||
writer.interface(
|
||||
self.interface_number,
|
||||
CLASS_CCID,
|
||||
SUBCLASS_NONE,
|
||||
TransferMode::Bulk as u8,
|
||||
)?;
|
||||
writer.write(
|
||||
FUNCTIONAL_INTERFACE,
|
||||
&FUNCTIONAL_INTERFACE_DESCRIPTOR,
|
||||
)?;
|
||||
writer.endpoint(&self.pipe.write);
|
||||
writer.endpoint(&self.pipe.read);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll(&mut self) {
|
||||
self.pipe.maybe_write_packet();
|
||||
}
|
||||
|
||||
fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
|
||||
if addr != self.pipe.write.address() { return; }
|
||||
|
||||
if self.must_send_zlp {
|
||||
self.pipe.write.write(&[]).ok();
|
||||
self.must_send_zlp = false;
|
||||
} else {
|
||||
self.pipe.maybe_write_packet();
|
||||
}
|
||||
}
|
||||
|
||||
fn endpoint_out(&mut self, addr: EndpointAddress) {
|
||||
if addr != self.pipe.read.address() { return; }
|
||||
|
||||
self.pipe.read_and_handle_packet();
|
||||
}
|
||||
|
||||
fn control_in(&mut self, transfer: ControlIn<Bus>) {
|
||||
use usb_device::control::*;
|
||||
|
||||
let Request { request_type, recipient, index, request, .. } = *transfer.request();
|
||||
|
||||
if index as u8 != u8::from(self.interface_number) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (request_type, recipient) == (RequestType::Class, Recipient::Interface) {
|
||||
match ClassRequest::try_from(request) {
|
||||
Ok(request) => {
|
||||
match request {
|
||||
ClassRequest::Abort => {
|
||||
// oh yeah?
|
||||
transfer.reject().ok();
|
||||
todo!();
|
||||
}
|
||||
|
||||
// not strictly needed, as our bNumClockSupported = 0
|
||||
ClassRequest::GetClockFrequencies => {
|
||||
transfer.accept(|data| {
|
||||
data.copy_from_slice(&CLOCK_FREQUENCY_KHZ);
|
||||
Ok(4)
|
||||
}).ok();
|
||||
},
|
||||
|
||||
// not strictly needed, as our bNumDataRatesSupported = 0
|
||||
ClassRequest::GetDataRates => {
|
||||
transfer.accept(|data| {
|
||||
data.copy_from_slice(&DATA_RATE_BPS);
|
||||
Ok(4)
|
||||
}).ok();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Err(()) => {
|
||||
hprintln!("unexpected request: {}", request).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn control_out(&mut self, transfer: ControlOut<Bus>) {
|
||||
// todo!();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// can be 8, 16, 32, 64 or 512
|
||||
#[cfg(feature = "highspeed-usb")]
|
||||
pub const PACKET_SIZE: usize = 512;
|
||||
#[cfg(not(feature = "highspeed-usb"))]
|
||||
pub const PACKET_SIZE: usize = 64;
|
||||
|
||||
pub const CLASS_CCID: u8 = 0x0B;
|
||||
pub const SUBCLASS_NONE: u8 = 0x0;
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum TransferMode {
|
||||
// bulk transfers, optional interrupt IN
|
||||
Bulk = 0,
|
||||
// control transfers, no interrupt IN
|
||||
ControlA = 1,
|
||||
// control transfers, optional interrupt IN
|
||||
ControlB = 2,
|
||||
}
|
||||
|
||||
pub const FUNCTIONAL_INTERFACE: u8 = 0x21;
|
||||
|
||||
pub enum ClassRequest {
|
||||
Abort = 1,
|
||||
GetClockFrequencies = 2,
|
||||
GetDataRates = 3,
|
||||
}
|
||||
|
||||
impl core::convert::TryFrom<u8> for ClassRequest {
|
||||
type Error = ();
|
||||
fn try_from(request: u8) -> core::result::Result<Self, ()> {
|
||||
Ok(match request {
|
||||
1 => Self::Abort,
|
||||
2 => Self::GetClockFrequencies,
|
||||
3 => Self::GetDataRates,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// NB: all numbers are little-endian
|
||||
|
||||
// 4000 KHz = 4MHz
|
||||
// pub const CLOCK_FREQUENCY: [u8; 4] = 4000u32.to_le_bytes();
|
||||
// instead, use Python: `import struct; struct.pack("<I", 4000)`
|
||||
pub const CLOCK_FREQUENCY_KHZ: [u8; 4] = [0xa0, 0x0f, 0x00, 0x00];
|
||||
// 307200 bps (gnuk: 9600)
|
||||
pub const DATA_RATE_BPS: [u8; 4] = [0x00, 0xb0, 0x04, 0x00];
|
||||
// 2038 (gnuk: 254)
|
||||
pub const MAX_IFSD: [u8; 4] = [0xf6, 0x07, 0x00, 0x00];
|
||||
|
||||
//
|
||||
// dwMaxCCIDMsgLen 3072 (gnuk: 271)
|
||||
pub const MAX_MSG_LENGTH: usize = 3072;
|
||||
pub const MAX_MSG_LENGTH_LE: [u8; 4] = [0x00, 0x0C, 0x00, 0x00];
|
||||
pub const NUM_SLOTS: u8 = 1;
|
||||
pub const MAX_BUSY_SLOTS: u8 = 1;
|
||||
// bPinSupport (0x0 = none, 0x01 = verification, 0x02 = modification)
|
||||
pub const PIN_SUPPORT: u8 = 0;
|
||||
|
||||
// cf. Sec. 5.1 in: https://www.usb.org/sites/default/files/DWG_Smart-Card_CCID_Rev110.pdf
|
||||
pub const FUNCTIONAL_INTERFACE_DESCRIPTOR: [u8; 52] = [
|
||||
// bcdCCID rev1.10 <-- Linux doesn't know about this
|
||||
// 0x10, 0x01,
|
||||
|
||||
// bcdCCID rev1.00
|
||||
0x00, 0x01,
|
||||
// bMaxSlotIndex
|
||||
NUM_SLOTS - 1,
|
||||
// bVoltageSupport (5.0V + 3.0V + 1.8V)
|
||||
0x07,
|
||||
// dwProtocols: T=1 only (0 = T=0, 3 = T0+T1)
|
||||
0x02, 0x00, 0x00, 0x00,
|
||||
|
||||
// dwDefaultClock (4 MHz)
|
||||
CLOCK_FREQUENCY_KHZ[0],
|
||||
CLOCK_FREQUENCY_KHZ[1],
|
||||
CLOCK_FREQUENCY_KHZ[2],
|
||||
CLOCK_FREQUENCY_KHZ[3],
|
||||
// dwMaximumClock (same)
|
||||
CLOCK_FREQUENCY_KHZ[0],
|
||||
CLOCK_FREQUENCY_KHZ[1],
|
||||
CLOCK_FREQUENCY_KHZ[2],
|
||||
CLOCK_FREQUENCY_KHZ[3],
|
||||
// bNumClockSupported
|
||||
0x00,
|
||||
|
||||
// dwDataRate (307200 bps)
|
||||
DATA_RATE_BPS[0],
|
||||
DATA_RATE_BPS[1],
|
||||
DATA_RATE_BPS[2],
|
||||
DATA_RATE_BPS[3],
|
||||
// dwMaxDataRate (same)
|
||||
DATA_RATE_BPS[0],
|
||||
DATA_RATE_BPS[1],
|
||||
DATA_RATE_BPS[2],
|
||||
DATA_RATE_BPS[3],
|
||||
// bNumDataRatesSupported
|
||||
0x00,
|
||||
|
||||
// dwMaxIFSD (2038)
|
||||
MAX_IFSD[0],
|
||||
MAX_IFSD[1],
|
||||
MAX_IFSD[2],
|
||||
MAX_IFSD[3],
|
||||
// dwSyncProtocols: none
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
// dwMechanical: no special characteristics
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// dwFeatures, see following comments
|
||||
// Auto configuration based on ATR
|
||||
// Auto activation on insert
|
||||
// Auto voltage selection
|
||||
// Auto clock change
|
||||
// Auto baud rate change
|
||||
// Auto parameter negotiation made by CCID
|
||||
// Short and extended APDU level exchange
|
||||
0xFE, 0x00, 0x04, 0x00,
|
||||
|
||||
// dwMaxCCIDMsgLen (3072)
|
||||
// gnuk: 271
|
||||
MAX_MSG_LENGTH_LE[0],
|
||||
MAX_MSG_LENGTH_LE[1],
|
||||
MAX_MSG_LENGTH_LE[2],
|
||||
MAX_MSG_LENGTH_LE[3],
|
||||
|
||||
// bClassGetResponse ("echo")
|
||||
0xFF,
|
||||
// bClassEnvelope ("echo"), gnuk: 0
|
||||
0xFF,
|
||||
// wlcdLayout (none)
|
||||
0x00, 0x00,
|
||||
// bPinSupport
|
||||
PIN_SUPPORT,
|
||||
// bMaxCCIDBusySlots
|
||||
MAX_BUSY_SLOTS,
|
||||
];
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#![no_std]
|
||||
|
||||
//! https://www.usb.org/sites/default/files/DWG_Smart-Card_CCID_Rev110.pdf
|
||||
//! https://www.usb.org/sites/default/files/DWG_Smart-Card_USB-ICC_ICCD_rev10.pdf
|
||||
|
||||
pub mod constants;
|
||||
pub mod class;
|
||||
pub mod pipe;
|
||||
|
||||
pub use class::Ccid;
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
use core::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
convert::{TryFrom, TryInto},
|
||||
};
|
||||
|
||||
use cortex_m_semihosting::hprintln;
|
||||
|
||||
use crate::constants::*;
|
||||
|
||||
use usb_device::class_prelude::*;
|
||||
|
||||
|
||||
pub struct Pipe<Bus>
|
||||
where
|
||||
Bus: UsbBus + 'static,
|
||||
{
|
||||
pub(crate) read: EndpointOut<'static, Bus>,
|
||||
pub(crate) write: EndpointIn<'static, Bus>,
|
||||
// pub(crate) rpc: TransportEndpoint<'rpc>,
|
||||
packet: Packet,
|
||||
buffer: [u8; MAX_MSG_LENGTH],
|
||||
}
|
||||
|
||||
impl<Bus> Pipe<Bus>
|
||||
where
|
||||
Bus: 'static + UsbBus,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
read: EndpointOut<'static, Bus>,
|
||||
write: EndpointIn<'static, Bus>,
|
||||
)
|
||||
-> Self { Self {
|
||||
read,
|
||||
write,
|
||||
packet: Packet { buffer: [0u8; PACKET_SIZE] },
|
||||
buffer: [0u8; MAX_MSG_LENGTH],
|
||||
} }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Packet {
|
||||
buffer: [u8; PACKET_SIZE as _],
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Packet {
|
||||
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let mut debug_struct = f.debug_struct("Packet");
|
||||
let mut debug_struct = match self.msg_type() {
|
||||
Ok(message_type) => debug_struct.field("type", &message_type),
|
||||
Err(()) => debug_struct.field("type", &self[0]),
|
||||
};
|
||||
debug_struct
|
||||
.field("slot", &self.slot())
|
||||
.field("seq", &self.slot())
|
||||
// other fields?
|
||||
// .field("data[..16]", &self.data()[..16])
|
||||
// .field("data", self.data())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum MessageType {
|
||||
PowerOn = 0x62,
|
||||
PowerOff = 0x63,
|
||||
GetSlotStatus = 0x65,
|
||||
TransferBlock = 0x6f,
|
||||
}
|
||||
|
||||
pub struct SlotStatus {
|
||||
seq: u8
|
||||
}
|
||||
|
||||
impl SlotStatus {
|
||||
pub fn new(seq: u8) -> Self {
|
||||
Self { seq }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlotStatus> for Packet {
|
||||
fn from(slot_status: SlotStatus) -> Self {
|
||||
let mut buffer = [0u8; PACKET_SIZE];
|
||||
buffer[0] = 0x81;
|
||||
// buffer[1..5] = 0, no extra data
|
||||
// buffer[5] = 0, only one slot
|
||||
buffer[6] = slot_status.seq;
|
||||
// buffer[7] = 0, status
|
||||
// buffer[8] = 0, error
|
||||
// buffer[9] = 0, chain parameter, this is complete
|
||||
Packet { buffer }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataBlock<'a> {
|
||||
seq: u8,
|
||||
data: &'a [u8],
|
||||
|
||||
}
|
||||
|
||||
impl<'a> DataBlock<'a> {
|
||||
pub fn new(seq: u8, data: &'a [u8]) -> Self {
|
||||
assert!(data.len() + 10 <= PACKET_SIZE);
|
||||
Self { seq, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<DataBlock<'a>> for Packet {
|
||||
fn from(data_block: DataBlock) -> Self {
|
||||
let mut buffer = [0u8; PACKET_SIZE];
|
||||
let len = data_block.data.len();
|
||||
buffer[0] = 0x80;
|
||||
buffer[1..5].copy_from_slice(&len.to_le_bytes());
|
||||
// buffer[5] = 0, only one slot
|
||||
buffer[6] = data_block.seq;
|
||||
// buffer[7] = 0, status
|
||||
// buffer[8] = 0, error
|
||||
// buffer[9] = 0, chain parameter, this is complete
|
||||
buffer[10..][..len].copy_from_slice(data_block.data);
|
||||
Packet { buffer }
|
||||
}
|
||||
}
|
||||
|
||||
impl core::convert::TryFrom<u8> for MessageType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(message_type_byte: u8) -> core::result::Result<Self, ()> {
|
||||
Ok(match message_type_byte {
|
||||
0x62 => Self::PowerOn,
|
||||
0x63 => Self::PowerOff,
|
||||
0x65 => Self::GetSlotStatus,
|
||||
0x6f => Self::TransferBlock,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
#[inline(always)]
|
||||
pub fn msg_type(&self) -> core::result::Result<MessageType, ()> {
|
||||
self[0].try_into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
u32::from_le_bytes(self[1..5].try_into().unwrap()) as usize
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn slot(&self) -> u8 {
|
||||
// we have only one slot
|
||||
assert!(self[5] == 0);
|
||||
*&self[5]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn seq(&self) -> u8 { *&self[6] }
|
||||
|
||||
// either three message specific bytes, or
|
||||
// a status field (1 byte), an error field and one message specific byte
|
||||
|
||||
#[inline(always)]
|
||||
pub fn data(&self) -> &[u8] { &self[10..] }
|
||||
}
|
||||
|
||||
// impl core::borrow::Borrow<[u8; PACKET_SIZE]> for Packet {
|
||||
// fn borrow(&self) -> &[u8; PACKET_SIZE] {
|
||||
// &self.buffer
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl core::borrow::BorrowMut<[u8; PACKET_SIZE]> for Packet {
|
||||
// fn borrow_mut(&mut self) -> &mut [u8; PACKET_SIZE] {
|
||||
// &mut self.buffer
|
||||
// }
|
||||
// }
|
||||
|
||||
impl core::ops::Deref for Packet {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// let len = self.len();
|
||||
// &self.buffer[..10 + len]
|
||||
&self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::DerefMut for Packet {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// let len = self.len();
|
||||
// &mut self.buffer[..10 + len]
|
||||
&mut self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<Bus> Pipe<Bus>
|
||||
where
|
||||
Bus: 'static + UsbBus
|
||||
{
|
||||
pub fn read_and_handle_packet(&mut self) {
|
||||
// let data: &mut [u8; PACKET_SIZE] = self.packet.borrow_mut();
|
||||
// let read = match self.read.read(&mut data[..]) {
|
||||
let read = match self.read.read(&mut self.packet) {
|
||||
// let read = match self.read.read(&mut self.packet.borrow_mut()[..]) {
|
||||
Ok(read) => {
|
||||
// all packets have 10 byte header before eventual data
|
||||
if read < 10 { panic!("unexpected small packet"); }
|
||||
read
|
||||
}
|
||||
Err(_) => {
|
||||
// usb-device lists WouldBlock + BufferOverflow as possible errors.
|
||||
// both should not occur here, and we can't do anything anyway.
|
||||
panic!("unexpected read error");
|
||||
}
|
||||
};
|
||||
|
||||
hprintln!("got a packet of len {}: {:?}", read, &self.packet).ok();
|
||||
|
||||
if let Ok(msg_type) = self.packet.msg_type() {
|
||||
match msg_type {
|
||||
MessageType::PowerOff |
|
||||
MessageType::GetSlotStatus => {
|
||||
self.send_slot_status();
|
||||
}
|
||||
|
||||
MessageType::PowerOn => {
|
||||
self.send_atr();
|
||||
}
|
||||
|
||||
MessageType::TransferBlock => {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_slot_status(&self) {
|
||||
let packet = Packet::from(SlotStatus::new(self.packet.seq()));
|
||||
hprintln!("answering with: {:?}", &packet).ok();
|
||||
match self.write.write(&packet[..10 + packet.len()]) {
|
||||
Ok(10) => {}
|
||||
|
||||
Ok(n) => panic!("expected to send exactly 10 bytes, sent {}", n),
|
||||
Err(UsbError::WouldBlock) => panic!("would block not handled yet"),
|
||||
Err(e) => panic!("unexpected error {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_atr(&self) {
|
||||
let packet = Packet::from(DataBlock::new(
|
||||
self.packet.seq(),
|
||||
&[0x3b, 0x8c,0x80,0x01],
|
||||
));
|
||||
hprintln!("answering with: {:?}", &packet).ok();
|
||||
|
||||
match self.write.write(&packet[..10 + packet.len()]) {
|
||||
Ok(14) => {}
|
||||
|
||||
Ok(n) => panic!("expected to send exactly 14 bytes, sent {}", n),
|
||||
Err(UsbError::WouldBlock) => panic!("would block not handled yet"),
|
||||
Err(e) => panic!("unexpected error {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_write_packet(&mut self) {
|
||||
// let result = self.write.write(&packet);
|
||||
|
||||
// match result {
|
||||
// Err(UsbError::WouldBlock) => {
|
||||
// // fine, can't write try later
|
||||
// // this shouldn't happen probably
|
||||
// },
|
||||
}
|
||||
|
||||
// pub fn read_address(&self) -> EndpointAddress {
|
||||
// self.read.address()
|
||||
// }
|
||||
|
||||
// pub fn write_address(&self) -> EndpointAddress {
|
||||
// self.write.address()
|
||||
// }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user