diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index a501df5..2b0dc43 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -23,6 +23,14 @@ jobs: - name: Run formatter run : cargo fmt --all --check + doc: + name: "doc" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run doc + run : cargo doc --all-features --keep-going + tests: name: "tests" runs-on: ubuntu-latest diff --git a/README.md b/README.md index 6b7c2ee..ad91ca7 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ crates.io + + ci status + dependency status @@ -20,24 +23,27 @@ Main features of this library: - Full Rust, don't use `adb *` shell commands to interact with devices -- Supports: - - **TCP/IP** protocol, using ADB server as a proxy (standard behavior when using `adb` CLI) - - **USB** protocol, interacting directly with end devices +- Supports + - Using ADB server as a proxy (standard behavior when using `adb` CLI) + - Connecting directly to end devices (without using adb-server) + - Over **USB** + - Over **TCP/IP** - Implements hidden `adb` features, like `framebuffer` - Highly configurable - Easy to use ! ## adb_client -Rust library implementing both ADB protocols and providing a high-level abstraction over many supported commands. +Rust library implementing both ADB protocols (server and end-devices) and providing a high-level abstraction over the many supported commands. -Improved documentation [here](./adb_client/README.md). +Improved documentation available [here](./adb_client/README.md). ## adb_cli -Rust binary providing an improved version of official `adb` CLI, wrapping `adb_client` library. Can act as an usage example of the library. +Rust binary providing an improved version of Google's official `adb` CLI, by using `adb_client` library. +Provides an usage example of the library. -Improved documentation [here](./adb_cli/README.md). +Improved documentation available [here](./adb_cli/README.md). ## Related publications diff --git a/adb_cli/src/commands/mod.rs b/adb_cli/src/commands/mod.rs index 3db1f7e..e526e01 100644 --- a/adb_cli/src/commands/mod.rs +++ b/adb_cli/src/commands/mod.rs @@ -1,9 +1,11 @@ mod emu; mod host; mod local; +mod tcp; mod usb; pub use emu::EmuCommand; pub use host::HostCommand; pub use local::LocalCommand; +pub use tcp::{TcpCommand, TcpCommands}; pub use usb::{UsbCommand, UsbCommands}; diff --git a/adb_cli/src/commands/tcp.rs b/adb_cli/src/commands/tcp.rs new file mode 100644 index 0000000..4eb5ac1 --- /dev/null +++ b/adb_cli/src/commands/tcp.rs @@ -0,0 +1,44 @@ +use std::net::SocketAddr; +use std::path::PathBuf; + +use clap::Parser; + +use crate::models::RebootTypeCommand; + +#[derive(Parser, Debug)] +pub struct TcpCommand { + pub address: SocketAddr, + #[clap(subcommand)] + pub commands: TcpCommands, +} + +#[derive(Parser, Debug)] +pub enum TcpCommands { + /// Spawn an interactive shell or run a list of commands on the device + Shell { commands: Vec }, + /// Pull a file from device + Pull { source: String, destination: String }, + /// Push a file on device + Push { filename: String, path: String }, + /// Stat a file on device + Stat { path: String }, + /// Run an activity on device specified by the intent + Run { + /// The package whose activity is to be invoked + #[clap(short = 'p', long = "package")] + package: String, + /// The activity to be invoked itself, Usually it is MainActivity + #[clap(short = 'a', long = "activity")] + activity: String, + }, + /// Reboot the device + Reboot { + #[clap(subcommand)] + reboot_type: RebootTypeCommand, + }, + /// Install an APK on device + Install { + /// Path to APK file. Extension must be ".apk" + path: PathBuf, + }, +} diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 012b130..5b70677 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -6,10 +6,12 @@ mod adb_termios; mod commands; mod models; -use adb_client::{ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBUSBDevice, DeviceShort}; +use adb_client::{ + ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBTcpDevice, ADBUSBDevice, DeviceShort, +}; use anyhow::{anyhow, Result}; use clap::Parser; -use commands::{EmuCommand, HostCommand, LocalCommand, UsbCommands}; +use commands::{EmuCommand, HostCommand, LocalCommand, TcpCommands, UsbCommands}; use models::{Command, Opts}; use std::fs::File; use std::io::Write; @@ -233,6 +235,60 @@ fn main() -> Result<()> { } } } + Command::Tcp(tcp) => { + let mut device = ADBTcpDevice::new(tcp.address)?; + + match tcp.commands { + TcpCommands::Shell { commands } => { + if commands.is_empty() { + // Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state. + // Using a scope here would call drop() too early.. + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + let mut adb_termios = adb_termios::ADBTermios::new(std::io::stdin())?; + adb_termios.set_adb_termios()?; + device.shell(std::io::stdin(), std::io::stdout())?; + } + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + device.shell(std::io::stdin(), std::io::stdout())?; + } + } else { + device.shell_command(commands, std::io::stdout())?; + } + } + TcpCommands::Pull { + source, + destination, + } => { + let mut output = File::create(Path::new(&destination))?; + device.pull(&source, &mut output)?; + log::info!("Downloaded {source} as {destination}"); + } + TcpCommands::Stat { path } => { + let stat_response = device.stat(&path)?; + println!("{}", stat_response); + } + TcpCommands::Reboot { reboot_type } => { + log::info!("Reboots device in mode {:?}", reboot_type); + device.reboot(reboot_type.into())? + } + TcpCommands::Push { filename, path } => { + let mut input = File::open(Path::new(&filename))?; + device.push(&mut input, &path)?; + log::info!("Uploaded {filename} to {path}"); + } + TcpCommands::Run { package, activity } => { + let output = device.run_activity(&package, &activity)?; + std::io::stdout().write_all(&output)?; + } + TcpCommands::Install { path } => { + log::info!("Starting installation of APK {}...", path.display()); + device.install(path)?; + } + } + } } Ok(()) diff --git a/adb_cli/src/models/opts.rs b/adb_cli/src/models/opts.rs index 397856c..7817a36 100644 --- a/adb_cli/src/models/opts.rs +++ b/adb_cli/src/models/opts.rs @@ -2,7 +2,7 @@ use std::net::SocketAddrV4; use clap::Parser; -use crate::commands::{EmuCommand, HostCommand, LocalCommand, UsbCommand}; +use crate::commands::{EmuCommand, HostCommand, LocalCommand, TcpCommand, UsbCommand}; #[derive(Parser, Debug)] #[clap(about, version, author)] @@ -27,4 +27,6 @@ pub enum Command { Emu(EmuCommand), /// Device commands via USB, no server needed Usb(UsbCommand), + /// Device commands via TCP, no server needed + Tcp(TcpCommand), } diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index bc15dfe..cf33e94 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -15,15 +15,17 @@ bincode = { version = "1.3.3" } byteorder = { version = "1.5.0" } chrono = { version = "0.4.38" } homedir = { version = "0.3.4" } -image = { version = "0.25.4" } -lazy_static = { version = "1.5.0" } -log = { version = "0.4.22" } +image = { version = "0.25.5" } +log = { version = "0.4.22", features = ["max_level_debug", "release_max_level_debug"]} num-bigint = { version = "0.8.4", package = "num-bigint-dig" } num-traits = { version = "0.2.19" } rand = { version = "0.8.5" } +rcgen = { version = "0.13.1" } regex = { version = "1.11.0", features = ["perf", "std", "unicode"] } -rsa = { version = "0.9.6" } +rsa = { version = "0.9.7" } rusb = { version = "0.9.4", features = ["vendored"] } +rustls = { version = "0.23.18" } +rustls-pki-types = "1.10.0" serde = { version = "1.0.210", features = ["derive"] } serde_repr = { version = "0.1.19" } sha1 = { version = "0.10.6", features = ["oid"] } diff --git a/adb_client/README.md b/adb_client/README.md index f29bca5..6595ec1 100644 --- a/adb_client/README.md +++ b/adb_client/README.md @@ -31,9 +31,9 @@ let mut server = ADBServer::new(SocketAddrV4::new(server_ip, server_port)); server.devices(); ``` -### Using ADB server as proxy +### Using ADB server as bridge -#### [TCP] Launch a command on device +#### Launch a command on device ```rust no_run use adb_client::{ADBServer, ADBDeviceExt}; @@ -43,7 +43,7 @@ let mut device = server.get_device().expect("cannot get device"); device.shell_command(["df", "-h"],std::io::stdout()); ``` -#### [TCP] Push a file to the device +#### Push a file to the device ```rust no_run use adb_client::ADBServer; @@ -57,9 +57,9 @@ let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file"); device.push(&mut input, "/data/local/tmp"); ``` -### Interacting directly with device +### Interact directly with end devices -#### [USB] Launch a command on device +#### (USB) Launch a command on device ```rust no_run use adb_client::{ADBUSBDevice, ADBDeviceExt}; @@ -70,7 +70,7 @@ let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find de device.shell_command(["df", "-h"],std::io::stdout()); ``` -#### [USB] Push a file to the device +#### (USB) Push a file to the device ```rust no_run use adb_client::{ADBUSBDevice, ADBDeviceExt}; @@ -83,3 +83,15 @@ let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find de let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file"); device.push(&mut input, "/data/local/tmp"); ``` + +### (TCP) Get a shell from device + +```rust no_run +use std::net::{SocketAddr, IpAddr, Ipv4Addr}; +use adb_client::{ADBTcpDevice, ADBDeviceExt}; + +let device_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 10)); +let device_port = 43210; +let mut device = ADBTcpDevice::new(SocketAddr::new(device_ip, device_port)).expect("cannot find device"); +device.shell(std::io::stdin(), std::io::stdout()); +``` diff --git a/adb_client/src/adb_device_ext.rs b/adb_client/src/adb_device_ext.rs index 5c92cad..2817a44 100644 --- a/adb_client/src/adb_device_ext.rs +++ b/adb_client/src/adb_device_ext.rs @@ -4,30 +4,30 @@ use std::path::Path; use crate::models::AdbStatResponse; use crate::{RebootType, Result}; -/// Trait representing all features available on both [`ADBServerDevice`] and [`ADBUSBDevice`] +/// Trait representing all features available on devices. pub trait ADBDeviceExt { - /// Runs 'command' in a shell on the device, and write its output and error streams into [`output`]. + /// Run 'command' in a shell on the device, and write its output and error streams into `output`. fn shell_command( &mut self, command: impl IntoIterator, output: W, ) -> Result<()>; - /// Starts an interactive shell session on the device. - /// Input data is read from [reader] and write to [writer]. - /// [W] has a 'static bound as it is internally used in a thread. + /// Start an interactive shell session on the device. + /// Input data is read from `reader` and write to `writer`. + /// `W` has a 'static bound as it is internally used in a thread. fn shell(&mut self, reader: R, writer: W) -> Result<()>; /// Display the stat information for a remote file fn stat(&mut self, remote_path: &str) -> Result; - /// Pull the remote file pointed to by [source] and write its contents into [`output`] + /// Pull the remote file pointed to by `source` and write its contents into `output` fn pull, W: Write>(&mut self, source: A, output: W) -> Result<()>; - /// Push [stream] to [path] on the device. + /// Push `stream` to `path` on the device. fn push>(&mut self, stream: R, path: A) -> Result<()>; - /// Reboots the device using given reboot type + /// Reboot the device using given reboot type fn reboot(&mut self, reboot_type: RebootType) -> Result<()>; /// Run `activity` from `package` on device. Return the command output. diff --git a/adb_client/src/device/adb_message_device.rs b/adb_client/src/device/adb_message_device.rs new file mode 100644 index 0000000..4df5f5f --- /dev/null +++ b/adb_client/src/device/adb_message_device.rs @@ -0,0 +1,236 @@ +use byteorder::{LittleEndian, ReadBytesExt}; +use rand::Rng; +use std::io::{Cursor, Read, Seek}; + +use crate::{constants::BUFFER_SIZE, ADBMessageTransport, AdbStatResponse, Result, RustADBError}; + +use super::{models::MessageSubcommand, ADBTransportMessage, MessageCommand}; + +/// Generic structure representing an ADB device reachable over an [`ADBMessageTransport`]. +/// Structure is totally agnostic over which transport is truly used. +#[derive(Debug)] +pub struct ADBMessageDevice { + transport: T, +} + +impl ADBMessageDevice { + /// Instantiate a new [`ADBMessageTransport`] + pub fn new(transport: T) -> Self { + Self { transport } + } + + pub(crate) fn get_transport(&mut self) -> &T { + &self.transport + } + + pub(crate) fn get_transport_mut(&mut self) -> &mut T { + &mut self.transport + } + + /// Receive a message and acknowledge it by replying with an `OKAY` command + pub(crate) fn recv_and_reply_okay( + &mut self, + local_id: u32, + remote_id: u32, + ) -> Result { + let message = self.transport.read_message()?; + self.transport.write_message(ADBTransportMessage::new( + MessageCommand::Okay, + local_id, + remote_id, + "".into(), + ))?; + Ok(message) + } + + /// Expect a message with an `OKAY` command after sending a message. + pub(crate) fn send_and_expect_okay( + &mut self, + message: ADBTransportMessage, + ) -> Result { + self.transport.write_message(message)?; + let message = self.transport.read_message()?; + let received_command = message.header().command(); + if received_command != MessageCommand::Okay { + return Err(RustADBError::ADBRequestFailed(format!( + "expected command OKAY after message, got {}", + received_command + ))); + } + Ok(message) + } + + pub(crate) fn recv_file( + &mut self, + local_id: u32, + remote_id: u32, + mut output: W, + ) -> std::result::Result<(), RustADBError> { + let mut len: Option = None; + loop { + let payload = self + .recv_and_reply_okay(local_id, remote_id)? + .into_payload(); + let mut rdr = Cursor::new(&payload); + while rdr.position() != payload.len() as u64 { + match len.take() { + Some(0) | None => { + rdr.seek_relative(4)?; + len.replace(rdr.read_u32::()? as u64); + } + Some(length) => { + let remaining_bytes = payload.len() as u64 - rdr.position(); + if length < remaining_bytes { + std::io::copy(&mut rdr.by_ref().take(length), &mut output)?; + } else { + std::io::copy(&mut rdr.take(remaining_bytes), &mut output)?; + len.replace(length - remaining_bytes); + // this payload is now exhausted + break; + } + } + } + } + if Cursor::new(&payload[(payload.len() - 8)..(payload.len() - 4)]) + .read_u32::()? + == MessageSubcommand::Done as u32 + { + break; + } + } + Ok(()) + } + + pub(crate) fn push_file( + &mut self, + local_id: u32, + remote_id: u32, + mut reader: R, + ) -> std::result::Result<(), RustADBError> { + let mut buffer = [0; BUFFER_SIZE]; + let amount_read = reader.read(&mut buffer)?; + let subcommand_data = MessageSubcommand::Data.with_arg(amount_read as u32); + + let mut serialized_message = + bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?; + serialized_message.append(&mut buffer[..amount_read].to_vec()); + + let message = ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + serialized_message, + ); + + self.send_and_expect_okay(message)?; + + loop { + let mut buffer = [0; BUFFER_SIZE]; + + match reader.read(&mut buffer) { + Ok(0) => { + // Currently file mtime is not forwarded + let subcommand_data = MessageSubcommand::Done.with_arg(0); + + let serialized_message = bincode::serialize(&subcommand_data) + .map_err(|_e| RustADBError::ConversionError)?; + + let message = ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + serialized_message, + ); + + self.send_and_expect_okay(message)?; + + // Command should end with a Write => Okay + let received = self.transport.read_message()?; + match received.header().command() { + MessageCommand::Write => return Ok(()), + c => { + return Err(RustADBError::ADBRequestFailed(format!( + "Wrong command received {}", + c + ))) + } + } + } + Ok(size) => { + let subcommand_data = MessageSubcommand::Data.with_arg(size as u32); + + let mut serialized_message = bincode::serialize(&subcommand_data) + .map_err(|_e| RustADBError::ConversionError)?; + serialized_message.append(&mut buffer[..size].to_vec()); + + let message = ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + serialized_message, + ); + + self.send_and_expect_okay(message)?; + } + Err(e) => { + return Err(RustADBError::IOError(e)); + } + } + } + } + + pub(crate) fn begin_synchronization(&mut self) -> Result<(u32, u32)> { + let sync_directive = "sync:\0"; + + let mut rng = rand::thread_rng(); + let message = ADBTransportMessage::new( + MessageCommand::Open, + rng.gen(), /* Our 'local-id' */ + 0, + sync_directive.into(), + ); + let message = self.send_and_expect_okay(message)?; + let local_id = message.header().arg1(); + let remote_id = message.header().arg0(); + Ok((local_id, remote_id)) + } + + pub(crate) fn stat_with_explicit_ids( + &mut self, + remote_path: &str, + local_id: u32, + remote_id: u32, + ) -> Result { + let stat_buffer = MessageSubcommand::Stat.with_arg(remote_path.len() as u32); + let message = ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + bincode::serialize(&stat_buffer).map_err(|_e| RustADBError::ConversionError)?, + ); + self.send_and_expect_okay(message)?; + self.send_and_expect_okay(ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + remote_path.into(), + ))?; + let response = self.transport.read_message()?; + // Skip first 4 bytes as this is the literal "STAT". + // Interesting part starts right after + bincode::deserialize(&response.into_payload()[4..]) + .map_err(|_e| RustADBError::ConversionError) + } + + pub(crate) fn end_transaction(&mut self, local_id: u32, remote_id: u32) -> Result<()> { + let quit_buffer = MessageSubcommand::Quit.with_arg(0u32); + self.send_and_expect_okay(ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + bincode::serialize(&quit_buffer).map_err(|_e| RustADBError::ConversionError)?, + ))?; + let _discard_close = self.transport.read_message()?; + Ok(()) + } +} diff --git a/adb_client/src/device/adb_message_device_commands.rs b/adb_client/src/device/adb_message_device_commands.rs new file mode 100644 index 0000000..3008e3c --- /dev/null +++ b/adb_client/src/device/adb_message_device_commands.rs @@ -0,0 +1,38 @@ +use crate::{models::AdbStatResponse, ADBDeviceExt, ADBMessageTransport, RebootType, Result}; +use std::io::{Read, Write}; + +use super::ADBMessageDevice; + +impl ADBDeviceExt for ADBMessageDevice { + fn shell_command( + &mut self, + command: impl IntoIterator, + output: W, + ) -> Result<()> { + self.shell_command(command, output) + } + + fn shell(&mut self, reader: R, writer: W) -> Result<()> { + self.shell(reader, writer) + } + + fn stat(&mut self, remote_path: &str) -> Result { + self.stat(remote_path) + } + + fn pull, W: Write>(&mut self, source: A, output: W) -> Result<()> { + self.pull(source, output) + } + + fn push>(&mut self, stream: R, path: A) -> Result<()> { + self.push(stream, path) + } + + fn reboot(&mut self, reboot_type: RebootType) -> Result<()> { + self.reboot(reboot_type) + } + + fn install>(&mut self, apk_path: P) -> Result<()> { + self.install(apk_path) + } +} diff --git a/adb_client/src/device/adb_tcp_device.rs b/adb_client/src/device/adb_tcp_device.rs new file mode 100644 index 0000000..4932221 --- /dev/null +++ b/adb_client/src/device/adb_tcp_device.rs @@ -0,0 +1,112 @@ +use std::net::SocketAddr; +use std::path::Path; + +use super::adb_message_device::ADBMessageDevice; +use super::models::MessageCommand; +use super::ADBTransportMessage; +use crate::{ADBDeviceExt, ADBMessageTransport, ADBTransport, Result, RustADBError, TcpTransport}; + +/// Represent a device reached and available over USB. +#[derive(Debug)] +pub struct ADBTcpDevice { + inner: ADBMessageDevice, +} + +impl ADBTcpDevice { + /// Instantiate a new [`ADBTcpDevice`] + pub fn new(address: SocketAddr) -> Result { + let mut device = Self { + inner: ADBMessageDevice::new(TcpTransport::new(address)?), + }; + + device.connect()?; + + Ok(device) + } + + /// Send initial connect + pub fn connect(&mut self) -> Result<()> { + self.get_transport_mut().connect()?; + + let message = ADBTransportMessage::new( + MessageCommand::Cnxn, + 0x01000000, + 1048576, + format!("host::{}\0", env!("CARGO_PKG_NAME")) + .as_bytes() + .to_vec(), + ); + + self.get_transport_mut().write_message(message)?; + + let message = self.get_transport_mut().read_message()?; + + // At this point, we should have received a STLS message + if message.header().command() != MessageCommand::Stls { + return Err(RustADBError::ADBRequestFailed(format!( + "Wrong command received {}", + message.header().command() + ))); + }; + + let message = ADBTransportMessage::new(MessageCommand::Stls, 1, 0, vec![]); + + self.get_transport_mut().write_message(message)?; + + // Upgrade TCP connection to TLS + self.get_transport_mut().upgrade_connection()?; + + log::debug!("Connection successfully upgraded from TCP to TLS"); + + Ok(()) + } + + fn get_transport_mut(&mut self) -> &mut TcpTransport { + self.inner.get_transport_mut() + } +} + +impl ADBDeviceExt for ADBTcpDevice { + fn shell_command( + &mut self, + command: impl IntoIterator, + output: W, + ) -> Result<()> { + self.inner.shell_command(command, output) + } + + fn shell( + &mut self, + reader: R, + writer: W, + ) -> Result<()> { + self.inner.shell(reader, writer) + } + + fn stat(&mut self, remote_path: &str) -> Result { + self.inner.stat(remote_path) + } + + fn pull, W: std::io::Write>(&mut self, source: A, output: W) -> Result<()> { + self.inner.pull(source, output) + } + + fn push>(&mut self, stream: R, path: A) -> Result<()> { + self.inner.push(stream, path) + } + + fn reboot(&mut self, reboot_type: crate::RebootType) -> Result<()> { + self.inner.reboot(reboot_type) + } + + fn install>(&mut self, apk_path: P) -> Result<()> { + self.inner.install(apk_path) + } +} + +impl Drop for ADBTcpDevice { + fn drop(&mut self) { + // Best effort here + let _ = self.get_transport_mut().disconnect(); + } +} diff --git a/adb_client/src/usb/adb_usb_message.rs b/adb_client/src/device/adb_transport_message.rs similarity index 54% rename from adb_client/src/usb/adb_usb_message.rs rename to adb_client/src/device/adb_transport_message.rs index 3fb58e7..921f939 100644 --- a/adb_client/src/usb/adb_usb_message.rs +++ b/adb_client/src/device/adb_transport_message.rs @@ -1,31 +1,32 @@ use serde::{Deserialize, Serialize}; -use super::usb_commands::USBCommand; use crate::RustADBError; +use super::models::MessageCommand; + pub const AUTH_TOKEN: u32 = 1; pub const AUTH_SIGNATURE: u32 = 2; pub const AUTH_RSAPUBLICKEY: u32 = 3; #[derive(Debug)] -pub struct ADBUsbMessage { - header: ADBUsbMessageHeader, +pub struct ADBTransportMessage { + header: ADBTransportMessageHeader, payload: Vec, } #[derive(Debug, Serialize, Deserialize)] #[repr(C)] -pub struct ADBUsbMessageHeader { - command: USBCommand, /* command identifier constant */ - arg0: u32, /* first argument */ - arg1: u32, /* second argument */ - data_length: u32, /* length of payload (0 is allowed) */ - data_crc32: u32, /* crc32 of data payload */ - magic: u32, /* command ^ 0xffffffff */ +pub struct ADBTransportMessageHeader { + command: MessageCommand, /* command identifier constant */ + arg0: u32, /* first argument */ + arg1: u32, /* second argument */ + data_length: u32, /* length of payload (0 is allowed) */ + data_crc32: u32, /* crc32 of data payload */ + magic: u32, /* command ^ 0xffffffff */ } -impl ADBUsbMessageHeader { - pub fn new(command: USBCommand, arg0: u32, arg1: u32, data: &[u8]) -> Self { +impl ADBTransportMessageHeader { + pub fn new(command: MessageCommand, arg0: u32, arg1: u32, data: &[u8]) -> Self { Self { command, arg0, @@ -36,7 +37,7 @@ impl ADBUsbMessageHeader { } } - pub fn command(&self) -> USBCommand { + pub fn command(&self) -> MessageCommand { self.command } @@ -60,7 +61,7 @@ impl ADBUsbMessageHeader { data.iter().map(|&x| x as u32).sum() } - fn compute_magic(command: USBCommand) -> u32 { + fn compute_magic(command: MessageCommand) -> u32 { let command_u32 = command as u32; command_u32 ^ 0xFFFFFFFF } @@ -70,24 +71,24 @@ impl ADBUsbMessageHeader { } } -impl ADBUsbMessage { - pub fn new(command: USBCommand, arg0: u32, arg1: u32, data: Vec) -> Self { +impl ADBTransportMessage { + pub fn new(command: MessageCommand, arg0: u32, arg1: u32, data: Vec) -> Self { Self { - header: ADBUsbMessageHeader::new(command, arg0, arg1, &data), + header: ADBTransportMessageHeader::new(command, arg0, arg1, &data), payload: data, } } - pub fn from_header_and_payload(header: ADBUsbMessageHeader, payload: Vec) -> Self { + pub fn from_header_and_payload(header: ADBTransportMessageHeader, payload: Vec) -> Self { Self { header, payload } } pub fn check_message_integrity(&self) -> bool { - ADBUsbMessageHeader::compute_magic(self.header.command) == self.header.magic - && ADBUsbMessageHeader::compute_crc32(&self.payload) == self.header.data_crc32 + ADBTransportMessageHeader::compute_magic(self.header.command) == self.header.magic + && ADBTransportMessageHeader::compute_crc32(&self.payload) == self.header.data_crc32 } - pub fn header(&self) -> &ADBUsbMessageHeader { + pub fn header(&self) -> &ADBTransportMessageHeader { &self.header } @@ -100,7 +101,7 @@ impl ADBUsbMessage { } } -impl TryFrom<[u8; 24]> for ADBUsbMessageHeader { +impl TryFrom<[u8; 24]> for ADBTransportMessageHeader { type Error = RustADBError; fn try_from(value: [u8; 24]) -> Result { diff --git a/adb_client/src/device/adb_usb_device.rs b/adb_client/src/device/adb_usb_device.rs new file mode 100644 index 0000000..1c3425c --- /dev/null +++ b/adb_client/src/device/adb_usb_device.rs @@ -0,0 +1,274 @@ +use rusb::Device; +use rusb::DeviceDescriptor; +use rusb::UsbContext; +use std::fs::read_to_string; +use std::path::Path; +use std::path::PathBuf; +use std::time::Duration; + +use super::adb_message_device::ADBMessageDevice; +use super::models::MessageCommand; +use super::{ADBRsaKey, ADBTransportMessage}; +use crate::device::adb_transport_message::{AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN}; +use crate::ADBDeviceExt; +use crate::ADBMessageTransport; +use crate::ADBTransport; +use crate::{Result, RustADBError, USBTransport}; + +/// Represent a device reached and available over USB. +#[derive(Debug)] +pub struct ADBUSBDevice { + private_key: ADBRsaKey, + inner: ADBMessageDevice, +} + +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 + } + } + })?) +} + +/// Search for adb devices with known interface class and subclass values +fn search_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())); + } + } + + 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 {:04x}:{:04x} and {:04x}:{:04x}", + vid1, pid1, vid2, pid2 + ))), + } +} + +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; + }; + 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 == ADB_PROTOCOL + && ((class == ADB_CLASS && subcl == ADB_SUBCLASS) + || (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS)) + { + return true; + } + } + } + } + false +} + +pub 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 { + 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 + pub fn new_with_custom_private_key( + vendor_id: u16, + product_id: u16, + private_key_path: PathBuf, + ) -> Result { + let private_key = match read_adb_private_key(private_key_path)? { + Some(pk) => pk, + None => ADBRsaKey::new_random()?, + }; + + let mut s = Self { + private_key, + inner: ADBMessageDevice::new(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 { + 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.get_transport_mut().connect()?; + + let message = ADBTransportMessage::new( + MessageCommand::Cnxn, + 0x01000000, + 1048576, + format!("host::{}\0", env!("CARGO_PKG_NAME")) + .as_bytes() + .to_vec(), + ); + + self.get_transport_mut().write_message(message)?; + + let message = self.get_transport_mut().read_message()?; + + // At this point, we should have received either: + // - an AUTH message with arg0 == 1 + // - a CNXN message + let auth_message = match message.header().command() { + MessageCommand::Auth if message.header().arg0() == AUTH_TOKEN => message, + MessageCommand::Auth if message.header().arg0() != AUTH_TOKEN => { + return Err(RustADBError::ADBRequestFailed( + "Received AUTH message with type != 1".into(), + )) + } + c => { + return Err(RustADBError::ADBRequestFailed(format!( + "Wrong command received {}", + c + ))) + } + }; + + let sign = self.private_key.sign(auth_message.into_payload())?; + + let message = ADBTransportMessage::new(MessageCommand::Auth, AUTH_SIGNATURE, 0, sign); + + self.get_transport_mut().write_message(message)?; + + let received_response = self.get_transport_mut().read_message()?; + + if received_response.header().command() == MessageCommand::Cnxn { + log::info!( + "Authentication OK, device info {}", + String::from_utf8(received_response.into_payload())? + ); + return Ok(()); + } + + let mut pubkey = self.private_key.android_pubkey_encode()?.into_bytes(); + pubkey.push(b'\0'); + + let message = ADBTransportMessage::new(MessageCommand::Auth, AUTH_RSAPUBLICKEY, 0, pubkey); + + self.get_transport_mut().write_message(message)?; + + let response = self + .get_transport_mut() + .read_message_with_timeout(Duration::from_secs(10))?; + + match response.header().command() { + MessageCommand::Cnxn => log::info!( + "Authentication OK, device info {}", + String::from_utf8(response.into_payload())? + ), + _ => { + return Err(RustADBError::ADBRequestFailed(format!( + "wrong response {}", + response.header().command() + ))) + } + } + + Ok(()) + } + + fn get_transport_mut(&mut self) -> &mut USBTransport { + self.inner.get_transport_mut() + } +} + +impl ADBDeviceExt for ADBUSBDevice { + fn shell_command( + &mut self, + command: impl IntoIterator, + output: W, + ) -> Result<()> { + self.inner.shell_command(command, output) + } + + fn shell( + &mut self, + reader: R, + writer: W, + ) -> Result<()> { + self.inner.shell(reader, writer) + } + + fn stat(&mut self, remote_path: &str) -> Result { + self.inner.stat(remote_path) + } + + fn pull, W: std::io::Write>(&mut self, source: A, output: W) -> Result<()> { + self.inner.pull(source, output) + } + + fn push>(&mut self, stream: R, path: A) -> Result<()> { + self.inner.push(stream, path) + } + + fn reboot(&mut self, reboot_type: crate::RebootType) -> Result<()> { + self.inner.reboot(reboot_type) + } + + fn install>(&mut self, apk_path: P) -> Result<()> { + self.inner.install(apk_path) + } +} + +impl Drop for ADBUSBDevice { + fn drop(&mut self) { + // Best effort here + let _ = self.get_transport_mut().disconnect(); + } +} diff --git a/adb_client/src/device/commands/install.rs b/adb_client/src/device/commands/install.rs new file mode 100644 index 0000000..592dea7 --- /dev/null +++ b/adb_client/src/device/commands/install.rs @@ -0,0 +1,59 @@ +use std::fs::File; + +use rand::Rng; + +use crate::{ + device::{ + adb_message_device::ADBMessageDevice, ADBTransportMessage, MessageCommand, MessageWriter, + }, + utils::check_extension_is_apk, + ADBMessageTransport, Result, +}; + +impl ADBMessageDevice { + pub(crate) fn install>(&mut self, apk_path: P) -> Result<()> { + let mut apk_file = File::open(&apk_path)?; + + check_extension_is_apk(&apk_path)?; + + let file_size = apk_file.metadata()?.len(); + + let mut rng = rand::thread_rng(); + + let local_id = rng.gen(); + + let message = ADBTransportMessage::new( + MessageCommand::Open, + local_id, + 0, + format!("exec:cmd package 'install' -S {}\0", file_size) + .as_bytes() + .to_vec(), + ); + self.get_transport_mut().write_message(message)?; + + let response = self.get_transport_mut().read_message()?; + let remote_id = response.header().arg0(); + + let transport = self.get_transport().clone(); + + let mut writer = MessageWriter::new(transport, local_id, remote_id); + + std::io::copy(&mut apk_file, &mut writer)?; + + let final_status = self.get_transport_mut().read_message()?; + + match final_status.into_payload().as_slice() { + b"Success\n" => { + log::info!( + "APK file {} successfully installed", + apk_path.as_ref().display() + ); + Ok(()) + } + d => Err(crate::RustADBError::ADBRequestFailed(String::from_utf8( + d.to_vec(), + )?)), + } + } +} diff --git a/adb_client/src/device/commands/mod.rs b/adb_client/src/device/commands/mod.rs new file mode 100644 index 0000000..d474563 --- /dev/null +++ b/adb_client/src/device/commands/mod.rs @@ -0,0 +1,6 @@ +mod install; +mod pull; +mod push; +mod reboot; +mod shell; +mod stat; diff --git a/adb_client/src/device/commands/pull.rs b/adb_client/src/device/commands/pull.rs new file mode 100644 index 0000000..68c0c7c --- /dev/null +++ b/adb_client/src/device/commands/pull.rs @@ -0,0 +1,49 @@ +use std::io::Write; + +use crate::{ + device::{ + adb_message_device::ADBMessageDevice, models::MessageSubcommand, ADBTransportMessage, + MessageCommand, + }, + ADBMessageTransport, Result, RustADBError, +}; + +impl ADBMessageDevice { + pub(crate) fn pull, W: Write>(&mut self, source: A, output: W) -> Result<()> { + let (local_id, remote_id) = self.begin_synchronization()?; + let source = source.as_ref(); + + let adb_stat_response = self.stat_with_explicit_ids(source, local_id, remote_id)?; + + if adb_stat_response.file_perm == 0 { + return Err(RustADBError::UnknownResponseType( + "mode is 0: source file does not exist".to_string(), + )); + } + + self.get_transport_mut().write_message_with_timeout( + ADBTransportMessage::new(MessageCommand::Okay, local_id, remote_id, "".into()), + std::time::Duration::from_secs(4), + )?; + + let recv_buffer = MessageSubcommand::Recv.with_arg(source.len() as u32); + let recv_buffer = + bincode::serialize(&recv_buffer).map_err(|_e| RustADBError::ConversionError)?; + self.send_and_expect_okay(ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + recv_buffer, + ))?; + self.send_and_expect_okay(ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + source.into(), + ))?; + + self.recv_file(local_id, remote_id, output)?; + self.end_transaction(local_id, remote_id)?; + Ok(()) + } +} diff --git a/adb_client/src/device/commands/push.rs b/adb_client/src/device/commands/push.rs new file mode 100644 index 0000000..fd6337f --- /dev/null +++ b/adb_client/src/device/commands/push.rs @@ -0,0 +1,35 @@ +use std::io::Read; + +use crate::{ + device::{ + adb_message_device::ADBMessageDevice, ADBTransportMessage, MessageCommand, + MessageSubcommand, + }, + ADBMessageTransport, Result, RustADBError, +}; + +impl ADBMessageDevice { + pub(crate) fn push>(&mut self, stream: R, path: A) -> Result<()> { + let (local_id, remote_id) = self.begin_synchronization()?; + + let path_header = format!("{},0777", path.as_ref()); + + let send_buffer = MessageSubcommand::Send.with_arg(path_header.len() as u32); + let mut send_buffer = + bincode::serialize(&send_buffer).map_err(|_e| RustADBError::ConversionError)?; + send_buffer.append(&mut path_header.as_bytes().to_vec()); + + self.send_and_expect_okay(ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + send_buffer, + ))?; + + self.push_file(local_id, remote_id, stream)?; + + self.end_transaction(local_id, remote_id)?; + + Ok(()) + } +} diff --git a/adb_client/src/device/commands/reboot.rs b/adb_client/src/device/commands/reboot.rs new file mode 100644 index 0000000..0d2d3ec --- /dev/null +++ b/adb_client/src/device/commands/reboot.rs @@ -0,0 +1,28 @@ +use rand::Rng; + +use crate::{ + device::{adb_message_device::ADBMessageDevice, ADBTransportMessage, MessageCommand}, + ADBMessageTransport, RebootType, Result, RustADBError, +}; + +impl ADBMessageDevice { + pub(crate) fn reboot(&mut self, reboot_type: RebootType) -> Result<()> { + let mut rng = rand::thread_rng(); + + let message = ADBTransportMessage::new( + MessageCommand::Open, + rng.gen(), // Our 'local-id' + 0, + format!("reboot:{}\0", reboot_type).as_bytes().to_vec(), + ); + self.get_transport_mut().write_message(message)?; + + let message = self.get_transport_mut().read_message()?; + + if message.header().command() != MessageCommand::Okay { + return Err(RustADBError::ADBShellNotSupported); + } + + Ok(()) + } +} diff --git a/adb_client/src/device/commands/shell.rs b/adb_client/src/device/commands/shell.rs new file mode 100644 index 0000000..98bc40c --- /dev/null +++ b/adb_client/src/device/commands/shell.rs @@ -0,0 +1,112 @@ +use rand::Rng; +use std::io::{Read, Write}; + +use crate::device::ShellMessageWriter; +use crate::Result; +use crate::{ + device::{ADBMessageDevice, ADBTransportMessage, MessageCommand}, + ADBMessageTransport, RustADBError, +}; + +impl ADBMessageDevice { + /// Runs 'command' in a shell on the device, and write its output and error streams into [`output`]. + pub(crate) fn shell_command( + &mut self, + command: impl IntoIterator, + mut output: W, + ) -> Result<()> { + let message = ADBTransportMessage::new( + MessageCommand::Open, + 1, + 0, + format!( + "shell:{}\0", + command + .into_iter() + .map(|v| v.to_string()) + .collect::>() + .join(" "), + ) + .as_bytes() + .to_vec(), + ); + self.get_transport_mut().write_message(message)?; + + let response = self.get_transport_mut().read_message()?; + if response.header().command() != MessageCommand::Okay { + return Err(RustADBError::ADBRequestFailed(format!( + "wrong command {}", + response.header().command() + ))); + } + + loop { + let response = self.get_transport_mut().read_message()?; + if response.header().command() != MessageCommand::Write { + break; + } + + output.write_all(&response.into_payload())?; + } + + Ok(()) + } + + /// Starts an interactive shell session on the device. + /// Input data is read from [reader] and write to [writer]. + /// [W] has a 'static bound as it is internally used in a thread. + pub(crate) fn shell( + &mut self, + mut reader: R, + mut writer: W, + ) -> Result<()> { + let sync_directive = "shell:\0"; + + let mut rng = rand::thread_rng(); + let message = ADBTransportMessage::new( + MessageCommand::Open, + rng.gen(), /* Our 'local-id' */ + 0, + sync_directive.into(), + ); + let message = self.send_and_expect_okay(message)?; + let local_id = message.header().arg1(); + let remote_id = message.header().arg0(); + + let mut transport = self.get_transport().clone(); + + // Reading thread, reads response from adbd + std::thread::spawn(move || -> Result<()> { + loop { + let message = transport.read_message()?; + + // Acknowledge for more data + let response = + ADBTransportMessage::new(MessageCommand::Okay, local_id, remote_id, vec![]); + transport.write_message(response)?; + + match message.header().command() { + MessageCommand::Write => {} + MessageCommand::Okay => continue, + _ => return Err(RustADBError::ADBShellNotSupported), + } + + writer.write_all(&message.into_payload())?; + writer.flush()?; + } + }); + + let transport = self.get_transport().clone(); + let mut shell_writer = ShellMessageWriter::new(transport, local_id, remote_id); + + // Read from given reader (that could be stdin e.g), and write content to device adbd + if let Err(e) = std::io::copy(&mut reader, &mut shell_writer) { + match e.kind() { + std::io::ErrorKind::BrokenPipe => return Ok(()), + _ => return Err(RustADBError::IOError(e)), + } + } + + Ok(()) + } +} diff --git a/adb_client/src/device/commands/stat.rs b/adb_client/src/device/commands/stat.rs new file mode 100644 index 0000000..0cd6a57 --- /dev/null +++ b/adb_client/src/device/commands/stat.rs @@ -0,0 +1,12 @@ +use crate::{ + device::adb_message_device::ADBMessageDevice, ADBMessageTransport, AdbStatResponse, Result, +}; + +impl ADBMessageDevice { + pub(crate) fn stat(&mut self, remote_path: &str) -> Result { + let (local_id, remote_id) = self.begin_synchronization()?; + let adb_stat_response = self.stat_with_explicit_ids(remote_path, local_id, remote_id)?; + self.end_transaction(local_id, remote_id)?; + Ok(adb_stat_response) + } +} diff --git a/adb_client/src/usb/usb_writer.rs b/adb_client/src/device/message_writer.rs similarity index 59% rename from adb_client/src/usb/usb_writer.rs rename to adb_client/src/device/message_writer.rs index 61b31b5..f7a554b 100644 --- a/adb_client/src/usb/usb_writer.rs +++ b/adb_client/src/device/message_writer.rs @@ -1,20 +1,20 @@ use std::io::{ErrorKind, Write}; -use crate::USBTransport; +use crate::ADBMessageTransport; -use super::{ADBUsbMessage, USBCommand}; +use super::{ADBTransportMessage, MessageCommand}; -/// Wraps a `Writer` to hide underlying ADB protocol write logic. +/// [`Write`] trait implementation to hide underlying ADB protocol write logic. /// -/// Read received responses to check that message has been received. -pub struct USBWriter { - transport: USBTransport, +/// Read received responses to check that message has been correctly received. +pub struct MessageWriter { + transport: T, local_id: u32, remote_id: u32, } -impl USBWriter { - pub fn new(transport: USBTransport, local_id: u32, remote_id: u32) -> Self { +impl MessageWriter { + pub fn new(transport: T, local_id: u32, remote_id: u32) -> Self { Self { transport, local_id, @@ -23,10 +23,10 @@ impl USBWriter { } } -impl Write for USBWriter { +impl Write for MessageWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { - let message = ADBUsbMessage::new( - USBCommand::Write, + let message = ADBTransportMessage::new( + MessageCommand::Write, self.local_id, self.remote_id, buf.to_vec(), @@ -37,7 +37,7 @@ impl Write for USBWriter { match self.transport.read_message() { Ok(response) => match response.header().command() { - USBCommand::Okay => Ok(buf.len()), + MessageCommand::Okay => Ok(buf.len()), c => Err(std::io::Error::new( ErrorKind::Other, format!("wrong response received: {c}"), diff --git a/adb_client/src/device/mod.rs b/adb_client/src/device/mod.rs new file mode 100644 index 0000000..d9fa0e9 --- /dev/null +++ b/adb_client/src/device/mod.rs @@ -0,0 +1,17 @@ +mod adb_message_device; +mod adb_message_device_commands; +mod adb_tcp_device; +mod adb_transport_message; +mod adb_usb_device; +mod commands; +mod message_writer; +mod models; +mod shell_message_writer; + +use adb_message_device::ADBMessageDevice; +pub use adb_tcp_device::ADBTcpDevice; +pub use adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader}; +pub use adb_usb_device::{get_default_adb_key_path, ADBUSBDevice}; +pub use message_writer::MessageWriter; +pub use models::{ADBRsaKey, MessageCommand, MessageSubcommand}; +pub use shell_message_writer::ShellMessageWriter; diff --git a/adb_client/src/usb/adb_rsa_key.rs b/adb_client/src/device/models/adb_rsa_key.rs similarity index 99% rename from adb_client/src/usb/adb_rsa_key.rs rename to adb_client/src/device/models/adb_rsa_key.rs index dceed5a..558c95b 100644 --- a/adb_client/src/usb/adb_rsa_key.rs +++ b/adb_client/src/device/models/adb_rsa_key.rs @@ -44,7 +44,7 @@ impl ADBRsaInternalPublicKey { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ADBRsaKey { private_key: RsaPrivateKey, } diff --git a/adb_client/src/usb/usb_commands.rs b/adb_client/src/device/models/message_commands.rs similarity index 66% rename from adb_client/src/usb/usb_commands.rs rename to adb_client/src/device/models/message_commands.rs index 28d3f11..de3ad94 100644 --- a/adb_client/src/usb/usb_commands.rs +++ b/adb_client/src/device/models/message_commands.rs @@ -4,7 +4,7 @@ use std::fmt::Display; #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)] #[repr(u32)] -pub enum USBCommand { +pub enum MessageCommand { /// Connect to a device Cnxn = 0x4e584e43, /// Close connection to a device @@ -18,12 +18,13 @@ pub enum USBCommand { /// Server understood the message Okay = 0x59414b4f, // Sync 0x434e5953 - // Stls 0x534C5453 + /// Start a connection using TLS + Stls = 0x534C5453, } #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize_repr, Deserialize_repr)] #[repr(u32)] -pub enum USBSubcommand { +pub enum MessageSubcommand { Stat = 0x54415453, Send = 0x444E4553, Recv = 0x56434552, @@ -36,11 +37,11 @@ pub enum USBSubcommand { #[derive(Debug, Serialize, Deserialize)] pub struct SubcommandWithArg { - subcommand: USBSubcommand, + subcommand: MessageSubcommand, arg: u32, } -impl USBSubcommand { +impl MessageSubcommand { pub fn with_arg(self, arg: u32) -> SubcommandWithArg { SubcommandWithArg { subcommand: self, @@ -49,15 +50,16 @@ impl USBSubcommand { } } -impl Display for USBCommand { +impl Display for MessageCommand { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - USBCommand::Cnxn => write!(f, "CNXN"), - USBCommand::Clse => write!(f, "CLSE"), - USBCommand::Auth => write!(f, "AUTH"), - USBCommand::Open => write!(f, "OPEN"), - USBCommand::Write => write!(f, "WRTE"), - USBCommand::Okay => write!(f, "OKAY"), + MessageCommand::Cnxn => write!(f, "CNXN"), + MessageCommand::Clse => write!(f, "CLSE"), + MessageCommand::Auth => write!(f, "AUTH"), + MessageCommand::Open => write!(f, "OPEN"), + MessageCommand::Write => write!(f, "WRTE"), + MessageCommand::Okay => write!(f, "OKAY"), + MessageCommand::Stls => write!(f, "STLS"), } } } diff --git a/adb_client/src/device/models/mod.rs b/adb_client/src/device/models/mod.rs new file mode 100644 index 0000000..7f97ceb --- /dev/null +++ b/adb_client/src/device/models/mod.rs @@ -0,0 +1,5 @@ +mod adb_rsa_key; +mod message_commands; + +pub use adb_rsa_key::ADBRsaKey; +pub use message_commands::{MessageCommand, MessageSubcommand}; diff --git a/adb_client/src/usb/usb_shell_writer.rs b/adb_client/src/device/shell_message_writer.rs similarity index 51% rename from adb_client/src/usb/usb_shell_writer.rs rename to adb_client/src/device/shell_message_writer.rs index 570a1cb..07a9358 100644 --- a/adb_client/src/usb/usb_shell_writer.rs +++ b/adb_client/src/device/shell_message_writer.rs @@ -1,18 +1,18 @@ use std::io::Write; -use crate::USBTransport; +use crate::ADBMessageTransport; -use super::{ADBUsbMessage, USBCommand}; +use super::{models::MessageCommand, ADBTransportMessage}; -/// Wraps a `Writer` to hide underlying ADB protocol write logic. -pub struct USBShellWriter { - transport: USBTransport, +/// [`Write`] trait implementation to hide underlying ADB protocol write logic for shell commands. +pub struct ShellMessageWriter { + transport: T, local_id: u32, remote_id: u32, } -impl USBShellWriter { - pub fn new(transport: USBTransport, local_id: u32, remote_id: u32) -> Self { +impl ShellMessageWriter { + pub fn new(transport: T, local_id: u32, remote_id: u32) -> Self { Self { transport, local_id, @@ -21,10 +21,10 @@ impl USBShellWriter { } } -impl Write for USBShellWriter { +impl Write for ShellMessageWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { - let message = ADBUsbMessage::new( - USBCommand::Write, + let message = ADBTransportMessage::new( + MessageCommand::Write, self.local_id, self.remote_id, buf.to_vec(), diff --git a/adb_client/src/server/adb_emulator_device.rs b/adb_client/src/emulator_device/adb_emulator_device.rs similarity index 85% rename from adb_client/src/server/adb_emulator_device.rs rename to adb_client/src/emulator_device/adb_emulator_device.rs index 65764bc..ed304ce 100644 --- a/adb_client/src/server/adb_emulator_device.rs +++ b/adb_client/src/emulator_device/adb_emulator_device.rs @@ -1,15 +1,14 @@ -use std::net::{Ipv4Addr, SocketAddrV4}; +use std::{ + net::{Ipv4Addr, SocketAddrV4}, + sync::LazyLock, +}; -use crate::{ADBTransport, Result, RustADBError, TCPEmulatorTransport}; -use lazy_static::lazy_static; +use crate::{ADBServerDevice, ADBTransport, Result, RustADBError, TCPEmulatorTransport}; use regex::Regex; -use super::ADBServerDevice; - -lazy_static! { - pub static ref EMULATOR_REGEX: Regex = - Regex::new("^emulator-(?P\\d+)$").expect("wrong syntax for emulator regex"); -} +static EMULATOR_REGEX: LazyLock = LazyLock::new(|| { + Regex::new("^emulator-(?P\\d+)$").expect("wrong syntax for emulator regex") +}); /// Represents an emulator connected to the ADB server. #[derive(Debug)] diff --git a/adb_client/src/emulator/mod.rs b/adb_client/src/emulator_device/commands/mod.rs similarity index 100% rename from adb_client/src/emulator/mod.rs rename to adb_client/src/emulator_device/commands/mod.rs diff --git a/adb_client/src/emulator/rotate.rs b/adb_client/src/emulator_device/commands/rotate.rs similarity index 100% rename from adb_client/src/emulator/rotate.rs rename to adb_client/src/emulator_device/commands/rotate.rs diff --git a/adb_client/src/emulator/sms.rs b/adb_client/src/emulator_device/commands/sms.rs similarity index 100% rename from adb_client/src/emulator/sms.rs rename to adb_client/src/emulator_device/commands/sms.rs diff --git a/adb_client/src/emulator_device/mod.rs b/adb_client/src/emulator_device/mod.rs new file mode 100644 index 0000000..41e1888 --- /dev/null +++ b/adb_client/src/emulator_device/mod.rs @@ -0,0 +1,3 @@ +mod adb_emulator_device; +mod commands; +pub use adb_emulator_device::ADBEmulatorDevice; diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index d03eb72..0477325 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -93,4 +93,25 @@ pub enum RustADBError { /// An error occurred with PKCS8 data #[error("error with pkcs8: {0}")] RsaPkcs8Error(#[from] rsa::pkcs8::Error), + /// Error during certificate generation + #[error(transparent)] + CertificateGenerationError(#[from] rcgen::Error), + /// TLS Error + #[error(transparent)] + TLSError(#[from] rustls::Error), + /// PEM certificate error + #[error(transparent)] + PemCertError(#[from] rustls_pki_types::pem::Error), + /// Error while locking mutex + #[error("error while locking data")] + PoisonError, + /// Cannot upgrade connection from TCP to TLS + #[error("upgrade error: {0}")] + UpgradeError(String), +} + +impl From> for RustADBError { + fn from(_err: std::sync::PoisonError) -> Self { + Self::PoisonError + } } diff --git a/adb_client/src/lib.rs b/adb_client/src/lib.rs index c3e0671..2190077 100644 --- a/adb_client/src/lib.rs +++ b/adb_client/src/lib.rs @@ -6,17 +6,20 @@ mod adb_device_ext; mod constants; -mod emulator; +mod device; +mod emulator_device; mod error; mod models; mod server; +mod server_device; mod transports; -mod usb; mod utils; pub use adb_device_ext::ADBDeviceExt; +pub use device::{ADBTcpDevice, ADBUSBDevice}; +pub use emulator_device::ADBEmulatorDevice; pub use error::{Result, RustADBError}; pub use models::{AdbStatResponse, AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType}; pub use server::*; +pub use server_device::ADBServerDevice; pub use transports::*; -pub use usb::ADBUSBDevice; diff --git a/adb_client/src/models/device_long.rs b/adb_client/src/models/device_long.rs index 291e10a..5daf9d0 100644 --- a/adb_client/src/models/device_long.rs +++ b/adb_client/src/models/device_long.rs @@ -1,16 +1,15 @@ -use lazy_static::lazy_static; use std::str::FromStr; +use std::sync::LazyLock; use std::{fmt::Display, str}; use crate::{DeviceState, RustADBError}; use regex::bytes::Regex; -lazy_static! { - static ref DEVICES_LONG_REGEX: Regex = Regex::new("^(?P\\S+)\\s+(?P\\w+) ((usb:(?P.*)|(?P\\d-\\d)) )?(product:(?P\\w+) model:(?P\\w+) device:(?P\\w+) )?transport_id:(?P\\d+)$").expect("cannot build devices long regex"); +static DEVICES_LONG_REGEX: LazyLock = LazyLock::new(|| { + Regex::new("^(?P\\S+)\\s+(?P\\w+) ((usb:(?P.*)|(?P\\d-\\d)) )?(product:(?P\\w+) model:(?P\\w+) device:(?P\\w+) )?transport_id:(?P\\d+)$").expect("cannot build devices long regex") +}); -} - -/// Represents a new device with more informations helded. +/// Represents a new device with more informations. #[derive(Debug)] pub struct DeviceLong { /// Unique device identifier. diff --git a/adb_client/src/models/device_short.rs b/adb_client/src/models/device_short.rs index b3ff0de..493cd6e 100644 --- a/adb_client/src/models/device_short.rs +++ b/adb_client/src/models/device_short.rs @@ -1,13 +1,10 @@ -use lazy_static::lazy_static; use regex::bytes::Regex; -use std::{fmt::Display, str::FromStr}; +use std::{fmt::Display, str::FromStr, sync::LazyLock}; use crate::{DeviceState, RustADBError}; -lazy_static! { - static ref DEVICES_REGEX: Regex = - Regex::new("^(\\S+)\t(\\w+)\n?$").expect("Cannot build devices regex"); -} +static DEVICES_REGEX: LazyLock = + LazyLock::new(|| Regex::new("^(\\S+)\t(\\w+)\n?$").expect("Cannot build devices regex")); /// Represents a device connected to the ADB server. #[derive(Debug, Clone)] diff --git a/adb_client/src/server/server_commands/connect.rs b/adb_client/src/server/commands/connect.rs similarity index 100% rename from adb_client/src/server/server_commands/connect.rs rename to adb_client/src/server/commands/connect.rs diff --git a/adb_client/src/server/server_commands/devices.rs b/adb_client/src/server/commands/devices.rs similarity index 100% rename from adb_client/src/server/server_commands/devices.rs rename to adb_client/src/server/commands/devices.rs diff --git a/adb_client/src/server/server_commands/disconnect.rs b/adb_client/src/server/commands/disconnect.rs similarity index 100% rename from adb_client/src/server/server_commands/disconnect.rs rename to adb_client/src/server/commands/disconnect.rs diff --git a/adb_client/src/server/server_commands/kill.rs b/adb_client/src/server/commands/kill.rs similarity index 100% rename from adb_client/src/server/server_commands/kill.rs rename to adb_client/src/server/commands/kill.rs diff --git a/adb_client/src/server/server_commands/mod.rs b/adb_client/src/server/commands/mod.rs similarity index 100% rename from adb_client/src/server/server_commands/mod.rs rename to adb_client/src/server/commands/mod.rs diff --git a/adb_client/src/server/server_commands/pair.rs b/adb_client/src/server/commands/pair.rs similarity index 100% rename from adb_client/src/server/server_commands/pair.rs rename to adb_client/src/server/commands/pair.rs diff --git a/adb_client/src/server/server_commands/version.rs b/adb_client/src/server/commands/version.rs similarity index 100% rename from adb_client/src/server/server_commands/version.rs rename to adb_client/src/server/commands/version.rs diff --git a/adb_client/src/server/mod.rs b/adb_client/src/server/mod.rs index 0ffbf24..848a9f2 100644 --- a/adb_client/src/server/mod.rs +++ b/adb_client/src/server/mod.rs @@ -1,10 +1,4 @@ -mod adb_emulator_device; mod adb_server; -mod adb_server_device; -mod adb_server_device_commands; -mod device_commands; -mod server_commands; +mod commands; -pub use adb_emulator_device::ADBEmulatorDevice; pub use adb_server::ADBServer; -pub use adb_server_device::ADBServerDevice; diff --git a/adb_client/src/server/adb_server_device.rs b/adb_client/src/server_device/adb_server_device.rs similarity index 100% rename from adb_client/src/server/adb_server_device.rs rename to adb_client/src/server_device/adb_server_device.rs diff --git a/adb_client/src/server/adb_server_device_commands.rs b/adb_client/src/server_device/adb_server_device_commands.rs similarity index 98% rename from adb_client/src/server/adb_server_device_commands.rs rename to adb_client/src/server_device/adb_server_device_commands.rs index 7e85d16..3882cbc 100644 --- a/adb_client/src/server/adb_server_device_commands.rs +++ b/adb_client/src/server_device/adb_server_device_commands.rs @@ -6,9 +6,11 @@ use std::{ use crate::{ constants::BUFFER_SIZE, models::{AdbServerCommand, AdbStatResponse, HostFeatures}, - ADBDeviceExt, ADBServerDevice, Result, RustADBError, + ADBDeviceExt, Result, RustADBError, }; +use super::ADBServerDevice; + impl ADBDeviceExt for ADBServerDevice { fn shell_command( &mut self, diff --git a/adb_client/src/server/device_commands/forward.rs b/adb_client/src/server_device/commands/forward.rs similarity index 100% rename from adb_client/src/server/device_commands/forward.rs rename to adb_client/src/server_device/commands/forward.rs diff --git a/adb_client/src/server/device_commands/framebuffer.rs b/adb_client/src/server_device/commands/framebuffer.rs similarity index 97% rename from adb_client/src/server/device_commands/framebuffer.rs rename to adb_client/src/server_device/commands/framebuffer.rs index 16e9518..706c663 100644 --- a/adb_client/src/server/device_commands/framebuffer.rs +++ b/adb_client/src/server_device/commands/framebuffer.rs @@ -104,7 +104,7 @@ impl TryFrom<[u8; std::mem::size_of::()]> for FrameBufferInfoV2 { impl ADBServerDevice { /// Dump framebuffer of this device into given ['path'] - /// Big help from source code (https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp) + /// Big help from source code () pub fn framebuffer>(&mut self, path: P) -> Result<()> { let img = self.framebuffer_inner()?; Ok(img.save(path.as_ref())?) diff --git a/adb_client/src/server/device_commands/host_features.rs b/adb_client/src/server_device/commands/host_features.rs similarity index 100% rename from adb_client/src/server/device_commands/host_features.rs rename to adb_client/src/server_device/commands/host_features.rs diff --git a/adb_client/src/server/device_commands/install.rs b/adb_client/src/server_device/commands/install.rs similarity index 91% rename from adb_client/src/server/device_commands/install.rs rename to adb_client/src/server_device/commands/install.rs index 275365b..ae31f9a 100644 --- a/adb_client/src/server/device_commands/install.rs +++ b/adb_client/src/server_device/commands/install.rs @@ -1,6 +1,8 @@ use std::{fs::File, io::Read, path::Path}; -use crate::{models::AdbServerCommand, utils::check_extension_is_apk, ADBServerDevice, Result}; +use crate::{ + models::AdbServerCommand, server_device::ADBServerDevice, utils::check_extension_is_apk, Result, +}; impl ADBServerDevice { /// Install an APK on device diff --git a/adb_client/src/server/device_commands/list.rs b/adb_client/src/server_device/commands/list.rs similarity index 98% rename from adb_client/src/server/device_commands/list.rs rename to adb_client/src/server_device/commands/list.rs index c2fb130..2cb464a 100644 --- a/adb_client/src/server/device_commands/list.rs +++ b/adb_client/src/server_device/commands/list.rs @@ -9,7 +9,7 @@ use std::{ }; impl ADBServerDevice { - /// Lists files in [path] on the device. + /// Lists files in `path` on the device. pub fn list>(&mut self, path: A) -> Result<()> { let serial = self.identifier.clone(); self.connect()? diff --git a/adb_client/src/server/device_commands/logcat.rs b/adb_client/src/server_device/commands/logcat.rs similarity index 100% rename from adb_client/src/server/device_commands/logcat.rs rename to adb_client/src/server_device/commands/logcat.rs diff --git a/adb_client/src/server/device_commands/mod.rs b/adb_client/src/server_device/commands/mod.rs similarity index 100% rename from adb_client/src/server/device_commands/mod.rs rename to adb_client/src/server_device/commands/mod.rs diff --git a/adb_client/src/server/device_commands/reboot.rs b/adb_client/src/server_device/commands/reboot.rs similarity index 100% rename from adb_client/src/server/device_commands/reboot.rs rename to adb_client/src/server_device/commands/reboot.rs diff --git a/adb_client/src/server/device_commands/recv.rs b/adb_client/src/server_device/commands/recv.rs similarity index 98% rename from adb_client/src/server/device_commands/recv.rs rename to adb_client/src/server_device/commands/recv.rs index 84fb788..250f470 100644 --- a/adb_client/src/server/device_commands/recv.rs +++ b/adb_client/src/server_device/commands/recv.rs @@ -69,7 +69,7 @@ impl Read for ADBRecvCommandReader { } impl ADBServerDevice { - /// Receives [path] to [stream] from the device. + /// Receives `path` to `stream` from the device. pub fn pull>(&mut self, path: A, stream: &mut dyn Write) -> Result<()> { let serial = self.identifier.clone(); self.connect()? diff --git a/adb_client/src/server/device_commands/reverse.rs b/adb_client/src/server_device/commands/reverse.rs similarity index 100% rename from adb_client/src/server/device_commands/reverse.rs rename to adb_client/src/server_device/commands/reverse.rs diff --git a/adb_client/src/server/device_commands/send.rs b/adb_client/src/server_device/commands/send.rs similarity index 98% rename from adb_client/src/server/device_commands/send.rs rename to adb_client/src/server_device/commands/send.rs index 2b7eff7..ca536cc 100644 --- a/adb_client/src/server/device_commands/send.rs +++ b/adb_client/src/server_device/commands/send.rs @@ -42,7 +42,7 @@ impl Write for ADBSendCommandWriter { } impl ADBServerDevice { - /// Send [stream] to [path] on the device. + /// Send `stream` to `path` on the device. pub fn push>(&mut self, stream: R, path: A) -> Result<()> { log::info!("Sending data to {}", path.as_ref()); let serial = self.identifier.clone(); diff --git a/adb_client/src/server/device_commands/stat.rs b/adb_client/src/server_device/commands/stat.rs similarity index 97% rename from adb_client/src/server/device_commands/stat.rs rename to adb_client/src/server_device/commands/stat.rs index 8cc76d4..0f3165d 100644 --- a/adb_client/src/server/device_commands/stat.rs +++ b/adb_client/src/server_device/commands/stat.rs @@ -41,7 +41,7 @@ impl ADBServerDevice { } } - /// Stat file given as [path] on the device. + /// Stat file given as `path` on the device. pub fn stat>(&mut self, path: A) -> Result { let serial = self.identifier.clone(); self.connect()? diff --git a/adb_client/src/server/device_commands/transport.rs b/adb_client/src/server_device/commands/transport.rs similarity index 100% rename from adb_client/src/server/device_commands/transport.rs rename to adb_client/src/server_device/commands/transport.rs diff --git a/adb_client/src/server_device/mod.rs b/adb_client/src/server_device/mod.rs new file mode 100644 index 0000000..c783001 --- /dev/null +++ b/adb_client/src/server_device/mod.rs @@ -0,0 +1,5 @@ +mod adb_server_device; +mod adb_server_device_commands; +mod commands; + +pub use adb_server_device::ADBServerDevice; diff --git a/adb_client/src/transports/mod.rs b/adb_client/src/transports/mod.rs index 0c26f44..28e89a0 100644 --- a/adb_client/src/transports/mod.rs +++ b/adb_client/src/transports/mod.rs @@ -1,8 +1,11 @@ mod tcp_emulator_transport; mod tcp_server_transport; -mod transport_trait; +mod tcp_transport; +mod traits; mod usb_transport; + pub use tcp_emulator_transport::TCPEmulatorTransport; pub use tcp_server_transport::TCPServerTransport; -pub use transport_trait::ADBTransport; +pub use tcp_transport::TcpTransport; +pub use traits::{ADBMessageTransport, ADBTransport}; pub use usb_transport::USBTransport; diff --git a/adb_client/src/transports/tcp_transport.rs b/adb_client/src/transports/tcp_transport.rs new file mode 100644 index 0000000..9094325 --- /dev/null +++ b/adb_client/src/transports/tcp_transport.rs @@ -0,0 +1,344 @@ +use rcgen::{CertificateParams, KeyPair, PKCS_RSA_SHA256}; +use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + pki_types::{pem::PemObject, CertificateDer, PrivatePkcs8KeyDer}, + ClientConfig, ClientConnection, KeyLogFile, SignatureScheme, StreamOwned, +}; + +use super::{ADBMessageTransport, ADBTransport}; +use crate::{ + device::{ + get_default_adb_key_path, ADBTransportMessage, ADBTransportMessageHeader, MessageCommand, + }, + Result, RustADBError, +}; +use std::{ + fs::read_to_string, + io::{Read, Write}, + net::{Shutdown, SocketAddr, TcpStream}, + ops::{Deref, DerefMut}, + path::PathBuf, + sync::{Arc, Mutex}, + time::Duration, +}; + +#[derive(Debug)] +enum CurrentConnection { + Tcp(TcpStream), + Tls(Box>), +} + +impl CurrentConnection { + fn set_read_timeout(&self, read_timeout: Duration) -> Result<()> { + match self { + CurrentConnection::Tcp(tcp_stream) => { + Ok(tcp_stream.set_read_timeout(Some(read_timeout))?) + } + CurrentConnection::Tls(stream_owned) => { + Ok(stream_owned.sock.set_read_timeout(Some(read_timeout))?) + } + } + } + + fn set_write_timeout(&self, write_timeout: Duration) -> Result<()> { + match self { + CurrentConnection::Tcp(tcp_stream) => { + Ok(tcp_stream.set_write_timeout(Some(write_timeout))?) + } + CurrentConnection::Tls(stream_owned) => { + Ok(stream_owned.sock.set_write_timeout(Some(write_timeout))?) + } + } + } +} + +impl Read for CurrentConnection { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + match self { + CurrentConnection::Tcp(tcp_stream) => tcp_stream.read(buf), + CurrentConnection::Tls(tls_conn) => tls_conn.read(buf), + } + } +} + +impl Write for CurrentConnection { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + match self { + CurrentConnection::Tcp(tcp_stream) => tcp_stream.write(buf), + CurrentConnection::Tls(tls_conn) => tls_conn.write(buf), + } + } + + fn flush(&mut self) -> std::io::Result<()> { + match self { + CurrentConnection::Tcp(tcp_stream) => tcp_stream.flush(), + CurrentConnection::Tls(tls_conn) => tls_conn.flush(), + } + } +} + +/// Transport running on USB +#[derive(Clone, Debug)] +pub struct TcpTransport { + address: SocketAddr, + current_connection: Option>>, + private_key_path: PathBuf, +} + +fn certificate_from_pk(key_pair: &KeyPair) -> Result>> { + let certificate_params = CertificateParams::default(); + let certificate = certificate_params.self_signed(key_pair)?; + Ok(vec![certificate.der().to_owned()]) +} + +impl TcpTransport { + /// Instantiate a new [`TcpTransport`] + pub fn new(address: SocketAddr) -> Result { + Self::new_with_custom_private_key(address, get_default_adb_key_path()?) + } + + /// Instantiate a new [`TcpTransport`] using a given private key + pub fn new_with_custom_private_key( + address: SocketAddr, + private_key_path: PathBuf, + ) -> Result { + Ok(Self { + address, + current_connection: None, + private_key_path, + }) + } + + fn get_current_connection(&mut self) -> Result>> { + self.current_connection + .as_ref() + .ok_or(RustADBError::IOError(std::io::Error::new( + std::io::ErrorKind::NotConnected, + "not connected", + ))) + .cloned() + } + + pub(crate) fn upgrade_connection(&mut self) -> Result<()> { + let current_connection = match self.current_connection.clone() { + Some(current_connection) => current_connection, + None => { + return Err(RustADBError::UpgradeError( + "cannot upgrade a non-existing connection...".into(), + )) + } + }; + + { + let mut current_conn_locked = current_connection.lock()?; + match current_conn_locked.deref() { + CurrentConnection::Tcp(tcp_stream) => { + // TODO: Check if we cannot be more precise + + let pk_content = read_to_string(&self.private_key_path)?; + + let key_pair = + KeyPair::from_pkcs8_pem_and_sign_algo(&pk_content, &PKCS_RSA_SHA256)?; + + let certificate = certificate_from_pk(&key_pair)?; + let private_key = PrivatePkcs8KeyDer::from_pem_file(&self.private_key_path)?; + + let mut client_config = ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoCertificateVerification {})) + .with_client_auth_cert(certificate, private_key.into())?; + + client_config.key_log = Arc::new(KeyLogFile::new()); + + let rc_config = Arc::new(client_config); + let example_com = self.address.ip().into(); + let conn = ClientConnection::new(rc_config, example_com)?; + let owned = tcp_stream.try_clone()?; + let client = StreamOwned::new(conn, owned); + + // Update current connection state to now use TLS protocol + *current_conn_locked = CurrentConnection::Tls(Box::new(client)); + } + CurrentConnection::Tls(_) => { + return Err(RustADBError::UpgradeError( + "cannot upgrade a TLS connection...".into(), + )) + } + } + } + + let message = self.read_message()?; + match message.header().command() { + MessageCommand::Cnxn => { + let device_infos = String::from_utf8(message.into_payload())?; + log::debug!("received device info: {device_infos}"); + Ok(()) + } + c => Err(RustADBError::ADBRequestFailed(format!( + "Wrong command received {}", + c + ))), + } + } +} + +impl ADBTransport for TcpTransport { + fn connect(&mut self) -> Result<()> { + let stream = TcpStream::connect(self.address)?; + self.current_connection = Some(Arc::new(Mutex::new(CurrentConnection::Tcp(stream)))); + Ok(()) + } + + fn disconnect(&mut self) -> Result<()> { + log::debug!("disconnecting..."); + if let Some(current_connection) = &self.current_connection { + let mut lock = current_connection.lock()?; + match lock.deref_mut() { + CurrentConnection::Tcp(tcp_stream) => { + let _ = tcp_stream.shutdown(Shutdown::Both); + } + CurrentConnection::Tls(tls_conn) => { + tls_conn.conn.send_close_notify(); + let _ = tls_conn.sock.shutdown(Shutdown::Both); + } + } + } + + Ok(()) + } +} + +impl ADBMessageTransport for TcpTransport { + fn read_message_with_timeout( + &mut self, + read_timeout: std::time::Duration, + ) -> Result { + let raw_connection_lock = self.get_current_connection()?; + let mut raw_connection = raw_connection_lock.lock()?; + + raw_connection.set_read_timeout(read_timeout)?; + + let mut data = [0; 24]; + let mut total_read = 0; + loop { + total_read += raw_connection.read(&mut data[total_read..])?; + if total_read == data.len() { + break; + } + } + + let header = ADBTransportMessageHeader::try_from(data)?; + + if header.data_length() != 0 { + let mut msg_data = vec![0_u8; header.data_length() as usize]; + let mut total_read = 0; + loop { + total_read += raw_connection.read(&mut msg_data[total_read..])?; + if total_read == msg_data.capacity() { + break; + } + } + + let message = ADBTransportMessage::from_header_and_payload(header, msg_data); + + // Check message integrity + if !message.check_message_integrity() { + return Err(RustADBError::InvalidIntegrity( + ADBTransportMessageHeader::compute_crc32(message.payload()), + message.header().data_crc32(), + )); + } + + return Ok(message); + } + + Ok(ADBTransportMessage::from_header_and_payload(header, vec![])) + } + + fn write_message_with_timeout( + &mut self, + message: ADBTransportMessage, + write_timeout: Duration, + ) -> Result<()> { + let message_bytes = message.header().as_bytes()?; + let raw_connection_lock = self.get_current_connection()?; + let mut raw_connection = raw_connection_lock.lock()?; + + raw_connection.set_write_timeout(write_timeout)?; + + let mut total_written = 0; + loop { + total_written += raw_connection.write(&message_bytes[total_written..])?; + if total_written == message_bytes.len() { + raw_connection.flush()?; + break; + } + } + + let payload = message.into_payload(); + if !payload.is_empty() { + let mut total_written = 0; + loop { + total_written += raw_connection.write(&payload[total_written..])?; + if total_written == payload.len() { + raw_connection.flush()?; + break; + } + } + } + + Ok(()) + } +} + +#[derive(Debug)] +struct NoCertificateVerification; + +impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::RSA_PKCS1_SHA1, + SignatureScheme::ECDSA_SHA1_Legacy, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + SignatureScheme::ED448, + ] + } +} diff --git a/adb_client/src/transports/traits/adb_message_transport.rs b/adb_client/src/transports/traits/adb_message_transport.rs new file mode 100644 index 0000000..74b8886 --- /dev/null +++ b/adb_client/src/transports/traits/adb_message_transport.rs @@ -0,0 +1,30 @@ +use std::time::Duration; + +use super::ADBTransport; +use crate::{device::ADBTransportMessage, Result}; + +const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(u64::MAX); +const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(2); + +/// Trait representing a transport able to read and write messages. +pub trait ADBMessageTransport: ADBTransport + Clone + Send + 'static { + /// Read a message using given timeout on the underlying transport + fn read_message_with_timeout(&mut self, read_timeout: Duration) -> Result; + + /// Read data to underlying connection, using default timeout + fn read_message(&mut self) -> Result { + self.read_message_with_timeout(DEFAULT_READ_TIMEOUT) + } + + /// Write a message using given timeout on the underlying transport + fn write_message_with_timeout( + &mut self, + message: ADBTransportMessage, + write_timeout: Duration, + ) -> Result<()>; + + /// Write data to underlying connection, using default timeout + fn write_message(&mut self, message: ADBTransportMessage) -> Result<()> { + self.write_message_with_timeout(message, DEFAULT_WRITE_TIMEOUT) + } +} diff --git a/adb_client/src/transports/traits/adb_transport.rs b/adb_client/src/transports/traits/adb_transport.rs new file mode 100644 index 0000000..f86cc4b --- /dev/null +++ b/adb_client/src/transports/traits/adb_transport.rs @@ -0,0 +1,10 @@ +use crate::Result; + +/// Trait representing a transport usable by ADB protocol. +pub trait ADBTransport { + /// Initializes the connection to this transport. + fn connect(&mut self) -> Result<()>; + + /// Shuts down the connection to this transport. + fn disconnect(&mut self) -> Result<()>; +} diff --git a/adb_client/src/transports/traits/mod.rs b/adb_client/src/transports/traits/mod.rs new file mode 100644 index 0000000..e655c81 --- /dev/null +++ b/adb_client/src/transports/traits/mod.rs @@ -0,0 +1,5 @@ +mod adb_message_transport; +mod adb_transport; + +pub use adb_message_transport::ADBMessageTransport; +pub use adb_transport::ADBTransport; diff --git a/adb_client/src/transports/transport_trait.rs b/adb_client/src/transports/transport_trait.rs deleted file mode 100644 index 3492424..0000000 --- a/adb_client/src/transports/transport_trait.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::Result; - -/// Trait representing a transport -pub trait ADBTransport { - /// Initializes the connection - fn connect(&mut self) -> Result<()>; - - /// Shuts down the connection - fn disconnect(&mut self) -> Result<()>; -} diff --git a/adb_client/src/transports/usb_transport.rs b/adb_client/src/transports/usb_transport.rs index 2258a5a..31d3895 100644 --- a/adb_client/src/transports/usb_transport.rs +++ b/adb_client/src/transports/usb_transport.rs @@ -4,9 +4,9 @@ use rusb::{ constants::LIBUSB_CLASS_VENDOR_SPEC, DeviceHandle, Direction, GlobalContext, TransferType, }; -use super::ADBTransport; +use super::{ADBMessageTransport, ADBTransport}; use crate::{ - usb::{ADBUsbMessage, ADBUsbMessageHeader, USBCommand}, + device::{ADBTransportMessage, ADBTransportMessageHeader, MessageCommand}, Result, RustADBError, }; @@ -16,9 +16,6 @@ struct Endpoint { address: u8, } -const MAX_READ_TIMEOUT: Duration = Duration::from_secs(u64::MAX); -const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(2); - /// Transport running on USB #[derive(Debug, Clone)] pub struct USBTransport { @@ -52,105 +49,6 @@ impl USBTransport { Ok(()) } - /// Write data to underlying connection, with default timeout - pub(crate) fn write_message(&mut self, message: ADBUsbMessage) -> Result<()> { - self.write_message_with_timeout(message, DEFAULT_WRITE_TIMEOUT) - } - - /// Write data to underlying connection - pub(crate) fn write_message_with_timeout( - &mut self, - message: ADBUsbMessage, - timeout: Duration, - ) -> Result<()> { - let endpoint = self.find_writable_endpoint()?; - let handle = self.get_raw_connection()?; - - if let Ok(true) = handle.kernel_driver_active(endpoint.iface) { - handle.detach_kernel_driver(endpoint.iface)?; - } - - Self::configure_endpoint(&handle, &endpoint)?; - - let message_bytes = message.header().as_bytes()?; - let mut total_written = 0; - loop { - total_written += - handle.write_bulk(endpoint.address, &message_bytes[total_written..], timeout)?; - if total_written == message_bytes.len() { - break; - } - } - - let payload = message.into_payload(); - let mut total_written = 0; - loop { - total_written += - handle.write_bulk(endpoint.address, &payload[total_written..], timeout)?; - if total_written == payload.len() { - break; - } - } - - Ok(()) - } - - /// Blocking method to read data from underlying connection. - pub(crate) fn read_message(&mut self) -> Result { - self.read_message_with_timeout(MAX_READ_TIMEOUT) - } - - /// Read data from underlying connection with given timeout. - pub(crate) fn read_message_with_timeout(&mut self, timeout: Duration) -> Result { - let endpoint = self.find_readable_endpoint()?; - let handle = self.get_raw_connection()?; - - if let Ok(true) = handle.kernel_driver_active(endpoint.iface) { - handle.detach_kernel_driver(endpoint.iface)?; - } - - Self::configure_endpoint(&handle, &endpoint)?; - - let mut data = [0; 24]; - let mut total_read = 0; - loop { - total_read += handle.read_bulk(endpoint.address, &mut data[total_read..], timeout)?; - if total_read == data.len() { - break; - } - } - - let header = ADBUsbMessageHeader::try_from(data)?; - - log::trace!("received header {header:?}"); - - if header.data_length() != 0 { - let mut msg_data = vec![0_u8; header.data_length() as usize]; - let mut total_read = 0; - loop { - total_read += - handle.read_bulk(endpoint.address, &mut msg_data[total_read..], timeout)?; - if total_read == msg_data.capacity() { - break; - } - } - - let message = ADBUsbMessage::from_header_and_payload(header, msg_data); - - // Check message integrity - if !message.check_message_integrity() { - return Err(RustADBError::InvalidIntegrity( - ADBUsbMessageHeader::compute_crc32(message.payload()), - message.header().data_crc32(), - )); - } - - return Ok(message); - } - - Ok(ADBUsbMessage::from_header_and_payload(header, vec![])) - } - fn find_readable_endpoint(&self) -> Result { let handle = self.get_raw_connection()?; for n in 0..handle.device().device_descriptor()?.num_configurations() { @@ -233,7 +131,98 @@ impl ADBTransport for USBTransport { } fn disconnect(&mut self) -> crate::Result<()> { - let message = ADBUsbMessage::new(USBCommand::Clse, 0, 0, "".into()); + let message = ADBTransportMessage::new(MessageCommand::Clse, 0, 0, "".into()); self.write_message(message) } } + +impl ADBMessageTransport for USBTransport { + fn write_message_with_timeout( + &mut self, + message: ADBTransportMessage, + timeout: Duration, + ) -> Result<()> { + let endpoint = self.find_writable_endpoint()?; + let handle = self.get_raw_connection()?; + + if let Ok(true) = handle.kernel_driver_active(endpoint.iface) { + handle.detach_kernel_driver(endpoint.iface)?; + } + + Self::configure_endpoint(&handle, &endpoint)?; + + let message_bytes = message.header().as_bytes()?; + let mut total_written = 0; + loop { + total_written += + handle.write_bulk(endpoint.address, &message_bytes[total_written..], timeout)?; + if total_written == message_bytes.len() { + break; + } + } + + let payload = message.into_payload(); + if !payload.is_empty() { + let mut total_written = 0; + loop { + total_written += + handle.write_bulk(endpoint.address, &payload[total_written..], timeout)?; + if total_written == payload.len() { + break; + } + } + } + + Ok(()) + } + + fn read_message_with_timeout(&mut self, timeout: Duration) -> Result { + let endpoint = self.find_readable_endpoint()?; + let handle = self.get_raw_connection()?; + + if let Ok(true) = handle.kernel_driver_active(endpoint.iface) { + handle.detach_kernel_driver(endpoint.iface)?; + } + + Self::configure_endpoint(&handle, &endpoint)?; + + let mut data = [0; 24]; + let mut total_read = 0; + loop { + total_read += handle.read_bulk(endpoint.address, &mut data[total_read..], timeout)?; + if total_read == data.len() { + break; + } + } + + let header = ADBTransportMessageHeader::try_from(data)?; + + log::trace!("received header {header:?}"); + + if header.data_length() != 0 { + let mut msg_data = vec![0_u8; header.data_length() as usize]; + let mut total_read = 0; + loop { + total_read += + handle.read_bulk(endpoint.address, &mut msg_data[total_read..], timeout)?; + if total_read == msg_data.capacity() { + break; + } + } + + let message = ADBTransportMessage::from_header_and_payload(header, msg_data); + + // Check message integrity + if !message.check_message_integrity() { + return Err(RustADBError::InvalidIntegrity( + ADBTransportMessageHeader::compute_crc32(message.payload()), + message.header().data_crc32(), + )); + } + + return Ok(message); + } + + Ok(ADBTransportMessage::from_header_and_payload(header, vec![])) + } +} diff --git a/adb_client/src/usb/adb_usb_device.rs b/adb_client/src/usb/adb_usb_device.rs deleted file mode 100644 index dec983f..0000000 --- a/adb_client/src/usb/adb_usb_device.rs +++ /dev/null @@ -1,447 +0,0 @@ -use byteorder::ReadBytesExt; -use rand::Rng; -use rusb::Device; -use rusb::DeviceDescriptor; -use rusb::UsbContext; -use std::fs::read_to_string; -use std::io::Cursor; -use std::io::Read; -use std::io::Seek; -use std::path::Path; -use std::path::PathBuf; -use std::time::Duration; - -use byteorder::LittleEndian; - -use super::{ADBRsaKey, ADBUsbMessage}; -use crate::constants::BUFFER_SIZE; -use crate::models::AdbStatResponse; -use crate::usb::adb_usb_message::{AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN}; -use crate::{ - usb::usb_commands::{USBCommand, USBSubcommand}, - ADBTransport, Result, RustADBError, USBTransport, -}; - -/// Represent a device reached directly over USB -#[derive(Debug)] -pub struct ADBUSBDevice { - private_key: ADBRsaKey, - pub(crate) transport: USBTransport, -} - -fn read_adb_private_key>(private_key_path: P) -> Result> { - read_to_string(private_key_path.as_ref()) - .map_err(RustADBError::from) - .map(|pk| match ADBRsaKey::new_from_pkcs8(&pk) { - Ok(pk) => Some(pk), - Err(e) => { - log::error!("Error while create RSA private key: {e}"); - None - } - }) -} -/// Search for adb devices with known interface class and subclass values -fn search_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())); - } - } - - 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 {:04x}:{:04x} and {:04x}:{:04x}", - vid1, pid1, vid2, pid2 - ))), - } -} - -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; - }; - 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 == ADB_PROTOCOL - && ((class == ADB_CLASS && subcl == ADB_SUBCLASS) - || (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS)) - { - return true; - } - } - } - } - 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 { - 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 - pub fn new_with_custom_private_key( - vendor_id: u16, - product_id: u16, - private_key_path: PathBuf, - ) -> Result { - let private_key = match read_adb_private_key(private_key_path)? { - Some(pk) => pk, - None => ADBRsaKey::new_random()?, - }; - - 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 { - 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()?; - - let message = ADBUsbMessage::new( - USBCommand::Cnxn, - 0x01000000, - 1048576, - format!("host::{}\0", env!("CARGO_PKG_NAME")) - .as_bytes() - .to_vec(), - ); - - self.transport.write_message(message)?; - - let message = self.transport.read_message()?; - - // At this point, we should have received either: - // - an AUTH message with arg0 == 1 - // - a CNXN message - let auth_message = match message.header().command() { - USBCommand::Auth if message.header().arg0() == AUTH_TOKEN => message, - USBCommand::Auth if message.header().arg0() != AUTH_TOKEN => { - return Err(RustADBError::ADBRequestFailed( - "Received AUTH message with type != 1".into(), - )) - } - c => { - return Err(RustADBError::ADBRequestFailed(format!( - "Wrong command received {}", - c - ))) - } - }; - - let sign = self.private_key.sign(auth_message.into_payload())?; - - let message = ADBUsbMessage::new(USBCommand::Auth, AUTH_SIGNATURE, 0, sign); - - self.transport.write_message(message)?; - - let received_response = self.transport.read_message()?; - - if received_response.header().command() == USBCommand::Cnxn { - log::info!( - "Authentication OK, device info {}", - String::from_utf8(received_response.into_payload())? - ); - return Ok(()); - } - - let mut pubkey = self.private_key.android_pubkey_encode()?.into_bytes(); - pubkey.push(b'\0'); - - let message = ADBUsbMessage::new(USBCommand::Auth, AUTH_RSAPUBLICKEY, 0, pubkey); - - self.transport.write_message(message)?; - - let response = self - .transport - .read_message_with_timeout(Duration::from_secs(10))?; - - match response.header().command() { - USBCommand::Cnxn => log::info!( - "Authentication OK, device info {}", - String::from_utf8(response.into_payload())? - ), - _ => { - return Err(RustADBError::ADBRequestFailed(format!( - "wrong response {}", - response.header().command() - ))) - } - } - - Ok(()) - } - /// Receive a message and acknowledge it by replying with an `OKAY` command - pub(crate) fn recv_and_reply_okay( - &mut self, - local_id: u32, - remote_id: u32, - ) -> Result { - let message = self.transport.read_message()?; - self.transport.write_message(ADBUsbMessage::new( - USBCommand::Okay, - local_id, - remote_id, - "".into(), - ))?; - Ok(message) - } - - /// Expect a message with an `OKAY` command after sending a message. - pub(crate) fn send_and_expect_okay(&mut self, message: ADBUsbMessage) -> Result { - self.transport.write_message(message)?; - let message = self.transport.read_message()?; - let received_command = message.header().command(); - if received_command != USBCommand::Okay { - return Err(RustADBError::ADBRequestFailed(format!( - "expected command OKAY after message, got {}", - received_command - ))); - } - Ok(message) - } - - pub(crate) fn recv_file( - &mut self, - local_id: u32, - remote_id: u32, - mut output: W, - ) -> std::result::Result<(), RustADBError> { - let mut len: Option = None; - loop { - let payload = self - .recv_and_reply_okay(local_id, remote_id)? - .into_payload(); - let mut rdr = Cursor::new(&payload); - while rdr.position() != payload.len() as u64 { - match len.take() { - Some(0) | None => { - rdr.seek_relative(4)?; - len.replace(rdr.read_u32::()? as u64); - } - Some(length) => { - log::debug!("len = {length}"); - let remaining_bytes = payload.len() as u64 - rdr.position(); - log::debug!( - "payload length {} - reader_position {} = {remaining_bytes}", - payload.len(), - rdr.position() - ); - if length < remaining_bytes { - let read = std::io::copy(&mut rdr.by_ref().take(length), &mut output)?; - log::debug!( - "expected to read {length} bytes, actually read {read} bytes" - ); - } else { - let read = std::io::copy(&mut rdr.take(remaining_bytes), &mut output)?; - len.replace(length - remaining_bytes); - log::debug!("expected to read {remaining_bytes} bytes, actually read {read} bytes"); - // this payload is exhausted - break; - } - } - } - } - if Cursor::new(&payload[(payload.len() - 8)..(payload.len() - 4)]) - .read_u32::()? - == USBSubcommand::Done as u32 - { - break; - } - } - Ok(()) - } - - pub(crate) fn push_file( - &mut self, - local_id: u32, - remote_id: u32, - mut reader: R, - ) -> std::result::Result<(), RustADBError> { - let mut buffer = [0; BUFFER_SIZE]; - let amount_read = reader.read(&mut buffer)?; - let subcommand_data = USBSubcommand::Data.with_arg(amount_read as u32); - - let mut serialized_message = - bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?; - serialized_message.append(&mut buffer[..amount_read].to_vec()); - - let message = - ADBUsbMessage::new(USBCommand::Write, local_id, remote_id, serialized_message); - - self.send_and_expect_okay(message)?; - - loop { - let mut buffer = [0; BUFFER_SIZE]; - - match reader.read(&mut buffer) { - Ok(0) => { - // Currently file mtime is not forwarded - let subcommand_data = USBSubcommand::Done.with_arg(0); - - let serialized_message = bincode::serialize(&subcommand_data) - .map_err(|_e| RustADBError::ConversionError)?; - - let message = ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - serialized_message, - ); - - self.send_and_expect_okay(message)?; - - // Command should end with a Write => Okay - let received = self.transport.read_message()?; - match received.header().command() { - USBCommand::Write => return Ok(()), - c => { - return Err(RustADBError::ADBRequestFailed(format!( - "Wrong command received {}", - c - ))) - } - } - } - Ok(size) => { - let subcommand_data = USBSubcommand::Data.with_arg(size as u32); - - let mut serialized_message = bincode::serialize(&subcommand_data) - .map_err(|_e| RustADBError::ConversionError)?; - serialized_message.append(&mut buffer[..size].to_vec()); - - let message = ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - serialized_message, - ); - - self.send_and_expect_okay(message)?; - } - Err(e) => { - return Err(RustADBError::IOError(e)); - } - } - } - } - - pub(crate) fn begin_synchronization(&mut self) -> Result<(u32, u32)> { - let sync_directive = "sync:\0"; - - let mut rng = rand::thread_rng(); - let message = ADBUsbMessage::new( - USBCommand::Open, - rng.gen(), /* Our 'local-id' */ - 0, - sync_directive.into(), - ); - let message = self.send_and_expect_okay(message)?; - let local_id = message.header().arg1(); - let remote_id = message.header().arg0(); - Ok((local_id, remote_id)) - } - - pub(crate) fn stat_with_explicit_ids( - &mut self, - remote_path: &str, - local_id: u32, - remote_id: u32, - ) -> Result { - let stat_buffer = USBSubcommand::Stat.with_arg(remote_path.len() as u32); - let message = ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - bincode::serialize(&stat_buffer).map_err(|_e| RustADBError::ConversionError)?, - ); - self.send_and_expect_okay(message)?; - self.send_and_expect_okay(ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - remote_path.into(), - ))?; - let response = self.transport.read_message()?; - // Skip first 4 bytes as this is the literal "STAT". - // Interesting part starts right after - bincode::deserialize(&response.into_payload()[4..]) - .map_err(|_e| RustADBError::ConversionError) - } - - pub(crate) fn end_transaction(&mut self, local_id: u32, remote_id: u32) -> Result<()> { - let quit_buffer = USBSubcommand::Quit.with_arg(0u32); - self.send_and_expect_okay(ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - bincode::serialize(&quit_buffer).map_err(|_e| RustADBError::ConversionError)?, - ))?; - let _discard_close = self.transport.read_message()?; - Ok(()) - } -} - -impl Drop for ADBUSBDevice { - fn drop(&mut self) { - // Best effort here - let _ = self.transport.disconnect(); - } -} diff --git a/adb_client/src/usb/adb_usb_device_commands.rs b/adb_client/src/usb/adb_usb_device_commands.rs deleted file mode 100644 index 36e9c80..0000000 --- a/adb_client/src/usb/adb_usb_device_commands.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::{ - models::AdbStatResponse, - usb::{ADBUsbMessage, USBCommand, USBSubcommand}, - utils::check_extension_is_apk, - ADBDeviceExt, ADBUSBDevice, RebootType, Result, RustADBError, -}; -use rand::Rng; -use std::{ - fs::File, - io::{Read, Write}, -}; - -use super::{USBShellWriter, USBWriter}; - -impl ADBDeviceExt for ADBUSBDevice { - fn shell_command( - &mut self, - command: impl IntoIterator, - mut output: W, - ) -> Result<()> { - let message = ADBUsbMessage::new( - USBCommand::Open, - 1, - 0, - format!( - "shell:{}\0", - command - .into_iter() - .map(|v| v.to_string()) - .collect::>() - .join(" "), - ) - .as_bytes() - .to_vec(), - ); - self.transport.write_message(message)?; - - let response = self.transport.read_message()?; - if response.header().command() != USBCommand::Okay { - return Err(RustADBError::ADBRequestFailed(format!( - "wrong command {}", - response.header().command() - ))); - } - - loop { - let response = self.transport.read_message()?; - if response.header().command() != USBCommand::Write { - break; - } - - output.write_all(&response.into_payload())?; - } - - Ok(()) - } - - fn shell( - &mut self, - mut reader: R, - mut writer: W, - ) -> Result<()> { - let sync_directive = "shell:\0"; - - let mut rng = rand::thread_rng(); - let message = ADBUsbMessage::new( - USBCommand::Open, - rng.gen(), /* Our 'local-id' */ - 0, - sync_directive.into(), - ); - let message = self.send_and_expect_okay(message)?; - let local_id = message.header().arg1(); - let remote_id = message.header().arg0(); - - let mut transport = self.transport.clone(); - - // Reading thread, reads response from adbd - std::thread::spawn(move || -> Result<()> { - loop { - let message = transport.read_message()?; - - // Acknowledge for more data - let response = ADBUsbMessage::new(USBCommand::Okay, local_id, remote_id, vec![]); - transport.write_message(response)?; - - match message.header().command() { - USBCommand::Write => {} - USBCommand::Okay => continue, - _ => return Err(RustADBError::ADBShellNotSupported), - } - - writer.write_all(&message.into_payload())?; - writer.flush()?; - } - }); - - let mut shell_writer = USBShellWriter::new(self.transport.clone(), local_id, remote_id); - - // Read from given reader (that could be stdin e.g), and write content to device adbd - if let Err(e) = std::io::copy(&mut reader, &mut shell_writer) { - match e.kind() { - std::io::ErrorKind::BrokenPipe => return Ok(()), - _ => return Err(RustADBError::IOError(e)), - } - } - - Ok(()) - } - - fn stat(&mut self, remote_path: &str) -> Result { - let (local_id, remote_id) = self.begin_synchronization()?; - let adb_stat_response = self.stat_with_explicit_ids(remote_path, local_id, remote_id)?; - self.end_transaction(local_id, remote_id)?; - Ok(adb_stat_response) - } - - fn pull, W: Write>(&mut self, source: A, output: W) -> Result<()> { - let (local_id, remote_id) = self.begin_synchronization()?; - let source = source.as_ref(); - - let adb_stat_response = self.stat_with_explicit_ids(source, local_id, remote_id)?; - self.transport.write_message(ADBUsbMessage::new( - USBCommand::Okay, - local_id, - remote_id, - "".into(), - ))?; - - log::debug!("{:?}", adb_stat_response); - if adb_stat_response.file_perm == 0 { - return Err(RustADBError::UnknownResponseType( - "mode is 0: source file does not exist".to_string(), - )); - } - - let recv_buffer = USBSubcommand::Recv.with_arg(source.len() as u32); - let recv_buffer = - bincode::serialize(&recv_buffer).map_err(|_e| RustADBError::ConversionError)?; - self.send_and_expect_okay(ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - recv_buffer, - ))?; - self.send_and_expect_okay(ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - source.into(), - ))?; - - self.recv_file(local_id, remote_id, output)?; - self.end_transaction(local_id, remote_id)?; - Ok(()) - } - - fn push>(&mut self, stream: R, path: A) -> Result<()> { - let (local_id, remote_id) = self.begin_synchronization()?; - - let path_header = format!("{},0777", path.as_ref()); - - let send_buffer = USBSubcommand::Send.with_arg(path_header.len() as u32); - let mut send_buffer = - bincode::serialize(&send_buffer).map_err(|_e| RustADBError::ConversionError)?; - send_buffer.append(&mut path_header.as_bytes().to_vec()); - - self.send_and_expect_okay(ADBUsbMessage::new( - USBCommand::Write, - local_id, - remote_id, - send_buffer, - ))?; - - self.push_file(local_id, remote_id, stream)?; - - self.end_transaction(local_id, remote_id)?; - - Ok(()) - } - - fn reboot(&mut self, reboot_type: RebootType) -> Result<()> { - let mut rng = rand::thread_rng(); - - let message = ADBUsbMessage::new( - USBCommand::Open, - rng.gen(), // Our 'local-id' - 0, - format!("reboot:{}\0", reboot_type).as_bytes().to_vec(), - ); - self.transport.write_message(message)?; - - let message = self.transport.read_message()?; - - if message.header().command() != USBCommand::Okay { - return Err(RustADBError::ADBShellNotSupported); - } - - Ok(()) - } - - fn install>(&mut self, apk_path: P) -> Result<()> { - let mut apk_file = File::open(&apk_path)?; - - check_extension_is_apk(&apk_path)?; - - let file_size = apk_file.metadata()?.len(); - - let mut rng = rand::thread_rng(); - - let local_id = rng.gen(); - - let message = ADBUsbMessage::new( - USBCommand::Open, - local_id, - 0, - format!("exec:cmd package 'install' -S {}\0", file_size) - .as_bytes() - .to_vec(), - ); - self.transport.write_message(message)?; - - let response = self.transport.read_message()?; - let remote_id = response.header().arg0(); - - let mut writer = USBWriter::new(self.transport.clone(), local_id, remote_id); - - std::io::copy(&mut apk_file, &mut writer)?; - - let final_status = self.transport.read_message()?; - - match final_status.into_payload().as_slice() { - b"Success\n" => { - log::info!( - "APK file {} successfully installed", - apk_path.as_ref().display() - ); - Ok(()) - } - d => Err(crate::RustADBError::ADBRequestFailed(String::from_utf8( - d.to_vec(), - )?)), - } - } -} diff --git a/adb_client/src/usb/mod.rs b/adb_client/src/usb/mod.rs deleted file mode 100644 index a23a998..0000000 --- a/adb_client/src/usb/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod adb_rsa_key; -mod adb_usb_device; -mod adb_usb_device_commands; -mod adb_usb_message; -mod usb_commands; -mod usb_shell_writer; -mod usb_writer; - -pub use adb_rsa_key::ADBRsaKey; -pub use adb_usb_device::ADBUSBDevice; -pub use adb_usb_message::{ADBUsbMessage, ADBUsbMessageHeader}; -pub use usb_commands::{USBCommand, USBSubcommand}; -pub use usb_shell_writer::USBShellWriter; -pub use usb_writer::USBWriter;