diff --git a/src/env/tock.rs b/src/env/tock.rs index 29f8ed3..ea34111 100644 --- a/src/env/tock.rs +++ b/src/env/tock.rs @@ -1,35 +1,36 @@ -use crypto::rng256::TockRng256; - -use crate::ctap::hid::ChannelID; +use crate::ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket}; use crate::ctap::status_code::Ctap2StatusCode; use crate::env::{Env, UserPresence}; +use core::cell::Cell; +#[cfg(feature = "debug_ctap")] +use core::fmt::Write; +use crypto::rng256::TockRng256; +use libtock_core::result::{CommandError, EALREADY}; +use libtock_drivers::buttons::{self, ButtonState}; +#[cfg(feature = "debug_ctap")] +use libtock_drivers::console::Console; +use libtock_drivers::result::{FlexUnwrap, TockError}; +use libtock_drivers::timer::Duration; +use libtock_drivers::{led, timer, usb_ctap_hid}; -pub struct TockEnv Result<(), Ctap2StatusCode>> { +pub struct TockEnv { rng: TockRng256, - check_user_presence: CheckUserPresence, } -impl Result<(), Ctap2StatusCode>> TockEnv { - pub fn new(check_user_presence: CheckUserPresence) -> Self { +impl TockEnv { + pub fn new() -> Self { let rng = TockRng256 {}; - TockEnv { - rng, - check_user_presence, - } + TockEnv { rng } } } -impl Result<(), Ctap2StatusCode>> UserPresence - for TockEnv -{ +impl UserPresence for TockEnv { fn check(&self, cid: ChannelID) -> Result<(), Ctap2StatusCode> { - (self.check_user_presence)(cid) + check_user_presence(cid) } } -impl Result<(), Ctap2StatusCode>> Env - for TockEnv -{ +impl Env for TockEnv { type Rng = TockRng256; type UserPresence = Self; @@ -41,3 +42,200 @@ impl Result<(), Ctap2StatusCode>> Env self } } + +// Returns whether the keepalive was sent, or false if cancelled. +fn send_keepalive_up_needed( + cid: ChannelID, + timeout: Duration, +) -> Result<(), Ctap2StatusCode> { + let keepalive_msg = CtapHid::keepalive(cid, KeepaliveStatus::UpNeeded); + for mut pkt in keepalive_msg { + let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt, timeout); + match status { + None => { + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Sending a KEEPALIVE packet timed out").unwrap(); + // TODO: abort user presence test? + } + Some(usb_ctap_hid::SendOrRecvStatus::Error) => panic!("Error sending KEEPALIVE packet"), + Some(usb_ctap_hid::SendOrRecvStatus::Sent) => { + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Sent KEEPALIVE packet").unwrap(); + } + Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + // We only parse one packet, because we only care about CANCEL. + let (received_cid, processed_packet) = CtapHid::process_single_packet(&pkt); + if received_cid != &cid { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "Received a packet on channel ID {:?} while sending a KEEPALIVE packet", + received_cid, + ) + .unwrap(); + return Ok(()); + } + match processed_packet { + ProcessedPacket::InitPacket { cmd, .. } => { + if cmd == CtapHid::COMMAND_CANCEL { + // We ignore the payload, we can't answer with an error code anyway. + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "User presence check cancelled").unwrap(); + return Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL); + } else { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "Discarded packet with command {} received while sending a KEEPALIVE packet", + cmd, + ) + .unwrap(); + } + } + ProcessedPacket::ContinuationPacket { .. } => { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "Discarded continuation packet received while sending a KEEPALIVE packet", + ) + .unwrap(); + } + } + } + } + } + Ok(()) +} + +pub fn blink_leds(pattern_seed: usize) { + for l in 0..led::count().flex_unwrap() { + if (pattern_seed ^ l).count_ones() & 1 != 0 { + led::get(l).flex_unwrap().on().flex_unwrap(); + } else { + led::get(l).flex_unwrap().off().flex_unwrap(); + } + } +} + +pub fn wink_leds(pattern_seed: usize) { + // This generates a "snake" pattern circling through the LEDs. + // Fox example with 4 LEDs the sequence of lit LEDs will be the following. + // 0 1 2 3 + // * * + // * * * + // * * + // * * * + // * * + // * * * + // * * + // * * * + // * * + let count = led::count().flex_unwrap(); + let a = (pattern_seed / 2) % count; + let b = ((pattern_seed + 1) / 2) % count; + let c = ((pattern_seed + 3) / 2) % count; + + for l in 0..count { + // On nRF52840-DK, logically swap LEDs 3 and 4 so that the order of LEDs form a circle. + let k = match l { + 2 => 3, + 3 => 2, + _ => l, + }; + if k == a || k == b || k == c { + led::get(l).flex_unwrap().on().flex_unwrap(); + } else { + led::get(l).flex_unwrap().off().flex_unwrap(); + } + } +} + +pub fn switch_off_leds() { + for l in 0..led::count().flex_unwrap() { + led::get(l).flex_unwrap().off().flex_unwrap(); + } +} + +const KEEPALIVE_DELAY_MS: isize = 100; +pub const KEEPALIVE_DELAY: Duration = Duration::from_ms(KEEPALIVE_DELAY_MS); + +fn check_user_presence(cid: ChannelID) -> Result<(), Ctap2StatusCode> { + // The timeout is N times the keepalive delay. + const TIMEOUT_ITERATIONS: usize = + crate::ctap::TOUCH_TIMEOUT_MS as usize / KEEPALIVE_DELAY_MS as usize; + + // First, send a keep-alive packet to notify that the keep-alive status has changed. + send_keepalive_up_needed(cid, KEEPALIVE_DELAY)?; + + // Listen to the button presses. + let button_touched = Cell::new(false); + let mut buttons_callback = buttons::with_callback(|_button_num, state| { + match state { + ButtonState::Pressed => button_touched.set(true), + ButtonState::Released => (), + }; + }); + let mut buttons = buttons_callback.init().flex_unwrap(); + // At the moment, all buttons are accepted. You can customize your setup here. + for mut button in &mut buttons { + button.enable().flex_unwrap(); + } + + let mut keepalive_response = Ok(()); + for i in 0..TIMEOUT_ITERATIONS { + blink_leds(i); + + // Setup a keep-alive callback. + let keepalive_expired = Cell::new(false); + let mut keepalive_callback = timer::with_callback(|_, _| { + keepalive_expired.set(true); + }); + let mut keepalive = keepalive_callback.init().flex_unwrap(); + let keepalive_alarm = keepalive.set_alarm(KEEPALIVE_DELAY).flex_unwrap(); + + // Wait for a button touch or an alarm. + libtock_drivers::util::yieldk_for(|| button_touched.get() || keepalive_expired.get()); + + // Cleanup alarm callback. + match keepalive.stop_alarm(keepalive_alarm) { + Ok(()) => (), + Err(TockError::Command(CommandError { + return_code: EALREADY, + .. + })) => assert!(keepalive_expired.get()), + Err(_e) => { + #[cfg(feature = "debug_ctap")] + panic!("Unexpected error when stopping alarm: {:?}", _e); + #[cfg(not(feature = "debug_ctap"))] + panic!("Unexpected error when stopping alarm: "); + } + } + + // TODO: this may take arbitrary time. The keepalive_delay should be adjusted accordingly, + // so that LEDs blink with a consistent pattern. + if keepalive_expired.get() { + // Do not return immediately, because we must clean up still. + keepalive_response = send_keepalive_up_needed(cid, KEEPALIVE_DELAY); + } + + if button_touched.get() || keepalive_response.is_err() { + break; + } + } + + switch_off_leds(); + + // Cleanup button callbacks. + for mut button in &mut buttons { + button.disable().flex_unwrap(); + } + + // Returns whether the user was present. + if keepalive_response.is_err() { + keepalive_response + } else if button_touched.get() { + Ok(()) + } else { + Err(Ctap2StatusCode::CTAP2_ERR_USER_ACTION_TIMEOUT) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5826f29..e068f26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,12 @@ use crate::ctap::CtapState; use crate::env::Env; use libtock_drivers::timer::ClockValue; +// Implementation details must be public for testing (in particular fuzzing). +#[cfg(feature = "std")] pub mod ctap; +#[cfg(not(feature = "std"))] +mod ctap; +// Store example binaries use the flash directly. Eventually, they should access it from env::tock. pub mod embedded_flash; pub mod env; diff --git a/src/main.rs b/src/main.rs index d8d67ad..04f4c3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,19 +21,18 @@ extern crate byteorder; extern crate core; extern crate lang_items; +#[cfg(feature = "with_ctap1")] use core::cell::Cell; #[cfg(feature = "debug_ctap")] use core::fmt::Write; -use ctap2::ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket}; -use ctap2::ctap::status_code::Ctap2StatusCode; -use ctap2::env::tock::TockEnv; -use libtock_core::result::{CommandError, EALREADY}; -use libtock_drivers::buttons; -use libtock_drivers::buttons::ButtonState; +#[cfg(feature = "with_ctap1")] +use ctap2::env::tock::blink_leds; +use ctap2::env::tock::{switch_off_leds, wink_leds, TockEnv, KEEPALIVE_DELAY}; +#[cfg(feature = "with_ctap1")] +use libtock_drivers::buttons::{self, ButtonState}; #[cfg(feature = "debug_ctap")] use libtock_drivers::console::Console; -use libtock_drivers::led; -use libtock_drivers::result::{FlexUnwrap, TockError}; +use libtock_drivers::result::FlexUnwrap; use libtock_drivers::timer; use libtock_drivers::timer::Duration; #[cfg(feature = "debug_ctap")] @@ -44,8 +43,6 @@ use libtock_drivers::usb_ctap_hid; libtock_core::stack_size! {0x4000} -const KEEPALIVE_DELAY_MS: isize = 100; -const KEEPALIVE_DELAY: Duration = Duration::from_ms(KEEPALIVE_DELAY_MS); const SEND_TIMEOUT: Duration = Duration::from_ms(1000); fn main() { @@ -60,7 +57,7 @@ fn main() { } let boot_time = timer.get_current_clock().flex_unwrap(); - let env = TockEnv::new(check_user_presence); + let env = TockEnv::new(); let mut ctap = ctap2::Ctap::new(env, boot_time); let mut led_counter = 0; @@ -192,197 +189,3 @@ fn print_packet_notice(notice_text: &str, timer: &Timer) { ) .unwrap(); } - -// Returns whether the keepalive was sent, or false if cancelled. -fn send_keepalive_up_needed( - cid: ChannelID, - timeout: Duration, -) -> Result<(), Ctap2StatusCode> { - let keepalive_msg = CtapHid::keepalive(cid, KeepaliveStatus::UpNeeded); - for mut pkt in keepalive_msg { - let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt, timeout); - match status { - None => { - #[cfg(feature = "debug_ctap")] - writeln!(Console::new(), "Sending a KEEPALIVE packet timed out").unwrap(); - // TODO: abort user presence test? - } - Some(usb_ctap_hid::SendOrRecvStatus::Error) => panic!("Error sending KEEPALIVE packet"), - Some(usb_ctap_hid::SendOrRecvStatus::Sent) => { - #[cfg(feature = "debug_ctap")] - writeln!(Console::new(), "Sent KEEPALIVE packet").unwrap(); - } - Some(usb_ctap_hid::SendOrRecvStatus::Received) => { - // We only parse one packet, because we only care about CANCEL. - let (received_cid, processed_packet) = CtapHid::process_single_packet(&pkt); - if received_cid != &cid { - #[cfg(feature = "debug_ctap")] - writeln!( - Console::new(), - "Received a packet on channel ID {:?} while sending a KEEPALIVE packet", - received_cid, - ) - .unwrap(); - return Ok(()); - } - match processed_packet { - ProcessedPacket::InitPacket { cmd, .. } => { - if cmd == CtapHid::COMMAND_CANCEL { - // We ignore the payload, we can't answer with an error code anyway. - #[cfg(feature = "debug_ctap")] - writeln!(Console::new(), "User presence check cancelled").unwrap(); - return Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL); - } else { - #[cfg(feature = "debug_ctap")] - writeln!( - Console::new(), - "Discarded packet with command {} received while sending a KEEPALIVE packet", - cmd, - ) - .unwrap(); - } - } - ProcessedPacket::ContinuationPacket { .. } => { - #[cfg(feature = "debug_ctap")] - writeln!( - Console::new(), - "Discarded continuation packet received while sending a KEEPALIVE packet", - ) - .unwrap(); - } - } - } - } - } - Ok(()) -} - -fn blink_leds(pattern_seed: usize) { - for l in 0..led::count().flex_unwrap() { - if (pattern_seed ^ l).count_ones() & 1 != 0 { - led::get(l).flex_unwrap().on().flex_unwrap(); - } else { - led::get(l).flex_unwrap().off().flex_unwrap(); - } - } -} - -fn wink_leds(pattern_seed: usize) { - // This generates a "snake" pattern circling through the LEDs. - // Fox example with 4 LEDs the sequence of lit LEDs will be the following. - // 0 1 2 3 - // * * - // * * * - // * * - // * * * - // * * - // * * * - // * * - // * * * - // * * - let count = led::count().flex_unwrap(); - let a = (pattern_seed / 2) % count; - let b = ((pattern_seed + 1) / 2) % count; - let c = ((pattern_seed + 3) / 2) % count; - - for l in 0..count { - // On nRF52840-DK, logically swap LEDs 3 and 4 so that the order of LEDs form a circle. - let k = match l { - 2 => 3, - 3 => 2, - _ => l, - }; - if k == a || k == b || k == c { - led::get(l).flex_unwrap().on().flex_unwrap(); - } else { - led::get(l).flex_unwrap().off().flex_unwrap(); - } - } -} - -fn switch_off_leds() { - for l in 0..led::count().flex_unwrap() { - led::get(l).flex_unwrap().off().flex_unwrap(); - } -} - -fn check_user_presence(cid: ChannelID) -> Result<(), Ctap2StatusCode> { - // The timeout is N times the keepalive delay. - const TIMEOUT_ITERATIONS: usize = - ctap2::ctap::TOUCH_TIMEOUT_MS as usize / KEEPALIVE_DELAY_MS as usize; - - // First, send a keep-alive packet to notify that the keep-alive status has changed. - send_keepalive_up_needed(cid, KEEPALIVE_DELAY)?; - - // Listen to the button presses. - let button_touched = Cell::new(false); - let mut buttons_callback = buttons::with_callback(|_button_num, state| { - match state { - ButtonState::Pressed => button_touched.set(true), - ButtonState::Released => (), - }; - }); - let mut buttons = buttons_callback.init().flex_unwrap(); - // At the moment, all buttons are accepted. You can customize your setup here. - for mut button in &mut buttons { - button.enable().flex_unwrap(); - } - - let mut keepalive_response = Ok(()); - for i in 0..TIMEOUT_ITERATIONS { - blink_leds(i); - - // Setup a keep-alive callback. - let keepalive_expired = Cell::new(false); - let mut keepalive_callback = timer::with_callback(|_, _| { - keepalive_expired.set(true); - }); - let mut keepalive = keepalive_callback.init().flex_unwrap(); - let keepalive_alarm = keepalive.set_alarm(KEEPALIVE_DELAY).flex_unwrap(); - - // Wait for a button touch or an alarm. - libtock_drivers::util::yieldk_for(|| button_touched.get() || keepalive_expired.get()); - - // Cleanup alarm callback. - match keepalive.stop_alarm(keepalive_alarm) { - Ok(()) => (), - Err(TockError::Command(CommandError { - return_code: EALREADY, - .. - })) => assert!(keepalive_expired.get()), - Err(_e) => { - #[cfg(feature = "debug_ctap")] - panic!("Unexpected error when stopping alarm: {:?}", _e); - #[cfg(not(feature = "debug_ctap"))] - panic!("Unexpected error when stopping alarm: "); - } - } - - // TODO: this may take arbitrary time. The keepalive_delay should be adjusted accordingly, - // so that LEDs blink with a consistent pattern. - if keepalive_expired.get() { - // Do not return immediately, because we must clean up still. - keepalive_response = send_keepalive_up_needed(cid, KEEPALIVE_DELAY); - } - - if button_touched.get() || keepalive_response.is_err() { - break; - } - } - - switch_off_leds(); - - // Cleanup button callbacks. - for mut button in &mut buttons { - button.disable().flex_unwrap(); - } - - // Returns whether the user was present. - if keepalive_response.is_err() { - keepalive_response - } else if button_touched.get() { - Ok(()) - } else { - Err(Ctap2StatusCode::CTAP2_ERR_USER_ACTION_TIMEOUT) - } -}