ledger_lib/transport/
usb.rs1use std::{ffi::CString, fmt::Display, io::ErrorKind, time::Duration};
10
11use hidapi::{HidApi, HidDevice, HidError};
12use tracing::{debug, error, trace, warn};
13
14use crate::{
15 info::{LedgerInfo, Model},
16 Error,
17};
18
19use super::{Exchange, Transport};
20
21#[derive(Clone, PartialEq, Debug)]
23#[cfg_attr(feature = "clap", derive(clap::Parser))]
24pub struct UsbInfo {
25 #[cfg_attr(feature = "clap", clap(long, value_parser=u16_parse_hex))]
26 pub vid: u16,
28
29 #[cfg_attr(feature = "clap", clap(long, value_parser=u16_parse_hex))]
30 pub pid: u16,
32
33 #[cfg_attr(feature = "clap", clap(long))]
34 pub path: Option<String>,
36}
37
38impl Display for UsbInfo {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "{:04x}:{:04x}", self.vid, self.pid)
41 }
42}
43
44#[cfg(feature = "clap")]
46fn u16_parse_hex(s: &str) -> Result<u16, std::num::ParseIntError> {
47 u16::from_str_radix(s, 16)
48}
49
50pub struct UsbTransport {
56 hid_api: HidApi,
57}
58
59pub struct UsbDevice {
61 pub info: UsbInfo,
62 device: HidDevice,
63}
64
65pub const LEDGER_VID: u16 = 0x2c97;
67
68impl UsbTransport {
69 pub fn new() -> Result<Self, Error> {
71 Ok(Self {
72 hid_api: HidApi::new()?,
73 })
74 }
75}
76
77#[cfg(feature = "unstable_async_trait")]
82impl !Send for UsbDevice {}
83#[cfg(feature = "unstable_async_trait")]
84impl !Sync for UsbDevice {}
85
86#[cfg(feature = "unstable_async_trait")]
87impl !Send for UsbTransport {}
88#[cfg(feature = "unstable_async_trait")]
89impl !Sync for UsbTransport {}
90
91#[cfg(not(feature = "unstable_async_trait"))]
93unsafe impl Send for UsbTransport {}
94
95#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
96impl Transport for UsbTransport {
97 type Filters = ();
98 type Info = UsbInfo;
99 type Device = UsbDevice;
100
101 async fn list(&mut self, _filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error> {
103 debug!("Listing USB devices");
104
105 if let Err(e) = self.hid_api.refresh_devices() {
108 warn!("Failed to refresh devices: {e:?}");
109 }
110
111 tokio::time::sleep(Duration::from_millis(200)).await;
112
113 let devices: Vec<_> = self
115 .hid_api
116 .device_list()
117 .filter(|d| d.vendor_id() == LEDGER_VID)
118 .map(|d| LedgerInfo {
119 model: Model::from_pid(d.product_id()),
120 conn: UsbInfo {
121 vid: d.vendor_id(),
122 pid: d.product_id(),
123 path: Some(d.path().to_string_lossy().to_string()),
124 }
125 .into(),
126 })
127 .collect();
128
129 debug!("devices: {:?}", devices);
130
131 Ok(devices)
132 }
133
134 async fn connect(&mut self, info: UsbInfo) -> Result<UsbDevice, Error> {
136 debug!("Connecting to USB device: {:?}", info);
137
138 let d = if let Some(p) = &info.path {
140 let p = CString::new(p.clone()).unwrap();
141 self.hid_api.open_path(&p)
142
143 } else {
145 self.hid_api.open(info.vid, info.pid)
146 };
147
148 match d {
149 Ok(d) => {
150 debug!("Connected to USB device: {:?}", info);
151 Ok(UsbDevice { device: d, info })
152 }
153 Err(e) => {
154 debug!("Failed to connect to USB device: {:?}", e);
155 Err(e.into())
156 }
157 }
158 }
159}
160
161const HID_PACKET_LEN: usize = 64;
163
164const HID_HEADER_LEN: usize = 5;
166
167impl UsbDevice {
168 pub fn write(&mut self, apdu: &[u8]) -> Result<(), Error> {
170 debug!("Write APDU");
171
172 let mut data = Vec::with_capacity(apdu.len() + 2);
174 data.extend_from_slice(&(apdu.len() as u16).to_be_bytes());
175 data.extend_from_slice(apdu);
176
177 debug!("TX: {:02x?}", data);
178
179 for (i, c) in data.chunks(HID_PACKET_LEN - HID_HEADER_LEN).enumerate() {
181 trace!("Writing chunk {} of {} bytes", i, c.len());
182
183 let mut packet = Vec::with_capacity(HID_PACKET_LEN + 1);
185
186 packet.push(0x00);
188
189 packet.extend_from_slice(&[0x01, 0x01, 0x05]);
191 packet.extend_from_slice(&(i as u16).to_be_bytes());
192 packet.extend_from_slice(c);
194
195 trace!("Write: 0x{:02x?}", packet);
196
197 self.device.write(&packet)?;
199 }
200
201 Ok(())
202 }
203
204 pub fn read(&mut self, timeout: Duration) -> Result<Vec<u8>, Error> {
206 debug!("Read APDU");
207
208 let mut buff = [0u8; HID_PACKET_LEN + 1];
209
210 let n = match self
213 .device
214 .read_timeout(&mut buff, timeout.as_millis() as i32)
215 {
216 Ok(n) => n,
217 Err(HidError::IoError { error }) if error.kind() == ErrorKind::TimedOut => {
218 return Err(Error::Timeout)
219 }
220 Err(e) => return Err(e.into()),
221 };
222
223 if n == 0 {
225 error!("Empty response");
226 return Err(Error::EmptyResponse);
227 } else if n < 7 {
228 error!("Unexpected read length {n}");
229 return Err(Error::UnexpectedResponse);
230 }
231
232 if buff[..5] != [0x01, 0x01, 0x05, 0x00, 0x00] {
234 error!("Unexpected response header: {:02x?}", &buff[..5]);
235 return Err(Error::UnexpectedResponse);
236 }
237
238 trace!("initial read: {buff:02x?}");
239
240 let len = u16::from_be_bytes([buff[5], buff[6]]) as usize;
242
243 trace!("Read len: {len}");
244
245 let mut resp = Vec::with_capacity(len);
247
248 let data_len = len.min(n - 7);
249 resp.extend_from_slice(&buff[7..][..data_len]);
250
251 let mut seq_idx = 1;
253 while resp.len() < len {
254 let rem = len - resp.len();
255
256 trace!("Read chunk {seq_idx} ({rem} bytes remaining)");
257
258 let n = self.device.read_timeout(&mut buff, 500)?;
260
261 if n < 5 {
262 error!("Invalid chunk length {n}");
263 return Err(Error::UnexpectedResponse);
264 }
265
266 if buff[..3] != [0x01, 0x01, 0x05] {
268 error!("Unexpected response header: {:02x?}", &buff[..5]);
269 return Err(Error::UnexpectedResponse);
270 }
271 if u16::from_be_bytes([buff[3], buff[4]]) != seq_idx {
272 error!("Unexpected sequence index: {:02x?}", &buff[5..7]);
273 return Err(Error::UnexpectedResponse);
274 }
275
276 let data_len = rem.min(n - 5);
278 resp.extend_from_slice(&buff[5..][..data_len]);
279 seq_idx += 1;
280 }
281
282 debug!("RX: {:02x?}", resp);
283
284 Ok(resp)
285 }
286
287 pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
288 Ok(self.device.get_device_info().is_ok())
289 }
290}
291
292#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
294impl Exchange for UsbDevice {
295 async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
296 self.write(command)?;
298 self.read(timeout)
300 }
301}