diff --git a/rs-matter/Cargo.toml b/rs-matter/Cargo.toml index 00dab01..8e49474 100644 --- a/rs-matter/Cargo.toml +++ b/rs-matter/Cargo.toml @@ -22,6 +22,7 @@ openssl = ["alloc", "dep:openssl", "foreign-types", "hmac", "sha2"] mbedtls = ["alloc", "dep:mbedtls"] rustcrypto = ["alloc", "sha2", "hmac", "pbkdf2", "hkdf", "aes", "ccm", "p256", "elliptic-curve", "crypto-bigint", "x509-cert", "rand_core"] embassy-net = ["dep:embassy-net", "dep:embassy-net-driver", "smoltcp"] +zeroconf = ["dep:zeroconf"] [dependencies] rs-matter-macros = { version = "0.1", path = "../rs-matter-macros" } @@ -76,6 +77,9 @@ x509-cert = { version = "0.2.0", default-features = false, features = ["pem"], o [target.'cfg(target_os = "macos")'.dependencies] astro-dnssd = { version = "0.3" } +[target.'cfg(target_os = "linux")'.dependencies] +zeroconf = { version = "0.12", optional = true } + [target.'cfg(not(target_os = "espidf"))'.dependencies] mbedtls = { version = "0.9", optional = true } env_logger = { version = "0.10.0", optional = true } diff --git a/rs-matter/src/mdns.rs b/rs-matter/src/mdns.rs index c66d12f..1a5dca9 100644 --- a/rs-matter/src/mdns.rs +++ b/rs-matter/src/mdns.rs @@ -23,6 +23,8 @@ use crate::{data_model::cluster_basic_information::BasicInfoConfig, error::Error pub mod astro; pub mod builtin; pub mod proto; +#[cfg(all(feature = "std", feature = "zeroconf", target_os = "linux"))] +pub mod zeroconf; pub trait Mdns { fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error>; diff --git a/rs-matter/src/mdns/zeroconf.rs b/rs-matter/src/mdns/zeroconf.rs new file mode 100644 index 0000000..2fd5d1f --- /dev/null +++ b/rs-matter/src/mdns/zeroconf.rs @@ -0,0 +1,176 @@ +use core::cell::RefCell; +use std::collections::HashMap; +use std::sync::mpsc::{sync_channel, SyncSender}; + +use super::{MdnsRunBuffers, ServiceMode}; +use crate::{ + data_model::cluster_basic_information::BasicInfoConfig, + error::{Error, ErrorCode}, + transport::pipe::Pipe, +}; +use zeroconf::{prelude::TEventLoop, service::TMdnsService, txt_record::TTxtRecord, ServiceType}; + +/// Only for API-compatibility with builtin::MdnsRunner +pub struct MdnsUdpBuffers(()); + +/// Only for API-compatibility with builtin::MdnsRunner +impl MdnsUdpBuffers { + #[inline(always)] + pub const fn new() -> Self { + Self(()) + } +} + +pub struct MdnsService<'a> { + dev_det: &'a BasicInfoConfig<'a>, + matter_port: u16, + services: RefCell>>, +} + +impl<'a> MdnsService<'a> { + /// This constructor takes extra parameters for API-compatibility with builtin::MdnsRunner + pub fn new( + _id: u16, + _hostname: &str, + _ip: [u8; 4], + _ipv6: Option<([u8; 16], u32)>, + dev_det: &'a BasicInfoConfig<'a>, + matter_port: u16, + ) -> Self { + Self::native_new(dev_det, matter_port) + } + + pub fn native_new(dev_det: &'a BasicInfoConfig<'a>, matter_port: u16) -> Self { + Self { + dev_det, + matter_port, + services: RefCell::new(HashMap::new()), + } + } + + pub fn add(&self, name: &str, mode: ServiceMode) -> Result<(), Error> { + log::info!("Registering mDNS service {}/{:?}", name, mode); + + let _ = self.remove(name); + + mode.service(self.dev_det, self.matter_port, name, |service| { + let service_name = service.service.strip_prefix('_').unwrap_or(service.service); + let protocol = service + .protocol + .strip_prefix('_') + .unwrap_or(service.protocol); + + let service_type = if !service.service_subtypes.is_empty() { + let subtypes = service + .service_subtypes + .into_iter() + .map(|subtype| subtype.strip_prefix('_').unwrap_or(*subtype)) + .collect(); + + ServiceType::with_sub_types(service_name, protocol, subtypes) + } else { + ServiceType::new(service_name, protocol) + } + .map_err(|err| { + log::error!( + "Encountered error building service type: {}", + err.to_string() + ); + ErrorCode::MdnsError + })?; + + let (sender, receiver) = sync_channel(1); + + let service_port = service.port; + let mut txt_kvs = vec![]; + for (k, v) in service.txt_kvs { + txt_kvs.push((k.to_string(), v.to_string())); + } + + let name_copy = name.to_owned(); + + std::thread::spawn(move || { + let mut mdns_service = zeroconf::MdnsService::new(service_type, service_port); + + let mut txt_record = zeroconf::TxtRecord::new(); + for (k, v) in txt_kvs { + log::info!("mDNS TXT key {k} val {v}"); + if let Err(err) = txt_record.insert(&k, &v) { + log::error!( + "Encountered error inserting kv-pair into txt record {}", + err.to_string() + ); + } + } + mdns_service.set_name(&name_copy); + mdns_service.set_txt_record(txt_record); + mdns_service.set_registered_callback(Box::new(|_, _| {})); + + match mdns_service.register() { + Ok(event_loop) => loop { + if let Ok(()) = receiver.try_recv() { + break; + } + if let Err(err) = event_loop.poll(std::time::Duration::from_secs(1)) { + log::error!( + "Failed to poll mDNS service event loop: {}", + err.to_string() + ); + break; + } + }, + Err(err) => log::error!( + "Encountered error registering mDNS service: {}", + err.to_string() + ), + } + }); + + self.services.borrow_mut().insert(name.to_owned(), sender); + + Ok(()) + }) + } + + pub fn remove(&self, name: &str) -> Result<(), Error> { + if let Some(cancellation_notice) = self.services.borrow_mut().remove(name) { + log::info!("Deregistering mDNS service {}", name); + cancellation_notice + .send(()) + .map_err(|_| ErrorCode::MdnsError)?; + } + + Ok(()) + } + + /// Only for API-compatibility with builtin::MdnsRunner + pub async fn run_piped( + &mut self, + _tx_pipe: &Pipe<'_>, + _rx_pipe: &Pipe<'_>, + ) -> Result<(), Error> { + core::future::pending::>().await + } + + /// Only for API-compatibility with builtin::MdnsRunner + pub async fn run( + &self, + _stack: &crate::transport::network::NetworkStack, + _buffers: &mut MdnsRunBuffers, + ) -> Result<(), Error> + where + D: crate::transport::network::NetworkStackDriver, + { + core::future::pending::>().await + } +} + +impl<'a> super::Mdns for MdnsService<'a> { + fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error> { + MdnsService::add(self, service, mode) + } + + fn remove(&self, service: &str) -> Result<(), Error> { + MdnsService::remove(self, service) + } +}