21 Commits

Author SHA1 Message Date
LIAUD Corentin
38d8384b98 fix: maturin non-interactive 2025-01-24 10:18:55 +01:00
cocool97
39591b6a0a ci: build python wheels (#84) 2025-01-24 10:11:51 +01:00
LIAUD Corentin
775b2421ec chore(ci): improve python build 2025-01-22 15:57:02 +01:00
LIAUD Corentin
62d16b70fb chore: version 2.1.0 2025-01-22 15:25:03 +01:00
cli
466d00e68a feat: Python package (#80)
* feat: create pyadb_client python package

* feat: add push / pull methods

* feat: add shell_command for USB

* feat(ci): add python package build
2025-01-22 15:22:36 +01:00
KFBI1706
144072ba1b fix: track_devices support multiple devices (#83) 2025-01-21 07:31:39 +01:00
LIAUD Corentin
331ef95530 chore: fix deps for adb_cli 2024-12-18 11:24:52 +01:00
LIAUD Corentin
61408cb470 chore: version 2.1.0 2024-12-18 11:12:56 +01:00
cli
c54942f25d feat: improve USB performances by storing endpoints (#79) 2024-12-18 10:55:40 +01:00
cli
3feda38cc3 core: bump deps + update README.md (#78) 2024-12-18 08:34:31 +01:00
cli
9002ecc0c8 chore: internal updates (#74)
* chore: update adb_client README.md

* chore(internal): add assert_command() method on ADBTransportMessage

* chore: minor internal changes

* chore: add server-only models
2024-12-13 14:08:48 +01:00
Stone
5a3ac68fce feat: do not detach kernel driver (#72) 2024-12-10 17:17:41 +01:00
cli
5dfd30cc5b breaking: make ADBDeviceExt dyn-compatible (#70)
* feat: make ADBDeviceExt dyn-compatible

* feat: clean CLI code
2024-12-06 17:47:38 +01:00
cli
66d124475d feat: add framebuffer method to USB/TCP direct devices (#69)
* feat: store local_id and remote_id in ADBMessageDevice
2024-12-03 13:55:12 +01:00
cocool97
8c382f0bde feat: add mdns devices discovery (#54)
* feat: add mdns devices discovery

---------

Co-authored-by: Jinke <164604729+JinkeJ@users.noreply.github.com>
2024-12-01 18:39:07 +01:00
cocool97
b933ab083f feat: add benches (#58)
* feat: add benches
2024-11-30 11:34:35 +01:00
cli
2f4d13bd29 feat: create an USBTransport from a rusb::Device (#66)
This allows to be more flexible on USB device we want to connect to,
without making public API to complex
2024-11-30 10:21:21 +01:00
cli
9eeb8f7da8 feat: add direct devices TLS support; refactoring (#64)
* feat: massive internal refactoring

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

* feat: improve code; add TLS

---------

Co-authored-by: LIAUD Corentin <corentinliaud26@gmail.com>
2024-11-29 13:30:03 +01:00
cocool97
152836fe54 feat: add framebuffer_bytes method (#61)
* feat: add `framebuffer_bytes` method
2024-11-18 17:15:11 +01:00
cocool97
507796887b feat: rework USB auth (#60)
* feat: rework USB auth

* ci: run clippy with `--all-features`
2024-11-18 14:43:40 +01:00
cocool97
2f08e5e16d fix: pub export AdbStatResponse (#59)
* chore: clarify licensing

* fix: pub export AdbStatResponse
2024-11-17 18:16:20 +01:00
132 changed files with 3861 additions and 1991 deletions

49
.github/workflows/python-build.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Python - Build packages & Release
on:
push: {}
pull_request: {}
release:
types: [created]
jobs:
build-python-packages:
runs-on: ubuntu-latest
defaults:
run:
working-directory: pyadb_client
steps:
- uses: actions/checkout@v4
- name: Install Python dependencies
run: pip install .
- name: Build Python packages
run: maturin build --release --interpreter "3.8" --interpreter "3.9" --interpreter "3.10" --interpreter "3.11" --interpreter "3.12" --interpreter "3.13"
publish-python-packages:
runs-on: ubuntu-latest
defaults:
run:
working-directory: pyadb_client
needs: [build-python-packages]
if: github.event_name == 'release' && github.event.action == 'created'
steps:
- uses: actions/checkout@v4
- name: Install Python dependencies
run: pip install .
- name: Publish Python packages
run: maturin publish --non-interactive
env:
MATURIN_PYPI_TOKEN: ${{ secrets.MATURIN_PYPI_TOKEN }}
- name: "Publish GitHub artefacts"
uses: softprops/action-gh-release@v2
with:
files: |
target/wheels/pyadb_client*.whl
target/wheels/pyadb_client*.tar.gz

View File

@@ -12,4 +12,4 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build project - name: Build project
run: cargo build --release run: cargo build --release --all-features

View File

@@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: rustup component add clippy - run: rustup component add clippy
- name: Run clippy - name: Run clippy
run : cargo clippy run: cargo clippy --all-features
fmt: fmt:
name: "fmt" name: "fmt"
@@ -21,7 +21,17 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run formatter - name: Run formatter
run : cargo fmt --all --check 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 --no-deps
env:
RUSTDOCFLAGS: "-D warnings"
tests: tests:
name: "tests" name: "tests"
@@ -29,4 +39,4 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run tests - name: Run tests
run: cargo test --verbose run: cargo test --verbose --all-features

View File

@@ -9,8 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: "Checkout repository" - uses: actions/checkout@v4
uses: actions/checkout@v4
- name: "Set up Rust" - name: "Set up Rust"
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -25,8 +24,15 @@ jobs:
cargo install cargo-deb cargo install cargo-deb
cargo install cargo-generate-rpm cargo install cargo-generate-rpm
- name: "Publish crates"
run: |
cargo publish -p adb_client --token ${CRATES_IO_TOKEN}
cargo publish -p adb_cli --token ${CRATES_IO_TOKEN}
env:
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
- name: "build-release" - name: "build-release"
run: cargo build --release run: cargo build --all-features --release
- name: "Build DEB package" - name: "Build DEB package"
run: cargo deb -p adb_cli run: cargo deb -p adb_cli
@@ -40,11 +46,4 @@ jobs:
files: | files: |
target/debian/*.deb target/debian/*.deb
target/generate-rpm/*.rpm target/generate-rpm/*.rpm
target/release/adb_cli target/release/adb_cli
- name: "Publish crates"
run: |
cargo publish -p adb_client --token ${CRATES_IO_TOKEN}
cargo publish -p adb_cli --token ${CRATES_IO_TOKEN}
env:
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}

6
.gitignore vendored
View File

@@ -1,3 +1,5 @@
target target
Cargo.lock /Cargo.lock
.vscode /.vscode
venv
/.mypy_cache

View File

@@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["adb_cli", "adb_client"] members = ["adb_cli", "adb_client", "pyadb_client"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@@ -9,7 +9,7 @@ homepage = "https://github.com/cocool97/adb_client"
keywords = ["adb", "android", "tcp", "usb"] keywords = ["adb", "android", "tcp", "usb"]
license = "MIT" license = "MIT"
repository = "https://github.com/cocool97/adb_client" repository = "https://github.com/cocool97/adb_client"
version = "2.0.3" version = "2.1.4"
# To build locally when working on a new release # To build locally when working on a new release
[patch.crates-io] [patch.crates-io]

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) [year] [fullname] Copyright (c) 2023-2024 Corentin LIAUD
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -8,36 +8,53 @@
<a href="https://crates.io/crates/adb_client"> <a href="https://crates.io/crates/adb_client">
<img alt="crates.io" src="https://img.shields.io/crates/v/adb_client.svg"/> <img alt="crates.io" src="https://img.shields.io/crates/v/adb_client.svg"/>
</a> </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"> <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"/> <img alt="dependency status" src="https://deps.rs/repo/github/cocool97/adb_client/status.svg"/>
</a> </a>
<a href="https://opensource.org/licenses/MIT">
<img alt="dependency status" src="https://img.shields.io/badge/License-MIT-yellow.svg"/>
</a>
</p> </p>
</p> </p>
Main features of this library: Main features of this library:
- Full Rust, don't use `adb *` shell commands to interact with devices - Full Rust, don't use `adb *` shell commands to interact with devices
- Supports: - Supports
- **TCP/IP** protocol, using ADB server as a proxy (standard behavior when using `adb` CLI) - Using ADB server as a proxy (standard behavior when using `adb` CLI)
- **USB** protocol, interacting directly with end devices - Connecting directly to end devices (without using adb-server)
- Over **USB**
- Over **TCP/IP**
- Implements hidden `adb` features, like `framebuffer` - Implements hidden `adb` features, like `framebuffer`
- Highly configurable - Highly configurable
- Provides wrappers to use directly from Python code
- Easy to use ! - Easy to use !
## adb_client ## 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 ## 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 a "real-world" usage example of this library.
Improved documentation [here](./adb_cli/README.md). Improved documentation available [here](./adb_cli/README.md).
## pyadb_client
Python wrapper using `adb_client` library to export classes usable directly from a Python environment.
Improved documentation available [here](./pyadb_client/README.md)
## Related publications ## Related publications
- [Diving into ADB protocol internals (1/2)](https://www.synacktiv.com/publications/diving-into-adb-protocol-internals-12) - [Diving into ADB protocol internals (1/2)](https://www.synacktiv.com/publications/diving-into-adb-protocol-internals-12)
- [Diving into ADB protocol internals (2/2)](https://www.synacktiv.com/publications/diving-into-adb-protocol-internals-22)
Some features may still be missing, all pull requests are welcome ! Some features may still be missing, all pull requests are welcome !

View File

@@ -10,9 +10,9 @@ repository.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
adb_client = { version = "2.0.1" } adb_client = { version = "^2.0.0" }
anyhow = { version = "1.0.89" } anyhow = { version = "1.0.94" }
clap = { version = "4.5.18", features = ["derive"] } clap = { version = "4.5.23", features = ["derive"] }
env_logger = { version = "0.11.5" } env_logger = { version = "0.11.5" }
log = { version = "0.4.22" } log = { version = "0.4.22" }

View File

@@ -18,38 +18,29 @@ Usage is quite simple, and tends to look like `adb`:
- To use ADB server as a proxy: - To use ADB server as a proxy:
```bash ```bash
user@laptop ~/adb_client (main)> adb_cli --help user@laptop ~/adb_client (main)> adb_cli local --help
Rust ADB (Android Debug Bridge) CLI Device related commands using server
Usage: adb_cli [OPTIONS] <COMMAND> Usage: adb_cli local [OPTIONS] <COMMAND>
Commands: Commands:
host-features List available server features
push Push a file on device
pull Pull a file from device
list List a directory on device
stat Stat a file specified on device
shell Spawn an interactive shell or run a list of commands on the device shell Spawn an interactive shell or run a list of commands on the device
pull Pull a file from device
push Push a file on device
stat Stat a file on device
run Run an activity on device specified by the intent
reboot Reboot the device reboot Reboot the device
install Install an APK on device
framebuffer Dump framebuffer of device framebuffer Dump framebuffer of device
host-features List available server features
list List a directory on device
logcat Get logs of device logcat Get logs of device
version Print current ADB version
kill Ask ADB server to quit immediately
devices List connected devices
track-devices Track new devices showing up
pair Pair device with a given code
connect Connect device over WI-FI
disconnect Disconnect device over WI-FI
sms Send a SMS with given phone number and given content
rotate Rotate device screen from 90°
help Print this message or the help of the given subcommand(s) help Print this message or the help of the given subcommand(s)
Options: Options:
-d, --debug
-a, --address <ADDRESS> [default: 127.0.0.1:5037] -a, --address <ADDRESS> [default: 127.0.0.1:5037]
-s, --serial <SERIAL> Serial id of a specific device. Every request will be sent to this device -s, --serial <SERIAL> Serial id of a specific device. Every request will be sent to this device
-h, --help Print help -h, --help Print help
-V, --version Print version
``` ```
- To interact directly with end devices - To interact directly with end devices

View File

@@ -1,12 +0,0 @@
use clap::Parser;
#[derive(Parser, Debug)]
pub enum EmuCommand {
/// Send a SMS with given phone number and given content
Sms {
phone_number: String,
content: String,
},
/// Rotate device screen from 90°
Rotate,
}

View File

@@ -1,9 +0,0 @@
mod emu;
mod host;
mod local;
mod usb;
pub use emu::EmuCommand;
pub use host::HostCommand;
pub use local::LocalCommand;
pub use usb::{UsbCommand, UsbCommands};

View File

@@ -1,56 +0,0 @@
use std::num::ParseIntError;
use std::path::PathBuf;
use clap::Parser;
use crate::models::RebootTypeCommand;
fn parse_hex_id(id: &str) -> Result<u16, ParseIntError> {
u16::from_str_radix(id, 16)
}
#[derive(Parser, Debug)]
pub struct UsbCommand {
/// Hexadecimal vendor id of this USB device
#[clap(short = 'v', long = "vendor-id", value_parser=parse_hex_id, value_name="VID")]
pub vendor_id: Option<u16>,
/// Hexadecimal product id of this USB device
#[clap(short = 'p', long = "product-id", value_parser=parse_hex_id, value_name="PID")]
pub product_id: Option<u16>,
/// Path to a custom private key to use for authentication
#[clap(short = 'k', long = "private-key")]
pub path_to_private_key: Option<PathBuf>,
#[clap(subcommand)]
pub commands: UsbCommands,
}
#[derive(Parser, Debug)]
pub enum UsbCommands {
/// 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,
},
}

View File

@@ -0,0 +1,20 @@
use adb_client::ADBEmulatorDevice;
use crate::models::{EmuCommand, EmulatorCommand};
pub fn handle_emulator_commands(emulator_command: EmulatorCommand) -> anyhow::Result<()> {
let mut emulator = ADBEmulatorDevice::new(emulator_command.serial, None)?;
match emulator_command.command {
EmuCommand::Sms {
phone_number,
content,
} => {
emulator.send_sms(&phone_number, &content)?;
log::info!("SMS sent to {phone_number}");
}
EmuCommand::Rotate => emulator.rotate()?,
}
Ok(())
}

View File

@@ -0,0 +1,78 @@
use adb_client::{ADBServer, DeviceShort, MDNSBackend, Result};
use crate::models::{HostCommand, MdnsCommand, ServerCommand};
pub fn handle_host_commands(server_command: ServerCommand<HostCommand>) -> Result<()> {
let mut adb_server = ADBServer::new(server_command.address);
match server_command.command {
HostCommand::Version => {
let version = adb_server.version()?;
log::info!("Android Debug Bridge version {}", version);
log::info!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
}
HostCommand::Kill => {
adb_server.kill()?;
}
HostCommand::Devices { long } => {
if long {
log::info!("List of devices attached (extended)");
for device in adb_server.devices_long()? {
log::info!("{}", device);
}
} else {
log::info!("List of devices attached");
for device in adb_server.devices()? {
log::info!("{}", device);
}
}
}
HostCommand::TrackDevices => {
let callback = |device: DeviceShort| {
log::info!("{}", device);
Ok(())
};
log::info!("Live list of devices attached");
adb_server.track_devices(callback)?;
}
HostCommand::Pair { address, code } => {
adb_server.pair(address, code)?;
log::info!("Paired device {address}");
}
HostCommand::Connect { address } => {
adb_server.connect_device(address)?;
log::info!("Connected to {address}");
}
HostCommand::Disconnect { address } => {
adb_server.disconnect_device(address)?;
log::info!("Disconnected {address}");
}
HostCommand::Mdns { subcommand } => match subcommand {
MdnsCommand::Check => {
let check = adb_server.mdns_check()?;
let server_status = adb_server.server_status()?;
match server_status.mdns_backend {
MDNSBackend::Unknown => log::info!("unknown mdns backend..."),
MDNSBackend::Bonjour => match check {
true => log::info!("mdns daemon version [Bonjour]"),
false => log::info!("ERROR: mdns daemon unavailable"),
},
MDNSBackend::OpenScreen => {
log::info!("mdns daemon version [Openscreen discovery 0.0.0]")
}
}
}
MdnsCommand::Services => {
log::info!("List of discovered mdns services");
for service in adb_server.mdns_services()? {
log::info!("{}", service);
}
}
},
HostCommand::ServerStatus => {
log::info!("{}", adb_server.server_status()?);
}
}
Ok(())
}

View File

@@ -0,0 +1,35 @@
use std::{fs::File, io::Write};
use adb_client::ADBServerDevice;
use anyhow::{anyhow, Result};
use crate::models::LocalDeviceCommand;
pub fn handle_local_commands(
mut device: ADBServerDevice,
local_device_commands: LocalDeviceCommand,
) -> Result<()> {
match local_device_commands {
LocalDeviceCommand::HostFeatures => {
let features = device
.host_features()?
.iter()
.map(|v| v.to_string())
.reduce(|a, b| format!("{a},{b}"))
.ok_or(anyhow!("cannot list features"))?;
log::info!("Available host features: {features}");
Ok(())
}
LocalDeviceCommand::List { path } => Ok(device.list(path)?),
LocalDeviceCommand::Logcat { path } => {
let writer: Box<dyn Write> = if let Some(path) = path {
let f = File::create(path)?;
Box::new(f)
} else {
Box::new(std::io::stdout())
};
Ok(device.get_logs(writer)?)
}
}
}

View File

@@ -0,0 +1,7 @@
mod emulator_commands;
mod host_commands;
mod local_commands;
pub use emulator_commands::handle_emulator_commands;
pub use host_commands::handle_host_commands;
pub use local_commands::handle_local_commands;

View File

@@ -3,235 +3,139 @@
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
mod adb_termios; mod adb_termios;
mod commands; mod handlers;
mod models; mod models;
mod utils;
use adb_client::{ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBUSBDevice, DeviceShort}; use adb_client::{ADBDeviceExt, ADBServer, ADBTcpDevice, ADBUSBDevice, MDNSDiscoveryService};
use anyhow::{anyhow, Result}; use adb_termios::ADBTermios;
use anyhow::Result;
use clap::Parser; use clap::Parser;
use commands::{EmuCommand, HostCommand, LocalCommand, UsbCommands}; use handlers::{handle_emulator_commands, handle_host_commands, handle_local_commands};
use models::{Command, Opts}; use models::{DeviceCommands, LocalCommand, MainCommand, Opts};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use utils::setup_logger;
fn main() -> Result<()> { fn main() -> Result<()> {
let opt = Opts::parse(); let opts = Opts::parse();
env_logger::init();
match opt.command { setup_logger(opts.debug);
Command::Local(local) => {
let mut adb_server = ADBServer::new(opt.address);
let mut device = match opt.serial { // Directly handling methods / commands that aren't linked to [`ADBDeviceExt`] trait.
// Other methods just have to create a concrete [`ADBDeviceExt`] instance, and return it.
// This instance will then be used to execute desired command.
let (mut device, commands) = match opts.command {
MainCommand::Host(server_command) => return Ok(handle_host_commands(server_command)?),
MainCommand::Emu(emulator_command) => return handle_emulator_commands(emulator_command),
MainCommand::Local(server_command) => {
let mut adb_server = ADBServer::new(server_command.address);
let device = match server_command.serial {
Some(serial) => adb_server.get_device_by_name(&serial)?, Some(serial) => adb_server.get_device_by_name(&serial)?,
None => adb_server.get_device()?, None => adb_server.get_device()?,
}; };
match local { match server_command.command {
LocalCommand::Pull { path, filename } => { LocalCommand::DeviceCommands(device_commands) => (device.boxed(), device_commands),
let mut output = File::create(Path::new(&filename))?; LocalCommand::LocalDeviceCommand(local_device_command) => {
device.pull(&path, &mut output)?; return handle_local_commands(device, local_device_command)
log::info!("Downloaded {path} as {filename}");
}
LocalCommand::Push { filename, path } => {
let mut input = File::open(Path::new(&filename))?;
device.push(&mut input, &path)?;
log::info!("Uploaded {filename} to {path}");
}
LocalCommand::List { path } => {
device.list(path)?;
}
LocalCommand::Stat { path } => {
let stat_response = device.stat(path)?;
println!("{}", stat_response);
}
LocalCommand::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())?;
}
}
LocalCommand::Run { package, activity } => {
let output = device.run_activity(&package, &activity)?;
std::io::stdout().write_all(&output)?;
}
LocalCommand::HostFeatures => {
let features = device
.host_features()?
.iter()
.map(|v| v.to_string())
.reduce(|a, b| format!("{a},{b}"))
.ok_or(anyhow!("cannot list features"))?;
log::info!("Available host features: {features}");
}
LocalCommand::Reboot { reboot_type } => {
log::info!("Reboots device in mode {:?}", reboot_type);
device.reboot(reboot_type.into())?
}
LocalCommand::Framebuffer { path } => {
device.framebuffer(&path)?;
log::info!("Framebuffer dropped: {path}");
}
LocalCommand::Logcat { path } => {
let writer: Box<dyn Write> = if let Some(path) = path {
let f = File::create(path)?;
Box::new(f)
} else {
Box::new(std::io::stdout())
};
device.get_logs(writer)?;
}
LocalCommand::Install { path } => {
log::info!("Starting installation of APK {}...", path.display());
device.install(path)?;
} }
} }
} }
Command::Host(host) => { MainCommand::Usb(usb_command) => {
let mut adb_server = ADBServer::new(opt.address); let device = match (usb_command.vendor_id, usb_command.product_id) {
(Some(vid), Some(pid)) => match usb_command.path_to_private_key {
match host {
HostCommand::Version => {
let version = adb_server.version()?;
log::info!("Android Debug Bridge version {}", version);
log::info!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
}
HostCommand::Kill => {
adb_server.kill()?;
}
HostCommand::Devices { long } => {
if long {
log::info!("List of devices attached (extended)");
for device in adb_server.devices_long()? {
log::info!("{}", device);
}
} else {
log::info!("List of devices attached");
for device in adb_server.devices()? {
log::info!("{}", device);
}
}
}
HostCommand::TrackDevices => {
let callback = |device: DeviceShort| {
log::info!("{}", device);
Ok(())
};
log::info!("Live list of devices attached");
adb_server.track_devices(callback)?;
}
HostCommand::Pair { address, code } => {
adb_server.pair(address, code)?;
log::info!("Paired device {address}");
}
HostCommand::Connect { address } => {
adb_server.connect_device(address)?;
log::info!("Connected to {address}");
}
HostCommand::Disconnect { address } => {
adb_server.disconnect_device(address)?;
log::info!("Disconnected {address}");
}
}
}
Command::Emu(emu) => {
let mut emulator = match opt.serial {
Some(serial) => ADBEmulatorDevice::new(serial, None)?,
None => return Err(anyhow!("Serial must be set to use emulators !")),
};
match emu {
EmuCommand::Sms {
phone_number,
content,
} => {
emulator.send_sms(&phone_number, &content)?;
log::info!("SMS sent to {phone_number}");
}
EmuCommand::Rotate => emulator.rotate()?,
}
}
Command::Usb(usb) => {
let mut device = match (usb.vendor_id, usb.product_id) {
(Some(vid), Some(pid)) => match usb.path_to_private_key {
Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?, Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?,
None => ADBUSBDevice::new(vid, pid)?, None => ADBUSBDevice::new(vid, pid)?,
}, },
(None, None) => match usb_command.path_to_private_key {
(None, None) => match usb.path_to_private_key {
Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?, Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?,
None => ADBUSBDevice::autodetect()?, None => ADBUSBDevice::autodetect()?,
}, },
_ => { _ => {
anyhow::bail!("please either supply values for both the --vendor-id and --product-id flags or none."); anyhow::bail!("please either supply values for both the --vendor-id and --product-id flags or none.");
} }
}; };
(device.boxed(), usb_command.commands)
}
MainCommand::Tcp(tcp_command) => {
let device = ADBTcpDevice::new(tcp_command.address)?;
(device.boxed(), tcp_command.commands)
}
MainCommand::Mdns => {
let mut service = MDNSDiscoveryService::new()?;
match usb.commands { let (tx, rx) = std::sync::mpsc::channel();
UsbCommands::Shell { commands } => { service.start(tx)?;
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")))] log::info!("Starting mdns discovery...");
{ while let Ok(device) = rx.recv() {
device.shell(std::io::stdin(), std::io::stdout())?; log::info!(
} "Found device {} with addresses {:?}",
} else { device.fullname,
device.shell_command(commands, std::io::stdout())?; device.addresses
} )
}
UsbCommands::Pull {
source,
destination,
} => {
let mut output = File::create(Path::new(&destination))?;
device.pull(&source, &mut output)?;
log::info!("Downloaded {source} as {destination}");
}
UsbCommands::Stat { path } => {
let stat_response = device.stat(&path)?;
println!("{}", stat_response);
}
UsbCommands::Reboot { reboot_type } => {
log::info!("Reboots device in mode {:?}", reboot_type);
device.reboot(reboot_type.into())?
}
UsbCommands::Push { filename, path } => {
let mut input = File::open(Path::new(&filename))?;
device.push(&mut input, &path)?;
log::info!("Uploaded {filename} to {path}");
}
UsbCommands::Run { package, activity } => {
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)?;
}
} }
return Ok(service.shutdown()?);
}
};
match commands {
DeviceCommands::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 = ADBTermios::new(std::io::stdin())?;
adb_termios.set_adb_termios()?;
device.shell(&mut std::io::stdin(), Box::new(std::io::stdout()))?;
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
device.shell(std::io::stdin(), std::io::stdout())?;
}
} else {
let commands: Vec<&str> = commands.iter().map(|v| v.as_str()).collect();
device.shell_command(&commands, &mut std::io::stdout())?;
}
}
DeviceCommands::Pull {
source,
destination,
} => {
let mut output = File::create(Path::new(&destination))?;
device.pull(&source, &mut output)?;
log::info!("Downloaded {source} as {destination}");
}
DeviceCommands::Stat { path } => {
let stat_response = device.stat(&path)?;
println!("{}", stat_response);
}
DeviceCommands::Reboot { reboot_type } => {
log::info!("Reboots device in mode {:?}", reboot_type);
device.reboot(reboot_type.into())?
}
DeviceCommands::Push { filename, path } => {
let mut input = File::open(Path::new(&filename))?;
device.push(&mut input, &path)?;
log::info!("Uploaded {filename} to {path}");
}
DeviceCommands::Run { package, activity } => {
let output = device.run_activity(&package, &activity)?;
std::io::stdout().write_all(&output)?;
}
DeviceCommands::Install { path } => {
log::info!("Starting installation of APK {}...", path.display());
device.install(&path)?;
}
DeviceCommands::Framebuffer { path } => {
device.framebuffer(&path)?;
log::info!("Successfully dumped framebuffer at path {path}");
} }
} }

View File

@@ -1,22 +1,19 @@
use clap::Parser;
use std::path::PathBuf; use std::path::PathBuf;
use crate::models::RebootTypeCommand; use clap::Parser;
use super::RebootTypeCommand;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
pub enum LocalCommand { pub enum DeviceCommands {
/// List available server features.
HostFeatures,
/// Push a file on device
Push { filename: String, path: String },
/// Pull a file from device
Pull { path: String, filename: String },
/// List a directory on device
List { path: String },
/// Stat a file specified on device
Stat { path: String },
/// Spawn an interactive shell or run a list of commands on the device /// Spawn an interactive shell or run a list of commands on the device
Shell { commands: Vec<String> }, 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 an activity on device specified by the intent
Run { Run {
/// The package whose activity is to be invoked /// The package whose activity is to be invoked
@@ -31,16 +28,14 @@ pub enum LocalCommand {
#[clap(subcommand)] #[clap(subcommand)]
reboot_type: RebootTypeCommand, reboot_type: RebootTypeCommand,
}, },
/// Dump framebuffer of device
Framebuffer { path: String },
/// Get logs of device
Logcat {
/// Path to output file (created if not exists)
path: Option<String>,
},
/// Install an APK on device /// Install an APK on device
Install { Install {
/// Path to APK file. Extension must be ".apk" /// Path to APK file. Extension must be ".apk"
path: PathBuf, path: PathBuf,
}, },
/// Dump framebuffer of device
Framebuffer {
/// Framebuffer image destination path
path: String,
},
} }

20
adb_cli/src/models/emu.rs Normal file
View File

@@ -0,0 +1,20 @@
use clap::{Parser, Subcommand};
#[derive(Debug, Parser)]
pub struct EmulatorCommand {
#[clap(short = 's', long = "serial")]
pub serial: String,
#[clap(subcommand)]
pub command: EmuCommand,
}
#[derive(Debug, Subcommand)]
pub enum EmuCommand {
/// Send a SMS with given phone number and given content
Sms {
phone_number: String,
content: String,
},
/// Rotate device screen from 90°
Rotate,
}

View File

@@ -21,4 +21,19 @@ pub enum HostCommand {
Connect { address: SocketAddrV4 }, Connect { address: SocketAddrV4 },
/// Disconnect device over WI-FI /// Disconnect device over WI-FI
Disconnect { address: SocketAddrV4 }, Disconnect { address: SocketAddrV4 },
/// MDNS services
Mdns {
#[clap(subcommand)]
subcommand: MdnsCommand,
},
/// Display server status
ServerStatus,
}
#[derive(Parser, Debug)]
pub enum MdnsCommand {
/// Check mdns status
Check,
/// List mdns services available
Services,
} }

View File

@@ -0,0 +1,24 @@
use clap::Parser;
use super::DeviceCommands;
#[derive(Parser, Debug)]
pub enum LocalCommand {
#[clap(flatten)]
DeviceCommands(DeviceCommands),
#[clap(flatten)]
LocalDeviceCommand(LocalDeviceCommand),
}
#[derive(Parser, Debug)]
pub enum LocalDeviceCommand {
/// List available server features.
HostFeatures,
/// List a directory on device
List { path: String },
/// Get logs of device
Logcat {
/// Path to output file (created if not exists)
path: Option<String>,
},
}

View File

@@ -1,5 +1,17 @@
mod device;
mod emu;
mod host;
mod local;
mod opts; mod opts;
mod reboot_type; mod reboot_type;
mod tcp;
mod usb;
pub use opts::{Command, Opts}; pub use device::DeviceCommands;
pub use emu::{EmuCommand, EmulatorCommand};
pub use host::{HostCommand, MdnsCommand};
pub use local::{LocalCommand, LocalDeviceCommand};
pub use opts::{MainCommand, Opts, ServerCommand};
pub use reboot_type::RebootTypeCommand; pub use reboot_type::RebootTypeCommand;
pub use tcp::TcpCommand;
pub use usb::UsbCommand;

View File

@@ -1,30 +1,41 @@
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use clap::Parser; use clap::{Parser, Subcommand};
use crate::commands::{EmuCommand, HostCommand, LocalCommand, UsbCommand}; use super::{EmulatorCommand, HostCommand, LocalCommand, TcpCommand, UsbCommand};
#[derive(Parser, Debug)] #[derive(Debug, Parser)]
#[clap(about, version, author)] #[clap(about, version, author)]
pub struct Opts { pub struct Opts {
#[clap(long = "debug")]
pub debug: bool,
#[clap(subcommand)]
pub command: MainCommand,
}
#[derive(Debug, Parser)]
pub enum MainCommand {
/// Server related commands
Host(ServerCommand<HostCommand>),
/// Device related commands using server
Local(ServerCommand<LocalCommand>),
/// Emulator related commands
Emu(EmulatorCommand),
/// USB device related commands
Usb(UsbCommand),
/// TCP device related commands
Tcp(TcpCommand),
/// MDNS discovery related commands
Mdns,
}
#[derive(Debug, Parser)]
pub struct ServerCommand<T: Subcommand> {
#[clap(short = 'a', long = "address", default_value = "127.0.0.1:5037")] #[clap(short = 'a', long = "address", default_value = "127.0.0.1:5037")]
pub address: SocketAddrV4, pub address: SocketAddrV4,
/// Serial id of a specific device. Every request will be sent to this device. /// Serial id of a specific device. Every request will be sent to this device.
#[clap(short = 's', long = "serial")] #[clap(short = 's', long = "serial")]
pub serial: Option<String>, pub serial: Option<String>,
#[clap(subcommand)] #[clap(subcommand)]
pub command: Command, pub command: T,
}
#[derive(Parser, Debug)]
pub enum Command {
#[clap(flatten)]
Local(LocalCommand),
#[clap(flatten)]
Host(HostCommand),
/// Emulator specific commands
#[clap(subcommand)]
Emu(EmuCommand),
/// Device commands via USB, no server needed
Usb(UsbCommand),
} }

11
adb_cli/src/models/tcp.rs Normal file
View File

@@ -0,0 +1,11 @@
use clap::Parser;
use std::net::SocketAddr;
use super::DeviceCommands;
#[derive(Parser, Debug)]
pub struct TcpCommand {
pub address: SocketAddr,
#[clap(subcommand)]
pub commands: DeviceCommands,
}

25
adb_cli/src/models/usb.rs Normal file
View File

@@ -0,0 +1,25 @@
use std::num::ParseIntError;
use std::path::PathBuf;
use clap::Parser;
use super::DeviceCommands;
fn parse_hex_id(id: &str) -> Result<u16, ParseIntError> {
u16::from_str_radix(id, 16)
}
#[derive(Parser, Debug)]
pub struct UsbCommand {
/// Hexadecimal vendor id of this USB device
#[clap(short = 'v', long = "vendor-id", value_parser=parse_hex_id, value_name="VID")]
pub vendor_id: Option<u16>,
/// Hexadecimal product id of this USB device
#[clap(short = 'p', long = "product-id", value_parser=parse_hex_id, value_name="PID")]
pub product_id: Option<u16>,
/// Path to a custom private key to use for authentication
#[clap(short = 'k', long = "private-key")]
pub path_to_private_key: Option<PathBuf>,
#[clap(subcommand)]
pub commands: DeviceCommands,
}

13
adb_cli/src/utils.rs Normal file
View File

@@ -0,0 +1,13 @@
pub fn setup_logger(debug: bool) {
// RUST_LOG variable has more priority then "--debug" flag
if std::env::var("RUST_LOG").is_err() {
let level = match debug {
true => "trace",
false => "info",
};
std::env::set_var("RUST_LOG", level);
}
env_logger::init();
}

View File

@@ -10,19 +10,35 @@ repository.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
base64 = "0.22.1" base64 = { version = "0.22.1" }
bincode = "1.3.3" bincode = { version = "1.3.3" }
byteorder = { version = "1.5.0" } byteorder = { version = "1.5.0" }
chrono = { version = "0.4.38" } chrono = { version = "0.4.39" }
homedir = { version = "0.3.4" } homedir = { version = "0.3.4" }
image = { version = "0.25.4" } image = { version = "0.25.5" }
lazy_static = { version = "1.5.0" } lazy_static = { version = "1.5.0" }
log = { version = "0.4.22" } log = { version = "0.4.22" }
num-bigint = { version = "0.6", package = "num-bigint-dig" } mdns-sd = { version = "0.13.1" }
rand = { version = "0.7.0" } num-bigint = { version = "0.8.4", package = "num-bigint-dig" }
regex = { version = "1.11.0", features = ["perf", "std", "unicode"] } num-traits = { version = "0.2.19" }
rsa = { version = "0.3.0" } quick-protobuf = { version = "0.8.1" }
rand = { version = "0.8.5" }
rcgen = { version = "0.13.1" }
regex = { version = "1.11.1", features = ["perf", "std", "unicode"] }
rsa = { version = "0.9.7" }
rusb = { version = "0.9.4", features = ["vendored"] } rusb = { version = "0.9.4", features = ["vendored"] }
serde = { version = "1.0.210", features = ["derive"] } rustls = { version = "0.23.18" }
serde_repr = "0.1.19" rustls-pki-types = "1.10.0"
thiserror = { version = "2.0.1" } serde = { version = "1.0.216", features = ["derive"] }
serde_repr = { version = "0.1.19" }
sha1 = { version = "0.10.6", features = ["oid"] }
thiserror = { version = "2.0.7" }
[dev-dependencies]
anyhow = { version = "1.0.93" }
criterion = { version = "0.5.1" } # Used for benchmarks
[[bench]]
harness = false
name = "benchmark_adb_push"
path = "../benches/benchmark_adb_push.rs"

View File

@@ -2,7 +2,7 @@
[![MIT licensed](https://img.shields.io/crates/l/adb_client.svg)](./LICENSE-MIT) [![MIT licensed](https://img.shields.io/crates/l/adb_client.svg)](./LICENSE-MIT)
[![Documentation](https://docs.rs/adb_client/badge.svg)](https://docs.rs/adb_client) [![Documentation](https://docs.rs/adb_client/badge.svg)](https://docs.rs/adb_client)
![Crates.io Total Downloads](https://img.shields.io/crates/d/adb_client) [![Crates.io Total Downloads](https://img.shields.io/crates/d/adb_client)](https://crates.io/crates/adb_client)
Rust library implementing ADB protocol. Rust library implementing ADB protocol.
@@ -15,6 +15,20 @@ Add `adb_client` crate as a dependency by simply adding it to your `Cargo.toml`:
adb_client = "*" adb_client = "*"
``` ```
## Benchmarks
Benchmarks run on `v2.0.6`, on a **Samsung S10 SM-G973F** device and an **Intel i7-1265U** CPU laptop
### `ADBServerDevice` push vs `adb push`
`ADBServerDevice` performs all operations by using adb server as a bridge.
|File size|Sample size|`ADBServerDevice`|`adb`|Difference|
|:-------:|:---------:|:----------:|:---:|:-----:|
|10 MB|100|350,79 ms|356,30 ms|<div style="color:green">-1,57 %</div>|
|500 MB|50|15,60 s|15,64 s|<div style="color:green">-0,25 %</div>|
|1 GB|20|31,09 s|31,12 s|<div style="color:green">-0,10 %</div>|
## Examples ## Examples
### Get available ADB devices ### Get available ADB devices
@@ -31,19 +45,19 @@ let mut server = ADBServer::new(SocketAddrV4::new(server_ip, server_port));
server.devices(); 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 ```rust no_run
use adb_client::{ADBServer, ADBDeviceExt}; use adb_client::{ADBServer, ADBDeviceExt};
let mut server = ADBServer::default(); let mut server = ADBServer::default();
let mut device = server.get_device().expect("cannot get device"); let mut device = server.get_device().expect("cannot get device");
device.shell_command(["df", "-h"],std::io::stdout()); device.shell_command(&["df", "-h"], &mut std::io::stdout());
``` ```
#### [TCP] Push a file to the device #### Push a file to the device
```rust no_run ```rust no_run
use adb_client::ADBServer; use adb_client::ADBServer;
@@ -57,9 +71,9 @@ let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file");
device.push(&mut input, "/data/local/tmp"); 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 ```rust no_run
use adb_client::{ADBUSBDevice, ADBDeviceExt}; use adb_client::{ADBUSBDevice, ADBDeviceExt};
@@ -67,10 +81,10 @@ use adb_client::{ADBUSBDevice, ADBDeviceExt};
let vendor_id = 0x04e8; let vendor_id = 0x04e8;
let product_id = 0x6860; let product_id = 0x6860;
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device"); let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
device.shell_command(["df", "-h"],std::io::stdout()); device.shell_command(&["df", "-h"], &mut std::io::stdout());
``` ```
#### [USB] Push a file to the device #### (USB) Push a file to the device
```rust no_run ```rust no_run
use adb_client::{ADBUSBDevice, ADBDeviceExt}; use adb_client::{ADBUSBDevice, ADBDeviceExt};
@@ -81,5 +95,17 @@ let vendor_id = 0x04e8;
let product_id = 0x6860; let product_id = 0x6860;
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device"); let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file"); let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file");
device.push(&mut input, "/data/local/tmp"); 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(&mut std::io::stdin(), Box::new(std::io::stdout()));
``` ```

View File

@@ -1,40 +1,37 @@
use std::io::{Read, Write}; use std::io::{Cursor, Read, Write};
use std::path::Path; use std::path::Path;
use image::{ImageBuffer, ImageFormat, Rgba};
use crate::models::AdbStatResponse; use crate::models::AdbStatResponse;
use crate::{RebootType, Result}; use crate::{RebootType, Result};
/// Trait representing all features available on both [`ADBServerDevice`] and [`ADBUSBDevice`] /// Trait representing all features available on both [`crate::ADBServerDevice`] and [`crate::ADBUSBDevice`]
pub trait ADBDeviceExt { pub trait ADBDeviceExt {
/// Runs 'command' in a shell on the device, and write its output and error streams into [`output`]. /// Runs command in a shell on the device, and write its output and error streams into output.
fn shell_command<S: ToString, W: Write>( fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()>;
&mut self,
command: impl IntoIterator<Item = S>,
output: W,
) -> Result<()>;
/// Starts an interactive shell session on the device. /// Starts an interactive shell session on the device.
/// Input data is read from [reader] and write to [writer]. /// 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: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()>;
fn shell<R: Read, W: Write + Send + 'static>(&mut self, reader: R, writer: W) -> Result<()>;
/// Display the stat information for a remote file /// Display the stat information for a remote file
fn stat(&mut self, remote_path: &str) -> Result<AdbStatResponse>; 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<()>; fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> 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<()>; fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()>;
/// Reboots the device using given reboot type /// Reboot the device using given reboot type
fn reboot(&mut self, reboot_type: RebootType) -> Result<()>; fn reboot(&mut self, reboot_type: RebootType) -> Result<()>;
/// Run `activity` from `package` on device. Return the command output. /// Run `activity` from `package` on device. Return the command output.
fn run_activity(&mut self, package: &str, activity: &str) -> Result<Vec<u8>> { fn run_activity(&mut self, package: &str, activity: &str) -> Result<Vec<u8>> {
let mut output = Vec::new(); let mut output = Vec::new();
self.shell_command( self.shell_command(
["am", "start", &format!("{package}/{package}.{activity}")], &["am", "start", &format!("{package}/{package}.{activity}")],
&mut output, &mut output,
)?; )?;
@@ -42,5 +39,35 @@ pub trait ADBDeviceExt {
} }
/// Install an APK pointed to by `apk_path` on device. /// Install an APK pointed to by `apk_path` on device.
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()>; fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()>;
/// Inner method requesting framebuffer from an Android device
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>>;
/// Dump framebuffer of this device into given path
fn framebuffer(&mut self, path: &dyn AsRef<Path>) -> Result<()> {
// Big help from AOSP source code (<https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp>)
let img = self.framebuffer_inner()?;
Ok(img.save(path.as_ref())?)
}
/// Dump framebuffer of this device and return corresponding bytes.
///
/// Output data format is currently only `PNG`.
fn framebuffer_bytes(&mut self) -> Result<Vec<u8>> {
let img = self.framebuffer_inner()?;
let mut vec = Cursor::new(Vec::new());
img.write_to(&mut vec, ImageFormat::Png)?;
Ok(vec.into_inner())
}
/// Return a boxed instance representing this trait
fn boxed(self) -> Box<dyn ADBDeviceExt>
where
Self: Sized,
Self: 'static,
{
Box::new(self)
}
} }

View File

@@ -0,0 +1,245 @@
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<T: ADBMessageTransport> {
transport: T,
local_id: Option<u32>,
remote_id: Option<u32>,
}
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
/// Instantiate a new [`ADBMessageTransport`]
pub fn new(transport: T) -> Self {
Self {
transport,
local_id: None,
remote_id: None,
}
}
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) -> Result<ADBTransportMessage> {
let message = self.transport.read_message()?;
self.transport.write_message(ADBTransportMessage::new(
MessageCommand::Okay,
self.get_local_id()?,
self.get_remote_id()?,
&[],
))?;
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<ADBTransportMessage> {
self.transport.write_message(message)?;
self.transport.read_message().and_then(|message| {
message.assert_command(MessageCommand::Okay)?;
Ok(message)
})
}
pub(crate) fn recv_file<W: std::io::Write>(
&mut self,
mut output: W,
) -> std::result::Result<(), RustADBError> {
let mut len: Option<u64> = None;
loop {
let payload = self.recv_and_reply_okay()?.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::<LittleEndian>()? 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::<LittleEndian>()?
== MessageSubcommand::Done as u32
{
break;
}
}
Ok(())
}
pub(crate) fn push_file<R: std::io::Read>(
&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<()> {
self.open_session(b"sync:\0")?;
Ok(())
}
pub(crate) fn stat_with_explicit_ids(&mut self, remote_path: &str) -> Result<AdbStatResponse> {
let stat_buffer = MessageSubcommand::Stat.with_arg(remote_path.len() as u32);
let message = ADBTransportMessage::new(
MessageCommand::Write,
self.get_local_id()?,
self.get_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,
self.get_local_id()?,
self.get_remote_id()?,
remote_path.as_bytes(),
))?;
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) -> Result<()> {
let quit_buffer = MessageSubcommand::Quit.with_arg(0u32);
self.send_and_expect_okay(ADBTransportMessage::new(
MessageCommand::Write,
self.get_local_id()?,
self.get_remote_id()?,
&bincode::serialize(&quit_buffer).map_err(|_e| RustADBError::ConversionError)?,
))?;
let _discard_close = self.transport.read_message()?;
Ok(())
}
pub(crate) fn open_session(&mut self, data: &[u8]) -> Result<ADBTransportMessage> {
let mut rng = rand::thread_rng();
let message = ADBTransportMessage::new(
MessageCommand::Open,
rng.gen(), // Our 'local-id'
0,
data,
);
self.get_transport_mut().write_message(message)?;
let response = self.get_transport_mut().read_message()?;
self.local_id = Some(response.header().arg1());
self.remote_id = Some(response.header().arg0());
Ok(response)
}
pub(crate) fn get_local_id(&self) -> Result<u32> {
self.local_id.ok_or(RustADBError::ADBRequestFailed(
"connection not opened, no local_id".into(),
))
}
pub(crate) fn get_remote_id(&self) -> Result<u32> {
self.remote_id.ok_or(RustADBError::ADBRequestFailed(
"connection not opened, no remote_id".into(),
))
}
}

View File

@@ -0,0 +1,41 @@
use crate::{models::AdbStatResponse, ADBDeviceExt, ADBMessageTransport, RebootType, Result};
use std::{
io::{Read, Write},
path::Path,
};
use super::ADBMessageDevice;
impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
self.shell_command(command, output)
}
fn shell(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()> {
self.shell(reader, writer)
}
fn stat(&mut self, remote_path: &str) -> Result<AdbStatResponse> {
self.stat(remote_path)
}
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.pull(source, output)
}
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
self.push(stream, path)
}
fn reboot(&mut self, reboot_type: RebootType) -> Result<()> {
self.reboot(reboot_type)
}
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
self.install(apk_path)
}
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.framebuffer_inner()
}
}

View File

@@ -0,0 +1,110 @@
use std::io::Write;
use std::path::Path;
use std::{io::Read, net::SocketAddr};
use super::adb_message_device::ADBMessageDevice;
use super::models::MessageCommand;
use super::ADBTransportMessage;
use crate::{ADBDeviceExt, ADBMessageTransport, ADBTransport, Result, TcpTransport};
/// Represent a device reached and available over USB.
#[derive(Debug)]
pub struct ADBTcpDevice {
inner: ADBMessageDevice<TcpTransport>,
}
impl ADBTcpDevice {
/// Instantiate a new [`ADBTcpDevice`]
pub fn new(address: SocketAddr) -> Result<Self> {
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(),
);
self.get_transport_mut().write_message(message)?;
// At this point, we should have received a STLS command indicating that the device wants to upgrade connection with TLS
self.get_transport_mut()
.read_message()
.and_then(|message| message.assert_command(MessageCommand::Stls))?;
self.get_transport_mut()
.write_message(ADBTransportMessage::new(MessageCommand::Stls, 1, 0, &[]))?;
// Upgrade TCP connection to TLS
self.get_transport_mut().upgrade_connection()?;
log::debug!("Connection successfully upgraded from TCP to TLS");
Ok(())
}
#[inline]
fn get_transport_mut(&mut self) -> &mut TcpTransport {
self.inner.get_transport_mut()
}
}
impl ADBDeviceExt for ADBTcpDevice {
#[inline]
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
self.inner.shell_command(command, output)
}
#[inline]
fn shell(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()> {
self.inner.shell(reader, writer)
}
#[inline]
fn stat(&mut self, remote_path: &str) -> Result<crate::AdbStatResponse> {
self.inner.stat(remote_path)
}
#[inline]
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.inner.pull(source, output)
}
#[inline]
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
self.inner.push(stream, path)
}
#[inline]
fn reboot(&mut self, reboot_type: crate::RebootType) -> Result<()> {
self.inner.reboot(reboot_type)
}
#[inline]
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
self.inner.install(apk_path)
}
#[inline]
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.inner.framebuffer_inner()
}
}
impl Drop for ADBTcpDevice {
fn drop(&mut self) {
// Best effort here
let _ = self.get_transport_mut().disconnect();
}
}

View File

@@ -0,0 +1,122 @@
use serde::{Deserialize, Serialize};
use crate::{Result, 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 ADBTransportMessage {
header: ADBTransportMessageHeader,
payload: Vec<u8>,
}
#[derive(Debug, Serialize, Deserialize)]
#[repr(C)]
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 ADBTransportMessageHeader {
pub fn new(command: MessageCommand, arg0: u32, arg1: u32, data: &[u8]) -> Self {
Self {
command,
arg0,
arg1,
data_length: data.len() as u32,
data_crc32: Self::compute_crc32(data),
magic: Self::compute_magic(command),
}
}
pub fn command(&self) -> MessageCommand {
self.command
}
pub fn arg0(&self) -> u32 {
self.arg0
}
pub fn arg1(&self) -> u32 {
self.arg1
}
pub fn data_length(&self) -> u32 {
self.data_length
}
pub fn data_crc32(&self) -> u32 {
self.data_crc32
}
pub(crate) fn compute_crc32(data: &[u8]) -> u32 {
data.iter().map(|&x| x as u32).sum()
}
fn compute_magic(command: MessageCommand) -> u32 {
let command_u32 = command as u32;
command_u32 ^ 0xFFFFFFFF
}
pub fn as_bytes(&self) -> Result<Vec<u8>> {
bincode::serialize(&self).map_err(|_e| RustADBError::ConversionError)
}
}
impl ADBTransportMessage {
pub fn new(command: MessageCommand, arg0: u32, arg1: u32, data: &[u8]) -> Self {
Self {
header: ADBTransportMessageHeader::new(command, arg0, arg1, data),
payload: data.to_vec(),
}
}
pub fn from_header_and_payload(header: ADBTransportMessageHeader, payload: Vec<u8>) -> Self {
Self { header, payload }
}
pub fn check_message_integrity(&self) -> bool {
ADBTransportMessageHeader::compute_magic(self.header.command) == self.header.magic
&& ADBTransportMessageHeader::compute_crc32(&self.payload) == self.header.data_crc32
}
pub fn assert_command(&self, expected_command: MessageCommand) -> Result<()> {
let our_command = self.header().command();
if expected_command == our_command {
return Ok(());
}
Err(RustADBError::WrongResponseReceived(
our_command.to_string(),
expected_command.to_string(),
))
}
pub fn header(&self) -> &ADBTransportMessageHeader {
&self.header
}
pub fn payload(&self) -> &Vec<u8> {
&self.payload
}
pub fn into_payload(self) -> Vec<u8> {
self.payload
}
}
impl TryFrom<[u8; 24]> for ADBTransportMessageHeader {
type Error = RustADBError;
fn try_from(value: [u8; 24]) -> Result<Self> {
bincode::deserialize(&value).map_err(|_e| RustADBError::ConversionError)
}
}

View File

@@ -0,0 +1,287 @@
use rusb::constants::LIBUSB_CLASS_VENDOR_SPEC;
use rusb::Device;
use rusb::DeviceDescriptor;
use rusb::UsbContext;
use std::fs::read_to_string;
use std::io::Read;
use std::io::Write;
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};
pub fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<ADBRsaKey>> {
Ok(read_to_string(private_key_path.as_ref()).map(|pk| {
match ADBRsaKey::new_from_pkcs8(&pk) {
Ok(pk) => Some(pk),
Err(e) => {
log::error!("Error while create RSA private key: {e}");
None
}
}
})?)
}
/// Search for adb devices with known interface class and subclass values
fn search_adb_devices() -> Result<Option<(u16, u16)>> {
let mut found_devices = vec![];
for device in rusb::devices()?.iter() {
let Ok(des) = device.device_descriptor() else {
continue;
};
if is_adb_device(&device, &des) {
log::debug!(
"Autodetect device {:04x}:{:04x}",
des.vendor_id(),
des.product_id()
);
found_devices.push((des.vendor_id(), des.product_id()));
}
}
match (found_devices.first(), found_devices.get(1)) {
(None, _) => Ok(None),
(Some(identifiers), None) => Ok(Some(*identifiers)),
(Some((vid1, pid1)), Some((vid2, pid2))) => Err(RustADBError::DeviceNotFound(format!(
"Found two Android devices {:04x}:{:04x} and {:04x}:{:04x}",
vid1, pid1, vid2, pid2
))),
}
}
fn is_adb_device<T: UsbContext>(device: &Device<T>, des: &DeviceDescriptor) -> bool {
const ADB_SUBCLASS: u8 = 0x42;
const ADB_PROTOCOL: u8 = 0x1;
// Some devices require choosing the file transfer mode
// for usb debugging to take effect.
const BULK_CLASS: u8 = 0xdc;
const BULK_ADB_SUBCLASS: u8 = 2;
for n in 0..des.num_configurations() {
let Ok(config_des) = device.config_descriptor(n) else {
continue;
};
for interface in config_des.interfaces() {
for interface_des in interface.descriptors() {
let proto = interface_des.protocol_code();
let class = interface_des.class_code();
let subcl = interface_des.sub_class_code();
if proto == ADB_PROTOCOL
&& ((class == LIBUSB_CLASS_VENDOR_SPEC && subcl == ADB_SUBCLASS)
|| (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS))
{
return true;
}
}
}
}
false
}
pub fn get_default_adb_key_path() -> Result<PathBuf> {
homedir::my_home()
.ok()
.flatten()
.map(|home| home.join(".android").join("adbkey"))
.ok_or(RustADBError::NoHomeDirectory)
}
/// Represent a device reached and available over USB.
#[derive(Debug)]
pub struct ADBUSBDevice {
private_key: ADBRsaKey,
inner: ADBMessageDevice<USBTransport>,
}
impl ADBUSBDevice {
/// Instantiate a new [`ADBUSBDevice`]
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
Self::new_with_custom_private_key(vendor_id, product_id, get_default_adb_key_path()?)
}
/// Instantiate a new [`ADBUSBDevice`] using a custom private key path
pub fn new_with_custom_private_key(
vendor_id: u16,
product_id: u16,
private_key_path: PathBuf,
) -> Result<Self> {
Self::new_from_transport_inner(USBTransport::new(vendor_id, product_id)?, private_key_path)
}
/// Instantiate a new [`ADBUSBDevice`] from a [`USBTransport`] and an optional private key path.
pub fn new_from_transport(
transport: USBTransport,
private_key_path: Option<PathBuf>,
) -> Result<Self> {
let private_key_path = match private_key_path {
Some(private_key_path) => private_key_path,
None => get_default_adb_key_path()?,
};
Self::new_from_transport_inner(transport, private_key_path)
}
fn new_from_transport_inner(
transport: USBTransport,
private_key_path: PathBuf,
) -> Result<Self> {
let private_key = match read_adb_private_key(private_key_path)? {
Some(pk) => pk,
None => ADBRsaKey::new_random()?,
};
let mut s = Self {
private_key,
inner: ADBMessageDevice::new(transport),
};
s.connect()?;
Ok(s)
}
/// autodetect connected ADB devices and establish a connection with the first device found
pub fn autodetect() -> Result<Self> {
Self::autodetect_with_custom_private_key(get_default_adb_key_path()?)
}
/// autodetect connected ADB devices and establish a connection with the first device found using a custom private key path
pub fn autodetect_with_custom_private_key(private_key_path: PathBuf) -> Result<Self> {
match search_adb_devices()? {
Some((vendor_id, product_id)) => {
ADBUSBDevice::new_with_custom_private_key(vendor_id, product_id, private_key_path)
}
_ => Err(RustADBError::DeviceNotFound(
"cannot find USB devices matching the signature of an ADB device".into(),
)),
}
}
/// 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(),
);
self.get_transport_mut().write_message(message)?;
let message = self.get_transport_mut().read_message()?;
message.assert_command(MessageCommand::Auth)?;
// At this point, we should have receive an AUTH message with arg0 == 1
let auth_message = match message.header().arg0() {
AUTH_TOKEN => message,
v => {
return Err(RustADBError::ADBRequestFailed(format!(
"Received AUTH message with type != 1 ({v})"
)))
}
};
let sign = self.private_key.sign(auth_message.into_payload())?;
let message = ADBTransportMessage::new(MessageCommand::Auth, AUTH_SIGNATURE, 0, &sign);
self.get_transport_mut().write_message(message)?;
let received_response = self.get_transport_mut().read_message()?;
if received_response.header().command() == MessageCommand::Cnxn {
log::info!(
"Authentication OK, device info {}",
String::from_utf8(received_response.into_payload())?
);
return Ok(());
}
let mut pubkey = self.private_key.android_pubkey_encode()?.into_bytes();
pubkey.push(b'\0');
let message = ADBTransportMessage::new(MessageCommand::Auth, AUTH_RSAPUBLICKEY, 0, &pubkey);
self.get_transport_mut().write_message(message)?;
let response = self
.get_transport_mut()
.read_message_with_timeout(Duration::from_secs(10))
.and_then(|message| {
message.assert_command(MessageCommand::Cnxn)?;
Ok(message)
})?;
log::info!(
"Authentication OK, device info {}",
String::from_utf8(response.into_payload())?
);
Ok(())
}
#[inline]
fn get_transport_mut(&mut self) -> &mut USBTransport {
self.inner.get_transport_mut()
}
}
impl ADBDeviceExt for ADBUSBDevice {
#[inline]
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
self.inner.shell_command(command, output)
}
#[inline]
fn shell<'a>(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()> {
self.inner.shell(reader, writer)
}
#[inline]
fn stat(&mut self, remote_path: &str) -> Result<crate::AdbStatResponse> {
self.inner.stat(remote_path)
}
#[inline]
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.inner.pull(source, output)
}
#[inline]
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
self.inner.push(stream, path)
}
#[inline]
fn reboot(&mut self, reboot_type: crate::RebootType) -> Result<()> {
self.inner.reboot(reboot_type)
}
#[inline]
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
self.inner.install(apk_path)
}
#[inline]
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.inner.framebuffer_inner()
}
}
impl Drop for ADBUSBDevice {
fn drop(&mut self) {
// Best effort here
let _ = self.get_transport_mut().disconnect();
}
}

View File

@@ -0,0 +1,98 @@
use std::io::{Cursor, Read};
use byteorder::{LittleEndian, ReadBytesExt};
use image::{ImageBuffer, Rgba};
use crate::{
device::{adb_message_device::ADBMessageDevice, MessageCommand},
models::{FrameBufferInfoV1, FrameBufferInfoV2},
ADBMessageTransport, Result, RustADBError,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
self.open_session(b"framebuffer:\0")?;
let response = self.recv_and_reply_okay()?;
let mut payload_cursor = Cursor::new(response.payload());
let version = payload_cursor.read_u32::<LittleEndian>()?;
let img = match version {
// RGBA_8888
1 => {
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV1>()];
payload_cursor.read_exact(&mut buf)?;
let framebuffer_info: FrameBufferInfoV1 = buf.try_into()?;
let mut framebuffer_data = Vec::new();
payload_cursor.read_to_end(&mut framebuffer_data)?;
loop {
if framebuffer_data.len() as u32 == framebuffer_info.size {
break;
}
let response = self.recv_and_reply_okay()?;
framebuffer_data.extend_from_slice(&response.into_payload());
log::debug!(
"received framebuffer data. new size {}",
framebuffer_data.len()
);
}
ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(
framebuffer_info.width,
framebuffer_info.height,
framebuffer_data,
)
.ok_or_else(|| RustADBError::FramebufferConversionError)?
}
// RGBX_8888
2 => {
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV2>()];
payload_cursor.read_exact(&mut buf)?;
let framebuffer_info: FrameBufferInfoV2 = buf.try_into()?;
let mut framebuffer_data = Vec::new();
payload_cursor.read_to_end(&mut framebuffer_data)?;
loop {
if framebuffer_data.len() as u32 == framebuffer_info.size {
break;
}
let response = self.recv_and_reply_okay()?;
framebuffer_data.extend_from_slice(&response.into_payload());
log::debug!(
"received framebuffer data. new size {}",
framebuffer_data.len()
);
}
ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(
framebuffer_info.width,
framebuffer_info.height,
framebuffer_data,
)
.ok_or_else(|| RustADBError::FramebufferConversionError)?
}
v => return Err(RustADBError::UnimplementedFramebufferImageVersion(v)),
};
self.get_transport_mut()
.read_message()
.and_then(|message| message.assert_command(MessageCommand::Clse))?;
Ok(img)
}
}

View File

@@ -0,0 +1,46 @@
use std::{fs::File, path::Path};
use rand::Rng;
use crate::{
device::{adb_message_device::ADBMessageDevice, MessageWriter},
utils::check_extension_is_apk,
ADBMessageTransport, Result,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn install(&mut self, apk_path: &dyn AsRef<Path>) -> 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();
self.open_session(format!("exec:cmd package 'install' -S {}\0", file_size).as_bytes())?;
let transport = self.get_transport().clone();
let mut writer = MessageWriter::new(transport, local_id, self.get_remote_id()?);
std::io::copy(&mut apk_file, &mut writer)?;
let final_status = self.get_transport_mut().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(),
)?)),
}
}
}

View File

@@ -0,0 +1,7 @@
mod framebuffer;
mod install;
mod pull;
mod push;
mod reboot;
mod shell;
mod stat;

View File

@@ -0,0 +1,52 @@
use std::io::Write;
use crate::{
device::{
adb_message_device::ADBMessageDevice, models::MessageSubcommand, ADBTransportMessage,
MessageCommand,
},
ADBMessageTransport, Result, RustADBError,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn pull<A: AsRef<str>, W: Write>(&mut self, source: A, output: W) -> Result<()> {
self.begin_synchronization()?;
let source = source.as_ref();
let adb_stat_response = self.stat_with_explicit_ids(source)?;
if adb_stat_response.file_perm == 0 {
return Err(RustADBError::UnknownResponseType(
"mode is 0: source file does not exist".to_string(),
));
}
let local_id = self.get_local_id()?;
let remote_id = self.get_remote_id()?;
self.get_transport_mut().write_message_with_timeout(
ADBTransportMessage::new(MessageCommand::Okay, local_id, remote_id, &[]),
std::time::Duration::from_secs(4),
)?;
let recv_buffer = MessageSubcommand::Recv.with_arg(source.len() as u32);
let recv_buffer =
bincode::serialize(&recv_buffer).map_err(|_e| RustADBError::ConversionError)?;
self.send_and_expect_okay(ADBTransportMessage::new(
MessageCommand::Write,
self.get_local_id()?,
self.get_remote_id()?,
&recv_buffer,
))?;
self.send_and_expect_okay(ADBTransportMessage::new(
MessageCommand::Write,
self.get_local_id()?,
self.get_remote_id()?,
source.as_bytes(),
))?;
self.recv_file(output)?;
self.end_transaction()?;
Ok(())
}
}

View File

@@ -0,0 +1,35 @@
use std::io::Read;
use crate::{
device::{
adb_message_device::ADBMessageDevice, ADBTransportMessage, MessageCommand,
MessageSubcommand,
},
ADBMessageTransport, Result, RustADBError,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
self.begin_synchronization()?;
let path_header = format!("{},0777", path.as_ref());
let send_buffer = MessageSubcommand::Send.with_arg(path_header.len() as u32);
let mut send_buffer =
bincode::serialize(&send_buffer).map_err(|_e| RustADBError::ConversionError)?;
send_buffer.append(&mut path_header.as_bytes().to_vec());
self.send_and_expect_okay(ADBTransportMessage::new(
MessageCommand::Write,
self.get_local_id()?,
self.get_remote_id()?,
&send_buffer,
))?;
self.push_file(self.get_local_id()?, self.get_remote_id()?, stream)?;
self.end_transaction()?;
Ok(())
}
}

View File

@@ -0,0 +1,14 @@
use crate::{
device::{adb_message_device::ADBMessageDevice, MessageCommand},
ADBMessageTransport, RebootType, Result,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn reboot(&mut self, reboot_type: RebootType) -> Result<()> {
self.open_session(format!("reboot:{}\0", reboot_type).as_bytes())?;
self.get_transport_mut()
.read_message()
.and_then(|message| message.assert_command(MessageCommand::Okay))
}
}

View File

@@ -0,0 +1,82 @@
use std::io::{ErrorKind, Read, Write};
use crate::device::ShellMessageWriter;
use crate::Result;
use crate::{
device::{ADBMessageDevice, ADBTransportMessage, MessageCommand},
ADBMessageTransport, RustADBError,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
/// Runs 'command' in a shell on the device, and write its output and error streams into output.
pub(crate) fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
let response = self.open_session(format!("shell:{}\0", command.join(" "),).as_bytes())?;
if response.header().command() != MessageCommand::Okay {
return Err(RustADBError::ADBRequestFailed(format!(
"wrong command {}",
response.header().command()
)));
}
loop {
let response = self.get_transport_mut().read_message()?;
if response.header().command() != MessageCommand::Write {
break;
}
output.write_all(&response.into_payload())?;
}
Ok(())
}
/// Starts an interactive shell session on the device.
/// Input data is read from [reader] and write to [writer].
pub(crate) fn shell(
&mut self,
mut reader: &mut dyn Read,
mut writer: Box<(dyn Write + Send)>,
) -> Result<()> {
self.open_session(b"shell:\0")?;
let mut transport = self.get_transport().clone();
let local_id = self.get_local_id()?;
let remote_id = self.get_remote_id()?;
// Reading thread, reads response from adbd
std::thread::spawn(move || -> Result<()> {
loop {
let message = transport.read_message()?;
// Acknowledge for more data
let response =
ADBTransportMessage::new(MessageCommand::Okay, local_id, remote_id, &[]);
transport.write_message(response)?;
match message.header().command() {
MessageCommand::Write => {
writer.write_all(&message.into_payload())?;
writer.flush()?;
}
MessageCommand::Okay => continue,
_ => return Err(RustADBError::ADBShellNotSupported),
}
}
});
let transport = self.get_transport().clone();
let mut shell_writer = ShellMessageWriter::new(transport, local_id, remote_id);
// Read from given reader (that could be stdin e.g), and write content to device adbd
if let Err(e) = std::io::copy(&mut reader, &mut shell_writer) {
match e.kind() {
ErrorKind::BrokenPipe => return Ok(()),
_ => return Err(RustADBError::IOError(e)),
}
}
Ok(())
}
}

View File

@@ -0,0 +1,12 @@
use crate::{
device::adb_message_device::ADBMessageDevice, ADBMessageTransport, AdbStatResponse, Result,
};
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn stat(&mut self, remote_path: &str) -> Result<AdbStatResponse> {
self.begin_synchronization()?;
let adb_stat_response = self.stat_with_explicit_ids(remote_path)?;
self.end_transaction()?;
Ok(adb_stat_response)
}
}

View File

@@ -0,0 +1,48 @@
use std::io::{Error, ErrorKind, Result, Write};
use crate::ADBMessageTransport;
use super::{ADBTransportMessage, MessageCommand};
/// [`Write`] trait implementation to hide underlying ADB protocol write logic.
///
/// Read received responses to check that message has been correctly received.
pub struct MessageWriter<T: ADBMessageTransport> {
transport: T,
local_id: u32,
remote_id: u32,
}
impl<T: ADBMessageTransport> MessageWriter<T> {
pub fn new(transport: T, local_id: u32, remote_id: u32) -> Self {
Self {
transport,
local_id,
remote_id,
}
}
}
impl<T: ADBMessageTransport> Write for MessageWriter<T> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let message =
ADBTransportMessage::new(MessageCommand::Write, self.local_id, self.remote_id, buf);
self.transport
.write_message(message)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
match self.transport.read_message() {
Ok(response) => {
response
.assert_command(MessageCommand::Okay)
.map_err(|e| Error::new(ErrorKind::Other, e))?;
Ok(buf.len())
}
Err(e) => Err(Error::new(ErrorKind::Other, e)),
}
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}

View File

@@ -0,0 +1,17 @@
mod adb_message_device;
mod adb_message_device_commands;
mod adb_tcp_device;
mod adb_transport_message;
mod adb_usb_device;
mod commands;
mod message_writer;
mod models;
mod shell_message_writer;
use adb_message_device::ADBMessageDevice;
pub use adb_tcp_device::ADBTcpDevice;
pub use adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader};
pub use adb_usb_device::{get_default_adb_key_path, ADBUSBDevice};
pub use message_writer::MessageWriter;
pub use models::{ADBRsaKey, MessageCommand, MessageSubcommand};
pub use shell_message_writer::ShellMessageWriter;

View File

@@ -0,0 +1,175 @@
use crate::{Result, RustADBError};
use base64::{engine::general_purpose::STANDARD, Engine};
use num_bigint::{BigUint, ModInverse};
use num_traits::cast::ToPrimitive;
use num_traits::FromPrimitive;
use rand::rngs::OsRng;
use rsa::pkcs8::DecodePrivateKey;
use rsa::traits::PublicKeyParts;
use rsa::{Pkcs1v15Sign, RsaPrivateKey};
const ADB_PRIVATE_KEY_SIZE: usize = 2048;
const ANDROID_PUBKEY_MODULUS_SIZE_WORDS: u32 = 64;
#[repr(C)]
#[derive(Debug, Default)]
/// Internal ADB representation of a public key
struct ADBRsaInternalPublicKey {
pub modulus_size_words: u32,
pub n0inv: u32,
pub modulus: BigUint,
pub rr: Vec<u8>,
pub exponent: u32,
}
impl ADBRsaInternalPublicKey {
pub fn new(exponent: &BigUint, modulus: &BigUint) -> Result<Self> {
Ok(Self {
modulus_size_words: ANDROID_PUBKEY_MODULUS_SIZE_WORDS,
exponent: exponent.to_u32().ok_or(RustADBError::ConversionError)?,
modulus: modulus.clone(),
..Default::default()
})
}
pub fn into_bytes(mut self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::new();
bytes.append(&mut self.modulus_size_words.to_le_bytes().to_vec());
bytes.append(&mut self.n0inv.to_le_bytes().to_vec());
bytes.append(&mut self.modulus.to_bytes_le());
bytes.append(&mut self.rr);
bytes.append(&mut self.exponent.to_le_bytes().to_vec());
bytes
}
}
#[derive(Debug, Clone)]
pub struct ADBRsaKey {
private_key: RsaPrivateKey,
}
impl ADBRsaKey {
pub fn new_random() -> Result<Self> {
Ok(Self {
private_key: RsaPrivateKey::new(&mut OsRng, ADB_PRIVATE_KEY_SIZE)?,
})
}
pub fn new_from_pkcs8(pkcs8_content: &str) -> Result<Self> {
Ok(ADBRsaKey {
private_key: RsaPrivateKey::from_pkcs8_pem(pkcs8_content)?,
})
}
pub fn android_pubkey_encode(&self) -> Result<String> {
// Helped from project: https://github.com/hajifkd/webadb
// Source code: https://android.googlesource.com/platform/system/core/+/refs/heads/main/libcrypto_utils/android_pubkey.cpp
// Useful function `android_pubkey_encode()`
let mut adb_rsa_pubkey =
ADBRsaInternalPublicKey::new(self.private_key.e(), self.private_key.n())?;
// r32 = 2 ^ 32
let r32 = BigUint::from_u64(1 << 32).ok_or(RustADBError::ConversionError)?;
// r = 2 ^ rsa_size = 2 ^ 2048
let r = set_bit(ADB_PRIVATE_KEY_SIZE)?;
// rr = r ^ 2 mod N
let rr = r.modpow(&BigUint::from(2u32), &adb_rsa_pubkey.modulus);
adb_rsa_pubkey.rr = rr.to_bytes_le();
// rem = N[0]
let rem = &adb_rsa_pubkey.modulus % &r32;
// n0inv = -1 / rem mod r32
let n0inv = rem
.mod_inverse(&r32)
.and_then(|v| v.to_biguint())
.ok_or(RustADBError::ConversionError)?;
// BN_sub(n0inv, r32, n0inv)
adb_rsa_pubkey.n0inv = (r32 - n0inv)
.to_u32()
.ok_or(RustADBError::ConversionError)?;
Ok(self.encode_public_key(adb_rsa_pubkey.into_bytes()))
}
fn encode_public_key(&self, pub_key: Vec<u8>) -> String {
let mut encoded = STANDARD.encode(pub_key);
encoded.push(' ');
encoded.push_str(&format!("adb_client@{}", env!("CARGO_PKG_VERSION")));
encoded
}
pub fn sign(&self, msg: impl AsRef<[u8]>) -> Result<Vec<u8>> {
Ok(self
.private_key
.sign(Pkcs1v15Sign::new::<sha1::Sha1>(), msg.as_ref())?)
}
}
fn set_bit(n: usize) -> Result<BigUint> {
BigUint::parse_bytes(
&{
let mut bits = vec![b'1'];
bits.append(&mut vec![b'0'; n]);
bits
}[..],
2,
)
.ok_or(RustADBError::ConversionError)
}
#[test]
fn test_pubkey_gen() {
const DEFAULT_PRIV_KEY: &'static str = r"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4Dyn85cxDJnjM
uYXQl/w469MDKdlGdviLfmFMWeYLVfL2Mz1AVyvKqscrtlhbbgMQ/M+3lDvEdHS0
14RIGAwWRtrlTTmhLvM2/IO+eSKSYeCrCVc4KLG3E3WRryUXbs2ynA29xjTJVw+Z
xYxDyn/tAYPEyMm4v+HIJHcOtRzxtO2vjMJ2vBT/ywYxjhncXbFSO09q2E4XrHli
SIPyO82hZgCkpzTZRp+nyA17TYuV9++mvUr9lWH9RbC+o8EF3yitlBsE2uXr97EV
i2Qy8CE7FIxsihXlukppwKRuz+1rJrvmZPTn49ZS+sIS99WE9GoCpsyQvTpvehrM
SIDRsVZPAgMBAAECggEAWNXAzzXeS36zCSR1yILCknqHotw86Pyc4z7BGUe+dzQp
itiaNIaeNTgN3zQoGyDSzA0o+BLMcfo/JdVrHBy3IL1cAxYtvXTaoGxp7bGrlPk2
pXZhqVJCy/jRYtokzdWF5DHbk/+pFJA3kGE/XKzM54g2n/DFI61A/QdUiz2w1ZtI
vc5cM08EM8B/TSI3SeWB8zkh5SlIuLsFO2J2+tCak6PdFfKOVIrFv9dKJYLxx+59
+edZamw2EvNlnl/sewgUk0gaZvQKVf4ivHyM+KSHuV4RFfiLvGuVcyA6XhSjztsG
EA++jDHP5ib/Izes7UK09v9y7kow+z6vUtnDDQOvgQKBgQD8WWAn7FQt9aziCw19
gZynzHG1bXI7uuEVSneuA3UwJImmDu8W+Qb9YL9Dc2nV0M5pGGdXKi2jzq8gPar6
GPAmy7TOlov6Nm0pbMXTAfuovG+gIXxelp3US3FvyRupi0/7UQRRwvetFYbDFwJX
ydF5uEtZdGSHAjPeU5FLq6tBwQKBgQC6uN0JwwZn+eaxguyKOXvp0KykhFI0HI1A
MBDZ1uuKt6OW5+r9NeQtTLctGlNKVQ8wz+Wr0C/nLGIIv4lySS9WFyc5/FnFhDdy
LsEi6whcca4vq3jsMOukvQGFnERsou4LqBEI1Es7jjeeEq+/8WnNTi6Y1flZ6UAp
YAOeFI98DwKBgQDvyfHgHeajwZalOQF5qGb24AOQ9c4dyefGNnvhA/IgbCfMftZc
iwhETuGQM6R3A7KQFRtlrXOu+2BYD6Ffg8D37IwD3vRmL7+tJGoapwC/B0g+7nLi
4tZY+9Nv+LbrdbDry8GB+/UkKJdk3IFicCk4M5KOD1bTH5mwAtLHB/p1QQKBgDHi
k8M45GxA+p4wMUvYgb987bLiWyfq/N3KOaZJYhJkb4MwoLpXfIeRuFqHbvsr8GwF
DwIxE6s6U1KtAWaUIN5qPyOhxMYdRcbusNDIZCp2gKfhsuO/SiVwDYkJr8oqWVip
5SsrtJHLtBY6PdQVBkRAf/h7KiwYQfkL2suQCKmHAoGBAJAkYImBYPHuRcnSXikn
xGDK/moPvzs0CjdPlRcEN+Myy/G0FUrOaC0FcpNoJOdQSYz3F6URA4nX+zj6Ie7G
CNkECiepaGyquQaffwR1CAi8dH6biJjlTQWQPFcCLA0hvernWo3eaSfiL7fHyym+
ile69MHFENUePSpuRSiF3Z02
-----END PRIVATE KEY-----";
let priv_key =
ADBRsaKey::new_from_pkcs8(DEFAULT_PRIV_KEY).expect("cannot create rsa key from data");
let pub_key = priv_key
.android_pubkey_encode()
.expect("cannot encode public key");
let pub_key_adb = "\
QAAAAFH/pU9PVrHRgEjMGnpvOr2QzKYCavSE1fcSwvpS1uPn9GTmuyZr7c9up\
MBpSrrlFYpsjBQ7IfAyZIsVsffr5doEG5StKN8FwaO+sEX9YZX9Sr2m7/eVi0\
17Dcinn0bZNKekAGahzTvyg0hieawXTthqTztSsV3cGY4xBsv/FLx2woyv7bT\
xHLUOdyTI4b+4ycjEgwHtf8pDjMWZD1fJNMa9DZyyzW4XJa+RdRO3sSg4Vwmr\
4GGSInm+g/w28y6hOU3l2kYWDBhIhNe0dHTEO5S3z/wQA25bWLYrx6rKK1dAP\
TP28lUL5llMYX6L+HZG2SkD0+s4/JfQhbnMeCZDzOX8KQ+4ThLy/gDTqCSTjj\
ic8BykdUIqYPwAjBMgQwLOLY5WNJMpjGlFINRcCGhvFFZ73sJTLerECuV/Oae\
nFRcORwnGIRgMrYXj4tjmxJC7sq3cfNX96YIcSCDE9SZFdlKXVK8Jc4YMLGF3\
MI8k1KoTby34uaIyxPJvwM1WR4Rdj60fwMyikFXNaOR2fPteZ3UMBA7CMrOEm\
9iYjntyEppA4hQXIO1TWTzkA/Kfl1i67k5NuLIQdhPFEc5ox5IYVHusauPQ7d\
Awu6BlgK37TUn0JdK0Z6Z4RaEIaNiEI0d5CoP6zQKV2QQnlscYpdsaUW5t9/F\
LioVXPwrz0tx35JyIUZPPYwEAAQA= ";
assert_eq!(&pub_key[..pub_key_adb.len()], pub_key_adb);
}

View File

@@ -4,43 +4,43 @@ use std::fmt::Display;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
#[repr(u32)] #[repr(u32)]
pub enum USBCommand { pub enum MessageCommand {
/// Connect to a device /// Connect to a device
Cnxn = 0x4e584e43, Cnxn = 0x4E584E43,
/// Close connection to a device /// Close connection to a device
Clse = 0x45534c43, Clse = 0x45534C43,
/// Device ask for authentication /// Device ask for authentication
Auth = 0x48545541, Auth = 0x48545541,
/// Open a data connection /// Open a data connection
Open = 0x4e45504f, Open = 0x4E45504F,
/// Write data to connection /// Write data to connection
Write = 0x45545257, Write = 0x45545257,
/// Server understood the message /// Server understood the message
Okay = 0x59414b4f, Okay = 0x59414B4F,
// Sync 0x434e5953 /// Start a connection using TLS
// Stls 0x534C5453 Stls = 0x534C5453,
} }
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize_repr, Deserialize_repr)] #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u32)] #[repr(u32)]
pub enum USBSubcommand { pub enum MessageSubcommand {
Stat = 0x54415453, Stat = 0x54415453,
Send = 0x444E4553, Send = 0x444E4553,
Recv = 0x56434552, Recv = 0x56434552,
Quit = 0x54495551, Quit = 0x54495551,
Fail = 0x4c494146, Fail = 0x4C494146,
Done = 0x454e4f44, Done = 0x454E4F44,
Data = 0x41544144, Data = 0x41544144,
List = 0x5453494c, List = 0x5453494C,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SubcommandWithArg { pub struct SubcommandWithArg {
subcommand: USBSubcommand, subcommand: MessageSubcommand,
arg: u32, arg: u32,
} }
impl USBSubcommand { impl MessageSubcommand {
pub fn with_arg(self, arg: u32) -> SubcommandWithArg { pub fn with_arg(self, arg: u32) -> SubcommandWithArg {
SubcommandWithArg { SubcommandWithArg {
subcommand: self, subcommand: self,
@@ -49,15 +49,16 @@ impl USBSubcommand {
} }
} }
impl Display for USBCommand { impl Display for MessageCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
USBCommand::Cnxn => write!(f, "CNXN"), MessageCommand::Cnxn => write!(f, "CNXN"),
USBCommand::Clse => write!(f, "CLSE"), MessageCommand::Clse => write!(f, "CLSE"),
USBCommand::Auth => write!(f, "AUTH"), MessageCommand::Auth => write!(f, "AUTH"),
USBCommand::Open => write!(f, "OPEN"), MessageCommand::Open => write!(f, "OPEN"),
USBCommand::Write => write!(f, "WRTE"), MessageCommand::Write => write!(f, "WRTE"),
USBCommand::Okay => write!(f, "OKAY"), MessageCommand::Okay => write!(f, "OKAY"),
MessageCommand::Stls => write!(f, "STLS"),
} }
} }
} }

View File

@@ -0,0 +1,5 @@
mod adb_rsa_key;
mod message_commands;
pub use adb_rsa_key::ADBRsaKey;
pub use message_commands::{MessageCommand, MessageSubcommand};

View File

@@ -0,0 +1,37 @@
use std::io::Write;
use crate::ADBMessageTransport;
use super::{models::MessageCommand, ADBTransportMessage};
/// [`Write`] trait implementation to hide underlying ADB protocol write logic for shell commands.
pub struct ShellMessageWriter<T: ADBMessageTransport> {
transport: T,
local_id: u32,
remote_id: u32,
}
impl<T: ADBMessageTransport> ShellMessageWriter<T> {
pub fn new(transport: T, local_id: u32, remote_id: u32) -> Self {
Self {
transport,
local_id,
remote_id,
}
}
}
impl<T: ADBMessageTransport> Write for ShellMessageWriter<T> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let message =
ADBTransportMessage::new(MessageCommand::Write, self.local_id, self.remote_id, buf);
self.transport
.write_message(message)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

View File

@@ -1,10 +0,0 @@
use crate::{models::ADBEmulatorCommand, ADBEmulatorDevice, Result};
impl ADBEmulatorDevice {
/// Send a SMS to this emulator with given content with given phone number
pub fn rotate(&mut self) -> Result<()> {
let transport = self.connect()?;
transport.send_command(ADBEmulatorCommand::Rotate)?;
Ok(())
}
}

View File

@@ -1,15 +1,14 @@
use std::net::{Ipv4Addr, SocketAddrV4}; use std::{
net::{Ipv4Addr, SocketAddrV4},
sync::LazyLock,
};
use crate::{ADBTransport, Result, RustADBError, TCPEmulatorTransport}; use crate::{ADBServerDevice, ADBTransport, Result, RustADBError, TCPEmulatorTransport};
use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use super::ADBServerDevice; static EMULATOR_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new("^emulator-(?P<port>\\d+)$").expect("wrong syntax for emulator regex")
lazy_static! { });
pub static ref EMULATOR_REGEX: Regex =
Regex::new("^emulator-(?P<port>\\d+)$").expect("wrong syntax for emulator regex");
}
/// Represents an emulator connected to the ADB server. /// Represents an emulator connected to the ADB server.
#[derive(Debug)] #[derive(Debug)]
@@ -68,7 +67,7 @@ impl TryFrom<ADBServerDevice> for ADBEmulatorDevice {
fn try_from(value: ADBServerDevice) -> std::result::Result<Self, Self::Error> { fn try_from(value: ADBServerDevice) -> std::result::Result<Self, Self::Error> {
ADBEmulatorDevice::new( ADBEmulatorDevice::new(
value.identifier.clone(), value.identifier.clone(),
Some(*value.get_transport().get_socketaddr().ip()), Some(*value.transport.get_socketaddr().ip()),
) )
} }
} }

View File

@@ -0,0 +1,8 @@
use crate::{emulator_device::ADBEmulatorCommand, ADBEmulatorDevice, Result};
impl ADBEmulatorDevice {
/// Send a SMS to this emulator with given content with given phone number
pub fn rotate(&mut self) -> Result<()> {
self.connect()?.send_command(ADBEmulatorCommand::Rotate)
}
}

View File

@@ -1,13 +1,11 @@
use crate::{models::ADBEmulatorCommand, ADBEmulatorDevice, Result}; use crate::{emulator_device::ADBEmulatorCommand, ADBEmulatorDevice, Result};
impl ADBEmulatorDevice { impl ADBEmulatorDevice {
/// Send a SMS to this emulator with given content with given phone number /// Send a SMS to this emulator with given content with given phone number
pub fn send_sms(&mut self, phone_number: &str, content: &str) -> Result<()> { pub fn send_sms(&mut self, phone_number: &str, content: &str) -> Result<()> {
let transport = self.connect()?; self.connect()?.send_command(ADBEmulatorCommand::Sms(
transport.send_command(ADBEmulatorCommand::Sms(
phone_number.to_string(), phone_number.to_string(),
content.to_string(), content.to_string(),
))?; ))
Ok(())
} }
} }

View File

@@ -0,0 +1,5 @@
mod adb_emulator_device;
mod commands;
mod models;
pub use adb_emulator_device::ADBEmulatorDevice;
pub(crate) use models::ADBEmulatorCommand;

View File

@@ -1,6 +1,6 @@
use std::fmt::Display; use std::fmt::Display;
pub(crate) enum ADBEmulatorCommand { pub enum ADBEmulatorCommand {
Authenticate(String), Authenticate(String),
Sms(String, String), Sms(String, String),
Rotate, Rotate,

View File

@@ -0,0 +1,2 @@
mod adb_emulator_command;
pub use adb_emulator_command::ADBEmulatorCommand;

View File

@@ -15,6 +15,9 @@ pub enum RustADBError {
/// Indicates that ADB server responded an unknown response type. /// Indicates that ADB server responded an unknown response type.
#[error("Unknown response type {0}")] #[error("Unknown response type {0}")]
UnknownResponseType(String), UnknownResponseType(String),
/// Indicated that an unexpected command has been received
#[error("Wrong response command received: {0}. Expected {1}")]
WrongResponseReceived(String, String),
/// Indicates that ADB server responses an unknown device state. /// Indicates that ADB server responses an unknown device state.
#[error("Unknown device state {0}")] #[error("Unknown device state {0}")]
UnknownDeviceState(String), UnknownDeviceState(String),
@@ -64,7 +67,7 @@ pub enum RustADBError {
#[error("Cannot get home directory")] #[error("Cannot get home directory")]
NoHomeDirectory, NoHomeDirectory,
/// Generic USB error /// Generic USB error
#[error(transparent)] #[error("USB Error: {0}")]
UsbError(#[from] rusb::Error), UsbError(#[from] rusb::Error),
/// USB device not found /// USB device not found
#[error("USB Device not found: {0} {1}")] #[error("USB Device not found: {0} {1}")]
@@ -90,4 +93,34 @@ pub enum RustADBError {
/// Given path does not represent an APK /// Given path does not represent an APK
#[error("wrong file extension: {0}")] #[error("wrong file extension: {0}")]
WrongFileExtension(String), WrongFileExtension(String),
/// An error occurred with PKCS8 data
#[error("error with pkcs8: {0}")]
RsaPkcs8Error(#[from] rsa::pkcs8::Error),
/// Error during certificate generation
#[error(transparent)]
CertificateGenerationError(#[from] rcgen::Error),
/// TLS Error
#[error(transparent)]
TLSError(#[from] rustls::Error),
/// PEM certificate error
#[error(transparent)]
PemCertError(#[from] rustls_pki_types::pem::Error),
/// Error while locking mutex
#[error("error while locking data")]
PoisonError,
/// Cannot upgrade connection from TCP to TLS
#[error("upgrade error: {0}")]
UpgradeError(String),
/// An error occurred while getting mdns devices
#[error(transparent)]
MDNSError(#[from] mdns_sd::Error),
/// An error occurred while sending data to channel
#[error(transparent)]
SendError(#[from] std::sync::mpsc::SendError<crate::MDNSDevice>),
}
impl<T> From<std::sync::PoisonError<T>> for RustADBError {
fn from(_err: std::sync::PoisonError<T>) -> Self {
Self::PoisonError
}
} }

View File

@@ -6,17 +6,22 @@
mod adb_device_ext; mod adb_device_ext;
mod constants; mod constants;
mod emulator; mod device;
mod emulator_device;
mod error; mod error;
mod mdns;
mod models; mod models;
mod server; mod server;
mod server_device;
mod transports; mod transports;
mod usb;
mod utils; mod utils;
pub use adb_device_ext::ADBDeviceExt; pub use adb_device_ext::ADBDeviceExt;
pub use device::{ADBTcpDevice, ADBUSBDevice};
pub use emulator_device::ADBEmulatorDevice;
pub use error::{Result, RustADBError}; pub use error::{Result, RustADBError};
pub use models::{AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType}; pub use mdns::*;
pub use models::{AdbStatResponse, RebootType};
pub use server::*; pub use server::*;
pub use server_device::ADBServerDevice;
pub use transports::*; pub use transports::*;
pub use usb::ADBUSBDevice;

View File

@@ -0,0 +1,19 @@
use std::{collections::HashSet, net::IpAddr};
/// Represent a device found from mdns search
#[derive(Debug)]
pub struct MDNSDevice {
/// Full device address when resolved
pub fullname: String,
/// Device IP addresses
pub addresses: HashSet<IpAddr>,
}
impl From<mdns_sd::ServiceInfo> for MDNSDevice {
fn from(value: mdns_sd::ServiceInfo) -> Self {
Self {
fullname: value.get_fullname().to_string(),
addresses: value.get_addresses().to_owned(),
}
}
}

View File

@@ -0,0 +1,73 @@
use mdns_sd::{ServiceDaemon, ServiceEvent};
use std::{sync::mpsc::Sender, thread::JoinHandle};
use crate::{MDNSDevice, Result, RustADBError};
const ADB_SERVICE_NAME: &str = "_adb-tls-connect._tcp.local.";
/// Structure holding responsibility over mdns discovery
pub struct MDNSDiscoveryService {
daemon: ServiceDaemon,
thread_handle: Option<JoinHandle<Result<()>>>,
}
impl std::fmt::Debug for MDNSDiscoveryService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MDNSDiscoveryService")
.field("daemon", &self.daemon.get_metrics())
.field("handle", &self.thread_handle)
.finish()
}
}
impl MDNSDiscoveryService {
/// Instantiate a new discovery service to find devices over mdns
pub fn new() -> Result<Self> {
Ok(MDNSDiscoveryService {
daemon: ServiceDaemon::new()?,
thread_handle: None,
})
}
/// Start discovery by spawning a new thread responsible of getting events.
pub fn start(&mut self, sender: Sender<MDNSDevice>) -> Result<()> {
let receiver = self.daemon.browse(ADB_SERVICE_NAME)?;
let handle: JoinHandle<Result<()>> = std::thread::spawn(move || loop {
while let Ok(event) = receiver.recv() {
match event {
ServiceEvent::SearchStarted(_)
| ServiceEvent::ServiceRemoved(_, _)
| ServiceEvent::ServiceFound(_, _)
| ServiceEvent::SearchStopped(_) => {
// Ignoring these events. We are only interesting in found devices
continue;
}
ServiceEvent::ServiceResolved(service_info) => {
if let Err(e) = sender.send(MDNSDevice::from(service_info)) {
return Err(e.into());
}
}
}
}
});
self.thread_handle = Some(handle);
Ok(())
}
/// Shutdown discovery engines.
pub fn shutdown(&mut self) -> Result<()> {
match self.daemon.shutdown() {
Ok(_) => Ok(()),
Err(e) => match e {
mdns_sd::Error::Again => {
self.daemon.shutdown()?;
Ok(())
}
e => Err(RustADBError::MDNSError(e)),
},
}
}
}

View File

@@ -0,0 +1,5 @@
mod mdns_device;
mod mdns_discovery;
pub use mdns_device::MDNSDevice;
pub use mdns_discovery::MDNSDiscoveryService;

View File

@@ -16,6 +16,10 @@ pub(crate) enum AdbServerCommand {
Pair(SocketAddrV4, String), Pair(SocketAddrV4, String),
TransportAny, TransportAny,
TransportSerial(String), TransportSerial(String),
MDNSCheck,
MDNSServices,
ServerStatus,
ReconnectOffline,
Install(u64), Install(u64),
// Local commands // Local commands
ShellCommand(String), ShellCommand(String),
@@ -23,8 +27,13 @@ pub(crate) enum AdbServerCommand {
FrameBuffer, FrameBuffer,
Sync, Sync,
Reboot(RebootType), Reboot(RebootType),
Forward(String, String, String), Forward(String, String),
ForwardRemoveAll,
Reverse(String, String), Reverse(String, String),
ReverseRemoveAll,
Reconnect,
TcpIp(u16),
Usb,
} }
impl Display for AdbServerCommand { impl Display for AdbServerCommand {
@@ -56,12 +65,23 @@ impl Display for AdbServerCommand {
write!(f, "host:pair:{code}:{addr}") write!(f, "host:pair:{code}:{addr}")
} }
AdbServerCommand::FrameBuffer => write!(f, "framebuffer:"), AdbServerCommand::FrameBuffer => write!(f, "framebuffer:"),
AdbServerCommand::Forward(serial, remote, local) => { AdbServerCommand::Forward(remote, local) => {
write!(f, "host-serial:{serial}:forward:{local};{remote}") write!(f, "host:forward:{local};{remote}")
} }
AdbServerCommand::ForwardRemoveAll => write!(f, "host:killforward-all"),
AdbServerCommand::Reverse(remote, local) => { AdbServerCommand::Reverse(remote, local) => {
write!(f, "reverse:forward:{remote};{local}") write!(f, "reverse:forward:{remote};{local}")
} }
AdbServerCommand::ReverseRemoveAll => write!(f, "reverse:killforward-all"),
AdbServerCommand::MDNSCheck => write!(f, "host:mdns:check"),
AdbServerCommand::MDNSServices => write!(f, "host:mdns:services"),
AdbServerCommand::ServerStatus => write!(f, "host:server-status"),
AdbServerCommand::Reconnect => write!(f, "reconnect"),
AdbServerCommand::ReconnectOffline => write!(f, "host:reconnect-offline"),
AdbServerCommand::TcpIp(port) => {
write!(f, "tcpip:{port}")
}
AdbServerCommand::Usb => write!(f, "usb:"),
AdbServerCommand::Install(size) => write!(f, "exec:cmd package 'install' -S {size}"), AdbServerCommand::Install(size) => write!(f, "exec:cmd package 'install' -S {size}"),
} }
} }

View File

@@ -8,10 +8,14 @@ use std::{
use byteorder::LittleEndian; use byteorder::LittleEndian;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Represents a `stat` response
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct AdbStatResponse { pub struct AdbStatResponse {
/// File permissions
pub file_perm: u32, pub file_perm: u32,
/// File size, in bytes
pub file_size: u32, pub file_size: u32,
/// File modification time
pub mod_time: u32, pub mod_time: u32,
} }

View File

@@ -0,0 +1,97 @@
use std::{iter::Map, slice::ChunksExact};
use byteorder::{ByteOrder, LittleEndian};
use crate::{Result, RustADBError};
type U32ChunkIter<'a> = Map<ChunksExact<'a, u8>, fn(&[u8]) -> Result<u32>>;
fn read_next(chunks: &mut U32ChunkIter) -> Result<u32> {
chunks
.next()
.ok_or(RustADBError::FramebufferConversionError)?
}
#[derive(Debug)]
pub(crate) struct FrameBufferInfoV1 {
pub _bpp: u32,
pub size: u32,
pub width: u32,
pub height: u32,
pub _red_offset: u32,
pub _red_length: u32,
pub _blue_offset: u32,
pub _blue_length: u32,
pub _green_offset: u32,
pub _green_length: u32,
pub _alpha_offset: u32,
pub _alpha_length: u32,
}
impl TryFrom<[u8; std::mem::size_of::<Self>()]> for FrameBufferInfoV1 {
type Error = RustADBError;
fn try_from(
value: [u8; std::mem::size_of::<Self>()],
) -> std::result::Result<Self, Self::Error> {
let mut chunks: U32ChunkIter = value.chunks_exact(4).map(|v| Ok(LittleEndian::read_u32(v)));
Ok(Self {
_bpp: read_next(&mut chunks)?,
size: read_next(&mut chunks)?,
width: read_next(&mut chunks)?,
height: read_next(&mut chunks)?,
_red_offset: read_next(&mut chunks)?,
_red_length: read_next(&mut chunks)?,
_blue_offset: read_next(&mut chunks)?,
_blue_length: read_next(&mut chunks)?,
_green_offset: read_next(&mut chunks)?,
_green_length: read_next(&mut chunks)?,
_alpha_offset: read_next(&mut chunks)?,
_alpha_length: read_next(&mut chunks)?,
})
}
}
#[derive(Debug)]
pub(crate) struct FrameBufferInfoV2 {
pub _bpp: u32,
pub _color_space: u32,
pub size: u32,
pub width: u32,
pub height: u32,
pub _red_offset: u32,
pub _red_length: u32,
pub _blue_offset: u32,
pub _blue_length: u32,
pub _green_offset: u32,
pub _green_length: u32,
pub _alpha_offset: u32,
pub _alpha_length: u32,
}
impl TryFrom<[u8; std::mem::size_of::<Self>()]> for FrameBufferInfoV2 {
type Error = RustADBError;
fn try_from(
value: [u8; std::mem::size_of::<Self>()],
) -> std::result::Result<Self, Self::Error> {
let mut chunks: U32ChunkIter = value.chunks_exact(4).map(|v| Ok(LittleEndian::read_u32(v)));
Ok(Self {
_bpp: read_next(&mut chunks)?,
_color_space: read_next(&mut chunks)?,
size: read_next(&mut chunks)?,
width: read_next(&mut chunks)?,
height: read_next(&mut chunks)?,
_red_offset: read_next(&mut chunks)?,
_red_length: read_next(&mut chunks)?,
_blue_offset: read_next(&mut chunks)?,
_blue_length: read_next(&mut chunks)?,
_green_offset: read_next(&mut chunks)?,
_green_length: read_next(&mut chunks)?,
_alpha_offset: read_next(&mut chunks)?,
_alpha_length: read_next(&mut chunks)?,
})
}
}

View File

@@ -1,23 +1,15 @@
mod adb_emulator_command;
mod adb_request_status; mod adb_request_status;
mod adb_server_command; mod adb_server_command;
mod adb_stat_response; mod adb_stat_response;
mod adb_version; mod framebuffer_info;
mod device_long;
mod device_short;
mod device_state;
mod host_features; mod host_features;
mod reboot_type; mod reboot_type;
mod sync_command; mod sync_command;
pub(crate) use adb_emulator_command::ADBEmulatorCommand;
pub use adb_request_status::AdbRequestStatus; pub use adb_request_status::AdbRequestStatus;
pub(crate) use adb_server_command::AdbServerCommand; pub(crate) use adb_server_command::AdbServerCommand;
pub use adb_stat_response::AdbStatResponse; pub use adb_stat_response::AdbStatResponse;
pub use adb_version::AdbVersion; pub(crate) use framebuffer_info::{FrameBufferInfoV1, FrameBufferInfoV2};
pub use device_long::DeviceLong;
pub use device_short::DeviceShort;
pub use device_state::DeviceState;
pub use host_features::HostFeatures; pub use host_features::HostFeatures;
pub use reboot_type::RebootType; pub use reboot_type::RebootType;
pub use sync_command::SyncCommand; pub use sync_command::SyncCommand;

View File

@@ -2,6 +2,7 @@ use crate::ADBTransport;
use crate::Result; use crate::Result;
use crate::RustADBError; use crate::RustADBError;
use crate::TCPServerTransport; use crate::TCPServerTransport;
use std::collections::HashMap;
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use std::process::Command; use std::process::Command;
@@ -12,6 +13,8 @@ pub struct ADBServer {
pub(crate) transport: Option<TCPServerTransport>, pub(crate) transport: Option<TCPServerTransport>,
/// Address to connect to /// Address to connect to
pub(crate) socket_addr: Option<SocketAddrV4>, pub(crate) socket_addr: Option<SocketAddrV4>,
/// adb-server start envs
pub(crate) envs: HashMap<String, String>,
} }
impl ADBServer { impl ADBServer {
@@ -20,6 +23,7 @@ impl ADBServer {
Self { Self {
transport: None, transport: None,
socket_addr: Some(address), socket_addr: Some(address),
envs: HashMap::new(),
} }
} }
@@ -49,7 +53,13 @@ impl ADBServer {
if is_local_ip { if is_local_ip {
// ADB Server is local, we start it if not already running // ADB Server is local, we start it if not already running
let child = Command::new("adb").arg("start-server").spawn(); let mut command = Command::new("adb");
command.arg("start-server");
for (env_k, env_v) in self.envs.iter() {
command.env(env_k, env_v);
}
let child = command.spawn();
match child { match child {
Ok(mut child) => { Ok(mut child) => {
if let Err(e) = child.wait() { if let Err(e) = child.wait() {

View File

@@ -1,8 +1,8 @@
use std::io::Read; use std::io::Read;
use crate::{ use crate::{
models::{AdbServerCommand, DeviceShort}, models::AdbServerCommand, ADBEmulatorDevice, ADBServer, ADBServerDevice, DeviceLong,
ADBEmulatorDevice, ADBServer, ADBServerDevice, DeviceLong, Result, RustADBError, DeviceShort, Result, RustADBError,
}; };
impl ADBServer { impl ADBServer {
@@ -97,7 +97,12 @@ impl ADBServer {
.get_raw_connection()? .get_raw_connection()?
.read_exact(&mut body)?; .read_exact(&mut body)?;
callback(DeviceShort::try_from(body)?)?; for device in body.split(|x| x.eq(&b'\n')) {
if device.is_empty() {
break;
}
callback(DeviceShort::try_from(device.to_vec())?)?;
}
} }
} }
} }

View File

@@ -0,0 +1,61 @@
use std::io::BufRead;
use crate::{
models::AdbServerCommand, server::models::MDNSBackend, ADBServer, MDNSServices, Result,
};
const OPENSCREEN_MDNS_BACKEND: &str = "ADB_MDNS_OPENSCREEN";
impl ADBServer {
/// Check if mdns discovery is available
pub fn mdns_check(&mut self) -> Result<bool> {
let response = self
.connect()?
.proxy_connection(AdbServerCommand::MDNSCheck, true)?;
match String::from_utf8(response) {
Ok(s) if s.starts_with("mdns daemon version") => Ok(true),
Ok(_) => Ok(false),
Err(e) => Err(e.into()),
}
}
/// List all discovered mdns services
pub fn mdns_services(&mut self) -> Result<Vec<MDNSServices>> {
let services = self
.connect()?
.proxy_connection(AdbServerCommand::MDNSServices, true)?;
let mut vec_services: Vec<MDNSServices> = vec![];
for service in services.lines() {
match service {
Ok(service) => {
vec_services.push(MDNSServices::try_from(service.as_bytes())?);
}
Err(e) => log::error!("{}", e),
}
}
Ok(vec_services)
}
/// Check if specified backend mdns service is used, otherwise restart adb server with envs
pub fn mdns_force_backend(&mut self, backend: MDNSBackend) -> Result<()> {
let server_status = self.server_status()?;
if server_status.mdns_backend != backend {
self.kill()?;
self.envs.insert(
OPENSCREEN_MDNS_BACKEND.to_string(),
(if backend == MDNSBackend::OpenScreen {
"1"
} else {
"0"
})
.to_string(),
);
self.connect()?;
}
Ok(())
}
}

View File

@@ -2,5 +2,8 @@ mod connect;
mod devices; mod devices;
mod disconnect; mod disconnect;
mod kill; mod kill;
mod mdns;
mod pair; mod pair;
mod reconnect;
mod server_status;
mod version; mod version;

View File

@@ -0,0 +1,10 @@
use crate::{models::AdbServerCommand, ADBServer, Result};
impl ADBServer {
/// Reconnect the device
pub fn reconnect_offline(&mut self) -> Result<()> {
self.connect()?
.proxy_connection(AdbServerCommand::ReconnectOffline, false)
.map(|_| ())
}
}

View File

@@ -0,0 +1,12 @@
use crate::{models::AdbServerCommand, server::models::ServerStatus, ADBServer, Result};
impl ADBServer {
/// Check ADB server status
pub fn server_status(&mut self) -> Result<ServerStatus> {
let status = self
.connect()?
.proxy_connection(AdbServerCommand::ServerStatus, true)?;
ServerStatus::try_from(status)
}
}

View File

@@ -1,176 +0,0 @@
use std::{io::Read, iter::Map, path::Path, slice::ChunksExact};
use byteorder::{LittleEndian, ReadBytesExt};
use image::{ImageBuffer, Rgba};
use crate::{models::AdbServerCommand, utils, ADBServerDevice, Result, RustADBError};
type U32ChunkIter<'a> = Map<ChunksExact<'a, u8>, fn(&[u8]) -> Result<u32>>;
fn read_next(chunks: &mut U32ChunkIter) -> Result<u32> {
chunks
.next()
.ok_or(RustADBError::FramebufferConversionError)?
}
#[derive(Debug)]
struct FrameBufferInfoV1 {
pub _bpp: u32,
pub size: u32,
pub width: u32,
pub height: u32,
pub _red_offset: u32,
pub _red_length: u32,
pub _blue_offset: u32,
pub _blue_length: u32,
pub _green_offset: u32,
pub _green_length: u32,
pub _alpha_offset: u32,
pub _alpha_length: u32,
}
impl TryFrom<[u8; std::mem::size_of::<Self>()]> for FrameBufferInfoV1 {
type Error = RustADBError;
fn try_from(
value: [u8; std::mem::size_of::<Self>()],
) -> std::result::Result<Self, Self::Error> {
let mut chunks: U32ChunkIter = value.chunks_exact(4).map(utils::u32_from_le);
Ok(Self {
_bpp: read_next(&mut chunks)?,
size: read_next(&mut chunks)?,
width: read_next(&mut chunks)?,
height: read_next(&mut chunks)?,
_red_offset: read_next(&mut chunks)?,
_red_length: read_next(&mut chunks)?,
_blue_offset: read_next(&mut chunks)?,
_blue_length: read_next(&mut chunks)?,
_green_offset: read_next(&mut chunks)?,
_green_length: read_next(&mut chunks)?,
_alpha_offset: read_next(&mut chunks)?,
_alpha_length: read_next(&mut chunks)?,
})
}
}
#[derive(Debug)]
struct FrameBufferInfoV2 {
pub _bpp: u32,
pub _color_space: u32,
pub size: u32,
pub width: u32,
pub height: u32,
pub _red_offset: u32,
pub _red_length: u32,
pub _blue_offset: u32,
pub _blue_length: u32,
pub _green_offset: u32,
pub _green_length: u32,
pub _alpha_offset: u32,
pub _alpha_length: u32,
}
impl TryFrom<[u8; std::mem::size_of::<Self>()]> for FrameBufferInfoV2 {
type Error = RustADBError;
fn try_from(
value: [u8; std::mem::size_of::<Self>()],
) -> std::result::Result<Self, Self::Error> {
let mut chunks: U32ChunkIter = value.chunks_exact(4).map(utils::u32_from_le);
Ok(Self {
_bpp: read_next(&mut chunks)?,
_color_space: read_next(&mut chunks)?,
size: read_next(&mut chunks)?,
width: read_next(&mut chunks)?,
height: read_next(&mut chunks)?,
_red_offset: read_next(&mut chunks)?,
_red_length: read_next(&mut chunks)?,
_blue_offset: read_next(&mut chunks)?,
_blue_length: read_next(&mut chunks)?,
_green_offset: read_next(&mut chunks)?,
_green_length: read_next(&mut chunks)?,
_alpha_offset: read_next(&mut chunks)?,
_alpha_length: read_next(&mut chunks)?,
})
}
}
impl ADBServerDevice {
/// Dump framebuffer of this device
/// Big help from source code (https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp)
pub fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let img = self.framebuffer_inner()?;
Ok(img.save(path.as_ref())?)
}
/// Inner method requesting framebuffer from Android device
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
let serial: String = self.identifier.clone();
self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.get_transport_mut()
.send_adb_request(AdbServerCommand::FrameBuffer)?;
let version = self
.get_transport_mut()
.get_raw_connection()?
.read_u32::<LittleEndian>()?;
match version {
// RGBA_8888
1 => {
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV1>()];
self.get_transport_mut()
.get_raw_connection()?
.read_exact(&mut buf)?;
let h: FrameBufferInfoV1 = buf.try_into()?;
let mut data = vec![
0_u8;
h.size
.try_into()
.map_err(|_| RustADBError::ConversionError)?
];
self.get_transport_mut()
.get_raw_connection()?
.read_exact(&mut data)?;
Ok(
ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(h.width, h.height, data)
.ok_or_else(|| RustADBError::FramebufferConversionError)?,
)
}
// RGBX_8888
2 => {
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV2>()];
self.get_transport_mut()
.get_raw_connection()?
.read_exact(&mut buf)?;
let h: FrameBufferInfoV2 = buf.try_into()?;
let mut data = vec![
0_u8;
h.size
.try_into()
.map_err(|_| RustADBError::ConversionError)?
];
self.get_transport_mut()
.get_raw_connection()?
.read_exact(&mut data)?;
Ok(
ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(h.width, h.height, data)
.ok_or_else(|| RustADBError::FramebufferConversionError)?,
)
}
v => Err(RustADBError::UnimplementedFramebufferImageVersion(v)),
}
}
}

View File

@@ -1,10 +1,6 @@
mod adb_emulator_device;
mod adb_server; mod adb_server;
mod adb_server_device; mod commands;
mod adb_server_device_commands; mod models;
mod device_commands;
mod server_commands;
pub use adb_emulator_device::ADBEmulatorDevice;
pub use adb_server::ADBServer; pub use adb_server::ADBServer;
pub use adb_server_device::ADBServerDevice; pub use models::*;

View File

@@ -1,16 +1,15 @@
use lazy_static::lazy_static;
use std::str::FromStr; use std::str::FromStr;
use std::sync::LazyLock;
use std::{fmt::Display, str}; use std::{fmt::Display, str};
use crate::{DeviceState, RustADBError}; use crate::{DeviceState, RustADBError};
use regex::bytes::Regex; use regex::bytes::Regex;
lazy_static! { static DEVICES_LONG_REGEX: LazyLock<Regex> = LazyLock::new(|| {
static ref DEVICES_LONG_REGEX: Regex = Regex::new("^(?P<identifier>\\S+)\\s+(?P<state>\\w+) ((usb:(?P<usb1>.*)|(?P<usb2>\\d-\\d)) )?(product:(?P<product>\\w+) model:(?P<model>\\w+) device:(?P<device>\\w+) )?transport_id:(?P<transport_id>\\d+)$").expect("cannot build devices long regex"); Regex::new("^(?P<identifier>\\S+)\\s+(?P<state>\\w+) ((usb:(?P<usb1>.*)|(?P<usb2>\\d-\\d)) )?(product:(?P<product>\\w+) model:(?P<model>\\w+) device:(?P<device>\\w+) )?transport_id:(?P<transport_id>\\d+)$").expect("cannot build devices long regex")
});
} /// Represents a new device with more informations.
/// Represents a new device with more informations helded.
#[derive(Debug)] #[derive(Debug)]
pub struct DeviceLong { pub struct DeviceLong {
/// Unique device identifier. /// Unique device identifier.

View File

@@ -1,13 +1,10 @@
use lazy_static::lazy_static;
use regex::bytes::Regex; use regex::bytes::Regex;
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr, sync::LazyLock};
use crate::{DeviceState, RustADBError}; use crate::{DeviceState, RustADBError};
lazy_static! { static DEVICES_REGEX: LazyLock<Regex> =
static ref DEVICES_REGEX: Regex = LazyLock::new(|| Regex::new("^(\\S+)\t(\\w+)\n?$").expect("Cannot build devices regex"));
Regex::new("^(\\S+)\t(\\w+)\n?$").expect("Cannot build devices regex");
}
/// Represents a device connected to the ADB server. /// Represents a device connected to the ADB server.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -15,6 +15,22 @@ pub enum DeviceState {
Authorizing, Authorizing,
/// The device is unauthorized. /// The device is unauthorized.
Unauthorized, Unauthorized,
/// Haven't received a response from the device yet.
Connecting,
/// Insufficient permissions to communicate with the device.
NoPerm,
/// USB device detached from the adb server (known but not opened/claimed).
Detached,
/// Device running fastboot OS (fastboot) or userspace fastboot (fastbootd).
Bootloader,
/// What a device sees from its end of a Transport (adb host).
Host,
/// Device with bootloader loaded but no ROM OS loaded (adbd).
Recovery,
/// Device running Android OS Sideload mode (minadbd sideload mode).
Sideload,
/// Device running Android OS Rescue mode (minadbd rescue mode).
Rescue,
} }
impl Display for DeviceState { impl Display for DeviceState {
@@ -25,6 +41,14 @@ impl Display for DeviceState {
DeviceState::NoDevice => write!(f, "no device"), DeviceState::NoDevice => write!(f, "no device"),
DeviceState::Authorizing => write!(f, "authorizing"), DeviceState::Authorizing => write!(f, "authorizing"),
DeviceState::Unauthorized => write!(f, "unauthorized"), DeviceState::Unauthorized => write!(f, "unauthorized"),
DeviceState::Connecting => write!(f, "connecting"),
DeviceState::NoPerm => write!(f, "noperm"),
DeviceState::Detached => write!(f, "detached"),
DeviceState::Bootloader => write!(f, "bootloader"),
DeviceState::Host => write!(f, "host"),
DeviceState::Recovery => write!(f, "recovery"),
DeviceState::Sideload => write!(f, "sideload"),
DeviceState::Rescue => write!(f, "rescue"),
} }
} }
} }
@@ -40,6 +64,14 @@ impl FromStr for DeviceState {
"no device" => Ok(Self::NoDevice), "no device" => Ok(Self::NoDevice),
"authorizing" => Ok(Self::Authorizing), "authorizing" => Ok(Self::Authorizing),
"unauthorized" => Ok(Self::Unauthorized), "unauthorized" => Ok(Self::Unauthorized),
"connecting" => Ok(Self::Connecting),
"noperm" => Ok(Self::NoPerm),
"detached" => Ok(Self::Detached),
"bootloader" => Ok(Self::Bootloader),
"host" => Ok(Self::Host),
"recovery" => Ok(Self::Recovery),
"sideload" => Ok(Self::Sideload),
"rescue" => Ok(Self::Rescue),
_ => Err(RustADBError::UnknownDeviceState(lowercased)), _ => Err(RustADBError::UnknownDeviceState(lowercased)),
} }
} }

View File

@@ -0,0 +1,64 @@
use regex::bytes::Regex;
use std::net::SocketAddrV4;
use std::sync::LazyLock;
use std::{fmt::Display, str::FromStr};
use crate::RustADBError;
static MDNS_SERVICES_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new("^(\\S+)\t(\\S+)\t([\\d\\.]+:\\d+)\n?$").expect("Cannot build mdns services regex")
});
/// Represents MDNS Services
#[derive(Debug, Clone)]
pub struct MDNSServices {
/// Service name
pub service_name: String,
/// Reg type
pub reg_type: String,
/// IP addr with port
pub socket_v4: SocketAddrV4,
}
impl Display for MDNSServices {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}\t{}\t{}",
self.service_name, self.reg_type, self.socket_v4
)
}
}
impl TryFrom<&[u8]> for MDNSServices {
type Error = RustADBError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let groups = MDNS_SERVICES_REGEX
.captures(value)
.ok_or(RustADBError::RegexParsingError)?;
Ok(MDNSServices {
service_name: String::from_utf8(
groups
.get(1)
.ok_or(RustADBError::RegexParsingError)?
.as_bytes()
.to_vec(),
)?,
reg_type: String::from_utf8(
groups
.get(2)
.ok_or(RustADBError::RegexParsingError)?
.as_bytes()
.to_vec(),
)?,
socket_v4: SocketAddrV4::from_str(&String::from_utf8(
groups
.get(3)
.ok_or(RustADBError::RegexParsingError)?
.as_bytes()
.to_vec(),
)?)?,
})
}
}

View File

@@ -0,0 +1,13 @@
mod adb_version;
mod device_long;
mod device_short;
mod device_state;
mod mdns_services;
mod server_status;
pub use adb_version::AdbVersion;
pub use device_long::DeviceLong;
pub use device_short::DeviceShort;
pub use device_state::DeviceState;
pub use mdns_services::MDNSServices;
pub use server_status::{MDNSBackend, ServerStatus};

View File

@@ -0,0 +1,167 @@
use quick_protobuf::{BytesReader, MessageRead};
use std::fmt::Display;
use crate::RustADBError;
#[derive(Debug, PartialEq, Default, Eq, Clone, Copy)]
pub enum UsbBackend {
#[default]
Unknown = 0,
Native = 1,
LibUSB = 2,
}
impl From<i32> for UsbBackend {
fn from(i: i32) -> Self {
match i {
0 => UsbBackend::Unknown,
1 => UsbBackend::Native,
2 => UsbBackend::LibUSB,
_ => Self::default(),
}
}
}
impl<'a> From<&'a str> for UsbBackend {
fn from(s: &'a str) -> Self {
match s {
"UNKNOWN_USB" => UsbBackend::Unknown,
"NATIVE" => UsbBackend::Native,
"LIBUSB" => UsbBackend::LibUSB,
_ => Self::default(),
}
}
}
impl Display for UsbBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UsbBackend::Unknown => write!(f, "UNKNOWN_USB"),
UsbBackend::Native => write!(f, "NATIVE"),
UsbBackend::LibUSB => write!(f, "LIBUSB"),
}
}
}
/// MDNS Backend Status
#[derive(Debug, Clone, PartialEq, Default)]
pub enum MDNSBackend {
#[default]
/// Unknown
Unknown = 0,
/// Bonjour
Bonjour = 1,
/// OpenScreen
OpenScreen = 2,
}
impl From<i32> for MDNSBackend {
fn from(i: i32) -> Self {
match i {
0 => MDNSBackend::Unknown,
1 => MDNSBackend::Bonjour,
2 => MDNSBackend::OpenScreen,
_ => Self::default(),
}
}
}
impl<'a> From<&'a str> for MDNSBackend {
fn from(s: &'a str) -> Self {
match s {
"UNKNOWN_MDNS" => MDNSBackend::Unknown,
"BONJOUR" => MDNSBackend::Bonjour,
"OPENSCREEN" => MDNSBackend::OpenScreen,
_ => Self::default(),
}
}
}
impl Display for MDNSBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MDNSBackend::Unknown => write!(f, "UNKNOWN_MDNS"),
MDNSBackend::Bonjour => write!(f, "BONJOUR"),
MDNSBackend::OpenScreen => write!(f, "OPENSCREEN"),
}
}
}
/// Structure representing current server status
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ServerStatus {
/// Currently active USB backend
pub usb_backend: UsbBackend,
/// Is USB backend forced ?
pub usb_backend_forced: bool,
/// Currently active MDNS backend
pub mdns_backend: MDNSBackend,
/// Is MDNS backend forced ?
pub mdns_backend_forced: bool,
/// Server version
pub version: String,
/// Server build information
pub build: String,
/// Server executable absolute path
pub executable_absolute_path: String,
/// Server logs absolute path
pub log_absolute_path: String,
/// OS server is running on
pub os: String,
}
impl<'a> MessageRead<'a> for ServerStatus {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> quick_protobuf::Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(8) => msg.usb_backend = r.read_enum(bytes)?,
Ok(16) => msg.usb_backend_forced = r.read_bool(bytes)?,
Ok(24) => msg.mdns_backend = r.read_enum(bytes)?,
Ok(32) => msg.mdns_backend_forced = r.read_bool(bytes)?,
Ok(42) => msg.version = r.read_string(bytes)?.to_string(),
Ok(50) => msg.build = r.read_string(bytes)?.to_string(),
Ok(58) => msg.executable_absolute_path = r.read_string(bytes)?.to_string(),
Ok(66) => msg.log_absolute_path = r.read_string(bytes)?.to_string(),
Ok(74) => msg.os = r.read_string(bytes)?.to_string(),
Ok(t) => {
r.read_unknown(bytes, t)?;
}
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
impl Display for ServerStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "usb_backend: {}", self.usb_backend)?;
if self.usb_backend_forced {
writeln!(f, "usb_backend_forced: {}", self.usb_backend_forced)?;
}
writeln!(f, "mdns_backend: {}", self.mdns_backend)?;
if self.mdns_backend_forced {
writeln!(f, "mdns_backend_forced: {}", self.mdns_backend_forced)?;
}
writeln!(f, "version: \"{}\"", self.version)?;
writeln!(f, "build: \"{}\"", self.build)?;
writeln!(
f,
"executable_absolute_path: \"{}\"",
self.executable_absolute_path
)?;
writeln!(f, "log_absolute_path: \"{}\"", self.log_absolute_path)?;
writeln!(f, "os: \"{}\"", self.os)
}
}
impl TryFrom<Vec<u8>> for ServerStatus {
type Error = RustADBError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let mut reader = BytesReader::from_bytes(&value);
ServerStatus::from_reader(&mut reader, &value).map_err(|_| RustADBError::ConversionError)
}
}

View File

@@ -7,7 +7,7 @@ pub struct ADBServerDevice {
/// Unique device identifier. /// Unique device identifier.
pub identifier: String, pub identifier: String,
/// Internal [TCPServerTransport] /// Internal [TCPServerTransport]
transport: TCPServerTransport, pub(crate) transport: TCPServerTransport,
} }
impl ADBServerDevice { impl ADBServerDevice {
@@ -25,19 +25,11 @@ impl ADBServerDevice {
} }
} }
pub(crate) fn get_transport(&self) -> &TCPServerTransport {
&self.transport
}
pub(crate) fn get_transport_mut(&mut self) -> &mut TCPServerTransport {
&mut self.transport
}
/// Connect to underlying transport /// Connect to underlying transport
pub(crate) fn connect(&mut self) -> Result<&mut TCPServerTransport> { pub(crate) fn connect(&mut self) -> Result<&mut TCPServerTransport> {
self.transport.connect()?; self.transport.connect()?;
Ok(self.get_transport_mut()) Ok(&mut self.transport)
} }
} }

View File

@@ -1,20 +1,18 @@
use std::{ use std::{
io::{Read, Write}, io::{ErrorKind, Read, Write},
path::Path, path::Path,
}; };
use crate::{ use crate::{
constants::BUFFER_SIZE, constants::BUFFER_SIZE,
models::{AdbServerCommand, AdbStatResponse, HostFeatures}, models::{AdbServerCommand, AdbStatResponse, HostFeatures},
ADBDeviceExt, ADBServerDevice, Result, RustADBError, ADBDeviceExt, Result, RustADBError,
}; };
use super::ADBServerDevice;
impl ADBDeviceExt for ADBServerDevice { impl ADBDeviceExt for ADBServerDevice {
fn shell_command<S: ToString, W: Write>( fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
&mut self,
command: impl IntoIterator<Item = S>,
mut output: W,
) -> Result<()> {
let supported_features = self.host_features()?; let supported_features = self.host_features()?;
if !supported_features.contains(&HostFeatures::ShellV2) if !supported_features.contains(&HostFeatures::ShellV2)
&& !supported_features.contains(&HostFeatures::Cmd) && !supported_features.contains(&HostFeatures::Cmd)
@@ -25,23 +23,12 @@ impl ADBDeviceExt for ADBServerDevice {
let serial = self.identifier.clone(); let serial = self.identifier.clone();
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.get_transport_mut() self.transport
.send_adb_request(AdbServerCommand::ShellCommand( .send_adb_request(AdbServerCommand::ShellCommand(command.join(" ")))?;
command
.into_iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" "),
))?;
const BUFFER_SIZE: usize = 4096;
loop { loop {
let mut buffer = [0; BUFFER_SIZE]; let mut buffer = [0; BUFFER_SIZE];
match self match self.transport.get_raw_connection()?.read(&mut buffer) {
.get_transport_mut()
.get_raw_connection()?
.read(&mut buffer)
{
Ok(size) => { Ok(size) => {
if size == 0 { if size == 0 {
return Ok(()); return Ok(());
@@ -60,10 +47,10 @@ impl ADBDeviceExt for ADBServerDevice {
self.stat(remote_path) self.stat(remote_path)
} }
fn shell<R: Read, W: Write + Send + 'static>( fn shell(
&mut self, &mut self,
mut reader: R, mut reader: &mut dyn Read,
mut writer: W, mut writer: Box<(dyn Write + Send)>,
) -> Result<()> { ) -> Result<()> {
let supported_features = self.host_features()?; let supported_features = self.host_features()?;
if !supported_features.contains(&HostFeatures::ShellV2) if !supported_features.contains(&HostFeatures::ShellV2)
@@ -75,10 +62,9 @@ impl ADBDeviceExt for ADBServerDevice {
let serial = self.identifier.clone(); let serial = self.identifier.clone();
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.get_transport_mut() self.transport.send_adb_request(AdbServerCommand::Shell)?;
.send_adb_request(AdbServerCommand::Shell)?;
let mut read_stream = self.get_transport_mut().get_raw_connection()?.try_clone()?; let mut read_stream = self.transport.get_raw_connection()?.try_clone()?;
let mut write_stream = read_stream.try_clone()?; let mut write_stream = read_stream.try_clone()?;
@@ -105,7 +91,7 @@ impl ADBDeviceExt for ADBServerDevice {
// Read from given reader (that could be stdin e.g), and write content to server socket // Read from given reader (that could be stdin e.g), and write content to server socket
if let Err(e) = std::io::copy(&mut reader, &mut write_stream) { if let Err(e) = std::io::copy(&mut reader, &mut write_stream) {
match e.kind() { match e.kind() {
std::io::ErrorKind::BrokenPipe => return Ok(()), ErrorKind::BrokenPipe => return Ok(()),
_ => return Err(RustADBError::IOError(e)), _ => return Err(RustADBError::IOError(e)),
} }
} }
@@ -113,7 +99,7 @@ impl ADBDeviceExt for ADBServerDevice {
Ok(()) Ok(())
} }
fn pull<A: AsRef<str>, W: Write>(&mut self, source: A, mut output: W) -> Result<()> { fn pull(&mut self, source: &dyn AsRef<str>, mut output: &mut dyn Write) -> Result<()> {
self.pull(source, &mut output) self.pull(source, &mut output)
} }
@@ -121,11 +107,15 @@ impl ADBDeviceExt for ADBServerDevice {
self.reboot(reboot_type) self.reboot(reboot_type)
} }
fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> { fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
self.push(stream, path) self.push(stream, path)
} }
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> { fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
self.install(apk_path) self.install(apk_path)
} }
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.framebuffer_inner()
}
} }

View File

@@ -0,0 +1,25 @@
use crate::{models::AdbServerCommand, ADBServerDevice, Result};
impl ADBServerDevice {
/// Forward socket connection
pub fn forward(&mut self, remote: String, local: String) -> Result<()> {
let serial = self.identifier.clone();
self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?;
self.transport
.proxy_connection(AdbServerCommand::Forward(remote, local), false)
.map(|_| ())
}
/// Remove all previously applied forward rules
pub fn forward_remove_all(&mut self) -> Result<()> {
let serial = self.identifier.clone();
self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?;
self.transport
.proxy_connection(AdbServerCommand::ForwardRemoveAll, false)
.map(|_| ())
}
}

View File

@@ -0,0 +1,78 @@
use std::io::Read;
use byteorder::{LittleEndian, ReadBytesExt};
use image::{ImageBuffer, Rgba};
use crate::{
models::{AdbServerCommand, FrameBufferInfoV1, FrameBufferInfoV2},
ADBServerDevice, Result, RustADBError,
};
impl ADBServerDevice {
/// Inner method requesting framebuffer from Android device
pub(crate) fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
let serial: String = self.identifier.clone();
self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.transport
.send_adb_request(AdbServerCommand::FrameBuffer)?;
let version = self
.transport
.get_raw_connection()?
.read_u32::<LittleEndian>()?;
match version {
// RGBA_8888
1 => {
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV1>()];
self.transport.get_raw_connection()?.read_exact(&mut buf)?;
let framebuffer_info: FrameBufferInfoV1 = buf.try_into()?;
let mut data = vec![
0_u8;
framebuffer_info
.size
.try_into()
.map_err(|_| RustADBError::ConversionError)?
];
self.transport.get_raw_connection()?.read_exact(&mut data)?;
Ok(ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(
framebuffer_info.width,
framebuffer_info.height,
data,
)
.ok_or_else(|| RustADBError::FramebufferConversionError)?)
}
// RGBX_8888
2 => {
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV2>()];
self.transport.get_raw_connection()?.read_exact(&mut buf)?;
let framebuffer_info: FrameBufferInfoV2 = buf.try_into()?;
let mut data = vec![
0_u8;
framebuffer_info
.size
.try_into()
.map_err(|_| RustADBError::ConversionError)?
];
self.transport.get_raw_connection()?.read_exact(&mut data)?;
Ok(ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(
framebuffer_info.width,
framebuffer_info.height,
data,
)
.ok_or_else(|| RustADBError::FramebufferConversionError)?)
}
v => Err(RustADBError::UnimplementedFramebufferImageVersion(v)),
}
}
}

View File

@@ -11,7 +11,7 @@ impl ADBServerDevice {
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
let features = self let features = self
.get_transport_mut() .transport
.proxy_connection(AdbServerCommand::HostFeatures, true)?; .proxy_connection(AdbServerCommand::HostFeatures, true)?;
Ok(features Ok(features

View File

@@ -1,6 +1,8 @@
use std::{fs::File, io::Read, path::Path}; use std::{fs::File, io::Read, path::Path};
use crate::{models::AdbServerCommand, utils::check_extension_is_apk, ADBServerDevice, Result}; use crate::{
models::AdbServerCommand, server_device::ADBServerDevice, utils::check_extension_is_apk, Result,
};
impl ADBServerDevice { impl ADBServerDevice {
/// Install an APK on device /// Install an APK on device
@@ -15,15 +17,15 @@ impl ADBServerDevice {
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.get_transport_mut() self.transport
.send_adb_request(AdbServerCommand::Install(file_size))?; .send_adb_request(AdbServerCommand::Install(file_size))?;
let mut raw_connection = self.get_transport_mut().get_raw_connection()?; let mut raw_connection = self.transport.get_raw_connection()?;
std::io::copy(&mut apk_file, &mut raw_connection)?; std::io::copy(&mut apk_file, &mut raw_connection)?;
let mut data = [0; 1024]; let mut data = [0; 1024];
let read_amount = self.get_transport().get_raw_connection()?.read(&mut data)?; let read_amount = self.transport.get_raw_connection()?.read(&mut data)?;
match &data[0..read_amount] { match &data[0..read_amount] {
b"Success\n" => { b"Success\n" => {

View File

@@ -9,19 +9,17 @@ use std::{
}; };
impl ADBServerDevice { impl ADBServerDevice {
/// Lists files in [path] on the device. /// Lists files in path on the device.
pub fn list<A: AsRef<str>>(&mut self, path: A) -> Result<()> { pub fn list<A: AsRef<str>>(&mut self, path: A) -> Result<()> {
let serial = self.identifier.clone(); let serial = self.identifier.clone();
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
// Set device in SYNC mode // Set device in SYNC mode
self.get_transport_mut() self.transport.send_adb_request(AdbServerCommand::Sync)?;
.send_adb_request(AdbServerCommand::Sync)?;
// Send a list command // Send a list command
self.get_transport_mut() self.transport.send_sync_request(SyncCommand::List)?;
.send_sync_request(SyncCommand::List)?;
self.handle_list_command(path) self.handle_list_command(path)
} }
@@ -33,19 +31,17 @@ impl ADBServerDevice {
LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32); LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32);
// 4 bytes of command name is already sent by send_sync_request // 4 bytes of command name is already sent by send_sync_request
self.get_transport_mut() self.transport.get_raw_connection()?.write_all(&len_buf)?;
.get_raw_connection()?
.write_all(&len_buf)?;
// List send the string of the directory to list, and then the server send a list of files // List send the string of the directory to list, and then the server send a list of files
self.get_transport_mut() self.transport
.get_raw_connection()? .get_raw_connection()?
.write_all(path.as_ref().to_string().as_bytes())?; .write_all(path.as_ref().to_string().as_bytes())?;
// Reads returned status code from ADB server // Reads returned status code from ADB server
let mut response = [0_u8; 4]; let mut response = [0_u8; 4];
loop { loop {
self.get_transport_mut() self.transport
.get_raw_connection()? .get_raw_connection()?
.read_exact(&mut response)?; .read_exact(&mut response)?;
match str::from_utf8(response.as_ref())? { match str::from_utf8(response.as_ref())? {
@@ -57,7 +53,7 @@ impl ADBServerDevice {
let mut mod_time = [0_u8; 4]; let mut mod_time = [0_u8; 4];
let mut name_len = [0_u8; 4]; let mut name_len = [0_u8; 4];
let mut connection = self.get_transport_mut().get_raw_connection()?; let mut connection = self.transport.get_raw_connection()?;
connection.read_exact(&mut file_mod)?; connection.read_exact(&mut file_mod)?;
connection.read_exact(&mut file_size)?; connection.read_exact(&mut file_size)?;
connection.read_exact(&mut mod_time)?; connection.read_exact(&mut mod_time)?;

View File

@@ -51,6 +51,6 @@ impl<W: Write> Write for LogFilter<W> {
impl ADBServerDevice { impl ADBServerDevice {
/// Get logs from device /// Get logs from device
pub fn get_logs<W: Write>(&mut self, output: W) -> Result<()> { pub fn get_logs<W: Write>(&mut self, output: W) -> Result<()> {
self.shell_command(["exec logcat"], LogFilter::new(output)) self.shell_command(&["exec logcat"], &mut LogFilter::new(output))
} }
} }

View File

@@ -5,8 +5,11 @@ mod install;
mod list; mod list;
mod logcat; mod logcat;
mod reboot; mod reboot;
mod reconnect;
mod recv; mod recv;
mod reverse; mod reverse;
mod send; mod send;
mod stat; mod stat;
mod tcpip;
mod transport; mod transport;
mod usb;

View File

@@ -10,7 +10,7 @@ impl ADBServerDevice {
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.get_transport_mut() self.transport
.proxy_connection(AdbServerCommand::Reboot(reboot_type), false) .proxy_connection(AdbServerCommand::Reboot(reboot_type), false)
.map(|_| ()) .map(|_| ())
} }

View File

@@ -1,14 +1,14 @@
use crate::{models::AdbServerCommand, ADBServerDevice, Result}; use crate::{models::AdbServerCommand, ADBServerDevice, Result};
impl ADBServerDevice { impl ADBServerDevice {
/// Reverse socket connection /// Reconnect device
pub fn reverse(&mut self, remote: String, local: String) -> Result<()> { pub fn reconnect(&mut self) -> Result<()> {
let serial = self.identifier.clone(); let serial = self.identifier.clone();
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.get_transport_mut() self.transport
.proxy_connection(AdbServerCommand::Reverse(remote, local), false) .proxy_connection(AdbServerCommand::Reconnect, false)
.map(|_| ()) .map(|_| ())
} }
} }

View File

@@ -69,19 +69,17 @@ impl<R: Read> Read for ADBRecvCommandReader<R> {
} }
impl ADBServerDevice { impl ADBServerDevice {
/// Receives [path] to [stream] from the device. /// Receives path to stream from the device.
pub fn pull<A: AsRef<str>>(&mut self, path: A, stream: &mut dyn Write) -> Result<()> { pub fn pull(&mut self, path: &dyn AsRef<str>, stream: &mut dyn Write) -> Result<()> {
let serial = self.identifier.clone(); let serial = self.identifier.clone();
self.connect()? self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
// Set device in SYNC mode // Set device in SYNC mode
self.get_transport_mut() self.transport.send_adb_request(AdbServerCommand::Sync)?;
.send_adb_request(AdbServerCommand::Sync)?;
// Send a recv command // Send a recv command
self.get_transport_mut() self.transport.send_sync_request(SyncCommand::Recv)?;
.send_sync_request(SyncCommand::Recv)?;
self.handle_recv_command(path, stream) self.handle_recv_command(path, stream)
} }
@@ -91,7 +89,7 @@ impl ADBServerDevice {
from: S, from: S,
output: &mut dyn Write, output: &mut dyn Write,
) -> Result<()> { ) -> Result<()> {
let mut raw_connection = self.get_transport().get_raw_connection()?; let mut raw_connection = self.transport.get_raw_connection()?;
let from_as_bytes = from.as_ref().as_bytes(); let from_as_bytes = from.as_ref().as_bytes();
let mut buffer = Vec::with_capacity(4 + from_as_bytes.len()); let mut buffer = Vec::with_capacity(4 + from_as_bytes.len());

View File

@@ -0,0 +1,25 @@
use crate::{models::AdbServerCommand, ADBServerDevice, Result};
impl ADBServerDevice {
/// Reverse socket connection
pub fn reverse(&mut self, remote: String, local: String) -> Result<()> {
let serial = self.identifier.clone();
self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
self.transport
.proxy_connection(AdbServerCommand::Reverse(remote, local), false)
.map(|_| ())
}
/// Remove all reverse rules
pub fn reverse_remove_all(&mut self) -> Result<()> {
let serial = self.identifier.clone();
self.connect()?
.send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?;
self.transport
.proxy_connection(AdbServerCommand::ReverseRemoveAll, false)
.map(|_| ())
}
}

View File

@@ -42,7 +42,7 @@ impl<W: Write> Write for ADBSendCommandWriter<W> {
} }
impl ADBServerDevice { impl ADBServerDevice {
/// Send [stream] to [path] on the device. /// Send stream to path on the device.
pub fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> { pub fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
log::info!("Sending data to {}", path.as_ref()); log::info!("Sending data to {}", path.as_ref());
let serial = self.identifier.clone(); let serial = self.identifier.clone();
@@ -50,12 +50,10 @@ impl ADBServerDevice {
.send_adb_request(AdbServerCommand::TransportSerial(serial))?; .send_adb_request(AdbServerCommand::TransportSerial(serial))?;
// Set device in SYNC mode // Set device in SYNC mode
self.get_transport_mut() self.transport.send_adb_request(AdbServerCommand::Sync)?;
.send_adb_request(AdbServerCommand::Sync)?;
// Send a send command // Send a send command
self.get_transport_mut() self.transport.send_sync_request(SyncCommand::Send)?;
.send_sync_request(SyncCommand::Send)?;
self.handle_send_command(stream, path) self.handle_send_command(stream, path)
} }
@@ -64,7 +62,7 @@ impl ADBServerDevice {
// Append the permission flags to the filename // Append the permission flags to the filename
let to = to.as_ref().to_string() + ",0777"; let to = to.as_ref().to_string() + ",0777";
let mut raw_connection = self.get_transport_mut().get_raw_connection()?; let mut raw_connection = self.transport.get_raw_connection()?;
// The name of the command is already sent by get_transport()?.send_sync_request // The name of the command is already sent by get_transport()?.send_sync_request
let to_as_bytes = to.as_bytes(); let to_as_bytes = to.as_bytes();
@@ -99,7 +97,7 @@ impl ADBServerDevice {
match AdbRequestStatus::from_str(str::from_utf8(&request_status)?)? { match AdbRequestStatus::from_str(str::from_utf8(&request_status)?)? {
AdbRequestStatus::Fail => { AdbRequestStatus::Fail => {
// We can keep reading to get further details // We can keep reading to get further details
let length = self.get_transport_mut().get_body_length()?; let length = self.transport.get_body_length()?;
let mut body = vec![ let mut body = vec![
0; 0;
@@ -108,9 +106,7 @@ impl ADBServerDevice {
.map_err(|_| RustADBError::ConversionError)? .map_err(|_| RustADBError::ConversionError)?
]; ];
if length > 0 { if length > 0 {
self.get_transport() self.transport.get_raw_connection()?.read_exact(&mut body)?;
.get_raw_connection()?
.read_exact(&mut body)?;
} }
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?)) Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))

Some files were not shown because too many files have changed in this diff Show More