From a4baa697b7a542aa821efb9134419224e3d93093 Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Tue, 15 Jul 2025 12:15:46 -0400 Subject: [PATCH 1/2] add logs --- CLAUDE.md | 94 ++++++++++++++++++++++ adb_client/src/device/adb_usb_device.rs | 60 +++++++++++--- adb_client/src/transports/usb_transport.rs | 13 ++- 3 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..dd3b468 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,94 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a pure Rust implementation of the Android Debug Bridge (ADB) client protocol. The project consists of three main components: + +- **adb_client**: Core library implementing ADB protocols for both server and direct device communication +- **adb_cli**: Command-line interface using the adb_client library +- **pyadb_client**: Python bindings for the adb_client library + +## Development Commands + +### Build Commands +```bash +# Build all workspace members +cargo build --release --all-features + +# Build specific package +cargo build -p adb_client --release --all-features +cargo build -p adb_cli --release --all-features +cargo build -p pyadb_client --release --all-features +``` + +### Testing and Quality +```bash +# Run all tests +cargo test --verbose --all-features + +# Run linting +cargo clippy --all-features + +# Format code +cargo fmt --all --check + +# Generate documentation +cargo doc --all-features --no-deps + +# Run benchmarks +cargo bench +``` + +### Local Development +When working on a new release locally, the workspace uses path dependencies via `[patch.crates-io]` to reference the local `adb_client` crate. + +## Architecture Overview + +### Core Device Types +The library provides multiple ways to connect to Android devices: + +1. **ADBServerDevice** (`server_device/`): Connects through ADB server (standard adb behavior) +2. **ADBUSBDevice** (`device/adb_usb_device.rs`): Direct USB connection to device +3. **ADBTcpDevice** (`device/adb_tcp_device.rs`): Direct TCP/IP connection to device +4. **ADBEmulatorDevice** (`emulator_device/`): Specialized emulator communication + +### Transport Layer +The transport layer (`transports/`) abstracts different connection types: +- **TCPServerTransport**: Communication with ADB server +- **USBTransport**: Direct USB device communication +- **TcpTransport**: Direct TCP device communication +- **TCPEmulatorTransport**: Emulator-specific transport + +### Message Protocol +Device communication uses the ADB message protocol (`device/adb_transport_message.rs`): +- **ADBTransportMessage**: Core message structure +- **MessageCommand**: Command types (AUTH, CNXN, OPEN, etc.) +- Authentication handled via RSA keys (`device/models/adb_rsa_key.rs`) + +### Command Implementation +Commands are organized by device type: +- **device/commands/**: Direct device commands (shell, push, pull, install, etc.) +- **server/commands/**: ADB server commands (devices, connect, disconnect, etc.) +- **server_device/commands/**: Server-mediated device commands +- **emulator_device/commands/**: Emulator-specific commands (SMS, rotate, etc.) + +### Key Features +- **Pure Rust**: No shell command execution - implements ADB protocol directly +- **Multiple connection modes**: Server proxy, direct USB, direct TCP/IP +- **Advanced features**: Framebuffer capture, mDNS device discovery +- **Authentication**: RSA key-based device authentication +- **Cross-platform**: Windows, macOS, Linux support + +### Error Handling +Centralized error handling via `error.rs` with `RustADBError` enum covering all failure modes. + +### Constants and Models +- **constants.rs**: Protocol constants and magic numbers +- **models/**: Data structures for requests/responses across all components + +## Testing Notes +- Tests require physical devices or emulators to be connected +- Use `cargo test --verbose --all-features` for comprehensive testing +- Benchmarks available for performance-critical operations like file push \ No newline at end of file diff --git a/adb_client/src/device/adb_usb_device.rs b/adb_client/src/device/adb_usb_device.rs index ce33d37..379410c 100644 --- a/adb_client/src/device/adb_usb_device.rs +++ b/adb_client/src/device/adb_usb_device.rs @@ -19,15 +19,29 @@ use crate::device::adb_transport_message::{AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AU use crate::{Result, RustADBError, USBTransport}; pub fn read_adb_private_key>(private_key_path: P) -> Result> { - Ok(read_to_string(private_key_path.as_ref()).map(|pk| { - match ADBRsaKey::new_from_pkcs8(&pk) { - Ok(pk) => Some(pk), - Err(e) => { - log::error!("Error while create RSA private key: {e}"); - None + let path = private_key_path.as_ref(); + log::debug!("Attempting to read ADB private key from: {}", path.display()); + + match read_to_string(path) { + Ok(pk) => { + log::debug!("Successfully read private key file"); + match ADBRsaKey::new_from_pkcs8(&pk) { + Ok(pk) => { + log::debug!("Successfully parsed RSA private key"); + Ok(Some(pk)) + }, + Err(e) => { + log::error!("Error while parsing RSA private key: {e}"); + Ok(None) + } } + }, + Err(e) => { + log::debug!("Could not read private key file '{}': {}", path.display(), e); + log::debug!("Will generate a random RSA key instead"); + Ok(None) } - })?) + } } /// Search for adb devices with known interface class and subclass values @@ -88,11 +102,15 @@ fn is_adb_device(device: &Device, des: &DeviceDescriptor) -> b } pub fn get_default_adb_key_path() -> Result { - homedir::my_home() + let home = homedir::my_home() .ok() .flatten() - .map(|home| home.join(".android").join("adbkey")) - .ok_or(RustADBError::NoHomeDirectory) + .ok_or(RustADBError::NoHomeDirectory)?; + + let adb_key_path = home.join(".android").join("adbkey"); + log::debug!("Default ADB key path: {}", adb_key_path.display()); + + Ok(adb_key_path) } /// Represent a device reached and available over USB. @@ -105,6 +123,7 @@ pub struct ADBUSBDevice { impl ADBUSBDevice { /// Instantiate a new [`ADBUSBDevice`] pub fn new(vendor_id: u16, product_id: u16) -> Result { + log::debug!("Creating ADBUSBDevice with vendor_id: {:#06x}, product_id: {:#06x}", vendor_id, product_id); Self::new_with_custom_private_key(vendor_id, product_id, get_default_adb_key_path()?) } @@ -168,8 +187,10 @@ impl ADBUSBDevice { /// Send initial connect pub fn connect(&mut self) -> Result<()> { + log::debug!("Connecting to USB transport..."); self.get_transport_mut().connect()?; + log::debug!("Sending initial CNXN message..."); let message = ADBTransportMessage::new( MessageCommand::Cnxn, 0x01000000, @@ -179,14 +200,17 @@ impl ADBUSBDevice { self.get_transport_mut().write_message(message)?; + log::debug!("Waiting for device response..."); let message = self.get_transport_mut().read_message()?; // If the device returned CNXN instead of AUTH it does not require authentication, // so we can skip the auth steps. if message.header().command() == MessageCommand::Cnxn { + log::debug!("Device accepted connection without authentication"); return Ok(()); } message.assert_command(MessageCommand::Auth)?; + log::debug!("Device requires authentication, received AUTH message"); // At this point, we should have receive an AUTH message with arg0 == 1 let auth_message = match message.header().arg0() { AUTH_TOKEN => message, @@ -197,12 +221,15 @@ impl ADBUSBDevice { } }; + log::debug!("Signing auth token..."); let sign = self.private_key.sign(auth_message.into_payload())?; + log::debug!("Sending AUTH signature..."); let message = ADBTransportMessage::new(MessageCommand::Auth, AUTH_SIGNATURE, 0, &sign); self.get_transport_mut().write_message(message)?; + log::debug!("Waiting for AUTH response..."); let received_response = self.get_transport_mut().read_message()?; if received_response.header().command() == MessageCommand::Cnxn { @@ -213,6 +240,7 @@ impl ADBUSBDevice { return Ok(()); } + log::debug!("Signature authentication failed, sending public key..."); let mut pubkey = self.private_key.android_pubkey_encode()?.into_bytes(); pubkey.push(b'\0'); @@ -220,12 +248,22 @@ impl ADBUSBDevice { self.get_transport_mut().write_message(message)?; + log::info!("Waiting for authorization from Android device..."); + log::info!("Please check your device screen and accept the USB debugging authorization if prompted."); + log::debug!("Waiting for final AUTH response with 30 second timeout..."); let response = self .get_transport_mut() - .read_message_with_timeout(Duration::from_secs(10)) + .read_message_with_timeout(Duration::from_secs(30)) .and_then(|message| { message.assert_command(MessageCommand::Cnxn)?; Ok(message) + }) + .map_err(|e| { + log::error!("Authentication timeout - please check that:"); + log::error!("1. USB debugging is enabled on your Android device"); + log::error!("2. You've accepted the USB debugging authorization dialog"); + log::error!("3. Your device is unlocked and accessible"); + e })?; log::info!( diff --git a/adb_client/src/transports/usb_transport.rs b/adb_client/src/transports/usb_transport.rs index 522c4ca..e146ad2 100644 --- a/adb_client/src/transports/usb_transport.rs +++ b/adb_client/src/transports/usb_transport.rs @@ -31,16 +31,25 @@ impl USBTransport { /// Instantiate a new [`USBTransport`]. /// Only the first device with given vendor_id and product_id is returned. pub fn new(vendor_id: u16, product_id: u16) -> Result { - for device in rusb::devices()?.iter() { + log::debug!("Searching for USB device with vendor_id: {:#06x}, product_id: {:#06x}", vendor_id, product_id); + + let devices = rusb::devices()?; + log::debug!("Found {} USB devices to scan", devices.len()); + + for device in devices.iter() { if let Ok(descriptor) = device.device_descriptor() { + log::debug!("Checking device - vendor_id: {:#06x}, product_id: {:#06x}", + descriptor.vendor_id(), descriptor.product_id()); + if descriptor.vendor_id() == vendor_id && descriptor.product_id() == product_id { + log::debug!("Found matching USB device!"); return Ok(Self::new_from_device(device)); } } } Err(RustADBError::DeviceNotFound(format!( - "cannot find USB device with vendor_id={} and product_id={}", + "cannot find USB device with vendor_id={:#06x} and product_id={:#06x}", vendor_id, product_id ))) } From bdb8dfa47e4948beb4ff070d4c5261eda74a86e0 Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Tue, 15 Jul 2025 20:42:46 -0400 Subject: [PATCH 2/2] write private key when generated --- adb_client/src/device/adb_usb_device.rs | 32 +++++++++++++++++++-- adb_client/src/device/models/adb_rsa_key.rs | 6 +++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/adb_client/src/device/adb_usb_device.rs b/adb_client/src/device/adb_usb_device.rs index 379410c..97971bb 100644 --- a/adb_client/src/device/adb_usb_device.rs +++ b/adb_client/src/device/adb_usb_device.rs @@ -2,7 +2,7 @@ use rusb::Device; use rusb::DeviceDescriptor; use rusb::UsbContext; use rusb::constants::LIBUSB_CLASS_VENDOR_SPEC; -use std::fs::read_to_string; +use std::fs::{create_dir_all, read_to_string, write}; use std::io::Read; use std::io::Write; use std::path::Path; @@ -44,6 +44,23 @@ pub fn read_adb_private_key>(private_key_path: P) -> Result>(private_key: &ADBRsaKey, private_key_path: P) -> Result<()> { + let path = private_key_path.as_ref(); + log::debug!("Saving ADB private key to: {}", path.display()); + + // Create parent directory if it doesn't exist + if let Some(parent) = path.parent() { + create_dir_all(parent)?; + log::debug!("Created directory: {}", parent.display()); + } + + let pem_content = private_key.to_pkcs8_pem()?; + write(path, pem_content)?; + log::info!("Successfully saved ADB private key to: {}", path.display()); + + Ok(()) +} + /// Search for adb devices with known interface class and subclass values fn search_adb_devices() -> Result> { let mut found_devices = vec![]; @@ -153,9 +170,18 @@ impl ADBUSBDevice { transport: USBTransport, private_key_path: PathBuf, ) -> Result { - let private_key = match read_adb_private_key(private_key_path)? { + let private_key = match read_adb_private_key(&private_key_path)? { Some(pk) => pk, - None => ADBRsaKey::new_random()?, + None => { + log::info!("Generating new random ADB private key"); + let new_key = ADBRsaKey::new_random()?; + if let Err(e) = save_adb_private_key(&new_key, &private_key_path) { + log::warn!("Failed to save generated ADB private key: {}", e); + } else { + log::info!("Successfully saved new ADB private key to: {}", private_key_path.display()); + } + new_key + }, }; let mut s = Self { diff --git a/adb_client/src/device/models/adb_rsa_key.rs b/adb_client/src/device/models/adb_rsa_key.rs index 396f040..2863ea5 100644 --- a/adb_client/src/device/models/adb_rsa_key.rs +++ b/adb_client/src/device/models/adb_rsa_key.rs @@ -3,7 +3,7 @@ use base64::{Engine, engine::general_purpose::STANDARD}; use num_bigint::{BigUint, ModInverse}; use num_traits::FromPrimitive; use num_traits::cast::ToPrimitive; -use rsa::pkcs8::DecodePrivateKey; +use rsa::pkcs8::{DecodePrivateKey, EncodePrivateKey}; use rsa::traits::PublicKeyParts; use rsa::{Pkcs1v15Sign, RsaPrivateKey}; @@ -108,6 +108,10 @@ impl ADBRsaKey { .private_key .sign(Pkcs1v15Sign::new::(), msg.as_ref())?) } + + pub fn to_pkcs8_pem(&self) -> Result { + Ok(self.private_key.to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)?.to_string()) + } } fn set_bit(n: usize) -> Result {