Move check_user_presence to env::tock
This commit is contained in:
234
src/env/tock.rs
vendored
234
src/env/tock.rs
vendored
@@ -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<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> {
|
||||
pub struct TockEnv {
|
||||
rng: TockRng256,
|
||||
check_user_presence: CheckUserPresence,
|
||||
}
|
||||
|
||||
impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> TockEnv<CheckUserPresence> {
|
||||
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<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> UserPresence
|
||||
for TockEnv<CheckUserPresence>
|
||||
{
|
||||
impl UserPresence for TockEnv {
|
||||
fn check(&self, cid: ChannelID) -> Result<(), Ctap2StatusCode> {
|
||||
(self.check_user_presence)(cid)
|
||||
check_user_presence(cid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> Env
|
||||
for TockEnv<CheckUserPresence>
|
||||
{
|
||||
impl Env for TockEnv {
|
||||
type Rng = TockRng256;
|
||||
type UserPresence = Self;
|
||||
|
||||
@@ -41,3 +42,200 @@ impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> Env
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether the keepalive was sent, or false if cancelled.
|
||||
fn send_keepalive_up_needed(
|
||||
cid: ChannelID,
|
||||
timeout: Duration<isize>,
|
||||
) -> 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<isize> = 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: <error is only visible with the debug_ctap feature>");
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user