From 2e56e16114808e56dfc9cdccf3da707f33120bf9 Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Fri, 17 Jan 2025 19:10:19 +0100 Subject: [PATCH 1/6] Listen to to service removal events in ServiceBrowser (WIP: Avahi only) --- zeroconf/src/avahi/browser.rs | 16 +++++++++++----- zeroconf/src/browser.rs | 16 +++++++++++----- zeroconf/src/lib.rs | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/zeroconf/src/avahi/browser.rs b/zeroconf/src/avahi/browser.rs index f041178..952e39e 100644 --- a/zeroconf/src/avahi/browser.rs +++ b/zeroconf/src/avahi/browser.rs @@ -14,8 +14,8 @@ use crate::ffi::{c_str, AsRaw, FromRaw}; use crate::prelude::*; use crate::Result; use crate::{ - EventLoop, NetworkInterface, ServiceDiscoveredCallback, ServiceDiscovery, ServiceType, - TxtRecord, + BrowserEvent, EventLoop, NetworkInterface, ServiceDiscoveredCallback, ServiceDiscovery, + ServiceType, TxtRecord, }; use avahi_sys::{ AvahiAddress, AvahiBrowserEvent, AvahiClient, AvahiClientFlags, AvahiClientState, AvahiIfIndex, @@ -56,7 +56,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser { avahi_util::interface_from_index(self.context.interface_index) } - fn set_service_discovered_callback( + fn set_service_callback( &mut self, service_discovered_callback: Box, ) { @@ -132,7 +132,7 @@ impl AvahiBrowserContext { } } - fn invoke_callback(&self, result: Result) { + fn invoke_callback(&self, result: Result) { if let Some(f) = &self.service_discovered_callback { f(result, self.user_context.clone()); } else { @@ -205,6 +205,12 @@ unsafe extern "C" fn browse_callback( avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_FAILURE => { context.invoke_callback(Err("browser failure".into())) } + avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_REMOVE => { + let name = c_str::raw_to_str(name).to_string(); + let kind = c_str::raw_to_str(kind).to_string(); + let domain = c_str::raw_to_str(domain).to_string(); + context.invoke_callback(Ok(BrowserEvent::Remove { name, kind, domain })); + } _ => {} }; } @@ -324,7 +330,7 @@ unsafe fn handle_resolver_found( debug!("Service resolved: {:?}", result); - context.invoke_callback(Ok(result)); + context.invoke_callback(Ok(BrowserEvent::New(result))); Ok(()) } diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs index 4561a31..770e53b 100644 --- a/zeroconf/src/browser.rs +++ b/zeroconf/src/browser.rs @@ -4,6 +4,15 @@ use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; use std::any::Any; use std::sync::Arc; +pub enum BrowserEvent { + New(ServiceDiscovery), + Remove { + name: String, // The "abc" part in "abc._http._udp.local" + kind: String, // The "_http._udp" part in "abc._http._udp.local" + domain: String, // The "local" part in "abc._http._udp.local" + }, +} + /// Interface for interacting with underlying mDNS implementation service browsing capabilities. pub trait TMdnsBrowser { /// Creates a new `MdnsBrowser` that browses for the specified `kind` (e.g. `_http._tcp`) @@ -22,10 +31,7 @@ pub trait TMdnsBrowser { /// resolved a service. /// /// [`ServiceDiscoveredCallback`]: ../type.ServiceDiscoveredCallback.html - fn set_service_discovered_callback( - &mut self, - service_discovered_callback: Box, - ); + fn set_service_callback(&mut self, service_discovered_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`. @@ -45,7 +51,7 @@ pub trait TMdnsBrowser { /// * `context` - The optional user context passed through /// /// [`MdnsBrowser`]: type.MdnsBrowser.html -pub type ServiceDiscoveredCallback = dyn Fn(Result, Option>); +pub type ServiceDiscoveredCallback = dyn Fn(Result, Option>); /// Represents a service that has been discovered by a [`MdnsBrowser`]. /// diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs index ad88d21..06461dd 100644 --- a/zeroconf/src/lib.rs +++ b/zeroconf/src/lib.rs @@ -228,7 +228,7 @@ pub mod avahi; #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] pub mod bonjour; -pub use browser::{ServiceDiscoveredCallback, ServiceDiscovery}; +pub use browser::{BrowserEvent, ServiceDiscoveredCallback, ServiceDiscovery}; pub use interface::*; pub use service::{ServiceRegisteredCallback, ServiceRegistration}; pub use service_type::*; From ccd6a668e78bc75b83a38a40069491733459a36d Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Tue, 21 Jan 2025 08:35:57 +0100 Subject: [PATCH 2/6] Listen to service removal events (Bonjour) --- zeroconf/src/bonjour/browser.rs | 52 ++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/zeroconf/src/bonjour/browser.rs b/zeroconf/src/bonjour/browser.rs index d410b3f..3056f49 100644 --- a/zeroconf/src/bonjour/browser.rs +++ b/zeroconf/src/bonjour/browser.rs @@ -8,7 +8,7 @@ use super::{bonjour_util, constants}; use crate::ffi::{c_str, AsRaw, FromRaw}; use crate::prelude::*; use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; -use crate::{ServiceDiscoveredCallback, ServiceDiscovery}; +use crate::{ServiceDiscoveredCallback, ServiceDiscovery, BrowserEvent}; #[cfg(target_vendor = "pc")] use bonjour_sys::sockaddr_in; use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef}; @@ -48,7 +48,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser { bonjour_util::interface_from_index(self.interface_index) } - fn set_service_discovered_callback( + fn set_service_callback( &mut self, service_discovered_callback: Box, ) { @@ -98,7 +98,7 @@ struct BonjourBrowserContext { } impl BonjourBrowserContext { - fn invoke_callback(&self, result: Result) { + fn invoke_callback(&self, result: Result) { if let Some(f) = &self.service_discovered_callback { f(result, self.user_context.clone()); } else { @@ -120,7 +120,7 @@ impl fmt::Debug for BonjourBrowserContext { unsafe extern "system" fn browse_callback( _sd_ref: DNSServiceRef, - _flags: DNSServiceFlags, + flags: DNSServiceFlags, interface_index: u32, error: DNSServiceErrorType, name: *const c_char, @@ -129,23 +129,28 @@ unsafe extern "system" fn browse_callback( context: *mut c_void, ) { let ctx = BonjourBrowserContext::from_raw(context); - if let Err(e) = handle_browse(ctx, error, name, regtype, domain, interface_index) { - ctx.invoke_callback(Err(e)); + + if error != 0 { + ctx.invoke_callback(Err(format!("browse_callback() reported error (code: {})", error).into())); + return; + } + + if flags & bonjour_sys::kDNSServiceFlagsAdd != 0 { + if let Err(e) = handle_browse_add(ctx, name, regtype, domain, interface_index) { + ctx.invoke_callback(Err(e)); + } + } else { + handle_browse_remove(ctx, name, regtype, domain); } } -unsafe fn handle_browse( +unsafe fn handle_browse_add( ctx: &mut BonjourBrowserContext, - error: DNSServiceErrorType, name: *const c_char, regtype: *const c_char, domain: *const c_char, interface_index: u32, ) -> Result<()> { - if error != 0 { - return Err(format!("browse_callback() reported error (code: {})", error).into()); - } - ctx.resolved_name = Some(c_str::copy_raw(name)); ctx.resolved_kind = Some(c_str::copy_raw(regtype)); ctx.resolved_domain = Some(c_str::copy_raw(domain)); @@ -163,6 +168,27 @@ unsafe fn handle_browse( ) } +unsafe fn handle_browse_remove( + ctx: &mut BonjourBrowserContext, + name: *const c_char, + regtype: *const c_char, + domain: *const c_char, +) { + let name = c_str::raw_to_str(name); + let regtype = c_str::raw_to_str(regtype); + let domain = c_str::raw_to_str(domain); + + // Remove the "." suffix to be consistent with the Avahi implementation. + let regtype = regtype.strip_suffix(".").unwrap_or(domain); + let domain = domain.strip_suffix(".").unwrap_or(domain); + + ctx.invoke_callback(Ok(BrowserEvent::Remove { + name: name.to_string(), + kind: regtype.to_string(), + domain: domain.to_string(), + })); +} + unsafe extern "system" fn resolve_callback( _sd_ref: DNSServiceRef, _flags: DNSServiceFlags, @@ -313,7 +339,7 @@ unsafe fn handle_get_address_info( .build() .expect("could not build ServiceResolution"); - ctx.invoke_callback(Ok(result)); + ctx.invoke_callback(Ok(BrowserEvent::New(result))); Ok(()) } From ed090d0756fd948fc3571327296d963b41195ce6 Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Tue, 21 Jan 2025 09:40:13 +0100 Subject: [PATCH 3/6] Fix tests --- examples/browser/src/main.rs | 8 +++--- zeroconf/src/browser.rs | 1 + zeroconf/src/lib.rs | 10 ++++---- zeroconf/src/tests/service_test.rs | 39 +++++++++++++++++------------- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs index 0797c83..2ad3800 100644 --- a/examples/browser/src/main.rs +++ b/examples/browser/src/main.rs @@ -7,7 +7,7 @@ use std::any::Any; use std::sync::Arc; use std::time::Duration; use zeroconf::prelude::*; -use zeroconf::{MdnsBrowser, ServiceDiscovery, ServiceType}; +use zeroconf::{BrowserEvent, MdnsBrowser, ServiceType}; /// Example of a simple mDNS browser #[derive(Parser, Debug)] @@ -45,7 +45,7 @@ fn main() -> zeroconf::Result<()> { let mut browser = MdnsBrowser::new(service_type); - browser.set_service_discovered_callback(Box::new(on_service_discovered)); + browser.set_service_callback(Box::new(on_service_discovered)); let event_loop = browser.browse_services()?; @@ -56,11 +56,11 @@ fn main() -> zeroconf::Result<()> { } fn on_service_discovered( - result: zeroconf::Result, + result: zeroconf::Result, _context: Option>, ) { info!( - "Service discovered: {:?}", + "Service event: {:?}", result.expect("service discovery failed") ); diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs index 770e53b..5798f9a 100644 --- a/zeroconf/src/browser.rs +++ b/zeroconf/src/browser.rs @@ -4,6 +4,7 @@ use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; use std::any::Any; use std::sync::Arc; +#[derive(Debug, Clone, PartialEq, Eq)] pub enum BrowserEvent { New(ServiceDiscovery), Remove { diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs index 06461dd..7bf6529 100644 --- a/zeroconf/src/lib.rs +++ b/zeroconf/src/lib.rs @@ -116,7 +116,7 @@ //! use std::sync::Arc; //! use std::time::Duration; //! use zeroconf::prelude::*; -//! use zeroconf::{MdnsBrowser, ServiceDiscovery, ServiceType}; +//! use zeroconf::{BrowserEvent, MdnsBrowser, ServiceDiscovery, ServiceType}; //! //! /// Example of a simple mDNS browser //! #[derive(Parser, Debug)] @@ -154,7 +154,7 @@ //! //! let mut browser = MdnsBrowser::new(service_type); //! -//! browser.set_service_discovered_callback(Box::new(on_service_discovered)); +//! browser.set_service_callback(Box::new(on_service_event)); //! //! let event_loop = browser.browse_services()?; //! @@ -164,12 +164,12 @@ //! } //! } //! -//! fn on_service_discovered( -//! result: zeroconf::Result, +//! fn on_service_event( +//! result: zeroconf::Result, //! _context: Option>, //! ) { //! info!( -//! "Service discovered: {:?}", +//! "Service event: {:?}", //! result.expect("service discovery failed") //! ); //! diff --git a/zeroconf/src/tests/service_test.rs b/zeroconf/src/tests/service_test.rs index 916d0d9..4cc684d 100644 --- a/zeroconf/src/tests/service_test.rs +++ b/zeroconf/src/tests/service_test.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::{MdnsBrowser, MdnsService, ServiceType, TxtRecord}; +use crate::{BrowserEvent, MdnsBrowser, MdnsService, ServiceType, TxtRecord}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -46,22 +46,27 @@ fn service_register_is_browsable() { browser.set_context(Box::new(context.clone())); - browser.set_service_discovered_callback(Box::new(|service, context| { - let service = service.unwrap(); - - if service.name() == SERVICE_NAME { - let mut mtx = context - .as_ref() - .unwrap() - .downcast_ref::>>() - .unwrap() - .lock() - .unwrap(); - - mtx.txt.clone_from(service.txt()); - mtx.is_discovered = true; - - debug!("Service discovered"); + browser.set_service_callback(Box::new(|event, context| { + match event.unwrap() { + BrowserEvent::New(service) => { + if service.name() == SERVICE_NAME { + let mut mtx = context + .as_ref() + .unwrap() + .downcast_ref::>>() + .unwrap() + .lock() + .unwrap(); + + mtx.txt.clone_from(service.txt()); + mtx.is_discovered = true; + + debug!("Service discovered"); + } + } + BrowserEvent::Remove { name, kind, domain } => { + debug!("Service removed: {name}.{kind}.{domain}"); + } } })); From 43b81707f17801d009cd1d1c40f95f44d97b3420 Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Tue, 21 Jan 2025 10:01:53 +0100 Subject: [PATCH 4/6] Refactor for consistency and documentation --- examples/browser/src/main.rs | 7 ++--- zeroconf/src/avahi/browser.rs | 44 ++++++++++++++++++++---------- zeroconf/src/bonjour/browser.rs | 30 +++++++++++--------- zeroconf/src/browser.rs | 39 +++++++++++++++++--------- zeroconf/src/lib.rs | 4 +-- zeroconf/src/tests/service_test.rs | 43 +++++++++++++++-------------- 6 files changed, 99 insertions(+), 68 deletions(-) diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs index 2ad3800..a4e8772 100644 --- a/examples/browser/src/main.rs +++ b/examples/browser/src/main.rs @@ -45,7 +45,7 @@ fn main() -> zeroconf::Result<()> { let mut browser = MdnsBrowser::new(service_type); - browser.set_service_callback(Box::new(on_service_discovered)); + browser.set_service_callback(Box::new(on_service_event)); let event_loop = browser.browse_services()?; @@ -55,10 +55,7 @@ fn main() -> zeroconf::Result<()> { } } -fn on_service_discovered( - result: zeroconf::Result, - _context: Option>, -) { +fn on_service_event(result: zeroconf::Result, _context: Option>) { info!( "Service event: {:?}", result.expect("service discovery failed") diff --git a/zeroconf/src/avahi/browser.rs b/zeroconf/src/avahi/browser.rs index 952e39e..6adab5e 100644 --- a/zeroconf/src/avahi/browser.rs +++ b/zeroconf/src/avahi/browser.rs @@ -14,8 +14,8 @@ use crate::ffi::{c_str, AsRaw, FromRaw}; use crate::prelude::*; use crate::Result; use crate::{ - BrowserEvent, EventLoop, NetworkInterface, ServiceDiscoveredCallback, ServiceDiscovery, - ServiceType, TxtRecord, + BrowserEvent, EventLoop, NetworkInterface, ServiceBrowserCallback, ServiceDiscovery, + ServiceRemoval, ServiceType, TxtRecord, }; use avahi_sys::{ AvahiAddress, AvahiBrowserEvent, AvahiClient, AvahiClientFlags, AvahiClientState, AvahiIfIndex, @@ -56,11 +56,8 @@ impl TMdnsBrowser for AvahiMdnsBrowser { avahi_util::interface_from_index(self.context.interface_index) } - fn set_service_callback( - &mut self, - service_discovered_callback: Box, - ) { - self.context.service_discovered_callback = Some(service_discovered_callback); + fn set_service_callback(&mut self, service_callback: Box) { + self.context.service_callback = Some(service_callback); } fn set_context(&mut self, context: Box) { @@ -112,7 +109,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser { struct AvahiBrowserContext { client: Option>, resolvers: ServiceResolverSet, - service_discovered_callback: Option>, + service_callback: Option>, user_context: Option>, interface_index: AvahiIfIndex, kind: CString, @@ -124,7 +121,7 @@ impl AvahiBrowserContext { Self { client: None, resolvers: ServiceResolverSet::default(), - service_discovered_callback: None, + service_callback: None, user_context: None, interface_index, kind, @@ -133,7 +130,7 @@ impl AvahiBrowserContext { } fn invoke_callback(&self, result: Result) { - if let Some(f) = &self.service_discovered_callback { + if let Some(f) = &self.service_callback { f(result, self.user_context.clone()); } else { warn!("attempted to invoke browser callback but none was set"); @@ -206,10 +203,7 @@ unsafe extern "C" fn browse_callback( context.invoke_callback(Err("browser failure".into())) } avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_REMOVE => { - let name = c_str::raw_to_str(name).to_string(); - let kind = c_str::raw_to_str(kind).to_string(); - let domain = c_str::raw_to_str(domain).to_string(); - context.invoke_callback(Ok(BrowserEvent::Remove { name, kind, domain })); + handle_browser_remove(context, name, kind, domain); } _ => {} }; @@ -248,6 +242,26 @@ unsafe fn handle_browser_new( Ok(()) } +unsafe fn handle_browser_remove( + ctx: &mut AvahiBrowserContext, + name: *const c_char, + regtype: *const c_char, + domain: *const c_char, +) { + let name = c_str::raw_to_str(name); + let regtype = c_str::raw_to_str(regtype); + let domain = c_str::raw_to_str(domain); + + ctx.invoke_callback(Ok(BrowserEvent::Remove( + ServiceRemoval::builder() + .name(name.to_string()) + .kind(regtype.to_string()) + .domain(domain.to_string()) + .build() + .expect("could not build ServiceRemoval"), + ))); +} + unsafe extern "C" fn resolve_callback( resolver: *mut AvahiServiceResolver, _interface: AvahiIfIndex, @@ -330,7 +344,7 @@ unsafe fn handle_resolver_found( debug!("Service resolved: {:?}", result); - context.invoke_callback(Ok(BrowserEvent::New(result))); + context.invoke_callback(Ok(BrowserEvent::Add(result))); Ok(()) } diff --git a/zeroconf/src/bonjour/browser.rs b/zeroconf/src/bonjour/browser.rs index 3056f49..20ea4b5 100644 --- a/zeroconf/src/bonjour/browser.rs +++ b/zeroconf/src/bonjour/browser.rs @@ -7,8 +7,8 @@ use super::txt_record_ref::ManagedTXTRecordRef; use super::{bonjour_util, constants}; use crate::ffi::{c_str, AsRaw, FromRaw}; use crate::prelude::*; +use crate::{BrowserEvent, ServiceBrowserCallback, ServiceDiscovery, ServiceRemoval}; use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; -use crate::{ServiceDiscoveredCallback, ServiceDiscovery, BrowserEvent}; #[cfg(target_vendor = "pc")] use bonjour_sys::sockaddr_in; use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef}; @@ -48,10 +48,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser { bonjour_util::interface_from_index(self.interface_index) } - fn set_service_callback( - &mut self, - service_discovered_callback: Box, - ) { + fn set_service_callback(&mut self, service_discovered_callback: Box) { self.context.service_discovered_callback = Some(service_discovered_callback); } @@ -88,7 +85,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser { #[derive(Default, FromRaw, AsRaw)] struct BonjourBrowserContext { - service_discovered_callback: Option>, + service_discovered_callback: Option>, resolved_name: Option, resolved_kind: Option, resolved_domain: Option, @@ -131,7 +128,11 @@ unsafe extern "system" fn browse_callback( let ctx = BonjourBrowserContext::from_raw(context); if error != 0 { - ctx.invoke_callback(Err(format!("browse_callback() reported error (code: {})", error).into())); + ctx.invoke_callback(Err(format!( + "browse_callback() reported error (code: {})", + error + ) + .into())); return; } @@ -182,11 +183,14 @@ unsafe fn handle_browse_remove( let regtype = regtype.strip_suffix(".").unwrap_or(domain); let domain = domain.strip_suffix(".").unwrap_or(domain); - ctx.invoke_callback(Ok(BrowserEvent::Remove { - name: name.to_string(), - kind: regtype.to_string(), - domain: domain.to_string(), - })); + ctx.invoke_callback(Ok(BrowserEvent::Remove( + ServiceRemoval::builder() + .name(name.to_string()) + .kind(regtype.to_string()) + .domain(domain.to_string()) + .build() + .expect("could not build ServiceRemoval"), + ))); } unsafe extern "system" fn resolve_callback( @@ -339,7 +343,7 @@ unsafe fn handle_get_address_info( .build() .expect("could not build ServiceResolution"); - ctx.invoke_callback(Ok(BrowserEvent::New(result))); + ctx.invoke_callback(Ok(BrowserEvent::Add(result))); Ok(()) } diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs index 5798f9a..5817e7d 100644 --- a/zeroconf/src/browser.rs +++ b/zeroconf/src/browser.rs @@ -4,14 +4,13 @@ use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord}; use std::any::Any; use std::sync::Arc; +/// Event from [`MdnsBrowser`] received by the `ServiceBrowserCallback`. +/// +/// [`MdnsBrowser`]: type.MdnsBrowser.html #[derive(Debug, Clone, PartialEq, Eq)] pub enum BrowserEvent { - New(ServiceDiscovery), - Remove { - name: String, // The "abc" part in "abc._http._udp.local" - kind: String, // The "_http._udp" part in "abc._http._udp.local" - domain: String, // The "local" part in "abc._http._udp.local" - }, + Add(ServiceDiscovery), + Remove(ServiceRemoval), } /// Interface for interacting with underlying mDNS implementation service browsing capabilities. @@ -28,11 +27,11 @@ pub trait TMdnsBrowser { /// Returns the network interface on which to browse for services on. fn network_interface(&self) -> NetworkInterface; - /// Sets the [`ServiceDiscoveredCallback`] that is invoked when the browser has discovered and - /// resolved a service. + /// Sets the [`ServiceBrowserCallback`] that is invoked when the browser has discovered and + /// resolved or removed a service. /// - /// [`ServiceDiscoveredCallback`]: ../type.ServiceDiscoveredCallback.html - fn set_service_callback(&mut self, service_discovered_callback: Box); + /// [`ServiceBrowserCallback`]: ../type.ServiceBrowserCallback.html + 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`. @@ -45,14 +44,15 @@ pub trait TMdnsBrowser { fn browse_services(&mut self) -> Result; } -/// Callback invoked from [`MdnsBrowser`] once a service has been discovered and resolved. +/// Callback invoked from [`MdnsBrowser`] once a service has been discovered and resolved or +/// removed. /// /// # Arguments -/// * `discovered_service` - The service that was disovered +/// * `browser_event` - The event received from Zeroconf /// * `context` - The optional user context passed through /// /// [`MdnsBrowser`]: type.MdnsBrowser.html -pub type ServiceDiscoveredCallback = dyn Fn(Result, Option>); +pub type ServiceBrowserCallback = dyn Fn(Result, Option>); /// Represents a service that has been discovered by a [`MdnsBrowser`]. /// @@ -68,3 +68,16 @@ pub struct ServiceDiscovery { port: u16, txt: Option, } + +/// Represents a service that has been removed by a [`MdnsBrowser`]. +/// +/// [`MdnsBrowser`]: type.MdnsBrowser.html +#[derive(Debug, Getters, Builder, BuilderDelegate, Clone, PartialEq, Eq)] +pub struct ServiceRemoval { + /// The "abc" part in "abc._http._udp.local" + name: String, + /// The "_http._udp" part in "abc._http._udp.local" + kind: String, + /// The "local" part in "abc._http._udp.local" + domain: String, +} diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs index 7bf6529..6263f21 100644 --- a/zeroconf/src/lib.rs +++ b/zeroconf/src/lib.rs @@ -116,7 +116,7 @@ //! use std::sync::Arc; //! use std::time::Duration; //! use zeroconf::prelude::*; -//! use zeroconf::{BrowserEvent, MdnsBrowser, ServiceDiscovery, ServiceType}; +//! use zeroconf::{BrowserEvent, MdnsBrowser, ServiceDiscovery, ServiceRemoval, ServiceType}; //! //! /// Example of a simple mDNS browser //! #[derive(Parser, Debug)] @@ -228,7 +228,7 @@ pub mod avahi; #[cfg(any(target_vendor = "apple", target_vendor = "pc"))] pub mod bonjour; -pub use browser::{BrowserEvent, ServiceDiscoveredCallback, ServiceDiscovery}; +pub use browser::{BrowserEvent, ServiceBrowserCallback, ServiceDiscovery, ServiceRemoval}; pub use interface::*; pub use service::{ServiceRegisteredCallback, ServiceRegistration}; pub use service_type::*; diff --git a/zeroconf/src/tests/service_test.rs b/zeroconf/src/tests/service_test.rs index 4cc684d..055f044 100644 --- a/zeroconf/src/tests/service_test.rs +++ b/zeroconf/src/tests/service_test.rs @@ -46,28 +46,31 @@ fn service_register_is_browsable() { browser.set_context(Box::new(context.clone())); - browser.set_service_callback(Box::new(|event, context| { - match event.unwrap() { - BrowserEvent::New(service) => { - if service.name() == SERVICE_NAME { - let mut mtx = context - .as_ref() - .unwrap() - .downcast_ref::>>() - .unwrap() - .lock() - .unwrap(); - - mtx.txt.clone_from(service.txt()); - mtx.is_discovered = true; - - debug!("Service discovered"); - } - } - BrowserEvent::Remove { name, kind, domain } => { - debug!("Service removed: {name}.{kind}.{domain}"); + browser.set_service_callback(Box::new(|event, context| match event.unwrap() { + BrowserEvent::Add(service) => { + if service.name() == SERVICE_NAME { + let mut mtx = context + .as_ref() + .unwrap() + .downcast_ref::>>() + .unwrap() + .lock() + .unwrap(); + + mtx.txt.clone_from(service.txt()); + mtx.is_discovered = true; + + debug!("Service discovered"); } } + BrowserEvent::Remove(service) => { + debug!( + "Service removed: {}.{}.{}", + service.name(), + service.kind(), + service.domain() + ); + } })); let event_loop = browser.browse_services().unwrap(); From f22b53092b58c08dcdc55114fc7092e7abd40651 Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Thu, 23 Jan 2025 14:10:57 +0100 Subject: [PATCH 5/6] Also rename the callback in the example --- examples/browser/src/main.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs index a4e8772..58bb460 100644 --- a/examples/browser/src/main.rs +++ b/examples/browser/src/main.rs @@ -45,7 +45,7 @@ fn main() -> zeroconf::Result<()> { let mut browser = MdnsBrowser::new(service_type); - browser.set_service_callback(Box::new(on_service_event)); + browser.set_service_callback(Box::new(on_service_discovery_event)); let event_loop = browser.browse_services()?; @@ -55,7 +55,10 @@ fn main() -> zeroconf::Result<()> { } } -fn on_service_event(result: zeroconf::Result, _context: Option>) { +fn on_service_discovery_event( + result: zeroconf::Result, + _context: Option>, +) { info!( "Service event: {:?}", result.expect("service discovery failed") From 9e66dc1292b29548b12b3588aa89923502ab3f03 Mon Sep 17 00:00:00 2001 From: Peter Jankuliak Date: Thu, 23 Jan 2025 14:11:27 +0100 Subject: [PATCH 6/6] Set the default example logging to "info" Otherwise the user wouldn't see any events unless they also set RUST_LOG --- examples/browser/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs index 58bb460..5f768ce 100644 --- a/examples/browser/src/main.rs +++ b/examples/browser/src/main.rs @@ -27,7 +27,7 @@ struct Args { } fn main() -> zeroconf::Result<()> { - env_logger::init(); + env_logger::Builder::from_env(env_logger::Env::new().filter_or("RUST_LOG", "info")).init(); let Args { name,