Skip to content

Commit

Permalink
Detect if no devices are connected and send a StatusUpdate accordingly.
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Sirringhaus committed Nov 13, 2023
1 parent be6526c commit 26aa766
Show file tree
Hide file tree
Showing 19 changed files with 160 additions and 37 deletions.
3 changes: 3 additions & 0 deletions examples/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ fn main() {
let (status_tx, status_rx) = channel::<StatusUpdate>();
thread::spawn(move || loop {
match status_rx.recv() {
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Expand Down
21 changes: 15 additions & 6 deletions examples/ctap2_discoverable_creds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use authenticator::{
};
use getopts::Options;
use sha2::{Digest, Sha256};
use std::io::Write;
use std::sync::mpsc::{channel, RecvError};
use std::{env, io, thread};
use std::io::Write;

fn print_usage(program: &str, opts: Options) {
println!("------------------------------------------------------------------------");
Expand Down Expand Up @@ -84,6 +84,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down Expand Up @@ -216,10 +219,7 @@ fn main() {
"timeout in seconds",
"SEC",
);
opts.optflag(
"s",
"skip_reg",
"Skip registration");
opts.optflag("s", "skip_reg", "Skip registration");

opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Expand Down Expand Up @@ -273,6 +273,9 @@ fn main() {
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down Expand Up @@ -368,7 +371,13 @@ fn main() {
println!("Found credentials:");
println!(
"{:?}",
assertion_object.assertion.user.clone().unwrap().name.unwrap() // Unwrapping here, as these shouldn't fail
assertion_object
.assertion
.user
.clone()
.unwrap()
.name
.unwrap() // Unwrapping here, as these shouldn't fail
);
println!("-----------------------------------------------------------------");
println!("Done.");
Expand Down
3 changes: 3 additions & 0 deletions examples/interactive_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
);
continue;
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down
3 changes: 3 additions & 0 deletions examples/reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ fn main() {

loop {
match status_rx.recv() {
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("ERROR: Please unplug all other tokens that should not be reset!");
// Needed to give the tokens enough time to start blinking
Expand Down
3 changes: 3 additions & 0 deletions examples/set_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ fn main() {
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down
3 changes: 3 additions & 0 deletions examples/test_exclude_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ fn main() {
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down
4 changes: 1 addition & 3 deletions src/ctap2/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::crypto::{CryptoError, PinUvAuthParam, PinUvAuthToken};
use crate::ctap2::commands::client_pin::{
GetPinRetries, GetUvRetries, PinError,
};
use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, PinError};
use crate::ctap2::commands::get_info::AuthenticatorInfo;
use crate::ctap2::server::UserVerificationRequirement;
use crate::errors::AuthenticatorError;
Expand Down
2 changes: 2 additions & 0 deletions src/status_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub enum StatusUpdate {
InteractiveManagement(InteractiveUpdate),
/// Sent when a token returns multiple results for a getAssertion request
SelectResultNotice(Sender<Option<usize>>, Vec<PublicKeyCredentialUserEntity>),
/// Inform user that no devices are plugged in
NoDevicesFound,
}

pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {
Expand Down
97 changes: 91 additions & 6 deletions src/transport/device_selector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::status_update::send_status;
use crate::transport::hid::HIDDevice;
use crate::StatusUpdate;

pub use crate::transport::platform::device::Device;

Expand Down Expand Up @@ -45,7 +47,7 @@ pub struct DeviceSelector {
}

impl DeviceSelector {
pub fn run() -> Self {
pub fn run(status: Sender<crate::StatusUpdate>) -> Self {
let (selector_send, selector_rec) = channel();
// let new_device_callback = Arc::new(new_device_cb);
let runloop = RunLoop::new(move |alive| {
Expand Down Expand Up @@ -77,6 +79,9 @@ impl DeviceSelector {
break; // We are done here. The selected device continues without us.
}
DeviceSelectorEvent::DevicesAdded(ids) => {
if ids.is_empty() && waiting_for_response.is_empty() && tokens.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
for id in ids {
debug!("Device added event: {:?}", id);
waiting_for_response.insert(id);
Expand All @@ -99,9 +104,15 @@ impl DeviceSelector {
tokens.retain(|dev_id, _| dev_id != id);
if tokens.is_empty() {
blinking = false;
if waiting_for_response.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
continue;
}
}
if waiting_for_response.is_empty() && tokens.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
// We are already blinking, so no need to run the code below this match
// that figures out if we should blink or not. In fact, currently, we do
// NOT want to run this code again, because if you have 2 blinking tokens
Expand All @@ -115,6 +126,9 @@ impl DeviceSelector {
DeviceSelectorEvent::NotAToken(ref id) => {
debug!("Device not a token event: {:?}", id);
waiting_for_response.remove(id);
if waiting_for_response.is_empty() && tokens.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
}
DeviceSelectorEvent::ImAToken((id, tx)) => {
let _ = waiting_for_response.remove(&id);
Expand Down Expand Up @@ -187,6 +201,7 @@ pub mod tests {
transport::FidoDevice,
u2ftypes::U2FDeviceInfo,
};
use std::sync::mpsc::TryRecvError;

fn gen_info(id: String) -> U2FDeviceInfo {
U2FDeviceInfo {
Expand Down Expand Up @@ -267,9 +282,10 @@ pub mod tests {
Device::new("device selector 4").unwrap(),
];

let (tx, rx) = channel();
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
let selector = DeviceSelector::run();
let selector = DeviceSelector::run(tx);

// Adding all
add_devices(devices.iter(), &selector);
Expand All @@ -278,6 +294,7 @@ pub mod tests {
send_no_token(d, &selector);
}
});
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

send_i_am_token(&devices[2], &selector);

Expand All @@ -292,18 +309,24 @@ pub mod tests {
fn test_device_selector_stop() {
let device = Device::new("device selector 1").unwrap();

let mut selector = DeviceSelector::run();
let (tx, rx) = channel();
let mut selector = DeviceSelector::run(tx);

// Adding all
selector
.clone_sender()
.send(DeviceSelectorEvent::DevicesAdded(vec![device.id()]))
.unwrap();
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

selector
.clone_sender()
.send(DeviceSelectorEvent::NotAToken(device.id()))
.unwrap();
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);
selector.stop();
}

Expand All @@ -323,7 +346,8 @@ pub mod tests {
make_device_with_pin(&mut devices[4]);
make_device_with_pin(&mut devices[5]);

let selector = DeviceSelector::run();
let (tx, rx) = channel();
let selector = DeviceSelector::run(tx);

// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter().take(5), &selector);
Expand Down Expand Up @@ -355,6 +379,7 @@ pub mod tests {
devices[5].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
}

#[test]
Expand All @@ -375,7 +400,8 @@ pub mod tests {
make_device_simple_u2f(&mut devices[4]);
make_device_simple_u2f(&mut devices[5]);

let selector = DeviceSelector::run();
let (tx, rx) = channel();
let selector = DeviceSelector::run(tx);

// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter().take(5), &selector);
Expand Down Expand Up @@ -417,6 +443,7 @@ pub mod tests {
devices[6].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
}

#[test]
Expand All @@ -437,7 +464,8 @@ pub mod tests {
make_device_with_pin(&mut devices[4]);
make_device_with_pin(&mut devices[5]);

let selector = DeviceSelector::run();
let (tx, rx) = channel();
let selector = DeviceSelector::run(tx);

// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter(), &selector);
Expand All @@ -456,11 +484,16 @@ pub mod tests {
DeviceCommand::Blink
);
}
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

// Remove all tokens
for idx in [2, 4, 5] {
remove_device(&devices[idx], &selector);
}
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);

// Adding one again
send_i_am_token(&devices[4], &selector);
Expand All @@ -471,4 +504,56 @@ pub mod tests {
DeviceCommand::Continue
);
}

#[test]
fn test_device_selector_no_devices() {
let mut devices = vec![
Device::new("device selector 1").unwrap(),
Device::new("device selector 2").unwrap(),
Device::new("device selector 3").unwrap(),
Device::new("device selector 4").unwrap(),
];

let (tx, rx) = channel();
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
make_device_with_pin(&mut devices[3]);
let selector = DeviceSelector::run(tx);

// Adding no devices first (none are plugged in when we start)
add_devices(std::iter::empty(), &selector);
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);

// Adding the devices
add_devices(devices.iter(), &selector);
devices.iter_mut().for_each(|d| {
if !d.is_u2f() {
send_no_token(d, &selector);
}
});
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

send_i_am_token(&devices[2], &selector);
send_i_am_token(&devices[3], &selector);

assert_eq!(
devices[2].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_eq!(
devices[3].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);

// Removing all blinking devices
remove_device(&devices[2], &selector);
remove_device(&devices[3], &selector);
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);
}
}
2 changes: 1 addition & 1 deletion src/transport/freebsd/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Transaction {
+ 'static,
T: 'static,
{
let device_selector = DeviceSelector::run();
let device_selector = DeviceSelector::run(status.clone());
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
Expand Down
2 changes: 1 addition & 1 deletion src/transport/linux/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Transaction {
+ 'static,
T: 'static,
{
let device_selector = DeviceSelector::run();
let device_selector = DeviceSelector::run(status.clone());
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
Expand Down
2 changes: 1 addition & 1 deletion src/transport/macos/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Transaction {
{
let (tx, rx) = channel();
let timeout = (timeout as f64) / 1000.0;
let device_selector = DeviceSelector::run();
let device_selector = DeviceSelector::run(status.clone());
let selector_sender = device_selector.clone_sender();
let builder = thread::Builder::new();
let thread = builder
Expand Down
Loading

0 comments on commit 26aa766

Please sign in to comment.