| // 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::L2cap { | |
| subcommand, | |
| device_config, | |
| transport, | |
| psm, | |
| l2cap_coc_max_credits, | |
| l2cap_coc_mtu, | |
| l2cap_coc_mps, | |
| } => { | |
| cli::l2cap::run( | |
| subcommand, | |
| device_config, | |
| transport, | |
| psm, | |
| l2cap_coc_max_credits, | |
| l2cap_coc_mtu, | |
| l2cap_coc_mps, | |
| ) | |
| .await? | |
| } | |
| 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, | |
| }, | |
| /// L2cap client/server operations | |
| L2cap { | |
| #[command(subcommand)] | |
| subcommand: L2cap, | |
| /// Device configuration file. | |
| /// | |
| /// See, for instance, `examples/device1.json` in the Python project. | |
| #[arg(long)] | |
| device_config: path::PathBuf, | |
| /// Bumble transport spec. | |
| /// | |
| /// <https://google.github.io/bumble/transports/index.html> | |
| #[arg(long)] | |
| transport: String, | |
| /// PSM for L2CAP Connection-oriented Channel. | |
| /// | |
| /// Must be in the range [0, 65535]. | |
| #[arg(long)] | |
| psm: u16, | |
| /// Maximum L2CAP CoC Credits. When not specified, lets Bumble set the default. | |
| /// | |
| /// Must be in the range [1, 65535]. | |
| #[arg(long, value_parser = clap::value_parser!(u16).range(1..))] | |
| l2cap_coc_max_credits: Option<u16>, | |
| /// L2CAP CoC MTU. When not specified, lets Bumble set the default. | |
| /// | |
| /// Must be in the range [23, 65535]. | |
| #[arg(long, value_parser = clap::value_parser!(u16).range(23..))] | |
| l2cap_coc_mtu: Option<u16>, | |
| /// L2CAP CoC MPS. When not specified, lets Bumble set the default. | |
| /// | |
| /// Must be in the range [23, 65535]. | |
| #[arg(long, value_parser = clap::value_parser!(u16).range(23..))] | |
| l2cap_coc_mps: Option<u16>, | |
| }, | |
| /// 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. | |
| /// | |
| /// <https://google.github.io/bumble/transports/index.html> | |
| #[arg(long)] | |
| transport: String, | |
| }, | |
| /// Show driver info for a USB device | |
| Info { | |
| /// Bumble transport spec. Must be for a USB device. | |
| /// | |
| /// <https://google.github.io/bumble/transports/index.html> | |
| #[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. | |
| /// | |
| /// <https://google.github.io/bumble/transports/index.html> | |
| #[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<path::PathBuf>, | |
| /// 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<String>, | |
| /// 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 L2cap { | |
| /// Starts an L2CAP server | |
| Server { | |
| /// TCP host that the l2cap server will connect to. | |
| /// Data is bridged like so: | |
| /// TCP server <-> (TCP client / **L2CAP server**) <-> (L2CAP client / TCP server) <-> TCP client | |
| #[arg(long, default_value = "localhost")] | |
| tcp_host: String, | |
| /// TCP port that the server will connect to. | |
| /// | |
| /// Must be in the range [1, 65535]. | |
| #[arg(long, default_value_t = 9544)] | |
| tcp_port: u16, | |
| }, | |
| /// Starts an L2CAP client | |
| Client { | |
| /// L2cap server address that this l2cap client will connect to. | |
| bluetooth_address: String, | |
| /// TCP host that the l2cap client will bind to and listen for incoming TCP connections. | |
| /// Data is bridged like so: | |
| /// TCP client <-> (TCP server / **L2CAP client**) <-> (L2CAP server / TCP client) <-> TCP server | |
| #[arg(long, default_value = "localhost")] | |
| tcp_host: String, | |
| /// TCP port that the client will connect to. | |
| /// | |
| /// Must be in the range [1, 65535]. | |
| #[arg(long, default_value_t = 9543)] | |
| tcp_port: u16, | |
| }, | |
| } | |
| #[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, | |
| } |