From 418a01ec93a29eb911a6956b405130771bb602d9 Mon Sep 17 00:00:00 2001 From: LIAUD Corentin Date: Wed, 12 Jan 2022 21:45:49 +0100 Subject: [PATCH] WIP ADB shell --- Cargo.toml | 3 +- README.md | 13 +++ bin/adb_client.rs | 9 ++ src/adb_tcp_connexion.rs | 168 +++++++++++++++++++---------- src/models/adb_command.rs | 37 +++++++ src/models/adb_request_status.rs | 1 + src/models/device.rs | 3 +- src/traits/adb_command_provider.rs | 6 ++ 8 files changed, 182 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de0e171..e4a348f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "adb_client" -version = "0.1.3" +version = "0.1.4" description = "Rust ADB (Android Debug Bridge) client library" keywords = ["adb", "android"] readme = "README.md" @@ -21,6 +21,7 @@ required-features = ["adbclient"] [dependencies] thiserror = { version = "1.0.30", default_features = false } regex = { version = "1.5.4", default_features = false, features = ["std", "perf", "unicode"] } +lazy_static = { version = "1.4.0", default_features = false } # Features needed by adb_client binary clap = { version = "3.0.5", default_features = false, features = ["std", "derive"], optional = true } diff --git a/README.md b/README.md index 4d14b08..2190efc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,19 @@ Simply add this to your `Cargo.toml`: adb_client = "*" ``` +To launch a command on host device : +```rust +let connexion = AdbTcpConnexion::new(); +connexion.shell_command("df -h"); +``` + +To get available ADB devices : +```rust +let connexion = AdbTcpConnexion::new(); +connexion.devices(); +``` + + ## Rust binary You can install the lightweight adb binary by running the following command : diff --git a/bin/adb_client.rs b/bin/adb_client.rs index 2e040ae..5dd4e57 100644 --- a/bin/adb_client.rs +++ b/bin/adb_client.rs @@ -29,6 +29,8 @@ enum Command { }, /// Tracks new devices showing up TrackDevices, + /// Run 'command' in a shell on the device, and return its output and error streams. + Shell { command: Vec }, } fn main() -> Result<()> { @@ -66,6 +68,13 @@ fn main() -> Result<()> { println!("Live list of devices attached"); connexion.track_devices(callback)?; } + Command::Shell { command } => { + if command.is_empty() { + connexion.shell()?; + } else { + connexion.shell_command(command)?; + } + } } Ok(()) diff --git a/src/adb_tcp_connexion.rs b/src/adb_tcp_connexion.rs index e56a519..345915d 100644 --- a/src/adb_tcp_connexion.rs +++ b/src/adb_tcp_connexion.rs @@ -3,6 +3,7 @@ use std::{ net::{Ipv4Addr, SocketAddrV4, TcpStream}, str, str::FromStr, + sync::Arc, }; use crate::{ @@ -42,28 +43,45 @@ impl AdbTcpConnexion { Self::send_adb_request(&mut tcp_stream, adb_command)?; + if with_response { + let length = Self::get_body_length(&mut tcp_stream)?; + let mut body = vec![ + 0; + length + .try_into() + .map_err(|_| RustADBError::ConvertionError)? + ]; + if length > 0 { + tcp_stream.read_exact(&mut body)?; + } + + Ok(body) + } else { + Ok(vec![]) + } + } + + /// Sends the given [AdbCommand] to ADB server, and checks that the request has been taken in consideration. + /// If an error occured, a [RustADBError] is returned with the response error string. + fn send_adb_request(tcp_stream: &mut TcpStream, command: AdbCommand) -> Result<()> { + let adb_command_string = command.to_string(); + + let adb_request = format!( + "{}{}", + format!("{:04x}", adb_command_string.len()), + adb_command_string + ); + + tcp_stream.write_all(adb_request.as_bytes())?; + + // Reads returned status code from ADB server let mut request_status = [0; 4]; tcp_stream.read_exact(&mut request_status)?; match AdbRequestStatus::from_str(str::from_utf8(&request_status.to_vec())?)? { - AdbRequestStatus::Okay if with_response => { - let length = Self::get_body_length(&mut tcp_stream)?; - - let mut body = vec![ - 0; - length - .try_into() - .map_err(|_| RustADBError::ConvertionError)? - ]; - if length > 0 { - tcp_stream.read_exact(&mut body)?; - } - - Ok(body) - } - AdbRequestStatus::Okay => Ok(vec![]), AdbRequestStatus::Fail => { - let length = Self::get_body_length(&mut tcp_stream)?; + // We can keep reading to get further details + let length = Self::get_body_length(tcp_stream)?; let mut body = vec![ 0; @@ -77,23 +95,10 @@ impl AdbTcpConnexion { Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?)) } + AdbRequestStatus::Okay => Ok(()), } } - fn send_adb_request(tcp_stream: &mut TcpStream, command: AdbCommand) -> Result<()> { - let adb_command_string = command.to_string(); - - let adb_request = format!( - "{}{}", - format!("{:04x}", adb_command_string.len()), - adb_command_string - ); - - tcp_stream.write_all(adb_request.as_bytes())?; - - Ok(()) - } - fn get_body_length(tcp_stream: &mut TcpStream) -> Result { let mut length = [0; 4]; tcp_stream.read_exact(&mut length)?; @@ -162,39 +167,90 @@ impl AdbCommandProvider for AdbTcpConnexion { Self::send_adb_request(&mut tcp_stream, AdbCommand::TrackDevices)?; - let mut request_status = [0; 4]; - tcp_stream.read_exact(&mut request_status)?; - - match AdbRequestStatus::from_str(str::from_utf8(&request_status.to_vec())?)? { - AdbRequestStatus::Okay => loop { - let length = Self::get_body_length(&mut tcp_stream)?; - - if length > 0 { - let mut body = vec![ - 0; - length - .try_into() - .map_err(|_| RustADBError::ConvertionError)? - ]; - tcp_stream.read_exact(&mut body)?; - - callback(Device::try_from(body)?)?; - } - }, - AdbRequestStatus::Fail => { - let length = Self::get_body_length(&mut tcp_stream)?; + loop { + let length = Self::get_body_length(&mut tcp_stream)?; + if length > 0 { let mut body = vec![ 0; length .try_into() .map_err(|_| RustADBError::ConvertionError)? ]; - if length > 0 { - tcp_stream.read_exact(&mut body)?; - } + tcp_stream.read_exact(&mut body)?; - Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?)) + callback(Device::try_from(body)?)?; + } + } + } + + fn transport_any(&self) -> Result<()> { + self.proxy_connexion(AdbCommand::TransportAny, false) + .map(|_| ()) + } + + fn shell_command(&self, command: Vec) -> Result<()> { + let mut tcp_stream = TcpStream::connect(self.socket_addr)?; + + Self::send_adb_request(&mut tcp_stream, AdbCommand::TransportAny)?; + + Self::send_adb_request(&mut tcp_stream, AdbCommand::ShellCommand(command.join(" ")))?; + + let buffer_size = 512; + loop { + let mut buffer = vec![0; buffer_size]; + match tcp_stream.read(&mut buffer) { + Ok(size) => { + if size == 0 { + return Ok(()); + } else { + print!("{}", String::from_utf8(buffer.to_vec())?); + std::io::stdout().flush()?; + } + } + Err(e) => { + return Err(RustADBError::IOError(e)); + } + } + } + } + + fn shell(&self) -> Result<()> { + let mut tcp_stream = TcpStream::connect(self.socket_addr)?; + + Self::send_adb_request(&mut tcp_stream, AdbCommand::TransportAny)?; + + Self::send_adb_request(&mut tcp_stream, AdbCommand::Shell)?; + + // Spawns 2 threads + // - Listener + // - stdin Reader + + let reader = std::thread::spawn(move || -> Result<()> { + let mut buf = [0; 1]; + loop { + std::io::stdin().read(&mut buf)?; + // tcp_stream.write(&buf)?; + } + }); + + // TODO: join thread and return if result is err + + let buffer_size = 512; + loop { + let mut buffer = vec![0; buffer_size]; + match tcp_stream.read(&mut buffer) { + Ok(size) => { + if size == 0 { + return Ok(()); + } else { + print!("{}", String::from_utf8(buffer.to_vec())?); + std::io::stdout().flush()?; + } + } + Err(e) => { + return Err(RustADBError::IOError(e)); + } } } } diff --git a/src/models/adb_command.rs b/src/models/adb_command.rs index d344a3a..0c527f2 100644 --- a/src/models/adb_command.rs +++ b/src/models/adb_command.rs @@ -4,6 +4,40 @@ pub enum AdbCommand { Devices, DevicesLong, TrackDevices, + // TODO: NOT IMPLEMENTED YET + // Emulator(u16), + // Transport(String), + // TransportUSB, + // TransportLocal, + TransportAny, + // Serial((String, String)), + // USB(String), + // Local(String), + // Request(String), + // GetProduct(String), + // GetSerialNo(String), + // GetDevPath(String), + // GetState(String), + // Forward((String, String, String)), + // ForwardNoRebind((String, String, String)), + // KillForward((String, String)), + // KillForwardAll(String), + // ListForward(String), + ShellCommand(String), + Shell, + // Remount, + // DevPath(String), + // Tcp(u16), + // Tcp((u16, String)), + // Local(String), + // LocalReserved(String), + // LocalAbstract(String), + // LocalFileSystem(String), + // FrameBuffer, + // JDWP(u32), + // TrackJDWP, + // Sync, + // Reverse(String) } impl ToString for AdbCommand { @@ -14,6 +48,9 @@ impl ToString for AdbCommand { AdbCommand::Devices => "host:devices".into(), AdbCommand::DevicesLong => "host:devices-l".into(), AdbCommand::TrackDevices => "host:track-devices".into(), + AdbCommand::TransportAny => "host:transport-any".into(), + AdbCommand::ShellCommand(command) => format!("shell:{}", command), + AdbCommand::Shell => "shell:".into(), } } } diff --git a/src/models/adb_request_status.rs b/src/models/adb_request_status.rs index ef356c5..6828f08 100644 --- a/src/models/adb_request_status.rs +++ b/src/models/adb_request_status.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use crate::RustADBError; +#[derive(PartialEq)] pub enum AdbRequestStatus { Okay, Fail, diff --git a/src/models/device.rs b/src/models/device.rs index e7edbe0..ca2c4c2 100644 --- a/src/models/device.rs +++ b/src/models/device.rs @@ -23,7 +23,8 @@ impl TryFrom> for Device { // TODO: Prevent regex compilation every call to try_from() fn try_from(value: Vec) -> Result { - let parse_regex = Regex::new("^(\\w+)\t(\\w+)$")?; + // Optional final '\n' is used to match TrackDevices inputs + let parse_regex = Regex::new("^(\\w+)\t(\\w+)\n?$")?; let groups = parse_regex.captures(&value).unwrap(); Ok(Device { diff --git a/src/traits/adb_command_provider.rs b/src/traits/adb_command_provider.rs index bf4b452..66afe70 100644 --- a/src/traits/adb_command_provider.rs +++ b/src/traits/adb_command_provider.rs @@ -15,4 +15,10 @@ pub trait AdbCommandProvider { fn kill(&self) -> Result<()>; /// Tracks new devices showing up. fn track_devices(&self, callback: fn(Device) -> Result<()>) -> Result<()>; + /// Asks ADB server to switch the connection to either the device or emulator connect to/running on the host. Will fail if there is more than one such device/emulator available. + fn transport_any(&self) -> Result<()>; + /// Runs 'command' in a shell on the device, and return its output and error streams. + fn shell_command(&self, command: Vec) -> Result<()>; + /// TODO + fn shell(&self) -> Result<()>; }