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