More CCID (transfers) + no more local deps

This commit is contained in:
Nicolas Stalder
2020-05-03 03:30:35 +02:00
parent 9ed8a6fd9f
commit 5355b05102
7 changed files with 864 additions and 298 deletions
+3
View File
@@ -8,6 +8,9 @@ edition = "2018"
[dependencies]
cortex-m-semihosting = { version = "0.3.5", optional = true}
heapless = "0.5.4"
heapless-bytes = { git = "https://github.com/ycrypto/heapless-bytes", branch = "main" }
# heapless-bytes = { path = "../../../heapless-bytes" }
usb-device = { version = "0.2.3", features = ["control-buffer-256"] }
[features]
+53 -30
View File
@@ -4,11 +4,11 @@ use cortex_m_semihosting::hprintln;
use crate::{
constants::*,
types::*,
pipe::Pipe,
};
use usb_device::class_prelude::*;
type Result<T> = core::result::Result<T, UsbError>;
pub struct Ccid<Bus>
@@ -16,8 +16,8 @@ where
Bus: 'static + UsbBus,
{
interface_number: InterfaceNumber,
read: EndpointOut<'static, Bus>,
pipe: Pipe<Bus>,
must_send_zlp: bool,
}
impl<Bus> Ccid<Bus>
@@ -25,17 +25,14 @@ 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 read = allocator.bulk(PACKET_SIZE as _);
let write = allocator.bulk(PACKET_SIZE as _);
let pipe = Pipe::new(write);
let interface_number = allocator.interface();
Self { interface_number, pipe, must_send_zlp: false }
Self { interface_number, read, pipe }
}
}
pub enum ClassRequests {
}
impl<Bus> UsbClass<Bus> for Ccid<Bus>
where
Bus: 'static + UsbBus,
@@ -53,37 +50,39 @@ where
FUNCTIONAL_INTERFACE,
&FUNCTIONAL_INTERFACE_DESCRIPTOR,
)?;
writer.endpoint(&self.pipe.write);
writer.endpoint(&self.pipe.read);
writer.endpoint(&self.pipe.write).unwrap();
writer.endpoint(&self.read).unwrap();
Ok(())
}
fn poll(&mut self) {
self.pipe.maybe_write_packet();
self.pipe.poll_app();
self.pipe.maybe_send_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();
}
self.pipe.maybe_send_packet();
}
fn endpoint_out(&mut self, addr: EndpointAddress) {
if addr != self.pipe.read.address() { return; }
if addr != self.read.address() { return; }
let maybe_packet = RawPacket::try_from(
|packet| self.read.read(packet));
// not much we can do in error cases
if let Ok(packet) = maybe_packet {
self.pipe.handle_packet(packet);
}
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();
let Request { request_type, recipient, index, request, .. }
= *transfer.request();
if index as u8 != u8::from(self.interface_number) {
return;
}
@@ -92,12 +91,6 @@ where
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| {
@@ -113,6 +106,7 @@ where
Ok(4)
}).ok();
},
_ => panic!("unexpected direction for {:?}", &request),
}
}
@@ -124,7 +118,36 @@ where
}
fn control_out(&mut self, transfer: ControlOut<Bus>) {
// todo!();
use usb_device::control::*;
let Request { request_type, recipient, index, request, value, .. }
= *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 => {
// spec: "slot in low, seq in high byte"
let [slot, seq] = value.to_le_bytes();
self.pipe.expect_abort(slot, seq);
transfer.accept().ok();
// // old behaviour
// transfer.reject().ok();
// todo!();
}
_ => panic!("unexpected direction for {:?}", &request),
}
}
Err(()) => {
hprintln!("unexpected request: {}", request).ok();
}
}
}
}
}
+34 -35
View File
@@ -1,8 +1,17 @@
#![allow(non_camel_case_types)]
use heapless_bytes::{
Bytes,
consts,
Unsigned as _
};
// can be 8, 16, 32, 64 or 512
#[cfg(feature = "highspeed-usb")]
pub const PACKET_SIZE: usize = 512;
pub type PACKET_SIZE_TYPE = consts::U512;
#[cfg(not(feature = "highspeed-usb"))]
pub const PACKET_SIZE: usize = 64;
pub type PACKET_SIZE_TYPE = consts::U64;
pub const PACKET_SIZE: usize = PACKET_SIZE_TYPE::USIZE;
pub const CLASS_CCID: u8 = 0x0B;
pub const SUBCLASS_NONE: u8 = 0x0;
@@ -19,38 +28,31 @@ pub enum TransferMode {
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
// 3580 KHz (as per ICCD spec) = 3.58 MHz
// (not relevant, fixed fixed for legacy reasonse
// Yubico: 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];
// pub const CLOCK_FREQUENCY_KHZ: [u8; 4] = [0xa0, 0x0f, 0x00, 0x00];
pub const CLOCK_FREQUENCY_KHZ: [u8; 4] = [0xfc, 0x0d, 0x00, 0x00];
// 9600 bps (as per ICCD spec)
// (not relevant, fixed fixed for legacy reasonse
// Yubico: 307200 bps, gnuk: 9600
// pub const DATA_RATE_BPS: [u8; 4] = [0x00, 0xb0, 0x04, 0x00];
pub const DATA_RATE_BPS: [u8; 4] = [0x80, 0x25, 0x00, 0x00];
// 254 (as per ICCD spec)
// Yubico: 2038, gnuk: 254
// pub const MAX_IFSD: [u8; 4] = [0xf6, 0x07, 0x00, 0x00];
pub const MAX_IFSD: [u8; 4] = [0xfe, 0x00, 0x00, 0x00];
//
// "The value shall be between 261 + 10 and 65544 + 10
// dwMaxCCIDMsgLen 3072 (gnuk: 271)
pub const MAX_MSG_LENGTH: usize = 3072;
// pub const MAX_MSG_LENGTH_TYPE: consts::U3072;
pub type MAX_MSG_LENGTH_TYPE = <consts::U2048 as core::ops::Add<consts::U1024>>::Output;
pub type MessageBuffer = Bytes<MAX_MSG_LENGTH_TYPE>;
pub const MAX_MSG_LENGTH: usize = MAX_MSG_LENGTH_TYPE::USIZE;
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;
@@ -59,11 +61,8 @@ 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,
// bcdCCID rev1.10
0x10, 0x01,
// bMaxSlotIndex
NUM_SLOTS - 1,
// bVoltageSupport (5.0V + 3.0V + 1.8V)
@@ -124,9 +123,9 @@ pub const FUNCTIONAL_INTERFACE_DESCRIPTOR: [u8; 52] = [
MAX_MSG_LENGTH_LE[2],
MAX_MSG_LENGTH_LE[3],
// bClassGetResponse ("echo")
// bClassGetResponse ("echo"), as per ICCD spec
0xFF,
// bClassEnvelope ("echo"), gnuk: 0
// bClassEnvelope ("echo"), as per ICCD spec, gnuk: 0
0xFF,
// wlcdLayout (none)
0x00, 0x00,
+1
View File
@@ -6,5 +6,6 @@
pub mod constants;
pub mod class;
pub mod pipe;
pub mod types;
pub use class::Ccid;
+237 -233
View File
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
// #[repr(u8)]
// #[derive(Copy, Clone, Debug)]
// pub enum MessageType {
// // REQUESTS
// // supported
// PowerOn = 0x62,
// PowerOff = 0x63,
// GetSlotStatus = 0x65,
// XfrBlock = 0x6f,
// Abort = 0x72,
// // unsupported
// GetParameters = 0x6c,
// ResetParameters = 0x6d,
// SetParameters = 0x61,
// Escape = 0x6b,// for vendor commands
// IccClock = 0x7e,
// T0Apdu = 0x6a,
// Secure = 0x69,
// Mechanical = 0x71,
// SetDataRateAndClockFrequency = 0x73,
// // RESPONSES
// // used
// DataBlock = 0x80,
// SlotStatus = 0x81,
// // unused
// Parameters = 0x82,
// // Escape = 0x83,
// DataRateAndClockFrequency = 0x84,
// }
// 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::XfrBlock,
// 0x71 => Self::Mechanical,
// 0x80 => Self::DataBlock,
// 0x81 => Self::SlotStatus,
// _ => return Err(()),
// })
// }
// }
+429
View File
@@ -0,0 +1,429 @@
use core::convert::TryInto;
use cortex_m_semihosting::hprintln;
use crate::constants::*;
pub type RawPacket = heapless_bytes::Bytes<PACKET_SIZE_TYPE>;
pub enum PacketError {
ShortPacket,
UnknownCommand(u8),
}
pub enum Message {
Command(Command),
Response(Response),
}
pub trait Packet: core::ops::Deref<Target = RawPacket> {
#[inline]
fn slot(&self) -> u8 {
// we have only one slot
assert!(self[5] == 0);
*&self[5]
}
#[inline]
fn seq(&self) -> u8 { *&self[6] }
}
pub trait PacketWithData: Packet {
#[inline]
fn data(&self) -> &[u8] {
// let len = u32::from_le_bytes(self[1..5].try_into().unwrap()) as usize;
let declared_len =
u32::from_le_bytes(self[1..5].try_into().unwrap()) as usize;
let len = core::cmp::min(PACKET_SIZE - 10, declared_len);
hprintln!("delcared = {}, len = {}", declared_len, len).ok();
&self[10..][..len]
}
}
pub trait ChainedPacket: Packet {
#[inline(always)]
fn chain(&self) -> Chain {
let level_parameter = u16::from_le_bytes(self[8..10].try_into().unwrap());
match level_parameter {
0 => Chain::BeginsAndEnds,
1 => Chain::Begins,
2 => Chain::Ends,
3 => Chain::Continues,
0x10 => Chain::ExpectingMore,
_ => panic!("invalid power select parameter"),
}
}
}
impl ChainedPacket for XfrBlock {}
pub struct DataBlock<'a> {
seq: u8,
chain: Chain,
data: &'a [u8],
}
impl<'a> DataBlock<'a> {
pub fn new(seq: u8, chain: Chain, data: &'a [u8]) -> Self {
assert!(data.len() + 10 <= PACKET_SIZE);
Self { seq, chain, data }
}
}
impl core::fmt::Debug for DataBlock<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut debug_struct = f.debug_struct("DataBlock");
debug_struct
.field("seq", &self.seq)
;
let l = core::cmp::min(self.data.len(), 16);
let escaped_bytes: heapless::Vec<u8, heapless::consts::U64> =
self.data.iter().take(l)
.flat_map(|byte| core::ascii::escape_default(*byte))
.collect();
let data_as_str = &core::str::from_utf8(&escaped_bytes).unwrap();
debug_struct
.field("chain", &self.chain)
.field("len", &self.data.len())
.field("data", &format_args!("b'{}'", data_as_str))
.finish()
}
}
// WELL. DataBlock does not deref to RawPacket
// impl Deref for DataBlock<_> {
// type Target: &
// impl Packet for DataBlock<'_> {
// fn slot(&self) -> u8 { 0 }
// fn seq(&self) -> u8 { self.seq }
// }
impl Into<RawPacket> for DataBlock<'_> {
fn into(self) -> RawPacket {
let mut packet = RawPacket::new();
let len = self.data.len();
packet.resize_default(10 + len).ok();
packet[0] = 0x80;
packet[1..][..4].copy_from_slice(&len.to_le_bytes());
packet[5] = 0;
packet[6] = self.seq;
// status
packet[7] = 0;
// error
packet[8] = 0;
// chain parameter
packet[9] = self.chain as u8;
packet[10..][..len].copy_from_slice(self.data);
packet
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum CommandType {
// REQUESTS
// supported
PowerOn = 0x62,
PowerOff = 0x63,
GetSlotStatus = 0x65,
XfrBlock = 0x6f,
Abort = 0x72,
// unsupported
GetParameters = 0x6c,
ResetParameters = 0x6d,
SetParameters = 0x61,
Escape = 0x6b,// for vendor commands
IccClock = 0x7e,
T0Apdu = 0x6a,
Secure = 0x69,
Mechanical = 0x71,
SetDataRateAndClockFrequency = 0x73,
}
macro_rules! command_message {
($($Name:ident: $code:expr,)*) => {
$(
pub struct $Name {
// use reference? pulls in lifetimes though...
raw: RawPacket,
}
impl core::ops::Deref for $Name {
type Target = RawPacket;
#[inline]
fn deref(&self) -> &Self::Target {
&self.raw
}
}
impl core::ops::DerefMut for $Name {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.raw
}
}
impl Packet for $Name {}
)*
pub enum Command {
$(
$Name($Name),
)*
}
impl Command {
pub fn seq(&self) -> u8 {
match self {
$(
Command::$Name(packet) => packet.seq(),
)*
}
}
pub fn command_type(&self) -> CommandType {
match self {
$(
Command::$Name(_) => CommandType::$Name,
)*
}
}
}
impl core::convert::TryFrom<RawPacket> for Command {
type Error = PacketError;
#[inline]
fn try_from(packet: RawPacket)
-> core::result::Result<Self, Self::Error>
{
if packet.len() < 10 {
return Err(PacketError::ShortPacket);
}
if packet[5] != 0 {
// wrong slot
}
let command_byte = packet[0];
Ok(match command_byte {
$(
$code => Command::$Name($Name { raw: packet } ),
)*
_ => return Err(PacketError::UnknownCommand(command_byte)),
})
}
}
impl core::ops::Deref for Command {
type Target = RawPacket;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
$(
Command::$Name(packet) => &packet,
)*
}
}
}
// impl core::ops::DerefMut for Command {
// #[inline]
// fn deref_mut(&mut self) -> &mut Self::Target {
// match self {
// $(
// Command::$Name(packet) => &mut packet,
// )*
// }
// }
// }
}
}
command_message!(
PowerOn: 0x62,
PowerOff: 0x63,
GetSlotStatus: 0x65,
XfrBlock: 0x6f,
Abort: 0x72,
);
impl PacketWithData for XfrBlock {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PowerSelection {
Automatic,
V5,
V3_3,
V1_8,
}
impl PowerOn {
#[inline(always)]
pub fn power_select(&self) -> PowerSelection {
match &self[7] {
0 => PowerSelection::Automatic,
1 => PowerSelection::V5,
2 => PowerSelection::V3_3,
3 => PowerSelection::V1_8,
_ => panic!("invalid power select parameter"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum Chain {
BeginsAndEnds = 0,
Begins = 1,
Ends = 2,
Continues = 3,
ExpectingMore = 0x10,
}
impl Chain {
pub fn transfer_ongoing(&self) -> bool {
match self {
Chain::BeginsAndEnds |
Chain::Ends |
Chain::ExpectingMore => true,
_ => false,
}
}
}
pub enum Response {
// DataBlock(DataBlock),
// SlotStatus(SlotStatus),
}
impl core::fmt::Debug for Command {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut debug_struct = f.debug_struct("Command");
// write!("Command({:?})", &self.command_type()));
// // "Command");
debug_struct
.field("cmd", &self.command_type())
.field("seq", &self.seq())
;
match self {
Command::XfrBlock(block) => {
let l = core::cmp::min(self.len(), 8);
let escaped_bytes: heapless::Vec<u8, heapless::consts::U64> =
block.data().iter().take(l)
.flat_map(|byte| core::ascii::escape_default(*byte))
.collect();
let data_as_str = &core::str::from_utf8(&escaped_bytes).unwrap();
debug_struct
.field("chain", &block.chain())
.field("len", &block.data().len())
;
if l < self.len() {
debug_struct.field("data[..8]", &format_args!("b'{}'", data_as_str))
} else {
debug_struct.field("data", &format_args!("b'{}'", data_as_str))
}
;
}
_ => {}
}
// let mut debug_struct = match self.msg_type() {
// Ok(message_type) => debug_struct.field("type", &message_type),
// Err(()) => debug_struct.field("type", &self[0]),
// };
// let has_data = self.len() > 0;
debug_struct
.finish()
}
}
// 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]),
// };
// let has_data = self.len() > 0;
// if has_data {
// debug_struct = debug_struct
// .field("len", &self.len());
// }
// debug_struct = debug_struct
// // .field("slot", &self.slot())
// .field("seq", &self.slot());
// debug_struct = debug_struct
// .field("power_select", &self.power_select());
// debug_struct = debug_struct
// .field("level_parameter", &self.level_parameter());
// // other fields?
// // .field("data[..16]", &self.data()[..16])
// if has_data {
// let l = core::cmp::min(self.len(), 16);
// // one byte can become `\x<y><z>`, so 4 bytes
// let escaped_bytes: heapless::Vec<u8, heapless::consts::U64> =
// self.data().iter().take(l)
// .flat_map(|byte| core::ascii::escape_default(*byte))
// .collect();
// let data_as_str = &core::str::from_utf8(&escaped_bytes).unwrap();
// debug_struct = debug_struct
// .field("data", &format_args!("b'{}'", data_as_str));
// }
// debug_struct
// .finish()
// }
// }
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
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(()),
})
}
}