Skip to main content

mdns_proto/wire/
question.rs

1//! DNS question record (RFC 1035 §4.1.2).
2
3use super::{NameRef, ResourceClass, ResourceType, resource_class::UNICAST_RESPONSE_BIT};
4use crate::error::{BufferTooShortDetail, ParseError};
5
6/// A parsed DNS question (zero-copy reference into a message buffer).
7#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
8pub struct QuestionRef<'a> {
9  qname: NameRef<'a>,
10  qtype: ResourceType,
11  qclass: ResourceClass,
12  unicast_response: bool,
13}
14
15impl<'a> QuestionRef<'a> {
16  /// Parse a question starting at `offset` in `message`.
17  /// Returns the question and the next offset to continue parsing from.
18  pub fn try_parse(message: &'a [u8], offset: usize) -> Result<(Self, usize), ParseError> {
19    let (qname, name_bytes) = NameRef::try_parse(message, offset)?;
20    let after_name = offset.saturating_add(name_bytes);
21
22    let qtype_slot = message
23      .get(after_name..after_name.saturating_add(2))
24      .and_then(|s| s.first_chunk::<2>())
25      .ok_or_else(|| {
26        ParseError::BufferTooShort(BufferTooShortDetail::new(
27          2,
28          after_name,
29          message.len().saturating_sub(after_name),
30        ))
31      })?;
32    let qtype = ResourceType::from_u16(u16::from_be_bytes(*qtype_slot));
33
34    let after_type = after_name.saturating_add(2);
35    let qclass_slot = message
36      .get(after_type..after_type.saturating_add(2))
37      .and_then(|s| s.first_chunk::<2>())
38      .ok_or_else(|| {
39        ParseError::BufferTooShort(BufferTooShortDetail::new(
40          2,
41          after_type,
42          message.len().saturating_sub(after_type),
43        ))
44      })?;
45    let raw = u16::from_be_bytes(*qclass_slot);
46    let unicast_response = (raw & UNICAST_RESPONSE_BIT) != 0;
47    let qclass = ResourceClass::from_u16(raw);
48
49    let after_class = after_type.saturating_add(2);
50    Ok((
51      Self {
52        qname,
53        qtype,
54        qclass,
55        unicast_response,
56      },
57      after_class,
58    ))
59  }
60
61  /// Returns the question's name.
62  #[inline(always)]
63  pub const fn qname(&self) -> &NameRef<'a> {
64    &self.qname
65  }
66
67  /// Returns the question type.
68  #[inline(always)]
69  pub const fn qtype(&self) -> ResourceType {
70    self.qtype
71  }
72
73  /// Returns the question class (with unicast-response bit stripped).
74  #[inline(always)]
75  pub const fn qclass(&self) -> ResourceClass {
76    self.qclass
77  }
78
79  /// `true` if the unicast-response bit (RFC 6762 §5.4) was set on this
80  /// question's class field.
81  #[inline(always)]
82  pub const fn unicast_response_requested(&self) -> bool {
83    self.unicast_response
84  }
85}
86
87#[cfg(test)]
88#[cfg(any(feature = "alloc", feature = "std"))]
89#[allow(clippy::unwrap_used)]
90mod tests;