diff --git a/.cargo/config.toml b/.cargo/config.toml index 4865338..68c7980 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,5 +3,5 @@ rustflags = ["--cfg=web_sys_unstable_apis"] [build] #just comment in the "current" target -#target = "x86_64-unknown-linux-gnu" -target = "wasm32-unknown-unknown" +target = "x86_64-unknown-linux-gnu" +#target = "wasm32-unknown-unknown" diff --git a/src/backend/native.rs b/src/backend/native.rs index 1d9e134..b369d6d 100644 --- a/src/backend/native.rs +++ b/src/backend/native.rs @@ -1,5 +1,5 @@ use crate::usb::{ - ControlIn, ControlOut, ControlType, UsbDeviceInfo, UsbDevice, UsbInterface, Recipient, UsbError, + ControlIn, ControlOut, ControlType, UsbDeviceInfo, UsbDevice, UsbInterface, Recipient, Error, }; #[derive(Clone, Debug)] @@ -60,7 +60,7 @@ impl DeviceFilter { pub async fn get_device( device_filters: Vec -) -> Result { +) -> Result { let devices = nusb::list_devices().unwrap(); let mut device_info = None; @@ -98,7 +98,7 @@ pub async fn get_device( let device_info = match device_info { Some(dev) => dev, - None => return Err(UsbError::DeviceNotFound), + None => return Err(Error::DeviceNotFound), }; Ok(DeviceInfo { device_info }) @@ -106,7 +106,7 @@ pub async fn get_device( pub async fn get_device_list( device_filters: Vec, -) -> Result, UsbError> { +) -> Result, Error> { let devices_info = nusb::list_devices().unwrap(); let mut devices = Vec::new(); @@ -142,7 +142,7 @@ pub async fn get_device_list( } if devices.is_empty() { - return Err(UsbError::DeviceNotFound); + return Err(Error::DeviceNotFound); } let devices_opened: Vec = devices @@ -156,13 +156,13 @@ pub async fn get_device_list( impl UsbDeviceInfo for DeviceInfo { type Device = Device; - async fn open(self) -> Result { + async fn open(self) -> Result { match self.device_info.open() { Ok(dev) => Ok(Self::Device { device_info: self, device: dev, }), - Err(err) => Err(UsbError::CommunicationError(err.to_string())), + Err(err) => Err(Error::CommunicationError(err.to_string())), } } @@ -194,10 +194,10 @@ impl UsbDeviceInfo for DeviceInfo { impl UsbDevice for Device { type Interface = Interface; - async fn open_interface(&self, number: u8) -> Result { + async fn open_interface(&self, number: u8) -> Result { let interface = match self.device.claim_interface(number) { Ok(inter) => inter, - Err(err) => return Err(UsbError::CommunicationError(err.to_string())), + Err(err) => return Err(Error::CommunicationError(err.to_string())), }; Ok(Interface { @@ -206,10 +206,10 @@ impl UsbDevice for Device { }) } - async fn detach_and_open_interface(&self, number: u8) -> Result { + async fn detach_and_open_interface(&self, number: u8) -> Result { let interface = match self.device.detach_and_claim_interface(number) { Ok(inter) => inter, - Err(err) => return Err(UsbError::CommunicationError(err.to_string())), + Err(err) => return Err(Error::CommunicationError(err.to_string())), }; Ok(Interface { @@ -218,14 +218,14 @@ impl UsbDevice for Device { }) } - async fn reset(&self) -> Result<(), UsbError> { + async fn reset(&self) -> Result<(), Error> { match self.device.reset() { Ok(_) => Ok(()), - Err(err) => Err(UsbError::CommunicationError(err.to_string())), + Err(err) => Err(Error::CommunicationError(err.to_string())), } } - async fn forget(&self) -> Result<(), UsbError> { + async fn forget(&self) -> Result<(), Error> { self.reset().await } @@ -261,23 +261,23 @@ impl Drop for Device { } impl<'a> UsbInterface<'a> for Interface { - async fn control_in(&self, data: ControlIn) -> Result, UsbError> { + async fn control_in(&self, data: ControlIn) -> Result, Error> { let result = match self.interface.control_in(data.into()).await.into_result() { Ok(res) => res, - Err(_) => return Err(UsbError::TransferError), + Err(_) => return Err(Error::TransferError), }; Ok(result) } - async fn control_out(&self, data: ControlOut<'a>) -> Result { + async fn control_out(&self, data: ControlOut<'a>) -> Result { match self.interface.control_out(data.into()).await.into_result() { Ok(bytes) => Ok(bytes.actual_length()), - Err(_) => Err(UsbError::TransferError), + Err(_) => Err(Error::TransferError), } } - async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError> { + async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, Error> { let request_buffer = nusb::transfer::RequestBuffer::new(length); match self @@ -287,11 +287,11 @@ impl<'a> UsbInterface<'a> for Interface { .into_result() { Ok(res) => Ok(res), - Err(_) => Err(UsbError::TransferError), + Err(_) => Err(Error::TransferError), } } - async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result { + async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result { match self .interface .bulk_out(endpoint, data.to_vec()) @@ -299,7 +299,7 @@ impl<'a> UsbInterface<'a> for Interface { .into_result() { Ok(len) => Ok(len.actual_length()), - Err(_) => Err(UsbError::TransferError), + Err(_) => Err(Error::TransferError), } } diff --git a/src/backend/wasm.rs b/src/backend/wasm.rs index 637adc0..878a8c0 100644 --- a/src/backend/wasm.rs +++ b/src/backend/wasm.rs @@ -10,7 +10,7 @@ use web_sys::{ // Crate stuff use crate::usb::{ - ControlIn, ControlOut, ControlType, UsbDeviceInfo, UsbDevice, UsbInterface, Recipient, UsbError, + ControlIn, ControlOut, ControlType, UsbDeviceInfo, UsbDevice, UsbInterface, Recipient, Error, }; #[wasm_bindgen] @@ -274,7 +274,7 @@ pub async fn get_device_list(device_filter: Vec) -> Result Result { + async fn open(self) -> Result { Ok(Self::Device { device: self.device, }) @@ -308,7 +308,7 @@ impl UsbDeviceInfo for DeviceInfo { impl UsbDevice for Device { type Interface = Interface; - async fn open_interface(&self, number: u8) -> Result { + async fn open_interface(&self, number: u8) -> Result { let dev_promise = JsFuture::from(Promise::resolve(&self.device.claim_interface(number))).await; @@ -316,7 +316,7 @@ impl UsbDevice for Device { let _device: WasmUsbDevice = match dev_promise { Ok(dev) => dev.into(), Err(err) => { - return Err(UsbError::CommunicationError( + return Err(Error::CommunicationError( err.as_string().unwrap_or_default(), )); } @@ -328,27 +328,27 @@ impl UsbDevice for Device { }) } - async fn detach_and_open_interface(&self, number: u8) -> Result { + async fn detach_and_open_interface(&self, number: u8) -> Result { self.open_interface(number).await } - async fn reset(&self) -> Result<(), UsbError> { + async fn reset(&self) -> Result<(), Error> { let result = JsFuture::from(Promise::resolve(&self.device.reset())).await; match result { Ok(_) => Ok(()), - Err(err) => Err(UsbError::CommunicationError( + Err(err) => Err(Error::CommunicationError( err.as_string().unwrap_or_default(), )), } } - async fn forget(&self) -> Result<(), UsbError> { + async fn forget(&self) -> Result<(), Error> { let result = JsFuture::from(Promise::resolve(&self.device.forget())).await; match result { Ok(_) => Ok(()), - Err(err) => Err(UsbError::CommunicationError( + Err(err) => Err(Error::CommunicationError( err.as_string().unwrap_or_default(), )), } @@ -380,7 +380,7 @@ impl UsbDevice for Device { } impl<'a> UsbInterface<'a> for Interface { - async fn control_in(&self, data: crate::usb::ControlIn) -> Result, UsbError> { + async fn control_in(&self, data: crate::usb::ControlIn) -> Result, Error> { let length = data.length; let params: UsbControlTransferParameters = data.into(); @@ -389,12 +389,12 @@ impl<'a> UsbInterface<'a> for Interface { let transfer_result: UsbInTransferResult = match result { Ok(res) => res.into(), - Err(_) => return Err(UsbError::TransferError), + Err(_) => return Err(Error::TransferError), }; let data = match transfer_result.data() { Some(res) => res.buffer(), - None => return Err(UsbError::TransferError), + None => return Err(Error::TransferError), }; let array = Uint8Array::new(&data); @@ -402,7 +402,7 @@ impl<'a> UsbInterface<'a> for Interface { Ok(array.to_vec()) } - async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result { + async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result { let array = Uint8Array::from(data.data); let array_obj = Object::try_from(&array).unwrap(); let params: UsbControlTransferParameters = data.into(); @@ -415,25 +415,25 @@ impl<'a> UsbInterface<'a> for Interface { .await { Ok(res) => res.into(), - Err(_) => return Err(UsbError::TransferError), + Err(_) => return Err(Error::TransferError), }; Ok(result.bytes_written() as usize) } - async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError> { + async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, Error> { let promise = Promise::resolve(&self.device.transfer_in(endpoint, length as u32)); let result = JsFuture::from(promise).await; let transfer_result: UsbInTransferResult = match result { Ok(res) => res.into(), - Err(_) => return Err(UsbError::TransferError), + Err(_) => return Err(Error::TransferError), }; let data = match transfer_result.data() { Some(res) => res.buffer(), - None => return Err(UsbError::TransferError), + None => return Err(Error::TransferError), }; let array = Uint8Array::new(&data); @@ -441,7 +441,7 @@ impl<'a> UsbInterface<'a> for Interface { Ok(array.to_vec()) } - async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result { + async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result { let array = Uint8Array::from(data); let array_obj = Object::try_from(&array).unwrap(); @@ -455,7 +455,7 @@ impl<'a> UsbInterface<'a> for Interface { let transfer_result: UsbOutTransferResult = match result { Ok(res) => res.into(), - Err(_) => return Err(UsbError::TransferError), + Err(_) => return Err(Error::TransferError), }; Ok(transfer_result.bytes_written() as usize) diff --git a/src/lib.rs b/src/lib.rs index 08bee22..99abf39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,18 @@ //! and comparable to the very popular `libusb` C library. Web Assembly support is provided by [web-sys](https://docs.rs/web-sys/latest/web_sys/) //! with the [Web USB API](https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API). //! -//! When an [`Interface`] is dropped, it is automatically released. //! //! ## CURRENT LIMITATIONS: -//! * Hotplug support is not implemented. Waiting on [hotplug support in nusb](https://github.com/kevinmehall/nusb/pull/20). +//! * Isochronous and interrupt transfers are currently not supported. This +//! will probably change in a future release. +//! +//! * Hotplug support is not implemented. Waiting on +//! [hotplug support in nusb](https://github.com/kevinmehall/nusb/pull/20). //! //! * When compiling this crate on a WASM target, you **must** use either //! `RUSTFLAGS=--cfg=web_sys_unstable_apis` or by passing the argument in a -//! `.cargo/config.toml` file. Read more here: +//! `.cargo/config.toml` file. Read more here: +//! //! //! ## Example: //! ```no_run @@ -79,19 +83,23 @@ mod context; /// without claiming it /// /// **Note:** On WASM targets, a device *must* be claimed in order to get -/// information about it in normal circumstances. +/// information about it in normal circumstances, so this is not as useful +/// on WASM. pub use crate::context::DeviceInfo; #[doc(inline)] -/// A USB device, you must open an [`Interface`] to perform transfers +/// A USB device, you must open an [`Interface`] to perform transfers. pub use crate::context::Device; #[doc(inline)] -/// A USB interface with which to perform transfers on +/// A USB interface to perform transfers with. pub use crate::context::Interface; -/// Information about a USB device for use in [`get_device`] -/// or [`get_device_list`] +/// Information about a USB device for use in [`get_device`] or +/// [`get_device_list`]. +/// +/// It's easiest to construct this using the [`device_filter`] +/// macro. #[doc(inline)] pub use crate::context::DeviceFilter; @@ -164,8 +172,7 @@ macro_rules! device_filter { } } -#[cfg(target_family = "wasm")] -#[cfg(not(web_sys_unstable_apis))] +#[cfg(all(target_family = "wasm", not(web_sys_unstable_apis)))] compile_error!{ "Cannot compile `web-sys` (a dependency of this crate) with USB support without `web_sys_unstable_apis`! Please check https://rustwasm.github.io/wasm-bindgen/web-sys/unstable-apis.html for more info." diff --git a/src/usb.rs b/src/usb.rs index 4b30aa3..a8558ea 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -4,12 +4,13 @@ use thiserror::Error; +/// Information about a USB device before claiming it. pub trait UsbDeviceInfo { /// A unique USB Device type Device; /// Opens the USB connection, returning a [Self::Device] - async fn open(self) -> Result; + async fn open(self) -> Result; /// 16 bit device Product ID async fn product_id(&self) -> u16; @@ -32,28 +33,30 @@ pub trait UsbDeviceInfo { async fn product_string(&self) -> Option; } -/// A unique USB device +/// A unique USB device. +/// +/// In order to perform transfers, an interface must be opened. pub trait UsbDevice { /// A unique Interface on a USB Device type Interface; /// Open a specific interface of the device - async fn open_interface(&self, number: u8) -> Result; + async fn open_interface(&self, number: u8) -> Result; /// Open a specific interface of the device, detaching any /// kernel drivers and claiming it. /// /// **Note:** This only has an effect on Native, and only on Linux. - async fn detach_and_open_interface(&self, number: u8) -> Result; + async fn detach_and_open_interface(&self, number: u8) -> Result; /// Reset the device, which causes it to no longer be usable. You must /// request a new device with [crate::get_device] - async fn reset(&self) -> Result<(), UsbError>; + async fn reset(&self) -> Result<(), Error>; /// Remove the device from the paired devices list, causing it to no longer be usable. You must request to reconnect using [crate::get_device] /// /// **Note:** On Native this simply resets the device. - async fn forget(&self) -> Result<(), UsbError>; + async fn forget(&self) -> Result<(), Error>; /// 16 bit device Product ID async fn product_id(&self) -> u16; @@ -80,20 +83,20 @@ pub trait UsbDevice { pub trait UsbInterface<'a> { /// A USB control in transfer (device to host) /// Returns a [Result] with the bytes in a `Vec` - async fn control_in(&self, data: ControlIn) -> Result, UsbError>; + async fn control_in(&self, data: ControlIn) -> Result, Error>; /// A USB control out transfer (host to device) - async fn control_out(&self, data: ControlOut<'a>) -> Result; + async fn control_out(&self, data: ControlOut<'a>) -> Result; /// A USB bulk in transfer (device to host) /// It takes in a bulk endpoint to send to along with the length of /// data to read, and returns a [Result] with the bytes - async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError>; + async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, Error>; /// A USB bulk out transfer (host to device). /// It takes in a bulk endpoint to send to along with some data as /// a slice, and returns a [Result] containing the number of bytes transferred - async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result; + async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result; /* TODO: Figure out interrupt transfers on Web USB /// A USB interrupt in transfer (device to host). @@ -108,54 +111,94 @@ pub trait UsbInterface<'a> { /// An error from a USB interface #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum UsbError { +pub enum Error { + /// The device was not found. #[error("device not found")] DeviceNotFound, + /// An error occured during a transfer. #[error("device transfer failed")] TransferError, + /// There was an error communicating with the device. #[error("device communication failed")] CommunicationError(String), + /// The device was disconnected and can no longer be accesed. #[error("device disconnected")] Disconnected, + /// The device has gone into an invalid state, and needs to be + /// reconnected to. #[error("device no longer valid")] Invalid, } -/// The type of USB transfer +/// The type of USB control transfer. pub enum ControlType { + /// A standard transfer. Standard = 0, + + /// A Class Device transfer. Class = 1, + + /// A Vendor defined transfer. Vendor = 2, } -/// The recipient of a USB transfer +/// The recipient of a USB transfer. pub enum Recipient { + /// The device is the recipient. Device = 0, + + /// An interface is the recipient. Interface = 1, + + /// An endpoint is the recipient. Endpoint = 2, + + /// Something else is the recipient. Other = 3, } -/// Parameters for [UsbInterface::control_in] +/// Parameters for [UsbInterface::control_in]. pub struct ControlIn { + /// The [`ControlType`] of this transfer, in the `bmRequestType` field. pub control_type: ControlType, + + /// The [`Recipient`] of this transfer, in the `bmRequestType` field. pub recipient: Recipient, + + /// The value of `bRequest` field. pub request: u8, + + /// The value of the `wValue` field. pub value: u16, + + /// The value of the `wIndex` field. pub index: u16, + + /// The number of bytes to read. pub length: u16, } -/// Parameters for [UsbInterface::control_out] +/// Parameters for [UsbInterface::control_out]. pub struct ControlOut<'a> { + /// The [`ControlType`] of this transfer, in the `bmRequestType` field. pub control_type: ControlType, + + /// The [`Recipient`] of this transfer, in the `bmRequestType` field. pub recipient: Recipient, + + /// The value of `bRequest` field. pub request: u8, + + /// The value of the `wValue` field. pub value: u16, + + /// The value of the `wIndex` field. pub index: u16, + + /// The data to send in this transfer. pub data: &'a [u8], }