hylarana_discovery/
lib.rs

1use std::{fmt::Debug, net::Ipv4Addr, thread};
2
3use mdns_sd::{IfKind, ServiceDaemon, ServiceEvent, ServiceInfo};
4use serde::{de::DeserializeOwned, Serialize};
5use thiserror::Error;
6use uuid::Uuid;
7
8#[derive(Debug, Error)]
9pub enum DiscoveryError {
10    #[error(transparent)]
11    MdnsError(#[from] mdns_sd::Error),
12    #[error(transparent)]
13    JsonError(#[from] serde_json::Error),
14}
15
16/// LAN service discovery.
17///
18/// which exposes its services through the MDNS protocol
19/// and can allow other nodes or clients to discover the current service.
20pub struct DiscoveryService(ServiceDaemon);
21
22impl DiscoveryService {
23    /// Register the service, the service type is fixed, you can customize the
24    /// port number, in properties you can add
25    /// customized data to the published service.
26    pub fn register<P: Serialize + Debug>(
27        port: u16,
28        properties: &P,
29    ) -> Result<Self, DiscoveryError> {
30        let mdns = ServiceDaemon::new()?;
31        mdns.disable_interface(IfKind::IPv6)?;
32
33        let id = Uuid::new_v4().to_string();
34        mdns.register(
35            ServiceInfo::new(
36                "_hylarana._udp.local.",
37                "sender",
38                &format!("{}._hylarana._udp.local.", id),
39                "",
40                port,
41                &[("p", &serde_json::to_string(properties)?)][..],
42            )?
43            .enable_addr_auto(),
44        )?;
45
46        log::info!(
47            "discovery service register sender, port={}, id={}, properties={:?}",
48            port,
49            id,
50            properties
51        );
52
53        Ok(Self(mdns))
54    }
55
56    /// Query the registered service, the service type is fixed, when the query
57    /// is published the callback function will call back all the network
58    /// addresses of the service publisher as well as the attribute information.
59    pub fn query<P: DeserializeOwned + Debug, T: Fn(Vec<Ipv4Addr>, P) + Send + 'static>(
60        func: T,
61    ) -> Result<Self, DiscoveryError> {
62        let mdns = ServiceDaemon::new()?;
63        mdns.disable_interface(IfKind::IPv6)?;
64
65        let receiver = mdns.browse("_hylarana._udp.local.")?;
66        thread::spawn(move || {
67            let process = |info: ServiceInfo| {
68                if let Some(properties) = info.get_property("p") {
69                    let properties = serde_json::from_str(properties.val_str())?;
70                    let addrs = info
71                        .get_addresses_v4()
72                        .into_iter()
73                        .map(|it| *it)
74                        .collect::<Vec<_>>();
75
76                    log::info!(
77                        "discovery service query a sender, host={}, address={:?}, properties={:?}",
78                        info.get_hostname(),
79                        addrs,
80                        properties,
81                    );
82
83                    func(addrs, properties);
84                }
85
86                Ok::<(), DiscoveryError>(())
87            };
88
89            loop {
90                match receiver.recv() {
91                    Ok(ServiceEvent::ServiceResolved(info)) => {
92                        if info.get_fullname() == "sender._hylarana._udp.local." {
93                            if let Err(e) = process(info) {
94                                log::warn!("discovery service resolved error={:?}", e);
95                            }
96                        }
97                    }
98                    Err(e) => {
99                        log::error!("discovery service query error={:?}", e);
100
101                        break;
102                    }
103                    Ok(event) => {
104                        log::info!("discovery service query event={:?}", event);
105                    }
106                }
107            }
108        });
109
110        Ok(Self(mdns))
111    }
112}
113
114impl Drop for DiscoveryService {
115    fn drop(&mut self) {
116        let _ = self.0.unregister("_hylarana._udp.local.");
117        let _ = self.0.stop_browse("_hylarana._udp.local.");
118    }
119}