adds a versatile API to make removing CtapState easier
This commit is contained in:
committed by
kaczmarczyck
parent
c595980a3b
commit
2df7164c1f
@@ -114,6 +114,18 @@ pub enum KeepaliveStatus {
|
|||||||
/// - information about requested winks.
|
/// - information about requested winks.
|
||||||
///
|
///
|
||||||
/// The wink information can be polled to decide to i.e. blink LEDs.
|
/// The wink information can be polled to decide to i.e. blink LEDs.
|
||||||
|
///
|
||||||
|
/// To process a packet and receive the response, you can call `process_hid_packet`.
|
||||||
|
/// If you want more control, you can also do the processing in steps:
|
||||||
|
///
|
||||||
|
/// 1. `HidPacket` -> `Option<Message>`
|
||||||
|
/// `parse_packet` assembles the message and preprocesses all pure HID commands and errors.
|
||||||
|
/// 2. `Option<Message>` -> `Message`
|
||||||
|
/// If you didn't receive any message or preprocessing discarded it, stop.
|
||||||
|
/// 3. `Message` -> `Message`
|
||||||
|
/// `process_message` handles all protocol interactions.
|
||||||
|
/// 4. `Message` -> `HidPacketIterator
|
||||||
|
/// `split_message` creates packets out of the response message.
|
||||||
pub struct CtapHid {
|
pub struct CtapHid {
|
||||||
assembler: MessageAssembler,
|
assembler: MessageAssembler,
|
||||||
// The specification only requires unique CIDs, the allocation algorithm is vendor specific.
|
// The specification only requires unique CIDs, the allocation algorithm is vendor specific.
|
||||||
@@ -175,58 +187,93 @@ impl CtapHid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes an assembled incoming message and returns an outgoing message if necessary.
|
/// Parses a packet, and preprocesses some messages and errors.
|
||||||
pub fn process_message(
|
///
|
||||||
&mut self,
|
/// The preprocessed commands are:
|
||||||
env: &mut impl Env,
|
/// - INIT
|
||||||
message: Message,
|
/// - CANCEL
|
||||||
clock_value: ClockValue,
|
/// - ERROR
|
||||||
ctap_state: &mut CtapState,
|
/// - Unknown and unexpected commands like KEEPALIVE
|
||||||
) -> Option<Message> {
|
/// - LOCK is not implemented and currently treated like an unknown command
|
||||||
#[cfg(feature = "debug_ctap")]
|
///
|
||||||
writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap();
|
/// Commands that may still be processed:
|
||||||
|
/// - PING
|
||||||
let cid = message.cid;
|
/// - MSG
|
||||||
if !self.has_valid_channel(&message) {
|
/// - WINK
|
||||||
#[cfg(feature = "debug_ctap")]
|
/// - CBOR
|
||||||
writeln!(&mut Console::new(), "Invalid channel: {:02x?}", cid).unwrap();
|
///
|
||||||
return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL));
|
/// You may ignore PING, it's behaving correctly by default (input == output).
|
||||||
}
|
/// Ignoring the others is incorrect behavior. You have to at least replace them with an error
|
||||||
// If another command arrives, stop winking to prevent accidential button touches.
|
/// message:
|
||||||
self.wink_permission = TimedPermission::waiting();
|
/// `CtapHid::error_message(message.cid, CtapHid::ERR_INVALID_CMD)`
|
||||||
|
pub fn parse_packet(&mut self, packet: &HidPacket, clock_value: ClockValue) -> Option<Message> {
|
||||||
match message.cmd {
|
match self
|
||||||
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.1.
|
.assembler
|
||||||
CtapHidCommand::Msg => {
|
.parse_packet(packet, Timestamp::<isize>::from_clock_value(clock_value))
|
||||||
// If we don't have CTAP1 backward compatibilty, this command is invalid.
|
{
|
||||||
#[cfg(not(feature = "with_ctap1"))]
|
Ok(Some(message)) => {
|
||||||
return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD));
|
#[cfg(feature = "debug_ctap")]
|
||||||
|
writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap();
|
||||||
#[cfg(feature = "with_ctap1")]
|
self.preprocess_message(message)
|
||||||
match ctap1::Ctap1Command::process_command(
|
}
|
||||||
env,
|
Ok(None) => {
|
||||||
&message.payload,
|
// Waiting for more packets to assemble the message, nothing to send for now.
|
||||||
ctap_state,
|
None
|
||||||
clock_value,
|
}
|
||||||
) {
|
Err((cid, error)) => {
|
||||||
Ok(payload) => Some(CtapHid::ctap1_success_message(cid, &payload)),
|
if !self.is_allocated_channel(cid)
|
||||||
Err(ctap1_status_code) => {
|
&& error != receive::Error::UnexpectedContinuation
|
||||||
Some(CtapHid::ctap1_error_message(cid, ctap1_status_code))
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
receive::Error::UnexpectedSeq => {
|
||||||
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
|
||||||
|
}
|
||||||
|
receive::Error::UnexpectedLen => {
|
||||||
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN))
|
||||||
|
}
|
||||||
|
receive::Error::Timeout => {
|
||||||
|
Some(CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.2.
|
}
|
||||||
CtapHidCommand::Cbor => {
|
}
|
||||||
// Each transaction is atomic, so we process the command directly here and
|
|
||||||
// don't handle any other packet in the meantime.
|
/// Processes HID-only commands of a message and returns an outgoing message if necessary.
|
||||||
// TODO: Send "Processing" type keep-alive packets in the meantime.
|
///
|
||||||
let response = ctap_state.process_command(env, &message.payload, cid, clock_value);
|
/// The preprocessed commands are:
|
||||||
Some(Message {
|
/// - INIT
|
||||||
cid,
|
/// - CANCEL
|
||||||
cmd: CtapHidCommand::Cbor,
|
/// - ERROR
|
||||||
payload: response,
|
/// - Unknown and unexpected commands like KEEPALIVE
|
||||||
})
|
/// - LOCK is not implemented and currently treated like an unknown command
|
||||||
}
|
fn preprocess_message(&mut self, message: Message) -> Option<Message> {
|
||||||
|
let cid = message.cid;
|
||||||
|
if !self.has_valid_channel(&message) {
|
||||||
|
return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
match message.cmd {
|
||||||
|
CtapHidCommand::Msg => Some(message),
|
||||||
|
CtapHidCommand::Cbor => Some(message),
|
||||||
// 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 {
|
||||||
@@ -268,16 +315,7 @@ impl CtapHid {
|
|||||||
// CANCEL is handled during user presence checks in main.
|
// CANCEL is handled during user presence checks in main.
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
// CTAP 2.1 from 2021-06-15, section 11.2.9.2.1.
|
CtapHidCommand::Wink => Some(message),
|
||||||
CtapHidCommand::Wink => {
|
|
||||||
if !message.payload.is_empty() {
|
|
||||||
return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN));
|
|
||||||
}
|
|
||||||
self.wink_permission =
|
|
||||||
TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION);
|
|
||||||
// The response is empty like the request.
|
|
||||||
Some(message)
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
// Unknown or unsupported command.
|
// Unknown or unsupported command.
|
||||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD))
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD))
|
||||||
@@ -285,6 +323,64 @@ impl CtapHid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Processes a message's commands that affect the protocol outside HID.
|
||||||
|
pub fn process_message(
|
||||||
|
&mut self,
|
||||||
|
env: &mut impl Env,
|
||||||
|
message: Message,
|
||||||
|
clock_value: ClockValue,
|
||||||
|
ctap_state: &mut CtapState,
|
||||||
|
) -> Message {
|
||||||
|
// If another command arrives, stop winking to prevent accidential button touches.
|
||||||
|
self.wink_permission = TimedPermission::waiting();
|
||||||
|
|
||||||
|
let cid = message.cid;
|
||||||
|
match message.cmd {
|
||||||
|
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.1.
|
||||||
|
CtapHidCommand::Msg => {
|
||||||
|
// If we don't have CTAP1 backward compatibilty, this command is invalid.
|
||||||
|
#[cfg(not(feature = "with_ctap1"))]
|
||||||
|
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD);
|
||||||
|
|
||||||
|
#[cfg(feature = "with_ctap1")]
|
||||||
|
match ctap1::Ctap1Command::process_command(
|
||||||
|
env,
|
||||||
|
&message.payload,
|
||||||
|
ctap_state,
|
||||||
|
clock_value,
|
||||||
|
) {
|
||||||
|
Ok(payload) => CtapHid::ctap1_success_message(cid, &payload),
|
||||||
|
Err(ctap1_status_code) => CtapHid::ctap1_error_message(cid, ctap1_status_code),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.2.
|
||||||
|
CtapHidCommand::Cbor => {
|
||||||
|
// Each transaction is atomic, so we process the command directly here and
|
||||||
|
// don't handle any other packet in the meantime.
|
||||||
|
// TODO: Send "Processing" type keep-alive packets in the meantime.
|
||||||
|
let response = ctap_state.process_command(env, &message.payload, cid, clock_value);
|
||||||
|
Message {
|
||||||
|
cid,
|
||||||
|
cmd: CtapHidCommand::Cbor,
|
||||||
|
payload: response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CTAP 2.1 from 2021-06-15, section 11.2.9.2.1.
|
||||||
|
CtapHidCommand::Wink => {
|
||||||
|
if message.payload.is_empty() {
|
||||||
|
self.wink_permission =
|
||||||
|
TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION);
|
||||||
|
// The response is empty like the request.
|
||||||
|
message
|
||||||
|
} else {
|
||||||
|
CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All other commands have already been processed, keep them as is.
|
||||||
|
_ => message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes an incoming USB HID packet, and returns an iterator for all outgoing packets.
|
/// Processes an incoming USB HID packet, and returns an iterator for all outgoing packets.
|
||||||
pub fn process_hid_packet(
|
pub fn process_hid_packet(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -293,52 +389,16 @@ impl CtapHid {
|
|||||||
clock_value: ClockValue,
|
clock_value: ClockValue,
|
||||||
ctap_state: &mut CtapState,
|
ctap_state: &mut CtapState,
|
||||||
) -> HidPacketIterator {
|
) -> HidPacketIterator {
|
||||||
let reponse_message = match self
|
if let Some(message) = self.parse_packet(packet, clock_value) {
|
||||||
.assembler
|
let processed_message = self.process_message(env, message, clock_value, ctap_state);
|
||||||
.parse_packet(packet, Timestamp::<isize>::from_clock_value(clock_value))
|
#[cfg(feature = "debug_ctap")]
|
||||||
{
|
writeln!(
|
||||||
Ok(Some(message)) => self.process_message(env, message, clock_value, ctap_state),
|
&mut Console::new(),
|
||||||
Ok(None) => {
|
"Sending message: {:02x?}",
|
||||||
// Waiting for more packets to assemble the message, nothing to send for now.
|
processed_message
|
||||||
None
|
)
|
||||||
}
|
.unwrap();
|
||||||
Err((cid, error)) => {
|
CtapHid::split_message(processed_message)
|
||||||
if !self.is_allocated_channel(cid)
|
|
||||||
&& 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
|
|
||||||
}
|
|
||||||
receive::Error::UnexpectedSeq => {
|
|
||||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
|
|
||||||
}
|
|
||||||
receive::Error::UnexpectedLen => {
|
|
||||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN))
|
|
||||||
}
|
|
||||||
receive::Error::Timeout => {
|
|
||||||
Some(CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(message) = reponse_message {
|
|
||||||
CtapHid::split_message(message)
|
|
||||||
} else {
|
} else {
|
||||||
HidPacketIterator::none()
|
HidPacketIterator::none()
|
||||||
}
|
}
|
||||||
@@ -402,8 +462,6 @@ impl CtapHid {
|
|||||||
/// correctly handled by the CTAP implementation. It is therefore an internal error from the
|
/// correctly handled by the CTAP implementation. It is therefore an internal error from the
|
||||||
/// HID perspective.
|
/// HID perspective.
|
||||||
fn split_message(message: Message) -> HidPacketIterator {
|
fn split_message(message: Message) -> HidPacketIterator {
|
||||||
#[cfg(feature = "debug_ctap")]
|
|
||||||
writeln!(&mut Console::new(), "Sending message: {:02x?}", message).unwrap();
|
|
||||||
let cid = message.cid;
|
let cid = message.cid;
|
||||||
HidPacketIterator::new(message).unwrap_or_else(|| {
|
HidPacketIterator::new(message).unwrap_or_else(|| {
|
||||||
// The error payload is 1 <= 7609 bytes, so unwrap() is safe.
|
// The error payload is 1 <= 7609 bytes, so unwrap() is safe.
|
||||||
|
|||||||
Reference in New Issue
Block a user