From b3d3eed0cd31d671c3f71be3cdb1ecb81f62d1bf Mon Sep 17 00:00:00 2001 From: Amol Bhave Date: Wed, 24 Aug 2022 22:17:39 -0400 Subject: [PATCH 01/30] fields.rs: Make request_type function a const fn Make request_type function in fields.rs a const fn so that it can used to create const global variables. Test: The following code now compiles ``` const CTRL_OUT: u8 = request_type( rusb::Direction::Out, rusb::RequestType::Vendor, rusb::Recipient::Device, ); ``` --- src/fields.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/fields.rs b/src/fields.rs index 678fdf4..1749c0a 100644 --- a/src/fields.rs +++ b/src/fields.rs @@ -206,7 +206,11 @@ impl std::fmt::Display for Version { /// /// rusb::request_type(Direction::In, RequestType::Standard, Recipient::Device); /// ``` -pub fn request_type(direction: Direction, request_type: RequestType, recipient: Recipient) -> u8 { +pub const fn request_type( + direction: Direction, + request_type: RequestType, + recipient: Recipient, +) -> u8 { let mut value: u8 = match direction { Direction::Out => LIBUSB_ENDPOINT_OUT, Direction::In => LIBUSB_ENDPOINT_IN, From 1402b289228a74cfa3b7621f8f868ee94630e1a4 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Fri, 7 Oct 2022 16:14:34 -0400 Subject: [PATCH 02/30] Increase endpoint descriptor's lifetime Before this commit, the lifetime of the endpoint descriptor was shorter than the lifetime of the config descriptor, since it was tied to the InterfaceDescriptor object. This commit amends the endpoint_descriptors signature so that an EndpointDescriptor has the same lifetime as the overarching ConfigDescriptor. From my understanding, all state derived from a ConfigDescriptor has a lifetime associated to that ConfigDescriptor. So, it's safe for the EndpointDescriptor to adopt the ConfigDescriptor's lifetime through the InterfaceDescriptor. The new test shows us that the lifetimes are now all equal. This test wouldn't compile without the signature change. --- src/config_descriptor.rs | 22 ++++++++++++++++++++++ src/interface_descriptor.rs | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/config_descriptor.rs b/src/config_descriptor.rs index 3a0aea8..f5cee6d 100644 --- a/src/config_descriptor.rs +++ b/src/config_descriptor.rs @@ -215,4 +215,26 @@ mod test { assert_eq!(vec![1], interface_numbers); }); } + + // Successful compilation shows that the lifetime of the endpoint descriptor(s) is the same + // as the lifetime of the config descriptor. + #[test] + fn it_had_interfaces_with_endpoints() { + let endpoint1 = endpoint_descriptor!(bEndpointAddress: 0x81); + let endpoint2 = endpoint_descriptor!(bEndpointAddress: 0x01); + let endpoint3 = endpoint_descriptor!(bEndpointAddress: 0x02); + let interface1 = interface!(interface_descriptor!(endpoint1, endpoint2)); + let interface2 = interface!(interface_descriptor!(endpoint3)); + + with_config!(config: config_descriptor!(interface1, interface2) => { + // Exists only to name config's lifetime. + fn named_lifetime<'a>(config: &'a super::ConfigDescriptor) { + let addresses: Vec<_> = config.interfaces().flat_map(|intf| intf.descriptors()).flat_map(|desc| desc.endpoint_descriptors()).map(|ep| ep.address()).collect(); + assert_eq!(addresses, &[0x81, 0x01, 0x02]); + let desc: crate::InterfaceDescriptor<'a> = config.interfaces().flat_map(|intf| intf.descriptors()).next().expect("There's one interface"); + let _: crate::EndpointDescriptor<'a> = desc.endpoint_descriptors().next().expect("There's one endpoint"); + } + named_lifetime(&*config); + }) + } } diff --git a/src/interface_descriptor.rs b/src/interface_descriptor.rs index 4dadaa2..b683ca0 100644 --- a/src/interface_descriptor.rs +++ b/src/interface_descriptor.rs @@ -90,7 +90,7 @@ impl<'a> InterfaceDescriptor<'a> { } /// Returns an iterator over the interface's endpoint descriptors. - pub fn endpoint_descriptors(&self) -> EndpointDescriptors { + pub fn endpoint_descriptors(&self) -> EndpointDescriptors<'a> { let endpoints = unsafe { slice::from_raw_parts( self.descriptor.endpoint, From d3c6d493d2bf6f485f65617417cbda5462362a18 Mon Sep 17 00:00:00 2001 From: Curtis Malainey Date: Sat, 29 Oct 2022 12:21:38 -0700 Subject: [PATCH 03/30] Fix timeout documentation Documentation specifies micros, but libusb consumes millis. Therefore passing in 1 micro results in an infinite timeout, not the shortest timeout. --- src/device_handle.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/device_handle.rs b/src/device_handle.rs index 5682767..8052fea 100644 --- a/src/device_handle.rs +++ b/src/device_handle.rs @@ -314,7 +314,8 @@ impl DeviceHandle { /// /// This function attempts to read from the interrupt endpoint with the address given by the /// `endpoint` parameter and fills `buf` with any data received from the endpoint. The function - /// blocks up to the amount of time specified by `timeout`. Minimal `timeout` is 1 microseconds. + /// blocks up to the amount of time specified by `timeout`. Minimal `timeout` is 1 milliseconds, + /// anything smaller will result in an infinite block. /// /// If the return value is `Ok(n)`, then `buf` is populated with `n` bytes of data received /// from the endpoint. @@ -369,7 +370,8 @@ impl DeviceHandle { /// /// This function attempts to write the contents of `buf` to the interrupt endpoint with the /// address given by the `endpoint` parameter. The function blocks up to the amount of time - /// specified by `timeout`. Minimal `timeout` is 1 microseconds. + /// specified by `timeout`. Minimal `timeout` is 1 milliseconds, anything smaller will + /// result in an infinite block. /// /// If the return value is `Ok(n)`, then `n` bytes of `buf` were written to the endpoint. /// @@ -422,7 +424,8 @@ impl DeviceHandle { /// /// This function attempts to read from the bulk endpoint with the address given by the /// `endpoint` parameter and fills `buf` with any data received from the endpoint. The function - /// blocks up to the amount of time specified by `timeout`. Minimal `timeout` is 1 microseconds. + /// blocks up to the amount of time specified by `timeout`. Minimal `timeout` is 1 milliseconds, + /// anything smaller will result in an infinite block. /// /// If the return value is `Ok(n)`, then `buf` is populated with `n` bytes of data received /// from the endpoint. @@ -477,7 +480,8 @@ impl DeviceHandle { /// /// This function attempts to write the contents of `buf` to the bulk endpoint with the address /// given by the `endpoint` parameter. The function blocks up to the amount of time specified - /// by `timeout`. Minimal `timeout` is 1 microseconds. + /// by `timeout`. Minimal `timeout` is 1 milliseconds, anything smaller will result in an + /// infinite block. /// /// If the return value is `Ok(n)`, then `n` bytes of `buf` were written to the endpoint. /// @@ -525,7 +529,8 @@ impl DeviceHandle { /// /// This function attempts to read data from the device using a control transfer and fills /// `buf` with any data received during the transfer. The function blocks up to the amount of - /// time specified by `timeout`. Minimal `timeout` is 1 microseconds. + /// time specified by `timeout`. Minimal `timeout` is 1 milliseconds, anything smaller will + /// result in an infinite block. /// /// The parameters `request_type`, `request`, `value`, and `index` specify the fields of the /// control transfer setup packet (`bmRequestType`, `bRequest`, `wValue`, and `wIndex` @@ -584,7 +589,7 @@ impl DeviceHandle { /// /// This function attempts to write the contents of `buf` to the device using a control /// transfer. The function blocks up to the amount of time specified by `timeout`. - /// Minimal `timeout` is 1 microseconds. + /// Minimal `timeout` is 1 milliseconds, anything smaller will result in an infinite block. /// /// The parameters `request_type`, `request`, `value`, and `index` specify the fields of the /// control transfer setup packet (`bmRequestType`, `bRequest`, `wValue`, and `wIndex` From aba20254d8adbe62e7ab892594d86ed84f8ef2fc Mon Sep 17 00:00:00 2001 From: alufers Date: Mon, 27 Feb 2023 19:43:46 +0100 Subject: [PATCH 04/30] fix: Add missing fields to libusb_bos_descriptor and libusb_bos_dev_capability_descriptor This fix adds missing fields to these two structs so that they can be used from rust without the size mismatching from the C version. See: https://libusb.sourceforge.io/api-1.0/structlibusb__bos__descriptor.html https://libusb.sourceforge.io/api-1.0/structlibusb__bos__dev__capability__descriptor.html --- libusb1-sys/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libusb1-sys/src/lib.rs b/libusb1-sys/src/lib.rs index 5cfb1db..a9ce629 100644 --- a/libusb1-sys/src/lib.rs +++ b/libusb1-sys/src/lib.rs @@ -126,6 +126,7 @@ pub struct libusb_bos_dev_capability_descriptor { pub bLength: u8, pub bDescriptorType: u8, pub bDevCapabilityType: u8, + pub dev_capability_data: [u8; 0], } #[allow(non_snake_case)] @@ -135,6 +136,7 @@ pub struct libusb_bos_descriptor { pub bDescriptorType: u8, pub wTotalLength: u16, pub bNumDeviceCaps: u8, + pub dev_capability: [libusb_bos_dev_capability_descriptor; 0], } #[allow(non_snake_case)] From 6dd18d54b4d724d607d4c48593d214f11cc3d754 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sun, 26 Mar 2023 12:10:32 +0300 Subject: [PATCH 05/30] Update usb-ids dev dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index daf70fa..12544f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,4 @@ libc = "0.2" [dev-dependencies] regex = "1" -usb-ids = "0.2.2" +usb-ids = "1.2023.0" From 85a3246b3781235fdbdf06ad16a8bf2b6f362e78 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sun, 26 Mar 2023 12:20:23 +0300 Subject: [PATCH 06/30] New release of rusb 0.9.2 --- CHANGELOG.md | 24 +++++++++++++++++++++++- Cargo.toml | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17af5ed..3d09585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # Changes -## unreleased +## 0.9.2 +* Random corrections around the code [#127] +* examples: list_devices: Add vendor and product name [#128] +* examples: read_devices: Improve usage [#125] +* context: create rusb `Context` from existing `libusb_context` [#135] +* `new` now uses `from_raw` [#135] +* Fix stack use after scope in tests [#138] +* Fix United Kingdom misspelling in languages docs [#137] +* fields.rs: Make request_type function a const fn [#142] +* Increase endpoint descriptor's lifetime [#149] +* Fix timeout documentation [#151] + +[#127]: https://github.com/a1ien/rusb/pull/116 +[#128]: https://github.com/a1ien/rusb/pull/116 +[#125]: https://github.com/a1ien/rusb/pull/116 +[#135]: https://github.com/a1ien/rusb/pull/116 +[#138]: https://github.com/a1ien/rusb/pull/116 +[#137]: https://github.com/a1ien/rusb/pull/116 +[#142]: https://github.com/a1ien/rusb/pull/116 +[#149]: https://github.com/a1ien/rusb/pull/116 +[#151]: https://github.com/a1ien/rusb/pull/116 + +## 0.9.1 * impl Ord and PartialOrd for Version [#116] [#116]: https://github.com/a1ien/rusb/pull/116 diff --git a/Cargo.toml b/Cargo.toml index 12544f9..5799173 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusb" -version = "0.9.1" +version = "0.9.2" authors = ["David Cuddeback ", "Ilya Averyanov "] description = "Rust library for accessing USB devices." license = "MIT" From b1175d629259fbca51c8cc5919ddab90c275579e Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sun, 26 Mar 2023 12:35:26 +0300 Subject: [PATCH 07/30] Update checkout@v2 to checkout@v3 --- .github/workflows/github-ci.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/github-ci.yml b/.github/workflows/github-ci.yml index 51b4b75..d381d57 100644 --- a/.github/workflows/github-ci.yml +++ b/.github/workflows/github-ci.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] env: CARGO_TERM_COLOR: always @@ -13,18 +13,18 @@ jobs: check-code: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true - - name: Install - run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libusb-1.0-0-dev + - name: Install + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libusb-1.0-0-dev - - name: Run check and format - run: | - cargo check --all-targets --examples - cargo fmt --check + - name: Run check and format + run: | + cargo check --all-targets --examples + cargo fmt --check build: needs: [check-code] @@ -34,12 +34,12 @@ jobs: matrix: include: - os: ubuntu-latest - apt: 'libusb-1.0-0-dev' + apt: "libusb-1.0-0-dev" experimental: false features: "" - os: ubuntu-latest - apt: 'libudev-dev' + apt: "libudev-dev" experimental: false features: "" @@ -67,7 +67,7 @@ jobs: features: "" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true From 6b8361a177b6806dfe9f4cc6ba75831614db0f58 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sun, 26 Mar 2023 12:56:43 +0300 Subject: [PATCH 08/30] Fix changelog urls --- CHANGELOG.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d09585..1e9fbd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,15 @@ * Increase endpoint descriptor's lifetime [#149] * Fix timeout documentation [#151] -[#127]: https://github.com/a1ien/rusb/pull/116 -[#128]: https://github.com/a1ien/rusb/pull/116 -[#125]: https://github.com/a1ien/rusb/pull/116 -[#135]: https://github.com/a1ien/rusb/pull/116 -[#138]: https://github.com/a1ien/rusb/pull/116 -[#137]: https://github.com/a1ien/rusb/pull/116 -[#142]: https://github.com/a1ien/rusb/pull/116 -[#149]: https://github.com/a1ien/rusb/pull/116 -[#151]: https://github.com/a1ien/rusb/pull/116 +[#127]: https://github.com/a1ien/rusb/pull/127 +[#128]: https://github.com/a1ien/rusb/pull/128 +[#125]: https://github.com/a1ien/rusb/pull/125 +[#135]: https://github.com/a1ien/rusb/pull/135 +[#138]: https://github.com/a1ien/rusb/pull/135 +[#137]: https://github.com/a1ien/rusb/pull/137 +[#142]: https://github.com/a1ien/rusb/pull/142 +[#149]: https://github.com/a1ien/rusb/pull/149 +[#151]: https://github.com/a1ien/rusb/pull/151 ## 0.9.1 * impl Ord and PartialOrd for Version [#116] From 7b0703d71a425205ccc94d565e062dc612f13fd7 Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 10 Feb 2023 10:19:20 +0100 Subject: [PATCH 09/30] Add a function to disable device discovery --- src/lib.rs | 2 ++ src/options.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 85d83bc..10165d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ pub use libusb1_sys as ffi; pub use libusb1_sys::constants; +#[cfg(unix)] +pub use crate::options::disable_device_discovery; pub use crate::{ config_descriptor::{ConfigDescriptor, Interfaces}, context::{Context, GlobalContext, LogLevel, UsbContext}, diff --git a/src/options.rs b/src/options.rs index 29201ec..c527bd8 100644 --- a/src/options.rs +++ b/src/options.rs @@ -37,3 +37,21 @@ enum OptionInner { #[cfg_attr(not(windows), allow(dead_code))] // only constructed on Windows UseUsbdk, } + +/// Disable device scanning in `libusb` init. +/// +/// Hotplug functionality will also be deactivated. +/// +/// This is a Linux only option and it must be set before any [`Context`] +/// creation. +/// +/// The option is useful in combination with [`Context::open_device_with_fd()`], +/// which can access a device directly without prior device scanning. +#[cfg(unix)] +pub fn disable_device_discovery() -> crate::Result<()> { + try_unsafe!(libusb1_sys::libusb_set_option( + std::ptr::null_mut(), + LIBUSB_OPTION_NO_DEVICE_DISCOVERY + )); + Ok(()) +} From 2f3007bb5b558a8c51ba5ca19f8aadbbec551de9 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev <8925621+elpiel@users.noreply.github.com> Date: Sat, 1 Apr 2023 21:00:48 +0300 Subject: [PATCH 10/30] fix: 404 link for languages --- src/language.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language.rs b/src/language.rs index 0354ffc..4fec0d1 100644 --- a/src/language.rs +++ b/src/language.rs @@ -17,7 +17,7 @@ impl Language { /// Returns the language's 16-bit `LANGID`. /// /// Each language's `LANGID` is defined by the USB forum - /// . + /// . pub fn lang_id(self) -> u16 { self.raw } From d0851e345e04bee31ab46fa1f84f72851af7861c Mon Sep 17 00:00:00 2001 From: Oystein Tveit Date: Tue, 20 Jun 2023 15:34:54 +0200 Subject: [PATCH 11/30] Add serde impls for enums --- Cargo.toml | 1 + src/error.rs | 4 ++++ src/fields.rs | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 5799173..f071f8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = ["libusb1-sys"] [dependencies] libusb1-sys = { path = "libusb1-sys", version = "0.6.0" } libc = "0.2" +serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] regex = "1" diff --git a/src/error.rs b/src/error.rs index 5ce466a..199b483 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,8 @@ use std::{fmt, result}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use libusb1_sys::constants::*; /// A result of a function that may return a `Error`. @@ -7,6 +10,7 @@ pub type Result = result::Result; /// Errors returned by the `libusb` library. #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Error { /// Input/output error. Io, diff --git a/src/fields.rs b/src/fields.rs index 1749c0a..2ead891 100644 --- a/src/fields.rs +++ b/src/fields.rs @@ -1,10 +1,14 @@ use libc::c_int; use libusb1_sys::constants::*; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// Device speeds. Indicates the speed at which a device is operating. /// - [libusb_supported_speed](http://libusb.sourceforge.net/api-1.0/group__libusb__dev.html#ga1454797ecc0de4d084c1619c420014f6) /// - [USB release versions](https://en.wikipedia.org/wiki/USB#Release_versions) #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] pub enum Speed { /// The operating system doesn't know the device speed. @@ -41,6 +45,7 @@ pub(crate) fn speed_from_libusb(n: c_int) -> Speed { /// Transfer and endpoint directions. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Direction { /// Direction for read (device to host) transfers. In, @@ -51,6 +56,7 @@ pub enum Direction { /// An endpoint's transfer type. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TransferType { /// Control endpoint. Control, @@ -67,6 +73,7 @@ pub enum TransferType { /// Isochronous synchronization mode. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum SyncType { /// No synchronisation. NoSync, @@ -83,6 +90,7 @@ pub enum SyncType { /// Isochronous usage type. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum UsageType { /// Data endpoint. Data, @@ -99,6 +107,7 @@ pub enum UsageType { /// Types of control transfers. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum RequestType { /// Requests that are defined by the USB standard. Standard, @@ -115,6 +124,7 @@ pub enum RequestType { /// Recipients of control transfers. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Recipient { /// The recipient is a device. Device, @@ -144,6 +154,7 @@ pub enum Recipient { /// The intended use case of `Version` is to extract meaning from the version fields in USB /// descriptors, such as `bcdUSB` and `bcdDevice` in device descriptors. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Version(pub u8, pub u8, pub u8); impl Version { From f9e6d05074ff83f1383f7f0b802bf6abb2762d06 Mon Sep 17 00:00:00 2001 From: Oystein Tveit Date: Wed, 23 Aug 2023 13:17:16 +0200 Subject: [PATCH 12/30] Prepare release rusb 0.9.3 --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9fbd3..18e8d5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changes +## 0.9.3 +* impl serde::{Serialize, Deserialize} for public enums [#167] +* Update deprecated doc link about language identifiers [#165] +* Fix changelog URLs for 0.9.2 [#164] + + +[#167]: https://github.com/a1ien/rusb/pull/167 +[#165]: https://github.com/a1ien/rusb/pull/165 +[#164]: https://github.com/a1ien/rusb/pull/164 + ## 0.9.2 * Random corrections around the code [#127] * examples: list_devices: Add vendor and product name [#128] diff --git a/Cargo.toml b/Cargo.toml index f071f8e..4339e6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusb" -version = "0.9.2" +version = "0.9.3" authors = ["David Cuddeback ", "Ilya Averyanov "] description = "Rust library for accessing USB devices." license = "MIT" From d1fa27c9110fbea945d841c116de8c21e160d4bb Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Fri, 25 Aug 2023 20:05:04 +0300 Subject: [PATCH 13/30] bump libusb1-sys to 0.6.4 in rusb --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4339e6e..427331e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ vendored = [ "libusb1-sys/vendored" ] members = ["libusb1-sys"] [dependencies] -libusb1-sys = { path = "libusb1-sys", version = "0.6.0" } +libusb1-sys = { path = "libusb1-sys", version = "0.6.4" } libc = "0.2" serde = { version = "1.0", features = ["derive"], optional = true } From 03d987373ebfced82e8c61b97a3585dd3e5eba10 Mon Sep 17 00:00:00 2001 From: John Whittington Date: Mon, 13 Nov 2023 17:44:07 +0100 Subject: [PATCH 14/30] bLength, bDescriptorType and wTotalLength to descriptors --- examples/list_devices.rs | 18 ++++++++++++++++++ src/config_descriptor.rs | 15 +++++++++++++++ src/device_descriptor.rs | 10 ++++++++++ src/endpoint_descriptor.rs | 10 ++++++++++ src/interface_descriptor.rs | 10 ++++++++++ 5 files changed, 63 insertions(+) diff --git a/examples/list_devices.rs b/examples/list_devices.rs index 5a8b749..1ae4950 100644 --- a/examples/list_devices.rs +++ b/examples/list_devices.rs @@ -94,6 +94,8 @@ fn print_device(device_desc: &DeviceDescriptor, handle: &mut Opti }; println!("Device Descriptor:"); + println!(" bLength {:3}", device_desc.length()); + println!(" bDescriptorType {:3}", device_desc.descriptor_type()); println!( " bcdUSB {:2}.{}{}", device_desc.usb_version().major(), @@ -147,6 +149,12 @@ fn print_device(device_desc: &DeviceDescriptor, handle: &mut Opti fn print_config(config_desc: &ConfigDescriptor, handle: &mut Option>) { println!(" Config Descriptor:"); + println!(" bLength {:3}", config_desc.length()); + println!( + " bDescriptorType {:3}", + config_desc.descriptor_type() + ); + println!(" wTotalLength {:#06x}", config_desc.total_length()); println!( " bNumInterfaces {:3}", config_desc.num_interfaces() @@ -177,6 +185,11 @@ fn print_interface( handle: &mut Option>, ) { println!(" Interface Descriptor:"); + println!(" bLength {:3}", interface_desc.length()); + println!( + " bDescriptorType {:3}", + interface_desc.descriptor_type() + ); println!( " bInterfaceNumber {:3}", interface_desc.interface_number() @@ -219,6 +232,11 @@ fn print_interface( fn print_endpoint(endpoint_desc: &EndpointDescriptor) { println!(" Endpoint Descriptor:"); + println!(" bLength {:3}", endpoint_desc.length()); + println!( + " bDescriptorType {:3}", + endpoint_desc.descriptor_type() + ); println!( " bEndpointAddress {:#04x} EP {} {:?}", endpoint_desc.address(), diff --git a/src/config_descriptor.rs b/src/config_descriptor.rs index f5cee6d..7f66c11 100644 --- a/src/config_descriptor.rs +++ b/src/config_descriptor.rs @@ -21,6 +21,21 @@ unsafe impl Sync for ConfigDescriptor {} unsafe impl Send for ConfigDescriptor {} impl ConfigDescriptor { + /// Returns the size of the descriptor in bytes + pub fn length(&self) -> u8 { + unsafe { (*self.descriptor).bLength } + } + + /// Returns the total length in bytes of data returned for this configuration: all interfaces and endpoints + pub fn total_length(&self) -> u16 { + unsafe { (*self.descriptor).wTotalLength } + } + + /// Returns the descriptor type + pub fn descriptor_type(&self) -> u8 { + unsafe { (*self.descriptor).bDescriptorType } + } + /// Returns the configuration number. pub fn number(&self) -> u8 { unsafe { (*self.descriptor).bConfigurationValue } diff --git a/src/device_descriptor.rs b/src/device_descriptor.rs index 60a3582..70c4bed 100644 --- a/src/device_descriptor.rs +++ b/src/device_descriptor.rs @@ -10,6 +10,16 @@ pub struct DeviceDescriptor { } impl DeviceDescriptor { + /// Returns the size of the descriptor in bytes + pub fn length(&self) -> u8 { + self.descriptor.bLength + } + + /// Returns the descriptor type + pub fn descriptor_type(&self) -> u8 { + self.descriptor.bDescriptorType + } + /// Returns the device's maximum supported USB version. pub fn usb_version(&self) -> Version { Version::from_bcd(self.descriptor.bcdUSB) diff --git a/src/endpoint_descriptor.rs b/src/endpoint_descriptor.rs index 16ae220..cefa70e 100644 --- a/src/endpoint_descriptor.rs +++ b/src/endpoint_descriptor.rs @@ -10,6 +10,16 @@ pub struct EndpointDescriptor<'a> { } impl<'a> EndpointDescriptor<'a> { + /// Returns the size of the descriptor in bytes + pub fn length(&self) -> u8 { + self.descriptor.bLength + } + + /// Returns the descriptor type + pub fn descriptor_type(&self) -> u8 { + self.descriptor.bDescriptorType + } + /// Returns the endpoint's address. pub fn address(&self) -> u8 { self.descriptor.bEndpointAddress diff --git a/src/interface_descriptor.rs b/src/interface_descriptor.rs index b683ca0..eb6419f 100644 --- a/src/interface_descriptor.rs +++ b/src/interface_descriptor.rs @@ -51,6 +51,16 @@ pub struct InterfaceDescriptor<'a> { } impl<'a> InterfaceDescriptor<'a> { + /// Returns the size of the descriptor in bytes + pub fn length(&self) -> u8 { + self.descriptor.bLength + } + + /// Returns the descriptor type + pub fn descriptor_type(&self) -> u8 { + self.descriptor.bDescriptorType + } + /// Returns the interface's number. pub fn interface_number(&self) -> u8 { self.descriptor.bInterfaceNumber From 98641f06ced66723816452453aa188dc337e7d3f Mon Sep 17 00:00:00 2001 From: Sebastian Urban Date: Mon, 13 Nov 2023 18:40:26 +0100 Subject: [PATCH 15/30] Use &self reference for all DeviceHandle methods Change the following methods to accept a non-exclusive reference: - DeviceHandle::set_active_configuration - DeviceHandle::unconfigure - DeviceHandle::reset - DeviceHandle::clear_halt - DeviceHandle::detach_kernel_driver - DeviceHandle::attach_kernel_driver - DeviceHandle::set_auto_detach_kernel_driver - DeviceHandle::claim_interface - DeviceHandle::release_interface - DeviceHandle::set_alternate_setting This is safe to do because libusb is thread-safe. Fixes #148 --- src/device_handle.rs | 47 +++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/device_handle.rs b/src/device_handle.rs index 8052fea..3bf19ba 100644 --- a/src/device_handle.rs +++ b/src/device_handle.rs @@ -2,6 +2,7 @@ use std::{ fmt::{self, Debug}, mem, ptr::NonNull, + sync::Mutex, time::Duration, }; @@ -109,18 +110,18 @@ impl<'a> Iterator for ClaimedInterfacesIter<'a> { } /// A handle to an open USB device. -#[derive(Eq, PartialEq)] pub struct DeviceHandle { context: T, handle: Option>, - interfaces: ClaimedInterfaces, + interfaces: Mutex, } impl Drop for DeviceHandle { /// Closes the device. fn drop(&mut self) { unsafe { - for iface in self.interfaces.iter() { + let interfaces = self.interfaces.lock().unwrap(); + for iface in interfaces.iter() { libusb_release_interface(self.as_raw(), iface as c_int); } @@ -139,11 +140,21 @@ impl Debug for DeviceHandle { f.debug_struct("DeviceHandle") .field("device", &self.device()) .field("handle", &self.handle) - .field("interfaces", &self.interfaces) + .field("interfaces", &*self.interfaces.lock().unwrap()) .finish() } } +impl PartialEq for DeviceHandle { + fn eq(&self, other: &Self) -> bool { + self.context == other.context + && self.handle == other.handle + && *self.interfaces.lock().unwrap() == *other.interfaces.lock().unwrap() + } +} + +impl Eq for DeviceHandle {} + impl DeviceHandle { /// Get the raw libusb_device_handle pointer, for advanced use in unsafe code. /// @@ -164,7 +175,7 @@ impl DeviceHandle { /// /// Panics if you have any claimed interfaces on this handle. pub fn into_raw(mut self) -> *mut libusb_device_handle { - assert_eq!(self.interfaces.size(), 0); + assert_eq!(self.interfaces.lock().unwrap().size(), 0); match self.handle.take() { Some(it) => it.as_ptr(), _ => unreachable!(), @@ -197,7 +208,7 @@ impl DeviceHandle { DeviceHandle { context, handle: Some(handle), - interfaces: ClaimedInterfaces::new(), + interfaces: Mutex::new(ClaimedInterfaces::new()), } } @@ -210,25 +221,25 @@ impl DeviceHandle { } /// Sets the device's active configuration. - pub fn set_active_configuration(&mut self, config: u8) -> crate::Result<()> { + pub fn set_active_configuration(&self, config: u8) -> crate::Result<()> { try_unsafe!(libusb_set_configuration(self.as_raw(), c_int::from(config))); Ok(()) } /// Puts the device in an unconfigured state. - pub fn unconfigure(&mut self) -> crate::Result<()> { + pub fn unconfigure(&self) -> crate::Result<()> { try_unsafe!(libusb_set_configuration(self.as_raw(), -1)); Ok(()) } /// Resets the device. - pub fn reset(&mut self) -> crate::Result<()> { + pub fn reset(&self) -> crate::Result<()> { try_unsafe!(libusb_reset_device(self.as_raw())); Ok(()) } /// Clear the halt/stall condition for an endpoint. - pub fn clear_halt(&mut self, endpoint: u8) -> crate::Result<()> { + pub fn clear_halt(&self, endpoint: u8) -> crate::Result<()> { try_unsafe!(libusb_clear_halt(self.as_raw(), endpoint)); Ok(()) } @@ -247,7 +258,7 @@ impl DeviceHandle { /// Detaches an attached kernel driver from the device. /// /// This method is not supported on all platforms. - pub fn detach_kernel_driver(&mut self, iface: u8) -> crate::Result<()> { + pub fn detach_kernel_driver(&self, iface: u8) -> crate::Result<()> { try_unsafe!(libusb_detach_kernel_driver( self.as_raw(), c_int::from(iface) @@ -258,7 +269,7 @@ impl DeviceHandle { /// Attaches a kernel driver to the device. /// /// This method is not supported on all platforms. - pub fn attach_kernel_driver(&mut self, iface: u8) -> crate::Result<()> { + pub fn attach_kernel_driver(&self, iface: u8) -> crate::Result<()> { try_unsafe!(libusb_attach_kernel_driver( self.as_raw(), c_int::from(iface) @@ -275,7 +286,7 @@ impl DeviceHandle { /// On platforms which do not have support, this function will /// return `Error::NotSupported`, and rusb will continue as if /// this function was never called. - pub fn set_auto_detach_kernel_driver(&mut self, auto_detach: bool) -> crate::Result<()> { + pub fn set_auto_detach_kernel_driver(&self, auto_detach: bool) -> crate::Result<()> { try_unsafe!(libusb_set_auto_detach_kernel_driver( self.as_raw(), auto_detach.into() @@ -287,21 +298,21 @@ impl DeviceHandle { /// /// An interface must be claimed before operating on it. All claimed interfaces are released /// when the device handle goes out of scope. - pub fn claim_interface(&mut self, iface: u8) -> crate::Result<()> { + pub fn claim_interface(&self, iface: u8) -> crate::Result<()> { try_unsafe!(libusb_claim_interface(self.as_raw(), c_int::from(iface))); - self.interfaces.insert(iface); + self.interfaces.lock().unwrap().insert(iface); Ok(()) } /// Releases a claimed interface. - pub fn release_interface(&mut self, iface: u8) -> crate::Result<()> { + pub fn release_interface(&self, iface: u8) -> crate::Result<()> { try_unsafe!(libusb_release_interface(self.as_raw(), c_int::from(iface))); - self.interfaces.remove(iface); + self.interfaces.lock().unwrap().remove(iface); Ok(()) } /// Sets an interface's active setting. - pub fn set_alternate_setting(&mut self, iface: u8, setting: u8) -> crate::Result<()> { + pub fn set_alternate_setting(&self, iface: u8, setting: u8) -> crate::Result<()> { try_unsafe!(libusb_set_interface_alt_setting( self.as_raw(), c_int::from(iface), From 4b1a7aa483f43957834cb13972f4ded51596829e Mon Sep 17 00:00:00 2001 From: Sprite Tong Date: Tue, 16 Jan 2024 19:12:09 +0800 Subject: [PATCH 16/30] Support pkg_config for MSVC. --- libusb1-sys/build.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libusb1-sys/build.rs b/libusb1-sys/build.rs index ede5219..0fcad31 100644 --- a/libusb1-sys/build.rs +++ b/libusb1-sys/build.rs @@ -24,8 +24,12 @@ fn find_libusb_pkg(_statik: bool) -> bool { match vcpkg::Config::new().find_package("libusb") { Ok(_) => true, Err(e) => { - println!("Can't find libusb pkg: {:?}", e); - false + if pkg_config::probe_library("libusb-1.0").is_ok() { + true + } else { + println!("Can't find libusb pkg: {:?}", e); + false + } } } } From 307a31518fa44125d7d56d26048c904c5f5fda21 Mon Sep 17 00:00:00 2001 From: "L. E. Segovia" Date: Fri, 25 Aug 2023 22:04:23 -0300 Subject: [PATCH 17/30] Fix package detection and build when cross-compiling from MSVC to GNU I and @dragonmux found two issues when building libusb from a MSVC host to a MinGW target. When the build.rs script is built for the MSVC host, - the vcpkg crate is used for detection (which correctly detects the MinGW target and reports an unsupported ABI) - a MSVC flag is applied to the build (and thus breaking the build altogether) This commit fixes both issues by changing vcpkg to a Windows-wide dependency, and then if CARGO_CFG_TARGET_ENV is indeed MSVC, it is used and the /source-charset:utf-8 flag is applied. --- libusb1-sys/Cargo.toml | 2 +- libusb1-sys/build.rs | 35 +++++++++++++++++------------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/libusb1-sys/Cargo.toml b/libusb1-sys/Cargo.toml index 519accf..2612e78 100644 --- a/libusb1-sys/Cargo.toml +++ b/libusb1-sys/Cargo.toml @@ -39,7 +39,7 @@ vendored = [] [dependencies] libc = "0.2" -[target.'cfg(target_env = "msvc")'.build-dependencies] +[target.'cfg(target_os = "windows")'.build-dependencies] vcpkg = "0.2" [build-dependencies] diff --git a/libusb1-sys/build.rs b/libusb1-sys/build.rs index 0fcad31..e3b16d2 100644 --- a/libusb1-sys/build.rs +++ b/libusb1-sys/build.rs @@ -19,21 +19,6 @@ pub fn link_framework(name: &str) { println!("cargo:rustc-link-lib=framework={}", name); } -#[cfg(target_env = "msvc")] -fn find_libusb_pkg(_statik: bool) -> bool { - match vcpkg::Config::new().find_package("libusb") { - Ok(_) => true, - Err(e) => { - if pkg_config::probe_library("libusb-1.0").is_ok() { - true - } else { - println!("Can't find libusb pkg: {:?}", e); - false - } - } - } -} - fn get_macos_major_version() -> Option { if !cfg!(target_os = "macos") { return None; @@ -49,8 +34,21 @@ fn get_macos_major_version() -> Option { Some(major) } -#[cfg(not(target_env = "msvc"))] fn find_libusb_pkg(statik: bool) -> bool { + if std::env::var("CARGO_CFG_TARGET_ENV") == Ok("msvc".into()) { + #[cfg(target_os = "windows")] + return match vcpkg::Config::new().find_package("libusb") { + Ok(_) => true, + Err(e) => { + if pkg_config::probe_library("libusb-1.0").is_ok() { + true + } else { + println!("Can't find libusb pkg: {:?}", e); + false + } + } + }; + } // https://github.com/rust-lang/rust/issues/96943 let needs_rustc_issue_96943_workaround: bool = get_macos_major_version() .map(|major| major >= 11) @@ -175,8 +173,9 @@ fn make_source() { } if std::env::var("CARGO_CFG_TARGET_OS") == Ok("windows".into()) { - #[cfg(target_env = "msvc")] - base_config.flag("/source-charset:utf-8"); + if std::env::var("CARGO_CFG_TARGET_ENV") == Ok("msvc".into()) { + base_config.flag("/source-charset:utf-8"); + } base_config.warnings(false); base_config.define("OS_WINDOWS", Some("1")); From 7f5023e71443273eb33fa8a6c86c8a9d67db1496 Mon Sep 17 00:00:00 2001 From: Dmitry Zakablukov Date: Wed, 28 Feb 2024 12:12:42 +0300 Subject: [PATCH 18/30] Log callback API added --- src/context.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/context.rs b/src/context.rs index 07efe87..2add6b3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,9 @@ -use libc::{c_int, timeval}; +use libc::{c_char, c_int, c_void, timeval}; -use std::{cmp::Ordering, mem, ptr, sync::Arc, sync::Once, time::Duration}; +use std::{ + cmp::Ordering, ffi::CStr, mem, ptr, sync::Arc, sync::Mutex, sync::Once, sync::OnceLock, + time::Duration, +}; #[cfg(unix)] use std::os::unix::io::RawFd; @@ -45,6 +48,43 @@ impl Drop for ContextInner { unsafe impl Sync for Context {} unsafe impl Send for Context {} +type LogCallback = Box; + +struct LogCallbackMap { + map: std::collections::HashMap<*mut libusb_context, LogCallback>, +} + +unsafe impl Sync for LogCallbackMap {} +unsafe impl Send for LogCallbackMap {} + +impl LogCallbackMap { + pub fn new() -> Self { + Self { + map: std::collections::HashMap::new(), + } + } +} + +static LOG_CALLBACK_MAP: OnceLock> = OnceLock::new(); + +extern "system" fn static_log_callback( + context: *mut libusb_context, + level: c_int, + text: *mut c_void, +) { + if let Some(log_callback_map) = LOG_CALLBACK_MAP.get() { + if let Ok(locked_table) = log_callback_map.lock() { + if let Some(logger) = locked_table.map.get(&context) { + let c_str: &CStr = unsafe { CStr::from_ptr(text as *const c_char) }; + let str_slice: &str = c_str.to_str().unwrap_or(""); + let log_message = str_slice.to_owned(); + + logger(LogLevel::from_c_int(level), log_message); + } + } + } +} + pub trait UsbContext: Clone + Sized + Send + Sync { /// Get the raw libusb_context pointer, for advanced use in unsafe code. fn as_raw(&self) -> *mut libusb_context; @@ -104,6 +144,17 @@ pub trait UsbContext: Clone + Sized + Send + Sync { } } + fn set_log_callback(&mut self, log_callback: LogCallback, mode: LogCallbackMode) { + let log_callback_map = LOG_CALLBACK_MAP.get_or_init(|| Mutex::new(LogCallbackMap::new())); + if let Ok(mut locked_table) = log_callback_map.lock() { + locked_table.map.insert(self.as_raw(), log_callback); + } + + unsafe { + libusb_set_log_cb(self.as_raw(), Some(static_log_callback), mode.as_c_int()); + } + } + /// Register a callback to be called on hotplug events. The callback's /// [Hotplug::device_arrived] method is called when a new device is added to /// the bus, and [Hotplug::device_left] is called when it is removed. @@ -292,4 +343,31 @@ impl LogLevel { LogLevel::Debug => LIBUSB_LOG_LEVEL_DEBUG, } } + + fn from_c_int(value: c_int) -> LogLevel { + match value { + LIBUSB_LOG_LEVEL_ERROR => LogLevel::Error, + LIBUSB_LOG_LEVEL_WARNING => LogLevel::Warning, + LIBUSB_LOG_LEVEL_INFO => LogLevel::Info, + LIBUSB_LOG_LEVEL_DEBUG => LogLevel::Debug, + _ => LogLevel::None, + } + } +} + +pub enum LogCallbackMode { + /// Callback function handling all log messages. + Global, + + /// Callback function handling context related log messages. + Context, +} + +impl LogCallbackMode { + fn as_c_int(&self) -> c_int { + match *self { + LogCallbackMode::Global => LIBUSB_LOG_CB_GLOBAL, + LogCallbackMode::Context => LIBUSB_LOG_CB_CONTEXT, + } + } } diff --git a/src/lib.rs b/src/lib.rs index 10165d2..4c16277 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ pub use libusb1_sys::constants; pub use crate::options::disable_device_discovery; pub use crate::{ config_descriptor::{ConfigDescriptor, Interfaces}, - context::{Context, GlobalContext, LogLevel, UsbContext}, + context::{Context, GlobalContext, LogCallbackMode, LogLevel, UsbContext}, device::Device, device_descriptor::DeviceDescriptor, device_handle::DeviceHandle, From 5e8e36e4e3328c5f5a2753b0fac633b2d0ebd0ff Mon Sep 17 00:00:00 2001 From: alufers Date: Tue, 5 Mar 2024 15:41:32 +0100 Subject: [PATCH 19/30] fix: panic when trying to iterate over an interface with zero endpoints Some USB classes (UVC, UAC) provide alternate setting descriptors of interfaces with zero endpoints. Trying to call `endpoint_descriptors` on them caused a panic, because NULL was passed to `slice::from_raw_parts`. A branch was introduced to check for that case and create an iterator over an empty slice. See: https://stackoverflow.com/a/49244874/2948315 --- src/interface_descriptor.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/interface_descriptor.rs b/src/interface_descriptor.rs index eb6419f..d4c64c3 100644 --- a/src/interface_descriptor.rs +++ b/src/interface_descriptor.rs @@ -101,11 +101,9 @@ impl<'a> InterfaceDescriptor<'a> { /// Returns an iterator over the interface's endpoint descriptors. pub fn endpoint_descriptors(&self) -> EndpointDescriptors<'a> { - let endpoints = unsafe { - slice::from_raw_parts( - self.descriptor.endpoint, - self.descriptor.bNumEndpoints as usize, - ) + let endpoints = match self.descriptor.bNumEndpoints { + 0 => &[], + n => unsafe { slice::from_raw_parts(self.descriptor.endpoint, n as usize) }, }; EndpointDescriptors { From 4a69bcae67c09e2f7b9b0ad5e2674167be95d412 Mon Sep 17 00:00:00 2001 From: alufers Date: Sun, 31 Mar 2024 00:31:28 +0100 Subject: [PATCH 20/30] Replace .get_unchecked_mut() with .as_mut_ptr().add() when accessing iso_packet_desc (fixes #199) This prevents panics on debug builds in new rust nightly versions (as of 2024-03-27), since they added bounds checks to `get_unchecked_mut`, which here was called on a zero-length slice representing a flexible array member. See: https://github.com/rust-lang/rust/commit/2b43e75c98cc5ae32328c8b49657bcd882eb5e75 --- libusb1-sys/src/lib.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/libusb1-sys/src/lib.rs b/libusb1-sys/src/lib.rs index 5cfb1db..3268c14 100644 --- a/libusb1-sys/src/lib.rs +++ b/libusb1-sys/src/lib.rs @@ -658,10 +658,7 @@ pub unsafe fn libusb_fill_iso_transfer( #[inline] pub unsafe fn libusb_set_iso_packet_lengths(transfer: *mut libusb_transfer, length: c_uint) { for i in 0..(*transfer).num_iso_packets { - (*transfer) - .iso_packet_desc - .get_unchecked_mut(i as usize) - .length = length; + (*(*transfer).iso_packet_desc.as_mut_ptr().add(i as usize)).length = length; } } @@ -675,10 +672,7 @@ pub unsafe fn libusb_get_iso_packet_buffer( } let mut offset = 0; for i in 0..packet { - offset += (*transfer) - .iso_packet_desc - .get_unchecked_mut(i as usize) - .length; + offset += (*(*transfer).iso_packet_desc.as_mut_ptr().add(i as usize)).length; } (*transfer).buffer.add(offset as usize) @@ -695,5 +689,5 @@ pub unsafe fn libusb_get_iso_packet_buffer_simple( (*transfer) .buffer - .add(((*transfer).iso_packet_desc.get_unchecked_mut(0).length * packet) as usize) + .add(((*(*transfer).iso_packet_desc.as_mut_ptr().add(0)).length * packet) as usize) } From 132128a8358c6b2c7626f98c004c9aa23456aa1f Mon Sep 17 00:00:00 2001 From: alufers Date: Wed, 3 Apr 2024 16:28:40 +0200 Subject: [PATCH 21/30] Bump libusb to 1.0.27 Fixes a few macOS issues --- libusb1-sys/build.rs | 2 +- libusb1-sys/libusb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libusb1-sys/build.rs b/libusb1-sys/build.rs index e3b16d2..bd1f43b 100644 --- a/libusb1-sys/build.rs +++ b/libusb1-sys/build.rs @@ -1,6 +1,6 @@ use std::{env, fs, path::PathBuf}; -static VERSION: &str = "1.0.24"; +static VERSION: &str = "1.0.27"; fn link(name: &str, bundled: bool) { use std::env::var; diff --git a/libusb1-sys/libusb b/libusb1-sys/libusb index 4239bc3..d52e355 160000 --- a/libusb1-sys/libusb +++ b/libusb1-sys/libusb @@ -1 +1 @@ -Subproject commit 4239bc3a50014b8e6a5a2a59df1fff3b7469543b +Subproject commit d52e355daa09f17ce64819122cb067b8a2ee0d4b From bf9381432f990d3fb2a011c539e9593463d98163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaud=20Rouill=C3=A9?= Date: Wed, 17 Apr 2024 09:38:47 +0200 Subject: [PATCH 22/30] Added libusb_free_pollfds() in the available FFI methods. --- libusb1-sys/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libusb1-sys/src/lib.rs b/libusb1-sys/src/lib.rs index 3268c14..be2bdc9 100644 --- a/libusb1-sys/src/lib.rs +++ b/libusb1-sys/src/lib.rs @@ -446,6 +446,7 @@ extern "system" { removed_cb: Option, user_data: *mut c_void, ); + pub fn libusb_free_pollfds(pollfds: *const *mut libusb_pollfd); pub fn libusb_hotplug_register_callback( ctx: *mut libusb_context, events: c_int, From adc50a8a3c170cbd1305c83349f91c0ef4523992 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sat, 27 Apr 2024 17:51:27 +0300 Subject: [PATCH 23/30] Remove unneeded mut --- libusb1-sys/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb1-sys/src/lib.rs b/libusb1-sys/src/lib.rs index 780f83a..b0ac87d 100644 --- a/libusb1-sys/src/lib.rs +++ b/libusb1-sys/src/lib.rs @@ -542,7 +542,7 @@ pub unsafe fn libusb_fill_control_setup( wIndex: u16, wLength: u16, ) { - let mut setup: *mut libusb_control_setup = buffer as *mut _; + let setup: *mut libusb_control_setup = buffer as *mut _; (*setup).bmRequestType = bmRequestType; (*setup).bRequest = bRequest; (*setup).wValue = wValue.to_le(); From 3f58a2327cba1bd0ad337d8a531bff24e232e63f Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sat, 27 Apr 2024 17:55:37 +0300 Subject: [PATCH 24/30] Release rusb 0.9.4 and libusb 0.7.0 libusb 0.7.0 * fix: Add missing fields to libusb_bos_descriptor and libusb_bos_dev_capability_descriptor * Bump libusb to 1.0.27 * Remove unneeded mut rusb 0.9.4 * bLength, bDescriptorType and wTotalLength to descriptors * Use &self reference for all DeviceHandle methods * fix: panic when trying to iterate over an interface with zero endpoints * Log callback API added * Bump libusb1-sys 0.7.0 --- CHANGELOG.md | 14 ++++++++++++++ Cargo.toml | 4 ++-- libusb1-sys/CHANGELOG.md | 37 ++++++++++++++++++++++++++++++++++++- libusb1-sys/Cargo.toml | 2 +- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e8d5d..de4a6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changes +## 0.9.4 + +* bLength, bDescriptorType and wTotalLength to descriptors [#185] +* Use &self reference for all DeviceHandle methods [#186] +* fix: panic when trying to iterate over an interface with zero endpoints [#195] +* Log callback API added [#194] +* Bump libusb1-sys 0.7.0 [#205] + +[#185]: https://github.com/a1ien/rusb/pull/185 +[#186]: https://github.com/a1ien/rusb/pull/186 +[#195]: https://github.com/a1ien/rusb/pull/195 +[#194]: https://github.com/a1ien/rusb/pull/194 +[#205]: https://github.com/a1ien/rusb/pull/205 + ## 0.9.3 * impl serde::{Serialize, Deserialize} for public enums [#167] * Update deprecated doc link about language identifiers [#165] diff --git a/Cargo.toml b/Cargo.toml index 427331e..e18b645 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusb" -version = "0.9.3" +version = "0.9.4" authors = ["David Cuddeback ", "Ilya Averyanov "] description = "Rust library for accessing USB devices." license = "MIT" @@ -21,7 +21,7 @@ vendored = [ "libusb1-sys/vendored" ] members = ["libusb1-sys"] [dependencies] -libusb1-sys = { path = "libusb1-sys", version = "0.6.4" } +libusb1-sys = { path = "libusb1-sys", version = "0.7" } libc = "0.2" serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/libusb1-sys/CHANGELOG.md b/libusb1-sys/CHANGELOG.md index d02576b..8f1acf6 100644 --- a/libusb1-sys/CHANGELOG.md +++ b/libusb1-sys/CHANGELOG.md @@ -1,6 +1,41 @@ # Changes -## unreleased +## 0.7.0 + +* fix: Add missing fields to libusb_bos_descriptor and libusb_bos_dev_capability_descriptor [#161] +* Bump libusb to 1.0.27 [#201] +* Remove unneeded mut [#204] + +[#161]: https://github.com/a1ien/rusb/pull/161 +[#201]: https://github.com/a1ien/rusb/pull/201 +[#204]: https://github.com/a1ien/rusb/pull/204 + +## 0.6.5 +* Support pkg_config for MSVC. [#191] +* Fix package detection and build when cross-compiling from MSVC to GNU [#180] +* libusb_set_iso_packet_lengths panics on debug builds in newest nightly (2024-03-27) [#199] +* Added libusb_free_pollfds() in the available FFI methods. [#203] + +[#191]: https://github.com/a1ien/rusb/pull/191 +[#180]: https://github.com/a1ien/rusb/pull/180 +[#199]: https://github.com/a1ien/rusb/pull/199 +[#203]: https://github.com/a1ien/rusb/pull/203 + +## 0.6.3-0.6.4 +* Patch for macOS Big Sur and newer allowing to link statically [#133] +* Add libudev include paths as specified by pkg-config [#140] + +[#133]: https://github.com/a1ien/rusb/pull/133 +[#140]: https://github.com/a1ien/rusb/pull/140 + + +## 0.6.2 +* Rename compiled library when vendored libusb is used [#130] + +[#130]: https://github.com/a1ien/rusb/pull/130 + +## 0.6.1 +* Add LIBUSB_OPTION_NO_DEVICE_DISCOVERY constant * Bump vendored libusb version from 1.0.24 to 1.0.25 [#119] [#119]: https://github.com/a1ien/rusb/pull/119 diff --git a/libusb1-sys/Cargo.toml b/libusb1-sys/Cargo.toml index 2612e78..c4d4fb7 100644 --- a/libusb1-sys/Cargo.toml +++ b/libusb1-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libusb1-sys" -version = "0.6.4" +version = "0.7.0" authors = ["David Cuddeback ", "Ilya Averyanov "] description = "FFI bindings for libusb." From 8fac5aad8fd1f7df629852091b9197afde316cfd Mon Sep 17 00:00:00 2001 From: Dhruv Gramopadhye Date: Sat, 20 Jul 2024 18:59:13 -0700 Subject: [PATCH 25/30] feat: libusb_init_context and associated structs --- libusb1-sys/src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/libusb1-sys/src/lib.rs b/libusb1-sys/src/lib.rs index b0ac87d..fb73783 100644 --- a/libusb1-sys/src/lib.rs +++ b/libusb1-sys/src/lib.rs @@ -204,6 +204,83 @@ pub struct libusb_pollfd { pub events: c_short, } +#[repr(C)] +pub union libusb_init_option__value { + pub ival: c_int, + pub log_cbval: libusb_log_cb, +} + +#[repr(C)] +pub enum libusb_option { + /// Set the log message verbosity. + /// + /// This option must be provided an argument of type \ref libusb_log_level. + /// The default level is LIBUSB_LOG_LEVEL_NONE, which means no messages are ever + /// printed. If you choose to increase the message verbosity level, ensure + /// that your application does not close the stderr file descriptor. + /// + /// You are advised to use level LIBUSB_LOG_LEVEL_WARNING. libusb is conservative + /// with its message logging and most of the time, will only log messages that + /// explain error conditions and other oddities. This will help you debug + /// your software. + /// + /// If the LIBUSB_DEBUG environment variable was set when libusb was + /// initialized, this option does nothing: the message verbosity is fixed + /// to the value in the environment variable. + /// + /// If libusb was compiled without any message logging, this option does + /// nothing: you'll never get any messages. + /// + /// If libusb was compiled with verbose debug message logging, this option + /// does nothing: you'll always get messages from all levels. + LIBUSB_OPTION_LOG_LEVEL = 0, + + /// Use the UsbDk backend for a specific context, if available. + /// + /// This option should be set at initialization with libusb_init_context() + /// otherwise unspecified behavior may occur. + /// + /// Only valid on Windows. Ignored on all other platforms. + LIBUSB_OPTION_USE_USBDK = 1, + + /// Do not scan for devices. LIBUSB_OPTION_WEAK_AUTHORITY is aliased to this + /// + /// With this option set, libusb will skip scanning devices in + /// libusb_init_context(). + /// + /// Hotplug functionality will also be deactivated. + /// + /// The option is useful in combination with libusb_wrap_sys_device(), + /// which can access a device directly without prior device scanning. + /// + /// This is typically needed on Android, where access to USB devices + /// is limited. + /// + /// This option should only be used with libusb_init_context() + /// otherwise unspecified behavior may occur. + /// + /// Only valid on Linux. Ignored on all other platforms. + LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2, + + /// Set the context log callback function. + /// + /// Set the log callback function either on a context or globally. This + /// option must be provided an argument of type \ref libusb_log_cb. + /// Using this option with a NULL context is equivalent to calling + /// libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_GLOBAL. + /// Using it with a non-NULL context is equivalent to calling + /// libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_CONTEXT. + LIBUSB_OPTION_LOG_CB = 3, + + LIBUSB_OPTION_MAX = 4, +} + +#[repr(C)] +pub struct libusb_init_option { + pub option: libusb_option, + pub value: libusb_init_option__value, +} + pub type libusb_hotplug_callback_handle = c_int; pub type libusb_hotplug_flag = c_int; pub type libusb_hotplug_event = c_int; @@ -230,6 +307,11 @@ extern "system" { pub fn libusb_strerror(errcode: c_int) -> *const c_char; pub fn libusb_init(context: *mut *mut libusb_context) -> c_int; + pub fn libusb_init_context( + context: *mut *mut libusb_context, + options: *mut libusb_init_option, + num_options: c_int, + ) -> c_int; pub fn libusb_exit(context: *mut libusb_context); pub fn libusb_set_debug(context: *mut libusb_context, level: c_int); pub fn libusb_set_log_cb(context: *mut libusb_context, cb: Option, mode: c_int); From 705eaddaa1a57101c7f76d3f4717bb86dbe416f9 Mon Sep 17 00:00:00 2001 From: Dhruv Gramopadhye Date: Sun, 21 Jul 2024 13:55:16 -0700 Subject: [PATCH 26/30] fix: no udev when building for android on linux Android does not have udev, but cross-compiling for Android on a Linux host with udev results in a udev link --- libusb1-sys/build.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libusb1-sys/build.rs b/libusb1-sys/build.rs index bd1f43b..3d05d43 100644 --- a/libusb1-sys/build.rs +++ b/libusb1-sys/build.rs @@ -158,14 +158,16 @@ fn make_source() { Some("__attribute__((visibility(\"default\")))"), ); - if let Ok(lib) = pkg_config::probe_library("libudev") { - base_config.define("USE_UDEV", Some("1")); - base_config.define("HAVE_LIBUDEV", Some("1")); - base_config.file(libusb_source.join("libusb/os/linux_udev.c")); - for path in lib.include_paths { - base_config.include(path.to_str().unwrap()); - } - }; + if std::env::var("CARGO_CFG_TARGET_OS") != Ok("android".into()) { + if let Ok(lib) = pkg_config::probe_library("libudev") { + base_config.define("USE_UDEV", Some("1")); + base_config.define("HAVE_LIBUDEV", Some("1")); + base_config.file(libusb_source.join("libusb/os/linux_udev.c")); + for path in lib.include_paths { + base_config.include(path.to_str().unwrap()); + } + }; + } println!("Including posix!"); base_config.file(libusb_source.join("libusb/os/events_posix.c")); From bfac2ec510850c06721d1fb1b7cedb9a7dc5f0e7 Mon Sep 17 00:00:00 2001 From: Jamin Martin Date: Fri, 31 Jan 2025 13:05:38 +1300 Subject: [PATCH 27/30] Added new constructor for languages + tests --- src/language.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/language.rs b/src/language.rs index 4fec0d1..7ecdd7b 100644 --- a/src/language.rs +++ b/src/language.rs @@ -31,6 +31,22 @@ impl Language { pub fn sub_language(self) -> SubLanguage { SubLanguage::from_raw(self.primary_language(), self.raw) } + /// Creates a new `Language` from a raw 16-bit `LANGID`. + /// + /// The `LANGID` should follow the USB forum's language identifier specification, where + /// the lower 10 bits represent the sub language and the upper 6 bits represent the primary language. + /// + /// # Examples + /// ``` + /// use rusb::{Language,SubLanguage,PrimaryLanguage}; + /// // Create English (United States) language + /// let lang = Language::new(0x0409); + /// assert_eq!(lang.primary_language(), PrimaryLanguage::English); + /// assert_eq!(lang.sub_language(), SubLanguage::UnitedStates); + /// ``` + pub fn new(raw: u16) -> Self { + Language { raw } + } } #[doc(hidden)] @@ -2555,4 +2571,20 @@ mod test { SubLanguage::Other(SUB_LANGUAGE_MASK) ); } + + #[test] + fn it_creates_arabic_from_egypt_with_correct_language_id() { + let arabic_egypt = super::Language::new(ARABIC_EGYPT); + assert_eq!(arabic_egypt.primary_language(), PrimaryLanguage::Arabic); + assert_eq!(arabic_egypt.lang_id(), ARABIC_EGYPT); + assert_eq!(arabic_egypt.sub_language(), SubLanguage::Egypt); + } + + #[test] + fn it_creates_language_from_hex_english() { + let english = super::Language::new(0x0409); // English (United States) + assert_eq!(english.primary_language(), PrimaryLanguage::English); + assert_eq!(english.lang_id(), 0x0409); + assert_eq!(english.sub_language(), SubLanguage::UnitedStates); + } } From c7b62802d066c52d609cb436e40634b01c6107f5 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Sun, 4 Sep 2022 20:10:31 +0300 Subject: [PATCH 28/30] First try rusb-async Co-authored-by: Ryan Butler Co-authored-by: Kevin Mehall --- Cargo.toml | 2 +- rusb-async/Cargo.toml | 22 ++ rusb-async/examples/read_async.rs | 54 +++++ rusb-async/examples/read_write_async.rs | 73 ++++++ rusb-async/src/error.rs | 48 ++++ rusb-async/src/lib.rs | 286 ++++++++++++++++++++++++ rusb-async/src/pool.rs | 183 +++++++++++++++ 7 files changed, 667 insertions(+), 1 deletion(-) create mode 100644 rusb-async/Cargo.toml create mode 100644 rusb-async/examples/read_async.rs create mode 100644 rusb-async/examples/read_write_async.rs create mode 100644 rusb-async/src/error.rs create mode 100644 rusb-async/src/lib.rs create mode 100644 rusb-async/src/pool.rs diff --git a/Cargo.toml b/Cargo.toml index e18b645..67aaefb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ travis-ci = { repository = "a1ien/rusb" } vendored = [ "libusb1-sys/vendored" ] [workspace] -members = ["libusb1-sys"] +members = [ "libusb1-sys", "rusb-async" ] [dependencies] libusb1-sys = { path = "libusb1-sys", version = "0.7" } diff --git a/rusb-async/Cargo.toml b/rusb-async/Cargo.toml new file mode 100644 index 0000000..c58ea2c --- /dev/null +++ b/rusb-async/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rusb-async" +version = "0.0.1-alpha" +edition = "2021" +authors = [ +"Ilya Averyanov ", +"Ryan Butler ", +"Kevin Mehall " +] + +description = "Rust library for accessing USB devices." +license = "MIT" +homepage = "https://github.com/a1ien/rusb" +repository = "https://github.com/a1ien/rusb.git" +keywords = ["usb", "libusb", "async"] + +[features] +vendored = [ "rusb/vendored" ] + +[dependencies] +rusb = { path = "..", version = "0.9.1" } +libc = "0.2" diff --git a/rusb-async/examples/read_async.rs b/rusb-async/examples/read_async.rs new file mode 100644 index 0000000..7685f08 --- /dev/null +++ b/rusb-async/examples/read_async.rs @@ -0,0 +1,54 @@ +use rusb_async::TransferPool; + +use rusb::{Context, UsbContext}; + +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + +fn convert_argument(input: &str) -> u16 { + if input.starts_with("0x") { + return u16::from_str_radix(input.trim_start_matches("0x"), 16).unwrap(); + } + u16::from_str_radix(input, 10) + .expect("Invalid input, be sure to add `0x` for hexadecimal values.") +} + +fn main() { + let args: Vec = std::env::args().collect(); + + if args.len() < 4 { + eprintln!("Usage: read_async "); + return; + } + + let vid = convert_argument(args[1].as_ref()); + let pid = convert_argument(args[2].as_ref()); + let endpoint: u8 = FromStr::from_str(args[3].as_ref()).unwrap(); + + let ctx = Context::new().expect("Could not initialize libusb"); + let device = Arc::new( + ctx.open_device_with_vid_pid(vid, pid) + .expect("Could not find device"), + ); + + const NUM_TRANSFERS: usize = 32; + const BUF_SIZE: usize = 64; + + let mut async_pool = TransferPool::new(device).expect("Failed to create async pool!"); + + while async_pool.pending() < NUM_TRANSFERS { + async_pool + .submit_bulk(endpoint, Vec::with_capacity(BUF_SIZE)) + .expect("Failed to submit transfer"); + } + + let timeout = Duration::from_secs(10); + loop { + let data = async_pool.poll(timeout).expect("Transfer failed"); + println!("Got data: {} {:?}", data.len(), data); + async_pool + .submit_bulk(endpoint, data) + .expect("Failed to resubmit transfer"); + } +} diff --git a/rusb-async/examples/read_write_async.rs b/rusb-async/examples/read_write_async.rs new file mode 100644 index 0000000..4ca8119 --- /dev/null +++ b/rusb-async/examples/read_write_async.rs @@ -0,0 +1,73 @@ +use rusb_async::TransferPool; + +use rusb::{Context, UsbContext}; + +use std::time::Duration; +use std::{sync::Arc, thread}; + +fn main() { + let args: Vec = std::env::args().collect(); + + if args.len() < 5 { + eprintln!("Usage: read_write_async (all numbers hex)"); + return; + } + + let vid = u16::from_str_radix(args[1].as_ref(), 16).unwrap(); + let pid = u16::from_str_radix(args[2].as_ref(), 16).unwrap(); + let out_endpoint = u8::from_str_radix(args[3].as_ref(), 16).unwrap(); + let in_endpoint = u8::from_str_radix(args[4].as_ref(), 16).unwrap(); + + let ctx = Context::new().expect("Could not initialize libusb"); + let device = Arc::new( + ctx.open_device_with_vid_pid(vid, pid) + .expect("Could not find device"), + ); + + thread::spawn({ + let device = device.clone(); + move || { + let mut write_pool = TransferPool::new(device).expect("Failed to create async pool!"); + + let mut i = 0u8; + + loop { + let mut buf = if write_pool.pending() < 8 { + Vec::with_capacity(64) + } else { + write_pool + .poll(Duration::from_secs(5)) + .expect("Failed to poll OUT transfer") + }; + + buf.clear(); + buf.push(i); + buf.resize(64, 0x2); + + write_pool + .submit_bulk(out_endpoint, buf) + .expect("Failed to submit OUT transfer"); + println!("Wrote {}", i); + i = i.wrapping_add(1); + } + } + }); + + let mut read_pool = TransferPool::new(device).expect("Failed to create async pool!"); + + while read_pool.pending() < 8 { + read_pool + .submit_bulk(in_endpoint, Vec::with_capacity(1024)) + .expect("Failed to submit IN transfer"); + } + + loop { + let data = read_pool + .poll(Duration::from_secs(10)) + .expect("Failed to poll IN transfer"); + println!("Got data: {} {:?}", data.len(), data[0]); + read_pool + .submit_bulk(in_endpoint, data) + .expect("Failed to resubmit IN transfer"); + } +} diff --git a/rusb-async/src/error.rs b/rusb-async/src/error.rs new file mode 100644 index 0000000..73fb42c --- /dev/null +++ b/rusb-async/src/error.rs @@ -0,0 +1,48 @@ +use std::{fmt, result}; + +/// A result of a function that may return a `Error`. +pub type Result = result::Result; + +#[derive(Debug)] +pub enum Error { + /// No transfers pending + NoTransfersPending, + + /// Poll timed out + PollTimeout, + + /// Transfer is stalled + Stall, + + /// Device was disconnected + Disconnected, + + /// Device sent more data than expected + Overflow, + + /// Other Error + Other(&'static str), + + /// Error code on other failure + Errno(&'static str, i32), + + /// Transfer was cancelled + Cancelled, +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { + match self { + Error::NoTransfersPending => fmt.write_str("No transfers pending"), + Error::PollTimeout => fmt.write_str("Poll timed out"), + Error::Stall => fmt.write_str("Transfer is stalled"), + Error::Disconnected => fmt.write_str("Device was disconnected"), + Error::Overflow => fmt.write_str("Device sent more data than expected"), + Error::Other(s) => write!(fmt, "Other Error: {s}"), + Error::Errno(s, n) => write!(fmt, "{s} ERRNO: {n}"), + Error::Cancelled => fmt.write_str("Transfer was cancelled"), + } + } +} + +impl std::error::Error for Error {} diff --git a/rusb-async/src/lib.rs b/rusb-async/src/lib.rs new file mode 100644 index 0000000..38b2422 --- /dev/null +++ b/rusb-async/src/lib.rs @@ -0,0 +1,286 @@ +use rusb::ffi::{self, constants::*}; + +use std::convert::TryInto; +use std::ptr::NonNull; + +use std::sync::atomic::{AtomicBool, Ordering}; + +mod error; +mod pool; + +use error::{Error, Result}; + +pub use pool::TransferPool; + +struct Transfer { + ptr: NonNull, + buffer: Vec, +} + +impl Transfer { + // Invariant: Caller must ensure `device` outlives this transfer + unsafe fn bulk( + device: *mut ffi::libusb_device_handle, + endpoint: u8, + mut buffer: Vec, + ) -> Self { + // non-isochronous endpoints (e.g. control, bulk, interrupt) specify a value of 0 + // This is step 1 of async API + + let ptr = + NonNull::new(ffi::libusb_alloc_transfer(0)).expect("Could not allocate transfer!"); + + let user_data = Box::into_raw(Box::new(AtomicBool::new(false))).cast::(); + + let length = if endpoint & ffi::constants::LIBUSB_ENDPOINT_DIR_MASK + == ffi::constants::LIBUSB_ENDPOINT_OUT + { + // for OUT endpoints: the currently valid data in the buffer + buffer.len() + } else { + // for IN endpoints: the full capacity + buffer.capacity() + } + .try_into() + .unwrap(); + + ffi::libusb_fill_bulk_transfer( + ptr.as_ptr(), + device, + endpoint, + buffer.as_mut_ptr(), + length, + Self::transfer_cb, + user_data, + 0, + ); + + Self { ptr, buffer } + } + + // Invariant: Caller must ensure `device` outlives this transfer + unsafe fn control( + device: *mut ffi::libusb_device_handle, + + request_type: u8, + request: u8, + value: u16, + index: u16, + data: &[u8], + ) -> Self { + let mut buf = Vec::with_capacity(data.len() + LIBUSB_CONTROL_SETUP_SIZE); + + let length = data.len() as u16; + + ffi::libusb_fill_control_setup( + buf.as_mut_ptr() as *mut u8, + request_type, + request, + value, + index, + length, + ); + Self::control_raw(device, buf) + } + + // Invariant: Caller must ensure `device` outlives this transfer + unsafe fn control_raw(device: *mut ffi::libusb_device_handle, mut buffer: Vec) -> Self { + // non-isochronous endpoints (e.g. control, bulk, interrupt) specify a value of 0 + // This is step 1 of async API + + let ptr = + NonNull::new(ffi::libusb_alloc_transfer(0)).expect("Could not allocate transfer!"); + + let user_data = Box::into_raw(Box::new(AtomicBool::new(false))).cast::(); + + ffi::libusb_fill_control_transfer( + ptr.as_ptr(), + device, + buffer.as_mut_ptr(), + Self::transfer_cb, + user_data, + 0, + ); + + Self { ptr, buffer } + } + + // Invariant: Caller must ensure `device` outlives this transfer + unsafe fn interrupt( + device: *mut ffi::libusb_device_handle, + endpoint: u8, + mut buffer: Vec, + ) -> Self { + // non-isochronous endpoints (e.g. control, bulk, interrupt) specify a value of 0 + // This is step 1 of async API + + let ptr = + NonNull::new(ffi::libusb_alloc_transfer(0)).expect("Could not allocate transfer!"); + + let user_data = Box::into_raw(Box::new(AtomicBool::new(false))).cast::(); + + let length = if endpoint & ffi::constants::LIBUSB_ENDPOINT_DIR_MASK + == ffi::constants::LIBUSB_ENDPOINT_OUT + { + // for OUT endpoints: the currently valid data in the buffer + buffer.len() + } else { + // for IN endpoints: the full capacity + buffer.capacity() + } + .try_into() + .unwrap(); + + ffi::libusb_fill_interrupt_transfer( + ptr.as_ptr(), + device, + endpoint, + buffer.as_mut_ptr(), + length, + Self::transfer_cb, + user_data, + 0, + ); + + Self { ptr, buffer } + } + + // Invariant: Caller must ensure `device` outlives this transfer + unsafe fn iso( + device: *mut ffi::libusb_device_handle, + endpoint: u8, + mut buffer: Vec, + iso_packets: i32, + ) -> Self { + // isochronous endpoints + // This is step 1 of async API + let ptr = NonNull::new(ffi::libusb_alloc_transfer(iso_packets)) + .expect("Could not allocate transfer!"); + + let user_data = Box::into_raw(Box::new(AtomicBool::new(false))).cast::(); + + let length = if endpoint & ffi::constants::LIBUSB_ENDPOINT_DIR_MASK + == ffi::constants::LIBUSB_ENDPOINT_OUT + { + // for OUT endpoints: the currently valid data in the buffer + buffer.len() + } else { + // for IN endpoints: the full capacity + buffer.capacity() + } + .try_into() + .unwrap(); + + ffi::libusb_fill_iso_transfer( + ptr.as_ptr(), + device, + endpoint, + buffer.as_mut_ptr(), + length, + iso_packets, + Self::transfer_cb, + user_data, + 0, + ); + ffi::libusb_set_iso_packet_lengths(ptr.as_ptr(), (length / iso_packets) as u32); + + Self { ptr, buffer } + } + // Part of step 4 of async API the transfer is finished being handled when + // `poll()` is called. + extern "system" fn transfer_cb(transfer: *mut ffi::libusb_transfer) { + // Safety: transfer is still valid because libusb just completed + // it but we haven't told anyone yet. user_data remains valid + // because it is freed only with the transfer. + // After the store to completed, these may no longer be valid if + // the polling thread freed it after seeing it completed. + let completed = unsafe { + let transfer = &mut *transfer; + &*transfer.user_data.cast::() + }; + completed.store(true, Ordering::SeqCst); + } + + fn transfer(&self) -> &ffi::libusb_transfer { + // Safety: transfer remains valid as long as self + unsafe { self.ptr.as_ref() } + } + + fn completed_flag(&self) -> &AtomicBool { + // Safety: transfer and user_data remain valid as long as self + unsafe { &*self.transfer().user_data.cast::() } + } + + /// Prerequisite: self.buffer ans self.ptr are both correctly set + fn swap_buffer(&mut self, new_buf: Vec) -> Vec { + let transfer_struct = unsafe { self.ptr.as_mut() }; + + let data = std::mem::replace(&mut self.buffer, new_buf); + + // Update transfer struct for new buffer + transfer_struct.actual_length = 0; // TODO: Is this necessary? + transfer_struct.buffer = self.buffer.as_mut_ptr(); + transfer_struct.length = self.buffer.capacity() as i32; + + data + } + + // Step 3 of async API + fn submit(&mut self) -> Result<()> { + self.completed_flag().store(false, Ordering::SeqCst); + let errno = unsafe { ffi::libusb_submit_transfer(self.ptr.as_ptr()) }; + + match errno { + 0 => Ok(()), + LIBUSB_ERROR_NO_DEVICE => Err(Error::Disconnected), + LIBUSB_ERROR_BUSY => { + unreachable!("We shouldn't be calling submit on transfers already submitted!") + } + LIBUSB_ERROR_NOT_SUPPORTED => Err(Error::Other("Transfer not supported")), + LIBUSB_ERROR_INVALID_PARAM => { + Err(Error::Other("Transfer size bigger than OS supports")) + } + _ => Err(Error::Errno("Error while submitting transfer: ", errno)), + } + } + + fn cancel(&mut self) { + unsafe { + ffi::libusb_cancel_transfer(self.ptr.as_ptr()); + } + } + + fn handle_completed(&mut self) -> Result> { + assert!(self.completed_flag().load(Ordering::Relaxed)); + let err = match self.transfer().status { + LIBUSB_TRANSFER_COMPLETED => { + debug_assert!(self.transfer().length >= self.transfer().actual_length); + unsafe { + self.buffer.set_len(self.transfer().actual_length as usize); + } + let data = self.swap_buffer(Vec::new()); + return Ok(data); + } + LIBUSB_TRANSFER_CANCELLED => Error::Cancelled, + LIBUSB_TRANSFER_ERROR => Error::Other("Error occurred during transfer execution"), + LIBUSB_TRANSFER_TIMED_OUT => { + unreachable!("We are using timeout=0 which means no timeout") + } + LIBUSB_TRANSFER_STALL => Error::Stall, + LIBUSB_TRANSFER_NO_DEVICE => Error::Disconnected, + LIBUSB_TRANSFER_OVERFLOW => Error::Overflow, + _ => panic!("Found an unexpected error value for transfer status"), + }; + Err(err) + } +} + +/// Invariant: transfer must not be pending +impl Drop for Transfer { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.transfer().user_data)); + ffi::libusb_free_transfer(self.ptr.as_ptr()); + } + } +} diff --git a/rusb-async/src/pool.rs b/rusb-async/src/pool.rs new file mode 100644 index 0000000..b4e6b33 --- /dev/null +++ b/rusb-async/src/pool.rs @@ -0,0 +1,183 @@ +use std::collections::VecDeque; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use rusb::{ffi, DeviceHandle, UsbContext}; + +use crate::{error::Error, error::Result, Transfer}; + +use std::sync::atomic::{AtomicBool, Ordering}; + +/// Represents a pool of asynchronous transfers, that can be polled to completion +pub struct TransferPool { + device: Arc>, + pending: VecDeque, +} + +impl TransferPool { + pub fn new(device: Arc>) -> Result { + Ok(Self { + device, + pending: VecDeque::new(), + }) + } + + pub fn submit_bulk(&mut self, endpoint: u8, buf: Vec) -> Result<()> { + // Safety: If transfer is submitted, it is pushed onto `pending` where it will be + // dropped before `device` is freed. + unsafe { + let mut transfer = Transfer::bulk(self.device.as_raw(), endpoint, buf); + transfer.submit()?; + self.pending.push_back(transfer); + Ok(()) + } + } + + pub fn submit_control( + &mut self, + request_type: u8, + request: u8, + value: u16, + index: u16, + data: &[u8], + ) -> Result<()> { + // Safety: If transfer is submitted, it is pushed onto `pending` where it will be + // dropped before `device` is freed. + unsafe { + let mut transfer = Transfer::control( + self.device.as_raw(), + request_type, + request, + value, + index, + data, + ); + transfer.submit()?; + self.pending.push_back(transfer); + Ok(()) + } + } + + pub unsafe fn submit_control_raw(&mut self, buffer: Vec) -> Result<()> { + // Safety: If transfer is submitted, it is pushed onto `pending` where it will be + // dropped before `device` is freed. + unsafe { + let mut transfer = Transfer::control_raw(self.device.as_raw(), buffer); + transfer.submit()?; + self.pending.push_back(transfer); + Ok(()) + } + } + + pub fn submit_interrupt(&mut self, endpoint: u8, buf: Vec) -> Result<()> { + // Safety: If transfer is submitted, it is pushed onto `pending` where it will be + // dropped before `device` is freed. + unsafe { + let mut transfer = Transfer::interrupt(self.device.as_raw(), endpoint, buf); + transfer.submit()?; + self.pending.push_back(transfer); + Ok(()) + } + } + + pub fn submit_iso(&mut self, endpoint: u8, buf: Vec, iso_packets: i32) -> Result<()> { + // Safety: If transfer is submitted, it is pushed onto `pending` where it will be + // dropped before `device` is freed. + unsafe { + let mut transfer = Transfer::iso(self.device.as_raw(), endpoint, buf, iso_packets); + transfer.submit()?; + self.pending.push_back(transfer); + Ok(()) + } + } + + pub fn poll(&mut self, timeout: Duration) -> Result> { + let next = self.pending.front().ok_or(Error::NoTransfersPending)?; + if poll_completed(self.device.context(), timeout, next.completed_flag()) { + let mut transfer = self.pending.pop_front().unwrap(); + let res = transfer.handle_completed(); + res + } else { + Err(Error::PollTimeout) + } + } + + pub fn cancel_all(&mut self) { + // Cancel in reverse order to avoid a race condition in which one + // transfer is cancelled but another submitted later makes its way onto + // the bus. + for transfer in self.pending.iter_mut().rev() { + transfer.cancel(); + } + } + + /// Returns the number of async transfers pending + pub fn pending(&self) -> usize { + self.pending.len() + } +} + +unsafe impl Send for TransferPool {} +unsafe impl Sync for TransferPool {} + +impl Drop for TransferPool { + fn drop(&mut self) { + self.cancel_all(); + while self.pending() > 0 { + self.poll(Duration::from_secs(1)).ok(); + } + } +} + +/// This is effectively libusb_handle_events_timeout_completed, but with +/// `completed` as `AtomicBool` instead of `c_int` so it is safe to access +/// without the events lock held. It also continues polling until completion, +/// timeout, or error, instead of potentially returning early. +/// +/// This design is based on +/// https://libusb.sourceforge.io/api-1.0/libusb_mtasync.html#threadwait +/// +/// Returns `true` when `completed` becomes true, `false` on timeout, and panics on +/// any other libusb error. +fn poll_completed(ctx: &impl UsbContext, timeout: Duration, completed: &AtomicBool) -> bool { + use ffi::constants::LIBUSB_ERROR_TIMEOUT; + + let deadline = Instant::now() + timeout; + + let mut err = 0; + while err == 0 && !completed.load(Ordering::SeqCst) && deadline > Instant::now() { + let remaining = deadline.saturating_duration_since(Instant::now()); + let timeval = libc::timeval { + tv_sec: remaining.as_secs().try_into().unwrap(), + tv_usec: remaining.subsec_micros().try_into().unwrap(), + }; + unsafe { + if ffi::libusb_try_lock_events(ctx.as_raw()) == 0 { + if !completed.load(Ordering::SeqCst) + && ffi::libusb_event_handling_ok(ctx.as_raw()) != 0 + { + err = ffi::libusb_handle_events_locked(ctx.as_raw(), &timeval as *const _); + } + ffi::libusb_unlock_events(ctx.as_raw()); + } else { + ffi::libusb_lock_event_waiters(ctx.as_raw()); + if !completed.load(Ordering::SeqCst) + && ffi::libusb_event_handler_active(ctx.as_raw()) != 0 + { + ffi::libusb_wait_for_event(ctx.as_raw(), &timeval as *const _); + } + ffi::libusb_unlock_event_waiters(ctx.as_raw()); + } + } + } + + match err { + 0 => completed.load(Ordering::SeqCst), + LIBUSB_ERROR_TIMEOUT => false, + _ => panic!( + "Error {} when polling transfers: {}", + err, + unsafe { std::ffi::CStr::from_ptr(ffi::libusb_strerror(err)) }.to_string_lossy() + ), + } +} From 122fffc2d5c3a98008a89ed395c154e98554aae0 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Mon, 28 Apr 2025 13:09:01 +0300 Subject: [PATCH 29/30] Mark rusb-async::{Error, Result} as public --- rusb-async/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rusb-async/src/lib.rs b/rusb-async/src/lib.rs index 38b2422..96bbfd7 100644 --- a/rusb-async/src/lib.rs +++ b/rusb-async/src/lib.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; mod error; mod pool; -use error::{Error, Result}; +pub use error::{Error, Result}; pub use pool::TransferPool; From 2b15592f0e9607bac4d5b759e8f67688e3c923fd Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Thu, 8 May 2025 20:19:51 +0300 Subject: [PATCH 30/30] Make TransferPool::new() infallible --- rusb-async/src/pool.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rusb-async/src/pool.rs b/rusb-async/src/pool.rs index b4e6b33..a433823 100644 --- a/rusb-async/src/pool.rs +++ b/rusb-async/src/pool.rs @@ -15,11 +15,11 @@ pub struct TransferPool { } impl TransferPool { - pub fn new(device: Arc>) -> Result { - Ok(Self { + pub fn new(device: Arc>) -> Self { + Self { device, pending: VecDeque::new(), - }) + } } pub fn submit_bulk(&mut self, endpoint: u8, buf: Vec) -> Result<()> {