Skip to main content

cobs_io_async/
error.rs

1/// A failure to decode a frame.
2///
3/// Generic parameters preserve the source and destination error types.
4/// Match their variants to inspect them: the current [`core::error::Error`]
5/// implementation does not forward `source()`.
6///
7/// Errors do not roll back partial output.
8/// Only [`InvalidFrame`](Self::InvalidFrame) carries progress information.
9///
10/// Implementations such as `Clone`, `Copy`, serialization, and compact formatting
11/// depend on the corresponding traits being implemented by both generic error parameters.
12///
13/// Error formatting is diagnostic output, not a stable serialization format.
14#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum DecodeError<SourceError, DestinationError> {
17    /// Reading the source failed.
18    ///
19    /// Never returned by the [`sync`](crate::sync) module, which reads from
20    /// memory.
21    Source(SourceError),
22
23    /// The destination cannot hold the next decoded byte.
24    ///
25    /// [`sync::decode_to_slice`](crate::sync::decode_to_slice) reports
26    /// [`SeekableError::OutOfBounds`] here. The destination may be partially
27    /// overwritten.
28    Destination(DestinationError),
29
30    /// The input ended before the current COBS block was complete.
31    UnexpectedSourceEof,
32
33    /// One-shot decoding reached EOF without starting an encoded frame.
34    ///
35    /// Empty input and zero padding alone produce this error. A valid
36    /// encoding of an empty payload, such as `[1]`, does not.
37    EmptyFrame,
38
39    /// A zero delimiter arrived before the current COBS block had enough data.
40    ///
41    /// Progress includes the delimiter and has `frame_len: None`. Partial
42    /// output already written to the destination is not removed.
43    ///
44    /// The next frame starts at `source[progress.consumed as usize..]`.
45    InvalidFrame(crate::DecodeProgress),
46}
47
48impl<S: core::error::Error, D: core::error::Error> core::error::Error for DecodeError<S, D> {}
49
50impl<S, D> core::fmt::Display for DecodeError<S, D>
51where
52    S: core::error::Error,
53    D: core::error::Error,
54{
55    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        match self {
57            DecodeError::Source(e) => write!(f, "Source error: {}", e),
58            DecodeError::Destination(e) => write!(f, "Destination error: {}", e),
59            DecodeError::UnexpectedSourceEof => write!(f, "Unexpected source EOF"),
60            DecodeError::EmptyFrame => write!(f, "Empty frame"),
61            DecodeError::InvalidFrame(progress) => {
62                write!(f, "Invalid frame after decoding {:?}", progress)
63            }
64        }
65    }
66}
67
68#[cfg(feature = "defmt")]
69impl<S, D> defmt::Format for DecodeError<S, D>
70where
71    D: defmt::Format,
72    S: defmt::Format,
73{
74    fn format(&self, f: defmt::Formatter<'_>) {
75        match self {
76            DecodeError::Source(e) => defmt::write!(f, "Source error: {}", e),
77            DecodeError::Destination(e) => defmt::write!(f, "Destination error: {}", e),
78            DecodeError::UnexpectedSourceEof => defmt::write!(f, "Unexpected source EOF"),
79            DecodeError::EmptyFrame => defmt::write!(f, "Empty frame"),
80            DecodeError::InvalidFrame(progress) => {
81                defmt::write!(f, "Invalid frame after decoding {:?}", progress)
82            }
83        }
84    }
85}
86
87/// A bounds error involving a slice-backed buffer.
88///
89/// The [`sync`](crate::sync) module returns this when the destination slice
90/// cannot hold the output.
91///
92/// Implements [`core::error::Error`].
93#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub enum SeekableError {
96    /// An operation would access a position or write bytes outside the
97    /// backing slice's supported bounds.
98    OutOfBounds,
99}
100
101impl core::error::Error for SeekableError {}
102impl core::fmt::Display for SeekableError {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        match self {
105            SeekableError::OutOfBounds => write!(f, "Out of bounds"),
106        }
107    }
108}
109
110#[cfg(feature = "defmt")]
111impl defmt::Format for SeekableError {
112    fn format(&self, f: defmt::Formatter<'_>) {
113        match self {
114            SeekableError::OutOfBounds => defmt::write!(f, "Out of bounds"),
115        }
116    }
117}
118
119#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121#[cfg_attr(feature = "defmt", derive(defmt::Format))]
122/// An invalid [`Reliable`](crate::Reliable) configuration, rejected at
123/// construction.
124pub enum ConfigError {
125    /// The buffer capacity `N` cannot hold a packet with one payload byte.
126    ///
127    /// A packet needs 17 bytes of header and checksum, COBS overhead and two
128    /// delimiters, so `N` must be at least 21.
129    BufferTooSmall,
130}
131
132impl core::error::Error for ConfigError {}
133
134impl core::fmt::Display for ConfigError {
135    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136        match self {
137            ConfigError::BufferTooSmall => write!(f, "Buffer cannot hold a protocol packet"),
138        }
139    }
140}
141
142#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144#[cfg_attr(feature = "defmt", derive(defmt::Format))]
145/// A terminal failure of a [`Reliable`](crate::Reliable) connection.
146///
147/// Corrupt or malformed frames never produce an error; they are discarded and
148/// recovered by retransmission. Once any of these occurs, the connection is
149/// permanently failed.
150pub enum ConnectionError<TransportError> {
151    /// The underlying transport returned an error.
152    Transport(TransportError),
153
154    /// The underlying transport reported end of stream.
155    UnexpectedEof,
156
157    /// The underlying transport accepted zero bytes of a nonempty write.
158    WriteZero,
159
160    /// A packet went unacknowledged after the configured number of
161    /// retransmissions.
162    Timeout,
163
164    /// All 2^32 sequence numbers of this session have been used.
165    ///
166    /// Start a new session with a fresh identifier.
167    SequenceExhausted,
168
169    /// The connection failed earlier and the original error was already
170    /// reported.
171    Failed,
172}
173
174impl<E: core::error::Error> core::error::Error for ConnectionError<E> {}
175
176impl<E: core::fmt::Display> core::fmt::Display for ConnectionError<E> {
177    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
178        match self {
179            ConnectionError::Transport(e) => write!(f, "Transport error: {}", e),
180            ConnectionError::UnexpectedEof => write!(f, "Transport closed unexpectedly"),
181            ConnectionError::WriteZero => write!(f, "Transport accepted zero bytes"),
182            ConnectionError::Timeout => write!(f, "Retransmission attempts exhausted"),
183            ConnectionError::SequenceExhausted => write!(f, "Sequence numbers exhausted"),
184            ConnectionError::Failed => write!(f, "Connection previously failed"),
185        }
186    }
187}