mirror of
https://github.com/trussed-dev/usbd-ccid.git
synced 2026-06-20 04:16:20 -07:00
More CCID (transfers) + no more local deps
This commit is contained in:
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
pub mod constants;
|
||||
pub mod class;
|
||||
pub mod pipe;
|
||||
pub mod types;
|
||||
|
||||
pub use class::Ccid;
|
||||
|
||||
+237
-233
File diff suppressed because it is too large
Load Diff
+107
@@ -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
@@ -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(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user