Skip to main content

mdns_proto/
error.rs

1//! Cross-cutting error types and their shared detail-struct payloads.
2//!
3//! Following `rust-type-conventions`: every multi-field error variant is
4//! extracted into a `*Detail` named struct (private fields + `const fn`
5//! accessors + `derive_more::Display`), and the parent enum carries a
6//! newtype variant with `#[error(transparent)]`. Single-field variants
7//! become plain newtypes; unit-only variants stay unit.
8
9use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
10
11use crate::{name::LabelTooLongDetail, wire::*};
12// `Name`/`QueryHandle`/`ServiceHandle` are only carried by the alloc/std-gated
13// error enums below, so gate the import to match (no_std + bare no-default have
14// no `Name`).
15cfg_heap! {
16  use crate::{Name, QueryHandle, ServiceHandle};
17}
18
19/// Detail payload for "buffer too short": a parser ran out of bytes.
20#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Display, thiserror::Error)]
21#[display("buffer too short: needed {needed} bytes at offset {at}, had {have}")]
22pub struct BufferTooShortDetail {
23  needed: usize,
24  at: usize,
25  have: usize,
26}
27
28impl BufferTooShortDetail {
29  /// Creates a new detail payload.
30  #[inline(always)]
31  pub const fn new(needed: usize, at: usize, have: usize) -> Self {
32    Self { needed, at, have }
33  }
34
35  /// Bytes the parser needed at the failure point.
36  #[inline(always)]
37  pub const fn needed(&self) -> usize {
38    self.needed
39  }
40
41  /// Offset into the input at which the parser stopped.
42  #[inline(always)]
43  pub const fn at(&self) -> usize {
44    self.at
45  }
46
47  /// Bytes actually available at the failure point.
48  #[inline(always)]
49  pub const fn have(&self) -> usize {
50    self.have
51  }
52
53  /// Returns `true` if the detail struct has no bytes (always false in
54  /// practice — zero-byte buffers are typically not validated through this
55  /// error type).
56  #[inline(always)]
57  pub const fn is_empty(&self) -> bool {
58    self.have == 0
59  }
60}
61
62/// Detail payload for "output buffer too small" during encoding.
63#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Display, thiserror::Error)]
64#[display("output buffer too small: needed {needed} bytes, have {have}")]
65pub struct BufferTooSmallDetail {
66  needed: usize,
67  have: usize,
68}
69
70impl BufferTooSmallDetail {
71  /// Creates a new detail payload.
72  #[inline(always)]
73  pub const fn new(needed: usize, have: usize) -> Self {
74    Self { needed, have }
75  }
76
77  /// Bytes the encoder needed.
78  #[inline(always)]
79  pub const fn needed(&self) -> usize {
80    self.needed
81  }
82
83  /// Bytes available in the output buffer.
84  #[inline(always)]
85  pub const fn have(&self) -> usize {
86    self.have
87  }
88
89  /// Returns `true` if the detail struct has no bytes (always false in
90  /// practice — zero-byte buffers are typically not validated through this
91  /// error type).
92  #[inline(always)]
93  pub const fn is_empty(&self) -> bool {
94    self.have == 0
95  }
96}
97
98/// Detail payload for [`ParseError::PointerForward`].
99#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Display, thiserror::Error)]
100#[display("compression pointer points forward: ptr {ptr} > current {at}")]
101pub struct PointerForwardDetail {
102  ptr: u16,
103  at: u16,
104}
105
106impl PointerForwardDetail {
107  /// Creates a new detail payload.
108  #[inline(always)]
109  pub const fn new(ptr: u16, at: u16) -> Self {
110    Self { ptr, at }
111  }
112
113  /// The forward-pointing pointer offset.
114  #[inline(always)]
115  pub const fn ptr(&self) -> u16 {
116    self.ptr
117  }
118
119  /// The current parsing position.
120  #[inline(always)]
121  pub const fn at(&self) -> u16 {
122    self.at
123  }
124}
125
126/// Detail payload for [`ParseError::RdlengthOverrun`].
127#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Display, thiserror::Error)]
128#[display("rdlength {rdlen} at offset {at} exceeds remaining {rem} bytes")]
129pub struct RdlengthOverrunDetail {
130  rdlen: u16,
131  at: usize,
132  rem: usize,
133}
134
135impl RdlengthOverrunDetail {
136  /// Creates a new detail payload.
137  #[inline(always)]
138  pub const fn new(rdlen: u16, at: usize, rem: usize) -> Self {
139    Self { rdlen, at, rem }
140  }
141
142  /// The resource record data length.
143  #[inline(always)]
144  pub const fn rdlen(&self) -> u16 {
145    self.rdlen
146  }
147
148  /// The offset at which the overrun occurred.
149  #[inline(always)]
150  pub const fn at(&self) -> usize {
151    self.at
152  }
153
154  /// Remaining bytes available in the input buffer.
155  #[inline(always)]
156  pub const fn rem(&self) -> usize {
157    self.rem
158  }
159}
160
161/// Errors raised while parsing an mDNS message off the wire.
162#[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
163#[unwrap(ref)]
164#[try_unwrap(ref)]
165#[non_exhaustive]
166pub enum ParseError {
167  /// Parser ran out of input bytes.
168  #[error(transparent)]
169  BufferTooShort(BufferTooShortDetail),
170
171  /// A label length byte exceeded [`MAX_LABEL_BYTES`](crate::constants::MAX_LABEL_BYTES).
172  #[error(transparent)]
173  LabelTooLong(LabelTooLongDetail),
174
175  /// A fully-resolved name exceeded [`MAX_NAME_BYTES`](crate::constants::MAX_NAME_BYTES).
176  #[error("name exceeds max length {0} bytes")]
177  NameTooLong(usize),
178
179  /// A compression pointer chain pointed back to a name still being resolved.
180  #[error("name compression pointer cycle detected")]
181  PointerCycle,
182
183  /// A compression pointer chain exceeded [`MAX_POINTER_HOPS`](crate::constants::MAX_POINTER_HOPS) hops.
184  #[error("name compression pointer chain exceeded {0} hops")]
185  PointerChainTooLong(u8),
186
187  /// A compression pointer pointed forward (>= current position), violating RFC 1035 §4.1.4.
188  #[error(transparent)]
189  PointerForward(PointerForwardDetail),
190
191  /// The top two bits of a label byte did not match a known label kind.
192  #[error("unknown label kind in byte {0:#010b}")]
193  InvalidLabelKind(u8),
194
195  /// A resource record's `rdlength` ran past the end of the message.
196  #[error(transparent)]
197  RdlengthOverrun(RdlengthOverrunDetail),
198
199  /// An integer conversion failed.
200  #[error(transparent)]
201  IntegerConversion(#[from] core::num::TryFromIntError),
202
203  /// A well-known name-bearing RR type (e.g. NS, SOA, MX, DNAME) that this
204  /// mDNS/DNS-SD stack does not type-specifically parse. Its RDATA may carry a
205  /// compressed/case-varied domain name that cannot be canonicalized for
206  /// storage or identity, so callers drop the record rather than cache a
207  /// compression-sensitive, non-dedupable entry.
208  #[error("unsupported name-bearing record type {0}")]
209  UnsupportedNameBearingType(u16),
210}
211
212/// Errors raised while encoding an mDNS message onto the wire.
213#[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
214#[unwrap(ref)]
215#[try_unwrap(ref)]
216#[non_exhaustive]
217pub enum EncodeError {
218  /// Output buffer cannot hold the encoded message.
219  #[error(transparent)]
220  BufferTooSmall(BufferTooSmallDetail),
221
222  /// An integer conversion failed during encoding.
223  #[error(transparent)]
224  IntegerConversion(#[from] core::num::TryFromIntError),
225}
226
227/// Errors raised by [`Service::handle_timeout`](crate::service::Service::handle_timeout).
228#[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
229#[unwrap(ref)]
230#[try_unwrap(ref)]
231#[non_exhaustive]
232pub enum HandleTimeoutError {
233  /// A deadline arithmetic operation overflowed.
234  #[error("deadline arithmetic overflow")]
235  Overflow,
236}
237
238/// Errors raised by [`Service::poll_transmit`](crate::service::Service::poll_transmit).
239#[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
240#[unwrap(ref)]
241#[try_unwrap(ref)]
242#[non_exhaustive]
243pub enum TransmitError {
244  /// The caller-supplied buffer is too small to hold the encoded message.
245  #[error(transparent)]
246  BufferTooSmall(BufferTooSmallDetail),
247}
248
249/// Returned when an internal pool is at capacity.
250#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
251#[non_exhaustive]
252#[error("pool capacity exceeded")]
253pub struct StorageFullError;
254
255/// Errors raised by [`Endpoint::handle`](crate::endpoint::Endpoint::handle).
256#[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
257#[unwrap(ref)]
258#[try_unwrap(ref)]
259#[non_exhaustive]
260pub enum HandleError {
261  /// The datagram could not be parsed.
262  #[error(transparent)]
263  Parse(#[from] ParseError),
264
265  /// The incoming opcode is not `Query` (mDNS only supports Query).
266  #[error("incoming opcode `{0}` is not Query")]
267  InvalidOpcode(Opcode),
268
269  /// The incoming response code is not `NoError` (mDNS requires NoError).
270  #[error("incoming response code `{0}` is not NoError")]
271  InvalidResponseCode(ResponseCode),
272}
273
274cfg_heap! {
275  /// Errors raised by `Endpoint::try_register_service`.
276  #[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
277  #[unwrap(ref)]
278  #[try_unwrap(ref)]
279  #[non_exhaustive]
280  pub enum RegisterServiceError {
281    /// A service with this name is already registered.
282    #[error("service `{0}` already registered")]
283    NameAlreadyRegistered(crate::Name),
284
285    /// The internal routing pool is full.
286    #[error(transparent)]
287    StorageFull(#[from] StorageFullError),
288  }
289}
290
291/// Errors raised by `Endpoint::try_start_query`.
292#[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
293#[unwrap(ref)]
294#[try_unwrap(ref)]
295#[non_exhaustive]
296pub enum StartQueryError {
297  /// The internal routing pool is full.
298  #[error(transparent)]
299  StorageFull(#[from] StorageFullError),
300}
301
302cfg_heap! {
303  /// Errors raised by
304  /// [`Endpoint::handle_service_renamed`](crate::endpoint::Endpoint::handle_service_renamed).
305  #[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
306  #[unwrap(ref)]
307  #[try_unwrap(ref)]
308  #[non_exhaustive]
309  pub enum HandleServiceRenamedError {
310    /// The new name is already registered to a different service.
311    #[error("name `{0}` is already registered to a different service")]
312    NameAlreadyRegistered(Name),
313
314    /// The handle does not refer to a registered service.
315    #[error("service handle {0} not found")]
316    ServiceNotFound(ServiceHandle),
317  }
318
319  /// Errors raised by query-handle lookups on `Endpoint` (`poll_query_*`,
320  /// `handle_query_timeout`, `cancel_query`, …) when the handle no longer
321  /// corresponds to an active query.  A query disappears from the endpoint
322  /// when its terminal update has been drained via `Endpoint::poll_query`
323  /// (auto-prune) or after an explicit `Endpoint::cancel_query` call.
324  #[derive(Debug, Clone, IsVariant, Unwrap, TryUnwrap, thiserror::Error)]
325  #[unwrap(ref)]
326  #[try_unwrap(ref)]
327  #[non_exhaustive]
328  pub enum CancelQueryError {
329    /// The handle does not refer to a currently registered query.
330    #[error("query handle {0} not found")]
331    QueryNotFound(QueryHandle),
332  }
333}
334
335#[cfg(test)]
336#[allow(clippy::unwrap_used)]
337mod tests;