1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3use std::collections::HashSet;
4
5use resource_record_manager::DomainResourceFilter;
6use simple_dns::{rdata::RData, Packet, TYPE};
7
8pub mod conversion_utils;
9
10mod instance_information;
11pub use instance_information::InstanceInformation;
12
13mod network_scope;
14pub use network_scope::NetworkScope;
15
16mod resource_record_manager;
17
18mod simple_mdns_error;
19pub use simple_mdns_error::SimpleMdnsError;
20
21mod socket_helper;
22
23#[cfg(feature = "async-tokio")]
24pub mod async_discovery;
25
26#[cfg(feature = "sync")]
27pub mod sync_discovery;
28
29#[allow(unused)]
30const UNICAST_RESPONSE: bool = cfg!(not(test));
31
32#[allow(unused)]
33pub(crate) fn build_reply<'b>(
34 packet: simple_dns::Packet,
35 resources: &'b resource_record_manager::ResourceRecordManager<'b>,
36) -> Option<(Packet<'b>, bool)> {
37 let mut reply_packet = Packet::new_reply(packet.id());
38
39 let mut unicast_response = false;
40 let mut additional_records = HashSet::new();
41
42 for question in packet.questions.iter() {
45 if question.unicast_response {
46 unicast_response = question.unicast_response
47 }
48
49 for d_resources in resources
51 .get_domain_resources(&question.qname, DomainResourceFilter::authoritative(true))
52 {
53 for answer in d_resources
54 .filter(|r| r.match_qclass(question.qclass) && r.match_qtype(question.qtype))
55 {
56 reply_packet.answers.push(answer.clone());
57
58 if let RData::SRV(srv) = &answer.rdata {
59 let target = resources
60 .get_domain_resources(
61 &srv.target,
62 DomainResourceFilter::authoritative(false),
63 )
64 .flatten()
65 .filter(|r| {
66 (r.match_qtype(TYPE::A.into()) || r.match_qtype(TYPE::AAAA.into()))
67 && r.match_qclass(question.qclass)
68 })
69 .cloned();
70
71 additional_records.extend(target);
72 }
73 }
74 }
75 }
76
77 for additional_record in additional_records {
78 reply_packet.additional_records.push(additional_record);
79 }
80
81 if !reply_packet.answers.is_empty() {
82 Some((reply_packet, unicast_response))
83 } else {
84 None
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use simple_dns::Name;
91 use std::{
92 convert::TryInto,
93 net::{Ipv4Addr, Ipv6Addr},
94 };
95
96 use simple_dns::Question;
97
98 use crate::{
99 build_reply,
100 conversion_utils::{ip_addr_to_resource_record, port_to_srv_record},
101 resource_record_manager::ResourceRecordManager,
102 };
103
104 use super::*;
105
106 fn get_resources() -> ResourceRecordManager<'static> {
107 let mut resources = ResourceRecordManager::new();
108 resources.add_authoritative_resource(port_to_srv_record(
109 &Name::new_unchecked("_res1._tcp.com"),
110 8080,
111 0,
112 ));
113 resources.add_authoritative_resource(ip_addr_to_resource_record(
114 &Name::new_unchecked("_res1._tcp.com"),
115 Ipv4Addr::LOCALHOST.into(),
116 0,
117 ));
118 resources.add_authoritative_resource(ip_addr_to_resource_record(
119 &Name::new_unchecked("_res1._tcp.com"),
120 Ipv6Addr::LOCALHOST.into(),
121 0,
122 ));
123
124 resources.add_authoritative_resource(port_to_srv_record(
125 &Name::new_unchecked("_res2._tcp.com"),
126 8080,
127 0,
128 ));
129 resources.add_authoritative_resource(ip_addr_to_resource_record(
130 &Name::new_unchecked("_res2._tcp.com"),
131 Ipv4Addr::LOCALHOST.into(),
132 0,
133 ));
134 resources
135 }
136
137 #[test]
138 fn test_build_reply_with_no_questions() {
139 let resources = get_resources();
140
141 let packet = Packet::new_query(1);
142 assert!(build_reply(packet, &resources).is_none());
143 }
144
145 #[test]
146 fn test_build_reply_without_valid_answers() {
147 let resources = get_resources();
148
149 let mut packet = Packet::new_query(1);
150 packet.questions.push(Question::new(
151 "_res3._tcp.com".try_into().unwrap(),
152 simple_dns::QTYPE::ANY,
153 simple_dns::QCLASS::ANY,
154 false,
155 ));
156
157 assert!(build_reply(packet, &resources).is_none());
158 }
159
160 #[test]
161 fn test_build_reply_with_valid_answer() {
162 let resources = get_resources();
163
164 let mut packet = Packet::new_query(1);
165 packet.questions.push(Question::new(
166 "_res1._tcp.com".try_into().unwrap(),
167 simple_dns::TYPE::A.into(),
168 simple_dns::QCLASS::ANY,
169 true,
170 ));
171
172 let (reply, unicast_response) = build_reply(packet, &resources).unwrap();
173
174 assert!(unicast_response);
175 assert_eq!(1, reply.answers.len());
176 assert_eq!(0, reply.additional_records.len());
177 }
178
179 #[test]
180 fn test_build_reply_for_srv() {
181 let resources = get_resources();
182
183 let mut packet = Packet::new_query(1);
184 packet.questions.push(Question::new(
185 "_res1._tcp.com".try_into().unwrap(),
186 simple_dns::TYPE::SRV.into(),
187 simple_dns::QCLASS::ANY,
188 false,
189 ));
190
191 let (reply, unicast_response) = build_reply(packet, &resources).unwrap();
192
193 assert!(!unicast_response);
194 assert_eq!(1, reply.answers.len());
195 assert_eq!(2, reply.additional_records.len());
196 }
197}