diff --git a/adb_cli/Cargo.toml b/adb_cli/Cargo.toml index 0d31a5d..6ac817b 100644 --- a/adb_cli/Cargo.toml +++ b/adb_cli/Cargo.toml @@ -11,11 +11,10 @@ rust-version.workspace = true version.workspace = true [dependencies] -adb_client = { version = "^2.1.17" } -anyhow = { version = "1.0.100" } -clap = { version = "4.5.51", features = ["derive"] } +adb_client = { version = "^2.1.18" } +clap = { version = "4.5.53", features = ["derive"] } env_logger = { version = "0.11.8" } -log = { version = "0.4.28" } +log = { version = "0.4.29" } [target.'cfg(unix)'.dependencies] termios = { version = "0.3.3" } diff --git a/adb_cli/README.md b/adb_cli/README.md index 6f9a6de..a77ef2f 100644 --- a/adb_cli/README.md +++ b/adb_cli/README.md @@ -1,4 +1,4 @@ -# adb_cli +# `adb_cli` [![MIT licensed](https://img.shields.io/crates/l/adb_cli.svg)](./LICENSE-MIT) ![Crates.io Total Downloads](https://img.shields.io/crates/d/adb_cli) diff --git a/adb_cli/src/adb_termios.rs b/adb_cli/src/adb_termios.rs index 2445c37..cfcff8d 100644 --- a/adb_cli/src/adb_termios.rs +++ b/adb_cli/src/adb_termios.rs @@ -4,7 +4,7 @@ use std::os::unix::prelude::{AsRawFd, RawFd}; use termios::{TCSANOW, Termios, VMIN, VTIME, tcsetattr}; -use crate::Result; +use crate::models::{ADBCliError, ADBCliResult}; pub struct ADBTermios { fd: RawFd, @@ -13,7 +13,7 @@ pub struct ADBTermios { } impl ADBTermios { - pub fn new(fd: &impl AsRawFd) -> Result { + pub fn new(fd: &impl AsRawFd) -> Result { let mut new_termios = Termios::from_fd(fd.as_raw_fd())?; let old_termios = new_termios; // Saves previous state new_termios.c_lflag = 0; @@ -27,7 +27,7 @@ impl ADBTermios { }) } - pub fn set_adb_termios(&mut self) -> Result<()> { + pub fn set_adb_termios(&mut self) -> ADBCliResult<()> { Ok(tcsetattr(self.fd, TCSANOW, &self.new_termios)?) } } diff --git a/adb_cli/src/handlers/emulator_commands.rs b/adb_cli/src/handlers/emulator_commands.rs index 5855cd7..ec271a9 100644 --- a/adb_cli/src/handlers/emulator_commands.rs +++ b/adb_cli/src/handlers/emulator_commands.rs @@ -1,8 +1,8 @@ use adb_client::ADBEmulatorDevice; -use crate::models::{EmuCommand, EmulatorCommand}; +use crate::models::{ADBCliResult, EmuCommand, EmulatorCommand}; -pub fn handle_emulator_commands(emulator_command: EmulatorCommand) -> anyhow::Result<()> { +pub fn handle_emulator_commands(emulator_command: EmulatorCommand) -> ADBCliResult<()> { let mut emulator = ADBEmulatorDevice::new(emulator_command.serial, None)?; match emulator_command.command { diff --git a/adb_cli/src/handlers/local_commands.rs b/adb_cli/src/handlers/local_commands.rs index 101310d..4e4a22a 100644 --- a/adb_cli/src/handlers/local_commands.rs +++ b/adb_cli/src/handlers/local_commands.rs @@ -1,14 +1,13 @@ use std::{fs::File, io::Write}; use adb_client::ADBServerDevice; -use anyhow::{Result, anyhow}; -use crate::models::LocalDeviceCommand; +use crate::models::{ADBCliResult, LocalDeviceCommand}; pub fn handle_local_commands( mut device: ADBServerDevice, local_device_commands: LocalDeviceCommand, -) -> Result<()> { +) -> ADBCliResult<()> { match local_device_commands { LocalDeviceCommand::HostFeatures => { let features = device @@ -16,7 +15,7 @@ pub fn handle_local_commands( .iter() .map(ToString::to_string) .reduce(|a, b| format!("{a},{b}")) - .ok_or(anyhow!("cannot list features"))?; + .unwrap_or_default(); log::info!("Available host features: {features}"); Ok(()) diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 32e6304..e703077 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -14,7 +14,6 @@ use adb_client::{ #[cfg(any(target_os = "linux", target_os = "macos"))] use adb_termios::ADBTermios; -use anyhow::Result; use clap::Parser; use handlers::{handle_emulator_commands, handle_host_commands, handle_local_commands}; use models::{DeviceCommands, LocalCommand, MainCommand, Opts}; @@ -24,87 +23,10 @@ use std::io::Write; use std::path::Path; use utils::setup_logger; -fn main() -> Result<()> { - // This depends on `clap` - let opts = Opts::parse(); +use crate::models::{ADBCliError, ADBCliResult}; - // SAFETY: - // We are assuming the entire process is single-threaded - // at this point. - // This seems true for the current version of `clap`, - // but there's no guarantee for future updates - unsafe { setup_logger(opts.debug) }; - - // Directly handling methods / commands that aren't linked to [`ADBDeviceExt`] trait. - // Other methods just have to create a concrete [`ADBDeviceExt`] instance, and return it. - // This instance will then be used to execute desired command. - let (mut device, commands) = match opts.command { - MainCommand::Host(server_command) => return Ok(handle_host_commands(server_command)?), - MainCommand::Emu(emulator_command) => return handle_emulator_commands(emulator_command), - MainCommand::Local(server_command) => { - // Must start server to communicate with device, but only if this is a local one. - let server_address_ip = server_command.address.ip(); - if server_address_ip.is_loopback() || server_address_ip.is_unspecified() { - ADBServer::start(&HashMap::default(), &None); - } - - let device = match server_command.serial { - Some(serial) => ADBServerDevice::new(serial, Some(server_command.address)), - None => ADBServerDevice::autodetect(Some(server_command.address)), - }; - - match server_command.command { - LocalCommand::DeviceCommands(device_commands) => (device.boxed(), device_commands), - LocalCommand::LocalDeviceCommand(local_device_command) => { - return handle_local_commands(device, local_device_command); - } - } - } - MainCommand::Usb(usb_command) => { - let device = match (usb_command.vendor_id, usb_command.product_id) { - (Some(vid), Some(pid)) => match usb_command.path_to_private_key { - Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?, - None => ADBUSBDevice::new(vid, pid)?, - }, - (None, None) => match usb_command.path_to_private_key { - Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?, - None => ADBUSBDevice::autodetect()?, - }, - _ => { - anyhow::bail!( - "please either supply values for both the --vendor-id and --product-id flags or none." - ); - } - }; - (device.boxed(), usb_command.commands) - } - MainCommand::Tcp(tcp_command) => { - let device = match tcp_command.path_to_private_key { - Some(pk) => ADBTcpDevice::new_with_custom_private_key(tcp_command.address, pk)?, - None => ADBTcpDevice::new(tcp_command.address)?, - }; - (device.boxed(), tcp_command.commands) - } - MainCommand::Mdns => { - 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 - ); - } - - return Ok(service.shutdown()?); - } - }; - - match commands { +fn run_command(mut device: Box, command: DeviceCommands) -> ADBCliResult<()> { + match command { DeviceCommands::Shell { commands } => { if commands.is_empty() { // Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state. @@ -166,3 +88,84 @@ fn main() -> Result<()> { Ok(()) } + +fn main() -> ADBCliResult<()> { + // This depends on `clap` + let opts = Opts::parse(); + + setup_logger(opts.debug); + + // Directly handling methods / commands that aren't linked to [`ADBDeviceExt`] trait. + // Other methods just have to create a concrete [`ADBDeviceExt`] instance, and return it. + // This instance will then be used to execute desired command. + let (device, commands) = match opts.command { + MainCommand::Host(server_command) => return Ok(handle_host_commands(server_command)?), + MainCommand::Emu(emulator_command) => return handle_emulator_commands(emulator_command), + MainCommand::Local(server_command) => { + // Must start server to communicate with device, but only if this is a local one. + let server_address_ip = server_command.address.ip(); + if server_address_ip.is_loopback() || server_address_ip.is_unspecified() { + ADBServer::start(&HashMap::default(), &None); + } + + let device = match server_command.serial { + Some(serial) => ADBServerDevice::new(serial, Some(server_command.address)), + None => ADBServerDevice::autodetect(Some(server_command.address)), + }; + + match server_command.command { + LocalCommand::DeviceCommands(device_commands) => (device.boxed(), device_commands), + LocalCommand::LocalDeviceCommand(local_device_command) => { + return handle_local_commands(device, local_device_command); + } + } + } + MainCommand::Usb(usb_command) => { + let device = match (usb_command.vendor_id, usb_command.product_id) { + (Some(vid), Some(pid)) => match usb_command.path_to_private_key { + Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?, + None => ADBUSBDevice::new(vid, pid)?, + }, + (None, None) => match usb_command.path_to_private_key { + Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?, + None => ADBUSBDevice::autodetect()?, + }, + _ => { + return Err(ADBCliError::Standard( + "cannot specify flags --vendor-id without --product-id or vice versa" + .into(), + )); + } + }; + (device.boxed(), usb_command.commands) + } + MainCommand::Tcp(tcp_command) => { + let device = match tcp_command.path_to_private_key { + Some(pk) => ADBTcpDevice::new_with_custom_private_key(tcp_command.address, pk)?, + None => ADBTcpDevice::new(tcp_command.address)?, + }; + (device.boxed(), tcp_command.commands) + } + MainCommand::Mdns => { + 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 + ); + } + + return Ok(service.shutdown()?); + } + }; + + run_command(device, commands)?; + + Ok(()) +} diff --git a/adb_cli/src/models/adb_cli_error.rs b/adb_cli/src/models/adb_cli_error.rs new file mode 100644 index 0000000..233b242 --- /dev/null +++ b/adb_cli/src/models/adb_cli_error.rs @@ -0,0 +1,84 @@ +use std::fmt::Debug; + +use adb_client::RustADBError; + +pub type ADBCliResult = Result; + +pub enum ADBCliError { + Standard(Box), + MayNeedAnIssue(Box), +} + +impl Debug for ADBCliError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ADBCliError::Standard(error) => write!(f, "{error}"), + ADBCliError::MayNeedAnIssue(error) => write!( + f, + r" + This error is abnormal and may need to fill an issue. + Please submit it to this project's repository here: https://github.com/cocool97/adb_client/issues. + Error source: + {error} + ", + ), + } + } +} + +impl From for ADBCliError { + fn from(value: std::io::Error) -> Self { + // We do not consider adb_cli related `std::io::error` as critical + Self::Standard(Box::new(value)) + } +} + +impl From for ADBCliError { + fn from(value: adb_client::RustADBError) -> Self { + let value = Box::new(value); + + match value.as_ref() { + // List of [`RustADBError`] that may need an issue as abnormal + RustADBError::RegexParsingError + | RustADBError::WrongResponseReceived(_, _) + | RustADBError::FramebufferImageError(_) + | RustADBError::FramebufferConversionError + | RustADBError::UnimplementedFramebufferImageVersion(_) + | RustADBError::IOError(_) + | RustADBError::ADBRequestFailed(_) + | RustADBError::UnknownDeviceState(_) + | RustADBError::Utf8StrError(_) + | RustADBError::Utf8StringError(_) + | RustADBError::RegexError(_) + | RustADBError::ParseIntError(_) + | RustADBError::ConversionError + | RustADBError::IntegerConversionError(_) + | RustADBError::HomeError + | RustADBError::NoHomeDirectory + | RustADBError::UsbError(_) + | RustADBError::InvalidIntegrity(_, _) + | RustADBError::Base64DecodeError(_) + | RustADBError::Base64EncodeError(_) + | RustADBError::RSAError(_) + | RustADBError::TryFromSliceError(_) + | RustADBError::RsaPkcs8Error(_) + | RustADBError::CertificateGenerationError(_) + | RustADBError::TLSError(_) + | RustADBError::PemCertError(_) + | RustADBError::PoisonError + | RustADBError::UpgradeError(_) + | RustADBError::MDNSError(_) + | RustADBError::SendError(_) + | RustADBError::UnknownTransport(_) => Self::MayNeedAnIssue(value), + // List of [`RustADBError`] that may occur in standard contexts and therefore do not require for issues + RustADBError::ADBDeviceNotPaired + | RustADBError::UnknownResponseType(_) + | RustADBError::DeviceNotFound(_) + | RustADBError::USBNoDescriptorFound + | RustADBError::ADBShellNotSupported + | RustADBError::USBDeviceNotFound(_, _) + | RustADBError::WrongFileExtension(_) + | RustADBError::AddrParseError(_) => Self::Standard(value), + } + } +} diff --git a/adb_cli/src/models/host.rs b/adb_cli/src/models/host.rs index 4161d18..67741d6 100644 --- a/adb_cli/src/models/host.rs +++ b/adb_cli/src/models/host.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddrV4; +use std::{net::SocketAddrV4, str::FromStr}; use adb_client::{RustADBError, WaitForDeviceTransport}; use clap::Parser; @@ -6,7 +6,7 @@ use clap::Parser; fn parse_wait_for_device_device_transport( value: &str, ) -> Result { - WaitForDeviceTransport::try_from(value) + WaitForDeviceTransport::from_str(value) } #[derive(Parser, Debug)] diff --git a/adb_cli/src/models/mod.rs b/adb_cli/src/models/mod.rs index ee38727..b4c8fed 100644 --- a/adb_cli/src/models/mod.rs +++ b/adb_cli/src/models/mod.rs @@ -1,3 +1,4 @@ +mod adb_cli_error; mod device; mod emu; mod host; @@ -7,6 +8,7 @@ mod reboot_type; mod tcp; mod usb; +pub use adb_cli_error::{ADBCliError, ADBCliResult}; pub use device::DeviceCommands; pub use emu::{EmuCommand, EmulatorCommand}; pub use host::{HostCommand, MdnsCommand}; diff --git a/adb_cli/src/utils.rs b/adb_cli/src/utils.rs index c471d5a..746954a 100644 --- a/adb_cli/src/utils.rs +++ b/adb_cli/src/utils.rs @@ -1,14 +1,9 @@ -/// # Safety -/// -/// This conditionally mutates the process' environment. -/// See [`std::env::set_var`] for more info. -pub unsafe fn setup_logger(debug: bool) { - // RUST_LOG variable has more priority then "--debug" flag - if std::env::var("RUST_LOG").is_err() { - let level = if debug { "trace" } else { "info" }; +use env_logger::{Builder, Env}; - unsafe { std::env::set_var("RUST_LOG", level) }; - } - - env_logger::init(); +/// Sets up appropriate logger level: +/// - if `RUST_LOG` environment variable is set, use its value +/// - else, use `debug` CLI option +pub fn setup_logger(debug: bool) { + Builder::from_env(Env::default().default_filter_or(if debug { "debug" } else { "info" })) + .init(); } diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index 6944a40..3adea54 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -15,21 +15,21 @@ base64 = { version = "0.22.1" } bincode = { version = "2.0.1", features = ["serde"] } byteorder = { version = "1.5.0" } chrono = { version = "0.4.42", default-features = false, features = ["std"] } -image = { version = "0.25.8", default-features = false } -log = { version = "0.4.28" } -mdns-sd = { version = "0.17.0", default-features = false, features = [ +image = { version = "0.25.9", default-features = false } +log = { version = "0.4.29" } +mdns-sd = { version = "0.17.1", default-features = false, features = [ "logging", ] } -num-bigint = { version = "0.8.5", package = "num-bigint-dig" } +num-bigint = { version = "0.8.6", package = "num-bigint-dig" } num-traits = { version = "0.2.19" } quick-protobuf = { version = "0.8.1" } rand = { version = "0.9.2" } rcgen = { version = "0.14.5" } regex = { version = "1.12.2", features = ["perf", "std", "unicode"] } -rsa = { version = "0.9.8" } +rsa = { version = "0.9.9" } rusb = { version = "0.9.4", features = ["vendored"] } rustls = { version = "0.23.35" } -rustls-pki-types = { version = "1.13.0" } +rustls-pki-types = { version = "1.13.1" } serde = { version = "1.0.228", features = ["derive"] } serde_repr = { version = "0.1.20" } sha1 = { version = "0.10.6", features = ["oid"] } diff --git a/adb_client/src/server/models/wait_for_device.rs b/adb_client/src/server/models/wait_for_device.rs index c62f635..4c9244a 100644 --- a/adb_client/src/server/models/wait_for_device.rs +++ b/adb_client/src/server/models/wait_for_device.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{fmt::Display, str::FromStr}; use crate::RustADBError; @@ -24,10 +24,10 @@ impl Display for WaitForDeviceTransport { } } -impl TryFrom<&str> for WaitForDeviceTransport { - type Error = RustADBError; +impl FromStr for WaitForDeviceTransport { + type Err = RustADBError; - fn try_from(value: &str) -> Result { + fn from_str(value: &str) -> Result { match value { "usb" => Ok(Self::Usb), "local" => Ok(Self::Local), diff --git a/adb_client/src/transports/usb_transport.rs b/adb_client/src/transports/usb_transport.rs index 5310486..fac40cd 100644 --- a/adb_client/src/transports/usb_transport.rs +++ b/adb_client/src/transports/usb_transport.rs @@ -47,6 +47,7 @@ impl USBTransport { /// Instantiate a new [`USBTransport`] from a [`rusb::Device`]. /// /// Devices can be enumerated using [`rusb::devices()`] and then filtered out to get desired device. + #[must_use] pub fn new_from_device(rusb_device: rusb::Device) -> Self { Self { device: rusb_device, diff --git a/pyadb_client/Cargo.toml b/pyadb_client/Cargo.toml index 6ee30d0..a7a0505 100644 --- a/pyadb_client/Cargo.toml +++ b/pyadb_client/Cargo.toml @@ -22,6 +22,6 @@ name = "stub_gen" [dependencies] adb_client = { path = "../adb_client" } anyhow = { version = "1.0.100" } -pyo3 = { version = "0.27.1", features = ["abi3-py310", "anyhow"] } -pyo3-stub-gen = { version = "0.17.0" } -pyo3-stub-gen-derive = { version = "0.17.0" } +pyo3 = { version = "0.27.2", features = ["abi3-py310", "anyhow"] } +pyo3-stub-gen = { version = "0.17.2" } +pyo3-stub-gen-derive = { version = "0.17.2" } diff --git a/pyadb_client/src/adb_server_device.rs b/pyadb_client/src/adb_server_device.rs index e6fd207..345736f 100644 --- a/pyadb_client/src/adb_server_device.rs +++ b/pyadb_client/src/adb_server_device.rs @@ -12,6 +12,7 @@ pub struct PyADBServerDevice(pub ADBServerDevice); #[gen_stub_pymethods] #[pymethods] impl PyADBServerDevice { + #[must_use] #[getter] /// Device identifier pub fn identifier(&self) -> Option { diff --git a/pyadb_client/src/models/devices.rs b/pyadb_client/src/models/devices.rs index 3ace3ef..597f297 100644 --- a/pyadb_client/src/models/devices.rs +++ b/pyadb_client/src/models/devices.rs @@ -12,12 +12,14 @@ pub struct PyDeviceShort(DeviceShort); #[gen_stub_pymethods] #[pymethods] impl PyDeviceShort { + #[must_use] #[getter] /// Device identifier pub fn identifier(&self) -> String { self.0.identifier.clone() } + #[must_use] #[getter] /// Device state pub fn state(&self) -> String {