diff --git a/adb_cli/Cargo.toml b/adb_cli/Cargo.toml index 2885bc9..c86c6bd 100644 --- a/adb_cli/Cargo.toml +++ b/adb_cli/Cargo.toml @@ -16,6 +16,7 @@ anyhow = { version = "1.0.94" } clap = { version = "4.5.23", features = ["derive"] } env_logger = { version = "0.11.5" } log = { version = "0.4.26" } +tabwriter = { version = "1.4.1" } [target.'cfg(unix)'.dependencies] termios = { version = "0.3.3" } diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 3ac5af6..b5e45d4 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -8,20 +8,22 @@ mod models; mod utils; use adb_client::{ - ADBDeviceExt, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice, MDNSDiscoveryService, + ADBDeviceExt, ADBDeviceInfo, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice, + MDNSDiscoveryService, find_all_connected_adb_devices, }; #[cfg(any(target_os = "linux", target_os = "macos"))] use adb_termios::ADBTermios; -use anyhow::Result; +use anyhow::{Result, bail}; use clap::Parser; use handlers::{handle_emulator_commands, handle_host_commands, handle_local_commands}; use models::{DeviceCommands, LocalCommand, MainCommand, Opts}; use std::collections::HashMap; use std::fs::File; -use std::io::Write; +use std::io::{Write, stdout}; use std::path::Path; +use tabwriter::TabWriter; use utils::setup_logger; fn main() -> Result<()> { @@ -61,6 +63,34 @@ fn main() -> Result<()> { } } MainCommand::Usb(usb_command) => { + if usb_command.list_devices { + let devices = find_all_connected_adb_devices()?; + + let mut writer = TabWriter::new(stdout()).alignment(tabwriter::Alignment::Center); + writeln!(writer, "Index\tVendor ID\tProduct ID\tDevice Description")?; + writeln!(writer, "-----\t---------\t----------\t----------------")?; + + for ( + index, + ADBDeviceInfo { + vendor_id, + product_id, + device_description, + }, + ) in devices.iter().enumerate() + { + writeln!( + writer, + "#{}\t{:04x}\t{:04x}\t{}", + index, vendor_id, product_id, device_description + )?; + } + + writer.flush()?; + + return Ok(()); + } + let device = match (usb_command.vendor_id, usb_command.product_id) { (Some(vid), Some(pid)) => match usb_command.path_to_private_key { Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?, @@ -76,7 +106,11 @@ fn main() -> Result<()> { ); } }; - (device.boxed(), usb_command.commands) + + match usb_command.commands { + Some(commands) => (device.boxed(), commands), + None => bail!("no command provided"), + } } MainCommand::Tcp(tcp_command) => { let device = ADBTcpDevice::new(tcp_command.address)?; diff --git a/adb_cli/src/models/usb.rs b/adb_cli/src/models/usb.rs index 76411b9..625b72e 100644 --- a/adb_cli/src/models/usb.rs +++ b/adb_cli/src/models/usb.rs @@ -20,6 +20,9 @@ pub struct UsbCommand { /// Path to a custom private key to use for authentication #[clap(short = 'k', long = "private-key")] pub path_to_private_key: Option, + /// List all connected Android devices + #[clap(short = 'l', long = "list")] + pub list_devices: bool, #[clap(subcommand)] - pub commands: DeviceCommands, + pub commands: Option, } diff --git a/adb_client/src/device/adb_usb_device.rs b/adb_client/src/device/adb_usb_device.rs index 1de4376..8d72085 100644 --- a/adb_client/src/device/adb_usb_device.rs +++ b/adb_client/src/device/adb_usb_device.rs @@ -35,28 +35,80 @@ pub fn read_adb_private_key>(private_key_path: P) -> Result Result> { +/// Represents an Android device connected via USB +#[derive(Clone, Debug)] +pub struct ADBDeviceInfo { + /// Vendor ID of the device + pub vendor_id: u16, + /// Product ID of the device + pub product_id: u16, + /// Textual description of the device + pub device_description: String, +} + +/// Find and return a list of all connected Android devices with known interface class and subclass values +pub fn find_all_connected_adb_devices() -> Result> { let mut found_devices = vec![]; + for device in rusb::devices()?.iter() { let Ok(des) = device.device_descriptor() else { continue; }; + if is_adb_device(&device, &des) { - log::debug!( - "Autodetect device {:04x}:{:04x}", - des.vendor_id(), - des.product_id() - ); - found_devices.push((des.vendor_id(), des.product_id())); + let device_handle = match device.open() { + Ok(h) => h, + Err(_) => { + found_devices.push(ADBDeviceInfo { + vendor_id: des.vendor_id(), + product_id: des.product_id(), + device_description: "Unknown device".to_string(), + }); + continue; + } + }; + + let manufacturer = device_handle + .read_manufacturer_string_ascii(&des) + .unwrap_or_else(|_| "Unknown".to_string()); + + let product = device_handle + .read_product_string_ascii(&des) + .unwrap_or_else(|_| "Unknown".to_string()); + + let device_description = format!("{} {}", manufacturer, product); + + found_devices.push(ADBDeviceInfo { + vendor_id: des.vendor_id(), + product_id: des.product_id(), + device_description, + }); } } + Ok(found_devices) +} + +/// Find and return an USB-connected Android device with known interface class and subclass values. +/// +/// Returns the first device found or None if no device is found. +/// If multiple devices are found, an error is returned. +pub fn get_single_connected_adb_device() -> Result> { + let found_devices = find_all_connected_adb_devices()?; match (found_devices.first(), found_devices.get(1)) { (None, _) => Ok(None), - (Some(identifiers), None) => Ok(Some(*identifiers)), - (Some((vid1, pid1)), Some((vid2, pid2))) => Err(RustADBError::DeviceNotFound(format!( - "Found two Android devices {vid1:04x}:{pid1:04x} and {vid2:04x}:{pid2:04x}", + (Some(device_info), None) => { + log::debug!( + "Autodetect device {:04x}:{:04x} - {}", + device_info.vendor_id, + device_info.product_id, + device_info.device_description + ); + Ok(Some(device_info.clone())) + } + (Some(device_1), Some(device_2)) => Err(RustADBError::DeviceNotFound(format!( + "Found two Android devices {:04x}:{:04x} and {:04x}:{:04x}", + device_1.vendor_id, device_1.product_id, device_2.vendor_id, device_2.product_id ))), } } @@ -167,10 +219,12 @@ impl ADBUSBDevice { /// autodetect connected ADB devices and establish a connection with the first device found using a custom private key path pub fn autodetect_with_custom_private_key(private_key_path: PathBuf) -> Result { - match search_adb_devices()? { - Some((vendor_id, product_id)) => { - ADBUSBDevice::new_with_custom_private_key(vendor_id, product_id, private_key_path) - } + match get_single_connected_adb_device()? { + Some(device_info) => ADBUSBDevice::new_with_custom_private_key( + device_info.vendor_id, + device_info.product_id, + private_key_path, + ), _ => Err(RustADBError::DeviceNotFound( "cannot find USB devices matching the signature of an ADB device".into(), )), diff --git a/adb_client/src/device/mod.rs b/adb_client/src/device/mod.rs index a34b6a1..16964e8 100644 --- a/adb_client/src/device/mod.rs +++ b/adb_client/src/device/mod.rs @@ -12,7 +12,8 @@ use adb_message_device::ADBMessageDevice; pub use adb_tcp_device::ADBTcpDevice; pub use adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader}; pub use adb_usb_device::{ - ADBUSBDevice, get_default_adb_key_path, is_adb_device, search_adb_devices, + ADBDeviceInfo, ADBUSBDevice, find_all_connected_adb_devices, get_default_adb_key_path, + get_single_connected_adb_device, is_adb_device, }; pub use message_writer::MessageWriter; pub use models::{ADBRsaKey, MessageCommand, MessageSubcommand}; diff --git a/adb_client/src/lib.rs b/adb_client/src/lib.rs index 8c88387..a1889b8 100644 --- a/adb_client/src/lib.rs +++ b/adb_client/src/lib.rs @@ -17,7 +17,10 @@ mod transports; mod utils; pub use adb_device_ext::ADBDeviceExt; -pub use device::{ADBTcpDevice, ADBUSBDevice, is_adb_device, search_adb_devices}; +pub use device::{ + ADBDeviceInfo, ADBTcpDevice, ADBUSBDevice, find_all_connected_adb_devices, + get_single_connected_adb_device, is_adb_device, +}; pub use emulator_device::ADBEmulatorDevice; pub use error::{Result, RustADBError}; pub use mdns::*;