diff --git a/.gitignore b/.gitignore index 96ef6c0..d787b70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -Cargo.lock +/result diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f18aec5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,614 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "env_filter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "io-kit-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d3a048d09fbb6597dbf7c69f40d14df4a49487db1487191618c893fc3b1c26" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nusb" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a330b3bc7f8b4fc729a4c63164b3927eeeaced198222a3ce6b8b6e034851b7a" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "futures-core", + "io-kit-sys", + "linux-raw-sys", + "log", + "once_cell", + "rustix", + "slab", + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", + "serde", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usbip" +version = "0.8.0" +dependencies = [ + "env_logger", + "log", + "num-derive", + "num-traits", + "nusb", + "rusb", + "serde", + "tokio", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 6dc0e7b..6ebb2cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "usbip" -version = "0.7.1" +version = "0.8.0" authors = ["Jiajie Chen "] edition = "2024" license = "MIT" @@ -16,7 +16,7 @@ num-traits = "0.2.15" num-derive = "0.4.2" rusb = "0.9.3" serde = { version = "1.0", features = ["derive"], optional = true } -nusb = "0.1.10" +nusb = "0.2.1" [dev-dependencies] tokio = { version = "1.22.0", features = ["full"] } diff --git a/LICENSE b/LICENSE index 2f9967a..8d0ea9b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Jiajie Chen +Copyright (c) 2020-2025 Jiajie Chen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ 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. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index fdd75db..f7db312 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,58 @@ [![Coverage Status](https://coveralls.io/repos/github/jiegec/usbip/badge.svg?branch=master)](https://coveralls.io/github/jiegec/usbip?branch=master) [![crates.io](https://img.shields.io/crates/v/usbip.svg)](https://crates.io/crates/usbip) -A Rust library to run a USB/IP server to simulate USB devices. +A Rust library to run a USB/IP server to simulate USB devices and share real USB devices over a network. -It also enables sharing devices from an OS supporting libusb(libusb claims that it supports Linux, macOS, Windows, OpenBSD/NetBSD, Haiku and Solaris) to another OS supporting USB/IP(Linux, Windows). Sharing an CCID SmartCard from macOS to Linux is tested by running `gpg --card-status`. +## What is USB/IP? + +USB/IP is a network protocol that allows USB devices to be shared between computers over a network. It enables: + +- **Device simulation**: Create virtual USB devices that can be accessed remotely +- **Device sharing**: Share physical USB devices from one machine to another +- **Cross-platform**: Works across different operating systems (Linux, etc.) + +## Installation + +### Prerequisites + +Install Rust from the [official documentation](https://www.rust-lang.org/tools/install). + +### Building from source + +```bash +git clone https://github.com/jiegec/usbip.git +cd usbip +cargo build --release +``` ## How to use -See examples directory. Three examples are provided: +### Examples -1. hid_keyboard: Simulate a hid keyboard that types something every second. -2. cdc_acm_serial: Simulate a serial that gets a character every second. -3. host: Act like original usb/ip sharing server, sharing one device from one machine to another. Also supports sharing from macOS to Linux! +The `examples/` directory contains three example programs: -To run example, run: +1. **hid_keyboard**: Simulate a HID keyboard that types something every second +2. **cdc_acm_serial**: Simulate a CDC ACM serial device that receives a character every second +3. **host**: Act as a USB/IP server, sharing physical devices from the host machine to remote clients + +#### Running an example ```bash -$ env RUST_LOG=info cargo run --example hid_keyboard +cargo run --example hid_keyboard ``` -Then, in a USB/IP client environment: +#### Connecting from a USB/IP client + +On the client machine (e.g. Linux with USB/IP support): ```bash -$ usbip list -r $remote_ip -$ usbip attach -r $remote_ip -b $bus_id +# List available devices +usbip list -r $remote_ip + +# Attach to a device +usbip attach -r $remote_ip -b $bus_id ``` -Then, you can inspect the simulated USB device behavior in both sides. +## License -## API - -See code comments. Not finalized yet, so get prepared for api breaking changes. +MIT License - see [LICENSE](LICENSE) file for details. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..752172b --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773628058, + "narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..98fb844 --- /dev/null +++ b/flake.nix @@ -0,0 +1,65 @@ +{ + description = "A library to run USB/IP server"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + nativeBuildInputs = with pkgs; [ + rustc + cargo + pkg-config + ]; + + buildInputs = with pkgs; [ + libusb1 + ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ + udev + ]; + + usbip = pkgs.rustPlatform.buildRustPackage { + pname = "usbip"; + version = "0.8.0"; + + src = self; + + cargoHash = "sha256-PiJvE9CWbNIZif/ku3G+A7g5vSzl2O80a33NZdgmFL4="; + + inherit nativeBuildInputs buildInputs; + + buildFeatures = [ "serde" ]; + + # Build both the library and all examples + cargoBuildFlags = [ "--examples" ]; + + postInstall = '' + for f in target/*/release/examples/{hid_keyboard,cdc_acm_serial,host}; do + [ -f "$f" ] && install -Dm755 "$f" "$out/bin/$(basename $f)" + done + ''; + + meta = with pkgs.lib; { + description = "A library to run USB/IP server"; + homepage = "https://github.com/jiegec/usbip"; + license = licenses.mit; + mainProgram = "host"; + }; + }; + in + { + packages = { + default = usbip; + inherit usbip; + }; + + devShells.default = pkgs.mkShell { + inherit nativeBuildInputs buildInputs; + }; + }); +} diff --git a/src/cdc.rs b/src/cdc.rs index 9b57c4b..7308a28 100644 --- a/src/cdc.rs +++ b/src/cdc.rs @@ -75,9 +75,17 @@ impl UsbInterfaceHandler for UsbCdcAcmHandler { return Ok(vec![]); } else { // bulk in - // TODO: handle max packet size - let resp = self.tx_buffer.clone(); - self.tx_buffer.clear(); + // Handle max packet size - return data in chunks of max_packet_size + let max_packet_size = ep.max_packet_size as usize; + let resp = if self.tx_buffer.len() > max_packet_size { + // Return only the first chunk (max_packet_size bytes) + self.tx_buffer.drain(..max_packet_size).collect::>() + } else { + // Return all data if it fits in one packet + let resp = self.tx_buffer.clone(); + self.tx_buffer.clear(); + resp + }; return Ok(resp); } } diff --git a/src/device.rs b/src/device.rs index 4005f14..19b135c 100644 --- a/src/device.rs +++ b/src/device.rs @@ -291,7 +291,7 @@ impl UsbDevice { match (FromPrimitive::from_u8(ep.attributes), ep.direction()) { (Some(Control), In) => { // control in - debug!("Control IN setup={:x?}", setup_packet); + debug!("Control IN setup={setup_packet:x?}"); match ( setup_packet.request_type, FromPrimitive::from_u8(setup_packet.request), @@ -436,7 +436,7 @@ impl UsbDevice { } else { Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, - format!("Invalid string index: {}", index), + format!("Invalid string index: {index}"), )) } } @@ -463,7 +463,7 @@ impl UsbDevice { Ok(desc) } _ => { - warn!("unknown desc type: {:x?}", setup_packet); + warn!("unknown desc type: {setup_packet:x?}"); Ok(vec![]) } } @@ -488,7 +488,7 @@ impl UsbDevice { } (Some(Control), Out) => { // control out - debug!("Control OUT setup={:x?}", setup_packet); + debug!("Control OUT setup={setup_packet:x?}"); match ( setup_packet.request_type, FromPrimitive::from_u8(setup_packet.request), diff --git a/src/hid.rs b/src/hid.rs index 0dc84ea..4dbd534 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -35,16 +35,17 @@ pub struct UsbHidKeyboardReport { impl UsbHidKeyboardReport { pub fn from_ascii(ascii: u8) -> UsbHidKeyboardReport { - // TODO: casing - let key = match ascii { - b'a'..=b'z' => ascii - b'a' + 4, - b'1'..=b'9' => ascii - b'1' + 30, - b'0' => 39, - b'\r' | b'\n' => 40, + let (modifier, key) = match ascii { + b'a'..=b'z' => (0, ascii - b'a' + 4), + b'A'..=b'Z' => (0x02, ascii - b'A' + 4), // Left Shift modifier + b'1'..=b'9' => (0, ascii - b'1' + 30), + b'0' => (0, 39), + b'\r' | b'\n' => (0, 40), + b' ' => (0, 44), // Space _ => unimplemented!("Unrecognized ascii {}", ascii), }; UsbHidKeyboardReport { - modifier: 0, + modifier, keys: [key, 0, 0, 0, 0, 0], } } diff --git a/src/host.rs b/src/host.rs index 20df83f..d95f715 100644 --- a/src/host.rs +++ b/src/host.rs @@ -1,5 +1,6 @@ //! Host USB use super::*; +use nusb::MaybeFuture; /// A handler to pass requests to interface of a rusb USB device of the host #[derive(Clone, Debug)] @@ -22,10 +23,7 @@ impl UsbInterfaceHandler for RusbUsbHostInterfaceHandler { setup: SetupPacket, req: &[u8], ) -> Result> { - debug!( - "To host device: ep={:?} setup={:?} req={:?}", - ep, setup, req - ); + debug!("To host device: ep={ep:?} setup={setup:?} req={req:?}",); let mut buffer = vec![0u8; transfer_buffer_length as usize]; let timeout = std::time::Duration::new(1, 0); let handle = self.handle.lock().unwrap(); @@ -111,7 +109,7 @@ impl UsbDeviceHandler for RusbUsbHostDeviceHandler { setup: SetupPacket, req: &[u8], ) -> Result> { - debug!("To host device: setup={:?} req={:?}", setup, req); + debug!("To host device: setup={setup:?} req={req:?}"); let mut buffer = vec![0u8; transfer_buffer_length as usize]; let timeout = std::time::Duration::new(1, 0); let handle = self.handle.lock().unwrap(); @@ -178,48 +176,112 @@ impl UsbInterfaceHandler for NusbUsbHostInterfaceHandler { setup: SetupPacket, req: &[u8], ) -> Result> { - debug!( - "To host device: ep={:?} setup={:?} req={:?}", - ep, setup, req - ); - let mut buffer = vec![0u8; transfer_buffer_length as usize]; + debug!("To host device: ep={ep:?} setup={setup:?} req={req:?}",); let timeout = std::time::Duration::new(1, 0); let handle = self.handle.lock().unwrap(); - let control = nusb::transfer::Control { - control_type: match (setup.request_type >> 5) & 0b11 { - 0 => nusb::transfer::ControlType::Standard, - 1 => nusb::transfer::ControlType::Class, - 2 => nusb::transfer::ControlType::Vendor, - _ => unimplemented!(), - }, - recipient: match setup.request_type & 0b11111 { - 0 => nusb::transfer::Recipient::Device, - 1 => nusb::transfer::Recipient::Interface, - 2 => nusb::transfer::Recipient::Endpoint, - 3 => nusb::transfer::Recipient::Other, - _ => unimplemented!(), - }, - request: setup.request, - value: setup.value, - index: setup.index, - }; if ep.attributes == EndpointAttributes::Control as u8 { // control if let Direction::In = ep.direction() { // control in - if let Ok(len) = handle.control_in_blocking(control, &mut buffer, timeout) { - return Ok(Vec::from(&buffer[..len])); + let control_in = nusb::transfer::ControlIn { + control_type: match (setup.request_type >> 5) & 0b11 { + 0 => nusb::transfer::ControlType::Standard, + 1 => nusb::transfer::ControlType::Class, + 2 => nusb::transfer::ControlType::Vendor, + _ => unimplemented!(), + }, + recipient: match setup.request_type & 0b11111 { + 0 => nusb::transfer::Recipient::Device, + 1 => nusb::transfer::Recipient::Interface, + 2 => nusb::transfer::Recipient::Endpoint, + 3 => nusb::transfer::Recipient::Other, + _ => unimplemented!(), + }, + request: setup.request, + value: setup.value, + index: setup.index, + length: transfer_buffer_length as u16, + }; + if let Ok(data) = handle.control_in(control_in, timeout).wait() { + return Ok(data); } } else { // control out - handle.control_out_blocking(control, req, timeout).ok(); + let control_out = nusb::transfer::ControlOut { + control_type: match (setup.request_type >> 5) & 0b11 { + 0 => nusb::transfer::ControlType::Standard, + 1 => nusb::transfer::ControlType::Class, + 2 => nusb::transfer::ControlType::Vendor, + _ => unimplemented!(), + }, + recipient: match setup.request_type & 0b11111 { + 0 => nusb::transfer::Recipient::Device, + 1 => nusb::transfer::Recipient::Interface, + 2 => nusb::transfer::Recipient::Endpoint, + 3 => nusb::transfer::Recipient::Other, + _ => unimplemented!(), + }, + request: setup.request, + value: setup.value, + index: setup.index, + data: req, + }; + handle.control_out(control_out, timeout).wait().ok(); } } else if ep.attributes == EndpointAttributes::Interrupt as u8 { // interrupt - todo!("Missing blocking api for interrupt transfer in nusb") + if let Direction::In = ep.direction() { + // interrupt in + let mut endpoint = handle + .endpoint::(ep.address) + .map_err(|e| { + std::io::Error::other(format!("Failed to open interrupt endpoint: {}", e)) + })?; + let buffer = endpoint.allocate(transfer_buffer_length as usize); + let completion = endpoint.transfer_blocking(buffer, timeout); + if completion.status.is_ok() { + return Ok(completion.buffer.to_vec()); + } + } else { + // interrupt out + let mut endpoint = handle + .endpoint::(ep.address) + .map_err(|e| { + std::io::Error::other(format!("Failed to open interrupt endpoint: {}", e)) + })?; + if !req.is_empty() { + let mut buffer = endpoint.allocate(req.len()); + buffer.copy_from_slice(req); + endpoint.transfer_blocking(buffer, timeout); + } + } } else if ep.attributes == EndpointAttributes::Bulk as u8 { // bulk - todo!("Missing blocking api for bulk transfer in nusb") + if let Direction::In = ep.direction() { + // bulk in + let mut endpoint = handle + .endpoint::(ep.address) + .map_err(|e| { + std::io::Error::other(format!("Failed to open bulk endpoint: {}", e)) + })?; + let buffer = endpoint.allocate(transfer_buffer_length as usize); + let completion = endpoint.transfer_blocking(buffer, timeout); + if completion.status.is_ok() { + return Ok(completion.buffer.to_vec()); + } + } else { + // bulk out + let mut endpoint = handle + .endpoint::(ep.address) + .map_err(|e| { + std::io::Error::other(format!("Failed to open bulk endpoint: {}", e)) + })?; + if !req.is_empty() { + let mut buffer = endpoint.allocate(req.len()); + buffer.copy_from_slice(req); + endpoint.transfer_blocking(buffer, timeout); + } + } } Ok(vec![]) } @@ -260,39 +322,62 @@ impl UsbDeviceHandler for NusbUsbHostDeviceHandler { setup: SetupPacket, req: &[u8], ) -> Result> { - debug!("To host device: setup={:?} req={:?}", setup, req); - let mut buffer = vec![0u8; transfer_buffer_length as usize]; + debug!("To host device: setup={setup:?} req={req:?}"); let timeout = std::time::Duration::new(1, 0); let handle = self.handle.lock().unwrap(); - let control = nusb::transfer::Control { - control_type: match (setup.request_type >> 5) & 0b11 { - 0 => nusb::transfer::ControlType::Standard, - 1 => nusb::transfer::ControlType::Class, - 2 => nusb::transfer::ControlType::Vendor, - _ => unimplemented!(), - }, - recipient: match setup.request_type & 0b11111 { - 0 => nusb::transfer::Recipient::Device, - 1 => nusb::transfer::Recipient::Interface, - 2 => nusb::transfer::Recipient::Endpoint, - 3 => nusb::transfer::Recipient::Other, - _ => unimplemented!(), - }, - request: setup.request, - value: setup.value, - index: setup.index, - }; // control if cfg!(not(target_os = "windows")) { if setup.request_type & 0x80 == 0 { // control out #[cfg(not(target_os = "windows"))] - handle.control_out_blocking(control, req, timeout).ok(); + { + let control_out = nusb::transfer::ControlOut { + control_type: match (setup.request_type >> 5) & 0b11 { + 0 => nusb::transfer::ControlType::Standard, + 1 => nusb::transfer::ControlType::Class, + 2 => nusb::transfer::ControlType::Vendor, + _ => unimplemented!(), + }, + recipient: match setup.request_type & 0b11111 { + 0 => nusb::transfer::Recipient::Device, + 1 => nusb::transfer::Recipient::Interface, + 2 => nusb::transfer::Recipient::Endpoint, + 3 => nusb::transfer::Recipient::Other, + _ => unimplemented!(), + }, + request: setup.request, + value: setup.value, + index: setup.index, + data: req, + }; + handle.control_out(control_out, timeout).wait().ok(); + } } else { // control in #[cfg(not(target_os = "windows"))] - if let Ok(len) = handle.control_in_blocking(control, &mut buffer, timeout) { - return Ok(Vec::from(&buffer[..len])); + { + let control_in = nusb::transfer::ControlIn { + control_type: match (setup.request_type >> 5) & 0b11 { + 0 => nusb::transfer::ControlType::Standard, + 1 => nusb::transfer::ControlType::Class, + 2 => nusb::transfer::ControlType::Vendor, + _ => unimplemented!(), + }, + recipient: match setup.request_type & 0b11111 { + 0 => nusb::transfer::Recipient::Device, + 1 => nusb::transfer::Recipient::Interface, + 2 => nusb::transfer::Recipient::Endpoint, + 3 => nusb::transfer::Recipient::Other, + _ => unimplemented!(), + }, + request: setup.request, + value: setup.value, + index: setup.index, + length: transfer_buffer_length as u16, + }; + if let Ok(data) = handle.control_in(control_in, timeout).wait() { + return Ok(data); + } } } } else { diff --git a/src/lib.rs b/src/lib.rs index 46da1dd..b9fae1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use log::*; use num_derive::FromPrimitive; use num_traits::FromPrimitive; +use nusb::MaybeFuture; use rusb::*; use std::any::Any; use std::collections::{HashMap, VecDeque}; @@ -58,13 +59,10 @@ impl UsbIpServer { pub fn with_nusb_devices(nusb_device_infos: Vec) -> Vec { let mut devices = vec![]; for device_info in nusb_device_infos { - let dev = match device_info.open() { + let dev = match device_info.open().wait() { Ok(dev) => dev, Err(err) => { - warn!( - "Impossible to open device {:?}: {}, ignoring device", - device_info, err - ); + warn!("Impossible to open device {device_info:?}: {err}, ignoring device",); continue; } }; @@ -72,8 +70,7 @@ impl UsbIpServer { Ok(cfg) => cfg, Err(err) => { warn!( - "Impossible to get active configuration {:?}: {}, ignoring device", - device_info, err + "Impossible to get active configuration {device_info:?}: {err}, ignoring device", ); continue; } @@ -82,7 +79,7 @@ impl UsbIpServer { for intf in cfg.interfaces() { // ignore alternate settings let intf_num = intf.interface_number(); - let intf = dev.claim_interface(intf_num).unwrap(); + let intf = dev.claim_interface(intf_num).wait().unwrap(); let alt_setting = intf.descriptors().next().unwrap(); let mut endpoints = vec![]; @@ -104,25 +101,29 @@ impl UsbIpServer { interface_subclass: alt_setting.subclass(), interface_protocol: alt_setting.protocol(), endpoints, - string_interface: alt_setting.string_index().unwrap_or(0), + string_interface: alt_setting.string_index().map(|nz| nz.get()).unwrap_or(0), class_specific_descriptor: Vec::new(), handler, }); } + + // Platform-specific bus number (Linux-only) + let bus_num_val: u32; + #[cfg(target_os = "linux")] + { + bus_num_val = device_info.busnum() as u32; + } + #[cfg(not(target_os = "linux"))] + { + bus_num_val = 0; + } + + let device_address = device_info.device_address(); + let mut device = UsbDevice { - path: format!( - "/sys/bus/{}/{}/{}", - device_info.bus_number(), - device_info.device_address(), - 0 - ), - bus_id: format!( - "{}-{}-{}", - device_info.bus_number(), - device_info.device_address(), - 0, - ), - bus_num: device_info.bus_number() as u32, + path: format!("/sys/bus/{}/{}/{}", bus_num_val, device_address, 0), + bus_id: format!("{}-{}-{}", bus_num_val, device_address, 0), + bus_num: bus_num_val, dev_num: 0, speed: device_info.speed().unwrap() as u32, vendor_id: device_info.vendor_id(), @@ -178,8 +179,7 @@ impl UsbIpServer { Ok(desc) => desc, Err(err) => { warn!( - "Impossible to get device descriptor for {:?}: {}, ignoring device", - dev, err + "Impossible to get device descriptor for {dev:?}: {err}, ignoring device", ); continue; } @@ -188,8 +188,7 @@ impl UsbIpServer { Ok(desc) => desc, Err(err) => { warn!( - "Impossible to get config descriptor for {:?}: {}, ignoring device", - dev, err + "Impossible to get config descriptor for {dev:?}: {err}, ignoring device", ); continue; } @@ -319,7 +318,7 @@ impl UsbIpServer { let open_device = match dev.open() { Ok(dev) => dev, Err(err) => { - warn!("Impossible to share {:?}: {}, ignoring device", dev, err); + warn!("Impossible to share {dev:?}: {err}, ignoring device"); continue; } }; @@ -377,7 +376,7 @@ impl UsbIpServer { } else { Err(std::io::Error::new( ErrorKind::NotFound, - format!("Device {} not found", bus_id), + format!("Device {bus_id} not found"), )) } } @@ -471,13 +470,13 @@ pub async fn handler( let res = match device.find_ep(real_ep as u8) { None => { - warn!("Endpoint {:02x?} not found", real_ep); + warn!("Endpoint {real_ep:02x?} not found"); UsbIpResponse::usbip_ret_submit_fail(&header) } Some((ep, intf)) => { - trace!("->Endpoint {:02x?}", ep); - trace!("->Setup {:02x?}", setup); - trace!("->Request {:02x?}", data); + trace!("->Endpoint {ep:02x?}"); + trace!("->Setup {setup:02x?}"); + trace!("->Request {data:02x?}"); let resp = device .handle_urb( ep, @@ -493,12 +492,12 @@ pub async fn handler( if out { trace!("<-Wrote {}", data.len()); } else { - trace!("<-Resp {:02x?}", resp); + trace!("<-Resp {resp:02x?}"); } UsbIpResponse::usbip_ret_submit_success(&header, 0, 0, resp, vec![]) } Err(err) => { - warn!("Error handling URB: {}", err); + warn!("Error handling URB: {err}"); UsbIpResponse::usbip_ret_submit_fail(&header) } } @@ -511,7 +510,7 @@ pub async fn handler( mut header, unlink_seqnum, } => { - trace!("Got USBIP_CMD_UNLINK for {:10x?}", unlink_seqnum); + trace!("Got USBIP_CMD_UNLINK for {unlink_seqnum:10x?}"); header.command = USBIP_RET_UNLINK.into(); @@ -535,11 +534,11 @@ pub async fn server(addr: SocketAddr, server: Arc) { let new_server = server.clone(); tokio::spawn(async move { let res = handler(&mut socket, new_server).await; - info!("Handler ended with {:?}", res); + info!("Handler ended with {res:?}"); }); } Err(err) => { - warn!("Got error {:?}", err); + warn!("Got error {err:?}"); } } } diff --git a/src/usbip_protocol.rs b/src/usbip_protocol.rs index c47d37b..aaebf9a 100644 --- a/src/usbip_protocol.rs +++ b/src/usbip_protocol.rs @@ -149,8 +149,7 @@ impl UsbIpCommand { if version != 0 && version != USBIP_VERSION { return Err(std::io::Error::other(format!( - "Unknown version: {:#04X}", - version + "Unknown version: {version:#04X}" ))); } @@ -243,8 +242,7 @@ impl UsbIpCommand { }) } _ => Err(std::io::Error::other(format!( - "Unknown command: {:#04X}", - command + "Unknown command: {command:#04X}" ))), } } @@ -386,8 +384,8 @@ impl UsbIpResponse { let mut result = Vec::with_capacity(48 + transfer_buffer.len() + iso_packet_descriptor.len()); - debug_assert!(header.command == USBIP_RET_SUBMIT.into()); - debug_assert!(if header.direction == Direction::In as u32 { + assert!(header.command == USBIP_RET_SUBMIT.into()); + assert!(if header.direction == Direction::In as u32 { actual_length == transfer_buffer.len() as u32 } else { actual_length == 0 @@ -407,7 +405,7 @@ impl UsbIpResponse { Self::UsbIpRetUnlink { ref header, status } => { let mut result = Vec::with_capacity(48); - debug_assert!(header.command == USBIP_RET_UNLINK.into()); + assert!(header.command == USBIP_RET_UNLINK.into()); result.extend_from_slice(&header.to_bytes()); result.extend_from_slice(&status.to_be_bytes()); @@ -623,7 +621,7 @@ mod tests { fn byte_serialize_op_rep_devlist() { setup_test_logger(); let device = example_device(); - let res = UsbIpResponse::op_rep_devlist(&[device.clone()]); + let res = UsbIpResponse::op_rep_devlist(std::slice::from_ref(&device)); assert_eq!( res.to_bytes(), [