hickory_proto/op/
query.rs

1/*
2 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Query struct for looking up resource records
18
19use std::fmt;
20use std::fmt::{Display, Formatter};
21
22use crate::error::*;
23use crate::rr::dns_class::DNSClass;
24use crate::rr::domain::Name;
25use crate::rr::record_type::RecordType;
26use crate::serialize::binary::*;
27
28#[cfg(feature = "mdns")]
29/// From [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4)
30/// ```text
31// To avoid large floods of potentially unnecessary responses in these
32// cases, Multicast DNS defines the top bit in the class field of a DNS
33// question as the unicast-response bit.
34/// ```
35const MDNS_UNICAST_RESPONSE: u16 = 1 << 15;
36
37/// Query struct for looking up resource records, basically a resource record without RDATA.
38///
39/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
40///
41/// ```text
42/// 4.1.2. Question section format
43///
44/// The question section is used to carry the "question" in most queries,
45/// i.e., the parameters that define what is being asked.  The section
46/// contains QDCOUNT (usually 1) entries, each of the following format:
47///
48///                                     1  1  1  1  1  1
49///       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
50///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
51///     |                                               |
52///     /                     QNAME / ZNAME             /
53///     /                                               /
54///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
55///     |                     QTYPE / ZTYPE             |
56///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
57///     |                     QCLASS / ZCLASS           |
58///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
59///
60/// ```
61#[derive(Clone, Debug, Eq, Hash, PartialEq)]
62pub struct Query {
63    name: Name,
64    query_type: RecordType,
65    query_class: DNSClass,
66    #[cfg(feature = "mdns")]
67    mdns_unicast_response: bool,
68}
69
70impl Default for Query {
71    /// Return a default query with an empty name and A, IN for the query_type and query_class
72    fn default() -> Self {
73        Self {
74            name: Name::new(),
75            query_type: RecordType::A,
76            query_class: DNSClass::IN,
77            #[cfg(feature = "mdns")]
78            mdns_unicast_response: false,
79        }
80    }
81}
82
83impl Query {
84    /// Return a default query with an empty name and A, IN for the query_type and query_class
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// Create a new query from name and type, class defaults to IN
90    #[allow(clippy::self_named_constructors)]
91    pub fn query(name: Name, query_type: RecordType) -> Self {
92        Self {
93            name,
94            query_type,
95            query_class: DNSClass::IN,
96            #[cfg(feature = "mdns")]
97            mdns_unicast_response: false,
98        }
99    }
100
101    /// replaces name with the new name
102    pub fn set_name(&mut self, name: Name) -> &mut Self {
103        self.name = name;
104        self
105    }
106
107    /// Specify the RecordType being queried
108    pub fn set_query_type(&mut self, query_type: RecordType) -> &mut Self {
109        self.query_type = query_type;
110        self
111    }
112
113    /// Specify÷ the DNS class of the Query, almost always IN
114    pub fn set_query_class(&mut self, query_class: DNSClass) -> &mut Self {
115        self.query_class = query_class;
116        self
117    }
118
119    /// Changes mDNS unicast-response bit
120    /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4)
121    #[cfg(feature = "mdns")]
122    #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
123    pub fn set_mdns_unicast_response(&mut self, flag: bool) -> &mut Self {
124        self.mdns_unicast_response = flag;
125        self
126    }
127
128    /// ```text
129    /// QNAME           a domain name represented as a sequence of labels, where
130    ///                 each label consists of a length octet followed by that
131    ///                 number of octets.  The domain name terminates with the
132    ///                 zero length octet for the null label of the root.  Note
133    ///                 that this field may be an odd number of octets; no
134    ///                 padding is used.
135    /// ```
136    pub fn name(&self) -> &Name {
137        &self.name
138    }
139
140    /// ```text
141    /// QTYPE           a two octet code which specifies the type of the query.
142    ///                 The values for this field include all codes valid for a
143    ///                 TYPE field, together with some more general codes which
144    ///                 can match more than one type of RR.
145    /// ```
146    pub fn query_type(&self) -> RecordType {
147        self.query_type
148    }
149
150    /// ```text
151    /// QCLASS          a two octet code that specifies the class of the query.
152    ///                 For example, the QCLASS field is IN for the Internet.
153    /// ```
154    pub fn query_class(&self) -> DNSClass {
155        self.query_class
156    }
157
158    /// Returns if the mDNS unicast-response bit is set or not
159    /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4)
160    #[cfg(feature = "mdns")]
161    #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
162    pub fn mdns_unicast_response(&self) -> bool {
163        self.mdns_unicast_response
164    }
165
166    /// Consumes `Query` and returns it's components
167    pub fn into_parts(self) -> QueryParts {
168        self.into()
169    }
170}
171
172/// Consumes `Query` giving public access to fields of `Query` so they can
173/// be destructured and taken by value.
174#[derive(Clone, Debug, Eq, Hash, PartialEq)]
175pub struct QueryParts {
176    /// QNAME
177    pub name: Name,
178    /// QTYPE
179    pub query_type: RecordType,
180    /// QCLASS
181    pub query_class: DNSClass,
182    /// mDNS unicast-response bit set or not
183    #[cfg(feature = "mdns")]
184    #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
185    pub mdns_unicast_response: bool,
186}
187
188impl From<Query> for QueryParts {
189    fn from(q: Query) -> Self {
190        cfg_if::cfg_if! {
191            if #[cfg(feature = "mdns")] {
192                let Query {
193                    name,
194                    query_type,
195                    query_class,
196                    mdns_unicast_response,
197                } = q;
198            } else {
199                let Query {
200                    name,
201                    query_type,
202                    query_class,
203                } = q;
204            }
205        }
206
207        Self {
208            name,
209            query_type,
210            query_class,
211            #[cfg(feature = "mdns")]
212            mdns_unicast_response,
213        }
214    }
215}
216
217impl BinEncodable for Query {
218    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
219        self.name.emit(encoder)?;
220        self.query_type.emit(encoder)?;
221
222        #[cfg(not(feature = "mdns"))]
223        self.query_class.emit(encoder)?;
224
225        #[cfg(feature = "mdns")]
226        {
227            if self.mdns_unicast_response {
228                encoder.emit_u16(u16::from(self.query_class()) | MDNS_UNICAST_RESPONSE)?;
229            } else {
230                self.query_class.emit(encoder)?;
231            }
232        }
233
234        Ok(())
235    }
236}
237
238impl<'r> BinDecodable<'r> for Query {
239    fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
240        let name = Name::read(decoder)?;
241        let query_type = RecordType::read(decoder)?;
242
243        #[cfg(feature = "mdns")]
244        let mut mdns_unicast_response = false;
245
246        #[cfg(not(feature = "mdns"))]
247        let query_class = DNSClass::read(decoder)?;
248
249        #[cfg(feature = "mdns")]
250        let query_class = {
251            let query_class_value =
252                decoder.read_u16()?.unverified(/*DNSClass::from_u16 will verify the value*/);
253            if query_class_value & MDNS_UNICAST_RESPONSE > 0 {
254                mdns_unicast_response = true;
255                DNSClass::from(query_class_value & !MDNS_UNICAST_RESPONSE)
256            } else {
257                DNSClass::from(query_class_value)
258            }
259        };
260
261        Ok(Self {
262            name,
263            query_type,
264            query_class,
265            #[cfg(feature = "mdns")]
266            mdns_unicast_response,
267        })
268    }
269}
270
271impl Display for Query {
272    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
273        #[cfg(not(feature = "mdns"))]
274        {
275            write!(
276                f,
277                "{name} {class} {ty}",
278                name = self.name,
279                class = self.query_class,
280                ty = self.query_type,
281            )
282        }
283
284        #[cfg(feature = "mdns")]
285        {
286            write!(
287                f,
288                "{name} {class} {ty}; mdns_unicast_response: {mdns}",
289                name = self.name,
290                class = self.query_class,
291                ty = self.query_type,
292                mdns = self.mdns_unicast_response
293            )
294        }
295    }
296}
297
298#[test]
299#[allow(clippy::needless_update)]
300fn test_read_and_emit() {
301    let expect = Query {
302        name: Name::from_ascii("WWW.example.com").unwrap(),
303        query_type: RecordType::AAAA,
304        query_class: DNSClass::IN,
305        ..Query::default()
306    };
307
308    let mut byte_vec: Vec<u8> = Vec::with_capacity(512);
309    {
310        let mut encoder = BinEncoder::new(&mut byte_vec);
311        expect.emit(&mut encoder).unwrap();
312    }
313
314    let mut decoder = BinDecoder::new(&byte_vec);
315    let got = Query::read(&mut decoder).unwrap();
316    assert_eq!(got, expect);
317}
318
319#[cfg(feature = "mdns")]
320#[test]
321fn test_mdns_unicast_response_bit_handling() {
322    const QCLASS_OFFSET: usize = 1 /* empty name */ +
323        std::mem::size_of::<u16>() /* query_type */;
324
325    let mut query = Query::new();
326    query.set_mdns_unicast_response(true);
327
328    let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
329    {
330        let mut encoder = BinEncoder::new(&mut vec_bytes);
331        query.emit(&mut encoder).unwrap();
332
333        let query_class_slice = encoder.slice_of(QCLASS_OFFSET, QCLASS_OFFSET + 2);
334        assert_eq!(query_class_slice, &[0x80, 0x01]);
335    }
336
337    let mut decoder = BinDecoder::new(&vec_bytes);
338
339    let got = Query::read(&mut decoder).unwrap();
340
341    assert_eq!(got.query_class(), DNSClass::IN);
342    assert!(got.mdns_unicast_response());
343}