зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1398268 - [u2f-hid-rs] Rewrite macOS IOHIDManager communication and state machine r=jcj
Review: https://github.com/jcjones/u2f-hid-rs/pull/52
This commit is contained in:
Родитель
ec80cf873c
Коммит
b350f42d65
|
@ -1,6 +1,7 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
|
|
|
@ -27,6 +27,9 @@ pub mod platform;
|
|||
#[path = "stub/mod.rs"]
|
||||
pub mod platform;
|
||||
|
||||
#[cfg(not(any(target_os = "macos")))]
|
||||
mod khmatcher;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate rand;
|
||||
|
@ -35,7 +38,6 @@ extern crate boxfnonce;
|
|||
extern crate runloop;
|
||||
|
||||
mod consts;
|
||||
mod khmatcher;
|
||||
mod u2ftypes;
|
||||
mod u2fprotocol;
|
||||
|
||||
|
|
|
@ -2,70 +2,31 @@
|
|||
* 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 std::fmt;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::slice;
|
||||
use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError};
|
||||
use std::time::Duration;
|
||||
|
||||
use core_foundation_sys::base::*;
|
||||
use libc::c_void;
|
||||
|
||||
use consts::{CID_BROADCAST, HID_RPT_SIZE};
|
||||
use core_foundation_sys::base::*;
|
||||
use platform::iokit::*;
|
||||
use std::{fmt, io};
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::mpsc::{Receiver, RecvTimeoutError};
|
||||
use std::time::Duration;
|
||||
use u2ftypes::U2FDevice;
|
||||
|
||||
use super::iokit::*;
|
||||
|
||||
const READ_TIMEOUT: u64 = 15;
|
||||
|
||||
pub struct Device {
|
||||
device_ref: IOHIDDeviceRef,
|
||||
cid: [u8; 4],
|
||||
report_rx: Receiver<Vec<u8>>,
|
||||
report_send_void: *mut c_void,
|
||||
scratch_buf_ptr: *mut u8,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(device_ref: IOHIDDeviceRef) -> Self {
|
||||
let (report_tx, report_rx) = channel();
|
||||
let report_send_void = Box::into_raw(Box::new(report_tx)) as *mut c_void;
|
||||
|
||||
let scratch_buf = [0; HID_RPT_SIZE];
|
||||
let scratch_buf_ptr = Box::into_raw(Box::new(scratch_buf)) as *mut u8;
|
||||
|
||||
unsafe {
|
||||
IOHIDDeviceRegisterInputReportCallback(
|
||||
device_ref,
|
||||
scratch_buf_ptr,
|
||||
HID_RPT_SIZE as CFIndex,
|
||||
read_new_data_cb,
|
||||
report_send_void,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn new(device_ref: IOHIDDeviceRef, report_rx: Receiver<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
device_ref,
|
||||
cid: CID_BROADCAST,
|
||||
report_rx,
|
||||
report_send_void,
|
||||
scratch_buf_ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Device {
|
||||
fn drop(&mut self) {
|
||||
debug!("Dropping U2F device {}", self);
|
||||
|
||||
unsafe {
|
||||
// Re-allocate raw pointers for destruction.
|
||||
let _ = Box::from_raw(self.report_send_void as *mut Sender<Vec<u8>>);
|
||||
let _ = Box::from_raw(self.scratch_buf_ptr as *mut [u8; HID_RPT_SIZE]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,45 +109,3 @@ impl U2FDevice for Device {
|
|||
self.cid = cid;
|
||||
}
|
||||
}
|
||||
|
||||
// This is called from the RunLoop thread
|
||||
extern "C" fn read_new_data_cb(
|
||||
context: *mut c_void,
|
||||
_: IOReturn,
|
||||
_: *mut c_void,
|
||||
report_type: IOHIDReportType,
|
||||
report_id: u32,
|
||||
report: *mut u8,
|
||||
report_len: CFIndex,
|
||||
) {
|
||||
unsafe {
|
||||
let tx = &mut *(context as *mut Sender<Vec<u8>>);
|
||||
|
||||
trace!(
|
||||
"read_new_data_cb type={} id={} report={:?} len={}",
|
||||
report_type,
|
||||
report_id,
|
||||
report,
|
||||
report_len
|
||||
);
|
||||
|
||||
let report_len = report_len as usize;
|
||||
if report_len > HID_RPT_SIZE {
|
||||
warn!(
|
||||
"read_new_data_cb got too much data! {} > {}",
|
||||
report_len,
|
||||
HID_RPT_SIZE
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let data = slice::from_raw_parts(report, report_len).to_vec();
|
||||
|
||||
if let Err(e) = tx.send(data) {
|
||||
// TOOD: This happens when the channel closes before this thread
|
||||
// does. This is pretty common, but let's deal with stopping
|
||||
// properly later.
|
||||
warn!("Problem returning read_new_data_cb data for thread: {}", e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,14 @@
|
|||
extern crate log;
|
||||
extern crate libc;
|
||||
|
||||
use std::io;
|
||||
|
||||
use super::iokit::*;
|
||||
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
|
||||
use core_foundation_sys::base::*;
|
||||
use core_foundation_sys::dictionary::*;
|
||||
use core_foundation_sys::number::*;
|
||||
use core_foundation_sys::runloop::*;
|
||||
use core_foundation_sys::string::*;
|
||||
|
||||
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
|
||||
use util::io_err;
|
||||
use libc::c_void;
|
||||
use platform::iokit::{CFRunLoopObserverContext, CFRunLoopObserverCreate};
|
||||
|
||||
pub struct IOHIDDeviceMatcher {
|
||||
dict: CFDictionaryRef,
|
||||
|
@ -92,36 +89,48 @@ impl Drop for IOHIDDeviceMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct IOHIDManager {
|
||||
manager: IOHIDManagerRef,
|
||||
pub struct CFRunLoopEntryObserver {
|
||||
observer: CFRunLoopObserverRef,
|
||||
// Keep alive until the observer goes away.
|
||||
context_ptr: *mut CFRunLoopObserverContext,
|
||||
}
|
||||
|
||||
impl IOHIDManager {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
|
||||
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 rv = unsafe { IOHIDManagerOpen(manager, kIOHIDManagerOptionNone) };
|
||||
if rv != 0 {
|
||||
return Err(io_err("Couldn't open HID Manager"));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserverCreate(
|
||||
kCFAllocatorDefault,
|
||||
kCFRunLoopEntry,
|
||||
false as Boolean,
|
||||
0,
|
||||
callback,
|
||||
context_ptr,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Self { manager })
|
||||
Self {
|
||||
observer,
|
||||
context_ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> IOHIDManagerRef {
|
||||
self.manager
|
||||
pub fn add_to_current_runloop(&self) {
|
||||
unsafe {
|
||||
CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IOHIDManager {
|
||||
impl Drop for CFRunLoopEntryObserver {
|
||||
fn drop(&mut self) {
|
||||
let rv = unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
|
||||
if rv != 0 {
|
||||
warn!("Couldn't close the HID Manager");
|
||||
}
|
||||
unsafe {
|
||||
CFRelease(self.observer as *mut c_void);
|
||||
|
||||
// Drop the CFRunLoopObserverContext.
|
||||
let _ = Box::from_raw(self.context_ptr);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
extern crate core_foundation_sys;
|
||||
extern crate libc;
|
||||
|
||||
use libc::c_void;
|
||||
use core_foundation_sys::base::{CFIndex, CFAllocatorRef};
|
||||
use core_foundation_sys::base::{Boolean, CFIndex, CFAllocatorRef, CFOptionFlags};
|
||||
use core_foundation_sys::string::CFStringRef;
|
||||
use core_foundation_sys::runloop::CFRunLoopRef;
|
||||
use core_foundation_sys::runloop::{CFRunLoopRef, CFRunLoopObserverRef, CFRunLoopObserverCallBack};
|
||||
use core_foundation_sys::dictionary::CFDictionaryRef;
|
||||
use libc::c_void;
|
||||
use std::ops::Deref;
|
||||
|
||||
type IOOptionBits = u32;
|
||||
|
||||
|
@ -28,7 +29,7 @@ pub type IOHIDDeviceCallback = extern "C" fn(context: *mut c_void,
|
|||
pub type IOHIDReportType = IOOptionBits;
|
||||
pub type IOHIDReportCallback = extern "C" fn(context: *mut c_void,
|
||||
result: IOReturn,
|
||||
sender: *mut c_void,
|
||||
sender: IOHIDDeviceRef,
|
||||
report_type: IOHIDReportType,
|
||||
report_id: u32,
|
||||
report: *mut u8,
|
||||
|
@ -50,8 +51,51 @@ pub struct IOHIDDeviceRef(*const c_void);
|
|||
unsafe impl Send for IOHIDDeviceRef {}
|
||||
unsafe impl Sync for IOHIDDeviceRef {}
|
||||
|
||||
pub struct SendableRunLoop(pub CFRunLoopRef);
|
||||
|
||||
unsafe impl Send for SendableRunLoop {}
|
||||
|
||||
impl Deref for SendableRunLoop {
|
||||
type Target = CFRunLoopRef;
|
||||
|
||||
fn deref(&self) -> &CFRunLoopRef {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CFRunLoopObserverContext {
|
||||
pub version: CFIndex,
|
||||
pub info: *mut c_void,
|
||||
pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
|
||||
pub release: Option<extern "C" fn(info: *const c_void)>,
|
||||
pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
|
||||
}
|
||||
|
||||
impl CFRunLoopObserverContext {
|
||||
pub fn new(context: *mut c_void) -> Self {
|
||||
Self {
|
||||
version: 0 as CFIndex,
|
||||
info: context,
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
|
@ -68,6 +112,11 @@ extern "C" {
|
|||
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(
|
||||
|
@ -84,11 +133,4 @@ extern "C" {
|
|||
report: *const u8,
|
||||
reportLength: CFIndex,
|
||||
) -> IOReturn;
|
||||
pub fn IOHIDDeviceRegisterInputReportCallback(
|
||||
device: IOHIDDeviceRef,
|
||||
report: *const u8,
|
||||
reportLength: CFIndex,
|
||||
callback: IOHIDReportCallback,
|
||||
context: *mut c_void,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,30 +3,24 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate log;
|
||||
extern crate libc;
|
||||
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
mod device;
|
||||
mod devicemap;
|
||||
mod iokit;
|
||||
mod iohid;
|
||||
mod monitor;
|
||||
|
||||
use self::devicemap::DeviceMap;
|
||||
use self::monitor::Monitor;
|
||||
mod transaction;
|
||||
|
||||
use consts::PARAMETER_SIZE;
|
||||
use khmatcher::KeyHandleMatcher;
|
||||
use runloop::RunLoop;
|
||||
use platform::device::Device;
|
||||
use platform::transaction::Transaction;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use u2fprotocol::{u2f_init_device, u2f_register, u2f_sign, u2f_is_keyhandle_valid};
|
||||
use util::{io_err, OnceCallback};
|
||||
use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PlatformManager {
|
||||
// Handle to the thread loop.
|
||||
thread: Option<RunLoop>,
|
||||
transaction: Option<Transaction>,
|
||||
}
|
||||
|
||||
impl PlatformManager {
|
||||
|
@ -47,56 +41,43 @@ impl PlatformManager {
|
|||
|
||||
let cbc = callback.clone();
|
||||
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
let mut devices = DeviceMap::new();
|
||||
let monitor = try_or!(Monitor::new(), |e| callback.call(Err(e)));
|
||||
let mut matches = KeyHandleMatcher::new(&key_handles);
|
||||
// Start a new "sign" transaction.
|
||||
let transaction = Transaction::new(timeout, cbc.clone(), move |device_ref, rx, alive| {
|
||||
// Create a new device.
|
||||
let dev = &mut Device::new(device_ref, rx);
|
||||
|
||||
'top: while alive() && monitor.alive() {
|
||||
for event in monitor.events() {
|
||||
devices.process_event(event);
|
||||
}
|
||||
|
||||
// Query newly added devices.
|
||||
matches.update(devices.iter_mut(), |device, key_handle| {
|
||||
u2f_is_keyhandle_valid(device, &challenge, &application, key_handle)
|
||||
.unwrap_or(false /* no match on failure */)
|
||||
});
|
||||
|
||||
// Iterate all devices that don't match any of the handles
|
||||
// in the exclusion list and try to register.
|
||||
for (path, device) in devices.iter_mut() {
|
||||
if matches.get(path).is_empty() {
|
||||
if let Ok(bytes) = u2f_register(device, &challenge, &application) {
|
||||
callback.call(Ok(bytes));
|
||||
// Try initializing it.
|
||||
if !u2f_init_device(dev) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate the exlude list and see if there are any matches.
|
||||
// Abort the state machine if we found a valid key handle.
|
||||
if key_handles.iter().any(|key_handle| {
|
||||
u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
|
||||
.unwrap_or(false) /* no match on failure */
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if monitor.events has any hotplug events that we'll need
|
||||
// to handle
|
||||
if monitor.events().size_hint().0 > 0 {
|
||||
debug!("Hotplug event; restarting loop");
|
||||
continue 'top;
|
||||
}
|
||||
while alive() {
|
||||
if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
|
||||
callback.call(Ok(bytes));
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep a bit before trying again.
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
});
|
||||
|
||||
callback.call(Err(io_err("aborted or timed out")));
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
|
||||
self.thread = Some(try_or!(
|
||||
thread,
|
||||
|_| cbc.call(Err(io_err("couldn't create runloop")))
|
||||
));
|
||||
// Store the transaction so we can cancel it, if needed.
|
||||
self.transaction = Some(try_or!(transaction, |_| {
|
||||
cbc.call(Err(io_err("couldn't create transaction")))
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
pub fn sign(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
|
@ -110,85 +91,58 @@ impl PlatformManager {
|
|||
|
||||
let cbc = callback.clone();
|
||||
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
let mut devices = DeviceMap::new();
|
||||
let monitor = try_or!(Monitor::new(), |e| callback.call(Err(e)));
|
||||
let mut matches = KeyHandleMatcher::new(&key_handles);
|
||||
// Start a new "register" transaction.
|
||||
let transaction = Transaction::new(timeout, cbc.clone(), move |device_ref, rx, alive| {
|
||||
// Create a new device.
|
||||
let dev = &mut Device::new(device_ref, rx);
|
||||
|
||||
'top: while alive() && monitor.alive() {
|
||||
for event in monitor.events() {
|
||||
devices.process_event(event);
|
||||
// Try initializing it.
|
||||
if !u2f_init_device(dev) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Query newly added devices.
|
||||
matches.update(devices.iter_mut(), |device, key_handle| {
|
||||
u2f_is_keyhandle_valid(device, &challenge, &application, key_handle)
|
||||
.unwrap_or(false /* no match on failure */)
|
||||
});
|
||||
|
||||
// Iterate all devices.
|
||||
for (path, device) in devices.iter_mut() {
|
||||
let key_handles = matches.get(path);
|
||||
// Find all matching key handles.
|
||||
let key_handles = key_handles
|
||||
.iter()
|
||||
.filter(|key_handle| {
|
||||
u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
|
||||
.unwrap_or(false) /* no match on failure */
|
||||
})
|
||||
.collect::<Vec<&Vec<u8>>>();
|
||||
|
||||
while alive() {
|
||||
// If the device matches none of the given key handles
|
||||
// then just make it blink with bogus data.
|
||||
if key_handles.is_empty() {
|
||||
let blank = vec![0u8; PARAMETER_SIZE];
|
||||
if let Ok(_) = u2f_register(device, &blank, &blank) {
|
||||
if let Ok(_) = u2f_register(dev, &blank, &blank) {
|
||||
callback.call(Err(io_err("invalid key")));
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Otherwise, try to sign.
|
||||
for key_handle in key_handles {
|
||||
if let Ok(bytes) = u2f_sign(
|
||||
device,
|
||||
&challenge,
|
||||
&application,
|
||||
key_handle,
|
||||
)
|
||||
{
|
||||
for key_handle in &key_handles {
|
||||
if let Ok(bytes) = u2f_sign(dev, &challenge, &application, key_handle) {
|
||||
callback.call(Ok((key_handle.to_vec(), bytes)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if monitor.events has any hotplug events that we'll
|
||||
// need to handle
|
||||
if monitor.events().size_hint().0 > 0 {
|
||||
debug!("Hotplug event; restarting loop");
|
||||
continue 'top;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep a bit before trying again.
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
});
|
||||
|
||||
callback.call(Err(io_err("aborted or timed out")));
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
|
||||
self.thread = Some(try_or!(
|
||||
thread,
|
||||
|_| cbc.call(Err(io_err("couldn't create runloop")))
|
||||
));
|
||||
// Store the transaction so we can cancel it, if needed.
|
||||
self.transaction = Some(try_or!(transaction, |_| {
|
||||
cbc.call(Err(io_err("couldn't create transaction")))
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self) {
|
||||
if let Some(thread) = self.thread.take() {
|
||||
thread.cancel();
|
||||
if let Some(mut transaction) = self.transaction.take() {
|
||||
transaction.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PlatformManager {
|
||||
fn drop(&mut self) {
|
||||
debug!("OSX PlatformManager dropped");
|
||||
self.cancel();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,115 +2,175 @@
|
|||
* 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/. */
|
||||
|
||||
use std::io;
|
||||
use std::sync::mpsc::{channel, Sender, Receiver, TryIter};
|
||||
use std::thread;
|
||||
|
||||
use super::iohid::*;
|
||||
use super::iokit::*;
|
||||
use core_foundation_sys::runloop::*;
|
||||
use runloop::RunLoop;
|
||||
|
||||
extern crate log;
|
||||
extern crate libc;
|
||||
|
||||
use core_foundation_sys::base::*;
|
||||
use core_foundation_sys::runloop::*;
|
||||
use libc::c_void;
|
||||
use platform::iohid::*;
|
||||
use platform::iokit::*;
|
||||
use runloop::RunLoop;
|
||||
use std::{io, slice};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use util::io_err;
|
||||
|
||||
pub enum Event {
|
||||
Add(IOHIDDeviceRef),
|
||||
Remove(IOHIDDeviceRef),
|
||||
struct DeviceData {
|
||||
tx: Sender<Vec<u8>>,
|
||||
runloop: RunLoop,
|
||||
}
|
||||
|
||||
pub struct Monitor {
|
||||
// Receive events from the thread.
|
||||
rx: Receiver<Event>,
|
||||
// Handle to the thread loop.
|
||||
thread: RunLoop,
|
||||
pub struct Monitor<F>
|
||||
where
|
||||
F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync,
|
||||
{
|
||||
manager: IOHIDManagerRef,
|
||||
// Keep alive until the monitor goes away.
|
||||
_matcher: IOHIDDeviceMatcher,
|
||||
map: HashMap<IOHIDDeviceRef, DeviceData>,
|
||||
new_device_cb: F,
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let (tx, rx) = channel();
|
||||
impl<F> Monitor<F>
|
||||
where
|
||||
F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync + 'static,
|
||||
{
|
||||
pub fn new(new_device_cb: F) -> Self {
|
||||
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
|
||||
|
||||
let thread = RunLoop::new(move |alive| -> io::Result<()> {
|
||||
let tx_box = Box::new(tx);
|
||||
let tx_ptr = Box::into_raw(tx_box) as *mut libc::c_void;
|
||||
// Match FIDO devices only.
|
||||
let _matcher = IOHIDDeviceMatcher::new();
|
||||
unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.get()) };
|
||||
|
||||
// This will keep `tx` alive only for the scope.
|
||||
let _tx = unsafe { Box::from_raw(tx_ptr as *mut Sender<Event>) };
|
||||
Self {
|
||||
manager,
|
||||
_matcher,
|
||||
new_device_cb,
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Create and initialize a scoped HID manager.
|
||||
let manager = IOHIDManager::new()?;
|
||||
pub fn start(&mut self) -> io::Result<()> {
|
||||
let context = self as *mut Self as *mut c_void;
|
||||
|
||||
// Match only U2F devices.
|
||||
let dict = IOHIDDeviceMatcher::new();
|
||||
unsafe { IOHIDManagerSetDeviceMatching(manager.get(), dict.get()) };
|
||||
|
||||
// Register callbacks.
|
||||
unsafe {
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(
|
||||
manager.get(),
|
||||
Monitor::device_add_cb,
|
||||
tx_ptr,
|
||||
self.manager,
|
||||
Monitor::<F>::on_device_matching,
|
||||
context,
|
||||
);
|
||||
IOHIDManagerRegisterDeviceRemovalCallback(
|
||||
manager.get(),
|
||||
Monitor::device_remove_cb,
|
||||
tx_ptr,
|
||||
self.manager,
|
||||
Monitor::<F>::on_device_removal,
|
||||
context,
|
||||
);
|
||||
IOHIDManagerRegisterInputReportCallback(
|
||||
self.manager,
|
||||
Monitor::<F>::on_input_report,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
// Run the Event Loop. CFRunLoopRunInMode() will dispatch HID
|
||||
// input reports into the various callbacks
|
||||
while alive() {
|
||||
trace!("OSX Runloop running, handle={:?}", thread::current());
|
||||
|
||||
if unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, 0) } ==
|
||||
kCFRunLoopRunStopped
|
||||
{
|
||||
debug!("OSX Runloop device stopped.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!("OSX Runloop completed, handle={:?}", thread::current());
|
||||
IOHIDManagerScheduleWithRunLoop(
|
||||
self.manager,
|
||||
CFRunLoopGetCurrent(),
|
||||
kCFRunLoopDefaultMode,
|
||||
);
|
||||
|
||||
let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone);
|
||||
if rv == 0 {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(Self { rx, thread })
|
||||
} else {
|
||||
Err(io_err(&format!("Couldn't open HID Manager, rv={}", rv)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn events(&self) -> TryIter<Event> {
|
||||
self.rx.try_iter()
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn alive(&self) -> bool {
|
||||
self.thread.alive()
|
||||
// Close the manager and its devices.
|
||||
unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
|
||||
}
|
||||
|
||||
extern "C" fn device_add_cb(
|
||||
fn remove_device(&mut self, device_ref: &IOHIDDeviceRef) {
|
||||
if let Some(DeviceData { tx, runloop }) = self.map.remove(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 { ref 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: IOHIDDeviceRef,
|
||||
device_ref: IOHIDDeviceRef,
|
||||
) {
|
||||
let tx = unsafe { &*(context as *mut Sender<Event>) };
|
||||
let _ = tx.send(Event::Add(device));
|
||||
let this = unsafe { &mut *(context as *mut Self) };
|
||||
|
||||
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, alive);
|
||||
}
|
||||
});
|
||||
|
||||
if let Ok(runloop) = runloop {
|
||||
this.map.insert(device_ref, DeviceData { tx, runloop });
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn device_remove_cb(
|
||||
extern "C" fn on_device_removal(
|
||||
context: *mut c_void,
|
||||
_: IOReturn,
|
||||
_: *mut c_void,
|
||||
device: IOHIDDeviceRef,
|
||||
device_ref: IOHIDDeviceRef,
|
||||
) {
|
||||
let tx = unsafe { &*(context as *mut Sender<Event>) };
|
||||
let _ = tx.send(Event::Remove(device));
|
||||
let this = unsafe { &mut *(context as *mut Self) };
|
||||
this.remove_device(&device_ref);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Monitor {
|
||||
impl<F> Drop for Monitor<F>
|
||||
where
|
||||
F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
debug!("OSX Runloop dropped");
|
||||
self.thread.cancel();
|
||||
unsafe { CFRelease(self.manager as *mut libc::c_void) };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* 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 core_foundation_sys::runloop::*;
|
||||
use libc::c_void;
|
||||
use platform::iohid::CFRunLoopEntryObserver;
|
||||
use platform::iokit::{IOHIDDeviceRef, SendableRunLoop};
|
||||
use platform::monitor::Monitor;
|
||||
use std::io;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread;
|
||||
use util::{io_err, to_io_err, OnceCallback};
|
||||
|
||||
// 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: SendableRunLoop,
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn new<F, T>(timeout: u64, callback: OnceCallback<T>, new_device_cb: F) -> io::Result<Self>
|
||||
where
|
||||
F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync + Send + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let (tx, rx) = channel();
|
||||
let cbc = callback.clone();
|
||||
let timeout = (timeout as f64) / 1000.0;
|
||||
|
||||
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);
|
||||
try_or!(monitor.start(), |e| cbc.call(Err(e)));
|
||||
|
||||
// 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.
|
||||
cbc.call(Err(io_err("aborted or timed out")));
|
||||
})?;
|
||||
|
||||
// Block until we enter the CFRunLoop.
|
||||
let runloop = rx.recv().map_err(to_io_err)?;
|
||||
|
||||
Ok(Self {
|
||||
runloop,
|
||||
thread: Some(thread),
|
||||
})
|
||||
}
|
||||
|
||||
extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
|
||||
let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
|
||||
|
||||
// Send the current runloop to the receiver to unblock it.
|
||||
let _ = tx.send(SendableRunLoop(unsafe { CFRunLoopGetCurrent() }));
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self) {
|
||||
// (This call doesn't block.)
|
||||
unsafe { CFRunLoopStop(*self.runloop) };
|
||||
|
||||
// This must never be None. Ignore return value.
|
||||
let _ = self.thread.take().unwrap().join();
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче