Skip to main content

zerodds_http2/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! HTTP/2 Error-Codes — RFC 9113 §7.
5
6use core::fmt;
7
8/// Error-Code (RFC 9113 §7).
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[repr(u32)]
11pub enum ErrorCode {
12    /// `NO_ERROR` (0x0).
13    NoError = 0x0,
14    /// `PROTOCOL_ERROR` (0x1).
15    ProtocolError = 0x1,
16    /// `INTERNAL_ERROR` (0x2).
17    InternalError = 0x2,
18    /// `FLOW_CONTROL_ERROR` (0x3).
19    FlowControlError = 0x3,
20    /// `SETTINGS_TIMEOUT` (0x4).
21    SettingsTimeout = 0x4,
22    /// `STREAM_CLOSED` (0x5).
23    StreamClosed = 0x5,
24    /// `FRAME_SIZE_ERROR` (0x6).
25    FrameSizeError = 0x6,
26    /// `REFUSED_STREAM` (0x7).
27    RefusedStream = 0x7,
28    /// `CANCEL` (0x8).
29    Cancel = 0x8,
30    /// `COMPRESSION_ERROR` (0x9).
31    CompressionError = 0x9,
32    /// `CONNECT_ERROR` (0xa).
33    ConnectError = 0xa,
34    /// `ENHANCE_YOUR_CALM` (0xb).
35    EnhanceYourCalm = 0xb,
36    /// `INADEQUATE_SECURITY` (0xc).
37    InadequateSecurity = 0xc,
38    /// `HTTP_1_1_REQUIRED` (0xd).
39    Http11Required = 0xd,
40}
41
42impl ErrorCode {
43    /// Mapping `u32 -> ErrorCode`. Unbekannte Codes liefern
44    /// `InternalError` als Fallback (Spec §7: Empfaenger duerfen
45    /// unbekannte Codes als `INTERNAL_ERROR` behandeln).
46    #[must_use]
47    pub fn from_u32(v: u32) -> Self {
48        match v {
49            0x0 => Self::NoError,
50            0x1 => Self::ProtocolError,
51            0x2 => Self::InternalError,
52            0x3 => Self::FlowControlError,
53            0x4 => Self::SettingsTimeout,
54            0x5 => Self::StreamClosed,
55            0x6 => Self::FrameSizeError,
56            0x7 => Self::RefusedStream,
57            0x8 => Self::Cancel,
58            0x9 => Self::CompressionError,
59            0xa => Self::ConnectError,
60            0xb => Self::EnhanceYourCalm,
61            0xc => Self::InadequateSecurity,
62            0xd => Self::Http11Required,
63            _ => Self::InternalError,
64        }
65    }
66}
67
68/// HTTP/2-Layer-Fehler.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum Http2Error {
71    /// Frame-Header zu kurz (< 9 Bytes).
72    ShortFrameHeader,
73    /// Payload zu kurz fuer den Frame-Type.
74    ShortPayload,
75    /// Frame-Length ueberschreitet `MAX_FRAME_SIZE`.
76    FrameTooLarge {
77        /// Erhaltene Length.
78        got: u32,
79        /// Aktuelle Max-Frame-Size.
80        max: u32,
81    },
82    /// Unbekannter/Reserved Frame-Type (Spec §4.1: SHOULD ignore).
83    UnknownFrameType(u8),
84    /// Falscher Connection-Preface.
85    BadPreface,
86    /// Stream-State erlaubt diesen Frame-Type nicht.
87    InvalidState,
88    /// Stream-Id 0, wo Stream != 0 erforderlich.
89    StreamIdZero,
90    /// Stream-Id != 0, wo Stream == 0 erforderlich (z.B. SETTINGS).
91    StreamIdNonZero,
92    /// Flow-Control-Window-Underrun.
93    FlowControlExceeded,
94    /// Generic Protocol-Error mit Code.
95    Protocol(ErrorCode),
96}
97
98impl fmt::Display for Http2Error {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            Self::ShortFrameHeader => f.write_str("short frame header (< 9 bytes)"),
102            Self::ShortPayload => f.write_str("short frame payload"),
103            Self::FrameTooLarge { got, max } => write!(f, "frame too large: {got} > {max}"),
104            Self::UnknownFrameType(t) => write!(f, "unknown frame type 0x{t:x}"),
105            Self::BadPreface => f.write_str("bad connection preface"),
106            Self::InvalidState => f.write_str("invalid stream state for frame"),
107            Self::StreamIdZero => f.write_str("stream id 0 not allowed"),
108            Self::StreamIdNonZero => f.write_str("stream id must be 0"),
109            Self::FlowControlExceeded => f.write_str("flow control window exceeded"),
110            Self::Protocol(e) => write!(f, "protocol error: {e:?}"),
111        }
112    }
113}
114
115#[cfg(feature = "std")]
116impl std::error::Error for Http2Error {}
117
118#[cfg(test)]
119#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn from_u32_round_trip() {
125        for code in [
126            ErrorCode::NoError,
127            ErrorCode::ProtocolError,
128            ErrorCode::InternalError,
129            ErrorCode::FlowControlError,
130            ErrorCode::SettingsTimeout,
131            ErrorCode::StreamClosed,
132            ErrorCode::FrameSizeError,
133            ErrorCode::RefusedStream,
134            ErrorCode::Cancel,
135            ErrorCode::CompressionError,
136            ErrorCode::ConnectError,
137            ErrorCode::EnhanceYourCalm,
138            ErrorCode::InadequateSecurity,
139            ErrorCode::Http11Required,
140        ] {
141            assert_eq!(ErrorCode::from_u32(code as u32), code);
142        }
143    }
144
145    #[test]
146    fn unknown_code_maps_to_internal_error() {
147        assert_eq!(ErrorCode::from_u32(0xffff), ErrorCode::InternalError);
148    }
149
150    #[test]
151    fn error_display_does_not_panic() {
152        let _ = alloc::format!("{}", Http2Error::ShortFrameHeader);
153        let _ = alloc::format!("{}", Http2Error::FrameTooLarge { got: 1, max: 0 });
154    }
155}