From 2a7c9bdc5775384f0746d284fa7e710d8cf81d75 Mon Sep 17 00:00:00 2001 From: Corentin LIAUD Date: Fri, 26 Dec 2025 12:06:16 +0100 Subject: [PATCH] feat(mdns): add feature flag + example --- Cargo.toml | 2 +- README.md | 6 ++++ adb_cli/Cargo.toml | 2 +- adb_cli/src/main.rs | 7 ++-- adb_cli/src/models/adb_cli_error.rs | 2 +- adb_client/Cargo.toml | 18 ++++++++-- adb_client/README.md | 23 +++++++++--- adb_client/src/error.rs | 6 ++-- adb_client/src/lib.rs | 13 +++++-- adb_client/src/mdns/mdns_device.rs | 52 +++++++++++++++++++++++++-- adb_client/src/mdns/mdns_discovery.rs | 11 +++--- examples/mdns/Cargo.toml | 19 ++++++++++ examples/mdns/main.rs | 19 ++++++++++ 13 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 examples/mdns/Cargo.toml create mode 100644 examples/mdns/main.rs diff --git a/Cargo.toml b/Cargo.toml index 2c44a8f..81c4092 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["adb_cli", "adb_client", "pyadb_client"] +members = ["adb_cli", "adb_client", "pyadb_client", "examples/mdns"] resolver = "2" [workspace.package] diff --git a/README.md b/README.md index 6fd1369..1a4f860 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ Rust library implementing both ADB protocols (server and end-devices) and provid Improved documentation available [here](./adb_client/README.md). +## examples + +Some examples showing of to use this library are available in the `examples` directory: + +- `examples/mdns`: mDNS device discovery + ## adb_cli Rust binary providing an improved version of Google's official `adb` CLI, by using `adb_client` library. diff --git a/adb_cli/Cargo.toml b/adb_cli/Cargo.toml index 6ac817b..8b76e00 100644 --- a/adb_cli/Cargo.toml +++ b/adb_cli/Cargo.toml @@ -11,7 +11,7 @@ rust-version.workspace = true version.workspace = true [dependencies] -adb_client = { version = "^2.1.18" } +adb_client = { version = "^2.1.19", features = ["mdns"] } clap = { version = "4.5.53", features = ["derive"] } env_logger = { version = "0.11.8" } log = { version = "0.4.29" } diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index 4b2b9d1..43bd14c 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -9,7 +9,7 @@ mod utils; use adb_client::{ ADBDeviceExt, ADBListItemType, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice, - MDNSDiscoveryService, + mdns::MDNSDiscoveryService, }; #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -184,9 +184,10 @@ fn inner_main() -> ADBCliResult<()> { log::info!("Starting mdns discovery..."); while let Ok(device) = rx.recv() { log::info!( - "Found device {} with addresses {:?}", + "Found device {} with ipv4 addresses {:?} and ipv6 addresses {:?}", device.fullname, - device.addresses + device.ipv4_addresses(), + device.ipv6_addresses() ); } diff --git a/adb_cli/src/models/adb_cli_error.rs b/adb_cli/src/models/adb_cli_error.rs index 6653cd6..bdc33c1 100644 --- a/adb_cli/src/models/adb_cli_error.rs +++ b/adb_cli/src/models/adb_cli_error.rs @@ -67,7 +67,7 @@ impl From for ADBCliError { | RustADBError::PoisonError | RustADBError::UpgradeError(_) | RustADBError::MDNSError(_) - | RustADBError::SendError(_) + | RustADBError::SendError | RustADBError::UnknownFileMode(_) | RustADBError::UnknownTransport(_) => Self::MayNeedAnIssue(value), // List of [`RustADBError`] that may occur in standard contexts and therefore do not require for issues diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index 17516c6..6dc3f1b 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -10,6 +10,14 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = [] +mdns = ["dep:mdns-sd"] + [dependencies] base64 = { version = "0.22.1" } bincode = { version = "2.0.1", features = ["serde"] } @@ -17,9 +25,6 @@ byteorder = { version = "1.5.0" } chrono = { version = "0.4.42", default-features = false, features = ["std"] } image = { version = "0.25.9", default-features = false, features = ["png"] } log = { version = "0.4.29" } -mdns-sd = { version = "0.17.1", default-features = false, features = [ - "logging", -] } num-bigint = { version = "0.8.6", package = "num-bigint-dig" } num-traits = { version = "0.2.19" } quick-protobuf = { version = "0.8.1" } @@ -35,6 +40,13 @@ serde_repr = { version = "0.1.20" } sha1 = { version = "0.10.6", features = ["oid"] } thiserror = { version = "2.0.17" } +######### +# `mdns` feature-specific dependencies +mdns-sd = { version = "0.17.1", default-features = false, features = [ + "logging", +], optional = true } +######### + [dev-dependencies] anyhow = { version = "1.0.100" } criterion = { version = "0.7.0" } # Used for benchmarks diff --git a/adb_client/README.md b/adb_client/README.md index 81f10e9..94116ac 100644 --- a/adb_client/README.md +++ b/adb_client/README.md @@ -16,6 +16,19 @@ Add `adb_client` crate as a dependency by simply adding it to your `Cargo.toml`: adb_client = "*" ``` +## Crate features + +| Feature | Description | Default? | +| :-----: | :---------------------------------------------: | :------: | +| `mdns` | Enables mDNS device discovery on local network. | No | + +To deactivate some default features you can use the `default-features = false` option in your `Cargo.toml` file and manually specify the features you want to activate: + +```toml +[dependencies] +adb_client = { version = "*", default-features = false, features = ["mdns"] } +``` + ## Benchmarks Benchmarks run on `v2.0.6`, on a **Samsung S10 SM-G973F** device and an **Intel i7-1265U** CPU laptop @@ -24,11 +37,11 @@ Benchmarks run on `v2.0.6`, on a **Samsung S10 SM-G973F** device and an **Intel `ADBServerDevice` performs all operations by using adb server as a bridge. -|File size|Sample size|`ADBServerDevice`|`adb`|Difference| -|:-------:|:---------:|:----------:|:---:|:-----:| -|10 MB|100|350,79 ms|356,30 ms|
-1,57 %
| -|500 MB|50|15,60 s|15,64 s|
-0,25 %
| -|1 GB|20|31,09 s|31,12 s|
-0,10 %
| +| File size | Sample size | `ADBServerDevice` | `adb` | Difference | +| :-------: | :---------: | :---------------: | :-------: | :------------------------------------: | +| 10 MB | 100 | 350,79 ms | 356,30 ms |
-1,57 %
| +| 500 MB | 50 | 15,60 s | 15,64 s |
-0,25 %
| +| 1 GB | 20 | 31,09 s | 31,12 s |
-0,10 %
| ## Examples diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index 83e2f9a..1cb6835 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -115,11 +115,13 @@ pub enum RustADBError { #[error("upgrade error: {0}")] UpgradeError(String), /// An error occurred while getting mdns devices + #[cfg(feature = "mdns")] + #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] #[error(transparent)] MDNSError(#[from] mdns_sd::Error), /// An error occurred while sending data to channel - #[error(transparent)] - SendError(#[from] std::sync::mpsc::SendError), + #[error("error sending data to channel")] + SendError, /// An unknown transport has been provided #[error("unknown transport: {0}")] UnknownTransport(String), diff --git a/adb_client/src/lib.rs b/adb_client/src/lib.rs index 3f8a74d..c289844 100644 --- a/adb_client/src/lib.rs +++ b/adb_client/src/lib.rs @@ -3,13 +3,23 @@ #![forbid(missing_debug_implementations)] #![forbid(missing_docs)] #![doc = include_str!("../README.md")] +// Feature `doc_cfg` is currently only available on nightly builds. +// It is activated when cfg `docsrs` is enabled. +// Documentation can be build locally using: +// `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features` +#![cfg_attr(docsrs, feature(doc_cfg))] mod adb_device_ext; mod constants; mod device; mod emulator_device; mod error; -mod mdns; + +/// MDNS-related definitions +#[cfg(feature = "mdns")] +#[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] +pub mod mdns; + mod models; mod server; mod server_device; @@ -20,7 +30,6 @@ pub use adb_device_ext::ADBDeviceExt; pub use device::{ADBTcpDevice, ADBUSBDevice, is_adb_device, search_adb_devices}; pub use emulator_device::ADBEmulatorDevice; pub use error::{Result, RustADBError}; -pub use mdns::*; pub use models::{ADBListItem, ADBListItemType, AdbStatResponse, RebootType}; pub use server::*; pub use server_device::ADBServerDevice; diff --git a/adb_client/src/mdns/mdns_device.rs b/adb_client/src/mdns/mdns_device.rs index 68046d2..85549b2 100644 --- a/adb_client/src/mdns/mdns_device.rs +++ b/adb_client/src/mdns/mdns_device.rs @@ -1,4 +1,8 @@ -use std::{collections::HashSet, net::IpAddr}; +use std::{ + collections::HashSet, + fmt::Display, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, +}; use mdns_sd::{ResolvedService, ScopedIp}; @@ -8,7 +12,41 @@ pub struct MDNSDevice { /// Full device address when resolved pub fullname: String, /// Device IP addresses - pub addresses: HashSet, + addresses: HashSet, +} + +impl MDNSDevice { + /// Return all adresses linked to this device + #[must_use] + pub fn addresses(&self) -> HashSet { + self.addresses.clone() + } + + /// Return all IPv4 addresses linked to this device + #[must_use] + pub fn ipv4_addresses(&self) -> HashSet { + self.addresses + .iter() + .filter_map(|addr| match addr { + IpAddr::V4(addr) => Some(addr), + IpAddr::V6(_) => None, + }) + .copied() + .collect() + } + + /// Return all IPv6 addresses linked to this device + #[must_use] + pub fn ipv6_addresses(&self) -> HashSet { + self.addresses + .iter() + .filter_map(|addr| match addr { + IpAddr::V4(_) => None, + IpAddr::V6(addr) => Some(addr), + }) + .copied() + .collect() + } } impl From> for MDNSDevice { @@ -19,3 +57,13 @@ impl From> for MDNSDevice { } } } + +impl Display for MDNSDevice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Device fullname: {}", self.fullname)?; + writeln!(f, "IPv4 Addresses: {:?}", self.ipv4_addresses())?; + write!(f, "IPv6 Addresses: {:?}", self.ipv6_addresses())?; + + Ok(()) + } +} diff --git a/adb_client/src/mdns/mdns_discovery.rs b/adb_client/src/mdns/mdns_discovery.rs index d2ce76f..bc8b538 100644 --- a/adb_client/src/mdns/mdns_discovery.rs +++ b/adb_client/src/mdns/mdns_discovery.rs @@ -1,7 +1,8 @@ use mdns_sd::{ServiceDaemon, ServiceEvent}; use std::{sync::mpsc::Sender, thread::JoinHandle}; -use crate::{MDNSDevice, Result, RustADBError}; +use super::MDNSDevice; +use crate::{Result, RustADBError}; const ADB_SERVICE_NAME: &str = "_adb-tls-connect._tcp.local."; @@ -29,7 +30,7 @@ impl MDNSDiscoveryService { }) } - /// Start discovery by spawning a new thread responsible of getting events. + /// Start discovery by spawning a new background thread responsible of getting events. pub fn start(&mut self, sender: Sender) -> Result<()> { let receiver = self.daemon.browse(ADB_SERVICE_NAME)?; @@ -44,9 +45,9 @@ impl MDNSDiscoveryService { // Ignoring these events. We are only interesting in found devices } ServiceEvent::ServiceResolved(service_info) => { - if let Err(e) = sender.send(MDNSDevice::from(service_info)) { - return Err(e.into()); - } + sender + .send(MDNSDevice::from(service_info)) + .map_err(|_| RustADBError::SendError)?; } e => { log::warn!("received unknown event type {e:?}"); diff --git a/examples/mdns/Cargo.toml b/examples/mdns/Cargo.toml new file mode 100644 index 0000000..84e6719 --- /dev/null +++ b/examples/mdns/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mdns" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true +rust-version.workspace = true + +[[bin]] +name = "mdns" +path = "main.rs" + +[dependencies] +adb_client = { path = "../../adb_client", default-features = false, features = [ + "mdns", +] } diff --git a/examples/mdns/main.rs b/examples/mdns/main.rs new file mode 100644 index 0000000..b642bce --- /dev/null +++ b/examples/mdns/main.rs @@ -0,0 +1,19 @@ +use std::sync::mpsc; + +use adb_client::mdns::{MDNSDevice, MDNSDiscoveryService}; + +fn main() -> Result<(), Box> { + println!("Starting mDNS device discovery..."); + + // Create a channel to receive discovered devices information + let (sender, receiver) = mpsc::channel::(); + + // Create and start the discovery service + let mut discovery = MDNSDiscoveryService::new()?; + discovery.start(sender)?; + + loop { + let device = receiver.recv()?; + println!("{device}"); + } +}