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}