From fff8bd0c202439bd4c6efaea17cd8d53b3205f60 Mon Sep 17 00:00:00 2001 From: jagenheim Date: Tue, 15 Aug 2023 21:07:41 +0200 Subject: [PATCH] Sync functionality (#11) * Sync functionality Added the following sync commands: SEND - Working as intended RECV - Working as intended STAT - Only checks if file exist LIST - Untested due to device issues --------- Co-authored-by: Fredrik Jagenheim Co-authored-by: LIAUD Corentin --- CHANGELOG.md | 10 ---- Cargo.toml | 4 +- README.md | 1 - examples/adb_cli.rs | 27 +++++++++ src/adb_tcp_connexion.rs | 27 +++++---- src/commands/devices.rs | 4 +- src/commands/host_features.rs | 9 ++- src/commands/list.rs | 72 ++++++++++++++++++++++++ src/commands/mod.rs | 4 ++ src/commands/reboot.rs | 9 ++- src/commands/recv.rs | 77 ++++++++++++++++++++++++++ src/commands/send.rs | 100 ++++++++++++++++++++++++++++++++++ src/commands/shell.rs | 39 ++++++------- src/commands/stat.rs | 99 +++++++++++++++++++++++++++++++++ src/models/adb_command.rs | 3 +- src/models/mod.rs | 2 + src/models/sync_command.rs | 22 ++++++++ 17 files changed, 452 insertions(+), 57 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 src/commands/list.rs create mode 100644 src/commands/recv.rs create mode 100644 src/commands/send.rs create mode 100644 src/commands/stat.rs create mode 100644 src/models/sync_command.rs diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 26cc16c..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## [0.5.0] - 2023-04-07 - -- [Breaking] Commands previously using `serial` argument now takes `&Option` instead of `Option`. (#8) -- Adds `serial` argument for `host-feature` command. (#8) - -Thanks @jagenheim for contributing ! diff --git a/Cargo.toml b/Cargo.toml index d3466df..f81b50f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,11 @@ name = "adb_cli" path = "examples/adb_cli.rs" [dependencies] +byteorder = { version = "1.4.3" } +chrono = { version = "0.4.26" } regex = { version = "1.9.3", features = ["perf", "std", "unicode"] } termios = { version = "0.3.3" } -thiserror = { version = "1.0.44" } +thiserror = { version = "1.0.46" } ## Binary-only dependencies ## Marked as optional so that lib users do not depend on them diff --git a/README.md b/README.md index feceec1..823ee0f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ cargo install adb_client --example adb_cli ## Missing features -- Pull / Push files - USB protocol All pull requests are welcome ! diff --git a/examples/adb_cli.rs b/examples/adb_cli.rs index 07c34b2..8924676 100644 --- a/examples/adb_cli.rs +++ b/examples/adb_cli.rs @@ -1,4 +1,6 @@ +use std::fs::File; use std::net::Ipv4Addr; +use std::path::Path; use adb_client::{AdbTcpConnexion, Device, RebootType, RustADBError}; use clap::Parser; @@ -34,6 +36,14 @@ pub enum Command { TrackDevices, /// Lists available server features. HostFeatures, + /// Pushes 'filename' to the 'path' on device + Push { filename: String, path: String }, + /// Pushes 'path' on the device to 'filename' + Pull { path: String, filename: String }, + /// List files for 'path' on device + List { path: String }, + /// Stat file specified as 'path' on device + Stat { path: String }, /// Run 'command' in a shell on the device, and return its output and error streams. Shell { command: Vec }, /// Reboots the device @@ -99,6 +109,23 @@ fn main() -> Result<(), RustADBError> { println!("Live list of devices attached"); connexion.track_devices(callback)?; } + Command::Pull { path, filename } => { + let mut output = File::create(Path::new(&filename)).unwrap(); // TODO: Better error handling + connexion.recv(opt.serial, &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 + connexion.send(opt.serial, &mut input, &path)?; + println!("Uploaded {filename} to {path}"); + } + Command::List { path } => { + connexion.list(opt.serial, path)?; + } + Command::Stat { path } => { + let stat_response = connexion.stat(opt.serial, path)?; + println!("{}", stat_response); + } Command::Shell { command } => { if command.is_empty() { connexion.shell(&opt.serial)?; diff --git a/src/adb_tcp_connexion.rs b/src/adb_tcp_connexion.rs index e0ecb6f..aeb3f2e 100644 --- a/src/adb_tcp_connexion.rs +++ b/src/adb_tcp_connexion.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::{ - models::{AdbCommand, AdbRequestStatus}, + models::{AdbCommand, AdbRequestStatus, SyncCommand}, Result, RustADBError, }; @@ -41,10 +41,10 @@ impl AdbTcpConnexion { adb_command: AdbCommand, with_response: bool, ) -> Result> { - Self::send_adb_request(&mut self.tcp_stream, adb_command)?; + self.send_adb_request(adb_command)?; if with_response { - let length = Self::get_body_length(&mut self.tcp_stream)?; + let length = self.get_body_length()?; let mut body = vec![ 0; length @@ -63,20 +63,20 @@ impl AdbTcpConnexion { /// 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. - pub(crate) fn send_adb_request(tcp_stream: &mut TcpStream, command: AdbCommand) -> Result<()> { + pub(crate) fn send_adb_request(&mut self, command: AdbCommand) -> Result<()> { let adb_command_string = command.to_string(); let adb_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string); - tcp_stream.write_all(adb_request.as_bytes())?; + self.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)?; + self.tcp_stream.read_exact(&mut request_status)?; match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? { AdbRequestStatus::Fail => { // We can keep reading to get further details - let length = Self::get_body_length(tcp_stream)?; + let length = self.get_body_length()?; let mut body = vec![ 0; @@ -85,7 +85,7 @@ impl AdbTcpConnexion { .map_err(|_| RustADBError::ConvertionError)? ]; if length > 0 { - tcp_stream.read_exact(&mut body)?; + self.tcp_stream.read_exact(&mut body)?; } Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?)) @@ -94,9 +94,16 @@ impl AdbTcpConnexion { } } - pub(crate) fn get_body_length(tcp_stream: &mut TcpStream) -> Result { + /// Sends the given [SyncCommand] to ADB server, and checks that the request has been taken in consideration. + pub(crate) fn send_sync_request(&mut self, command: SyncCommand) -> Result<()> { + // First 4 bytes are the name of the command we want to send + // (e.g. "SEND", "RECV", "STAT", "LIST") + Ok(self.tcp_stream.write_all(command.to_string().as_bytes())?) + } + + pub(crate) fn get_body_length(&mut self) -> Result { let mut length = [0; 4]; - tcp_stream.read_exact(&mut length)?; + self.tcp_stream.read_exact(&mut length)?; Ok(u32::from_str_radix(str::from_utf8(&length)?, 16)?) } diff --git a/src/commands/devices.rs b/src/commands/devices.rs index 72829a5..d23700d 100644 --- a/src/commands/devices.rs +++ b/src/commands/devices.rs @@ -38,10 +38,10 @@ impl AdbTcpConnexion { /// 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(&mut self.tcp_stream, AdbCommand::TrackDevices)?; + self.send_adb_request(AdbCommand::TrackDevices)?; loop { - let length = Self::get_body_length(&mut self.tcp_stream)?; + let length = self.get_body_length()?; if length > 0 { let mut body = vec![ diff --git a/src/commands/host_features.rs b/src/commands/host_features.rs index 45ebe45..1c1eebb 100644 --- a/src/commands/host_features.rs +++ b/src/commands/host_features.rs @@ -7,11 +7,10 @@ impl AdbTcpConnexion { /// Lists available ADB server features. pub fn host_features(&mut self, serial: &Option) -> Result> { match serial { - None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?, - Some(serial) => Self::send_adb_request( - &mut self.tcp_stream, - AdbCommand::TransportSerial(serial.to_string()), - )?, + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } } let features = self.proxy_connexion(AdbCommand::HostFeatures, true)?; diff --git a/src/commands/list.rs b/src/commands/list.rs new file mode 100644 index 0000000..d7eb003 --- /dev/null +++ b/src/commands/list.rs @@ -0,0 +1,72 @@ +use crate::{ + models::{AdbCommand, SyncCommand}, + AdbTcpConnexion, Result, +}; +use byteorder::{ByteOrder, LittleEndian}; +use std::{ + io::{Read, Write}, + str, +}; + +impl AdbTcpConnexion { + /// Lists files in [path] on the device. + pub fn list>(&mut self, serial: Option, path: A) -> Result<()> { + self.new_connection()?; + + match serial { + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } + } + + // Set device in SYNC mode + self.send_adb_request(AdbCommand::Sync)?; + + // Send a list command + self.send_sync_request(SyncCommand::List(path.as_ref()))?; + + self.handle_list_command(path) + } + + // This command does not seem to work correctly. The devices I test it on just resturn + // 'DONE' directly without listing anything. + fn handle_list_command>(&mut self, path: S) -> Result<()> { + let mut len_buf = [0_u8; 4]; + LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32); + + // 4 bytes of command name is already sent by send_sync_request + self.tcp_stream.write_all(&len_buf)?; + + // List sends the string of the directory to list, and then the server sends a list of files + self.tcp_stream + .write_all(path.as_ref().to_string().as_bytes())?; + + // Reads returned status code from ADB server + let mut response = [0_u8; 4]; + loop { + self.tcp_stream.read_exact(&mut response)?; + match str::from_utf8(response.as_ref())? { + "DENT" => { + // TODO: Move this to a struct that extract this data, but as the device + // I test this on does not return anything, I can't test it. + let mut file_mod = [0_u8; 4]; + let mut file_size = [0_u8; 4]; + let mut mod_time = [0_u8; 4]; + let mut name_len = [0_u8; 4]; + self.tcp_stream.read_exact(&mut file_mod)?; + self.tcp_stream.read_exact(&mut file_size)?; + self.tcp_stream.read_exact(&mut mod_time)?; + self.tcp_stream.read_exact(&mut name_len)?; + let name_len = LittleEndian::read_u32(&name_len); + let mut name_buf = vec![0_u8; name_len as usize]; + self.tcp_stream.read_exact(&mut name_buf)?; + } + "DONE" => { + return Ok(()); + } + x => println!("Unknown response {}", x), + } + } + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 7c4007a..1b66786 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,7 +1,11 @@ mod devices; mod host_features; mod kill; +mod list; mod reboot; +mod recv; +mod send; mod shell; +mod stat; mod transport; mod version; diff --git a/src/commands/reboot.rs b/src/commands/reboot.rs index 82dd467..17d2071 100644 --- a/src/commands/reboot.rs +++ b/src/commands/reboot.rs @@ -11,11 +11,10 @@ impl AdbTcpConnexion { reboot_type: RebootType, ) -> Result<()> { match serial { - None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?, - Some(serial) => Self::send_adb_request( - &mut self.tcp_stream, - AdbCommand::TransportSerial(serial.to_string()), - )?, + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } } self.proxy_connexion(AdbCommand::Reboot(reboot_type), false) diff --git a/src/commands/recv.rs b/src/commands/recv.rs new file mode 100644 index 0000000..a4bb003 --- /dev/null +++ b/src/commands/recv.rs @@ -0,0 +1,77 @@ +use crate::{ + models::{AdbCommand, SyncCommand}, + AdbTcpConnexion, Result, RustADBError, +}; +use byteorder::{ByteOrder, LittleEndian}; +use std::io::{Read, Write}; + +impl AdbTcpConnexion { + /// Receives [path] to [stream] from the device. + pub fn recv>( + &mut self, + serial: Option, + path: A, + stream: &mut dyn Write, + ) -> Result<()> { + self.new_connection()?; + + match serial { + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } + } + + // Set device in SYNC mode + self.send_adb_request(AdbCommand::Sync)?; + + // Send a recv command + self.send_sync_request(SyncCommand::Recv(path.as_ref(), stream))?; + + self.handle_recv_command(path, stream) + } + + fn handle_recv_command>( + &mut self, + from: S, + output: &mut dyn Write, + ) -> Result<()> { + // First send 8 byte common header + let mut len_buf = [0_u8; 4]; + LittleEndian::write_u32(&mut len_buf, from.as_ref().len() as u32); + self.tcp_stream.write_all(&len_buf)?; + self.tcp_stream.write_all(from.as_ref().as_bytes())?; + + // Then we receive the byte data in chunks of up to 64k + // Chunk looks like 'DATA' + let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed? + let mut data_header = [0_u8; 4]; // DATA + let mut len_header = [0_u8; 4]; // + loop { + self.tcp_stream.read_exact(&mut data_header)?; + // Check if data_header is DATA or DONE + if data_header.eq(b"DATA") { + self.tcp_stream.read_exact(&mut len_header)?; + let length: usize = LittleEndian::read_u32(&len_header).try_into().unwrap(); + self.tcp_stream.read_exact(&mut buffer[..length])?; + output.write_all(&buffer)?; + } else if data_header.eq(b"DONE") { + // We're done here + break; + } else if data_header.eq(b"FAIL") { + // Handle fail + self.tcp_stream.read_exact(&mut len_header)?; + let length: usize = LittleEndian::read_u32(&len_header).try_into().unwrap(); + self.tcp_stream.read_exact(&mut buffer[..length])?; + Err(RustADBError::ADBRequestFailed(String::from_utf8( + buffer[..length].to_vec(), + )?))?; + } else { + panic!("Unknown response from device {:#?}", data_header); + } + } + + // Connection should've left SYNC by now + Ok(()) + } +} diff --git a/src/commands/send.rs b/src/commands/send.rs new file mode 100644 index 0000000..bdf5a9a --- /dev/null +++ b/src/commands/send.rs @@ -0,0 +1,100 @@ +use crate::{ + models::{AdbCommand, AdbRequestStatus, SyncCommand}, + AdbTcpConnexion, Result, RustADBError, +}; +use byteorder::{ByteOrder, LittleEndian}; +use std::{ + convert::TryInto, + io::{Read, Write}, + str::{self, FromStr}, + time::SystemTime, +}; + +impl AdbTcpConnexion { + /// Sends [stream] to [path] on the device. + pub fn send>( + &mut self, + serial: Option, + stream: &mut dyn Read, + path: A, + ) -> Result<()> { + self.new_connection()?; + + match serial { + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } + } + + // Set device in SYNC mode + self.send_adb_request(AdbCommand::Sync)?; + + // Send a send command + self.send_sync_request(SyncCommand::Send(stream, path.as_ref()))?; + + self.handle_send_command(stream, path) + } + + fn handle_send_command>(&mut self, input: &mut dyn Read, to: S) -> Result<()> { + // Append the permission flags to the filename + let to = to.as_ref().to_string() + ",0777"; + + // The name of command is already sent by send_sync_request + let mut len_buf = [0_u8; 4]; + LittleEndian::write_u32(&mut len_buf, to.len() as u32); + self.tcp_stream.write_all(&len_buf)?; + + // Send appends the filemode to the string sent + self.tcp_stream.write_all(to.as_bytes())?; + + // Then we send the byte data in chunks of up to 64k + // Chunk looks like 'DATA' + let mut buffer = [0_u8; 64 * 1024]; + loop { + let bytes_read = input.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + let mut chunk_len_buf = [0_u8; 4]; + LittleEndian::write_u32(&mut chunk_len_buf, bytes_read as u32); + self.tcp_stream.write_all(b"DATA")?; + self.tcp_stream.write_all(&chunk_len_buf)?; + self.tcp_stream.write_all(&buffer[..bytes_read])?; + } + + // When we are done sending, we send 'DONE' + // Re-use len_buf to send the last modified time + let last_modified = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n, + Err(_) => panic!("SystemTime before UNIX EPOCH!"), + }; + LittleEndian::write_u32(&mut len_buf, last_modified.as_secs() as u32); + self.tcp_stream.write_all(b"DONE")?; + self.tcp_stream.write_all(&len_buf)?; + + // We expect 'OKAY' response from this + let mut request_status = [0; 4]; + self.tcp_stream.read_exact(&mut request_status)?; + + match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? { + AdbRequestStatus::Fail => { + // We can keep reading to get further details + let length = self.get_body_length()?; + + let mut body = vec![ + 0; + length + .try_into() + .map_err(|_| RustADBError::ConvertionError)? + ]; + if length > 0 { + self.tcp_stream.read_exact(&mut body)?; + } + + Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?)) + } + AdbRequestStatus::Okay => Ok(()), + } + } +} diff --git a/src/commands/shell.rs b/src/commands/shell.rs index f4b86c2..1959c4a 100644 --- a/src/commands/shell.rs +++ b/src/commands/shell.rs @@ -23,22 +23,18 @@ impl AdbTcpConnexion { self.new_connection()?; match serial { - None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?, - Some(serial) => Self::send_adb_request( - &mut self.tcp_stream, - AdbCommand::TransportSerial(serial.to_string()), - )?, + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } } - Self::send_adb_request( - &mut self.tcp_stream, - 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(" "), + ))?; let buffer_size = 512; loop { @@ -66,7 +62,7 @@ impl AdbTcpConnexion { self.tcp_stream.set_nodelay(true)?; - // FORWARD CRTL+C !! + // FORWARD CTRL+C !! let supported_features = self.host_features(serial)?; if !supported_features.contains(&HostFeatures::ShellV2) @@ -78,13 +74,12 @@ impl AdbTcpConnexion { self.new_connection()?; match serial { - None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?, - Some(serial) => Self::send_adb_request( - &mut self.tcp_stream, - AdbCommand::TransportSerial(serial.to_string()), - )?, + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } } - Self::send_adb_request(&mut self.tcp_stream, AdbCommand::Shell)?; + self.send_adb_request(AdbCommand::Shell)?; // 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 new file mode 100644 index 0000000..8c7a401 --- /dev/null +++ b/src/commands/stat.rs @@ -0,0 +1,99 @@ +use std::{ + fmt::Display, + io::{Read, Write}, + time::{Duration, UNIX_EPOCH}, +}; + +use byteorder::{ByteOrder, LittleEndian}; +use chrono::{DateTime, Utc}; + +use crate::{ + models::{AdbCommand, SyncCommand}, + AdbTcpConnexion, Result, RustADBError, +}; + +#[derive(Debug)] +pub struct AdbStatResponse { + pub file_perm: u32, + pub file_size: u32, + pub mod_time: u32, +} + +impl From<[u8; 12]> for AdbStatResponse { + fn from(value: [u8; 12]) -> Self { + Self { + file_perm: LittleEndian::read_u32(&value[0..4]), + file_size: LittleEndian::read_u32(&value[4..8]), + mod_time: LittleEndian::read_u32(&value[8..]), + } + } +} + +impl Display for AdbStatResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let d = UNIX_EPOCH + Duration::from_secs(self.mod_time.into()); + // Create DateTime from SystemTime + let datetime = DateTime::::from(d); + + writeln!(f, "File permissions: {}", self.file_perm)?; + writeln!(f, "File size: {} bytes", self.file_size)?; + write!( + f, + "Modification time: {}", + datetime.format("%Y-%m-%d %H:%M:%S.%f %Z") + )?; + Ok(()) + } +} + +impl AdbTcpConnexion { + fn handle_stat_command>(&mut self, path: S) -> Result { + let mut len_buf = [0_u8; 4]; + LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32); + + // 4 bytes of command name is already sent by send_sync_request + self.tcp_stream.write_all(&len_buf)?; + self.tcp_stream + .write_all(path.as_ref().to_string().as_bytes())?; + + // Reads returned status code from ADB server + let mut response = [0_u8; 4]; + self.tcp_stream.read_exact(&mut response)?; + match std::str::from_utf8(response.as_ref())? { + "STAT" => { + let mut data = [0_u8; 12]; + self.tcp_stream.read_exact(&mut data)?; + + Ok(data.into()) + } + x => Err(RustADBError::UnknownResponseType(format!( + "Unknown response {}", + x + ))), + } + } + + /// Stat file given as [path] on the device. + pub fn stat>( + &mut self, + serial: Option, + path: A, + ) -> Result { + self.new_connection()?; + + match serial { + None => self.send_adb_request(AdbCommand::TransportAny)?, + Some(serial) => { + self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))? + } + } + + // Set device in SYNC mode + self.send_adb_request(AdbCommand::Sync)?; + + // Send a "Stat" command + self.send_sync_request(SyncCommand::Stat(path.as_ref()))?; + + self.handle_stat_command(path) + } +} diff --git a/src/models/adb_command.rs b/src/models/adb_command.rs index 9889ac2..d76976e 100644 --- a/src/models/adb_command.rs +++ b/src/models/adb_command.rs @@ -40,7 +40,7 @@ pub enum AdbCommand { // FrameBuffer, // JDWP(u32), // TrackJDWP, - // Sync, + Sync, // Reverse(String), Reboot(RebootType), } @@ -52,6 +52,7 @@ impl ToString for AdbCommand { AdbCommand::Kill => "host:kill".into(), AdbCommand::Devices => "host:devices".into(), AdbCommand::DevicesLong => "host:devices-l".into(), + AdbCommand::Sync => "sync:".into(), AdbCommand::TrackDevices => "host:track-devices".into(), AdbCommand::TransportAny => "host:transport-any".into(), AdbCommand::TransportSerial(serial) => format!("host:transport:{serial}"), diff --git a/src/models/mod.rs b/src/models/mod.rs index c533f40..d16a8dd 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -6,6 +6,7 @@ mod device_long; mod device_state; mod host_features; mod reboot_type; +mod sync_command; pub use adb_command::AdbCommand; pub use adb_request_status::AdbRequestStatus; @@ -15,3 +16,4 @@ pub use device_long::DeviceLong; pub use device_state::DeviceState; pub use host_features::HostFeatures; pub use reboot_type::RebootType; +pub use sync_command::SyncCommand; diff --git a/src/models/sync_command.rs b/src/models/sync_command.rs new file mode 100644 index 0000000..4ac0325 --- /dev/null +++ b/src/models/sync_command.rs @@ -0,0 +1,22 @@ +pub enum SyncCommand<'a> { + /// List files in a folder + List(&'a str), + /// Receive a file from the device + Recv(&'a str, &'a mut dyn std::io::Write), + /// Send a file to the device + Send(&'a mut dyn std::io::Read, &'a str), + // Stat a file + Stat(&'a str), +} + +impl ToString for SyncCommand<'_> { + fn to_string(&self) -> String { + match self { + SyncCommand::List(_) => "LIST", + SyncCommand::Recv(_, _) => "RECV", + SyncCommand::Send(_, _) => "SEND", + SyncCommand::Stat(_) => "STAT", + } + .to_string() + } +}