feat: add emu commands interface
This commit is contained in:
38
Cargo.toml
38
Cargo.toml
@@ -1,35 +1,9 @@
|
||||
[package]
|
||||
description = "Rust ADB (Android Debug Bridge) client library"
|
||||
[workspace]
|
||||
members = ["adb_cli"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
keywords = ["adb", "android"]
|
||||
license = "MIT"
|
||||
name = "adb_client"
|
||||
readme = "README.md"
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/cocool97/adb_client"
|
||||
version = "1.0.3"
|
||||
|
||||
[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" }
|
||||
image = { version = "0.25.2" }
|
||||
lazy_static = { version = "1.5.0" }
|
||||
log = { version = "0.4.22" }
|
||||
mio = { version = "1.0.1", features = ["os-ext", "os-poll"] }
|
||||
regex = { version = "1.10.5", features = ["perf", "std", "unicode"] }
|
||||
termios = { version = "0.3.3" }
|
||||
thiserror = { version = "1.0.63" }
|
||||
|
||||
## Binary-only dependencies
|
||||
## Marked as optional so that lib users do not depend on them
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "1.0.86" }
|
||||
clap = { version = "4.5.9", features = ["derive"] }
|
||||
rand = { version = "0.8.5" }
|
||||
|
||||
@@ -66,7 +66,7 @@ device.send(&mut input, "/data/local/tmp");
|
||||
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
|
||||
cargo install adb_cli
|
||||
```
|
||||
|
||||
## Missing features
|
||||
|
||||
14
adb_cli/Cargo.toml
Normal file
14
adb_cli/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[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 = { path = "../adb_client" }
|
||||
anyhow = { version = "1.0.86" }
|
||||
clap = { version = "4.5.9", features = ["derive"] }
|
||||
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 {
|
||||
/// Sends a SMS with given phone number and given content
|
||||
Sms {
|
||||
phone_number: String,
|
||||
content: String,
|
||||
},
|
||||
/// Rotates 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: u32 },
|
||||
/// Connect device over WI-FI
|
||||
Connect { address: SocketAddrV4 },
|
||||
/// Disconnect device over WI-FI
|
||||
Disconnect { address: SocketAddrV4 },
|
||||
}
|
||||
26
adb_cli/src/commands/local.rs
Normal file
26
adb_cli/src/commands/local.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
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,
|
||||
},
|
||||
/// Get framebuffer of device
|
||||
Framebuffer { path: 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;
|
||||
134
adb_cli/src/main.rs
Normal file
134
adb_cli/src/main.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
mod commands;
|
||||
mod models;
|
||||
|
||||
use adb_client::{ADBEmulatorDevice, ADBServer, DeviceShort};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
use commands::{EmuCommand, HostCommand, LocalCommand};
|
||||
use models::{Command, Opts};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opt = Opts::parse();
|
||||
|
||||
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)?;
|
||||
println!("Downloaded {path} as {filename}");
|
||||
}
|
||||
LocalCommand::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.send(&mut input, &path)?;
|
||||
println!("Uploaded {filename} to {path}");
|
||||
}
|
||||
LocalCommand::List { path } => {
|
||||
device.list(path)?;
|
||||
}
|
||||
LocalCommand::Stat { path } => {
|
||||
let stat_response = device.stat(path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
LocalCommand::Shell { command } => {
|
||||
if command.is_empty() {
|
||||
device.shell()?;
|
||||
} else {
|
||||
let stdout = device.shell_command(command)?;
|
||||
io::stdout().write_all(&stdout)?;
|
||||
}
|
||||
}
|
||||
LocalCommand::HostFeatures => {
|
||||
println!("Available host features");
|
||||
for feature in device.host_features()? {
|
||||
println!("- {}", feature);
|
||||
}
|
||||
}
|
||||
LocalCommand::Reboot { sub_command } => {
|
||||
println!("Reboots device");
|
||||
device.reboot(sub_command.into())?
|
||||
}
|
||||
LocalCommand::Framebuffer { path } => {
|
||||
device.framebuffer(&path)?;
|
||||
println!("Framebuffer dropped: {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Host(host) => {
|
||||
let mut adb_server = ADBServer::new(opt.address);
|
||||
|
||||
match host {
|
||||
HostCommand::Version => {
|
||||
let version = adb_server.version()?;
|
||||
println!("Android Debug Bridge version {}", version);
|
||||
println!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
HostCommand::Kill => {
|
||||
adb_server.kill()?;
|
||||
}
|
||||
HostCommand::Devices { long } => {
|
||||
if long {
|
||||
println!("List of devices attached (extended)");
|
||||
for device in adb_server.devices_long()? {
|
||||
println!("{}", device);
|
||||
}
|
||||
} else {
|
||||
println!("List of devices attached");
|
||||
for device in adb_server.devices()? {
|
||||
println!("{}", device);
|
||||
}
|
||||
}
|
||||
}
|
||||
HostCommand::TrackDevices => {
|
||||
let callback = |device: DeviceShort| {
|
||||
println!("{}", device);
|
||||
Ok(())
|
||||
};
|
||||
println!("Live list of devices attached");
|
||||
adb_server.track_devices(callback)?;
|
||||
}
|
||||
HostCommand::Pair { address, code } => {
|
||||
adb_server.pair(address, code)?;
|
||||
println!("paired device {address}");
|
||||
}
|
||||
HostCommand::Connect { address } => {
|
||||
adb_server.connect_device(address)?;
|
||||
println!("connected to {address}");
|
||||
}
|
||||
HostCommand::Disconnect { address } => {
|
||||
adb_server.disconnect_device(address)?;
|
||||
println!("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)?;
|
||||
println!("sms sent...");
|
||||
}
|
||||
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;
|
||||
27
adb_cli/src/models/opts.rs
Normal file
27
adb_cli/src/models/opts.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
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 = '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 = { version = "0.4.22" }
|
||||
mio = { version = "1.0.1", features = ["os-ext", "os-poll"] }
|
||||
regex = { version = "1.10.5", features = ["perf", "std", "unicode"] }
|
||||
termios = { version = "0.3.3" }
|
||||
thiserror = { version = "1.0.63" }
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -54,4 +54,7 @@ pub enum RustADBError {
|
||||
/// 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),
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![forbid(missing_debug_implementations)]
|
||||
#![forbid(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc = include_str!("../../README.md")]
|
||||
|
||||
mod adb_termios;
|
||||
mod emulator;
|
||||
mod error;
|
||||
mod models;
|
||||
mod server;
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
58
adb_client/src/models/adb_server_command.rs
Normal file
58
adb_client/src/models/adb_server_command.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
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, u32),
|
||||
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:"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
mod adb_command;
|
||||
mod adb_emulator_command;
|
||||
mod adb_request_status;
|
||||
mod adb_server_command;
|
||||
mod adb_version;
|
||||
mod device_long;
|
||||
mod device_short;
|
||||
@@ -8,8 +9,9 @@ mod host_features;
|
||||
mod reboot_type;
|
||||
mod sync_command;
|
||||
|
||||
pub(crate) 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_long::DeviceLong;
|
||||
pub use device_short::DeviceShort;
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::ADBTransport;
|
||||
use crate::Result;
|
||||
use crate::RustADBError;
|
||||
use crate::TCPServerProtocol;
|
||||
use crate::TCPServerTransport;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::process::Command;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ADBServer {
|
||||
/// Internal [TcpStream], lazily initialized
|
||||
pub(crate) transport: Option<TCPServerProtocol>,
|
||||
pub(crate) transport: Option<TCPServerTransport>,
|
||||
/// Address to connect to
|
||||
pub(crate) socket_addr: Option<SocketAddrV4>,
|
||||
}
|
||||
@@ -24,7 +24,7 @@ impl ADBServer {
|
||||
}
|
||||
|
||||
/// Returns the current selected transport
|
||||
pub(crate) fn get_transport(&mut self) -> Result<&mut TCPServerProtocol> {
|
||||
pub(crate) fn get_transport(&mut self) -> Result<&mut TCPServerTransport> {
|
||||
self.transport
|
||||
.as_mut()
|
||||
.ok_or(RustADBError::IOError(std::io::Error::new(
|
||||
@@ -34,17 +34,17 @@ impl ADBServer {
|
||||
}
|
||||
|
||||
/// Connect to underlying transport
|
||||
pub(crate) fn connect(&mut self) -> Result<&mut TCPServerProtocol> {
|
||||
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;
|
||||
}
|
||||
TCPServerProtocol::new(*addr)
|
||||
TCPServerTransport::new(*addr)
|
||||
} else {
|
||||
is_local_ip = true;
|
||||
TCPServerProtocol::default()
|
||||
TCPServerTransport::default()
|
||||
};
|
||||
|
||||
if is_local_ip {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use std::{io::Read, iter::Map, path::Path, slice::ChunksExact};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use image::{ImageBuffer, Rgba};
|
||||
|
||||
use crate::{models::AdbCommand, utils, ADBServerDevice, Result, RustADBError};
|
||||
use crate::{models::AdbServerCommand, utils, ADBServerDevice, Result, RustADBError};
|
||||
|
||||
type U32ChunkIter<'a> = Map<ChunksExact<'a, u8>, fn(&[u8]) -> Result<u32>>;
|
||||
|
||||
@@ -109,14 +109,14 @@ impl ADBServerDevice {
|
||||
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
let serial: String = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
self.get_transport()?
|
||||
.send_adb_request(AdbCommand::FrameBuffer)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::FrameBuffer)?;
|
||||
|
||||
let version = self
|
||||
.get_transport()?
|
||||
.get_connection()?
|
||||
.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_u32::<LittleEndian>()?;
|
||||
|
||||
match version {
|
||||
@@ -124,8 +124,8 @@ impl ADBServerDevice {
|
||||
1 => {
|
||||
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV1>()];
|
||||
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut buf)?;
|
||||
|
||||
let h: FrameBufferInfoV1 = buf.try_into()?;
|
||||
@@ -136,8 +136,8 @@ impl ADBServerDevice {
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut data)?;
|
||||
|
||||
Ok(
|
||||
@@ -149,8 +149,8 @@ impl ADBServerDevice {
|
||||
2 => {
|
||||
let mut buf = [0u8; std::mem::size_of::<FrameBufferInfoV2>()];
|
||||
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut buf)?;
|
||||
|
||||
let h: FrameBufferInfoV2 = buf.try_into()?;
|
||||
@@ -161,11 +161,10 @@ impl ADBServerDevice {
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut data)?;
|
||||
|
||||
println!("{h:?}");
|
||||
Ok(
|
||||
ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(h.width, h.height, data)
|
||||
.ok_or_else(|| RustADBError::FramebufferConversionError)?,
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, HostFeatures},
|
||||
models::{AdbServerCommand, HostFeatures},
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
|
||||
@@ -8,11 +8,11 @@ impl ADBServerDevice {
|
||||
pub fn host_features(&mut self) -> Result<Vec<HostFeatures>> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
let features = self
|
||||
.get_transport()?
|
||||
.proxy_connection(AdbCommand::HostFeatures, true)?;
|
||||
.get_transport_mut()
|
||||
.proxy_connection(AdbServerCommand::HostFeatures, true)?;
|
||||
|
||||
Ok(features
|
||||
.split(|x| x.eq(&b','))
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
@@ -13,13 +13,15 @@ impl ADBServerDevice {
|
||||
pub fn list<A: AsRef<str>>(&mut self, path: A) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.get_transport()?.send_adb_request(AdbCommand::Sync)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a list command
|
||||
self.get_transport()?.send_sync_request(SyncCommand::List)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::List)?;
|
||||
|
||||
self.handle_list_command(path)
|
||||
}
|
||||
@@ -31,20 +33,20 @@ impl ADBServerDevice {
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut response)?;
|
||||
match str::from_utf8(response.as_ref())? {
|
||||
"DENT" => {
|
||||
@@ -54,23 +56,16 @@ impl ADBServerDevice {
|
||||
let mut file_size = [0_u8; 4];
|
||||
let mut mod_time = [0_u8; 4];
|
||||
let mut name_len = [0_u8; 4];
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
.read_exact(&mut file_mod)?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
.read_exact(&mut file_size)?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
.read_exact(&mut mod_time)?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
.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.get_transport()?
|
||||
.get_connection()?
|
||||
.read_exact(&mut name_buf)?;
|
||||
connection.read_exact(&mut name_buf)?;
|
||||
}
|
||||
"DONE" => {
|
||||
return Ok(());
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, RebootType},
|
||||
models::{AdbServerCommand, RebootType},
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
|
||||
@@ -8,10 +8,10 @@ impl ADBServerDevice {
|
||||
pub fn reboot(&mut self, reboot_type: RebootType) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
self.get_transport()?
|
||||
.proxy_connection(AdbCommand::Reboot(reboot_type), false)
|
||||
self.get_transport_mut()
|
||||
.proxy_connection(AdbServerCommand::Reboot(reboot_type), false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
@@ -10,13 +10,15 @@ impl ADBServerDevice {
|
||||
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(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.get_transport()?.send_adb_request(AdbCommand::Sync)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a recv command
|
||||
self.get_transport()?.send_sync_request(SyncCommand::Recv)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::Recv)?;
|
||||
|
||||
self.handle_recv_command(path, stream)
|
||||
}
|
||||
@@ -29,11 +31,11 @@ impl ADBServerDevice {
|
||||
// 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()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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
|
||||
@@ -41,27 +43,33 @@ impl ADBServerDevice {
|
||||
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
||||
let mut data_header = [0_u8; 4]; // DATA
|
||||
loop {
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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()?.get_body_length()?.try_into().unwrap();
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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()?.get_body_length()?.try_into().unwrap();
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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(),
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
||||
models::{AdbRequestStatus, AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
@@ -15,13 +15,15 @@ impl ADBServerDevice {
|
||||
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(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.get_transport()?.send_adb_request(AdbCommand::Sync)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a send command
|
||||
self.get_transport()?.send_sync_request(SyncCommand::Send)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::Send)?;
|
||||
|
||||
self.handle_send_command(stream, path)
|
||||
}
|
||||
@@ -33,13 +35,13 @@ impl ADBServerDevice {
|
||||
// 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.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
|
||||
// Send appends the filemode to the string sent
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(to.as_bytes())?;
|
||||
|
||||
// Then we send the byte data in chunks of up to 64k
|
||||
@@ -52,12 +54,14 @@ impl ADBServerDevice {
|
||||
}
|
||||
let mut chunk_len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut chunk_len_buf, bytes_read as u32);
|
||||
self.get_transport()?.get_connection()?.write_all(b"DATA")?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&buffer[..bytes_read])?;
|
||||
}
|
||||
|
||||
@@ -68,21 +72,23 @@ impl ADBServerDevice {
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
};
|
||||
LittleEndian::write_u32(&mut len_buf, last_modified.as_secs() as u32);
|
||||
self.get_transport()?.get_connection()?.write_all(b"DONE")?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
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_transport()?.get_body_length()?;
|
||||
let length = self.get_transport_mut().get_body_length()?;
|
||||
|
||||
let mut body = vec![
|
||||
0;
|
||||
@@ -91,8 +97,8 @@ impl ADBServerDevice {
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use mio::{unix::SourceFd, Events, Interest, Poll, Token};
|
||||
|
||||
use crate::{
|
||||
adb_termios::ADBTermios,
|
||||
models::{AdbCommand, HostFeatures},
|
||||
models::{AdbServerCommand, HostFeatures},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
|
||||
@@ -40,9 +40,9 @@ impl ADBServerDevice {
|
||||
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
self.get_transport()?
|
||||
.send_adb_request(AdbCommand::ShellCommand(
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::ShellCommand(
|
||||
command
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
@@ -54,7 +54,11 @@ impl ADBServerDevice {
|
||||
let mut result = Vec::new();
|
||||
loop {
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
match self.get_transport()?.get_connection()?.read(&mut buffer) {
|
||||
match self
|
||||
.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read(&mut buffer)
|
||||
{
|
||||
Ok(size) => {
|
||||
if size == 0 {
|
||||
return Ok(result);
|
||||
@@ -74,7 +78,7 @@ impl ADBServerDevice {
|
||||
let mut adb_termios = ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
|
||||
self.connect()?.get_connection()?.set_nodelay(true)?;
|
||||
self.connect()?.get_raw_connection()?.set_nodelay(true)?;
|
||||
|
||||
// TODO: FORWARD CTRL+C !!
|
||||
|
||||
@@ -87,11 +91,12 @@ impl ADBServerDevice {
|
||||
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
self.get_transport()?.send_adb_request(AdbCommand::Shell)?;
|
||||
.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.get_transport()?.get_connection()?.try_clone()?;
|
||||
let mut read_stream = self.get_transport_mut().get_raw_connection()?.try_clone()?;
|
||||
|
||||
let (tx, rx) = mpsc::channel::<bool>();
|
||||
|
||||
@@ -8,7 +8,7 @@ use byteorder::{ByteOrder, LittleEndian};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
|
||||
@@ -52,23 +52,23 @@ impl ADBServerDevice {
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
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.get_transport()?
|
||||
.get_connection()?
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut data)?;
|
||||
|
||||
Ok(data.into())
|
||||
@@ -84,13 +84,15 @@ impl ADBServerDevice {
|
||||
pub fn stat<A: AsRef<str>>(&mut self, path: A) -> Result<AdbStatResponse> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbCommand::TransportSerial(serial))?;
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.get_transport()?.send_adb_request(AdbCommand::Sync)?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Sync)?;
|
||||
|
||||
// Send a "Stat" command
|
||||
self.get_transport()?.send_sync_request(SyncCommand::Stat)?;
|
||||
self.get_transport_mut()
|
||||
.send_sync_request(SyncCommand::Stat)?;
|
||||
|
||||
self.handle_stat_command(path)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::{models::AdbCommand, ADBServerDevice, Result};
|
||||
use crate::{models::AdbServerCommand, ADBServerDevice, Result};
|
||||
|
||||
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.connect()?
|
||||
.proxy_connection(AdbCommand::TransportAny, false)
|
||||
.proxy_connection(AdbServerCommand::TransportAny, false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,7 +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;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{models::AdbCommand, ADBServer, Result, RustADBError};
|
||||
use crate::{models::AdbServerCommand, ADBServer, Result, RustADBError};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
impl ADBServer {
|
||||
@@ -6,7 +6,7 @@ impl ADBServer {
|
||||
pub fn connect_device(&mut self, address: SocketAddrV4) -> Result<()> {
|
||||
let response = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbCommand::Connect(address), true)?;
|
||||
.proxy_connection(AdbServerCommand::Connect(address), true)?;
|
||||
|
||||
match String::from_utf8(response).unwrap() {
|
||||
s if s.starts_with("connected to") => Ok(()),
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::io::Read;
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, DeviceShort},
|
||||
ADBServer, ADBServerDevice, DeviceLong, Result, RustADBError,
|
||||
models::{AdbServerCommand, DeviceShort},
|
||||
ADBEmulatorDevice, ADBServer, ADBServerDevice, DeviceLong, Result, RustADBError,
|
||||
};
|
||||
|
||||
impl ADBServer {
|
||||
@@ -10,7 +10,7 @@ impl ADBServer {
|
||||
pub fn devices(&mut self) -> Result<Vec<DeviceShort>> {
|
||||
let devices = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbCommand::Devices, true)?;
|
||||
.proxy_connection(AdbServerCommand::Devices, true)?;
|
||||
|
||||
let mut vec_devices: Vec<DeviceShort> = vec![];
|
||||
for device in devices.split(|x| x.eq(&b'\n')) {
|
||||
@@ -28,7 +28,7 @@ impl ADBServer {
|
||||
pub fn devices_long(&mut self) -> Result<Vec<DeviceLong>> {
|
||||
let devices_long = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbCommand::DevicesLong, true)?;
|
||||
.proxy_connection(AdbServerCommand::DevicesLong, true)?;
|
||||
|
||||
let mut vec_devices: Vec<DeviceLong> = vec![];
|
||||
for device in devices_long.split(|x| x.eq(&b'\n')) {
|
||||
@@ -62,7 +62,7 @@ impl ADBServer {
|
||||
/// - 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: String) -> Result<ADBServerDevice> {
|
||||
pub fn get_device_by_name(&mut self, name: &str) -> Result<ADBServerDevice> {
|
||||
let nb_devices = self
|
||||
.devices()?
|
||||
.into_iter()
|
||||
@@ -81,7 +81,8 @@ impl ADBServer {
|
||||
/// 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(AdbCommand::TrackDevices)?;
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TrackDevices)?;
|
||||
|
||||
loop {
|
||||
let length = self.get_transport()?.get_hex_body_length()?;
|
||||
@@ -94,11 +95,25 @@ impl ADBServer {
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
self.get_transport()?
|
||||
.get_connection()?
|
||||
.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)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{models::AdbCommand, ADBServer, Result, RustADBError};
|
||||
use crate::{models::AdbServerCommand, ADBServer, Result, RustADBError};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
impl ADBServer {
|
||||
@@ -6,7 +6,7 @@ impl ADBServer {
|
||||
pub fn disconnect_device(&mut self, address: SocketAddrV4) -> Result<()> {
|
||||
let response = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbCommand::Disconnect(address), true)?;
|
||||
.proxy_connection(AdbServerCommand::Disconnect(address), true)?;
|
||||
|
||||
match String::from_utf8(response).unwrap() {
|
||||
s if s.starts_with("disconnected") => Ok(()),
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::{models::AdbCommand, ADBServer, Result};
|
||||
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(AdbCommand::Kill, false)
|
||||
.proxy_connection(AdbServerCommand::Kill, false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::AdbCommand;
|
||||
use crate::models::AdbServerCommand;
|
||||
use crate::{ADBServer, Result, RustADBError};
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
@@ -7,7 +7,7 @@ impl ADBServer {
|
||||
pub fn pair(&mut self, address: SocketAddrV4, code: u32) -> Result<()> {
|
||||
let response = self
|
||||
.connect()?
|
||||
.proxy_connection(AdbCommand::Pair(address, code), true)?;
|
||||
.proxy_connection(AdbServerCommand::Pair(address, code), true)?;
|
||||
|
||||
match String::from_utf8(response).unwrap() {
|
||||
s if s.starts_with("Successfully paired to") => Ok(()),
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{models::AdbCommand, ADBServer, AdbVersion, Result};
|
||||
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(AdbCommand::Version, true)?;
|
||||
.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;
|
||||
122
adb_client/src/transports/tcp_emulator_transport.rs
Normal file
122
adb_client/src/transports/tcp_emulator_transport.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
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)?;
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
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()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use std::str::FromStr;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
use crate::models::{AdbRequestStatus, SyncCommand};
|
||||
use crate::{models::AdbCommand, ADBTransport};
|
||||
use crate::{models::AdbServerCommand, ADBTransport};
|
||||
use crate::{Result, RustADBError};
|
||||
|
||||
const DEFAULT_SERVER_IP: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
@@ -13,20 +13,19 @@ const DEFAULT_SERVER_PORT: u16 = 5037;
|
||||
|
||||
/// Server transport running on top on TCP
|
||||
#[derive(Debug)]
|
||||
pub struct TCPServerProtocol {
|
||||
/// Address to use for further connections
|
||||
pub struct TCPServerTransport {
|
||||
socket_addr: SocketAddrV4,
|
||||
tcp_stream: Option<TcpStream>,
|
||||
}
|
||||
|
||||
impl Default for TCPServerProtocol {
|
||||
impl Default for TCPServerTransport {
|
||||
fn default() -> Self {
|
||||
Self::new(SocketAddrV4::new(DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT))
|
||||
}
|
||||
}
|
||||
|
||||
impl TCPServerProtocol {
|
||||
/// Instantiates a new instance of [AdbTcpConnection]
|
||||
impl TCPServerTransport {
|
||||
/// Instantiates a new instance of [TCPServerTransport]
|
||||
pub fn new(socket_addr: SocketAddrV4) -> Self {
|
||||
Self {
|
||||
socket_addr,
|
||||
@@ -34,9 +33,14 @@ impl TCPServerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get underlying [SocketAddrV4]
|
||||
pub fn get_socketaddr(&self) -> SocketAddrV4 {
|
||||
self.socket_addr
|
||||
}
|
||||
|
||||
pub(crate) fn proxy_connection(
|
||||
&mut self,
|
||||
adb_command: AdbCommand,
|
||||
adb_command: AdbServerCommand,
|
||||
with_response: bool,
|
||||
) -> Result<Vec<u8>> {
|
||||
self.send_adb_request(adb_command)?;
|
||||
@@ -50,7 +54,7 @@ impl TCPServerProtocol {
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.get_connection()?.read_exact(&mut body)?;
|
||||
self.get_raw_connection()?.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Ok(body)
|
||||
@@ -59,7 +63,7 @@ impl TCPServerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_connection(&self) -> Result<&TcpStream> {
|
||||
pub(crate) fn get_raw_connection(&self) -> Result<&TcpStream> {
|
||||
self.tcp_stream
|
||||
.as_ref()
|
||||
.ok_or(RustADBError::IOError(std::io::Error::new(
|
||||
@@ -68,19 +72,6 @@ impl TCPServerProtocol {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Creates a new connection to ADB server.
|
||||
///
|
||||
/// Can be used after requests that closes connection.
|
||||
pub(crate) 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)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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()?;
|
||||
@@ -95,7 +86,7 @@ impl TCPServerProtocol {
|
||||
// First 4 bytes are the name of the command we want to send
|
||||
// (e.g. "SEND", "RECV", "STAT", "LIST")
|
||||
Ok(self
|
||||
.get_connection()?
|
||||
.get_raw_connection()?
|
||||
.write_all(command.to_string().as_bytes())?)
|
||||
}
|
||||
|
||||
@@ -108,22 +99,23 @@ impl TCPServerProtocol {
|
||||
/// Read 4 bytes representing body length
|
||||
fn read_body_length(&mut self) -> Result<[u8; 4]> {
|
||||
let mut length_buffer = [0; 4];
|
||||
self.get_connection()?.read_exact(&mut length_buffer)?;
|
||||
self.get_raw_connection()?.read_exact(&mut length_buffer)?;
|
||||
|
||||
Ok(length_buffer)
|
||||
}
|
||||
|
||||
/// 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) -> Result<()> {
|
||||
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_connection()?.write_all(adb_request.as_bytes())?;
|
||||
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_connection()?.read_exact(&mut request_status)?;
|
||||
self.get_raw_connection()?.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(std::str::from_utf8(request_status.as_ref())?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
@@ -137,7 +129,7 @@ impl TCPServerProtocol {
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.get_connection()?.read_exact(&mut body)?;
|
||||
self.get_raw_connection()?.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))
|
||||
@@ -147,7 +139,7 @@ impl TCPServerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBTransport for TCPServerProtocol {
|
||||
impl ADBTransport for TCPServerTransport {
|
||||
fn disconnect(&mut self) -> Result<()> {
|
||||
if let Some(conn) = &mut self.tcp_stream {
|
||||
conn.shutdown(std::net::Shutdown::Both)?;
|
||||
@@ -157,6 +149,12 @@ impl ADBTransport for TCPServerProtocol {
|
||||
}
|
||||
|
||||
fn connect(&mut self) -> Result<()> {
|
||||
self.connect()
|
||||
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)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
use adb_client::{ADBServer, DeviceShort, RebootType};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::net::SocketAddrV4;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(about, version, author)]
|
||||
pub struct Args {
|
||||
#[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)]
|
||||
LocalCommand(LocalCommand),
|
||||
#[clap(flatten)]
|
||||
HostCommand(HostCommand),
|
||||
}
|
||||
|
||||
#[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,
|
||||
},
|
||||
/// Get framebuffer of device
|
||||
Framebuffer { path: String },
|
||||
}
|
||||
|
||||
#[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: u32 },
|
||||
/// Connect device over WI-FI
|
||||
Connect { address: SocketAddrV4 },
|
||||
/// Disconnect device over WI-FI
|
||||
Disconnect { address: SocketAddrV4 },
|
||||
}
|
||||
|
||||
#[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<()> {
|
||||
let opt = Args::parse();
|
||||
|
||||
let mut adb_server = ADBServer::new(opt.address);
|
||||
|
||||
match opt.command {
|
||||
Command::LocalCommand(local) => {
|
||||
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)?;
|
||||
println!("Downloaded {path} as {filename}");
|
||||
}
|
||||
LocalCommand::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.send(&mut input, &path)?;
|
||||
println!("Uploaded {filename} to {path}");
|
||||
}
|
||||
LocalCommand::List { path } => {
|
||||
device.list(path)?;
|
||||
}
|
||||
LocalCommand::Stat { path } => {
|
||||
let stat_response = device.stat(path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
LocalCommand::Shell { command } => {
|
||||
if command.is_empty() {
|
||||
device.shell()?;
|
||||
} else {
|
||||
let stdout = device.shell_command(command)?;
|
||||
io::stdout().write_all(&stdout)?;
|
||||
}
|
||||
}
|
||||
LocalCommand::HostFeatures => {
|
||||
println!("Available host features");
|
||||
for feature in device.host_features()? {
|
||||
println!("- {}", feature);
|
||||
}
|
||||
}
|
||||
LocalCommand::Reboot { sub_command } => {
|
||||
println!("Reboots device");
|
||||
device.reboot(sub_command.into())?
|
||||
}
|
||||
LocalCommand::Framebuffer { path } => {
|
||||
device.framebuffer(&path)?;
|
||||
println!("Framebuffer dropped: {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::HostCommand(host) => match host {
|
||||
HostCommand::Version => {
|
||||
let version = adb_server.version()?;
|
||||
println!("Android Debug Bridge version {}", version);
|
||||
println!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
HostCommand::Kill => {
|
||||
adb_server.kill()?;
|
||||
}
|
||||
HostCommand::Devices { long } => {
|
||||
if long {
|
||||
println!("List of devices attached (extended)");
|
||||
for device in adb_server.devices_long()? {
|
||||
println!("{}", device);
|
||||
}
|
||||
} else {
|
||||
println!("List of devices attached");
|
||||
for device in adb_server.devices()? {
|
||||
println!("{}", device);
|
||||
}
|
||||
}
|
||||
}
|
||||
HostCommand::TrackDevices => {
|
||||
let callback = |device: DeviceShort| {
|
||||
println!("{}", device);
|
||||
Ok(())
|
||||
};
|
||||
println!("Live list of devices attached");
|
||||
adb_server.track_devices(callback)?;
|
||||
}
|
||||
HostCommand::Pair { address, code } => {
|
||||
adb_server.pair(address, code)?;
|
||||
println!("paired device {address}");
|
||||
}
|
||||
HostCommand::Connect { address } => {
|
||||
adb_server.connect_device(address)?;
|
||||
println!("connected to {address}");
|
||||
}
|
||||
HostCommand::Disconnect { address } => {
|
||||
adb_server.disconnect_device(address)?;
|
||||
println!("disconnected {address}");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::RebootType;
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
pub(crate) enum AdbCommand {
|
||||
Version,
|
||||
Kill,
|
||||
Devices,
|
||||
DevicesLong,
|
||||
TrackDevices,
|
||||
HostFeatures,
|
||||
Connect(SocketAddrV4),
|
||||
Disconnect(SocketAddrV4),
|
||||
Pair(SocketAddrV4, u32),
|
||||
TransportAny,
|
||||
TransportSerial(String),
|
||||
ShellCommand(String),
|
||||
Shell,
|
||||
FrameBuffer,
|
||||
Sync,
|
||||
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}")
|
||||
}
|
||||
AdbCommand::Connect(addr) => write!(f, "host:connect:{}", addr),
|
||||
AdbCommand::Disconnect(addr) => write!(f, "host:disconnect:{}", addr),
|
||||
AdbCommand::Pair(addr, code) => {
|
||||
write!(f, "host:pair:{code}:{}", addr)
|
||||
}
|
||||
AdbCommand::FrameBuffer => write!(f, "framebuffer:"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
use crate::{ADBTransport, Result, RustADBError, TCPServerProtocol};
|
||||
|
||||
/// Represents a device connected to the ADB server.
|
||||
#[derive(Debug)]
|
||||
pub struct ADBServerDevice {
|
||||
/// Unique device identifier.
|
||||
pub identifier: String,
|
||||
/// Address to connect to
|
||||
pub(crate) socket_addr: Option<SocketAddrV4>,
|
||||
/// Internal [TcpStream], lazily initialized
|
||||
pub(crate) transport: Option<TCPServerProtocol>,
|
||||
}
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Instantiates a new [ADBServerDevice]
|
||||
pub fn new(identifier: String, socket_addr: Option<SocketAddrV4>) -> Self {
|
||||
Self {
|
||||
identifier,
|
||||
transport: None,
|
||||
socket_addr,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_transport(&mut self) -> Result<&mut TCPServerProtocol> {
|
||||
self.transport
|
||||
.as_mut()
|
||||
.ok_or(RustADBError::IOError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotConnected,
|
||||
"not connected",
|
||||
)))
|
||||
}
|
||||
|
||||
/// Connect to underlying transport
|
||||
pub(crate) fn connect(&mut self) -> Result<&mut TCPServerProtocol> {
|
||||
let mut transport = if let Some(addr) = &self.socket_addr {
|
||||
TCPServerProtocol::new(*addr)
|
||||
} else {
|
||||
TCPServerProtocol::default()
|
||||
};
|
||||
transport.connect()?;
|
||||
self.transport = Some(transport);
|
||||
|
||||
self.get_transport()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ADBServerDevice {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ref mut transport) = &mut self.transport {
|
||||
let _ = transport.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
mod tcp_server_transport;
|
||||
mod transport_trait;
|
||||
pub use tcp_server_transport::TCPServerProtocol;
|
||||
pub use transport_trait::ADBTransport;
|
||||
@@ -86,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