Adds host:track-devices command

- Also clean code and TCP socket reading
This commit is contained in:
LIAUD Corentin
2022-01-05 20:41:23 +01:00
parent 7670c1f0a0
commit 33ecd8a80a
10 changed files with 196 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "adb_client"
version = "0.1.0"
version = "0.1.1"
description = "Rust ADB (Android Debug Bridge) client library"
keywords = ["adb"]
readme = "README.md"
@@ -21,7 +21,8 @@ required-features = ["adbclient"]
thiserror = { version = "1.0.30", default_features = false }
# Features needed by adb_client binary
clap = { version = "3.0.1", default_features = false, features = ["std", "derive"], optional = true }
clap = { version = "3.0.4", default_features = false, features = ["std", "derive"], optional = true }
anyhow = { version = "1.0.52", default_features = false, features = ["std"], optional = true }
[features]
adbclient = ["clap"]
adbclient = ["clap", "anyhow"]

View File

@@ -1,2 +1,23 @@
# adb_client - Rust ADB (Android Debug Bridge) client library
This crate is not affiliated with Android development core team.
## Rust crate
Simply add this to your `Cargo.toml`:
```toml
[dependencies]
adb_client = "*"
```
## Rust binary
You can install the lightweight adb binary by running the following command :
```
cargo install adb_client --features adbclient
```
https://developer.android.com/studio/command-line/adb
https://github.com/cstyan/adbDocumentation

View File

