Skip to main content

wire_codec/
error.rs

1//! Error type returned by codec and framing operations.
2//!
3//! All fallible APIs in this crate return [`Result<T>`][`crate::Result`], which is
4//! [`core::result::Result<T, Error>`]. The [`Error`] enum is `#[non_exhaustive]`
5//! so future variants can be added without breaking semver.
6//!
7//! # Cursor state on error
8//!
9//! Every read, write, encode, decode, and frame operation in this crate
10//! leaves the [`ReadBuf`][`crate::ReadBuf`] or
11//! [`WriteBuf`][`crate::WriteBuf`] it was given at the position it had
12//! before the call when it returns `Err`. See the crate-level "Error
13//! contract" for which writers also leave the bytes past the cursor
14//! untouched. Use [`ReadBuf::position`][`crate::ReadBuf::position`]
15//! to report where in the input a failure occurred.
16
17use core::fmt;
18
19/// Errors returned by codec and framing operations.
20///
21/// The variants fall into three groups:
22///
23/// - **Need more room or more data:** [`Error::UnexpectedEof`] and
24///   [`Error::BufferFull`]. These are not protocol violations; a streaming
25///   caller can retry once more input has arrived or the output has been
26///   flushed.
27/// - **Hostile or corrupt input:** [`Error::VarintOverflow`],
28///   [`Error::FrameTooLarge`], [`Error::InvalidLengthPrefix`],
29///   [`Error::InvalidEncoding`], and [`Error::ChecksumMismatch`]. The peer
30///   sent bytes that violate the protocol. The usual response is to drop the
31///   connection or discard the datagram; resynchronising inside a corrupted
32///   stream is rarely possible.
33/// - **Caller misuse:** [`Error::BitOverflow`], [`Error::EmptyDelimiter`],
34///   and [`Error::DelimiterInPayload`]. The local program asked for
35///   something the codec cannot represent. Fix the call site.
36///
37/// # Example
38///
39/// ```
40/// use wire_codec::{Error, ReadBuf};
41///
42/// let mut buf = ReadBuf::new(&[0x01]);
43/// // Only one byte available but we ask for two.
44/// let result = buf.read_u16_be();
45/// assert!(matches!(result, Err(Error::UnexpectedEof)));
46/// assert_eq!(buf.position(), 0, "the cursor did not move");
47/// ```
48#[non_exhaustive]
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Error {
51    /// Reader ran out of bytes before the requested value could be decoded.
52    ///
53    /// When decoding a stream, wait for more input and retry the same call.
54    /// When decoding a complete message, the message is truncated.
55    UnexpectedEof,
56    /// Writer ran out of capacity before the requested value could be encoded.
57    ///
58    /// Flush or grow the output buffer and retry. The cursor did not move,
59    /// and the crate's own writers check room before writing, so they leave
60    /// the buffer untouched (see the crate-level error contract for the
61    /// composite exceptions).
62    BufferFull,
63    /// A varint exceeded the maximum encoded length for its target width, or
64    /// its value does not fit in the target type.
65    ///
66    /// The input is corrupt or hostile.
67    VarintOverflow,
68    /// A frame's length exceeds the configured limit for the framer.
69    ///
70    /// On decode, the peer announced (or, for delimited framing, has already
71    /// sent at least) `len` payload bytes against a limit of `limit`. Treat it
72    /// as hostile input. On encode, the local payload is larger than the
73    /// framer allows. Both fields saturate at `usize::MAX`.
74    FrameTooLarge {
75        /// Frame payload length advertised by the protocol, or a lower bound
76        /// on it when the exact length is not yet known.
77        len: usize,
78        /// Configured upper bound enforced by the framer.
79        limit: usize,
80    },
81    /// A length prefix was structurally invalid: after applying the framer's
82    /// length adjustment it describes a negative payload length, or a payload
83    /// cannot be represented under the configured adjustment.
84    InvalidLengthPrefix,
85    /// A delimiter byte sequence was not found within the available input.
86    ///
87    /// Not produced by the built-in framers, which report an over-long
88    /// undelimited input as [`Error::FrameTooLarge`]. Available to custom
89    /// [`Framer`][`crate::framing::Framer`] implementations.
90    DelimiterNotFound,
91    /// Decoded data violated a structural invariant required by the codec.
92    ///
93    /// Produced by the canonical varint decoders and the varint-prefixed
94    /// framer for overlong encodings, by [`cobs`][`crate::cobs`] decoding
95    /// for malformed blocks, by `bool` decoding for bytes other than `0` and
96    /// `1`, by packed structs whose padding bits are not zero, and by the
97    /// `bytes` codec when a custom framer breaks the
98    /// [`Frame`][`crate::framing::Frame`] contract. The input (or the
99    /// framer) is at fault.
100    InvalidEncoding,
101    /// A bit-level write requested more bits than the target type can hold,
102    /// or a value did not fit in the requested bit width.
103    BitOverflow,
104    /// A delimited-framer constructor was given an empty delimiter, which
105    /// cannot uniquely separate frames.
106    EmptyDelimiter,
107    /// A payload passed to a delimited framer's `write_frame` contains the
108    /// delimiter sequence, so the receiver would split it into several frames.
109    ///
110    /// Escape the payload (for example with [`cobs`][`crate::cobs`]) or switch
111    /// to a length-prefixed framer. Nothing was written.
112    DelimiterInPayload,
113    /// A frame's checksum trailer did not match the checksum computed over
114    /// the received bytes.
115    ///
116    /// The frame was corrupted in transit. Discard it. A CRC does not
117    /// authenticate the sender; see [`checksum`][`crate::checksum`].
118    ChecksumMismatch,
119}
120
121impl fmt::Display for Error {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        match *self {
124            Error::UnexpectedEof => f.write_str("unexpected end of input"),
125            Error::BufferFull => f.write_str("output buffer is full"),
126            Error::VarintOverflow => f.write_str("varint encoded length exceeded the target width"),
127            Error::FrameTooLarge { len, limit } => {
128                write!(f, "frame length {len} exceeds configured limit {limit}")
129            }
130            Error::InvalidLengthPrefix => f.write_str("length prefix is structurally invalid"),
131            Error::DelimiterNotFound => f.write_str("delimiter not found in input"),
132            Error::InvalidEncoding => f.write_str("encoded data violates a structural invariant"),
133            Error::BitOverflow => f.write_str("bit width or value out of range"),
134            Error::EmptyDelimiter => f.write_str("delimiter must be non-empty"),
135            Error::DelimiterInPayload => f.write_str("payload contains the frame delimiter"),
136            Error::ChecksumMismatch => f.write_str("frame checksum mismatch"),
137        }
138    }
139}
140
141/// Implemented unconditionally, including on `no_std` targets.
142/// `std::error::Error` is the same trait, so `Error` converts into
143/// `Box<dyn std::error::Error>`, `anyhow::Error`, and friends whether or not
144/// the `std` feature is enabled.
145impl core::error::Error for Error {}
146
147/// Convert into an [`std::io::Error`], for use in I/O-based codecs.
148///
149/// The original [`Error`] is kept as the source and can be recovered with
150/// `io::Error::get_ref` and `downcast_ref`. The [`std::io::ErrorKind`] is
151/// chosen by group: [`Error::UnexpectedEof`] maps to `UnexpectedEof`,
152/// [`Error::BufferFull`] to `WriteZero`, caller misuse
153/// ([`Error::BitOverflow`], [`Error::EmptyDelimiter`],
154/// [`Error::DelimiterInPayload`]) to `InvalidInput`, and everything else to
155/// `InvalidData`.
156///
157/// # Example
158///
159/// ```
160/// use std::io;
161/// use wire_codec::Error;
162///
163/// let io_err = io::Error::from(Error::ChecksumMismatch);
164/// assert_eq!(io_err.kind(), io::ErrorKind::InvalidData);
165/// let inner = io_err.get_ref().and_then(|e| e.downcast_ref::<Error>());
166/// assert_eq!(inner, Some(&Error::ChecksumMismatch));
167/// ```
168#[cfg(feature = "std")]
169#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
170impl From<Error> for std::io::Error {
171    fn from(err: Error) -> Self {
172        use std::io::ErrorKind;
173        let kind = match err {
174            Error::UnexpectedEof => ErrorKind::UnexpectedEof,
175            Error::BufferFull => ErrorKind::WriteZero,
176            Error::BitOverflow | Error::EmptyDelimiter | Error::DelimiterInPayload => {
177                ErrorKind::InvalidInput
178            }
179            _ => ErrorKind::InvalidData,
180        };
181        std::io::Error::new(kind, err)
182    }
183}
184
185/// Result alias used throughout the crate.
186pub type Result<T> = core::result::Result<T, Error>;
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn display_messages_are_distinct_and_non_empty() {
194        let all = [
195            Error::UnexpectedEof,
196            Error::BufferFull,
197            Error::VarintOverflow,
198            Error::FrameTooLarge { len: 9, limit: 4 },
199            Error::InvalidLengthPrefix,
200            Error::DelimiterNotFound,
201            Error::InvalidEncoding,
202            Error::BitOverflow,
203            Error::EmptyDelimiter,
204            Error::DelimiterInPayload,
205            Error::ChecksumMismatch,
206        ];
207        let mut seen: [Option<String>; 11] = Default::default();
208        for (slot, err) in seen.iter_mut().zip(all) {
209            let text = err.to_string();
210            assert!(!text.is_empty());
211            *slot = Some(text);
212        }
213        for i in 0..seen.len() {
214            for j in (i + 1)..seen.len() {
215                assert_ne!(seen[i], seen[j]);
216            }
217        }
218        assert_eq!(
219            Error::FrameTooLarge { len: 9, limit: 4 }.to_string(),
220            "frame length 9 exceeds configured limit 4"
221        );
222    }
223
224    #[cfg(feature = "std")]
225    #[test]
226    fn io_error_kinds() {
227        use std::io::{self, ErrorKind};
228        let cases = [
229            (Error::UnexpectedEof, ErrorKind::UnexpectedEof),
230            (Error::BufferFull, ErrorKind::WriteZero),
231            (Error::BitOverflow, ErrorKind::InvalidInput),
232            (Error::EmptyDelimiter, ErrorKind::InvalidInput),
233            (Error::DelimiterInPayload, ErrorKind::InvalidInput),
234            (Error::VarintOverflow, ErrorKind::InvalidData),
235            (Error::InvalidEncoding, ErrorKind::InvalidData),
236        ];
237        for (err, kind) in cases {
238            let io_err = io::Error::from(err);
239            assert_eq!(io_err.kind(), kind);
240            assert_eq!(
241                io_err.get_ref().and_then(|e| e.downcast_ref::<Error>()),
242                Some(&err)
243            );
244        }
245    }
246
247    #[test]
248    fn implements_core_error() {
249        fn takes_error(_: &dyn core::error::Error) {}
250        takes_error(&Error::BufferFull);
251    }
252}