use crate::{
ledger::{
comm::ping,
},
errors::HWKeyError,
};
use hidapi::{HidApi, HidDevice, DeviceInfo};
use std::{
thread,
time,
sync::{Arc, Mutex}
};
use std::ops::Deref;
pub const CHUNK_SIZE: usize = 255;
const LEDGER_VID: u16 = 0x2c97;
const LEDGER_S_PID_1: u16 = 0x0001; const LEDGER_S_PID_2: u16 = 0x1011; const LEDGER_S_PID_3: u16 = 0x1015;
const LEDGER_X_PID_1: u16 = 0x4011; const LEDGER_X_PID_2: u16 = 0x0004;
pub type DevicesList = Vec<(String, String)>;
#[derive(Debug)]
struct Device {
fd: String,
address: String,
hid_info: DeviceInfo,
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.fd == other.fd
}
}
impl From<&DeviceInfo> for Device {
fn from(hid_info: &DeviceInfo) -> Self {
let info = hid_info.clone();
Device {
fd: info.path().to_string_lossy().to_string(),
address: "".to_string(),
hid_info: info,
}
}
}
pub struct LedgerKey {
hid: Arc<Mutex<HidApi>>,
device: Option<Device>,
}
impl LedgerKey {
pub fn new() -> Result<LedgerKey, HWKeyError> {
let hid = HidApi::new().map_err(|_| HWKeyError::CommError("HID API is not available".to_string()))?;
Ok(Self {
hid: Arc::new(Mutex::new(hid)),
device: None,
})
}
pub fn new_connected() -> Result<LedgerKey, HWKeyError> {
let mut instance = LedgerKey::new()?;
instance.connect()?;
Ok(instance)
}
pub fn devices(&self) -> DevicesList {
self.device
.iter()
.map(|d| (d.address.clone(), d.fd.clone()))
.collect()
}
pub fn have_device(&self) -> bool {
self.device.is_some()
}
pub fn connect(&mut self) -> Result<(), HWKeyError> {
let hid_mutex = self.hid.deref();
let mut hid = hid_mutex.lock()
.map_err(|_| HWKeyError::CommError("HID API is locked".to_string()))?;
hid.refresh_devices()
.map_err(|_| HWKeyError::CommError("Failed to refresh".to_string()))?;
let current = hid.device_list().find(|hid_info| {
debug!("device {:?}", hid_info);
hid_info.vendor_id() == LEDGER_VID
&& (hid_info.product_id() == LEDGER_S_PID_1
|| hid_info.product_id() == LEDGER_S_PID_2
|| hid_info.product_id() == LEDGER_S_PID_3
|| hid_info.product_id() == LEDGER_X_PID_1
|| hid_info.product_id() == LEDGER_X_PID_2)
});
if current.is_none() {
debug!("No device connected");
self.device = None;
return Err(HWKeyError::Unavailable);
}
let hid_info = current.unwrap();
let d = Device::from(hid_info);
self.device = Some(d);
Ok(())
}
pub fn open(&self) -> Result<HidDevice, HWKeyError> {
if self.device.is_none() {
return Err(HWKeyError::Unavailable);
}
let target = self.device.as_ref().unwrap();
let mut retry_delay = 50;
for _ in 0..11 {
if let Ok(h) = self
.hid.lock().unwrap()
.open(target.hid_info.vendor_id(), target.hid_info.product_id())
{
match ping(&h) {
Ok(v) => {
if v {
return Ok(h);
}
}
Err(_) => {}
}
}
thread::sleep(time::Duration::from_millis(retry_delay));
retry_delay += 25;
}
Err(HWKeyError::CommError(format!(
"Device is locked by another application: {:?}",
target.hid_info
)))
}
}