From 8c382f0bded34ef0ee1f70097053b367b09ef04e Mon Sep 17 00:00:00 2001 From: cocool97 <34218602+cocool97@users.noreply.github.com> Date: Sun, 1 Dec 2024 18:39:07 +0100 Subject: [PATCH] feat: add mdns devices discovery (#54) * feat: add mdns devices discovery --------- Co-authored-by: Jinke <164604729+JinkeJ@users.noreply.github.com> --- .github/workflows/rust-build.yml | 2 +- .github/workflows/rust-quality.yml | 2 +- .github/workflows/rust-release.yml | 2 +- Cargo.toml | 2 +- adb_cli/src/commands/host.rs | 15 ++ adb_cli/src/commands/mod.rs | 2 +- adb_cli/src/main.rs | 72 +++++++- adb_cli/src/models/opts.rs | 4 + adb_client/Cargo.toml | 5 +- adb_client/src/error.rs | 6 + adb_client/src/lib.rs | 6 +- adb_client/src/mdns/mdns_device.rs | 19 +++ adb_client/src/mdns/mdns_discovery.rs | 73 ++++++++ adb_client/src/mdns/mod.rs | 5 + adb_client/src/models/adb_server_command.rs | 26 ++- adb_client/src/models/device_state.rs | 32 ++++ adb_client/src/models/mdns_services.rs | 64 +++++++ adb_client/src/models/mod.rs | 5 + adb_client/src/models/server_status.rs | 158 ++++++++++++++++++ adb_client/src/server/adb_server.rs | 12 +- adb_client/src/server/commands/mdns.rs | 62 +++++++ adb_client/src/server/commands/mod.rs | 3 + adb_client/src/server/commands/reconnect.rs | 10 ++ .../src/server/commands/server_status.rs | 15 ++ .../src/server_device/commands/forward.rs | 13 +- adb_client/src/server_device/commands/mod.rs | 3 + .../src/server_device/commands/reconnect.rs | 14 ++ .../src/server_device/commands/reverse.rs | 11 ++ .../src/server_device/commands/tcpip.rs | 14 ++ adb_client/src/server_device/commands/usb.rs | 14 ++ 30 files changed, 652 insertions(+), 19 deletions(-) create mode 100644 adb_client/src/mdns/mdns_device.rs create mode 100644 adb_client/src/mdns/mdns_discovery.rs create mode 100644 adb_client/src/mdns/mod.rs create mode 100644 adb_client/src/models/mdns_services.rs create mode 100644 adb_client/src/models/server_status.rs create mode 100644 adb_client/src/server/commands/mdns.rs create mode 100644 adb_client/src/server/commands/reconnect.rs create mode 100644 adb_client/src/server/commands/server_status.rs create mode 100644 adb_client/src/server_device/commands/reconnect.rs create mode 100644 adb_client/src/server_device/commands/tcpip.rs create mode 100644 adb_client/src/server_device/commands/usb.rs diff --git a/.github/workflows/rust-build.yml b/.github/workflows/rust-build.yml index 062b435..1dcb6d6 100644 --- a/.github/workflows/rust-build.yml +++ b/.github/workflows/rust-build.yml @@ -12,4 +12,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build project - run: cargo build --release \ No newline at end of file + run: cargo build --release --all-features \ No newline at end of file diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index f52b187..adaddb5 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -39,4 +39,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run tests - run: cargo test --verbose + run: cargo test --verbose --all-features diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index af396b6..90223a7 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -26,7 +26,7 @@ jobs: cargo install cargo-generate-rpm - name: "build-release" - run: cargo build --release + run: cargo build --all-features --release - name: "Build DEB package" run: cargo deb -p adb_cli diff --git a/Cargo.toml b/Cargo.toml index ae93963..fee449d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ homepage = "https://github.com/cocool97/adb_client" keywords = ["adb", "android", "tcp", "usb"] license = "MIT" repository = "https://github.com/cocool97/adb_client" -version = "2.0.5" +version = "2.0.6" # To build locally when working on a new release [patch.crates-io] diff --git a/adb_cli/src/commands/host.rs b/adb_cli/src/commands/host.rs index c72587f..21505bb 100644 --- a/adb_cli/src/commands/host.rs +++ b/adb_cli/src/commands/host.rs @@ -21,4 +21,19 @@ pub enum HostCommand { Connect { address: SocketAddrV4 }, /// Disconnect device over WI-FI Disconnect { address: SocketAddrV4 }, + /// MDNS services + Mdns { + #[clap(subcommand)] + subcommand: MdnsCommand, + }, + /// Display server status + ServerStatus, +} + +#[derive(Parser, Debug)] +pub enum MdnsCommand { + /// Check mdns status + Check, + /// List mdns services available + Services, } diff --git a/adb_cli/src/commands/mod.rs b/adb_cli/src/commands/mod.rs index e526e01..ec6b1d7 100644 --- a/adb_cli/src/commands/mod.rs +++ b/adb_cli/src/commands/mod.rs @@ -5,7 +5,7 @@ mod tcp; mod usb; pub use emu::EmuCommand; -pub use host::HostCommand; +pub use host::{HostCommand, MdnsCommand}; pub use local::LocalCommand; pub use tcp::{TcpCommand, TcpCommands}; pub use usb::{UsbCommand, UsbCommands}; diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 5b70677..9e19249 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -8,24 +8,40 @@ mod models; use adb_client::{ ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBTcpDevice, ADBUSBDevice, DeviceShort, + MDNSBackend, MDNSDiscoveryService, }; use anyhow::{anyhow, Result}; use clap::Parser; -use commands::{EmuCommand, HostCommand, LocalCommand, TcpCommands, UsbCommands}; +use commands::{EmuCommand, HostCommand, LocalCommand, MdnsCommand, TcpCommands, UsbCommands}; use models::{Command, Opts}; use std::fs::File; use std::io::Write; use std::path::Path; fn main() -> Result<()> { - let opt = Opts::parse(); + let opts = Opts::parse(); + + // RUST_LOG variable has more priority then "--debug" flag + if std::env::var("RUST_LOG").is_err() { + let level = match opts.debug { + true => "trace", + false => "info", + }; + + std::env::set_var("RUST_LOG", level); + } + + // Setting default log level as "info" if not set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info"); + } env_logger::init(); - match opt.command { + match opts.command { Command::Local(local) => { - let mut adb_server = ADBServer::new(opt.address); + let mut adb_server = ADBServer::new(opts.address); - let mut device = match opt.serial { + let mut device = match opts.serial { Some(serial) => adb_server.get_device_by_name(&serial)?, None => adb_server.get_device()?, }; @@ -104,7 +120,7 @@ fn main() -> Result<()> { } } Command::Host(host) => { - let mut adb_server = ADBServer::new(opt.address); + let mut adb_server = ADBServer::new(opts.address); match host { HostCommand::Version => { @@ -148,10 +164,35 @@ fn main() -> Result<()> { adb_server.disconnect_device(address)?; log::info!("Disconnected {address}"); } + HostCommand::Mdns { subcommand } => match subcommand { + MdnsCommand::Check => { + let check = adb_server.mdns_check()?; + let server_status = adb_server.server_status()?; + match server_status.mdns_backend { + MDNSBackend::Unknown => log::info!("unknown mdns backend..."), + MDNSBackend::Bonjour => match check { + true => log::info!("mdns daemon version [Bonjour]"), + false => log::info!("ERROR: mdns daemon unavailable"), + }, + MDNSBackend::OpenScreen => { + log::info!("mdns daemon version [Openscreen discovery 0.0.0]") + } + } + } + MdnsCommand::Services => { + log::info!("List of discovered mdns services"); + for service in adb_server.mdns_services()? { + log::info!("{}", service); + } + } + }, + HostCommand::ServerStatus => { + log::info!("{}", adb_server.server_status()?); + } } } Command::Emu(emu) => { - let mut emulator = match opt.serial { + let mut emulator = match opts.serial { Some(serial) => ADBEmulatorDevice::new(serial, None)?, None => return Err(anyhow!("Serial must be set to use emulators !")), }; @@ -289,6 +330,23 @@ fn main() -> Result<()> { } } } + Command::MdnsDiscovery => { + let mut service = MDNSDiscoveryService::new()?; + + let (tx, rx) = std::sync::mpsc::channel(); + service.start(tx)?; + + log::info!("Starting mdns discovery..."); + while let Ok(device) = rx.recv() { + log::info!( + "Found device {} with addresses {:?}", + device.fullname, + device.addresses + ) + } + + service.shutdown()?; + } } Ok(()) diff --git a/adb_cli/src/models/opts.rs b/adb_cli/src/models/opts.rs index 7817a36..1b28670 100644 --- a/adb_cli/src/models/opts.rs +++ b/adb_cli/src/models/opts.rs @@ -7,6 +7,8 @@ use crate::commands::{EmuCommand, HostCommand, LocalCommand, TcpCommand, UsbComm #[derive(Parser, Debug)] #[clap(about, version, author)] pub struct Opts { + #[clap(long = "debug")] + pub debug: bool, #[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. @@ -29,4 +31,6 @@ pub enum Command { Usb(UsbCommand), /// Device commands via TCP, no server needed Tcp(TcpCommand), + /// Discover devices over MDNS without using adb-server + MdnsDiscovery, } diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index 37c9010..16516e8 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -16,9 +16,12 @@ byteorder = { version = "1.5.0" } chrono = { version = "0.4.38" } homedir = { version = "0.3.4" } image = { version = "0.25.5" } -log = { version = "0.4.22", features = ["max_level_debug", "release_max_level_debug"]} +lazy_static = { version = "1.5.0" } +log = { version = "0.4.22" } +mdns-sd = { version = "0.12.0" } num-bigint = { version = "0.8.4", package = "num-bigint-dig" } num-traits = { version = "0.2.19" } +quick-protobuf = { version = "0.8.1" } rand = { version = "0.8.5" } rcgen = { version = "0.13.1" } regex = { version = "1.11.0", features = ["perf", "std", "unicode"] } diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index 0477325..c53cb34 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -108,6 +108,12 @@ pub enum RustADBError { /// Cannot upgrade connection from TCP to TLS #[error("upgrade error: {0}")] UpgradeError(String), + /// An error occurred while getting mdns devices + #[error(transparent)] + MDNSError(#[from] mdns_sd::Error), + /// An error occurred while sending data to channel + #[error(transparent)] + SendError(#[from] std::sync::mpsc::SendError), } impl From> for RustADBError { diff --git a/adb_client/src/lib.rs b/adb_client/src/lib.rs index 2190077..bb0997f 100644 --- a/adb_client/src/lib.rs +++ b/adb_client/src/lib.rs @@ -9,6 +9,7 @@ mod constants; mod device; mod emulator_device; mod error; +mod mdns; mod models; mod server; mod server_device; @@ -19,7 +20,10 @@ pub use adb_device_ext::ADBDeviceExt; pub use device::{ADBTcpDevice, ADBUSBDevice}; pub use emulator_device::ADBEmulatorDevice; pub use error::{Result, RustADBError}; -pub use models::{AdbStatResponse, AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType}; +pub use mdns::*; +pub use models::{ + AdbStatResponse, AdbVersion, DeviceLong, DeviceShort, DeviceState, MDNSBackend, RebootType, +}; pub use server::*; pub use server_device::ADBServerDevice; pub use transports::*; diff --git a/adb_client/src/mdns/mdns_device.rs b/adb_client/src/mdns/mdns_device.rs new file mode 100644 index 0000000..52fe348 --- /dev/null +++ b/adb_client/src/mdns/mdns_device.rs @@ -0,0 +1,19 @@ +use std::{collections::HashSet, net::IpAddr}; + +/// Represent a device found from mdns search +#[derive(Debug)] +pub struct MDNSDevice { + /// Full device address when resolved + pub fullname: String, + /// Device IP addresses + pub addresses: HashSet, +} + +impl From for MDNSDevice { + fn from(value: mdns_sd::ServiceInfo) -> Self { + Self { + fullname: value.get_fullname().to_string(), + addresses: value.get_addresses().to_owned(), + } + } +} diff --git a/adb_client/src/mdns/mdns_discovery.rs b/adb_client/src/mdns/mdns_discovery.rs new file mode 100644 index 0000000..376d12f --- /dev/null +++ b/adb_client/src/mdns/mdns_discovery.rs @@ -0,0 +1,73 @@ +use mdns_sd::{ServiceDaemon, ServiceEvent}; +use std::{sync::mpsc::Sender, thread::JoinHandle}; + +use crate::{MDNSDevice, Result, RustADBError}; + +const ADB_SERVICE_NAME: &str = "_adb-tls-connect._tcp.local."; + +/// Structure holding responsibility over mdns discovery +pub struct MDNSDiscoveryService { + daemon: ServiceDaemon, + thread_handle: Option>>, +} + +impl std::fmt::Debug for MDNSDiscoveryService { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MDNSDiscoveryService") + .field("daemon", &self.daemon.get_metrics()) + .field("handle", &self.thread_handle) + .finish() + } +} + +impl MDNSDiscoveryService { + /// Instantiate a new discovery service to find devices over mdns + pub fn new() -> Result { + Ok(MDNSDiscoveryService { + daemon: ServiceDaemon::new()?, + thread_handle: None, + }) + } + + /// Start discovery by spawning a new thread responsible of getting events. + pub fn start(&mut self, sender: Sender) -> Result<()> { + let receiver = self.daemon.browse(ADB_SERVICE_NAME)?; + + let handle: JoinHandle> = std::thread::spawn(move || loop { + while let Ok(event) = receiver.recv() { + match event { + ServiceEvent::SearchStarted(_) + | ServiceEvent::ServiceRemoved(_, _) + | ServiceEvent::ServiceFound(_, _) + | ServiceEvent::SearchStopped(_) => { + // Ignoring these events. We are only interesting in found devices + continue; + } + ServiceEvent::ServiceResolved(service_info) => { + if let Err(e) = sender.send(MDNSDevice::from(service_info)) { + return Err(e.into()); + } + } + } + } + }); + + self.thread_handle = Some(handle); + + Ok(()) + } + + /// Shutdown discovery engines. + pub fn shutdown(&mut self) -> Result<()> { + match self.daemon.shutdown() { + Ok(_) => Ok(()), + Err(e) => match e { + mdns_sd::Error::Again => { + self.daemon.shutdown()?; + Ok(()) + } + e => Err(RustADBError::MDNSError(e)), + }, + } + } +} diff --git a/adb_client/src/mdns/mod.rs b/adb_client/src/mdns/mod.rs new file mode 100644 index 0000000..eee0660 --- /dev/null +++ b/adb_client/src/mdns/mod.rs @@ -0,0 +1,5 @@ +mod mdns_device; +mod mdns_discovery; + +pub use mdns_device::MDNSDevice; +pub use mdns_discovery::MDNSDiscoveryService; diff --git a/adb_client/src/models/adb_server_command.rs b/adb_client/src/models/adb_server_command.rs index c66d089..1782726 100644 --- a/adb_client/src/models/adb_server_command.rs +++ b/adb_client/src/models/adb_server_command.rs @@ -16,6 +16,10 @@ pub(crate) enum AdbServerCommand { Pair(SocketAddrV4, String), TransportAny, TransportSerial(String), + MDNSCheck, + MDNSServices, + ServerStatus, + ReconnectOffline, Install(u64), // Local commands ShellCommand(String), @@ -23,8 +27,13 @@ pub(crate) enum AdbServerCommand { FrameBuffer, Sync, Reboot(RebootType), - Forward(String, String, String), + Forward(String, String), + ForwardRemoveAll, Reverse(String, String), + ReverseRemoveAll, + Reconnect, + TcpIp(u16), + Usb, } impl Display for AdbServerCommand { @@ -56,12 +65,23 @@ impl Display for AdbServerCommand { write!(f, "host:pair:{code}:{addr}") } AdbServerCommand::FrameBuffer => write!(f, "framebuffer:"), - AdbServerCommand::Forward(serial, remote, local) => { - write!(f, "host-serial:{serial}:forward:{local};{remote}") + AdbServerCommand::Forward(remote, local) => { + write!(f, "host:forward:{local};{remote}") } + AdbServerCommand::ForwardRemoveAll => write!(f, "host:killforward-all"), AdbServerCommand::Reverse(remote, local) => { write!(f, "reverse:forward:{remote};{local}") } + AdbServerCommand::ReverseRemoveAll => write!(f, "reverse:killforward-all"), + AdbServerCommand::MDNSCheck => write!(f, "host:mdns:check"), + AdbServerCommand::MDNSServices => write!(f, "host:mdns:services"), + AdbServerCommand::ServerStatus => write!(f, "host:server-status"), + AdbServerCommand::Reconnect => write!(f, "reconnect"), + AdbServerCommand::ReconnectOffline => write!(f, "host:reconnect-offline"), + AdbServerCommand::TcpIp(port) => { + write!(f, "tcpip:{port}") + } + AdbServerCommand::Usb => write!(f, "usb:"), AdbServerCommand::Install(size) => write!(f, "exec:cmd package 'install' -S {size}"), } } diff --git a/adb_client/src/models/device_state.rs b/adb_client/src/models/device_state.rs index 9b5c24e..ab174e6 100644 --- a/adb_client/src/models/device_state.rs +++ b/adb_client/src/models/device_state.rs @@ -15,6 +15,22 @@ pub enum DeviceState { Authorizing, /// The device is unauthorized. Unauthorized, + /// Haven't received a response from the device yet. + Connecting, + /// Insufficient permissions to communicate with the device. + NoPerm, + /// USB device detached from the adb server (known but not opened/claimed). + Detached, + /// Device running fastboot OS (fastboot) or userspace fastboot (fastbootd). + Bootloader, + /// What a device sees from its end of a Transport (adb host). + Host, + /// Device with bootloader loaded but no ROM OS loaded (adbd). + Recovery, + /// Device running Android OS Sideload mode (minadbd sideload mode). + Sideload, + /// Device running Android OS Rescue mode (minadbd rescue mode). + Rescue, } impl Display for DeviceState { @@ -25,6 +41,14 @@ impl Display for DeviceState { DeviceState::NoDevice => write!(f, "no device"), DeviceState::Authorizing => write!(f, "authorizing"), DeviceState::Unauthorized => write!(f, "unauthorized"), + DeviceState::Connecting => write!(f, "connecting"), + DeviceState::NoPerm => write!(f, "noperm"), + DeviceState::Detached => write!(f, "detached"), + DeviceState::Bootloader => write!(f, "bootloader"), + DeviceState::Host => write!(f, "host"), + DeviceState::Recovery => write!(f, "recovery"), + DeviceState::Sideload => write!(f, "sideload"), + DeviceState::Rescue => write!(f, "rescue"), } } } @@ -40,6 +64,14 @@ impl FromStr for DeviceState { "no device" => Ok(Self::NoDevice), "authorizing" => Ok(Self::Authorizing), "unauthorized" => Ok(Self::Unauthorized), + "connecting" => Ok(Self::Connecting), + "noperm" => Ok(Self::NoPerm), + "detached" => Ok(Self::Detached), + "bootloader" => Ok(Self::Bootloader), + "host" => Ok(Self::Host), + "recovery" => Ok(Self::Recovery), + "sideload" => Ok(Self::Sideload), + "rescue" => Ok(Self::Rescue), _ => Err(RustADBError::UnknownDeviceState(lowercased)), } } diff --git a/adb_client/src/models/mdns_services.rs b/adb_client/src/models/mdns_services.rs new file mode 100644 index 0000000..69926f5 --- /dev/null +++ b/adb_client/src/models/mdns_services.rs @@ -0,0 +1,64 @@ +use regex::bytes::Regex; +use std::net::SocketAddrV4; +use std::sync::LazyLock; +use std::{fmt::Display, str::FromStr}; + +use crate::RustADBError; + +static MDNS_SERVICES_REGEX: LazyLock = LazyLock::new(|| { + Regex::new("^(\\S+)\t(\\S+)\t([\\d\\.]+:\\d+)\n?$").expect("Cannot build mdns services regex") +}); + +/// Represents MDNS Services +#[derive(Debug, Clone)] +pub struct MDNSServices { + /// Service name + pub service_name: String, + /// Reg type + pub reg_type: String, + /// IP addr with port + pub socket_v4: SocketAddrV4, +} + +impl Display for MDNSServices { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}\t{}\t{}", + self.service_name, self.reg_type, self.socket_v4 + ) + } +} + +impl TryFrom<&[u8]> for MDNSServices { + type Error = RustADBError; + + fn try_from(value: &[u8]) -> Result { + let groups = MDNS_SERVICES_REGEX + .captures(value) + .ok_or(RustADBError::RegexParsingError)?; + Ok(MDNSServices { + service_name: String::from_utf8( + groups + .get(1) + .ok_or(RustADBError::RegexParsingError)? + .as_bytes() + .to_vec(), + )?, + reg_type: String::from_utf8( + groups + .get(2) + .ok_or(RustADBError::RegexParsingError)? + .as_bytes() + .to_vec(), + )?, + socket_v4: SocketAddrV4::from_str(&String::from_utf8( + groups + .get(3) + .ok_or(RustADBError::RegexParsingError)? + .as_bytes() + .to_vec(), + )?)?, + }) + } +} diff --git a/adb_client/src/models/mod.rs b/adb_client/src/models/mod.rs index 6195273..305ea87 100644 --- a/adb_client/src/models/mod.rs +++ b/adb_client/src/models/mod.rs @@ -7,7 +7,9 @@ mod device_long; mod device_short; mod device_state; mod host_features; +mod mdns_services; mod reboot_type; +mod server_status; mod sync_command; pub(crate) use adb_emulator_command::ADBEmulatorCommand; @@ -19,5 +21,8 @@ pub use device_long::DeviceLong; pub use device_short::DeviceShort; pub use device_state::DeviceState; pub use host_features::HostFeatures; +pub use mdns_services::MDNSServices; pub use reboot_type::RebootType; +pub use server_status::MDNSBackend; +pub use server_status::ServerStatus; pub use sync_command::SyncCommand; diff --git a/adb_client/src/models/server_status.rs b/adb_client/src/models/server_status.rs new file mode 100644 index 0000000..8beb48b --- /dev/null +++ b/adb_client/src/models/server_status.rs @@ -0,0 +1,158 @@ +use quick_protobuf::{BytesReader, MessageRead}; + +use std::fmt::Display; + +use crate::RustADBError; + +#[derive(Debug, PartialEq, Default, Eq, Clone, Copy)] +pub enum UsbBackend { + #[default] + Unknown = 0, + Native = 1, + LibUSB = 2, +} + +impl From for UsbBackend { + fn from(i: i32) -> Self { + match i { + 0 => UsbBackend::Unknown, + 1 => UsbBackend::Native, + 2 => UsbBackend::LibUSB, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for UsbBackend { + fn from(s: &'a str) -> Self { + match s { + "UNKNOWN_USB" => UsbBackend::Unknown, + "NATIVE" => UsbBackend::Native, + "LIBUSB" => UsbBackend::LibUSB, + _ => Self::default(), + } + } +} + +impl Display for UsbBackend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UsbBackend::Unknown => write!(f, "UNKNOWN_USB"), + UsbBackend::Native => write!(f, "NATIVE"), + UsbBackend::LibUSB => write!(f, "LIBUSB"), + } + } +} + +/// MDNS Backend Status +#[derive(Debug, Clone, PartialEq, Default)] +pub enum MDNSBackend { + #[default] + /// Unknown + Unknown = 0, + /// Bonjour + Bonjour = 1, + /// OpenScreen + OpenScreen = 2, +} + +impl From for MDNSBackend { + fn from(i: i32) -> Self { + match i { + 0 => MDNSBackend::Unknown, + 1 => MDNSBackend::Bonjour, + 2 => MDNSBackend::OpenScreen, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for MDNSBackend { + fn from(s: &'a str) -> Self { + match s { + "UNKNOWN_MDNS" => MDNSBackend::Unknown, + "BONJOUR" => MDNSBackend::Bonjour, + "OPENSCREEN" => MDNSBackend::OpenScreen, + _ => Self::default(), + } + } +} + +impl Display for MDNSBackend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MDNSBackend::Unknown => write!(f, "UNKNOWN_MDNS"), + MDNSBackend::Bonjour => write!(f, "BONJOUR"), + MDNSBackend::OpenScreen => write!(f, "OPENSCREEN"), + } + } +} + +/// Structure representing current server status +#[derive(Debug, Clone, Default, PartialEq)] +pub struct ServerStatus { + pub usb_backend: UsbBackend, + pub usb_backend_forced: bool, + pub mdns_backend: MDNSBackend, + pub mdns_backend_forced: bool, + pub version: String, + pub build: String, + pub executable_absolute_path: String, + pub log_absolute_path: String, + pub os: String, +} + +impl<'a> MessageRead<'a> for ServerStatus { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> quick_protobuf::Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.usb_backend = r.read_enum(bytes)?, + Ok(16) => msg.usb_backend_forced = r.read_bool(bytes)?, + Ok(24) => msg.mdns_backend = r.read_enum(bytes)?, + Ok(32) => msg.mdns_backend_forced = r.read_bool(bytes)?, + Ok(42) => msg.version = r.read_string(bytes)?.to_string(), + Ok(50) => msg.build = r.read_string(bytes)?.to_string(), + Ok(58) => msg.executable_absolute_path = r.read_string(bytes)?.to_string(), + Ok(66) => msg.log_absolute_path = r.read_string(bytes)?.to_string(), + Ok(74) => msg.os = r.read_string(bytes)?.to_string(), + Ok(t) => { + r.read_unknown(bytes, t)?; + } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl Display for ServerStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "usb_backend: {}", self.usb_backend)?; + if self.usb_backend_forced { + writeln!(f, "usb_backend_forced: {}", self.usb_backend_forced)?; + } + writeln!(f, "mdns_backend: {}", self.mdns_backend)?; + if self.mdns_backend_forced { + writeln!(f, "mdns_backend_forced: {}", self.mdns_backend_forced)?; + } + writeln!(f, "version: \"{}\"", self.version)?; + writeln!(f, "build: \"{}\"", self.build)?; + writeln!( + f, + "executable_absolute_path: \"{}\"", + self.executable_absolute_path + )?; + writeln!(f, "log_absolute_path: \"{}\"", self.log_absolute_path)?; + writeln!(f, "os: \"{}\"", self.os) + } +} + +impl TryFrom> for ServerStatus { + type Error = RustADBError; + + fn try_from(value: Vec) -> Result { + let mut reader = BytesReader::from_bytes(&value); + ServerStatus::from_reader(&mut reader, &value).map_err(|_| RustADBError::ConversionError) + } +} diff --git a/adb_client/src/server/adb_server.rs b/adb_client/src/server/adb_server.rs index ca2129b..353dc11 100644 --- a/adb_client/src/server/adb_server.rs +++ b/adb_client/src/server/adb_server.rs @@ -2,6 +2,7 @@ use crate::ADBTransport; use crate::Result; use crate::RustADBError; use crate::TCPServerTransport; +use std::collections::HashMap; use std::net::SocketAddrV4; use std::process::Command; @@ -12,6 +13,8 @@ pub struct ADBServer { pub(crate) transport: Option, /// Address to connect to pub(crate) socket_addr: Option, + /// adb-server start envs + pub(crate) envs: HashMap, } impl ADBServer { @@ -20,6 +23,7 @@ impl ADBServer { Self { transport: None, socket_addr: Some(address), + envs: HashMap::new(), } } @@ -49,7 +53,13 @@ impl ADBServer { if is_local_ip { // ADB Server is local, we start it if not already running - let child = Command::new("adb").arg("start-server").spawn(); + let mut command = Command::new("adb"); + command.arg("start-server"); + for (env_k, env_v) in self.envs.iter() { + command.env(env_k, env_v); + } + + let child = command.spawn(); match child { Ok(mut child) => { if let Err(e) = child.wait() { diff --git a/adb_client/src/server/commands/mdns.rs b/adb_client/src/server/commands/mdns.rs new file mode 100644 index 0000000..d8a1d87 --- /dev/null +++ b/adb_client/src/server/commands/mdns.rs @@ -0,0 +1,62 @@ +use std::io::BufRead; + +use crate::{ + models::{AdbServerCommand, MDNSBackend, MDNSServices}, + ADBServer, Result, +}; + +const OPENSCREEN_MDNS_BACKEND: &str = "ADB_MDNS_OPENSCREEN"; + +impl ADBServer { + /// Check if mdns discovery is available + pub fn mdns_check(&mut self) -> Result { + let response = self + .connect()? + .proxy_connection(AdbServerCommand::MDNSCheck, true)?; + + match String::from_utf8(response) { + Ok(s) if s.starts_with("mdns daemon version") => Ok(true), + Ok(_) => Ok(false), + Err(e) => Err(e.into()), + } + } + + /// List all discovered mdns services + pub fn mdns_services(&mut self) -> Result> { + let services = self + .connect()? + .proxy_connection(AdbServerCommand::MDNSServices, true)?; + + let mut vec_services: Vec = vec![]; + for service in services.lines() { + match service { + Ok(service) => { + vec_services.push(MDNSServices::try_from(service.as_bytes())?); + } + Err(e) => log::error!("{}", e), + } + } + + Ok(vec_services) + } + + /// Check if specified backend mdns service is used, otherwise restart adb server with envs + pub fn mdns_force_backend(&mut self, backend: MDNSBackend) -> Result<()> { + let server_status = self.server_status()?; + if server_status.mdns_backend != backend { + self.kill()?; + self.envs.insert( + OPENSCREEN_MDNS_BACKEND.to_string(), + (if backend == MDNSBackend::OpenScreen { + "1" + } else { + "0" + }) + .to_string(), + ); + self.connect()?; + } + + Ok(()) + } +} diff --git a/adb_client/src/server/commands/mod.rs b/adb_client/src/server/commands/mod.rs index 64310a3..3931b39 100644 --- a/adb_client/src/server/commands/mod.rs +++ b/adb_client/src/server/commands/mod.rs @@ -2,5 +2,8 @@ mod connect; mod devices; mod disconnect; mod kill; +mod mdns; mod pair; +mod reconnect; +mod server_status; mod version; diff --git a/adb_client/src/server/commands/reconnect.rs b/adb_client/src/server/commands/reconnect.rs new file mode 100644 index 0000000..e7b9338 --- /dev/null +++ b/adb_client/src/server/commands/reconnect.rs @@ -0,0 +1,10 @@ +use crate::{models::AdbServerCommand, ADBServer, Result}; + +impl ADBServer { + /// Reconnect the device + pub fn reconnect_offline(&mut self) -> Result<()> { + self.connect()? + .proxy_connection(AdbServerCommand::ReconnectOffline, false) + .map(|_| ()) + } +} diff --git a/adb_client/src/server/commands/server_status.rs b/adb_client/src/server/commands/server_status.rs new file mode 100644 index 0000000..570aade --- /dev/null +++ b/adb_client/src/server/commands/server_status.rs @@ -0,0 +1,15 @@ +use crate::{ + models::{AdbServerCommand, ServerStatus}, + ADBServer, Result, +}; + +impl ADBServer { + /// Check ADB server status + pub fn server_status(&mut self) -> Result { + let status = self + .connect()? + .proxy_connection(AdbServerCommand::ServerStatus, true)?; + + ServerStatus::try_from(status) + } +} diff --git a/adb_client/src/server_device/commands/forward.rs b/adb_client/src/server_device/commands/forward.rs index ab9e90c..80b538a 100644 --- a/adb_client/src/server_device/commands/forward.rs +++ b/adb_client/src/server_device/commands/forward.rs @@ -8,7 +8,18 @@ impl ADBServerDevice { .send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?; self.get_transport_mut() - .proxy_connection(AdbServerCommand::Forward(serial, remote, local), false) + .proxy_connection(AdbServerCommand::Forward(remote, local), false) + .map(|_| ()) + } + + /// Remove all previously applied forward rules + pub fn forward_remove_all(&mut self) -> Result<()> { + let serial = self.identifier.clone(); + self.connect()? + .send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?; + + self.get_transport_mut() + .proxy_connection(AdbServerCommand::ForwardRemoveAll, false) .map(|_| ()) } } diff --git a/adb_client/src/server_device/commands/mod.rs b/adb_client/src/server_device/commands/mod.rs index 3117163..40010b8 100644 --- a/adb_client/src/server_device/commands/mod.rs +++ b/adb_client/src/server_device/commands/mod.rs @@ -5,8 +5,11 @@ mod install; mod list; mod logcat; mod reboot; +mod reconnect; mod recv; mod reverse; mod send; mod stat; +mod tcpip; mod transport; +mod usb; diff --git a/adb_client/src/server_device/commands/reconnect.rs b/adb_client/src/server_device/commands/reconnect.rs new file mode 100644 index 0000000..4f65abe --- /dev/null +++ b/adb_client/src/server_device/commands/reconnect.rs @@ -0,0 +1,14 @@ +use crate::{models::AdbServerCommand, ADBServerDevice, Result}; + +impl ADBServerDevice { + /// Reconnect device + pub fn reconnect(&mut self) -> Result<()> { + let serial = self.identifier.clone(); + self.connect()? + .send_adb_request(AdbServerCommand::TransportSerial(serial))?; + + self.get_transport_mut() + .proxy_connection(AdbServerCommand::Reconnect, false) + .map(|_| ()) + } +} diff --git a/adb_client/src/server_device/commands/reverse.rs b/adb_client/src/server_device/commands/reverse.rs index 2062653..a7f3a56 100644 --- a/adb_client/src/server_device/commands/reverse.rs +++ b/adb_client/src/server_device/commands/reverse.rs @@ -11,4 +11,15 @@ impl ADBServerDevice { .proxy_connection(AdbServerCommand::Reverse(remote, local), false) .map(|_| ()) } + + /// Remove all reverse rules + pub fn reverse_remove_all(&mut self) -> Result<()> { + let serial = self.identifier.clone(); + self.connect()? + .send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?; + + self.get_transport_mut() + .proxy_connection(AdbServerCommand::ReverseRemoveAll, false) + .map(|_| ()) + } } diff --git a/adb_client/src/server_device/commands/tcpip.rs b/adb_client/src/server_device/commands/tcpip.rs new file mode 100644 index 0000000..8d06d31 --- /dev/null +++ b/adb_client/src/server_device/commands/tcpip.rs @@ -0,0 +1,14 @@ +use crate::{models::AdbServerCommand, ADBServerDevice, Result}; + +impl ADBServerDevice { + /// Set adb daemon to tcp/ip mode + pub fn tcpip(&mut self, port: u16) -> Result<()> { + let serial = self.identifier.clone(); + self.connect()? + .send_adb_request(AdbServerCommand::TransportSerial(serial))?; + + self.get_transport_mut() + .proxy_connection(AdbServerCommand::TcpIp(port), false) + .map(|_| ()) + } +} diff --git a/adb_client/src/server_device/commands/usb.rs b/adb_client/src/server_device/commands/usb.rs new file mode 100644 index 0000000..8d4fb07 --- /dev/null +++ b/adb_client/src/server_device/commands/usb.rs @@ -0,0 +1,14 @@ +use crate::{models::AdbServerCommand, ADBServerDevice, Result}; + +impl ADBServerDevice { + /// Set adb daemon to usb mode + pub fn usb(&mut self) -> Result<()> { + let serial = self.identifier.clone(); + self.connect()? + .send_adb_request(AdbServerCommand::TransportSerial(serial))?; + + self.get_transport_mut() + .proxy_connection(AdbServerCommand::Usb, false) + .map(|_| ()) + } +}