Repo of no-std crates for my personal embedded projects
at main 5.4 kB view raw
1use core::net::IpAddr; 2 3use alloc::{ 4 string::{String, ToString}, 5 vec::Vec, 6}; 7 8use crate::dns::{ 9 label::Label, 10 query::{Answer, QClass}, 11 records::{A, AAAA, PTR, QType, Record, SRV, TXT}, 12}; 13 14#[derive(Debug, Default)] 15#[cfg_attr(feature = "defmt", derive(defmt::Format))] 16pub struct Service { 17 service_type: String, 18 instance: String, 19 hostname: String, 20 ip: Option<IpAddr>, 21 port: u16, 22} 23 24impl Service { 25 pub fn new( 26 service_type: impl Into<String>, 27 instance: impl Into<String>, 28 hostname: impl Into<String>, 29 ip: Option<IpAddr>, 30 port: u16, 31 ) -> Self { 32 let service_type = service_type.into(); 33 let mut instance = instance.into(); 34 let mut hostname = hostname.into(); 35 36 instance.push('.'); 37 instance.push_str(&service_type); 38 hostname.push_str(".local"); 39 40 Self { 41 service_type, 42 instance, 43 hostname, 44 ip, 45 port, 46 } 47 } 48 49 pub fn service_type(&self) -> Label<'_> { 50 Label::from(self.service_type.as_ref()) 51 } 52 53 pub fn instance(&self) -> Label<'_> { 54 Label::from(self.instance.as_ref()) 55 } 56 57 pub fn hostname(&self) -> Label<'_> { 58 Label::from(self.hostname.as_ref()) 59 } 60 61 pub fn ip(&self) -> Option<IpAddr> { 62 self.ip 63 } 64 65 pub fn port(&self) -> u16 { 66 self.port 67 } 68 69 pub(crate) fn ptr_answer(&self, aclass: QClass) -> Option<Answer<'_>> { 70 Some(Answer { 71 name: self.service_type(), 72 atype: QType::PTR, 73 aclass, 74 ttl: 4500, 75 record: Record::PTR(PTR { 76 name: self.instance(), 77 }), 78 }) 79 } 80 81 pub(crate) fn srv_answer(&self, aclass: QClass) -> Option<Answer<'_>> { 82 Some(Answer { 83 name: self.instance(), 84 atype: QType::SRV, 85 aclass, 86 ttl: 120, 87 record: Record::SRV(SRV { 88 priority: 0, 89 weight: 0, 90 port: self.port, 91 target: self.hostname(), 92 }), 93 }) 94 } 95 96 pub(crate) fn txt_answer(&self, aclass: QClass) -> Option<Answer<'_>> { 97 Some(Answer { 98 name: self.instance(), 99 atype: QType::TXT, 100 aclass, 101 ttl: 120, 102 record: Record::TXT(TXT { text: Vec::new() }), 103 }) 104 } 105 106 pub(crate) fn ip_answer(&self, aclass: QClass) -> Option<Answer<'_>> { 107 self.ip().map(|address| match address { 108 IpAddr::V4(address) => Answer { 109 name: self.hostname(), 110 atype: QType::A, 111 aclass, 112 ttl: 120, 113 record: Record::A(A { address }), 114 }, 115 IpAddr::V6(address) => Answer { 116 name: self.hostname(), 117 atype: QType::AAAA, 118 aclass, 119 ttl: 120, 120 record: Record::AAAA(AAAA { address }), 121 }, 122 }) 123 } 124 125 pub(crate) fn as_answers(&self, aclass: QClass) -> impl Iterator<Item = Answer<'_>> { 126 self.ptr_answer(aclass) 127 .into_iter() 128 .chain(self.srv_answer(aclass)) 129 .chain(self.txt_answer(aclass)) 130 .chain(self.ip_answer(aclass)) 131 } 132 133 #[allow(dead_code)] 134 pub(crate) fn from_answers(answers: &[Answer<'_>]) -> Vec<Self> { 135 let mut output = Vec::new(); 136 137 // Step 1: Process PTR records 138 for answer in answers { 139 if let Record::PTR(ptr) = &answer.record { 140 let instance = ptr.name.to_string(); 141 let service_type = answer.name.to_string(); 142 output.push(Self { 143 service_type, 144 instance, 145 ..Default::default() 146 }); 147 } 148 } 149 150 // Step 2: Process SRV records, A and AAAA records and merge data 151 for answer in answers { 152 match &answer.record { 153 Record::SRV(srv) => { 154 if let Some(stub) = output 155 .iter_mut() 156 .find(|stub| answer.name == stub.instance.as_ref()) 157 { 158 stub.hostname = srv.target.to_string(); 159 stub.port = srv.port; 160 } 161 } 162 Record::A(a) => { 163 if let Some(stub) = output 164 .iter_mut() 165 .find(|stub| answer.name == stub.hostname.as_ref()) 166 { 167 stub.ip = Some(IpAddr::V4(a.address)); 168 } 169 } 170 Record::AAAA(aaaa) => { 171 if let Some(stub) = output 172 .iter_mut() 173 .find(|stub| answer.name == stub.hostname.as_ref()) 174 { 175 stub.ip = Some(IpAddr::V6(aaaa.address)); 176 } 177 } 178 _ => {} 179 } 180 } 181 182 // Final step: Retain only complete services 183 output.retain(|stub| { 184 !stub.service_type.is_empty() 185 && !stub.instance.is_empty() 186 && !stub.hostname.is_empty() 187 && stub.ip.is_some() 188 && stub.port != 0 189 }); 190 191 output 192 } 193}