1use std::net::{Ipv4Addr, Ipv6Addr};
4use std::time::{Duration, Instant};
5
6use byteorder::{BigEndian, WriteBytesExt};
7
8use crate::mdns;
9
10#[derive(Debug, Clone)]
12pub struct ServiceRegistration {
13 pub service_type: String,
14 pub instance_name: String,
15 pub port: u16,
16 pub hostname: String,
17 pub txt_records: Vec<(String, String)>,
18 pub ttl: u32,
19 pub subtypes: Vec<String>,
20}
21
22#[derive(Debug, Clone)]
24pub enum MdnsEvent {
25 ServiceDiscovered {
26 name: String,
27 target: String,
28 records: Vec<mdns::RR>,
29 },
30 ServiceExpired {
31 name: String,
32 rtype: u16,
33 },
34}
35
36pub(super) struct PeriodicQuery {
37 pub label: String,
38 pub qtype: u16,
39 pub interval: Duration,
40 pub last_sent: Instant,
41}
42
43pub(super) fn build_service_records(
45 reg: &ServiceRegistration,
46 ips_v4: &[Ipv4Addr],
47 ips_v6: &[Ipv6Addr],
48) -> Vec<mdns::RR> {
49 let mut records = Vec::new();
50 let instance_full = format!("{}.{}", reg.instance_name, reg.service_type);
51
52 records.push(mdns::RR {
54 name: format!("{}.", reg.service_type),
55 typ: mdns::TYPE_PTR,
56 class: 1,
57 ttl: reg.ttl,
58 rdata: {
59 let mut buf = Vec::new();
60 let _ = mdns::encode_label(&instance_full, &mut buf);
61 buf
62 },
63 target: None,
64 data: mdns::RRData::PTR(instance_full.clone()),
65 });
66
67 for sub in ®.subtypes {
69 let subtype_name = format!("{}._sub.{}", sub, reg.service_type);
70 records.push(mdns::RR {
71 name: format!("{}.", subtype_name),
72 typ: mdns::TYPE_PTR,
73 class: 1,
74 ttl: reg.ttl,
75 rdata: {
76 let mut buf = Vec::new();
77 let _ = mdns::encode_label(&instance_full, &mut buf);
78 buf
79 },
80 target: None,
81 data: mdns::RRData::PTR(instance_full.clone()),
82 });
83 }
84
85 let mut srv_rdata = Vec::new();
87 let _ = srv_rdata.write_u16::<BigEndian>(0); let _ = srv_rdata.write_u16::<BigEndian>(0); let _ = srv_rdata.write_u16::<BigEndian>(reg.port);
90 let _ = mdns::encode_label(reg.hostname.trim_end_matches('.'), &mut srv_rdata);
91 records.push(mdns::RR {
92 name: format!("{}.", instance_full),
93 typ: mdns::TYPE_SRV,
94 class: 1,
95 ttl: reg.ttl,
96 rdata: srv_rdata,
97 target: Some(format!("{}.", reg.hostname.trim_end_matches('.'))),
98 data: mdns::RRData::SRV {
99 priority: 0,
100 weight: 0,
101 port: reg.port,
102 target: format!("{}.", reg.hostname.trim_end_matches('.')),
103 },
104 });
105
106 let mut txt_rdata = Vec::new();
108 for (k, v) in ®.txt_records {
109 let entry = format!("{}={}", k, v);
110 let _ = txt_rdata.write_u8(entry.len() as u8);
111 txt_rdata.extend_from_slice(entry.as_bytes());
112 }
113 if txt_rdata.is_empty() {
114 txt_rdata.push(0); }
116 records.push(mdns::RR {
117 name: format!("{}.", instance_full),
118 typ: mdns::TYPE_TXT,
119 class: 1,
120 ttl: reg.ttl,
121 rdata: txt_rdata,
122 target: None,
123 data: mdns::RRData::TXT(
124 reg.txt_records
125 .iter()
126 .map(|(k, v)| format!("{}={}", k, v))
127 .collect(),
128 ),
129 });
130
131
132 for ip in ips_v4 {
133 records.push(mdns::RR {
134 name: format!("{}.", reg.hostname.trim_end_matches('.')),
135 typ: mdns::TYPE_A,
136 class: 1,
137 ttl: reg.ttl,
138 rdata: ip.octets().to_vec(),
139 target: None,
140 data: mdns::RRData::A(*ip),
141 });
142 }
143
144
145 for ip in ips_v6 {
146 records.push(mdns::RR {
147 name: format!("{}.", reg.hostname.trim_end_matches('.')),
148 typ: mdns::TYPE_AAAA,
149 class: 1,
150 ttl: reg.ttl,
151 rdata: ip.octets().to_vec(),
152 target: None,
153 data: mdns::RRData::AAAA(*ip),
154 });
155 }
156
157 records
158}
159
160pub(super) fn find_matching_services(
162 query_name: &str,
163 query_type: u16,
164 services: &[ServiceRegistration],
165 ips_v4: &[Ipv4Addr],
166 ips_v6: &[Ipv6Addr],
167) -> (Vec<mdns::RR>, Vec<mdns::RR>) {
168 let mut answers = Vec::new();
169 let mut additional = Vec::new();
170
171 let qname = query_name.to_lowercase();
172 let qname = qname.trim_end_matches('.');
173
174 for reg in services {
175 let svc_type = reg.service_type.trim_end_matches('.').to_lowercase();
176 let instance_full = format!("{}.{}", reg.instance_name.to_lowercase(), svc_type);
177
178 let all_records = build_service_records(reg, ips_v4, ips_v6);
179 let is_any = query_type == mdns::QTYPE_ANY;
180
181 let is_subtype_match = reg.subtypes.iter().any(|sub| {
183 let subtype_name = format!("{}._sub.{}", sub.to_lowercase(), svc_type);
184 qname == subtype_name
185 });
186
187 if qname == svc_type || is_subtype_match {
189 for r in &all_records {
190 let rname = r.name.trim_end_matches('.').to_lowercase();
191 let name_matches = rname == qname || (rname == svc_type && !is_subtype_match);
192 if name_matches && (is_any || r.typ == mdns::TYPE_PTR || r.typ == query_type) {
193 answers.push(r.clone());
194 } else if r.typ != mdns::TYPE_PTR {
195 additional.push(r.clone());
197 }
198 }
199 }
200 else if qname == instance_full {
202 for r in &all_records {
203 let rname = r.name.trim_end_matches('.').to_lowercase();
204 if rname == instance_full && (is_any || r.typ == query_type) {
205 answers.push(r.clone());
206 } else if r.typ == mdns::TYPE_A || r.typ == mdns::TYPE_AAAA {
207 additional.push(r.clone());
208 }
209 }
210 }
211 else if qname == reg.hostname.trim_end_matches('.').to_lowercase() {
213 for r in &all_records {
214 if (r.typ == mdns::TYPE_A || r.typ == mdns::TYPE_AAAA)
215 && (is_any || r.typ == query_type)
216 {
217 answers.push(r.clone());
218 }
219 }
220 }
221 }
222
223 (answers, additional)
224}
225