1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] 6 7 extern crate libc; 8 9 use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; 10 use core_foundation::array::*; 11 use core_foundation::base::*; 12 use core_foundation::dictionary::*; 13 use core_foundation::number::*; 14 use core_foundation::runloop::*; 15 use core_foundation::string::*; 16 use std::ops::Deref; 17 use std::os::raw::c_void; 18 19 type IOOptionBits = u32; 20 21 pub type IOReturn = libc::c_int; 22 23 pub type IOHIDManagerRef = *mut __IOHIDManager; 24 pub type IOHIDManagerOptions = IOOptionBits; 25 26 pub type IOHIDDeviceCallback = extern "C" fn( 27 context: *mut c_void, 28 result: IOReturn, 29 sender: *mut c_void, 30 device: IOHIDDeviceRef, 31 ); 32 33 pub type IOHIDReportType = IOOptionBits; 34 pub type IOHIDReportCallback = extern "C" fn( 35 context: *mut c_void, 36 result: IOReturn, 37 sender: IOHIDDeviceRef, 38 report_type: IOHIDReportType, 39 report_id: u32, 40 report: *mut u8, 41 report_len: CFIndex, 42 ); 43 44 pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0; 45 46 pub const kIOHIDReportTypeOutput: IOHIDReportType = 1; 47 48 #[repr(C)] 49 pub struct __IOHIDManager { 50 __private: c_void, 51 } 52 53 #[repr(C)] 54 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 55 pub struct IOHIDDeviceRef(*const c_void); 56 57 unsafe impl Send for IOHIDDeviceRef {} 58 unsafe impl Sync for IOHIDDeviceRef {} 59 60 pub struct SendableRunLoop(CFRunLoopRef); 61 62 impl SendableRunLoop { new(runloop: CFRunLoopRef) -> Self63 pub fn new(runloop: CFRunLoopRef) -> Self { 64 // Keep the CFRunLoop alive for as long as we are. 65 unsafe { CFRetain(runloop as *mut c_void) }; 66 67 SendableRunLoop(runloop) 68 } 69 } 70 71 unsafe impl Send for SendableRunLoop {} 72 73 impl Deref for SendableRunLoop { 74 type Target = CFRunLoopRef; 75 deref(&self) -> &CFRunLoopRef76 fn deref(&self) -> &CFRunLoopRef { 77 &self.0 78 } 79 } 80 81 impl Drop for SendableRunLoop { drop(&mut self)82 fn drop(&mut self) { 83 unsafe { CFRelease(self.0 as *mut c_void) }; 84 } 85 } 86 87 #[repr(C)] 88 pub struct CFRunLoopObserverContext { 89 pub version: CFIndex, 90 pub info: *mut c_void, 91 pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>, 92 pub release: Option<extern "C" fn(info: *const c_void)>, 93 pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>, 94 } 95 96 impl CFRunLoopObserverContext { new(context: *mut c_void) -> Self97 pub fn new(context: *mut c_void) -> Self { 98 Self { 99 version: 0 as CFIndex, 100 info: context, 101 retain: None, 102 release: None, 103 copyDescription: None, 104 } 105 } 106 } 107 108 pub struct CFRunLoopEntryObserver { 109 observer: CFRunLoopObserverRef, 110 // Keep alive until the observer goes away. 111 context_ptr: *mut CFRunLoopObserverContext, 112 } 113 114 impl CFRunLoopEntryObserver { new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self115 pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self { 116 let context = CFRunLoopObserverContext::new(context); 117 let context_ptr = Box::into_raw(Box::new(context)); 118 119 let observer = unsafe { 120 CFRunLoopObserverCreate( 121 kCFAllocatorDefault, 122 kCFRunLoopEntry, 123 false as Boolean, 124 0, 125 callback, 126 context_ptr, 127 ) 128 }; 129 130 Self { 131 observer, 132 context_ptr, 133 } 134 } 135 add_to_current_runloop(&self)136 pub fn add_to_current_runloop(&self) { 137 unsafe { 138 CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode) 139 }; 140 } 141 } 142 143 impl Drop for CFRunLoopEntryObserver { drop(&mut self)144 fn drop(&mut self) { 145 unsafe { 146 CFRelease(self.observer as *mut c_void); 147 148 // Drop the CFRunLoopObserverContext. 149 let _ = Box::from_raw(self.context_ptr); 150 }; 151 } 152 } 153 154 pub struct IOHIDDeviceMatcher { 155 pub dict: CFDictionary<CFString, CFNumber>, 156 } 157 158 impl IOHIDDeviceMatcher { new() -> Self159 pub fn new() -> Self { 160 let dict = CFDictionary::<CFString, CFNumber>::from_CFType_pairs(&[ 161 ( 162 CFString::from_static_string("DeviceUsage"), 163 CFNumber::from(i32::from(FIDO_USAGE_U2FHID)), 164 ), 165 ( 166 CFString::from_static_string("DeviceUsagePage"), 167 CFNumber::from(i32::from(FIDO_USAGE_PAGE)), 168 ), 169 ]); 170 Self { dict } 171 } 172 } 173 174 #[link(name = "IOKit", kind = "framework")] 175 extern "C" { 176 // CFRunLoop CFRunLoopObserverCreate( allocator: CFAllocatorRef, activities: CFOptionFlags, repeats: Boolean, order: CFIndex, callout: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) -> CFRunLoopObserverRef177 pub fn CFRunLoopObserverCreate( 178 allocator: CFAllocatorRef, 179 activities: CFOptionFlags, 180 repeats: Boolean, 181 order: CFIndex, 182 callout: CFRunLoopObserverCallBack, 183 context: *mut CFRunLoopObserverContext, 184 ) -> CFRunLoopObserverRef; 185 186 // IOHIDManager IOHIDManagerCreate( allocator: CFAllocatorRef, options: IOHIDManagerOptions, ) -> IOHIDManagerRef187 pub fn IOHIDManagerCreate( 188 allocator: CFAllocatorRef, 189 options: IOHIDManagerOptions, 190 ) -> IOHIDManagerRef; IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef)191 pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef); IOHIDManagerRegisterDeviceMatchingCallback( manager: IOHIDManagerRef, callback: IOHIDDeviceCallback, context: *mut c_void, )192 pub fn IOHIDManagerRegisterDeviceMatchingCallback( 193 manager: IOHIDManagerRef, 194 callback: IOHIDDeviceCallback, 195 context: *mut c_void, 196 ); IOHIDManagerRegisterDeviceRemovalCallback( manager: IOHIDManagerRef, callback: IOHIDDeviceCallback, context: *mut c_void, )197 pub fn IOHIDManagerRegisterDeviceRemovalCallback( 198 manager: IOHIDManagerRef, 199 callback: IOHIDDeviceCallback, 200 context: *mut c_void, 201 ); IOHIDManagerRegisterInputReportCallback( manager: IOHIDManagerRef, callback: IOHIDReportCallback, context: *mut c_void, )202 pub fn IOHIDManagerRegisterInputReportCallback( 203 manager: IOHIDManagerRef, 204 callback: IOHIDReportCallback, 205 context: *mut c_void, 206 ); IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn207 pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn208 pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn; IOHIDManagerScheduleWithRunLoop( manager: IOHIDManagerRef, runLoop: CFRunLoopRef, runLoopMode: CFStringRef, )209 pub fn IOHIDManagerScheduleWithRunLoop( 210 manager: IOHIDManagerRef, 211 runLoop: CFRunLoopRef, 212 runLoopMode: CFStringRef, 213 ); 214 215 // IOHIDDevice IOHIDDeviceSetReport( device: IOHIDDeviceRef, reportType: IOHIDReportType, reportID: CFIndex, report: *const u8, reportLength: CFIndex, ) -> IOReturn216 pub fn IOHIDDeviceSetReport( 217 device: IOHIDDeviceRef, 218 reportType: IOHIDReportType, 219 reportID: CFIndex, 220 report: *const u8, 221 reportLength: CFIndex, 222 ) -> IOReturn; IOHIDDeviceGetProperty(device: IOHIDDeviceRef, key: CFStringRef) -> CFTypeRef223 pub fn IOHIDDeviceGetProperty(device: IOHIDDeviceRef, key: CFStringRef) -> CFTypeRef; 224 } 225 226 //////////////////////////////////////////////////////////////////////// 227 // Tests 228 //////////////////////////////////////////////////////////////////////// 229 230 #[cfg(test)] 231 mod tests { 232 use super::*; 233 use std::os::raw::c_void; 234 use std::ptr; 235 use std::sync::mpsc::{channel, Sender}; 236 use std::thread; 237 observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void)238 extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) { 239 let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) }; 240 241 // Send the current runloop to the receiver to unblock it. 242 let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() })); 243 } 244 245 #[test] test_sendable_runloop()246 fn test_sendable_runloop() { 247 let (tx, rx) = channel(); 248 249 let thread = thread::spawn(move || { 250 // Send the runloop to the owning thread. 251 let context = &tx as *const _ as *mut c_void; 252 let obs = CFRunLoopEntryObserver::new(observe, context); 253 obs.add_to_current_runloop(); 254 255 unsafe { 256 // We need some source for the runloop to run. 257 let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0); 258 assert!(!manager.is_null()); 259 260 IOHIDManagerScheduleWithRunLoop( 261 manager, 262 CFRunLoopGetCurrent(), 263 kCFRunLoopDefaultMode, 264 ); 265 IOHIDManagerSetDeviceMatching(manager, ptr::null_mut()); 266 267 let rv = IOHIDManagerOpen(manager, 0); 268 assert_eq!(rv, 0); 269 270 // This will run until `CFRunLoopStop()` is called. 271 CFRunLoopRun(); 272 273 let rv = IOHIDManagerClose(manager, 0); 274 assert_eq!(rv, 0); 275 276 CFRelease(manager as *mut c_void); 277 } 278 }); 279 280 // Block until we enter the CFRunLoop. 281 let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop"); 282 283 // Stop the runloop. 284 unsafe { CFRunLoopStop(*runloop) }; 285 286 // Stop the thread. 287 thread.join().expect("failed to join the thread"); 288 289 // Try to stop the runloop again (without crashing). 290 unsafe { CFRunLoopStop(*runloop) }; 291 } 292 } 293