Clean public HID interface (#442)

* clear public HID interface and tests

* fixes nits
This commit is contained in:
kaczmarczyck
2022-03-14 12:08:44 +01:00
committed by GitHub
parent 2050f9f272
commit 163057daf0
5 changed files with 283 additions and 218 deletions

View File

@@ -24,9 +24,9 @@ use ctap2::ctap::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, AuthenticatorMakeCredentialParameters,
}; };
use ctap2::ctap::hid::receive::MessageAssembler; use ctap2::ctap::hid::{
use ctap2::ctap::hid::send::HidPacketIterator; ChannelID, CtapHidCommand, HidPacket, HidPacketIterator, Message, MessageAssembler,
use ctap2::ctap::hid::{ChannelID, CtapHidCommand, HidPacket, Message}; };
use ctap2::env::test::TestEnv; use ctap2::env::test::TestEnv;
use ctap2::Ctap; use ctap2::Ctap;

View File

@@ -12,11 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
pub mod receive; mod receive;
pub mod send; mod send;
// Implementation details must be public for testing (in particular fuzzing).
#[cfg(feature = "std")]
pub use self::receive::MessageAssembler;
#[cfg(not(feature = "std"))]
use self::receive::MessageAssembler; use self::receive::MessageAssembler;
use self::send::HidPacketIterator; pub use self::send::HidPacketIterator;
use super::super::clock::{ClockInt, CtapInstant}; use super::super::clock::{ClockInt, CtapInstant};
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
use super::ctap1; use super::ctap1;
@@ -28,6 +32,8 @@ use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use arrayref::{array_ref, array_refs}; use arrayref::{array_ref, array_refs};
use embedded_time::duration::Milliseconds; use embedded_time::duration::Milliseconds;
#[cfg(test)]
use enum_iterator::IntoEnumIterator;
pub type HidPacket = [u8; 64]; pub type HidPacket = [u8; 64];
pub type ChannelID = [u8; 4]; pub type ChannelID = [u8; 4];
@@ -36,6 +42,7 @@ pub type ChannelID = [u8; 4];
/// ///
/// See section 11.2.9. of FIDO 2.1 (2021-06-15). /// See section 11.2.9. of FIDO 2.1 (2021-06-15).
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(test, derive(IntoEnumIterator))]
pub enum CtapHidCommand { pub enum CtapHidCommand {
Ping = 0x01, Ping = 0x01,
Msg = 0x03, Msg = 0x03,
@@ -47,9 +54,7 @@ pub enum CtapHidCommand {
Cancel = 0x11, Cancel = 0x11,
Keepalive = 0x3B, Keepalive = 0x3B,
Error = 0x3F, Error = 0x3F,
// VendorFirst and VendorLast describe a range, and are not commands themselves. // The vendor range starts here, going from 0x40 to 0x7F.
_VendorFirst = 0x40,
_VendorLast = 0x7F,
} }
impl From<u8> for CtapHidCommand { impl From<u8> for CtapHidCommand {
@@ -70,9 +75,40 @@ impl From<u8> for CtapHidCommand {
} }
} }
/// CTAPHID errors
///
/// See section 11.2.9.1.6. of FIDO 2.1 (2021-06-15).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CtapHidError {
/// The command in the request is invalid.
InvalidCmd = 0x01,
/// A parameter in the request is invalid.
_InvalidPar = 0x02,
/// The length of a message is too big.
InvalidLen = 0x03,
/// Expected a continuation packet with a specific sequence number, got another sequence number.
///
/// This error code is also used if we expect a continuation packet, and receive an init
/// packet. We interpreted it as invalid seq number 0.
InvalidSeq = 0x04,
/// This packet arrived after a timeout.
MsgTimeout = 0x05,
/// A packet arrived on one channel while another is busy.
ChannelBusy = 0x06,
/// Command requires channel lock.
_LockRequired = 0x0A,
/// The requested channel ID is invalid.
InvalidChannel = 0x0B,
/// Unspecified error.
_Other = 0x7F,
/// This error is silently ignored.
UnexpectedContinuation,
}
/// Describes the structure of a parsed HID packet. /// Describes the structure of a parsed HID packet.
/// ///
/// A packet is either an Init or a Continuation packet. /// A packet is either an Init or a Continuation packet.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProcessedPacket<'a> { pub enum ProcessedPacket<'a> {
InitPacket { InitPacket {
cmd: u8, cmd: u8,
@@ -98,6 +134,7 @@ pub struct Message {
/// A keepalive packet reports the reason why a command does not finish. /// A keepalive packet reports the reason why a command does not finish.
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeepaliveStatus { pub enum KeepaliveStatus {
Processing = 0x01, Processing = 0x01,
UpNeeded = 0x02, UpNeeded = 0x02,
@@ -146,16 +183,6 @@ impl CtapHid {
const TYPE_INIT_BIT: u8 = 0x80; const TYPE_INIT_BIT: u8 = 0x80;
const PACKET_TYPE_MASK: u8 = 0x80; const PACKET_TYPE_MASK: u8 = 0x80;
const ERR_INVALID_CMD: u8 = 0x01;
const _ERR_INVALID_PAR: u8 = 0x02;
const ERR_INVALID_LEN: u8 = 0x03;
const ERR_INVALID_SEQ: u8 = 0x04;
const ERR_MSG_TIMEOUT: u8 = 0x05;
const ERR_CHANNEL_BUSY: u8 = 0x06;
const _ERR_LOCK_REQUIRED: u8 = 0x0A;
const ERR_INVALID_CHANNEL: u8 = 0x0B;
const _ERR_OTHER: u8 = 0x7F;
// See section 11.2.9.1.3. CTAPHID_INIT (0x06). // See section 11.2.9.1.3. CTAPHID_INIT (0x06).
const PROTOCOL_VERSION: u8 = 2; const PROTOCOL_VERSION: u8 = 2;
// The device version number is vendor-defined. // The device version number is vendor-defined.
@@ -220,37 +247,12 @@ impl CtapHid {
None None
} }
Err((cid, error)) => { Err((cid, error)) => {
if !self.is_allocated_channel(cid) if matches!(error, CtapHidError::UnexpectedContinuation) {
&& error != receive::Error::UnexpectedContinuation
{
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL))
} else {
match error {
receive::Error::UnexpectedChannel => {
Some(CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY))
}
receive::Error::UnexpectedInit => {
// TODO: Should we send another error code in this case?
// Technically, we were expecting a sequence number and got another
// byte, although the command/seqnum bit has higher-level semantics
// than sequence numbers.
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
}
receive::Error::UnexpectedContinuation => {
// CTAP specification (version 20190130) section 8.1.5.4
// Spurious continuation packets will be ignored.
None None
} } else if !self.is_allocated_channel(cid) {
receive::Error::UnexpectedSeq => { Some(CtapHid::error_message(cid, CtapHidError::InvalidChannel))
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)) } else {
} Some(CtapHid::error_message(cid, error))
receive::Error::UnexpectedLen => {
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN))
}
receive::Error::Timeout => {
Some(CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT))
}
}
} }
} }
} }
@@ -267,7 +269,7 @@ impl CtapHid {
fn preprocess_message(&mut self, message: Message) -> Option<Message> { fn preprocess_message(&mut self, message: Message) -> Option<Message> {
let cid = message.cid; let cid = message.cid;
if !self.has_valid_channel(&message) { if !self.has_valid_channel(&message) {
return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)); return Some(CtapHid::error_message(cid, CtapHidError::InvalidChannel));
} }
match message.cmd { match message.cmd {
@@ -276,7 +278,7 @@ impl CtapHid {
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.3. // CTAP 2.1 from 2021-06-15, section 11.2.9.1.3.
CtapHidCommand::Init => { CtapHidCommand::Init => {
if message.payload.len() != 8 { if message.payload.len() != 8 {
return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)); return Some(CtapHid::error_message(cid, CtapHidError::InvalidLen));
} }
let new_cid = if cid == CtapHid::CHANNEL_BROADCAST { let new_cid = if cid == CtapHid::CHANNEL_BROADCAST {
@@ -317,7 +319,7 @@ impl CtapHid {
CtapHidCommand::Wink => Some(message), CtapHidCommand::Wink => Some(message),
_ => { _ => {
// Unknown or unsupported command. // Unknown or unsupported command.
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD)) Some(CtapHid::error_message(cid, CtapHidError::InvalidCmd))
} }
} }
} }
@@ -339,7 +341,7 @@ impl CtapHid {
CtapHidCommand::Msg => { CtapHidCommand::Msg => {
// If we don't have CTAP1 backward compatibilty, this command is invalid. // If we don't have CTAP1 backward compatibilty, this command is invalid.
#[cfg(not(feature = "with_ctap1"))] #[cfg(not(feature = "with_ctap1"))]
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD); return CtapHid::error_message(cid, CtapHidError::InvalidCmd);
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
match ctap1::Ctap1Command::process_command( match ctap1::Ctap1Command::process_command(
@@ -372,7 +374,7 @@ impl CtapHid {
// The response is empty like the request. // The response is empty like the request.
message message
} else { } else {
CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN) CtapHid::error_message(cid, CtapHidError::InvalidLen)
} }
} }
// All other commands have already been processed, keep them as is. // All other commands have already been processed, keep them as is.
@@ -410,11 +412,11 @@ impl CtapHid {
cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids
} }
fn error_message(cid: ChannelID, error_code: u8) -> Message { pub fn error_message(cid: ChannelID, error_code: CtapHidError) -> Message {
Message { Message {
cid, cid,
cmd: CtapHidCommand::Error, cmd: CtapHidCommand::Error,
payload: vec![error_code], payload: vec![error_code as u8],
} }
} }
@@ -469,7 +471,6 @@ impl CtapHid {
/// Generates the HID response packets for a keepalive status. /// Generates the HID response packets for a keepalive status.
pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator { pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator {
// This unwrap is safe because the payload length is 1 <= 7609 bytes.
CtapHid::split_message(Message { CtapHid::split_message(Message {
cid, cid,
cmd: CtapHidCommand::Keepalive, cmd: CtapHidCommand::Keepalive,
@@ -503,6 +504,18 @@ impl CtapHid {
payload: response, payload: response,
} }
} }
#[cfg(test)]
pub fn new_initialized() -> (CtapHid, ChannelID) {
(
CtapHid {
assembler: MessageAssembler::new(),
allocated_cids: 1,
wink_permission: TimedPermission::waiting(),
},
[0x00, 0x00, 0x00, 0x01],
)
}
} }
#[cfg(test)] #[cfg(test)]
@@ -510,61 +523,6 @@ mod test {
use super::*; use super::*;
use crate::env::test::TestEnv; use crate::env::test::TestEnv;
fn process_messages(
env: &mut TestEnv,
ctap_hid: &mut CtapHid,
ctap_state: &mut CtapState,
request: Vec<Message>,
) -> Option<Vec<Message>> {
let mut result = Vec::new();
let mut assembler_reply = MessageAssembler::new();
// Except for tests for timeouts (done in ctap1.rs), transactions are time independant.
for msg_request in request {
for pkt_request in HidPacketIterator::new(msg_request).unwrap() {
for pkt_reply in
ctap_hid.process_hid_packet(env, &pkt_request, CtapInstant::new(0), ctap_state)
{
match assembler_reply.parse_packet(&pkt_reply, CtapInstant::new(0)) {
Ok(Some(message)) => result.push(message),
Ok(None) => (),
Err(_) => return None,
}
}
}
}
Some(result)
}
fn cid_from_init(
env: &mut TestEnv,
ctap_hid: &mut CtapHid,
ctap_state: &mut CtapState,
) -> ChannelID {
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let reply = process_messages(
env,
ctap_hid,
ctap_state,
vec![Message {
cid: CtapHid::CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init,
payload: nonce.clone(),
}],
);
let mut cid_in_payload: ChannelID = Default::default();
if let Some(messages) = reply {
assert_eq!(messages.len(), 1);
assert!(messages[0].payload.len() >= 12);
assert_eq!(nonce, &messages[0].payload[..8]);
cid_in_payload.copy_from_slice(&messages[0].payload[8..12]);
} else {
panic!("The init process was not successful to generate a valid channel ID.")
}
cid_in_payload
}
#[test] #[test]
fn test_split_assemble() { fn test_split_assemble() {
for payload_len in 0..7609 { for payload_len in 0..7609 {
@@ -591,45 +549,28 @@ mod test {
#[test] #[test]
fn test_spurious_continuation_packet() { fn test_spurious_continuation_packet() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let mut packet = [0x00; 64]; let mut packet = [0x00; 64];
packet[0..7].copy_from_slice(&[0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x51, 0x51]); packet[0..7].copy_from_slice(&[0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x51, 0x51]);
let mut assembler_reply = MessageAssembler::new();
for pkt_reply in
ctap_hid.process_hid_packet(&mut env, &packet, CtapInstant::new(0), &mut ctap_state)
{
// Continuation packets are silently ignored. // Continuation packets are silently ignored.
assert_eq!( assert_eq!(
assembler_reply ctap_hid.parse_packet(&mut env, &packet, CtapInstant::new(0)),
.parse_packet(&pkt_reply, CtapInstant::new(0))
.unwrap(),
None None
); );
} }
}
#[test] #[test]
fn test_command_init() { fn test_command_init() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let init_message = Message {
let reply = process_messages(
&mut env,
&mut ctap_hid,
&mut ctap_state,
vec![Message {
cid: CtapHid::CHANNEL_BROADCAST, cid: CtapHid::CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init, cmd: CtapHidCommand::Init,
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
}], };
); let reply = ctap_hid.preprocess_message(init_message);
assert_eq!( assert_eq!(
reply, reply,
Some(vec![Message { Some(Message {
cid: CtapHid::CHANNEL_BROADCAST, cid: CtapHid::CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init, cmd: CtapHidCommand::Init,
payload: vec![ payload: vec![
@@ -651,16 +592,14 @@ mod test {
0x00, 0x00,
CtapHid::CAPABILITIES CtapHid::CAPABILITIES
] ]
}]) })
); );
} }
#[test] #[test]
fn test_command_init_for_sync() { fn test_command_init_for_sync() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); let (mut ctap_hid, cid) = CtapHid::new_initialized();
let mut ctap_hid = CtapHid::new();
let cid = cid_from_init(&mut env, &mut ctap_hid, &mut ctap_state);
// Ping packet with a length longer than one packet. // Ping packet with a length longer than one packet.
let mut packet1 = [0x51; 64]; let mut packet1 = [0x51; 64];
@@ -672,26 +611,13 @@ mod test {
packet2[4..15].copy_from_slice(&[ packet2[4..15].copy_from_slice(&[
0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
]); ]);
let mut result = Vec::new();
let mut assembler_reply = MessageAssembler::new();
for pkt_request in &[packet1, packet2] {
for pkt_reply in ctap_hid.process_hid_packet(
&mut env,
pkt_request,
CtapInstant::new(0),
&mut ctap_state,
) {
if let Some(message) = assembler_reply
.parse_packet(&pkt_reply, CtapInstant::new(0))
.unwrap()
{
result.push(message);
}
}
}
assert_eq!( assert_eq!(
result, ctap_hid.parse_packet(&mut env, &packet1, CtapInstant::new(0)),
vec![Message { None
);
assert_eq!(
ctap_hid.parse_packet(&mut env, &packet2, CtapInstant::new(0)),
Some(Message {
cid, cid,
cmd: CtapHidCommand::Init, cmd: CtapHidCommand::Init,
payload: vec![ payload: vec![
@@ -713,35 +639,188 @@ mod test {
0x00, 0x00,
CtapHid::CAPABILITIES CtapHid::CAPABILITIES
] ]
}] })
); );
} }
#[test] #[test]
fn test_command_ping() { fn test_command_ping() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); let (mut ctap_hid, cid) = CtapHid::new_initialized();
let mut ctap_hid = CtapHid::new();
let cid = cid_from_init(&mut env, &mut ctap_hid, &mut ctap_state);
let reply = process_messages(
&mut env,
&mut ctap_hid,
&mut ctap_state,
vec![Message {
cid,
cmd: CtapHidCommand::Ping,
payload: vec![0x99, 0x99],
}],
);
let mut ping_packet = [0x00; 64];
ping_packet[..4].copy_from_slice(&cid);
ping_packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
assert_eq!( assert_eq!(
reply, ctap_hid.parse_packet(&mut env, &ping_packet, CtapInstant::new(0)),
Some(vec![Message { Some(Message {
cid, cid,
cmd: CtapHidCommand::Ping, cmd: CtapHidCommand::Ping,
payload: vec![0x99, 0x99] payload: vec![0x99, 0x99]
}]) })
);
}
#[test]
fn test_command_cancel() {
let mut env = TestEnv::new();
let (mut ctap_hid, cid) = CtapHid::new_initialized();
let mut cancel_packet = [0x00; 64];
cancel_packet[..4].copy_from_slice(&cid);
cancel_packet[4..7].copy_from_slice(&[0x91, 0x00, 0x00]);
let response = ctap_hid.parse_packet(&mut env, &cancel_packet, CtapInstant::new(0));
assert_eq!(response, None);
}
#[test]
fn test_process_hid_packet() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let (mut ctap_hid, cid) = CtapHid::new_initialized();
let mut ping_packet = [0x00; 64];
ping_packet[..4].copy_from_slice(&cid);
ping_packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
let mut response = ctap_hid.process_hid_packet(
&mut env,
&ping_packet,
CtapInstant::new(0),
&mut ctap_state,
);
assert_eq!(response.next(), Some(ping_packet));
assert_eq!(response.next(), None);
}
#[test]
fn test_process_hid_packet_empty() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let (mut ctap_hid, cid) = CtapHid::new_initialized();
let mut cancel_packet = [0x00; 64];
cancel_packet[..4].copy_from_slice(&cid);
cancel_packet[4..7].copy_from_slice(&[0x91, 0x00, 0x00]);
let mut response = ctap_hid.process_hid_packet(
&mut env,
&cancel_packet,
CtapInstant::new(0),
&mut ctap_state,
);
assert_eq!(response.next(), None);
}
#[test]
fn test_wink() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let (mut ctap_hid, cid) = CtapHid::new_initialized();
assert!(!ctap_hid.should_wink(CtapInstant::new(0)));
let mut wink_packet = [0x00; 64];
wink_packet[..4].copy_from_slice(&cid);
wink_packet[4..7].copy_from_slice(&[0x88, 0x00, 0x00]);
let mut response = ctap_hid.process_hid_packet(
&mut env,
&wink_packet,
CtapInstant::new(0),
&mut ctap_state,
);
assert_eq!(response.next(), Some(wink_packet));
assert_eq!(response.next(), None);
assert!(ctap_hid.should_wink(CtapInstant::new(0)));
}
#[test]
fn test_split_message() {
let message = Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
payload: vec![0x99, 0x99],
};
let mut response = CtapHid::split_message(message);
let mut expected_packet = [0x00; 64];
expected_packet[..9]
.copy_from_slice(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x02, 0x99, 0x99]);
assert_eq!(response.next(), Some(expected_packet));
assert_eq!(response.next(), None);
}
#[test]
fn test_split_message_too_large() {
let payload = vec![0xFF; 7609 + 1];
let message = Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Cbor,
payload,
};
let mut response = CtapHid::split_message(message);
let mut expected_packet = [0x00; 64];
expected_packet[..8].copy_from_slice(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x01, 0xF2]);
assert_eq!(response.next(), Some(expected_packet));
assert_eq!(response.next(), None);
}
#[test]
fn test_keepalive() {
for &status in [KeepaliveStatus::Processing, KeepaliveStatus::UpNeeded].iter() {
let cid = [0x12, 0x34, 0x56, 0x78];
let mut response = CtapHid::keepalive(cid, status);
let mut expected_packet = [0x00; 64];
expected_packet[..8].copy_from_slice(&[
0x12,
0x34,
0x56,
0x78,
0xBB,
0x00,
0x01,
status as u8,
]);
assert_eq!(response.next(), Some(expected_packet));
assert_eq!(response.next(), None);
}
}
#[test]
fn test_process_single_packet() {
let cid = [0x12, 0x34, 0x56, 0x78];
let mut packet = [0x00; 64];
packet[..4].copy_from_slice(&cid);
packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
let (processed_cid, processed_packet) = CtapHid::process_single_packet(&packet);
assert_eq!(processed_cid, &cid);
let expected_packet = ProcessedPacket::InitPacket {
cmd: CtapHidCommand::Ping as u8,
len: 2,
data: array_ref!(packet, 7, 57),
};
assert_eq!(processed_packet, expected_packet);
}
#[test]
fn test_from_ctap_hid_command() {
// 0x3E is unassigned.
assert_eq!(CtapHidCommand::from(0x3E), CtapHidCommand::Error);
for command in CtapHidCommand::into_enum_iter() {
assert_eq!(CtapHidCommand::from(command as u8), command);
}
}
#[test]
fn test_error_message() {
let cid = [0x12, 0x34, 0x56, 0x78];
assert_eq!(
CtapHid::error_message(cid, CtapHidError::InvalidCmd),
Message {
cid,
cmd: CtapHidCommand::Error,
payload: vec![0x01],
}
); );
} }
} }

