Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 6 additions & 0 deletions examples/ctap2_discoverable_creds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ fn register_user(
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 @@ -355,6 +358,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/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/prf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,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/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 @@ -80,6 +80,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
2 changes: 2 additions & 0 deletions src/status_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ pub enum StatusUpdate {
/// After MakeCredential, supply the user with the large blob key and let
/// them calculate the payload, to send back to us.
LargeBlobData(Sender<LargeBlobArrayElement>, Vec<u8>),
/// 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 @@ -43,7 +45,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 @@ -75,6 +77,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 @@ -97,9 +102,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 @@ -113,6 +124,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 @@ -185,6 +199,7 @@ pub mod tests {
transport::FidoDevice,
u2ftypes::U2FDeviceInfo,
};
use std::sync::mpsc::TryRecvError;

pub(crate) fn gen_info(id: String) -> U2FDeviceInfo {
U2FDeviceInfo {
Expand Down Expand Up @@ -268,9 +283,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 @@ -279,6 +295,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 @@ -293,18 +310,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 @@ -324,7 +347,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 @@ -356,6 +380,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 @@ -376,7 +401,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 @@ -418,6 +444,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 @@ -438,7 +465,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 @@ -457,11 +485,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 @@ -472,4 +505,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
17 changes: 13 additions & 4 deletions src/transport/netbsd/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ where
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
// Loop until we're stopped by the controlling thread, or fail.
while alive() {
let mut added = Vec::new();
for n in 0..100 {
let uhidpath = OsString::from(format!("/dev/uhid{n}"));
match Fd::open(&uhidpath, libc::O_RDWR | libc::O_CLOEXEC) {
Ok(uhid) => {
// The device is available if it can be opened.
let _ = self
.selector_sender
.send(DeviceSelectorEvent::DevicesAdded(vec![uhidpath.clone()]));
self.add_device(WrappedOpenDevice {
added.push(WrappedOpenDevice {
fd: uhid,
os_path: uhidpath,
});
Expand All @@ -85,6 +83,17 @@ where
},
}
}

// We have to notify additions in batches to avoid
// arbitrarily selecting the first added device and
// to know when there are no devices present (then
// we send an empty vec here).
let _ = self.selector_sender.send(DeviceSelectorEvent::DevicesAdded(
added.iter().map(|e| e.os_path.clone()).collect(),
));
for device in added {
self.add_device(device);
}
thread::sleep(Duration::from_millis(POLL_TIMEOUT));
}

Expand Down
2 changes: 1 addition & 1 deletion src/transport/netbsd/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
Loading