Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
target
|
||||
Cargo.lock
|
||||
.vscode
|
||||
27
Cargo.toml
Normal file
27
Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "adb_client"
|
||||
version = "0.1.0"
|
||||
description = "Rust ADB (Android Debug Bridge) client library"
|
||||
keywords = ["adb"]
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "adb_client"
|
||||
crate-type = ["lib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "adb_client"
|
||||
path = "bin/adb_client.rs"
|
||||
required-features = ["adbclient"]
|
||||
|
||||
[dependencies]
|
||||
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 }
|
||||
|
||||
[features]
|
||||
adbclient = ["clap"]
|
||||
2
README.md
Normal file
2
README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
https://developer.android.com/studio/command-line/adb
|
||||
https://github.com/cstyan/adbDocumentation
|
||||
56
bin/adb_client.rs
Normal file
56
bin/adb_client.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use adb_client::{AdbCommandProvider, AdbTcpConnexion};
|
||||
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"
|
||||
)]
|
||||
pub address: String,
|
||||
#[clap(
|
||||
short = 'p',
|
||||
long = "port",
|
||||
help = "Sets the listening port of ADB server",
|
||||
default_value = "5037"
|
||||
)]
|
||||
pub port: u16,
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
enum Command {
|
||||
Version,
|
||||
Devices {
|
||||
#[clap(short = 'l', long = "long")]
|
||||
long: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opt = Args::parse();
|
||||
|
||||
let connexion = AdbTcpConnexion::new()
|
||||
.address(opt.address)
|
||||
.unwrap()
|
||||
.port(opt.port);
|
||||
|
||||
match opt.command {
|
||||
Command::Version => {
|
||||
connexion.version().unwrap();
|
||||
}
|
||||
Command::Devices { long } => {
|
||||
if long {
|
||||
connexion.devices_long().unwrap();
|
||||
} else {
|
||||
for device in connexion.devices().unwrap() {
|
||||
println!("{}\t{}", device.identifier, device.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/adb_tcp_connexion.rs
Normal file
128
src/adb_tcp_connexion.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{Ipv4Addr, SocketAddrV4, TcpStream},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, AdbRequestStatus},
|
||||
AdbCommandProvider, Device, DeviceState, Result, RustADBError,
|
||||
};
|
||||
|
||||
/// Represents an ADB-over-TCP connexion.
|
||||
pub struct AdbTcpConnexion {
|
||||
socket_addr: SocketAddrV4,
|
||||
port: u16,
|
||||
address: Ipv4Addr,
|
||||
}
|
||||
|
||||
impl AdbTcpConnexion {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self.socket_addr = SocketAddrV4::new(self.address, self.port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn address<S: ToString>(mut self, address: S) -> Result<Self> {
|
||||
self.address = Ipv4Addr::from_str(&address.to_string())?;
|
||||
self.socket_addr = SocketAddrV4::new(self.address, self.port);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn proxy_connexion(&self, adb_command: AdbCommand) -> Result<Vec<u8>> {
|
||||
let mut tcp_stream = TcpStream::connect(self.socket_addr)?;
|
||||
|
||||
let adb_command_string = adb_command.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(String::from_utf8(request_status.to_vec())?.as_str())? {
|
||||
AdbRequestStatus::Okay => {
|
||||
// Read 4 bytes length
|
||||
// Read exact length
|
||||
|
||||
let mut body: Vec<u8> = Vec::new();
|
||||
tcp_stream.read_to_end(&mut body)?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
AdbRequestStatus::Fail => Err(RustADBError::ADBRequestFailed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AdbTcpConnexion {
|
||||
fn default() -> Self {
|
||||
let default_port: u16 = 5037;
|
||||
let default_address = Ipv4Addr::new(127, 0, 0, 1);
|
||||
|
||||
Self {
|
||||
socket_addr: SocketAddrV4::new(default_address, default_port),
|
||||
address: default_address,
|
||||
port: default_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AdbCommandProvider for AdbTcpConnexion {
|
||||
fn version(&self) -> Result<()> {
|
||||
let version = self.proxy_connexion(AdbCommand::Version)?;
|
||||
|
||||
println!("Version: {:?}", version.to_ascii_uppercase());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn devices(&self) -> Result<Vec<Device>> {
|
||||
let devices = self.proxy_connexion(AdbCommand::Devices)?;
|
||||
|
||||
let mut vec_devices: Vec<Device> = vec![];
|
||||
for device in devices.split(|x| x.eq(&b'\n')) {
|
||||
if device.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut iter = device.split(|x| x.eq(&b'\t'));
|
||||
let identifier = iter.next().unwrap();
|
||||
let state = iter.next().unwrap();
|
||||
|
||||
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(),
|
||||
)?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(vec_devices)
|
||||
}
|
||||
|
||||
fn devices_long(&self) -> Result<Vec<Device>> {
|
||||
let devices_long = self.proxy_connexion(AdbCommand::DevicesLong)?;
|
||||
|
||||
// Split at '\n' (lines())
|
||||
// Split at '\t'
|
||||
// Identifier = [0]
|
||||
// Device state = [1]
|
||||
|
||||
println!(
|
||||
"Devices long: {:?}",
|
||||
std::str::from_utf8(&devices_long.to_ascii_lowercase())
|
||||
);
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
19
src/error.rs
Normal file
19
src/error.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, RustADBError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RustADBError {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("ADB request failed")]
|
||||
ADBRequestFailed,
|
||||
#[error("Unknown result type {0}")]
|
||||
UnknownResultType(String),
|
||||
#[error("Unknown device state {0}")]
|
||||
UnknownDeviceState(String),
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] std::string::FromUtf8Error),
|
||||
#[error(transparent)]
|
||||
AddrParseError(#[from] std::net::AddrParseError),
|
||||
}
|
||||
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod adb_tcp_connexion;
|
||||
mod error;
|
||||
mod models;
|
||||
mod traits;
|
||||
pub use adb_tcp_connexion::AdbTcpConnexion;
|
||||
pub use error::{Result, RustADBError};
|
||||
pub use models::{Device, DeviceState};
|
||||
pub use traits::AdbCommandProvider;
|
||||
15
src/models/adb_command.rs
Normal file
15
src/models/adb_command.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub enum AdbCommand {
|
||||
Version,
|
||||
Devices,
|
||||
DevicesLong,
|
||||
}
|
||||
|
||||
impl ToString for AdbCommand {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
AdbCommand::Version => "host:version".into(),
|
||||
AdbCommand::Devices => "host:devices".into(),
|
||||
AdbCommand::DevicesLong => "host:devices-l".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/models/adb_request_status.rs
Normal file
21
src/models/adb_request_status.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::RustADBError;
|
||||
|
||||
pub enum AdbRequestStatus {
|
||||
Okay,
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl FromStr for AdbRequestStatus {
|
||||
type Err = RustADBError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let lowercased = s.to_ascii_lowercase();
|
||||
match lowercased.as_str() {
|
||||
"okay" => Ok(Self::Okay),
|
||||
"fail" => Ok(Self::Fail),
|
||||
_ => Err(RustADBError::UnknownResultType(lowercased)),
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/models/device.rs
Normal file
6
src/models/device.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use crate::DeviceState;
|
||||
|
||||
pub struct Device {
|
||||
pub identifier: String,
|
||||
pub state: DeviceState,
|
||||
}
|
||||
33
src/models/device_state.rs
Normal file
33
src/models/device_state.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use crate::RustADBError;
|
||||
|
||||
pub enum DeviceState {
|
||||
Offline,
|
||||
Device,
|
||||
NoDevice,
|
||||
}
|
||||
|
||||
impl Display for DeviceState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DeviceState::Offline => write!(f, "offline"),
|
||||
DeviceState::Device => write!(f, "device"),
|
||||
DeviceState::NoDevice => write!(f, "no device"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DeviceState {
|
||||
type Err = RustADBError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let lowercased = s.to_ascii_lowercase();
|
||||
match lowercased.as_str() {
|
||||
"offline" => Ok(Self::Offline),
|
||||
"device" => Ok(Self::Device),
|
||||
"no device" => Ok(Self::NoDevice),
|
||||
_ => Err(RustADBError::UnknownDeviceState(lowercased)),
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/models/mod.rs
Normal file
9
src/models/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod adb_command;
|
||||
mod adb_request_status;
|
||||
mod device;
|
||||
mod device_state;
|
||||
|
||||
pub use adb_command::AdbCommand;
|
||||
pub use adb_request_status::AdbRequestStatus;
|
||||
pub use device::Device;
|
||||
pub use device_state::DeviceState;
|
||||
11
src/traits/adb_command_provider.rs
Normal file
11
src/traits/adb_command_provider.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::{Device, Result};
|
||||
|
||||
/// Represents the property to serve ADB commands.
|
||||
pub trait AdbCommandProvider {
|
||||
/// Gets server's internal version number.
|
||||
fn version(&self) -> Result<()>;
|
||||
/// 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>>;
|
||||
}
|
||||
2
src/traits/mod.rs
Normal file
2
src/traits/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod adb_command_provider;
|
||||
pub use adb_command_provider::AdbCommandProvider;
|
||||
29
tests/tests.rs
Normal file
29
tests/tests.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use adb_client::AdbCommandProvider;
|
||||
use adb_client::AdbTcpConnexion;
|
||||
|
||||
#[test]
|
||||
fn test_version() {
|
||||
let adb = AdbTcpConnexion::new();
|
||||
adb.version().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_devices() {
|
||||
let adb = AdbTcpConnexion::new();
|
||||
adb.devices().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_devices_long() {
|
||||
let adb = AdbTcpConnexion::new();
|
||||
adb.devices_long().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_wrong_addr() {
|
||||
let _ = AdbTcpConnexion::new().address("127.0.0.300").unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user