From 2705fa2dc9a618260970ccb744383467d7c847c6 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Wed, 15 Oct 2025 22:47:25 +0200 Subject: [PATCH 1/2] avahi: Introduce feature flag In preparation to contribute other implementations on Linux, such as one based on `systemd-resolved`, add a feature to this crate to enable `avahi`. To maximise backwards compatibility, the newly introduced feature flag is enabled by default. Dependents that deliberately disabled default features will see breakage, but can easily recover by enabling the new feature. --- zeroconf/Cargo.toml | 6 +++++- zeroconf/src/lib.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/zeroconf/Cargo.toml b/zeroconf/Cargo.toml index ab3065d..4c39543 100644 --- a/zeroconf/Cargo.toml +++ b/zeroconf/Cargo.toml @@ -18,6 +18,10 @@ categories = [ ] documentation = "https://docs.rs/zeroconf" +[features] +default = ["avahi"] +avahi = ["dep:avahi-sys"] + [dependencies] serde = { version = "1.0.188", features = ["derive"], optional = true } derive-getters = "0.3.0" @@ -34,7 +38,7 @@ serde_json = "1.0.107" clap = { version = "4.4.4", features = ["derive"] } [target.'cfg(unix)'.dependencies] -avahi-sys = "0.10.1" +avahi-sys = { version = "0.10.1", optional = true } [target.'cfg(target_vendor = "apple")'.dependencies] bonjour-sys = "0.3.0" diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs index 6263f21..fda794c 100644 --- a/zeroconf/src/lib.rs +++ b/zeroconf/src/lib.rs @@ -192,7 +192,7 @@ extern crate serde; extern crate derive_builder; #[macro_use] extern crate zeroconf_macros; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "avahi"))] extern crate avahi_sys; #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] extern crate bonjour_sys; @@ -223,7 +223,7 @@ pub mod prelude; pub mod service; pub mod txt_record; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "avahi"))] pub mod avahi; #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] pub mod bonjour; @@ -234,21 +234,21 @@ pub use service::{ServiceRegisteredCallback, ServiceRegistration}; pub use service_type::*; /// Type alias for the platform-specific mDNS browser implementation -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "avahi"))] pub type MdnsBrowser = avahi::browser::AvahiMdnsBrowser; /// Type alias for the platform-specific mDNS browser implementation #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] pub type MdnsBrowser = bonjour::browser::BonjourMdnsBrowser; /// Type alias for the platform-specific mDNS service implementation -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "avahi"))] pub type MdnsService = avahi::service::AvahiMdnsService; /// Type alias for the platform-specific mDNS service implementation #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] pub type MdnsService = bonjour::service::BonjourMdnsService; /// Type alias for the platform-specific structure responsible for polling the mDNS event loop -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "avahi"))] pub type EventLoop = avahi::event_loop::AvahiEventLoop; /// Type alias for the platform-specific structure responsible for polling the mDNS event loop #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] @@ -256,7 +256,7 @@ pub type EventLoop = bonjour::event_loop::BonjourEventLoop; /// Type alias for the platform-specific structure responsible for storing and accessing TXT /// record data -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "avahi"))] pub type TxtRecord = avahi::txt_record::AvahiTxtRecord; /// Type alias for the platform-specific structure responsible for storing and accessing TXT /// record data From f1c21f618f847571d9bf74d6042006795eb563cd Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Wed, 15 Oct 2025 22:59:06 +0200 Subject: [PATCH 2/2] Associate Types `TxtRecord` and `EventLoop` The layout of the crate promises extensibility, but the traits depend on `crate::{TxtRecord,EventLoop}` which actually are particular implementations. This makes it impossible to e.g. provide additional implementations for the traits on Linux, which are not directly tied to the already existing Avahi implementation. To resolve this, use associated types. --- zeroconf/src/avahi/browser.rs | 3 +++ zeroconf/src/avahi/service.rs | 3 +++ zeroconf/src/bonjour/browser.rs | 3 +++ zeroconf/src/bonjour/service.rs | 3 +++ zeroconf/src/browser.rs | 25 +++++++++++++++++-------- zeroconf/src/service.rs | 12 ++++++++---- zeroconf/src/txt_record.rs | 8 +++++++- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/zeroconf/src/avahi/browser.rs b/zeroconf/src/avahi/browser.rs index 6adab5e..50b12a0 100644 --- a/zeroconf/src/avahi/browser.rs +++ b/zeroconf/src/avahi/browser.rs @@ -37,6 +37,9 @@ pub struct AvahiMdnsBrowser { } impl TMdnsBrowser for AvahiMdnsBrowser { + type EventLoop = EventLoop; + type TxtRecord = TxtRecord; + fn new(service_type: ServiceType) -> Self { Self { client: None, diff --git a/zeroconf/src/avahi/service.rs b/zeroconf/src/avahi/service.rs index 5e068bc..4dd513c 100644 --- a/zeroconf/src/avahi/service.rs +++ b/zeroconf/src/avahi/service.rs @@ -33,6 +33,9 @@ pub struct AvahiMdnsService { } impl TMdnsService for AvahiMdnsService { + type EventLoop = EventLoop; + type TxtRecord = TxtRecord; + fn new(service_type: ServiceType, port: u16) -> Self { let kind = avahi_util::format_service_type(&service_type); diff --git a/zeroconf/src/bonjour/browser.rs b/zeroconf/src/bonjour/browser.rs index 20ea4b5..166fd5f 100644 --- a/zeroconf/src/bonjour/browser.rs +++ b/zeroconf/src/bonjour/browser.rs @@ -31,6 +31,9 @@ pub struct BonjourMdnsBrowser { } impl TMdnsBrowser for BonjourMdnsBrowser { + type EventLoop = EventLoop; + type TxtRecord = TxtRecord; + fn new(service_type: ServiceType) -> Self { Self { service: Arc::default(), diff --git a/zeroconf/src/bonjour/service.rs b/zeroconf/src/bonjour/service.rs index 014d6f7..e4c6591 100644 --- a/zeroconf/src/bonjour/service.rs +++ b/zeroconf/src/bonjour/service.rs @@ -29,6 +29,9 @@ pub struct BonjourMdnsService { } impl TMdnsService for BonjourMdnsService { + type EventLoop = EventLoop; + type TxtRecord = TxtRecord; + fn new(service_type: ServiceType, port: u16) -> Self { Self { service: Arc::default(), diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs index 5817e7d..9728a72 100644 --- a/zeroconf/src/browser.rs +++ b/zeroconf/src/browser.rs @@ -1,6 +1,7 @@ //! Trait definition for cross-platform browser -use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; +use crate::prelude::{TEventLoop, TTxtRecord}; +use crate::{NetworkInterface, Result, ServiceType}; use std::any::Any; use std::sync::Arc; @@ -8,13 +9,16 @@ use std::sync::Arc; /// /// [`MdnsBrowser`]: type.MdnsBrowser.html #[derive(Debug, Clone, PartialEq, Eq)] -pub enum BrowserEvent { - Add(ServiceDiscovery), +pub enum BrowserEvent { + Add(ServiceDiscovery), Remove(ServiceRemoval), } /// Interface for interacting with underlying mDNS implementation service browsing capabilities. pub trait TMdnsBrowser { + type EventLoop: TEventLoop; + type TxtRecord: TTxtRecord; + /// Creates a new `MdnsBrowser` that browses for the specified `kind` (e.g. `_http._tcp`) fn new(service_type: ServiceType) -> Self; @@ -31,7 +35,10 @@ pub trait TMdnsBrowser { /// resolved or removed a service. /// /// [`ServiceBrowserCallback`]: ../type.ServiceBrowserCallback.html - fn set_service_callback(&mut self, service_callback: Box); + fn set_service_callback( + &mut self, + service_callback: Box>, + ); /// Sets the optional user context to pass through to the callback. This is useful if you need /// to share state between pre and post-callback. The context type must implement `Any`. @@ -41,7 +48,7 @@ pub trait TMdnsBrowser { fn context(&self) -> Option<&dyn Any>; /// Starts the browser. Returns an `EventLoop` which can be called to keep the browser alive. - fn browse_services(&mut self) -> Result; + fn browse_services(&mut self) -> Result; } /// Callback invoked from [`MdnsBrowser`] once a service has been discovered and resolved or @@ -52,14 +59,16 @@ pub trait TMdnsBrowser { /// * `context` - The optional user context passed through /// /// [`MdnsBrowser`]: type.MdnsBrowser.html -pub type ServiceBrowserCallback = dyn Fn(Result, Option>); +pub type ServiceBrowserCallback = + dyn Fn(Result>, Option>); /// Represents a service that has been discovered by a [`MdnsBrowser`]. /// /// [`MdnsBrowser`]: type.MdnsBrowser.html #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Getters, Builder, BuilderDelegate, Clone, PartialEq, Eq)] -pub struct ServiceDiscovery { +// TODO: Restore derive(BuilderDelegate) +#[derive(Debug, Getters, Builder, Clone, PartialEq, Eq)] +pub struct ServiceDiscovery { name: String, service_type: ServiceType, domain: String, diff --git a/zeroconf/src/service.rs b/zeroconf/src/service.rs index e6e7360..e9478b6 100644 --- a/zeroconf/src/service.rs +++ b/zeroconf/src/service.rs @@ -1,12 +1,16 @@ //! Trait definition for cross-platform service. -use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; +use crate::prelude::{TEventLoop, TTxtRecord}; +use crate::{NetworkInterface, Result, ServiceType}; use std::any::Any; use std::sync::Arc; /// Interface for interacting with underlying mDNS service implementation registration /// capabilities. pub trait TMdnsService { + type EventLoop: TEventLoop; + type TxtRecord: TTxtRecord; + /// Creates a new `MdnsService` with the specified `ServiceType` (e.g. `_http._tcp`) and `port`. fn new(service_type: ServiceType, port: u16) -> Self; @@ -45,10 +49,10 @@ pub trait TMdnsService { fn host(&self) -> Option<&str>; /// Sets the optional `TxtRecord` to register this service with. - fn set_txt_record(&mut self, txt_record: TxtRecord); + fn set_txt_record(&mut self, txt_record: Self::TxtRecord); /// Returns the optional `TxtRecord` to register this service with. - fn txt_record(&self) -> Option<&TxtRecord>; + fn txt_record(&self) -> Option<&Self::TxtRecord>; /// Sets the [`ServiceRegisteredCallback`] that is invoked when the service has been /// registered. @@ -65,7 +69,7 @@ pub trait TMdnsService { /// Registers and start's the service. Returns an `EventLoop` which can be called to keep /// the service alive. - fn register(&mut self) -> Result; + fn register(&mut self) -> Result; } /// Callback invoked from [`MdnsService`] once it has successfully registered. diff --git a/zeroconf/src/txt_record.rs b/zeroconf/src/txt_record.rs index d2706ac..90e2277 100644 --- a/zeroconf/src/txt_record.rs +++ b/zeroconf/src/txt_record.rs @@ -149,7 +149,13 @@ impl Debug for TxtRecord { } } -#[cfg(test)] +#[cfg(all( + test, + any( + all(target_os = "linux", feature = "avahi"), + all(target_vendor = "apple", target_vendor = "pc"), + ) +))] mod tests { use super::*; use crate::TxtRecord;