Skip to content

Commit

Permalink
feat: add install command (tcp + usb) (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
cocool97 authored Nov 14, 2024
1 parent ec0ae68 commit b7ae0b1
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 5 deletions.
6 changes: 6 additions & 0 deletions adb_cli/src/commands/local.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::Parser;
use std::path::PathBuf;

use crate::models::RebootTypeCommand;

Expand Down Expand Up @@ -37,4 +38,9 @@ pub enum LocalCommand {
/// Path to output file (created if not exists)
path: Option<String>,
},
/// Install an APK on device
Install {
/// Path to APK file. Extension must be ".apk"
path: PathBuf,
},
}
5 changes: 5 additions & 0 deletions adb_cli/src/commands/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
8 changes: 8 additions & 0 deletions adb_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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)?;
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::io::{Read, Write};
use std::path::Path;

use crate::models::AdbStatResponse;
use crate::{RebootType, Result};
Expand Down Expand Up @@ -39,4 +40,7 @@ pub trait ADBDeviceExt {

Ok(output)
}

/// Install an APK pointed to by `apk_path` on device.
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()>;
}
3 changes: 3 additions & 0 deletions adb_client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
2 changes: 2 additions & 0 deletions adb_client/src/models/adb_server_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) enum AdbServerCommand {
Pair(SocketAddrV4, String),
TransportAny,
TransportSerial(String),
Install(u64),
// Local commands
ShellCommand(String),
Shell,
Expand Down Expand Up @@ -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}"),
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion adb_client/src/server/adb_server_device_commands.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::io::{Read, Write};
use std::{
io::{Read, Write},
path::Path,
};

use crate::{
constants::BUFFER_SIZE,
Expand Down Expand Up @@ -121,4 +124,8 @@ impl ADBDeviceExt for ADBServerDevice {
fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
self.push(stream, path)
}

fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> {
self.install(apk_path)
}
}
41 changes: 41 additions & 0 deletions adb_client/src/server/device_commands/install.rs
Original file line number Diff line number Diff line change
@@ -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<P: AsRef<Path>>(&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(),
)?)),
}
}
}
1 change: 1 addition & 0 deletions adb_client/src/server/device_commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod forward;
mod framebuffer;
mod host_features;
mod install;
mod list;
mod logcat;
mod reboot;
Expand Down
52 changes: 50 additions & 2 deletions adb_client/src/usb/adb_usb_device_commands.rs
Original file line number Diff line number Diff line change
@@ -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<S: ToString, W: Write>(
Expand Down Expand Up @@ -194,4 +198,48 @@ impl ADBDeviceExt for ADBUSBDevice {

Ok(())
}

fn install<P: AsRef<std::path::Path>>(&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(),
)?)),
}
}
}
6 changes: 4 additions & 2 deletions adb_client/src/usb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
File renamed without changes.
53 changes: 53 additions & 0 deletions adb_client/src/usb/usb_writer.rs
Original file line number Diff line number Diff line change
@@ -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<usize> {
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(())
}
}
15 changes: 15 additions & 0 deletions adb_client/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::{ffi::OsStr, path::Path};

use crate::{Result, RustADBError};

pub fn u32_from_le(value: &[u8]) -> Result<u32> {
Expand All @@ -7,3 +9,16 @@ pub fn u32_from_le(value: &[u8]) -> Result<u32> {
.map_err(|_| RustADBError::ConversionError)?,
))
}

pub fn check_extension_is_apk<P: AsRef<Path>>(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(())
}

0 comments on commit b7ae0b1

Please sign in to comment.