Skip to main content

mdns_proto/wire/
reader.rs

1//! Lazy section-iterator view over a parsed mDNS message.
2
3use super::{HEADER_SIZE, Header, QuestionRef, Ref};
4use crate::error::ParseError;
5
6/// Borrowed view over a parsed mDNS message. Lazy iteration; nothing is
7/// allocated.
8#[derive(Debug, Copy, Clone)]
9pub struct MessageReader<'a> {
10  message: &'a [u8],
11  header: Header,
12}
13
14impl<'a> MessageReader<'a> {
15  /// Parses just the header; sections are walked on demand via the
16  /// iterator methods.
17  pub fn try_parse(message: &'a [u8]) -> Result<Self, ParseError> {
18    let (header, _rest) = Header::try_parse(message)?;
19    Ok(Self { message, header })
20  }
21
22  /// Returns the parsed header.
23  #[inline(always)]
24  pub const fn header(&self) -> &Header {
25    &self.header
26  }
27
28  /// Iterator over the question section.
29  pub fn questions(&self) -> Questions<'a> {
30    Questions {
31      message: self.message,
32      cursor: HEADER_SIZE,
33      remaining: self.header.question_count(),
34    }
35  }
36
37  /// Iterator over the answer section.
38  pub fn answers(&self) -> Records<'a> {
39    Records::new_after_questions(self.message, &self.header, self.header.answer_count())
40  }
41
42  /// Iterator over the authority section (NS records, used in mDNS probes).
43  /// Walks past the questions and answer sections to locate authority records.
44  pub fn authority(&self) -> Records<'a> {
45    Records::new_after_answers(self.message, &self.header, self.header.authority_count())
46  }
47
48  /// Iterator over the additional section. Standard DNS-SD responders place
49  /// the SRV/TXT/A/AAAA records that accompany a PTR answer here (RFC 6763
50  /// §12), so a querier MUST process them to complete discovery. Walks past
51  /// the questions, answer, and authority sections.
52  pub fn additional(&self) -> Records<'a> {
53    Records::new_after_authority(self.message, &self.header, self.header.additional_count())
54  }
55}
56
57/// Iterator over `QuestionRef`s in the question section.
58pub struct Questions<'a> {
59  message: &'a [u8],
60  cursor: usize,
61  remaining: u16,
62}
63
64impl<'a> Iterator for Questions<'a> {
65  type Item = Result<QuestionRef<'a>, ParseError>;
66
67  fn next(&mut self) -> Option<Self::Item> {
68    if self.remaining == 0 {
69      return None;
70    }
71    match QuestionRef::try_parse(self.message, self.cursor) {
72      Ok((q, next)) => {
73        self.cursor = next;
74        self.remaining = self.remaining.saturating_sub(1);
75        Some(Ok(q))
76      }
77      Err(e) => {
78        self.remaining = 0;
79        Some(Err(e))
80      }
81    }
82  }
83}
84
85/// Iterator over `Ref`s in a non-question section.
86pub struct Records<'a> {
87  message: &'a [u8],
88  cursor: usize,
89  remaining: u16,
90}
91
92/// Skip the question section, returning the cursor at the start of the answer
93/// section, or `None` if any question fails to parse — a malformed earlier
94/// section means later sections cannot be reliably located.
95fn skip_questions(message: &[u8], header: &Header) -> Option<usize> {
96  let mut cursor = HEADER_SIZE;
97  let mut remaining = header.question_count();
98  while remaining > 0 {
99    let (_, next) = QuestionRef::try_parse(message, cursor).ok()?;
100    cursor = next;
101    remaining = remaining.saturating_sub(1);
102  }
103  Some(cursor)
104}
105
106/// Skip `count` resource records starting at `cursor`, returning the cursor
107/// after them, or `None` if any record fails to parse.
108fn skip_records(message: &[u8], mut cursor: usize, mut count: u16) -> Option<usize> {
109  while count > 0 {
110    let (_, next) = Ref::try_parse(message, cursor).ok()?;
111    cursor = next;
112    count = count.saturating_sub(1);
113  }
114  Some(cursor)
115}
116
117impl<'a> Records<'a> {
118  /// Position at the answer section (after the questions). Used by
119  /// `MessageReader::answers()`.
120  fn new_after_questions(message: &'a [u8], header: &Header, count: u16) -> Self {
121    Self::at_or_empty(message, count, skip_questions(message, header))
122  }
123
124  /// Position at the authority section (after questions + answers). Used by
125  /// `MessageReader::authority()`.
126  fn new_after_answers(message: &'a [u8], header: &Header, count: u16) -> Self {
127    let cursor =
128      skip_questions(message, header).and_then(|c| skip_records(message, c, header.answer_count()));
129    Self::at_or_empty(message, count, cursor)
130  }
131
132  /// Position at the additional section (after questions + answers +
133  /// authority). Used by `MessageReader::additional()`.
134  fn new_after_authority(message: &'a [u8], header: &Header, count: u16) -> Self {
135    let cursor = skip_questions(message, header)
136      .and_then(|c| skip_records(message, c, header.answer_count()))
137      .and_then(|c| skip_records(message, c, header.authority_count()));
138    Self::at_or_empty(message, count, cursor)
139  }
140
141  /// Build a `Records` iterator at `cursor` with `count` records, or an EMPTY
142  /// iterator when `cursor` is `None` (a preceding section could not
143  /// be cleanly skipped, so the cursor is unreliable — surface no records from
144  /// a misaligned offset rather than parsing garbage).
145  fn at_or_empty(message: &'a [u8], count: u16, cursor: Option<usize>) -> Self {
146    match cursor {
147      Some(cursor) => Self {
148        message,
149        cursor,
150        remaining: count,
151      },
152      None => Self {
153        message,
154        cursor: HEADER_SIZE,
155        remaining: 0,
156      },
157    }
158  }
159}
160
161impl<'a> Iterator for Records<'a> {
162  type Item = Result<Ref<'a>, ParseError>;
163
164  fn next(&mut self) -> Option<Self::Item> {
165    if self.remaining == 0 {
166      return None;
167    }
168    match Ref::try_parse(self.message, self.cursor) {
169      Ok((r, next)) => {
170        self.cursor = next;
171        self.remaining = self.remaining.saturating_sub(1);
172        Some(Ok(r))
173      }
174      Err(e) => {
175        self.remaining = 0;
176        Some(Err(e))
177      }
178    }
179  }
180}
181
182#[cfg(all(test, any(feature = "alloc", feature = "std")))]
183#[allow(warnings)]
184mod tests;