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