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 core_foundation_sys;
8 extern crate libc;
9 
10 use consts::{FIDO_USAGE_U2FHID, FIDO_USAGE_PAGE};
11 use core_foundation_sys::base::*;
12 use core_foundation_sys::dictionary::*;
13 use core_foundation_sys::number::*;
14 use core_foundation_sys::runloop::*;
15 use core_foundation_sys::string::*;
16 use libc::c_void;
17 use std::ops::Deref;
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     dict: CFDictionaryRef,
156     keys: Vec<CFStringRef>,
157     values: Vec<CFNumberRef>,
158 }
159 
160 impl IOHIDDeviceMatcher {
new() -> Self161     pub fn new() -> Self {
162         let keys = vec![
163             IOHIDDeviceMatcher::cf_string("DeviceUsage"),
164             IOHIDDeviceMatcher::cf_string("DeviceUsagePage"),
165         ];
166 
167         let values = vec![
168             IOHIDDeviceMatcher::cf_number(FIDO_USAGE_U2FHID as i32),
169             IOHIDDeviceMatcher::cf_number(FIDO_USAGE_PAGE as i32),
170         ];
171 
172         let dict = unsafe {
173             CFDictionaryCreate(
174                 kCFAllocatorDefault,
175                 keys.as_ptr() as *const *const libc::c_void,
176                 values.as_ptr() as *const *const libc::c_void,
177                 keys.len() as CFIndex,
178                 &kCFTypeDictionaryKeyCallBacks,
179                 &kCFTypeDictionaryValueCallBacks,
180             )
181         };
182 
183         Self { dict, keys, values }
184     }
185 
cf_number(number: i32) -> CFNumberRef186     fn cf_number(number: i32) -> CFNumberRef {
187         let nbox = Box::new(number);
188         let nptr = Box::into_raw(nbox) as *mut libc::c_void;
189 
190         unsafe {
191             // Drop when out of scope.
192             let _num = Box::from_raw(nptr as *mut i32);
193             CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, nptr)
194         }
195     }
196 
cf_string(string: &str) -> CFStringRef197     fn cf_string(string: &str) -> CFStringRef {
198         unsafe {
199             CFStringCreateWithBytes(
200                 kCFAllocatorDefault,
201                 string.as_ptr(),
202                 string.len() as CFIndex,
203                 kCFStringEncodingUTF8,
204                 false as Boolean,
205             )
206         }
207     }
208 
get(&self) -> CFDictionaryRef209     pub fn get(&self) -> CFDictionaryRef {
210         self.dict
211     }
212 }
213 
214 impl Drop for IOHIDDeviceMatcher {
drop(&mut self)215     fn drop(&mut self) {
216         unsafe { CFRelease(self.dict as *mut libc::c_void) };
217 
218         for key in &self.keys {
219             unsafe { CFRelease(*key as *mut libc::c_void) };
220         }
221 
222         for value in &self.values {
223             unsafe { CFRelease(*value as *mut libc::c_void) };
224         }
225     }
226 }
227 
228 #[link(name = "IOKit", kind = "framework")]
229 extern "C" {
230     // CFRunLoop
CFRunLoopObserverCreate( allocator: CFAllocatorRef, activities: CFOptionFlags, repeats: Boolean, order: CFIndex, callout: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) -> CFRunLoopObserverRef231     pub fn CFRunLoopObserverCreate(
232         allocator: CFAllocatorRef,
233         activities: CFOptionFlags,
234         repeats: Boolean,
235         order: CFIndex,
236         callout: CFRunLoopObserverCallBack,
237         context: *mut CFRunLoopObserverContext,
238     ) -> CFRunLoopObserverRef;
239 
240     // IOHIDManager
IOHIDManagerCreate( allocator: CFAllocatorRef, options: IOHIDManagerOptions, ) -> IOHIDManagerRef241     pub fn IOHIDManagerCreate(
242         allocator: CFAllocatorRef,
243         options: IOHIDManagerOptions,
244     ) -> IOHIDManagerRef;
IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef)245     pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
IOHIDManagerRegisterDeviceMatchingCallback( manager: IOHIDManagerRef, callback: IOHIDDeviceCallback, context: *mut c_void, )246     pub fn IOHIDManagerRegisterDeviceMatchingCallback(
247         manager: IOHIDManagerRef,
248         callback: IOHIDDeviceCallback,
249         context: *mut c_void,
250     );
IOHIDManagerRegisterDeviceRemovalCallback( manager: IOHIDManagerRef, callback: IOHIDDeviceCallback, context: *mut c_void, )251     pub fn IOHIDManagerRegisterDeviceRemovalCallback(
252         manager: IOHIDManagerRef,
253         callback: IOHIDDeviceCallback,
254         context: *mut c_void,
255     );
IOHIDManagerRegisterInputReportCallback( manager: IOHIDManagerRef, callback: IOHIDReportCallback, context: *mut c_void, )256     pub fn IOHIDManagerRegisterInputReportCallback(
257         manager: IOHIDManagerRef,
258         callback: IOHIDReportCallback,
259         context: *mut c_void,
260     );
IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn261     pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn262     pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
IOHIDManagerScheduleWithRunLoop( manager: IOHIDManagerRef, runLoop: CFRunLoopRef, runLoopMode: CFStringRef, )263     pub fn IOHIDManagerScheduleWithRunLoop(
264         manager: IOHIDManagerRef,
265         runLoop: CFRunLoopRef,
266         runLoopMode: CFStringRef,
267     );
268 
269     // IOHIDDevice
IOHIDDeviceSetReport( device: IOHIDDeviceRef, reportType: IOHIDReportType, reportID: CFIndex, report: *const u8, reportLength: CFIndex, ) -> IOReturn270     pub fn IOHIDDeviceSetReport(
271         device: IOHIDDeviceRef,
272         reportType: IOHIDReportType,
273         reportID: CFIndex,
274         report: *const u8,
275         reportLength: CFIndex,
276     ) -> IOReturn;
277 }
278 
279 ////////////////////////////////////////////////////////////////////////
280 // Tests
281 ////////////////////////////////////////////////////////////////////////
282 
283 #[cfg(test)]
284 mod tests {
285     use core_foundation_sys::base::*;
286     use core_foundation_sys::runloop::*;
287     use libc::c_void;
288     use std::ptr;
289     use std::sync::mpsc::{channel, Sender};
290     use std::thread;
291     use super::*;
292 
observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void)293     extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
294         let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
295 
296         // Send the current runloop to the receiver to unblock it.
297         let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() }));
298     }
299 
300     #[test]
test_sendable_runloop()301     fn test_sendable_runloop() {
302         let (tx, rx) = channel();
303 
304         let thread = thread::spawn(move || {
305             // Send the runloop to the owning thread.
306             let context = &tx as *const _ as *mut c_void;
307             let obs = CFRunLoopEntryObserver::new(observe, context);
308             obs.add_to_current_runloop();
309 
310             unsafe {
311                 // We need some source for the runloop to run.
312                 let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0);
313                 assert!(!manager.is_null());
314 
315                 IOHIDManagerScheduleWithRunLoop(
316                     manager,
317                     CFRunLoopGetCurrent(),
318                     kCFRunLoopDefaultMode,
319                 );
320                 IOHIDManagerSetDeviceMatching(manager, ptr::null_mut());
321 
322                 let rv = IOHIDManagerOpen(manager, 0);
323                 assert_eq!(rv, 0);
324 
325                 // This will run until `CFRunLoopStop()` is called.
326                 CFRunLoopRun();
327 
328                 let rv = IOHIDManagerClose(manager, 0);
329                 assert_eq!(rv, 0);
330 
331                 CFRelease(manager as *mut c_void);
332             }
333         });
334 
335         // Block until we enter the CFRunLoop.
336         let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop");
337 
338         // Stop the runloop.
339         unsafe { CFRunLoopStop(*runloop) };
340 
341         // Stop the thread.
342         thread.join().expect("failed to join the thread");
343 
344         // Try to stop the runloop again (without crashing).
345         unsafe { CFRunLoopStop(*runloop) };
346     }
347 }
348