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;
10use super::name::{ParsedName, ToName};
11use super::wire::{Composer, ParseError};
12use core::cmp::Ordering;
13use core::str::FromStr;
14use core::{fmt, hash};
15use octseq::builder::ShortBuf;
16use octseq::octets::{Octets, OctetsFrom};
17use octseq::parse::Parser;
18
19//------------ Question ------------------------------------------------------
20
21/// A question in a DNS message.
22///
23/// In DNS, a question describes what is requested in a query. It consists
24/// of three elements: a domain name, a record type, and a class. This type
25/// represents such a question.
26///
27/// Questions are generic over the domain name type. When read from an
28/// actual message, a [`ParsedName`] has to be used because the name part
29/// may be compressed.
30///
31/// [`ParsedName`]: ../name/struct.ParsedName.html
32/// [`MessageBuilder`]: ../message_builder/struct.MessageBuilder.html
33#[derive(Clone, Copy)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub struct Question<N> {
36    /// The domain name of the question.
37    qname: N,
38
39    /// The record type of the question.
40    qtype: Rtype,
41
42    /// The class of the quesiton.
43    qclass: Class,
44}
45
46/// # Creation and Conversion
47///
48impl<N> Question<N> {
49    /// Creates a new question from its three componets.
50    pub fn new(qname: N, qtype: Rtype, qclass: Class) -> Self {
51        Question {
52            qname,
53            qtype,
54            qclass,
55        }
56    }
57
58    /// Creates a new question from a name and record type, assuming class IN.
59    pub fn new_in(qname: N, qtype: Rtype) -> Self {
60        Question {
61            qname,
62            qtype,
63            qclass: Class::IN,
64        }
65    }
66
67    /// Converts the question into the qname.
68    pub fn into_qname(self) -> N {
69        self.qname
70    }
71}
72
73/// # Field Access
74///
75impl<N: ToName> Question<N> {
76    /// Returns a reference to the domain nmae in the question,
77    pub fn qname(&self) -> &N {
78        &self.qname
79    }
80
81    /// Returns the record type of the question.
82    pub fn qtype(&self) -> Rtype {
83        self.qtype
84    }
85
86    /// Returns the class of the question.
87    pub fn qclass(&self) -> Class {
88        self.qclass
89    }
90}
91
92/// # Parsing and Composing
93///
94impl<Octs> Question<ParsedName<Octs>> {
95    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
96        parser: &mut Parser<'a, Src>,
97    ) -> Result<Self, ParseError> {
98        Ok(Question::new(
99            ParsedName::parse(parser)?,
100            Rtype::parse(parser)?,
101            Class::parse(parser)?,
102        ))
103    }
104}
105
106impl<N: ToName> Question<N> {
107    pub fn compose<Target: Composer + ?Sized>(
108        &self,
109        target: &mut Target,
110    ) -> Result<(), Target::AppendError> {
111        target.append_compressed_name(&self.qname)?;
112        self.qtype.compose(target)?;
113        self.qclass.compose(target)
114    }
115}
116
117//--- From and FromStr
118
119impl<N: ToName> From<(N, Rtype, Class)> for Question<N> {
120    fn from((name, rtype, class): (N, Rtype, Class)) -> Self {
121        Question::new(name, rtype, class)
122    }
123}
124
125impl<N: ToName> From<(N, Rtype)> for Question<N> {
126    fn from((name, rtype): (N, Rtype)) -> Self {
127        Question::new(name, rtype, Class::IN)
128    }
129}
130
131impl<N: FromStr<Err = name::FromStrError>> FromStr for Question<N> {
132    type Err = FromStrError;
133
134    /// Parses a question from a string.
135    ///
136    /// The string should contain a question as the query name, class, and
137    /// query type separated by white space. The query name should be first
138    /// and in the same form as `Name::from_str` requires. The class and
139    /// query type follow the name in either order. If the class is left out,
140    /// it is assumed to be IN.
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        let mut s = s.split_whitespace();
143
144        let qname = match s.next() {
145            Some(qname) => qname,
146            None => return Err(PresentationErrorEnum::MissingQname.into()),
147        };
148        let qname = N::from_str(qname)?;
149        let class_or_qtype = match s.next() {
150            Some(value) => value,
151            None => {
152                return Err(PresentationErrorEnum::MissingClassAndQtype.into())
153            }
154        };
155        let res = match Class::from_str(class_or_qtype) {
156            Ok(class) => {
157                let qtype = match s.next() {
158                    Some(qtype) => qtype,
159                    None => {
160                        return Err(PresentationErrorEnum::MissingQtype.into())
161                    }
162                };
163                match Rtype::from_str(qtype) {
164                    Ok(qtype) => Self::new(qname, qtype, class),
165                    Err(_) => {
166                        return Err(PresentationErrorEnum::BadQtype.into())
167                    }
168                }
169            }
170            Err(_) => {
171                let qtype = match Rtype::from_str(class_or_qtype) {
172                    Ok(qtype) => qtype,
173                    Err(_) => {
174                        return Err(PresentationErrorEnum::BadQtype.into())
175                    }
176                };
177                let class = match s.next() {
178                    Some(class) => class,
179                    None => return Ok(Self::new(qname, qtype, Class::IN)),
180                };
181                match Class::from_str(class) {
182                    Ok(class) => Self::new(qname, qtype, class),
183                    Err(_) => {
184                        return Err(PresentationErrorEnum::BadClass.into())
185                    }
186                }
187            }
188        };
189        if s.next().is_some() {
190            return Err(PresentationErrorEnum::TrailingData.into());
191        }
192        Ok(res)
193    }
194}
195
196//--- OctetsFrom
197
198impl<Name, SrcName> OctetsFrom<Question<SrcName>> for Question<Name>
199where
200    Name: OctetsFrom<SrcName>,
201{
202    type Error = Name::Error;
203
204    fn try_octets_from(
205        source: Question<SrcName>,
206    ) -> Result<Self, Self::Error> {
207        Ok(Question::new(
208            Name::try_octets_from(source.qname)?,
209            source.qtype,
210            source.qclass,
211        ))
212    }
213}
214
215//--- PartialEq and Eq
216
217impl<N, NN> PartialEq<Question<NN>> for Question<N>
218where
219    N: ToName,
220    NN: ToName,
221{
222    fn eq(&self, other: &Question<NN>) -> bool {
223        self.qname.name_eq(&other.qname)
224            && self.qtype == other.qtype
225            && self.qclass == other.qclass
226    }
227}
228
229impl<N: ToName> Eq for Question<N> {}
230
231//--- PartialOrd, CanonicalOrd, and Ord
232
233impl<N, NN> PartialOrd<Question<NN>> for Question<N>
234where
235    N: ToName,
236    NN: ToName,
237{
238    fn partial_cmp(&self, other: &Question<NN>) -> Option<Ordering> {
239        match self.qname.name_cmp(&other.qname) {
240            Ordering::Equal => {}
241            other => return Some(other),
242        }
243        match self.qtype.partial_cmp(&other.qtype) {
244            Some(Ordering::Equal) => {}
245            other => return other,
246        }
247        self.qclass.partial_cmp(&other.qclass)
248    }
249}
250
251impl<N, NN> CanonicalOrd<Question<NN>> for Question<N>
252where
253    N: ToName,
254    NN: ToName,
255{
256    fn canonical_cmp(&self, other: &Question<NN>) -> Ordering {
257        match self.qname.lowercase_composed_cmp(&other.qname) {
258            Ordering::Equal => {}
259            other => return other,
260        }
261        match self.qtype.cmp(&other.qtype) {
262            Ordering::Equal => {}
263            other => return other,
264        }
265        self.qclass.cmp(&other.qclass)
266    }
267}
268
269impl<N: ToName> Ord for Question<N> {
270    fn cmp(&self, other: &Self) -> Ordering {
271        match self.qname.name_cmp(&other.qname) {
272            Ordering::Equal => {}
273            other => return other,
274        }
275        match self.qtype.cmp(&other.qtype) {
276            Ordering::Equal => {}
277            other => return other,
278        }
279        self.qclass.cmp(&other.qclass)
280    }
281}
282
283//--- Hash
284
285impl<N: hash::Hash> hash::Hash for Question<N> {
286    fn hash<H: hash::Hasher>(&self, state: &mut H) {
287        self.qname.hash(state);
288        self.qtype.hash(state);
289        self.qclass.hash(state);
290    }
291}
292
293//--- Display and Debug
294
295impl<N: fmt::Display> fmt::Display for Question<N> {
296    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297        write!(f, "{}.\t{}\t{}", self.qname, self.qtype, self.qclass)
298    }
299}
300
301impl<N: fmt::Debug> fmt::Debug for Question<N> {
302    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
303        f.debug_struct("Question")
304            .field("qname", &self.qname)
305            .field("qtype", &self.qtype)
306            .field("qclass", &self.qclass)
307            .finish()
308    }
309}
310
311//------------ ComposeQuestion -----------------------------------------------
312
313/// A helper trait allowing construction of questions on the fly.
314///
315/// The trait’s primary user is the [`QuestionBuilder`] type of the message
316/// builder system. It’s [`push`] method accepts anything that implements
317/// this trait.
318///
319/// Implementations are provided for [`Question`] values and references. In
320/// addition, a tuple of a domain name, record type and class can be used as
321/// this trait, saving the detour of constructing a question first. Since
322/// the class is pretty much always [`Class::IN`], a tuple of just a domain
323/// name and record type works as well by assuming that class.
324///
325/// [`QuestionBuilder`]: super::message_builder::QuestionBuilder
326/// [`push`]: super::message_builder::QuestionBuilder::push
327pub trait ComposeQuestion {
328    fn compose_question<Target: Composer + ?Sized>(
329        &self,
330        target: &mut Target,
331    ) -> Result<(), Target::AppendError>;
332}
333
334impl<Q: ComposeQuestion> ComposeQuestion for &Q {
335    fn compose_question<Target: Composer + ?Sized>(
336        &self,
337        target: &mut Target,
338    ) -> Result<(), Target::AppendError> {
339        (*self).compose_question(target)
340    }
341}
342
343impl<Name: ToName> ComposeQuestion for Question<Name> {
344    fn compose_question<Target: Composer + ?Sized>(
345        &self,
346        target: &mut Target,
347    ) -> Result<(), Target::AppendError> {
348        self.compose(target)
349    }
350}
351
352impl<Name: ToName> ComposeQuestion for (Name, Rtype, Class) {
353    fn compose_question<Target: Composer + ?Sized>(
354        &self,
355        target: &mut Target,
356    ) -> Result<(), Target::AppendError> {
357        Question::new(&self.0, self.1, self.2).compose(target)
358    }
359}
360
361impl<Name: ToName> ComposeQuestion for (Name, Rtype) {
362    fn compose_question<Target: Composer + ?Sized>(
363        &self,
364        target: &mut Target,
365    ) -> Result<(), Target::AppendError> {
366        Question::new(&self.0, self.1, Class::IN).compose(target)
367    }
368}
369
370//------------ FromStrError --------------------------------------------------
371
372#[derive(Clone, Copy, Debug, Eq, PartialEq)]
373pub enum FromStrError {
374    /// The string content was wrongly formatted.
375    Presentation(PresentationError),
376
377    /// The buffer is too short to contain the name.
378    ShortBuf,
379}
380
381//--- From
382
383impl From<name::FromStrError> for FromStrError {
384    fn from(err: name::FromStrError) -> FromStrError {
385        match err {
386            name::FromStrError::Presentation(err) => {
387                Self::Presentation(err.into())
388            }
389            name::FromStrError::ShortBuf => Self::ShortBuf,
390        }
391    }
392}
393
394impl From<PresentationErrorEnum> for FromStrError {
395    fn from(err: PresentationErrorEnum) -> Self {
396        Self::Presentation(err.into())
397    }
398}
399
400//--- Display and Error
401
402impl fmt::Display for FromStrError {
403    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
404        match *self {
405            FromStrError::Presentation(err) => err.fmt(f),
406            FromStrError::ShortBuf => ShortBuf.fmt(f),
407        }
408    }
409}
410
411#[cfg(feature = "std")]
412impl std::error::Error for FromStrError {}
413
414//------------ PresentationError ---------------------------------------------
415
416/// An illegal presentation format was encountered.
417#[derive(Clone, Copy, Debug, Eq, PartialEq)]
418pub struct PresentationError(PresentationErrorEnum);
419
420#[derive(Clone, Copy, Debug, Eq, PartialEq)]
421enum PresentationErrorEnum {
422    BadName(name::PresentationError),
423    MissingQname,
424    MissingClassAndQtype,
425    MissingQtype,
426    BadClass,
427    BadQtype,
428    TrailingData,
429}
430
431//--- From
432
433impl From<PresentationErrorEnum> for PresentationError {
434    fn from(err: PresentationErrorEnum) -> Self {
435        Self(err)
436    }
437}
438
439impl From<name::PresentationError> for PresentationError {
440    fn from(err: name::PresentationError) -> Self {
441        Self(PresentationErrorEnum::BadName(err))
442    }
443}
444
445//--- Display and Error
446
447impl fmt::Display for PresentationError {
448    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449        match self.0 {
450            PresentationErrorEnum::BadName(err) => err.fmt(f),
451            PresentationErrorEnum::MissingQname => {
452                f.write_str("missing qname")
453            }
454            PresentationErrorEnum::MissingClassAndQtype => {
455                f.write_str("missing class and qtype")
456            }
457            PresentationErrorEnum::MissingQtype => {
458                f.write_str("missing qtype")
459            }
460            PresentationErrorEnum::BadClass => f.write_str("invalid class"),
461            PresentationErrorEnum::BadQtype => f.write_str("invalid qtype"),
462            PresentationErrorEnum::TrailingData => {
463                f.write_str("trailing data")
464            }
465        }
466    }
467}
468
469#[cfg(feature = "std")]
470impl std::error::Error for PresentationError {}