use super::{
UsbConnection, UsbTimeout, YUBICO_VENDOR_ID, YUBIHSM2_BULK_IN_ENDPOINT, YUBIHSM2_INTERFACE_NUM,
YUBIHSM2_PRODUCT_ID,
};
use crate::{
command::MAX_MSG_SIZE,
connector::{
self,
ErrorKind::{AddrInvalid, DeviceBusyError, UsbError},
},
device::SerialNumber,
};
use lazy_static::lazy_static;
use libusb;
use std::{
fmt::{self, Debug},
process::exit,
slice::Iter,
str::FromStr,
time::Duration,
vec::IntoIter,
};
lazy_static! {
static ref GLOBAL_USB_CONTEXT: libusb::Context = libusb::Context::new().unwrap_or_else(|e| {
eprintln!("*** ERROR: yubihsm-rs USB context init failed: {}", e);
exit(1);
});
}
pub struct Devices(Vec<Device>);
impl Devices {
pub fn serial_numbers() -> Result<Vec<SerialNumber>, connector::Error> {
let devices = Self::detect(UsbTimeout::default())?;
let serials: Vec<_> = devices.iter().map(|a| a.serial_number).collect();
Ok(serials)
}
pub fn open(
serial_number: Option<SerialNumber>,
timeout: UsbTimeout,
) -> Result<UsbConnection, connector::Error> {
let mut devices = Self::detect(timeout)?;
if let Some(sn) = serial_number {
while let Some(device) = devices.0.pop() {
if device.serial_number == sn {
return device.open(timeout);
}
}
fail!(
UsbError,
"no YubiHSM 2 found with serial number: {:?}",
serial_number
)
} else {
match devices.0.len() {
1 => devices.0.remove(0).open(timeout),
0 => fail!(UsbError, "no YubiHSM 2 devices detected"),
_ => fail!(
UsbError,
"expected a single YubiHSM 2 device to be connected, found {}: {}",
devices.0.len(),
devices
.0
.iter()
.map(|d| d.serial_number.to_string())
.collect::<Vec<_>>()
.join(", ")
),
}
}
}
pub fn detect(timeout: UsbTimeout) -> Result<Self, connector::Error> {
let device_list = GLOBAL_USB_CONTEXT.devices()?;
let mut devices = vec![];
debug!("USB: enumerating devices...");
for device in device_list.iter() {
let desc = device.device_descriptor()?;
if desc.vendor_id() != YUBICO_VENDOR_ID || desc.product_id() != YUBIHSM2_PRODUCT_ID {
continue;
}
usb_debug!(device, "found YubiHSM device");
let mut handle = device
.open()
.map_err(|e| usb_err!(device, "error opening device: {}", e))?;
handle.reset().map_err(|error| match error {
libusb::Error::NoDevice => err!(
DeviceBusyError,
"USB(bus={},addr={}): couldn't reset device (already in use or disconnected)",
device.bus_number(),
device.address()
),
other => usb_err!(device, "error resetting device: {}", other),
})?;
let language = *handle
.read_languages(timeout.duration())?
.first()
.ok_or_else(|| {
usb_err!(
device,
"couldn't read YubiHSM serial number (missing language info)"
)
})?;
let t = timeout.duration();
let manufacturer = handle.read_manufacturer_string(language, &desc, t)?;
let product = handle.read_product_string(language, &desc, t)?;
let product_name = format!("{} {}", manufacturer, product);
let serial_number =
SerialNumber::from_str(&handle.read_serial_number_string(language, &desc, t)?)
.map_err(|e| err!(AddrInvalid, "{}", e))?;
debug!(
"USB(bus={},addr={}): found {} (serial #{})",
device.bus_number(),
device.address(),
product_name,
serial_number,
);
devices.push(Device::new(device, product_name, serial_number));
}
if devices.is_empty() {
debug!("no YubiHSM 2 devices found");
}
Ok(Devices(devices))
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn as_slice(&self) -> &[Device] {
self.0.as_slice()
}
pub fn iter(&self) -> Iter<Device> {
self.0.iter()
}
}
impl IntoIterator for Devices {
type Item = Device;
type IntoIter = IntoIter<Device>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
pub struct Device {
pub(super) device: libusb::Device<'static>,
pub product_name: String,
pub serial_number: SerialNumber,
}
impl Device {
pub(super) fn new(
device: libusb::Device<'static>,
product_name: String,
serial_number: SerialNumber,
) -> Self {
Self {
serial_number,
product_name,
device,
}
}
pub fn open(self, timeout: UsbTimeout) -> Result<UsbConnection, connector::Error> {
let connection = UsbConnection::create(self, timeout)?;
debug!(
"USB(bus={},addr={}): successfully opened {} (serial #{})",
connection.device().bus_number(),
connection.device().address(),
connection.device().product_name,
connection.device().serial_number,
);
Ok(connection)
}
pub fn bus_number(&self) -> u8 {
self.device.bus_number()
}
pub fn address(&self) -> u8 {
self.device.address()
}
pub(super) fn open_handle(&self) -> Result<libusb::DeviceHandle<'static>, connector::Error> {
let mut handle = self.device.open()?;
handle.reset()?;
handle.claim_interface(YUBIHSM2_INTERFACE_NUM)?;
flush(&mut handle)?;
Ok(handle)
}
}
impl Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"yubihsm::connector::usb::Device(bus={} addr={} serial=#{})",
self.bus_number(),
self.address(),
self.serial_number,
)
}
}
fn flush(handle: &mut libusb::DeviceHandle) -> Result<(), connector::Error> {
let mut buffer = [0u8; MAX_MSG_SIZE];
let timeout = Duration::from_millis(1);
match handle.read_bulk(YUBIHSM2_BULK_IN_ENDPOINT, &mut buffer, timeout) {
Ok(_) | Err(libusb::Error::Timeout) => Ok(()),
Err(e) => Err(e.into()),
}
}