View File

@@ -15,7 +15,9 @@
use crate::clock::CtapInstant; use crate::clock::CtapInstant;
use super::super::customization::MAX_MSG_SIZE; use super::super::customization::MAX_MSG_SIZE;
use super::{ChannelID, CtapHid, CtapHidCommand, HidPacket, Message, ProcessedPacket}; use super::{
ChannelID, CtapHid, CtapHidCommand, CtapHidError, HidPacket, Message, ProcessedPacket,
};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::mem::swap; use core::mem::swap;
@@ -37,22 +39,6 @@ pub struct MessageAssembler {
payload: Vec<u8>, payload: Vec<u8>,
} }
#[derive(PartialEq, Debug)]
pub enum Error {
// Expected a continuation packet on a specific channel, got a packet on another channel.
UnexpectedChannel,
// Expected a continuation packet, got an init packet.
UnexpectedInit,
// Expected an init packet, got a continuation packet.
UnexpectedContinuation,
// Expected a continuation packet with a specific sequence number, got another sequence number.
UnexpectedSeq,
// The length of a message is too big.
UnexpectedLen,
// This packet arrived after a timeout.
Timeout,
}
impl MessageAssembler { impl MessageAssembler {
pub fn new() -> MessageAssembler { pub fn new() -> MessageAssembler {
MessageAssembler { MessageAssembler {
@@ -68,7 +54,7 @@ impl MessageAssembler {
// Resets the message assembler to the idle state. // Resets the message assembler to the idle state.
// The caller can reset the assembler for example due to a timeout. // The caller can reset the assembler for example due to a timeout.
pub fn reset(&mut self) { fn reset(&mut self) {
self.idle = true; self.idle = true;
self.cid = [0, 0, 0, 0]; self.cid = [0, 0, 0, 0];
self.last_timestamp = CtapInstant::new(0); self.last_timestamp = CtapInstant::new(0);
@@ -89,7 +75,7 @@ impl MessageAssembler {
&mut self, &mut self,
packet: &HidPacket, packet: &HidPacket,
timestamp: CtapInstant, timestamp: CtapInstant,
) -> Result<Option<Message>, (ChannelID, Error)> { ) -> Result<Option<Message>, (ChannelID, CtapHidError)> {
// TODO: Support non-full-speed devices (i.e. packet len != 64)? This isn't recommended by // TODO: Support non-full-speed devices (i.e. packet len != 64)? This isn't recommended by
// section 8.8.1 // section 8.8.1
let (cid, processed_packet) = CtapHid::process_single_packet(packet); let (cid, processed_packet) = CtapHid::process_single_packet(packet);
@@ -103,7 +89,7 @@ impl MessageAssembler {
// If the packet is from the timed-out channel, send back a timeout error. // If the packet is from the timed-out channel, send back a timeout error.
// Otherwise, proceed with processing the packet. // Otherwise, proceed with processing the packet.
if *cid == current_cid { if *cid == current_cid {
return Err((*cid, Error::Timeout)); return Err((*cid, CtapHidError::MsgTimeout));
} }
} }
@@ -116,7 +102,7 @@ impl MessageAssembler {
ProcessedPacket::ContinuationPacket { .. } => { ProcessedPacket::ContinuationPacket { .. } => {
// CTAP specification (version 20190130) section 8.1.5.4 // CTAP specification (version 20190130) section 8.1.5.4
// Spurious continuation packets will be ignored. // Spurious continuation packets will be ignored.
Err((*cid, Error::UnexpectedContinuation)) Err((*cid, CtapHidError::UnexpectedContinuation))
} }
} }
} else { } else {
@@ -125,7 +111,7 @@ impl MessageAssembler {
// CTAP specification (version 20190130) section 8.1.5.1 // CTAP specification (version 20190130) section 8.1.5.1
// Reject packets from other channels. // Reject packets from other channels.
if *cid != self.cid { if *cid != self.cid {
return Err((*cid, Error::UnexpectedChannel)); return Err((*cid, CtapHidError::ChannelBusy));
} }
match processed_packet { match processed_packet {
@@ -135,14 +121,14 @@ impl MessageAssembler {
if cmd == CtapHidCommand::Init as u8 { if cmd == CtapHidCommand::Init as u8 {
self.parse_init_packet(*cid, cmd, len, data, timestamp) self.parse_init_packet(*cid, cmd, len, data, timestamp)
} else { } else {
Err((*cid, Error::UnexpectedInit)) Err((*cid, CtapHidError::InvalidSeq))
} }
} }
ProcessedPacket::ContinuationPacket { seq, data } => { ProcessedPacket::ContinuationPacket { seq, data } => {
if seq != self.seq { if seq != self.seq {
// Reject packets with the wrong sequence number. // Reject packets with the wrong sequence number.
self.reset(); self.reset();
Err((*cid, Error::UnexpectedSeq)) Err((*cid, CtapHidError::InvalidSeq))
} else { } else {
// Update the last timestamp. // Update the last timestamp.
self.last_timestamp = timestamp; self.last_timestamp = timestamp;
@@ -162,11 +148,11 @@ impl MessageAssembler {
len: usize, len: usize,
data: &[u8], data: &[u8],
timestamp: CtapInstant, timestamp: CtapInstant,
) -> Result<Option<Message>, (ChannelID, Error)> { ) -> Result<Option<Message>, (ChannelID, CtapHidError)> {
// Reject invalid lengths early to reduce the risk of running out of memory. // Reject invalid lengths early to reduce the risk of running out of memory.
// TODO: also reject invalid commands early? // TODO: also reject invalid commands early?
if len > MAX_MSG_SIZE { if len > MAX_MSG_SIZE {
return Err((cid, Error::UnexpectedLen)); return Err((cid, CtapHidError::InvalidLen));
} }
self.cid = cid; self.cid = cid;
self.last_timestamp = timestamp; self.last_timestamp = timestamp;
@@ -455,7 +441,7 @@ mod test {
&byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte), &byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte),
CtapInstant::new(0) CtapInstant::new(0)
), ),
Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel)) Err(([0x12, 0x34, 0x56, 0x9A], CtapHidError::ChannelBusy))
); );
} }
} }
@@ -501,7 +487,10 @@ mod test {
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]), &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
CtapInstant::new(0) CtapInstant::new(0)
), ),
Err(([0x12, 0x34, 0x56, 0x78], Error::UnexpectedContinuation)) Err((
[0x12, 0x34, 0x56, 0x78],
CtapHidError::UnexpectedContinuation
))
); );
} }
} }
@@ -521,7 +510,7 @@ mod test {
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]), &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]),
CtapInstant::new(0) CtapInstant::new(0)
), ),
Err(([0x12, 0x34, 0x56, 0x78], Error::UnexpectedInit)) Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::InvalidSeq))
); );
} }
@@ -540,7 +529,7 @@ mod test {
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]), &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]),
CtapInstant::new(0) CtapInstant::new(0)
), ),
Err(([0x12, 0x34, 0x56, 0x78], Error::UnexpectedSeq)) Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::InvalidSeq))
); );
} }
@@ -559,7 +548,7 @@ mod test {
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]), &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
CtapInstant::new(0) + CtapHid::TIMEOUT_DURATION CtapInstant::new(0) + CtapHid::TIMEOUT_DURATION
), ),
Err(([0x12, 0x34, 0x56, 0x78], Error::Timeout)) Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::MsgTimeout))
); );
} }

View File

@@ -45,7 +45,7 @@ impl Iterator for HidPacketIterator {
} }
} }
pub struct MessageSplitter { struct MessageSplitter {
message: Message, message: Message,
packet: HidPacket, packet: HidPacket,
seq: Option<u8>, seq: Option<u8>,
@@ -292,6 +292,4 @@ mod test {
}; };
assert!(HidPacketIterator::new(message).is_none()); assert!(HidPacketIterator::new(message).is_none());
} }
// TODO(kaczmarczyck) implement and test limits (maximum bytes and packets)
} }

View File

@@ -18,8 +18,7 @@ extern crate alloc;
#[macro_use] #[macro_use]
extern crate arrayref; extern crate arrayref;
use crate::ctap::hid::send::HidPacketIterator; use crate::ctap::hid::{CtapHid, HidPacket, HidPacketIterator};
use crate::ctap::hid::{CtapHid, HidPacket};
use crate::ctap::CtapState; use crate::ctap::CtapState;
use crate::env::Env; use crate::env::Env;
use clock::CtapInstant; use clock::CtapInstant;