//! mDNS advertisement for the local peer server. use std::{collections::HashMap, net::SocketAddr, time::Duration}; use lanspread_mdns::{DaemonEvent, LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, MdnsMonitor}; use lanspread_proto::{PROTOCOL_VERSION, PeerId}; use tokio_util::sync::CancellationToken; use crate::{context::PeerCtx, network::select_advertise_ip, scoped_blocking::scoped_blocking}; pub(super) async fn start_mdns_advertiser( ctx: &PeerCtx, server_addr: SocketAddr, ) -> eyre::Result { let advertise_ip = select_advertise_ip()?; let advertise_addr = SocketAddr::new(advertise_ip, server_addr.port()); log::info!("Advertising peer via mDNS from {advertise_addr}"); { let mut guard = ctx.local_peer_addr.write().await; *guard = Some(advertise_addr); } let peer_id = ctx.peer_id; let hostname = gethostname::gethostname().to_string_lossy().into_owned(); let advertised_name = advertised_service_name(&hostname, peer_id); let monitor_name = advertised_name.clone(); let properties = advertisement_properties(&hostname, peer_id); let mdns = scoped_blocking(move || { MdnsAdvertiser::new( LANSPREAD_SERVICE_TYPE, &advertised_name, advertise_addr, Some(properties), ) })?; log::info!("Registered mDNS service with name: {monitor_name}"); Ok(mdns) } pub(super) fn close_mdns_advertiser(advertiser: MdnsAdvertiser) -> eyre::Result<()> { scoped_blocking(move || advertiser.close()) } pub(super) async fn monitor_mdns_events(monitor: MdnsMonitor, shutdown: CancellationToken) { loop { let event = tokio::select! { () = shutdown.cancelled() => break, event = monitor.recv_async() => event, }; match event { Ok(DaemonEvent::Error(err)) => { log::error!("mDNS error: {err}"); tokio::select! { () = shutdown.cancelled() => break, () = tokio::time::sleep(Duration::from_secs(1)) => {} } } Ok(other_event) => { log::trace!("mDNS event: {other_event:?}"); } Err(err) => { log::debug!("mDNS monitor channel closed: {err}"); break; } } } } fn advertised_service_name(hostname: &str, peer_id: PeerId) -> String { let peer_id = peer_id.to_string(); let max_hostname_len = 63usize.saturating_sub(peer_id.len() + 1); let truncated_hostname = if hostname.len() > max_hostname_len { hostname.get(..max_hostname_len).unwrap_or(hostname) } else { hostname }; if truncated_hostname.is_empty() { peer_id } else { format!("{truncated_hostname}-{peer_id}") } } fn advertisement_properties(hostname: &str, peer_id: PeerId) -> HashMap { let mut properties = HashMap::new(); properties.insert("peer_id".to_string(), peer_id.to_string()); properties.insert("proto_ver".to_string(), PROTOCOL_VERSION.to_string()); if !hostname.is_empty() { properties.insert("hostname".to_string(), hostname.to_string()); } properties }