Skip to main content

ntex_h2/
error.rs

1use ntex_error::{Error, ErrorDiagnostic, ResultType};
2
3pub use crate::codec::EncoderError;
4
5use crate::frame::{self, GoAway, Reason, StreamId};
6use crate::stream::StreamRef;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
9pub enum ConnectionError {
10    #[error("Go away: {0}")]
11    GoAway(Reason),
12    #[error("Unknown stream id in {0} frame")]
13    UnknownStream(&'static str),
14    #[error("Encoder error: {0}")]
15    Encoder(#[from] EncoderError),
16    #[error("Decoder error: {0}")]
17    Decoder(#[from] frame::FrameError),
18    #[error("{0:?} is closed, {1}")]
19    StreamClosed(StreamId, &'static str),
20    /// An invalid stream identifier was provided
21    #[error("An invalid stream identifier was provided: {0}")]
22    InvalidStreamId(&'static str),
23    #[error("Unexpected setting ack received")]
24    UnexpectedSettingsAck,
25    /// Missing pseudo header
26    #[error("Missing pseudo header {0:?}")]
27    MissingPseudo(&'static str),
28    /// Missing pseudo header
29    #[error("Unexpected pseudo header {0:?}")]
30    UnexpectedPseudo(&'static str),
31    /// Window update value is zero
32    #[error("Window update value is zero")]
33    ZeroWindowUpdateValue,
34    #[error("Window value is overflowed")]
35    WindowValueOverflow,
36    #[error("Max concurrent streams count achieved")]
37    ConcurrencyOverflow,
38    #[error("Stream rapid reset count achieved")]
39    StreamResetsLimit,
40    /// Keep-alive timeout
41    #[error("Keep-alive timeout")]
42    KeepaliveTimeout,
43    /// Read timeout
44    #[error("Read timeout")]
45    ReadTimeout,
46}
47
48impl ConnectionError {
49    pub fn to_goaway(&self) -> GoAway {
50        match self {
51            ConnectionError::GoAway(reason) => GoAway::new(*reason),
52            ConnectionError::Encoder(_) => {
53                GoAway::new(Reason::PROTOCOL_ERROR).set_data("Error during frame encoding")
54            }
55            ConnectionError::Decoder(_) => {
56                GoAway::new(Reason::PROTOCOL_ERROR).set_data("Error during frame decoding")
57            }
58            ConnectionError::MissingPseudo(s) => {
59                GoAway::new(Reason::PROTOCOL_ERROR).set_data(format!("Missing pseudo header {s:?}"))
60            }
61            ConnectionError::UnexpectedPseudo(s) => GoAway::new(Reason::PROTOCOL_ERROR)
62                .set_data(format!("Unexpected pseudo header {s:?}")),
63            ConnectionError::UnknownStream(_) => {
64                GoAway::new(Reason::PROTOCOL_ERROR).set_data("Unknown stream")
65            }
66            ConnectionError::InvalidStreamId(_) => GoAway::new(Reason::PROTOCOL_ERROR)
67                .set_data("An invalid stream identifier was provided"),
68            ConnectionError::StreamClosed(s, _) => {
69                GoAway::new(Reason::STREAM_CLOSED).set_data(format!("{s:?} is closed"))
70            }
71            ConnectionError::UnexpectedSettingsAck => {
72                GoAway::new(Reason::PROTOCOL_ERROR).set_data("Received unexpected settings ack")
73            }
74            ConnectionError::ZeroWindowUpdateValue => GoAway::new(Reason::PROTOCOL_ERROR)
75                .set_data("Zero value for window update frame is not allowed"),
76            ConnectionError::WindowValueOverflow => GoAway::new(Reason::FLOW_CONTROL_ERROR)
77                .set_data("Updated value for window is overflowed"),
78            ConnectionError::ConcurrencyOverflow => GoAway::new(Reason::FLOW_CONTROL_ERROR)
79                .set_data("Max concurrent streams count achieved"),
80            ConnectionError::StreamResetsLimit => GoAway::new(Reason::FLOW_CONTROL_ERROR)
81                .set_data("Stream rapid reset count achieved"),
82            ConnectionError::KeepaliveTimeout => {
83                GoAway::new(Reason::NO_ERROR).set_data("Keep-alive timeout")
84            }
85            ConnectionError::ReadTimeout => {
86                GoAway::new(Reason::NO_ERROR).set_data("Frame read timeout")
87            }
88        }
89    }
90}
91
92impl ErrorDiagnostic for ConnectionError {
93    fn typ(&self) -> ResultType {
94        ResultType::ServiceError
95    }
96
97    fn signature(&self) -> &'static str {
98        match self {
99            ConnectionError::GoAway(_) => "h2-conn-GoAway",
100            ConnectionError::UnknownStream(_) => "h2-conn-UnknownStream",
101            ConnectionError::Encoder(_) => "h2-conn-Encoder",
102            ConnectionError::Decoder(_) => "h2-conn-Decoder",
103            ConnectionError::StreamClosed(..) => "h2-conn-StreamClosed",
104            ConnectionError::InvalidStreamId(_) => "h2-conn-InvalidStreamId",
105            ConnectionError::UnexpectedSettingsAck => "h2-conn-UnexpectedSettingsAck",
106            ConnectionError::MissingPseudo(_) => "h2-conn-MissingPseudo",
107            ConnectionError::UnexpectedPseudo(_) => "h2-conn-UnexpectedPseudo",
108            ConnectionError::ZeroWindowUpdateValue => "h2-conn-ZeroWindowUpdateValue",
109            ConnectionError::WindowValueOverflow => "h2-conn-WindowValueOverflow",
110            ConnectionError::ConcurrencyOverflow => "h2-conn-ConcurrencyOverflow",
111            ConnectionError::StreamResetsLimit => "h2-conn-StreamResetsLimit",
112            ConnectionError::KeepaliveTimeout => "h2-conn-KeepaliveTimeout",
113            ConnectionError::ReadTimeout => "h2-conn-ReadTimeout",
114        }
115    }
116}
117
118#[derive(Debug, Clone, thiserror::Error)]
119#[error("Stream error: {kind:?}")]
120pub(crate) struct StreamErrorInner {
121    kind: Error<StreamError>,
122    stream: StreamRef,
123}
124
125impl StreamErrorInner {
126    pub(crate) fn new(stream: StreamRef, kind: Error<StreamError>) -> Self {
127        Self { kind, stream }
128    }
129
130    pub(crate) fn into_inner(self) -> (StreamRef, Error<StreamError>) {
131        (self.stream, self.kind)
132    }
133}
134
135#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
136pub enum StreamError {
137    #[error("Stream in idle state: {0}")]
138    Idle(&'static str),
139    #[error("Stream is closed")]
140    Closed,
141    #[error("Window value is overflowed")]
142    WindowOverflowed,
143    #[error("Zero value for window")]
144    WindowZeroUpdateValue,
145    #[error("Trailers headers without end of stream flags")]
146    TrailersWithoutEos,
147    #[error("Invalid content length")]
148    InvalidContentLength,
149    #[error("Payload length does not match content-length header")]
150    WrongPayloadLength,
151    #[error("Non-empty payload for HEAD response")]
152    NonEmptyPayload,
153    #[error("Stream has been reset with {0}")]
154    Reset(Reason),
155}
156
157impl StreamError {
158    #[inline]
159    pub(crate) fn reason(&self) -> Reason {
160        match self {
161            StreamError::Closed => Reason::STREAM_CLOSED,
162            StreamError::WindowOverflowed => Reason::FLOW_CONTROL_ERROR,
163            StreamError::Idle(_)
164            | StreamError::WindowZeroUpdateValue
165            | StreamError::TrailersWithoutEos
166            | StreamError::InvalidContentLength
167            | StreamError::WrongPayloadLength
168            | StreamError::NonEmptyPayload => Reason::PROTOCOL_ERROR,
169            StreamError::Reset(r) => *r,
170        }
171    }
172}
173
174impl ErrorDiagnostic for StreamError {
175    fn typ(&self) -> ResultType {
176        ResultType::ServiceError
177    }
178
179    fn signature(&self) -> &'static str {
180        match self {
181            StreamError::Idle(_) => "h2-stream-Idle",
182            StreamError::Closed => "h2-stream-Closed",
183            StreamError::WindowOverflowed => "h2-stream-WindowOverflowed",
184            StreamError::WindowZeroUpdateValue => "h2-stream-WindowZeroUpdateValue",
185            StreamError::TrailersWithoutEos => "h2-stream-TrailersWithoutEos",
186            StreamError::InvalidContentLength => "h2-stream-InvalidContentLength",
187            StreamError::WrongPayloadLength => "h2-stream-WrongPayloadLength",
188            StreamError::NonEmptyPayload => "h2-stream-NonEmptyPayload",
189            StreamError::Reset(_) => "h2-stream-Reset",
190        }
191    }
192}
193
194/// Operation errors
195#[derive(Debug, Clone, thiserror::Error)]
196pub enum OperationError {
197    #[error("{0:?}")]
198    Stream(#[from] StreamError),
199
200    #[error("{0}")]
201    Connection(#[from] ConnectionError),
202
203    /// Cannot process operation for idle stream
204    #[error("Cannot process operation for idle stream")]
205    Idle,
206
207    /// Cannot process operation for stream in payload state
208    #[error("Cannot process operation for stream in payload state")]
209    Payload,
210
211    /// Stream is closed
212    #[error("Stream is closed {0:?}")]
213    Closed(Option<Reason>),
214
215    /// Stream has been reset from the peer
216    #[error("Stream has been reset from the peer with {0}")]
217    RemoteReset(Reason),
218
219    /// Stream has been reset from local side
220    #[error("Stream has been reset from local side with {0}")]
221    LocalReset(Reason),
222
223    /// The stream ID space is overflowed
224    ///
225    /// A new connection is needed.
226    #[error("The stream ID space is overflowed")]
227    OverflowedStreamId,
228
229    /// Disconnecting
230    #[error("Connection is disconnecting")]
231    Disconnecting,
232
233    /// Disconnected
234    #[error("Connection is closed")]
235    Disconnected,
236}
237
238impl ErrorDiagnostic for OperationError {
239    fn typ(&self) -> ResultType {
240        ResultType::ServiceError
241    }
242
243    fn signature(&self) -> &'static str {
244        match self {
245            OperationError::Stream(err) => err.signature(),
246            OperationError::Connection(err) => err.signature(),
247            OperationError::Idle => "h2-oper-Idle",
248            OperationError::Payload => "h2-oper-Payload",
249            OperationError::Closed(_) => "h2-oper-Closed",
250            OperationError::RemoteReset(_) => "h2-oper-RemoteReset",
251            OperationError::LocalReset(_) => "h2-oper-LocalReset",
252            OperationError::OverflowedStreamId => "h2-oper-OverflowedStreamId",
253            OperationError::Disconnecting => "h2-oper-Disconnecting",
254            OperationError::Disconnected => "h2-oper-Disconnected",
255        }
256    }
257}