diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..45f4bdb1 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +rustflags = "--cfg=web_sys_unstable_apis" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d3ca6990..99b728eb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] env: CARGO_TERM_COLOR: always @@ -21,35 +21,47 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - rust: ['stable', '1.79'] + rust: ["stable", "1.80"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - name: Install toolchain - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - override: true - - name: Build - run: cargo build --verbose - - name: Run tests - run: | - cargo test --verbose - cargo test --verbose --features tokio - cargo test --verbose --features smol - cargo test --verbose --features smol,tokio + - uses: actions/checkout@v3 + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + override: true + - name: Build + run: cargo build --verbose + - name: Run tests + run: | + cargo test --verbose + cargo test --verbose --features tokio + cargo test --verbose --features smol + cargo test --verbose --features smol,tokio build_android: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: 'aarch64-linux-android, armv7-linux-androideabi' - - name: build - run: | - cargo build --target aarch64-linux-android --all-features - cargo build --target armv7-linux-androideabi --all-features + - uses: actions/checkout@v4 + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: "aarch64-linux-android, armv7-linux-androideabi" + - name: build + run: | + cargo build --target aarch64-linux-android --all-features + cargo build --target armv7-linux-androideabi --all-features + + build_wasm32: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: "wasm32-unknown-unknown" + - name: build + run: | + cargo build --target wasm32-unknown-unknown --all-features diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..842fb106 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.check.targets": ["wasm32-unknown-unknown"], + "rust-analyzer.cargo.target": "wasm32-unknown-unknown", + "rust-analyzer.showUnlinkedFileNotification": false +} diff --git a/Cargo.toml b/Cargo.toml index 57499a3b..a3c0f386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Kevin Mehall "] edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://github.com/kevinmehall/nusb" -rust-version = "1.79" # keep in sync with .github/workflows/rust.yml +rust-version = "1.80" # keep in sync with .github/workflows/rust.yml [dependencies] atomic-waker = "1.1.2" @@ -16,17 +16,31 @@ futures-core = "0.3.29" log = "0.4.20" once_cell = "1.18.0" slab = "0.4.9" +tracing = "0.1" [dev-dependencies] env_logger = "0.10.0" futures-lite = "1.13.0" +pollster = { version = "0.4", features = ["macro"] } [target.'cfg(any(target_os="linux", target_os="android"))'.dependencies] -rustix = { version = "1.0.1", features = ["fs", "event", "net", "time", "mm"] } linux-raw-sys = { version = "0.9.2", features = ["ioctl"] } +[target.'cfg(all(any(target_os="linux", target_os="android"), not(target_arch="wasm32")))'.dependencies] +rustix = { version = "1.0.1", features = ["fs", "event", "net", "time", "mm"] } + [target.'cfg(target_os="windows")'.dependencies] -windows-sys = { version = "0.59.0", features = ["Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Com"] } +windows-sys = { version = "0.59.0", features = [ + "Win32_Devices_Usb", + "Win32_Devices_DeviceAndDriverInstallation", + "Win32_Foundation", + "Win32_Devices_Properties", + "Win32_Storage_FileSystem", + "Win32_Security", + "Win32_System_IO", + "Win32_System_Registry", + "Win32_System_Com", +] } [target.'cfg(target_os="macos")'.dependencies] core-foundation = "0.9.3" @@ -37,6 +51,31 @@ io-kit-sys = "0.4.0" blocking = { version = "1.6.1", optional = true } tokio = { version = "1", optional = true, features = ["rt"] } + +[target.'cfg(target_arch="wasm32")'.dependencies] +web-sys = { version = "*", features = [ + "Document", + "Window", + "Navigator", + "Usb", + "UsbDevice", + "UsbDeviceRequestOptions", + "UsbConfiguration", + "UsbInterface", + "UsbAlternateInterface", + "UsbControlTransferParameters", + "UsbRequestType", + "UsbRecipient", + "UsbInTransferResult", + "UsbTransferStatus", + "UsbOutTransferResult", + "UsbDirection", + "UsbConnectionEvent", + "WorkerGlobalScope", + "WorkerNavigator", +] } +wasm-bindgen-futures = { version = "*" } + [features] # Use the `blocking` crate for making blocking IO async smol = ["dep:blocking"] @@ -44,6 +83,5 @@ smol = ["dep:blocking"] # Use `tokio`'s IO threadpool for making blocking IO async tokio = ["dep:tokio"] - [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } diff --git a/examples/control.rs b/examples/control.rs index ced08fd0..5e8b3d39 100644 --- a/examples/control.rs +++ b/examples/control.rs @@ -4,7 +4,6 @@ use nusb::{ transfer::{ControlIn, ControlOut, ControlType, Recipient}, MaybeFuture, }; - fn main() { env_logger::init(); let di = nusb::list_devices() diff --git a/examples/descriptors.rs b/examples/descriptors.rs index fcaf47e6..bc0f3553 100644 --- a/examples/descriptors.rs +++ b/examples/descriptors.rs @@ -37,6 +37,6 @@ fn inspect_device(dev: DeviceInfo) { for config in dev.configurations() { println!("{config:#?}"); } - println!(""); - println!(""); + println!(); + println!(); } diff --git a/examples/string_descriptors.rs b/examples/string_descriptors.rs index bb6cd931..d4e35614 100644 --- a/examples/string_descriptors.rs +++ b/examples/string_descriptors.rs @@ -61,5 +61,5 @@ fn inspect_device(dev: DeviceInfo) { println!(" Serial({i_serial}): {s:?}"); } - println!(""); + println!(); } diff --git a/src/descriptors.rs b/src/descriptors.rs index b8192779..373784ce 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -18,15 +18,22 @@ use crate::{transfer::Direction, Error}; pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01; pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_TYPE_CONFIGURATION: u8 = 0x02; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_LEN_CONFIGURATION: u8 = 9; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_TYPE_INTERFACE: u8 = 0x04; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_LEN_INTERFACE: u8 = 9; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_TYPE_ENDPOINT: u8 = 0x05; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_LEN_ENDPOINT: u8 = 7; +/// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors pub(crate) const DESCRIPTOR_TYPE_STRING: u8 = 0x03; /// USB defined language IDs for string descriptors. @@ -46,7 +53,7 @@ pub mod language_id { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Descriptor<'a>(&'a [u8]); -impl<'a> Descriptor<'a> { +impl Descriptor<'_> { /// Create a `Descriptor` from a buffer. /// /// Returns `None` if @@ -73,7 +80,7 @@ impl<'a> Descriptor<'a> { } } -impl<'a> Deref for Descriptor<'a> { +impl Deref for Descriptor<'_> { type Target = [u8]; fn deref(&self) -> &[u8] { @@ -192,7 +199,7 @@ impl DeviceDescriptor { /// This ignores any trailing data after the `bLength` specified in the descriptor. pub fn new(buf: &[u8]) -> Option { let Some(buf) = buf.get(0..DESCRIPTOR_LEN_DEVICE as usize) else { - if buf.len() != 0 { + if !buf.is_empty() { warn!( "device descriptor buffer is {} bytes, need {}", buf.len(), @@ -203,7 +210,7 @@ impl DeviceDescriptor { }; let buf: [u8; DESCRIPTOR_LEN_DEVICE as usize] = buf.try_into().ok()?; if buf[0] < DESCRIPTOR_LEN_DEVICE { - warn!("invalid device descriptor bLength"); + warn!("invalid config descriptor bLength. expected {DESCRIPTOR_LEN_CONFIGURATION}, got {}", buf[0]); None } else if buf[1] != DESCRIPTOR_TYPE_DEVICE { warn!( @@ -221,7 +228,7 @@ impl DeviceDescriptor { &self.0 } - #[allow(unused)] + #[allow(unused, clippy::too_many_arguments)] pub(crate) fn from_fields( usb_version: u16, class: u8, @@ -360,7 +367,7 @@ impl<'a> ConfigurationDescriptor<'a> { /// This ignores any trailing data after the length specified in `wTotalLen`. pub fn new(buf: &[u8]) -> Option { if buf.len() < DESCRIPTOR_LEN_CONFIGURATION as usize { - if buf.len() != 0 { + if !buf.is_empty() { warn!( "config descriptor buffer is {} bytes, need {}", buf.len(), @@ -463,7 +470,7 @@ descriptor_fields! { } } -impl<'a> ConfigurationDescriptor<'a> { +impl ConfigurationDescriptor<'_> { /// Index of the string descriptor describing this configuration. #[doc(alias = "iConfiguration")] pub fn string_index(&self) -> Option { @@ -484,7 +491,7 @@ where } } -impl<'a> Debug for ConfigurationDescriptor<'a> { +impl Debug for ConfigurationDescriptor<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Configuration") .field("configuration_value", &self.configuration_value()) @@ -591,7 +598,7 @@ descriptor_fields! { } } -impl<'a> InterfaceDescriptor<'a> { +impl InterfaceDescriptor<'_> { /// Index of the string descriptor describing this interface or alternate setting. #[doc(alias = "iInterface")] pub fn string_index(&self) -> Option { @@ -599,7 +606,7 @@ impl<'a> InterfaceDescriptor<'a> { } } -impl<'a> Debug for InterfaceDescriptor<'a> { +impl Debug for InterfaceDescriptor<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InterfaceAltSetting") .field("interface_number", &self.interface_number()) @@ -680,7 +687,7 @@ descriptor_fields! { } } -impl<'a> Debug for EndpointDescriptor<'a> { +impl Debug for EndpointDescriptor<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Endpoint") .field("address", &format_args!("0x{:02X}", self.address())) @@ -738,6 +745,7 @@ impl From for Error { } } +#[cfg(not(target_arch = "wasm32"))] /// Split a chain of concatenated configuration descriptors by `wTotalLength` #[allow(unused)] pub(crate) fn parse_concatenated_config_descriptors( @@ -774,6 +782,7 @@ pub fn fuzz_parse_concatenated_config_descriptors(buf: &[u8]) -> impl Iterator Endpoint { /// /// ## Panics /// * if there are no transfers pending (that is, if [`Self::pending()`] - /// would return 0). + /// would return 0). pub fn poll_next_complete(&mut self, cx: &mut Context<'_>) -> Poll { self.backend.poll_next_complete(cx) } @@ -676,7 +686,8 @@ impl Endpoint { /// /// ## Panics /// * if there are no transfers pending (that is, if [`Self::pending()`] - /// would return 0). + /// would return 0). + #[cfg(not(target_arch = "wasm32"))] pub fn wait_next_complete(&mut self, timeout: Duration) -> Option { self.backend.wait_next_complete(timeout) } @@ -697,6 +708,7 @@ impl Endpoint { } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn assert_send_sync() { use crate::transfer::{Bulk, In, Interrupt, Out}; diff --git a/src/enumeration.rs b/src/enumeration.rs index 25f157a6..f4d68996 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -52,6 +52,9 @@ pub struct DeviceInfo { #[cfg(target_os = "macos")] pub(crate) location_id: u32, + #[cfg(target_arch = "wasm32")] + pub(crate) device: std::sync::Arc, + pub(crate) bus_id: String, pub(crate) device_address: u8, pub(crate) port_chain: Vec, @@ -96,6 +99,11 @@ impl DeviceInfo { { DeviceId(self.registry_id) } + + #[cfg(target_family = "wasm")] + { + DeviceId(crate::platform::DeviceId::from_device(&self.device)) + } } /// *(Linux-only)* Sysfs path for the device. @@ -457,7 +465,7 @@ impl UsbControllerType { match lower_s .find("hci") .filter(|i| *i > 0) - .and_then(|i| lower_s.bytes().nth(i - 1)) + .and_then(|i| lower_s.as_bytes().get(i - 1).copied()) { Some(b'x') => Some(UsbControllerType::XHCI), Some(b'e') => Some(UsbControllerType::EHCI), @@ -654,6 +662,11 @@ impl BusInfo { { self.name.as_deref() } + + #[cfg(target_family = "wasm")] + { + Some("webusb") + } } } diff --git a/src/lib.rs b/src/lib.rs index c342ad8f..716b5ec5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,10 +162,12 @@ pub type Error = io::Error; /// ### Example /// /// ```no_run +/// # fn main() { /// use nusb::{self, MaybeFuture}; /// let device = nusb::list_devices().wait().unwrap() /// .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB) /// .expect("device not connected"); +/// # } /// ``` pub fn list_devices() -> impl MaybeFuture, Error>> { @@ -179,6 +181,7 @@ pub fn list_devices() -> impl MaybeFuture impl MaybeFuture impl MaybeFuture impl MaybeFuture MaybeSend for T where T: Send {} + + /// An extension trait that enforces `Sync` only on native platforms. + /// + /// Useful for writing cross-platform async code! + pub trait MaybeSync: Sync {} + + impl MaybeSync for T where T: Sync {} +} + +#[cfg(target_arch = "wasm32")] +mod platform { + /// An extension trait that enforces `Send` only on native platforms. + /// + /// Useful for writing cross-platform async code! + pub trait MaybeSend {} + + impl MaybeSend for T {} + + /// An extension trait that enforces `Sync` only on native platforms. + /// + /// Useful for writing cross-platform async code! + pub trait MaybeSync {} + + impl MaybeSync for T {} +} + +pub use platform::{MaybeSend, MaybeSync}; diff --git a/src/maybe_future.rs b/src/maybe_future.rs index f0d8721a..78e57061 100644 --- a/src/maybe_future.rs +++ b/src/maybe_future.rs @@ -33,6 +33,36 @@ pub trait NonWasmSend {} #[cfg(target_arch = "wasm32")] impl NonWasmSend for T {} +#[cfg(target_arch = "wasm32")] +pub mod future { + use std::{ + future::{Future, IntoFuture}, + marker::PhantomData, + }; + + use super::MaybeFuture; + + pub struct ActualFuture<'a, F: Future + 'a>(F, PhantomData<&'a F>); + + impl<'a, F: Future + 'a> ActualFuture<'a, F> { + pub fn new(fut: F) -> Self { + Self(fut, PhantomData) + } + } + + impl<'a, F: Future + 'a> MaybeFuture for ActualFuture<'a, F> {} + + impl<'a, F: Future + 'a, O> IntoFuture for ActualFuture<'a, F> { + type Output = O; + + type IntoFuture = F; + + fn into_future(self) -> Self::IntoFuture { + self.0 + } + } +} + #[cfg(any( target_os = "linux", target_os = "android", diff --git a/src/platform/macos_iokit/iokit_usb.rs b/src/platform/macos_iokit/iokit_usb.rs index a51da9ea..af2a7918 100644 --- a/src/platform/macos_iokit/iokit_usb.rs +++ b/src/platform/macos_iokit/iokit_usb.rs @@ -109,10 +109,10 @@ impl IoKitDevice { return Ok(IoKitDevice { raw: raw_device }); } - return Err(Error::new( + Err(Error::new( ErrorKind::NotFound, "Couldn't create device after retries", - )); + )) } } @@ -240,7 +240,7 @@ impl IoKitInterface { panic!("query_interface returned a null pointer, which Apple says is impossible"); } - return Ok(IoKitInterface { raw }); + Ok(IoKitInterface { raw }) } } diff --git a/src/platform/macos_iokit/mod.rs b/src/platform/macos_iokit/mod.rs index 3c9bae58..0f359b9c 100644 --- a/src/platform/macos_iokit/mod.rs +++ b/src/platform/macos_iokit/mod.rs @@ -31,6 +31,6 @@ fn status_to_transfer_result(status: IOReturn) -> Result<(), TransferError> { io_kit_sys::ret::kIOReturnNoDevice => Err(TransferError::Disconnected), io_kit_sys::ret::kIOReturnAborted => Err(TransferError::Cancelled), iokit_c::kIOUSBPipeStalled => Err(TransferError::Stall), - _ => Err(TransferError::Unknown(status as i32)), + _ => Err(TransferError::Unknown(status)), } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 83ef39d6..ce2adb51 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -15,3 +15,9 @@ mod macos_iokit; #[cfg(target_os = "macos")] pub use macos_iokit::*; + +#[cfg(target_family = "wasm")] +mod webusb; + +#[cfg(target_family = "wasm")] +pub use webusb::*; diff --git a/src/platform/webusb/device.rs b/src/platform/webusb/device.rs new file mode 100644 index 00000000..d22a5698 --- /dev/null +++ b/src/platform/webusb/device.rs @@ -0,0 +1,585 @@ +use std::{ + collections::VecDeque, + io::{Error, ErrorKind}, + mem::ManuallyDrop, + sync::{Arc, Mutex}, + task::{Context, Poll}, + time::Duration, +}; + +pub use private::UniqueUsbDevice; +use wasm_bindgen_futures::{js_sys::Array, spawn_local, wasm_bindgen::JsCast, JsFuture}; +use web_sys::{ + js_sys::{Object, Uint8Array}, + UsbControlTransferParameters, UsbDevice, UsbInTransferResult, UsbOutTransferResult, +}; + +use crate::{ + bitset::EndpointBitSet, + descriptors::{ + ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor, + DESCRIPTOR_TYPE_CONFIGURATION, + }, + maybe_future::future::ActualFuture, + transfer::{ + internal::{notify_completion, take_completed_from_queue, Idle, Notify, Pending}, + Buffer, Completion, ControlIn, ControlOut, Direction, TransferError, + }, + ClaimEndpointError, DeviceInfo, MaybeFuture, Speed, +}; + +use super::{ + js_value_to_io_error, js_value_to_transfer_error, webusb_status_to_nusb_transfer_error, + TransferData, +}; + +pub mod private { + use std::ops::{Deref, DerefMut}; + + use web_sys::UsbDevice; + + pub struct UniqueUsbDevice(UsbDevice); + + // TODO: How do I best make this Send/Sync? + // Maybe store an atomic count on the JS Object and get that? + // Probably needs a global lock too to make sure not two people write to it. + // If that's the case, we can probably store the open devices in a global mutex. + // Singleton is probably ok here because devices should be uniquely accessible? + unsafe impl Send for UniqueUsbDevice {} + unsafe impl Sync for UniqueUsbDevice {} + + impl Deref for UniqueUsbDevice { + type Target = UsbDevice; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for UniqueUsbDevice { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl UniqueUsbDevice { + pub fn new(device: UsbDevice) -> Self { + Self(device) + } + } +} + +#[derive(Clone)] +pub(crate) struct WebusbDevice { + pub device: Arc, + config_descriptors: Vec>, + speed: Option, +} + +impl WebusbDevice { + pub(crate) fn from_device_info( + d: &DeviceInfo, + ) -> impl MaybeFuture, std::io::Error>> { + let target_device = d.device.clone(); + let speed = d.speed; + ActualFuture::new(async move { + let usb = super::usb()?; + let devices = JsFuture::from(usb.get_devices()) + .await + .map_err(js_value_to_io_error)?; + let devices: Array = JsCast::unchecked_from_js(devices); + + for device in devices { + let device: UsbDevice = JsCast::unchecked_from_js(device); + if device.eq(&target_device) { + JsFuture::from(device.open()) + .await + .map_err(js_value_to_io_error)?; + + let config_descriptors = extract_decriptors(&device).await?; + + #[allow(clippy::arc_with_non_send_sync)] + return Ok(Arc::new(Self { + // TODO: Check that we only open this once. + device: Arc::new(UniqueUsbDevice::new(device)), + config_descriptors, + speed, + })); + } + } + Err(Error::other("device not found")) + }) + } + + pub(crate) fn device_descriptor(&self) -> DeviceDescriptor { + DeviceDescriptor::new(&self.config_descriptors[0]).unwrap() + } + + pub(crate) fn speed(&self) -> Option { + self.speed + } + + pub(crate) fn configuration_descriptors( + &self, + ) -> impl Iterator { + self.config_descriptors + .iter() + .map(|d| ConfigurationDescriptor::new(&d[..]).unwrap()) + } + + pub(crate) fn active_configuration_value(&self) -> u8 { + self.device + .configuration() + .map(|c| c.configuration_value()) + .unwrap_or_default() + } + + pub(crate) fn set_configuration( + self: Arc, + configuration: u8, + ) -> impl MaybeFuture> { + ActualFuture::new(async move { + JsFuture::from(self.device.select_configuration(configuration)) + .await + .map_err(|e| { + Error::other( + e.as_string() + .unwrap_or_else(|| "No further error clarification available".into()), + ) + }) + .map(|_| ()) + }) + } + + pub(crate) fn reset(self: Arc) -> impl MaybeFuture> { + ActualFuture::new(async move { + JsFuture::from(self.device.reset()) + .await + .map_err(|e| { + Error::other( + e.as_string() + .unwrap_or_else(|| "No further error clarification available".into()), + ) + }) + .map(|_| ()) + }) + } + + pub(crate) fn claim_interface( + self: Arc, + interface_number: u8, + ) -> impl MaybeFuture, Error>> { + ActualFuture::new(async move { + JsFuture::from(self.device.claim_interface(interface_number)) + .await + .map_err(js_value_to_io_error)?; + + #[allow(clippy::arc_with_non_send_sync)] + Ok(Arc::new(WebusbInterface { + state: Arc::new(Mutex::new(InterfaceState { + alt_setting: 0, + endpoints_used: Default::default(), + })), + interface_number, + device: self.clone(), + })) + }) + } + + pub(crate) fn detach_and_claim_interface( + self: Arc, + interface_number: u8, + ) -> impl MaybeFuture, Error>> { + self.claim_interface(interface_number) + } + + pub async fn get_descriptor( + &self, + desc_type: u8, + desc_index: u8, + language_id: u16, + timeout: Duration, + ) -> Result, Error> { + get_descriptor(&self.device, desc_type, desc_index, language_id, timeout).await + } +} + +pub async fn extract_decriptors(device: &UsbDevice) -> Result>, Error> { + let num_configurations = device.configurations().length() as usize; + let mut config_descriptors = Vec::with_capacity(num_configurations); + + for i in 0..num_configurations { + let language_id = 0; + let desc_type = DESCRIPTOR_TYPE_CONFIGURATION; + let desc_index = i as u8; + let data = get_descriptor( + device, + desc_type, + desc_index, + language_id, + Duration::from_millis(500), + ) + .await?; + config_descriptors.push(data) + } + Ok(config_descriptors) +} + +pub async fn get_descriptor( + device: &UsbDevice, + desc_type: u8, + desc_index: u8, + language_id: u16, + _timeout: Duration, +) -> Result, Error> { + let setup = UsbControlTransferParameters::new( + language_id, + web_sys::UsbRecipient::Device, + 0x6, // Get descriptor: https://www.beyondlogic.org/usbnutshell/usb6.shtml#StandardDeviceRequests + web_sys::UsbRequestType::Standard, + ((desc_type as u16) << 8) | (desc_index as u16), + ); + let res = wasm_bindgen_futures::JsFuture::from(device.control_transfer_in(&setup, 255)) + .await + .map_err(js_value_to_io_error)?; + let res: UsbInTransferResult = JsCast::unchecked_from_js(res); + Ok(Uint8Array::new(&res.data().expect("a data buffer").buffer()).to_vec()) +} + +pub async fn extract_string(device: &UsbDevice, id: u16) -> Result { + let setup = UsbControlTransferParameters::new( + 0, + web_sys::UsbRecipient::Device, + 0x6, // Get descriptor: https://www.beyondlogic.org/usbnutshell/usb6.shtml#StandardDeviceRequests + web_sys::UsbRequestType::Standard, + (0x03_u16 << 8) | (id), + ); + let res = JsFuture::from(device.control_transfer_in(&setup, 255)) + .await + .map_err(js_value_to_io_error)?; + let res: UsbInTransferResult = JsCast::unchecked_from_js(res); + let mut data = Uint8Array::new(&res.data().expect("a data buffer").buffer()).to_vec(); + + String::from_utf16( + &data + .drain(2..data[0] as usize) + .collect::>() + .chunks(2) + .map(|c| ((c[1] as u16) << 8) | c[0] as u16) + .collect::>(), + ) + .map_err(|_| Error::other("invalid utf16")) +} + +#[derive(Clone)] +pub(crate) struct WebusbInterface { + pub interface_number: u8, + pub(crate) device: Arc, + state: Arc>, +} + +#[derive(Default)] +struct InterfaceState { + alt_setting: u8, + endpoints_used: EndpointBitSet, +} + +impl WebusbInterface { + pub fn set_alt_setting( + self: Arc, + alternate_setting: u8, + ) -> impl MaybeFuture> { + ActualFuture::new(async move { + { + // TODO: We need an async mutex to lock for the whole duration. + let state = self.state.lock().unwrap(); + + if !state.endpoints_used.is_empty() { + // TODO: Use ErrorKind::ResourceBusy once compatible with MSRV + + return Err(Error::new( + ErrorKind::Other, + "must drop endpoints before changing alt setting", + )); + } + } + + JsFuture::from( + self.device + .device + .select_alternate_interface(self.interface_number, alternate_setting), + ) + .await + .map_err(|e| { + Error::other( + e.as_string() + .unwrap_or_else(|| "No further error clarification available".into()), + ) + }) + .map(|_| ())?; + + { + let mut state = self.state.lock().unwrap(); + state.alt_setting = alternate_setting; + } + + Ok(()) + }) + } + + pub fn get_alt_setting(&self) -> u8 { + self.state.lock().unwrap().alt_setting + } + + pub async fn clear_halt(&self, endpoint: u8) -> Result<(), Error> { + let endpoint_in = endpoint & 0x80 != 0; + JsFuture::from(self.device.device.clear_halt( + if endpoint_in { + web_sys::UsbDirection::In + } else { + web_sys::UsbDirection::Out + }, + endpoint, + )) + .await + .map_err(|e| { + Error::other( + e.as_string() + .unwrap_or_else(|| "No further error clarification available".into()), + ) + }) + .map(|_| ()) + } + + #[allow(dead_code)] + pub fn control_in( + self: Arc, + control: ControlIn, + _timeout: Duration, + ) -> impl MaybeFuture, TransferError>> { + ActualFuture::new(async move { + let setup = UsbControlTransferParameters::new( + control.index, + control.recipient.into(), + control.request, + control.control_type.into(), + control.value, + ); + let res = JsFuture::from(self.device.device.control_transfer_in(&setup, 255)) + .await + .map_err(js_value_to_transfer_error)?; + let res: UsbInTransferResult = JsCast::unchecked_from_js(res); + let array = Uint8Array::new(&res.data().expect("a data buffer").buffer()); + + Ok(array.to_vec()) + }) + } + + #[allow(dead_code)] + pub(crate) fn control_out( + self: Arc, + control: ControlOut<'_>, + _timeout: Duration, + ) -> impl MaybeFuture> { + let setup = UsbControlTransferParameters::new( + control.index, + control.recipient.into(), + control.request, + control.control_type.into(), + control.value, + ); + let mut data = control.data.to_vec(); + + ActualFuture::new(async move { + let res = JsFuture::from( + self.device + .device + .control_transfer_out_with_u8_slice(&setup, &mut data) + .map_err(js_value_to_transfer_error)?, + ) + .await + .map_err(js_value_to_transfer_error)?; + let res: UsbOutTransferResult = JsCast::unchecked_from_js(res); + + webusb_status_to_nusb_transfer_error(res.status()) + }) + } + + pub fn endpoint( + self: &Arc, + descriptor: EndpointDescriptor, + ) -> Result { + let address = descriptor.address(); + let max_packet_size = descriptor.max_packet_size(); + + Ok(WebusbEndpoint { + inner: Arc::new(EndpointInner { + address, + notify: Arc::new(Notify::new()), + interface: self.clone(), + }), + max_packet_size, + pending: VecDeque::new(), + idle_transfer: None, + }) + } +} + +pub(crate) struct WebusbEndpoint { + inner: Arc, + + pub(crate) max_packet_size: usize, + + /// A queue of pending transfers, expected to complete in order + pending: VecDeque>, + + idle_transfer: Option>, +} + +struct EndpointInner { + interface: Arc, + address: u8, + notify: Arc, +} + +impl WebusbEndpoint { + pub(crate) fn endpoint_address(&self) -> u8 { + self.inner.address + } + + pub(crate) fn pending(&self) -> usize { + self.pending.len() + } + + pub(crate) fn cancel_all(&mut self) { + // Cancel transfers in reverse order to ensure subsequent transfers + // can't complete out of order while we're going through them. + // TODO: Implement cancelling. + // for transfer in self.pending.iter_mut().rev() { + // self.inner.interface.device.device.cancel(transfer); + // } + } + + pub(crate) fn submit(&mut self, buffer: Buffer) { + let transfer = self + .idle_transfer + .take() + .unwrap_or_else(|| Idle::new(self.inner.notify.clone(), super::TransferData::new())); + + let buffer = ManuallyDrop::new(buffer); + + let address = self.inner.address; + let dir = Direction::from_address(self.inner.address); + + let transfer = transfer.pre_submit(); + let ptr = transfer.as_ptr(); + + let device = self.inner.interface.clone(); + + spawn_local(async move { + match dir { + Direction::Out => { + let data = buffer.to_vec(); + let array = Uint8Array::from(data.as_slice()); + let array_obj = Object::try_from(&array).expect("an object"); + let endpoint_number = address; + + let result = JsFuture::from( + device + .device + .device + .transfer_out_with_buffer_source(endpoint_number, array_obj) + .expect("transfers are possible"), + ) + .await + .expect("transfers don't fail"); + + let transfer_result: UsbOutTransferResult = JsCast::unchecked_from_js(result); + + unsafe { + (*ptr).status = transfer_result.status(); + (*ptr).actual_len = transfer_result.bytes_written(); + (*ptr).actual_len = data.len() as u32; + notify_completion::(ptr) + } + } + Direction::In => { + let endpoint_number = address & (!0x80); + let mut data = buffer.to_vec(); + let len = data.len() as u32; + let result = + JsFuture::from(device.device.device.transfer_in(endpoint_number, len)) + .await + .expect("transfers are possible"); + + let transfer_result: UsbInTransferResult = JsCast::unchecked_from_js(result); + let received_data = Uint8Array::new( + &transfer_result + .data() + .expect("a data buffer is present") + .buffer(), + ); + data.resize(received_data.length() as usize, 0); + received_data.copy_to(&mut data[..received_data.length() as usize]); + + unsafe { + (*ptr).status = transfer_result.status(); + (*ptr).actual_len = len; + (*ptr).requested_len = len; + notify_completion::(ptr) + } + } + } + }); + self.pending.push_back(transfer); + } + + pub(crate) fn poll_next_complete(&mut self, cx: &mut Context) -> Poll { + self.inner.notify.subscribe(cx); + let dir = Direction::from_address(self.inner.address); + if let Some(mut transfer) = take_completed_from_queue(&mut self.pending) { + let completion = unsafe { transfer.take_completion(dir) }; + self.idle_transfer = Some(transfer); + Poll::Ready(completion) + } else { + Poll::Pending + } + } + + pub(crate) fn clear_halt(&self) -> impl MaybeFuture> { + let device = self.inner.interface.device.clone(); + let endpoint = self.inner.address; + let endpoint_in = endpoint & 0x80 != 0; + ActualFuture::new(async move { + JsFuture::from(device.device.clear_halt( + if endpoint_in { + web_sys::UsbDirection::In + } else { + web_sys::UsbDirection::Out + }, + endpoint, + )) + .await + .map_err(|e| { + Error::other( + e.as_string() + .unwrap_or_else(|| "No further error clarification available".into()), + ) + }) + .map(|_| ()) + }) + } +} + +impl Drop for WebusbEndpoint { + fn drop(&mut self) { + self.cancel_all(); + } +} + +impl Drop for EndpointInner { + fn drop(&mut self) { + let mut state = self.interface.state.lock().unwrap(); + state.endpoints_used.clear(self.address); + } +} diff --git a/src/platform/webusb/enumeration.rs b/src/platform/webusb/enumeration.rs new file mode 100644 index 00000000..b8a261b6 --- /dev/null +++ b/src/platform/webusb/enumeration.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; + +use wasm_bindgen_futures::{js_sys::Array, wasm_bindgen::JsCast, JsFuture}; +use web_sys::UsbDevice; + +use crate::{ + descriptors::ConfigurationDescriptor, + maybe_future::{future::ActualFuture, Ready}, + platform::webusb::device::{extract_decriptors, extract_string}, + BusInfo, DeviceInfo, Error, InterfaceInfo, MaybeFuture, +}; + +use super::UniqueUsbDevice; + +pub fn list_devices() -> impl MaybeFuture, Error>> +{ + async fn inner() -> Result, Error> { + let usb = super::usb()?; + let devices = JsFuture::from(usb.get_devices()) + .await + .map_err(|e| Error::other(format!("WebUSB devices could not be listed: {e:?}")))?; + + let devices: Array = JsCast::unchecked_from_js(devices); + + let mut result = vec![]; + for device in devices { + let device: UsbDevice = JsCast::unchecked_from_js(device); + JsFuture::from(device.open()) + .await + .map_err(|e| Error::other(format!("WebUSB device could not be opened: {e:?}")))?; + + let device = Arc::new(UniqueUsbDevice::new(device)); + + let device_info = device_to_info(device.clone()).await?; + result.push(device_info); + JsFuture::from(device.close()) + .await + .map_err(|e| Error::other(format!("WebUSB device could not be closed: {e:?}")))?; + } + + Ok(result) + } + + ActualFuture::new(async move { Ok(inner().await?.into_iter()) }) +} + +pub fn list_buses() -> impl MaybeFuture, Error>> { + Ready(Ok(vec![].into_iter())) +} + +pub(crate) async fn device_to_info(device: Arc) -> Result { + Ok(DeviceInfo { + bus_id: "webusb".to_string(), + device_address: 0, + vendor_id: device.vendor_id(), + product_id: device.product_id(), + device_version: ((device.device_version_major() as u16) << 8) + | device.device_version_minor() as u16, + usb_version: ((device.usb_version_major() as u16) << 8) | device.usb_version_minor() as u16, + class: device.device_class(), + subclass: device.device_subclass(), + protocol: device.device_protocol(), + speed: None, + manufacturer_string: device.manufacturer_name(), + product_string: device.product_name(), + serial_number: device.serial_number(), + interfaces: { + let descriptors = extract_decriptors(&device).await?; + let mut interfaces = vec![]; + for descriptor in descriptors.into_iter() { + // TODO(webusb): Remove unwrap() + let configuration = ConfigurationDescriptor::new(&descriptor).unwrap(); + for interface_group in configuration.interfaces() { + let alternate = interface_group.first_alt_setting(); + let interface_string = if let Some(id) = alternate.string_index() { + Some(extract_string(&device, id.get() as u16).await?) + } else { + None + }; + + interfaces.push(InterfaceInfo { + interface_number: interface_group.interface_number(), + class: alternate.class(), + subclass: alternate.subclass(), + protocol: alternate.protocol(), + interface_string, + }); + } + } + interfaces + }, + port_chain: vec![], + max_packet_size_0: 255, + device: device.clone(), + }) +} diff --git a/src/platform/webusb/hotplug.rs b/src/platform/webusb/hotplug.rs new file mode 100644 index 00000000..5e78af33 --- /dev/null +++ b/src/platform/webusb/hotplug.rs @@ -0,0 +1,99 @@ +use std::{ + sync::{ + mpsc::{channel, Receiver, TryRecvError}, + Arc, + }, + task::Poll, +}; + +use atomic_waker::AtomicWaker; +use wasm_bindgen_futures::spawn_local; +use web_sys::{ + wasm_bindgen::{prelude::Closure, JsCast}, + UsbConnectionEvent, +}; + +use crate::{hotplug::HotplugEvent, Error}; + +use super::{enumeration::device_to_info, DeviceId, UniqueUsbDevice}; + +pub(crate) struct WebusbHotplugWatch { + waker: Arc, + events: Receiver, +} + +// Safety: Structurally Send and only method is &mut self, so Sync +// doesn't have any additional requirements. +unsafe impl Sync for WebusbHotplugWatch {} + +impl WebusbHotplugWatch { + pub fn new() -> Result { + let usb = super::usb()?; + let waker = Arc::new(AtomicWaker::new()); + let (sender, receiver) = channel(); + { + let sender = sender.clone(); + let waker = waker.clone(); + let onconnect = Closure::wrap(Box::new(move |event: UsbConnectionEvent| { + let sender = sender.clone(); + let waker = waker.clone(); + spawn_local(async move { + let info = device_to_info(Arc::new(UniqueUsbDevice::new(event.device()))).await; + match info { + Ok(info) => { + let result = sender.clone().send(HotplugEvent::Connected(info)); + if let Err(e) = result { + tracing::warn!( + "Could not send the connect event to the internal channel: {e:?}", + ) + } else { + waker.wake(); + } + } + Err(e) => { + tracing::warn!( + "Could not read device descriptors for internal connect event dispatch: {e:?}", + ) + } + } + }) + }) as Box); + usb.set_onconnect(Some((onconnect).as_ref().unchecked_ref())); + } + { + let sender = sender.clone(); + let waker = waker.clone(); + usb.set_ondisconnect(Some( + (Closure::wrap(Box::new(move |event: UsbConnectionEvent| { + let sender = sender.clone(); + let waker = waker.clone(); + let result = sender.send(HotplugEvent::Disconnected(crate::DeviceId( + DeviceId::from_device(&event.device()), + ))); + if let Err(e) = result { + tracing::warn!( + "Could not send the disconnect event to the internal channel: {e:?}", + ) + } else { + waker.wake(); + } + }) as Box)) + .as_ref() + .unchecked_ref(), + )); + } + Ok(Self { + waker, + events: receiver, + }) + } + + pub(crate) fn poll_next(&mut self, cx: &mut std::task::Context<'_>) -> Poll { + self.waker.register(cx.waker()); + match self.events.try_recv() { + Ok(event) => Poll::Ready(event), + Err(TryRecvError::Empty) => Poll::Pending, + Err(TryRecvError::Disconnected) => Poll::Pending, + } + } +} diff --git a/src/platform/webusb/mod.rs b/src/platform/webusb/mod.rs new file mode 100644 index 00000000..24361662 --- /dev/null +++ b/src/platform/webusb/mod.rs @@ -0,0 +1,100 @@ +mod device; +mod enumeration; +mod hotplug; +mod transfer; + +use std::io::Error; + +pub(crate) use transfer::TransferData; + +pub use enumeration::{list_buses, list_devices}; + +pub(crate) use device::UniqueUsbDevice; +pub(crate) use device::WebusbDevice as Device; +pub(crate) use device::WebusbEndpoint as Endpoint; +pub(crate) use device::WebusbInterface as Interface; +pub(crate) use hotplug::WebusbHotplugWatch as HotplugWatch; + +use web_sys::js_sys; +use web_sys::js_sys::Reflect; +use web_sys::wasm_bindgen::JsCast; +use web_sys::wasm_bindgen::JsValue; +use web_sys::Usb; +use web_sys::UsbDevice; +use web_sys::Window; +use web_sys::WorkerGlobalScope; + +use crate::transfer::TransferError; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct DeviceId { + pub(crate) id: usize, +} + +impl DeviceId { + pub(crate) fn from_device(device: &UsbDevice) -> Self { + let key = JsValue::from_str("nusbUniqueId"); + static INCREMENT: std::sync::LazyLock> = + std::sync::LazyLock::new(|| std::sync::Mutex::new(0)); + let id = if let Ok(device_id) = Reflect::get(device, &key) { + device_id + .as_f64() + .expect("Expected an integer ID. This is a bug. Please report it.") + as usize + } else { + let mut lock = INCREMENT + .lock() + .expect("this should never be poisoned as we do not have multiple threads"); + *lock += 1; + Reflect::set(device, &key, &JsValue::from_f64(*lock as f64)) + .expect("Could not set ID on JS object. This is a bug. Please report it."); + *lock + }; + + DeviceId { id } + } +} + +pub(crate) fn webusb_status_to_nusb_transfer_error( + status: web_sys::UsbTransferStatus, +) -> Result<(), TransferError> { + match status { + web_sys::UsbTransferStatus::Ok => Ok(()), + web_sys::UsbTransferStatus::Stall => Err(TransferError::Stall), + // TODO: Maybe make this a Unknown error. + web_sys::UsbTransferStatus::Babble => Err(TransferError::Fault), + _ => unreachable!(), + } +} + +pub(crate) fn usb() -> Result { + let window = js_sys::global().dyn_into::().ok(); + + if let Some(window) = window { + return Ok(window.navigator().usb()); + } + + let wgs = js_sys::global().dyn_into::().ok(); + + if let Some(wgs) = wgs { + return Ok(wgs.navigator().usb()); + } + + Err(Error::other("WebUSB is not available on this platform")) +} + +pub fn js_value_to_io_error(value: JsValue) -> std::io::Error { + let value: js_sys::Error = value + .dyn_into() + .unwrap_or_else(|_| js_sys::Error::new("error could not be constructed")); + std::io::Error::other(value.message().as_string().unwrap_or_default()) +} + +pub fn js_value_to_transfer_error(value: JsValue) -> TransferError { + let value: js_sys::Error = value + .dyn_into() + .unwrap_or_else(|_| js_sys::Error::new("error could not be constructed")); + tracing::info!("{:?}", value); + // TODO: Fix this to return the correct error. + TransferError::Fault +} diff --git a/src/platform/webusb/transfer.rs b/src/platform/webusb/transfer.rs new file mode 100644 index 00000000..9086ed95 --- /dev/null +++ b/src/platform/webusb/transfer.rs @@ -0,0 +1,76 @@ +use std::mem::{self, ManuallyDrop}; + +use web_sys::UsbTransferStatus; + +use crate::transfer::{Allocator, Buffer, Completion, Direction, TransferError}; + +use super::webusb_status_to_nusb_transfer_error; + +pub struct TransferData { + pub(super) buf: *mut u8, + pub(super) capacity: u32, + pub(super) requested_len: u32, + pub(super) actual_len: u32, + pub(super) status: UsbTransferStatus, +} + +impl Drop for TransferData { + fn drop(&mut self) { + unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity as usize)) } + } +} + +impl TransferData { + pub(super) fn new() -> TransferData { + let mut empty = ManuallyDrop::new(Vec::with_capacity(0)); + unsafe { Self::from_raw(empty.as_mut_ptr(), 0, 0) } + } + + pub(super) unsafe fn from_raw(buf: *mut u8, requested_len: u32, capacity: u32) -> TransferData { + TransferData { + buf, + capacity, + requested_len, + actual_len: 0, + status: UsbTransferStatus::Ok, + } + } + + #[inline] + pub fn status(&self) -> Result<(), TransferError> { + webusb_status_to_nusb_transfer_error(self.status) + } + + /// # Safety + /// The transfer must have been completed to initialize the buffer. The direction must be correct. + pub unsafe fn take_completion(&mut self, direction: Direction) -> Completion { + let status = self.status(); + + let mut empty = ManuallyDrop::new(Vec::new()); + let ptr = mem::replace(&mut self.buf, empty.as_mut_ptr()); + let capacity = mem::replace(&mut self.capacity, 0); + let len = match direction { + Direction::Out => self.requested_len, + Direction::In => self.actual_len, + }; + let requested_len = mem::replace(&mut self.requested_len, 0); + let actual_len = mem::replace(&mut self.actual_len, 0) as usize; + + let buffer = Buffer { + ptr, + len, + requested_len, + capacity, + allocator: Allocator::Default, + }; + + Completion { + status, + actual_len, + buffer, + } + } +} + +unsafe impl Send for TransferData {} +unsafe impl Sync for TransferData {} diff --git a/src/transfer/buffer.rs b/src/transfer/buffer.rs index 89bc61f0..ee62534b 100644 --- a/src/transfer/buffer.rs +++ b/src/transfer/buffer.rs @@ -101,6 +101,7 @@ impl Buffer { /// For OUT transfers, this is the amount of data written to the buffer which will be sent when the buffer is submitted. /// For IN transfers, this is the amount of data received from the device. This length is updated when the transfer is returned. #[inline] + #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.len as usize } diff --git a/src/transfer/control.rs b/src/transfer/control.rs index 805125c7..f01ba292 100644 --- a/src/transfer/control.rs +++ b/src/transfer/control.rs @@ -33,6 +33,17 @@ pub enum ControlType { Vendor = 2, } +#[cfg(target_arch = "wasm32")] +impl From for web_sys::UsbRequestType { + fn from(value: ControlType) -> Self { + match value { + ControlType::Standard => web_sys::UsbRequestType::Standard, + ControlType::Class => web_sys::UsbRequestType::Class, + ControlType::Vendor => web_sys::UsbRequestType::Vendor, + } + } +} + /// Entity targeted by the request. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(u8)] @@ -50,6 +61,18 @@ pub enum Recipient { Other = 3, } +#[cfg(target_arch = "wasm32")] +impl From for web_sys::UsbRecipient { + fn from(value: Recipient) -> Self { + match value { + Recipient::Device => web_sys::UsbRecipient::Device, + Recipient::Interface => web_sys::UsbRecipient::Interface, + Recipient::Endpoint => web_sys::UsbRecipient::Endpoint, + Recipient::Other => web_sys::UsbRecipient::Other, + } + } +} + /// SETUP packet and associated data to make an **OUT** request on a control endpoint. #[derive(Debug, Clone, Copy)] pub struct ControlOut<'a> { @@ -80,7 +103,7 @@ pub struct ControlOut<'a> { pub data: &'a [u8], } -impl<'a> ControlOut<'a> { +impl ControlOut<'_> { #[allow(unused)] pub(crate) fn setup_packet(&self) -> [u8; SETUP_PACKET_SIZE] { pack_setup( diff --git a/src/transfer/internal.rs b/src/transfer/internal.rs index 515c6330..7660718e 100644 --- a/src/transfer/internal.rs +++ b/src/transfer/internal.rs @@ -4,7 +4,7 @@ use std::{ mem::ManuallyDrop, ops::{Deref, DerefMut}, pin::Pin, - ptr::{addr_of_mut, NonNull}, + ptr::NonNull, sync::{ atomic::{AtomicU8, Ordering}, Arc, Mutex, @@ -160,7 +160,7 @@ impl

Pending

{ fn state(&self) -> &AtomicU8 { // Get state without dereferencing as `TransferInner`, because // its `platform_data` may be mutably aliased. - unsafe { &*(addr_of_mut!((*self.ptr.as_ptr()).state)) } + unsafe { &(*self.ptr.as_ptr()).state } } pub fn is_complete(&self) -> bool { @@ -189,7 +189,7 @@ pub fn take_completed_from_queue

(queue: &mut VecDeque>) -> Option< pub fn take_completed_from_option

(option: &mut Option>) -> Option> { // TODO: use Option::take_if once supported by MSRV - if option.as_mut().map_or(false, |next| next.is_complete()) { + if option.as_mut().is_some_and(|next| next.is_complete()) { option.take().map(|t| unsafe { t.into_idle() }) } else { None @@ -256,6 +256,7 @@ impl MaybeFuture for TransferFuture where D: Send, { + #[cfg(not(target_arch = "wasm32"))] fn wait(mut self) -> Self::Output { self.notify .wait(|| take_completed_from_option(&mut self.transfer))