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 #[error("An invalid stream identifier was provided: {0}")]
22 InvalidStreamId(&'static str),
23 #[error("Unexpected setting ack received")]
24 UnexpectedSettingsAck,
25 #[error("Missing pseudo header {0:?}")]
27 MissingPseudo(&'static str),
28 #[error("Unexpected pseudo header {0:?}")]
30 UnexpectedPseudo(&'static str),
31 #[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 #[error("Keep-alive timeout")]
42 KeepaliveTimeout,
43 #[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#[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 #[error("Cannot process operation for idle stream")]
205 Idle,
206
207 #[error("Cannot process operation for stream in payload state")]
209 Payload,
210
211 #[error("Stream is closed {0:?}")]
213 Closed(Option<Reason>),
214
215 #[error("Stream has been reset from the peer with {0}")]
217 RemoteReset(Reason),
218
219 #[error("Stream has been reset from local side with {0}")]
221 LocalReset(Reason),
222
223 #[error("The stream ID space is overflowed")]
227 OverflowedStreamId,
228
229 #[error("Connection is disconnecting")]
231 Disconnecting,
232
233 #[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}