@@ -1,22 +1,15 @@
use adb_client::{AdbCommandProvider, AdbTcpConnexion};
use adb_client::{AdbCommandProvider, AdbTcpConnexion, Device};
use anyhow::Result;
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(about, version, author)]
struct Args {
#[clap(
short = 'a',
long = "address",
help = "Sets the listening address of ADB server",
default_value = "127.0.0.1"
)]
/// Sets the listening address of ADB server
#[clap(short = 'a', long = "address", default_value = "127.0.0.1")]
pub address: String,
#[clap(
short = 'p',
long = "port",
help = "Sets the listening port of ADB server",
default_value = "5037"
)]
/// Sets the listening port of ADB server
#[clap(short = 'p', long = "port", default_value = "5037")]
pub port: u16,
#[clap(subcommand)]
pub command: Command,
@@ -24,33 +17,54 @@ struct Args {
#[derive(Parser, Debug)]
enum Command {
/// Prints current ADB version
Version,
/// Asks ADB server to quit immediately
Kill,
/// List connected devices
Devices {
#[clap(short = 'l', long = "long")]
long: bool,
},
/// Tracks new devices showing up
TrackDevices,
}
fn main() {
fn main() -> Result<()> {
let opt = Args::parse();
let connexion = AdbTcpConnexion::new()
.address(opt.address)
.unwrap()
.port(opt.port);
let connexion = AdbTcpConnexion::new().address(opt.address)?.port(opt.port);
match opt.command {
Command::Version => {
connexion.version().unwrap();
let version = connexion.version()?;
println!("Android Debug Bridge version {}", version);
println!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
}
Command::Kill => {
connexion.kill()?;
}
Command::Devices { long } => {
if long {
connexion.devices_long().unwrap();
println!("List of devices attached (long)");
connexion.devices_long()?;
} else {
for device in connexion.devices().unwrap() {
println!("List of devices attached");
for device in connexion.devices()? {
println!("{}\t{}", device.identifier, device.state);
}
}
}
Command::TrackDevices => {
let callback = |device: Device| {
println!("{}", device);
Ok(())
};
println!("Live list of devices attached");
connexion.track_devices(callback)?;
}
}
Ok(())
}

View File

@@ -1,11 +1,12 @@
use std::{
io::{Read, Write},
net::{Ipv4Addr, SocketAddrV4, TcpStream},
str,
str::FromStr,
};
use crate::{
models::{AdbCommand, AdbRequestStatus},
models::{AdbCommand, AdbRequestStatus, AdbVersion},
AdbCommandProvider, Device, DeviceState, Result, RustADBError,
};
@@ -49,13 +50,22 @@ impl AdbTcpConnexion {
let mut request_status = [0; 4];
tcp_stream.read_exact(&mut request_status)?;
match AdbRequestStatus::from_str(String::from_utf8(request_status.to_vec())?.as_str())? {
match AdbRequestStatus::from_str(str::from_utf8(&request_status.to_vec())?)? {
AdbRequestStatus::Okay => {
// Read 4 bytes length
// Read exact length
let mut length = [0; 4];
tcp_stream.read_exact(&mut length)?;
let mut body: Vec<u8> = Vec::new();
tcp_stream.read_to_end(&mut body)?;
let u32_length = u32::from_str_radix(str::from_utf8(&length)?, 16)?;
let mut body = vec![
0;
u32_length
.try_into()
.map_err(|_| RustADBError::ConvertionError)?
];
if u32_length > 0 {
tcp_stream.read_exact(&mut body)?;
}
Ok(body)
}
@@ -78,12 +88,13 @@ impl Default for AdbTcpConnexion {
}
impl AdbCommandProvider for AdbTcpConnexion {
fn version(&self) -> Result<()> {
fn version(&self) -> Result<AdbVersion> {
let version = self.proxy_connexion(AdbCommand::Version)?;
println!("Version: {:?}", version.to_ascii_uppercase());
Ok(())
Ok(AdbVersion::new(
u32::from_str_radix(str::from_utf8(&version[0..2])?, 16)?,
u32::from_str_radix(str::from_utf8(&version[2..4])?, 16)?,
))
}
fn devices(&self) -> Result<Vec<Device>> {
@@ -96,14 +107,12 @@ impl AdbCommandProvider for AdbTcpConnexion {
}
let mut iter = device.split(|x| x.eq(&b'\t'));
let identifier = iter.next().unwrap();
let state = iter.next().unwrap();
let identifier = iter.next().ok_or(RustADBError::IteratorError)?;
let state = iter.next().ok_or(RustADBError::IteratorError)?;
vec_devices.push(Device {
identifier: String::from_utf8(identifier.to_ascii_lowercase()).unwrap(),
state: DeviceState::from_str(
&String::from_utf8(state.to_ascii_lowercase()).unwrap(),
)?,
identifier: String::from_utf8(identifier.to_vec())?,
state: DeviceState::from_str(str::from_utf8(state)?)?,
});
}
@@ -118,11 +127,66 @@ impl AdbCommandProvider for AdbTcpConnexion {
// Identifier = [0]
// Device state = [1]
println!(
"Devices long: {:?}",
std::str::from_utf8(&devices_long.to_ascii_lowercase())
);
println!("Devices long: {:?}", std::str::from_utf8(&devices_long));
Ok(vec![])
}
fn kill(&self) -> Result<()> {
self.proxy_connexion(AdbCommand::Kill).map(|_| ())
}
// TODO: Change with Generator when feature stabilizes
fn track_devices(&self, callback: fn(Device) -> Result<()>) -> Result<()> {
let mut tcp_stream = TcpStream::connect(self.socket_addr)?;
let adb_command_string = AdbCommand::TrackDevices.to_string();
let adb_request = format!(
"{}{}",
format!("{:04x}", adb_command_string.len()),
adb_command_string
);
tcp_stream.write_all(adb_request.as_bytes())?;
let mut request_status = [0; 4];
tcp_stream.read_exact(&mut request_status)?;
match AdbRequestStatus::from_str(str::from_utf8(&request_status.to_vec())?)? {
AdbRequestStatus::Okay => {
loop {
// Reads first 4 bytes indicating payload length
let mut length = [0; 4];
tcp_stream.read_exact(&mut length)?;
let u32_length = u32::from_str_radix(str::from_utf8(&length)?, 16)?;
if u32_length > 0 {
let mut body = vec![
0;
u32_length
.try_into()
.map_err(|_| RustADBError::ConvertionError)?
];
tcp_stream.read_exact(&mut body)?;
let mut iter = body.split(|x| x.eq(&b'\t'));
let identifier = iter.next().ok_or(RustADBError::IteratorError)?;
let state = iter.next().ok_or(RustADBError::IteratorError)?;
let device = Device {
identifier: String::from_utf8(identifier.to_vec())?,
state: DeviceState::from_str(
str::from_utf8(state)?.trim_matches('\n'),
)?,
};
callback(device)?;
}
}
}
AdbRequestStatus::Fail => Err(RustADBError::ADBRequestFailed),
}
}
}

View File

@@ -13,7 +13,15 @@ pub enum RustADBError {
#[error("Unknown device state {0}")]
UnknownDeviceState(String),
#[error(transparent)]
Utf8Error(#[from] std::string::FromUtf8Error),
Utf8StrError(#[from] std::str::Utf8Error),
#[error(transparent)]
Utf8StringError(#[from] std::string::FromUtf8Error),
#[error(transparent)]
AddrParseError(#[from] std::net::AddrParseError),
#[error(transparent)]
ParseIntError(#[from] std::num::ParseIntError),
#[error("Nothing to iterate over")]
IteratorError,
#[error("Convertion error")]
ConvertionError,
}

View File

@@ -1,15 +1,19 @@
pub enum AdbCommand {
Version,
Kill,
Devices,
DevicesLong,
TrackDevices,
}
impl ToString for AdbCommand {
fn to_string(&self) -> String {
match self {
AdbCommand::Version => "host:version".into(),
AdbCommand::Kill => "host:kill".into(),
AdbCommand::Devices => "host:devices".into(),
AdbCommand::DevicesLong => "host:devices-l".into(),
AdbCommand::TrackDevices => "host:track-devices".into(),
}
}
}

23
src/models/adb_version.rs Normal file
View File

@@ -0,0 +1,23 @@
use std::fmt::Display;
pub struct AdbVersion {
major: u32,
minor: u32,
revision: u32,
}
impl AdbVersion {
pub fn new(minor: u32, revision: u32) -> Self {
Self {
major: 1,
minor,
revision,
}
}
}
impl Display for AdbVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.revision)
}
}

View File

@@ -1,6 +1,14 @@
use std::fmt::Display;
use crate::DeviceState;
pub struct Device {
pub identifier: String,
pub state: DeviceState,
}
impl Display for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}\t{}", self.identifier, self.state)
}
}

View File

@@ -1,9 +1,11 @@
mod adb_command;
mod adb_request_status;
mod adb_version;
mod device;
mod device_state;
pub use adb_command::AdbCommand;
pub use adb_request_status::AdbRequestStatus;
pub use adb_version::AdbVersion;
pub use device::Device;
pub use device_state::DeviceState;

View File

@@ -1,11 +1,15 @@
use crate::{Device, Result};
use crate::{models::AdbVersion, Device, Result};
/// Represents the property to serve ADB commands.
pub trait AdbCommandProvider {
/// Gets server's internal version number.
fn version(&self) -> Result<()>;
fn version(&self) -> Result<AdbVersion>;
/// Gets a list of connected devices.
fn devices(&self) -> Result<Vec<Device>>;
/// Gets an extended list of connected devices including the device paths in the state.
fn devices_long(&self) -> Result<Vec<Device>>;
/// Asks the ADB server to quit immediately.
fn kill(&self) -> Result<()>;
/// Tracks new devices showing up.
fn track_devices(&self, callback: fn(Device) -> Result<()>) -> Result<()>;
}