diff --git a/adb_cli/src/commands/local.rs b/adb_cli/src/commands/local.rs index 8e4fa9c..fc5fbda 100644 --- a/adb_cli/src/commands/local.rs +++ b/adb_cli/src/commands/local.rs @@ -1,4 +1,5 @@ use clap::Parser; +use std::path::PathBuf; use crate::models::RebootTypeCommand; @@ -37,4 +38,9 @@ pub enum LocalCommand { /// Path to output file (created if not exists) path: Option, }, + /// Install an APK on device + Install { + /// Path to APK file. Extension must be ".apk" + path: PathBuf, + }, } diff --git a/adb_cli/src/commands/usb.rs b/adb_cli/src/commands/usb.rs index fbea473..cb21461 100644 --- a/adb_cli/src/commands/usb.rs +++ b/adb_cli/src/commands/usb.rs @@ -48,4 +48,9 @@ pub enum UsbCommands { #[clap(subcommand)] reboot_type: RebootTypeCommand, }, + /// Install an APK on device + Install { + /// Path to APK file. Extension must be ".apk" + path: PathBuf, + }, } diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 8b50ff0..012b130 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -95,6 +95,10 @@ fn main() -> Result<()> { }; device.get_logs(writer)?; } + LocalCommand::Install { path } => { + log::info!("Starting installation of APK {}...", path.display()); + device.install(path)?; + } } } Command::Host(host) => { @@ -223,6 +227,10 @@ fn main() -> Result<()> { let output = device.run_activity(&package, &activity)?; std::io::stdout().write_all(&output)?; } + UsbCommands::Install { path } => { + log::info!("Starting installation of APK {}...", path.display()); + device.install(path)?; + } } } } diff --git a/adb_client/src/adb_device_ext.rs b/adb_client/src/adb_device_ext.rs index 52b725b..5c92cad 100644 --- a/adb_client/src/adb_device_ext.rs +++ b/adb_client/src/adb_device_ext.rs @@ -1,4 +1,5 @@ use std::io::{Read, Write}; +use std::path::Path; use crate::models::AdbStatResponse; use crate::{RebootType, Result}; @@ -39,4 +40,7 @@ pub trait ADBDeviceExt { Ok(output) } + + /// Install an APK pointed to by `apk_path` on device. + fn install>(&mut self, apk_path: P) -> Result<()>; } diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index 1fa4cc8..2f24bb4 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -87,4 +87,7 @@ pub enum RustADBError { /// Cannot convert given data from slice #[error(transparent)] TryFromSliceError(#[from] std::array::TryFromSliceError), + /// Given path does not represent an APK + #[error("wrong file extension: {0}")] + WrongFileExtension(String), } diff --git a/adb_client/src/models/adb_server_command.rs b/adb_client/src/models/adb_server_command.rs index be72674..c66d089 100644 --- a/adb_client/src/models/adb_server_command.rs +++ b/adb_client/src/models/adb_server_command.rs @@ -16,6 +16,7 @@ pub(crate) enum AdbServerCommand { Pair(SocketAddrV4, String), TransportAny, TransportSerial(String), + Install(u64), // Local commands ShellCommand(String), Shell, @@ -61,6 +62,7 @@ impl Display for AdbServerCommand { AdbServerCommand::Reverse(remote, local) => { write!(f, "reverse:forward:{remote};{local}") } + AdbServerCommand::Install(size) => write!(f, "exec:cmd package 'install' -S {size}"), } } } diff --git a/adb_client/src/server/adb_server_device_commands.rs b/adb_client/src/server/adb_server_device_commands.rs index 16e7796..7e85d16 100644 --- a/adb_client/src/server/adb_server_device_commands.rs +++ b/adb_client/src/server/adb_server_device_commands.rs @@ -1,4 +1,7 @@ -use std::io::{Read, Write}; +use std::{ + io::{Read, Write}, + path::Path, +}; use crate::{ constants::BUFFER_SIZE, @@ -121,4 +124,8 @@ impl ADBDeviceExt for ADBServerDevice { fn push>(&mut self, stream: R, path: A) -> Result<()> { self.push(stream, path) } + + fn install>(&mut self, apk_path: P) -> Result<()> { + self.install(apk_path) + } } diff --git a/adb_client/src/server/device_commands/install.rs b/adb_client/src/server/device_commands/install.rs new file mode 100644 index 0000000..275365b --- /dev/null +++ b/adb_client/src/server/device_commands/install.rs @@ -0,0 +1,41 @@ +use std::{fs::File, io::Read, path::Path}; + +use crate::{models::AdbServerCommand, utils::check_extension_is_apk, ADBServerDevice, Result}; + +impl ADBServerDevice { + /// Install an APK on device + pub fn install>(&mut self, apk_path: P) -> Result<()> { + let mut apk_file = File::open(&apk_path)?; + + check_extension_is_apk(&apk_path)?; + + let file_size = apk_file.metadata()?.len(); + + let serial: String = self.identifier.clone(); + self.connect()? + .send_adb_request(AdbServerCommand::TransportSerial(serial))?; + + self.get_transport_mut() + .send_adb_request(AdbServerCommand::Install(file_size))?; + + let mut raw_connection = self.get_transport_mut().get_raw_connection()?; + + std::io::copy(&mut apk_file, &mut raw_connection)?; + + let mut data = [0; 1024]; + let read_amount = self.get_transport().get_raw_connection()?.read(&mut data)?; + + match &data[0..read_amount] { + b"Success\n" => { + log::info!( + "APK file {} successfully installed", + apk_path.as_ref().display() + ); + Ok(()) + } + d => Err(crate::RustADBError::ADBRequestFailed(String::from_utf8( + d.to_vec(), + )?)), + } + } +} diff --git a/adb_client/src/server/device_commands/mod.rs b/adb_client/src/server/device_commands/mod.rs index ea6bae4..3117163 100644 --- a/adb_client/src/server/device_commands/mod.rs +++ b/adb_client/src/server/device_commands/mod.rs @@ -1,6 +1,7 @@ mod forward; mod framebuffer; mod host_features; +mod install; mod list; mod logcat; mod reboot; diff --git a/adb_client/src/usb/adb_usb_device_commands.rs b/adb_client/src/usb/adb_usb_device_commands.rs index 025e9a9..36e9c80 100644 --- a/adb_client/src/usb/adb_usb_device_commands.rs +++ b/adb_client/src/usb/adb_usb_device_commands.rs @@ -1,12 +1,16 @@ use crate::{ models::AdbStatResponse, usb::{ADBUsbMessage, USBCommand, USBSubcommand}, + utils::check_extension_is_apk, ADBDeviceExt, ADBUSBDevice, RebootType, Result, RustADBError, }; use rand::Rng; -use std::io::{Read, Write}; +use std::{ + fs::File, + io::{Read, Write}, +}; -use super::USBShellWriter; +use super::{USBShellWriter, USBWriter}; impl ADBDeviceExt for ADBUSBDevice { fn shell_command( @@ -194,4 +198,48 @@ impl ADBDeviceExt for ADBUSBDevice { Ok(()) } + + fn install>(&mut self, apk_path: P) -> Result<()> { + let mut apk_file = File::open(&apk_path)?; + + check_extension_is_apk(&apk_path)?; + + let file_size = apk_file.metadata()?.len(); + + let mut rng = rand::thread_rng(); + + let local_id = rng.gen(); + + let message = ADBUsbMessage::new( + USBCommand::Open, + local_id, + 0, + format!("exec:cmd package 'install' -S {}\0", file_size) + .as_bytes() + .to_vec(), + ); + self.transport.write_message(message)?; + + let response = self.transport.read_message()?; + let remote_id = response.header().arg0(); + + let mut writer = USBWriter::new(self.transport.clone(), local_id, remote_id); + + std::io::copy(&mut apk_file, &mut writer)?; + + let final_status = self.transport.read_message()?; + + match final_status.into_payload().as_slice() { + b"Success\n" => { + log::info!( + "APK file {} successfully installed", + apk_path.as_ref().display() + ); + Ok(()) + } + d => Err(crate::RustADBError::ADBRequestFailed(String::from_utf8( + d.to_vec(), + )?)), + } + } } diff --git a/adb_client/src/usb/mod.rs b/adb_client/src/usb/mod.rs index 24d9e73..a23a998 100644 --- a/adb_client/src/usb/mod.rs +++ b/adb_client/src/usb/mod.rs @@ -3,10 +3,12 @@ mod adb_usb_device; mod adb_usb_device_commands; mod adb_usb_message; mod usb_commands; -mod usb_shell; +mod usb_shell_writer; +mod usb_writer; pub use adb_rsa_key::ADBRsaKey; pub use adb_usb_device::ADBUSBDevice; pub use adb_usb_message::{ADBUsbMessage, ADBUsbMessageHeader}; pub use usb_commands::{USBCommand, USBSubcommand}; -pub use usb_shell::USBShellWriter; +pub use usb_shell_writer::USBShellWriter; +pub use usb_writer::USBWriter; diff --git a/adb_client/src/usb/usb_shell.rs b/adb_client/src/usb/usb_shell_writer.rs similarity index 100% rename from adb_client/src/usb/usb_shell.rs rename to adb_client/src/usb/usb_shell_writer.rs diff --git a/adb_client/src/usb/usb_writer.rs b/adb_client/src/usb/usb_writer.rs new file mode 100644 index 0000000..61b31b5 --- /dev/null +++ b/adb_client/src/usb/usb_writer.rs @@ -0,0 +1,53 @@ +use std::io::{ErrorKind, Write}; + +use crate::USBTransport; + +use super::{ADBUsbMessage, USBCommand}; + +/// Wraps a `Writer` to hide underlying ADB protocol write logic. +/// +/// Read received responses to check that message has been received. +pub struct USBWriter { + transport: USBTransport, + local_id: u32, + remote_id: u32, +} + +impl USBWriter { + pub fn new(transport: USBTransport, local_id: u32, remote_id: u32) -> Self { + Self { + transport, + local_id, + remote_id, + } + } +} + +impl Write for USBWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let message = ADBUsbMessage::new( + USBCommand::Write, + self.local_id, + self.remote_id, + buf.to_vec(), + ); + self.transport + .write_message(message) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + + match self.transport.read_message() { + Ok(response) => match response.header().command() { + USBCommand::Okay => Ok(buf.len()), + c => Err(std::io::Error::new( + ErrorKind::Other, + format!("wrong response received: {c}"), + )), + }, + Err(e) => Err(std::io::Error::new(ErrorKind::Other, e)), + } + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/adb_client/src/utils.rs b/adb_client/src/utils.rs index 5b05a60..051ea57 100644 --- a/adb_client/src/utils.rs +++ b/adb_client/src/utils.rs @@ -1,3 +1,5 @@ +use std::{ffi::OsStr, path::Path}; + use crate::{Result, RustADBError}; pub fn u32_from_le(value: &[u8]) -> Result { @@ -7,3 +9,16 @@ pub fn u32_from_le(value: &[u8]) -> Result { .map_err(|_| RustADBError::ConversionError)?, )) } + +pub fn check_extension_is_apk>(path: P) -> Result<()> { + if let Some(extension) = path.as_ref().extension() { + if ![OsStr::new("apk")].contains(&extension) { + return Err(RustADBError::WrongFileExtension(format!( + "{} is not an APK file", + extension.to_string_lossy() + ))); + } + } + + Ok(()) +}