use std::time::{Duration, Instant}; use mdns_sd::{ServiceDaemon, ServiceEvent}; use crate::{Error, Result, client::Supernote}; const SERVICE: &str = "_http._tcp.local."; pub fn discover(timeout: Duration) -> Result> { let mdns = ServiceDaemon::new().map_err(Error::from_mdns)?; let receiver = mdns.browse(SERVICE).map_err(Error::from_mdns)?; let deadline = Instant::now() + timeout; let mut found: Vec = Vec::new(); while let Some(remaining) = deadline.checked_duration_since(Instant::now()) { let Ok(evt) = receiver.recv_timeout(remaining) else { break; }; let ServiceEvent::ServiceResolved(info) = evt else { continue; }; if !looks_like_supernote(info.get_fullname()) { continue; } let Some(addr) = info.get_addresses().iter().next() else { continue; }; let host = addr.to_string(); if found.iter().any(|d| d.host() == host) { continue; } found.push(Supernote::at(host).with_port(info.get_port())); } let _ = mdns.shutdown(); Ok(found) } fn looks_like_supernote(fullname: &str) -> bool { let lower = fullname.to_lowercase(); lower.contains("supernote") || lower.contains("ratta") } #[cfg(test)] mod tests { use super::*; #[test] fn matches_supernote_service_names() { assert!(looks_like_supernote("Supernote A6X._http._tcp.local.")); assert!(looks_like_supernote("ratta-nomad._http._tcp.local.")); assert!(!looks_like_supernote("printer._http._tcp.local.")); } }