Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f46c996095 | ||
|
|
6fe4905bb7 | ||
|
|
2a551182ec | ||
|
|
3c5efb2dae | ||
|
|
11d8621934 | ||
|
|
787b7b02fd | ||
|
|
b9b5c57f9d | ||
|
|
84431f169f | ||
|
|
08e9f4f512 |
12
Cargo.toml
12
Cargo.toml
@@ -6,7 +6,7 @@ license = "MIT"
|
|||||||
name = "adb_client"
|
name = "adb_client"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/cocool97/adb_client"
|
repository = "https://github.com/cocool97/adb_client"
|
||||||
version = "1.0.1"
|
version = "1.0.3"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "adb_client"
|
name = "adb_client"
|
||||||
@@ -18,13 +18,15 @@ path = "examples/adb_cli.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = { version = "1.5.0" }
|
byteorder = { version = "1.5.0" }
|
||||||
chrono = { version = "0.4.37" }
|
chrono = { version = "0.4.38" }
|
||||||
lazy_static = { version = "1.4.0" }
|
lazy_static = { version = "1.5.0" }
|
||||||
regex = { version = "1.10.4", features = ["perf", "std", "unicode"] }
|
mio = { version = "1.0.0", features = ["os-ext", "os-poll"] }
|
||||||
|
regex = { version = "1.10.5", features = ["perf", "std", "unicode"] }
|
||||||
termios = { version = "0.3.3" }
|
termios = { version = "0.3.3" }
|
||||||
thiserror = { version = "1.0.58" }
|
thiserror = { version = "1.0.61" }
|
||||||
|
|
||||||
## Binary-only dependencies
|
## Binary-only dependencies
|
||||||
## Marked as optional so that lib users do not depend on them
|
## Marked as optional so that lib users do not depend on them
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = { version = "4.5.4", features = ["derive"] }
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
|
rand = { version = "0.8.5" }
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use adb_client::AdbTcpConnection;
|
|||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
||||||
connection.shell_command(None, vec!["df", "-h"]);
|
connection.shell_command(None, ["df", "-h"]);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get available ADB devices
|
### Get available ADB devices
|
||||||
@@ -47,8 +47,8 @@ use std::fs::File;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
||||||
let mut input = File::open(Path::new(&filename)).unwrap();
|
let mut input = File::open(Path::new("/tmp")).unwrap();
|
||||||
connection.send(None, &mut input, &path)?;
|
connection.send::<&str,&str>(None, &mut input, "/data/local/tmp");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rust binary
|
## Rust binary
|
||||||
|
|||||||
@@ -111,16 +111,16 @@ fn main() -> Result<(), RustADBError> {
|
|||||||
}
|
}
|
||||||
Command::Pull { path, filename } => {
|
Command::Pull { path, filename } => {
|
||||||
let mut output = File::create(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
let mut output = File::create(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
||||||
connection.recv(opt.serial, &path, &mut output)?;
|
connection.recv(opt.serial.as_ref(), &path, &mut output)?;
|
||||||
println!("Downloaded {path} as {filename}");
|
println!("Downloaded {path} as {filename}");
|
||||||
}
|
}
|
||||||
Command::Push { filename, path } => {
|
Command::Push { filename, path } => {
|
||||||
let mut input = File::open(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
let mut input = File::open(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
||||||
connection.send(opt.serial, &mut input, &path)?;
|
connection.send(opt.serial.as_ref(), &mut input, &path)?;
|
||||||
println!("Uploaded {filename} to {path}");
|
println!("Uploaded {filename} to {path}");
|
||||||
}
|
}
|
||||||
Command::List { path } => {
|
Command::List { path } => {
|
||||||
connection.list(opt.serial, path)?;
|
connection.list(opt.serial.as_ref(), path)?;
|
||||||
}
|
}
|
||||||
Command::Stat { path } => {
|
Command::Stat { path } => {
|
||||||
let stat_response = connection.stat(opt.serial, path)?;
|
let stat_response = connection.stat(opt.serial, path)?;
|
||||||
@@ -128,20 +128,20 @@ fn main() -> Result<(), RustADBError> {
|
|||||||
}
|
}
|
||||||
Command::Shell { command } => {
|
Command::Shell { command } => {
|
||||||
if command.is_empty() {
|
if command.is_empty() {
|
||||||
connection.shell(&opt.serial)?;
|
connection.shell(opt.serial.as_ref())?;
|
||||||
} else {
|
} else {
|
||||||
connection.shell_command(&opt.serial, command)?;
|
connection.shell_command(opt.serial.as_ref(), command)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::HostFeatures => {
|
Command::HostFeatures => {
|
||||||
println!("Available host features");
|
println!("Available host features");
|
||||||
for feature in connection.host_features(&opt.serial)? {
|
for feature in connection.host_features(opt.serial.as_ref())? {
|
||||||
println!("- {}", feature);
|
println!("- {}", feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Reboot { sub_command } => {
|
Command::Reboot { sub_command } => {
|
||||||
println!("Reboots device");
|
println!("Reboots device");
|
||||||
connection.reboot(&opt.serial, sub_command.into())?
|
connection.reboot(opt.serial.as_ref(), sub_command.into())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use std::{
|
|||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
||||||
Result, RustADBError,
|
Result, RustADBError,
|
||||||
@@ -40,11 +42,12 @@ impl AdbTcpConnection {
|
|||||||
&mut self,
|
&mut self,
|
||||||
adb_command: AdbCommand,
|
adb_command: AdbCommand,
|
||||||
with_response: bool,
|
with_response: bool,
|
||||||
|
fresh_connection: bool
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
self.send_adb_request(adb_command)?;
|
self.send_adb_request(adb_command, fresh_connection)?;
|
||||||
|
|
||||||
if with_response {
|
if with_response {
|
||||||
let length = self.get_body_length()?;
|
let length = self.get_hex_body_length()?;
|
||||||
let mut body = vec![
|
let mut body = vec![
|
||||||
0;
|
0;
|
||||||
length
|
length
|
||||||
@@ -63,7 +66,16 @@ impl AdbTcpConnection {
|
|||||||
|
|
||||||
/// Sends the given [AdbCommand] to ADB server, and checks that the request has been taken in consideration.
|
/// Sends the given [AdbCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||||
/// If an error occurred, a [RustADBError] is returned with the response error string.
|
/// If an error occurred, a [RustADBError] is returned with the response error string.
|
||||||
pub(crate) fn send_adb_request(&mut self, command: AdbCommand) -> Result<()> {
|
pub(crate) fn send_adb_request(
|
||||||
|
&mut self,
|
||||||
|
command: AdbCommand,
|
||||||
|
fresh_connection: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
if fresh_connection {
|
||||||
|
// Recreate a new connection (likely because command does not need "state" server side)
|
||||||
|
self.new_connection()?;
|
||||||
|
}
|
||||||
|
|
||||||
let adb_command_string = command.to_string();
|
let adb_command_string = command.to_string();
|
||||||
let adb_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string);
|
let adb_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string);
|
||||||
|
|
||||||
@@ -101,10 +113,23 @@ impl AdbTcpConnection {
|
|||||||
Ok(self.tcp_stream.write_all(command.to_string().as_bytes())?)
|
Ok(self.tcp_stream.write_all(command.to_string().as_bytes())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_body_length(&mut self) -> Result<u32> {
|
/// Gets the body length from hexadecimal value
|
||||||
let mut length = [0; 4];
|
pub(crate) fn get_hex_body_length(&mut self) -> Result<u32> {
|
||||||
self.tcp_stream.read_exact(&mut length)?;
|
let length_buffer = self.read_body_length()?;
|
||||||
|
Ok(u32::from_str_radix(str::from_utf8(&length_buffer)?, 16)?)
|
||||||
|
}
|
||||||
|
|
||||||
Ok(u32::from_str_radix(str::from_utf8(&length)?, 16)?)
|
/// Gets the body length from a LittleEndian value
|
||||||
|
pub(crate) fn get_body_length(&mut self) -> Result<u32> {
|
||||||
|
let length_buffer = self.read_body_length()?;
|
||||||
|
Ok(LittleEndian::read_u32(&length_buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read 4 bytes representing body length
|
||||||
|
fn read_body_length(&mut self) -> Result<[u8; 4]> {
|
||||||
|
let mut length_buffer = [0; 4];
|
||||||
|
self.tcp_stream.read_exact(&mut length_buffer)?;
|
||||||
|
|
||||||
|
Ok(length_buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::{models::AdbCommand, AdbTcpConnection, Device, DeviceLong, Result, Ru
|
|||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// Gets a list of connected devices.
|
/// Gets a list of connected devices.
|
||||||
pub fn devices(&mut self) -> Result<Vec<Device>> {
|
pub fn devices(&mut self) -> Result<Vec<Device>> {
|
||||||
let devices = self.proxy_connection(AdbCommand::Devices, true)?;
|
let devices = self.proxy_connection(AdbCommand::Devices, true, true)?;
|
||||||
|
|
||||||
let mut vec_devices: Vec<Device> = vec![];
|
let mut vec_devices: Vec<Device> = vec![];
|
||||||
for device in devices.split(|x| x.eq(&b'\n')) {
|
for device in devices.split(|x| x.eq(&b'\n')) {
|
||||||
@@ -21,7 +21,7 @@ impl AdbTcpConnection {
|
|||||||
|
|
||||||
/// Gets an extended list of connected devices including the device paths in the state.
|
/// Gets an extended list of connected devices including the device paths in the state.
|
||||||
pub fn devices_long(&mut self) -> Result<Vec<DeviceLong>> {
|
pub fn devices_long(&mut self) -> Result<Vec<DeviceLong>> {
|
||||||
let devices_long = self.proxy_connection(AdbCommand::DevicesLong, true)?;
|
let devices_long = self.proxy_connection(AdbCommand::DevicesLong, true, true)?;
|
||||||
|
|
||||||
let mut vec_devices: Vec<DeviceLong> = vec![];
|
let mut vec_devices: Vec<DeviceLong> = vec![];
|
||||||
for device in devices_long.split(|x| x.eq(&b'\n')) {
|
for device in devices_long.split(|x| x.eq(&b'\n')) {
|
||||||
@@ -38,10 +38,10 @@ impl AdbTcpConnection {
|
|||||||
/// Tracks new devices showing up.
|
/// Tracks new devices showing up.
|
||||||
// TODO: Change with Generator when feature stabilizes
|
// TODO: Change with Generator when feature stabilizes
|
||||||
pub fn track_devices(&mut self, callback: impl Fn(Device) -> Result<()>) -> Result<()> {
|
pub fn track_devices(&mut self, callback: impl Fn(Device) -> Result<()>) -> Result<()> {
|
||||||
self.send_adb_request(AdbCommand::TrackDevices)?;
|
self.send_adb_request(AdbCommand::TrackDevices, true)?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let length = self.get_body_length()?;
|
let length = self.get_hex_body_length()?;
|
||||||
|
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
let mut body = vec![
|
let mut body = vec![
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ use crate::{
|
|||||||
|
|
||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// Lists available ADB server features.
|
/// Lists available ADB server features.
|
||||||
pub fn host_features<S: ToString>(&mut self, serial: &Option<S>) -> Result<Vec<HostFeatures>> {
|
pub fn host_features<S: ToString>(&mut self, serial: Option<&S>) -> Result<Vec<HostFeatures>> {
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let features = self.proxy_connection(AdbCommand::HostFeatures, true)?;
|
let features = self.proxy_connection(AdbCommand::HostFeatures, true, false)?;
|
||||||
|
|
||||||
Ok(features
|
Ok(features
|
||||||
.split(|x| x.eq(&b','))
|
.split(|x| x.eq(&b','))
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ use crate::{models::AdbCommand, AdbTcpConnection, Result};
|
|||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// Asks the ADB server to quit immediately.
|
/// Asks the ADB server to quit immediately.
|
||||||
pub fn kill(&mut self) -> Result<()> {
|
pub fn kill(&mut self) -> Result<()> {
|
||||||
self.proxy_connection(AdbCommand::Kill, false).map(|_| ())
|
self.proxy_connection(AdbCommand::Kill, false, true).map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,21 +10,19 @@ use std::{
|
|||||||
|
|
||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// Lists files in [path] on the device.
|
/// Lists files in [path] on the device.
|
||||||
pub fn list<S: ToString, A: AsRef<str>>(&mut self, serial: Option<S>, path: A) -> Result<()> {
|
pub fn list<S: ToString, A: AsRef<str>>(&mut self, serial: Option<&S>, path: A) -> Result<()> {
|
||||||
self.new_connection()?;
|
|
||||||
|
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, false)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), false)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set device in SYNC mode
|
// Set device in SYNC mode
|
||||||
self.send_adb_request(AdbCommand::Sync)?;
|
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||||
|
|
||||||
// Send a list command
|
// Send a list command
|
||||||
self.send_sync_request(SyncCommand::List(path.as_ref()))?;
|
self.send_sync_request(SyncCommand::List)?;
|
||||||
|
|
||||||
self.handle_list_command(path)
|
self.handle_list_command(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ impl AdbTcpConnection {
|
|||||||
/// Reboots the device
|
/// Reboots the device
|
||||||
pub fn reboot<S: ToString>(
|
pub fn reboot<S: ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
serial: &Option<S>,
|
serial: Option<&S>,
|
||||||
reboot_type: RebootType,
|
reboot_type: RebootType,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.proxy_connection(AdbCommand::Reboot(reboot_type), false)
|
self.proxy_connection(AdbCommand::Reboot(reboot_type), false, false)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,22 @@ impl AdbTcpConnection {
|
|||||||
/// Receives [path] to [stream] from the device.
|
/// Receives [path] to [stream] from the device.
|
||||||
pub fn recv<S: ToString, A: AsRef<str>>(
|
pub fn recv<S: ToString, A: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
serial: Option<S>,
|
serial: Option<&S>,
|
||||||
path: A,
|
path: A,
|
||||||
stream: &mut dyn Write,
|
stream: &mut dyn Write,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.new_connection()?;
|
|
||||||
|
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set device in SYNC mode
|
// Set device in SYNC mode
|
||||||
self.send_adb_request(AdbCommand::Sync)?;
|
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||||
|
|
||||||
// Send a recv command
|
// Send a recv command
|
||||||
self.send_sync_request(SyncCommand::Recv(path.as_ref(), stream))?;
|
self.send_sync_request(SyncCommand::Recv)?;
|
||||||
|
|
||||||
self.handle_recv_command(path, stream)
|
self.handle_recv_command(path, stream)
|
||||||
}
|
}
|
||||||
@@ -46,28 +44,26 @@ impl AdbTcpConnection {
|
|||||||
// Chunk looks like 'DATA' <length> <data>
|
// Chunk looks like 'DATA' <length> <data>
|
||||||
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
||||||
let mut data_header = [0_u8; 4]; // DATA
|
let mut data_header = [0_u8; 4]; // DATA
|
||||||
let mut len_header = [0_u8; 4]; // <len>
|
|
||||||
loop {
|
loop {
|
||||||
self.tcp_stream.read_exact(&mut data_header)?;
|
self.tcp_stream.read_exact(&mut data_header)?;
|
||||||
// Check if data_header is DATA or DONE
|
// Check if data_header is DATA or DONE or FAIL
|
||||||
if data_header.eq(b"DATA") {
|
match &data_header {
|
||||||
self.tcp_stream.read_exact(&mut len_header)?;
|
b"DATA" => {
|
||||||
let length: usize = LittleEndian::read_u32(&len_header).try_into().unwrap();
|
// Handle received data
|
||||||
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
let length: usize = self.get_body_length()?.try_into().unwrap();
|
||||||
output.write_all(&buffer)?;
|
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
||||||
} else if data_header.eq(b"DONE") {
|
output.write_all(&buffer[..length])?;
|
||||||
// We're done here
|
}
|
||||||
break;
|
b"DONE" => break, // We're done here
|
||||||
} else if data_header.eq(b"FAIL") {
|
b"FAIL" => {
|
||||||
// Handle fail
|
// Handle fail
|
||||||
self.tcp_stream.read_exact(&mut len_header)?;
|
let length: usize = self.get_body_length()?.try_into().unwrap();
|
||||||
let length: usize = LittleEndian::read_u32(&len_header).try_into().unwrap();
|
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
||||||
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
Err(RustADBError::ADBRequestFailed(String::from_utf8(
|
||||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(
|
buffer[..length].to_vec(),
|
||||||
buffer[..length].to_vec(),
|
)?))?;
|
||||||
)?))?;
|
}
|
||||||
} else {
|
_ => panic!("Unknown response from device {:#?}", data_header),
|
||||||
panic!("Unknown response from device {:#?}", data_header);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,24 +14,22 @@ impl AdbTcpConnection {
|
|||||||
/// Sends [stream] to [path] on the device.
|
/// Sends [stream] to [path] on the device.
|
||||||
pub fn send<S: ToString, A: AsRef<str>>(
|
pub fn send<S: ToString, A: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
serial: Option<S>,
|
serial: Option<&S>,
|
||||||
stream: &mut dyn Read,
|
stream: &mut dyn Read,
|
||||||
path: A,
|
path: A,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.new_connection()?;
|
|
||||||
|
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set device in SYNC mode
|
// Set device in SYNC mode
|
||||||
self.send_adb_request(AdbCommand::Sync)?;
|
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||||
|
|
||||||
// Send a send command
|
// Send a send command
|
||||||
self.send_sync_request(SyncCommand::Send(stream, path.as_ref()))?;
|
self.send_sync_request(SyncCommand::Send)?;
|
||||||
|
|
||||||
self.handle_send_command(stream, path)
|
self.handle_send_command(stream, path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
use std::io::{ErrorKind, Read, Write};
|
use std::{
|
||||||
|
io::{self, Read, Write},
|
||||||
|
sync::mpsc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
adb_termios::ADBTermios,
|
adb_termios::ADBTermios,
|
||||||
@@ -6,11 +12,24 @@ use crate::{
|
|||||||
AdbTcpConnection, Result, RustADBError,
|
AdbTcpConnection, Result, RustADBError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STDIN: Token = Token(0);
|
||||||
|
const BUFFER_SIZE: usize = 512;
|
||||||
|
const POLL_DURATION: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
fn setup_poll_stdin() -> std::result::Result<Poll, io::Error> {
|
||||||
|
let poll = Poll::new()?;
|
||||||
|
let stdin_fd = 0;
|
||||||
|
poll.registry()
|
||||||
|
.register(&mut SourceFd(&stdin_fd), STDIN, Interest::READABLE)?;
|
||||||
|
|
||||||
|
Ok(poll)
|
||||||
|
}
|
||||||
|
|
||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// Runs 'command' in a shell on the device, and return its output and error streams.
|
/// Runs 'command' in a shell on the device, and return its output and error streams.
|
||||||
pub fn shell_command<S: ToString>(
|
pub fn shell_command<S: ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
serial: &Option<S>,
|
serial: Option<&S>,
|
||||||
command: impl IntoIterator<Item = S>,
|
command: impl IntoIterator<Item = S>,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let supported_features = self.host_features(serial)?;
|
let supported_features = self.host_features(serial)?;
|
||||||
@@ -20,24 +39,25 @@ impl AdbTcpConnection {
|
|||||||
return Err(RustADBError::ADBShellNotSupported);
|
return Err(RustADBError::ADBShellNotSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.new_connection()?;
|
|
||||||
|
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.send_adb_request(AdbCommand::ShellCommand(
|
self.send_adb_request(
|
||||||
command
|
AdbCommand::ShellCommand(
|
||||||
.into_iter()
|
command
|
||||||
.map(|v| v.to_string())
|
.into_iter()
|
||||||
.collect::<Vec<_>>()
|
.map(|v| v.to_string())
|
||||||
.join(" "),
|
.collect::<Vec<_>>()
|
||||||
))?;
|
.join(" "),
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 512;
|
const BUFFER_SIZE: usize = 512;
|
||||||
let result = (|| {
|
(|| {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
let mut buffer = [0; BUFFER_SIZE];
|
let mut buffer = [0; BUFFER_SIZE];
|
||||||
@@ -54,20 +74,17 @@ impl AdbTcpConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})()
|
||||||
|
|
||||||
self.new_connection()?;
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts an interactive shell session on the device. Redirects stdin/stdout/stderr as appropriate.
|
/// Starts an interactive shell session on the device. Redirects stdin/stdout/stderr as appropriate.
|
||||||
pub fn shell<S: ToString>(&mut self, serial: &Option<S>) -> Result<()> {
|
pub fn shell<S: ToString>(&mut self, serial: Option<&S>) -> Result<()> {
|
||||||
let mut adb_termios = ADBTermios::new(std::io::stdin())?;
|
let mut adb_termios = ADBTermios::new(std::io::stdin())?;
|
||||||
adb_termios.set_adb_termios()?;
|
adb_termios.set_adb_termios()?;
|
||||||
|
|
||||||
self.tcp_stream.set_nodelay(true)?;
|
self.tcp_stream.set_nodelay(true)?;
|
||||||
|
|
||||||
// FORWARD CTRL+C !!
|
// TODO: FORWARD CTRL+C !!
|
||||||
|
|
||||||
let supported_features = self.host_features(serial)?;
|
let supported_features = self.host_features(serial)?;
|
||||||
if !supported_features.contains(&HostFeatures::ShellV2)
|
if !supported_features.contains(&HostFeatures::ShellV2)
|
||||||
@@ -76,37 +93,29 @@ impl AdbTcpConnection {
|
|||||||
return Err(RustADBError::ADBShellNotSupported);
|
return Err(RustADBError::ADBShellNotSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.new_connection()?;
|
|
||||||
|
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.send_adb_request(AdbCommand::Shell)?;
|
self.send_adb_request(AdbCommand::Shell, false)?;
|
||||||
|
|
||||||
// let read_stream = Arc::new(self.tcp_stream);
|
// let read_stream = Arc::new(self.tcp_stream);
|
||||||
let mut read_stream = self.tcp_stream.try_clone()?;
|
let mut read_stream = self.tcp_stream.try_clone()?;
|
||||||
|
|
||||||
// Writing thread
|
let (tx, rx) = mpsc::channel::<bool>();
|
||||||
let mut write_stream = read_stream.try_clone()?;
|
|
||||||
let writer_t = std::thread::spawn(move || -> Result<()> {
|
|
||||||
let mut buf = [0; 1024];
|
|
||||||
loop {
|
|
||||||
let size = std::io::stdin().read(&mut buf)?;
|
|
||||||
|
|
||||||
write_stream.write_all(&buf[0..size])?;
|
let mut write_stream = read_stream.try_clone()?;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reading thread
|
// Reading thread
|
||||||
let reader_t = std::thread::spawn(move || -> Result<()> {
|
std::thread::spawn(move || -> Result<()> {
|
||||||
const BUFFER_SIZE: usize = 512;
|
|
||||||
loop {
|
loop {
|
||||||
let mut buffer = [0; BUFFER_SIZE];
|
let mut buffer = [0; BUFFER_SIZE];
|
||||||
match read_stream.read(&mut buffer) {
|
match read_stream.read(&mut buffer) {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
|
let _ = tx.send(true);
|
||||||
|
read_stream.shutdown(std::net::Shutdown::Both)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Ok(size) => {
|
Ok(size) => {
|
||||||
@@ -120,24 +129,33 @@ impl AdbTcpConnection {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(e) = reader_t.join().unwrap() {
|
let mut buf = [0; BUFFER_SIZE];
|
||||||
match e {
|
let mut events = Events::with_capacity(1);
|
||||||
RustADBError::IOError(e) if e.kind() == ErrorKind::BrokenPipe => {}
|
|
||||||
_ => {
|
let mut poll = setup_poll_stdin()?;
|
||||||
return Err(e);
|
|
||||||
|
// Polling either by checking that reading socket hasn't been closed, and if is there is something to read on stdin.
|
||||||
|
loop {
|
||||||
|
poll.poll(&mut events, Some(POLL_DURATION))?;
|
||||||
|
match rx.try_recv() {
|
||||||
|
Ok(_) | Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
|
||||||
|
Err(mpsc::TryRecvError::Empty) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in events.iter() {
|
||||||
|
match event.token() {
|
||||||
|
STDIN => {
|
||||||
|
let size = match std::io::stdin().read(&mut buf) {
|
||||||
|
Ok(0) => return Ok(()),
|
||||||
|
Ok(size) => size,
|
||||||
|
Err(_) => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
write_stream.write_all(&buf[0..size])?;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = writer_t.join().unwrap() {
|
|
||||||
match e {
|
|
||||||
RustADBError::IOError(e) if e.kind() == ErrorKind::BrokenPipe => {}
|
|
||||||
_ => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,20 +79,18 @@ impl AdbTcpConnection {
|
|||||||
serial: Option<S>,
|
serial: Option<S>,
|
||||||
path: A,
|
path: A,
|
||||||
) -> Result<AdbStatResponse> {
|
) -> Result<AdbStatResponse> {
|
||||||
self.new_connection()?;
|
|
||||||
|
|
||||||
match serial {
|
match serial {
|
||||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
None => self.send_adb_request(AdbCommand::TransportAny, false)?,
|
||||||
Some(serial) => {
|
Some(serial) => {
|
||||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), false)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set device in SYNC mode
|
// Set device in SYNC mode
|
||||||
self.send_adb_request(AdbCommand::Sync)?;
|
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||||
|
|
||||||
// Send a "Stat" command
|
// Send a "Stat" command
|
||||||
self.send_sync_request(SyncCommand::Stat(path.as_ref()))?;
|
self.send_sync_request(SyncCommand::Stat)?;
|
||||||
|
|
||||||
self.handle_stat_command(path)
|
self.handle_stat_command(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{models::AdbCommand, AdbTcpConnection, Result};
|
|||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// 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.
|
/// 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.
|
||||||
pub fn transport_any(&mut self) -> Result<()> {
|
pub fn transport_any(&mut self) -> Result<()> {
|
||||||
self.proxy_connection(AdbCommand::TransportAny, false)
|
self.proxy_connection(AdbCommand::TransportAny, false, true)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{models::AdbCommand, AdbTcpConnection, AdbVersion, Result};
|
|||||||
impl AdbTcpConnection {
|
impl AdbTcpConnection {
|
||||||
/// Gets server's internal version number.
|
/// Gets server's internal version number.
|
||||||
pub fn version(&mut self) -> Result<AdbVersion> {
|
pub fn version(&mut self) -> Result<AdbVersion> {
|
||||||
let version = self.proxy_connection(AdbCommand::Version, true)?;
|
let version = self.proxy_connection(AdbCommand::Version, true, true)?;
|
||||||
|
|
||||||
AdbVersion::try_from(version)
|
AdbVersion::try_from(version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::RebootType;
|
use super::RebootType;
|
||||||
|
|
||||||
pub enum AdbCommand {
|
pub enum AdbCommand {
|
||||||
@@ -45,28 +47,28 @@ pub enum AdbCommand {
|
|||||||
Reboot(RebootType),
|
Reboot(RebootType),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for AdbCommand {
|
impl Display for AdbCommand {
|
||||||
fn to_string(&self) -> String {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
AdbCommand::Version => "host:version".into(),
|
AdbCommand::Version => write!(f, "host:version"),
|
||||||
AdbCommand::Kill => "host:kill".into(),
|
AdbCommand::Kill => write!(f, "host:kill"),
|
||||||
AdbCommand::Devices => "host:devices".into(),
|
AdbCommand::Devices => write!(f, "host:devices"),
|
||||||
AdbCommand::DevicesLong => "host:devices-l".into(),
|
AdbCommand::DevicesLong => write!(f, "host:devices-l"),
|
||||||
AdbCommand::Sync => "sync:".into(),
|
AdbCommand::Sync => write!(f, "sync:"),
|
||||||
AdbCommand::TrackDevices => "host:track-devices".into(),
|
AdbCommand::TrackDevices => write!(f, "host:track-devices"),
|
||||||
AdbCommand::TransportAny => "host:transport-any".into(),
|
AdbCommand::TransportAny => write!(f, "host:transport-any"),
|
||||||
AdbCommand::TransportSerial(serial) => format!("host:transport:{serial}"),
|
AdbCommand::TransportSerial(serial) => write!(f, "host:transport:{serial}"),
|
||||||
AdbCommand::ShellCommand(command) => match std::env::var("TERM") {
|
AdbCommand::ShellCommand(command) => match std::env::var("TERM") {
|
||||||
Ok(term) => format!("shell,TERM={term},raw:{command}"),
|
Ok(term) => write!(f, "shell,TERM={term},raw:{command}"),
|
||||||
Err(_) => format!("shell,raw:{command}"),
|
Err(_) => write!(f, "shell,raw:{command}"),
|
||||||
},
|
},
|
||||||
AdbCommand::Shell => match std::env::var("TERM") {
|
AdbCommand::Shell => match std::env::var("TERM") {
|
||||||
Ok(term) => format!("shell,TERM={term},raw:"),
|
Ok(term) => write!(f, "shell,TERM={term},raw:"),
|
||||||
Err(_) => "shell,raw:".into(),
|
Err(_) => write!(f, "shell,raw:"),
|
||||||
},
|
},
|
||||||
AdbCommand::HostFeatures => "host:features".into(),
|
AdbCommand::HostFeatures => write!(f, "host:features"),
|
||||||
AdbCommand::Reboot(reboot_type) => {
|
AdbCommand::Reboot(reboot_type) => {
|
||||||
format!("reboot:{reboot_type}")
|
write!(f, "reboot:{reboot_type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
pub enum SyncCommand<'a> {
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub enum SyncCommand {
|
||||||
/// List files in a folder
|
/// List files in a folder
|
||||||
List(&'a str),
|
List,
|
||||||
/// Receive a file from the device
|
/// Receive a file from the device
|
||||||
Recv(&'a str, &'a mut dyn std::io::Write),
|
Recv,
|
||||||
/// Send a file to the device
|
/// Send a file to the device
|
||||||
Send(&'a mut dyn std::io::Read, &'a str),
|
Send,
|
||||||
// Stat a file
|
// Stat a file
|
||||||
Stat(&'a str),
|
Stat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for SyncCommand<'_> {
|
impl Display for SyncCommand {
|
||||||
fn to_string(&self) -> String {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
SyncCommand::List(_) => "LIST",
|
SyncCommand::List => write!(f, "LIST"),
|
||||||
SyncCommand::Recv(_, _) => "RECV",
|
SyncCommand::Recv => write!(f, "RECV"),
|
||||||
SyncCommand::Send(_, _) => "SEND",
|
SyncCommand::Send => write!(f, "SEND"),
|
||||||
SyncCommand::Stat(_) => "STAT",
|
SyncCommand::Stat => write!(f, "STAT"),
|
||||||
}
|
}
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use adb_client::AdbTcpConnection;
|
use adb_client::AdbTcpConnection;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
fn new_client() -> AdbTcpConnection {
|
fn new_client() -> AdbTcpConnection {
|
||||||
let address = Ipv4Addr::from_str("127.0.0.1").unwrap();
|
let address = Ipv4Addr::from_str("127.0.0.1").unwrap();
|
||||||
@@ -19,8 +21,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_shell() {
|
fn test_shell() {
|
||||||
let mut adb = new_client();
|
let mut adb = new_client();
|
||||||
adb.shell_command(&None, vec!["ls"]).unwrap();
|
adb.shell_command(None, vec!["ls"]).unwrap();
|
||||||
adb.shell_command(&None, vec!["pwd"]).unwrap();
|
adb.shell_command(None, vec!["pwd"]).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -41,4 +43,42 @@ mod tests {
|
|||||||
let address = Ipv4Addr::from_str("127.0.0.300").unwrap();
|
let address = Ipv4Addr::from_str("127.0.0.300").unwrap();
|
||||||
let _ = AdbTcpConnection::new(address, 5037).expect("Could not create ADB connection...");
|
let _ = AdbTcpConnection::new(address, 5037).expect("Could not create ADB connection...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send_recv() {
|
||||||
|
// Create random "Reader" in memory
|
||||||
|
let mut key = [0u8; 1000];
|
||||||
|
rand::thread_rng().fill(&mut key[..]);
|
||||||
|
let mut c: Cursor<Vec<u8>> = Cursor::new(key.to_vec());
|
||||||
|
|
||||||
|
let mut connection = new_client();
|
||||||
|
|
||||||
|
const TEST_FILENAME: &'static str = "/data/local/tmp/test_file";
|
||||||
|
// Send it
|
||||||
|
connection
|
||||||
|
.send::<&str, &str>(None, &mut c, TEST_FILENAME)
|
||||||
|
.expect("cannot send file");
|
||||||
|
|
||||||
|
// Pull it to memory
|
||||||
|
let mut res = vec![];
|
||||||
|
connection
|
||||||
|
.recv::<&str, &str>(None, TEST_FILENAME, &mut res)
|
||||||
|
.expect("cannot recv file");
|
||||||
|
|
||||||
|
// diff
|
||||||
|
assert_eq!(c.get_ref(), &res);
|
||||||
|
|
||||||
|
connection
|
||||||
|
.shell_command::<&str>(None, [format!("rm {TEST_FILENAME}").as_str()])
|
||||||
|
.expect("cannot remove test file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_connexions() {
|
||||||
|
let mut connection = new_client();
|
||||||
|
|
||||||
|
for _ in 0..2 {
|
||||||
|
let _ = connection.devices().expect("cannot get version");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user