From 0e2fc805099535633db98a40d57ace0aaead5379 Mon Sep 17 00:00:00 2001 From: Marshall Pierce Date: Mon, 28 Aug 2023 17:35:03 -0600 Subject: [PATCH] Rust tools for working with Realtek firmware Further adventures in porting tools to Rust to flesh out the supported API. These tools didn't feel like `example`s, so I made a top level `bumble` CLI tool that hosts them all as subcommands. I also moved the usb probe not-really-an-`example` into it as well. I'm open to suggestions on how best to organize the subcommands to make them intuitive to explore with `--help`, and how to leave room for other future tools. I also adopted the per-OS project data dir for a default firmware location so that users can download once and then use those .bin files from anywhere without having to sprinkle .bin files in project directories or reaching inside the python package dir hierarchy. --- .github/workflows/python-build-test.yml | 17 +- bumble/drivers/__init__.py | 22 + bumble/drivers/rtk.py | 18 + rust/Cargo.lock | 832 +++++++++++++++--- rust/Cargo.toml | 30 +- rust/README.md | 16 +- rust/pytests/wrapper.rs | 8 +- .../resources/test/firmware/realtek/README.md | 4 + .../realtek/rtl8723b_fw_structure.bin | Bin 0 -> 45048 bytes .../realtek/rtl8761bu_fw_structure.bin | Bin 0 -> 44484 bytes rust/src/cli/firmware/mod.rs | 15 + rust/src/cli/firmware/rtk.rs | 265 ++++++ rust/src/cli/mod.rs | 17 + .../usb_probe.rs => src/cli/usb/mod.rs} | 16 +- rust/src/internal/drivers/mod.rs | 17 + rust/src/internal/drivers/rtk.rs | 253 ++++++ rust/src/internal/mod.rs | 20 + rust/src/lib.rs | 2 + rust/src/main.rs | 179 ++++ rust/src/wrapper/device.rs | 40 +- rust/src/wrapper/drivers/mod.rs | 17 + rust/src/wrapper/drivers/rtk.rs | 141 +++ rust/src/wrapper/host.rs | 71 ++ rust/src/wrapper/mod.rs | 16 +- rust/src/wrapper/profile.rs | 13 +- setup.cfg | 1 + tools/rtk_fw_download.py | 10 +- tools/rtk_util.py | 5 +- 28 files changed, 1881 insertions(+), 164 deletions(-) create mode 100644 rust/resources/test/firmware/realtek/README.md create mode 100644 rust/resources/test/firmware/realtek/rtl8723b_fw_structure.bin create mode 100644 rust/resources/test/firmware/realtek/rtl8761bu_fw_structure.bin create mode 100644 rust/src/cli/firmware/mod.rs create mode 100644 rust/src/cli/firmware/rtk.rs create mode 100644 rust/src/cli/mod.rs rename rust/{examples/usb_probe.rs => src/cli/usb/mod.rs} (97%) create mode 100644 rust/src/internal/drivers/mod.rs create mode 100644 rust/src/internal/drivers/rtk.rs create mode 100644 rust/src/internal/mod.rs create mode 100644 rust/src/main.rs create mode 100644 rust/src/wrapper/drivers/mod.rs create mode 100644 rust/src/wrapper/drivers/rtk.rs create mode 100644 rust/src/wrapper/host.rs diff --git a/.github/workflows/python-build-test.yml b/.github/workflows/python-build-test.yml index 3f20093..c8a1031 100644 --- a/.github/workflows/python-build-test.yml +++ b/.github/workflows/python-build-test.yml @@ -45,7 +45,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.8", "3.9", "3.10" ] + python-version: [ "3.8", "3.9", "3.10", "3.11" ] + rust-version: [ "1.70.0", "stable" ] fail-fast: false steps: - name: Check out from Git @@ -62,9 +63,15 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: components: clippy,rustfmt - - name: Rust Lints - run: cd rust && cargo fmt --check && cargo clippy --all-targets -- --deny warnings + toolchain: ${{ matrix.rust-version }} - name: Rust Build - run: cd rust && cargo build --all-targets + run: cd rust && cargo build --all-targets && cargo build --all-features --all-targets + # Lints after build so what clippy needs is already built + - name: Rust Lints + run: cd rust && cargo fmt --check && cargo clippy --all-targets -- --deny warnings && cargo clippy --all-features --all-targets -- --deny warnings - name: Rust Tests - run: cd rust && cargo test \ No newline at end of file + run: cd rust && cargo test + # At some point, hook up publishing the binary. For now, just make sure it builds. + # Once we're ready to publish binaries, this should be built with `--release`. + - name: Build Bumble CLI + run: cd rust && cargo build --features bumble-tools --bin bumble \ No newline at end of file diff --git a/bumble/drivers/__init__.py b/bumble/drivers/__init__.py index 2decab7..bb63dd7 100644 --- a/bumble/drivers/__init__.py +++ b/bumble/drivers/__init__.py @@ -21,6 +21,9 @@ like loading firmware after a cold start. # ----------------------------------------------------------------------------- import abc import logging +import pathlib +import platform +import platformdirs from . import rtk @@ -66,3 +69,22 @@ async def get_driver_for_host(host): return driver return None + + +def project_data_dir() -> pathlib.Path: + """ + Returns: + A path to an OS-specific directory for bumble data. The directory is created if + it doesn't exist. + """ + if platform.system() == 'Darwin': + # platformdirs doesn't handle macOS right: it doesn't assemble a bundle id + # out of author & project + return platformdirs.user_data_path( + appname='com.google.bumble', ensure_exists=True + ) + else: + # windows and linux don't use the com qualifier + return platformdirs.user_data_path( + appname='bumble', appauthor='google', ensure_exists=True + ) diff --git a/bumble/drivers/rtk.py b/bumble/drivers/rtk.py index e7e3c1f..0bce67d 100644 --- a/bumble/drivers/rtk.py +++ b/bumble/drivers/rtk.py @@ -446,6 +446,11 @@ class Driver: # When the environment variable is set, don't look elsewhere return None + # Then, look where the firmware download tool writes by default + if (path := rtk_firmware_dir() / file_name).is_file(): + logger.debug(f"{file_name} found in project data dir") + return path + # Then, look in the package's driver directory if (path := pathlib.Path(__file__).parent / "rtk_fw" / file_name).is_file(): logger.debug(f"{file_name} found in package dir") @@ -646,3 +651,16 @@ class Driver: await self.download_firmware() await self.host.send_command(HCI_Reset_Command(), check_result=True) logger.info(f"loaded FW image {self.driver_info.fw_name}") + + +def rtk_firmware_dir() -> pathlib.Path: + """ + Returns: + A path to a subdir of the project data dir for Realtek firmware. + The directory is created if it doesn't exist. + """ + from bumble.drivers import project_data_dir + + p = project_data_dir() / "firmware" / "realtek" + p.mkdir(parents=True, exist_ok=True) + return p diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 399f916..024a1a2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -19,33 +19,32 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" @@ -67,9 +66,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys", @@ -77,9 +76,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "atty" @@ -100,9 +99,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -113,6 +112,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" + [[package]] name = "bitflags" version = "1.3.2" @@ -121,16 +126,17 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bumble" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.3.19", + "clap 4.4.1", + "directories", "env_logger", "hex", "itertools", @@ -142,6 +148,7 @@ dependencies = [ "pyo3", "pyo3-asyncio", "rand", + "reqwest", "rusb", "strum", "strum_macros", @@ -150,6 +157,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + [[package]] name = "bytes" version = "1.4.0" @@ -158,9 +171,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] @@ -188,9 +201,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" dependencies = [ "clap_builder", "clap_derive", @@ -199,26 +212,26 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" dependencies = [ "anstream", "anstyle", - "clap_lex 0.5.0", + "clap_lex 0.5.1", "strsim", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -232,9 +245,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "colorchoice" @@ -242,12 +255,58 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -263,9 +322,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -288,6 +347,36 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.28" @@ -344,7 +433,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -390,9 +479,28 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] name = "hashbrown" @@ -427,12 +535,93 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -451,9 +640,15 @@ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "inventory" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a53088c87cf71c9d4f3372a2cb9eea1e7b8a0b1bf8b7f7d23fe5b76dbb07e63b" +checksum = "e1be380c410bf0595e94992a648ea89db4dd3f3354ba54af206fd2a68cf5ac8e" + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" @@ -475,6 +670,21 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -517,15 +727,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] name = "memoffset" @@ -545,6 +755,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -572,17 +788,34 @@ dependencies = [ ] [[package]] -name = "nix" -version = "0.26.2" +name = "native-tls" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", "pin-utils", - "static_assertions", ] [[package]] @@ -607,9 +840,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] @@ -620,6 +853,56 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.5.1" @@ -650,16 +933,22 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets", ] [[package]] -name = "pin-project-lite" -version = "0.2.10" +name = "percent-encoding" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -778,9 +1067,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -815,6 +1104,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -825,10 +1123,21 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.9.1" +name = "redox_users" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ "aho-corasick", "memchr", @@ -838,9 +1147,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -849,15 +1158,52 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] [[package]] name = "rusb" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a8c36914f9b1a3be712c1dfa48c9b397131f9a75707e570a391735f785c5d1" +checksum = "45fff149b6033f25e825cbb7b2c625a11ee8e6dac09264d49beb125e39aa97bf" dependencies = [ "libc", "libusb1-sys", @@ -871,11 +1217,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.6" +version = "0.38.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" +checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", @@ -888,12 +1234,93 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -905,9 +1332,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -929,10 +1356,14 @@ dependencies = [ ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "socket2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] [[package]] name = "strsim" @@ -948,15 +1379,15 @@ checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6069ca09d878a33f883cc06aaa9718ede171841d3832450354410b718b097232" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -972,9 +1403,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -989,13 +1420,13 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.7.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys", ] @@ -1017,31 +1448,45 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] -name = "tokio" -version = "1.29.1" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -1050,7 +1495,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys", ] @@ -1063,21 +1508,103 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unindent" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -1090,12 +1617,97 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1138,9 +1750,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1153,42 +1765,52 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 523074c..c12709f 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,7 +10,7 @@ documentation = "https://docs.rs/crate/bumble" authors = ["Marshall Pierce "] keywords = ["bluetooth", "ble"] categories = ["api-bindings", "network-programming"] -rust-version = "1.69.0" +rust-version = "1.70.0" [dependencies] pyo3 = { version = "0.18.3", features = ["macros"] } @@ -23,7 +23,16 @@ hex = "0.4.3" itertools = "0.11.0" lazy_static = "1.4.0" thiserror = "1.0.41" + +# CLI anyhow = { version = "1.0.71", optional = true } +clap = { version = "4.3.3", features = ["derive"], optional = true } +directories = { version = "5.0.1", optional = true } +owo-colors = { version = "3.5.0", optional = true } +reqwest = { version = "0.11.20", features = ["blocking"], optional = true } +rusb = { version = "0.9.2", optional = true } +log = { version = "0.4.19", optional = true } +env_logger = { version = "0.10.0", optional = true } [dev-dependencies] tokio = { version = "1.28.2", features = ["full"] } @@ -32,17 +41,25 @@ nix = "0.26.2" anyhow = "1.0.71" pyo3 = { version = "0.18.3", features = ["macros", "anyhow"] } pyo3-asyncio = { version = "0.18.0", features = ["tokio-runtime", "attributes", "testing"] } +rusb = "0.9.2" +rand = "0.8.5" clap = { version = "4.3.3", features = ["derive"] } owo-colors = "3.5.0" log = "0.4.19" env_logger = "0.10.0" -rusb = "0.9.2" -rand = "0.8.5" + +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] [[bin]] name = "gen-assigned-numbers" path = "tools/gen_assigned_numbers.rs" -required-features = ["bumble-dev-tools"] +required-features = ["bumble-codegen"] + +[[bin]] +name = "bumble" +path = "src/main.rs" +required-features = ["bumble-tools"] # test entry point that uses pyo3_asyncio's test harness [[test]] @@ -53,4 +70,7 @@ harness = false [features] anyhow = ["pyo3/anyhow"] pyo3-asyncio-attributes = ["pyo3-asyncio/attributes"] -bumble-dev-tools = ["dep:anyhow"] \ No newline at end of file +bumble-codegen = ["dep:anyhow"] +# separate feature for CLI so that dependencies don't spend time building these +bumble-tools = ["dep:clap", "anyhow", "dep:anyhow", "dep:directories", "pyo3-asyncio-attributes", "dep:owo-colors", "dep:reqwest", "dep:rusb", "dep:log", "dep:env_logger"] +default = [] \ No newline at end of file diff --git a/rust/README.md b/rust/README.md index 2684875..23dec03 100644 --- a/rust/README.md +++ b/rust/README.md @@ -5,7 +5,8 @@ Rust wrappers around the [Bumble](https://github.com/google/bumble) Python API. Method calls are mapped to the equivalent Python, and return types adapted where relevant. -See the `examples` directory for usage. +See the CLI in `src/main.rs` or the `examples` directory for how to use the +Bumble API. # Usage @@ -27,6 +28,15 @@ PYTHONPATH=..:~/.virtualenvs/bumble/lib/python3.10/site-packages/ \ Run the corresponding `battery_server` Python example, and launch an emulator in Android Studio (currently, Canary is required) to run netsim. +# CLI + +Explore the available subcommands: + +``` +PYTHONPATH=..:[virtualenv site-packages] \ + cargo run --features bumble-tools --bin bumble -- --help +``` + # Development Run the tests: @@ -43,7 +53,7 @@ cargo clippy --all-targets ## Code gen -To have the fastest startup while keeping the build simple, code gen for +To have the fastest startup while keeping the build simple, code gen for assigned numbers is done with the `gen_assigned_numbers` tool. It should be re-run whenever the Python assigned numbers are changed. To ensure that the generated code is kept up to date, the Rust data is compared to the Python @@ -52,5 +62,5 @@ in tests at `pytests/assigned_numbers.rs`. To regenerate the assigned number tables based on the Python codebase: ``` -PYTHONPATH=.. cargo run --bin gen-assigned-numbers --features bumble-dev-tools +PYTHONPATH=.. cargo run --bin gen-assigned-numbers --features bumble-codegen ``` \ No newline at end of file diff --git a/rust/pytests/wrapper.rs b/rust/pytests/wrapper.rs index 333005b..8f69dd7 100644 --- a/rust/pytests/wrapper.rs +++ b/rust/pytests/wrapper.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bumble::wrapper::transport::Transport; +use bumble::wrapper::{drivers::rtk::DriverInfo, transport::Transport}; use nix::sys::stat::Mode; use pyo3::PyResult; @@ -29,3 +29,9 @@ async fn fifo_transport_can_open() -> PyResult<()> { Ok(()) } + +#[pyo3_asyncio::tokio::test] +async fn realtek_driver_info_all_drivers() -> PyResult<()> { + assert_eq!(12, DriverInfo::all_drivers()?.len()); + Ok(()) +} diff --git a/rust/resources/test/firmware/realtek/README.md b/rust/resources/test/firmware/realtek/README.md new file mode 100644 index 0000000..4c49608 --- /dev/null +++ b/rust/resources/test/firmware/realtek/README.md @@ -0,0 +1,4 @@ +This dir contains samples firmware images in the format used for Realtek chips, +but with repetitions of the length of the section as a little-endian 32-bit int +for the patch data instead of actual firmware, since we only need the structure +to test parsing. \ No newline at end of file diff --git a/rust/resources/test/firmware/realtek/rtl8723b_fw_structure.bin b/rust/resources/test/firmware/realtek/rtl8723b_fw_structure.bin new file mode 100644 index 0000000000000000000000000000000000000000..077cdc35e601f1df26fa386fbfd72be58e691024 GIT binary patch literal 45048 zcmeI)F$%&^3I zc^LK%-hSL@s|c|J1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h%A VW8gfw-ro84YuV>0iTU8Me*plDdoln3 literal 0 HcmV?d00001 diff --git a/rust/src/cli/firmware/mod.rs b/rust/src/cli/firmware/mod.rs new file mode 100644 index 0000000..1fa1417 --- /dev/null +++ b/rust/src/cli/firmware/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) mod rtk; diff --git a/rust/src/cli/firmware/rtk.rs b/rust/src/cli/firmware/rtk.rs new file mode 100644 index 0000000..f5524a4 --- /dev/null +++ b/rust/src/cli/firmware/rtk.rs @@ -0,0 +1,265 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Realtek firmware tools + +use crate::{Download, Source}; +use anyhow::anyhow; +use bumble::wrapper::{ + drivers::rtk::{Driver, DriverInfo, Firmware}, + host::{DriverFactory, Host}, + transport::Transport, +}; +use owo_colors::{colors::css, OwoColorize}; +use pyo3::PyResult; +use std::{fs, path}; + +pub(crate) async fn download(dl: Download) -> PyResult<()> { + let data_dir = dl + .output_dir + .or_else(|| { + directories::ProjectDirs::from("com", "google", "bumble") + .map(|pd| pd.data_local_dir().join("firmware").join("realtek")) + }) + .unwrap_or_else(|| { + eprintln!("Could not determine standard data directory"); + path::PathBuf::from(".") + }); + fs::create_dir_all(&data_dir)?; + + let (base_url, uses_bin_suffix) = match dl.source { + Source::LinuxKernel => ("https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/rtl_bt", true), + Source::RealtekOpensource => ("https://github.com/Realtek-OpenSource/android_hardware_realtek/raw/rtk1395/bt/rtkbt/Firmware/BT", false), + Source::LinuxFromScratch => ("https://anduin.linuxfromscratch.org/sources/linux-firmware/rtl_bt", true), + }; + + println!("Downloading"); + println!("{} {}", "FROM:".green(), base_url); + println!("{} {}", "TO:".green(), data_dir.to_string_lossy()); + + let url_for_file = |file_name: &str| { + let url_suffix = if uses_bin_suffix { + file_name + } else { + file_name.trim_end_matches(".bin") + }; + + let mut url = base_url.to_string(); + url.push('/'); + url.push_str(url_suffix); + url + }; + + let to_download = if let Some(single) = dl.single { + vec![( + format!("{single}_fw.bin"), + Some(format!("{single}_config.bin")), + false, + )] + } else { + DriverInfo::all_drivers()? + .iter() + .map(|di| Ok((di.firmware_name()?, di.config_name()?, di.config_needed()?))) + .collect::>>()? + }; + + let client = SimpleClient::new(); + + for (fw_filename, config_filename, config_needed) in to_download { + println!("{}", "---".yellow()); + let fw_path = data_dir.join(&fw_filename); + let config_path = config_filename.as_ref().map(|f| data_dir.join(f)); + + if fw_path.exists() && !dl.overwrite { + println!( + "{}", + format!("{} already exists, skipping", fw_path.to_string_lossy()) + .fg::() + ); + continue; + } + if let Some(cp) = config_path.as_ref() { + if cp.exists() && !dl.overwrite { + println!( + "{}", + format!("{} already exists, skipping", cp.to_string_lossy()) + .fg::() + ); + continue; + } + } + + let fw_contents = match client.get(&url_for_file(&fw_filename)).await { + Ok(data) => { + println!("Downloaded {}: {} bytes", fw_filename, data.len()); + data + } + Err(e) => { + eprintln!( + "{} {} {:?}", + "Failed to download".red(), + fw_filename.red(), + e + ); + continue; + } + }; + + let config_contents = if let Some(cn) = &config_filename { + match client.get(&url_for_file(cn)).await { + Ok(data) => { + println!("Downloaded {}: {} bytes", cn, data.len()); + Some(data) + } + Err(e) => { + if config_needed { + eprintln!("{} {} {:?}", "Failed to download".red(), cn.red(), e); + continue; + } else { + eprintln!( + "{}", + format!("No config available as {cn}").fg::() + ); + None + } + } + } + } else { + None + }; + + fs::write(&fw_path, &fw_contents)?; + if !dl.no_parse && config_filename.is_some() { + println!("{} {}", "Parsing:".cyan(), &fw_filename); + match Firmware::parse(&fw_contents).map_err(|e| anyhow!("Parse error: {:?}", e)) { + Ok(fw) => dump_firmware_desc(&fw), + Err(e) => { + eprintln!( + "{} {:?}", + "Could not parse firmware:".fg::(), + e + ); + } + } + } + if let Some((cp, cd)) = config_path + .as_ref() + .and_then(|p| config_contents.map(|c| (p, c))) + { + fs::write(cp, &cd)?; + } + } + + Ok(()) +} + +pub(crate) fn parse(firmware_path: &path::Path) -> PyResult<()> { + let contents = fs::read(firmware_path)?; + let fw = Firmware::parse(&contents) + // squish the error into a string to avoid the error type requiring that the input be + // 'static + .map_err(|e| anyhow!("Parse error: {:?}", e))?; + + dump_firmware_desc(&fw); + + Ok(()) +} + +pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> { + let transport = Transport::open(transport).await?; + + let mut host = Host::new(transport.source()?, transport.sink()?)?; + host.reset(DriverFactory::None).await?; + + if !force && !Driver::check(&host).await? { + println!("USB device not supported by this RTK driver"); + } else if let Some(driver_info) = Driver::driver_info_for_host(&host).await? { + println!("Driver:"); + println!(" {:10} {:04X}", "ROM:", driver_info.rom()?); + println!(" {:10} {}", "Firmware:", driver_info.firmware_name()?); + println!( + " {:10} {}", + "Config:", + driver_info.config_name()?.unwrap_or_default() + ); + } else { + println!("Firmware already loaded or no supported driver for this device.") + } + + Ok(()) +} + +pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> { + let transport = Transport::open(transport).await?; + + let mut host = Host::new(transport.source()?, transport.sink()?)?; + host.reset(DriverFactory::None).await?; + + match Driver::for_host(&host, force).await? { + None => { + eprintln!("Firmware already loaded or no supported driver for this device."); + } + Some(mut d) => d.download_firmware().await?, + }; + + Ok(()) +} + +pub(crate) async fn drop(transport: &str) -> PyResult<()> { + let transport = Transport::open(transport).await?; + + let mut host = Host::new(transport.source()?, transport.sink()?)?; + host.reset(DriverFactory::None).await?; + + Driver::drop_firmware(&mut host).await?; + + Ok(()) +} + +fn dump_firmware_desc(fw: &Firmware) { + println!( + "Firmware: version=0x{:08X} project_id=0x{:04X}", + fw.version(), + fw.project_id() + ); + for p in fw.patches() { + println!( + " Patch: chip_id=0x{:04X}, {} bytes, SVN Version={:08X}", + p.chip_id(), + p.contents().len(), + p.svn_version() + ) + } +} + +struct SimpleClient { + client: reqwest::Client, +} + +impl SimpleClient { + fn new() -> Self { + Self { + client: reqwest::Client::new(), + } + } + + async fn get(&self, url: &str) -> anyhow::Result> { + let resp = self.client.get(url).send().await?; + if !resp.status().is_success() { + return Err(anyhow!("Bad status: {}", resp.status())); + } + let bytes = resp.bytes().await?; + Ok(bytes.as_ref().to_vec()) + } +} diff --git a/rust/src/cli/mod.rs b/rust/src/cli/mod.rs new file mode 100644 index 0000000..2648e12 --- /dev/null +++ b/rust/src/cli/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) mod firmware; + +pub(crate) mod usb; diff --git a/rust/examples/usb_probe.rs b/rust/src/cli/usb/mod.rs similarity index 97% rename from rust/examples/usb_probe.rs rename to rust/src/cli/usb/mod.rs index 3ba3b61..7adbd75 100644 --- a/rust/examples/usb_probe.rs +++ b/rust/src/cli/usb/mod.rs @@ -23,7 +23,6 @@ //! whether it is a Bluetooth device that uses a non-standard Class, or some other //! type of device (there's no way to tell). -use clap::Parser as _; use itertools::Itertools as _; use owo_colors::{OwoColorize, Style}; use rusb::{Device, DeviceDescriptor, Direction, TransferType, UsbContext}; @@ -31,15 +30,12 @@ use std::{ collections::{HashMap, HashSet}, time::Duration, }; - const USB_DEVICE_CLASS_DEVICE: u8 = 0x00; const USB_DEVICE_CLASS_WIRELESS_CONTROLLER: u8 = 0xE0; const USB_DEVICE_SUBCLASS_RF_CONTROLLER: u8 = 0x01; const USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER: u8 = 0x01; -fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); - +pub(crate) fn probe(verbose: bool) -> anyhow::Result<()> { let mut bt_dev_count = 0; let mut device_serials_by_id: HashMap<(u16, u16), HashSet> = HashMap::new(); for device in rusb::devices()?.iter() { @@ -159,7 +155,7 @@ fn main() -> anyhow::Result<()> { println!("{:26}{}", " Product:".green(), p); } - if cli.verbose { + if verbose { print_device_details(&device, &device_desc)?; } @@ -332,11 +328,3 @@ impl From<&DeviceDescriptor> for ClassInfo { ) } } - -#[derive(clap::Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - /// Show additional info for each USB device - #[arg(long, default_value_t = false)] - verbose: bool, -} diff --git a/rust/src/internal/drivers/mod.rs b/rust/src/internal/drivers/mod.rs new file mode 100644 index 0000000..5e72c59 --- /dev/null +++ b/rust/src/internal/drivers/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Device drivers + +pub(crate) mod rtk; diff --git a/rust/src/internal/drivers/rtk.rs b/rust/src/internal/drivers/rtk.rs new file mode 100644 index 0000000..2d4e685 --- /dev/null +++ b/rust/src/internal/drivers/rtk.rs @@ -0,0 +1,253 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Drivers for Realtek controllers + +use nom::{bytes, combinator, error, multi, number, sequence}; + +/// Realtek firmware file contents +pub struct Firmware { + version: u32, + project_id: u8, + patches: Vec, +} + +impl Firmware { + /// Parse a `*_fw.bin` file + pub fn parse(input: &[u8]) -> Result>> { + let extension_sig = [0x51, 0x04, 0xFD, 0x77]; + + let (_rem, (_tag, fw_version, patch_count, payload)) = + combinator::all_consuming(combinator::map_parser( + // ignore the sig suffix + sequence::terminated( + bytes::complete::take( + // underflow will show up as parse failure + input.len().saturating_sub(extension_sig.len()), + ), + bytes::complete::tag(extension_sig.as_slice()), + ), + sequence::tuple(( + bytes::complete::tag(b"Realtech"), + // version + number::complete::le_u32, + // patch count + combinator::map(number::complete::le_u16, |c| c as usize), + // everything else except suffix + combinator::rest, + )), + ))(input)?; + + // ignore remaining input, since patch offsets are relative to the complete input + let (_rem, (chip_ids, patch_lengths, patch_offsets)) = sequence::tuple(( + // chip id + multi::many_m_n(patch_count, patch_count, number::complete::le_u16), + // patch length + multi::many_m_n(patch_count, patch_count, number::complete::le_u16), + // patch offset + multi::many_m_n(patch_count, patch_count, number::complete::le_u32), + ))(payload)?; + + let patches = chip_ids + .into_iter() + .zip(patch_lengths.into_iter()) + .zip(patch_offsets.into_iter()) + .map(|((chip_id, patch_length), patch_offset)| { + combinator::map( + sequence::preceded( + bytes::complete::take(patch_offset), + // ignore trailing 4-byte suffix + sequence::terminated( + // patch including svn version, but not suffix + combinator::consumed(sequence::preceded( + // patch before svn version or version suffix + // prefix length underflow will show up as parse failure + bytes::complete::take(patch_length.saturating_sub(8)), + // svn version + number::complete::le_u32, + )), + // dummy suffix, overwritten with firmware version + bytes::complete::take(4_usize), + ), + ), + |(patch_contents_before_version, svn_version): (&[u8], u32)| { + let mut contents = patch_contents_before_version.to_vec(); + // replace what would have been the trailing dummy suffix with fw version + contents.extend_from_slice(&fw_version.to_le_bytes()); + + Patch { + contents, + svn_version, + chip_id, + } + }, + )(input) + .map(|(_rem, output)| output) + }) + .collect::, _>>()?; + + // look for project id from the end + let mut offset = payload.len(); + let mut project_id: Option = None; + while offset >= 2 { + // Won't panic, since offset >= 2 + let chunk = &payload[offset - 2..offset]; + let length: usize = chunk[0].into(); + let opcode = chunk[1]; + offset -= 2; + + if opcode == 0xFF { + break; + } + if length == 0 { + // report what nom likely would have done, if nom was good at parsing backwards + return Err(nom::Err::Error(error::Error::new( + chunk, + error::ErrorKind::Verify, + ))); + } + if opcode == 0 && length == 1 { + project_id = offset + .checked_sub(1) + .and_then(|index| payload.get(index)) + .copied(); + break; + } + + offset -= length; + } + + match project_id { + Some(project_id) => Ok(Firmware { + project_id, + version: fw_version, + patches, + }), + None => { + // we ran out of file without finding a project id + Err(nom::Err::Error(error::Error::new( + payload, + error::ErrorKind::Eof, + ))) + } + } + } + + /// Patch version + pub fn version(&self) -> u32 { + self.version + } + + /// Project id + pub fn project_id(&self) -> u8 { + self.project_id + } + + /// Patches + pub fn patches(&self) -> &[Patch] { + &self.patches + } +} + +/// Patch in a [Firmware} +pub struct Patch { + chip_id: u16, + contents: Vec, + svn_version: u32, +} + +impl Patch { + /// Chip id + pub fn chip_id(&self) -> u16 { + self.chip_id + } + /// Contents of the patch, including the 4-byte firmware version suffix + pub fn contents(&self) -> &[u8] { + &self.contents + } + /// SVN version + pub fn svn_version(&self) -> u32 { + self.svn_version + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::anyhow; + use std::{fs, io, path}; + + #[test] + fn parse_firmware_rtl8723b() -> anyhow::Result<()> { + let fw = Firmware::parse(&firmware_contents("rtl8723b_fw_structure.bin")?) + .map_err(|e| anyhow!("{:?}", e))?; + + let fw_version = 0x0E2F9F73; + assert_eq!(fw_version, fw.version()); + assert_eq!(0x0001, fw.project_id()); + assert_eq!( + vec![(0x0001, 0x00002BBF, 22368,), (0x0002, 0x00002BBF, 22496,),], + patch_summaries(fw, fw_version) + ); + + Ok(()) + } + + #[test] + fn parse_firmware_rtl8761bu() -> anyhow::Result<()> { + let fw = Firmware::parse(&firmware_contents("rtl8761bu_fw_structure.bin")?) + .map_err(|e| anyhow!("{:?}", e))?; + + let fw_version = 0xDFC6D922; + assert_eq!(fw_version, fw.version()); + assert_eq!(0x000E, fw.project_id()); + assert_eq!( + vec![(0x0001, 0x00005060, 14048,), (0x0002, 0xD6D525A4, 30204,),], + patch_summaries(fw, fw_version) + ); + + Ok(()) + } + + fn firmware_contents(filename: &str) -> io::Result> { + fs::read( + path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("resources/test/firmware/realtek") + .join(filename), + ) + } + + /// Return a tuple of (chip id, svn version, contents len, contents sha256) + fn patch_summaries(fw: Firmware, fw_version: u32) -> Vec<(u16, u32, usize)> { + fw.patches() + .iter() + .map(|p| { + let contents = p.contents(); + let mut dummy_contents = dummy_contents(contents.len()); + dummy_contents.extend_from_slice(&p.svn_version().to_le_bytes()); + dummy_contents.extend_from_slice(&fw_version.to_le_bytes()); + assert_eq!(&dummy_contents, contents); + (p.chip_id(), p.svn_version(), contents.len()) + }) + .collect::>() + } + + fn dummy_contents(len: usize) -> Vec { + let mut vec = (len as u32).to_le_bytes().as_slice().repeat(len / 4 + 1); + assert!(vec.len() >= len); + // leave room for svn version and firmware version + vec.truncate(len - 8); + vec + } +} diff --git a/rust/src/internal/mod.rs b/rust/src/internal/mod.rs new file mode 100644 index 0000000..f474c2d --- /dev/null +++ b/rust/src/internal/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! It's not clear where to put Rust code that isn't simply a wrapper around Python. Until we have +//! a good answer for what to do there, the idea is to put it in this (non-public) module, and +//! `pub use` it into the relevant areas of the `wrapper` module so that it's still easy for users +//! to discover. + +pub(crate) mod drivers; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 73001e6..2bcb398 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -29,3 +29,5 @@ pub mod wrapper; pub mod adv; + +pub(crate) mod internal; diff --git a/rust/src/main.rs b/rust/src/main.rs new file mode 100644 index 0000000..f8401e9 --- /dev/null +++ b/rust/src/main.rs @@ -0,0 +1,179 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! CLI tools for Bumble + +#![deny(missing_docs, unsafe_code)] + +use bumble::wrapper::logging::{bumble_env_logging_level, py_logging_basic_config}; +use clap::Parser as _; +use pyo3::PyResult; +use std::{fmt, path}; + +mod cli; + +#[pyo3_asyncio::tokio::main] +async fn main() -> PyResult<()> { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .init(); + + py_logging_basic_config(bumble_env_logging_level("INFO"))?; + + let cli: Cli = Cli::parse(); + + match cli.subcommand { + Subcommand::Firmware { subcommand: fw } => match fw { + Firmware::Realtek { subcommand: rtk } => match rtk { + Realtek::Download(dl) => { + cli::firmware::rtk::download(dl).await?; + } + Realtek::Drop { transport } => cli::firmware::rtk::drop(&transport).await?, + Realtek::Info { transport, force } => { + cli::firmware::rtk::info(&transport, force).await?; + } + Realtek::Load { transport, force } => { + cli::firmware::rtk::load(&transport, force).await? + } + Realtek::Parse { firmware_path } => cli::firmware::rtk::parse(&firmware_path)?, + }, + }, + Subcommand::Usb { subcommand } => match subcommand { + Usb::Probe(probe) => cli::usb::probe(probe.verbose)?, + }, + } + + Ok(()) +} + +#[derive(clap::Parser)] +struct Cli { + #[clap(subcommand)] + subcommand: Subcommand, +} + +#[derive(clap::Subcommand, Debug, Clone)] +enum Subcommand { + /// Manage device firmware + Firmware { + #[clap(subcommand)] + subcommand: Firmware, + }, + /// USB operations + Usb { + #[clap(subcommand)] + subcommand: Usb, + }, +} + +#[derive(clap::Subcommand, Debug, Clone)] +enum Firmware { + /// Manage Realtek chipset firmware + Realtek { + #[clap(subcommand)] + subcommand: Realtek, + }, +} + +#[derive(clap::Subcommand, Debug, Clone)] + +enum Realtek { + /// Download Realtek firmware + Download(Download), + /// Drop firmware from a USB device + Drop { + /// Bumble transport spec. Must be for a USB device. + /// + /// + #[arg(long)] + transport: String, + }, + /// Show driver info for a USB device + Info { + /// Bumble transport spec. Must be for a USB device. + /// + /// + #[arg(long)] + transport: String, + /// Try to resolve driver info even if USB info is not available, or if the USB + /// (vendor,product) tuple is not in the list of known compatible RTK USB dongles. + #[arg(long, default_value_t = false)] + force: bool, + }, + /// Load firmware onto a USB device + Load { + /// Bumble transport spec. Must be for a USB device. + /// + /// + #[arg(long)] + transport: String, + /// Load firmware even if the USB info doesn't match. + #[arg(long, default_value_t = false)] + force: bool, + }, + /// Parse a firmware file + Parse { + /// Firmware file to parse + firmware_path: path::PathBuf, + }, +} + +#[derive(clap::Args, Debug, Clone)] +struct Download { + /// Directory to download to. Defaults to an OS-specific path specific to the Bumble tool. + #[arg(long)] + output_dir: Option, + /// Source to download from + #[arg(long, default_value_t = Source::LinuxKernel)] + source: Source, + /// Only download a single image + #[arg(long, value_name = "base name")] + single: Option, + /// Overwrite existing files + #[arg(long, default_value_t = false)] + overwrite: bool, + /// Don't print the parse results for the downloaded file names + #[arg(long)] + no_parse: bool, +} + +#[derive(Debug, Clone, clap::ValueEnum)] +enum Source { + LinuxKernel, + RealtekOpensource, + LinuxFromScratch, +} + +impl fmt::Display for Source { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Source::LinuxKernel => write!(f, "linux-kernel"), + Source::RealtekOpensource => write!(f, "realtek-opensource"), + Source::LinuxFromScratch => write!(f, "linux-from-scratch"), + } + } +} + +#[derive(clap::Subcommand, Debug, Clone)] +enum Usb { + /// Probe the USB bus for Bluetooth devices + Probe(Probe), +} + +#[derive(clap::Args, Debug, Clone)] +struct Probe { + /// Show additional info for each USB device + #[arg(long, default_value_t = false)] + verbose: bool, +} diff --git a/rust/src/wrapper/device.rs b/rust/src/wrapper/device.rs index d635754..11770a3 100644 --- a/rust/src/wrapper/device.rs +++ b/rust/src/wrapper/device.rs @@ -20,12 +20,17 @@ use crate::{ core::AdvertisingData, gatt_client::{ProfileServiceProxy, ServiceProxy}, hci::Address, + host::Host, transport::{Sink, Source}, - ClosureCallback, + ClosureCallback, PyObjectExt, }, }; -use pyo3::types::PyDict; -use pyo3::{intern, types::PyModule, PyObject, PyResult, Python, ToPyObject}; +use pyo3::{ + intern, + types::{PyDict, PyModule}, + PyObject, PyResult, Python, ToPyObject, +}; +use pyo3_asyncio::tokio::into_future; use std::path; /// A device that can send/receive HCI frames. @@ -65,7 +70,7 @@ impl Device { Python::with_gil(|py| { self.0 .call_method0(py, intern!(py, "power_on")) - .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) })? .await .map(|_| ()) @@ -76,7 +81,7 @@ impl Device { Python::with_gil(|py| { self.0 .call_method1(py, intern!(py, "connect"), (peer_addr,)) - .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) })? .await .map(Connection) @@ -89,7 +94,7 @@ impl Device { kwargs.set_item("filter_duplicates", filter_duplicates)?; self.0 .call_method(py, intern!(py, "start_scanning"), (), Some(kwargs)) - .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) })? .await .map(|_| ()) @@ -123,6 +128,15 @@ impl Device { .map(|_| ()) } + /// Returns the host used by the device, if any + pub fn host(&mut self) -> PyResult> { + Python::with_gil(|py| { + self.0 + .getattr(py, intern!(py, "host")) + .map(|obj| obj.into_option(Host::from)) + }) + } + /// Start advertising the data set with [Device.set_advertisement]. pub async fn start_advertising(&mut self, auto_restart: bool) -> PyResult<()> { Python::with_gil(|py| { @@ -131,7 +145,7 @@ impl Device { self.0 .call_method(py, intern!(py, "start_advertising"), (), Some(kwargs)) - .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) })? .await .map(|_| ()) @@ -142,7 +156,7 @@ impl Device { Python::with_gil(|py| { self.0 .call_method0(py, intern!(py, "stop_advertising")) - .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) })? .await .map(|_| ()) @@ -173,7 +187,7 @@ impl Peer { Python::with_gil(|py| { self.0 .call_method0(py, intern!(py, "discover_services")) - .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) })? .await .and_then(|list| { @@ -207,13 +221,7 @@ impl Peer { let class = module.getattr(P::PROXY_CLASS_NAME)?; self.0 .call_method1(py, intern!(py, "create_service_proxy"), (class,)) - .map(|obj| { - if obj.is_none(py) { - None - } else { - Some(P::wrap(obj)) - } - }) + .map(|obj| obj.into_option(P::wrap)) }) } } diff --git a/rust/src/wrapper/drivers/mod.rs b/rust/src/wrapper/drivers/mod.rs new file mode 100644 index 0000000..ff38ac1 --- /dev/null +++ b/rust/src/wrapper/drivers/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Device drivers + +pub mod rtk; diff --git a/rust/src/wrapper/drivers/rtk.rs b/rust/src/wrapper/drivers/rtk.rs new file mode 100644 index 0000000..1f629d1 --- /dev/null +++ b/rust/src/wrapper/drivers/rtk.rs @@ -0,0 +1,141 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Drivers for Realtek controllers + +use crate::wrapper::{host::Host, PyObjectExt}; +use pyo3::{intern, types::PyModule, PyObject, PyResult, Python, ToPyObject}; +use pyo3_asyncio::tokio::into_future; + +pub use crate::internal::drivers::rtk::{Firmware, Patch}; + +/// Driver for a Realtek controller +pub struct Driver(PyObject); + +impl Driver { + /// Locate the driver for the provided host. + pub async fn for_host(host: &Host, force: bool) -> PyResult> { + Python::with_gil(|py| { + PyModule::import(py, intern!(py, "bumble.drivers.rtk"))? + .getattr(intern!(py, "Driver"))? + .call_method1(intern!(py, "for_host"), (&host.obj, force)) + .and_then(into_future) + })? + .await + .map(|obj| obj.into_option(Self)) + } + + /// Check if the host has a known driver. + pub async fn check(host: &Host) -> PyResult { + Python::with_gil(|py| { + PyModule::import(py, intern!(py, "bumble.drivers.rtk"))? + .getattr(intern!(py, "Driver"))? + .call_method1(intern!(py, "check"), (&host.obj,)) + .and_then(|obj| obj.extract::()) + }) + } + + /// Find the [DriverInfo] for the host, if one matches + pub async fn driver_info_for_host(host: &Host) -> PyResult> { + Python::with_gil(|py| { + PyModule::import(py, intern!(py, "bumble.drivers.rtk"))? + .getattr(intern!(py, "Driver"))? + .call_method1(intern!(py, "driver_info_for_host"), (&host.obj,)) + .and_then(into_future) + })? + .await + .map(|obj| obj.into_option(DriverInfo)) + } + + /// Send a command to the device to drop firmware + pub async fn drop_firmware(host: &mut Host) -> PyResult<()> { + Python::with_gil(|py| { + PyModule::import(py, intern!(py, "bumble.drivers.rtk"))? + .getattr(intern!(py, "Driver"))? + .call_method1(intern!(py, "drop_firmware"), (&host.obj,)) + .and_then(into_future) + })? + .await + .map(|_| ()) + } + + /// Load firmware onto the device. + pub async fn download_firmware(&mut self) -> PyResult<()> { + Python::with_gil(|py| { + self.0 + .call_method0(py, intern!(py, "download_firmware")) + .and_then(|coroutine| into_future(coroutine.as_ref(py))) + })? + .await + .map(|_| ()) + } +} + +/// Metadata about a known driver & applicable device +pub struct DriverInfo(PyObject); + +impl DriverInfo { + /// Returns a list of all drivers that Bumble knows how to handle. + pub fn all_drivers() -> PyResult> { + Python::with_gil(|py| { + PyModule::import(py, intern!(py, "bumble.drivers.rtk"))? + .getattr(intern!(py, "Driver"))? + .getattr(intern!(py, "DRIVER_INFOS"))? + .iter()? + .map(|r| r.map(|h| DriverInfo(h.to_object(py)))) + .collect::>>() + }) + } + + /// The firmware file name to load from the filesystem, e.g. `foo_fw.bin`. + pub fn firmware_name(&self) -> PyResult { + Python::with_gil(|py| { + self.0 + .getattr(py, intern!(py, "fw_name"))? + .as_ref(py) + .extract::() + }) + } + + /// The config file name, if any, to load from the filesystem, e.g. `foo_config.bin`. + pub fn config_name(&self) -> PyResult> { + Python::with_gil(|py| { + let obj = self.0.getattr(py, intern!(py, "config_name"))?; + let handle = obj.as_ref(py); + + if handle.is_none() { + Ok(None) + } else { + handle + .extract::() + .map(|s| if s.is_empty() { None } else { Some(s) }) + } + }) + } + + /// Whether or not config is required. + pub fn config_needed(&self) -> PyResult { + Python::with_gil(|py| { + self.0 + .getattr(py, intern!(py, "config_needed"))? + .as_ref(py) + .extract::() + }) + } + + /// ROM id + pub fn rom(&self) -> PyResult { + Python::with_gil(|py| self.0.getattr(py, intern!(py, "rom"))?.as_ref(py).extract()) + } +} diff --git a/rust/src/wrapper/host.rs b/rust/src/wrapper/host.rs new file mode 100644 index 0000000..ab81450 --- /dev/null +++ b/rust/src/wrapper/host.rs @@ -0,0 +1,71 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Host-side types + +use crate::wrapper::transport::{Sink, Source}; +use pyo3::{intern, prelude::PyModule, types::PyDict, PyObject, PyResult, Python}; + +/// Host HCI commands +pub struct Host { + pub(crate) obj: PyObject, +} + +impl Host { + /// Create a Host that wraps the provided obj + pub(crate) fn from(obj: PyObject) -> Self { + Self { obj } + } + + /// Create a new Host + pub fn new(source: Source, sink: Sink) -> PyResult { + Python::with_gil(|py| { + PyModule::import(py, intern!(py, "bumble.host"))? + .getattr(intern!(py, "Host"))? + .call((source.0, sink.0), None) + .map(|any| Self { obj: any.into() }) + }) + } + + /// Send a reset command and perform other reset tasks. + pub async fn reset(&mut self, driver_factory: DriverFactory) -> PyResult<()> { + Python::with_gil(|py| { + let kwargs = match driver_factory { + DriverFactory::None => { + let kw = PyDict::new(py); + kw.set_item("driver_factory", py.None())?; + Some(kw) + } + DriverFactory::Auto => { + // leave the default in place + None + } + }; + self.obj + .call_method(py, intern!(py, "reset"), (), kwargs) + .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py))) + })? + .await + .map(|_| ()) + } +} + +/// Driver factory to use when initializing a host +#[derive(Debug, Clone)] +pub enum DriverFactory { + /// Do not load drivers + None, + /// Load appropriate driver, if any is found + Auto, +} diff --git a/rust/src/wrapper/mod.rs b/rust/src/wrapper/mod.rs index 2ab71c3..cb0730b 100644 --- a/rust/src/wrapper/mod.rs +++ b/rust/src/wrapper/mod.rs @@ -31,14 +31,17 @@ pub use pyo3_asyncio; pub mod assigned_numbers; pub mod core; pub mod device; + +pub mod drivers; pub mod gatt_client; pub mod hci; +pub mod host; pub mod logging; pub mod profile; pub mod transport; /// Convenience extensions to [PyObject] -pub trait PyObjectExt { +pub trait PyObjectExt: Sized { /// Get a GIL-bound reference fn gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny; @@ -49,6 +52,17 @@ pub trait PyObjectExt { { Python::with_gil(|py| self.gil_ref(py).extract::()) } + + /// If the Python object is a Python `None`, return a Rust `None`, otherwise `Some` with the mapped type + fn into_option(self, map_obj: impl Fn(Self) -> T) -> Option { + Python::with_gil(|py| { + if self.gil_ref(py).is_none() { + None + } else { + Some(map_obj(self)) + } + }) + } } impl PyObjectExt for PyObject { diff --git a/rust/src/wrapper/profile.rs b/rust/src/wrapper/profile.rs index 854ba80..fc473ff 100644 --- a/rust/src/wrapper/profile.rs +++ b/rust/src/wrapper/profile.rs @@ -14,7 +14,10 @@ //! GATT profiles -use crate::wrapper::gatt_client::{CharacteristicProxy, ProfileServiceProxy}; +use crate::wrapper::{ + gatt_client::{CharacteristicProxy, ProfileServiceProxy}, + PyObjectExt, +}; use pyo3::{intern, PyObject, PyResult, Python}; /// Exposes the battery GATT service @@ -26,13 +29,7 @@ impl BatteryServiceProxy { Python::with_gil(|py| { self.0 .getattr(py, intern!(py, "battery_level")) - .map(|level| { - if level.is_none(py) { - None - } else { - Some(CharacteristicProxy(level)) - } - }) + .map(|level| level.into_option(CharacteristicProxy)) }) } } diff --git a/setup.cfg b/setup.cfg index 1072acc..86ff0f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,6 +40,7 @@ install_requires = humanize >= 4.6.0; platform_system!='Emscripten' libusb1 >= 2.0.1; platform_system!='Emscripten' libusb-package == 1.0.26.1; platform_system!='Emscripten' + platformdirs == 3.10.0; platform_system!='Emscripten' prompt_toolkit >= 3.0.16; platform_system!='Emscripten' prettytable >= 3.6.0; platform_system!='Emscripten' protobuf >= 3.12.4; platform_system!='Emscripten' diff --git a/tools/rtk_fw_download.py b/tools/rtk_fw_download.py index b027141..89c49b2 100644 --- a/tools/rtk_fw_download.py +++ b/tools/rtk_fw_download.py @@ -67,8 +67,9 @@ def download_file(base_url, name, remove_suffix): @click.command @click.option( "--output-dir", - default=".", - help="Output directory where the files will be saved", + default="", + help="Output directory where the files will be saved. Defaults to the OS-specific" + "app data dir, which the driver will check when trying to find firmware", show_default=True, ) @click.option( @@ -84,7 +85,10 @@ def main(output_dir, source, single, force, parse): """Download RTK firmware images and configs.""" # Check that the output dir exists - output_dir = pathlib.Path(output_dir) + if output_dir == '': + output_dir = rtk.rtk_firmware_dir() + else: + output_dir = pathlib.Path(output_dir) if not output_dir.is_dir(): print("Output dir does not exist or is not a directory") return diff --git a/tools/rtk_util.py b/tools/rtk_util.py index 7452915..35afd92 100644 --- a/tools/rtk_util.py +++ b/tools/rtk_util.py @@ -61,9 +61,8 @@ async def do_load(usb_transport, force): # Get the driver. driver = await rtk.Driver.for_host(host, force) if driver is None: - if not force: - print("Firmware already loaded or no supported driver for this device.") - return + print("Firmware already loaded or no supported driver for this device.") + return await driver.download_firmware()