1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! CLI tools for Bumble
16 
17 #![deny(missing_docs, unsafe_code)]
18 
19 use bumble::wrapper::logging::{bumble_env_logging_level, py_logging_basic_config};
20 use clap::Parser as _;
21 use pyo3::PyResult;
22 use std::{fmt, path};
23 
24 mod cli;
25 
26 #[pyo3_asyncio::tokio::main]
main() -> PyResult<()>27 async fn main() -> PyResult<()> {
28     env_logger::builder()
29         .filter_level(log::LevelFilter::Info)
30         .init();
31 
32     py_logging_basic_config(bumble_env_logging_level("INFO"))?;
33 
34     let cli: Cli = Cli::parse();
35 
36     match cli.subcommand {
37         Subcommand::Firmware { subcommand: fw } => match fw {
38             Firmware::Realtek { subcommand: rtk } => match rtk {
39                 Realtek::Download(dl) => {
40                     cli::firmware::rtk::download(dl).await?;
41                 }
42                 Realtek::Drop { transport } => cli::firmware::rtk::drop(&transport).await?,
43                 Realtek::Info { transport, force } => {
44                     cli::firmware::rtk::info(&transport, force).await?;
45                 }
46                 Realtek::Load { transport, force } => {
47                     cli::firmware::rtk::load(&transport, force).await?
48                 }
49                 Realtek::Parse { firmware_path } => cli::firmware::rtk::parse(&firmware_path)?,
50             },
51         },
52         Subcommand::L2cap {
53             subcommand,
54             device_config,
55             transport,
56             psm,
57             l2cap_coc_max_credits,
58             l2cap_coc_mtu,
59             l2cap_coc_mps,
60         } => {
61             cli::l2cap::run(
62                 subcommand,
63                 device_config,
64                 transport,
65                 psm,
66                 l2cap_coc_max_credits,
67                 l2cap_coc_mtu,
68                 l2cap_coc_mps,
69             )
70             .await?
71         }
72         Subcommand::Usb { subcommand } => match subcommand {
73             Usb::Probe(probe) => cli::usb::probe(probe.verbose)?,
74         },
75     }
76 
77     Ok(())
78 }
79 
80 #[derive(clap::Parser)]
81 struct Cli {
82     #[clap(subcommand)]
83     subcommand: Subcommand,
84 }
85 
86 #[derive(clap::Subcommand, Debug, Clone)]
87 enum Subcommand {
88     /// Manage device firmware
89     Firmware {
90         #[clap(subcommand)]
91         subcommand: Firmware,
92     },
93     /// L2cap client/server operations
94     L2cap {
95         #[command(subcommand)]
96         subcommand: L2cap,
97 
98         /// Device configuration file.
99         ///
100         /// See, for instance, `examples/device1.json` in the Python project.
101         #[arg(long)]
102         device_config: path::PathBuf,
103         /// Bumble transport spec.
104         ///
105         /// <https://google.github.io/bumble/transports/index.html>
106         #[arg(long)]
107         transport: String,
108 
109         /// PSM for L2CAP Connection-oriented Channel.
110         ///
111         /// Must be in the range [0, 65535].
112         #[arg(long)]
113         psm: u16,
114 
115         /// Maximum L2CAP CoC Credits. When not specified, lets Bumble set the default.
116         ///
117         /// Must be in the range [1, 65535].
118         #[arg(long, value_parser = clap::value_parser!(u16).range(1..))]
119         l2cap_coc_max_credits: Option<u16>,
120 
121         /// L2CAP CoC MTU. When not specified, lets Bumble set the default.
122         ///
123         /// Must be in the range [23, 65535].
124         #[arg(long, value_parser = clap::value_parser!(u16).range(23..))]
125         l2cap_coc_mtu: Option<u16>,
126 
127         /// L2CAP CoC MPS. When not specified, lets Bumble set the default.
128         ///
129         /// Must be in the range [23, 65535].
130         #[arg(long, value_parser = clap::value_parser!(u16).range(23..))]
131         l2cap_coc_mps: Option<u16>,
132     },
133     /// USB operations
134     Usb {
135         #[clap(subcommand)]
136         subcommand: Usb,
137     },
138 }
139 
140 #[derive(clap::Subcommand, Debug, Clone)]
141 enum Firmware {
142     /// Manage Realtek chipset firmware
143     Realtek {
144         #[clap(subcommand)]
145         subcommand: Realtek,
146     },
147 }
148 
149 #[derive(clap::Subcommand, Debug, Clone)]
150 
151 enum Realtek {
152     /// Download Realtek firmware
153     Download(Download),
154     /// Drop firmware from a USB device
155     Drop {
156         /// Bumble transport spec. Must be for a USB device.
157         ///
158         /// <https://google.github.io/bumble/transports/index.html>
159         #[arg(long)]
160         transport: String,
161     },
162     /// Show driver info for a USB device
163     Info {
164         /// Bumble transport spec. Must be for a USB device.
165         ///
166         /// <https://google.github.io/bumble/transports/index.html>
167         #[arg(long)]
168         transport: String,
169         /// Try to resolve driver info even if USB info is not available, or if the USB
170         /// (vendor,product) tuple is not in the list of known compatible RTK USB dongles.
171         #[arg(long, default_value_t = false)]
172         force: bool,
173     },
174     /// Load firmware onto a USB device
175     Load {
176         /// Bumble transport spec. Must be for a USB device.
177         ///
178         /// <https://google.github.io/bumble/transports/index.html>
179         #[arg(long)]
180         transport: String,
181         /// Load firmware even if the USB info doesn't match.
182         #[arg(long, default_value_t = false)]
183         force: bool,
184     },
185     /// Parse a firmware file
186     Parse {
187         /// Firmware file to parse
188         firmware_path: path::PathBuf,
189     },
190 }
191 
192 #[derive(clap::Args, Debug, Clone)]
193 struct Download {
194     /// Directory to download to. Defaults to an OS-specific path specific to the Bumble tool.
195     #[arg(long)]
196     output_dir: Option<path::PathBuf>,
197     /// Source to download from
198     #[arg(long, default_value_t = Source::LinuxKernel)]
199     source: Source,
200     /// Only download a single image
201     #[arg(long, value_name = "base name")]
202     single: Option<String>,
203     /// Overwrite existing files
204     #[arg(long, default_value_t = false)]
205     overwrite: bool,
206     /// Don't print the parse results for the downloaded file names
207     #[arg(long)]
208     no_parse: bool,
209 }
210 
211 #[derive(Debug, Clone, clap::ValueEnum)]
212 enum Source {
213     LinuxKernel,
214     RealtekOpensource,
215     LinuxFromScratch,
216 }
217 
218 impl fmt::Display for Source {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result219     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220         match self {
221             Source::LinuxKernel => write!(f, "linux-kernel"),
222             Source::RealtekOpensource => write!(f, "realtek-opensource"),
223             Source::LinuxFromScratch => write!(f, "linux-from-scratch"),
224         }
225     }
226 }
227 
228 #[derive(clap::Subcommand, Debug, Clone)]
229 enum L2cap {
230     /// Starts an L2CAP server
231     Server {
232         /// TCP host that the l2cap server will connect to.
233         /// Data is bridged like so:
234         ///     TCP server <-> (TCP client / **L2CAP server**) <-> (L2CAP client / TCP server) <-> TCP client
235         #[arg(long, default_value = "localhost")]
236         tcp_host: String,
237         /// TCP port that the server will connect to.
238         ///
239         /// Must be in the range [1, 65535].
240         #[arg(long, default_value_t = 9544)]
241         tcp_port: u16,
242     },
243     /// Starts an L2CAP client
244     Client {
245         /// L2cap server address that this l2cap client will connect to.
246         bluetooth_address: String,
247         /// TCP host that the l2cap client will bind to and listen for incoming TCP connections.
248         /// Data is bridged like so:
249         ///     TCP client <-> (TCP server / **L2CAP client**) <-> (L2CAP server / TCP client) <-> TCP server
250         #[arg(long, default_value = "localhost")]
251         tcp_host: String,
252         /// TCP port that the client will connect to.
253         ///
254         /// Must be in the range [1, 65535].
255         #[arg(long, default_value_t = 9543)]
256         tcp_port: u16,
257     },
258 }
259 
260 #[derive(clap::Subcommand, Debug, Clone)]
261 enum Usb {
262     /// Probe the USB bus for Bluetooth devices
263     Probe(Probe),
264 }
265 
266 #[derive(clap::Args, Debug, Clone)]
267 struct Probe {
268     /// Show additional info for each USB device
269     #[arg(long, default_value_t = false)]
270     verbose: bool,
271 }
272