1use std::{
4 collections::{btree_map::Entry, BTreeMap},
5 fmt,
6 str::FromStr,
7};
8
9use crate::{
10 name::{DomainName, Label},
11 packet::records::{PTR, SRV, TXT},
12 Error,
13};
14
15pub mod advertising;
16pub mod discovery;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum ServiceTransport {
21 TCP,
23 Other,
25}
26
27impl ServiceTransport {
28 fn as_str(&self) -> &str {
29 match self {
30 ServiceTransport::TCP => "_tcp",
31 ServiceTransport::Other => "_udp",
32 }
33 }
34
35 pub fn to_label(&self) -> Label {
36 Label::new(self.as_str())
37 }
38}
39
40impl FromStr for ServiceTransport {
41 type Err = Error;
42
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 match s {
45 "_tcp" => Ok(Self::TCP),
46 "_udp" => Ok(Self::Other),
47 _ => Err(Error::InvalidValue),
48 }
49 }
50}
51
52#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct Service {
60 name: Label,
62 transport: ServiceTransport,
63}
64
65impl Service {
66 pub fn new(name: Label, transport: ServiceTransport) -> Self {
72 assert!(name.as_bytes().starts_with(b"_"));
73 Self { name, transport }
74 }
75
76 pub fn from_ptr(ptr: PTR<'_>) -> Result<Self, Error> {
77 let mut labels = ptr.ptrdname().labels().iter();
78 let service_name = labels.next().ok_or(Error::Eof)?;
79 let transport = labels.next().ok_or(Error::Eof)?;
80 if labels.next().is_none() {
81 return Err(Error::Eof);
83 }
84 Ok(Service {
85 name: service_name.clone(),
86 transport: match transport.as_bytes() {
87 b"_tcp" => ServiceTransport::TCP,
88 b"_udp" => ServiceTransport::Other,
89 _ => return Err(Error::InvalidValue),
90 },
91 })
92 }
93
94 #[inline]
95 pub fn name(&self) -> &Label {
96 &self.name
97 }
98
99 #[inline]
100 pub fn transport(&self) -> ServiceTransport {
101 self.transport
102 }
103}
104
105impl fmt::Display for Service {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 write!(f, "{}.{}", self.name, self.transport.as_str())
108 }
109}
110
111impl fmt::Debug for Service {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 fmt::Display::fmt(self, f)
114 }
115}
116
117#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
119pub struct ServiceInstance {
120 instance_name: Label,
121 service: Service,
122}
123
124impl ServiceInstance {
125 pub fn new(instance_name: Label, service_name: Label, transport: ServiceTransport) -> Self {
137 Self {
138 instance_name,
139 service: Service::new(service_name, transport),
140 }
141 }
142
143 pub fn from_service(instance_name: Label, service: Service) -> Self {
144 Self {
145 instance_name,
146 service,
147 }
148 }
149
150 pub fn from_ptr(ptr: PTR<'_>) -> Result<Self, Error> {
151 let mut labels = ptr.ptrdname().labels().iter();
152 let instance_name = labels.next().ok_or(Error::Eof)?;
153 let service_name = labels.next().ok_or(Error::Eof)?;
154 let transport = labels.next().ok_or(Error::Eof)?;
155 if labels.next().is_none() {
156 return Err(Error::Eof);
158 }
159 Ok(ServiceInstance {
160 instance_name: instance_name.clone(),
161 service: Service {
162 name: service_name.clone(),
163 transport: match transport.as_bytes() {
164 b"_tcp" => ServiceTransport::TCP,
165 b"_udp" => ServiceTransport::Other,
166 _ => return Err(Error::InvalidValue),
167 },
168 },
169 })
170 }
171
172 #[inline]
173 pub fn instance_name(&self) -> &Label {
174 &self.instance_name
175 }
176
177 #[inline]
178 pub fn service(&self) -> &Service {
179 &self.service
180 }
181
182 #[inline]
183 pub fn service_name(&self) -> &Label {
184 self.service.name()
185 }
186
187 #[inline]
188 pub fn service_transport(&self) -> ServiceTransport {
189 self.service.transport
190 }
191}
192
193impl fmt::Display for ServiceInstance {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}.{}", self.instance_name, self.service,)
196 }
197}
198
199impl fmt::Debug for ServiceInstance {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 fmt::Display::fmt(self, f)
202 }
203}
204
205pub struct InstanceDetails {
207 host: DomainName,
208 port: u16,
209 txt: TxtRecords,
210}
211
212impl InstanceDetails {
213 pub fn new(host: DomainName, port: u16) -> Self {
214 Self {
215 host,
216 port,
217 txt: TxtRecords::new(),
218 }
219 }
220
221 pub fn from_srv(srv: &SRV<'_>) -> Result<Self, Error> {
223 Ok(Self {
224 host: srv.target().clone(),
225 port: srv.port(),
226 txt: TxtRecords::new(),
227 })
228 }
229
230 #[inline]
232 pub fn host(&self) -> &DomainName {
233 &self.host
234 }
235
236 #[inline]
238 pub fn port(&self) -> u16 {
239 self.port
240 }
241
242 #[inline]
243 pub fn txt_records(&self) -> &TxtRecords {
244 &self.txt
245 }
246
247 #[inline]
248 pub fn txt_records_mut(&mut self) -> &mut TxtRecords {
249 &mut self.txt
250 }
251}
252
253#[derive(Debug)]
255pub struct TxtRecords {
256 map: BTreeMap<String, TxtRecord>,
259}
260
261#[derive(Debug)]
262struct TxtRecord {
263 key: String,
264 value: Option<Vec<u8>>,
265}
266
267impl TxtRecords {
268 pub fn new() -> Self {
269 Self {
270 map: BTreeMap::new(),
271 }
272 }
273
274 pub fn from_txt(txt: &TXT<'_>) -> Self {
275 let mut map = BTreeMap::new();
276
277 for entry in txt.entries() {
278 let mut split = entry.splitn(2, |&b| b == b'=');
279 let key = split.next().unwrap();
280 let key = match String::from_utf8(key.to_vec()) {
281 Ok(key) => key,
282 Err(e) => {
283 log::debug!("non-ASCII TXT key: {}", e);
284 continue;
285 }
286 };
287 let entry = map.entry(key.to_ascii_lowercase());
288 if let Entry::Occupied(_) = entry {
289 log::debug!("TXT key '{}' already occupied, ignoring", entry.key());
290 }
291
292 match split.next() {
293 Some(value) => {
294 entry.or_insert(TxtRecord {
295 key,
296 value: Some(value.to_vec()),
297 });
298 }
299 None => {
300 entry.or_insert(TxtRecord { key, value: None });
302 }
303 }
304 }
305
306 Self { map }
307 }
308
309 pub fn add_flag(&mut self, key: String) {
311 self.map
312 .insert(key.to_ascii_lowercase(), TxtRecord { key, value: None });
313 }
314
315 pub fn iter(&self) -> impl Iterator<Item = (&str, TxtRecordValue<'_>)> {
317 self.map.iter().map(|(_, rec)| match &rec.value {
318 Some(v) => (rec.key.as_str(), TxtRecordValue::Value(&v)),
319 None => (rec.key.as_str(), TxtRecordValue::NoValue),
320 })
321 }
322
323 pub fn get(&self, key: &str) -> Option<TxtRecordValue<'_>> {
324 self.map
325 .get(&key.to_ascii_lowercase())
326 .map(|rec| match &rec.value {
327 Some(v) => TxtRecordValue::Value(v),
328 None => TxtRecordValue::NoValue,
329 })
330 }
331
332 pub fn is_empty(&self) -> bool {
333 self.map.is_empty()
334 }
335}
336
337impl fmt::Display for TxtRecords {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 for (i, rec) in self.map.values().enumerate() {
340 if i != 0 {
341 f.write_str(" ")?;
342 }
343
344 f.write_str(&rec.key)?;
345 match &rec.value {
346 Some(v) => {
347 f.write_str("=")?;
348 v.escape_ascii().fmt(f)?;
349 }
350 None => {}
351 }
352 }
353 Ok(())
354 }
355}
356
357pub enum TxtRecordValue<'a> {
358 NoValue,
359 Value(&'a [u8]),
360}
361
362impl<'a> fmt::Debug for TxtRecordValue<'a> {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 match self {
365 Self::NoValue => f.write_str("-"),
366 Self::Value(v) => match std::str::from_utf8(v) {
367 Ok(s) => s.fmt(f),
368 Err(_) => {
369 for byte in *v {
370 byte.escape_ascii().fmt(f)?;
371 }
372 Ok(())
373 }
374 },
375 }
376 }
377}