WIP ADB shell
This commit is contained in:
@@ -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 }
|
||||
|
||||
13
README.md
13
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 :
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::str::FromStr;
|
||||
|
||||
use crate::RustADBError;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum AdbRequestStatus {
|
||||
Okay,
|
||||
Fail,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<()>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user