Skip to content

Commit

Permalink
feat: add direct devices TLS support; refactoring (#64)
Browse files Browse the repository at this point in the history
* feat: massive internal refactoring

* feat: fix doc + add 'doc' github action

* feat: improve code; add TLS

---------

Co-authored-by: LIAUD Corentin <[email protected]>
  • Loading branch information
cli-s1n and cocool97 authored Nov 29, 2024
1 parent 152836f commit 9eeb8f7
Show file tree
Hide file tree
Showing 70 changed files with 1,754 additions and 946 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/rust-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<a href="https://crates.io/crates/adb_client">
<img alt="crates.io" src="https://img.shields.io/crates/v/adb_client.svg"/>
</a>
<a href="https://github.com/cocool97/adb_client/actions">
<img alt="ci status" src="https://github.com/cocool97/adb_client/actions/workflows/rust-build.yml/badge.svg"/>
</a>
<a href="https://deps.rs/repo/github/cocool97/adb_client">
<img alt="dependency status" src="https://deps.rs/repo/github/cocool97/adb_client/status.svg"/>
</a>
Expand All @@ -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

Expand Down
2 changes: 2 additions & 0 deletions adb_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
44 changes: 44 additions & 0 deletions adb_cli/src/commands/tcp.rs
Original file line number Diff line number Diff line change
@@ -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<String> },
/// 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,
},
}
60 changes: 58 additions & 2 deletions adb_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(())
Expand Down
4 changes: 3 additions & 1 deletion adb_cli/src/models/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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),
}
10 changes: 6 additions & 4 deletions adb_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
24 changes: 18 additions & 6 deletions adb_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;
Expand All @@ -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};
Expand All @@ -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};
Expand All @@ -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());
```
16 changes: 8 additions & 8 deletions adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S: ToString, W: Write>(
&mut self,
command: impl IntoIterator<Item = S>,
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<R: Read, W: Write + Send + 'static>(&mut self, reader: R, writer: W) -> Result<()>;

/// Display the stat information for a remote file
fn stat(&mut self, remote_path: &str) -> Result<AdbStatResponse>;

/// 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<A: AsRef<str>, 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<R: Read, A: AsRef<str>>(&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.
Expand Down
Loading

0 comments on commit 9eeb8f7

Please sign in to comment.