rusb-0.9.3/.cargo_vcs_info.json0000644000000001360000000000100120200ustar { "git": { "sha1": "d1fa27c9110fbea945d841c116de8c21e160d4bb" }, "path_in_vcs": "" }rusb-0.9.3/.github/workflows/github-ci.yml000064400000000000000000000044621046102023000166110ustar 00000000000000name: Rust on: push: branches: [master] pull_request: branches: [master] env: CARGO_TERM_COLOR: always jobs: check-code: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: submodules: true - name: Install run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libusb-1.0-0-dev - name: Run check and format run: | cargo check --all-targets --examples cargo fmt --check build: needs: [check-code] runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: matrix: include: - os: ubuntu-latest apt: "libusb-1.0-0-dev" experimental: false features: "" - os: ubuntu-latest apt: "libudev-dev" experimental: false features: "" - os: macos-latest experimental: true - os: macos-latest experimental: true features: "--features vendored" - os: windows-latest experimental: false features: "" - os: windows-latest vcpkg: x86-windows-static-md rust: stable-i686-pc-windows-msvc experimental: false features: "" - os: windows-latest vcpkg: x64-windows-static-md rust: stable-x86_64-pc-windows-msvc experimental: false features: "" steps: - uses: actions/checkout@v3 with: submodules: true - name: Install if: ${{ contains(matrix.os, 'ubuntu') }} run: sudo apt-get update && sudo apt-get install --no-install-recommends -y ${{ matrix.apt }} - name: Install additional rust toolchain if: ${{ matrix.rust != '' }} run: rustup toolchain install ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - name: Install vcpkg if: ${{ matrix.vcpkg != '' }} run: C:/vcpkg/vcpkg integrate install && C:/vcpkg/vcpkg install libusb:${{ matrix.vcpkg }} - name: Build run: cargo build --workspace -vv --examples ${{ matrix.features }} - name: Run tests run: cargo test --workspace --verbose --all ${{ matrix.features }} -- --nocapture rusb-0.9.3/.gitignore000064400000000000000000000000311046102023000125720ustar 00000000000000target Cargo.lock /.idea rusb-0.9.3/.gitmodules000064400000000000000000000001341046102023000127630ustar 00000000000000[submodule "libusb"] path = libusb1-sys/libusb url = https://github.com/libusb/libusb.git rusb-0.9.3/.travis.yml000064400000000000000000000031451046102023000127240ustar 00000000000000language: rust rust: stable cache: cargo matrix: include: - name: Linux with packaged libusb-1.0 library os: linux addons: apt: packages: - libusb-1.0-0-dev env: - PKGCONFIG: libusb-1.0 - name: Linux building libusb-1.0 from source os: linux addons: apt: packages: - libudev-dev env: - PKGCONFIG: libudev - name: OS X building libusb-1.0 from source os: osx - name: Windows 64-bit using vcpkg os: windows env: - VCPKG_ROOT: $TRAVIS_BUILD_DIR/vcp - VCPKG_PLATFORM: x64-windows-static - TARGET: stable-x86_64-pc-windows-msvc - name: Windows 32-bit using vcpkg env: - VCPKG_ROOT: $TRAVIS_BUILD_DIR/vcp - VCPKG_PLATFORM: x86-windows-static - TARGET: stable-i686-pc-windows-msvc os: windows before_script: - | if [ "$TARGET" != "" ]; then echo "Rust extra toolchain" rustup toolchain install ${TARGET} rustup default ${TARGET} fi for pkg in $PKGCONFIG; do echo "pkgconfig information:" echo " $pkg version: $(pkg-config --modversion $pkg) libs: $(pkg-config --libs $pkg)" done if [ "$VCPKG_PLATFORM" != "" ]; then echo "Handle vcpkg support" git clone https://github.com/Microsoft/vcpkg.git vcp [ $TRAVIS_OS_NAME == "windows" ] && vcp/bootstrap-vcpkg.bat || vcp/bootstrap-vcpkg.sh ./vcp/vcpkg install libusb:$VCPKG_PLATFORM fi script: - cargo test --workspace --verbose --all - cargo build --workspace --verbose --examples rusb-0.9.3/CHANGELOG.md000064400000000000000000000052511046102023000124240ustar 00000000000000# Changes ## 0.9.3 * impl serde::{Serialize, Deserialize} for public enums [#167] * Update deprecated doc link about language identifiers [#165] * Fix changelog URLs for 0.9.2 [#164] [#167]: https://github.com/a1ien/rusb/pull/167 [#165]: https://github.com/a1ien/rusb/pull/165 [#164]: https://github.com/a1ien/rusb/pull/164 ## 0.9.2 * Random corrections around the code [#127] * examples: list_devices: Add vendor and product name [#128] * 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 rusb-0.9.3/Cargo.lock0000644000000162350000000000100100020ustar # 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.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" 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.3" 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" rusb-0.9.3/Cargo.toml0000644000000023450000000000100100220ustar # 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.3" authors = [ "David Cuddeback ", "Ilya Averyanov ", ] 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.6.4" [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" rusb-0.9.3/Cargo.toml.orig000064400000000000000000000013731046102023000135030ustar 00000000000000[package] name = "rusb" version = "0.9.3" authors = ["David Cuddeback ", "Ilya Averyanov "] 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.6.4" } libc = "0.2" serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] regex = "1" usb-ids = "1.2023.0" rusb-0.9.3/LICENSE000064400000000000000000000021051046102023000116130ustar 00000000000000Copyright (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. rusb-0.9.3/README.md000064400000000000000000000036441046102023000120760ustar 00000000000000# 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. rusb-0.9.3/build.rs000064400000000000000000000020171046102023000122550ustar 00000000000000use 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()); } } rusb-0.9.3/examples/hotplug.rs000064400000000000000000000020301046102023000144510ustar 00000000000000use rusb::{Context, Device, HotplugBuilder, UsbContext}; struct HotPlugHandler; impl rusb::Hotplug for HotPlugHandler { fn device_arrived(&mut self, device: Device) { println!("device arrived {:?}", device); } fn device_left(&mut self, device: Device) { 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(()) } } rusb-0.9.3/examples/libusb_info.rs000064400000000000000000000016501046102023000152710ustar 00000000000000use 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() ) } rusb-0.9.3/examples/list_devices.rs000064400000000000000000000176611046102023000154640ustar 00000000000000use rusb::{ ConfigDescriptor, DeviceDescriptor, DeviceHandle, DeviceList, EndpointDescriptor, InterfaceDescriptor, Language, Result, Speed, UsbContext, }; use std::time::Duration; use usb_ids::{self, FromId}; struct UsbDevice { handle: DeviceHandle, 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(device_desc: &DeviceDescriptor, handle: &mut Option>) { 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!( " 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(config_desc: &ConfigDescriptor, handle: &mut Option>) { println!(" Config Descriptor:"); 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( interface_desc: &InterfaceDescriptor, handle: &mut Option>, ) { println!(" Interface Descriptor:"); 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!( " 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)", } } rusb-0.9.3/examples/read_device.rs000064400000000000000000000143641046102023000152360ustar 00000000000000use 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 = std::env::args().collect(); if args.len() < 3 { println!("usage: read_device "); 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( context: &mut T, vid: u16, pid: u16, ) -> Option<(Device, DeviceDescriptor, DeviceHandle)> { 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( device: &mut Device, device_desc: &DeviceDescriptor, handle: &mut DeviceHandle, ) -> 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( device: &mut Device, device_desc: &DeviceDescriptor, transfer_type: TransferType, ) -> Option { 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( handle: &mut DeviceHandle, 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( handle: &mut DeviceHandle, endpoint: &Endpoint, ) -> Result<()> { handle.set_active_configuration(endpoint.config)?; handle.claim_interface(endpoint.iface)?; handle.set_alternate_setting(endpoint.iface, endpoint.setting)?; Ok(()) } rusb-0.9.3/src/config_descriptor.rs000064400000000000000000000202621046102023000154520ustar 00000000000000use 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 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 { 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> { self.iter .next() .map(|interface| unsafe { interface_descriptor::from_libusb(interface) }) } fn size_hint(&self) -> (usize, Option) { 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::>(); 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); }) } } rusb-0.9.3/src/context.rs000064400000000000000000000237341046102023000134420ustar 00000000000000use libc::{c_int, timeval}; use std::{cmp::Ordering, mem, ptr, sync::Arc, sync::Once, 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, } #[derive(Debug, Eq, PartialEq)] struct ContextInner { inner: ptr::NonNull, } 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 {} 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::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> { 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> { 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()); } } /// 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, product_id: Option, class: Option, callback: Box>, ) -> crate::Result> { 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) {} /// 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) -> 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> { 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 { 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 { 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, } } } rusb-0.9.3/src/device.rs000064400000000000000000000126431046102023000132120ustar 00000000000000use 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 { context: T, device: NonNull, } impl Drop for Device { /// Releases the device reference. fn drop(&mut self) { unsafe { libusb_unref_device(self.device.as_ptr()); } } } impl Clone for Device { fn clone(&self) -> Self { unsafe { Self::from_libusb(self.context.clone(), self.device) } } } unsafe impl Send for Device {} unsafe impl Sync for Device {} impl Debug for Device { 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 Device { /// 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`. /// `device` must be a pointer to a valid `libusb_device`. Rusb increments refcount. pub unsafe fn from_libusb(context: T, device: NonNull) -> Device { libusb_ref_device(device.as_ptr()); Device { context, device } } /// Reads the device descriptor. pub fn device_descriptor(&self) -> crate::Result { let mut descriptor = mem::MaybeUninit::::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 { 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 { 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> { 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 { 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, 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()) } } rusb-0.9.3/src/device_descriptor.rs000064400000000000000000000146311046102023000154470ustar 00000000000000use std::fmt; use libusb1_sys::*; use crate::fields::Version; /// Describes a device. pub struct DeviceDescriptor { descriptor: libusb_device_descriptor, } impl DeviceDescriptor { /// 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 { 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 { 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 { 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() ); } } rusb-0.9.3/src/device_handle.rs000064400000000000000000000771751046102023000145400ustar 00000000000000use std::{ fmt::{self, Debug}, mem, ptr::NonNull, 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::() 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 { 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) { (self.remaining, Some(self.remaining)) } } /// A handle to an open USB device. #[derive(Eq, PartialEq)] pub struct DeviceHandle { context: T, handle: Option>, interfaces: ClaimedInterfaces, } impl Drop for DeviceHandle { /// Closes the device. fn drop(&mut self) { unsafe { for iface in self.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 Send for DeviceHandle {} unsafe impl Sync for DeviceHandle {} impl Debug for DeviceHandle { 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) .finish() } } impl DeviceHandle { /// 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.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 { 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`. /// `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, ) -> DeviceHandle { DeviceHandle { context, handle: Some(handle), interfaces: ClaimedInterfaces::new(), } } /// Returns the active configuration number. pub fn active_configuration(&self) -> crate::Result { let mut config = mem::MaybeUninit::::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(&mut self, config: u8) -> crate::Result<()> { try_unsafe!(libusb_set_configuration(self.as_raw(), c_int::from(config))); Ok(()) } /// Puts the device in an unconfigured state. pub fn unconfigure(&mut self) -> crate::Result<()> { try_unsafe!(libusb_set_configuration(self.as_raw(), -1)); Ok(()) } /// Resets the device. pub fn reset(&mut self) -> crate::Result<()> { try_unsafe!(libusb_reset_device(self.as_raw())); Ok(()) } /// Clear the halt/stall condition for an endpoint. pub fn clear_halt(&mut self, endpoint: u8) -> crate::Result<()> { 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 { 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(&mut 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(&mut 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(&mut 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(&mut self, iface: u8) -> crate::Result<()> { try_unsafe!(libusb_claim_interface(self.as_raw(), c_int::from(iface))); self.interfaces.insert(iface); Ok(()) } /// Releases a claimed interface. pub fn release_interface(&mut self, iface: u8) -> crate::Result<()> { try_unsafe!(libusb_release_interface(self.as_raw(), c_int::from(iface))); self.interfaces.remove(iface); Ok(()) } /// Sets an interface's active setting. pub fn set_alternate_setting(&mut 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 { if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_IN { return Err(Error::InvalidParam); } let mut transferred = mem::MaybeUninit::::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 { if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_OUT { return Err(Error::InvalidParam); } let mut transferred = mem::MaybeUninit::::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 { if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_IN { return Err(Error::InvalidParam); } let mut transferred = mem::MaybeUninit::::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 { if endpoint & LIBUSB_ENDPOINT_DIR_MASK != LIBUSB_ENDPOINT_OUT { return Err(Error::InvalidParam); } let mut transferred = mem::MaybeUninit::::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 { 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 { 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> { 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 { let mut buf = Vec::::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 { 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::(), 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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::>().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, } } } } rusb-0.9.3/src/device_list.rs000064400000000000000000000060211046102023000142360ustar 00000000000000use 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 { context: T, list: *const *mut libusb_device, len: usize, } impl Drop for DeviceList { /// Frees the device list. fn drop(&mut self) { unsafe { libusb_free_device_list(self.list, 1); } } } impl DeviceList { pub fn new() -> crate::Result> { 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 DeviceList { pub fn new_with_context(context: T) -> crate::Result> { 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 { 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; fn next(&mut self) -> Option> { 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) { let remaining = self.devices.len() - self.index; (remaining, Some(remaining)) } } rusb-0.9.3/src/endpoint_descriptor.rs000064400000000000000000000216621046102023000160320ustar 00000000000000use 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 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() ); } } rusb-0.9.3/src/error.rs000064400000000000000000000057431046102023000131070ustar 00000000000000use 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 = result::Result; /// Errors returned by the `libusb` library. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Error { /// Input/output error. Io, /// 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)), } }; } rusb-0.9.3/src/fields.rs000064400000000000000000000252631046102023000132230ustar 00000000000000use 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 ); } } rusb-0.9.3/src/hotplug.rs000064400000000000000000000160661046102023000134400ustar 00000000000000use 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: Send { fn device_arrived(&mut self, device: Device); fn device_left(&mut self, device: Device); } #[derive(Debug)] #[must_use = "USB hotplug callbacks will be deregistered if the registration is dropped"] pub struct Registration { handle: libusb_hotplug_callback_handle, call_back: Box>, } impl Registration { fn get_handle(&self) -> libusb_hotplug_callback_handle { self.handle } } impl Drop for Registration { 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, product_id: Option, class: Option, 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>( self, context: T, callback: Box>, ) -> crate::Result> { 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::, user_data, &mut handle, ) }; if n < 0 { Err(error::from_libusb(n)) } else { Ok(Registration { handle, call_back }) } } } struct CallbackData { context: T, hotplug: Box>, } impl Debug for CallbackData 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( _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) }; 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, } } rusb-0.9.3/src/interface_descriptor.rs000064400000000000000000000205561046102023000161530ustar 00000000000000use 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> { self.iter .next() .map(|descriptor| InterfaceDescriptor { descriptor }) } fn size_hint(&self) -> (usize, Option) { 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 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 { 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 = unsafe { slice::from_raw_parts( self.descriptor.endpoint, self.descriptor.bNumEndpoints 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> { self.iter.next().map(endpoint_descriptor::from_libusb) } fn size_hint(&self) -> (usize, Option) { 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::>() ); } #[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::>() ); } #[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::>() ); } #[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::>() ); } #[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::>() ); } #[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::>() ); } #[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::>() ); } #[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::>() ); } #[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::>(); assert_eq!(vec![0x87], endpoint_addresses); } } rusb-0.9.3/src/language.rs000064400000000000000000002227331046102023000135410ustar 00000000000000const 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 /// . 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) ); } } rusb-0.9.3/src/lib.rs000064400000000000000000000071201046102023000125130ustar 00000000000000//! 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, 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> { 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> { 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), ) }) } } rusb-0.9.3/src/options.rs000064400000000000000000000031401046102023000134360ustar 00000000000000use 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(&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(()) } rusb-0.9.3/src/test_helpers.rs000064400000000000000000000122161046102023000144500ustar 00000000000000pub 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),* ) } } rusb-0.9.3/src/version.rs000064400000000000000000000032721046102023000134360ustar 00000000000000use 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 } }