1mod errors;
17use std::{io::Cursor, ops::Deref, sync::Mutex};
18
19use byteorder::{BigEndian, ReadBytesExt};
20pub use errors::LedgerHIDError;
21pub use hidapi;
22use hidapi::{DeviceInfo, HidApi, HidDevice};
23use ledger_transport::{async_trait, APDUAnswer, APDUCommand, Exchange};
24use log::info;
25
26const LEDGER_VID: u16 = 0x2c97;
27const LEDGER_USAGE_PAGE: u16 = 0xFFA0;
28const LEDGER_CHANNEL: u16 = 0x0101;
29const LEDGER_PACKET_WRITE_SIZE: u8 = 65;
32const LEDGER_PACKET_READ_SIZE: u8 = 64;
33const LEDGER_TIMEOUT: i32 = 10_000_000;
34
35pub struct TransportNativeHID {
36 device: Mutex<HidDevice>,
37}
38
39impl TransportNativeHID {
40 fn is_ledger(dev: &DeviceInfo) -> bool {
41 dev.vendor_id() == LEDGER_VID && dev.usage_page() == LEDGER_USAGE_PAGE
42 }
43
44 pub fn list_ledgers(api: &HidApi) -> impl Iterator<Item = &DeviceInfo> {
46 api.device_list()
47 .filter(|dev| Self::is_ledger(dev))
48 }
49
50 pub fn new(api: &HidApi) -> Result<Self, LedgerHIDError> {
55 let first_ledger = Self::list_ledgers(api)
56 .next()
57 .ok_or(LedgerHIDError::DeviceNotFound)?;
58
59 Self::open_device(api, first_ledger)
60 }
61
62 pub fn open_device(
71 api: &HidApi,
72 device: &DeviceInfo,
73 ) -> Result<Self, LedgerHIDError> {
74 let device = device.open_device(api)?;
75 let _ = device.set_blocking_mode(true);
76
77 let ledger = TransportNativeHID { device: Mutex::new(device) };
78
79 Ok(ledger)
80 }
81
82 fn write_apdu(
83 device: &HidDevice,
84 channel: u16,
85 apdu_command: &[u8],
86 ) -> Result<i32, LedgerHIDError> {
87 let command_length = apdu_command.len();
88 let mut in_data = Vec::with_capacity(command_length + 2);
89 in_data.push(((command_length >> 8) & 0xFF) as u8);
90 in_data.push((command_length & 0xFF) as u8);
91 in_data.extend_from_slice(apdu_command);
92
93 let mut buffer = vec![0u8; LEDGER_PACKET_WRITE_SIZE as usize];
94 buffer[0] = 0x00;
96 buffer[1] = ((channel >> 8) & 0xFF) as u8; buffer[2] = (channel & 0xFF) as u8; buffer[3] = 0x05u8;
99
100 for (sequence_idx, chunk) in in_data
101 .chunks((LEDGER_PACKET_WRITE_SIZE - 6) as usize)
102 .enumerate()
103 {
104 buffer[4] = ((sequence_idx >> 8) & 0xFF) as u8; buffer[5] = (sequence_idx & 0xFF) as u8; buffer[6 .. 6 + chunk.len()].copy_from_slice(chunk);
107
108 info!("[{:3}] << {:}", buffer.len(), hex::encode(&buffer));
109
110 let result = device.write(&buffer);
111
112 match result {
113 Ok(size) => {
114 if size < buffer.len() {
115 return Err(LedgerHIDError::Comm("USB write error. Could not send whole message"));
116 }
117 },
118 Err(x) => return Err(LedgerHIDError::Hid(x)),
119 }
120 }
121 Ok(1)
122 }
123
124 fn read_apdu(
125 device: &HidDevice,
126 channel: u16,
127 apdu_answer: &mut Vec<u8>,
128 ) -> Result<usize, LedgerHIDError> {
129 let mut buffer = vec![0u8; LEDGER_PACKET_READ_SIZE as usize];
130 let mut sequence_idx = 0u16;
131 let mut expected_apdu_len = 0usize;
132
133 loop {
134 let res = device.read_timeout(&mut buffer, LEDGER_TIMEOUT)?;
135
136 if (sequence_idx == 0 && res < 7) || res < 5 {
137 return Err(LedgerHIDError::Comm("Read error. Incomplete header"));
138 }
139
140 let mut rdr = Cursor::new(&buffer);
141
142 let rcv_channel = rdr.read_u16::<BigEndian>()?;
143 let rcv_tag = rdr.read_u8()?;
144 let rcv_seq_idx = rdr.read_u16::<BigEndian>()?;
145
146 if rcv_channel != channel {
147 return Err(LedgerHIDError::Comm("Invalid channel"));
148 }
149 if rcv_tag != 0x05u8 {
150 return Err(LedgerHIDError::Comm("Invalid tag"));
151 }
152
153 if rcv_seq_idx != sequence_idx {
154 return Err(LedgerHIDError::Comm("Invalid sequence idx"));
155 }
156
157 if rcv_seq_idx == 0 {
158 expected_apdu_len = rdr.read_u16::<BigEndian>()? as usize;
159 }
160
161 let available: usize = buffer.len() - rdr.position() as usize;
162 let missing: usize = expected_apdu_len - apdu_answer.len();
163 let end_p = rdr.position() as usize + std::cmp::min(available, missing);
164
165 let new_chunk = &buffer[rdr.position() as usize .. end_p];
166
167 info!("[{:3}] << {:}", new_chunk.len(), hex::encode(new_chunk));
168
169 apdu_answer.extend_from_slice(new_chunk);
170
171 if apdu_answer.len() >= expected_apdu_len {
172 return Ok(apdu_answer.len());
173 }
174
175 sequence_idx += 1;
176 }
177 }
178
179 pub fn exchange<I: Deref<Target = [u8]>>(
180 &self,
181 command: &APDUCommand<I>,
182 ) -> Result<APDUAnswer<Vec<u8>>, LedgerHIDError> {
183 let device = self
184 .device
185 .lock()
186 .expect("HID device poisoned");
187
188 Self::write_apdu(&device, LEDGER_CHANNEL, &command.serialize())?;
189
190 let mut answer: Vec<u8> = Vec::with_capacity(256);
191 Self::read_apdu(&device, LEDGER_CHANNEL, &mut answer)?;
192
193 APDUAnswer::from_answer(answer).map_err(|_| LedgerHIDError::Comm("response was too short"))
194 }
195}
196
197#[async_trait]
198impl Exchange for TransportNativeHID {
199 type Error = LedgerHIDError;
200 type AnswerType = Vec<u8>;
201
202 async fn exchange<I>(
203 &self,
204 command: &APDUCommand<I>,
205 ) -> Result<APDUAnswer<Self::AnswerType>, Self::Error>
206 where
207 I: Deref<Target = [u8]> + Send + Sync,
208 {
209 self.exchange(command)
210 }
211}
212
213#[cfg(test)]
214mod integration_tests {
215 use hidapi::HidApi;
216 use log::info;
217 use once_cell::sync::Lazy;
218 use serial_test::serial;
219
220 use crate::{APDUCommand, TransportNativeHID};
221
222 fn init_logging() {
223 let _ = env_logger::builder()
224 .is_test(true)
225 .try_init();
226 }
227
228 fn hidapi() -> &'static HidApi {
229 static HIDAPI: Lazy<HidApi> = Lazy::new(|| HidApi::new().expect("unable to get HIDAPI"));
230
231 &HIDAPI
232 }
233
234 #[test]
235 #[serial]
236 fn list_all_devices() {
237 init_logging();
238 let api = hidapi();
239
240 for device_info in api.device_list() {
241 info!(
242 "{:#?} - {:#x}/{:#x}/{:#x}/{:#x} {:#} {:#}",
243 device_info.path(),
244 device_info.vendor_id(),
245 device_info.product_id(),
246 device_info.usage_page(),
247 device_info.interface_number(),
248 device_info
249 .manufacturer_string()
250 .unwrap_or_default(),
251 device_info
252 .product_string()
253 .unwrap_or_default()
254 );
255 }
256 }
257
258 #[test]
259 #[serial]
260 fn ledger_device_path() {
261 init_logging();
262 let api = hidapi();
263
264 let mut ledgers = TransportNativeHID::list_ledgers(api);
265
266 let a_ledger = ledgers
267 .next()
268 .expect("could not find any ledger device");
269 info!("{:?}", a_ledger.path());
270 }
271
272 #[test]
273 #[serial]
274 fn serialize() {
275 let data = vec![0, 0, 0, 1, 0, 0, 0, 1];
276
277 let command = APDUCommand { cla: 0x56, ins: 0x01, p1: 0x00, p2: 0x00, data };
278
279 let serialized_command = command.serialize();
280
281 let expected = vec![86, 1, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 1];
282
283 assert_eq!(serialized_command, expected)
284 }
285
286 #[test]
287 #[serial]
288 fn exchange() {
289 use ledger_zondax_generic::{App, AppExt};
290 struct Dummy;
291 impl App for Dummy {
292 const CLA: u8 = 0;
293 }
294
295 init_logging();
296
297 let ledger = TransportNativeHID::new(hidapi()).expect("Could not get a device");
298
299 let result = futures::executor::block_on(Dummy::get_device_info(&ledger)).expect("Error during exchange");
301 info!("{:x?}", result);
302 }
303
304 #[test]
305 #[serial]
306 #[ignore] fn open_same_device_twice() {
308 use ledger_zondax_generic::{App, AppExt};
309 struct Dummy;
310 impl App for Dummy {
311 const CLA: u8 = 0;
312 }
313
314 init_logging();
315
316 let api = hidapi();
317 let ledger = TransportNativeHID::list_ledgers(api)
318 .next()
319 .expect("could not get a device");
320
321 let t1 = TransportNativeHID::open_device(api, ledger).expect("Could not open device");
322 let t2 = TransportNativeHID::open_device(api, ledger).expect("Could not open device");
323
324 let (r1, r2) = futures::executor::block_on(futures::future::join(
326 Dummy::get_device_info(&t1),
327 Dummy::get_device_info(&t2),
328 ));
329
330 let (r1, r2) = (r1.expect("error during exchange (t1)"), r2.expect("error during exchange (t2)"));
331
332 info!("r1: {:x?}", r1);
333 info!("r2: {:x?}", r2);
334
335 assert_eq!(r1, r2);
336 }
337}