Compare commits
10 commits
ad1537a0f8
...
4df87e3b84
| Author | SHA1 | Date | |
|---|---|---|---|
| 4df87e3b84 | |||
|
|
0878920532 | ||
|
|
a19bfe8301 | ||
|
|
c8cd214e1d | ||
|
|
48d0d9ca34 | ||
|
|
b83f584391 | ||
|
|
fdbb9574aa | ||
|
|
3ff5df5c2e | ||
|
|
156ec45a81 | ||
|
|
e14858f918 |
13 changed files with 991 additions and 135 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
/result
|
||||
|
|
|
|||
614
Cargo.lock
generated
Normal file
614
Cargo.lock
generated
Normal file
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "usbip"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
authors = ["Jiajie Chen <c@jia.je>"]
|
||||
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"] }
|
||||
|
|
|
|||
4
LICENSE
4
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.
|
||||
SOFTWARE.
|
||||
|
|
|
|||
55
README.md
55
README.md
|
|
@ -3,33 +3,58 @@
|
|||
[](https://coveralls.io/github/jiegec/usbip?branch=master)
|
||||
[](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.
|
||||
|
|
|
|||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
|
|
@ -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
|
||||
}
|
||||
65
flake.nix
Normal file
65
flake.nix
Normal file
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
}
|
||||
14
src/cdc.rs
14
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::<Vec<_>>()
|
||||
} else {
|
||||
// Return all data if it fits in one packet
|
||||
let resp = self.tx_buffer.clone();
|
||||
self.tx_buffer.clear();
|
||||
resp
|
||||
};
|
||||
return Ok(resp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
15
src/hid.rs
15
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],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
197
src/host.rs
197
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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::<nusb::transfer::Interrupt, nusb::transfer::In>(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::<nusb::transfer::Interrupt, nusb::transfer::Out>(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::<nusb::transfer::Bulk, nusb::transfer::In>(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::<nusb::transfer::Bulk, nusb::transfer::Out>(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<Vec<u8>> {
|
||||
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 {
|
||||
|
|
|
|||
73
src/lib.rs
73
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<nusb::DeviceInfo>) -> Vec<UsbDevice> {
|
||||
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<T: AsyncReadExt + AsyncWriteExt + Unpin>(
|
|||
|
||||
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<T: AsyncReadExt + AsyncWriteExt + Unpin>(
|
|||
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<T: AsyncReadExt + AsyncWriteExt + Unpin>(
|
|||
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<UsbIpServer>) {
|
|||
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:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue