1use std::{
11 cmp::min,
12 time::{Duration, Instant},
13};
14
15use crate::{
16 cache::MAX_TTL,
17 proto::{
18 op::{Message, OpCode, Query},
19 rr::{RData, Record},
20 },
21};
22
23#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct Lookup {
28 message: Message,
29 valid_until: Instant,
30}
31
32impl Lookup {
33 pub(crate) fn new(message: Message, valid_until: Instant) -> Self {
35 debug_assert!(
36 !message.queries.is_empty(),
37 "lookup message must have at least one query"
38 );
39
40 Self {
41 message,
42 valid_until,
43 }
44 }
45
46 pub fn from_rdata(query: Query, rdata: RData) -> Self {
48 let record = Record::from_rdata(query.name().clone(), MAX_TTL, rdata);
49 Self::new_with_max_ttl(query, [record])
50 }
51
52 pub fn new_with_max_ttl(query: Query, answers: impl IntoIterator<Item = Record>) -> Self {
54 let valid_until = Instant::now() + Duration::from_secs(u64::from(MAX_TTL));
55 Self::new_with_deadline(query, answers, valid_until)
56 }
57
58 pub fn new_with_deadline(
60 query: Query,
61 answers: impl IntoIterator<Item = Record>,
62 valid_until: Instant,
63 ) -> Self {
64 let mut message = Message::response(0, OpCode::Query);
65 message.add_query(query.clone());
66 message.add_answers(answers);
67
68 Self {
69 message,
70 valid_until,
71 }
72 }
73
74 pub fn query(&self) -> &Query {
76 self.message
77 .queries
78 .first()
79 .expect("Lookup message always has a query")
80 }
81
82 pub fn message(&self) -> &Message {
84 &self.message
85 }
86
87 pub fn answers(&self) -> &[Record] {
89 &self.message.answers
90 }
91
92 pub fn authorities(&self) -> &[Record] {
94 &self.message.authorities
95 }
96
97 pub fn additionals(&self) -> &[Record] {
99 &self.message.additionals
100 }
101
102 pub fn valid_until(&self) -> Instant {
104 self.valid_until
105 }
106
107 pub(crate) fn append(&self, other: Self) -> Self {
111 let mut result = self.clone();
113
114 result.message.add_answers(other.answers().iter().cloned());
116 result
117 .message
118 .add_authorities(other.authorities().iter().cloned());
119 result
120 .message
121 .add_additionals(other.additionals().iter().cloned());
122
123 result.valid_until = min(self.valid_until(), other.valid_until());
125 result
126 }
127
128 #[doc(hidden)] pub fn extend_authorities(&mut self, records: impl IntoIterator<Item = Record>) {
130 self.message.add_authorities(records);
131 }
132
133 #[doc(hidden)] pub fn extend_additionals(&mut self, records: impl IntoIterator<Item = Record>) {
135 self.message.add_additionals(records);
136 }
137
138 #[cfg(test)]
142 fn extend_answers(&mut self, other: Vec<Record>) {
143 self.message.add_answers(other);
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use std::str::FromStr;
151
152 use crate::proto::op::Query;
153 use crate::proto::rr::rdata::{A, NS};
154 use crate::proto::rr::{Name, RData, Record, RecordType};
155
156 use super::*;
157
158 #[test]
159 #[cfg(feature = "__dnssec")]
160 fn test_dnssec_lookup() {
161 use hickory_proto::dnssec::Proof;
162
163 let mut a1 = Record::from_rdata(
164 Name::from_str("www.example.com.").unwrap(),
165 80,
166 RData::A(A::new(127, 0, 0, 1)),
167 );
168 a1.proof = Proof::Secure;
169
170 let mut a2 = Record::from_rdata(
171 Name::from_str("www.example.com.").unwrap(),
172 80,
173 RData::A(A::new(127, 0, 0, 2)),
174 );
175 a2.proof = Proof::Insecure;
176
177 let mut message = Message::response(0, OpCode::Query);
178 message.add_query(Query::default());
179 message.add_answers([a1.clone(), a2.clone()]);
180
181 let lookup = Lookup {
182 message,
183 valid_until: Instant::now(),
184 };
185
186 let mut lookup = lookup.message().dnssec_answers();
187
188 assert_eq!(*lookup.next().unwrap().require(Proof::Secure).unwrap(), a1);
189 assert_eq!(
190 *lookup.next().unwrap().require(Proof::Insecure).unwrap(),
191 a2
192 );
193 assert_eq!(lookup.next(), None);
194 }
195
196 #[test]
197 fn test_extend_answers_preserves_sections() {
198 let mut message = Message::response(0, OpCode::Query);
200 let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
201 message.add_query(query.clone());
202
203 message.add_answers(vec![Record::from_rdata(
204 Name::from_str("www.example.com.").unwrap(),
205 80,
206 RData::A(A::new(127, 0, 0, 1)),
207 )]);
208 message.add_authority(Record::from_rdata(
209 Name::from_str("example.com.").unwrap(),
210 80,
211 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
212 ));
213 message.add_additionals(vec![Record::from_rdata(
214 Name::from_str("ns1.example.com.").unwrap(),
215 80,
216 RData::A(A::new(192, 0, 2, 1)),
217 )]);
218
219 let mut lookup = Lookup {
220 message,
221 valid_until: Instant::now(),
222 };
223
224 let new_record = Record::from_rdata(
226 Name::from_str("www.example.com.").unwrap(),
227 80,
228 RData::A(A::new(127, 0, 0, 2)),
229 );
230 lookup.extend_answers(vec![new_record.clone()]);
231
232 assert_eq!(lookup.answers().len(), 2);
234 assert_eq!(lookup.answers()[1], new_record);
235
236 assert_eq!(lookup.authorities().len(), 1);
238 assert_eq!(lookup.additionals().len(), 1);
239
240 if let RData::NS(ns) = &lookup.authorities()[0].data {
242 assert_eq!(ns.0, Name::from_str("ns1.example.com.").unwrap());
243 } else {
244 panic!("Authority record should be NS");
245 }
246
247 if let RData::A(a) = lookup.additionals()[0].data {
248 assert_eq!(a, A::new(192, 0, 2, 1));
249 } else {
250 panic!("Additional record should be A");
251 }
252 }
253
254 #[test]
255 fn test_append_preserves_sections() {
256 let mut message1 = Message::response(0, OpCode::Query);
258 let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
259 message1.add_query(query.clone());
260 message1.add_answers(vec![Record::from_rdata(
261 Name::from_str("www.example.com.").unwrap(),
262 80,
263 RData::A(A::new(127, 0, 0, 1)),
264 )]);
265 message1.add_authority(Record::from_rdata(
266 Name::from_str("example.com.").unwrap(),
267 80,
268 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
269 ));
270 message1.add_additionals(vec![Record::from_rdata(
271 Name::from_str("ns1.example.com.").unwrap(),
272 80,
273 RData::A(A::new(192, 0, 2, 1)),
274 )]);
275
276 let lookup1 = Lookup {
277 message: message1,
278 valid_until: Instant::now(),
279 };
280
281 let mut message2 = Message::response(0, OpCode::Query);
283 message2.add_query(query.clone());
284 message2.add_answers(vec![Record::from_rdata(
285 Name::from_str("www.example.com.").unwrap(),
286 80,
287 RData::A(A::new(127, 0, 0, 2)),
288 )]);
289 message2.add_authority(Record::from_rdata(
290 Name::from_str("example.com.").unwrap(),
291 80,
292 RData::NS(NS(Name::from_str("ns2.example.com.").unwrap())),
293 ));
294 message2.add_additionals(vec![Record::from_rdata(
295 Name::from_str("ns2.example.com.").unwrap(),
296 80,
297 RData::A(A::new(192, 0, 2, 2)),
298 )]);
299
300 let lookup2 = Lookup {
301 message: message2,
302 valid_until: Instant::now(),
303 };
304
305 let combined = lookup1.append(lookup2);
307
308 assert_eq!(combined.answers().len(), 2);
310 assert_eq!(combined.authorities().len(), 2);
311 assert_eq!(combined.additionals().len(), 2);
312
313 if let RData::A(a) = combined.answers()[0].data {
315 assert_eq!(a, A::new(127, 0, 0, 1));
316 } else {
317 panic!("First answer should be A");
318 }
319 if let RData::A(a) = combined.answers()[1].data {
320 assert_eq!(a, A::new(127, 0, 0, 2));
321 } else {
322 panic!("Second answer should be A");
323 }
324
325 if let RData::NS(ns) = &combined.authorities()[0].data {
327 assert_eq!(ns.0, Name::from_str("ns1.example.com.").unwrap());
328 } else {
329 panic!("First authority should be NS");
330 }
331 if let RData::NS(ns) = &combined.authorities()[1].data {
332 assert_eq!(ns.0, Name::from_str("ns2.example.com.").unwrap());
333 } else {
334 panic!("Second authority should be NS");
335 }
336
337 if let RData::A(a) = combined.additionals()[0].data {
339 assert_eq!(a, A::new(192, 0, 2, 1));
340 } else {
341 panic!("First additional should be A");
342 }
343 if let RData::A(a) = combined.additionals()[1].data {
344 assert_eq!(a, A::new(192, 0, 2, 2));
345 } else {
346 panic!("Second additional should be A");
347 }
348 }
349}