feat: add emu commands interface

This commit is contained in:
LIAUD Corentin
2024-08-09 15:32:51 +02:00
parent fbb65373a8
commit 7abfa451d2
60 changed files with 883 additions and 508 deletions

View File

@@ -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" }

View File

@@ -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
View 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"] }

View 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,
}

View 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 },
}

View 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 },
}

View 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
View 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(())
}

View File

@@ -0,0 +1,5 @@
mod opts;
mod reboot_type;
pub use opts::{Command, Opts};
pub use reboot_type::RebootTypeCommand;

View 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),
}

View 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
View 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" }

View File

@@ -0,0 +1,2 @@
mod rotate;
mod sms;

View 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(())
}
}

View 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(())
}
}

View File

@@ -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),
}

View File

@@ -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;

View 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,
}
}
}

View 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:"),
}
}
}

View File

@@ -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;

View 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();
}
}

View File

@@ -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 {

View 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();
}
}

View File

@@ -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)?,

View File

@@ -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','))

View File

@@ -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(());

View File

@@ -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(|_| ())
}
}

View File

@@ -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(),

View File

@@ -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)?;
}

View File

@@ -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>();

View File

@@ -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)
}

View File

@@ -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(|_| ())
}
}

View File

@@ -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;

View File

@@ -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(()),

View File

@@ -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)
}
}

View File

@@ -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(()),

View File

@@ -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(|_| ())
}
}

View File

@@ -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(()),

View File

@@ -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)
}

View 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;

View 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(())
}
}

View File

@@ -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(())
}
}

View File

@@ -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(())
}

View File

@@ -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:"),
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,4 +0,0 @@
mod tcp_server_transport;
mod transport_trait;
pub use tcp_server_transport::TCPServerProtocol;
pub use transport_trait::ADBTransport;

View File

@@ -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");
}
}