use std::fmt;
use std::time::Duration;
use libusb;
use super::super::AvailableDevice;
use crate::transport::error::Error;
use crate::transport::protocol::{Link, Protocol, ProtocolV1};
use crate::transport::{derive_model, AvailableDeviceTransport, ProtoMessage, Transport};
mod constants {
pub use super::super::constants::*;
pub const CONFIG_ID: u8 = 0;
pub const INTERFACE_DESCRIPTOR: u8 = 0;
pub const LIBUSB_CLASS_VENDOR_SPEC: u8 = 0xff;
pub const INTERFACE: u8 = 0;
pub const INTERFACE_DEBUG: u8 = 1;
pub const ENDPOINT: u8 = 1;
pub const ENDPOINT_DEBUG: u8 = 2;
pub const READ_ENDPOINT_MASK: u8 = 0x80;
}
const CHUNK_SIZE: usize = 64;
const READ_TIMEOUT_MS: u64 = 100000;
const WRITE_TIMEOUT_MS: u64 = 100000;
#[derive(Debug)]
pub struct AvailableWebUsbTransport {
pub bus: u8,
pub address: u8,
}
impl fmt::Display for AvailableWebUsbTransport {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "WebUSB ({}:{})", self.bus, self.address)
}
}
pub struct WebUsbLink {
libusb_context: &'static libusb::Context,
handle: &'static mut libusb::DeviceHandle<'static>,
endpoint: u8,
}
impl Drop for WebUsbLink {
fn drop(&mut self) {
drop(unsafe { Box::from_raw(self.handle) });
let context_ptr = self.libusb_context as *const _ as *mut libusb::Context;
drop(unsafe { Box::from_raw(context_ptr) });
}
}
impl Link for WebUsbLink {
fn write_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> {
debug_assert_eq!(CHUNK_SIZE, chunk.len());
let timeout = Duration::from_millis(WRITE_TIMEOUT_MS);
if let Err(e) = self.handle.write_interrupt(self.endpoint, &chunk, timeout) {
return Err(e)?;
}
Ok(())
}
fn read_chunk(&mut self) -> Result<Vec<u8>, Error> {
let mut chunk = vec![0; CHUNK_SIZE];
let endpoint = constants::READ_ENDPOINT_MASK | self.endpoint;
let timeout = Duration::from_millis(READ_TIMEOUT_MS);
let n = self.handle.read_interrupt(endpoint, &mut chunk, timeout)?;
if n == CHUNK_SIZE {
Ok(chunk)
} else {
Err(Error::DeviceReadTimeout)
}
}
}
pub struct WebUsbTransport {
protocol: ProtocolV1<WebUsbLink>,
}
impl WebUsbTransport {
pub fn find_devices(debug: bool) -> Result<Vec<AvailableDevice>, Error> {
let usb_ctx = libusb::Context::new()?;
let mut devices = Vec::new();
for dev in usb_ctx.devices()?.iter() {
let desc = dev.device_descriptor()?;
let dev_id = (desc.vendor_id(), desc.product_id());
let model = match derive_model(dev_id) {
Some(m) => m,
None => continue,
};
let class_code = dev
.config_descriptor(constants::CONFIG_ID)?
.interfaces()
.find(|i| i.number() == constants::INTERFACE)
.ok_or(libusb::Error::Other)?
.descriptors()
.find(|d| d.setting_number() == constants::INTERFACE_DESCRIPTOR)
.ok_or(libusb::Error::Other)?
.class_code();
if class_code != constants::LIBUSB_CLASS_VENDOR_SPEC {
continue;
}
devices.push(AvailableDevice {
model: model,
debug: debug,
transport: AvailableDeviceTransport::WebUsb(AvailableWebUsbTransport {
bus: dev.bus_number(),
address: dev.address(),
}),
});
}
Ok(devices)
}
pub fn connect(device: &AvailableDevice) -> Result<Box<dyn Transport>, Error> {
let transport = match device.transport {
AvailableDeviceTransport::WebUsb(ref t) => t,
_ => panic!("passed wrong AvailableDevice in WebUsbTransport::connect"),
};
let interface = match device.debug {
false => constants::INTERFACE,
true => constants::INTERFACE_DEBUG,
};
let context = libusb::Context::new()?;
let context_ptr = Box::into_raw(Box::new(context));
let context_ref = unsafe { &*context_ptr as &'static libusb::Context };
let handle = {
let dev = context_ref
.devices()?
.iter()
.find(|dev| dev.bus_number() == transport.bus && dev.address() == transport.address)
.ok_or(Error::DeviceDisconnected)?;
let dev_desc = dev.device_descriptor()?;
let dev_id = (dev_desc.vendor_id(), dev_desc.product_id());
if derive_model(dev_id).as_ref() != Some(&device.model) {
return Err(Error::DeviceDisconnected);
}
let mut handle = dev.open()?;
handle.claim_interface(interface)?;
handle
};
let handle_ptr = Box::into_raw(Box::new(handle));
let handle_ref = unsafe { &mut *handle_ptr as &'static mut libusb::DeviceHandle<'static> };
Ok(Box::new(WebUsbTransport {
protocol: ProtocolV1 {
link: WebUsbLink {
libusb_context: context_ref,
handle: handle_ref,
endpoint: match device.debug {
false => constants::ENDPOINT,
true => constants::ENDPOINT_DEBUG,
},
},
},
}))
}
}
impl super::Transport for WebUsbTransport {
fn session_begin(&mut self) -> Result<(), Error> {
self.protocol.session_begin()
}
fn session_end(&mut self) -> Result<(), Error> {
self.protocol.session_end()
}
fn write_message(&mut self, message: ProtoMessage) -> Result<(), Error> {
self.protocol.write(message)
}
fn read_message(&mut self) -> Result<ProtoMessage, Error> {
self.protocol.read()
}
}