commit 7670c1f0a0f86b405f56e6247ff9b4e9bf632575 Author: LIAUD Corentin Date: Wed Jan 5 11:04:15 2022 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8e9e48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +.vscode diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..16aa275 --- /dev/null +++ b/Cargo.toml @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3019723 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +https://developer.android.com/studio/command-line/adb +https://github.com/cstyan/adbDocumentation diff --git a/bin/adb_client.rs b/bin/adb_client.rs new file mode 100644 index 0000000..5f14c59 --- /dev/null +++ b/bin/adb_client.rs @@ -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); + } + } + } + } +} diff --git a/src/adb_tcp_connexion.rs b/src/adb_tcp_connexion.rs new file mode 100644 index 0000000..79f772a --- /dev/null +++ b/src/adb_tcp_connexion.rs @@ -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(mut self, address: S) -> Result { + 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> { + 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 = 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> { + let devices = self.proxy_connexion(AdbCommand::Devices)?; + + let mut vec_devices: Vec = 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> { + 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![]) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..33fe195 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +pub type Result = std::result::Result; + +#[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), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0f77ae2 --- /dev/null +++ b/src/lib.rs @@ -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; diff --git a/src/models/adb_command.rs b/src/models/adb_command.rs new file mode 100644 index 0000000..01d9fa1 --- /dev/null +++ b/src/models/adb_command.rs @@ -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(), + } + } +} diff --git a/src/models/adb_request_status.rs b/src/models/adb_request_status.rs new file mode 100644 index 0000000..60c68e3 --- /dev/null +++ b/src/models/adb_request_status.rs @@ -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 { + let lowercased = s.to_ascii_lowercase(); + match lowercased.as_str() { + "okay" => Ok(Self::Okay), + "fail" => Ok(Self::Fail), + _ => Err(RustADBError::UnknownResultType(lowercased)), + } + } +} diff --git a/src/models/device.rs b/src/models/device.rs new file mode 100644 index 0000000..56b1120 --- /dev/null +++ b/src/models/device.rs @@ -0,0 +1,6 @@ +use crate::DeviceState; + +pub struct Device { + pub identifier: String, + pub state: DeviceState, +} diff --git a/src/models/device_state.rs b/src/models/device_state.rs new file mode 100644 index 0000000..10c6830 --- /dev/null +++ b/src/models/device_state.rs @@ -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 { + 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)), + } + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..f2e16ac --- /dev/null +++ b/src/models/mod.rs @@ -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; diff --git a/src/traits/adb_command_provider.rs b/src/traits/adb_command_provider.rs new file mode 100644 index 0000000..e096351 --- /dev/null +++ b/src/traits/adb_command_provider.rs @@ -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>; + /// Gets an extended list of connected devices including the device paths in the state. + fn devices_long(&self) -> Result>; +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs new file mode 100644 index 0000000..d293a3c --- /dev/null +++ b/src/traits/mod.rs @@ -0,0 +1,2 @@ +mod adb_command_provider; +pub use adb_command_provider::AdbCommandProvider; diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..d700d31 --- /dev/null +++ b/tests/tests.rs @@ -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(); + } +}