Initial commit

This commit is contained in:
LIAUD Corentin
2022-01-05 11:04:15 +01:00
commit 7670c1f0a0
15 changed files with 369 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
target
Cargo.lock
.vscode

27
Cargo.toml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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(),
}
}
}

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

@@ -0,0 +1,6 @@
use crate::DeviceState;
pub struct Device {
pub identifier: String,
pub state: DeviceState,
}

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

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

@@ -0,0 +1,2 @@
mod adb_command_provider;
pub use adb_command_provider::AdbCommandProvider;

29
tests/tests.rs Normal file
View 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();
}
}