Skip to main content

flowrlib/
discovery.rs

1//! Optional mDNS-SD service discovery helpers.
2//!
3//! These functions provide a convenient way to advertise and discover flow services
4//! using mDNS-SD. They are not required — other binaries using `flowrlib` can
5//! implement their own discovery mechanism.
6
7use std::time::{Duration, Instant};
8
9use log::info;
10use mdns_sd::{ServiceDaemon, ServiceEvent, ServiceInfo};
11
12use flowcore::errors::{bail, Result};
13
14use crate::services::FLOW_SERVICE_TYPE;
15
16const DEFAULT_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(30);
17
18/// Register a service for mDNS-SD discovery.
19///
20/// The returned `ServiceDaemon` must be kept alive by the caller — the service is
21/// unregistered when the daemon is dropped.
22///
23/// # Errors
24///
25/// Returns an error if the mDNS daemon or service registration fails.
26pub fn enable_service_discovery(name: &str, service_port: u16) -> Result<ServiceDaemon> {
27    let mdns = ServiceDaemon::new().map_err(|e| format!("Could not create mDNS daemon: {e}"))?;
28
29    let service_hostname = format!("{name}.local.");
30
31    let service_info = ServiceInfo::new(
32        FLOW_SERVICE_TYPE,
33        name,
34        &service_hostname,
35        "",
36        service_port,
37        None,
38    )
39    .map_err(|e| format!("Could not create mDNS ServiceInfo: {e}"))?
40    .enable_addr_auto();
41
42    mdns.register(service_info)
43        .map_err(|e| format!("Could not register mDNS service: {e}"))?;
44
45    info!("mDNS service registered: '{name}' on port {service_port}");
46
47    Ok(mdns)
48}
49
50/// Discover a service by name using mDNS-SD. Blocks until the service is found
51/// or the default timeout (30 seconds) expires.
52///
53/// Returns the service address as `"{ip}:{port}"`.
54///
55/// # Errors
56/// - Cannot create `ServiceDaemon`
57/// - Cannot get receiver for discovery messages
58pub fn discover_service(name: &str) -> Result<String> {
59    let mdns = ServiceDaemon::new().map_err(|e| format!("Could not create mDNS daemon: {e}"))?;
60
61    let receiver = mdns
62        .browse(FLOW_SERVICE_TYPE)
63        .map_err(|e| format!("Could not browse for mDNS services: {e}"))?;
64
65    let full_name_suffix = format!(".{FLOW_SERVICE_TYPE}");
66    let start = Instant::now();
67
68    loop {
69        if start.elapsed() > DEFAULT_DISCOVERY_TIMEOUT {
70            mdns.shutdown().ok();
71            bail!(format!(
72                "mDNS discovery timed out after {}s for '{name}'",
73                DEFAULT_DISCOVERY_TIMEOUT.as_secs()
74            ));
75        }
76
77        if let Ok(ServiceEvent::ServiceResolved(info)) =
78            receiver.recv_timeout(Duration::from_millis(500))
79        {
80            let instance = info
81                .get_fullname()
82                .strip_suffix(&full_name_suffix)
83                .unwrap_or(info.get_fullname());
84
85            if instance == name {
86                let port = info.get_port();
87                if let Some(addr) = info.get_addresses_v4().into_iter().next() {
88                    let address = format!("{addr}:{port}");
89                    info!("Discovered mDNS service '{name}' at {address}");
90                    mdns.shutdown().ok();
91                    return Ok(address);
92                }
93            }
94        }
95    }
96}