use core::net::IpAddr; use alloc::{ string::{String, ToString}, vec::Vec, }; use crate::dns::{ label::Label, query::{Answer, QClass}, records::{A, AAAA, PTR, QType, Record, SRV, TXT}, }; #[derive(Debug, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Service { service_type: String, instance: String, hostname: String, ip: Option, port: u16, } impl Service { pub fn new( service_type: impl Into, instance: impl Into, hostname: impl Into, ip: Option, port: u16, ) -> Self { let service_type = service_type.into(); let mut instance = instance.into(); let mut hostname = hostname.into(); instance.push('.'); instance.push_str(&service_type); hostname.push_str(".local"); Self { service_type, instance, hostname, ip, port, } } pub fn service_type(&self) -> Label<'_> { Label::from(self.service_type.as_ref()) } pub fn instance(&self) -> Label<'_> { Label::from(self.instance.as_ref()) } pub fn hostname(&self) -> Label<'_> { Label::from(self.hostname.as_ref()) } pub fn ip(&self) -> Option { self.ip } pub fn port(&self) -> u16 { self.port } pub(crate) fn ptr_answer(&self, aclass: QClass) -> Option> { Some(Answer { name: self.service_type(), atype: QType::PTR, aclass, ttl: 4500, record: Record::PTR(PTR { name: self.instance(), }), }) } pub(crate) fn srv_answer(&self, aclass: QClass) -> Option> { Some(Answer { name: self.instance(), atype: QType::SRV, aclass, ttl: 120, record: Record::SRV(SRV { priority: 0, weight: 0, port: self.port, target: self.hostname(), }), }) } pub(crate) fn txt_answer(&self, aclass: QClass) -> Option> { Some(Answer { name: self.instance(), atype: QType::TXT, aclass, ttl: 120, record: Record::TXT(TXT { text: Vec::new() }), }) } pub(crate) fn ip_answer(&self, aclass: QClass) -> Option> { self.ip().map(|address| match address { IpAddr::V4(address) => Answer { name: self.hostname(), atype: QType::A, aclass, ttl: 120, record: Record::A(A { address }), }, IpAddr::V6(address) => Answer { name: self.hostname(), atype: QType::AAAA, aclass, ttl: 120, record: Record::AAAA(AAAA { address }), }, }) } pub(crate) fn as_answers(&self, aclass: QClass) -> impl Iterator> { self.ptr_answer(aclass) .into_iter() .chain(self.srv_answer(aclass)) .chain(self.txt_answer(aclass)) .chain(self.ip_answer(aclass)) } #[allow(dead_code)] pub(crate) fn from_answers(answers: &[Answer<'_>]) -> Vec { let mut output = Vec::new(); // Step 1: Process PTR records for answer in answers { if let Record::PTR(ptr) = &answer.record { let instance = ptr.name.to_string(); let service_type = answer.name.to_string(); output.push(Self { service_type, instance, ..Default::default() }); } } // Step 2: Process SRV records, A and AAAA records and merge data for answer in answers { match &answer.record { Record::SRV(srv) => { if let Some(stub) = output .iter_mut() .find(|stub| answer.name == stub.instance.as_ref()) { stub.hostname = srv.target.to_string(); stub.port = srv.port; } } Record::A(a) => { if let Some(stub) = output .iter_mut() .find(|stub| answer.name == stub.hostname.as_ref()) { stub.ip = Some(IpAddr::V4(a.address)); } } Record::AAAA(aaaa) => { if let Some(stub) = output .iter_mut() .find(|stub| answer.name == stub.hostname.as_ref()) { stub.ip = Some(IpAddr::V6(aaaa.address)); } } _ => {} } } // Final step: Retain only complete services output.retain(|stub| { !stub.service_type.is_empty() && !stub.instance.is_empty() && !stub.hostname.is_empty() && stub.ip.is_some() && stub.port != 0 }); output } }