WIP ADB shell

This commit is contained in:
LIAUD Corentin
2022-01-12 21:45:49 +01:00
parent c0738ca9cc
commit 418a01ec93
8 changed files with 182 additions and 58 deletions

View File

@@ -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 }

View File

@@ -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 :

View File

@@ -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<String> },
}
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(())

View File

@@ -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<u32> {
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<String>) -> 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));
}
}
}
}

View File

@@ -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(),
}
}
}

View File

@@ -2,6 +2,7 @@ use std::str::FromStr;
use crate::RustADBError;
#[derive(PartialEq)]
pub enum AdbRequestStatus {
Okay,
Fail,

View File

@@ -23,7 +23,8 @@ impl TryFrom<Vec<u8>> for Device {
// TODO: Prevent regex compilation every call to try_from()
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
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 {

View File

@@ -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<String>) -> Result<()>;
/// TODO
fn shell(&self) -> Result<()>;
}