Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: massive internal refactoring, no breaking change #64

Merged
merged 6 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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