Skip to main content

eth_valkyoth_codec/
error.rs

1use core::fmt;
2
3/// Shared decode failure categories.
4#[non_exhaustive]
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum DecodeError {
7    /// The byte input is larger than the active decode budget.
8    InputTooLarge,
9    /// The input contains trailing bytes after a decoded value.
10    TrailingBytes,
11    /// A decoder reported consuming more bytes than the input contains.
12    DecoderOverread,
13    /// The input is malformed for the selected wire format.
14    Malformed,
15    /// A decoded list contains more items than the active list budget.
16    ListTooLong,
17    /// Decoding exceeded the active nesting-depth budget.
18    NestingTooDeep,
19    /// A decoder requested allocation beyond the active allocation budget.
20    AllocationExceeded,
21    /// A proof contains more nodes than the active proof-node budget.
22    ProofTooLarge,
23    /// A decoder visited more items than the active cumulative item budget.
24    ItemCountExceeded,
25    /// Length or offset arithmetic overflowed.
26    LengthOverflow,
27    /// An offset or range points outside the input.
28    OffsetOutOfBounds,
29}
30
31impl DecodeError {
32    /// Stable machine-readable error code.
33    #[must_use]
34    pub const fn code(self) -> &'static str {
35        match self {
36            Self::InputTooLarge => "ETH_CODEC_INPUT_TOO_LARGE",
37            Self::TrailingBytes => "ETH_CODEC_TRAILING_BYTES",
38            Self::DecoderOverread => "ETH_CODEC_DECODER_OVERREAD",
39            Self::Malformed => "ETH_CODEC_MALFORMED",
40            Self::ListTooLong => "ETH_CODEC_LIST_TOO_LONG",
41            Self::NestingTooDeep => "ETH_CODEC_NESTING_TOO_DEEP",
42            Self::AllocationExceeded => "ETH_CODEC_ALLOCATION_EXCEEDED",
43            Self::ProofTooLarge => "ETH_CODEC_PROOF_TOO_LARGE",
44            Self::ItemCountExceeded => "ETH_CODEC_ITEM_COUNT_EXCEEDED",
45            Self::LengthOverflow => "ETH_CODEC_LENGTH_OVERFLOW",
46            Self::OffsetOutOfBounds => "ETH_CODEC_OFFSET_OUT_OF_BOUNDS",
47        }
48    }
49
50    /// Stable human-readable error message.
51    #[must_use]
52    pub const fn message(self) -> &'static str {
53        match self {
54            Self::InputTooLarge => "input exceeds the active decode byte limit",
55            Self::TrailingBytes => "decoded value did not consume the full input",
56            Self::DecoderOverread => "decoder consumed more bytes than were available",
57            Self::Malformed => "input is malformed for the selected codec",
58            Self::ListTooLong => "decoded list exceeds the active item limit",
59            Self::NestingTooDeep => "decoded structure exceeds the active nesting limit",
60            Self::AllocationExceeded => "decoder exceeded the active allocation limit",
61            Self::ProofTooLarge => "proof exceeds the active proof-node limit",
62            Self::ItemCountExceeded => "decoder exceeded the active cumulative item limit",
63            Self::LengthOverflow => "length or offset arithmetic overflowed",
64            Self::OffsetOutOfBounds => "offset or range is outside the input",
65        }
66    }
67
68    /// Stable high-level category for policy decisions.
69    #[must_use]
70    pub const fn category(self) -> DecodeErrorCategory {
71        match self {
72            Self::InputTooLarge
73            | Self::ListTooLong
74            | Self::NestingTooDeep
75            | Self::AllocationExceeded
76            | Self::ProofTooLarge
77            | Self::ItemCountExceeded => DecodeErrorCategory::ResourceExhaustion,
78            Self::TrailingBytes
79            | Self::DecoderOverread
80            | Self::Malformed
81            | Self::LengthOverflow
82            | Self::OffsetOutOfBounds => DecodeErrorCategory::MalformedInput,
83        }
84    }
85
86    /// Returns the resource budget that was exceeded, if this is a resource
87    /// exhaustion error.
88    #[must_use]
89    pub const fn resource(self) -> Option<ResourceError> {
90        match self {
91            Self::InputTooLarge => Some(ResourceError::InputBytes),
92            Self::ListTooLong => Some(ResourceError::ListItems),
93            Self::NestingTooDeep => Some(ResourceError::NestingDepth),
94            Self::AllocationExceeded => Some(ResourceError::AllocationBytes),
95            Self::ProofTooLarge => Some(ResourceError::ProofNodes),
96            Self::ItemCountExceeded => Some(ResourceError::TotalItems),
97            Self::TrailingBytes
98            | Self::DecoderOverread
99            | Self::Malformed
100            | Self::LengthOverflow
101            | Self::OffsetOutOfBounds => None,
102        }
103    }
104}
105
106impl fmt::Display for DecodeError {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        formatter.write_str(self.message())
109    }
110}
111
112#[cfg(feature = "std")]
113impl std::error::Error for DecodeError {}
114
115/// Stable high-level decode error categories.
116#[non_exhaustive]
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
118pub enum DecodeErrorCategory {
119    /// The input is malformed or internally inconsistent.
120    MalformedInput,
121    /// The input exceeded an explicit resource budget.
122    ResourceExhaustion,
123}
124
125/// Stable resource budget categories.
126#[non_exhaustive]
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub enum ResourceError {
129    /// Input byte limit was exceeded.
130    InputBytes,
131    /// Per-list item limit was exceeded.
132    ListItems,
133    /// Nesting depth limit was exceeded.
134    NestingDepth,
135    /// Cumulative allocation limit was exceeded.
136    AllocationBytes,
137    /// Proof-node limit was exceeded.
138    ProofNodes,
139    /// Cumulative decoded item limit was exceeded.
140    TotalItems,
141}
142
143impl ResourceError {
144    /// Stable machine-readable error code.
145    #[must_use]
146    pub const fn code(self) -> &'static str {
147        match self {
148            Self::InputBytes => "ETH_RESOURCE_INPUT_BYTES",
149            Self::ListItems => "ETH_RESOURCE_LIST_ITEMS",
150            Self::NestingDepth => "ETH_RESOURCE_NESTING_DEPTH",
151            Self::AllocationBytes => "ETH_RESOURCE_ALLOCATION_BYTES",
152            Self::ProofNodes => "ETH_RESOURCE_PROOF_NODES",
153            Self::TotalItems => "ETH_RESOURCE_TOTAL_ITEMS",
154        }
155    }
156
157    /// Stable human-readable error message.
158    #[must_use]
159    pub const fn message(self) -> &'static str {
160        match self {
161            Self::InputBytes => "input byte budget exceeded",
162            Self::ListItems => "list item budget exceeded",
163            Self::NestingDepth => "nesting depth budget exceeded",
164            Self::AllocationBytes => "allocation byte budget exceeded",
165            Self::ProofNodes => "proof-node budget exceeded",
166            Self::TotalItems => "total item budget exceeded",
167        }
168    }
169}
170
171impl fmt::Display for ResourceError {
172    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
173        formatter.write_str(self.message())
174    }
175}
176
177#[cfg(feature = "std")]
178impl std::error::Error for ResourceError {}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    extern crate std;
184    use std::string::ToString;
185
186    #[test]
187    fn decode_errors_have_stable_codes_and_messages() {
188        assert_eq!(DecodeError::Malformed.code(), "ETH_CODEC_MALFORMED");
189        assert_eq!(
190            DecodeError::Malformed.message(),
191            "input is malformed for the selected codec"
192        );
193        assert_eq!(
194            DecodeError::Malformed.category(),
195            DecodeErrorCategory::MalformedInput
196        );
197        assert_eq!(
198            DecodeError::Malformed.to_string(),
199            "input is malformed for the selected codec"
200        );
201    }
202
203    #[test]
204    fn resource_errors_are_classified_without_payloads() {
205        let error = DecodeError::AllocationExceeded;
206
207        assert_eq!(error.category(), DecodeErrorCategory::ResourceExhaustion);
208        assert_eq!(error.resource(), Some(ResourceError::AllocationBytes));
209        assert_eq!(
210            ResourceError::AllocationBytes.code(),
211            "ETH_RESOURCE_ALLOCATION_BYTES"
212        );
213        assert_eq!(
214            ResourceError::AllocationBytes.to_string(),
215            "allocation byte budget exceeded"
216        );
217    }
218
219    #[test]
220    fn new_resource_errors_are_classified() {
221        assert_eq!(
222            DecodeError::ProofTooLarge.resource(),
223            Some(ResourceError::ProofNodes)
224        );
225        assert_eq!(
226            DecodeError::ItemCountExceeded.resource(),
227            Some(ResourceError::TotalItems)
228        );
229    }
230
231    #[test]
232    fn arithmetic_errors_are_malformed_input() {
233        assert_eq!(
234            DecodeError::LengthOverflow.category(),
235            DecodeErrorCategory::MalformedInput
236        );
237        assert_eq!(DecodeError::OffsetOutOfBounds.resource(), None);
238    }
239}