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