use crate::u2f::{receive_user_request, send_user_response};
use crate::{MAXIMUM_CTAPHID_MESSAGE, MAXIMUM_CTAPHID_MESSAGE_X2};
use arrayvec::ArrayVec;
use bbqueue::Producer;
use usbd_human_interface_device::device::fido::RawFidoReport;
pub struct InProgressTransaction {
pub cid: u32,
pub request_buffer: [u8; MAXIMUM_CTAPHID_MESSAGE],
pub current_request_payload_size: usize,
pub current_request_payload_bytes_written: usize,
pub response_continuation_state: ContinuationState,
pub response_ready_to_send: bool,
pub response_final_packet_is_ready_to_send: bool,
}
#[derive(Clone, Copy)]
pub enum ContinuationState {
Initial,
Continuation { sequence: u8 },
}
impl InProgressTransaction {
pub fn receive_user_request(
&mut self,
data: &[u8],
tx: &mut Producer<MAXIMUM_CTAPHID_MESSAGE_X2>,
web_origin_filter: &dyn Fn([u8; 32]) -> bool,
) -> Option<ArrayVec<u8, 255>> {
self.request_buffer[self.current_request_payload_bytes_written
..self.current_request_payload_bytes_written + data.len()]
.copy_from_slice(data);
self.current_request_payload_bytes_written += data.len();
if self.current_request_payload_bytes_written >= self.current_request_payload_size {
return receive_user_request(
&self.request_buffer[..self.current_request_payload_size],
tx,
web_origin_filter,
);
}
None
}
pub fn send_user_response(
&mut self,
response: &[u8],
bytes_sent: &mut u32,
tx: &mut Producer<MAXIMUM_CTAPHID_MESSAGE_X2>,
) {
send_user_response(response, bytes_sent, tx);
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CtapHidRequest {
pub cid: u32,
pub ty: CtapHidRequestTy,
}
impl CtapHidRequest {
pub fn parse(report: &RawFidoReport) -> Self {
let packet = &report.packet;
let cid = u32::from_be_bytes(packet[0..4].try_into().unwrap());
let ty = if packet[4] & 0b10000000 == 0 {
CtapHidRequestTy::Continuation {
sequence: packet[4],
data: packet[5..].try_into().unwrap(),
}
} else {
let bcnt: u16 = u16::from_be_bytes(packet[5..7].try_into().unwrap());
let cmd = packet[4] & 0b01111111;
match cmd {
0x01 => CtapHidRequestTy::Ping,
0x03 => CtapHidRequestTy::Message {
length: bcnt,
data: packet[7..].try_into().unwrap(),
},
0x06 => CtapHidRequestTy::Init {
nonce8: packet[7..15].try_into().unwrap(),
},
0x10 => CtapHidRequestTy::CborMessage,
0x11 => CtapHidRequestTy::Cancel,
cmd => CtapHidRequestTy::Unknown { cmd },
}
};
CtapHidRequest { cid, ty }
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CtapHidRequestTy {
Init {
nonce8: [u8; 8],
},
Ping,
Message {
length: u16,
data: [u8; 57],
},
Continuation {
sequence: u8,
data: [u8; 59],
},
Cancel,
CborMessage,
Unknown {
cmd: u8,
},
}
pub struct CtapHidResponse<'a> {
pub cid: u32,
pub continuation_state: ContinuationState,
pub ty: CtapHidResponseTy<'a>,
}
pub enum CtapHidResponseTy<'a> {
Init(InitResponse),
Message {
length: u16,
data: &'a [u8],
},
RawReport(RawFidoReport),
Error(CtapHidError),
}
#[derive(Clone, Copy)]
pub enum CtapHidError {
InvalidCommand = 0x01,
InvalidParameter = 0x02,
InvalidLen = 0x03,
InvalidSeq = 0x04,
MessageTimeout = 0x05,
ChannelBusy = 0x06,
LockRequired = 0x0A,
InvalidChannel = 0x0B,
KeepAliveCancel = 0x2D,
Other = 0x7F,
}
pub struct InitResponse {
pub nonce_8_bytes: [u8; 8],
pub channel_id: [u8; 4],
pub protocol_version: u8,
pub device_version_major: u8,
pub device_version_minor: u8,
pub device_version_build: u8,
pub capabilities: u8,
}
impl CtapHidResponse<'_> {
pub fn encode(&self, report: &mut RawFidoReport) {
report.packet.fill(0);
match &self.ty {
CtapHidResponseTy::Init(response) => {
CtapHeaderInitialization {
cid: self.cid,
cmd: 0x86,
bcnt: 17,
}
.encode(report);
let data = &mut report.packet[7..];
data[0..8].copy_from_slice(&response.nonce_8_bytes);
data[8..12].copy_from_slice(&response.channel_id);
data[13] = response.protocol_version;
data[14] = response.device_version_major;
data[15] = response.device_version_minor;
data[16] = response.device_version_build;
data[17] = response.capabilities;
}
CtapHidResponseTy::Message { length, data } => match self.continuation_state {
ContinuationState::Initial => {
CtapHeaderInitialization {
cid: self.cid,
cmd: 0x83,
bcnt: *length,
}
.encode(report);
if data.len() > report.packet.len() - 7 {
panic!(
"message data is too long for one initial packet, was {} but must be less than or equal to {}",
data.len(),
report.packet.len() - 7
);
}
report.packet[7..7 + data.len()].copy_from_slice(data);
}
ContinuationState::Continuation { sequence } => {
CtapHeaderContinuation {
cid: self.cid,
seq: sequence,
}
.encode(report);
if data.len() > report.packet.len() - 5 {
panic!(
"message data is too long for one continuation packet, was {} but must be less than or equal to {}",
data.len(),
report.packet.len() - 5
);
}
report.packet[5..5 + data.len()].copy_from_slice(data);
}
},
CtapHidResponseTy::RawReport(raw) => *report = *raw,
CtapHidResponseTy::Error(error) => {
CtapHeaderInitialization {
cid: self.cid,
cmd: 0x3F,
bcnt: 1,
}
.encode(report);
report.packet[7] = *error as u8;
}
}
}
}
pub struct CtapHeaderInitialization {
pub cid: u32,
pub cmd: u8,
pub bcnt: u16,
}
impl CtapHeaderInitialization {
fn encode(self, report: &mut RawFidoReport) {
report.packet[0..4].copy_from_slice(&self.cid.to_be_bytes());
report.packet[4] = self.cmd;
report.packet[5..7].copy_from_slice(&self.bcnt.to_be_bytes());
}
}
pub struct CtapHeaderContinuation {
pub cid: u32,
pub seq: u8,
}
impl CtapHeaderContinuation {
fn encode(self, report: &mut RawFidoReport) {
report.packet[0..4].copy_from_slice(&self.cid.to_be_bytes());
report.packet[4] = self.seq;
}
}