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