Merge remote-tracking branch 'origin/upstream'
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..7c51c84
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,33 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+ default_applicable_licenses: ["external_rust_crates_rusb_license"],
+ default_team: "trendy_team_android_rust",
+}
+
+license {
+ name: "external_rust_crates_rusb_license",
+ visibility: [":__subpackages__"],
+ license_kinds: ["SPDX-license-identifier-MIT"],
+ license_text: ["LICENSE"],
+}
+
+rust_library {
+ name: "librusb",
+ host_supported: true,
+ crate_name: "rusb",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.9.4",
+ crate_root: "src/lib.rs",
+ edition: "2018",
+ rustlibs: [
+ "liblibc",
+ "liblibusb1_sys",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ vendor_available: true,
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..de4a6d7
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,87 @@
+# 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]
+* 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]
+* 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/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]
+
+[#116]: https://github.com/a1ien/rusb/pull/116
+
+## 0.9.0
+* Re-export libusb1-sys as ffi [#75]
+* impl Debug for DeviceHandle [#78]
+* Add bind to libusb_get_next_timeout [#95]
+* Add DeviceHandle::into_raw() [#97]
+* Improve read_string_descriptor [#99]
+* Derive Debug for Context [#103]
+* Implement Clone for Device [#104]
+* Add Context::interrupt_handle_events() [#101]
+* context: add open_device_with_fd() [#106]
+* Rewrite hotplug registration. Add `HotplugBuilder` [#110]. And rewrite [#72]
+* ConfigDescriptor and InterfaceDescriptor extra return just slice [#111]
+
+[#72]: https://github.com/a1ien/rusb/pull/72
+[#75]: https://github.com/a1ien/rusb/pull/75
+[#78]: https://github.com/a1ien/rusb/pull/78
+[#95]: https://github.com/a1ien/rusb/pull/95
+[#97]: https://github.com/a1ien/rusb/pull/97
+[#99]: https://github.com/a1ien/rusb/pull/99
+[#101]: https://github.com/a1ien/rusb/pull/101
+[#103]: https://github.com/a1ien/rusb/pull/103
+[#104]: https://github.com/a1ien/rusb/pull/104
+[#106]: https://github.com/a1ien/rusb/pull/106
+[#110]: https://github.com/a1ien/rusb/pull/110
+[#111]: https://github.com/a1ien/rusb/pull/111
+
+## 0.8.1
+* Add getters for bRefresh and bSynchAddress [#61]
+* Implement Display for Version. [#59]
+* Add Device/DeviceHandle::context getter methods [#57]
+
+[#61]: https://github.com/a1ien/rusb/pull/61
+[#59]: https://github.com/a1ien/rusb/pull/59
+[#57]: https://github.com/a1ien/rusb/pull/57
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..7bfeaec
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,285 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "libusb1-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "phf"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "963adb11cf22ee65dfd401cf75577c1aa0eca58c0b97f9337d2da61d3e640503"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "regex"
+version = "1.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
+
+[[package]]
+name = "rusb"
+version = "0.9.4"
+dependencies = [
+ "libc",
+ "libusb1-sys",
+ "regex",
+ "serde",
+ "usb-ids",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "syn"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
+
+[[package]]
+name = "usb-ids"
+version = "1.2023.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f6ee7f9e28c6c96a3432e89e5eaf19f88e4a4c477e9db208ab083405ee8a765"
+dependencies = [
+ "nom",
+ "phf",
+ "phf_codegen",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0ce0f6a
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,54 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "rusb"
+version = "0.9.4"
+authors = [
+ "David Cuddeback <[email protected]>",
+ "Ilya Averyanov <[email protected]>",
+]
+build = "build.rs"
+description = "Rust library for accessing USB devices."
+homepage = "https://github.com/a1ien/rusb"
+readme = "README.md"
+keywords = [
+ "usb",
+ "libusb",
+ "hardware",
+ "bindings",
+]
+license = "MIT"
+repository = "https://github.com/a1ien/rusb.git"
+
+[dependencies.libc]
+version = "0.2"
+
+[dependencies.libusb1-sys]
+version = "0.7"
+
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+optional = true
+
+[dev-dependencies.regex]
+version = "1"
+
+[dev-dependencies.usb-ids]
+version = "1.2023.0"
+
+[features]
+vendored = ["libusb1-sys/vendored"]
+
+[badges.travis-ci]
+repository = "a1ien/rusb"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..e18b645
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,30 @@
+[package]
+name = "rusb"
+version = "0.9.4"
+authors = ["David Cuddeback <[email protected]>", "Ilya Averyanov <[email protected]>"]
+description = "Rust library for accessing USB devices."
+license = "MIT"
+homepage = "https://github.com/a1ien/rusb"
+repository = "https://github.com/a1ien/rusb.git"
+readme = "README.md"
+keywords = ["usb", "libusb", "hardware", "bindings"]
+edition = "2018"
+build = "build.rs"
+
+[badges]
+travis-ci = { repository = "a1ien/rusb" }
+
+[features]
+vendored = [ "libusb1-sys/vendored" ]
+
+[workspace]
+members = ["libusb1-sys"]
+
+[dependencies]
+libusb1-sys = { path = "libusb1-sys", version = "0.7" }
+libc = "0.2"
+serde = { version = "1.0", features = ["derive"], optional = true }
+
+[dev-dependencies]
+regex = "1"
+usb-ids = "1.2023.0"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cf8ea43
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2015 David Cuddeback
+ 2019 Ilya Averyanov
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..fcbe2c9
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "rusb"
+description: "Rust library for accessing USB devices."
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "rusb"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/rusb/rusb-0.9.4.crate"
+ primary_source: true
+ }
+ version: "0.9.4"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 11
+ day: 8
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..ff26fee
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+include platform/prebuilts/rust:main:/OWNERS
[email protected]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b10746b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+# Rusb
+This crate provides a safe wrapper around the native `libusb` library. It applies the RAII pattern
+and Rust lifetimes to ensure safe usage of all `libusb` functionality. The RAII pattern ensures that
+all acquired resources are released when they're no longer needed, and Rust lifetimes ensure that
+resources are released in a proper order.
+
+* [Documentation](https://docs.rs/rusb)
+
+## Dependencies
+To use rusb no extra setup is required as rusb will automatically download the source for libusb and build it.
+
+However if building libusb fails you can also try setting up the native `libusb` library where it can
+be found by `pkg-config` or `vcpkg`.
+
+All systems supported by the native `libusb` library are also supported by the `libusb` crate. It's
+been tested on Linux, OS X, and Windows.
+
+### Cross-Compiling
+The `rusb` crate can be used when cross-compiling to a foreign target. Details on how to
+cross-compile `rusb` are explained in the [`libusb1-sys` crate's
+README](libusb1-sys/README.md#cross-compiling).
+
+## Usage
+Add `rusb` as a dependency in `Cargo.toml`:
+
+```toml
+[dependencies]
+rusb = "0.9"
+```
+
+Import the `rusb` crate. The starting point for nearly all `rusb` functionality is to create a
+context object. With a context object, you can list devices, read their descriptors, open them, and
+communicate with their endpoints:
+
+```rust
+fn main() {
+ for device in rusb::devices().unwrap().iter() {
+ let device_desc = device.device_descriptor().unwrap();
+
+ println!("Bus {:03} Device {:03} ID {:04x}:{:04x}",
+ device.bus_number(),
+ device.address(),
+ device_desc.vendor_id(),
+ device_desc.product_id());
+ }
+}
+```
+
+## License
+Distributed under the [MIT License](LICENSE).
+
+### License note.
+If you link native `libusb` (by example using `vendored` features) library statically then
+you must follow [GNU LGPL](https://github.com/libusb/libusb/blob/master/COPYING) from libusb.
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..9d65603
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,32 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+
+fn get_api_version(libusb_source: &Path) {
+ use std::io::BufRead;
+ if let Ok(f) = fs::File::open(libusb_source) {
+ let f = std::io::BufReader::new(f);
+ for line in f.lines().flatten() {
+ if line.starts_with("#define LIBUSB_API_VERSION") {
+ if let Some(api_version) = line.rsplit(' ').next().and_then(|s| {
+ if let Some(s) = s.strip_prefix("0x") {
+ u32::from_str_radix(s, 16).ok()
+ } else {
+ None
+ }
+ }) {
+ if api_version >= 0x01000108 {
+ println!("cargo:rustc-cfg=libusb_hotplug_get_user_data");
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+fn main() {
+ if let Ok(include_path) = std::env::var("DEP_USB_1.0_INCLUDE") {
+ let path = PathBuf::from(include_path);
+ get_api_version(path.join("libusb.h").as_path());
+ }
+}
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..311c48a
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,5 @@
+{
+ "tests": false,
+ "run_cargo": false,
+ "product_available": false
+}
diff --git a/examples/hotplug.rs b/examples/hotplug.rs
new file mode 100644
index 0000000..7d0f023
--- /dev/null
+++ b/examples/hotplug.rs
@@ -0,0 +1,43 @@
+use rusb::{Context, Device, HotplugBuilder, UsbContext};
+
+struct HotPlugHandler;
+
+impl<T: UsbContext> rusb::Hotplug<T> for HotPlugHandler {
+ fn device_arrived(&mut self, device: Device<T>) {
+ println!("device arrived {:?}", device);
+ }
+
+ fn device_left(&mut self, device: Device<T>) {
+ println!("device left {:?}", device);
+ }
+}
+
+impl Drop for HotPlugHandler {
+ fn drop(&mut self) {
+ println!("HotPlugHandler dropped");
+ }
+}
+
+fn main() -> rusb::Result<()> {
+ if rusb::has_hotplug() {
+ let context = Context::new()?;
+
+ let mut reg = Some(
+ HotplugBuilder::new()
+ .enumerate(true)
+ .register(&context, Box::new(HotPlugHandler {}))?,
+ );
+
+ loop {
+ context.handle_events(None).unwrap();
+ if let Some(reg) = reg.take() {
+ context.unregister_callback(reg);
+ break;
+ }
+ }
+ Ok(())
+ } else {
+ eprint!("libusb hotplug api unsupported");
+ Ok(())
+ }
+}
diff --git a/examples/libusb_info.rs b/examples/libusb_info.rs
new file mode 100644
index 0000000..2465306
--- /dev/null
+++ b/examples/libusb_info.rs
@@ -0,0 +1,33 @@
+use rusb::UsbContext;
+
+fn main() {
+ let version = rusb::version();
+
+ println!(
+ "libusb v{}.{}.{}.{}{}",
+ version.major(),
+ version.minor(),
+ version.micro(),
+ version.nano(),
+ version.rc().unwrap_or("")
+ );
+
+ let mut context = match rusb::Context::new() {
+ Ok(c) => c,
+ Err(e) => panic!("libusb::Context::new(): {}", e),
+ };
+
+ context.set_log_level(rusb::LogLevel::Debug);
+ context.set_log_level(rusb::LogLevel::Info);
+ context.set_log_level(rusb::LogLevel::Warning);
+ context.set_log_level(rusb::LogLevel::Error);
+ context.set_log_level(rusb::LogLevel::None);
+
+ println!("has capability? {}", rusb::has_capability());
+ println!("has hotplug? {}", rusb::has_hotplug());
+ println!("has HID access? {}", rusb::has_hid_access());
+ println!(
+ "supports detach kernel driver? {}",
+ rusb::supports_detach_kernel_driver()
+ )
+}
diff --git a/examples/list_devices.rs b/examples/list_devices.rs
new file mode 100644
index 0000000..1ae4950
--- /dev/null
+++ b/examples/list_devices.rs
@@ -0,0 +1,278 @@
+use rusb::{
+ ConfigDescriptor, DeviceDescriptor, DeviceHandle, DeviceList, EndpointDescriptor,
+ InterfaceDescriptor, Language, Result, Speed, UsbContext,
+};
+use std::time::Duration;
+
+use usb_ids::{self, FromId};
+
+struct UsbDevice<T: UsbContext> {
+ handle: DeviceHandle<T>,
+ language: Language,
+ timeout: Duration,
+}
+
+fn main() {
+ list_devices().unwrap();
+}
+
+fn list_devices() -> Result<()> {
+ let timeout = Duration::from_secs(1);
+
+ for device in DeviceList::new()?.iter() {
+ let device_desc = match device.device_descriptor() {
+ Ok(d) => d,
+ Err(_) => continue,
+ };
+
+ let mut usb_device = {
+ match device.open() {
+ Ok(h) => match h.read_languages(timeout) {
+ Ok(l) => {
+ if !l.is_empty() {
+ Some(UsbDevice {
+ handle: h,
+ language: l[0],
+ timeout,
+ })
+ } else {
+ None
+ }
+ }
+ Err(_) => None,
+ },
+ Err(_) => None,
+ }
+ };
+
+ println!(
+ "Bus {:03} Device {:03} ID {:04x}:{:04x} {}",
+ device.bus_number(),
+ device.address(),
+ device_desc.vendor_id(),
+ device_desc.product_id(),
+ get_speed(device.speed())
+ );
+ print_device(&device_desc, &mut usb_device);
+
+ for n in 0..device_desc.num_configurations() {
+ let config_desc = match device.config_descriptor(n) {
+ Ok(c) => c,
+ Err(_) => continue,
+ };
+
+ print_config(&config_desc, &mut usb_device);
+
+ for interface in config_desc.interfaces() {
+ for interface_desc in interface.descriptors() {
+ print_interface(&interface_desc, &mut usb_device);
+
+ for endpoint_desc in interface_desc.endpoint_descriptors() {
+ print_endpoint(&endpoint_desc);
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn print_device<T: UsbContext>(device_desc: &DeviceDescriptor, handle: &mut Option<UsbDevice<T>>) {
+ let vid = device_desc.vendor_id();
+ let pid = device_desc.product_id();
+
+ let vendor_name = match usb_ids::Vendor::from_id(device_desc.vendor_id()) {
+ Some(vendor) => vendor.name(),
+ None => "Unknown vendor",
+ };
+
+ let product_name =
+ match usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id()) {
+ Some(product) => product.name(),
+ None => "Unknown product",
+ };
+
+ println!("Device Descriptor:");
+ println!(" bLength {:3}", device_desc.length());
+ println!(" bDescriptorType {:3}", device_desc.descriptor_type());
+ println!(
+ " bcdUSB {:2}.{}{}",
+ device_desc.usb_version().major(),
+ device_desc.usb_version().minor(),
+ device_desc.usb_version().sub_minor()
+ );
+ println!(" bDeviceClass {:#04x}", device_desc.class_code());
+ println!(
+ " bDeviceSubClass {:#04x}",
+ device_desc.sub_class_code()
+ );
+ println!(" bDeviceProtocol {:#04x}", device_desc.protocol_code());
+ println!(" bMaxPacketSize0 {:3}", device_desc.max_packet_size());
+ println!(" idVendor {vid:#06x} {vendor_name}",);
+ println!(" idProduct {pid:#06x} {product_name}",);
+ println!(
+ " bcdDevice {:2}.{}{}",
+ device_desc.device_version().major(),
+ device_desc.device_version().minor(),
+ device_desc.device_version().sub_minor()
+ );
+ println!(
+ " iManufacturer {:3} {}",
+ device_desc.manufacturer_string_index().unwrap_or(0),
+ handle.as_mut().map_or(String::new(), |h| h
+ .handle
+ .read_manufacturer_string(h.language, device_desc, h.timeout)
+ .unwrap_or_default())
+ );
+ println!(
+ " iProduct {:3} {}",
+ device_desc.product_string_index().unwrap_or(0),
+ handle.as_mut().map_or(String::new(), |h| h
+ .handle
+ .read_product_string(h.language, device_desc, h.timeout)
+ .unwrap_or_default())
+ );
+ println!(
+ " iSerialNumber {:3} {}",
+ device_desc.serial_number_string_index().unwrap_or(0),
+ handle.as_mut().map_or(String::new(), |h| h
+ .handle
+ .read_serial_number_string(h.language, device_desc, h.timeout)
+ .unwrap_or_default())
+ );
+ println!(
+ " bNumConfigurations {:3}",
+ device_desc.num_configurations()
+ );
+}
+
+fn print_config<T: UsbContext>(config_desc: &ConfigDescriptor, handle: &mut Option<UsbDevice<T>>) {
+ 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()
+ );
+ println!(" bConfigurationValue {:3}", config_desc.number());
+ println!(
+ " iConfiguration {:3} {}",
+ config_desc.description_string_index().unwrap_or(0),
+ handle.as_mut().map_or(String::new(), |h| h
+ .handle
+ .read_configuration_string(h.language, config_desc, h.timeout)
+ .unwrap_or_default())
+ );
+ println!(" bmAttributes:");
+ println!(" Self Powered {:>5}", config_desc.self_powered());
+ println!(" Remote Wakeup {:>5}", config_desc.remote_wakeup());
+ println!(" bMaxPower {:4}mW", config_desc.max_power());
+
+ if !config_desc.extra().is_empty() {
+ println!(" {:?}", config_desc.extra());
+ } else {
+ println!(" no extra data");
+ }
+}
+
+fn print_interface<T: UsbContext>(
+ interface_desc: &InterfaceDescriptor,
+ handle: &mut Option<UsbDevice<T>>,
+) {
+ println!(" Interface Descriptor:");
+ println!(" bLength {:3}", interface_desc.length());
+ println!(
+ " bDescriptorType {:3}",
+ interface_desc.descriptor_type()
+ );
+ println!(
+ " bInterfaceNumber {:3}",
+ interface_desc.interface_number()
+ );
+ println!(
+ " bAlternateSetting {:3}",
+ interface_desc.setting_number()
+ );
+ println!(
+ " bNumEndpoints {:3}",
+ interface_desc.num_endpoints()
+ );
+ println!(
+ " bInterfaceClass {:#04x}",
+ interface_desc.class_code()
+ );
+ println!(
+ " bInterfaceSubClass {:#04x}",
+ interface_desc.sub_class_code()
+ );
+ println!(
+ " bInterfaceProtocol {:#04x}",
+ interface_desc.protocol_code()
+ );
+ println!(
+ " iInterface {:3} {}",
+ interface_desc.description_string_index().unwrap_or(0),
+ handle.as_mut().map_or(String::new(), |h| h
+ .handle
+ .read_interface_string(h.language, interface_desc, h.timeout)
+ .unwrap_or_default())
+ );
+
+ if interface_desc.extra().is_empty() {
+ println!(" {:?}", interface_desc.extra());
+ } else {
+ println!(" no extra data");
+ }
+}
+
+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(),
+ endpoint_desc.number(),
+ endpoint_desc.direction()
+ );
+ println!(" bmAttributes:");
+ println!(
+ " Transfer Type {:?}",
+ endpoint_desc.transfer_type()
+ );
+ println!(
+ " Synch Type {:?}",
+ endpoint_desc.sync_type()
+ );
+ println!(
+ " Usage Type {:?}",
+ endpoint_desc.usage_type()
+ );
+ println!(
+ " wMaxPacketSize {:#06x}",
+ endpoint_desc.max_packet_size()
+ );
+ println!(
+ " bInterval {:3}",
+ endpoint_desc.interval()
+ );
+}
+
+fn get_speed(speed: Speed) -> &'static str {
+ match speed {
+ Speed::SuperPlus => "10000 Mbps",
+ Speed::Super => "5000 Mbps",
+ Speed::High => " 480 Mbps",
+ Speed::Full => " 12 Mbps",
+ Speed::Low => " 1.5 Mbps",
+ _ => "(unknown)",
+ }
+}
diff --git a/examples/read_device.rs b/examples/read_device.rs
new file mode 100644
index 0000000..bf1d943
--- /dev/null
+++ b/examples/read_device.rs
@@ -0,0 +1,209 @@
+use std::time::Duration;
+
+use rusb::{
+ Context, Device, DeviceDescriptor, DeviceHandle, Direction, Result, TransferType, UsbContext,
+};
+
+#[derive(Debug)]
+struct Endpoint {
+ config: u8,
+ iface: u8,
+ setting: u8,
+ address: u8,
+}
+
+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<String> = std::env::args().collect();
+
+ if args.len() < 3 {
+ println!("usage: read_device <base-10/0xbase-16> <base-10/0xbase-16>");
+ return;
+ }
+
+ let vid = convert_argument(args[1].as_ref());
+ let pid = convert_argument(args[2].as_ref());
+
+ match Context::new() {
+ Ok(mut context) => match open_device(&mut context, vid, pid) {
+ Some((mut device, device_desc, mut handle)) => {
+ read_device(&mut device, &device_desc, &mut handle).unwrap()
+ }
+ None => println!("could not find device {:04x}:{:04x}", vid, pid),
+ },
+ Err(e) => panic!("could not initialize libusb: {}", e),
+ }
+}
+
+fn open_device<T: UsbContext>(
+ context: &mut T,
+ vid: u16,
+ pid: u16,
+) -> Option<(Device<T>, DeviceDescriptor, DeviceHandle<T>)> {
+ let devices = match context.devices() {
+ Ok(d) => d,
+ Err(_) => return None,
+ };
+
+ for device in devices.iter() {
+ let device_desc = match device.device_descriptor() {
+ Ok(d) => d,
+ Err(_) => continue,
+ };
+
+ if device_desc.vendor_id() == vid && device_desc.product_id() == pid {
+ match device.open() {
+ Ok(handle) => return Some((device, device_desc, handle)),
+ Err(e) => panic!("Device found but failed to open: {}", e),
+ }
+ }
+ }
+
+ None
+}
+
+fn read_device<T: UsbContext>(
+ device: &mut Device<T>,
+ device_desc: &DeviceDescriptor,
+ handle: &mut DeviceHandle<T>,
+) -> Result<()> {
+ handle.reset()?;
+
+ let timeout = Duration::from_secs(1);
+ let languages = handle.read_languages(timeout)?;
+
+ println!("Active configuration: {}", handle.active_configuration()?);
+ println!("Languages: {:?}", languages);
+
+ if !languages.is_empty() {
+ let language = languages[0];
+
+ println!(
+ "Manufacturer: {:?}",
+ handle
+ .read_manufacturer_string(language, device_desc, timeout)
+ .ok()
+ );
+ println!(
+ "Product: {:?}",
+ handle
+ .read_product_string(language, device_desc, timeout)
+ .ok()
+ );
+ println!(
+ "Serial Number: {:?}",
+ handle
+ .read_serial_number_string(language, device_desc, timeout)
+ .ok()
+ );
+ }
+
+ match find_readable_endpoint(device, device_desc, TransferType::Interrupt) {
+ Some(endpoint) => read_endpoint(handle, endpoint, TransferType::Interrupt),
+ None => println!("No readable interrupt endpoint"),
+ }
+
+ match find_readable_endpoint(device, device_desc, TransferType::Bulk) {
+ Some(endpoint) => read_endpoint(handle, endpoint, TransferType::Bulk),
+ None => println!("No readable bulk endpoint"),
+ }
+
+ Ok(())
+}
+
+fn find_readable_endpoint<T: UsbContext>(
+ device: &mut Device<T>,
+ device_desc: &DeviceDescriptor,
+ transfer_type: TransferType,
+) -> Option<Endpoint> {
+ for n in 0..device_desc.num_configurations() {
+ let config_desc = match device.config_descriptor(n) {
+ Ok(c) => c,
+ Err(_) => continue,
+ };
+
+ for interface in config_desc.interfaces() {
+ for interface_desc in interface.descriptors() {
+ for endpoint_desc in interface_desc.endpoint_descriptors() {
+ if endpoint_desc.direction() == Direction::In
+ && endpoint_desc.transfer_type() == transfer_type
+ {
+ return Some(Endpoint {
+ config: config_desc.number(),
+ iface: interface_desc.interface_number(),
+ setting: interface_desc.setting_number(),
+ address: endpoint_desc.address(),
+ });
+ }
+ }
+ }
+ }
+ }
+
+ None
+}
+
+fn read_endpoint<T: UsbContext>(
+ handle: &mut DeviceHandle<T>,
+ endpoint: Endpoint,
+ transfer_type: TransferType,
+) {
+ println!("Reading from endpoint: {:?}", endpoint);
+
+ let has_kernel_driver = match handle.kernel_driver_active(endpoint.iface) {
+ Ok(true) => {
+ handle.detach_kernel_driver(endpoint.iface).ok();
+ true
+ }
+ _ => false,
+ };
+
+ println!(" - kernel driver? {}", has_kernel_driver);
+
+ match configure_endpoint(handle, &endpoint) {
+ Ok(_) => {
+ let mut buf = [0; 256];
+ let timeout = Duration::from_secs(1);
+
+ match transfer_type {
+ TransferType::Interrupt => {
+ match handle.read_interrupt(endpoint.address, &mut buf, timeout) {
+ Ok(len) => {
+ println!(" - read: {:?}", &buf[..len]);
+ }
+ Err(err) => println!("could not read from endpoint: {}", err),
+ }
+ }
+ TransferType::Bulk => match handle.read_bulk(endpoint.address, &mut buf, timeout) {
+ Ok(len) => {
+ println!(" - read: {:?}", &buf[..len]);
+ }
+ Err(err) => println!("could not read from endpoint: {}", err),
+ },
+ _ => (),
+ }
+ }
+ Err(err) => println!("could not configure endpoint: {}", err),
+ }
+
+ if has_kernel_driver {
+ handle.attach_kernel_driver(endpoint.iface).ok();
+ }
+}
+
+fn configure_endpoint<T: UsbContext>(
+ handle: &mut DeviceHandle<T>,
+ endpoint: &Endpoint,
+) -> Result<()> {
+ handle.set_active_configuration(endpoint.config)?;
+ handle.claim_interface(endpoint.iface)?;
+ handle.set_alternate_setting(endpoint.iface, endpoint.setting)?;
+ Ok(())
+}
diff --git a/src/config_descriptor.rs b/src/config_descriptor.rs
new file mode 100644
index 0000000..7f66c11
--- /dev/null
+++ b/src/config_descriptor.rs
@@ -0,0 +1,255 @@
+use std::{fmt, slice};
+
+use libusb1_sys::*;
+
+use crate::interface_descriptor::{self, Interface};
+
+/// Describes a configuration.
+pub struct ConfigDescriptor {
+ descriptor: *const libusb_config_descriptor,
+}
+
+impl Drop for ConfigDescriptor {
+ fn drop(&mut self) {
+ unsafe {
+ libusb_free_config_descriptor(self.descriptor);
+ }
+ }
+}
+
+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 }
+ }
+
+ /// Returns the device's maximum power consumption (in milliamps) in this configuration.
+ pub fn max_power(&self) -> u16 {
+ unsafe { u16::from((*self.descriptor).bMaxPower) * 2 }
+ }
+
+ /// Indicates if the device is self-powered in this configuration.
+ pub fn self_powered(&self) -> bool {
+ unsafe { (*self.descriptor).bmAttributes & 0x40 != 0 }
+ }
+
+ /// Indicates if the device has remote wakeup capability in this configuration.
+ pub fn remote_wakeup(&self) -> bool {
+ unsafe { (*self.descriptor).bmAttributes & 0x20 != 0 }
+ }
+
+ /// Returns the index of the string descriptor that describes the configuration.
+ pub fn description_string_index(&self) -> Option<u8> {
+ unsafe {
+ match (*self.descriptor).iConfiguration {
+ 0 => None,
+ n => Some(n),
+ }
+ }
+ }
+
+ /// Returns the number of interfaces for this configuration.
+ pub fn num_interfaces(&self) -> u8 {
+ unsafe { (*self.descriptor).bNumInterfaces }
+ }
+
+ /// Returns a collection of the configuration's interfaces.
+ pub fn interfaces(&self) -> Interfaces {
+ let interfaces = unsafe {
+ slice::from_raw_parts(
+ (*self.descriptor).interface,
+ (*self.descriptor).bNumInterfaces as usize,
+ )
+ };
+
+ Interfaces {
+ iter: interfaces.iter(),
+ }
+ }
+
+ /// Returns the unknown 'extra' bytes that libusb does not understand.
+ pub fn extra(&self) -> &[u8] {
+ unsafe {
+ match (*self.descriptor).extra_length {
+ len if len > 0 => slice::from_raw_parts((*self.descriptor).extra, len as usize),
+ _ => &[],
+ }
+ }
+ }
+}
+
+impl fmt::Debug for ConfigDescriptor {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let mut debug = fmt.debug_struct("ConfigDescriptor");
+
+ let descriptor: &libusb_config_descriptor = unsafe { &*self.descriptor };
+
+ debug.field("bLength", &descriptor.bLength);
+ debug.field("bDescriptorType", &descriptor.bDescriptorType);
+ debug.field("wTotalLength", &descriptor.wTotalLength);
+ debug.field("bNumInterfaces", &descriptor.bNumInterfaces);
+ debug.field("bConfigurationValue", &descriptor.bConfigurationValue);
+ debug.field("iConfiguration", &descriptor.iConfiguration);
+ debug.field("bmAttributes", &descriptor.bmAttributes);
+ debug.field("bMaxPower", &descriptor.bMaxPower);
+ debug.field("extra", &self.extra());
+
+ debug.finish()
+ }
+}
+
+/// Iterator over a configuration's interfaces.
+pub struct Interfaces<'a> {
+ iter: slice::Iter<'a, libusb_interface>,
+}
+
+impl<'a> Iterator for Interfaces<'a> {
+ type Item = Interface<'a>;
+
+ fn next(&mut self) -> Option<Interface<'a>> {
+ self.iter
+ .next()
+ .map(|interface| unsafe { interface_descriptor::from_libusb(interface) })
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+#[doc(hidden)]
+pub(crate) unsafe fn from_libusb(config: *const libusb_config_descriptor) -> ConfigDescriptor {
+ ConfigDescriptor { descriptor: config }
+}
+
+#[cfg(test)]
+mod test {
+ use std::mem::ManuallyDrop;
+
+ // The Drop trait impl calls libusb_free_config_descriptor(), which would attempt to free
+ // unallocated memory for a stack-allocated config descriptor. Allocating a config descriptor
+ // is not a simple malloc()/free() inside libusb. Mimicking libusb's allocation would be
+ // error-prone, difficult to maintain, and provide little benefit for the tests. It's easier to
+ // use mem::forget() to prevent the Drop trait impl from running. The config descriptor passed
+ // as `$config` should be stack-allocated to prevent memory leaks in the test suite.
+ macro_rules! with_config {
+ ($name:ident : $config:expr => $body:block) => {{
+ let config = $config;
+ let $name = ManuallyDrop::new(unsafe { super::from_libusb(&config) });
+ $body;
+ }};
+ }
+
+ #[test]
+ fn it_has_number() {
+ with_config!(config: config_descriptor!(bConfigurationValue: 42) => {
+ assert_eq!(42, config.number());
+ });
+ }
+
+ #[test]
+ fn it_has_max_power() {
+ with_config!(config: config_descriptor!(bMaxPower: 21) => {
+ assert_eq!(42, config.max_power());
+ });
+ }
+
+ #[test]
+ fn it_interprets_self_powered_bit_in_attributes() {
+ with_config!(config: config_descriptor!(bmAttributes: 0b0000_0000) => {
+ assert_eq!(false, config.self_powered());
+ });
+
+ with_config!(config: config_descriptor!(bmAttributes: 0b0100_0000) => {
+ assert_eq!(true, config.self_powered());
+ });
+ }
+
+ #[test]
+ fn it_interprets_remote_wakeup_bit_in_attributes() {
+ with_config!(config: config_descriptor!(bmAttributes: 0b0000_0000) => {
+ assert_eq!(false, config.remote_wakeup());
+ });
+
+ with_config!(config: config_descriptor!(bmAttributes: 0b0010_0000) => {
+ assert_eq!(true, config.remote_wakeup());
+ });
+ }
+
+ #[test]
+ fn it_has_description_string_index() {
+ with_config!(config: config_descriptor!(iConfiguration: 42) => {
+ assert_eq!(Some(42), config.description_string_index());
+ });
+ }
+
+ #[test]
+ fn it_handles_missing_description_string_index() {
+ with_config!(config: config_descriptor!(iConfiguration: 0) => {
+ assert_eq!(None, config.description_string_index());
+ });
+ }
+
+ #[test]
+ fn it_has_num_interfaces() {
+ let interface1 = interface!(interface_descriptor!(bInterfaceNumber: 1));
+ let interface2 = interface!(interface_descriptor!(bInterfaceNumber: 2));
+
+ with_config!(config: config_descriptor!(interface1, interface2) => {
+ assert_eq!(2, config.num_interfaces());
+ });
+ }
+
+ #[test]
+ fn it_has_interfaces() {
+ let interface = interface!(interface_descriptor!(bInterfaceNumber: 1));
+
+ with_config!(config: config_descriptor!(interface) => {
+ let interface_numbers = config.interfaces().map(|interface| {
+ interface.number()
+ }).collect::<Vec<_>>();
+
+ 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/context.rs b/src/context.rs
new file mode 100644
index 0000000..2add6b3
--- /dev/null
+++ b/src/context.rs
@@ -0,0 +1,373 @@
+use libc::{c_char, c_int, c_void, timeval};
+
+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;
+
+use crate::hotplug::{Hotplug, HotplugBuilder, Registration};
+use crate::{device_handle::DeviceHandle, device_list::DeviceList, error};
+use libusb1_sys::{constants::*, *};
+
+#[cfg(windows)]
+type Seconds = ::libc::c_long;
+#[cfg(windows)]
+type MicroSeconds = ::libc::c_long;
+
+#[cfg(not(windows))]
+type Seconds = ::libc::time_t;
+#[cfg(not(windows))]
+type MicroSeconds = ::libc::suseconds_t;
+
+#[derive(Copy, Clone, Eq, PartialEq, Default)]
+pub struct GlobalContext {}
+
+/// A `libusb` context.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Context {
+ context: Arc<ContextInner>,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+struct ContextInner {
+ inner: ptr::NonNull<libusb_context>,
+}
+
+impl Drop for ContextInner {
+ /// Closes the `libusb` context.
+ fn drop(&mut self) {
+ unsafe {
+ libusb_exit(self.inner.as_ptr());
+ }
+ }
+}
+
+unsafe impl Sync for Context {}
+unsafe impl Send for Context {}
+
+type LogCallback = Box<dyn Fn(LogLevel, String)>;
+
+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<Mutex<LogCallbackMap>> = 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;
+
+ /// Returns a list of the current USB devices.
+ fn devices(&self) -> crate::Result<DeviceList<Self>> {
+ DeviceList::new_with_context(self.clone())
+ }
+
+ /// Convenience function to open a device by its vendor ID and product ID.
+ ///
+ /// This function is provided as a convenience for building prototypes without having to
+ /// iterate a [`DeviceList`](struct.DeviceList.html). It is not meant for production
+ /// applications.
+ ///
+ /// Returns a device handle for the first device found matching `vendor_id` and `product_id`.
+ /// On error, or if the device could not be found, it returns `None`.
+ fn open_device_with_vid_pid(
+ &self,
+ vendor_id: u16,
+ product_id: u16,
+ ) -> Option<DeviceHandle<Self>> {
+ let handle =
+ unsafe { libusb_open_device_with_vid_pid(self.as_raw(), vendor_id, product_id) };
+ let ptr = std::ptr::NonNull::new(handle)?;
+ Some(unsafe { DeviceHandle::from_libusb(self.clone(), ptr) })
+ }
+
+ /// Opens the device with a pre-opened file descriptor.
+ ///
+ /// This is UNIX-only and platform-specific. It is currently working with
+ /// Linux/Android, but might work with other systems in the future.
+ ///
+ /// Note: This function does not take ownership of the specified file
+ /// descriptor. The caller has the responsibility of keeping it opened for
+ /// as long as the device handle.
+ #[cfg(unix)]
+ #[doc(alias = "libusb_wrap_sys_device")]
+ unsafe fn open_device_with_fd(&self, fd: RawFd) -> crate::Result<DeviceHandle<Self>> {
+ let mut handle = mem::MaybeUninit::<*mut libusb_device_handle>::uninit();
+
+ match libusb_wrap_sys_device(self.as_raw(), fd as _, handle.as_mut_ptr()) {
+ 0 => {
+ let ptr =
+ std::ptr::NonNull::new(handle.assume_init()).ok_or(crate::Error::NoDevice)?;
+
+ Ok(DeviceHandle::from_libusb(self.clone(), ptr))
+ }
+ err => Err(error::from_libusb(err)),
+ }
+ }
+
+ /// Sets the log level of a `libusb` for context.
+ fn set_log_level(&mut self, level: LogLevel) {
+ unsafe {
+ libusb_set_debug(self.as_raw(), level.as_c_int());
+ }
+ }
+
+ 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.
+ ///
+ /// Devices can optionally be filtered by vendor (`vendor_id`) and device id
+ /// (`product_id`).
+ ///
+ /// The callback will remain registered until the returned [Registration] is
+ /// dropped, which can be done explicitly with [Context::unregister_callback].
+ ///
+ /// When handling a [Hotplug::device_arrived] event it is considered safe to call
+ /// any `rusb` function that takes a [crate::Device]. It also safe to open a device and
+ /// submit **asynchronous** transfers.
+ /// However, most other functions that take a [DeviceHandle] are **not safe** to call.
+ /// Examples of such functions are any of the synchronous API functions or
+ /// the blocking functions that retrieve various USB descriptors.
+ /// These functions must be used outside of the context of the [Hotplug] functions.
+ #[deprecated(since = "0.9.0", note = "Use HotplugBuilder")]
+ fn register_callback(
+ &self,
+ vendor_id: Option<u16>,
+ product_id: Option<u16>,
+ class: Option<u8>,
+ callback: Box<dyn Hotplug<Self>>,
+ ) -> crate::Result<Registration<Self>> {
+ let mut builder = HotplugBuilder::new();
+
+ let mut builder = &mut builder;
+ if let Some(vendor_id) = vendor_id {
+ builder = builder.vendor_id(vendor_id)
+ }
+ if let Some(product_id) = product_id {
+ builder = builder.product_id(product_id)
+ }
+ if let Some(class) = class {
+ builder = builder.class(class)
+ }
+
+ builder.register(self, callback)
+ }
+
+ /// Unregisters the callback corresponding to the given registration. The
+ /// same thing can be achieved by dropping the registration.
+ fn unregister_callback(&self, _reg: Registration<Self>) {}
+
+ /// Handle any pending events.
+ /// If timeout less then 1 microseconds then this function will handle any already-pending
+ /// events and then immediately return in non-blocking style.
+ /// If timeout is [None] then function will handle any pending events in blocking mode.
+ fn handle_events(&self, timeout: Option<Duration>) -> crate::Result<()> {
+ let n = unsafe {
+ match timeout {
+ Some(t) => {
+ let tv = timeval {
+ tv_sec: t.as_secs() as Seconds,
+ tv_usec: t.subsec_nanos() as MicroSeconds / 1000,
+ };
+ libusb_handle_events_timeout_completed(self.as_raw(), &tv, ptr::null_mut())
+ }
+ None => libusb_handle_events_completed(self.as_raw(), ptr::null_mut()),
+ }
+ };
+ if n < 0 {
+ Err(error::from_libusb(n as c_int))
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Interrupt any active thread that is handling events (for example with
+ /// [handle_events][`Self::handle_events()`]).
+ #[doc(alias = "libusb_interrupt_event_handler")]
+ fn interrupt_handle_events(&self) {
+ unsafe { libusb_interrupt_event_handler(self.as_raw()) }
+ }
+
+ fn next_timeout(&self) -> crate::Result<Option<Duration>> {
+ let mut tv = timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ };
+ let n = unsafe { libusb_get_next_timeout(self.as_raw(), &mut tv) };
+
+ match n.cmp(&0) {
+ Ordering::Less => Err(error::from_libusb(n as c_int)),
+ Ordering::Equal => Ok(None),
+ Ordering::Greater => {
+ let duration = Duration::new(tv.tv_sec as _, (tv.tv_usec * 1000) as _);
+ Ok(Some(duration))
+ }
+ }
+ }
+}
+
+impl UsbContext for Context {
+ fn as_raw(&self) -> *mut libusb_context {
+ self.context.inner.as_ptr()
+ }
+}
+
+impl UsbContext for GlobalContext {
+ fn as_raw(&self) -> *mut libusb_context {
+ static mut USB_CONTEXT: *mut libusb_context = ptr::null_mut();
+ static ONCE: Once = Once::new();
+
+ ONCE.call_once(|| {
+ let mut context = mem::MaybeUninit::<*mut libusb_context>::uninit();
+ unsafe {
+ USB_CONTEXT = match libusb_init(context.as_mut_ptr()) {
+ 0 => context.assume_init(),
+ err => panic!(
+ "Can't init Global usb context, error {:?}",
+ error::from_libusb(err)
+ ),
+ }
+ };
+ });
+ // Clone data that is safe to use concurrently.
+ unsafe { USB_CONTEXT }
+ }
+}
+
+impl Context {
+ /// Opens a new `libusb` context.
+ pub fn new() -> crate::Result<Self> {
+ let mut context = mem::MaybeUninit::<*mut libusb_context>::uninit();
+
+ try_unsafe!(libusb_init(context.as_mut_ptr()));
+
+ Ok(unsafe { Self::from_raw(context.assume_init()) })
+ }
+
+ /// Creates a new `libusb` context and sets runtime options.
+ pub fn with_options(opts: &[crate::UsbOption]) -> crate::Result<Self> {
+ let mut this = Self::new()?;
+
+ for opt in opts {
+ opt.apply(&mut this)?;
+ }
+
+ Ok(this)
+ }
+
+ /// Creates rusb Context from existing libusb context.
+ /// Note: This transfers ownership of the context to Rust.
+ /// # Safety
+ /// This is unsafe because it does not check if the context is valid,
+ /// so the caller must guarantee that libusb_context is created properly.
+ pub unsafe fn from_raw(raw: *mut libusb_context) -> Self {
+ Context {
+ context: Arc::new(ContextInner {
+ inner: ptr::NonNull::new_unchecked(raw),
+ }),
+ }
+ }
+}
+
+/// Library logging levels.
+#[derive(Clone, Copy)]
+pub enum LogLevel {
+ /// No messages are printed by `libusb` (default).
+ None,
+
+ /// Error messages printed to `stderr`.
+ Error,
+
+ /// Warning and error messages are printed to `stderr`.
+ Warning,
+
+ /// Informational messages are printed to `stdout`. Warnings and error messages are printed to
+ /// `stderr`.
+ Info,
+
+ /// Debug and informational messages are printed to `stdout`. Warnings and error messages are
+ /// printed to `stderr`.
+ Debug,
+}
+
+impl LogLevel {
+ pub(crate) fn as_c_int(self) -> c_int {
+ match self {
+ LogLevel::None => LIBUSB_LOG_LEVEL_NONE,
+ LogLevel::Error => LIBUSB_LOG_LEVEL_ERROR,
+ LogLevel::Warning => LIBUSB_LOG_LEVEL_WARNING,
+ LogLevel::Info => LIBUSB_LOG_LEVEL_INFO,
+ 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/device.rs b/src/device.rs
new file mode 100644
index 0000000..af0042d
--- /dev/null
+++ b/src/device.rs
@@ -0,0 +1,178 @@
+use std::{
+ fmt::{self, Debug},
+ mem,
+ ptr::NonNull,
+};
+
+use libusb1_sys::*;
+
+use crate::{
+ config_descriptor::{self, ConfigDescriptor},
+ device_descriptor::{self, DeviceDescriptor},
+ device_handle::DeviceHandle,
+ error,
+ fields::{self, Speed},
+ Error, UsbContext,
+};
+
+/// A reference to a USB device.
+#[derive(Eq, PartialEq)]
+pub struct Device<T: UsbContext> {
+ context: T,
+ device: NonNull<libusb_device>,
+}
+
+impl<T: UsbContext> Drop for Device<T> {
+ /// Releases the device reference.
+ fn drop(&mut self) {
+ unsafe {
+ libusb_unref_device(self.device.as_ptr());
+ }
+ }
+}
+
+impl<T: UsbContext> Clone for Device<T> {
+ fn clone(&self) -> Self {
+ unsafe { Self::from_libusb(self.context.clone(), self.device) }
+ }
+}
+
+unsafe impl<T: UsbContext> Send for Device<T> {}
+unsafe impl<T: UsbContext> Sync for Device<T> {}
+
+impl<T: UsbContext> Debug for Device<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let descriptor = match self.device_descriptor() {
+ Ok(descriptor) => descriptor,
+ Err(e) => {
+ return write!(f, "Can't read device descriptor {:?}", e);
+ }
+ };
+ write!(
+ f,
+ "Bus {:03} Device {:03}: ID {:04x}:{:04x}",
+ self.bus_number(),
+ self.address(),
+ descriptor.vendor_id(),
+ descriptor.product_id(),
+ )
+ }
+}
+
+impl<T: UsbContext> Device<T> {
+ /// Get the raw libusb_device pointer, for advanced use in unsafe code
+ pub fn as_raw(&self) -> *mut libusb_device {
+ self.device.as_ptr()
+ }
+
+ /// Get the context associated with this device
+ pub fn context(&self) -> &T {
+ &self.context
+ }
+
+ /// # Safety
+ ///
+ /// Converts an existing `libusb_device` pointer into a `Device<T>`.
+ /// `device` must be a pointer to a valid `libusb_device`. Rusb increments refcount.
+ pub unsafe fn from_libusb(context: T, device: NonNull<libusb_device>) -> Device<T> {
+ libusb_ref_device(device.as_ptr());
+
+ Device { context, device }
+ }
+
+ /// Reads the device descriptor.
+ pub fn device_descriptor(&self) -> crate::Result<DeviceDescriptor> {
+ let mut descriptor = mem::MaybeUninit::<libusb_device_descriptor>::uninit();
+
+ // since libusb 1.0.16, this function always succeeds
+ try_unsafe!(libusb_get_device_descriptor(
+ self.device.as_ptr(),
+ descriptor.as_mut_ptr()
+ ));
+
+ Ok(device_descriptor::from_libusb(unsafe {
+ descriptor.assume_init()
+ }))
+ }
+
+ /// Reads a configuration descriptor.
+ pub fn config_descriptor(&self, config_index: u8) -> crate::Result<ConfigDescriptor> {
+ let mut config = mem::MaybeUninit::<*const libusb_config_descriptor>::uninit();
+
+ try_unsafe!(libusb_get_config_descriptor(
+ self.device.as_ptr(),
+ config_index,
+ config.as_mut_ptr()
+ ));
+
+ Ok(unsafe { config_descriptor::from_libusb(config.assume_init()) })
+ }
+
+ /// Reads the configuration descriptor for the current configuration.
+ pub fn active_config_descriptor(&self) -> crate::Result<ConfigDescriptor> {
+ let mut config = mem::MaybeUninit::<*const libusb_config_descriptor>::uninit();
+
+ try_unsafe!(libusb_get_active_config_descriptor(
+ self.device.as_ptr(),
+ config.as_mut_ptr()
+ ));
+
+ Ok(unsafe { config_descriptor::from_libusb(config.assume_init()) })
+ }
+
+ /// Returns the number of the bus that the device is connected to.
+ pub fn bus_number(&self) -> u8 {
+ unsafe { libusb_get_bus_number(self.device.as_ptr()) }
+ }
+
+ /// Returns the device's address on the bus that it's connected to.
+ pub fn address(&self) -> u8 {
+ unsafe { libusb_get_device_address(self.device.as_ptr()) }
+ }
+
+ /// Returns the device's connection speed.
+ pub fn speed(&self) -> Speed {
+ fields::speed_from_libusb(unsafe { libusb_get_device_speed(self.device.as_ptr()) })
+ }
+
+ /// Opens the device.
+ pub fn open(&self) -> crate::Result<DeviceHandle<T>> {
+ let mut handle = mem::MaybeUninit::<*mut libusb_device_handle>::uninit();
+
+ try_unsafe!(libusb_open(self.device.as_ptr(), handle.as_mut_ptr()));
+
+ Ok(unsafe {
+ let ptr = NonNull::new(handle.assume_init()).ok_or(Error::NoDevice)?;
+ DeviceHandle::from_libusb(self.context.clone(), ptr)
+ })
+ }
+
+ /// Returns the device's port number
+ pub fn port_number(&self) -> u8 {
+ unsafe { libusb_get_port_number(self.device.as_ptr()) }
+ }
+
+ /// Returns the device's parent
+ pub fn get_parent(&self) -> Option<Self> {
+ let device = unsafe { libusb_get_parent(self.device.as_ptr()) };
+ NonNull::new(device)
+ .map(|device| unsafe { Device::from_libusb(self.context.clone(), device) })
+ }
+
+ /// Get the list of all port numbers from root for the specified device
+ pub fn port_numbers(&self) -> Result<Vec<u8>, Error> {
+ // As per the USB 3.0 specs, the current maximum limit for the depth is 7.
+ let mut ports = [0; 7];
+
+ let result = unsafe {
+ libusb_get_port_numbers(self.device.as_ptr(), ports.as_mut_ptr(), ports.len() as i32)
+ };
+
+ let ports_number = if result < 0 {
+ return Err(error::from_libusb(result));
+ } else {
+ result
+ };
+ Ok(ports[0..ports_number as usize].to_vec())
+ }
+}
diff --git a/src/device_descriptor.rs b/src/device_descriptor.rs
new file mode 100644
index 0000000..70c4bed
--- /dev/null
+++ b/src/device_descriptor.rs
@@ -0,0 +1,244 @@
+use std::fmt;
+
+use libusb1_sys::*;
+
+use crate::fields::Version;
+
+/// Describes a device.
+pub struct DeviceDescriptor {
+ descriptor: libusb_device_descriptor,
+}
+
+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)
+ }
+
+ /// Returns the manufacturer's version of the device.
+ pub fn device_version(&self) -> Version {
+ Version::from_bcd(self.descriptor.bcdDevice)
+ }
+
+ /// Returns the index of the string descriptor that contains the manufacturer name.
+ pub fn manufacturer_string_index(&self) -> Option<u8> {
+ match self.descriptor.iManufacturer {
+ 0 => None,
+ n => Some(n),
+ }
+ }
+
+ /// Returns the index of the string descriptor that contains the product name.
+ pub fn product_string_index(&self) -> Option<u8> {
+ match self.descriptor.iProduct {
+ 0 => None,
+ n => Some(n),
+ }
+ }
+
+ /// Returns the index of the string descriptor that contains the device's serial number.
+ pub fn serial_number_string_index(&self) -> Option<u8> {
+ match self.descriptor.iSerialNumber {
+ 0 => None,
+ n => Some(n),
+ }
+ }
+
+ /// Returns the device's class code.
+ pub fn class_code(&self) -> u8 {
+ self.descriptor.bDeviceClass
+ }
+
+ /// Returns the device's sub class code.
+ pub fn sub_class_code(&self) -> u8 {
+ self.descriptor.bDeviceSubClass
+ }
+
+ /// Returns the device's protocol code.
+ pub fn protocol_code(&self) -> u8 {
+ self.descriptor.bDeviceProtocol
+ }
+
+ /// Returns the device's vendor ID.
+ pub fn vendor_id(&self) -> u16 {
+ self.descriptor.idVendor
+ }
+
+ /// Returns the device's product ID.
+ pub fn product_id(&self) -> u16 {
+ self.descriptor.idProduct
+ }
+
+ /// Returns the maximum packet size of the device's first endpoint.
+ pub fn max_packet_size(&self) -> u8 {
+ self.descriptor.bMaxPacketSize0
+ }
+
+ /// Returns the number of config descriptors available for the device.
+ pub fn num_configurations(&self) -> u8 {
+ self.descriptor.bNumConfigurations
+ }
+}
+
+impl fmt::Debug for DeviceDescriptor {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let mut debug = fmt.debug_struct("DeviceDescriptor");
+
+ debug.field("bLength", &self.descriptor.bLength);
+ debug.field("bDescriptorType", &self.descriptor.bDescriptorType);
+ debug.field("bcdUSB", &self.descriptor.bcdUSB);
+ debug.field("bDeviceClass", &self.descriptor.bDeviceClass);
+ debug.field("bDeviceSubClass", &self.descriptor.bDeviceSubClass);
+ debug.field("bDeviceProtocol", &self.descriptor.bDeviceProtocol);
+ debug.field("bMaxPacketSize", &self.descriptor.bMaxPacketSize0);
+ debug.field("idVendor", &self.descriptor.idVendor);
+ debug.field("idProduct", &self.descriptor.idProduct);
+ debug.field("bcdDevice", &self.descriptor.bcdDevice);
+ debug.field("iManufacturer", &self.descriptor.iManufacturer);
+ debug.field("iProduct", &self.descriptor.iProduct);
+ debug.field("iSerialNumber", &self.descriptor.iSerialNumber);
+ debug.field("bNumConfigurations", &self.descriptor.bNumConfigurations);
+
+ debug.finish()
+ }
+}
+
+#[doc(hidden)]
+pub fn from_libusb(device: libusb_device_descriptor) -> DeviceDescriptor {
+ DeviceDescriptor { descriptor: device }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::fields::Version;
+
+ #[test]
+ fn it_has_usb_version() {
+ assert_eq!(
+ Version::from_bcd(0x1234),
+ super::from_libusb(device_descriptor!(bcdUSB: 0x1234)).usb_version()
+ );
+ }
+
+ #[test]
+ fn it_has_device_version() {
+ assert_eq!(
+ Version::from_bcd(0x1234),
+ super::from_libusb(device_descriptor!(bcdDevice: 0x1234)).device_version()
+ );
+ }
+
+ #[test]
+ fn it_has_manufacturer_string_index() {
+ assert_eq!(
+ Some(42),
+ super::from_libusb(device_descriptor!(iManufacturer: 42)).manufacturer_string_index()
+ );
+ }
+
+ #[test]
+ fn it_handles_missing_manufacturer_string_index() {
+ assert_eq!(
+ None,
+ super::from_libusb(device_descriptor!(iManufacturer: 0)).manufacturer_string_index()
+ );
+ }
+
+ #[test]
+ fn it_has_product_string_index() {
+ assert_eq!(
+ Some(42),
+ super::from_libusb(device_descriptor!(iProduct: 42)).product_string_index()
+ );
+ }
+
+ #[test]
+ fn it_handles_missing_product_string_index() {
+ assert_eq!(
+ None,
+ super::from_libusb(device_descriptor!(iProduct: 0)).product_string_index()
+ );
+ }
+
+ #[test]
+ fn it_has_serial_number_string_index() {
+ assert_eq!(
+ Some(42),
+ super::from_libusb(device_descriptor!(iSerialNumber: 42)).serial_number_string_index()
+ );
+ }
+
+ #[test]
+ fn it_handles_missing_serial_number_string_index() {
+ assert_eq!(
+ None,
+ super::from_libusb(device_descriptor!(iSerialNumber: 0)).serial_number_string_index()
+ );
+ }
+
+ #[test]
+ fn it_has_class_code() {
+ assert_eq!(
+ 42,
+ super::from_libusb(device_descriptor!(bDeviceClass: 42)).class_code()
+ );
+ }
+
+ #[test]
+ fn it_has_sub_class_code() {
+ assert_eq!(
+ 42,
+ super::from_libusb(device_descriptor!(bDeviceSubClass: 42)).sub_class_code()
+ );
+ }
+
+ #[test]
+ fn it_has_protocol_code() {
+ assert_eq!(
+ 42,
+ super::from_libusb(device_descriptor!(bDeviceProtocol: 42)).protocol_code()
+ );
+ }
+
+ #[test]
+ fn it_has_vendor_id() {
+ assert_eq!(
+ 42,
+ super::from_libusb(device_descriptor!(idVendor: 42)).vendor_id()
+ );
+ }
+
+ #[test]
+ fn it_has_product_id() {
+ assert_eq!(
+ 42,
+ super::from_libusb(device_descriptor!(idProduct: 42)).product_id()
+ );
+ }
+
+ #[test]
+ fn it_has_max_packet_size() {
+ assert_eq!(
+ 42,
+ super::from_libusb(device_descriptor!(bMaxPacketSize0: 42)).max_packet_size()
+ );
+ }
+
+ #[test]
+ fn it_has_num_configurations() {
+ assert_eq!(
+ 3,
+ super::from_libusb(device_descriptor!(bNumConfigurations: 3)).num_configurations()
+ );
+ }
+}
diff --git a/src/device_handle.rs b/src/device_handle.rs
new file mode 100644
index 0000000..3bf19ba
--- /dev/null
+++ b/src/device_handle.rs
@@ -0,0 +1,939 @@
+use std::{
+ fmt::{self, Debug},
+ mem,
+ ptr::NonNull,
+ sync::Mutex,
+ time::Duration,
+};
+
+use libc::{c_int, c_uchar, c_uint};
+use libusb1_sys::{constants::*, *};
+
+use crate::{
+ config_descriptor::ConfigDescriptor,
+ device::{self, Device},
+ device_descriptor::DeviceDescriptor,
+ error::{self, Error},
+ fields::{request_type, Direction, Recipient, RequestType},
+ interface_descriptor::InterfaceDescriptor,
+ language::Language,
+ UsbContext,
+};
+
+/// Bit set representing claimed USB interfaces.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+struct ClaimedInterfaces {
+ inner: [u128; 2],
+}
+
+impl ClaimedInterfaces {
+ /// Create a new bit set.
+ fn new() -> Self {
+ Self { inner: [0, 0] }
+ }
+
+ fn get_index_and_mask(interface: u8) -> (usize, u128) {
+ ((interface / 128) as usize, 1 << (interface % 128))
+ }
+
+ /// Mark `interface` as claimed.
+ fn insert(&mut self, interface: u8) {
+ let (index, mask) = ClaimedInterfaces::get_index_and_mask(interface);
+ self.inner[index] |= mask;
+ }
+
+ /// Mark `interface` as not claimed.
+ fn remove(&mut self, interface: u8) {
+ let (index, mask) = ClaimedInterfaces::get_index_and_mask(interface);
+ self.inner[index] &= !mask;
+ }
+
+ /// Returns true if this set contains `interface`.
+ fn contains(&self, interface: u8) -> bool {
+ let (index, mask) = ClaimedInterfaces::get_index_and_mask(interface);
+ self.inner[index] & mask != 0
+ }
+
+ /// Returns a count of the interfaces contained in this set.
+ fn size(&self) -> usize {
+ self.inner.iter().map(|v| v.count_ones()).sum::<u32>() as usize
+ }
+
+ /// Returns an iterator over the interfaces in this set.
+ fn iter(&self) -> ClaimedInterfacesIter {
+ ClaimedInterfacesIter::new(self)
+ }
+}
+
+/// Iterator over interfaces.
+struct ClaimedInterfacesIter<'a> {
+ // Next interface to check as a possible value to return from the interator.
+ index: u16,
+
+ // Number of elements remaining in this iterator.
+ remaining: usize,
+
+ // The ClaimedInterfaces object that we're iterating over.
+ source: &'a ClaimedInterfaces,
+}
+
+impl ClaimedInterfacesIter<'_> {
+ /// Create a new iterator over the interfaces in `source`.
+ fn new(source: &ClaimedInterfaces) -> ClaimedInterfacesIter {
+ ClaimedInterfacesIter {
+ index: 0,
+ remaining: source.size(),
+ source,
+ }
+ }
+}
+
+impl<'a> Iterator for ClaimedInterfacesIter<'a> {
+ type Item = u8;
+
+ fn next(&mut self) -> Option<u8> {
+ while self.index <= u8::MAX as u16 {
+ let index = self.index as u8;
+ let contains = self.source.contains(index);
+ self.index += 1;
+ if contains {
+ self.remaining -= 1;
+ return Some(index);
+ }
+ }
+ None
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.remaining, Some(self.remaining))
+ }
+}
+
+/// A handle to an open USB device.
+pub struct DeviceHandle<T: UsbContext> {
+ context: T,
+ handle: Option<NonNull<libusb_device_handle>>,
+ interfaces: Mutex<ClaimedInterfaces>,
+}
+
+impl<T: UsbContext> Drop for DeviceHandle<T> {
+ /// Closes the device.
+ fn drop(&mut self) {
+ unsafe {
+ let interfaces = self.interfaces.lock().unwrap();
+ for iface in interfaces.iter() {
+ libusb_release_interface(self.as_raw(), iface as c_int);
+ }
+
+ if let Some(handle) = self.handle {
+ libusb_close(handle.as_ptr());
+ }
+ }
+ }
+}
+
+unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
+unsafe impl<T: UsbContext> Sync for DeviceHandle<T> {}
+
+impl<T: UsbContext> Debug for DeviceHandle<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("DeviceHandle")
+ .field("device", &self.device())
+ .field("handle", &self.handle)
+ .field("interfaces", &*self.interfaces.lock().unwrap())
+ .finish()
+ }
+}
+
+impl<T: UsbContext + PartialEq> PartialEq for DeviceHandle<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.context == other.context
+ && self.handle == other.handle
+ && *self.interfaces.lock().unwrap() == *other.interfaces.lock().unwrap()
+ }
+}
+
+impl<T: UsbContext + PartialEq> Eq for DeviceHandle<T> {}
+
+impl<T: UsbContext> DeviceHandle<T> {
+ /// Get the raw libusb_device_handle pointer, for advanced use in unsafe code.
+ ///
+ /// This structure tracks claimed interfaces, and will get out if sync if interfaces are
+ /// manipulated externally. Use only libusb endpoint IO functions.
+ pub fn as_raw(&self) -> *mut libusb_device_handle {
+ // Safety: handle is Some() after initialization
+ match self.handle {
+ Some(it) => it.as_ptr(),
+ _ => unreachable!(),
+ }
+ }
+
+ /// Consumes the `DeviceHandle`, returning the raw libusb_device_handle
+ /// pointer, for advanced use in unsafe code.
+ ///
+ /// # Safety
+ ///
+ /// Panics if you have any claimed interfaces on this handle.
+ pub fn into_raw(mut self) -> *mut libusb_device_handle {
+ assert_eq!(self.interfaces.lock().unwrap().size(), 0);
+ match self.handle.take() {
+ Some(it) => it.as_ptr(),
+ _ => unreachable!(),
+ }
+ }
+
+ /// Get the context associated with this device
+ pub fn context(&self) -> &T {
+ &self.context
+ }
+
+ /// Get the device associated to this handle
+ pub fn device(&self) -> Device<T> {
+ unsafe {
+ device::Device::from_libusb(
+ self.context.clone(),
+ std::ptr::NonNull::new_unchecked(libusb_get_device(self.as_raw())),
+ )
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Converts an existing `libusb_device_handle` pointer into a `DeviceHandle<T>`.
+ /// `handle` must be a pointer to a valid `libusb_device_handle`. Rusb assumes ownership of the handle, and will close it on `drop`.
+ pub unsafe fn from_libusb(
+ context: T,
+ handle: NonNull<libusb_device_handle>,
+ ) -> DeviceHandle<T> {
+ DeviceHandle {
+ context,
+ handle: Some(handle),
+ interfaces: Mutex::new(ClaimedInterfaces::new()),
+ }
+ }
+
+ /// Returns the active configuration number.
+ pub fn active_configuration(&self) -> crate::Result<u8> {
+ let mut config = mem::MaybeUninit::<c_int>::uninit();
+
+ try_unsafe!(libusb_get_configuration(self.as_raw(), config.as_mut_ptr()));
+ Ok(unsafe { config.assume_init() } as u8)
+ }
+
+ /// Sets the device's active configuration.
+ 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(&self) -> crate::Result<()> {
+ try_unsafe!(libusb_set_configuration(self.as_raw(), -1));
+ Ok(())
+ }
+
+ /// Resets the device.
+ 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(&self, endpoint: u8) -> crate::Result<()> {
+ try_unsafe!(libusb_clear_halt(self.as_raw(), endpoint));
+ Ok(())
+ }
+
+ /// Indicates whether the device has an attached kernel driver.
+ ///
+ /// This method is not supported on all platforms.
+ pub fn kernel_driver_active(&self, iface: u8) -> crate::Result<bool> {
+ match unsafe { libusb_kernel_driver_active(self.as_raw(), c_int::from(iface)) } {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ err => Err(error::from_libusb(err)),
+ }
+ }
+
+ /// Detaches an attached kernel driver from the device.
+ ///
+ /// This method is not supported on all platforms.
+ pub fn detach_kernel_driver(&self, iface: u8) -> crate::Result<()> {
+ try_unsafe!(libusb_detach_kernel_driver(
+ self.as_raw(),
+ c_int::from(iface)
+ ));
+ Ok(())
+ }
+
+ /// Attaches a kernel driver to the device.
+ ///
+ /// This method is not supported on all platforms.
+ pub fn attach_kernel_driver(&self, iface: u8) -> crate::Result<()> {
+ try_unsafe!(libusb_attach_kernel_driver(
+ self.as_raw(),
+ c_int::from(iface)
+ ));
+ Ok(())
+ }
+
+ /// Enable/disable automatic kernel driver detachment.
+ ///
+ /// When this is enabled rusb will automatically detach the
+ /// kernel driver on an interface when claiming the interface, and
+ /// attach it when releasing the interface.
+ ///
+ /// 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(&self, auto_detach: bool) -> crate::Result<()> {
+ try_unsafe!(libusb_set_auto_detach_kernel_driver(
+ self.as_raw(),
+ auto_detach.into()
+ ));
+ Ok(())
+ }
+
+ /// Claims one of the device's interfaces.
+ ///
+ /// 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(&self, iface: u8) -> crate::Result<()> {
+ try_unsafe!(libusb_claim_interface(self.as_raw(), c_int::from(iface)));
+ self.interfaces.lock().unwrap().insert(iface);
+ Ok(())
+ }
+
+ /// Releases a claimed interface.
+ pub fn release_interface(&self, iface: u8) -> crate::Result<()> {
+ try_unsafe!(libusb_release_interface(self.as_raw(), c_int::from(iface)));
+ self.interfaces.lock().unwrap().remove(iface);
+ Ok(())
+ }
+
+ /// Sets an interface's active setting.
+ 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),
+ c_int::from(setting)
+ ));
+ Ok(())
+ }
+
+ /// Reads from an interrupt endpoint.
+ ///
+ /// 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 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.
+ ///
+ /// ## Errors
+ ///
+ /// If this function encounters any form of error while fulfilling the transfer request, an
+ /// error variant will be returned. If an error variant is returned, no bytes were read.
+ ///
+ /// The errors returned by this function include:
+ ///
+ /// * `InvalidParam` if the endpoint is not an input endpoint.
+ /// * `Timeout` if the transfer timed out.
+ /// * `Pipe` if the endpoint halted.
+ /// * `Overflow` if the device offered more data.
+ /// * `NoDevice` if the device has been disconnected.
+ /// * `Io` if the transfer encountered an I/O error.
+ pub fn read_interrupt(
+ &self,
+ endpoint: u8,
+ buf: &mut [u8],
+ timeout: Duration,
+ ) -> crate::Result<usize> {
+ if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_IN {
+ return Err(Error::InvalidParam);
+ }
+ let mut transferred = mem::MaybeUninit::<c_int>::uninit();
+ unsafe {
+ match libusb_interrupt_transfer(
+ self.as_raw(),
+ endpoint,
+ buf.as_mut_ptr() as *mut c_uchar,
+ buf.len() as c_int,
+ transferred.as_mut_ptr(),
+ timeout.as_millis() as c_uint,
+ ) {
+ 0 => Ok(transferred.assume_init() as usize),
+ err if err == LIBUSB_ERROR_INTERRUPTED => {
+ let transferred = transferred.assume_init();
+ if transferred > 0 {
+ Ok(transferred as usize)
+ } else {
+ Err(error::from_libusb(err))
+ }
+ }
+ err => Err(error::from_libusb(err)),
+ }
+ }
+ }
+
+ /// Writes to an interrupt endpoint.
+ ///
+ /// 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 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.
+ ///
+ /// ## Errors
+ ///
+ /// If this function encounters any form of error while fulfilling the transfer request, an
+ /// error variant will be returned. If an error variant is returned, no bytes were written.
+ ///
+ /// The errors returned by this function include:
+ ///
+ /// * `InvalidParam` if the endpoint is not an output endpoint.
+ /// * `Timeout` if the transfer timed out.
+ /// * `Pipe` if the endpoint halted.
+ /// * `NoDevice` if the device has been disconnected.
+ /// * `Io` if the transfer encountered an I/O error.
+ pub fn write_interrupt(
+ &self,
+ endpoint: u8,
+ buf: &[u8],
+ timeout: Duration,
+ ) -> crate::Result<usize> {
+ if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_OUT {
+ return Err(Error::InvalidParam);
+ }
+ let mut transferred = mem::MaybeUninit::<c_int>::uninit();
+ unsafe {
+ match libusb_interrupt_transfer(
+ self.as_raw(),
+ endpoint,
+ buf.as_ptr() as *mut c_uchar,
+ buf.len() as c_int,
+ transferred.as_mut_ptr(),
+ timeout.as_millis() as c_uint,
+ ) {
+ 0 => Ok(transferred.assume_init() as usize),
+ err if err == LIBUSB_ERROR_INTERRUPTED => {
+ let transferred = transferred.assume_init();
+ if transferred > 0 {
+ Ok(transferred as usize)
+ } else {
+ Err(error::from_libusb(err))
+ }
+ }
+ err => Err(error::from_libusb(err)),
+ }
+ }
+ }
+
+ /// Reads from a bulk endpoint.
+ ///
+ /// 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 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.
+ ///
+ /// ## Errors
+ ///
+ /// If this function encounters any form of error while fulfilling the transfer request, an
+ /// error variant will be returned. If an error variant is returned, no bytes were read.
+ ///
+ /// The errors returned by this function include:
+ ///
+ /// * `InvalidParam` if the endpoint is not an input endpoint.
+ /// * `Timeout` if the transfer timed out.
+ /// * `Pipe` if the endpoint halted.
+ /// * `Overflow` if the device offered more data.
+ /// * `NoDevice` if the device has been disconnected.
+ /// * `Io` if the transfer encountered an I/O error.
+ pub fn read_bulk(
+ &self,
+ endpoint: u8,
+ buf: &mut [u8],
+ timeout: Duration,
+ ) -> crate::Result<usize> {
+ if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_IN {
+ return Err(Error::InvalidParam);
+ }
+ let mut transferred = mem::MaybeUninit::<c_int>::uninit();
+ unsafe {
+ match libusb_bulk_transfer(
+ self.as_raw(),
+ endpoint,
+ buf.as_mut_ptr() as *mut c_uchar,
+ buf.len() as c_int,
+ transferred.as_mut_ptr(),
+ timeout.as_millis() as c_uint,
+ ) {
+ 0 => Ok(transferred.assume_init() as usize),
+ err if err == LIBUSB_ERROR_INTERRUPTED || err == LIBUSB_ERROR_TIMEOUT => {
+ let transferred = transferred.assume_init();
+ if transferred > 0 {
+ Ok(transferred as usize)
+ } else {
+ Err(error::from_libusb(err))
+ }
+ }
+ err => Err(error::from_libusb(err)),
+ }
+ }
+ }
+
+ /// Writes to a bulk endpoint.
+ ///
+ /// 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 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.
+ ///
+ /// ## Errors
+ ///
+ /// If this function encounters any form of error while fulfilling the transfer request, an
+ /// error variant will be returned. If an error variant is returned, no bytes were written.
+ ///
+ /// The errors returned by this function include:
+ ///
+ /// * `InvalidParam` if the endpoint is not an output endpoint.
+ /// * `Timeout` if the transfer timed out.
+ /// * `Pipe` if the endpoint halted.
+ /// * `NoDevice` if the device has been disconnected.
+ /// * `Io` if the transfer encountered an I/O error.
+ pub fn write_bulk(&self, endpoint: u8, buf: &[u8], timeout: Duration) -> crate::Result<usize> {
+ if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_OUT {
+ return Err(Error::InvalidParam);
+ }
+ let mut transferred = mem::MaybeUninit::<c_int>::uninit();
+ unsafe {
+ match libusb_bulk_transfer(
+ self.as_raw(),
+ endpoint,
+ buf.as_ptr() as *mut c_uchar,
+ buf.len() as c_int,
+ transferred.as_mut_ptr(),
+ timeout.as_millis() as c_uint,
+ ) {
+ 0 => Ok(transferred.assume_init() as usize),
+ err if err == LIBUSB_ERROR_INTERRUPTED || err == LIBUSB_ERROR_TIMEOUT => {
+ let transferred = transferred.assume_init();
+ if transferred > 0 {
+ Ok(transferred as usize)
+ } else {
+ Err(error::from_libusb(err))
+ }
+ }
+ err => Err(error::from_libusb(err)),
+ }
+ }
+ }
+
+ /// Reads data using a control transfer.
+ ///
+ /// 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 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`
+ /// respectively). The values for each of these parameters shall be given in host-endian byte
+ /// order. The value for the `request_type` parameter can be built with the helper function,
+ /// [request_type()](fn.request_type.html). The meaning of the other parameters depends on the
+ /// type of control request.
+ ///
+ /// If the return value is `Ok(n)`, then `buf` is populated with `n` bytes of data.
+ ///
+ /// ## Errors
+ ///
+ /// If this function encounters any form of error while fulfilling the transfer request, an
+ /// error variant will be returned. If an error variant is returned, no bytes were read.
+ ///
+ /// The errors returned by this function include:
+ ///
+ /// * `InvalidParam` if `request_type` does not specify a read transfer.
+ /// * `Timeout` if the transfer timed out.
+ /// * `Pipe` if the control request was not supported by the device.
+ /// * `NoDevice` if the device has been disconnected.
+ /// * `Io` if the transfer encountered an I/O error.
+ pub fn read_control(
+ &self,
+ request_type: u8,
+ request: u8,
+ value: u16,
+ index: u16,
+ buf: &mut [u8],
+ timeout: Duration,
+ ) -> crate::Result<usize> {
+ if request_type & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_IN {
+ return Err(Error::InvalidParam);
+ }
+ let res = unsafe {
+ libusb_control_transfer(
+ self.as_raw(),
+ request_type,
+ request,
+ value,
+ index,
+ buf.as_mut_ptr() as *mut c_uchar,
+ buf.len() as u16,
+ timeout.as_millis() as c_uint,
+ )
+ };
+
+ if res < 0 {
+ Err(error::from_libusb(res))
+ } else {
+ Ok(res as usize)
+ }
+ }
+
+ /// Writes data using a control transfer.
+ ///
+ /// 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 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`
+ /// respectively). The values for each of these parameters shall be given in host-endian byte
+ /// order. The value for the `request_type` parameter can be built with the helper function,
+ /// [request_type()](fn.request_type.html). The meaning of the other parameters depends on the
+ /// type of control request.
+ ///
+ /// If the return value is `Ok(n)`, then `n` bytes of `buf` were transfered.
+ ///
+ /// ## Errors
+ ///
+ /// If this function encounters any form of error while fulfilling the transfer request, an
+ /// error variant will be returned. If an error variant is returned, no bytes were read.
+ ///
+ /// The errors returned by this function include:
+ ///
+ /// * `InvalidParam` if `request_type` does not specify a write transfer.
+ /// * `Timeout` if the transfer timed out.
+ /// * `Pipe` if the control request was not supported by the device.
+ /// * `NoDevice` if the device has been disconnected.
+ /// * `Io` if the transfer encountered an I/O error.
+ pub fn write_control(
+ &self,
+ request_type: u8,
+ request: u8,
+ value: u16,
+ index: u16,
+ buf: &[u8],
+ timeout: Duration,
+ ) -> crate::Result<usize> {
+ if request_type & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_OUT {
+ return Err(Error::InvalidParam);
+ }
+ let res = unsafe {
+ libusb_control_transfer(
+ self.as_raw(),
+ request_type,
+ request,
+ value,
+ index,
+ buf.as_ptr() as *mut c_uchar,
+ buf.len() as u16,
+ timeout.as_millis() as c_uint,
+ )
+ };
+
+ if res < 0 {
+ Err(error::from_libusb(res))
+ } else {
+ Ok(res as usize)
+ }
+ }
+
+ /// Reads the languages supported by the device's string descriptors.
+ ///
+ /// This function returns a list of languages that can be used to read the device's string
+ /// descriptors.
+ pub fn read_languages(&self, timeout: Duration) -> crate::Result<Vec<Language>> {
+ let mut buf = [0u8; 255];
+
+ let len = self.read_control(
+ request_type(Direction::In, RequestType::Standard, Recipient::Device),
+ LIBUSB_REQUEST_GET_DESCRIPTOR,
+ u16::from(LIBUSB_DT_STRING) << 8,
+ 0,
+ &mut buf,
+ timeout,
+ )?;
+
+ if len < 2 || buf[0] != len as u8 || len & 0x01 != 0 {
+ return Err(Error::BadDescriptor);
+ }
+
+ if len == 2 {
+ return Ok(Vec::new());
+ }
+
+ Ok(buf[0..len]
+ .chunks(2)
+ .skip(1)
+ .map(|chunk| {
+ let lang_id = u16::from(chunk[0]) | u16::from(chunk[1]) << 8;
+ crate::language::from_lang_id(lang_id)
+ })
+ .collect())
+ }
+
+ /// Reads a ascii string descriptor from the device.
+ ///
+ pub fn read_string_descriptor_ascii(&self, index: u8) -> crate::Result<String> {
+ let mut buf = Vec::<u8>::with_capacity(255);
+
+ let ptr = buf.as_mut_ptr() as *mut c_uchar;
+ let capacity = buf.capacity() as i32;
+
+ let res =
+ unsafe { libusb_get_string_descriptor_ascii(self.as_raw(), index, ptr, capacity) };
+
+ if res < 0 {
+ return Err(error::from_libusb(res));
+ }
+
+ unsafe {
+ buf.set_len(res as usize);
+ }
+
+ String::from_utf8(buf).map_err(|_| Error::Other)
+ }
+
+ /// Reads a string descriptor from the device.
+ ///
+ /// `language` should be one of the languages returned from [`read_languages`](#method.read_languages).
+ pub fn read_string_descriptor(
+ &self,
+ language: Language,
+ index: u8,
+ timeout: Duration,
+ ) -> crate::Result<String> {
+ let mut buf = [0u16; 128];
+
+ let len = {
+ // SAFETY: since we create slice from existing slice pointer valid
+ // alignment of [u8] less or equal to the [u16]
+ // size is less then allocated buffer (128 * 2 = 256 => 256 < 255)
+ let buf = unsafe {
+ std::slice::from_raw_parts_mut(
+ buf.as_mut_ptr().cast::<u8>(),
+ 255, // Some devices choke on size > 255
+ )
+ };
+
+ let len = self.read_control(
+ request_type(Direction::In, RequestType::Standard, Recipient::Device),
+ LIBUSB_REQUEST_GET_DESCRIPTOR,
+ u16::from(LIBUSB_DT_STRING) << 8 | u16::from(index),
+ language.lang_id(),
+ buf,
+ timeout,
+ )?;
+
+ if len < 2 || buf[0] != len as u8 || len & 0x01 != 0 {
+ return Err(Error::BadDescriptor);
+ }
+
+ len
+ };
+
+ if len == 2 {
+ return Ok(String::new());
+ }
+
+ // len in bytes, skip first element(it's contain descriptor type and len)
+ String::from_utf16(&buf[1..(len / 2)]).map_err(|_| Error::Other)
+ }
+
+ /// Reads the device's manufacturer string descriptor (ascii).
+ pub fn read_manufacturer_string_ascii(
+ &self,
+ device: &DeviceDescriptor,
+ ) -> crate::Result<String> {
+ match device.manufacturer_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor_ascii(n),
+ }
+ }
+
+ /// Reads the device's manufacturer string descriptor.
+ pub fn read_manufacturer_string(
+ &self,
+ language: Language,
+ device: &DeviceDescriptor,
+ timeout: Duration,
+ ) -> crate::Result<String> {
+ match device.manufacturer_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor(language, n, timeout),
+ }
+ }
+
+ /// Reads the device's product string descriptor (ascii).
+ pub fn read_product_string_ascii(&self, device: &DeviceDescriptor) -> crate::Result<String> {
+ match device.product_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor_ascii(n),
+ }
+ }
+
+ /// Reads the device's product string descriptor.
+ pub fn read_product_string(
+ &self,
+ language: Language,
+ device: &DeviceDescriptor,
+ timeout: Duration,
+ ) -> crate::Result<String> {
+ match device.product_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor(language, n, timeout),
+ }
+ }
+
+ /// Reads the device's serial number string descriptor (ascii).
+ pub fn read_serial_number_string_ascii(
+ &self,
+ device: &DeviceDescriptor,
+ ) -> crate::Result<String> {
+ match device.serial_number_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor_ascii(n),
+ }
+ }
+
+ /// Reads the device's serial number string descriptor.
+ pub fn read_serial_number_string(
+ &self,
+ language: Language,
+ device: &DeviceDescriptor,
+ timeout: Duration,
+ ) -> crate::Result<String> {
+ match device.serial_number_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor(language, n, timeout),
+ }
+ }
+
+ /// Reads the string descriptor for a configuration's description.
+ pub fn read_configuration_string(
+ &self,
+ language: Language,
+ configuration: &ConfigDescriptor,
+ timeout: Duration,
+ ) -> crate::Result<String> {
+ match configuration.description_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor(language, n, timeout),
+ }
+ }
+
+ /// Reads the string descriptor for a interface's description.
+ pub fn read_interface_string(
+ &self,
+ language: Language,
+ interface: &InterfaceDescriptor,
+ timeout: Duration,
+ ) -> crate::Result<String> {
+ match interface.description_string_index() {
+ None => Err(Error::InvalidParam),
+ Some(n) => self.read_string_descriptor(language, n, timeout),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ClaimedInterfaces;
+ use std::u8;
+
+ #[test]
+ fn claimed_interfaces_empty() {
+ let empty = ClaimedInterfaces::new();
+ assert_eq!(empty.size(), 0);
+ for i in 0..=u8::MAX {
+ assert!(!empty.contains(i), "empty set should not contain {}", i);
+ }
+
+ let mut iter = empty.iter();
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+ assert_eq!(iter.next(), None);
+ }
+
+ #[test]
+ fn claimed_interfaces_one_element() {
+ let mut interfaces = ClaimedInterfaces::new();
+ interfaces.insert(94);
+ assert_eq!(interfaces.size(), 1);
+ assert!(interfaces.contains(94));
+ for i in 0..=u8::MAX {
+ if i == 94 {
+ continue;
+ }
+ assert!(
+ !interfaces.contains(i),
+ "interfaces should not contain {}",
+ i
+ );
+ }
+
+ let mut iter = interfaces.iter();
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+ assert_eq!(iter.next(), Some(94));
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+ assert_eq!(iter.next(), None);
+ }
+
+ #[test]
+ fn claimed_interfaces_many_elements() {
+ let mut interfaces = ClaimedInterfaces::new();
+ let elements = vec![94, 0, 255, 17, 183, 6];
+
+ for (index, &interface) in elements.iter().enumerate() {
+ interfaces.insert(interface);
+ assert_eq!(interfaces.size(), index + 1);
+ }
+
+ // Validate contains().
+ for &interface in elements.iter() {
+ assert!(
+ interfaces.contains(interface),
+ "interfaces should contain {}",
+ interface
+ );
+ }
+
+ // Validate iter().
+ let contents = interfaces.iter().collect::<Vec<_>>().sort();
+ assert_eq!(contents, elements.clone().sort());
+
+ // Validate size_hint().
+ let mut iter = interfaces.iter();
+ let mut read = 0;
+ loop {
+ assert!(
+ read <= elements.len(),
+ "read elements {} should not exceed elements size {}",
+ read,
+ elements.len()
+ );
+ let remaining = elements.len() - read;
+ assert_eq!(iter.size_hint(), (remaining, Some(remaining)));
+ match iter.next() {
+ Some(_) => read += 1,
+ None => break,
+ }
+ }
+ }
+}
diff --git a/src/device_list.rs b/src/device_list.rs
new file mode 100644
index 0000000..3eaf757
--- /dev/null
+++ b/src/device_list.rs
@@ -0,0 +1,120 @@
+use libc::c_int;
+
+use std::{mem, slice};
+
+use crate::{
+ context::{GlobalContext, UsbContext},
+ device::{self, Device},
+ error,
+};
+use libusb1_sys::*;
+
+/// A list of detected USB devices.
+pub struct DeviceList<T: UsbContext> {
+ context: T,
+ list: *const *mut libusb_device,
+ len: usize,
+}
+
+impl<T: UsbContext> Drop for DeviceList<T> {
+ /// Frees the device list.
+ fn drop(&mut self) {
+ unsafe {
+ libusb_free_device_list(self.list, 1);
+ }
+ }
+}
+
+impl DeviceList<GlobalContext> {
+ pub fn new() -> crate::Result<DeviceList<GlobalContext>> {
+ let mut list = mem::MaybeUninit::<*const *mut libusb_device>::uninit();
+
+ let n =
+ unsafe { libusb_get_device_list(GlobalContext::default().as_raw(), list.as_mut_ptr()) };
+
+ if n < 0 {
+ Err(error::from_libusb(n as c_int))
+ } else {
+ Ok(unsafe {
+ DeviceList {
+ context: Default::default(),
+ list: list.assume_init(),
+ len: n as usize,
+ }
+ })
+ }
+ }
+}
+
+impl<T: UsbContext> DeviceList<T> {
+ pub fn new_with_context(context: T) -> crate::Result<DeviceList<T>> {
+ let mut list = mem::MaybeUninit::<*const *mut libusb_device>::uninit();
+
+ let len = unsafe { libusb_get_device_list(context.as_raw(), list.as_mut_ptr()) };
+
+ if len < 0 {
+ Err(error::from_libusb(len as c_int))
+ } else {
+ Ok(unsafe {
+ DeviceList {
+ context,
+ list: list.assume_init(),
+ len: len as usize,
+ }
+ })
+ }
+ }
+
+ /// Returns the number of devices in the list.
+ pub fn len(&self) -> usize {
+ self.len
+ }
+
+ /// Returns true if the list is empty, else returns false.
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ /// Returns an iterator over the devices in the list.
+ ///
+ /// The iterator yields a sequence of `Device` objects.
+ pub fn iter(&self) -> Devices<T> {
+ Devices {
+ context: self.context.clone(),
+ devices: unsafe { slice::from_raw_parts(self.list, self.len) },
+ index: 0,
+ }
+ }
+}
+
+/// Iterator over detected USB devices.
+pub struct Devices<'a, T> {
+ context: T,
+ devices: &'a [*mut libusb_device],
+ index: usize,
+}
+
+impl<'a, T: UsbContext> Iterator for Devices<'a, T> {
+ type Item = Device<T>;
+
+ fn next(&mut self) -> Option<Device<T>> {
+ if self.index < self.devices.len() {
+ let device = self.devices[self.index];
+
+ self.index += 1;
+ Some(unsafe {
+ device::Device::from_libusb(
+ self.context.clone(),
+ std::ptr::NonNull::new_unchecked(device),
+ )
+ })
+ } else {
+ None
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let remaining = self.devices.len() - self.index;
+ (remaining, Some(remaining))
+ }
+}
diff --git a/src/endpoint_descriptor.rs b/src/endpoint_descriptor.rs
new file mode 100644
index 0000000..cefa70e
--- /dev/null
+++ b/src/endpoint_descriptor.rs
@@ -0,0 +1,286 @@
+use std::{fmt, slice};
+
+use libusb1_sys::{constants::*, libusb_endpoint_descriptor};
+
+use crate::fields::{Direction, SyncType, TransferType, UsageType};
+
+/// Describes an endpoint.
+pub struct EndpointDescriptor<'a> {
+ descriptor: &'a libusb_endpoint_descriptor,
+}
+
+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
+ }
+
+ /// Returns the endpoint number.
+ pub fn number(&self) -> u8 {
+ self.descriptor.bEndpointAddress & 0x07
+ }
+
+ /// Returns the endpoint's direction.
+ pub fn direction(&self) -> Direction {
+ match self.descriptor.bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK {
+ LIBUSB_ENDPOINT_OUT => Direction::Out,
+ LIBUSB_ENDPOINT_IN | _ => Direction::In,
+ }
+ }
+
+ /// Returns the endpoint's transfer type.
+ pub fn transfer_type(&self) -> TransferType {
+ match self.descriptor.bmAttributes & LIBUSB_TRANSFER_TYPE_MASK {
+ LIBUSB_TRANSFER_TYPE_CONTROL => TransferType::Control,
+ LIBUSB_TRANSFER_TYPE_ISOCHRONOUS => TransferType::Isochronous,
+ LIBUSB_TRANSFER_TYPE_BULK => TransferType::Bulk,
+ LIBUSB_TRANSFER_TYPE_INTERRUPT | _ => TransferType::Interrupt,
+ }
+ }
+
+ /// Returns the endpoint's synchronisation mode.
+ ///
+ /// The return value of this method is only valid for isochronous endpoints.
+ pub fn sync_type(&self) -> SyncType {
+ match (self.descriptor.bmAttributes & LIBUSB_ISO_SYNC_TYPE_MASK) >> 2 {
+ LIBUSB_ISO_SYNC_TYPE_NONE => SyncType::NoSync,
+ LIBUSB_ISO_SYNC_TYPE_ASYNC => SyncType::Asynchronous,
+ LIBUSB_ISO_SYNC_TYPE_ADAPTIVE => SyncType::Adaptive,
+ LIBUSB_ISO_SYNC_TYPE_SYNC | _ => SyncType::Synchronous,
+ }
+ }
+
+ /// Returns the endpoint's usage type.
+ ///
+ /// The return value of this method is only valid for isochronous endpoints.
+ pub fn usage_type(&self) -> UsageType {
+ match (self.descriptor.bmAttributes & LIBUSB_ISO_USAGE_TYPE_MASK) >> 4 {
+ LIBUSB_ISO_USAGE_TYPE_DATA => UsageType::Data,
+ LIBUSB_ISO_USAGE_TYPE_FEEDBACK => UsageType::Feedback,
+ LIBUSB_ISO_USAGE_TYPE_IMPLICIT => UsageType::FeedbackData,
+ _ => UsageType::Reserved,
+ }
+ }
+
+ /// Returns the endpoint's maximum packet size.
+ pub fn max_packet_size(&self) -> u16 {
+ self.descriptor.wMaxPacketSize
+ }
+
+ /// Returns the endpoint's polling interval.
+ pub fn interval(&self) -> u8 {
+ self.descriptor.bInterval
+ }
+
+ /// Returns the unknown 'extra' bytes that libusb does not understand.
+ pub fn extra(&'a self) -> Option<&'a [u8]> {
+ unsafe {
+ match (*self.descriptor).extra_length {
+ len if len > 0 => Some(slice::from_raw_parts(
+ (*self.descriptor).extra,
+ len as usize,
+ )),
+ _ => None,
+ }
+ }
+ }
+
+ /// For audio devices only: return the rate at which synchronization feedback is provided.
+ pub fn refresh(&self) -> u8 {
+ self.descriptor.bRefresh
+ }
+
+ /// For audio devices only: return the address if the synch endpoint.
+ pub fn synch_address(&self) -> u8 {
+ self.descriptor.bSynchAddress
+ }
+}
+
+impl<'a> fmt::Debug for EndpointDescriptor<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let mut debug = fmt.debug_struct("EndpointDescriptor");
+
+ debug.field("bLength", &self.descriptor.bLength);
+ debug.field("bDescriptorType", &self.descriptor.bDescriptorType);
+ debug.field("bEndpointAddress", &self.descriptor.bEndpointAddress);
+ debug.field("bmAttributes", &self.descriptor.bmAttributes);
+ debug.field("wMaxPacketSize", &self.descriptor.wMaxPacketSize);
+ debug.field("bInterval", &self.descriptor.bInterval);
+
+ debug.finish()
+ }
+}
+
+#[doc(hidden)]
+pub(crate) fn from_libusb(endpoint: &libusb_endpoint_descriptor) -> EndpointDescriptor {
+ EndpointDescriptor {
+ descriptor: endpoint,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::fields::{Direction, SyncType, TransferType, UsageType};
+
+ #[test]
+ fn it_interprets_number_for_output_endpoints() {
+ assert_eq!(
+ 0,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0000_0000)).number()
+ );
+ assert_eq!(
+ 1,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0000_0001)).number()
+ );
+ }
+
+ #[test]
+ fn it_interprets_number_for_input_endpoints() {
+ assert_eq!(
+ 2,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b1000_0010)).number()
+ );
+ assert_eq!(
+ 3,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b1000_0011)).number()
+ );
+ }
+
+ #[test]
+ fn it_ignores_reserved_bits_in_address() {
+ assert_eq!(
+ 0,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0000_1000)).number()
+ );
+ assert_eq!(
+ 0,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0001_0000)).number()
+ );
+ assert_eq!(
+ 0,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0010_0000)).number()
+ );
+ assert_eq!(
+ 0,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0100_0000)).number()
+ );
+ assert_eq!(
+ 7,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b1111_1111)).number()
+ );
+ }
+
+ #[test]
+ fn it_interprets_direction_bit_in_address() {
+ assert_eq!(
+ Direction::Out,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b0000_0000)).direction()
+ );
+ assert_eq!(
+ Direction::In,
+ super::from_libusb(&endpoint_descriptor!(bEndpointAddress: 0b1000_0000)).direction()
+ );
+ }
+
+ #[test]
+ fn it_interprets_transfer_type_in_attributes() {
+ assert_eq!(
+ TransferType::Control,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0000)).transfer_type()
+ );
+ assert_eq!(
+ TransferType::Isochronous,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0001)).transfer_type()
+ );
+ assert_eq!(
+ TransferType::Bulk,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0010)).transfer_type()
+ );
+ assert_eq!(
+ TransferType::Interrupt,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0011)).transfer_type()
+ );
+ }
+
+ #[test]
+ fn it_interprets_synchronization_type_in_attributes() {
+ assert_eq!(
+ SyncType::NoSync,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0001)).sync_type()
+ );
+ assert_eq!(
+ SyncType::Asynchronous,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0101)).sync_type()
+ );
+ assert_eq!(
+ SyncType::Adaptive,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_1001)).sync_type()
+ );
+ assert_eq!(
+ SyncType::Synchronous,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_1101)).sync_type()
+ );
+ }
+
+ #[test]
+ fn it_interprets_usage_type_in_attributes() {
+ assert_eq!(
+ UsageType::Data,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0000_0001)).usage_type()
+ );
+ assert_eq!(
+ UsageType::Feedback,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0001_0001)).usage_type()
+ );
+ assert_eq!(
+ UsageType::FeedbackData,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0010_0001)).usage_type()
+ );
+ assert_eq!(
+ UsageType::Reserved,
+ super::from_libusb(&endpoint_descriptor!(bmAttributes: 0b0011_0001)).usage_type()
+ );
+ }
+
+ #[test]
+ fn it_has_max_packet_size() {
+ assert_eq!(
+ 64,
+ super::from_libusb(&endpoint_descriptor!(wMaxPacketSize: 64)).max_packet_size()
+ );
+ assert_eq!(
+ 4096,
+ super::from_libusb(&endpoint_descriptor!(wMaxPacketSize: 4096)).max_packet_size()
+ );
+ assert_eq!(
+ 65535,
+ super::from_libusb(&endpoint_descriptor!(wMaxPacketSize: 65535)).max_packet_size()
+ );
+ }
+
+ #[test]
+ fn it_has_interval() {
+ assert_eq!(
+ 1,
+ super::from_libusb(&endpoint_descriptor!(bInterval: 1)).interval()
+ );
+ assert_eq!(
+ 20,
+ super::from_libusb(&endpoint_descriptor!(bInterval: 20)).interval()
+ );
+ assert_eq!(
+ 255,
+ super::from_libusb(&endpoint_descriptor!(bInterval: 255)).interval()
+ );
+ }
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..199b483
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,108 @@
+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`.
+pub type Result<T> = result::Result<T, Error>;
+
+/// 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,
+
+ /// Invalid parameter.
+ InvalidParam,
+
+ /// Access denied (insufficient permissions).
+ Access,
+
+ /// No such device (it may have been disconnected).
+ NoDevice,
+
+ /// Entity not found.
+ NotFound,
+
+ /// Resource busy.
+ Busy,
+
+ /// Operation timed out.
+ Timeout,
+
+ /// Overflow.
+ Overflow,
+
+ /// Pipe error.
+ Pipe,
+
+ /// System call interrupted (perhaps due to signal).
+ Interrupted,
+
+ /// Insufficient memory.
+ NoMem,
+
+ /// Operation not supported or unimplemented on this platform.
+ NotSupported,
+
+ /// The device returned a malformed descriptor.
+ BadDescriptor,
+
+ /// Other error.
+ Other,
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
+ fmt.write_str(match self {
+ Error::Io => "Input/Output Error",
+ Error::InvalidParam => "Invalid parameter",
+ Error::Access => "Access denied (insufficient permissions)",
+ Error::NoDevice => "No such device (it may have been disconnected)",
+ Error::NotFound => "Entity not found",
+ Error::Busy => "Resource busy",
+ Error::Timeout => "Operation timed out",
+ Error::Overflow => "Overflow",
+ Error::Pipe => "Pipe error",
+ Error::Interrupted => "System call interrupted (perhaps due to signal)",
+ Error::NoMem => "Insufficient memory",
+ Error::NotSupported => "Operation not supported or unimplemented on this platform",
+ Error::BadDescriptor => "Malformed descriptor",
+ Error::Other => "Other error",
+ })
+ }
+}
+
+impl std::error::Error for Error {}
+
+#[doc(hidden)]
+pub(crate) fn from_libusb(err: i32) -> Error {
+ match err {
+ LIBUSB_ERROR_IO => Error::Io,
+ LIBUSB_ERROR_INVALID_PARAM => Error::InvalidParam,
+ LIBUSB_ERROR_ACCESS => Error::Access,
+ LIBUSB_ERROR_NO_DEVICE => Error::NoDevice,
+ LIBUSB_ERROR_NOT_FOUND => Error::NotFound,
+ LIBUSB_ERROR_BUSY => Error::Busy,
+ LIBUSB_ERROR_TIMEOUT => Error::Timeout,
+ LIBUSB_ERROR_OVERFLOW => Error::Overflow,
+ LIBUSB_ERROR_PIPE => Error::Pipe,
+ LIBUSB_ERROR_INTERRUPTED => Error::Interrupted,
+ LIBUSB_ERROR_NO_MEM => Error::NoMem,
+ LIBUSB_ERROR_NOT_SUPPORTED => Error::NotSupported,
+ LIBUSB_ERROR_OTHER | _ => Error::Other,
+ }
+}
+
+#[doc(hidden)]
+macro_rules! try_unsafe {
+ ($x:expr) => {
+ match unsafe { $x } {
+ 0 => (),
+ err => return Err($crate::error::from_libusb(err)),
+ }
+ };
+}
diff --git a/src/fields.rs b/src/fields.rs
new file mode 100644
index 0000000..2ead891
--- /dev/null
+++ b/src/fields.rs
@@ -0,0 +1,398 @@
+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.
+ Unknown,
+
+ /// The device is operating at low speed (1.5 Mbps).
+ Low,
+
+ /// The device is operating at full speed (12 Mbps).
+ Full,
+
+ /// The device is operating at high speed (480 Mbps).
+ High,
+
+ /// The device is operating at super speed (5 Gbps).
+ Super,
+
+ /// The device is operating at super speed (10 Gbps).
+ SuperPlus,
+}
+
+#[doc(hidden)]
+pub(crate) fn speed_from_libusb(n: c_int) -> Speed {
+ match n {
+ LIBUSB_SPEED_SUPER_PLUS => Speed::SuperPlus,
+ LIBUSB_SPEED_SUPER => Speed::Super,
+ LIBUSB_SPEED_HIGH => Speed::High,
+ LIBUSB_SPEED_FULL => Speed::Full,
+ LIBUSB_SPEED_LOW => Speed::Low,
+
+ LIBUSB_SPEED_UNKNOWN | _ => Speed::Unknown,
+ }
+}
+
+/// 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,
+
+ /// Direction for write (host to device) transfers.
+ Out,
+}
+
+/// 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,
+
+ /// Isochronous endpoint.
+ Isochronous,
+
+ /// Bulk endpoint.
+ Bulk,
+
+ /// Interrupt endpoint.
+ Interrupt,
+}
+
+/// Isochronous synchronization mode.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub enum SyncType {
+ /// No synchronisation.
+ NoSync,
+
+ /// Asynchronous.
+ Asynchronous,
+
+ /// Adaptive.
+ Adaptive,
+
+ /// Synchronous.
+ Synchronous,
+}
+
+/// Isochronous usage type.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub enum UsageType {
+ /// Data endpoint.
+ Data,
+
+ /// Feedback endpoint.
+ Feedback,
+
+ /// Explicit feedback data endpoint.
+ FeedbackData,
+
+ /// Reserved.
+ Reserved,
+}
+
+/// 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,
+
+ /// Requests that are defined by a device class, e.g., HID.
+ Class,
+
+ /// Vendor-specific requests.
+ Vendor,
+
+ /// Reserved for future use.
+ Reserved,
+}
+
+/// 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,
+
+ /// The recipient is an interface.
+ Interface,
+
+ /// The recipient is an endpoint.
+ Endpoint,
+
+ /// Other.
+ Other,
+}
+
+/// A three-part version consisting of major, minor, and sub minor components.
+///
+/// This can be used to represent versions of the format `J.M.N`, where `J` is the major version,
+/// `M` is the minor version, and `N` is the sub minor version. A version is constructed by
+/// providing the fields in the same order to the tuple. For example:
+///
+/// ```
+/// rusb::Version(0, 2, 1);
+/// ```
+///
+/// represents the version 0.2.1.
+///
+/// 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 {
+ /// Extracts a version from a binary coded decimal (BCD) field. BCD fields exist in USB
+ /// descriptors as 16-bit integers encoding a version as `0xJJMN`, where `JJ` is the major
+ /// version, `M` is the minor version, and `N` is the sub minor version. For example, 2.0 is
+ /// encoded as `0x0200` and 1.1 is encoded as `0x0110`.
+ pub fn from_bcd(mut raw: u16) -> Self {
+ let sub_minor: u8 = (raw & 0x000F) as u8;
+ raw >>= 4;
+
+ let minor: u8 = (raw & 0x000F) as u8;
+ raw >>= 4;
+
+ let mut major: u8 = (raw & 0x000F) as u8;
+ raw >>= 4;
+
+ major += (10 * raw) as u8;
+
+ Version(major, minor, sub_minor)
+ }
+
+ /// Returns the major version.
+ pub fn major(self) -> u8 {
+ let Version(major, _, _) = self;
+ major
+ }
+
+ /// Returns the minor version.
+ pub fn minor(self) -> u8 {
+ let Version(_, minor, _) = self;
+ minor
+ }
+
+ /// Returns the sub minor version.
+ pub fn sub_minor(self) -> u8 {
+ let Version(_, _, sub_minor) = self;
+ sub_minor
+ }
+}
+
+impl std::fmt::Display for Version {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}.{}.{}", self.major(), self.minor(), self.sub_minor())
+ }
+}
+
+/// Builds a value for the `bmRequestType` field of a control transfer setup packet.
+///
+/// The `bmRequestType` field of a USB control transfer setup packet is a bit field specifying
+/// three parameters, which are given to this function by corresponding enum values.
+///
+/// ## Examples
+///
+/// The following example returns a `bmRequestType` value for a standard inbound transfer from the
+/// device, which could be used for reading a device's descriptors:
+///
+/// ```no_run
+/// use rusb::{Direction,RequestType,Recipient};
+///
+/// rusb::request_type(Direction::In, RequestType::Standard, Recipient::Device);
+/// ```
+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,
+ };
+
+ value |= match request_type {
+ RequestType::Standard => LIBUSB_REQUEST_TYPE_STANDARD,
+ RequestType::Class => LIBUSB_REQUEST_TYPE_CLASS,
+ RequestType::Vendor => LIBUSB_REQUEST_TYPE_VENDOR,
+ RequestType::Reserved => LIBUSB_REQUEST_TYPE_RESERVED,
+ };
+
+ value |= match recipient {
+ Recipient::Device => LIBUSB_RECIPIENT_DEVICE,
+ Recipient::Interface => LIBUSB_RECIPIENT_INTERFACE,
+ Recipient::Endpoint => LIBUSB_RECIPIENT_ENDPOINT,
+ Recipient::Other => LIBUSB_RECIPIENT_OTHER,
+ };
+
+ value
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // Version
+
+ #[test]
+ fn version_returns_major_version() {
+ assert_eq!(1, Version(1, 0, 0).major());
+ assert_eq!(2, Version(2, 0, 0).major());
+ }
+
+ #[test]
+ fn version_returns_minor_version() {
+ assert_eq!(1, Version(0, 1, 0).minor());
+ assert_eq!(2, Version(0, 2, 0).minor());
+ }
+
+ #[test]
+ fn version_returns_sub_minor_version() {
+ assert_eq!(1, Version(0, 0, 1).sub_minor());
+ assert_eq!(2, Version(0, 0, 2).sub_minor());
+ }
+
+ #[test]
+ fn version_parses_major_version() {
+ assert_eq!(3, Version::from_bcd(0x0300).major());
+ }
+
+ #[test]
+ fn version_parses_long_major_version() {
+ assert_eq!(12, Version::from_bcd(0x1200).major());
+ }
+
+ #[test]
+ fn version_parses_minor_version() {
+ assert_eq!(1, Version::from_bcd(0x0010).minor());
+ assert_eq!(2, Version::from_bcd(0x0020).minor());
+ }
+
+ #[test]
+ fn version_parses_sub_minor_version() {
+ assert_eq!(1, Version::from_bcd(0x0001).sub_minor());
+ assert_eq!(2, Version::from_bcd(0x0002).sub_minor());
+ }
+
+ #[test]
+ fn version_parses_full_version() {
+ assert_eq!(Version(12, 3, 4), Version::from_bcd(0x1234));
+ }
+
+ #[test]
+ fn version_display() {
+ assert_eq!(Version(2, 45, 13).to_string(), "2.45.13");
+ }
+
+ #[test]
+ fn version_ord() {
+ assert!(Version(0, 0, 0) < Version(1, 2, 3));
+ assert!(Version(1, 0, 0) < Version(1, 2, 3));
+ assert!(Version(1, 2, 0) < Version(1, 2, 3));
+ assert!(Version(1, 2, 0) < Version(1, 3, 0));
+ assert!(Version(255, 255, 255) > Version(254, 0, 0));
+ assert!(Version(0, 255, 0) > Version(0, 254, 255));
+ }
+
+ // request_type for direction
+
+ #[test]
+ fn request_type_builds_value_for_out_direction() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Standard, Recipient::Device) & 0x80,
+ 0x00
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_in_direction() {
+ assert_eq!(
+ request_type(Direction::In, RequestType::Standard, Recipient::Device) & 0x80,
+ 0x80
+ );
+ }
+
+ // request_type for request type
+
+ #[test]
+ fn request_type_builds_value_for_standard_request() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Standard, Recipient::Device) & 0x60,
+ 0x00
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_class_request() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Class, Recipient::Device) & 0x60,
+ 0x20
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_vendor_request() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Vendor, Recipient::Device) & 0x60,
+ 0x40
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_reserved_request() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Reserved, Recipient::Device) & 0x60,
+ 0x60
+ );
+ }
+
+ // request_type for recipient
+
+ #[test]
+ fn request_type_builds_value_for_device_recipient() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Standard, Recipient::Device) & 0x0F,
+ 0x00
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_interface_recipient() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Standard, Recipient::Interface) & 0x0F,
+ 0x01
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_endpoint_recipient() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Standard, Recipient::Endpoint) & 0x0F,
+ 0x02
+ );
+ }
+
+ #[test]
+ fn request_type_builds_value_for_other_recipient() {
+ assert_eq!(
+ request_type(Direction::Out, RequestType::Standard, Recipient::Other) & 0x0F,
+ 0x03
+ );
+ }
+}
diff --git a/src/hotplug.rs b/src/hotplug.rs
new file mode 100644
index 0000000..e067ffe
--- /dev/null
+++ b/src/hotplug.rs
@@ -0,0 +1,211 @@
+use crate::constants::{
+ LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
+ LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_NO_FLAGS,
+};
+use crate::ffi::{
+ libusb_context, libusb_device, libusb_hotplug_callback_handle,
+ libusb_hotplug_deregister_callback, libusb_hotplug_event, libusb_hotplug_register_callback,
+};
+use crate::{error, Device, UsbContext};
+use std::{
+ borrow::Borrow,
+ ffi::c_void,
+ fmt::{self, Debug},
+ os::raw::c_int,
+};
+
+/// When handling a [method@Hotplug::device_arrived] event it is considered safe to call
+/// any `rusb` function that takes a [`Device`]. It also safe to open a device and
+/// submit **asynchronous** transfers.
+/// However, most other functions that take a [`DeviceHandle`] are **not safe** to call.
+/// Examples of such functions are any of the synchronous API functions or
+/// the blocking functions that retrieve various USB descriptors.
+/// These functions must be used outside of the context of the [Hotplug] functions.
+///
+/// [`Device`]: crate::Device
+/// [`DeviceHandle`]: crate::DeviceHandle
+/// [`Context::unregister_callback`]: method@crate::Context::unregister_callback
+pub trait Hotplug<T: UsbContext>: Send {
+ fn device_arrived(&mut self, device: Device<T>);
+ fn device_left(&mut self, device: Device<T>);
+}
+
+#[derive(Debug)]
+#[must_use = "USB hotplug callbacks will be deregistered if the registration is dropped"]
+pub struct Registration<T: UsbContext> {
+ handle: libusb_hotplug_callback_handle,
+ call_back: Box<CallbackData<T>>,
+}
+
+impl<T: UsbContext> Registration<T> {
+ fn get_handle(&self) -> libusb_hotplug_callback_handle {
+ self.handle
+ }
+}
+
+impl<T: UsbContext> Drop for Registration<T> {
+ fn drop(&mut self) {
+ unsafe {
+ libusb_hotplug_deregister_callback(self.call_back.context.as_raw(), self.get_handle())
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[doc(alias = "libusb_hotplug_register_callback")]
+/// Builds hotplug [Registration] with custom configuration values.
+pub struct HotplugBuilder {
+ vendor_id: Option<u16>,
+ product_id: Option<u16>,
+ class: Option<u8>,
+ enumerate: bool,
+}
+
+impl HotplugBuilder {
+ /// Returns a new builder with the no filter
+ /// Devices can optionally be filtered by [HotplugBuilder::vendor_id]
+ /// and [HotplugBuilder::product_id]
+ ///
+ /// Registration is done by by calling [`register`].
+ ///
+ /// [`register`]: method@Self::register
+ pub fn new() -> Self {
+ HotplugBuilder {
+ vendor_id: None,
+ product_id: None,
+ class: None,
+ enumerate: false,
+ }
+ }
+
+ /// Devices can optionally be filtered by vendor
+ pub fn vendor_id(&mut self, vendor_id: u16) -> &mut Self {
+ self.vendor_id = Some(vendor_id);
+ self
+ }
+
+ /// Devices can optionally be filtered by product id
+ pub fn product_id(&mut self, product_id: u16) -> &mut Self {
+ self.product_id = Some(product_id);
+ self
+ }
+
+ /// Devices can optionally be filtered by class
+ pub fn class(&mut self, class: u8) -> &mut Self {
+ self.class = Some(class);
+ self
+ }
+
+ /// If `enumerate` is `true`, then devices that are already
+ /// connected will cause your callback's [Hotplug::device_arrived] method to be
+ /// called for them.
+ pub fn enumerate(&mut self, enumerate: bool) -> &mut Self {
+ self.enumerate = enumerate;
+ self
+ }
+
+ /// Register a `callback` to be called on hotplug events. The callback's
+ /// [method@Hotplug::device_arrived] method is called when a new device is added to
+ /// the bus, and [method@Hotplug::device_left] is called when it is removed.
+ ///
+ /// The callback will remain registered until the returned [Registration] is
+ /// dropped, which can be done explicitly with [`Context::unregister_callback`].
+ ///
+ /// When handling a [method@Hotplug::device_arrived] event it is considered safe to call
+ /// any `rusb` function that takes a [`Device`]. It also safe to open a device and
+ /// submit **asynchronous** transfers.
+ /// However, most other functions that take a [`DeviceHandle`] are **not safe** to call.
+ /// Examples of such functions are any of the synchronous API functions or
+ /// the blocking functions that retrieve various USB descriptors.
+ /// These functions must be used outside of the context of the [Hotplug] functions.
+ ///
+ /// [`Device`]: crate::Device
+ /// [`DeviceHandle`]: crate::DeviceHandle
+ /// [`Context::unregister_callback`]: method@crate::Context::unregister_callback
+ pub fn register<U: UsbContext, T: Borrow<U>>(
+ self,
+ context: T,
+ callback: Box<dyn Hotplug<U>>,
+ ) -> crate::Result<Registration<U>> {
+ let mut handle: libusb_hotplug_callback_handle = 0;
+ let mut call_back = Box::new(CallbackData {
+ context: context.borrow().clone(),
+ hotplug: callback,
+ });
+
+ let hotplug_flags = if self.enumerate {
+ LIBUSB_HOTPLUG_ENUMERATE
+ } else {
+ LIBUSB_HOTPLUG_NO_FLAGS
+ };
+
+ let user_data = &mut *call_back as *mut _ as *mut _;
+
+ let n = unsafe {
+ libusb_hotplug_register_callback(
+ context.borrow().as_raw(),
+ LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+ hotplug_flags,
+ self.vendor_id
+ .map(c_int::from)
+ .unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY),
+ self.product_id
+ .map(c_int::from)
+ .unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY),
+ self.class
+ .map(c_int::from)
+ .unwrap_or(LIBUSB_HOTPLUG_MATCH_ANY),
+ hotplug_callback::<U>,
+ user_data,
+ &mut handle,
+ )
+ };
+ if n < 0 {
+ Err(error::from_libusb(n))
+ } else {
+ Ok(Registration { handle, call_back })
+ }
+ }
+}
+
+struct CallbackData<T: UsbContext> {
+ context: T,
+ hotplug: Box<dyn Hotplug<T>>,
+}
+
+impl<T> Debug for CallbackData<T>
+where
+ T: UsbContext + Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("CallbackData")
+ .field("context", &self.context)
+ .finish()
+ }
+}
+
+pub extern "system" fn hotplug_callback<T: UsbContext>(
+ _ctx: *mut libusb_context,
+ device: *mut libusb_device,
+ event: libusb_hotplug_event,
+ user_data: *mut c_void,
+) -> c_int {
+ let ret = std::panic::catch_unwind(|| {
+ let reg = unsafe { &mut *(user_data as *mut CallbackData<T>) };
+ let device = unsafe {
+ Device::from_libusb(
+ reg.context.clone(),
+ std::ptr::NonNull::new_unchecked(device),
+ )
+ };
+ match event {
+ LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED => reg.hotplug.device_arrived(device),
+ LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT => reg.hotplug.device_left(device),
+ _ => (),
+ };
+ });
+ match ret {
+ Ok(_) => 0,
+ Err(_) => 1,
+ }
+}
diff --git a/src/interface_descriptor.rs b/src/interface_descriptor.rs
new file mode 100644
index 0000000..d4c64c3
--- /dev/null
+++ b/src/interface_descriptor.rs
@@ -0,0 +1,294 @@
+use std::{fmt, slice};
+
+use libusb1_sys::{libusb_endpoint_descriptor, libusb_interface, libusb_interface_descriptor};
+
+use crate::endpoint_descriptor::{self, EndpointDescriptor};
+
+/// A device interface.
+///
+/// An interface can have several descriptors, each describing an alternate setting of the
+/// interface.
+pub struct Interface<'a> {
+ descriptors: &'a [libusb_interface_descriptor],
+}
+
+impl<'a> Interface<'a> {
+ /// Returns the interface's number.
+ pub fn number(&self) -> u8 {
+ self.descriptors[0].bInterfaceNumber
+ }
+
+ /// Returns an iterator over the interface's descriptors.
+ pub fn descriptors(&self) -> InterfaceDescriptors<'a> {
+ InterfaceDescriptors {
+ iter: self.descriptors.iter(),
+ }
+ }
+}
+
+/// Iterator over an interface's descriptors.
+pub struct InterfaceDescriptors<'a> {
+ iter: slice::Iter<'a, libusb_interface_descriptor>,
+}
+
+impl<'a> Iterator for InterfaceDescriptors<'a> {
+ type Item = InterfaceDescriptor<'a>;
+
+ fn next(&mut self) -> Option<InterfaceDescriptor<'a>> {
+ self.iter
+ .next()
+ .map(|descriptor| InterfaceDescriptor { descriptor })
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+/// Describes an alternate setting for an interface.
+pub struct InterfaceDescriptor<'a> {
+ descriptor: &'a libusb_interface_descriptor,
+}
+
+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
+ }
+
+ /// Returns the alternate setting number.
+ pub fn setting_number(&self) -> u8 {
+ self.descriptor.bAlternateSetting
+ }
+
+ /// Returns the interface's class code.
+ pub fn class_code(&self) -> u8 {
+ self.descriptor.bInterfaceClass
+ }
+
+ /// Returns the interface's sub class code.
+ pub fn sub_class_code(&self) -> u8 {
+ self.descriptor.bInterfaceSubClass
+ }
+
+ /// Returns the interface's protocol code.
+ pub fn protocol_code(&self) -> u8 {
+ self.descriptor.bInterfaceProtocol
+ }
+
+ /// Returns the index of the string descriptor that describes the interface.
+ pub fn description_string_index(&self) -> Option<u8> {
+ match self.descriptor.iInterface {
+ 0 => None,
+ n => Some(n),
+ }
+ }
+
+ /// Returns the number of endpoints belonging to this interface.
+ pub fn num_endpoints(&self) -> u8 {
+ self.descriptor.bNumEndpoints
+ }
+
+ /// Returns an iterator over the interface's endpoint descriptors.
+ pub fn endpoint_descriptors(&self) -> EndpointDescriptors<'a> {
+ let endpoints = match self.descriptor.bNumEndpoints {
+ 0 => &[],
+ n => unsafe { slice::from_raw_parts(self.descriptor.endpoint, n as usize) },
+ };
+
+ EndpointDescriptors {
+ iter: endpoints.iter(),
+ }
+ }
+
+ /// Returns the unknown 'extra' bytes that libusb does not understand.
+ pub fn extra(&self) -> &[u8] {
+ unsafe {
+ match (*self.descriptor).extra_length {
+ len if len > 0 => slice::from_raw_parts((*self.descriptor).extra, len as usize),
+ _ => &[],
+ }
+ }
+ }
+}
+
+impl<'a> fmt::Debug for InterfaceDescriptor<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let mut debug = fmt.debug_struct("InterfaceDescriptor");
+
+ debug.field("bLength", &self.descriptor.bLength);
+ debug.field("bDescriptorType", &self.descriptor.bDescriptorType);
+ debug.field("bInterfaceNumber", &self.descriptor.bInterfaceNumber);
+ debug.field("bAlternateSetting", &self.descriptor.bAlternateSetting);
+ debug.field("bNumEndpoints", &self.descriptor.bNumEndpoints);
+ debug.field("bInterfaceClass", &self.descriptor.bInterfaceClass);
+ debug.field("bInterfaceSubClass", &self.descriptor.bInterfaceSubClass);
+ debug.field("bInterfaceProtocol", &self.descriptor.bInterfaceProtocol);
+ debug.field("iInterface", &self.descriptor.iInterface);
+
+ debug.finish()
+ }
+}
+
+/// Iterator over an interface's endpoint descriptors.
+pub struct EndpointDescriptors<'a> {
+ iter: slice::Iter<'a, libusb_endpoint_descriptor>,
+}
+
+impl<'a> Iterator for EndpointDescriptors<'a> {
+ type Item = EndpointDescriptor<'a>;
+
+ fn next(&mut self) -> Option<EndpointDescriptor<'a>> {
+ self.iter.next().map(endpoint_descriptor::from_libusb)
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+#[doc(hidden)]
+pub(crate) unsafe fn from_libusb(interface: &libusb_interface) -> Interface {
+ let descriptors =
+ slice::from_raw_parts(interface.altsetting, interface.num_altsetting as usize);
+ debug_assert!(!descriptors.is_empty());
+
+ Interface { descriptors }
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn it_has_interface_number() {
+ assert_eq!(
+ 42,
+ unsafe { super::from_libusb(&interface!(interface_descriptor!(bInterfaceNumber: 42))) }
+ .number()
+ );
+ }
+
+ #[test]
+ fn it_has_interface_number_in_descriptor() {
+ assert_eq!(
+ vec!(42),
+ unsafe { super::from_libusb(&interface!(interface_descriptor!(bInterfaceNumber: 42))) }
+ .descriptors()
+ .map(|setting| setting.interface_number())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_alternate_setting_number() {
+ assert_eq!(
+ vec!(42),
+ unsafe {
+ super::from_libusb(&interface!(interface_descriptor!(bAlternateSetting: 42)))
+ }
+ .descriptors()
+ .map(|setting| setting.setting_number())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_class_code() {
+ assert_eq!(
+ vec!(42),
+ unsafe { super::from_libusb(&interface!(interface_descriptor!(bInterfaceClass: 42))) }
+ .descriptors()
+ .map(|setting| setting.class_code())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_sub_class_code() {
+ assert_eq!(
+ vec!(42),
+ unsafe {
+ super::from_libusb(&interface!(interface_descriptor!(bInterfaceSubClass: 42)))
+ }
+ .descriptors()
+ .map(|setting| setting.sub_class_code())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_protocol_code() {
+ assert_eq!(
+ vec!(42),
+ unsafe {
+ super::from_libusb(&interface!(interface_descriptor!(bInterfaceProtocol: 42)))
+ }
+ .descriptors()
+ .map(|setting| setting.protocol_code())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_description_string_index() {
+ assert_eq!(
+ vec!(Some(42)),
+ unsafe { super::from_libusb(&interface!(interface_descriptor!(iInterface: 42))) }
+ .descriptors()
+ .map(|setting| setting.description_string_index())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_handles_missing_description_string_index() {
+ assert_eq!(
+ vec!(None),
+ unsafe { super::from_libusb(&interface!(interface_descriptor!(iInterface: 0))) }
+ .descriptors()
+ .map(|setting| setting.description_string_index())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_num_endpoints() {
+ let endpoint1 = endpoint_descriptor!(bEndpointAddress: 0x81);
+ let endpoint2 = endpoint_descriptor!(bEndpointAddress: 0x01);
+
+ assert_eq!(
+ vec!(2),
+ unsafe { super::from_libusb(&interface!(interface_descriptor!(endpoint1, endpoint2))) }
+ .descriptors()
+ .map(|setting| setting.num_endpoints())
+ .collect::<Vec<_>>()
+ );
+ }
+
+ #[test]
+ fn it_has_endpoints() {
+ let libusb_interface = interface!(interface_descriptor!(
+ endpoint_descriptor!(bEndpointAddress: 0x87)
+ ));
+ let interface = unsafe { super::from_libusb(&libusb_interface) };
+
+ let endpoint_addresses = interface
+ .descriptors()
+ .next()
+ .unwrap()
+ .endpoint_descriptors()
+ .map(|endpoint| endpoint.address())
+ .collect::<Vec<_>>();
+
+ assert_eq!(vec![0x87], endpoint_addresses);
+ }
+}
diff --git a/src/language.rs b/src/language.rs
new file mode 100644
index 0000000..4fec0d1
--- /dev/null
+++ b/src/language.rs
@@ -0,0 +1,2558 @@
+const PRIMARY_LANGUAGE_MASK: u16 = 0x03FF;
+const SUB_LANGUAGE_MASK: u16 = 0xFC00;
+
+/// A language used to read string descriptors from USB devices.
+///
+/// A language consists of a primary language and a sub language. Primary languages are language
+/// families, such as English or Spanish. Sub languages identify a dialect of the primary language.
+/// The dialect may be based on regional differences (United States English compared to United
+/// Kingdom English), writing systems (Cyrillic compared to Latin), or age (Modern compared to
+/// Traditional). Each primary language has its own set of sub languages.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct Language {
+ raw: u16,
+}
+
+impl Language {
+ /// Returns the language's 16-bit `LANGID`.
+ ///
+ /// Each language's `LANGID` is defined by the USB forum
+ /// <https://learn.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings>.
+ pub fn lang_id(self) -> u16 {
+ self.raw
+ }
+
+ /// Returns the primary language.
+ pub fn primary_language(self) -> PrimaryLanguage {
+ PrimaryLanguage::from_raw(self.raw)
+ }
+
+ /// Returns the sub language.
+ pub fn sub_language(self) -> SubLanguage {
+ SubLanguage::from_raw(self.primary_language(), self.raw)
+ }
+}
+
+#[doc(hidden)]
+pub(crate) fn from_lang_id(raw: u16) -> Language {
+ Language { raw }
+}
+
+/// Primary language families.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum PrimaryLanguage {
+ Afrikaans,
+ Albanian,
+ Arabic,
+ Armenian,
+ Assamese,
+ Azeri,
+ Basque,
+ Belarussian,
+ Bengali,
+ Bulgarian,
+ Burmese,
+ Catalan,
+ Chinese,
+ Croatian,
+ Czech,
+ Danish,
+ Dutch,
+ English,
+ Estonian,
+ Faeroese,
+ Farsi,
+ Finnish,
+ French,
+ Georgian,
+ German,
+ Greek,
+ Gujarati,
+ Hebrew,
+ Hindi,
+ Hungarian,
+ Icelandic,
+ Indonesian,
+ Italian,
+ Japanese,
+ Kannada,
+ Kashmiri,
+ Kazakh,
+ Konkani,
+ Korean,
+ Latvian,
+ Lithuanian,
+ Macedonian,
+ Malay,
+ Malayalam,
+ Manipuri,
+ Marathi,
+ Nepali,
+ Norwegian,
+ Oriya,
+ Polish,
+ Portuguese,
+ Punjabi,
+ Romanian,
+ Russian,
+ Sanskrit,
+ Serbian,
+ Sindhi,
+ Slovak,
+ Slovenian,
+ Spanish,
+ Sutu,
+ Swahili,
+ Swedish,
+ Tamil,
+ Tatar,
+ Telugu,
+ Thai,
+ Turkish,
+ Ukrainian,
+ Urdu,
+ Uzbek,
+ Vietnamese,
+
+ HID,
+ Other(u16),
+}
+
+impl PrimaryLanguage {
+ fn from_raw(raw: u16) -> PrimaryLanguage {
+ match raw & PRIMARY_LANGUAGE_MASK {
+ 0x0036 => PrimaryLanguage::Afrikaans,
+ 0x001C => PrimaryLanguage::Albanian,
+ 0x0001 => PrimaryLanguage::Arabic,
+ 0x002B => PrimaryLanguage::Armenian,
+ 0x004D => PrimaryLanguage::Assamese,
+ 0x002C => PrimaryLanguage::Azeri,
+ 0x002D => PrimaryLanguage::Basque,
+ 0x0023 => PrimaryLanguage::Belarussian,
+ 0x0045 => PrimaryLanguage::Bengali,
+ 0x0002 => PrimaryLanguage::Bulgarian,
+ 0x0055 => PrimaryLanguage::Burmese,
+ 0x0003 => PrimaryLanguage::Catalan,
+ 0x0004 => PrimaryLanguage::Chinese,
+ 0x001A => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => PrimaryLanguage::Croatian,
+ _ => PrimaryLanguage::Serbian,
+ },
+ 0x0005 => PrimaryLanguage::Czech,
+ 0x0006 => PrimaryLanguage::Danish,
+ 0x0013 => PrimaryLanguage::Dutch,
+ 0x0009 => PrimaryLanguage::English,
+ 0x0025 => PrimaryLanguage::Estonian,
+ 0x0038 => PrimaryLanguage::Faeroese,
+ 0x0029 => PrimaryLanguage::Farsi,
+ 0x000B => PrimaryLanguage::Finnish,
+ 0x000C => PrimaryLanguage::French,
+ 0x0037 => PrimaryLanguage::Georgian,
+ 0x0007 => PrimaryLanguage::German,
+ 0x0008 => PrimaryLanguage::Greek,
+ 0x0047 => PrimaryLanguage::Gujarati,
+ 0x000D => PrimaryLanguage::Hebrew,
+ 0x0039 => PrimaryLanguage::Hindi,
+ 0x000E => PrimaryLanguage::Hungarian,
+ 0x000F => PrimaryLanguage::Icelandic,
+ 0x0021 => PrimaryLanguage::Indonesian,
+ 0x0010 => PrimaryLanguage::Italian,
+ 0x0011 => PrimaryLanguage::Japanese,
+ 0x004B => PrimaryLanguage::Kannada,
+ 0x0060 => PrimaryLanguage::Kashmiri,
+ 0x003F => PrimaryLanguage::Kazakh,
+ 0x0057 => PrimaryLanguage::Konkani,
+ 0x0012 => PrimaryLanguage::Korean,
+ 0x0026 => PrimaryLanguage::Latvian,
+ 0x0027 => PrimaryLanguage::Lithuanian,
+ 0x002F => PrimaryLanguage::Macedonian,
+ 0x003E => PrimaryLanguage::Malay,
+ 0x004C => PrimaryLanguage::Malayalam,
+ 0x0058 => PrimaryLanguage::Manipuri,
+ 0x004E => PrimaryLanguage::Marathi,
+ 0x0061 => PrimaryLanguage::Nepali,
+ 0x0014 => PrimaryLanguage::Norwegian,
+ 0x0048 => PrimaryLanguage::Oriya,
+ 0x0015 => PrimaryLanguage::Polish,
+ 0x0016 => PrimaryLanguage::Portuguese,
+ 0x0046 => PrimaryLanguage::Punjabi,
+ 0x0018 => PrimaryLanguage::Romanian,
+ 0x0019 => PrimaryLanguage::Russian,
+ 0x004F => PrimaryLanguage::Sanskrit,
+ 0x0059 => PrimaryLanguage::Sindhi,
+ 0x001B => PrimaryLanguage::Slovak,
+ 0x0024 => PrimaryLanguage::Slovenian,
+ 0x000A => PrimaryLanguage::Spanish,
+ 0x0030 => PrimaryLanguage::Sutu,
+ 0x0041 => PrimaryLanguage::Swahili,
+ 0x001D => PrimaryLanguage::Swedish,
+ 0x0049 => PrimaryLanguage::Tamil,
+ 0x0044 => PrimaryLanguage::Tatar,
+ 0x004A => PrimaryLanguage::Telugu,
+ 0x001E => PrimaryLanguage::Thai,
+ 0x001F => PrimaryLanguage::Turkish,
+ 0x0022 => PrimaryLanguage::Ukrainian,
+ 0x0020 => PrimaryLanguage::Urdu,
+ 0x0043 => PrimaryLanguage::Uzbek,
+ 0x002A => PrimaryLanguage::Vietnamese,
+ 0x00FF => PrimaryLanguage::HID,
+ n => PrimaryLanguage::Other(n),
+ }
+ }
+}
+
+/// Language dialects and writing systems.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum SubLanguage {
+ Standard,
+ Classic,
+ Traditional,
+ Modern,
+
+ Algeria, // arabic
+ Argentina, // spanish
+ Australia, // english
+ Austria, // german
+ Bahrain, // arabic
+ Belgium, // dutch, french
+ Belize, // english
+ Bokmal, // norwegian
+ Bolivia, // spanish
+ Brazil, // portuguese
+ BruneiDarussalam, // malay
+ Canada, // english, french
+ Caribbean, // english
+ Chile, // spanish
+ China, // chinese
+ Colombia, // spanish
+ CostaRica, // spanish
+ Cyrillic, // azeri, serbian, uzbek
+ DominicanRepublic, // spanish
+ Ecuador, // spanish
+ Egypt, // arabic
+ ElSalvador, // spanish
+ Finland, // swedish
+ Guatemala, // spanish
+ Honduras, // spanish
+ HongKong, // chinese
+ India, // kashmiri, nepali, urdu
+ Iraq, // arabic
+ Ireland, // english
+ Jamaica, // english
+ Johab, // korean
+ Jordan, // arabic
+ Kuwait, // arabic
+ Latin, // azeri, serbian, uzbek
+ Lebanon, // arabic
+ Libya, // arabic
+ Liechtenstein, // german
+ Luxembourg, // french, german
+ Macau, // chinese
+ Malaysia, // malay
+ Mexico, // spanish
+ Monaco, // french
+ Morocco, // arabic
+ Netherlands, // dutch
+ NewZealand, // english
+ Nicaragua, // spanish
+ Nynorsk, // norwegian
+ Oman, // arabic
+ Pakistan, // urdu
+ Panama, // spanish
+ Paraguay, // spanish
+ Peru, // spanish
+ Philippines, // english
+ PuertoRico, // spanish
+ Qatar, // arabic
+ SaudiArabia, // arabic
+ Singapore, // chinese
+ SouthAfrica, // english
+ Switzerland, // french, german, italian
+ Syria, // arabic
+ Taiwan, // chinese
+ Trinidad, // english
+ Tunisia, // arabic
+ UnitedArabEmirates, // arabic
+ UnitedKingdom, // english
+ UnitedStates, // english
+ Uruguay, // spanish
+ Venezuela, // spanish
+ Yemen, // arabic
+ Zimbabwe, // english
+
+ UsageDataDescriptor, // HID
+ VendorDefined1, // HID
+ VendorDefined2, // HID
+ VendorDefined3, // HID
+ VendorDefined4, // HID
+
+ Other(u16),
+}
+
+impl SubLanguage {
+ fn from_raw(language: PrimaryLanguage, raw: u16) -> SubLanguage {
+ match language {
+ PrimaryLanguage::Arabic => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::SaudiArabia,
+ 0x0800 => SubLanguage::Iraq,
+ 0x0C00 => SubLanguage::Egypt,
+ 0x1000 => SubLanguage::Libya,
+ 0x1400 => SubLanguage::Algeria,
+ 0x1800 => SubLanguage::Morocco,
+ 0x1C00 => SubLanguage::Tunisia,
+ 0x2000 => SubLanguage::Oman,
+ 0x2400 => SubLanguage::Yemen,
+ 0x2800 => SubLanguage::Syria,
+ 0x2C00 => SubLanguage::Jordan,
+ 0x3000 => SubLanguage::Lebanon,
+ 0x3400 => SubLanguage::Kuwait,
+ 0x3800 => SubLanguage::UnitedArabEmirates,
+ 0x3C00 => SubLanguage::Bahrain,
+ 0x4000 => SubLanguage::Qatar,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Azeri => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Latin,
+ 0x0800 => SubLanguage::Cyrillic,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Chinese => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Taiwan,
+ 0x0800 => SubLanguage::China,
+ 0x0C00 => SubLanguage::HongKong,
+ 0x1000 => SubLanguage::Singapore,
+ 0x1400 => SubLanguage::Macau,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Dutch => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Netherlands,
+ 0x0800 => SubLanguage::Belgium,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::English => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::UnitedStates,
+ 0x0800 => SubLanguage::UnitedKingdom,
+ 0x0C00 => SubLanguage::Australia,
+ 0x1000 => SubLanguage::Canada,
+ 0x1400 => SubLanguage::NewZealand,
+ 0x1800 => SubLanguage::Ireland,
+ 0x1C00 => SubLanguage::SouthAfrica,
+ 0x2000 => SubLanguage::Jamaica,
+ 0x2400 => SubLanguage::Caribbean,
+ 0x2800 => SubLanguage::Belize,
+ 0x2C00 => SubLanguage::Trinidad,
+ 0x3000 => SubLanguage::Zimbabwe,
+ 0x3400 => SubLanguage::Philippines,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::French => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Standard,
+ 0x0800 => SubLanguage::Belgium,
+ 0x0C00 => SubLanguage::Canada,
+ 0x1000 => SubLanguage::Switzerland,
+ 0x1400 => SubLanguage::Luxembourg,
+ 0x1800 => SubLanguage::Monaco,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::German => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Standard,
+ 0x0800 => SubLanguage::Switzerland,
+ 0x0C00 => SubLanguage::Austria,
+ 0x1000 => SubLanguage::Luxembourg,
+ 0x1400 => SubLanguage::Liechtenstein,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Italian => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Standard,
+ 0x0800 => SubLanguage::Switzerland,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Korean => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Standard,
+ 0x0800 => SubLanguage::Johab,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Lithuanian => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Standard,
+ 0x0800 => SubLanguage::Classic,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Malay => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Malaysia,
+ 0x0800 => SubLanguage::BruneiDarussalam,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Norwegian => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Bokmal,
+ 0x0800 => SubLanguage::Nynorsk,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Portuguese => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Brazil,
+ 0x0800 => SubLanguage::Standard,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Serbian => match raw & SUB_LANGUAGE_MASK {
+ 0x0C00 => SubLanguage::Cyrillic,
+ 0x0800 => SubLanguage::Latin,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Spanish => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Traditional,
+ 0x0800 => SubLanguage::Mexico,
+ 0x0C00 => SubLanguage::Modern,
+ 0x1000 => SubLanguage::Guatemala,
+ 0x1400 => SubLanguage::CostaRica,
+ 0x1800 => SubLanguage::Panama,
+ 0x1C00 => SubLanguage::DominicanRepublic,
+ 0x2000 => SubLanguage::Venezuela,
+ 0x2400 => SubLanguage::Colombia,
+ 0x2800 => SubLanguage::Peru,
+ 0x2C00 => SubLanguage::Argentina,
+ 0x3000 => SubLanguage::Ecuador,
+ 0x3400 => SubLanguage::Chile,
+ 0x3800 => SubLanguage::Uruguay,
+ 0x3C00 => SubLanguage::Paraguay,
+ 0x4000 => SubLanguage::Bolivia,
+ 0x4400 => SubLanguage::ElSalvador,
+ 0x4800 => SubLanguage::Honduras,
+ 0x4C00 => SubLanguage::Nicaragua,
+ 0x5000 => SubLanguage::PuertoRico,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Swedish => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Standard,
+ 0x0800 => SubLanguage::Finland,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Urdu => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Pakistan,
+ 0x0800 => SubLanguage::India,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Uzbek => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::Latin,
+ 0x0800 => SubLanguage::Cyrillic,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::HID => match raw & SUB_LANGUAGE_MASK {
+ 0x0400 => SubLanguage::UsageDataDescriptor,
+ 0xF000 => SubLanguage::VendorDefined1,
+ 0xF400 => SubLanguage::VendorDefined2,
+ 0xF800 => SubLanguage::VendorDefined3,
+ 0xFC00 => SubLanguage::VendorDefined4,
+ n => SubLanguage::Other(n),
+ },
+ PrimaryLanguage::Other(_) => SubLanguage::Other(raw & SUB_LANGUAGE_MASK),
+ _ => SubLanguage::Standard,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{PrimaryLanguage, SubLanguage};
+ use super::{PRIMARY_LANGUAGE_MASK, SUB_LANGUAGE_MASK};
+
+ // language ids defined in http://www.usb.org/developers/docs/USB_LANGIDs.pdf
+ const AFRIKAANS: u16 = 0x0436;
+ const ALBANIAN: u16 = 0x041C;
+ const ARABIC_SAUDI_ARABIA: u16 = 0x0401;
+ const ARABIC_IRAQ: u16 = 0x0801;
+ const ARABIC_EGYPT: u16 = 0x0C01;
+ const ARABIC_LIBYA: u16 = 0x1001;
+ const ARABIC_ALGERIA: u16 = 0x1401;
+ const ARABIC_MOROCCO: u16 = 0x1801;
+ const ARABIC_TUNISIA: u16 = 0x1C01;
+ const ARABIC_OMAN: u16 = 0x2001;
+ const ARABIC_YEMEN: u16 = 0x2401;
+ const ARABIC_SYRIA: u16 = 0x2801;
+ const ARABIC_JORDAN: u16 = 0x2C01;
+ const ARABIC_LEBANON: u16 = 0x3001;
+ const ARABIC_KUWAIT: u16 = 0x3401;
+ const ARABIC_UAE: u16 = 0x3801;
+ const ARABIC_BAHRAIN: u16 = 0x3C01;
+ const ARABIC_QATAR: u16 = 0x4001;
+ const ARMENIAN: u16 = 0x042B;
+ const ASSAMESE: u16 = 0x044D;
+ const AZERI_LATIN: u16 = 0x042C;
+ const AZERI_CYRILLIC: u16 = 0x082C;
+ const BASQUE: u16 = 0x042D;
+ const BELARUSSIAN: u16 = 0x0423;
+ const BENGALI: u16 = 0x0445;
+ const BULGARIAN: u16 = 0x0402;
+ const BURMESE: u16 = 0x0455;
+ const CATALAN: u16 = 0x0403;
+ const CHINESE_TAIWAN: u16 = 0x0404;
+ const CHINESE_CHINA: u16 = 0x0804;
+ const CHINESE_HONG_KONG: u16 = 0x0C04;
+ const CHINESE_SINGAPORE: u16 = 0x1004;
+ const CHINESE_MACAU: u16 = 0x1404;
+ const CROATIAN: u16 = 0x041A;
+ const CZECH: u16 = 0x0405;
+ const DANISH: u16 = 0x0406;
+ const DUTCH_NETHERLANDS: u16 = 0x0413;
+ const DUTCH_BELGIUM: u16 = 0x0813;
+ const ENGLISH_UNITED_STATES: u16 = 0x0409;
+ const ENGLISH_UNITED_KINGDOM: u16 = 0x0809;
+ const ENGLISH_AUSTRALIAN: u16 = 0x0C09;
+ const ENGLISH_CANADIAN: u16 = 0x1009;
+ const ENGLISH_NEW_ZEALAND: u16 = 0x1409;
+ const ENGLISH_IRELAND: u16 = 0x1809;
+ const ENGLISH_SOUTH_AFRICA: u16 = 0x1C09;
+ const ENGLISH_JAMAICA: u16 = 0x2009;
+ const ENGLISH_CARIBBEAN: u16 = 0x2409;
+ const ENGLISH_BELIZE: u16 = 0x2809;
+ const ENGLISH_TRINIDAD: u16 = 0x2C09;
+ const ENGLISH_ZIMBABWE: u16 = 0x3009;
+ const ENGLISH_PHILIPPINES: u16 = 0x3409;
+ const ESTONIAN: u16 = 0x0425;
+ const FAEROESE: u16 = 0x0438;
+ const FARSI: u16 = 0x0429;
+ const FINNISH: u16 = 0x040B;
+ const FRENCH_STANDARD: u16 = 0x040C;
+ const FRENCH_BELGIAN: u16 = 0x080C;
+ const FRENCH_CANADIAN: u16 = 0x0C0C;
+ const FRENCH_SWITZERLAND: u16 = 0x100C;
+ const FRENCH_LUXEMBOURG: u16 = 0x140C;
+ const FRENCH_MONACO: u16 = 0x180C;
+ const GEORGIAN: u16 = 0x0437;
+ const GERMAN_STANDARD: u16 = 0x0407;
+ const GERMAN_SWITZERLAND: u16 = 0x0807;
+ const GERMAN_AUSTRIA: u16 = 0x0C07;
+ const GERMAN_LUXEMBOURG: u16 = 0x1007;
+ const GERMAN_LIECHTENSTEIN: u16 = 0x1407;
+ const GREEK: u16 = 0x0408;
+ const GUJARATI: u16 = 0x0447;
+ const HEBREW: u16 = 0x040D;
+ const HINDI: u16 = 0x0439;
+ const HUNGARIAN: u16 = 0x040E;
+ const ICELANDIC: u16 = 0x040F;
+ const INDONESIAN: u16 = 0x0421;
+ const ITALIAN_STANDARD: u16 = 0x0410;
+ const ITALIAN_SWITZERLAND: u16 = 0x0810;
+ const JAPANESE: u16 = 0x0411;
+ const KANNADA: u16 = 0x044B;
+ const KASHMIRI_INDIA: u16 = 0x0860;
+ const KAZAKH: u16 = 0x043F;
+ const KONKANI: u16 = 0x0457;
+ const KOREAN: u16 = 0x0412;
+ const KOREAN_JOHAB: u16 = 0x0812;
+ const LATVIAN: u16 = 0x0426;
+ const LITHUANIAN: u16 = 0x0427;
+ const LITHUANIAN_CLASSIC: u16 = 0x0827;
+ const MACEDONIAN: u16 = 0x042F;
+ const MALAY_MALAYSIAN: u16 = 0x043E;
+ const MALAY_BRUNEI_DARUSSALAM: u16 = 0x083E;
+ const MALAYALAM: u16 = 0x044C;
+ const MANIPURI: u16 = 0x0458;
+ const MARATHI: u16 = 0x044E;
+ const NEPALI_INDIA: u16 = 0x0861;
+ const NORWEGIAN_BOKMAL: u16 = 0x0414;
+ const NORWEGIAN_NYNORSK: u16 = 0x0814;
+ const ORIYA: u16 = 0x0448;
+ const POLISH: u16 = 0x0415;
+ const PORTUGUESE_BRAZIL: u16 = 0x0416;
+ const PORTUGUESE_STANDARD: u16 = 0x0816;
+ const PUNJABI: u16 = 0x0446;
+ const ROMANIAN: u16 = 0x0418;
+ const RUSSIAN: u16 = 0x0419;
+ const SANSKRIT: u16 = 0x044F;
+ const SERBIAN_CYRILLIC: u16 = 0x0C1A;
+ const SERBIAN_LATIN: u16 = 0x081A;
+ const SINDHI: u16 = 0x0459;
+ const SLOVAK: u16 = 0x041B;
+ const SLOVENIAN: u16 = 0x0424;
+ const SPANISH_TRADITIONAL_SORT: u16 = 0x040A;
+ const SPANISH_MEXICAN: u16 = 0x080A;
+ const SPANISH_MODERN_SORT: u16 = 0x0C0A;
+ const SPANISH_GUATEMALA: u16 = 0x100A;
+ const SPANISH_COSTA_RICA: u16 = 0x140A;
+ const SPANISH_PANAMA: u16 = 0x180A;
+ const SPANISH_DOMINICAN_REPUBLIC: u16 = 0x1C0A;
+ const SPANISH_VENEZUELA: u16 = 0x200A;
+ const SPANISH_COLOMBIA: u16 = 0x240A;
+ const SPANISH_PERU: u16 = 0x280A;
+ const SPANISH_ARGENTINA: u16 = 0x2C0A;
+ const SPANISH_ECUADOR: u16 = 0x300A;
+ const SPANISH_CHILE: u16 = 0x340A;
+ const SPANISH_URUGUAY: u16 = 0x380A;
+ const SPANISH_PARAGUAY: u16 = 0x3C0A;
+ const SPANISH_BOLIVIA: u16 = 0x400A;
+ const SPANISH_EL_SALVADOR: u16 = 0x440A;
+ const SPANISH_HONDURAS: u16 = 0x480A;
+ const SPANISH_NICARAGUA: u16 = 0x4C0A;
+ const SPANISH_PUERTO_RICO: u16 = 0x500A;
+ const SUTU: u16 = 0x0430;
+ const SWAHILI_KENYA: u16 = 0x0441;
+ const SWEDISH: u16 = 0x041D;
+ const SWEDISH_FINLAND: u16 = 0x081D;
+ const TAMIL: u16 = 0x0449;
+ const TATAR_TATARSTAN: u16 = 0x0444;
+ const TELUGU: u16 = 0x044A;
+ const THAI: u16 = 0x041E;
+ const TURKISH: u16 = 0x041F;
+ const UKRAINIAN: u16 = 0x0422;
+ const URDU_PAKISTAN: u16 = 0x0420;
+ const URDU_INDIA: u16 = 0x0820;
+ const UZBEK_LATIN: u16 = 0x0443;
+ const UZBEK_CYRILLIC: u16 = 0x0843;
+ const VIETNAMESE: u16 = 0x042A;
+ const HID_USAGE_DATA_DESCRIPTOR: u16 = 0x04FF;
+ const HID_VENDOR_DEFINED_1: u16 = 0xF0FF;
+ const HID_VENDOR_DEFINED_2: u16 = 0xF4FF;
+ const HID_VENDOR_DEFINED_3: u16 = 0xF8FF;
+ const HID_VENDOR_DEFINED_4: u16 = 0xFCFF;
+
+ #[test]
+ fn it_recognizes_afrikaans_as_afrikaans_language() {
+ assert_eq!(
+ super::from_lang_id(AFRIKAANS).primary_language(),
+ PrimaryLanguage::Afrikaans
+ );
+ }
+
+ #[test]
+ fn it_recognizes_albanian_as_albanian_language() {
+ assert_eq!(
+ super::from_lang_id(ALBANIAN).primary_language(),
+ PrimaryLanguage::Albanian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_saudi_arabia_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_SAUDI_ARABIA).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_saudi_arabia_as_saudi_arabia_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_SAUDI_ARABIA).sub_language(),
+ SubLanguage::SaudiArabia
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_iraq_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_IRAQ).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_iraq_as_iraq_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_IRAQ).sub_language(),
+ SubLanguage::Iraq
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_egypt_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_EGYPT).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_egypt_as_egypt_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_EGYPT).sub_language(),
+ SubLanguage::Egypt
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_libya_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_LIBYA).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_libya_as_libya_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_LIBYA).sub_language(),
+ SubLanguage::Libya
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_algeria_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_ALGERIA).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_algeria_as_algeria_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_ALGERIA).sub_language(),
+ SubLanguage::Algeria
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_morocco_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_MOROCCO).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_morocco_as_morocco_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_MOROCCO).sub_language(),
+ SubLanguage::Morocco
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_tunisia_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_TUNISIA).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_tunisia_as_tunisia_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_TUNISIA).sub_language(),
+ SubLanguage::Tunisia
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_oman_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_OMAN).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_oman_as_oman_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_OMAN).sub_language(),
+ SubLanguage::Oman
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_yemen_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_YEMEN).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_yemen_as_yemen_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_YEMEN).sub_language(),
+ SubLanguage::Yemen
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_syria_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_SYRIA).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_syria_as_syria_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_SYRIA).sub_language(),
+ SubLanguage::Syria
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_jordan_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_JORDAN).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_jordan_as_jordan_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_JORDAN).sub_language(),
+ SubLanguage::Jordan
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_lebanon_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_LEBANON).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_lebanon_as_lebanon_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_LEBANON).sub_language(),
+ SubLanguage::Lebanon
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_kuwait_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_KUWAIT).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_kuwait_as_kuwait_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_KUWAIT).sub_language(),
+ SubLanguage::Kuwait
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_uae_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_UAE).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_uae_as_uae_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_UAE).sub_language(),
+ SubLanguage::UnitedArabEmirates
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_bahrain_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_BAHRAIN).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_bahrain_as_bahrain_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_BAHRAIN).sub_language(),
+ SubLanguage::Bahrain
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_qatar_as_arabic_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_QATAR).primary_language(),
+ PrimaryLanguage::Arabic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_arabic_from_qatar_as_qatar_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ARABIC_QATAR).sub_language(),
+ SubLanguage::Qatar
+ );
+ }
+
+ #[test]
+ fn it_recognizes_armenian_as_armenian_language() {
+ assert_eq!(
+ super::from_lang_id(ARMENIAN).primary_language(),
+ PrimaryLanguage::Armenian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_assamese_as_assamese_language() {
+ assert_eq!(
+ super::from_lang_id(ASSAMESE).primary_language(),
+ PrimaryLanguage::Assamese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_azeri_latin_as_azeri_language() {
+ assert_eq!(
+ super::from_lang_id(AZERI_LATIN).primary_language(),
+ PrimaryLanguage::Azeri
+ );
+ }
+
+ #[test]
+ fn it_recognizes_azeri_latin_as_latin_sub_language() {
+ assert_eq!(
+ super::from_lang_id(AZERI_LATIN).sub_language(),
+ SubLanguage::Latin
+ );
+ }
+
+ #[test]
+ fn it_recognizes_azeri_cyrillic_as_azeri_language() {
+ assert_eq!(
+ super::from_lang_id(AZERI_CYRILLIC).primary_language(),
+ PrimaryLanguage::Azeri
+ );
+ }
+
+ #[test]
+ fn it_recognizes_azeri_cyrillic_as_cyrillic_sub_language() {
+ assert_eq!(
+ super::from_lang_id(AZERI_CYRILLIC).sub_language(),
+ SubLanguage::Cyrillic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_basque_as_basque_language() {
+ assert_eq!(
+ super::from_lang_id(BASQUE).primary_language(),
+ PrimaryLanguage::Basque
+ );
+ }
+
+ #[test]
+ fn it_recognizes_belarussian_as_belarussian_language() {
+ assert_eq!(
+ super::from_lang_id(BELARUSSIAN).primary_language(),
+ PrimaryLanguage::Belarussian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_bengali_as_bengali_language() {
+ assert_eq!(
+ super::from_lang_id(BENGALI).primary_language(),
+ PrimaryLanguage::Bengali
+ );
+ }
+
+ #[test]
+ fn it_recognizes_bulgarian_as_bulgarian_language() {
+ assert_eq!(
+ super::from_lang_id(BULGARIAN).primary_language(),
+ PrimaryLanguage::Bulgarian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_burmese_as_burmese_language() {
+ assert_eq!(
+ super::from_lang_id(BURMESE).primary_language(),
+ PrimaryLanguage::Burmese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_catalan_as_catalan_language() {
+ assert_eq!(
+ super::from_lang_id(CATALAN).primary_language(),
+ PrimaryLanguage::Catalan
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_taiwan_as_chinese_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_TAIWAN).primary_language(),
+ PrimaryLanguage::Chinese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_taiwan_as_taiwan_sub_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_TAIWAN).sub_language(),
+ SubLanguage::Taiwan
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_china_as_chinese_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_CHINA).primary_language(),
+ PrimaryLanguage::Chinese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_china_as_china_sub_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_CHINA).sub_language(),
+ SubLanguage::China
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_hong_kong_as_chinese_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_HONG_KONG).primary_language(),
+ PrimaryLanguage::Chinese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_hong_kong_as_hong_kong_sub_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_HONG_KONG).sub_language(),
+ SubLanguage::HongKong
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_singapore_as_chinese_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_SINGAPORE).primary_language(),
+ PrimaryLanguage::Chinese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_singapore_as_singapore_sub_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_SINGAPORE).sub_language(),
+ SubLanguage::Singapore
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_macau_as_chinese_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_MACAU).primary_language(),
+ PrimaryLanguage::Chinese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_chinese_from_macau_as_macau_sub_language() {
+ assert_eq!(
+ super::from_lang_id(CHINESE_MACAU).sub_language(),
+ SubLanguage::Macau
+ );
+ }
+
+ #[test]
+ fn it_recognizes_croatian_as_croatian_language() {
+ assert_eq!(
+ super::from_lang_id(CROATIAN).primary_language(),
+ PrimaryLanguage::Croatian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_czech_as_czech_language() {
+ assert_eq!(
+ super::from_lang_id(CZECH).primary_language(),
+ PrimaryLanguage::Czech
+ );
+ }
+
+ #[test]
+ fn it_recognizes_danish_as_danish_language() {
+ assert_eq!(
+ super::from_lang_id(DANISH).primary_language(),
+ PrimaryLanguage::Danish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_dutch_from_netherlands_as_dutch_language() {
+ assert_eq!(
+ super::from_lang_id(DUTCH_NETHERLANDS).primary_language(),
+ PrimaryLanguage::Dutch
+ );
+ }
+
+ #[test]
+ fn it_recognizes_dutch_from_netherlands_as_netherlands_sub_language() {
+ assert_eq!(
+ super::from_lang_id(DUTCH_NETHERLANDS).sub_language(),
+ SubLanguage::Netherlands
+ );
+ }
+
+ #[test]
+ fn it_recognizes_dutch_from_belgium_as_dutch_language() {
+ assert_eq!(
+ super::from_lang_id(DUTCH_BELGIUM).primary_language(),
+ PrimaryLanguage::Dutch
+ );
+ }
+
+ #[test]
+ fn it_recognizes_dutch_from_belgium_as_belgium_sub_language() {
+ assert_eq!(
+ super::from_lang_id(DUTCH_BELGIUM).sub_language(),
+ SubLanguage::Belgium
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_united_states_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_UNITED_STATES).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_united_states_as_united_states_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_UNITED_STATES).sub_language(),
+ SubLanguage::UnitedStates
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_united_kingdom_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_UNITED_KINGDOM).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_united_kingdom_as_united_kingdom_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_UNITED_KINGDOM).sub_language(),
+ SubLanguage::UnitedKingdom
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_australia_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_AUSTRALIAN).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_australia_as_australia_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_AUSTRALIAN).sub_language(),
+ SubLanguage::Australia
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_canada_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_CANADIAN).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_canada_as_canada_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_CANADIAN).sub_language(),
+ SubLanguage::Canada
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_new_zealand_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_NEW_ZEALAND).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_new_zealand_as_new_zealand_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_NEW_ZEALAND).sub_language(),
+ SubLanguage::NewZealand
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_ireland_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_IRELAND).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_ireland_as_ireland_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_IRELAND).sub_language(),
+ SubLanguage::Ireland
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_south_africa_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_SOUTH_AFRICA).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_south_africa_as_south_africa_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_SOUTH_AFRICA).sub_language(),
+ SubLanguage::SouthAfrica
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_jamaica_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_JAMAICA).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_jamaica_as_jamaica_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_JAMAICA).sub_language(),
+ SubLanguage::Jamaica
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_caribbean_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_CARIBBEAN).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_caribbean_as_caribbean_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_CARIBBEAN).sub_language(),
+ SubLanguage::Caribbean
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_belize_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_BELIZE).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_belize_as_belize_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_BELIZE).sub_language(),
+ SubLanguage::Belize
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_trinidad_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_TRINIDAD).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_trinidad_as_trinidad_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_TRINIDAD).sub_language(),
+ SubLanguage::Trinidad
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_zimbabwe_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_ZIMBABWE).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_zimbabwe_as_zimbabwe_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_ZIMBABWE).sub_language(),
+ SubLanguage::Zimbabwe
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_philippines_as_english_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_PHILIPPINES).primary_language(),
+ PrimaryLanguage::English
+ );
+ }
+
+ #[test]
+ fn it_recognizes_english_from_philippines_as_philippines_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ENGLISH_PHILIPPINES).sub_language(),
+ SubLanguage::Philippines
+ );
+ }
+
+ #[test]
+ fn it_recognizes_estonian_as_estonian_language() {
+ assert_eq!(
+ super::from_lang_id(ESTONIAN).primary_language(),
+ PrimaryLanguage::Estonian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_faeroese_as_faeroese_language() {
+ assert_eq!(
+ super::from_lang_id(FAEROESE).primary_language(),
+ PrimaryLanguage::Faeroese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_farsi_as_farsi_language() {
+ assert_eq!(
+ super::from_lang_id(FARSI).primary_language(),
+ PrimaryLanguage::Farsi
+ );
+ }
+
+ #[test]
+ fn it_recognizes_finnish_as_finnish_language() {
+ assert_eq!(
+ super::from_lang_id(FINNISH).primary_language(),
+ PrimaryLanguage::Finnish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_standard_as_french_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_STANDARD).primary_language(),
+ PrimaryLanguage::French
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_standard_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_STANDARD).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_belgium_as_french_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_BELGIAN).primary_language(),
+ PrimaryLanguage::French
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_belgium_as_belgium_sub_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_BELGIAN).sub_language(),
+ SubLanguage::Belgium
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_canada_as_french_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_CANADIAN).primary_language(),
+ PrimaryLanguage::French
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_canada_as_canada_sub_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_CANADIAN).sub_language(),
+ SubLanguage::Canada
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_switzerland_as_french_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_SWITZERLAND).primary_language(),
+ PrimaryLanguage::French
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_switzerland_as_switzerland_sub_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_SWITZERLAND).sub_language(),
+ SubLanguage::Switzerland
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_luxembourg_as_french_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_LUXEMBOURG).primary_language(),
+ PrimaryLanguage::French
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_luxembourg_as_luxembourg_sub_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_LUXEMBOURG).sub_language(),
+ SubLanguage::Luxembourg
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_monaco_as_french_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_MONACO).primary_language(),
+ PrimaryLanguage::French
+ );
+ }
+
+ #[test]
+ fn it_recognizes_french_from_monaco_as_monaco_sub_language() {
+ assert_eq!(
+ super::from_lang_id(FRENCH_MONACO).sub_language(),
+ SubLanguage::Monaco
+ );
+ }
+
+ #[test]
+ fn it_recognizes_georgian_as_georgian_language() {
+ assert_eq!(
+ super::from_lang_id(GEORGIAN).primary_language(),
+ PrimaryLanguage::Georgian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_standard_as_german_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_STANDARD).primary_language(),
+ PrimaryLanguage::German
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_standard_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_STANDARD).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_switzerland_as_german_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_SWITZERLAND).primary_language(),
+ PrimaryLanguage::German
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_switzerland_as_switzerland_sub_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_SWITZERLAND).sub_language(),
+ SubLanguage::Switzerland
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_austria_as_german_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_AUSTRIA).primary_language(),
+ PrimaryLanguage::German
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_austria_as_austria_sub_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_AUSTRIA).sub_language(),
+ SubLanguage::Austria
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_luxembourg_as_german_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_LUXEMBOURG).primary_language(),
+ PrimaryLanguage::German
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_luxembourg_as_luxembourg_sub_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_LUXEMBOURG).sub_language(),
+ SubLanguage::Luxembourg
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_liechtenstein_as_german_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_LIECHTENSTEIN).primary_language(),
+ PrimaryLanguage::German
+ );
+ }
+
+ #[test]
+ fn it_recognizes_german_from_liechtenstein_as_liechtenstein_sub_language() {
+ assert_eq!(
+ super::from_lang_id(GERMAN_LIECHTENSTEIN).sub_language(),
+ SubLanguage::Liechtenstein
+ );
+ }
+
+ #[test]
+ fn it_recognizes_greek_as_greek_language() {
+ assert_eq!(
+ super::from_lang_id(GREEK).primary_language(),
+ PrimaryLanguage::Greek
+ );
+ }
+
+ #[test]
+ fn it_recognizes_gujarati_as_gujarati_language() {
+ assert_eq!(
+ super::from_lang_id(GUJARATI).primary_language(),
+ PrimaryLanguage::Gujarati
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hebrew_as_hebrew_language() {
+ assert_eq!(
+ super::from_lang_id(HEBREW).primary_language(),
+ PrimaryLanguage::Hebrew
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hindi_as_hindi_language() {
+ assert_eq!(
+ super::from_lang_id(HINDI).primary_language(),
+ PrimaryLanguage::Hindi
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hungarian_as_hungarian_language() {
+ assert_eq!(
+ super::from_lang_id(HUNGARIAN).primary_language(),
+ PrimaryLanguage::Hungarian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_icelandic_as_icelandic_language() {
+ assert_eq!(
+ super::from_lang_id(ICELANDIC).primary_language(),
+ PrimaryLanguage::Icelandic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_indonesian_as_indonesian_language() {
+ assert_eq!(
+ super::from_lang_id(INDONESIAN).primary_language(),
+ PrimaryLanguage::Indonesian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_italian_standard_as_italian_language() {
+ assert_eq!(
+ super::from_lang_id(ITALIAN_STANDARD).primary_language(),
+ PrimaryLanguage::Italian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_italian_standard_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ITALIAN_STANDARD).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_italian_from_switzerland_as_italian_language() {
+ assert_eq!(
+ super::from_lang_id(ITALIAN_SWITZERLAND).primary_language(),
+ PrimaryLanguage::Italian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_italian_from_switzerland_as_switzerland_sub_language() {
+ assert_eq!(
+ super::from_lang_id(ITALIAN_SWITZERLAND).sub_language(),
+ SubLanguage::Switzerland
+ );
+ }
+
+ #[test]
+ fn it_recognizes_japanese_as_japanese_language() {
+ assert_eq!(
+ super::from_lang_id(JAPANESE).primary_language(),
+ PrimaryLanguage::Japanese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_kannada_as_kannada_language() {
+ assert_eq!(
+ super::from_lang_id(KANNADA).primary_language(),
+ PrimaryLanguage::Kannada
+ );
+ }
+
+ #[test]
+ fn it_recognizes_kashmiri_as_kashmiri_language() {
+ assert_eq!(
+ super::from_lang_id(KASHMIRI_INDIA).primary_language(),
+ PrimaryLanguage::Kashmiri
+ );
+ }
+
+ #[test]
+ fn it_recognizes_kazakh_as_kazakh_language() {
+ assert_eq!(
+ super::from_lang_id(KAZAKH).primary_language(),
+ PrimaryLanguage::Kazakh
+ );
+ }
+
+ #[test]
+ fn it_recognizes_konkani_as_konkani_language() {
+ assert_eq!(
+ super::from_lang_id(KONKANI).primary_language(),
+ PrimaryLanguage::Konkani
+ );
+ }
+
+ #[test]
+ fn it_recognizes_korean_as_korean_language() {
+ assert_eq!(
+ super::from_lang_id(KOREAN).primary_language(),
+ PrimaryLanguage::Korean
+ );
+ }
+
+ #[test]
+ fn it_recognizes_korean_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(KOREAN).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_korean_johab_as_korean_language() {
+ assert_eq!(
+ super::from_lang_id(KOREAN_JOHAB).primary_language(),
+ PrimaryLanguage::Korean
+ );
+ }
+
+ #[test]
+ fn it_recognizes_korean_johab_as_johab_sub_language() {
+ assert_eq!(
+ super::from_lang_id(KOREAN_JOHAB).sub_language(),
+ SubLanguage::Johab
+ );
+ }
+
+ #[test]
+ fn it_recognizes_latvian_as_latvian_language() {
+ assert_eq!(
+ super::from_lang_id(LATVIAN).primary_language(),
+ PrimaryLanguage::Latvian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_lithuanian_as_lithuanian_language() {
+ assert_eq!(
+ super::from_lang_id(LITHUANIAN).primary_language(),
+ PrimaryLanguage::Lithuanian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_lithuanian_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(LITHUANIAN).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_lithuanian_classic_as_lithuanian_language() {
+ assert_eq!(
+ super::from_lang_id(LITHUANIAN_CLASSIC).primary_language(),
+ PrimaryLanguage::Lithuanian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_lithuanian_classic_as_classic_sub_language() {
+ assert_eq!(
+ super::from_lang_id(LITHUANIAN_CLASSIC).sub_language(),
+ SubLanguage::Classic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_macedonian_as_macedonian_language() {
+ assert_eq!(
+ super::from_lang_id(MACEDONIAN).primary_language(),
+ PrimaryLanguage::Macedonian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_malay_from_malaysia_as_malay_language() {
+ assert_eq!(
+ super::from_lang_id(MALAY_MALAYSIAN).primary_language(),
+ PrimaryLanguage::Malay
+ );
+ }
+
+ #[test]
+ fn it_recognizes_malay_from_malaysia_as_malaysia_sub_language() {
+ assert_eq!(
+ super::from_lang_id(MALAY_MALAYSIAN).sub_language(),
+ SubLanguage::Malaysia
+ );
+ }
+
+ #[test]
+ fn it_recognizes_malay_from_brunei_darussalam_as_malay_language() {
+ assert_eq!(
+ super::from_lang_id(MALAY_BRUNEI_DARUSSALAM).primary_language(),
+ PrimaryLanguage::Malay
+ );
+ }
+
+ #[test]
+ fn it_recognizes_malay_from_brunei_darussalam_as_brunei_darussalam_sub_language() {
+ assert_eq!(
+ super::from_lang_id(MALAY_BRUNEI_DARUSSALAM).sub_language(),
+ SubLanguage::BruneiDarussalam
+ );
+ }
+
+ #[test]
+ fn it_recognizes_malayalam_as_malayalam_language() {
+ assert_eq!(
+ super::from_lang_id(MALAYALAM).primary_language(),
+ PrimaryLanguage::Malayalam
+ );
+ }
+
+ #[test]
+ fn it_recognizes_manipuri_as_manipuri_language() {
+ assert_eq!(
+ super::from_lang_id(MANIPURI).primary_language(),
+ PrimaryLanguage::Manipuri
+ );
+ }
+
+ #[test]
+ fn it_recognizes_marathi_as_marathi_language() {
+ assert_eq!(
+ super::from_lang_id(MARATHI).primary_language(),
+ PrimaryLanguage::Marathi
+ );
+ }
+
+ #[test]
+ fn it_recognizes_nepali_as_nepali_language() {
+ assert_eq!(
+ super::from_lang_id(NEPALI_INDIA).primary_language(),
+ PrimaryLanguage::Nepali
+ );
+ }
+
+ #[test]
+ fn it_recognizes_norwegian_bokmal_as_norwegian_language() {
+ assert_eq!(
+ super::from_lang_id(NORWEGIAN_BOKMAL).primary_language(),
+ PrimaryLanguage::Norwegian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_norwegian_bokmal_as_bokmal_sub_language() {
+ assert_eq!(
+ super::from_lang_id(NORWEGIAN_BOKMAL).sub_language(),
+ SubLanguage::Bokmal
+ );
+ }
+
+ #[test]
+ fn it_recognizes_norwegian_nynorsk_as_norwegian_language() {
+ assert_eq!(
+ super::from_lang_id(NORWEGIAN_NYNORSK).primary_language(),
+ PrimaryLanguage::Norwegian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_norwegian_nynorsk_as_nynorsk_sub_language() {
+ assert_eq!(
+ super::from_lang_id(NORWEGIAN_NYNORSK).sub_language(),
+ SubLanguage::Nynorsk
+ );
+ }
+
+ #[test]
+ fn it_recognizes_oriya_as_oriya_language() {
+ assert_eq!(
+ super::from_lang_id(ORIYA).primary_language(),
+ PrimaryLanguage::Oriya
+ );
+ }
+
+ #[test]
+ fn it_recognizes_polish_as_polish_language() {
+ assert_eq!(
+ super::from_lang_id(POLISH).primary_language(),
+ PrimaryLanguage::Polish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_portuguese_from_brazil_as_portuguese_language() {
+ assert_eq!(
+ super::from_lang_id(PORTUGUESE_BRAZIL).primary_language(),
+ PrimaryLanguage::Portuguese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_portuguese_from_brazil_as_brazil_sub_language() {
+ assert_eq!(
+ super::from_lang_id(PORTUGUESE_BRAZIL).sub_language(),
+ SubLanguage::Brazil
+ );
+ }
+
+ #[test]
+ fn it_recognizes_portuguese_standard_as_portuguese_language() {
+ assert_eq!(
+ super::from_lang_id(PORTUGUESE_STANDARD).primary_language(),
+ PrimaryLanguage::Portuguese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_portuguese_standard_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(PORTUGUESE_STANDARD).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_punjabi_as_punjabi_language() {
+ assert_eq!(
+ super::from_lang_id(PUNJABI).primary_language(),
+ PrimaryLanguage::Punjabi
+ );
+ }
+
+ #[test]
+ fn it_recognizes_romanian_as_romanian_language() {
+ assert_eq!(
+ super::from_lang_id(ROMANIAN).primary_language(),
+ PrimaryLanguage::Romanian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_russian_as_russian_language() {
+ assert_eq!(
+ super::from_lang_id(RUSSIAN).primary_language(),
+ PrimaryLanguage::Russian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_sanskrit_as_sanskrit_language() {
+ assert_eq!(
+ super::from_lang_id(SANSKRIT).primary_language(),
+ PrimaryLanguage::Sanskrit
+ );
+ }
+
+ #[test]
+ fn it_recognizes_serbian_cyrillic_as_serbian_language() {
+ assert_eq!(
+ super::from_lang_id(SERBIAN_CYRILLIC).primary_language(),
+ PrimaryLanguage::Serbian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_serbian_cyrillic_as_cyrillic_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SERBIAN_CYRILLIC).sub_language(),
+ SubLanguage::Cyrillic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_serbian_latin_as_serbian_language() {
+ assert_eq!(
+ super::from_lang_id(SERBIAN_LATIN).primary_language(),
+ PrimaryLanguage::Serbian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_serbian_latin_as_latin_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SERBIAN_LATIN).sub_language(),
+ SubLanguage::Latin
+ );
+ }
+
+ #[test]
+ fn it_recognizes_sindhi_as_sindhi_language() {
+ assert_eq!(
+ super::from_lang_id(SINDHI).primary_language(),
+ PrimaryLanguage::Sindhi
+ );
+ }
+
+ #[test]
+ fn it_recognizes_slovak_as_slovak_language() {
+ assert_eq!(
+ super::from_lang_id(SLOVAK).primary_language(),
+ PrimaryLanguage::Slovak
+ );
+ }
+
+ #[test]
+ fn it_recognizes_slovenian_as_slovenian_language() {
+ assert_eq!(
+ super::from_lang_id(SLOVENIAN).primary_language(),
+ PrimaryLanguage::Slovenian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_traditional_sort_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_TRADITIONAL_SORT).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_traditional_sort_as_traditional_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_TRADITIONAL_SORT).sub_language(),
+ SubLanguage::Traditional
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_mexico_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_MEXICAN).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_mexico_as_mexico_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_MEXICAN).sub_language(),
+ SubLanguage::Mexico
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_modern_sort_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_MODERN_SORT).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_modern_sort_as_modern_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_MODERN_SORT).sub_language(),
+ SubLanguage::Modern
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_guatemala_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_GUATEMALA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_guatemala_as_guatemala_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_GUATEMALA).sub_language(),
+ SubLanguage::Guatemala
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_costa_rica_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_COSTA_RICA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_costa_rica_as_costa_rica_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_COSTA_RICA).sub_language(),
+ SubLanguage::CostaRica
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_panama_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PANAMA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_panama_as_panama_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PANAMA).sub_language(),
+ SubLanguage::Panama
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_dominican_republic_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_DOMINICAN_REPUBLIC).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_dominican_republic_as_dominican_republic_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_DOMINICAN_REPUBLIC).sub_language(),
+ SubLanguage::DominicanRepublic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_venezuela_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_VENEZUELA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_venezuela_as_venezuela_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_VENEZUELA).sub_language(),
+ SubLanguage::Venezuela
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_colombia_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_COLOMBIA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_colombia_as_colombia_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_COLOMBIA).sub_language(),
+ SubLanguage::Colombia
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_peru_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PERU).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_peru_as_peru_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PERU).sub_language(),
+ SubLanguage::Peru
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_argentina_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_ARGENTINA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_argentina_as_argentina_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_ARGENTINA).sub_language(),
+ SubLanguage::Argentina
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_ecuador_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_ECUADOR).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_ecuador_as_ecuador_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_ECUADOR).sub_language(),
+ SubLanguage::Ecuador
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_chile_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_CHILE).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_chile_as_chile_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_CHILE).sub_language(),
+ SubLanguage::Chile
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_uruguay_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_URUGUAY).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_uruguay_as_uruguay_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_URUGUAY).sub_language(),
+ SubLanguage::Uruguay
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_paraguay_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PARAGUAY).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_paraguay_as_paraguay_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PARAGUAY).sub_language(),
+ SubLanguage::Paraguay
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_bolivia_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_BOLIVIA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_bolivia_as_bolivia_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_BOLIVIA).sub_language(),
+ SubLanguage::Bolivia
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_el_salvador_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_EL_SALVADOR).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_el_salvador_as_el_salvador_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_EL_SALVADOR).sub_language(),
+ SubLanguage::ElSalvador
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_honduras_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_HONDURAS).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_honduras_as_honduras_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_HONDURAS).sub_language(),
+ SubLanguage::Honduras
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_nicaragua_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_NICARAGUA).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_nicaragua_as_nicaragua_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_NICARAGUA).sub_language(),
+ SubLanguage::Nicaragua
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_puerto_rico_as_spanish_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PUERTO_RICO).primary_language(),
+ PrimaryLanguage::Spanish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_spanish_from_puerto_rico_as_puerto_rico_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SPANISH_PUERTO_RICO).sub_language(),
+ SubLanguage::PuertoRico
+ );
+ }
+
+ #[test]
+ fn it_recognizes_sutu_as_sutu_language() {
+ assert_eq!(
+ super::from_lang_id(SUTU).primary_language(),
+ PrimaryLanguage::Sutu
+ );
+ }
+
+ #[test]
+ fn it_recognizes_swahili_as_swahili_language() {
+ assert_eq!(
+ super::from_lang_id(SWAHILI_KENYA).primary_language(),
+ PrimaryLanguage::Swahili
+ );
+ }
+
+ #[test]
+ fn it_recognizes_swedish_as_swedish_language() {
+ assert_eq!(
+ super::from_lang_id(SWEDISH).primary_language(),
+ PrimaryLanguage::Swedish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_swedish_as_standard_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SWEDISH).sub_language(),
+ SubLanguage::Standard
+ );
+ }
+
+ #[test]
+ fn it_recognizes_swedish_from_finland_as_swedish_language() {
+ assert_eq!(
+ super::from_lang_id(SWEDISH_FINLAND).primary_language(),
+ PrimaryLanguage::Swedish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_swedish_from_finland_as_finland_sub_language() {
+ assert_eq!(
+ super::from_lang_id(SWEDISH_FINLAND).sub_language(),
+ SubLanguage::Finland
+ );
+ }
+
+ #[test]
+ fn it_recognizes_tamil_as_tamil_language() {
+ assert_eq!(
+ super::from_lang_id(TAMIL).primary_language(),
+ PrimaryLanguage::Tamil
+ );
+ }
+
+ #[test]
+ fn it_recognizes_tatar_as_tatar_language() {
+ assert_eq!(
+ super::from_lang_id(TATAR_TATARSTAN).primary_language(),
+ PrimaryLanguage::Tatar
+ );
+ }
+
+ #[test]
+ fn it_recognizes_telugu_as_telugu_language() {
+ assert_eq!(
+ super::from_lang_id(TELUGU).primary_language(),
+ PrimaryLanguage::Telugu
+ );
+ }
+
+ #[test]
+ fn it_recognizes_thai_as_thai_language() {
+ assert_eq!(
+ super::from_lang_id(THAI).primary_language(),
+ PrimaryLanguage::Thai
+ );
+ }
+
+ #[test]
+ fn it_recognizes_turkish_as_turkish_language() {
+ assert_eq!(
+ super::from_lang_id(TURKISH).primary_language(),
+ PrimaryLanguage::Turkish
+ );
+ }
+
+ #[test]
+ fn it_recognizes_ukrainian_as_ukrainian_language() {
+ assert_eq!(
+ super::from_lang_id(UKRAINIAN).primary_language(),
+ PrimaryLanguage::Ukrainian
+ );
+ }
+
+ #[test]
+ fn it_recognizes_urdu_from_pakistan_as_urdu_language() {
+ assert_eq!(
+ super::from_lang_id(URDU_PAKISTAN).primary_language(),
+ PrimaryLanguage::Urdu
+ );
+ }
+
+ #[test]
+ fn it_recognizes_urdu_from_pakistan_as_pakistan_sub_language() {
+ assert_eq!(
+ super::from_lang_id(URDU_PAKISTAN).sub_language(),
+ SubLanguage::Pakistan
+ );
+ }
+
+ #[test]
+ fn it_recognizes_urdu_from_india_as_urdu_language() {
+ assert_eq!(
+ super::from_lang_id(URDU_INDIA).primary_language(),
+ PrimaryLanguage::Urdu
+ );
+ }
+
+ #[test]
+ fn it_recognizes_urdu_from_india_as_india_sub_language() {
+ assert_eq!(
+ super::from_lang_id(URDU_INDIA).sub_language(),
+ SubLanguage::India
+ );
+ }
+
+ #[test]
+ fn it_recognizes_uzbek_latin_as_uzbek_language() {
+ assert_eq!(
+ super::from_lang_id(UZBEK_LATIN).primary_language(),
+ PrimaryLanguage::Uzbek
+ );
+ }
+
+ #[test]
+ fn it_recognizes_uzbek_latin_as_latin_sub_language() {
+ assert_eq!(
+ super::from_lang_id(UZBEK_LATIN).sub_language(),
+ SubLanguage::Latin
+ );
+ }
+
+ #[test]
+ fn it_recognizes_uzbek_cyrillic_as_uzbek_language() {
+ assert_eq!(
+ super::from_lang_id(UZBEK_CYRILLIC).primary_language(),
+ PrimaryLanguage::Uzbek
+ );
+ }
+
+ #[test]
+ fn it_recognizes_uzbek_cyrillic_as_cyrillic_sub_language() {
+ assert_eq!(
+ super::from_lang_id(UZBEK_CYRILLIC).sub_language(),
+ SubLanguage::Cyrillic
+ );
+ }
+
+ #[test]
+ fn it_recognizes_vietnamese_as_vietnamese_language() {
+ assert_eq!(
+ super::from_lang_id(VIETNAMESE).primary_language(),
+ PrimaryLanguage::Vietnamese
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_usage_data_descriptor_as_hid_language() {
+ assert_eq!(
+ super::from_lang_id(HID_USAGE_DATA_DESCRIPTOR).primary_language(),
+ PrimaryLanguage::HID
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_usage_data_descriptor_as_usage_data_descriptor_sub_language() {
+ assert_eq!(
+ super::from_lang_id(HID_USAGE_DATA_DESCRIPTOR).sub_language(),
+ SubLanguage::UsageDataDescriptor
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_1_as_hid_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_1).primary_language(),
+ PrimaryLanguage::HID
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_1_as_vendor_defined_1_sub_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_1).sub_language(),
+ SubLanguage::VendorDefined1
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_2_as_hid_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_2).primary_language(),
+ PrimaryLanguage::HID
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_1_as_vendor_defined_2_sub_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_2).sub_language(),
+ SubLanguage::VendorDefined2
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_3_as_hid_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_3).primary_language(),
+ PrimaryLanguage::HID
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_1_as_vendor_defined_3_sub_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_3).sub_language(),
+ SubLanguage::VendorDefined3
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_4_as_hid_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_4).primary_language(),
+ PrimaryLanguage::HID
+ );
+ }
+
+ #[test]
+ fn it_recognizes_hid_vendor_defined_1_as_vendor_defined_4_sub_language() {
+ assert_eq!(
+ super::from_lang_id(HID_VENDOR_DEFINED_4).sub_language(),
+ SubLanguage::VendorDefined4
+ );
+ }
+
+ #[test]
+ fn it_recognizes_other_as_other_language() {
+ assert_eq!(
+ super::from_lang_id(0xFFFF).primary_language(),
+ PrimaryLanguage::Other(PRIMARY_LANGUAGE_MASK)
+ );
+ }
+
+ #[test]
+ fn it_recognizes_other_as_other_sub_language() {
+ assert_eq!(
+ super::from_lang_id(0xFFFF).sub_language(),
+ SubLanguage::Other(SUB_LANGUAGE_MASK)
+ );
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..4c16277
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,121 @@
+//! This crate provides a safe wrapper around the native `libusb` library.
+
+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, LogCallbackMode, LogLevel, UsbContext},
+ device::Device,
+ device_descriptor::DeviceDescriptor,
+ device_handle::DeviceHandle,
+ device_list::{DeviceList, Devices},
+ endpoint_descriptor::EndpointDescriptor,
+ error::{Error, Result},
+ fields::{
+ request_type, Direction, Recipient, RequestType, Speed, SyncType, TransferType, UsageType,
+ Version,
+ },
+ hotplug::{Hotplug, HotplugBuilder, Registration},
+ interface_descriptor::{
+ EndpointDescriptors, Interface, InterfaceDescriptor, InterfaceDescriptors,
+ },
+ language::{Language, PrimaryLanguage, SubLanguage},
+ options::UsbOption,
+ version::{version, LibraryVersion},
+};
+
+#[cfg(test)]
+#[macro_use]
+mod test_helpers;
+
+#[macro_use]
+mod error;
+mod version;
+
+mod context;
+mod device;
+mod device_handle;
+mod device_list;
+
+mod config_descriptor;
+mod device_descriptor;
+mod endpoint_descriptor;
+mod fields;
+mod hotplug;
+mod interface_descriptor;
+mod language;
+mod options;
+
+/// Tests whether the running `libusb` library supports capability API.
+pub fn has_capability() -> bool {
+ GlobalContext::default().as_raw();
+ unsafe { libusb1_sys::libusb_has_capability(constants::LIBUSB_CAP_HAS_CAPABILITY) != 0 }
+}
+
+/// Tests whether the running `libusb` library supports hotplug.
+pub fn has_hotplug() -> bool {
+ GlobalContext::default().as_raw();
+ unsafe { libusb1_sys::libusb_has_capability(constants::LIBUSB_CAP_HAS_HOTPLUG) != 0 }
+}
+
+/// Tests whether the running `libusb` library has HID access.
+pub fn has_hid_access() -> bool {
+ GlobalContext::default().as_raw();
+ unsafe { libusb1_sys::libusb_has_capability(constants::LIBUSB_CAP_HAS_HID_ACCESS) != 0 }
+}
+
+/// Tests whether the running `libusb` library supports detaching the kernel driver.
+pub fn supports_detach_kernel_driver() -> bool {
+ GlobalContext::default().as_raw();
+ unsafe {
+ libusb1_sys::libusb_has_capability(constants::LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER) != 0
+ }
+}
+
+/// Returns a list of the current USB devices. Using global context
+pub fn devices() -> crate::Result<DeviceList<GlobalContext>> {
+ GlobalContext::default().devices()
+}
+
+/// Sets the log level of a `libusb` global context.
+pub fn set_log_level(level: LogLevel) {
+ unsafe {
+ libusb1_sys::libusb_set_debug(GlobalContext::default().as_raw(), level.as_c_int());
+ }
+}
+
+/// Convenience function to open a device by its vendor ID and product ID.
+/// Using global context
+///
+/// This function is provided as a convenience for building prototypes without having to
+/// iterate a [`DeviceList`](struct.DeviceList.html). It is not meant for production
+/// applications.
+///
+/// Returns a device handle for the first device found matching `vendor_id` and `product_id`.
+/// On error, or if the device could not be found, it returns `None`.
+pub fn open_device_with_vid_pid(
+ vendor_id: u16,
+ product_id: u16,
+) -> Option<DeviceHandle<GlobalContext>> {
+ let handle = unsafe {
+ libusb1_sys::libusb_open_device_with_vid_pid(
+ GlobalContext::default().as_raw(),
+ vendor_id,
+ product_id,
+ )
+ };
+
+ if handle.is_null() {
+ None
+ } else {
+ Some(unsafe {
+ DeviceHandle::from_libusb(
+ GlobalContext::default(),
+ std::ptr::NonNull::new_unchecked(handle),
+ )
+ })
+ }
+}
diff --git a/src/options.rs b/src/options.rs
new file mode 100644
index 0000000..c527bd8
--- /dev/null
+++ b/src/options.rs
@@ -0,0 +1,57 @@
+use crate::{error, UsbContext};
+use libusb1_sys::{constants::*, libusb_set_option};
+
+/// A `libusb` runtime option that can be enabled for a context.
+pub struct UsbOption {
+ inner: OptionInner,
+}
+
+impl UsbOption {
+ /// Use the [UsbDk] backend if available.
+ ///
+ /// **Note**: This method is available on **Windows** only!
+ ///
+ /// [UsbDk]: https://github.com/daynix/UsbDk
+ #[cfg(windows)]
+ pub fn use_usbdk() -> Self {
+ Self {
+ inner: OptionInner::UseUsbdk,
+ }
+ }
+
+ pub(crate) fn apply<T: UsbContext>(&self, ctx: &mut T) -> crate::Result<()> {
+ match self.inner {
+ OptionInner::UseUsbdk => {
+ let err = unsafe { libusb_set_option(ctx.as_raw(), LIBUSB_OPTION_USE_USBDK) };
+ if err == LIBUSB_SUCCESS {
+ Ok(())
+ } else {
+ Err(error::from_libusb(err))
+ }
+ }
+ }
+ }
+}
+
+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(())
+}
diff --git a/src/test_helpers.rs b/src/test_helpers.rs
new file mode 100644
index 0000000..e981819
--- /dev/null
+++ b/src/test_helpers.rs
@@ -0,0 +1,164 @@
+pub use std::ptr;
+
+macro_rules! merge {
+ ($default:expr => $($field:ident : $value:expr),*) => {
+ {
+ let mut x = $default;
+ $( x.$field = $value; )*
+
+ x
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! endpoint_descriptor {
+ ($($key:ident : $value:expr),*) => {
+ merge!(
+ libusb1_sys::libusb_endpoint_descriptor {
+ bLength: 7,
+ bDescriptorType: 0x05,
+ bEndpointAddress: 0x00,
+ bmAttributes: 0x00,
+ wMaxPacketSize: 16,
+ bInterval: 1,
+ bRefresh: 1,
+ bSynchAddress: 0,
+ extra: $crate::test_helpers::ptr::null(),
+ extra_length: 0
+ } => $($key: $value),*
+ )
+ }
+}
+
+#[macro_export]
+macro_rules! interface_descriptor {
+ ($($key:ident : $value:expr),*) => {
+ merge!(
+ libusb1_sys::libusb_interface_descriptor {
+ bLength: 9,
+ bDescriptorType: 0x04,
+ bInterfaceNumber: 0,
+ bAlternateSetting: 0,
+ bNumEndpoints: 0,
+ bInterfaceClass: 0,
+ bInterfaceSubClass: 0,
+ bInterfaceProtocol: 0,
+ iInterface: 0,
+ endpoint: $crate::test_helpers::ptr::null(),
+ extra: $crate::test_helpers::ptr::null(),
+ extra_length: 0
+ } => $($key: $value),*
+ )
+ };
+ ($($endpoint:expr),+) => {
+ {
+ let endpoints = vec![$($endpoint),+];
+
+ let r = libusb1_sys::libusb_interface_descriptor {
+ bLength: 9,
+ bDescriptorType: 0x04,
+ bInterfaceNumber: 0,
+ bAlternateSetting: 0,
+ bNumEndpoints: endpoints.len() as u8,
+ bInterfaceClass: 0,
+ bInterfaceSubClass: 0,
+ bInterfaceProtocol: 0,
+ iInterface: 0,
+ endpoint: (&endpoints[..]).as_ptr(),
+ extra: $crate::test_helpers::ptr::null(),
+ extra_length: 0
+ };
+
+ // leak the Vec so the returned pointer remains valid
+ ::std::mem::forget(endpoints);
+ r
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! interface {
+ ($($descriptor:expr),*) => {
+ {
+ let descriptors = vec![$($descriptor),*];
+
+ let r = libusb1_sys::libusb_interface {
+ altsetting: descriptors.as_ptr(),
+ num_altsetting: descriptors.len() as ::libc::c_int
+ };
+
+ // leak the Vec so the returned pointer remains valid
+ ::std::mem::forget(descriptors);
+ r
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! config_descriptor {
+ ($($key:ident : $value:expr),*) => {
+ merge!(
+ libusb1_sys::libusb_config_descriptor {
+ bLength: 9,
+ bDescriptorType: 0x02,
+ wTotalLength: 9,
+ bNumInterfaces: 0,
+ bConfigurationValue: 0,
+ iConfiguration: 0,
+ bmAttributes: 0x00,
+ bMaxPower: 10,
+ interface: $crate::test_helpers::ptr::null(),
+ extra: $crate::test_helpers::ptr::null(),
+ extra_length: 0
+ } => $($key: $value),*
+ )
+ };
+ ($($interface:expr),+) => {
+ {
+ let interfaces = vec![$($interface),+];
+
+ let r = libusb1_sys::libusb_config_descriptor {
+ bLength: 9,
+ bDescriptorType: 0x02,
+ wTotalLength: 9,
+ bNumInterfaces: interfaces.len() as u8,
+ bConfigurationValue: 0,
+ iConfiguration: 0,
+ bmAttributes: 0x00,
+ bMaxPower: 10,
+ interface: (&interfaces[..]).as_ptr(),
+ extra: $crate::test_helpers::ptr::null(),
+ extra_length: 0
+ };
+
+ // leak the Vec so the returned pointer remains valid
+ ::std::mem::forget(interfaces);
+ r
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! device_descriptor {
+ ($($key:ident : $value:expr),*) => {
+ merge!(
+ libusb1_sys::libusb_device_descriptor {
+ bLength: 18,
+ bDescriptorType: 0x01,
+ bcdUSB: 0x0110,
+ bDeviceClass: 0,
+ bDeviceSubClass: 0,
+ bDeviceProtocol: 0,
+ bMaxPacketSize0: 16,
+ idVendor: 0x1234,
+ idProduct: 0x5678,
+ bcdDevice: 0x0123,
+ iManufacturer: 0,
+ iProduct: 0,
+ iSerialNumber: 0,
+ bNumConfigurations: 1
+ } => $($key: $value),*
+ )
+ }
+}
diff --git a/src/version.rs b/src/version.rs
new file mode 100644
index 0000000..fb624e6
--- /dev/null
+++ b/src/version.rs
@@ -0,0 +1,67 @@
+use std::{ffi::CStr, fmt, str};
+
+use libusb1_sys::{libusb_get_version, libusb_version};
+
+/// A structure that describes the version of the underlying `libusb` library.
+pub struct LibraryVersion {
+ inner: &'static libusb_version,
+}
+
+impl LibraryVersion {
+ /// Library major version.
+ pub fn major(&self) -> u16 {
+ self.inner.major
+ }
+
+ /// Library minor version.
+ pub fn minor(&self) -> u16 {
+ self.inner.minor
+ }
+
+ /// Library micro version.
+ pub fn micro(&self) -> u16 {
+ self.inner.micro
+ }
+
+ /// Library nano version.
+ pub fn nano(&self) -> u16 {
+ self.inner.nano
+ }
+
+ /// Library release candidate suffix string, e.g., `"-rc4"`.
+ pub fn rc(&self) -> Option<&'static str> {
+ let cstr = unsafe { CStr::from_ptr(self.inner.rc) };
+
+ match str::from_utf8(cstr.to_bytes()) {
+ Ok(s) => {
+ if s.is_empty() {
+ None
+ } else {
+ Some(s)
+ }
+ }
+ Err(_) => None,
+ }
+ }
+}
+
+impl fmt::Debug for LibraryVersion {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let mut debug = fmt.debug_struct("LibraryVersion");
+
+ debug.field("major", &self.major());
+ debug.field("minor", &self.minor());
+ debug.field("micro", &self.micro());
+ debug.field("nano", &self.nano());
+ debug.field("rc", &self.rc());
+
+ debug.finish()
+ }
+}
+
+/// Returns a structure with the version of the running libusb library.
+pub fn version() -> LibraryVersion {
+ let version: &'static libusb_version = unsafe { &*libusb_get_version() };
+
+ LibraryVersion { inner: version }
+}