domain/base/
question.rs

1//! A single question in a DNS message.
2//!
3//! This module defines the type `Question` which represents an entry in
4//! the question section of a DNS message and the `ComposeQuestion` trait for
5//! producing questions on the fly.
6
7use super::cmp::CanonicalOrd;
8use super::iana::{Class, Rtype};
9use super::name::{ParsedDname, ToDname};
10use super::wire::{Composer, ParseError};
11use core::cmp::Ordering;
12use core::{fmt, hash};
13use octseq::octets::{Octets, OctetsFrom};
14use octseq::parse::Parser;
15
16//------------ Question ------------------------------------------------------
17
18/// A question in a DNS message.
19///
20/// In DNS, a question describes what is requested in a query. It consists
21/// of three elements: a domain name, a record type, and a class. This type
22/// represents such a question.
23///
24/// Questions are generic over the domain name type. When read from an
25/// actual message, a [`ParsedDname`] has to be used because the name part
26/// may be compressed.
27///
28/// [`ParsedDname`]: ../name/struct.ParsedDname.html
29/// [`MessageBuilder`]: ../message_builder/struct.MessageBuilder.html
30#[derive(Clone, Copy)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Question<N> {
33    /// The domain name of the question.
34    qname: N,
35
36    /// The record type of the question.
37    qtype: Rtype,
38
39    /// The class of the quesiton.
40    qclass: Class,
41}
42
43/// # Creation and Conversion
44///
45impl<N> Question<N> {
46    /// Creates a new question from its three componets.
47    pub fn new(qname: N, qtype: Rtype, qclass: Class) -> Self {
48        Question {
49            qname,
50            qtype,
51            qclass,
52        }
53    }
54
55    /// Creates a new question from a name and record type, assuming class IN.
56    pub fn new_in(qname: N, qtype: Rtype) -> Self {
57        Question {
58            qname,
59            qtype,
60            qclass: Class::In,
61        }
62    }
63
64    /// Converts the question into the qname.
65    pub fn into_qname(self) -> N {
66        self.qname
67    }
68}
69
70/// # Field Access
71///
72impl<N: ToDname> Question<N> {
73    /// Returns a reference to the domain nmae in the question,
74    pub fn qname(&self) -> &N {
75        &self.qname
76    }
77
78    /// Returns the record type of the question.
79    pub fn qtype(&self) -> Rtype {
80        self.qtype
81    }
82
83    /// Returns the class of the question.
84    pub fn qclass(&self) -> Class {
85        self.qclass
86    }
87}
88
89/// # Parsing and Composing
90///
91impl<Octs> Question<ParsedDname<Octs>> {
92    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
93        parser: &mut Parser<'a, Src>,
94    ) -> Result<Self, ParseError> {
95        Ok(Question::new(
96            ParsedDname::parse(parser)?,
97            Rtype::parse(parser)?,
98            Class::parse(parser)?,
99        ))
100    }
101}
102
103impl<N: ToDname> Question<N> {
104    pub fn compose<Target: Composer + ?Sized>(
105        &self,
106        target: &mut Target,
107    ) -> Result<(), Target::AppendError> {
108        target.append_compressed_dname(&self.qname)?;
109        self.qtype.compose(target)?;
110        self.qclass.compose(target)
111    }
112}
113
114//--- From
115
116impl<N: ToDname> From<(N, Rtype, Class)> for Question<N> {
117    fn from((name, rtype, class): (N, Rtype, Class)) -> Self {
118        Question::new(name, rtype, class)
119    }
120}
121
122impl<N: ToDname> From<(N, Rtype)> for Question<N> {
123    fn from((name, rtype): (N, Rtype)) -> Self {
124        Question::new(name, rtype, Class::In)
125    }
126}
127
128//--- OctetsFrom
129
130impl<Name, SrcName> OctetsFrom<Question<SrcName>> for Question<Name>
131where
132    Name: OctetsFrom<SrcName>,
133{
134    type Error = Name::Error;
135
136    fn try_octets_from(
137        source: Question<SrcName>,
138    ) -> Result<Self, Self::Error> {
139        Ok(Question::new(
140            Name::try_octets_from(source.qname)?,
141            source.qtype,
142            source.qclass,
143        ))
144    }
145}
146
147//--- PartialEq and Eq
148
149impl<N, NN> PartialEq<Question<NN>> for Question<N>
150where
151    N: ToDname,
152    NN: ToDname,
153{
154    fn eq(&self, other: &Question<NN>) -> bool {
155        self.qname.name_eq(&other.qname)
156            && self.qtype == other.qtype
157            && self.qclass == other.qclass
158    }
159}
160
161impl<N: ToDname> Eq for Question<N> {}
162
163//--- PartialOrd, CanonicalOrd, and Ord
164
165impl<N, NN> PartialOrd<Question<NN>> for Question<N>
166where
167    N: ToDname,
168    NN: ToDname,
169{
170    fn partial_cmp(&self, other: &Question<NN>) -> Option<Ordering> {
171        match self.qname.name_cmp(&other.qname) {
172            Ordering::Equal => {}
173            other => return Some(other),
174        }
175        match self.qtype.partial_cmp(&other.qtype) {
176            Some(Ordering::Equal) => {}
177            other => return other,
178        }
179        self.qclass.partial_cmp(&other.qclass)
180    }
181}
182
183impl<N, NN> CanonicalOrd<Question<NN>> for Question<N>
184where
185    N: ToDname,
186    NN: ToDname,
187{
188    fn canonical_cmp(&self, other: &Question<NN>) -> Ordering {
189        match self.qname.lowercase_composed_cmp(&other.qname) {
190            Ordering::Equal => {}
191            other => return other,
192        }
193        match self.qtype.cmp(&other.qtype) {
194            Ordering::Equal => {}
195            other => return other,
196        }
197        self.qclass.cmp(&other.qclass)
198    }
199}
200
201impl<N: ToDname> Ord for Question<N> {
202    fn cmp(&self, other: &Self) -> Ordering {
203        match self.qname.name_cmp(&other.qname) {
204            Ordering::Equal => {}
205            other => return other,
206        }
207        match self.qtype.cmp(&other.qtype) {
208            Ordering::Equal => {}
209            other => return other,
210        }
211        self.qclass.cmp(&other.qclass)
212    }
213}
214
215//--- Hash
216
217impl<N: hash::Hash> hash::Hash for Question<N> {
218    fn hash<H: hash::Hasher>(&self, state: &mut H) {
219        self.qname.hash(state);
220        self.qtype.hash(state);
221        self.qclass.hash(state);
222    }
223}
224
225//--- Display and Debug
226
227impl<N: fmt::Display> fmt::Display for Question<N> {
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        write!(f, "{}.\t{}\t{}", self.qname, self.qtype, self.qclass)
230    }
231}
232
233impl<N: fmt::Debug> fmt::Debug for Question<N> {
234    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
235        f.debug_struct("Question")
236            .field("qname", &self.qname)
237            .field("qtype", &self.qtype)
238            .field("qclass", &self.qclass)
239            .finish()
240    }
241}
242
243//------------ ComposeQuestion -----------------------------------------------
244
245/// A helper trait allowing construction of questions on the fly.
246///
247/// The trait’s primary user is the [`QuestionBuilder`] type of the message
248/// builder system. It’s [`push`] method accepts anything that implements
249/// this trait.
250///
251/// Implementations are provided for [`Question`] values and references. In
252/// addition, a tuple of a domain name, record type and class can be used as
253/// this trait, saving the detour of constructing a question first. Since
254/// the class is pretty much always `Class::In`, a tuple of just a domain
255/// name and record type works as well by assuming that class.
256///
257/// [`Class::In`]: ../iana/class/enum.Class.html#variant.In
258/// [`Question`]: struct.Question.html
259/// [`QuestionBuilder`]: ../message_builder/struct.QuestionBuilder.html
260/// [`push`]: ../message_builder/struct.QuestionBuilder.html#method.push
261pub trait ComposeQuestion {
262    fn compose_question<Target: Composer + ?Sized>(
263        &self,
264        target: &mut Target,
265    ) -> Result<(), Target::AppendError>;
266}
267
268impl<'a, Q: ComposeQuestion> ComposeQuestion for &'a Q {
269    fn compose_question<Target: Composer + ?Sized>(
270        &self,
271        target: &mut Target,
272    ) -> Result<(), Target::AppendError> {
273        (*self).compose_question(target)
274    }
275}
276
277impl<Name: ToDname> ComposeQuestion for Question<Name> {
278    fn compose_question<Target: Composer + ?Sized>(
279        &self,
280        target: &mut Target,
281    ) -> Result<(), Target::AppendError> {
282        self.compose(target)
283    }
284}
285
286impl<Name: ToDname> ComposeQuestion for (Name, Rtype, Class) {
287    fn compose_question<Target: Composer + ?Sized>(
288        &self,
289        target: &mut Target,
290    ) -> Result<(), Target::AppendError> {
291        Question::new(&self.0, self.1, self.2).compose(target)
292    }
293}
294
295impl<Name: ToDname> ComposeQuestion for (Name, Rtype) {
296    fn compose_question<Target: Composer + ?Sized>(
297        &self,
298        target: &mut Target,
299    ) -> Result<(), Target::AppendError> {
300        Question::new(&self.0, self.1, Class::In).compose(target)
301    }
302}