9 Commits

Author SHA1 Message Date
LIAUD Corentin
f46c996095 release: v1.0.3 2024-07-13 19:02:52 +02:00
LIAUD Corentin
6fe4905bb7 fix: Error when reusing same connection 2024-07-13 19:01:15 +02:00
LIAUD Corentin
2a551182ec fix: README.md 2024-07-13 19:01:15 +02:00
LIAUD Corentin
3c5efb2dae fix: fix stdin read when using shell function 2024-07-13 19:01:15 +02:00
LIAUD Corentin
11d8621934 feat: clippy + bump deps 2024-06-29 12:56:34 +02:00
LIAUD Corentin
787b7b02fd fix: split get_body_length() into two methods 2024-06-29 12:37:22 +02:00
Daerckdev
b9b5c57f9d fix: get_body_length needs to handle hex length too 2024-06-20 14:28:49 +02:00
Daerckdev
84431f169f remove duplicate code 2024-06-20 14:02:54 +02:00
Daerckdev
08e9f4f512 fix: get_body_lenght method did not read bytes correctly 2024-06-20 13:16:38 +02:00
18 changed files with 244 additions and 166 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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![

View File

@@ -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','))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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");
}
}
} }