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