// 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. /// /// #[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, /// 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, /// 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, }, /// 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 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, }