use astro_dnssd::{DNSServiceBuilder, RegisteredDnsService, ServiceBrowserBuilder};
use embassy_futures::select::select3;
use embassy_time::Timer;
use crate::utils::select::Coalesce;
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, ToSocketAddrs};
use std::time::Duration;
use crate::error::{Error, ErrorCode};
use crate::transport::network::mdns::{CommissionableFilter, DottedName, MdnsRemoteService};
use crate::transport::network::MatterLocalService;
use crate::Matter;
const BROWSE_POLL_INTERVAL_MS: u64 = 50;
pub struct AstroMdns {
services: HashMap<MatterLocalService, RegisteredDnsService>,
}
impl Default for AstroMdns {
fn default() -> Self {
Self::new()
}
}
impl AstroMdns {
pub fn new() -> Self {
Self {
services: HashMap::new(),
}
}
pub async fn run(&mut self, matter: &Matter<'_>) -> Result<(), Error> {
let mut respond = core::pin::pin!(self.run_respond(matter));
let mut resolve = core::pin::pin!(Self::run_resolve(matter));
let mut browse = core::pin::pin!(Self::run_browse(matter));
select3(&mut respond, &mut resolve, &mut browse)
.coalesce()
.await
}
async fn run_respond(&mut self, matter: &Matter<'_>) -> Result<(), Error> {
loop {
matter.transport().wait_mdns().await;
let mut services = HashSet::new();
matter.mdns_services(|service| {
services.insert(service);
Ok(())
})?;
info!("mDNS services changed, updating...");
self.update_services(matter, &services)?;
info!("mDNS services updated");
}
}
async fn run_resolve(matter: &Matter<'_>) -> Result<(), Error> {
loop {
let service = matter.transport().wait_mdns_resolve_request().await;
let mut name_buf: heapless::String<128> = heapless::String::new();
service.instance_name(&mut name_buf);
let label = name_buf.split('.').next().unwrap_or("").to_string();
let browser = ServiceBrowserBuilder::new(service.service_type())
.browse()
.map_err(|e| {
error!("Failed to create service browser: {:?}", e);
ErrorCode::MdnsError
})?;
while matter.transport().mdns_resolve_in_flight() {
match browser.recv_timeout(Duration::ZERO) {
Ok(svc) if svc.name == label => {
let host_with_port = format!("{}:{}", svc.hostname, svc.port);
let ips: Vec<IpAddr> = host_with_port
.to_socket_addrs()
.map(|addrs| addrs.map(|addr| addr.ip()).collect())
.unwrap_or_default();
if !ips.is_empty() {
let mut txt: Vec<(&str, &str)> = Vec::new();
if let Some(ref txt_record) = svc.txt_record {
for (key, value) in txt_record {
txt.push((key, value));
}
}
matter
.transport()
.try_deposit_mdns_resolve(&MdnsRemoteService {
instance_name: DottedName(name_buf.as_str()),
port: Some(svc.port),
addrs: ips.iter().copied(),
txt: txt.iter().copied(),
scope_id: link_local_scope_id(&ips, svc.interface_index),
});
}
}
Ok(_) => {} Err(astro_dnssd::BrowseError::Timeout) => {}
Err(e) => debug!("Browse error: {:?}", e),
}
Timer::after(embassy_time::Duration::from_millis(BROWSE_POLL_INTERVAL_MS)).await;
}
}
}
async fn run_browse(matter: &Matter<'_>) -> Result<(), Error> {
loop {
let _filter: CommissionableFilter = matter.transport().wait_mdns_browse_request().await;
let browser = ServiceBrowserBuilder::new("_matterc._udp")
.browse()
.map_err(|e| {
error!("Failed to create service browser: {:?}", e);
ErrorCode::MdnsError
})?;
while matter.transport().mdns_browse_in_flight() {
match browser.recv_timeout(Duration::ZERO) {
Ok(service) => {
let host_with_port = format!("{}:{}", service.hostname, service.port);
let ips: Vec<IpAddr> = host_with_port
.to_socket_addrs()
.map(|addrs| addrs.map(|addr| addr.ip()).collect())
.unwrap_or_default();
if !ips.is_empty() {
let mut txt: Vec<(&str, &str)> = Vec::new();
if let Some(ref txt_record) = service.txt_record {
for (key, value) in txt_record {
txt.push((key, value));
}
}
matter
.transport()
.try_deposit_mdns_browse(&MdnsRemoteService {
instance_name: DottedName(service.name.as_str()),
port: Some(service.port),
addrs: ips.iter().copied(),
txt: txt.iter().copied(),
scope_id: link_local_scope_id(&ips, service.interface_index),
});
} else {
warn!("Could not resolve hostname: {}", service.hostname);
}
}
Err(astro_dnssd::BrowseError::Timeout) => {}
Err(e) => debug!("Browse error: {:?}", e),
}
Timer::after(embassy_time::Duration::from_millis(BROWSE_POLL_INTERVAL_MS)).await;
}
}
}
fn update_services(
&mut self,
matter: &Matter<'_>,
services: &HashSet<MatterLocalService>,
) -> Result<(), Error> {
for service in services {
if !self.services.contains_key(service) {
info!("Registering mDNS service: {:?}", service);
let registered = self.register(matter, service)?;
self.services.insert(service.clone(), registered);
}
}
loop {
let removed = self
.services
.iter()
.find(|(service, _)| !services.contains(service));
if let Some((service, _)) = removed {
info!("Deregistering mDNS service: {:?}", service);
self.services.remove(&service.clone());
} else {
break;
}
}
Ok(())
}
fn register(
&mut self,
matter: &Matter<'_>,
service: &MatterLocalService,
) -> Result<RegisteredDnsService, Error> {
let mut buf = [0u8; 512];
let (service, _) = service.service(matter.dev_det(), matter.port(), &mut buf)?;
let subtypes: Vec<&str> = service.service_subtypes.clone().collect();
let composite_service_type = if !subtypes.is_empty() {
format!(
"{}.{},{}",
service.service,
service.protocol,
subtypes.join(",")
)
} else {
format!("{}.{}", service.service, service.protocol)
};
let mut builder =
DNSServiceBuilder::new(&composite_service_type, service.port).with_name(service.name);
for (k, v) in service.txt_kvs.clone() {
trace!("mDNS TXT key {} val {}", k, v);
builder = builder.with_key_value(k.to_string(), v.to_string());
}
let svc = builder.register().map_err(|_| ErrorCode::MdnsError)?;
Ok(svc)
}
}
fn link_local_scope_id(ips: &[IpAddr], interface_index: Option<u32>) -> u32 {
let has_link_local = ips
.iter()
.any(|ip| matches!(ip, IpAddr::V6(v6) if v6.is_unicast_link_local()));
match interface_index {
Some(idx) if has_link_local && idx > 0 => idx,
_ => 0,
}
}