Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions examples/browser/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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();
Comment thread
windy1 marked this conversation as resolved.

let Args {
name,
Expand All @@ -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_discovery_event));

let event_loop = browser.browse_services()?;

Expand All @@ -55,12 +55,12 @@ fn main() -> zeroconf::Result<()> {
}
}

fn on_service_discovered(
result: zeroconf::Result<ServiceDiscovery>,
fn on_service_discovery_event(
result: zeroconf::Result<BrowserEvent>,
_context: Option<Arc<dyn Any>>,
) {
info!(
"Service discovered: {:?}",
"Service event: {:?}",
result.expect("service discovery failed")
);

Expand Down
44 changes: 32 additions & 12 deletions zeroconf/src/avahi/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ServiceBrowserCallback, ServiceDiscovery,
ServiceRemoval, ServiceType, TxtRecord,
};
use avahi_sys::{
AvahiAddress, AvahiBrowserEvent, AvahiClient, AvahiClientFlags, AvahiClientState, AvahiIfIndex,
Expand Down Expand Up @@ -56,11 +56,8 @@ impl TMdnsBrowser for AvahiMdnsBrowser {
avahi_util::interface_from_index(self.context.interface_index)
}

fn set_service_discovered_callback(
&mut self,
service_discovered_callback: Box<ServiceDiscoveredCallback>,
) {
self.context.service_discovered_callback = Some(service_discovered_callback);
fn set_service_callback(&mut self, service_callback: Box<ServiceBrowserCallback>) {
self.context.service_callback = Some(service_callback);
}

fn set_context(&mut self, context: Box<dyn Any>) {
Expand Down Expand Up @@ -112,7 +109,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser {
struct AvahiBrowserContext {
client: Option<Arc<ManagedAvahiClient>>,
resolvers: ServiceResolverSet,
service_discovered_callback: Option<Box<ServiceDiscoveredCallback>>,
service_callback: Option<Box<ServiceBrowserCallback>>,
user_context: Option<Arc<dyn Any>>,
interface_index: AvahiIfIndex,
kind: CString,
Expand All @@ -124,16 +121,16 @@ impl AvahiBrowserContext {
Self {
client: None,
resolvers: ServiceResolverSet::default(),
service_discovered_callback: None,
service_callback: None,
user_context: None,
interface_index,
kind,
browser: None,
}
}

fn invoke_callback(&self, result: Result<ServiceDiscovery>) {
if let Some(f) = &self.service_discovered_callback {
fn invoke_callback(&self, result: Result<BrowserEvent>) {
if let Some(f) = &self.service_callback {
f(result, self.user_context.clone());
} else {
warn!("attempted to invoke browser callback but none was set");
Expand Down Expand Up @@ -205,6 +202,9 @@ 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 => {
handle_browser_remove(context, name, kind, domain);
}
_ => {}
};
}
Expand Down Expand Up @@ -242,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,
Expand Down Expand Up @@ -324,7 +344,7 @@ unsafe fn handle_resolver_found(

debug!("Service resolved: {:?}", result);

context.invoke_callback(Ok(result));
context.invoke_callback(Ok(BrowserEvent::Add(result)));

Ok(())
}
64 changes: 47 additions & 17 deletions zeroconf/src/bonjour/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
#[cfg(target_vendor = "pc")]
use bonjour_sys::sockaddr_in;
use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef};
Expand Down Expand Up @@ -48,10 +48,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser {
bonjour_util::interface_from_index(self.interface_index)
}

fn set_service_discovered_callback(
&mut self,
service_discovered_callback: Box<ServiceDiscoveredCallback>,
) {
fn set_service_callback(&mut self, service_discovered_callback: Box<ServiceBrowserCallback>) {
self.context.service_discovered_callback = Some(service_discovered_callback);
}

Expand Down Expand Up @@ -88,7 +85,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser {

#[derive(Default, FromRaw, AsRaw)]
struct BonjourBrowserContext {
service_discovered_callback: Option<Box<ServiceDiscoveredCallback>>,
service_discovered_callback: Option<Box<ServiceBrowserCallback>>,
resolved_name: Option<String>,
resolved_kind: Option<String>,
resolved_domain: Option<String>,
Expand All @@ -98,7 +95,7 @@ struct BonjourBrowserContext {
}

impl BonjourBrowserContext {
fn invoke_callback(&self, result: Result<ServiceDiscovery>) {
fn invoke_callback(&self, result: Result<BrowserEvent>) {
if let Some(f) = &self.service_discovered_callback {
f(result, self.user_context.clone());
} else {
Expand All @@ -120,7 +117,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,
Expand All @@ -129,23 +126,32 @@ 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));
Expand All @@ -163,6 +169,30 @@ 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(
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(
_sd_ref: DNSServiceRef,
_flags: DNSServiceFlags,
Expand Down Expand Up @@ -313,7 +343,7 @@ unsafe fn handle_get_address_info(
.build()
.expect("could not build ServiceResolution");

ctx.invoke_callback(Ok(result));
ctx.invoke_callback(Ok(BrowserEvent::Add(result)));

Ok(())
}
40 changes: 30 additions & 10 deletions zeroconf/src/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ 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 {
Add(ServiceDiscovery),
Remove(ServiceRemoval),
}

/// 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`)
Expand All @@ -18,14 +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_discovered_callback(
&mut self,
service_discovered_callback: Box<ServiceDiscoveredCallback>,
);
/// [`ServiceBrowserCallback`]: ../type.ServiceBrowserCallback.html
fn set_service_callback(&mut self, service_callback: Box<ServiceBrowserCallback>);

/// 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`.
Expand All @@ -38,14 +44,15 @@ pub trait TMdnsBrowser {
fn browse_services(&mut self) -> Result<EventLoop>;
}

/// 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<ServiceDiscovery>, Option<Arc<dyn Any>>);
pub type ServiceBrowserCallback = dyn Fn(Result<BrowserEvent>, Option<Arc<dyn Any>>);

/// Represents a service that has been discovered by a [`MdnsBrowser`].
///
Expand All @@ -61,3 +68,16 @@ pub struct ServiceDiscovery {
port: u16,
txt: Option<TxtRecord>,
}

/// 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,
}
12 changes: 6 additions & 6 deletions zeroconf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ServiceRemoval, ServiceType};
//!
//! /// Example of a simple mDNS browser
//! #[derive(Parser, Debug)]
Expand Down Expand Up @@ -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()?;
//!
Expand All @@ -164,12 +164,12 @@
//! }
//! }
//!
//! fn on_service_discovered(
//! result: zeroconf::Result<ServiceDiscovery>,
//! fn on_service_event(
//! result: zeroconf::Result<BrowserEvent>,
//! _context: Option<Arc<dyn Any>>,
//! ) {
//! info!(
//! "Service discovered: {:?}",
//! "Service event: {:?}",
//! result.expect("service discovery failed")
//! );
//!
Expand Down Expand Up @@ -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, ServiceBrowserCallback, ServiceDiscovery, ServiceRemoval};
pub use interface::*;
pub use service::{ServiceRegisteredCallback, ServiceRegistration};
pub use service_type::*;
Expand Down
Loading