Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83d716d685 | ||
|
|
101fafe4ec | ||
|
|
251276c766 | ||
|
|
d00290c450 | ||
|
|
265971bd6d | ||
|
|
7abfa451d2 | ||
|
|
fbb65373a8 | ||
|
|
aa22472952 | ||
|
|
2919003a9b | ||
|
|
1a62105565 | ||
|
|
1805c60e32 | ||
|
|
24a6e49ab8 | ||
|
|
13b69120f5 | ||
|
|
a7b4cf7d80 | ||
|
|
1342b4c34a | ||
|
|
8a682f7472 | ||
|
|
3d3546106e | ||
|
|
c6ffa2ff6b |
37
Cargo.toml
37
Cargo.toml
@@ -1,32 +1,13 @@
|
||||
[package]
|
||||
description = "Rust ADB (Android Debug Bridge) client library"
|
||||
[workspace]
|
||||
members = ["adb_cli", "adb_client"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
keywords = ["adb", "android"]
|
||||
license = "MIT"
|
||||
name = "adb_client"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/cocool97/adb_client"
|
||||
version = "1.0.3"
|
||||
version = "1.0.6"
|
||||
|
||||
[lib]
|
||||
name = "adb_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "adb_cli"
|
||||
path = "examples/adb_cli.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1.5.0" }
|
||||
chrono = { version = "0.4.38" }
|
||||
lazy_static = { version = "1.5.0" }
|
||||
mio = { version = "1.0.0", features = ["os-ext", "os-poll"] }
|
||||
regex = { version = "1.10.5", features = ["perf", "std", "unicode"] }
|
||||
termios = { version = "0.3.3" }
|
||||
thiserror = { version = "1.0.61" }
|
||||
|
||||
## Binary-only dependencies
|
||||
## Marked as optional so that lib users do not depend on them
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
rand = { version = "0.8.5" }
|
||||
# To build locally when working on a new version
|
||||
[patch.crates-io]
|
||||
adb_client = { path = "./adb_client" }
|
||||
67
README.md
67
README.md
@@ -1,72 +1,31 @@
|
||||
# adb_client
|
||||
# Rust adb_client
|
||||
|
||||
Android Debug Bridge (ADB) client implementation in pure Rust !
|
||||
[](https://crates.io/crates/adb_client)
|
||||
[](https://deps.rs/repo/github/cocool97/adb_client)
|
||||
|
||||
**A**ndroid **D**ebug **B**ridge (ADB) client implementation in pure Rust !
|
||||
|
||||
Main features :
|
||||
|
||||
- Full Rust, no need to use shell commands
|
||||
- Full Rust, no need to use `adb *` shell commands
|
||||
- Currently only support server TCP/IP protocol
|
||||
- Highly configurable
|
||||
- Easy to use !
|
||||
|
||||
## Examples
|
||||
## adb_client
|
||||
|
||||
First declare `adb_client` as a dependency by simply adding this to your `Cargo.toml`:
|
||||
Rust library implementing ADB protocol and providing high-level abstraction over commands.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
adb_client = "*"
|
||||
```
|
||||
Improved documentation [here](./adb_client/README.md).
|
||||
|
||||
### Launch a command on host device
|
||||
## adb_cli
|
||||
|
||||
```rust
|
||||
use adb_client::AdbTcpConnection;
|
||||
use std::net::Ipv4Addr;
|
||||
Rust binary providing an improved version of `adb` CLI, using `adb_client` library. Can be used as an usage example of the library.
|
||||
|
||||
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
||||
connection.shell_command(None, ["df", "-h"]);
|
||||
```
|
||||
|
||||
### Get available ADB devices
|
||||
|
||||
```rust
|
||||
use adb_client::AdbTcpConnection;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
||||
connection.devices();
|
||||
```
|
||||
|
||||
### Push a file to the device
|
||||
|
||||
```rust
|
||||
use adb_client::AdbTcpConnection;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
let mut connection = AdbTcpConnection::new(Ipv4Addr::from([127,0,0,1]), 5037).unwrap();
|
||||
let mut input = File::open(Path::new("/tmp")).unwrap();
|
||||
connection.send::<&str,&str>(None, &mut input, "/data/local/tmp");
|
||||
```
|
||||
|
||||
## Rust binary
|
||||
|
||||
This crate also provides a lightweight binary based on the `adb_client` crate. You can install it by running the following command :
|
||||
|
||||
```shell
|
||||
cargo install adb_client --example adb_cli
|
||||
```
|
||||
Improved documentation [here](./adb_cli/README.md).
|
||||
|
||||
## Missing features
|
||||
|
||||
- USB protocol
|
||||
- USB protocol (Work in progress)
|
||||
|
||||
All pull requests are welcome !
|
||||
|
||||
## Documentation
|
||||
|
||||
- <https://developer.android.com/studio/command-line/adb>
|
||||
|
||||
- <https://github.com/cstyan/adbDocumentation>
|
||||
|
||||
16
adb_cli/Cargo.toml
Normal file
16
adb_cli/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
description = "Rust ADB (Android Debug Bridge) CLI"
|
||||
edition.workspace = true
|
||||
keywords = ["adb", "android"]
|
||||
license.workspace = true
|
||||
name = "adb_cli"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adb_client = { version = "1.0.6" }
|
||||
anyhow = { version = "1.0.86" }
|
||||
clap = { version = "4.5.17", features = ["derive"] }
|
||||
env_logger = { version = "0.11.5" }
|
||||
log = "0.4.22"
|
||||
51
adb_cli/README.md
Normal file
51
adb_cli/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# adb_cli
|
||||
|
||||
[](./LICENSE-MIT)
|
||||

|
||||
|
||||
Rust binary providing an improved version of `adb` CLI.
|
||||
|
||||
## Rust binary
|
||||
|
||||
This crate provides a lightweight binary based on the `adb_client` crate. You can install it by running the following command :
|
||||
|
||||
```shell
|
||||
cargo install adb_cli
|
||||
```
|
||||
|
||||
Usage is quite simple, and tends to look like `adb`:
|
||||
|
||||
```bash
|
||||
user@laptop ~/adb_client (main)> adb_cli --help
|
||||
Rust ADB (Android Debug Bridge) CLI
|
||||
|
||||
Usage: adb_cli [OPTIONS] <COMMAND>
|
||||
|
||||
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
|
||||
reboot Reboot the device
|
||||
framebuffer Dump framebuffer 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)
|
||||
|
||||
Options:
|
||||
-d, --debug
|
||||
-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
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
12
adb_cli/src/commands/emu.rs
Normal file
12
adb_cli/src/commands/emu.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
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,
|
||||
}
|
||||
24
adb_cli/src/commands/host.rs
Normal file
24
adb_cli/src/commands/host.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum HostCommand {
|
||||
/// Print current ADB version.
|
||||
Version,
|
||||
/// Ask ADB server to quit immediately.
|
||||
Kill,
|
||||
/// List connected devices.
|
||||
Devices {
|
||||
#[clap(short = 'l', long = "long")]
|
||||
long: bool,
|
||||
},
|
||||
/// Track new devices showing up.
|
||||
TrackDevices,
|
||||
/// Pair device with a given code
|
||||
Pair { address: SocketAddrV4, code: String },
|
||||
/// Connect device over WI-FI
|
||||
Connect { address: SocketAddrV4 },
|
||||
/// Disconnect device over WI-FI
|
||||
Disconnect { address: SocketAddrV4 },
|
||||
}
|
||||
31
adb_cli/src/commands/local.rs
Normal file
31
adb_cli/src/commands/local.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use clap::Parser;
|
||||
|
||||
use crate::models::RebootTypeCommand;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum LocalCommand {
|
||||
/// 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
|
||||
Shell { command: Vec<String> },
|
||||
/// Reboot the device
|
||||
Reboot {
|
||||
#[clap(subcommand)]
|
||||
sub_command: RebootTypeCommand,
|
||||
},
|
||||
/// Dump framebuffer of device
|
||||
Framebuffer { path: String },
|
||||
/// Get logs of device
|
||||
Logcat {
|
||||
/// Path to output file (created if not exists)
|
||||
path: Option<String>,
|
||||
},
|
||||
}
|
||||
7
adb_cli/src/commands/mod.rs
Normal file
7
adb_cli/src/commands/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod emu;
|
||||
mod host;
|
||||
mod local;
|
||||
|
||||
pub use emu::EmuCommand;
|
||||
pub use host::HostCommand;
|
||||
pub use local::LocalCommand;
|
||||
156
adb_cli/src/main.rs
Normal file
156
adb_cli/src/main.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
mod commands;
|
||||
mod models;
|
||||
|
||||
use adb_client::{ADBEmulatorDevice, ADBServer, DeviceShort};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
use commands::{EmuCommand, HostCommand, LocalCommand};
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
use models::{Command, Opts};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opt = Opts::parse();
|
||||
|
||||
let max_level = if opt.verbose {
|
||||
LevelFilter::Trace
|
||||
} else {
|
||||
LevelFilter::Info
|
||||
};
|
||||
let mut builder = Builder::default();
|
||||
builder.filter_level(max_level);
|
||||
builder.init();
|
||||
|
||||
match opt.command {
|
||||
Command::Local(local) => {
|
||||
let mut adb_server = ADBServer::new(opt.address);
|
||||
|
||||
let mut device = match opt.serial {
|
||||
Some(serial) => adb_server.get_device_by_name(&serial)?,
|
||||
None => adb_server.get_device()?,
|
||||
};
|
||||
|
||||
match local {
|
||||
LocalCommand::Pull { path, filename } => {
|
||||
let mut output = File::create(Path::new(&filename))?;
|
||||
device.recv(&path, &mut output)?;
|
||||
log::info!("Downloaded {path} as {filename}");
|
||||
}
|
||||
LocalCommand::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.send(&mut input, &path)?;
|
||||
log::info!("Uploaded {filename} to {path}");
|
||||
}
|
||||
LocalCommand::List { path } => {
|
||||
device.list(path)?;
|
||||
}
|
||||
LocalCommand::Stat { path } => {
|
||||
let stat_response = device.stat(path)?;
|
||||
log::info!("{}", stat_response);
|
||||
}
|
||||
LocalCommand::Shell { command } => {
|
||||
if command.is_empty() {
|
||||
device.shell()?;
|
||||
} else {
|
||||
device.shell_command(command, std::io::stdout())?;
|
||||
}
|
||||
}
|
||||
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 { sub_command } => {
|
||||
log::info!("Reboots device");
|
||||
device.reboot(sub_command.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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Host(host) => {
|
||||
let mut adb_server = ADBServer::new(opt.address);
|
||||
|
||||
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()?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
5
adb_cli/src/models/mod.rs
Normal file
5
adb_cli/src/models/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod opts;
|
||||
mod reboot_type;
|
||||
|
||||
pub use opts::{Command, Opts};
|
||||
pub use reboot_type::RebootTypeCommand;
|
||||
29
adb_cli/src/models/opts.rs
Normal file
29
adb_cli/src/models/opts.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::commands::{EmuCommand, HostCommand, LocalCommand};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(about, version, author)]
|
||||
pub struct Opts {
|
||||
#[clap(short = 'v', long = "verbose")]
|
||||
pub verbose: bool,
|
||||
#[clap(short = 'a', long = "address", default_value = "127.0.0.1:5037")]
|
||||
pub address: SocketAddrV4,
|
||||
/// Serial id of a specific device. Every request will be sent to this device.
|
||||
#[clap(short = 's', long = "serial")]
|
||||
pub serial: Option<String>,
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum Command {
|
||||
#[clap(flatten)]
|
||||
Local(LocalCommand),
|
||||
#[clap(flatten)]
|
||||
Host(HostCommand),
|
||||
#[clap(flatten)]
|
||||
Emu(EmuCommand),
|
||||
}
|
||||
23
adb_cli/src/models/reboot_type.rs
Normal file
23
adb_cli/src/models/reboot_type.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use adb_client::RebootType;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum RebootTypeCommand {
|
||||
System,
|
||||
Bootloader,
|
||||
Recovery,
|
||||
Sideload,
|
||||
SideloadAutoReboot,
|
||||
}
|
||||
|
||||
impl From<RebootTypeCommand> for RebootType {
|
||||
fn from(value: RebootTypeCommand) -> Self {
|
||||
match value {
|
||||
RebootTypeCommand::System => RebootType::System,
|
||||
RebootTypeCommand::Bootloader => RebootType::Bootloader,
|
||||
RebootTypeCommand::Recovery => RebootType::Recovery,
|
||||
RebootTypeCommand::Sideload => RebootType::Sideload,
|
||||
RebootTypeCommand::SideloadAutoReboot => RebootType::SideloadAutoReboot,
|
||||
}
|
||||
}
|
||||
}
|
||||
21
adb_client/Cargo.toml
Normal file
21
adb_client/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
description = "Rust ADB (Android Debug Bridge) client library"
|
||||
edition.workspace = true
|
||||
keywords = ["adb", "android"]
|
||||
license.workspace = true
|
||||
name = "adb_client"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1.5.0" }
|
||||
chrono = { version = "0.4.38" }
|
||||
homedir = "0.3.3"
|
||||
image = { version = "0.25.2" }
|
||||
lazy_static = { version = "1.5.0" }
|
||||
log = "0.4.22"
|
||||
mio = { version = "1.0.2", features = ["os-ext", "os-poll"] }
|
||||
regex = { version = "1.10.6", features = ["perf", "std", "unicode"] }
|
||||
termios = { version = "0.3.3" }
|
||||
thiserror = { version = "1.0.63" }
|
||||
56
adb_client/README.md
Normal file
56
adb_client/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# adb_client
|
||||
|
||||
[](./LICENSE-MIT)
|
||||
[](https://docs.rs/adb_client)
|
||||

|
||||
|
||||
Rust library implementing ADB protocol.
|
||||
|
||||
## Installation
|
||||
|
||||
Add `adb_client` crate as a dependency by simply adding it to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
adb_client = "*"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Launch a command on device via ADB server
|
||||
|
||||
```rust no_run
|
||||
use adb_client::ADBServer;
|
||||
|
||||
let mut server = ADBServer::default();
|
||||
let mut device = server.get_device().expect("cannot get device");
|
||||
device.shell_command(["df", "-h"],std::io::stdout());
|
||||
```
|
||||
|
||||
### Get available ADB devices
|
||||
|
||||
```rust no_run
|
||||
use adb_client::ADBServer;
|
||||
use std::net::{SocketAddrV4, Ipv4Addr};
|
||||
|
||||
// A custom server address can be provided
|
||||
let server_ip = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let server_port = 5037;
|
||||
|
||||
let mut server = ADBServer::new(SocketAddrV4::new(server_ip, server_port));
|
||||
server.devices();
|
||||
```
|
||||
|
||||
### Push a file to the device
|
||||
|
||||
```rust no_run
|
||||
use adb_client::ADBServer;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
let mut server = ADBServer::default();
|
||||
let mut device = server.get_device().expect("cannot get device");
|
||||
let mut input = File::open(Path::new("/tmp")).unwrap();
|
||||
device.send(&mut input, "/data/local/tmp");
|
||||
```
|
||||
2
adb_client/src/emulator/mod.rs
Normal file
2
adb_client/src/emulator/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod rotate;
|
||||
mod sms;
|
||||
10
adb_client/src/emulator/rotate.rs
Normal file
10
adb_client/src/emulator/rotate.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
13
adb_client/src/emulator/sms.rs
Normal file
13
adb_client/src/emulator/sms.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use crate::{models::ADBEmulatorCommand, ADBEmulatorDevice, Result};
|
||||
|
||||
impl ADBEmulatorDevice {
|
||||
/// 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<()> {
|
||||
let transport = self.connect()?;
|
||||
transport.send_command(ADBEmulatorCommand::Sms(
|
||||
phone_number.to_string(),
|
||||
content.to_string(),
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -42,4 +42,19 @@ pub enum RustADBError {
|
||||
/// Remote ADB server does not support shell feature.
|
||||
#[error("Remote ADB server does not support shell feature")]
|
||||
ADBShellNotSupported,
|
||||
/// Desired device has not been found
|
||||
#[error("Device not found: {0}")]
|
||||
DeviceNotFound(String),
|
||||
/// Indicates that the device must be paired before attempting a connection over WI-FI
|
||||
#[error("Device not paired before attempting to connect")]
|
||||
ADBDeviceNotPaired,
|
||||
/// An error occurred when getting device's framebuffer image
|
||||
#[error(transparent)]
|
||||
FramebufferImageError(#[from] image::error::ImageError),
|
||||
/// An error occurred when converting framebuffer content
|
||||
#[error("Cannot convert framebuffer into image")]
|
||||
FramebufferConversionError,
|
||||
/// An error occurred while getting user's home directory
|
||||
#[error(transparent)]
|
||||
HomeError(#[from] homedir::GetHomeError),
|
||||
}
|
||||
@@ -4,11 +4,15 @@
|
||||
#![forbid(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod adb_tcp_connection;
|
||||
mod adb_termios;
|
||||
mod commands;
|
||||
mod emulator;
|
||||
mod error;
|
||||
mod models;
|
||||
pub use adb_tcp_connection::AdbTcpConnection;
|
||||
mod server;
|
||||
mod transports;
|
||||
mod utils;
|
||||
|
||||
pub use error::{Result, RustADBError};
|
||||
pub use models::{AdbVersion, Device, DeviceLong, DeviceState, RebootType};
|
||||
pub use models::{AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType};
|
||||
pub use server::*;
|
||||
pub use transports::*;
|
||||
31
adb_client/src/models/adb_emulator_command.rs
Normal file
31
adb_client/src/models/adb_emulator_command.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
pub(crate) enum ADBEmulatorCommand {
|
||||
Authenticate(String),
|
||||
Sms(String, String),
|
||||
Rotate,
|
||||
}
|
||||
|
||||
impl Display for ADBEmulatorCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// Need to call `writeln!` because emulator commands are '\n' terminated
|
||||
match self {
|
||||
ADBEmulatorCommand::Authenticate(token) => writeln!(f, "auth {token}"),
|
||||
ADBEmulatorCommand::Sms(phone_number, content) => {
|
||||
writeln!(f, "sms send {phone_number} {content}")
|
||||
}
|
||||
ADBEmulatorCommand::Rotate => writeln!(f, "rotate"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBEmulatorCommand {
|
||||
/// Return the number of lines to skip per command when checking its result
|
||||
pub(crate) fn skip_response_lines(&self) -> u8 {
|
||||
match self {
|
||||
ADBEmulatorCommand::Authenticate(_) => 1,
|
||||
ADBEmulatorCommand::Sms(_, _) => 0,
|
||||
ADBEmulatorCommand::Rotate => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
69
adb_client/src/models/adb_server_command.rs
Normal file
69
adb_client/src/models/adb_server_command.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::RebootType;
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
pub(crate) enum AdbServerCommand {
|
||||
// Host commands
|
||||
Version,
|
||||
Kill,
|
||||
Devices,
|
||||
DevicesLong,
|
||||
TrackDevices,
|
||||
HostFeatures,
|
||||
Connect(SocketAddrV4),
|
||||
Disconnect(SocketAddrV4),
|
||||
Pair(SocketAddrV4, String),
|
||||
TransportAny,
|
||||
TransportSerial(String),
|
||||
// Local commands
|
||||
ShellCommand(String),
|
||||
Shell,
|
||||
FrameBuffer,
|
||||
Sync,
|
||||
Reboot(RebootType),
|
||||
}
|
||||
|
||||
impl Display for AdbServerCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AdbServerCommand::Version => write!(f, "host:version"),
|
||||
AdbServerCommand::Kill => write!(f, "host:kill"),
|
||||
AdbServerCommand::Devices => write!(f, "host:devices"),
|
||||
AdbServerCommand::DevicesLong => write!(f, "host:devices-l"),
|
||||
AdbServerCommand::Sync => write!(f, "sync:"),
|
||||
AdbServerCommand::TrackDevices => write!(f, "host:track-devices"),
|
||||
AdbServerCommand::TransportAny => write!(f, "host:transport-any"),
|
||||
AdbServerCommand::TransportSerial(serial) => write!(f, "host:transport:{serial}"),
|
||||
AdbServerCommand::ShellCommand(command) => match std::env::var("TERM") {
|
||||
Ok(term) => write!(f, "shell,TERM={term},raw:{command}"),
|
||||
Err(_) => write!(f, "shell,raw:{command}"),
|
||||
},
|
||||
AdbServerCommand::Shell => match std::env::var("TERM") {
|
||||
Ok(term) => write!(f, "shell,TERM={term},raw:"),
|
||||
Err(_) => write!(f, "shell,raw:"),
|
||||
},
|
||||
AdbServerCommand::HostFeatures => write!(f, "host:features"),
|
||||
AdbServerCommand::Reboot(reboot_type) => {
|
||||
write!(f, "reboot:{reboot_type}")
|
||||
}
|
||||
AdbServerCommand::Connect(addr) => write!(f, "host:connect:{}", addr),
|
||||
AdbServerCommand::Disconnect(addr) => write!(f, "host:disconnect:{}", addr),
|
||||
AdbServerCommand::Pair(addr, code) => {
|
||||
write!(f, "host:pair:{code}:{addr}")
|
||||
}
|
||||
AdbServerCommand::FrameBuffer => write!(f, "framebuffer:"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pair_command() {
|
||||
let host = "192.168.0.197:34783";
|
||||
let code = "091102";
|
||||
let code_u32 = code.parse::<u32>().expect("cannot parse u32");
|
||||
let pair = AdbServerCommand::Pair(host.parse().expect("cannot parse host"), code.into());
|
||||
|
||||
assert_eq!(pair.to_string(), format!("host:pair:{code}:{host}"));
|
||||
assert_ne!(pair.to_string(), format!("host:pair:{code_u32}:{host}"))
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::str::FromStr;
|
||||
use std::{fmt::Display, str};
|
||||
|
||||
use crate::{DeviceState, RustADBError};
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use crate::{DeviceState, RustADBError};
|
||||
lazy_static! {
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
/// Represents a new device with more informations helded.
|
||||
#[derive(Debug)]
|
||||
@@ -43,11 +48,8 @@ impl Display for DeviceLong {
|
||||
impl TryFrom<Vec<u8>> for DeviceLong {
|
||||
type Error = RustADBError;
|
||||
|
||||
// TODO: Prevent regex compilation every call to try_from()
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let parse_regex = Regex::new("^(?P<identifier>\\w+)\\s+(?P<state>\\w+) usb:(?P<usb>.*) (product:(?P<product>\\w+) model:(?P<model>\\w+) device:(?P<device>\\w+) )?transport_id:(?P<transport_id>\\d+)$")?;
|
||||
|
||||
let groups = parse_regex
|
||||
let groups = DEVICES_LONG_REGEX
|
||||
.captures(&value)
|
||||
.ok_or(RustADBError::RegexParsingError)?;
|
||||
|
||||
@@ -68,7 +70,8 @@ impl TryFrom<Vec<u8>> for DeviceLong {
|
||||
)?)?,
|
||||
usb: String::from_utf8(
|
||||
groups
|
||||
.name("usb")
|
||||
.name("usb1")
|
||||
.or_else(|| groups.name("usb2"))
|
||||
.ok_or(RustADBError::RegexParsingError)?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
@@ -9,27 +9,27 @@ lazy_static! {
|
||||
}
|
||||
|
||||
/// Represents a device connected to the ADB server.
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeviceShort {
|
||||
/// Unique device identifier.
|
||||
pub identifier: String,
|
||||
/// Connection state of the device.
|
||||
pub state: DeviceState,
|
||||
}
|
||||
|
||||
impl Display for Device {
|
||||
impl Display for DeviceShort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}\t{}", self.identifier, self.state)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Device {
|
||||
impl TryFrom<Vec<u8>> for DeviceShort {
|
||||
type Error = RustADBError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
// Optional final '\n' is used to match TrackDevices inputs
|
||||
let groups = DEVICES_REGEX.captures(&value).unwrap();
|
||||
Ok(Device {
|
||||
Ok(DeviceShort {
|
||||
identifier: String::from_utf8(
|
||||
groups
|
||||
.get(1)
|
||||
@@ -3,7 +3,7 @@ use std::{fmt::Display, str::FromStr};
|
||||
use crate::RustADBError;
|
||||
|
||||
/// Represents the connection state of the device.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeviceState {
|
||||
/// The device is not connected to adb or is not responding.
|
||||
Offline,
|
||||
@@ -1,18 +1,20 @@
|
||||
mod adb_command;
|
||||
mod adb_emulator_command;
|
||||
mod adb_request_status;
|
||||
mod adb_server_command;
|
||||
mod adb_version;
|
||||
mod device;
|
||||
mod device_long;
|
||||
mod device_short;
|
||||
mod device_state;
|
||||
mod host_features;
|
||||
mod reboot_type;
|
||||
mod sync_command;
|
||||
|
||||
pub use adb_command::AdbCommand;
|
||||
pub(crate) use adb_emulator_command::ADBEmulatorCommand;
|
||||
pub use adb_request_status::AdbRequestStatus;
|
||||
pub(crate) use adb_server_command::AdbServerCommand;
|
||||
pub use adb_version::AdbVersion;
|
||||
pub use device::Device;
|
||||
pub use device_long::DeviceLong;
|
||||
pub use device_short::DeviceShort;
|
||||
pub use device_state::DeviceState;
|
||||
pub use host_features::HostFeatures;
|
||||
pub use reboot_type::RebootType;
|
||||
81
adb_client/src/server/adb_emulator_device.rs
Normal file
81
adb_client/src/server/adb_emulator_device.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
|
||||
use crate::{ADBTransport, Result, RustADBError, TCPEmulatorTransport};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use super::ADBServerDevice;
|
||||
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub struct ADBEmulatorDevice {
|
||||
/// Unique device identifier.
|
||||
pub identifier: String,
|
||||
/// Internal [TCPEmulatorTransport]
|
||||
transport: TCPEmulatorTransport,
|
||||
}
|
||||
|
||||
impl ADBEmulatorDevice {
|
||||
/// Instantiates a new [ADBEmulatorDevice]
|
||||
pub fn new(identifier: String, ip_address: Option<Ipv4Addr>) -> Result<Self> {
|
||||
let ip_address = match ip_address {
|
||||
Some(ip_address) => ip_address,
|
||||
None => Ipv4Addr::new(127, 0, 0, 1),
|
||||
};
|
||||
|
||||
let groups = EMULATOR_REGEX
|
||||
.captures(&identifier)
|
||||
.ok_or(RustADBError::DeviceNotFound(format!(
|
||||
"Device {} is likely not an emulator",
|
||||
identifier
|
||||
)))?;
|
||||
|
||||
let port = groups
|
||||
.name("port")
|
||||
.ok_or(RustADBError::RegexParsingError)?
|
||||
.as_str()
|
||||
.parse::<u16>()?;
|
||||
|
||||
let socket_addr = SocketAddrV4::new(ip_address, port);
|
||||
|
||||
let transport = TCPEmulatorTransport::new(socket_addr);
|
||||
Ok(Self {
|
||||
identifier,
|
||||
transport,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_transport_mut(&mut self) -> &mut TCPEmulatorTransport {
|
||||
&mut self.transport
|
||||
}
|
||||
|
||||
/// Connect to underlying transport
|
||||
pub(crate) fn connect(&mut self) -> Result<&mut TCPEmulatorTransport> {
|
||||
self.transport.connect()?;
|
||||
|
||||
Ok(self.get_transport_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ADBServerDevice> for ADBEmulatorDevice {
|
||||
type Error = RustADBError;
|
||||
|
||||
fn try_from(value: ADBServerDevice) -> std::result::Result<Self, Self::Error> {
|
||||
ADBEmulatorDevice::new(
|
||||
value.identifier.clone(),
|
||||
Some(*value.get_transport().get_socketaddr().ip()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ADBEmulatorDevice {
|
||||
fn drop(&mut self) {
|
||||
// Best effort here
|
||||
let _ = self.transport.disconnect();
|
||||
}
|
||||
}
|
||||
76
adb_client/src/server/adb_server.rs
Normal file
76
adb_client/src/server/adb_server.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use crate::ADBTransport;
|
||||
use crate::Result;
|
||||
use crate::RustADBError;
|
||||
use crate::TCPServerTransport;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::process::Command;
|
||||
|
||||
/// Represents an ADB Server
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ADBServer {
|
||||
/// Internal [TcpStream], lazily initialized
|
||||
pub(crate) transport: Option<TCPServerTransport>,
|
||||
/// Address to connect to
|
||||
pub(crate) socket_addr: Option<SocketAddrV4>,
|
||||
}
|
||||
|
||||
impl ADBServer {
|
||||
/// Instantiates a new [ADBServer]
|
||||
pub fn new(address: SocketAddrV4) -> Self {
|
||||
Self {
|
||||
transport: None,
|
||||
socket_addr: Some(address),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current selected transport
|
||||
pub(crate) fn get_transport(&mut self) -> Result<&mut TCPServerTransport> {
|
||||
self.transport
|
||||
.as_mut()
|
||||
.ok_or(RustADBError::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotConnected,
|
||||
"server connection not initialized",
|
||||
)))
|
||||
}
|
||||
|
||||
/// Connect to underlying transport
|
||||
pub(crate) fn connect(&mut self) -> Result<&mut TCPServerTransport> {
|
||||
let mut is_local_ip = false;
|
||||
let mut transport = if let Some(addr) = &self.socket_addr {
|
||||
let ip = addr.ip();
|
||||
if ip.is_loopback() || ip.is_unspecified() {
|
||||
is_local_ip = true;
|
||||
}
|
||||
TCPServerTransport::new(*addr)
|
||||
} else {
|
||||
is_local_ip = true;
|
||||
TCPServerTransport::default()
|
||||
};
|
||||
|
||||
if is_local_ip {
|
||||
// ADB Server is local, we start it if not already running
|
||||
let child = Command::new("adb").arg("start-server").spawn();
|
||||
match child {
|
||||
Ok(mut child) => {
|
||||
if let Err(e) = child.wait() {
|
||||
log::error!("error while starting adb server: {e}")
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("error while starting adb server: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
transport.connect()?;
|
||||
self.transport = Some(transport);
|
||||
|
||||
self.get_transport()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ADBServer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ref mut transport) = &mut self.transport {
|
||||
let _ = transport.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
adb_client/src/server/adb_server_device.rs
Normal file
49
adb_client/src/server/adb_server_device.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::{ADBTransport, Result, TCPServerTransport};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
/// Represents a device connected to the ADB server.
|
||||
#[derive(Debug)]
|
||||
pub struct ADBServerDevice {
|
||||
/// Unique device identifier.
|
||||
pub identifier: String,
|
||||
/// Internal [TCPServerTransport]
|
||||
transport: TCPServerTransport,
|
||||
}
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Instantiates a new [ADBServerDevice]
|
||||
pub fn new(identifier: String, socket_addr: Option<SocketAddrV4>) -> Self {
|
||||
let transport = if let Some(addr) = socket_addr {
|
||||
TCPServerTransport::new(addr)
|
||||
} else {
|
||||
TCPServerTransport::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
identifier,
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
pub(crate) fn connect(&mut self) -> Result<&mut TCPServerTransport> {
|
||||
self.transport.connect()?;
|
||||
|
||||
Ok(self.get_transport_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ADBServerDevice {
|
||||
fn drop(&mut self) {
|
||||
// Best effort here
|
||||
let _ = self.transport.disconnect();
|
||||
}
|
||||
}
|
||||
176
adb_client/src/server/device_commands/framebuffer.rs
Normal file
176
adb_client/src/server/device_commands/framebuffer.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
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 => unimplemented!("Version {} not implemented", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
22
adb_client/src/server/device_commands/host_features.rs
Normal file
22
adb_client/src/server/device_commands/host_features.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::{
|
||||
models::{AdbServerCommand, HostFeatures},
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Lists available ADB server features.
|
||||
pub fn host_features(&mut self) -> Result<Vec<HostFeatures>> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
let features = self
|
||||
.get_transport_mut()
|
||||
.proxy_connection(AdbServerCommand::HostFeatures, true)?;
|
||||
|
||||
Ok(features
|
||||
.split(|x| x.eq(&b','))
|
||||
.filter_map(|v| HostFeatures::try_from(v).ok())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
AdbTcpConnection, Result,
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::{
|
||||
@@ -8,21 +8,20 @@ use std::{
|
||||
str,
|
||||
};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
impl ADBServerDevice {
|
||||
/// Lists files in [path] on the device.
|
||||
pub fn list<S: ToString, A: AsRef<str>>(&mut self, serial: Option<&S>, path: A) -> Result<()> {
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, false)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), false)?
|
||||
}
|
||||
}
|
||||
pub fn list<A: AsRef<str>>(&mut self, path: A) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a list command
|
||||
self.send_sync_request(SyncCommand::List)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::List)?;
|
||||
|
||||
self.handle_list_command(path)
|
||||
}
|
||||
@@ -34,16 +33,21 @@ impl AdbTcpConnection {
|
||||
LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32);
|
||||
|
||||
// 4 bytes of command name is already sent by send_sync_request
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
|
||||
// List sends the string of the directory to list, and then the server sends a list of files
|
||||
self.tcp_stream
|
||||
// List send the string of the directory to list, and then the server send a list of files
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(path.as_ref().to_string().as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut response = [0_u8; 4];
|
||||
loop {
|
||||
self.tcp_stream.read_exact(&mut response)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut response)?;
|
||||
match str::from_utf8(response.as_ref())? {
|
||||
"DENT" => {
|
||||
// TODO: Move this to a struct that extract this data, but as the device
|
||||
@@ -52,18 +56,21 @@ impl AdbTcpConnection {
|
||||
let mut file_size = [0_u8; 4];
|
||||
let mut mod_time = [0_u8; 4];
|
||||
let mut name_len = [0_u8; 4];
|
||||
self.tcp_stream.read_exact(&mut file_mod)?;
|
||||
self.tcp_stream.read_exact(&mut file_size)?;
|
||||
self.tcp_stream.read_exact(&mut mod_time)?;
|
||||
self.tcp_stream.read_exact(&mut name_len)?;
|
||||
|
||||
let mut connection = self.get_transport_mut().get_raw_connection()?;
|
||||
connection.read_exact(&mut file_mod)?;
|
||||
connection.read_exact(&mut file_size)?;
|
||||
connection.read_exact(&mut mod_time)?;
|
||||
connection.read_exact(&mut name_len)?;
|
||||
|
||||
let name_len = LittleEndian::read_u32(&name_len);
|
||||
let mut name_buf = vec![0_u8; name_len as usize];
|
||||
self.tcp_stream.read_exact(&mut name_buf)?;
|
||||
connection.read_exact(&mut name_buf)?;
|
||||
}
|
||||
"DONE" => {
|
||||
return Ok(());
|
||||
}
|
||||
x => println!("Unknown response {}", x),
|
||||
x => log::error!("Got an unknown response {}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
56
adb_client/src/server/device_commands/logcat.rs
Normal file
56
adb_client/src/server/device_commands/logcat.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::{ADBServerDevice, Result};
|
||||
|
||||
struct LogFilter<W: Write> {
|
||||
writer: W,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<W: Write> LogFilter<W> {
|
||||
pub fn new(writer: W) -> Self {
|
||||
LogFilter {
|
||||
writer,
|
||||
buffer: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn should_write(&self, _line: &[u8]) -> bool {
|
||||
// Can implement checks here to ensure if logs have to be written
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for LogFilter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buffer.extend_from_slice(buf);
|
||||
|
||||
let buf_clone = self.buffer.clone();
|
||||
let mut lines = buf_clone.split_inclusive(|&byte| byte == b'\n').peekable();
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
if lines.peek().is_some() {
|
||||
if self.should_write(line) {
|
||||
self.writer.write_all(line)?;
|
||||
}
|
||||
} else {
|
||||
// This is the last (unfinished) element, we keep it for next round
|
||||
self.buffer = line.to_vec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Get logs from device
|
||||
pub fn get_logs<W: Write>(&mut self, output: W) -> Result<()> {
|
||||
self.shell_command(["exec logcat"], LogFilter::new(output))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
mod devices;
|
||||
mod framebuffer;
|
||||
mod host_features;
|
||||
mod kill;
|
||||
mod list;
|
||||
mod logcat;
|
||||
mod reboot;
|
||||
mod recv;
|
||||
mod send;
|
||||
mod shell;
|
||||
mod stat;
|
||||
mod transport;
|
||||
mod version;
|
||||
17
adb_client/src/server/device_commands/reboot.rs
Normal file
17
adb_client/src/server/device_commands/reboot.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::{
|
||||
models::{AdbServerCommand, RebootType},
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Reboots the device
|
||||
pub fn reboot(&mut self, reboot_type: RebootType) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
self.get_transport_mut()
|
||||
.proxy_connection(AdbServerCommand::Reboot(reboot_type), false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
85
adb_client/src/server/device_commands/recv.rs
Normal file
85
adb_client/src/server/device_commands/recv.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::{
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Receives [path] to [stream] from the device.
|
||||
pub fn recv<A: AsRef<str>>(&mut self, path: A, stream: &mut dyn Write) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a recv command
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::Recv)?;
|
||||
|
||||
self.handle_recv_command(path, stream)
|
||||
}
|
||||
|
||||
fn handle_recv_command<S: AsRef<str>>(
|
||||
&mut self,
|
||||
from: S,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
// First send 8 byte common header
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, from.as_ref().len() as u32);
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(from.as_ref().as_bytes())?;
|
||||
|
||||
// Then we receive the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
||||
let mut data_header = [0_u8; 4]; // DATA
|
||||
loop {
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut data_header)?;
|
||||
// Check if data_header is DATA or DONE or FAIL
|
||||
match &data_header {
|
||||
b"DATA" => {
|
||||
// Handle received data
|
||||
let length: usize = self
|
||||
.get_transport_mut()
|
||||
.get_body_length()?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut buffer[..length])?;
|
||||
output.write_all(&buffer[..length])?;
|
||||
}
|
||||
b"DONE" => break, // We're done here
|
||||
b"FAIL" => {
|
||||
// Handle fail
|
||||
let length: usize = self
|
||||
.get_transport_mut()
|
||||
.get_body_length()?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut buffer[..length])?;
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(
|
||||
buffer[..length].to_vec(),
|
||||
)?))?;
|
||||
}
|
||||
_ => panic!("Unknown response from device {:#?}", data_header),
|
||||
}
|
||||
}
|
||||
|
||||
// Connection should've left SYNC by now
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
||||
AdbTcpConnection, Result, RustADBError,
|
||||
models::{AdbRequestStatus, AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::{
|
||||
@@ -10,26 +10,20 @@ use std::{
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Sends [stream] to [path] on the device.
|
||||
pub fn send<S: ToString, A: AsRef<str>>(
|
||||
&mut self,
|
||||
serial: Option<&S>,
|
||||
stream: &mut dyn Read,
|
||||
path: A,
|
||||
) -> Result<()> {
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||
}
|
||||
}
|
||||
impl ADBServerDevice {
|
||||
/// Send [stream] to [path] on the device.
|
||||
pub fn send<A: AsRef<str>>(&mut self, stream: &mut dyn Read, path: A) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a send command
|
||||
self.send_sync_request(SyncCommand::Send)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::Send)?;
|
||||
|
||||
self.handle_send_command(stream, path)
|
||||
}
|
||||
@@ -38,13 +32,17 @@ impl AdbTcpConnection {
|
||||
// Append the permission flags to the filename
|
||||
let to = to.as_ref().to_string() + ",0777";
|
||||
|
||||
// The name of command is already sent by send_sync_request
|
||||
// The name of command is already sent by get_transport()?.send_sync_request
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, to.len() as u32);
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
|
||||
// Send appends the filemode to the string sent
|
||||
self.tcp_stream.write_all(to.as_bytes())?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(to.as_bytes())?;
|
||||
|
||||
// Then we send the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
@@ -56,9 +54,15 @@ impl AdbTcpConnection {
|
||||
}
|
||||
let mut chunk_len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut chunk_len_buf, bytes_read as u32);
|
||||
self.tcp_stream.write_all(b"DATA")?;
|
||||
self.tcp_stream.write_all(&chunk_len_buf)?;
|
||||
self.tcp_stream.write_all(&buffer[..bytes_read])?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(b"DATA")?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&chunk_len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&buffer[..bytes_read])?;
|
||||
}
|
||||
|
||||
// When we are done sending, we send 'DONE' <last modified time>
|
||||
@@ -68,17 +72,23 @@ impl AdbTcpConnection {
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
};
|
||||
LittleEndian::write_u32(&mut len_buf, last_modified.as_secs() as u32);
|
||||
self.tcp_stream.write_all(b"DONE")?;
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(b"DONE")?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
|
||||
// We expect 'OKAY' response from this
|
||||
let mut request_status = [0; 4];
|
||||
self.tcp_stream.read_exact(&mut request_status)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
// We can keep reading to get further details
|
||||
let length = self.get_body_length()?;
|
||||
let length = self.get_transport_mut().get_body_length()?;
|
||||
|
||||
let mut body = vec![
|
||||
0;
|
||||
@@ -87,7 +97,9 @@ impl AdbTcpConnection {
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.tcp_stream.read_exact(&mut body)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))
|
||||
@@ -8,8 +8,8 @@ use mio::{unix::SourceFd, Events, Interest, Poll, Token};
|
||||
|
||||
use crate::{
|
||||
adb_termios::ADBTermios,
|
||||
models::{AdbCommand, HostFeatures},
|
||||
AdbTcpConnection, Result, RustADBError,
|
||||
models::{AdbServerCommand, HostFeatures},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
|
||||
const STDIN: Token = Token(0);
|
||||
@@ -25,84 +25,78 @@ fn setup_poll_stdin() -> std::result::Result<Poll, io::Error> {
|
||||
Ok(poll)
|
||||
}
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Runs 'command' in a shell on the device, and return its output and error streams.
|
||||
pub fn shell_command<S: ToString>(
|
||||
impl ADBServerDevice {
|
||||
/// Runs 'command' in a shell on the device, and write its output and error streams into [`output`].
|
||||
pub fn shell_command<S: ToString, W: Write>(
|
||||
&mut self,
|
||||
serial: Option<&S>,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let supported_features = self.host_features(serial)?;
|
||||
mut output: W,
|
||||
) -> Result<()> {
|
||||
let supported_features = self.host_features()?;
|
||||
if !supported_features.contains(&HostFeatures::ShellV2)
|
||||
&& !supported_features.contains(&HostFeatures::Cmd)
|
||||
{
|
||||
return Err(RustADBError::ADBShellNotSupported);
|
||||
}
|
||||
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||
}
|
||||
}
|
||||
self.send_adb_request(
|
||||
AdbCommand::ShellCommand(
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::ShellCommand(
|
||||
command
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
),
|
||||
false,
|
||||
)?;
|
||||
))?;
|
||||
|
||||
const BUFFER_SIZE: usize = 512;
|
||||
(|| {
|
||||
let mut result = Vec::new();
|
||||
loop {
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
match self.tcp_stream.read(&mut buffer) {
|
||||
Ok(size) => {
|
||||
if size == 0 {
|
||||
return Ok(result);
|
||||
} else {
|
||||
result.extend_from_slice(&buffer[..size]);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(RustADBError::IOError(e));
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
loop {
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
match self
|
||||
.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read(&mut buffer)
|
||||
{
|
||||
Ok(size) => {
|
||||
if size == 0 {
|
||||
return Ok(());
|
||||
} else {
|
||||
output.write_all(&buffer[..size])?;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(RustADBError::IOError(e));
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts an interactive shell session on the device. Redirects stdin/stdout/stderr as appropriate.
|
||||
pub fn shell<S: ToString>(&mut self, serial: Option<&S>) -> Result<()> {
|
||||
pub fn shell(&mut self) -> Result<()> {
|
||||
let mut adb_termios = ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
|
||||
self.tcp_stream.set_nodelay(true)?;
|
||||
self.connect()?.get_raw_connection()?.set_nodelay(true)?;
|
||||
|
||||
// TODO: FORWARD CTRL+C !!
|
||||
|
||||
let supported_features = self.host_features(serial)?;
|
||||
let supported_features = self.host_features()?;
|
||||
if !supported_features.contains(&HostFeatures::ShellV2)
|
||||
&& !supported_features.contains(&HostFeatures::Cmd)
|
||||
{
|
||||
return Err(RustADBError::ADBShellNotSupported);
|
||||
}
|
||||
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||
}
|
||||
}
|
||||
self.send_adb_request(AdbCommand::Shell, false)?;
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Shell)?;
|
||||
|
||||
// let read_stream = Arc::new(self.tcp_stream);
|
||||
let mut read_stream = self.tcp_stream.try_clone()?;
|
||||
let mut read_stream = self.get_transport_mut().get_raw_connection()?.try_clone()?;
|
||||
|
||||
let (tx, rx) = mpsc::channel::<bool>();
|
||||
|
||||
@@ -8,8 +8,8 @@ use byteorder::{ByteOrder, LittleEndian};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
AdbTcpConnection, Result, RustADBError,
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -46,23 +46,30 @@ impl Display for AdbStatResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl AdbTcpConnection {
|
||||
impl ADBServerDevice {
|
||||
fn handle_stat_command<S: AsRef<str>>(&mut self, path: S) -> Result<AdbStatResponse> {
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32);
|
||||
|
||||
// 4 bytes of command name is already sent by send_sync_request
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.tcp_stream
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(path.as_ref().to_string().as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut response = [0_u8; 4];
|
||||
self.tcp_stream.read_exact(&mut response)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut response)?;
|
||||
match std::str::from_utf8(response.as_ref())? {
|
||||
"STAT" => {
|
||||
let mut data = [0_u8; 12];
|
||||
self.tcp_stream.read_exact(&mut data)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut data)?;
|
||||
|
||||
Ok(data.into())
|
||||
}
|
||||
@@ -74,23 +81,18 @@ impl AdbTcpConnection {
|
||||
}
|
||||
|
||||
/// Stat file given as [path] on the device.
|
||||
pub fn stat<S: ToString, A: AsRef<str>>(
|
||||
&mut self,
|
||||
serial: Option<S>,
|
||||
path: A,
|
||||
) -> Result<AdbStatResponse> {
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, false)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), false)?
|
||||
}
|
||||
}
|
||||
pub fn stat<A: AsRef<str>>(&mut self, path: A) -> Result<AdbStatResponse> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a "Stat" command
|
||||
self.send_sync_request(SyncCommand::Stat)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::Stat)?;
|
||||
|
||||
self.handle_stat_command(path)
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::{models::AdbCommand, AdbTcpConnection, Result};
|
||||
use crate::{models::AdbServerCommand, ADBServerDevice, Result};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
impl ADBServerDevice {
|
||||
/// Asks ADB server to switch the connection to either the device or emulator connect to/running on the host. Will fail if there is more than one such device/emulator available.
|
||||
pub fn transport_any(&mut self) -> Result<()> {
|
||||
self.proxy_connection(AdbCommand::TransportAny, false, true)
|
||||
self.connect()?
|
||||
.proxy_connection(AdbServerCommand::TransportAny, false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
9
adb_client/src/server/mod.rs
Normal file
9
adb_client/src/server/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod adb_emulator_device;
|
||||
mod adb_server;
|
||||
mod adb_server_device;
|
||||
mod device_commands;
|
||||
mod server_commands;
|
||||
|
||||
pub use adb_emulator_device::ADBEmulatorDevice;
|
||||
pub use adb_server::ADBServer;
|
||||
pub use adb_server_device::ADBServerDevice;
|
||||
16
adb_client/src/server/server_commands/connect.rs
Normal file
16
adb_client/src/server/server_commands/connect.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use crate::{models::AdbServerCommand, ADBServer, Result, RustADBError};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
impl ADBServer {
|
||||
/// Connect device over tcp with address and port
|
||||
pub fn connect_device(&mut self, address: SocketAddrV4) -> Result<()> {
|
||||
let response = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbServerCommand::Connect(address), true)?;
|
||||
|
||||
match String::from_utf8(response).unwrap() {
|
||||
s if s.starts_with("connected to") => Ok(()),
|
||||
s => Err(RustADBError::ADBRequestFailed(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
119
adb_client/src/server/server_commands/devices.rs
Normal file
119
adb_client/src/server/server_commands/devices.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::io::Read;
|
||||
|
||||
use crate::{
|
||||
models::{AdbServerCommand, DeviceShort},
|
||||
ADBEmulatorDevice, ADBServer, ADBServerDevice, DeviceLong, Result, RustADBError,
|
||||
};
|
||||
|
||||
impl ADBServer {
|
||||
/// Gets a list of connected devices.
|
||||
pub fn devices(&mut self) -> Result<Vec<DeviceShort>> {
|
||||
let devices = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbServerCommand::Devices, true)?;
|
||||
|
||||
let mut vec_devices: Vec<DeviceShort> = vec![];
|
||||
for device in devices.split(|x| x.eq(&b'\n')) {
|
||||
if device.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
vec_devices.push(DeviceShort::try_from(device.to_vec())?);
|
||||
}
|
||||
|
||||
Ok(vec_devices)
|
||||
}
|
||||
|
||||
/// Gets an extended list of connected devices including the device paths in the state.
|
||||
pub fn devices_long(&mut self) -> Result<Vec<DeviceLong>> {
|
||||
let devices_long = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbServerCommand::DevicesLong, true)?;
|
||||
|
||||
let mut vec_devices: Vec<DeviceLong> = vec![];
|
||||
for device in devices_long.split(|x| x.eq(&b'\n')) {
|
||||
if device.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
vec_devices.push(DeviceLong::try_from(device.to_vec())?);
|
||||
}
|
||||
|
||||
Ok(vec_devices)
|
||||
}
|
||||
|
||||
/// Get a device, assuming that only this device is connected.
|
||||
pub fn get_device(&mut self) -> Result<ADBServerDevice> {
|
||||
let mut devices = self.devices()?.into_iter();
|
||||
match devices.next() {
|
||||
Some(device) => match devices.next() {
|
||||
Some(_) => Err(RustADBError::DeviceNotFound(
|
||||
"too many devices connected".to_string(),
|
||||
)),
|
||||
None => Ok(ADBServerDevice::new(device.identifier, self.socket_addr)),
|
||||
},
|
||||
None => Err(RustADBError::DeviceNotFound(
|
||||
"no device connected".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a device matching the given name, if existing.
|
||||
/// - There is no device connected => Error
|
||||
/// - There is a single device connected => Ok
|
||||
/// - There are more than 1 device connected => Error
|
||||
pub fn get_device_by_name(&mut self, name: &str) -> Result<ADBServerDevice> {
|
||||
let nb_devices = self
|
||||
.devices()?
|
||||
.into_iter()
|
||||
.filter(|d| d.identifier.as_str() == name)
|
||||
.collect::<Vec<DeviceShort>>()
|
||||
.len();
|
||||
if nb_devices != 1 {
|
||||
Err(RustADBError::DeviceNotFound(format!(
|
||||
"could not find device {name}"
|
||||
)))
|
||||
} else {
|
||||
Ok(ADBServerDevice::new(name.to_string(), self.socket_addr))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks new devices showing up.
|
||||
// TODO: Change with Generator when feature stabilizes
|
||||
pub fn track_devices(&mut self, callback: impl Fn(DeviceShort) -> Result<()>) -> Result<()> {
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TrackDevices)?;
|
||||
|
||||
loop {
|
||||
let length = self.get_transport()?.get_hex_body_length()?;
|
||||
|
||||
if length > 0 {
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
self.get_transport()?
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut body)?;
|
||||
|
||||
callback(DeviceShort::try_from(body)?)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an emulator, assuming that only this device is connected.
|
||||
pub fn get_emulator_device(&mut self) -> Result<ADBEmulatorDevice> {
|
||||
let device = self.get_device()?;
|
||||
|
||||
ADBEmulatorDevice::try_from(device)
|
||||
}
|
||||
|
||||
/// Get an emulator by its name
|
||||
pub fn get_emulator_device_by_name(&mut self, name: &str) -> Result<ADBEmulatorDevice> {
|
||||
let device = self.get_device_by_name(name)?;
|
||||
|
||||
ADBEmulatorDevice::try_from(device)
|
||||
}
|
||||
}
|
||||
16
adb_client/src/server/server_commands/disconnect.rs
Normal file
16
adb_client/src/server/server_commands/disconnect.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use crate::{models::AdbServerCommand, ADBServer, Result, RustADBError};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
impl ADBServer {
|
||||
/// Connect device over tcp with address and port
|
||||
pub fn disconnect_device(&mut self, address: SocketAddrV4) -> Result<()> {
|
||||
let response = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbServerCommand::Disconnect(address), true)?;
|
||||
|
||||
match String::from_utf8(response).unwrap() {
|
||||
s if s.starts_with("disconnected") => Ok(()),
|
||||
s => Err(RustADBError::ADBRequestFailed(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
10
adb_client/src/server/server_commands/kill.rs
Normal file
10
adb_client/src/server/server_commands/kill.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use crate::{models::AdbServerCommand, ADBServer, Result};
|
||||
|
||||
impl ADBServer {
|
||||
/// Asks the ADB server to quit immediately.
|
||||
pub fn kill(&mut self) -> Result<()> {
|
||||
self.connect()?
|
||||
.proxy_connection(AdbServerCommand::Kill, false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
6
adb_client/src/server/server_commands/mod.rs
Normal file
6
adb_client/src/server/server_commands/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod connect;
|
||||
mod devices;
|
||||
mod disconnect;
|
||||
mod kill;
|
||||
mod pair;
|
||||
mod version;
|
||||
17
adb_client/src/server/server_commands/pair.rs
Normal file
17
adb_client/src/server/server_commands/pair.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::models::AdbServerCommand;
|
||||
use crate::{ADBServer, Result, RustADBError};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
impl ADBServer {
|
||||
/// Pair device on a specific port with a generated 'code'
|
||||
pub fn pair(&mut self, address: SocketAddrV4, code: String) -> Result<()> {
|
||||
let response = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbServerCommand::Pair(address, code), true)?;
|
||||
|
||||
match String::from_utf8(response).unwrap() {
|
||||
s if s.starts_with("Successfully paired to") => Ok(()),
|
||||
s => Err(RustADBError::ADBRequestFailed(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
12
adb_client/src/server/server_commands/version.rs
Normal file
12
adb_client/src/server/server_commands/version.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use crate::{models::AdbServerCommand, ADBServer, AdbVersion, Result};
|
||||
|
||||
impl ADBServer {
|
||||
/// Gets server's internal version number.
|
||||
pub fn version(&mut self) -> Result<AdbVersion> {
|
||||
let version = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbServerCommand::Version, true)?;
|
||||
|
||||
AdbVersion::try_from(version)
|
||||
}
|
||||
}
|
||||
6
adb_client/src/transports/mod.rs
Normal file
6
adb_client/src/transports/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod tcp_emulator_transport;
|
||||
mod tcp_server_transport;
|
||||
mod transport_trait;
|
||||
pub use tcp_emulator_transport::TCPEmulatorTransport;
|
||||
pub use tcp_server_transport::TCPServerTransport;
|
||||
pub use transport_trait::ADBTransport;
|
||||
127
adb_client/src/transports/tcp_emulator_transport.rs
Normal file
127
adb_client/src/transports/tcp_emulator_transport.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Read, Write},
|
||||
net::{SocketAddrV4, TcpStream},
|
||||
};
|
||||
|
||||
use homedir::my_home;
|
||||
|
||||
use super::ADBTransport;
|
||||
use crate::{models::ADBEmulatorCommand, Result, RustADBError};
|
||||
|
||||
/// Emulator transport running on top on TCP.
|
||||
#[derive(Debug)]
|
||||
pub struct TCPEmulatorTransport {
|
||||
socket_addr: SocketAddrV4,
|
||||
tcp_stream: Option<TcpStream>,
|
||||
}
|
||||
|
||||
impl TCPEmulatorTransport {
|
||||
/// Instantiates a new instance of [TCPEmulatorTransport]
|
||||
pub fn new(socket_addr: SocketAddrV4) -> Self {
|
||||
Self {
|
||||
socket_addr,
|
||||
tcp_stream: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_raw_connection(&self) -> Result<&TcpStream> {
|
||||
self.tcp_stream
|
||||
.as_ref()
|
||||
.ok_or(RustADBError::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotConnected,
|
||||
"not connected",
|
||||
)))
|
||||
}
|
||||
|
||||
/// Return authentication token stored in $HOME/.emulator_console_auth_token
|
||||
pub fn get_authentication_token(&mut self) -> Result<String> {
|
||||
let home = match my_home()? {
|
||||
Some(home) => home,
|
||||
None => todo!(),
|
||||
};
|
||||
|
||||
let mut f = File::open(home.join(".emulator_console_auth_token"))?;
|
||||
let mut token = String::new();
|
||||
f.read_to_string(&mut token)?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Send an authenticate request to this emulator
|
||||
pub fn authenticate(&mut self) -> Result<()> {
|
||||
let token = self.get_authentication_token()?;
|
||||
self.send_command(ADBEmulatorCommand::Authenticate(token))
|
||||
}
|
||||
|
||||
/// Send an [ADBEmulatorCommand] to this emulator
|
||||
pub(crate) fn send_command(&mut self, command: ADBEmulatorCommand) -> Result<()> {
|
||||
let mut connection = self.get_raw_connection()?;
|
||||
|
||||
// Send command
|
||||
connection.write_all(command.to_string().as_bytes())?;
|
||||
|
||||
// Check is an error occurred skipping lines depending on command
|
||||
self.check_error(command.skip_response_lines())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_error(&mut self, skipping: u8) -> Result<()> {
|
||||
let mut reader = BufReader::new(self.get_raw_connection()?);
|
||||
for _ in 0..skipping {
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line)?;
|
||||
if line.starts_with("KO:") {
|
||||
return Err(RustADBError::ADBRequestFailed(line));
|
||||
}
|
||||
}
|
||||
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line)?;
|
||||
|
||||
match line.starts_with("OK") {
|
||||
true => Ok(()),
|
||||
false => Err(RustADBError::ADBRequestFailed(line)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBTransport for TCPEmulatorTransport {
|
||||
fn disconnect(&mut self) -> Result<()> {
|
||||
if let Some(conn) = &mut self.tcp_stream {
|
||||
conn.shutdown(std::net::Shutdown::Both)?;
|
||||
log::trace!("Disconnected from {}", conn.peer_addr()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connect to current emulator and authenticate
|
||||
fn connect(&mut self) -> Result<()> {
|
||||
if self.tcp_stream.is_none() {
|
||||
let stream = TcpStream::connect(self.socket_addr)?;
|
||||
|
||||
log::trace!("Successfully connected to {}", self.socket_addr);
|
||||
|
||||
self.tcp_stream = Some(stream.try_clone()?);
|
||||
|
||||
let mut reader = BufReader::new(stream);
|
||||
|
||||
// Android Console: Authentication required
|
||||
// Android Console: type 'auth <auth_token>' to authenticate
|
||||
// Android Console: you can find your <auth_token> in
|
||||
// '/home/xxx/.emulator_console_auth_token'
|
||||
for _ in 0..=4 {
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line)?;
|
||||
}
|
||||
|
||||
self.authenticate()?;
|
||||
|
||||
log::trace!("Authentication successful");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
162
adb_client/src/transports/tcp_server_transport.rs
Normal file
162
adb_client/src/transports/tcp_server_transport.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{Ipv4Addr, SocketAddrV4, TcpStream};
|
||||
use std::str::FromStr;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
use crate::models::{AdbRequestStatus, SyncCommand};
|
||||
use crate::{models::AdbServerCommand, ADBTransport};
|
||||
use crate::{Result, RustADBError};
|
||||
|
||||
const DEFAULT_SERVER_IP: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
const DEFAULT_SERVER_PORT: u16 = 5037;
|
||||
|
||||
/// Server transport running on top on TCP
|
||||
#[derive(Debug)]
|
||||
pub struct TCPServerTransport {
|
||||
socket_addr: SocketAddrV4,
|
||||
tcp_stream: Option<TcpStream>,
|
||||
}
|
||||
|
||||
impl Default for TCPServerTransport {
|
||||
fn default() -> Self {
|
||||
Self::new(SocketAddrV4::new(DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT))
|
||||
}
|
||||
}
|
||||
|
||||
impl TCPServerTransport {
|
||||
/// Instantiates a new instance of [TCPServerTransport]
|
||||
pub fn new(socket_addr: SocketAddrV4) -> Self {
|
||||
Self {
|
||||
socket_addr,
|
||||
tcp_stream: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get underlying [SocketAddrV4]
|
||||
pub fn get_socketaddr(&self) -> SocketAddrV4 {
|
||||
self.socket_addr
|
||||
}
|
||||
|
||||
pub(crate) fn proxy_connection(
|
||||
&mut self,
|
||||
adb_command: AdbServerCommand,
|
||||
with_response: bool,
|
||||
) -> Result<Vec<u8>> {
|
||||
self.send_adb_request(adb_command)?;
|
||||
|
||||
if with_response {
|
||||
let length = self.get_hex_body_length()?;
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.get_raw_connection()?.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Ok(body)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_raw_connection(&self) -> Result<&TcpStream> {
|
||||
self.tcp_stream
|
||||
.as_ref()
|
||||
.ok_or(RustADBError::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotConnected,
|
||||
"not connected",
|
||||
)))
|
||||
}
|
||||
|
||||
/// Gets the body length from hexadecimal value
|
||||
pub(crate) fn get_hex_body_length(&mut self) -> Result<u32> {
|
||||
let length_buffer = self.read_body_length()?;
|
||||
Ok(u32::from_str_radix(
|
||||
std::str::from_utf8(&length_buffer)?,
|
||||
16,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Send the given [SyncCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||
pub(crate) fn send_sync_request(&mut self, command: SyncCommand) -> Result<()> {
|
||||
// First 4 bytes are the name of the command we want to send
|
||||
// (e.g. "SEND", "RECV", "STAT", "LIST")
|
||||
Ok(self
|
||||
.get_raw_connection()?
|
||||
.write_all(command.to_string().as_bytes())?)
|
||||
}
|
||||
|
||||
/// Gets the body length from a LittleEndian value
|
||||
pub(crate) fn get_body_length(&mut self) -> Result<u32> {
|
||||
let length_buffer = self.read_body_length()?;
|
||||
Ok(LittleEndian::read_u32(&length_buffer))
|
||||
}
|
||||
|
||||
/// Read 4 bytes representing body length
|
||||
fn read_body_length(&mut self) -> Result<[u8; 4]> {
|
||||
let mut length_buffer = [0; 4];
|
||||
self.get_raw_connection()?.read_exact(&mut length_buffer)?;
|
||||
|
||||
Ok(length_buffer)
|
||||
}
|
||||
|
||||
/// Send the given [AdbCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||
/// If an error occurred, a [RustADBError] is returned with the response error string.
|
||||
pub(crate) fn send_adb_request(&mut self, command: AdbServerCommand) -> Result<()> {
|
||||
let adb_command_string = command.to_string();
|
||||
let adb_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string);
|
||||
|
||||
self.get_raw_connection()?
|
||||
.write_all(adb_request.as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut request_status = [0; 4];
|
||||
self.get_raw_connection()?.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(std::str::from_utf8(request_status.as_ref())?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
// We can keep reading to get further details
|
||||
let length = self.get_hex_body_length()?;
|
||||
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.get_raw_connection()?.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))
|
||||
}
|
||||
AdbRequestStatus::Okay => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBTransport for TCPServerTransport {
|
||||
fn disconnect(&mut self) -> Result<()> {
|
||||
if let Some(conn) = &mut self.tcp_stream {
|
||||
conn.shutdown(std::net::Shutdown::Both)?;
|
||||
log::trace!("Disconnected from {}", conn.peer_addr()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect(&mut self) -> Result<()> {
|
||||
if let Some(previous) = &self.tcp_stream {
|
||||
// Ignoring underlying error, we will recreate a new connection
|
||||
let _ = previous.shutdown(std::net::Shutdown::Both);
|
||||
}
|
||||
self.tcp_stream = Some(TcpStream::connect(self.socket_addr)?);
|
||||
log::trace!("Successfully connected to {}", self.socket_addr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
10
adb_client/src/transports/transport_trait.rs
Normal file
10
adb_client/src/transports/transport_trait.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use crate::Result;
|
||||
|
||||
/// Trait representing a transport
|
||||
pub trait ADBTransport {
|
||||
/// Initializes the connection
|
||||
fn connect(&mut self) -> Result<()>;
|
||||
|
||||
/// Shuts down the connection
|
||||
fn disconnect(&mut self) -> Result<()>;
|
||||
}
|
||||
9
adb_client/src/utils.rs
Normal file
9
adb_client/src/utils.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use crate::{Result, RustADBError};
|
||||
|
||||
pub fn u32_from_le(value: &[u8]) -> Result<u32> {
|
||||
Ok(u32::from_le_bytes(
|
||||
value
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?,
|
||||
))
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
use std::fs::File;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
|
||||
use adb_client::{AdbTcpConnection, Device, RebootType, RustADBError};
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(about, version, author)]
|
||||
pub struct Args {
|
||||
/// Sets the listening address of ADB server
|
||||
#[clap(short = 'a', long = "address", default_value = "127.0.0.1")]
|
||||
pub address: Ipv4Addr,
|
||||
/// Sets the listening port of ADB server
|
||||
#[clap(short = 'p', long = "port", default_value = "5037")]
|
||||
pub port: u16,
|
||||
/// Serial id of a specific device. Every request will be sent to this device.
|
||||
#[clap(short = 's', long = "serial")]
|
||||
pub serial: Option<String>,
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum Command {
|
||||
/// Prints current ADB version.
|
||||
Version,
|
||||
/// Asks ADB server to quit immediately.
|
||||
Kill,
|
||||
/// List connected devices.
|
||||
Devices {
|
||||
#[clap(short = 'l', long = "long")]
|
||||
long: bool,
|
||||
},
|
||||
/// Tracks new devices showing up.
|
||||
TrackDevices,
|
||||
/// Lists available server features.
|
||||
HostFeatures,
|
||||
/// Pushes 'filename' to the 'path' on device
|
||||
Push { filename: String, path: String },
|
||||
/// Pushes 'path' on the device to 'filename'
|
||||
Pull { path: String, filename: String },
|
||||
/// List files for 'path' on device
|
||||
List { path: String },
|
||||
/// Stat file specified as 'path' on device
|
||||
Stat { path: String },
|
||||
/// Run 'command' in a shell on the device, and return its output and error streams.
|
||||
Shell { command: Vec<String> },
|
||||
/// Reboots the device
|
||||
Reboot {
|
||||
#[clap(subcommand)]
|
||||
sub_command: RebootTypeCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum RebootTypeCommand {
|
||||
System,
|
||||
Bootloader,
|
||||
Recovery,
|
||||
Sideload,
|
||||
SideloadAutoReboot,
|
||||
}
|
||||
|
||||
impl From<RebootTypeCommand> for RebootType {
|
||||
fn from(value: RebootTypeCommand) -> Self {
|
||||
match value {
|
||||
RebootTypeCommand::System => RebootType::System,
|
||||
RebootTypeCommand::Bootloader => RebootType::Bootloader,
|
||||
RebootTypeCommand::Recovery => RebootType::Recovery,
|
||||
RebootTypeCommand::Sideload => RebootType::Sideload,
|
||||
RebootTypeCommand::SideloadAutoReboot => RebootType::SideloadAutoReboot,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), RustADBError> {
|
||||
let opt = Args::parse();
|
||||
|
||||
let mut connection = AdbTcpConnection::new(opt.address, opt.port)?;
|
||||
|
||||
match opt.command {
|
||||
Command::Version => {
|
||||
let version = connection.version()?;
|
||||
println!("Android Debug Bridge version {}", version);
|
||||
println!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
Command::Kill => {
|
||||
connection.kill()?;
|
||||
}
|
||||
Command::Devices { long } => {
|
||||
if long {
|
||||
println!("List of devices attached (extended)");
|
||||
for device in connection.devices_long()? {
|
||||
println!("{}", device);
|
||||
}
|
||||
} else {
|
||||
println!("List of devices attached");
|
||||
for device in connection.devices()? {
|
||||
println!("{}", device);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::TrackDevices => {
|
||||
let callback = |device: Device| {
|
||||
println!("{}", device);
|
||||
Ok(())
|
||||
};
|
||||
println!("Live list of devices attached");
|
||||
connection.track_devices(callback)?;
|
||||
}
|
||||
Command::Pull { path, filename } => {
|
||||
let mut output = File::create(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
||||
connection.recv(opt.serial.as_ref(), &path, &mut output)?;
|
||||
println!("Downloaded {path} as {filename}");
|
||||
}
|
||||
Command::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
||||
connection.send(opt.serial.as_ref(), &mut input, &path)?;
|
||||
println!("Uploaded {filename} to {path}");
|
||||
}
|
||||
Command::List { path } => {
|
||||
connection.list(opt.serial.as_ref(), path)?;
|
||||
}
|
||||
Command::Stat { path } => {
|
||||
let stat_response = connection.stat(opt.serial, path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
Command::Shell { command } => {
|
||||
if command.is_empty() {
|
||||
connection.shell(opt.serial.as_ref())?;
|
||||
} else {
|
||||
connection.shell_command(opt.serial.as_ref(), command)?;
|
||||
}
|
||||
}
|
||||
Command::HostFeatures => {
|
||||
println!("Available host features");
|
||||
for feature in connection.host_features(opt.serial.as_ref())? {
|
||||
println!("- {}", feature);
|
||||
}
|
||||
}
|
||||
Command::Reboot { sub_command } => {
|
||||
println!("Reboots device");
|
||||
connection.reboot(opt.serial.as_ref(), sub_command.into())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{Ipv4Addr, SocketAddrV4, TcpStream},
|
||||
str,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
||||
Result, RustADBError,
|
||||
};
|
||||
|
||||
/// Represents an ADB-over-TCP connection.
|
||||
#[derive(Debug)]
|
||||
pub struct AdbTcpConnection {
|
||||
pub(crate) socket_addr: SocketAddrV4,
|
||||
pub(crate) tcp_stream: TcpStream,
|
||||
}
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Instantiates a new instance of [AdbTcpConnection]
|
||||
pub fn new(address: Ipv4Addr, port: u16) -> Result<Self> {
|
||||
let addr = SocketAddrV4::new(address, port);
|
||||
Ok(Self {
|
||||
socket_addr: addr,
|
||||
tcp_stream: TcpStream::connect(addr)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new connection to ADB server.
|
||||
///
|
||||
/// Can be used after requests that closes connection.
|
||||
pub(crate) fn new_connection(&mut self) -> Result<()> {
|
||||
self.tcp_stream = TcpStream::connect(self.socket_addr)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn proxy_connection(
|
||||
&mut self,
|
||||
adb_command: AdbCommand,
|
||||
with_response: bool,
|
||||
fresh_connection: bool
|
||||
) -> Result<Vec<u8>> {
|
||||
self.send_adb_request(adb_command, fresh_connection)?;
|
||||
|
||||
if with_response {
|
||||
let length = self.get_hex_body_length()?;
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.tcp_stream.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Ok(body)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the given [AdbCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||
/// If an error occurred, a [RustADBError] is returned with the response error string.
|
||||
pub(crate) fn send_adb_request(
|
||||
&mut self,
|
||||
command: AdbCommand,
|
||||
fresh_connection: bool,
|
||||
) -> Result<()> {
|
||||
if fresh_connection {
|
||||
// Recreate a new connection (likely because command does not need "state" server side)
|
||||
self.new_connection()?;
|
||||
}
|
||||
|
||||
let adb_command_string = command.to_string();
|
||||
let adb_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string);
|
||||
|
||||
self.tcp_stream.write_all(adb_request.as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut request_status = [0; 4];
|
||||
self.tcp_stream.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
// We can keep reading to get further details
|
||||
let length = self.get_body_length()?;
|
||||
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.tcp_stream.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))
|
||||
}
|
||||
AdbRequestStatus::Okay => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the given [SyncCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||
pub(crate) fn send_sync_request(&mut self, command: SyncCommand) -> Result<()> {
|
||||
// First 4 bytes are the name of the command we want to send
|
||||
// (e.g. "SEND", "RECV", "STAT", "LIST")
|
||||
Ok(self.tcp_stream.write_all(command.to_string().as_bytes())?)
|
||||
}
|
||||
|
||||
/// Gets the body length from hexadecimal value
|
||||
pub(crate) fn get_hex_body_length(&mut self) -> Result<u32> {
|
||||
let length_buffer = self.read_body_length()?;
|
||||
Ok(u32::from_str_radix(str::from_utf8(&length_buffer)?, 16)?)
|
||||
}
|
||||
|
||||
/// Gets the body length from a LittleEndian value
|
||||
pub(crate) fn get_body_length(&mut self) -> Result<u32> {
|
||||
let length_buffer = self.read_body_length()?;
|
||||
Ok(LittleEndian::read_u32(&length_buffer))
|
||||
}
|
||||
|
||||
/// Read 4 bytes representing body length
|
||||
fn read_body_length(&mut self) -> Result<[u8; 4]> {
|
||||
let mut length_buffer = [0; 4];
|
||||
self.tcp_stream.read_exact(&mut length_buffer)?;
|
||||
|
||||
Ok(length_buffer)
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
use std::io::Read;
|
||||
|
||||
use crate::{models::AdbCommand, AdbTcpConnection, Device, DeviceLong, Result, RustADBError};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Gets a list of connected devices.
|
||||
pub fn devices(&mut self) -> Result<Vec<Device>> {
|
||||
let devices = self.proxy_connection(AdbCommand::Devices, true, true)?;
|
||||
|
||||
let mut vec_devices: Vec<Device> = vec![];
|
||||
for device in devices.split(|x| x.eq(&b'\n')) {
|
||||
if device.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
vec_devices.push(Device::try_from(device.to_vec())?);
|
||||
}
|
||||
|
||||
Ok(vec_devices)
|
||||
}
|
||||
|
||||
/// Gets an extended list of connected devices including the device paths in the state.
|
||||
pub fn devices_long(&mut self) -> Result<Vec<DeviceLong>> {
|
||||
let devices_long = self.proxy_connection(AdbCommand::DevicesLong, true, true)?;
|
||||
|
||||
let mut vec_devices: Vec<DeviceLong> = vec![];
|
||||
for device in devices_long.split(|x| x.eq(&b'\n')) {
|
||||
if device.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
vec_devices.push(DeviceLong::try_from(device.to_vec())?);
|
||||
}
|
||||
|
||||
Ok(vec_devices)
|
||||
}
|
||||
|
||||
/// Tracks new devices showing up.
|
||||
// TODO: Change with Generator when feature stabilizes
|
||||
pub fn track_devices(&mut self, callback: impl Fn(Device) -> Result<()>) -> Result<()> {
|
||||
self.send_adb_request(AdbCommand::TrackDevices, true)?;
|
||||
|
||||
loop {
|
||||
let length = self.get_hex_body_length()?;
|
||||
|
||||
if length > 0 {
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
self.tcp_stream.read_exact(&mut body)?;
|
||||
|
||||
callback(Device::try_from(body)?)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, HostFeatures},
|
||||
AdbTcpConnection, Result,
|
||||
};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Lists available ADB server features.
|
||||
pub fn host_features<S: ToString>(&mut self, serial: Option<&S>) -> Result<Vec<HostFeatures>> {
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||
}
|
||||
}
|
||||
|
||||
let features = self.proxy_connection(AdbCommand::HostFeatures, true, false)?;
|
||||
|
||||
Ok(features
|
||||
.split(|x| x.eq(&b','))
|
||||
.filter_map(|v| HostFeatures::try_from(v).ok())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
use crate::{models::AdbCommand, AdbTcpConnection, Result};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Asks the ADB server to quit immediately.
|
||||
pub fn kill(&mut self) -> Result<()> {
|
||||
self.proxy_connection(AdbCommand::Kill, false, true).map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, RebootType},
|
||||
AdbTcpConnection, Result,
|
||||
};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Reboots the device
|
||||
pub fn reboot<S: ToString>(
|
||||
&mut self,
|
||||
serial: Option<&S>,
|
||||
reboot_type: RebootType,
|
||||
) -> Result<()> {
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||
}
|
||||
}
|
||||
|
||||
self.proxy_connection(AdbCommand::Reboot(reboot_type), false, false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
AdbTcpConnection, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Receives [path] to [stream] from the device.
|
||||
pub fn recv<S: ToString, A: AsRef<str>>(
|
||||
&mut self,
|
||||
serial: Option<&S>,
|
||||
path: A,
|
||||
stream: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny, true)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()), true)?
|
||||
}
|
||||
}
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync, false)?;
|
||||
|
||||
// Send a recv command
|
||||
self.send_sync_request(SyncCommand::Recv)?;
|
||||
|
||||
self.handle_recv_command(path, stream)
|
||||
}
|
||||
|
||||
fn handle_recv_command<S: AsRef<str>>(
|
||||
&mut self,
|
||||
from: S,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
// First send 8 byte common header
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, from.as_ref().len() as u32);
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.tcp_stream.write_all(from.as_ref().as_bytes())?;
|
||||
|
||||
// Then we receive the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
||||
let mut data_header = [0_u8; 4]; // DATA
|
||||
loop {
|
||||
self.tcp_stream.read_exact(&mut data_header)?;
|
||||
// Check if data_header is DATA or DONE or FAIL
|
||||
match &data_header {
|
||||
b"DATA" => {
|
||||
// Handle received data
|
||||
let length: usize = self.get_body_length()?.try_into().unwrap();
|
||||
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
||||
output.write_all(&buffer[..length])?;
|
||||
}
|
||||
b"DONE" => break, // We're done here
|
||||
b"FAIL" => {
|
||||
// Handle fail
|
||||
let length: usize = self.get_body_length()?.try_into().unwrap();
|
||||
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(
|
||||
buffer[..length].to_vec(),
|
||||
)?))?;
|
||||
}
|
||||
_ => panic!("Unknown response from device {:#?}", data_header),
|
||||
}
|
||||
}
|
||||
|
||||
// Connection should've left SYNC by now
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
use crate::{models::AdbCommand, AdbTcpConnection, AdbVersion, Result};
|
||||
|
||||
impl AdbTcpConnection {
|
||||
/// Gets server's internal version number.
|
||||
pub fn version(&mut self) -> Result<AdbVersion> {
|
||||
let version = self.proxy_connection(AdbCommand::Version, true, true)?;
|
||||
|
||||
AdbVersion::try_from(version)
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::RebootType;
|
||||
|
||||
pub enum AdbCommand {
|
||||
Version,
|
||||
Kill,
|
||||
Devices,
|
||||
DevicesLong,
|
||||
TrackDevices,
|
||||
HostFeatures,
|
||||
// TODO: NOT IMPLEMENTED YET
|
||||
// Emulator(u16),
|
||||
// Transport(String),
|
||||
// TransportUSB,
|
||||
// TransportLocal,
|
||||
TransportAny,
|
||||
TransportSerial(String),
|
||||
// Serial((String, String)),
|
||||
// USB(String),
|
||||
// Local(String),
|
||||
// Request(String),
|
||||
// GetProduct(String),
|
||||
// GetSerialNo(String),
|
||||
// GetDevPath(String),
|
||||
// GetState(String),
|
||||
// Forward((String, String, String)),
|
||||
// ForwardNoRebind((String, String, String)),
|
||||
// KillForward((String, String)),
|
||||
// KillForwardAll(String),
|
||||
// ListForward(String),
|
||||
ShellCommand(String),
|
||||
Shell,
|
||||
// Remount,
|
||||
// DevPath(String),
|
||||
// Tcp(u16),
|
||||
// Tcp((u16, String)),
|
||||
// Local(String),
|
||||
// LocalReserved(String),
|
||||
// LocalAbstract(String),
|
||||
// LocalFileSystem(String),
|
||||
// FrameBuffer,
|
||||
// JDWP(u32),
|
||||
// TrackJDWP,
|
||||
Sync,
|
||||
// Reverse(String),
|
||||
Reboot(RebootType),
|
||||
}
|
||||
|
||||
impl Display for AdbCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AdbCommand::Version => write!(f, "host:version"),
|
||||
AdbCommand::Kill => write!(f, "host:kill"),
|
||||
AdbCommand::Devices => write!(f, "host:devices"),
|
||||
AdbCommand::DevicesLong => write!(f, "host:devices-l"),
|
||||
AdbCommand::Sync => write!(f, "sync:"),
|
||||
AdbCommand::TrackDevices => write!(f, "host:track-devices"),
|
||||
AdbCommand::TransportAny => write!(f, "host:transport-any"),
|
||||
AdbCommand::TransportSerial(serial) => write!(f, "host:transport:{serial}"),
|
||||
AdbCommand::ShellCommand(command) => match std::env::var("TERM") {
|
||||
Ok(term) => write!(f, "shell,TERM={term},raw:{command}"),
|
||||
Err(_) => write!(f, "shell,raw:{command}"),
|
||||
},
|
||||
AdbCommand::Shell => match std::env::var("TERM") {
|
||||
Ok(term) => write!(f, "shell,TERM={term},raw:"),
|
||||
Err(_) => write!(f, "shell,raw:"),
|
||||
},
|
||||
AdbCommand::HostFeatures => write!(f, "host:features"),
|
||||
AdbCommand::Reboot(reboot_type) => {
|
||||
write!(f, "reboot:{reboot_type}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use adb_client::AdbTcpConnection;
|
||||
use adb_client::{ADBServer, ADBServerDevice, DeviceLong};
|
||||
use rand::Rng;
|
||||
|
||||
fn new_client() -> AdbTcpConnection {
|
||||
let address = Ipv4Addr::from_str("127.0.0.1").unwrap();
|
||||
AdbTcpConnection::new(address, 5037).expect("Could not build ADB client...")
|
||||
fn new_client() -> ADBServer {
|
||||
ADBServer::default()
|
||||
}
|
||||
|
||||
fn new_device() -> ADBServerDevice {
|
||||
let mut client = new_client();
|
||||
return client.get_device().expect("cannot get device");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -20,9 +22,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_shell() {
|
||||
let mut adb = new_client();
|
||||
adb.shell_command(None, vec!["ls"]).unwrap();
|
||||
adb.shell_command(None, vec!["pwd"]).unwrap();
|
||||
let mut device = new_device();
|
||||
|
||||
device.shell_command(vec!["ls"]).unwrap();
|
||||
device.shell_command(vec!["pwd"]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -38,10 +41,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_wrong_addr() {
|
||||
let address = Ipv4Addr::from_str("127.0.0.300").unwrap();
|
||||
let _ = AdbTcpConnection::new(address, 5037).expect("Could not create ADB connection...");
|
||||
fn test_static_devices_long() {
|
||||
let inputs = ["7a5158f05122195aa device 1-5 product:gts210vewifixx model:SM_T813 device:gts210vewifi transport_id:4"];
|
||||
for input in inputs {
|
||||
DeviceLong::try_from(input.as_bytes().to_vec())
|
||||
.expect(&format!("cannot parse input: '{input}'"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -51,25 +56,25 @@ mod tests {
|
||||
rand::thread_rng().fill(&mut key[..]);
|
||||
let mut c: Cursor<Vec<u8>> = Cursor::new(key.to_vec());
|
||||
|
||||
let mut connection = new_client();
|
||||
let mut device = new_device();
|
||||
|
||||
const TEST_FILENAME: &'static str = "/data/local/tmp/test_file";
|
||||
// Send it
|
||||
connection
|
||||
.send::<&str, &str>(None, &mut c, TEST_FILENAME)
|
||||
device
|
||||
.send(&mut c, TEST_FILENAME)
|
||||
.expect("cannot send file");
|
||||
|
||||
// Pull it to memory
|
||||
let mut res = vec![];
|
||||
connection
|
||||
.recv::<&str, &str>(None, TEST_FILENAME, &mut res)
|
||||
device
|
||||
.recv(TEST_FILENAME, &mut res)
|
||||
.expect("cannot recv file");
|
||||
|
||||
// diff
|
||||
assert_eq!(c.get_ref(), &res);
|
||||
|
||||
connection
|
||||
.shell_command::<&str>(None, [format!("rm {TEST_FILENAME}").as_str()])
|
||||
device
|
||||
.shell_command::<&str>([format!("rm {TEST_FILENAME}").as_str()])
|
||||
.expect("cannot remove test file");
|
||||
}
|
||||
|
||||
@@ -81,4 +86,13 @@ mod tests {
|
||||
let _ = connection.devices().expect("cannot get version");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_emulator() {
|
||||
let mut connection = new_client();
|
||||
let mut emulator = connection
|
||||
.get_emulator_device()
|
||||
.expect("no emulator running");
|
||||
emulator.hello().expect("cannot hello");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user