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 extern crate log;
6 
7 use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
8 use crate::platform::iokit::*;
9 use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
10 use core_foundation::base::*;
11 use core_foundation::string::*;
12 use std::convert::TryInto;
13 use std::io;
14 use std::io::{Read, Write};
15 use std::sync::mpsc::{Receiver, RecvTimeoutError};
16 use std::time::Duration;
17 
18 const READ_TIMEOUT: u64 = 15;
19 
20 pub struct Device {
21     device_ref: IOHIDDeviceRef,
22     cid: [u8; 4],
23     report_rx: Receiver<Vec<u8>>,
24     dev_info: Option<U2FDeviceInfo>,
25 }
26 
27 impl Device {
new(dev_ids: (IOHIDDeviceRef, Receiver<Vec<u8>>)) -> io::Result<Self>28     pub fn new(dev_ids: (IOHIDDeviceRef, Receiver<Vec<u8>>)) -> io::Result<Self> {
29         let (device_ref, report_rx) = dev_ids;
30         Ok(Self {
31             device_ref,
32             cid: CID_BROADCAST,
33             report_rx,
34             dev_info: None,
35         })
36     }
37 
is_u2f(&self) -> bool38     pub fn is_u2f(&self) -> bool {
39         true
40     }
41 
get_property_macos(&self, prop_name: &str) -> io::Result<String>42     unsafe fn get_property_macos(&self, prop_name: &str) -> io::Result<String> {
43         let prop_ref = IOHIDDeviceGetProperty(
44             self.device_ref,
45             CFString::new(prop_name).as_concrete_TypeRef(),
46         );
47         if prop_ref.is_null() {
48             return Err(io::Error::new(
49                 io::ErrorKind::InvalidData,
50                 format!(
51                     "IOHIDDeviceGetProperty received nullptr for property {}",
52                     prop_name
53                 ),
54             ));
55         }
56 
57         if CFGetTypeID(prop_ref) != CFStringGetTypeID() {
58             return Err(io::Error::new(
59                 io::ErrorKind::InvalidInput,
60                 format!(
61                     "IOHIDDeviceGetProperty returned non-string type for property {}",
62                     prop_name
63                 ),
64             ));
65         }
66 
67         Ok(CFString::from_void(prop_ref).to_string())
68     }
69 }
70 
71 impl PartialEq for Device {
eq(&self, other_device: &Device) -> bool72     fn eq(&self, other_device: &Device) -> bool {
73         self.device_ref == other_device.device_ref
74     }
75 }
76 
77 impl Read for Device {
read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize>78     fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> {
79         let timeout = Duration::from_secs(READ_TIMEOUT);
80         let data = match self.report_rx.recv_timeout(timeout) {
81             Ok(v) => v,
82             Err(e) if e == RecvTimeoutError::Timeout => {
83                 return Err(io::Error::new(io::ErrorKind::TimedOut, e));
84             }
85             Err(e) => {
86                 return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e));
87             }
88         };
89         bytes.write(&data)
90     }
91 }
92 
93 impl Write for Device {
write(&mut self, bytes: &[u8]) -> io::Result<usize>94     fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
95         assert_eq!(bytes.len(), self.out_rpt_size() + 1);
96 
97         let report_id = i64::from(bytes[0]);
98         // Skip report number when not using numbered reports.
99         let start = if report_id == 0x0 { 1 } else { 0 };
100         let data = &bytes[start..];
101 
102         let result = unsafe {
103             IOHIDDeviceSetReport(
104                 self.device_ref,
105                 kIOHIDReportTypeOutput,
106                 report_id.try_into().unwrap(),
107                 data.as_ptr(),
108                 data.len() as CFIndex,
109             )
110         };
111         if result != 0 {
112             warn!("set_report sending failure = {0:X}", result);
113             return Err(io::Error::from_raw_os_error(result));
114         }
115         trace!("set_report sending success = {0:X}", result);
116 
117         Ok(bytes.len())
118     }
119 
120     // USB HID writes don't buffer, so this will be a nop.
flush(&mut self) -> io::Result<()>121     fn flush(&mut self) -> io::Result<()> {
122         Ok(())
123     }
124 }
125 
126 impl U2FDevice for Device {
get_cid(&self) -> &[u8; 4]127     fn get_cid(&self) -> &[u8; 4] {
128         &self.cid
129     }
130 
set_cid(&mut self, cid: [u8; 4])131     fn set_cid(&mut self, cid: [u8; 4]) {
132         self.cid = cid;
133     }
134 
in_rpt_size(&self) -> usize135     fn in_rpt_size(&self) -> usize {
136         MAX_HID_RPT_SIZE
137     }
138 
out_rpt_size(&self) -> usize139     fn out_rpt_size(&self) -> usize {
140         MAX_HID_RPT_SIZE
141     }
142 
get_property(&self, prop_name: &str) -> io::Result<String>143     fn get_property(&self, prop_name: &str) -> io::Result<String> {
144         unsafe { self.get_property_macos(prop_name) }
145     }
146 
get_device_info(&self) -> U2FDeviceInfo147     fn get_device_info(&self) -> U2FDeviceInfo {
148         // unwrap is okay, as dev_info must have already been set, else
149         // a programmer error
150         self.dev_info.clone().unwrap()
151     }
152 
set_device_info(&mut self, dev_info: U2FDeviceInfo)153     fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
154         self.dev_info = Some(dev_info);
155     }
156 }
157