diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml
index a501df5..2b0dc43 100644
--- a/.github/workflows/rust-quality.yml
+++ b/.github/workflows/rust-quality.yml
@@ -23,6 +23,14 @@ jobs:
- name: Run formatter
run : cargo fmt --all --check
+ doc:
+ name: "doc"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Run doc
+ run : cargo doc --all-features --keep-going
+
tests:
name: "tests"
runs-on: ubuntu-latest
diff --git a/README.md b/README.md
index 6b7c2ee..ad91ca7 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,9 @@
+
+
+
@@ -20,24 +23,27 @@
Main features of this library:
- Full Rust, don't use `adb *` shell commands to interact with devices
-- Supports:
- - **TCP/IP** protocol, using ADB server as a proxy (standard behavior when using `adb` CLI)
- - **USB** protocol, interacting directly with end devices
+- Supports
+ - Using ADB server as a proxy (standard behavior when using `adb` CLI)
+ - Connecting directly to end devices (without using adb-server)
+ - Over **USB**
+ - Over **TCP/IP**
- Implements hidden `adb` features, like `framebuffer`
- Highly configurable
- Easy to use !
## adb_client
-Rust library implementing both ADB protocols and providing a high-level abstraction over many supported commands.
+Rust library implementing both ADB protocols (server and end-devices) and providing a high-level abstraction over the many supported commands.
-Improved documentation [here](./adb_client/README.md).
+Improved documentation available [here](./adb_client/README.md).
## adb_cli
-Rust binary providing an improved version of official `adb` CLI, wrapping `adb_client` library. Can act as an usage example of the library.
+Rust binary providing an improved version of Google's official `adb` CLI, by using `adb_client` library.
+Provides an usage example of the library.
-Improved documentation [here](./adb_cli/README.md).
+Improved documentation available [here](./adb_cli/README.md).
## Related publications
diff --git a/adb_cli/src/commands/mod.rs b/adb_cli/src/commands/mod.rs
index 3db1f7e..e526e01 100644
--- a/adb_cli/src/commands/mod.rs
+++ b/adb_cli/src/commands/mod.rs
@@ -1,9 +1,11 @@
mod emu;
mod host;
mod local;
+mod tcp;
mod usb;
pub use emu::EmuCommand;
pub use host::HostCommand;
pub use local::LocalCommand;
+pub use tcp::{TcpCommand, TcpCommands};
pub use usb::{UsbCommand, UsbCommands};
diff --git a/adb_cli/src/commands/tcp.rs b/adb_cli/src/commands/tcp.rs
new file mode 100644
index 0000000..4eb5ac1
--- /dev/null
+++ b/adb_cli/src/commands/tcp.rs
@@ -0,0 +1,44 @@
+use std::net::SocketAddr;
+use std::path::PathBuf;
+
+use clap::Parser;
+
+use crate::models::RebootTypeCommand;
+
+#[derive(Parser, Debug)]
+pub struct TcpCommand {
+ pub address: SocketAddr,
+ #[clap(subcommand)]
+ pub commands: TcpCommands,
+}
+
+#[derive(Parser, Debug)]
+pub enum TcpCommands {
+ /// Spawn an interactive shell or run a list of commands on the device
+ Shell { commands: Vec },
+ /// Pull a file from device
+ Pull { source: String, destination: String },
+ /// Push a file on device
+ Push { filename: String, path: String },
+ /// Stat a file on device
+ Stat { path: String },
+ /// Run an activity on device specified by the intent
+ Run {
+ /// The package whose activity is to be invoked
+ #[clap(short = 'p', long = "package")]
+ package: String,
+ /// The activity to be invoked itself, Usually it is MainActivity
+ #[clap(short = 'a', long = "activity")]
+ activity: String,
+ },
+ /// Reboot the device
+ Reboot {
+ #[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 012b130..5b70677 100644
--- a/adb_cli/src/main.rs
+++ b/adb_cli/src/main.rs
@@ -6,10 +6,12 @@ mod adb_termios;
mod commands;
mod models;
-use adb_client::{ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBUSBDevice, DeviceShort};
+use adb_client::{
+ ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBTcpDevice, ADBUSBDevice, DeviceShort,
+};
use anyhow::{anyhow, Result};
use clap::Parser;
-use commands::{EmuCommand, HostCommand, LocalCommand, UsbCommands};
+use commands::{EmuCommand, HostCommand, LocalCommand, TcpCommands, UsbCommands};
use models::{Command, Opts};
use std::fs::File;
use std::io::Write;
@@ -233,6 +235,60 @@ fn main() -> Result<()> {
}
}
}
+ Command::Tcp(tcp) => {
+ let mut device = ADBTcpDevice::new(tcp.address)?;
+
+ match tcp.commands {
+ TcpCommands::Shell { commands } => {
+ if commands.is_empty() {
+ // Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
+ // Using a scope here would call drop() too early..
+ #[cfg(any(target_os = "linux", target_os = "macos"))]
+ {
+ let mut adb_termios = adb_termios::ADBTermios::new(std::io::stdin())?;
+ adb_termios.set_adb_termios()?;
+ device.shell(std::io::stdin(), std::io::stdout())?;
+ }
+
+ #[cfg(not(any(target_os = "linux", target_os = "macos")))]
+ {
+ device.shell(std::io::stdin(), std::io::stdout())?;
+ }
+ } else {
+ device.shell_command(commands, std::io::stdout())?;
+ }
+ }
+ TcpCommands::Pull {
+ source,
+ destination,
+ } => {
+ let mut output = File::create(Path::new(&destination))?;
+ device.pull(&source, &mut output)?;
+ log::info!("Downloaded {source} as {destination}");
+ }
+ TcpCommands::Stat { path } => {
+ let stat_response = device.stat(&path)?;
+ println!("{}", stat_response);
+ }
+ TcpCommands::Reboot { reboot_type } => {
+ log::info!("Reboots device in mode {:?}", reboot_type);
+ device.reboot(reboot_type.into())?
+ }
+ TcpCommands::Push { filename, path } => {
+ let mut input = File::open(Path::new(&filename))?;
+ device.push(&mut input, &path)?;
+ log::info!("Uploaded {filename} to {path}");
+ }
+ TcpCommands::Run { package, activity } => {
+ let output = device.run_activity(&package, &activity)?;
+ std::io::stdout().write_all(&output)?;
+ }
+ TcpCommands::Install { path } => {
+ log::info!("Starting installation of APK {}...", path.display());
+ device.install(path)?;
+ }
+ }
+ }
}
Ok(())
diff --git a/adb_cli/src/models/opts.rs b/adb_cli/src/models/opts.rs
index 397856c..7817a36 100644
--- a/adb_cli/src/models/opts.rs
+++ b/adb_cli/src/models/opts.rs
@@ -2,7 +2,7 @@ use std::net::SocketAddrV4;
use clap::Parser;
-use crate::commands::{EmuCommand, HostCommand, LocalCommand, UsbCommand};
+use crate::commands::{EmuCommand, HostCommand, LocalCommand, TcpCommand, UsbCommand};
#[derive(Parser, Debug)]
#[clap(about, version, author)]
@@ -27,4 +27,6 @@ pub enum Command {
Emu(EmuCommand),
/// Device commands via USB, no server needed
Usb(UsbCommand),
+ /// Device commands via TCP, no server needed
+ Tcp(TcpCommand),
}
diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml
index bc15dfe..cf33e94 100644
--- a/adb_client/Cargo.toml
+++ b/adb_client/Cargo.toml
@@ -15,15 +15,17 @@ bincode = { version = "1.3.3" }
byteorder = { version = "1.5.0" }
chrono = { version = "0.4.38" }
homedir = { version = "0.3.4" }
-image = { version = "0.25.4" }
-lazy_static = { version = "1.5.0" }
-log = { version = "0.4.22" }
+image = { version = "0.25.5" }
+log = { version = "0.4.22", features = ["max_level_debug", "release_max_level_debug"]}
num-bigint = { version = "0.8.4", package = "num-bigint-dig" }
num-traits = { version = "0.2.19" }
rand = { version = "0.8.5" }
+rcgen = { version = "0.13.1" }
regex = { version = "1.11.0", features = ["perf", "std", "unicode"] }
-rsa = { version = "0.9.6" }
+rsa = { version = "0.9.7" }
rusb = { version = "0.9.4", features = ["vendored"] }
+rustls = { version = "0.23.18" }
+rustls-pki-types = "1.10.0"
serde = { version = "1.0.210", features = ["derive"] }
serde_repr = { version = "0.1.19" }
sha1 = { version = "0.10.6", features = ["oid"] }
diff --git a/adb_client/README.md b/adb_client/README.md
index f29bca5..6595ec1 100644
--- a/adb_client/README.md
+++ b/adb_client/README.md
@@ -31,9 +31,9 @@ let mut server = ADBServer::new(SocketAddrV4::new(server_ip, server_port));
server.devices();
```
-### Using ADB server as proxy
+### Using ADB server as bridge
-#### [TCP] Launch a command on device
+#### Launch a command on device
```rust no_run
use adb_client::{ADBServer, ADBDeviceExt};
@@ -43,7 +43,7 @@ let mut device = server.get_device().expect("cannot get device");
device.shell_command(["df", "-h"],std::io::stdout());
```
-#### [TCP] Push a file to the device
+#### Push a file to the device
```rust no_run
use adb_client::ADBServer;
@@ -57,9 +57,9 @@ let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file");
device.push(&mut input, "/data/local/tmp");
```
-### Interacting directly with device
+### Interact directly with end devices
-#### [USB] Launch a command on device
+#### (USB) Launch a command on device
```rust no_run
use adb_client::{ADBUSBDevice, ADBDeviceExt};
@@ -70,7 +70,7 @@ let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find de
device.shell_command(["df", "-h"],std::io::stdout());
```
-#### [USB] Push a file to the device
+#### (USB) Push a file to the device
```rust no_run
use adb_client::{ADBUSBDevice, ADBDeviceExt};
@@ -83,3 +83,15 @@ let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find de
let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file");
device.push(&mut input, "/data/local/tmp");
```
+
+### (TCP) Get a shell from device
+
+```rust no_run
+use std::net::{SocketAddr, IpAddr, Ipv4Addr};
+use adb_client::{ADBTcpDevice, ADBDeviceExt};
+
+let device_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 10));
+let device_port = 43210;
+let mut device = ADBTcpDevice::new(SocketAddr::new(device_ip, device_port)).expect("cannot find device");
+device.shell(std::io::stdin(), std::io::stdout());
+```
diff --git a/adb_client/src/adb_device_ext.rs b/adb_client/src/adb_device_ext.rs
index 5c92cad..2817a44 100644
--- a/adb_client/src/adb_device_ext.rs
+++ b/adb_client/src/adb_device_ext.rs
@@ -4,30 +4,30 @@ use std::path::Path;
use crate::models::AdbStatResponse;
use crate::{RebootType, Result};
-/// Trait representing all features available on both [`ADBServerDevice`] and [`ADBUSBDevice`]
+/// Trait representing all features available on devices.
pub trait ADBDeviceExt {
- /// Runs 'command' in a shell on the device, and write its output and error streams into [`output`].
+ /// Run 'command' in a shell on the device, and write its output and error streams into `output`.
fn shell_command(
&mut self,
command: impl IntoIterator- ,
output: W,
) -> Result<()>;
- /// Starts an interactive shell session on the device.
- /// Input data is read from [reader] and write to [writer].
- /// [W] has a 'static bound as it is internally used in a thread.
+ /// Start an interactive shell session on the device.
+ /// Input data is read from `reader` and write to `writer`.
+ /// `W` has a 'static bound as it is internally used in a thread.
fn shell(&mut self, reader: R, writer: W) -> Result<()>;
/// Display the stat information for a remote file
fn stat(&mut self, remote_path: &str) -> Result;
- /// Pull the remote file pointed to by [source] and write its contents into [`output`]
+ /// Pull the remote file pointed to by `source` and write its contents into `output`
fn pull, W: Write>(&mut self, source: A, output: W) -> Result<()>;
- /// Push [stream] to [path] on the device.
+ /// Push `stream` to `path` on the device.
fn push>(&mut self, stream: R, path: A) -> Result<()>;
- /// Reboots the device using given reboot type
+ /// Reboot the device using given reboot type
fn reboot(&mut self, reboot_type: RebootType) -> Result<()>;
/// Run `activity` from `package` on device. Return the command output.
diff --git a/adb_client/src/device/adb_message_device.rs b/adb_client/src/device/adb_message_device.rs
new file mode 100644
index 0000000..4df5f5f
--- /dev/null
+++ b/adb_client/src/device/adb_message_device.rs
@@ -0,0 +1,236 @@
+use byteorder::{LittleEndian, ReadBytesExt};
+use rand::Rng;
+use std::io::{Cursor, Read, Seek};
+
+use crate::{constants::BUFFER_SIZE, ADBMessageTransport, AdbStatResponse, Result, RustADBError};
+
+use super::{models::MessageSubcommand, ADBTransportMessage, MessageCommand};
+
+/// Generic structure representing an ADB device reachable over an [`ADBMessageTransport`].
+/// Structure is totally agnostic over which transport is truly used.
+#[derive(Debug)]
+pub struct ADBMessageDevice {
+ transport: T,
+}
+
+impl ADBMessageDevice {
+ /// Instantiate a new [`ADBMessageTransport`]
+ pub fn new(transport: T) -> Self {
+ Self { transport }
+ }
+
+ pub(crate) fn get_transport(&mut self) -> &T {
+ &self.transport
+ }
+
+ pub(crate) fn get_transport_mut(&mut self) -> &mut T {
+ &mut self.transport
+ }
+
+ /// Receive a message and acknowledge it by replying with an `OKAY` command
+ pub(crate) fn recv_and_reply_okay(
+ &mut self,
+ local_id: u32,
+ remote_id: u32,
+ ) -> Result {
+ let message = self.transport.read_message()?;
+ self.transport.write_message(ADBTransportMessage::new(
+ MessageCommand::Okay,
+ local_id,
+ remote_id,
+ "".into(),
+ ))?;
+ Ok(message)
+ }
+
+ /// Expect a message with an `OKAY` command after sending a message.
+ pub(crate) fn send_and_expect_okay(
+ &mut self,
+ message: ADBTransportMessage,
+ ) -> Result {
+ self.transport.write_message(message)?;
+ let message = self.transport.read_message()?;
+ let received_command = message.header().command();
+ if received_command != MessageCommand::Okay {
+ return Err(RustADBError::ADBRequestFailed(format!(
+ "expected command OKAY after message, got {}",
+ received_command
+ )));
+ }
+ Ok(message)
+ }
+
+ pub(crate) fn recv_file(
+ &mut self,
+ local_id: u32,
+ remote_id: u32,
+ mut output: W,
+ ) -> std::result::Result<(), RustADBError> {
+ let mut len: Option = None;
+ loop {
+ let payload = self
+ .recv_and_reply_okay(local_id, remote_id)?
+ .into_payload();
+ let mut rdr = Cursor::new(&payload);
+ while rdr.position() != payload.len() as u64 {
+ match len.take() {
+ Some(0) | None => {
+ rdr.seek_relative(4)?;
+ len.replace(rdr.read_u32::()? as u64);
+ }
+ Some(length) => {
+ let remaining_bytes = payload.len() as u64 - rdr.position();
+ if length < remaining_bytes {
+ std::io::copy(&mut rdr.by_ref().take(length), &mut output)?;
+ } else {
+ std::io::copy(&mut rdr.take(remaining_bytes), &mut output)?;
+ len.replace(length - remaining_bytes);
+ // this payload is now exhausted
+ break;
+ }
+ }
+ }
+ }
+ if Cursor::new(&payload[(payload.len() - 8)..(payload.len() - 4)])
+ .read_u32::()?
+ == MessageSubcommand::Done as u32
+ {
+ break;
+ }
+ }
+ Ok(())
+ }
+
+ pub(crate) fn push_file(
+ &mut self,
+ local_id: u32,
+ remote_id: u32,
+ mut reader: R,
+ ) -> std::result::Result<(), RustADBError> {
+ let mut buffer = [0; BUFFER_SIZE];
+ let amount_read = reader.read(&mut buffer)?;
+ let subcommand_data = MessageSubcommand::Data.with_arg(amount_read as u32);
+
+ let mut serialized_message =
+ bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?;
+ serialized_message.append(&mut buffer[..amount_read].to_vec());
+
+ let message = ADBTransportMessage::new(
+ MessageCommand::Write,
+ local_id,
+ remote_id,
+ serialized_message,
+ );
+
+ self.send_and_expect_okay(message)?;
+
+ loop {
+ let mut buffer = [0; BUFFER_SIZE];
+
+ match reader.read(&mut buffer) {
+ Ok(0) => {
+ // Currently file mtime is not forwarded
+ let subcommand_data = MessageSubcommand::Done.with_arg(0);
+
+ let serialized_message = bincode::serialize(&subcommand_data)
+ .map_err(|_e| RustADBError::ConversionError)?;
+
+ let message = ADBTransportMessage::new(
+ MessageCommand::Write,
+ local_id,
+ remote_id,
+ serialized_message,
+ );
+
+ self.send_and_expect_okay(message)?;
+
+ // Command should end with a Write => Okay
+ let received = self.transport.read_message()?;
+ match received.header().command() {
+ MessageCommand::Write => return Ok(()),
+ c => {
+ return Err(RustADBError::ADBRequestFailed(format!(
+ "Wrong command received {}",
+ c
+ )))
+ }
+ }
+ }
+ Ok(size) => {
+ let subcommand_data = MessageSubcommand::Data.with_arg(size as u32);
+
+ let mut serialized_message = bincode::serialize(&subcommand_data)
+ .map_err(|_e| RustADBError::ConversionError)?;
+ serialized_message.append(&mut buffer[..size].to_vec());
+
+ let message = ADBTransportMessage::new(
+ MessageCommand::Write,
+ local_id,
+ remote_id,
+ serialized_message,
+ );
+
+ self.send_and_expect_okay(message)?;
+ }
+ Err(e) => {
+ return Err(RustADBError::IOError(e));
+ }
+ }
+ }
+ }
+
+ pub(crate) fn begin_synchronization(&mut self) -> Result<(u32, u32)> {
+ let sync_directive = "sync:\0";
+
+ let mut rng = rand::thread_rng();
+ let message = ADBTransportMessage::new(
+ MessageCommand::Open,
+ rng.gen(), /* Our 'local-id' */
+ 0,
+ sync_directive.into(),
+ );
+ let message = self.send_and_expect_okay(message)?;
+ let local_id = message.header().arg1();
+ let remote_id = message.header().arg0();
+ Ok((local_id, remote_id))
+ }
+
+ pub(crate) fn stat_with_explicit_ids(
+ &mut self,
+ remote_path: &str,
+ local_id: u32,
+ remote_id: u32,
+ ) -> Result {
+ let stat_buffer = MessageSubcommand::Stat.with_arg(remote_path.len() as u32);
+ let message = ADBTransportMessage::new(
+ MessageCommand::Write,
+ local_id,
+ remote_id,
+ bincode::serialize(&stat_buffer).map_err(|_e| RustADBError::ConversionError)?,
+ );
+ self.send_and_expect_okay(message)?;
+ self.send_and_expect_okay(ADBTransportMessage::new(
+ MessageCommand::Write,
+ local_id,
+ remote_id,
+ remote_path.into(),
+ ))?;
+ let response = self.transport.read_message()?;
+ // Skip first 4 bytes as this is the literal "STAT".
+ // Interesting part starts right after
+ bincode::deserialize(&response.into_payload()[4..])
+ .map_err(|_e| RustADBError::ConversionError)
+ }
+
+ pub(crate) fn end_transaction(&mut self, local_id: u32, remote_id: u32) -> Result<()> {
+ let quit_buffer = MessageSubcommand::Quit.with_arg(0u32);
+ self.send_and_expect_okay(ADBTransportMessage::new(
+ MessageCommand::Write,
+ local_id,
+ remote_id,
+ bincode::serialize(&quit_buffer).map_err(|_e| RustADBError::ConversionError)?,
+ ))?;
+ let _discard_close = self.transport.read_message()?;
+ Ok(())
+ }
+}
diff --git a/adb_client/src/device/adb_message_device_commands.rs b/adb_client/src/device/adb_message_device_commands.rs
new file mode 100644
index 0000000..3008e3c
--- /dev/null
+++ b/adb_client/src/device/adb_message_device_commands.rs
@@ -0,0 +1,38 @@
+use crate::{models::AdbStatResponse, ADBDeviceExt, ADBMessageTransport, RebootType, Result};
+use std::io::{Read, Write};
+
+use super::ADBMessageDevice;
+
+impl ADBDeviceExt for ADBMessageDevice {
+ fn shell_command(
+ &mut self,
+ command: impl IntoIterator
- ,
+ output: W,
+ ) -> Result<()> {
+ self.shell_command(command, output)
+ }
+
+ fn shell(&mut self, reader: R, writer: W) -> Result<()> {
+ self.shell(reader, writer)
+ }
+
+ fn stat(&mut self, remote_path: &str) -> Result {
+ self.stat(remote_path)
+ }
+
+ fn pull, W: Write>(&mut self, source: A, output: W) -> Result<()> {
+ self.pull(source, output)
+ }
+
+ fn push>(&mut self, stream: R, path: A) -> Result<()> {
+ self.push(stream, path)
+ }
+
+ fn reboot(&mut self, reboot_type: RebootType) -> Result<()> {
+ self.reboot(reboot_type)
+ }
+
+ fn install>(&mut self, apk_path: P) -> Result<()> {
+ self.install(apk_path)
+ }
+}
diff --git a/adb_client/src/device/adb_tcp_device.rs b/adb_client/src/device/adb_tcp_device.rs
new file mode 100644
index 0000000..4932221
--- /dev/null
+++ b/adb_client/src/device/adb_tcp_device.rs
@@ -0,0 +1,112 @@
+use std::net::SocketAddr;
+use std::path::Path;
+
+use super::adb_message_device::ADBMessageDevice;
+use super::models::MessageCommand;
+use super::ADBTransportMessage;
+use crate::{ADBDeviceExt, ADBMessageTransport, ADBTransport, Result, RustADBError, TcpTransport};
+
+/// Represent a device reached and available over USB.
+#[derive(Debug)]
+pub struct ADBTcpDevice {
+ inner: ADBMessageDevice,
+}
+
+impl ADBTcpDevice {
+ /// Instantiate a new [`ADBTcpDevice`]
+ pub fn new(address: SocketAddr) -> Result {
+ let mut device = Self {
+ inner: ADBMessageDevice::new(TcpTransport::new(address)?),
+ };
+
+ device.connect()?;
+
+ Ok(device)
+ }
+
+ /// Send initial connect
+ pub fn connect(&mut self) -> Result<()> {
+ self.get_transport_mut().connect()?;
+
+ let message = ADBTransportMessage::new(
+ MessageCommand::Cnxn,
+ 0x01000000,
+ 1048576,
+ format!("host::{}\0", env!("CARGO_PKG_NAME"))
+ .as_bytes()
+ .to_vec(),
+ );
+
+ self.get_transport_mut().write_message(message)?;
+
+ let message = self.get_transport_mut().read_message()?;
+
+ // At this point, we should have received a STLS message
+ if message.header().command() != MessageCommand::Stls {
+ return Err(RustADBError::ADBRequestFailed(format!(
+ "Wrong command received {}",
+ message.header().command()
+ )));
+ };
+
+ let message = ADBTransportMessage::new(MessageCommand::Stls, 1, 0, vec![]);
+
+ self.get_transport_mut().write_message(message)?;
+
+ // Upgrade TCP connection to TLS
+ self.get_transport_mut().upgrade_connection()?;
+
+ log::debug!("Connection successfully upgraded from TCP to TLS");
+
+ Ok(())
+ }
+
+ fn get_transport_mut(&mut self) -> &mut TcpTransport {
+ self.inner.get_transport_mut()
+ }
+}
+
+impl ADBDeviceExt for ADBTcpDevice {
+ fn shell_command(
+ &mut self,
+ command: impl IntoIterator
- ,
+ output: W,
+ ) -> Result<()> {
+ self.inner.shell_command(command, output)
+ }
+
+ fn shell(
+ &mut self,
+ reader: R,
+ writer: W,
+ ) -> Result<()> {
+ self.inner.shell(reader, writer)
+ }
+
+ fn stat(&mut self, remote_path: &str) -> Result {
+ self.inner.stat(remote_path)
+ }
+
+ fn pull, W: std::io::Write>(&mut self, source: A, output: W) -> Result<()> {
+ self.inner.pull(source, output)
+ }
+
+ fn push>(&mut self, stream: R, path: A) -> Result<()> {
+ self.inner.push(stream, path)
+ }
+
+ fn reboot(&mut self, reboot_type: crate::RebootType) -> Result<()> {
+ self.inner.reboot(reboot_type)
+ }
+
+ fn install>(&mut self, apk_path: P) -> Result<()> {
+ self.inner.install(apk_path)
+ }
+}
+
+impl Drop for ADBTcpDevice {
+ fn drop(&mut self) {
+ // Best effort here
+ let _ = self.get_transport_mut().disconnect();
+ }
+}
diff --git a/adb_client/src/usb/adb_usb_message.rs b/adb_client/src/device/adb_transport_message.rs
similarity index 54%
rename from adb_client/src/usb/adb_usb_message.rs
rename to adb_client/src/device/adb_transport_message.rs
index 3fb58e7..921f939 100644
--- a/adb_client/src/usb/adb_usb_message.rs
+++ b/adb_client/src/device/adb_transport_message.rs
@@ -1,31 +1,32 @@
use serde::{Deserialize, Serialize};
-use super::usb_commands::USBCommand;
use crate::RustADBError;
+use super::models::MessageCommand;
+
pub const AUTH_TOKEN: u32 = 1;
pub const AUTH_SIGNATURE: u32 = 2;
pub const AUTH_RSAPUBLICKEY: u32 = 3;
#[derive(Debug)]
-pub struct ADBUsbMessage {
- header: ADBUsbMessageHeader,
+pub struct ADBTransportMessage {
+ header: ADBTransportMessageHeader,
payload: Vec,
}
#[derive(Debug, Serialize, Deserialize)]
#[repr(C)]
-pub struct ADBUsbMessageHeader {
- command: USBCommand, /* command identifier constant */
- arg0: u32, /* first argument */
- arg1: u32, /* second argument */
- data_length: u32, /* length of payload (0 is allowed) */
- data_crc32: u32, /* crc32 of data payload */
- magic: u32, /* command ^ 0xffffffff */
+pub struct ADBTransportMessageHeader {
+ command: MessageCommand, /* command identifier constant */
+ arg0: u32, /* first argument */
+ arg1: u32, /* second argument */
+ data_length: u32, /* length of payload (0 is allowed) */
+ data_crc32: u32, /* crc32 of data payload */
+ magic: u32, /* command ^ 0xffffffff */
}
-impl ADBUsbMessageHeader {
- pub fn new(command: USBCommand, arg0: u32, arg1: u32, data: &[u8]) -> Self {
+impl ADBTransportMessageHeader {
+ pub fn new(command: MessageCommand, arg0: u32, arg1: u32, data: &[u8]) -> Self {
Self {
command,
arg0,
@@ -36,7 +37,7 @@ impl ADBUsbMessageHeader {
}
}
- pub fn command(&self) -> USBCommand {
+ pub fn command(&self) -> MessageCommand {
self.command
}
@@ -60,7 +61,7 @@ impl ADBUsbMessageHeader {
data.iter().map(|&x| x as u32).sum()
}
- fn compute_magic(command: USBCommand) -> u32 {
+ fn compute_magic(command: MessageCommand) -> u32 {
let command_u32 = command as u32;
command_u32 ^ 0xFFFFFFFF
}
@@ -70,24 +71,24 @@ impl ADBUsbMessageHeader {
}
}
-impl ADBUsbMessage {
- pub fn new(command: USBCommand, arg0: u32, arg1: u32, data: Vec) -> Self {
+impl ADBTransportMessage {
+ pub fn new(command: MessageCommand, arg0: u32, arg1: u32, data: Vec) -> Self {
Self {
- header: ADBUsbMessageHeader::new(command, arg0, arg1, &data),
+ header: ADBTransportMessageHeader::new(command, arg0, arg1, &data),
payload: data,
}
}
- pub fn from_header_and_payload(header: ADBUsbMessageHeader, payload: Vec) -> Self {
+ pub fn from_header_and_payload(header: ADBTransportMessageHeader, payload: Vec) -> Self {
Self { header, payload }
}
pub fn check_message_integrity(&self) -> bool {
- ADBUsbMessageHeader::compute_magic(self.header.command) == self.header.magic
- && ADBUsbMessageHeader::compute_crc32(&self.payload) == self.header.data_crc32
+ ADBTransportMessageHeader::compute_magic(self.header.command) == self.header.magic
+ && ADBTransportMessageHeader::compute_crc32(&self.payload) == self.header.data_crc32
}
- pub fn header(&self) -> &ADBUsbMessageHeader {
+ pub fn header(&self) -> &ADBTransportMessageHeader {
&self.header
}
@@ -100,7 +101,7 @@ impl ADBUsbMessage {
}
}
-impl TryFrom<[u8; 24]> for ADBUsbMessageHeader {
+impl TryFrom<[u8; 24]> for ADBTransportMessageHeader {
type Error = RustADBError;
fn try_from(value: [u8; 24]) -> Result {
diff --git a/adb_client/src/device/adb_usb_device.rs b/adb_client/src/device/adb_usb_device.rs
new file mode 100644
index 0000000..1c3425c
--- /dev/null
+++ b/adb_client/src/device/adb_usb_device.rs
@@ -0,0 +1,274 @@
+use rusb::Device;
+use rusb::DeviceDescriptor;
+use rusb::UsbContext;
+use std::fs::read_to_string;
+use std::path::Path;
+use std::path::PathBuf;
+use std::time::Duration;
+
+use super::adb_message_device::ADBMessageDevice;
+use super::models::MessageCommand;
+use super::{ADBRsaKey, ADBTransportMessage};
+use crate::device::adb_transport_message::{AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN};
+use crate::ADBDeviceExt;
+use crate::ADBMessageTransport;
+use crate::ADBTransport;
+use crate::{Result, RustADBError, USBTransport};
+
+/// Represent a device reached and available over USB.
+#[derive(Debug)]
+pub struct ADBUSBDevice {
+ private_key: ADBRsaKey,
+ inner: ADBMessageDevice,
+}
+
+pub fn read_adb_private_key>(private_key_path: P) -> Result