diff --git a/Cargo.toml b/Cargo.toml index b636917..6c27e4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ thiserror = { version = "1.0.61" } ## Marked as optional so that lib users do not depend on them [dev-dependencies] clap = { version = "4.5.4", features = ["derive"] } +rand = { version = "0.8.5" } diff --git a/README.md b/README.md index c3c0fc2..5fc9bbb 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,8 @@ use std::fs::File; use std::path::Path; let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap(); -let serial: Option<&str> = None; let mut input = File::open(Path::new("/tmp")).unwrap(); -connection.send(serial, &mut input, "/data/local/tmp"); +connection.send::<&str,&str>(None, &mut input, "/data/local/tmp"); ``` ## Rust binary diff --git a/examples/adb_cli.rs b/examples/adb_cli.rs index 7325695..4d804de 100644 --- a/examples/adb_cli.rs +++ b/examples/adb_cli.rs @@ -111,16 +111,16 @@ fn main() -> Result<(), RustADBError> { } Command::Pull { path, filename } => { 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}"); } Command::Push { filename, path } => { 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}"); } Command::List { path } => { - connection.list(opt.serial, path)?; + connection.list(opt.serial.as_ref(), path)?; } Command::Stat { path } => { let stat_response = connection.stat(opt.serial, path)?; @@ -128,20 +128,20 @@ fn main() -> Result<(), RustADBError> { } Command::Shell { command } => { if command.is_empty() { - connection.shell(&opt.serial)?; + connection.shell(opt.serial.as_ref())?; } else { - connection.shell_command(&opt.serial, command)?; + connection.shell_command(opt.serial.as_ref(), command)?; } } Command::HostFeatures => { println!("Available host features"); - for feature in connection.host_features(&opt.serial)? { + for feature in connection.host_features(opt.serial.as_ref())? { println!("- {}", feature); } } Command::Reboot { sub_command } => { println!("Reboots device"); - connection.reboot(&opt.serial, sub_command.into())? + connection.reboot(opt.serial.as_ref(), sub_command.into())? } } diff --git a/src/adb_tcp_connection.rs b/src/adb_tcp_connection.rs index 23855d4..e03b113 100644 --- a/src/adb_tcp_connection.rs +++ b/src/adb_tcp_connection.rs @@ -42,8 +42,9 @@ impl AdbTcpConnection { &mut self, adb_command: AdbCommand, with_response: bool, + fresh_connection: bool ) -> Result> { - self.send_adb_request(adb_command)?; + self.send_adb_request(adb_command, fresh_connection)?; if with_response { let length = self.get_hex_body_length()?; @@ -65,7 +66,16 @@ impl AdbTcpConnection { /// 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. - 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_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string); diff --git a/src/commands/devices.rs b/src/commands/devices.rs index efd8c0c..8f7eea6 100644 --- a/src/commands/devices.rs +++ b/src/commands/devices.rs @@ -5,7 +5,7 @@ use crate::{models::AdbCommand, AdbTcpConnection, Device, DeviceLong, Result, Ru impl AdbTcpConnection { /// Gets a list of connected devices. pub fn devices(&mut self) -> Result> { - let devices = self.proxy_connection(AdbCommand::Devices, true)?; + let devices = self.proxy_connection(AdbCommand::Devices, true, true)?; let mut vec_devices: Vec = vec![]; 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. pub fn devices_long(&mut self) -> Result> { - let devices_long = self.proxy_connection(AdbCommand::DevicesLong, true)?; + let devices_long = self.proxy_connection(AdbCommand::DevicesLong, true, true)?; let mut vec_devices: Vec = vec![]; for device in devices_long.split(|x| x.eq(&b'\n')) { @@ -38,7 +38,7 @@ impl AdbTcpConnection { /// Tracks new devices showing up. // TODO: Change with Generator when feature stabilizes 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 { let length = self.get_hex_body_length()?; diff --git a/src/commands/host_features.rs b/src/commands/host_features.rs index 8203735..5043ba3 100644 --- a/src/commands/host_features.rs +++ b/src/commands/host_features.rs @@ -5,15 +5,15 @@ use crate::{ impl AdbTcpConnection { /// Lists available ADB server features. - pub fn host_features(&mut self, serial: &Option) -> Result> { + pub fn host_features(&mut self, serial: Option<&S>) -> Result> { match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, true)?, 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 .split(|x| x.eq(&b',')) diff --git a/src/commands/kill.rs b/src/commands/kill.rs index 22bee83..022af2d 100644 --- a/src/commands/kill.rs +++ b/src/commands/kill.rs @@ -3,6 +3,6 @@ use crate::{models::AdbCommand, AdbTcpConnection, Result}; impl AdbTcpConnection { /// Asks the ADB server to quit immediately. pub fn kill(&mut self) -> Result<()> { - self.proxy_connection(AdbCommand::Kill, false).map(|_| ()) + self.proxy_connection(AdbCommand::Kill, false, true).map(|_| ()) } } diff --git a/src/commands/list.rs b/src/commands/list.rs index 2217e6f..84b88f2 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -10,18 +10,16 @@ use std::{ impl AdbTcpConnection { /// Lists files in [path] on the device. - pub fn list>(&mut self, serial: Option, path: A) -> Result<()> { - self.new_connection()?; - + pub fn list>(&mut self, serial: Option<&S>, path: A) -> Result<()> { match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, false)?, 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 - self.send_adb_request(AdbCommand::Sync)?; + self.send_adb_request(AdbCommand::Sync, false)?; // Send a list command self.send_sync_request(SyncCommand::List)?; diff --git a/src/commands/reboot.rs b/src/commands/reboot.rs index 911121a..f5a65eb 100644 --- a/src/commands/reboot.rs +++ b/src/commands/reboot.rs @@ -7,17 +7,17 @@ impl AdbTcpConnection { /// Reboots the device pub fn reboot( &mut self, - serial: &Option, + serial: Option<&S>, reboot_type: RebootType, ) -> Result<()> { match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, true)?, 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(|_| ()) } } diff --git a/src/commands/recv.rs b/src/commands/recv.rs index 17bafe9..e5e78bf 100644 --- a/src/commands/recv.rs +++ b/src/commands/recv.rs @@ -9,21 +9,19 @@ impl AdbTcpConnection { /// Receives [path] to [stream] from the device. pub fn recv>( &mut self, - serial: Option, + serial: Option<&S>, path: A, stream: &mut dyn Write, ) -> Result<()> { - self.new_connection()?; - match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, true)?, 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 - self.send_adb_request(AdbCommand::Sync)?; + self.send_adb_request(AdbCommand::Sync, false)?; // Send a recv command self.send_sync_request(SyncCommand::Recv)?; @@ -54,7 +52,7 @@ impl AdbTcpConnection { // Handle received data let length: usize = self.get_body_length()?.try_into().unwrap(); self.tcp_stream.read_exact(&mut buffer[..length])?; - output.write_all(&buffer)?; + output.write_all(&buffer[..length])?; } b"DONE" => break, // We're done here b"FAIL" => { diff --git a/src/commands/send.rs b/src/commands/send.rs index a5d251b..0fc38ac 100644 --- a/src/commands/send.rs +++ b/src/commands/send.rs @@ -14,21 +14,19 @@ impl AdbTcpConnection { /// Sends [stream] to [path] on the device. pub fn send>( &mut self, - serial: Option, + serial: Option<&S>, stream: &mut dyn Read, path: A, ) -> Result<()> { - self.new_connection()?; - match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, true)?, 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 - self.send_adb_request(AdbCommand::Sync)?; + self.send_adb_request(AdbCommand::Sync, false)?; // Send a send command self.send_sync_request(SyncCommand::Send)?; diff --git a/src/commands/shell.rs b/src/commands/shell.rs index 61a7680..5dfdb87 100644 --- a/src/commands/shell.rs +++ b/src/commands/shell.rs @@ -29,7 +29,7 @@ impl AdbTcpConnection { /// Runs 'command' in a shell on the device, and return its output and error streams. pub fn shell_command( &mut self, - serial: &Option, + serial: Option<&S>, command: impl IntoIterator, ) -> Result> { let supported_features = self.host_features(serial)?; @@ -39,24 +39,25 @@ impl AdbTcpConnection { return Err(RustADBError::ADBShellNotSupported); } - self.new_connection()?; - match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, true)?, 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( - command - .into_iter() - .map(|v| v.to_string()) - .collect::>() - .join(" "), - ))?; + self.send_adb_request( + AdbCommand::ShellCommand( + command + .into_iter() + .map(|v| v.to_string()) + .collect::>() + .join(" "), + ), + false, + )?; const BUFFER_SIZE: usize = 512; - let result = (|| { + (|| { let mut result = Vec::new(); loop { let mut buffer = [0; BUFFER_SIZE]; @@ -73,20 +74,17 @@ impl AdbTcpConnection { } } } - })(); - - self.new_connection()?; - result + })() } /// Starts an interactive shell session on the device. Redirects stdin/stdout/stderr as appropriate. - pub fn shell(&mut self, serial: &Option) -> Result<()> { + pub fn shell(&mut self, serial: Option<&S>) -> Result<()> { let mut adb_termios = ADBTermios::new(std::io::stdin())?; adb_termios.set_adb_termios()?; self.tcp_stream.set_nodelay(true)?; - // FORWARD CTRL+C !! + // TODO: FORWARD CTRL+C !! let supported_features = self.host_features(serial)?; if !supported_features.contains(&HostFeatures::ShellV2) @@ -95,15 +93,13 @@ impl AdbTcpConnection { return Err(RustADBError::ADBShellNotSupported); } - self.new_connection()?; - match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, true)?, 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 mut read_stream = self.tcp_stream.try_clone()?; diff --git a/src/commands/stat.rs b/src/commands/stat.rs index 4238a11..0637d6b 100644 --- a/src/commands/stat.rs +++ b/src/commands/stat.rs @@ -79,17 +79,15 @@ impl AdbTcpConnection { serial: Option, path: A, ) -> Result { - self.new_connection()?; - match serial { - None => self.send_adb_request(AdbCommand::TransportAny)?, + None => self.send_adb_request(AdbCommand::TransportAny, false)?, 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 - self.send_adb_request(AdbCommand::Sync)?; + self.send_adb_request(AdbCommand::Sync, false)?; // Send a "Stat" command self.send_sync_request(SyncCommand::Stat)?; diff --git a/src/commands/transport.rs b/src/commands/transport.rs index d9b81d1..7ba97c5 100644 --- a/src/commands/transport.rs +++ b/src/commands/transport.rs @@ -3,7 +3,7 @@ use crate::{models::AdbCommand, AdbTcpConnection, Result}; 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. pub fn transport_any(&mut self) -> Result<()> { - self.proxy_connection(AdbCommand::TransportAny, false) + self.proxy_connection(AdbCommand::TransportAny, false, true) .map(|_| ()) } } diff --git a/src/commands/version.rs b/src/commands/version.rs index 80c80fb..0e6bf5f 100644 --- a/src/commands/version.rs +++ b/src/commands/version.rs @@ -3,7 +3,7 @@ use crate::{models::AdbCommand, AdbTcpConnection, AdbVersion, Result}; impl AdbTcpConnection { /// Gets server's internal version number. pub fn version(&mut self) -> Result { - let version = self.proxy_connection(AdbCommand::Version, true)?; + let version = self.proxy_connection(AdbCommand::Version, true, true)?; AdbVersion::try_from(version) } diff --git a/tests/tests.rs b/tests/tests.rs index 9cfec42..c503cad 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,9 +1,11 @@ #[cfg(test)] mod tests { + use std::io::Cursor; use std::net::Ipv4Addr; use std::str::FromStr; use adb_client::AdbTcpConnection; + use rand::Rng; fn new_client() -> AdbTcpConnection { let address = Ipv4Addr::from_str("127.0.0.1").unwrap(); @@ -19,8 +21,8 @@ mod tests { #[test] fn test_shell() { let mut adb = new_client(); - adb.shell_command(&None, vec!["ls"]).unwrap(); - adb.shell_command(&None, vec!["pwd"]).unwrap(); + adb.shell_command(None, vec!["ls"]).unwrap(); + adb.shell_command(None, vec!["pwd"]).unwrap(); } #[test] @@ -41,4 +43,42 @@ mod tests { let address = Ipv4Addr::from_str("127.0.0.300").unwrap(); 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> = 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"); + } + } }