Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
rustflags = "--cfg=web_sys_unstable_apis"
64 changes: 38 additions & 26 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

env:
CARGO_TERM_COLOR: always
Expand All @@ -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
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rust-analyzer.check.targets": ["wasm32-unknown-unknown"],
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"rust-analyzer.showUnlinkedFileNotification": false
}
46 changes: 42 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,39 @@ authors = ["Kevin Mehall <km@kevinmehall.net>"]
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"
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"
Expand All @@ -37,13 +51,37 @@ 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"]

# Use `tokio`'s IO threadpool for making blocking IO async
tokio = ["dep:tokio"]


[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
1 change: 0 additions & 1 deletion examples/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use nusb::{
transfer::{ControlIn, ControlOut, ControlType, Recipient},
MaybeFuture,
};

fn main() {
env_logger::init();
let di = nusb::list_devices()
Expand Down
4 changes: 2 additions & 2 deletions examples/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ fn inspect_device(dev: DeviceInfo) {
for config in dev.configurations() {
println!("{config:#?}");
}
println!("");
println!("");
println!();
println!();
}
2 changes: 1 addition & 1 deletion examples/string_descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@ fn inspect_device(dev: DeviceInfo) {
println!(" Serial({i_serial}): {s:?}");
}

println!("");
println!();
}
31 changes: 20 additions & 11 deletions src/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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] {
Expand Down Expand Up @@ -192,7 +199,7 @@ impl DeviceDescriptor {
/// This ignores any trailing data after the `bLength` specified in the descriptor.
pub fn new(buf: &[u8]) -> Option<Self> {
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(),
Expand All @@ -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!(
Expand All @@ -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,
Expand Down Expand Up @@ -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<ConfigurationDescriptor> {
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(),
Expand Down Expand Up @@ -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<NonZeroU8> {
Expand All @@ -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())
Expand Down Expand Up @@ -591,15 +598,15 @@ 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<NonZeroU8> {
NonZeroU8::new(self.string_index_raw())
}
}

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())
Expand Down Expand Up @@ -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()))
Expand Down Expand Up @@ -738,6 +745,7 @@ impl From<ActiveConfigurationError> 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(
Expand Down Expand Up @@ -774,6 +782,7 @@ pub fn fuzz_parse_concatenated_config_descriptors(buf: &[u8]) -> impl Iterator<I
parse_concatenated_config_descriptors(buf)
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod test_concatenated {
use super::parse_concatenated_config_descriptors;
Expand Down
20 changes: 16 additions & 4 deletions src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl Device {

self.configurations()
.find(|c| c.configuration_value() == active)
.ok_or_else(|| ActiveConfigurationError {
.ok_or(ActiveConfigurationError {
configuration_value: active,
})
}
Expand Down Expand Up @@ -190,7 +190,7 @@ impl Device {
.get_descriptor(desc_type, desc_index, language_id)
}

#[cfg(not(target_os = "windows"))]
#[cfg(not(any(target_os = "windows", target_family = "wasm")))]
{
const STANDARD_REQUEST_GET_DESCRIPTOR: u8 = 0x06;
use crate::transfer::{ControlType, Recipient};
Expand All @@ -208,6 +208,16 @@ impl Device {
)
.map(|r| Ok(r?))
}

#[cfg(target_family = "wasm")]
{
let device = self.backend.clone();
crate::maybe_future::future::ActualFuture::new(async move {
device
.get_descriptor(desc_type, desc_index, language_id, timeout)
.await
})
}
}

/// Request the list of supported languages for string descriptors.
Expand Down Expand Up @@ -661,7 +671,7 @@ impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Endpoint<EpType, Dir> {
///
/// ## 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<Completion> {
self.backend.poll_next_complete(cx)
}
Expand All @@ -676,7 +686,8 @@ impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Endpoint<EpType, Dir> {
///
/// ## 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<Completion> {
self.backend.wait_next_complete(timeout)
}
Expand All @@ -697,6 +708,7 @@ impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Endpoint<EpType, Dir> {
}
}

#[cfg(not(target_arch = "wasm32"))]
#[test]
fn assert_send_sync() {
use crate::transfer::{Bulk, In, Interrupt, Out};
Expand Down
Loading
Loading