From 65661f13db487fbae2709b7535e14f8518a574c1 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Wed, 6 Sep 2023 23:43:09 -0600 Subject: [PATCH 1/5] Implement linux mdns with avahi/zerconf --- rs-matter/Cargo.toml | 4 + rs-matter/src/mdns.rs | 2 + rs-matter/src/mdns/zeroconf.rs | 164 +++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 rs-matter/src/mdns/zeroconf.rs diff --git a/rs-matter/Cargo.toml b/rs-matter/Cargo.toml index 00dab01..e83569c 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.11.1", 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..7cd0734 --- /dev/null +++ b/rs-matter/src/mdns/zeroconf.rs @@ -0,0 +1,164 @@ +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_type = if !service.service_subtypes.is_empty() { + ServiceType::with_sub_types( + service.service, + service.protocol, + Vec::from(service.service_subtypes), + ) + } else { + ServiceType::new(service.service, service.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())); + } + + 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_txt_record(txt_record); + + 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) + } +} From b93875658bebacb0bb924eb7303251f77bba9a54 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Tue, 12 Sep 2023 18:23:42 -0600 Subject: [PATCH 2/5] Update with fork for zeroconf --- rs-matter/Cargo.toml | 2 +- rs-matter/src/mdns/zeroconf.rs | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/rs-matter/Cargo.toml b/rs-matter/Cargo.toml index e83569c..1619548 100644 --- a/rs-matter/Cargo.toml +++ b/rs-matter/Cargo.toml @@ -78,7 +78,7 @@ x509-cert = { version = "0.2.0", default-features = false, features = ["pem"], o astro-dnssd = { version = "0.3" } [target.'cfg(target_os = "linux")'.dependencies] -zeroconf = { version = "0.11.1", optional = true } +zeroconf = { version = "0.11.1", optional = true, path = "../../zeroconf-rs/zeroconf" } [target.'cfg(not(target_os = "espidf"))'.dependencies] mbedtls = { version = "0.9", optional = true } diff --git a/rs-matter/src/mdns/zeroconf.rs b/rs-matter/src/mdns/zeroconf.rs index 7cd0734..4676731 100644 --- a/rs-matter/src/mdns/zeroconf.rs +++ b/rs-matter/src/mdns/zeroconf.rs @@ -54,14 +54,22 @@ impl<'a> MdnsService<'a> { let _ = self.remove(name); mode.service(self.dev_det, self.matter_port, name, |service| { + let 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() { - ServiceType::with_sub_types( - service.service, - service.protocol, - Vec::from(service.service_subtypes), - ) + let subtypes = service + .service_subtypes + .into_iter() + .map(|subtype| subtype.strip_prefix('_').unwrap_or(*subtype)) + .collect(); + + ServiceType::with_sub_types(name, protocol, subtypes) } else { - ServiceType::new(service.service, service.protocol) + ServiceType::new(name, protocol) } .map_err(|err| { log::error!( @@ -93,6 +101,7 @@ impl<'a> MdnsService<'a> { } } mdns_service.set_txt_record(txt_record); + mdns_service.set_registered_callback(Box::new(|_, _| {})); match mdns_service.register() { Ok(event_loop) => loop { From c064fb12a457dd64a3a600f36dbcbdb2f4f61c18 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Sun, 17 Sep 2023 11:23:44 -0600 Subject: [PATCH 3/5] Update with service name --- rs-matter/Cargo.toml | 2 +- rs-matter/src/mdns/zeroconf.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/rs-matter/Cargo.toml b/rs-matter/Cargo.toml index 1619548..cdccfd4 100644 --- a/rs-matter/Cargo.toml +++ b/rs-matter/Cargo.toml @@ -78,7 +78,7 @@ x509-cert = { version = "0.2.0", default-features = false, features = ["pem"], o astro-dnssd = { version = "0.3" } [target.'cfg(target_os = "linux")'.dependencies] -zeroconf = { version = "0.11.1", optional = true, path = "../../zeroconf-rs/zeroconf" } +zeroconf = { version = "0.11.1", optional = true, git = "https://github.com/ssnover/zeroconf-rs" } [target.'cfg(not(target_os = "espidf"))'.dependencies] mbedtls = { version = "0.9", optional = true } diff --git a/rs-matter/src/mdns/zeroconf.rs b/rs-matter/src/mdns/zeroconf.rs index 4676731..2fd5d1f 100644 --- a/rs-matter/src/mdns/zeroconf.rs +++ b/rs-matter/src/mdns/zeroconf.rs @@ -54,7 +54,7 @@ impl<'a> MdnsService<'a> { let _ = self.remove(name); mode.service(self.dev_det, self.matter_port, name, |service| { - let name = service.service.strip_prefix('_').unwrap_or(service.service); + let service_name = service.service.strip_prefix('_').unwrap_or(service.service); let protocol = service .protocol .strip_prefix('_') @@ -67,9 +67,9 @@ impl<'a> MdnsService<'a> { .map(|subtype| subtype.strip_prefix('_').unwrap_or(*subtype)) .collect(); - ServiceType::with_sub_types(name, protocol, subtypes) + ServiceType::with_sub_types(service_name, protocol, subtypes) } else { - ServiceType::new(name, protocol) + ServiceType::new(service_name, protocol) } .map_err(|err| { log::error!( @@ -87,6 +87,8 @@ impl<'a> MdnsService<'a> { 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); @@ -100,6 +102,7 @@ impl<'a> MdnsService<'a> { ); } } + mdns_service.set_name(&name_copy); mdns_service.set_txt_record(txt_record); mdns_service.set_registered_callback(Box::new(|_, _| {})); From 53b8c9ffd750bf6de03aca4eb959e2874accd87e Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Sun, 24 Sep 2023 19:05:23 -0600 Subject: [PATCH 4/5] Bump the zeroconf version --- rs-matter/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs-matter/Cargo.toml b/rs-matter/Cargo.toml index cdccfd4..8e49474 100644 --- a/rs-matter/Cargo.toml +++ b/rs-matter/Cargo.toml @@ -78,7 +78,7 @@ x509-cert = { version = "0.2.0", default-features = false, features = ["pem"], o astro-dnssd = { version = "0.3" } [target.'cfg(target_os = "linux")'.dependencies] -zeroconf = { version = "0.11.1", optional = true, git = "https://github.com/ssnover/zeroconf-rs" } +zeroconf = { version = "0.12", optional = true } [target.'cfg(not(target_os = "espidf"))'.dependencies] mbedtls = { version = "0.9", optional = true } From 7caf1febe395d04a8d08b98eafe77ef421a37bb9 Mon Sep 17 00:00:00 2001 From: Shane Snover Date: Sun, 24 Sep 2023 20:04:33 -0600 Subject: [PATCH 5/5] Pull in the change for unsoundness in lifetime to get the build working --- rs-matter/src/transport/network.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs-matter/src/transport/network.rs b/rs-matter/src/transport/network.rs index 850dde3..7a2ca9f 100644 --- a/rs-matter/src/transport/network.rs +++ b/rs-matter/src/transport/network.rs @@ -64,7 +64,7 @@ pub use embassy_net_stack::*; #[cfg(feature = "std")] pub mod std_stack { - pub trait NetworkStackDriver {} + pub trait NetworkStackDriver: 'static {} impl NetworkStackDriver for () {}