1use crate::service::ServiceInfo;
4use crate::error::{DiscoveryError, Result};
5use serde::{Deserialize, Serialize};
6use std::{
7 collections::HashMap,
8 fmt,
9 net::{IpAddr, Ipv4Addr, Ipv6Addr},
10 str::FromStr,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct ServiceType {
16 service_name: String,
18 protocol: String,
20 domain: Option<String>,
22}
23
24impl ServiceType {
25 pub fn new<S: Into<String>>(service: S) -> Result<Self> {
27 let service_type_str = service.into();
28
29 if service_type_str.is_empty() {
30 return Err(DiscoveryError::invalid_service("Service type cannot be empty"));
31 }
32
33 if service_type_str.starts_with("urn:") {
35 return Ok(ServiceType {
36 service_name: service_type_str.clone(),
37 protocol: "".to_string(), domain: None,
39 });
40 }
41
42 let parts: Vec<&str> = service_type_str.split('.').collect();
44
45 if parts.len() < 2 {
46 return Err(DiscoveryError::invalid_service(
47 "Service type must contain protocol (e.g., '._tcp')",
48 ));
49 }
50
51 let service_name = parts[0].to_string();
53
54 let protocol_part = parts[1];
56 if !protocol_part.starts_with('_') {
57 return Err(DiscoveryError::invalid_service(
58 "Service type must contain protocol (e.g., '._tcp')",
59 ));
60 }
61 let protocol = format!(".{}", protocol_part);
62
63 let domain = if parts.len() > 2 {
65 Some(parts[2..].join("."))
66 } else {
67 None
68 };
69
70 let final_service_name = if service_name.starts_with('_') {
72 service_name
73 } else {
74 format!("_{}", service_name)
75 };
76
77 Ok(ServiceType {
78 service_name: final_service_name,
79 protocol,
80 domain,
81 })
82 }
83
84 pub fn with_protocol<S: Into<String>>(service: S, protocol: S) -> Result<Self> {
86 let mut protocol_str = protocol.into();
87 if !protocol_str.starts_with('_') {
88 protocol_str = format!("_{}", protocol_str);
89 }
90
91 Ok(ServiceType {
92 service_name: service.into(),
93 protocol: protocol_str,
94 domain: None,
95 })
96 }
97
98 pub fn with_domain<S: Into<String>>(service: S, domain: S) -> Result<Self> {
100 Ok(ServiceType {
101 service_name: service.into(),
102 protocol: "_tcp".to_string(),
103 domain: Some(domain.into()),
104 })
105 }
106
107 pub fn service_name(&self) -> &str {
109 &self.service_name
110 }
111
112 pub fn protocol(&self) -> &str {
114 &self.protocol
115 }
116
117 pub fn domain(&self) -> Option<&str> {
119 self.domain.as_deref()
120 }
121
122 pub fn full_name(&self) -> String {
124 if self.service_name.starts_with("urn:") {
126 return self.service_name.clone();
127 }
128
129 match &self.domain {
130 None => format!("{}_{}", self.service_name, self.protocol),
131 Some(domain) => format!("{}_{}.{}", self.service_name, self.protocol, domain),
132 }
133 }
134
135 pub fn to_string(&self) -> String {
137 let mut result = self.service_name.clone();
138 result.push_str(&self.protocol);
139 if let Some(domain) = &self.domain {
140 result.push('.');
141 result.push_str(domain);
142 }
143 result
144 }
145
146 pub fn is_valid(&self) -> bool {
148 !self.service_name.is_empty() && !self.protocol.is_empty()
149 }
150}
151
152impl FromStr for ServiceType {
153 type Err = DiscoveryError;
154
155 fn from_str(s: &str) -> Result<Self> {
156 ServiceType::new(s)
157 }
158}
159
160impl fmt::Display for ServiceType {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 if let Some(domain) = &self.domain {
163 write!(
164 f,
165 "{}{}.{}",
166 self.service_name, self.protocol, domain
167 )
168 } else {
169 write!(f, "{}{}", self.service_name, self.protocol)
170 }
171 }
172}
173
174impl From<ServiceType> for String {
175 fn from(service_type: ServiceType) -> Self {
176 service_type.to_string()
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
182pub enum ProtocolType {
183 Mdns,
185 DnsSd,
187 Upnp,
189}
190
191impl Default for ProtocolType {
192 fn default() -> Self {
193 ProtocolType::Mdns
194 }
195}
196
197impl fmt::Display for ProtocolType {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 ProtocolType::Mdns => write!(f, "mDNS"),
201 ProtocolType::DnsSd => write!(f, "DNS-SD"),
202 ProtocolType::Upnp => write!(f, "UPnP"),
203 }
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
209pub struct NetworkInterface {
210 pub name: String,
212 pub ipv4_addresses: Vec<Ipv4Addr>,
214 pub ipv6_addresses: Vec<Ipv6Addr>,
216 pub is_up: bool,
218 pub supports_multicast: bool,
220}
221
222impl NetworkInterface {
223 pub fn new<S: Into<String>>(name: S) -> Self {
225 Self {
226 name: name.into(),
227 ipv4_addresses: Vec::new(),
228 ipv6_addresses: Vec::new(),
229 is_up: false,
230 supports_multicast: false,
231 }
232 }
233
234 pub fn with_ipv4(mut self, addr: Ipv4Addr) -> Self {
236 self.ipv4_addresses.push(addr);
237 self
238 }
239
240 pub fn with_ipv6(mut self, addr: Ipv6Addr) -> Self {
242 self.ipv6_addresses.push(addr);
243 self
244 }
245
246 pub fn with_status(mut self, is_up: bool, supports_multicast: bool) -> Self {
248 self.is_up = is_up;
249 self.supports_multicast = supports_multicast;
250 self
251 }
252
253 pub fn all_addresses(&self) -> Vec<IpAddr> {
255 let mut addresses = Vec::new();
256 addresses.extend(self.ipv4_addresses.iter().map(|&addr| IpAddr::V4(addr)));
257 addresses.extend(self.ipv6_addresses.iter().map(|&addr| IpAddr::V6(addr)));
258 addresses
259 }
260}
261
262pub type ServiceAttributes = HashMap<String, String>;
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct DiscoveryFilter {
268 pub service_type_filters: Vec<ServiceType>,
270 pub protocol_filters: Vec<ProtocolType>,
272 pub attribute_patterns: Vec<(String, String)>,
274}
275
276impl DiscoveryFilter {
277 pub fn new() -> Self {
279 Self {
280 service_type_filters: Vec::new(),
281 protocol_filters: Vec::new(),
282 attribute_patterns: Vec::new(),
283 }
284 }
285
286 pub fn with_service_type(mut self, service_type: ServiceType) -> Self {
288 self.service_type_filters.push(service_type);
289 self
290 }
291
292 pub fn with_protocol(mut self, protocol: ProtocolType) -> Self {
294 self.protocol_filters.push(protocol);
295 self
296 }
297
298 pub fn with_attribute_pattern(mut self, key_pattern: String, value_pattern: String) -> Self {
300 self.attribute_patterns.push((key_pattern, value_pattern));
301 self
302 }
303
304 pub fn matches(&self, service: &ServiceInfo) -> bool {
306 if !self.service_type_filters.is_empty()
308 && !self.service_type_filters.contains(&service.service_type) {
309 return false;
310 }
311
312 if !self.protocol_filters.is_empty()
314 && !self.protocol_filters.contains(&service.protocol_type) {
315 return false;
316 }
317
318 for (key_pattern, value_pattern) in &self.attribute_patterns {
320 let mut matches = false;
321 for (key, value) in &service.attributes {
322 if key.contains(key_pattern) && value.contains(value_pattern) {
324 matches = true;
325 break;
326 }
327 }
328 if !matches {
329 return false;
330 }
331 }
332
333 true
334 }
335}
336
337impl Default for DiscoveryFilter {
338 fn default() -> Self {
339 Self::new()
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use std::time::Duration;
347
348 #[test]
349 fn test_service_type() -> Result<()> {
350 let service = ServiceType::new("_http._tcp")?;
351 assert_eq!(service.service_name, "_http");
352 assert_eq!(service.protocol, "._tcp");
353 assert_eq!(service.domain, None);
354 assert_eq!(service.to_string(), "_http._tcp");
355 Ok(())
356 }
357
358 #[test]
359 fn test_service_type_with_domain() -> Result<()> {
360 let service = ServiceType::new("_http._tcp.local")?;
361 assert_eq!(service.service_name, "_http");
362 assert_eq!(service.protocol, "._tcp");
363 assert_eq!(service.domain, Some("local".to_string()));
364 assert_eq!(service.to_string(), "_http._tcp.local");
365 Ok(())
366 }
367
368 #[test]
369 fn test_invalid_service_type() {
370 assert!(ServiceType::new("").is_err());
371 assert!(ServiceType::new("invalid").is_err());
372 assert!(ServiceType::new("_http").is_err()); }
374
375 #[test]
376 fn test_discovery_filter() -> Result<()> {
377 use std::net::{IpAddr, Ipv4Addr};
378 use crate::service::ServiceInfo;
379
380 let filter = DiscoveryFilter::new()
381 .with_service_type(ServiceType::new("_http._tcp")?);
382
383 let service = ServiceInfo::new(
384 "Test Service",
385 "_http._tcp",
386 8080,
387 Some(vec![("version", "1.0")]),
388 )?;
389
390 assert!(filter.matches(&service));
391 Ok(())
392 }
393
394 #[test]
395 fn test_protocol_type_default() {
396 assert_eq!(ProtocolType::default(), ProtocolType::Mdns);
397 }
398}