1use std::{ffi::CString, fmt::Display, io::ErrorKind, marker::PhantomData, time::Duration};
4
5use hidapi::{HidApi, HidDevice, HidError};
6use tracing::{debug, error, trace, warn};
7
8use crate::{
9 info::{LedgerInfo, Model},
10 transport::PhantomNonSend,
11 Error, NonSendExchange, Transport,
12};
13
14#[derive(Clone, PartialEq, Debug)]
16#[cfg_attr(feature = "clap", derive(clap::Parser))]
17pub struct UsbInfo {
18 #[cfg_attr(feature = "clap", clap(long, value_parser=u16_parse_hex))]
19 pub vid: u16,
21
22 #[cfg_attr(feature = "clap", clap(long, value_parser=u16_parse_hex))]
23 pub pid: u16,
25
26 #[cfg_attr(feature = "clap", clap(long))]
27 pub path: Option<String>,
29}
30
31impl Display for UsbInfo {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(f, "{:04x}:{:04x}", self.vid, self.pid)
34 }
35}
36
37#[cfg(feature = "clap")]
39fn u16_parse_hex(s: &str) -> Result<u16, std::num::ParseIntError> {
40 u16::from_str_radix(s, 16)
41}
42
43pub struct UsbTransport {
49 hid_api: HidApi,
50 _phantom: PhantomNonSend,
51}
52
53pub struct UsbDevice {
58 pub info: UsbInfo,
59 device: HidDevice,
60 _phantom: PhantomNonSend,
61}
62
63pub const LEDGER_VID: u16 = 0x2c97;
65
66#[allow(unused)]
68const LEDGER_APDU_USAGE_PAGE: u16 = 0xffa0;
69#[allow(unused)]
71const LEDGER_APDU_INTREFACE_NUMBER: i32 = 0;
72
73fn is_apdu_interface(device_info: &hidapi::DeviceInfo) -> bool {
74 #[cfg(all(feature = "transport_usb_libusb", target_os = "linux"))]
88 {
89 let is_apdu = device_info.interface_number() == LEDGER_APDU_INTREFACE_NUMBER;
90 debug!(
91 "(PID={pid:#x}) USB interface #{inum} is APDU: {is_apdu}",
92 pid = device_info.product_id(),
93 inum = device_info.interface_number()
94 );
95 is_apdu
96 }
97
98 #[cfg(not(all(feature = "transport_usb_libusb", target_os = "linux")))]
99 {
100 let is_apdu = device_info.usage_page() == LEDGER_APDU_USAGE_PAGE;
101 debug!(
102 "(PID={pid:#x}) USB interface #{inum} (usage page = {uspg:#x}) is APDU: {is_apdu}",
103 pid = device_info.product_id(),
104 inum = device_info.interface_number(),
105 uspg = device_info.usage_page(),
106 );
107 is_apdu
108 }
109}
110
111impl UsbTransport {
112 pub fn new() -> Result<Self, Error> {
114 #[cfg(feature = "transport_usb_libusb")]
115 debug!("Feature transport_usb_libusb is enabled");
116
117 #[cfg(feature = "transport_usb_hidraw")]
118 debug!("Feature transport_usb_hidraw is enabled");
119
120 Ok(Self {
121 hid_api: HidApi::new()?,
122 _phantom: PhantomData,
123 })
124 }
125}
126
127impl Transport for UsbTransport {
128 type Filters = ();
129 type Info = UsbInfo;
130 type Device = UsbDevice;
131
132 async fn list(&mut self, _filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error> {
134 debug!("Listing USB devices");
135
136 if let Err(e) = self.hid_api.refresh_devices() {
139 warn!("Failed to refresh devices: {e:?}");
140 }
141
142 tokio::time::sleep(Duration::from_millis(200)).await;
143
144 let devices: Vec<_> = self
146 .hid_api
147 .device_list()
148 .filter(|d| d.vendor_id() == LEDGER_VID && is_apdu_interface(d))
149 .map(|d| LedgerInfo {
150 model: Model::from_usb_pid(d.product_id()),
151 conn: UsbInfo {
152 vid: d.vendor_id(),
153 pid: d.product_id(),
154 path: Some(d.path().to_string_lossy().to_string()),
155 }
156 .into(),
157 })
158 .collect();
159
160 debug!("devices: {:?}", devices);
161
162 Ok(devices)
163 }
164
165 async fn connect(&mut self, info: UsbInfo) -> Result<UsbDevice, Error> {
167 debug!("Connecting to USB device: {:?}", info);
168
169 let d = if let Some(p) = &info.path {
171 let p = CString::new(p.clone()).unwrap();
172 self.hid_api.open_path(&p)
173
174 } else {
176 self.hid_api.open(info.vid, info.pid)
177 };
178
179 match d {
180 Ok(d) => {
181 debug!("Connected to USB device: {:?}", info);
182 Ok(UsbDevice {
183 device: d,
184 info,
185 _phantom: PhantomData,
186 })
187 }
188 Err(e) => {
189 debug!("Failed to connect to USB device: {:?}", e);
190 Err(e.into())
191 }
192 }
193 }
194}
195
196const HID_PACKET_LEN: usize = 64;
198
199const HID_HEADER_LEN: usize = 5;
201
202impl UsbDevice {
203 pub fn write(&mut self, apdu: &[u8]) -> Result<(), Error> {
205 debug!("Write APDU");
206
207 let mut data = Vec::with_capacity(apdu.len() + 2);
209 data.extend_from_slice(&(apdu.len() as u16).to_be_bytes());
210 data.extend_from_slice(apdu);
211
212 debug!("TX: {:02x?}", data);
213
214 for (i, c) in data.chunks(HID_PACKET_LEN - HID_HEADER_LEN).enumerate() {
216 trace!("Writing chunk {} of {} bytes", i, c.len());
217
218 let mut packet = Vec::with_capacity(HID_PACKET_LEN + 1);
220
221 packet.push(0x00);
223
224 packet.extend_from_slice(&[0x01, 0x01, 0x05]);
226 packet.extend_from_slice(&(i as u16).to_be_bytes());
227 packet.extend_from_slice(c);
229
230 trace!("Write: 0x{:02x?}", packet);
231
232 self.device.write(&packet)?;
234 }
235
236 Ok(())
237 }
238
239 pub fn read(&mut self, timeout: Duration) -> Result<Vec<u8>, Error> {
241 debug!("Read APDU");
242
243 let mut buff = [0u8; HID_PACKET_LEN + 1];
244
245 let n = match self
248 .device
249 .read_timeout(&mut buff, timeout.as_millis() as i32)
250 {
251 Ok(n) => n,
252 Err(HidError::IoError { error }) if error.kind() == ErrorKind::TimedOut => {
253 return Err(Error::Timeout)
254 }
255 Err(e) => return Err(e.into()),
256 };
257
258 if n == 0 {
260 error!("Empty response");
261 return Err(Error::EmptyResponse);
262 } else if n < 7 {
263 error!("Unexpected read length {n}");
264 return Err(Error::UnexpectedResponse);
265 }
266
267 if buff[..5] != [0x01, 0x01, 0x05, 0x00, 0x00] {
269 error!("Unexpected response header: {:02x?}", &buff[..5]);
270 return Err(Error::UnexpectedResponse);
271 }
272
273 trace!("initial read: {buff:02x?}");
274
275 let len = u16::from_be_bytes([buff[5], buff[6]]) as usize;
277
278 trace!("Read len: {len}");
279
280 let mut resp = Vec::with_capacity(len);
282
283 let data_len = len.min(n - 7);
284 resp.extend_from_slice(&buff[7..][..data_len]);
285
286 let mut seq_idx = 1;
288 while resp.len() < len {
289 let rem = len - resp.len();
290
291 trace!("Read chunk {seq_idx} ({rem} bytes remaining)");
292
293 let n = self.device.read_timeout(&mut buff, 500)?;
295
296 if n < 5 {
297 error!("Invalid chunk length {n}");
298 return Err(Error::UnexpectedResponse);
299 }
300
301 if buff[..3] != [0x01, 0x01, 0x05] {
303 error!("Unexpected response header: {:02x?}", &buff[..5]);
304 return Err(Error::UnexpectedResponse);
305 }
306 if u16::from_be_bytes([buff[3], buff[4]]) != seq_idx {
307 error!("Unexpected sequence index: {:02x?}", &buff[5..7]);
308 return Err(Error::UnexpectedResponse);
309 }
310
311 let data_len = rem.min(n - 5);
313 resp.extend_from_slice(&buff[5..][..data_len]);
314 seq_idx += 1;
315 }
316
317 debug!("RX: {:02x?}", resp);
318
319 Ok(resp)
320 }
321
322 pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
323 Ok(self.device.get_device_info().is_ok())
324 }
325}
326
327impl NonSendExchange for UsbDevice {
329 async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
330 self.write(command)?;
332 self.read(timeout)
334 }
335}