Skip to main content

trillium_http/h2/
error.rs

1use std::fmt;
2
3/// H2 error codes per RFC 9113 §7.
4///
5/// The same codes appear in both GOAWAY (connection errors) and `RST_STREAM` (stream errors);
6/// whether a given use is connection- or stream-level is determined by context, not by the code
7/// itself. Unknown wire values decode to [`Self::NoError`] per §5.4.4 / §5.4.5.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum H2ErrorCode {
11    /// Graceful shutdown or no error to signal.
12    NoError = 0x0,
13
14    /// Peer violated protocol requirements.
15    ProtocolError = 0x1,
16
17    /// An internal error in the HTTP stack.
18    InternalError = 0x2,
19
20    /// Peer violated flow-control limits.
21    FlowControlError = 0x3,
22
23    /// Settings frame was not acknowledged in a timely manner.
24    SettingsTimeout = 0x4,
25
26    /// A frame was received on a closed stream.
27    StreamClosed = 0x5,
28
29    /// A frame of an incorrect size was received.
30    FrameSizeError = 0x6,
31
32    /// The stream was refused before any application processing.
33    RefusedStream = 0x7,
34
35    /// The stream was cancelled.
36    Cancel = 0x8,
37
38    /// HPACK compression state could not be maintained.
39    CompressionError = 0x9,
40
41    /// TCP connection for a CONNECT request was reset or abnormally closed.
42    ConnectError = 0xa,
43
44    /// Peer is generating excessive load.
45    EnhanceYourCalm = 0xb,
46
47    /// Negotiated TLS parameters are unacceptable.
48    InadequateSecurity = 0xc,
49
50    /// Request must be retried over HTTP/1.1.
51    Http1_1Required = 0xd,
52}
53
54impl H2ErrorCode {
55    /// A reason phrase suitable for GOAWAY debug data.
56    pub(crate) fn reason(self) -> &'static str {
57        match self {
58            Self::NoError => "Graceful shutdown or no error to signal.",
59            Self::ProtocolError => "Peer violated protocol requirements.",
60            Self::InternalError => "An internal error in the HTTP stack.",
61            Self::FlowControlError => "Peer violated flow-control limits.",
62            Self::SettingsTimeout => "Settings frame was not acknowledged in a timely manner.",
63            Self::StreamClosed => "A frame was received on a closed stream.",
64            Self::FrameSizeError => "A frame of an incorrect size was received.",
65            Self::RefusedStream => "The stream was refused before any application processing.",
66            Self::Cancel => "The stream was cancelled.",
67            Self::CompressionError => "HPACK compression state could not be maintained.",
68            Self::ConnectError => {
69                "TCP connection for a CONNECT request was reset or abnormally closed."
70            }
71            Self::EnhanceYourCalm => "Peer is generating excessive load.",
72            Self::InadequateSecurity => "Negotiated TLS parameters are unacceptable.",
73            Self::Http1_1Required => "Request must be retried over HTTP/1.1.",
74        }
75    }
76}
77
78impl fmt::Display for H2ErrorCode {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        f.write_str((*self).reason())
81    }
82}
83
84impl std::error::Error for H2ErrorCode {}
85
86impl From<u32> for H2ErrorCode {
87    /// Unknown error codes decode to [`Self::NoError`] per RFC 9113 §5.4.4 / §5.4.5.
88    fn from(value: u32) -> Self {
89        match value {
90            0x1 => Self::ProtocolError,
91            0x2 => Self::InternalError,
92            0x3 => Self::FlowControlError,
93            0x4 => Self::SettingsTimeout,
94            0x5 => Self::StreamClosed,
95            0x6 => Self::FrameSizeError,
96            0x7 => Self::RefusedStream,
97            0x8 => Self::Cancel,
98            0x9 => Self::CompressionError,
99            0xa => Self::ConnectError,
100            0xb => Self::EnhanceYourCalm,
101            0xc => Self::InadequateSecurity,
102            0xd => Self::Http1_1Required,
103            _ => Self::NoError,
104        }
105    }
106}
107
108impl From<H2ErrorCode> for u32 {
109    fn from(code: H2ErrorCode) -> u32 {
110        code as u32
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn known_codes_roundtrip() {
120        for code in [
121            H2ErrorCode::NoError,
122            H2ErrorCode::ProtocolError,
123            H2ErrorCode::InternalError,
124            H2ErrorCode::FlowControlError,
125            H2ErrorCode::SettingsTimeout,
126            H2ErrorCode::StreamClosed,
127            H2ErrorCode::FrameSizeError,
128            H2ErrorCode::RefusedStream,
129            H2ErrorCode::Cancel,
130            H2ErrorCode::CompressionError,
131            H2ErrorCode::ConnectError,
132            H2ErrorCode::EnhanceYourCalm,
133            H2ErrorCode::InadequateSecurity,
134            H2ErrorCode::Http1_1Required,
135        ] {
136            let wire: u32 = code.into();
137            assert_eq!(
138                H2ErrorCode::from(wire),
139                code,
140                "roundtrip failed for {code:?}"
141            );
142        }
143    }
144
145    #[test]
146    fn unknown_codes_decode_as_no_error() {
147        assert_eq!(H2ErrorCode::from(0xdead_beef), H2ErrorCode::NoError);
148        assert_eq!(H2ErrorCode::from(0xe), H2ErrorCode::NoError);
149        assert_eq!(H2ErrorCode::from(u32::MAX), H2ErrorCode::NoError);
150    }
151}