From 4f1327e3df24b8308b69342d72fade10680e51c8 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:43:25 +0530 Subject: [PATCH 1/6] feat: autodetect an ADB device when no vendor or product ID is specified ### Changes - Added methods `autodetect` to `autodetect_with_custom_private_key` to `ADBUSBDevice` - Added private functions to check if a usb device has the signature of an ADB device - Made the `vendor_id` and `product_id` USB arguments optional and default to aforementioned methods --- adb_cli/src/commands/usb.rs | 4 +- adb_cli/src/main.rs | 14 ++++-- adb_client/Cargo.toml | 1 + adb_client/src/usb/adb_usb_device.rs | 70 ++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/adb_cli/src/commands/usb.rs b/adb_cli/src/commands/usb.rs index 9aff77d..99d70d6 100644 --- a/adb_cli/src/commands/usb.rs +++ b/adb_cli/src/commands/usb.rs @@ -13,10 +13,10 @@ fn parse_hex_id(id: &str) -> Result { pub struct UsbCommand { /// Hexadecimal vendor id of this USB device #[clap(short = 'v', long = "vendor-id", value_parser=parse_hex_id, value_name="VID")] - pub vendor_id: u16, + pub vendor_id: Option, /// Hexadecimal product id of this USB device #[clap(short = 'p', long = "product-id", value_parser=parse_hex_id, value_name="PID")] - pub product_id: u16, + pub product_id: Option, /// Path to a custom private key to use for authentication #[clap(short = 'k', long = "private-key")] pub path_to_private_key: Option, diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 0f21b92..221eea5 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -158,11 +158,15 @@ fn main() -> Result<()> { } } Command::Usb(usb) => { - let mut device = match usb.path_to_private_key { - Some(pk) => { - ADBUSBDevice::new_with_custom_private_key(usb.vendor_id, usb.product_id, pk)? - } - None => ADBUSBDevice::new(usb.vendor_id, usb.product_id)?, + let mut device = match (usb.vendor_id, usb.product_id) { + (Some(vid), Some(pid)) => match usb.path_to_private_key { + Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?, + None => ADBUSBDevice::new(vid, pid)?, + }, + _ => match usb.path_to_private_key { + Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?, + None => ADBUSBDevice::autodetect()?, + }, }; match usb.commands { diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index a7155cc..8646d50 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -20,6 +20,7 @@ log = { version = "0.4.22" } num-bigint = { version = "0.6", package = "num-bigint-dig" } rand = { version = "0.7.0" } regex = { version = "1.11.0", features = ["perf", "std", "unicode"] } +retry = "2.0.0" rsa = { version = "0.3.0" } rusb = { version = "0.9.4", features = ["vendored"] } serde = { version = "1.0.210", features = ["derive"] } diff --git a/adb_client/src/usb/adb_usb_device.rs b/adb_client/src/usb/adb_usb_device.rs index 49b5aeb..47433f6 100644 --- a/adb_client/src/usb/adb_usb_device.rs +++ b/adb_client/src/usb/adb_usb_device.rs @@ -1,5 +1,9 @@ use byteorder::ReadBytesExt; use rand::Rng; +use retry::{delay::Fixed, retry}; +use rusb::Device; +use rusb::DeviceDescriptor; +use rusb::UsbContext; use std::fs::read_to_string; use std::io::Cursor; use std::io::Read; @@ -37,6 +41,38 @@ fn read_adb_private_key>(private_key_path: P) -> Result Option<(u16, u16)> { + for device in rusb::devices().unwrap().iter() { + let Ok(des) = device.device_descriptor() else { + continue; + }; + if is_adb_device(&device, &des) { + return Some((des.vendor_id(), des.product_id())); + } + } + None +} + +fn is_adb_device(device: &Device, des: &DeviceDescriptor) -> bool { + for n in 0..des.num_configurations() { + let Ok(config_des) = device.config_descriptor(n) else { + continue; + }; + for interface in config_des.interfaces() { + for interface_des in interface.descriptors() { + let proto = interface_des.protocol_code(); + let class = interface_des.class_code(); + let subcl = interface_des.sub_class_code(); + if proto == 1 && ((class == 0xff && subcl == 0x42) || (class == 0xdc && subcl == 2)) + { + return true; + } + } + } + } + false +} impl ADBUSBDevice { /// Instantiate a new [ADBUSBDevice] @@ -62,6 +98,40 @@ impl ADBUSBDevice { Ok(s) } + /// autodetect connected ADB devices and establish a connection with the + /// first device found + pub fn autodetect() -> Result { + retry(Fixed::from_millis(3000).take(5), || { + let Some((vid, pid)) = search_adb_devices() else { + return Err(RustADBError::ADBRequestFailed(format!( + "no USB devices found that match the signature of an ADB device" + ))); + }; + log::trace!("Trying to connect to ({vid}, {pid})"); + ADBUSBDevice::new(vid, pid) + }) + .map_err(|e| { + RustADBError::ADBRequestFailed(format!("the device took too long to respond: {e}")) + }) + } + + /// 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 { + retry(Fixed::from_millis(3000).take(5), || { + let Some((vid, pid)) = search_adb_devices() else { + return Err(RustADBError::ADBRequestFailed(format!( + "no USB devices found that match the signature of an ADB device" + ))); + }; + log::trace!("Trying to connect to ({vid}, {pid})"); + ADBUSBDevice::new_with_custom_private_key(vid, pid, private_key_path.clone()) + }) + .map_err(|e| { + RustADBError::ADBRequestFailed(format!("the device took too long to respond: {e}")) + }) + } + /// Instantiate a new [ADBUSBDevice] using a custom private key path pub fn new_with_custom_private_key( vendor_id: u16, From e93eeaa0fb8089d259508a3edbf25d4752d8c3a8 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:56:49 +0530 Subject: [PATCH 2/6] fix: remove retry and add docs for constants --- adb_client/Cargo.toml | 1 - adb_client/src/error.rs | 4 ++ adb_client/src/usb/adb_usb_device.rs | 69 +++++++++++++++++----------- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index 8646d50..a7155cc 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -20,7 +20,6 @@ log = { version = "0.4.22" } num-bigint = { version = "0.6", package = "num-bigint-dig" } rand = { version = "0.7.0" } regex = { version = "1.11.0", features = ["perf", "std", "unicode"] } -retry = "2.0.0" rsa = { version = "0.3.0" } rusb = { version = "0.9.4", features = ["vendored"] } serde = { version = "1.0.210", features = ["derive"] } diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index 1fa4cc8..eaadb19 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -87,4 +87,8 @@ pub enum RustADBError { /// Cannot convert given data from slice #[error(transparent)] TryFromSliceError(#[from] std::array::TryFromSliceError), + + /// Join errors when joining spwaned threads via their handles + #[error("Failed to join handle to thread, context: {0}")] + ThreadJoinError(String), } diff --git a/adb_client/src/usb/adb_usb_device.rs b/adb_client/src/usb/adb_usb_device.rs index 47433f6..f4f3b48 100644 --- a/adb_client/src/usb/adb_usb_device.rs +++ b/adb_client/src/usb/adb_usb_device.rs @@ -1,6 +1,5 @@ use byteorder::ReadBytesExt; use rand::Rng; -use retry::{delay::Fixed, retry}; use rusb::Device; use rusb::DeviceDescriptor; use rusb::UsbContext; @@ -42,19 +41,29 @@ fn read_adb_private_key>(private_key_path: P) -> Result Option<(u16, u16)> { - for device in rusb::devices().unwrap().iter() { +fn search_adb_devices() -> Result> { + for device in rusb::devices()?.iter() { let Ok(des) = device.device_descriptor() else { continue; }; if is_adb_device(&device, &des) { - return Some((des.vendor_id(), des.product_id())); + return Ok(Some((des.vendor_id(), des.product_id()))); } } - None + Ok(None) } fn is_adb_device(device: &Device, des: &DeviceDescriptor) -> bool { + const ADB_CLASS: u8 = 0xff; + + const ADB_SUBCLASS: u8 = 0x42; + const ADB_PROTOCOL: u8 = 0x1; + + // Some devices require choosing the file transfer mode + // for usb debugging to take effect. + const BULK_CLASS: u8 = 0xdc; + const BULK_ADB_SUBCLASS: u8 = 2; + for n in 0..des.num_configurations() { let Ok(config_des) = device.config_descriptor(n) else { continue; @@ -64,7 +73,9 @@ fn is_adb_device(device: &Device, des: &DeviceDescriptor) -> b let proto = interface_des.protocol_code(); let class = interface_des.class_code(); let subcl = interface_des.sub_class_code(); - if proto == 1 && ((class == 0xff && subcl == 0x42) || (class == 0xdc && subcl == 2)) + if proto == ADB_PROTOCOL + && ((class == ADB_CLASS && subcl == ADB_SUBCLASS) + || (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS)) { return true; } @@ -101,35 +112,37 @@ impl ADBUSBDevice { /// autodetect connected ADB devices and establish a connection with the /// first device found pub fn autodetect() -> Result { - retry(Fixed::from_millis(3000).take(5), || { - let Some((vid, pid)) = search_adb_devices() else { - return Err(RustADBError::ADBRequestFailed(format!( - "no USB devices found that match the signature of an ADB device" - ))); - }; - log::trace!("Trying to connect to ({vid}, {pid})"); - ADBUSBDevice::new(vid, pid) - }) - .map_err(|e| { - RustADBError::ADBRequestFailed(format!("the device took too long to respond: {e}")) + std::thread::spawn(move || -> Result { + loop { + let Some((vid, pid)) = search_adb_devices()? else { + log::warn!("no USB devices found that match the signature of an ADB device"); + std::thread::sleep(Duration::from_secs(3)); + continue; + }; + log::trace!("Trying to connect to ({vid}, {pid})"); + break ADBUSBDevice::new(vid, pid); + } }) + .join() + .map_err(|_e| RustADBError::ThreadJoinError("device scanning".into()))? } /// 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 { - retry(Fixed::from_millis(3000).take(5), || { - let Some((vid, pid)) = search_adb_devices() else { - return Err(RustADBError::ADBRequestFailed(format!( - "no USB devices found that match the signature of an ADB device" - ))); - }; - log::trace!("Trying to connect to ({vid}, {pid})"); - ADBUSBDevice::new_with_custom_private_key(vid, pid, private_key_path.clone()) - }) - .map_err(|e| { - RustADBError::ADBRequestFailed(format!("the device took too long to respond: {e}")) + std::thread::spawn(move || -> Result { + loop { + let Some((vid, pid)) = search_adb_devices()? else { + log::warn!("no USB devices found that match the signature of an ADB device"); + std::thread::sleep(Duration::from_secs(3)); + continue; + }; + log::trace!("Trying to connect to ({vid}, {pid})"); + break ADBUSBDevice::new_with_custom_private_key(vid, pid, private_key_path); + } }) + .join() + .map_err(|_e| RustADBError::ThreadJoinError("device scanning".into()))? } /// Instantiate a new [ADBUSBDevice] using a custom private key path From f6c80db2d81378fa707af6359a2488e5a6aeb988 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:04:58 +0530 Subject: [PATCH 3/6] fix: bail if the user has supplied half the id info --- adb_cli/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 221eea5..3a4dac1 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -163,10 +163,15 @@ fn main() -> Result<()> { Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?, None => ADBUSBDevice::new(vid, pid)?, }, - _ => match usb.path_to_private_key { + + (None, None) => match usb.path_to_private_key { Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?, None => ADBUSBDevice::autodetect()?, }, + + _ => { + anyhow::bail!("please either supply values for both the --vendor-id and --product-id flags or none."); + } }; match usb.commands { From 9fe59f7a4a99d60ce1945b6e6c30e613033f0703 Mon Sep 17 00:00:00 2001 From: LIAUD Corentin Date: Thu, 31 Oct 2024 15:47:06 +0100 Subject: [PATCH 4/6] fix: remove device retrieval thread; fix: raise if two android devices are found --- adb_client/src/error.rs | 6 +- adb_client/src/usb/adb_usb_device.rs | 99 ++++++++++++---------------- 2 files changed, 43 insertions(+), 62 deletions(-) diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index eaadb19..b19bc44 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -86,9 +86,5 @@ pub enum RustADBError { RSAError(#[from] rsa::errors::Error), /// Cannot convert given data from slice #[error(transparent)] - TryFromSliceError(#[from] std::array::TryFromSliceError), - - /// Join errors when joining spwaned threads via their handles - #[error("Failed to join handle to thread, context: {0}")] - ThreadJoinError(String), + TryFromSliceError(#[from] std::array::TryFromSliceError) } diff --git a/adb_client/src/usb/adb_usb_device.rs b/adb_client/src/usb/adb_usb_device.rs index f4f3b48..e4fda72 100644 --- a/adb_client/src/usb/adb_usb_device.rs +++ b/adb_client/src/usb/adb_usb_device.rs @@ -42,15 +42,29 @@ fn read_adb_private_key>(private_key_path: P) -> Result 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) { - return Ok(Some((des.vendor_id(), des.product_id()))); + log::debug!( + "Autodetect device {:04x}:{:04x}", + des.vendor_id(), + des.product_id() + ); + found_devices.push((des.vendor_id(), des.product_id())); } } - Ok(None) + + match (found_devices.get(0), 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 {:04x}:{:04x} and {:04x}:{:04x}", + vid1, pid1, vid2, pid2 + ))), + } } fn is_adb_device(device: &Device, des: &DeviceDescriptor) -> bool { @@ -85,64 +99,18 @@ fn is_adb_device(device: &Device, des: &DeviceDescriptor) -> b false } +fn get_default_adb_key_path() -> Result { + homedir::my_home() + .ok() + .flatten() + .map(|home| home.join(".android").join("adbkey")) + .ok_or(RustADBError::NoHomeDirectory) +} + impl ADBUSBDevice { /// Instantiate a new [ADBUSBDevice] pub fn new(vendor_id: u16, product_id: u16) -> Result { - let private_key_path = homedir::my_home() - .ok() - .flatten() - .map(|home| home.join(".android").join("adbkey")) - .ok_or(RustADBError::NoHomeDirectory)?; - - let private_key = match read_adb_private_key(private_key_path)? { - Some(pk) => pk, - None => ADBRsaKey::random_with_size(2048)?, - }; - - let mut s = Self { - private_key, - transport: USBTransport::new(vendor_id, product_id), - }; - - s.connect()?; - - Ok(s) - } - - /// autodetect connected ADB devices and establish a connection with the - /// first device found - pub fn autodetect() -> Result { - std::thread::spawn(move || -> Result { - loop { - let Some((vid, pid)) = search_adb_devices()? else { - log::warn!("no USB devices found that match the signature of an ADB device"); - std::thread::sleep(Duration::from_secs(3)); - continue; - }; - log::trace!("Trying to connect to ({vid}, {pid})"); - break ADBUSBDevice::new(vid, pid); - } - }) - .join() - .map_err(|_e| RustADBError::ThreadJoinError("device scanning".into()))? - } - - /// 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 { - std::thread::spawn(move || -> Result { - loop { - let Some((vid, pid)) = search_adb_devices()? else { - log::warn!("no USB devices found that match the signature of an ADB device"); - std::thread::sleep(Duration::from_secs(3)); - continue; - }; - log::trace!("Trying to connect to ({vid}, {pid})"); - break ADBUSBDevice::new_with_custom_private_key(vid, pid, private_key_path); - } - }) - .join() - .map_err(|_e| RustADBError::ThreadJoinError("device scanning".into()))? + Self::new_with_custom_private_key(vendor_id, product_id, get_default_adb_key_path()?) } /// Instantiate a new [ADBUSBDevice] using a custom private key path @@ -166,6 +134,23 @@ impl ADBUSBDevice { Ok(s) } + /// autodetect connected ADB devices and establish a connection with the first device found + pub fn autodetect() -> Result { + Self::autodetect_with_custom_private_key(get_default_adb_key_path()?) + } + + /// 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) + } + _ => Err(RustADBError::DeviceNotFound( + "cannot find USB devices matching the signature of an ADB device".into(), + )), + } + } + /// Send initial connect pub fn connect(&mut self) -> Result<()> { self.transport.connect()?; From ad474aa6f2b1cb39ec6270bce40efd86e88820f4 Mon Sep 17 00:00:00 2001 From: LIAUD Corentin Date: Thu, 31 Oct 2024 15:54:24 +0100 Subject: [PATCH 5/6] fix: improve github actions workflows --- .github/workflows/rust-build.yml | 2 +- .github/workflows/rust-quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-build.yml b/.github/workflows/rust-build.yml index 5be9de5..062b435 100644 --- a/.github/workflows/rust-build.yml +++ b/.github/workflows/rust-build.yml @@ -1,6 +1,6 @@ name: Rust - Build -on: [push] +on: [push, pull_request] env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index fa1ff09..bf8d89b 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -1,6 +1,6 @@ name: Rust - Quality -on: [push] +on: [push, pull_request] env: CARGO_TERM_COLOR: always From d3ba429d6aa7cb4f96be404f29d33c4c146b2988 Mon Sep 17 00:00:00 2001 From: LIAUD Corentin Date: Thu, 31 Oct 2024 15:56:21 +0100 Subject: [PATCH 6/6] fix: ci --- adb_client/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index b19bc44..1fa4cc8 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -86,5 +86,5 @@ pub enum RustADBError { RSAError(#[from] rsa::errors::Error), /// Cannot convert given data from slice #[error(transparent)] - TryFromSliceError(#[from] std::array::TryFromSliceError) + TryFromSliceError(#[from] std::array::TryFromSliceError), }