summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src/transport/macos/monitor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src/transport/macos/monitor.rs')
-rw-r--r--third_party/rust/authenticator/src/transport/macos/monitor.rs212
1 files changed, 212 insertions, 0 deletions
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<Vec<u8>>,
+ runloop: RunLoop,
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(
+ (IOHIDDeviceRef, Receiver<Vec<u8>>),
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ manager: IOHIDManagerRef,
+ // Keep alive until the monitor goes away.
+ _matcher: IOHIDDeviceMatcher,
+ map: HashMap<IOHIDDeviceRef, DeviceData>,
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(
+ (IOHIDDeviceRef, Receiver<Vec<u8>>),
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> 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::<F>::on_device_matching,
+ context,
+ );
+ IOHIDManagerRegisterDeviceRemovalCallback(
+ self.manager,
+ Monitor::<F>::on_device_removal,
+ context,
+ );
+ IOHIDManagerRegisterInputReportCallback(
+ self.manager,
+ Monitor::<F>::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<F> Drop for Monitor<F>
+where
+ F: Fn(
+ (IOHIDDeviceRef, Receiver<Vec<u8>>),
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ fn drop(&mut self) {
+ unsafe { CFRelease(self.manager as *mut c_void) };
+ }
+}