From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../authenticator/src/transport/macos/device.rs | 226 ++++++++++++++++ .../authenticator/src/transport/macos/iokit.rs | 292 +++++++++++++++++++++ .../rust/authenticator/src/transport/macos/mod.rs | 9 + .../authenticator/src/transport/macos/monitor.rs | 212 +++++++++++++++ .../src/transport/macos/transaction.rs | 107 ++++++++ 5 files changed, 846 insertions(+) create mode 100644 third_party/rust/authenticator/src/transport/macos/device.rs create mode 100644 third_party/rust/authenticator/src/transport/macos/iokit.rs create mode 100644 third_party/rust/authenticator/src/transport/macos/mod.rs create mode 100644 third_party/rust/authenticator/src/transport/macos/monitor.rs create mode 100644 third_party/rust/authenticator/src/transport/macos/transaction.rs (limited to 'third_party/rust/authenticator/src/transport/macos') diff --git a/third_party/rust/authenticator/src/transport/macos/device.rs b/third_party/rust/authenticator/src/transport/macos/device.rs new file mode 100644 index 0000000000..9acce3aa29 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/macos/device.rs @@ -0,0 +1,226 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate log; + +use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE}; +use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::transport::hid::HIDDevice; +use crate::transport::platform::iokit::*; +use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret}; +use crate::u2ftypes::U2FDeviceInfo; +use core_foundation::base::*; +use core_foundation::string::*; +use std::convert::TryInto; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::io; +use std::io::{Read, Write}; +use std::sync::mpsc::{Receiver, RecvTimeoutError}; +use std::time::Duration; + +const READ_TIMEOUT: u64 = 15; + +pub struct Device { + device_ref: IOHIDDeviceRef, + cid: [u8; 4], + report_rx: Option>>, + dev_info: Option, + secret: Option, + authenticator_info: Option, + protocol: FidoProtocol, +} + +impl Device { + unsafe fn get_property_macos(&self, prop_name: &str) -> io::Result { + let prop_ref = IOHIDDeviceGetProperty( + self.device_ref, + CFString::new(prop_name).as_concrete_TypeRef(), + ); + if prop_ref.is_null() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("IOHIDDeviceGetProperty received nullptr for property {prop_name}"), + )); + } + + if CFGetTypeID(prop_ref) != CFStringGetTypeID() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("IOHIDDeviceGetProperty returned non-string type for property {prop_name}"), + )); + } + + Ok(CFString::from_void(prop_ref).to_string()) + } +} + +impl fmt::Debug for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Device").field("cid", &self.cid).finish() + } +} + +impl PartialEq for Device { + fn eq(&self, other_device: &Device) -> bool { + self.device_ref == other_device.device_ref + } +} + +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + // The path should be the only identifying member for a device + // If the path is the same, its the same device + self.device_ref.hash(state); + } +} + +impl Read for Device { + fn read(&mut self, mut bytes: &mut [u8]) -> io::Result { + if let Some(rx) = &self.report_rx { + let timeout = Duration::from_secs(READ_TIMEOUT); + let data = match rx.recv_timeout(timeout) { + Ok(v) => v, + Err(e) if e == RecvTimeoutError::Timeout => { + return Err(io::Error::new(io::ErrorKind::TimedOut, e)); + } + Err(e) => { + return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e)); + } + }; + bytes.write(&data) + } else { + Err(io::Error::from(io::ErrorKind::Unsupported)) + } + } +} + +impl Write for Device { + fn write(&mut self, bytes: &[u8]) -> io::Result { + assert_eq!(bytes.len(), self.out_rpt_size() + 1); + + let report_id = i64::from(bytes[0]); + // Skip report number when not using numbered reports. + let start = if report_id == 0x0 { 1 } else { 0 }; + let data = &bytes[start..]; + + let result = unsafe { + IOHIDDeviceSetReport( + self.device_ref, + kIOHIDReportTypeOutput, + report_id.try_into().unwrap(), + data.as_ptr(), + data.len() as CFIndex, + ) + }; + if result != 0 { + warn!("set_report sending failure = {0:X}", result); + return Err(io::Error::from_raw_os_error(result)); + } + trace!("set_report sending success = {0:X}", result); + + Ok(bytes.len()) + } + + // USB HID writes don't buffer, so this will be a nop. + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl HIDDevice for Device { + type BuildParameters = (IOHIDDeviceRef, Receiver>); + type Id = IOHIDDeviceRef; + + fn new(dev_ids: Self::BuildParameters) -> Result { + let (device_ref, report_rx) = dev_ids; + Ok(Self { + device_ref, + cid: CID_BROADCAST, + report_rx: Some(report_rx), + dev_info: None, + secret: None, + authenticator_info: None, + protocol: FidoProtocol::CTAP2, + }) + } + + fn id(&self) -> Self::Id { + self.device_ref + } + + fn get_cid(&self) -> &[u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, prop_name: &str) -> io::Result { + unsafe { self.get_property_macos(prop_name) } + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} + +impl FidoDevice for Device { + fn pre_init(&mut self) -> Result<(), HIDError> { + HIDDevice::pre_init(self) + } + + fn should_try_ctap2(&self) -> bool { + HIDDevice::get_device_info(self) + .cap_flags + .contains(Capability::CBOR) + } + + fn initialized(&self) -> bool { + self.cid != CID_BROADCAST + } + fn is_u2f(&mut self) -> bool { + true + } + fn get_shared_secret(&self) -> Option<&SharedSecret> { + self.secret.as_ref() + } + + fn set_shared_secret(&mut self, secret: SharedSecret) { + self.secret = Some(secret); + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + self.authenticator_info.as_ref() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + self.authenticator_info = Some(authenticator_info); + } + + fn get_protocol(&self) -> FidoProtocol { + self.protocol + } + + fn downgrade_to_ctap1(&mut self) { + self.protocol = FidoProtocol::CTAP1; + } +} diff --git a/third_party/rust/authenticator/src/transport/macos/iokit.rs b/third_party/rust/authenticator/src/transport/macos/iokit.rs new file mode 100644 index 0000000000..656cdb045d --- /dev/null +++ b/third_party/rust/authenticator/src/transport/macos/iokit.rs @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] + +extern crate libc; + +use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; +use core_foundation::array::*; +use core_foundation::base::*; +use core_foundation::dictionary::*; +use core_foundation::number::*; +use core_foundation::runloop::*; +use core_foundation::string::*; +use std::ops::Deref; +use std::os::raw::c_void; + +type IOOptionBits = u32; + +pub type IOReturn = libc::c_int; + +pub type IOHIDManagerRef = *mut __IOHIDManager; +pub type IOHIDManagerOptions = IOOptionBits; + +pub type IOHIDDeviceCallback = extern "C" fn( + context: *mut c_void, + result: IOReturn, + sender: *mut c_void, + device: IOHIDDeviceRef, +); + +pub type IOHIDReportType = IOOptionBits; +pub type IOHIDReportCallback = extern "C" fn( + context: *mut c_void, + result: IOReturn, + sender: IOHIDDeviceRef, + report_type: IOHIDReportType, + report_id: u32, + report: *mut u8, + report_len: CFIndex, +); + +pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0; + +pub const kIOHIDReportTypeOutput: IOHIDReportType = 1; + +#[repr(C)] +pub struct __IOHIDManager { + __private: c_void, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct IOHIDDeviceRef(*const c_void); + +unsafe impl Send for IOHIDDeviceRef {} +unsafe impl Sync for IOHIDDeviceRef {} + +pub struct SendableRunLoop(CFRunLoopRef); + +impl SendableRunLoop { + pub fn new(runloop: CFRunLoopRef) -> Self { + // Keep the CFRunLoop alive for as long as we are. + unsafe { CFRetain(runloop as *mut c_void) }; + + SendableRunLoop(runloop) + } +} + +unsafe impl Send for SendableRunLoop {} + +impl Deref for SendableRunLoop { + type Target = CFRunLoopRef; + + fn deref(&self) -> &CFRunLoopRef { + &self.0 + } +} + +impl Drop for SendableRunLoop { + fn drop(&mut self) { + unsafe { CFRelease(self.0 as *mut c_void) }; + } +} + +#[repr(C)] +pub struct CFRunLoopObserverContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, +} + +impl CFRunLoopObserverContext { + pub fn new(context: *mut c_void) -> Self { + Self { + version: 0 as CFIndex, + info: context, + retain: None, + release: None, + copyDescription: None, + } + } +} + +pub struct CFRunLoopEntryObserver { + observer: CFRunLoopObserverRef, + // Keep alive until the observer goes away. + context_ptr: *mut CFRunLoopObserverContext, +} + +impl CFRunLoopEntryObserver { + pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self { + let context = CFRunLoopObserverContext::new(context); + let context_ptr = Box::into_raw(Box::new(context)); + + let observer = unsafe { + CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopEntry, + false as Boolean, + 0, + callback, + context_ptr, + ) + }; + + Self { + observer, + context_ptr, + } + } + + pub fn add_to_current_runloop(&self) { + unsafe { + CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode) + }; + } +} + +impl Drop for CFRunLoopEntryObserver { + fn drop(&mut self) { + unsafe { + CFRelease(self.observer as *mut c_void); + + // Drop the CFRunLoopObserverContext. + let _ = Box::from_raw(self.context_ptr); + }; + } +} + +pub struct IOHIDDeviceMatcher { + pub dict: CFDictionary, +} + +impl IOHIDDeviceMatcher { + pub fn new() -> Self { + let dict = CFDictionary::::from_CFType_pairs(&[ + ( + CFString::from_static_string("DeviceUsage"), + CFNumber::from(i32::from(FIDO_USAGE_U2FHID)), + ), + ( + CFString::from_static_string("DeviceUsagePage"), + CFNumber::from(i32::from(FIDO_USAGE_PAGE)), + ), + ]); + Self { dict } + } +} + +#[link(name = "IOKit", kind = "framework")] +extern "C" { + // CFRunLoop + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + + // IOHIDManager + pub fn IOHIDManagerCreate( + allocator: CFAllocatorRef, + options: IOHIDManagerOptions, + ) -> IOHIDManagerRef; + pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef); + pub fn IOHIDManagerRegisterDeviceMatchingCallback( + manager: IOHIDManagerRef, + callback: IOHIDDeviceCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerRegisterDeviceRemovalCallback( + manager: IOHIDManagerRef, + callback: IOHIDDeviceCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerRegisterInputReportCallback( + manager: IOHIDManagerRef, + callback: IOHIDReportCallback, + context: *mut c_void, + ); + pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; + pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; + pub fn IOHIDManagerScheduleWithRunLoop( + manager: IOHIDManagerRef, + runLoop: CFRunLoopRef, + runLoopMode: CFStringRef, + ); + + // IOHIDDevice + pub fn IOHIDDeviceSetReport( + device: IOHIDDeviceRef, + reportType: IOHIDReportType, + reportID: CFIndex, + report: *const u8, + reportLength: CFIndex, + ) -> IOReturn; + pub fn IOHIDDeviceGetProperty(device: IOHIDDeviceRef, key: CFStringRef) -> CFTypeRef; +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use std::os::raw::c_void; + use std::ptr; + use std::sync::mpsc::{channel, Sender}; + use std::thread; + + extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) { + let tx: &Sender = unsafe { &*(context as *mut _) }; + + // Send the current runloop to the receiver to unblock it. + let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() })); + } + + #[test] + fn test_sendable_runloop() { + let (tx, rx) = channel(); + + let thread = thread::spawn(move || { + // Send the runloop to the owning thread. + let context = &tx as *const _ as *mut c_void; + let obs = CFRunLoopEntryObserver::new(observe, context); + obs.add_to_current_runloop(); + + unsafe { + // We need some source for the runloop to run. + let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0); + assert!(!manager.is_null()); + + IOHIDManagerScheduleWithRunLoop( + manager, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode, + ); + IOHIDManagerSetDeviceMatching(manager, ptr::null_mut()); + + let rv = IOHIDManagerOpen(manager, 0); + assert_eq!(rv, 0); + + // This will run until `CFRunLoopStop()` is called. + CFRunLoopRun(); + + let rv = IOHIDManagerClose(manager, 0); + assert_eq!(rv, 0); + + CFRelease(manager as *mut c_void); + } + }); + + // Block until we enter the CFRunLoop. + let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop"); + + // Stop the runloop. + unsafe { CFRunLoopStop(*runloop) }; + + // Stop the thread. + thread.join().expect("failed to join the thread"); + + // Try to stop the runloop again (without crashing). + unsafe { CFRunLoopStop(*runloop) }; + } +} diff --git a/third_party/rust/authenticator/src/transport/macos/mod.rs b/third_party/rust/authenticator/src/transport/macos/mod.rs new file mode 100644 index 0000000000..44e85094d0 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/macos/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod iokit; +mod monitor; diff --git a/third_party/rust/authenticator/src/transport/macos/monitor.rs b/third_party/rust/authenticator/src/transport/macos/monitor.rs new file mode 100644 index 0000000000..32200ee7a4 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/macos/monitor.rs @@ -0,0 +1,212 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; +extern crate log; + +use crate::transport::device_selector::DeviceSelectorEvent; +use crate::transport::platform::iokit::*; +use crate::util::io_err; +use core_foundation::base::*; +use core_foundation::runloop::*; +use runloop::RunLoop; +use std::collections::HashMap; +use std::os::raw::c_void; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::{io, slice}; + +struct DeviceData { + tx: Sender>, + runloop: RunLoop, +} + +pub struct Monitor +where + F: Fn( + (IOHIDDeviceRef, Receiver>), + Sender, + Sender, + &dyn Fn() -> bool, + ) + Send + + Sync + + 'static, +{ + manager: IOHIDManagerRef, + // Keep alive until the monitor goes away. + _matcher: IOHIDDeviceMatcher, + map: HashMap, + new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, +} + +impl Monitor +where + F: Fn( + (IOHIDDeviceRef, Receiver>), + Sender, + Sender, + &dyn Fn() -> bool, + ) + Send + + Sync + + 'static, +{ + pub fn new( + new_device_cb: F, + selector_sender: Sender, + status_sender: Sender, + ) -> Self { + let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) }; + + // Match FIDO devices only. + let _matcher = IOHIDDeviceMatcher::new(); + unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.dict.as_concrete_TypeRef()) }; + + Self { + manager, + _matcher, + new_device_cb, + map: HashMap::new(), + selector_sender, + status_sender, + } + } + + pub fn start(&mut self) -> io::Result<()> { + let context = self as *mut Self as *mut c_void; + + unsafe { + IOHIDManagerRegisterDeviceMatchingCallback( + self.manager, + Monitor::::on_device_matching, + context, + ); + IOHIDManagerRegisterDeviceRemovalCallback( + self.manager, + Monitor::::on_device_removal, + context, + ); + IOHIDManagerRegisterInputReportCallback( + self.manager, + Monitor::::on_input_report, + context, + ); + + IOHIDManagerScheduleWithRunLoop( + self.manager, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode, + ); + + let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone); + if rv == 0 { + Ok(()) + } else { + Err(io_err(&format!("Couldn't open HID Manager, rv={rv}"))) + } + } + } + + pub fn stop(&mut self) { + // Remove all devices. + while !self.map.is_empty() { + let device_ref = *self.map.keys().next().unwrap(); + self.remove_device(device_ref); + } + + // Close the manager and its devices. + unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) }; + } + + fn remove_device(&mut self, device_ref: IOHIDDeviceRef) { + if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DeviceRemoved(device_ref)); + // Dropping `tx` will make Device::read() fail eventually. + drop(tx); + + // Wait until the runloop stopped. + runloop.cancel(); + } + } + + extern "C" fn on_input_report( + context: *mut c_void, + _: IOReturn, + device_ref: IOHIDDeviceRef, + _: IOHIDReportType, + _: u32, + report: *mut u8, + report_len: CFIndex, + ) { + let this = unsafe { &mut *(context as *mut Self) }; + let mut send_failed = false; + + // Ignore the report if we can't find a device for it. + if let Some(DeviceData { tx, .. }) = this.map.get(&device_ref) { + let data = unsafe { slice::from_raw_parts(report, report_len as usize).to_vec() }; + send_failed = tx.send(data).is_err(); + } + + // Remove the device if sending fails. + if send_failed { + this.remove_device(device_ref); + } + } + + extern "C" fn on_device_matching( + context: *mut c_void, + _: IOReturn, + _: *mut c_void, + device_ref: IOHIDDeviceRef, + ) { + let this = unsafe { &mut *(context as *mut Self) }; + let _ = this + .selector_sender + .send(DeviceSelectorEvent::DevicesAdded(vec![device_ref])); + let selector_sender = this.selector_sender.clone(); + let status_sender = this.status_sender.clone(); + let (tx, rx) = channel(); + let f = &this.new_device_cb; + + // Create a new per-device runloop. + let runloop = RunLoop::new(move |alive| { + // Ensure that the runloop is still alive. + if alive() { + f((device_ref, rx), selector_sender, status_sender, alive); + } + }); + + if let Ok(runloop) = runloop { + this.map.insert(device_ref, DeviceData { tx, runloop }); + } + } + + extern "C" fn on_device_removal( + context: *mut c_void, + _: IOReturn, + _: *mut c_void, + device_ref: IOHIDDeviceRef, + ) { + let this = unsafe { &mut *(context as *mut Self) }; + this.remove_device(device_ref); + } +} + +impl Drop for Monitor +where + F: Fn( + (IOHIDDeviceRef, Receiver>), + Sender, + Sender, + &dyn Fn() -> bool, + ) + Send + + Sync + + 'static, +{ + fn drop(&mut self) { + unsafe { CFRelease(self.manager as *mut c_void) }; + } +} diff --git a/third_party/rust/authenticator/src/transport/macos/transaction.rs b/third_party/rust/authenticator/src/transport/macos/transaction.rs new file mode 100644 index 0000000000..d9709e7364 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/macos/transaction.rs @@ -0,0 +1,107 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate libc; + +use crate::errors; +use crate::statecallback::StateCallback; +use crate::transport::device_selector::{ + DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent, +}; +use crate::transport::platform::iokit::{CFRunLoopEntryObserver, SendableRunLoop}; +use crate::transport::platform::monitor::Monitor; +use core_foundation::runloop::*; +use std::os::raw::c_void; +use std::sync::mpsc::{channel, Sender}; +use std::thread; + +// A transaction will run the given closure in a new thread, thereby using a +// separate per-thread state machine for each HID. It will either complete or +// fail through user action, timeout, or be cancelled when overridden by a new +// transaction. +pub struct Transaction { + runloop: Option, + thread: Option>, + device_selector: DeviceSelector, +} + +impl Transaction { + pub fn new( + timeout: u64, + callback: StateCallback>, + status: Sender, + new_device_cb: F, + ) -> crate::Result + where + F: Fn( + DeviceBuildParameters, + Sender, + Sender, + &dyn Fn() -> bool, + ) + Sync + + Send + + 'static, + T: 'static, + { + let (tx, rx) = channel(); + let timeout = (timeout as f64) / 1000.0; + let device_selector = DeviceSelector::run(); + let selector_sender = device_selector.clone_sender(); + let builder = thread::Builder::new(); + let thread = builder + .spawn(move || { + // Add a runloop observer that will be notified when we enter the + // runloop and tx.send() the current runloop to the owning thread. + // We need to ensure the runloop was entered before unblocking + // Transaction::new(), so we can always properly cancel. + let context = &tx as *const _ as *mut c_void; + let obs = CFRunLoopEntryObserver::new(Transaction::observe, context); + obs.add_to_current_runloop(); + + // Create a new HID device monitor and start polling. + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); + try_or!(monitor.start(), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // This will block until completion, abortion, or timeout. + unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) }; + + // Close the monitor and its devices. + monitor.stop(); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + // Block until we enter the CFRunLoop. + let runloop = rx + .recv() + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + runloop: Some(runloop), + thread: Some(thread), + device_selector, + }) + } + + extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) { + let tx: &Sender = unsafe { &*(context as *mut _) }; + + // Send the current runloop to the receiver to unblock it. + let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() })); + } + + pub fn cancel(&mut self) { + // This must never be None. This won't block. + unsafe { CFRunLoopStop(*self.runloop.take().unwrap()) }; + + self.device_selector.stop(); + // This must never be None. Ignore return value. + let _ = self.thread.take().unwrap().join(); + } +} -- cgit v1.2.3