diff --git a/Cargo.toml b/Cargo.toml index d5b69c6..7e9833a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/README.md b/README.md index ab8f654..cd5f4ec 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/adb_cli/Cargo.toml b/adb_cli/Cargo.toml new file mode 100644 index 0000000..786af51 --- /dev/null +++ b/adb_cli/Cargo.toml @@ -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"] } diff --git a/adb_cli/src/commands/emu.rs b/adb_cli/src/commands/emu.rs new file mode 100644 index 0000000..a1069cd --- /dev/null +++ b/adb_cli/src/commands/emu.rs @@ -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, +} diff --git a/adb_cli/src/commands/host.rs b/adb_cli/src/commands/host.rs new file mode 100644 index 0000000..91b07af --- /dev/null +++ b/adb_cli/src/commands/host.rs @@ -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 }, +} diff --git a/adb_cli/src/commands/local.rs b/adb_cli/src/commands/local.rs new file mode 100644 index 0000000..3cb7eca --- /dev/null +++ b/adb_cli/src/commands/local.rs @@ -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 }, + /// Reboot the device + Reboot { + #[clap(subcommand)] + sub_command: RebootTypeCommand, + }, + /// Get framebuffer of device + Framebuffer { path: String }, +} diff --git a/adb_cli/src/commands/mod.rs b/adb_cli/src/commands/mod.rs new file mode 100644 index 0000000..d59e7c7 --- /dev/null +++ b/adb_cli/src/commands/mod.rs @@ -0,0 +1,7 @@ +mod emu; +mod host; +mod local; + +pub use emu::EmuCommand; +pub use host::HostCommand; +pub use local::LocalCommand; diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs new file mode 100644 index 0000000..aab7639 --- /dev/null +++ b/adb_cli/src/main.rs @@ -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(()) +} diff --git a/adb_cli/src/models/mod.rs b/adb_cli/src/models/mod.rs new file mode 100644 index 0000000..ddd72a3 --- /dev/null +++ b/adb_cli/src/models/mod.rs @@ -0,0 +1,5 @@ +mod opts; +mod reboot_type; + +pub use opts::{Command, Opts}; +pub use reboot_type::RebootTypeCommand; diff --git a/adb_cli/src/models/opts.rs b/adb_cli/src/models/opts.rs new file mode 100644 index 0000000..fd7e1dd --- /dev/null +++ b/adb_cli/src/models/opts.rs @@ -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, + #[clap(subcommand)] + pub command: Command, +} + +#[derive(Parser, Debug)] +pub enum Command { + #[clap(flatten)] + Local(LocalCommand), + #[clap(flatten)] + Host(HostCommand), + #[clap(flatten)] + Emu(EmuCommand), +} diff --git a/adb_cli/src/models/reboot_type.rs b/adb_cli/src/models/reboot_type.rs new file mode 100644 index 0000000..06b4f6d --- /dev/null +++ b/adb_cli/src/models/reboot_type.rs @@ -0,0 +1,23 @@ +use adb_client::RebootType; +use clap::Parser; + +#[derive(Parser, Debug)] +pub enum RebootTypeCommand { + System, + Bootloader, + Recovery, + Sideload, + SideloadAutoReboot, +} + +impl From 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, + } + } +} diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml new file mode 100644 index 0000000..2cd3234 --- /dev/null +++ b/adb_client/Cargo.toml @@ -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" } diff --git a/src/adb_termios.rs b/adb_client/src/adb_termios.rs similarity index 100% rename from src/adb_termios.rs rename to adb_client/src/adb_termios.rs diff --git a/adb_client/src/emulator/mod.rs b/adb_client/src/emulator/mod.rs new file mode 100644 index 0000000..dd57671 --- /dev/null +++ b/adb_client/src/emulator/mod.rs @@ -0,0 +1,2 @@ +mod rotate; +mod sms; diff --git a/adb_client/src/emulator/rotate.rs b/adb_client/src/emulator/rotate.rs new file mode 100644 index 0000000..9b3ad82 --- /dev/null +++ b/adb_client/src/emulator/rotate.rs @@ -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(()) + } +} diff --git a/adb_client/src/emulator/sms.rs b/adb_client/src/emulator/sms.rs new file mode 100644 index 0000000..f325123 --- /dev/null +++ b/adb_client/src/emulator/sms.rs @@ -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(()) + } +} diff --git a/src/error.rs b/adb_client/src/error.rs similarity index 94% rename from src/error.rs rename to adb_client/src/error.rs index e12d293..a8f9614 100644 --- a/src/error.rs +++ b/adb_client/src/error.rs @@ -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), } diff --git a/src/lib.rs b/adb_client/src/lib.rs similarity index 86% rename from src/lib.rs rename to adb_client/src/lib.rs index ad18521..5672185 100644 --- a/src/lib.rs +++ b/adb_client/src/lib.rs @@ -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; diff --git a/adb_client/src/models/adb_emulator_command.rs b/adb_client/src/models/adb_emulator_command.rs new file mode 100644 index 0000000..2f4e76e --- /dev/null +++ b/adb_client/src/models/adb_emulator_command.rs @@ -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, + } + } +} diff --git a/src/models/adb_request_status.rs b/adb_client/src/models/adb_request_status.rs similarity index 100% rename from src/models/adb_request_status.rs rename to adb_client/src/models/adb_request_status.rs diff --git a/adb_client/src/models/adb_server_command.rs b/adb_client/src/models/adb_server_command.rs new file mode 100644 index 0000000..c2b0820 --- /dev/null +++ b/adb_client/src/models/adb_server_command.rs @@ -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:"), + } + } +} diff --git a/src/models/adb_version.rs b/adb_client/src/models/adb_version.rs similarity index 100% rename from src/models/adb_version.rs rename to adb_client/src/models/adb_version.rs diff --git a/src/models/device_long.rs b/adb_client/src/models/device_long.rs similarity index 100% rename from src/models/device_long.rs rename to adb_client/src/models/device_long.rs diff --git a/src/models/device_short.rs b/adb_client/src/models/device_short.rs similarity index 100% rename from src/models/device_short.rs rename to adb_client/src/models/device_short.rs diff --git a/src/models/device_state.rs b/adb_client/src/models/device_state.rs similarity index 100% rename from src/models/device_state.rs rename to adb_client/src/models/device_state.rs diff --git a/src/models/host_features.rs b/adb_client/src/models/host_features.rs similarity index 100% rename from src/models/host_features.rs rename to adb_client/src/models/host_features.rs diff --git a/src/models/mod.rs b/adb_client/src/models/mod.rs similarity index 73% rename from src/models/mod.rs rename to adb_client/src/models/mod.rs index c47787e..bfbea28 100644 --- a/src/models/mod.rs +++ b/adb_client/src/models/mod.rs @@ -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; diff --git a/src/models/reboot_type.rs b/adb_client/src/models/reboot_type.rs similarity index 100% rename from src/models/reboot_type.rs rename to adb_client/src/models/reboot_type.rs diff --git a/src/models/sync_command.rs b/adb_client/src/models/sync_command.rs similarity index 100% rename from src/models/sync_command.rs rename to adb_client/src/models/sync_command.rs diff --git a/adb_client/src/server/adb_emulator_device.rs b/adb_client/src/server/adb_emulator_device.rs new file mode 100644 index 0000000..65764bc --- /dev/null +++ b/adb_client/src/server/adb_emulator_device.rs @@ -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\\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) -> Result { + 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::()?; + + 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 for ADBEmulatorDevice { + type Error = RustADBError; + + fn try_from(value: ADBServerDevice) -> std::result::Result { + 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(); + } +} diff --git a/src/server/adb_server.rs b/adb_client/src/server/adb_server.rs similarity index 88% rename from src/server/adb_server.rs rename to adb_client/src/server/adb_server.rs index 3aaf11b..ca2129b 100644 --- a/src/server/adb_server.rs +++ b/adb_client/src/server/adb_server.rs @@ -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, + pub(crate) transport: Option, /// Address to connect to pub(crate) socket_addr: Option, } @@ -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 { diff --git a/adb_client/src/server/adb_server_device.rs b/adb_client/src/server/adb_server_device.rs new file mode 100644 index 0000000..8db18af --- /dev/null +++ b/adb_client/src/server/adb_server_device.rs @@ -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) -> 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(); + } +} diff --git a/src/server/device_commands/framebuffer.rs b/adb_client/src/server/device_commands/framebuffer.rs similarity index 88% rename from src/server/device_commands/framebuffer.rs rename to adb_client/src/server/device_commands/framebuffer.rs index ac496d1..5a8302f 100644 --- a/src/server/device_commands/framebuffer.rs +++ b/adb_client/src/server/device_commands/framebuffer.rs @@ -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, fn(&[u8]) -> Result>; @@ -109,14 +109,14 @@ impl ADBServerDevice { fn framebuffer_inner(&mut self) -> Result, Vec>> { 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::()?; match version { @@ -124,8 +124,8 @@ impl ADBServerDevice { 1 => { let mut buf = [0u8; std::mem::size_of::()]; - 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::()]; - 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::, Vec>::from_vec(h.width, h.height, data) .ok_or_else(|| RustADBError::FramebufferConversionError)?, diff --git a/src/server/device_commands/host_features.rs b/adb_client/src/server/device_commands/host_features.rs similarity index 65% rename from src/server/device_commands/host_features.rs rename to adb_client/src/server/device_commands/host_features.rs index 44a4efe..c993619 100644 --- a/src/server/device_commands/host_features.rs +++ b/adb_client/src/server/device_commands/host_features.rs @@ -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> { 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',')) diff --git a/src/server/device_commands/list.rs b/adb_client/src/server/device_commands/list.rs similarity index 63% rename from src/server/device_commands/list.rs rename to adb_client/src/server/device_commands/list.rs index aeea758..a09e9d5 100644 --- a/src/server/device_commands/list.rs +++ b/adb_client/src/server/device_commands/list.rs @@ -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>(&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(()); diff --git a/src/server/device_commands/mod.rs b/adb_client/src/server/device_commands/mod.rs similarity index 100% rename from src/server/device_commands/mod.rs rename to adb_client/src/server/device_commands/mod.rs diff --git a/src/server/device_commands/reboot.rs b/adb_client/src/server/device_commands/reboot.rs similarity index 54% rename from src/server/device_commands/reboot.rs rename to adb_client/src/server/device_commands/reboot.rs index 1fc4edf..a547f61 100644 --- a/src/server/device_commands/reboot.rs +++ b/adb_client/src/server/device_commands/reboot.rs @@ -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(|_| ()) } } diff --git a/src/server/device_commands/recv.rs b/adb_client/src/server/device_commands/recv.rs similarity index 64% rename from src/server/device_commands/recv.rs rename to adb_client/src/server/device_commands/recv.rs index 43ca828..73a0708 100644 --- a/src/server/device_commands/recv.rs +++ b/adb_client/src/server/device_commands/recv.rs @@ -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>(&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(), diff --git a/src/server/device_commands/send.rs b/adb_client/src/server/device_commands/send.rs similarity index 71% rename from src/server/device_commands/send.rs rename to adb_client/src/server/device_commands/send.rs index 9223c4e..d40e363 100644 --- a/src/server/device_commands/send.rs +++ b/adb_client/src/server/device_commands/send.rs @@ -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>(&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)?; } diff --git a/src/server/device_commands/shell.rs b/adb_client/src/server/device_commands/shell.rs similarity index 86% rename from src/server/device_commands/shell.rs rename to adb_client/src/server/device_commands/shell.rs index 2b12dd4..18ad59b 100644 --- a/src/server/device_commands/shell.rs +++ b/adb_client/src/server/device_commands/shell.rs @@ -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::(); diff --git a/src/server/device_commands/stat.rs b/adb_client/src/server/device_commands/stat.rs similarity index 80% rename from src/server/device_commands/stat.rs rename to adb_client/src/server/device_commands/stat.rs index e6fb8eb..12d8900 100644 --- a/src/server/device_commands/stat.rs +++ b/adb_client/src/server/device_commands/stat.rs @@ -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>(&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 "Stat" command - self.get_transport()?.send_sync_request(SyncCommand::Stat)?; + self.get_transport_mut() + .send_sync_request(SyncCommand::Stat)?; self.handle_stat_command(path) } diff --git a/src/server/device_commands/transport.rs b/adb_client/src/server/device_commands/transport.rs similarity index 70% rename from src/server/device_commands/transport.rs rename to adb_client/src/server/device_commands/transport.rs index 1702d25..2dee6c6 100644 --- a/src/server/device_commands/transport.rs +++ b/adb_client/src/server/device_commands/transport.rs @@ -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(|_| ()) } } diff --git a/src/server/mod.rs b/adb_client/src/server/mod.rs similarity index 68% rename from src/server/mod.rs rename to adb_client/src/server/mod.rs index 34632d7..131e820 100644 --- a/src/server/mod.rs +++ b/adb_client/src/server/mod.rs @@ -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; diff --git a/src/server/server_commands/connect.rs b/adb_client/src/server/server_commands/connect.rs similarity index 73% rename from src/server/server_commands/connect.rs rename to adb_client/src/server/server_commands/connect.rs index bfaca38..cb43a1a 100644 --- a/src/server/server_commands/connect.rs +++ b/adb_client/src/server/server_commands/connect.rs @@ -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(()), diff --git a/src/server/server_commands/devices.rs b/adb_client/src/server/server_commands/devices.rs similarity index 76% rename from src/server/server_commands/devices.rs rename to adb_client/src/server/server_commands/devices.rs index 6fc45f3..19f5d88 100644 --- a/src/server/server_commands/devices.rs +++ b/adb_client/src/server/server_commands/devices.rs @@ -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> { let devices = self .connect()? - .proxy_connection(AdbCommand::Devices, true)?; + .proxy_connection(AdbServerCommand::Devices, true)?; let mut vec_devices: Vec = vec![]; for device in devices.split(|x| x.eq(&b'\n')) { @@ -28,7 +28,7 @@ impl ADBServer { pub fn devices_long(&mut self) -> Result> { let devices_long = self .connect()? - .proxy_connection(AdbCommand::DevicesLong, true)?; + .proxy_connection(AdbServerCommand::DevicesLong, true)?; let mut vec_devices: Vec = 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 { + pub fn get_device_by_name(&mut self, name: &str) -> Result { 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 { + 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 { + let device = self.get_device_by_name(name)?; + + ADBEmulatorDevice::try_from(device) + } } diff --git a/src/server/server_commands/disconnect.rs b/adb_client/src/server/server_commands/disconnect.rs similarity index 73% rename from src/server/server_commands/disconnect.rs rename to adb_client/src/server/server_commands/disconnect.rs index c5948d5..715fb21 100644 --- a/src/server/server_commands/disconnect.rs +++ b/adb_client/src/server/server_commands/disconnect.rs @@ -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(()), diff --git a/src/server/server_commands/kill.rs b/adb_client/src/server/server_commands/kill.rs similarity index 58% rename from src/server/server_commands/kill.rs rename to adb_client/src/server/server_commands/kill.rs index d5ca0be..65b6d8e 100644 --- a/src/server/server_commands/kill.rs +++ b/adb_client/src/server/server_commands/kill.rs @@ -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(|_| ()) } } diff --git a/src/server/server_commands/mod.rs b/adb_client/src/server/server_commands/mod.rs similarity index 100% rename from src/server/server_commands/mod.rs rename to adb_client/src/server/server_commands/mod.rs diff --git a/src/server/server_commands/pair.rs b/adb_client/src/server/server_commands/pair.rs similarity index 80% rename from src/server/server_commands/pair.rs rename to adb_client/src/server/server_commands/pair.rs index 9a26492..5116300 100644 --- a/src/server/server_commands/pair.rs +++ b/adb_client/src/server/server_commands/pair.rs @@ -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(()), diff --git a/src/server/server_commands/version.rs b/adb_client/src/server/server_commands/version.rs similarity index 61% rename from src/server/server_commands/version.rs rename to adb_client/src/server/server_commands/version.rs index ff96896..a39f061 100644 --- a/src/server/server_commands/version.rs +++ b/adb_client/src/server/server_commands/version.rs @@ -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 { let version = self .connect()? - .proxy_connection(AdbCommand::Version, true)?; + .proxy_connection(AdbServerCommand::Version, true)?; AdbVersion::try_from(version) } diff --git a/adb_client/src/transports/mod.rs b/adb_client/src/transports/mod.rs new file mode 100644 index 0000000..c5e3b3b --- /dev/null +++ b/adb_client/src/transports/mod.rs @@ -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; diff --git a/adb_client/src/transports/tcp_emulator_transport.rs b/adb_client/src/transports/tcp_emulator_transport.rs new file mode 100644 index 0000000..424ed4f --- /dev/null +++ b/adb_client/src/transports/tcp_emulator_transport.rs @@ -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, +} + +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 { + 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 ' to authenticate + // Android Console: you can find your in + // '/home/xxx/.emulator_console_auth_token' + for _ in 0..=4 { + let mut line = String::new(); + reader.read_line(&mut line)?; + } + + self.authenticate()?; + } + + Ok(()) + } +} diff --git a/src/transports/tcp_server_transport.rs b/adb_client/src/transports/tcp_server_transport.rs similarity index 80% rename from src/transports/tcp_server_transport.rs rename to adb_client/src/transports/tcp_server_transport.rs index b6ef3aa..8a2aec3 100644 --- a/src/transports/tcp_server_transport.rs +++ b/adb_client/src/transports/tcp_server_transport.rs @@ -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, } -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> { 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 { 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(()) } } diff --git a/src/transports/transport_trait.rs b/adb_client/src/transports/transport_trait.rs similarity index 100% rename from src/transports/transport_trait.rs rename to adb_client/src/transports/transport_trait.rs diff --git a/src/utils.rs b/adb_client/src/utils.rs similarity index 100% rename from src/utils.rs rename to adb_client/src/utils.rs diff --git a/examples/adb_cli.rs b/examples/adb_cli.rs deleted file mode 100644 index db3328b..0000000 --- a/examples/adb_cli.rs +++ /dev/null @@ -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, - #[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 }, - /// 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 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(()) -} diff --git a/src/models/adb_command.rs b/src/models/adb_command.rs deleted file mode 100644 index 361a499..0000000 --- a/src/models/adb_command.rs +++ /dev/null @@ -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:"), - } - } -} diff --git a/src/server/adb_server_device.rs b/src/server/adb_server_device.rs deleted file mode 100644 index 6c74db6..0000000 --- a/src/server/adb_server_device.rs +++ /dev/null @@ -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, - /// Internal [TcpStream], lazily initialized - pub(crate) transport: Option, -} - -impl ADBServerDevice { - /// Instantiates a new [ADBServerDevice] - pub fn new(identifier: String, socket_addr: Option) -> 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(); - } - } -} diff --git a/src/transports/mod.rs b/src/transports/mod.rs deleted file mode 100644 index d95397d..0000000 --- a/src/transports/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod tcp_server_transport; -mod transport_trait; -pub use tcp_server_transport::TCPServerProtocol; -pub use transport_trait::ADBTransport; diff --git a/tests/tests.rs b/tests/tests.rs index 09a9010..3646187 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -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"); + } }