diff --git a/Cargo.toml b/Cargo.toml index 8ed8fea..ecb1d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ tokio-test = "0.4.3" [profile.release] opt-level = "s" +[dependencies] +thiserror = "1.0.56" + [package.metadata.wasm-pack.profile.dev.wasm-bindgen] dwarf-debug-info = true diff --git a/src/backend/native.rs b/src/backend/native.rs index c23ed03..5d388e0 100644 --- a/src/backend/native.rs +++ b/src/backend/native.rs @@ -1,6 +1,4 @@ -use std::error::Error; - -use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient}; +use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; pub struct UsbDevice { device_info: nusb::DeviceInfo, @@ -11,7 +9,7 @@ pub struct UsbInterface { interface: nusb::Interface, } -pub async fn get_device(vendor_id: u16, product_id: u16) -> Result> { +pub async fn get_device(vendor_id: u16, product_id: u16) -> Result { let devices = nusb::list_devices().unwrap(); let mut device_info = None; @@ -24,10 +22,13 @@ pub async fn get_device(vendor_id: u16, product_id: u16) -> Result dev, - None => return Err("Device not found".into()), + None => return Err(UsbError::DeviceNotFound), }; - let device = device_info.open()?; + let device = match device_info.open() { + Ok(dev) => dev, + Err(_) => return Err(UsbError::CommunicationError) + }; Ok(UsbDevice { device_info, @@ -35,38 +36,87 @@ pub async fn get_device(vendor_id: u16, product_id: u16) -> Result, + pub product_id: Option, + pub class: Option, + pub subclass: Option, + pub protocol: Option, + serial_number: Option, +} + +impl DeviceFilter { + pub fn serial_number(&self) -> &Option { + &self.serial_number + } +} + +impl Default for DeviceFilter { + fn default() -> Self { + Self { + vendor_id: None, + product_id: None, + class: None, + subclass: None, + protocol: None, + serial_number: None, + } + } +} pub async fn get_device_filter( - device_filter: Vec, -) -> Result> { + device_filter: Vec, +) -> Result { let devices = nusb::list_devices().unwrap(); let mut device_info = None; - for device in devices { - match device_filter - .iter() - .position(|i| i == &FilterTuple(device.vendor_id(), device.product_id())) - { - Some(_) => { - device_info = Some(device); - break; - } - None => device_info = None, - } - } + for prelim_dev_inf in devices { + // See if the device exists in the list + if device_filter.iter().position(|info| + { + let mut result = false; - if device_info.is_none() { - return Err("No devices from the list found".into()); + if info.vendor_id.is_some() { + result = info.vendor_id.unwrap() == prelim_dev_inf.vendor_id(); + } + + if info.product_id.is_some() { + result = info.product_id.unwrap() == prelim_dev_inf.product_id(); + } + + if info.class.is_some() { + result = info.class.unwrap() == prelim_dev_inf.class(); + } + + if info.subclass.is_some() { + result = info.subclass.unwrap() == prelim_dev_inf.subclass(); + } + + if info.protocol.is_some() { + result = info.protocol.unwrap() == prelim_dev_inf.protocol(); + } + + if info.serial_number().is_some() && prelim_dev_inf.serial_number().is_some() { + result = info.serial_number().as_ref().unwrap() == &prelim_dev_inf.serial_number().unwrap().to_owned(); + } + + result + } + ).is_some() { + device_info = Some(prelim_dev_inf) + } } let device_info = match device_info { Some(dev) => dev, - None => return Err("Device not found".into()), + None => return Err(UsbError::DeviceNotFound), }; - let device = device_info.open()?; + let device = match device_info.open() { + Ok(dev) => dev, + Err(_) => return Err(UsbError::CommunicationError), + }; Ok(UsbDevice { device_info, @@ -78,19 +128,19 @@ impl Device for UsbDevice { type UsbDevice = UsbDevice; type UsbInterface = UsbInterface; - 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(e) => return Err(e.into()), + Err(_) => return Err(UsbError::CommunicationError), }; Ok(UsbInterface { interface }) } - async fn reset(&self) -> Result<(), Box> { + async fn reset(&self) -> Result<(), UsbError> { match self.device.reset() { Ok(_) => Ok(()), - Err(e) => Err(e.into()), + Err(_) => Err(UsbError::CommunicationError), } } @@ -120,28 +170,36 @@ impl Device for UsbDevice { } impl<'a> Interface<'a> for UsbInterface { - async fn control_in(&self, data: ControlIn) -> Result, Box> { - Ok(self.interface.control_in(data.into()).await.into_result()?) + async fn control_in(&self, data: ControlIn) -> Result, UsbError> { + let result = match self.interface.control_in(data.into()).await.into_result() { + Ok(res) => res, + Err(_) => return Err(UsbError::TransferError), + }; + + Ok(result) } - async fn control_out(&self, data: ControlOut<'a>) -> Result<(), Box> { + async fn control_out(&self, data: ControlOut<'a>) -> Result { match self.interface.control_out(data.into()).await.into_result() { - Ok(_) => Ok(()), - Err(e) => Err(e.into()), + Ok(bytes) => Ok(bytes.actual_length()), + Err(_) => Err(UsbError::TransferError), } } - async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, Box> { + async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError> { let request_buffer = nusb::transfer::RequestBuffer::new(length); - Ok(self + match self .interface .bulk_in(endpoint, request_buffer) .await - .into_result()?) + .into_result() { + Ok(res) => Ok(res), + Err(_) => Err(UsbError::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()) @@ -149,7 +207,7 @@ impl<'a> Interface<'a> for UsbInterface { .into_result() { Ok(len) => Ok(len.actual_length()), - Err(e) => Err(e.into()), + Err(_) => Err(UsbError::TransferError), } } } diff --git a/src/backend/wasm.rs b/src/backend/wasm.rs index 9c301fb..81a3e1e 100644 --- a/src/backend/wasm.rs +++ b/src/backend/wasm.rs @@ -1,16 +1,15 @@ #![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] -use std::error::Error; use wasm_bindgen::prelude::*; use js_sys::{Array, Object, Promise, Uint8Array}; use wasm_bindgen_futures::JsFuture; use web_sys::{ - console, UsbControlTransferParameters, UsbDevice as WasmUsbDevice, UsbDeviceRequestOptions, + UsbControlTransferParameters, UsbDevice as WasmUsbDevice, UsbDeviceRequestOptions, UsbInTransferResult, UsbOutTransferResult, UsbRecipient, UsbRequestType, }; // Crate stuff -use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient}; +use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; #[wasm_bindgen] pub struct UsbDevice { @@ -22,6 +21,36 @@ pub struct UsbInterface { device: WasmUsbDevice, } +#[wasm_bindgen] +#[derive(PartialEq, Clone)] +pub struct DeviceFilter { + pub vendor_id: Option, + pub product_id: Option, + pub class: Option, + pub subclass: Option, + pub protocol: Option, + serial_number: Option, +} + +impl DeviceFilter { + pub fn serial_number(&self) -> &Option { + &self.serial_number + } +} + +impl Default for DeviceFilter { + fn default() -> Self { + Self { + vendor_id: None, + product_id: None, + class: None, + subclass: None, + protocol: None, + serial_number: None, + } + } +} + #[wasm_bindgen] pub async fn get_device(vendor_id: u16, product_id: u16) -> Result { let window = web_sys::window().unwrap(); @@ -71,13 +100,9 @@ pub async fn get_device(vendor_id: u16, product_id: u16) -> Result, + device_filter: Vec, ) -> Result { let window = web_sys::window().unwrap(); @@ -91,31 +116,93 @@ pub async fn get_device_filter( for js_device in device_list { let device: WasmUsbDevice = js_device.into(); - for filter_info in &device_filter { - if device.vendor_id() == filter_info.0 && device.product_id() == filter_info.1 { - let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; + if device_filter.iter().position(|info| + { + let mut result = false; - return Ok(UsbDevice { device }); + if info.vendor_id.is_some() { + result = info.vendor_id.unwrap() == device.vendor_id(); + } + + if info.product_id.is_some() { + result = info.product_id.unwrap() == device.product_id(); + } + + if info.class.is_some() { + result = info.class.unwrap() == device.device_class(); + } + + if info.subclass.is_some() { + result = info.subclass.unwrap() == device.device_subclass(); + } + + if info.protocol.is_some() { + result = info.protocol.unwrap() == device.device_protocol(); + } + + if info.serial_number().is_some() && device.serial_number().is_some() { + result = info.serial_number().as_ref().unwrap() == &device.serial_number().unwrap().to_owned(); + } + + result } + ).is_some() { + return Ok(UsbDevice { device }); } } let arr = Array::new(); - for device in device_filter { - let filter1 = js_sys::Object::new(); - js_sys::Reflect::set( - &filter1, - &JsValue::from_str("vendorId"), - &JsValue::from(device.0), - ) - .unwrap(); - js_sys::Reflect::set( - &filter1, - &JsValue::from_str("productId"), - &JsValue::from(device.1), - ) - .unwrap(); - arr.push(&filter1); + for filter in device_filter { + let js_filter = js_sys::Object::new(); + if let Some(vid) = filter.vendor_id { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("vendorId"), + &JsValue::from(vid), + ) + .unwrap(); + } + if let Some(pid) = filter.product_id { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("productId"), + &JsValue::from(pid), + ) + .unwrap(); + } + if let Some(class) = filter.class { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("classCode"), + &JsValue::from(class), + ) + .unwrap(); + } + if let Some(subclass) = filter.subclass { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("subclassCode"), + &JsValue::from(subclass), + ) + .unwrap(); + } + if let Some(pro) = filter.protocol { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("protocolCode"), + &JsValue::from(pro), + ) + .unwrap(); + } + if let Some(serial) = filter.serial_number { + js_sys::Reflect::set( + &js_filter, + &JsValue::from_str("serialNumber"), + &JsValue::from(serial), + ) + .unwrap(); + } + arr.push(&js_filter); } let filters = JsValue::from(&arr); @@ -134,16 +221,15 @@ impl Device for UsbDevice { type UsbDevice = UsbDevice; type UsbInterface = UsbInterface; - 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; // Wait for the interface to be claimed let _device: WasmUsbDevice = match dev_promise { Ok(dev) => dev.into(), - Err(err) => { - console::log_1(&err.clone()); - return Err(format!("{:?}", err).into()); + Err(_) => { + return Err(UsbError::CommunicationError); } }; @@ -152,14 +238,14 @@ impl Device for UsbDevice { }) } - async fn reset(&self) -> Result<(), Box> { + async fn reset(&self) -> Result<(), UsbError> { let promise = Promise::resolve(&self.device.reset()); let result = JsFuture::from(promise).await; match result { Ok(_) => Ok(()), - Err(_) => Err("cancelled".into()), + Err(_) => Err(UsbError::CommunicationError), } } @@ -189,7 +275,7 @@ impl Device for UsbDevice { } impl<'a> Interface<'a> for UsbInterface { - async fn control_in(&self, data: crate::usb::ControlIn) -> Result, Box> { + async fn control_in(&self, data: crate::usb::ControlIn) -> Result, UsbError> { let length = data.length; let params: UsbControlTransferParameters = data.into(); @@ -198,12 +284,12 @@ impl<'a> Interface<'a> for UsbInterface { let transfer_result: UsbInTransferResult = match result { Ok(res) => res.into(), - Err(err) => return Err(format!("Error {:?}", err).into()), + Err(_) => return Err(UsbError::TransferError), }; let data = match transfer_result.data() { Some(res) => res.buffer(), - None => return Err("No data returned".into()), + None => return Err(UsbError::TransferError), }; let array = Uint8Array::new(&data); @@ -211,37 +297,36 @@ impl<'a> Interface<'a> for UsbInterface { Ok(array.to_vec()) } - async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result<(), Box> { + 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(); - let promise = Promise::resolve( + let result: UsbOutTransferResult = match JsFuture::from(Promise::resolve( &self .device .control_transfer_out_with_buffer_source(¶ms, array_obj), - ); - let result = JsFuture::from(promise).await; + )).await { + Ok(res) => res.into(), + Err(_) => return Err(UsbError::TransferError) + }; - match result { - Ok(_) => Ok(()), - Err(err) => Err(format!("{:?}", err).into()), - } + Ok(result.bytes_written() as usize) } - async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, Box> { + async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError> { 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(err) => return Err(format!("Error {:?}", err).into()), + Err(_) => return Err(UsbError::TransferError), }; let data = match transfer_result.data() { Some(res) => res.buffer(), - None => return Err("No data returned".into()), + None => return Err(UsbError::TransferError), }; let array = Uint8Array::new(&data); @@ -249,7 +334,7 @@ impl<'a> Interface<'a> for UsbInterface { 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(); @@ -263,7 +348,7 @@ impl<'a> Interface<'a> for UsbInterface { let transfer_result: UsbOutTransferResult = match result { Ok(res) => res.into(), - Err(err) => return Err(format!("Error {:?}", err).into()), + Err(_) => return Err(UsbError::TransferError), }; Ok(transfer_result.bytes_written() as usize) diff --git a/src/lib.rs b/src/lib.rs index 298d911..c203afc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ pub use crate::context::UsbInterface; /// A single Device ID and Product ID pair to find when looking /// for new USB devices using [get_device_filter] #[doc(inline)] -pub use crate::context::FilterTuple; +pub use crate::context::DeviceFilter; /// Gets a single device from the VendorID and ProductID #[doc(inline)] diff --git a/src/usb.rs b/src/usb.rs index d853d8b..e7d2ef5 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -3,7 +3,7 @@ //! structs which allow for USB communication. use crate::context::UsbInterface; -use std::error::Error; +use thiserror::Error; /// A unique USB device pub trait Device { @@ -14,10 +14,10 @@ pub trait Device { type UsbInterface; /// Open a specific interface of the device - async fn open_interface(&self, number: u8) -> Result>; + async fn open_interface(&self, number: u8) -> Result; /// Reset the device, which causes it to no longer be usable - async fn reset(&self) -> Result<(), Box>; + async fn reset(&self) -> Result<(), UsbError>; /// 16 bit device Product ID async fn product_id(&self) -> u16; @@ -44,20 +44,20 @@ pub trait Device { pub trait Interface<'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, Box>; + async fn control_in(&self, data: ControlIn) -> Result, UsbError>; /// A USB control out transfer (host to device) - async fn control_out(&self, data: ControlOut<'a>) -> Result<(), Box>; + 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, Box>; + async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError>; /// 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; /* Interrupt transfers are a work in progress async fn interrupt_in(&self, _endpoint: u8, _buf: Vec) { @@ -70,6 +70,22 @@ pub trait Interface<'a> { */ } +/// An error from a USB interface +#[derive(Error, Debug)] +pub enum UsbError { + #[error("device not found")] + DeviceNotFound, + + #[error("device transfer failed")] + TransferError, + + #[error("device communication failed")] + CommunicationError, + + #[error("device disconnected")] + Disconnected, +} + /// The type of USB transfer pub enum ControlType { Standard = 0,