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 //! Rust version of the Python `usb_probe.py`.
16 //!
17 //! This tool lists all the USB devices, with details about each device.
18 //! For each device, the different possible Bumble transport strings that can
19 //! refer to it are listed. If the device is known to be a Bluetooth HCI device,
20 //! its identifier is printed in reverse colors, and the transport names in cyan color.
21 //! For other devices, regardless of their type, the transport names are printed
22 //! in red. Whether that device is actually a Bluetooth device or not depends on
23 //! whether it is a Bluetooth device that uses a non-standard Class, or some other
24 //! type of device (there's no way to tell).
25
26 use itertools::Itertools as _;
27 use owo_colors::{OwoColorize, Style};
28 use rusb::{Device, DeviceDescriptor, Direction, TransferType, UsbContext};
29 use std::{
30 collections::{HashMap, HashSet},
31 time::Duration,
32 };
33 const USB_DEVICE_CLASS_DEVICE: u8 = 0x00;
34 const USB_DEVICE_CLASS_WIRELESS_CONTROLLER: u8 = 0xE0;
35 const USB_DEVICE_SUBCLASS_RF_CONTROLLER: u8 = 0x01;
36 const USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER: u8 = 0x01;
37
probe(verbose: bool) -> anyhow::Result<()>38 pub(crate) fn probe(verbose: bool) -> anyhow::Result<()> {
39 let mut bt_dev_count = 0;
40 let mut device_serials_by_id: HashMap<(u16, u16), HashSet<String>> = HashMap::new();
41 for device in rusb::devices()?.iter() {
42 let device_desc = device.device_descriptor().unwrap();
43
44 let class_info = ClassInfo::from(&device_desc);
45 let handle = device.open()?;
46 let timeout = Duration::from_secs(1);
47 // some devices don't have languages
48 let lang = handle
49 .read_languages(timeout)
50 .ok()
51 .and_then(|langs| langs.into_iter().next());
52 let serial = lang.and_then(|l| {
53 handle
54 .read_serial_number_string(l, &device_desc, timeout)
55 .ok()
56 });
57 let mfg = lang.and_then(|l| {
58 handle
59 .read_manufacturer_string(l, &device_desc, timeout)
60 .ok()
61 });
62 let product = lang.and_then(|l| handle.read_product_string(l, &device_desc, timeout).ok());
63
64 let is_hci = is_bluetooth_hci(&device, &device_desc)?;
65 let addr_style = if is_hci {
66 bt_dev_count += 1;
67 Style::new().black().on_yellow()
68 } else {
69 Style::new().yellow().on_black()
70 };
71
72 let mut transport_names = Vec::new();
73 let basic_transport_name = format!(
74 "usb:{:04X}:{:04X}",
75 device_desc.vendor_id(),
76 device_desc.product_id()
77 );
78
79 if is_hci {
80 transport_names.push(format!("usb:{}", bt_dev_count - 1));
81 }
82
83 let device_id = (device_desc.vendor_id(), device_desc.product_id());
84 if !device_serials_by_id.contains_key(&device_id) {
85 transport_names.push(basic_transport_name.clone());
86 } else {
87 transport_names.push(format!(
88 "{}#{}",
89 basic_transport_name,
90 device_serials_by_id
91 .get(&device_id)
92 .map(|serials| serials.len())
93 .unwrap_or(0)
94 ))
95 }
96
97 if let Some(s) = &serial {
98 if !device_serials_by_id
99 .get(&device_id)
100 .map(|serials| serials.contains(s))
101 .unwrap_or(false)
102 {
103 transport_names.push(format!("{}/{}", basic_transport_name, s))
104 }
105 }
106
107 println!(
108 "{}",
109 format!(
110 "ID {:04X}:{:04X}",
111 device_desc.vendor_id(),
112 device_desc.product_id()
113 )
114 .style(addr_style)
115 );
116 if !transport_names.is_empty() {
117 let style = if is_hci {
118 Style::new().cyan()
119 } else {
120 Style::new().red()
121 };
122 println!(
123 "{:26}{}",
124 " Bumble Transport Names:".blue(),
125 transport_names.iter().map(|n| n.style(style)).join(" or ")
126 )
127 }
128 println!(
129 "{:26}{:03}/{:03}",
130 " Bus/Device:".green(),
131 device.bus_number(),
132 device.address()
133 );
134 println!(
135 "{:26}{}",
136 " Class:".green(),
137 class_info.formatted_class_name()
138 );
139 println!(
140 "{:26}{}",
141 " Subclass/Protocol:".green(),
142 class_info.formatted_subclass_protocol()
143 );
144 if let Some(s) = serial {
145 println!("{:26}{}", " Serial:".green(), s);
146 device_serials_by_id.entry(device_id).or_default().insert(s);
147 }
148 if let Some(m) = mfg {
149 println!("{:26}{}", " Manufacturer:".green(), m);
150 }
151 if let Some(p) = product {
152 println!("{:26}{}", " Product:".green(), p);
153 }
154
155 if verbose {
156 print_device_details(&device, &device_desc)?;
157 }
158
159 println!();
160 }
161
162 Ok(())
163 }
164
is_bluetooth_hci<T: UsbContext>( device: &Device<T>, device_desc: &DeviceDescriptor, ) -> rusb::Result<bool>165 fn is_bluetooth_hci<T: UsbContext>(
166 device: &Device<T>,
167 device_desc: &DeviceDescriptor,
168 ) -> rusb::Result<bool> {
169 if device_desc.class_code() == USB_DEVICE_CLASS_WIRELESS_CONTROLLER
170 && device_desc.sub_class_code() == USB_DEVICE_SUBCLASS_RF_CONTROLLER
171 && device_desc.protocol_code() == USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
172 {
173 Ok(true)
174 } else if device_desc.class_code() == USB_DEVICE_CLASS_DEVICE {
175 for i in 0..device_desc.num_configurations() {
176 for interface in device.config_descriptor(i)?.interfaces() {
177 for d in interface.descriptors() {
178 if d.class_code() == USB_DEVICE_CLASS_WIRELESS_CONTROLLER
179 && d.sub_class_code() == USB_DEVICE_SUBCLASS_RF_CONTROLLER
180 && d.protocol_code() == USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
181 {
182 return Ok(true);
183 }
184 }
185 }
186 }
187
188 Ok(false)
189 } else {
190 Ok(false)
191 }
192 }
193
print_device_details<T: UsbContext>( device: &Device<T>, device_desc: &DeviceDescriptor, ) -> anyhow::Result<()>194 fn print_device_details<T: UsbContext>(
195 device: &Device<T>,
196 device_desc: &DeviceDescriptor,
197 ) -> anyhow::Result<()> {
198 for i in 0..device_desc.num_configurations() {
199 println!(" Configuration {}", i + 1);
200 for interface in device.config_descriptor(i)?.interfaces() {
201 let interface_descriptors: Vec<_> = interface.descriptors().collect();
202 for d in &interface_descriptors {
203 let class_info =
204 ClassInfo::new(d.class_code(), d.sub_class_code(), d.protocol_code());
205
206 println!(
207 " Interface: {}{} ({}, {})",
208 interface.number(),
209 if interface_descriptors.len() > 1 {
210 format!("/{}", d.setting_number())
211 } else {
212 String::new()
213 },
214 class_info.formatted_class_name(),
215 class_info.formatted_subclass_protocol()
216 );
217
218 for e in d.endpoint_descriptors() {
219 println!(
220 " Endpoint {:#04X}: {} {}",
221 e.address(),
222 match e.transfer_type() {
223 TransferType::Control => "CONTROL",
224 TransferType::Isochronous => "ISOCHRONOUS",
225 TransferType::Bulk => "BULK",
226 TransferType::Interrupt => "INTERRUPT",
227 },
228 match e.direction() {
229 Direction::In => "IN",
230 Direction::Out => "OUT",
231 }
232 )
233 }
234 }
235 }
236 }
237
238 Ok(())
239 }
240
241 struct ClassInfo {
242 class: u8,
243 sub_class: u8,
244 protocol: u8,
245 }
246
247 impl ClassInfo {
new(class: u8, sub_class: u8, protocol: u8) -> Self248 fn new(class: u8, sub_class: u8, protocol: u8) -> Self {
249 Self {
250 class,
251 sub_class,
252 protocol,
253 }
254 }
255
class_name(&self) -> Option<&str>256 fn class_name(&self) -> Option<&str> {
257 match self.class {
258 0x00 => Some("Device"),
259 0x01 => Some("Audio"),
260 0x02 => Some("Communications and CDC Control"),
261 0x03 => Some("Human Interface Device"),
262 0x05 => Some("Physical"),
263 0x06 => Some("Still Imaging"),
264 0x07 => Some("Printer"),
265 0x08 => Some("Mass Storage"),
266 0x09 => Some("Hub"),
267 0x0A => Some("CDC Data"),
268 0x0B => Some("Smart Card"),
269 0x0D => Some("Content Security"),
270 0x0E => Some("Video"),
271 0x0F => Some("Personal Healthcare"),
272 0x10 => Some("Audio/Video"),
273 0x11 => Some("Billboard"),
274 0x12 => Some("USB Type-C Bridge"),
275 0x3C => Some("I3C"),
276 0xDC => Some("Diagnostic"),
277 USB_DEVICE_CLASS_WIRELESS_CONTROLLER => Some("Wireless Controller"),
278 0xEF => Some("Miscellaneous"),
279 0xFE => Some("Application Specific"),
280 0xFF => Some("Vendor Specific"),
281 _ => None,
282 }
283 }
284
protocol_name(&self) -> Option<&str>285 fn protocol_name(&self) -> Option<&str> {
286 match self.class {
287 USB_DEVICE_CLASS_WIRELESS_CONTROLLER => match self.sub_class {
288 0x01 => match self.protocol {
289 0x01 => Some("Bluetooth"),
290 0x02 => Some("UWB"),
291 0x03 => Some("Remote NDIS"),
292 0x04 => Some("Bluetooth AMP"),
293 _ => None,
294 },
295 _ => None,
296 },
297 _ => None,
298 }
299 }
300
formatted_class_name(&self) -> String301 fn formatted_class_name(&self) -> String {
302 self.class_name()
303 .map(|s| s.to_string())
304 .unwrap_or_else(|| format!("{:#04X}", self.class))
305 }
306
formatted_subclass_protocol(&self) -> String307 fn formatted_subclass_protocol(&self) -> String {
308 format!(
309 "{}/{}{}",
310 self.sub_class,
311 self.protocol,
312 self.protocol_name()
313 .map(|s| format!(" [{}]", s))
314 .unwrap_or_default()
315 )
316 }
317 }
318
319 impl From<&DeviceDescriptor> for ClassInfo {
from(value: &DeviceDescriptor) -> Self320 fn from(value: &DeviceDescriptor) -> Self {
321 Self::new(
322 value.class_code(),
323 value.sub_class_code(),
324 value.protocol_code(),
325 )
326 }
327 }
328