use super::{
Device, Devices, UsbConfig, UsbTimeout, YUBIHSM2_BULK_IN_ENDPOINT, YUBIHSM2_BULK_OUT_ENDPOINT,
};
use crate::{
command::MAX_MSG_SIZE,
connector::{self, Connection, ErrorKind::UsbError, Message},
};
use libusb;
use std::sync::Mutex;
use uuid::Uuid;
const MAX_RECV_RETRIES: usize = 3;
pub struct UsbConnection {
handle: Mutex<libusb::DeviceHandle<'static>>,
device: Device,
timeout: UsbTimeout,
}
impl UsbConnection {
pub fn open(config: &UsbConfig) -> Result<Self, connector::Error> {
Devices::open(config.serial, UsbTimeout::from_millis(config.timeout_ms))
}
pub(super) fn create(device: Device, timeout: UsbTimeout) -> Result<Self, connector::Error> {
let mut handle = device.open_handle()?;
for _ in 0..MAX_RECV_RETRIES {
if recv_message(&mut handle, UsbTimeout::from_millis(1)).is_err() {
break;
}
}
Ok(Self {
device,
timeout,
handle: Mutex::new(handle),
})
}
pub fn device(&self) -> &Device {
&self.device
}
}
impl Connection for UsbConnection {
fn send_message(&self, _uuid: Uuid, cmd: Message) -> Result<Message, connector::Error> {
let mut handle = self.handle.lock().unwrap();
send_message(&mut handle, cmd.as_ref(), self.timeout)?;
recv_message(&mut handle, self.timeout)
}
}
impl Default for UsbConnection {
fn default() -> Self {
Devices::open(None, UsbTimeout::default()).unwrap()
}
}
fn send_message(
handle: &mut libusb::DeviceHandle,
data: &[u8],
timeout: UsbTimeout,
) -> Result<usize, connector::Error> {
let nbytes = handle.write_bulk(YUBIHSM2_BULK_OUT_ENDPOINT, data, timeout.duration())?;
if data.len() == nbytes {
Ok(nbytes)
} else {
fail!(
UsbError,
"incomplete bulk transfer: {} of {} bytes",
nbytes,
data.len()
);
}
}
fn recv_message(
handle: &mut libusb::DeviceHandle,
timeout: UsbTimeout,
) -> Result<Message, connector::Error> {
let mut response = vec![0u8; MAX_MSG_SIZE];
for attempts_remaining in (0..MAX_RECV_RETRIES).rev() {
match handle.read_bulk(YUBIHSM2_BULK_IN_ENDPOINT, &mut response, timeout.duration()) {
Ok(nbytes) => {
response.truncate(nbytes);
return Ok(response.into());
}
Err(libusb::Error::Io) => {
debug!(
"I/O error during USB bulk message receive, retrying ({} attempts remaining)",
attempts_remaining
);
}
Err(err) => Err(err)?,
}
}
fail!(UsbError, "irrecoverable I/O error receiving bulk message")
}