Skip to main content

ntex/ws/
error.rs

1//! WebSocket protocol related errors.
2use std::io;
3
4use crate::error::ErrorDiagnostic;
5use crate::http::error::{DecodeError, EncodeError, ResponseError};
6use crate::http::header::{ALLOW, HeaderValue, SEC_WEBSOCKET_VERSION};
7use crate::http::{Response, StatusCode};
8use crate::{connect::ConnectError, util::Either, util::clone_io_error};
9
10use super::OpCode;
11
12/// Errors produced by a WebSocket dispatcher.
13#[derive(Debug, thiserror::Error)]
14pub enum WsError<E> {
15    /// Error returned by the frame service.
16    #[error("Service error")]
17    Service(#[source] E),
18    /// The keep-alive timer expired.
19    #[error("Keep-alive error")]
20    KeepAlive,
21    /// Reading a frame timed out.
22    #[error("Frame read timeout")]
23    ReadTimeout,
24    /// Write backpressure stayed enabled for longer than the write timeout.
25    #[error("Write timeout")]
26    WriteTimeout,
27    /// WebSocket protocol error.
28    #[error("Ws protocol level error")]
29    Protocol(#[source] ProtocolError),
30    /// WebSocket opening-handshake error.
31    #[error("Ws handshake error")]
32    Handshake(#[from] HandshakeError),
33    /// The peer disconnected.
34    #[error("Peer has been disconnected: {0:?}")]
35    Disconnected(#[source] Option<io::Error>),
36}
37
38/// WebSocket protocol errors.
39#[derive(Copy, Clone, Debug, thiserror::Error)]
40pub enum ProtocolError {
41    /// Received an unmasked frame from client
42    #[error("Received an unmasked frame from client")]
43    UnmaskedFrame,
44    /// Received a masked frame from server
45    #[error("Received a masked frame from server")]
46    MaskedFrame,
47    /// Encountered invalid opcode
48    #[error("Invalid opcode: {0}")]
49    InvalidOpcode(u8),
50    /// Reserved frame bits are set without a negotiated extension.
51    #[error("Reserved frame bits are set: {0:#05b}")]
52    ReservedBits(u8),
53    /// A control frame is fragmented.
54    #[error("Fragmented control frame: {0}")]
55    FragmentedControlFrame(OpCode),
56    /// Invalid control frame length
57    #[error("Invalid control frame length: {0}")]
58    InvalidLength(usize),
59    /// A payload length does not use its shortest valid encoding.
60    #[error("Invalid payload length encoding")]
61    InvalidLengthEncoding,
62    /// Invalid close status code.
63    #[error("Invalid close status code: {0}")]
64    InvalidCloseCode(u16),
65    /// Invalid close-frame payload.
66    #[error("Invalid close-frame payload")]
67    InvalidClosePayload,
68    /// A close-frame description is not valid UTF-8.
69    #[error("Invalid UTF-8 in close-frame description")]
70    InvalidUtf8,
71    /// A message was encoded after a close message.
72    #[error("WebSocket codec is closed")]
73    Closed,
74    /// A payload reached size limit.
75    #[error("A payload reached size limit.")]
76    Overflow,
77    /// Continuation is not started
78    #[error("Continuation is not started.")]
79    ContinuationNotStarted,
80    /// Received new continuation but it is already started
81    #[error("Received new continuation but it is already started")]
82    ContinuationStarted,
83}
84
85/// Errors produced while configuring a WebSocket client.
86#[derive(Clone, Debug, thiserror::Error)]
87pub enum WsConfigError {
88    /// The URI does not contain a scheme.
89    #[error("Missing url scheme")]
90    MissingScheme,
91    /// The URI uses an unsupported scheme.
92    #[error("Unknown url scheme")]
93    UnknownScheme,
94    /// The URI does not contain a host.
95    #[error("Missing host name")]
96    MissingHost,
97    /// The URI could not be parsed.
98    #[error("Url parse error: {0}")]
99    Parse(
100        #[from]
101        #[source]
102        urly::InvalidUrl,
103    ),
104}
105
106impl From<std::convert::Infallible> for WsConfigError {
107    fn from(err: std::convert::Infallible) -> WsConfigError {
108        match err {}
109    }
110}
111
112/// Errors produced while establishing or using a WebSocket client connection.
113#[derive(Debug, thiserror::Error)]
114pub enum WsClientError {
115    /// Invalid client configuration.
116    #[error("Invalid client configuration: {0}")]
117    Config(
118        #[from]
119        #[source]
120        WsConfigError,
121    ),
122    /// Invalid request
123    #[error("Invalid request")]
124    InvalidRequest(
125        #[from]
126        #[source]
127        EncodeError,
128    ),
129    /// Invalid response
130    #[error("Invalid response")]
131    InvalidResponse(
132        #[from]
133        #[source]
134        DecodeError,
135    ),
136    /// Invalid response status
137    #[error("Invalid response status: {0}")]
138    InvalidResponseStatus(StatusCode),
139    /// Invalid upgrade header
140    #[error("Invalid upgrade header")]
141    InvalidUpgradeHeader,
142    /// Invalid connection header
143    #[error("Invalid connection header")]
144    InvalidConnectionHeader(HeaderValue),
145    /// Missing CONNECTION header
146    #[error("Missing CONNECTION header")]
147    MissingConnectionHeader,
148    /// Missing SEC-WEBSOCKET-ACCEPT header
149    #[error("Missing SEC-WEBSOCKET-ACCEPT header")]
150    MissingWebSocketAcceptHeader,
151    /// Invalid challenge response
152    #[error("Invalid challenge response")]
153    InvalidChallengeResponse(String, HeaderValue),
154    /// The server selected an invalid or unrequested WebSocket subprotocol.
155    #[error("Invalid WebSocket subprotocol: {0:?}")]
156    InvalidWebSocketProtocol(HeaderValue),
157    /// The server returned an extension that the client did not offer.
158    #[error("Unexpected WebSocket extensions: {0:?}")]
159    UnexpectedWebSocketExtensions(HeaderValue),
160    /// Protocol error
161    #[error("{0}")]
162    Protocol(
163        #[from]
164        #[source]
165        ProtocolError,
166    ),
167    /// The opening handshake timed out.
168    #[error("Timeout while waiting for response")]
169    Timeout,
170    /// Failed to connect to host
171    #[error("Failed to connect to host: {0}")]
172    Connect(
173        #[from]
174        #[source]
175        ConnectError,
176    ),
177    /// Connector has been disconnected
178    #[error("Connector has been disconnected: {0:?}")]
179    Disconnected(#[source] Option<io::Error>),
180}
181
182impl From<Either<DecodeError, io::Error>> for WsClientError {
183    fn from(err: Either<DecodeError, io::Error>) -> Self {
184        match err {
185            Either::Left(err) => WsClientError::InvalidResponse(err),
186            Either::Right(err) => WsClientError::Disconnected(Some(err)),
187        }
188    }
189}
190
191impl From<Either<EncodeError, io::Error>> for WsClientError {
192    fn from(err: Either<EncodeError, io::Error>) -> Self {
193        match err {
194            Either::Left(err) => WsClientError::InvalidRequest(err),
195            Either::Right(err) => WsClientError::Disconnected(Some(err)),
196        }
197    }
198}
199
200impl Clone for WsClientError {
201    fn clone(&self) -> Self {
202        match self {
203            WsClientError::Config(err) => WsClientError::Config(err.clone()),
204            WsClientError::InvalidRequest(err) => WsClientError::InvalidRequest(err.clone()),
205            WsClientError::InvalidResponse(err) => WsClientError::InvalidResponse(*err),
206            WsClientError::InvalidResponseStatus(err) => WsClientError::InvalidResponseStatus(*err),
207            WsClientError::InvalidUpgradeHeader => WsClientError::InvalidUpgradeHeader,
208            WsClientError::InvalidConnectionHeader(err) => {
209                WsClientError::InvalidConnectionHeader(err.clone())
210            }
211            WsClientError::MissingConnectionHeader => WsClientError::MissingConnectionHeader,
212            WsClientError::MissingWebSocketAcceptHeader => {
213                WsClientError::MissingWebSocketAcceptHeader
214            }
215            WsClientError::InvalidChallengeResponse(n, val) => {
216                WsClientError::InvalidChallengeResponse(n.clone(), val.clone())
217            }
218            WsClientError::InvalidWebSocketProtocol(val) => {
219                WsClientError::InvalidWebSocketProtocol(val.clone())
220            }
221            WsClientError::UnexpectedWebSocketExtensions(val) => {
222                WsClientError::UnexpectedWebSocketExtensions(val.clone())
223            }
224            WsClientError::Protocol(err) => WsClientError::Protocol(*err),
225            WsClientError::Timeout => WsClientError::Timeout,
226            WsClientError::Connect(err) => WsClientError::Connect(err.clone()),
227            WsClientError::Disconnected(err) => {
228                WsClientError::Disconnected(err.as_ref().map(clone_io_error))
229            }
230        }
231    }
232}
233
234impl ErrorDiagnostic for WsClientError {
235    fn signature(&self) -> &'static str {
236        "ntex-ws-client"
237    }
238}
239
240/// Errors produced while validating a WebSocket opening handshake.
241#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
242pub enum HandshakeError {
243    /// Only get method is allowed
244    #[error("Method not allowed")]
245    GetMethodRequired,
246    /// Upgrade header if not set to websocket
247    #[error("Websocket upgrade is expected")]
248    NoWebsocketUpgrade,
249    /// Connection header is not set to upgrade
250    #[error("Connection upgrade is expected")]
251    NoConnectionUpgrade,
252    /// Websocket version header is not set
253    #[error("Websocket version header is required")]
254    NoVersionHeader,
255    /// Unsupported websocket version
256    #[error("Unsupported version")]
257    UnsupportedVersion,
258    /// Websocket key is not set or wrong
259    #[error("Unknown websocket key")]
260    BadWebsocketKey,
261    /// The selected WebSocket subprotocol was not requested by the client.
262    #[error("Invalid websocket subprotocol")]
263    BadWebsocketProtocol,
264}
265
266impl ResponseError for HandshakeError {
267    fn error_response(&self) -> Response {
268        match *self {
269            HandshakeError::GetMethodRequired => {
270                Response::MethodNotAllowed().header(ALLOW, "GET").build()
271            }
272            HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
273                .reason("No WebSocket UPGRADE header found")
274                .build(),
275            HandshakeError::NoConnectionUpgrade => Response::BadRequest()
276                .reason("No CONNECTION upgrade")
277                .build(),
278            HandshakeError::NoVersionHeader => Response::BadRequest()
279                .reason("Websocket version header is required")
280                .header(SEC_WEBSOCKET_VERSION, "13")
281                .build(),
282            HandshakeError::UnsupportedVersion => Response::BadRequest()
283                .reason("Unsupported version")
284                .header(SEC_WEBSOCKET_VERSION, "13")
285                .build(),
286            HandshakeError::BadWebsocketKey => {
287                Response::BadRequest().reason("Handshake error").build()
288            }
289            HandshakeError::BadWebsocketProtocol => Response::BadRequest()
290                .reason("Invalid websocket subprotocol")
291                .build(),
292        }
293    }
294}
295
296impl ResponseError for ProtocolError {}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301
302    #[test]
303    fn test_client_error_from_either() {
304        let err = WsClientError::from(Either::<DecodeError, io::Error>::Left(DecodeError::Method));
305        assert!(matches!(
306            err,
307            WsClientError::InvalidResponse(DecodeError::Method)
308        ));
309        let err = WsClientError::from(Either::<DecodeError, _>::Right(io::Error::other("x")));
310        assert!(matches!(err, WsClientError::Disconnected(Some(_))));
311
312        let err = WsClientError::from(Either::<EncodeError, io::Error>::Left(
313            EncodeError::UnexpectedEof,
314        ));
315        assert!(matches!(
316            err,
317            WsClientError::InvalidRequest(EncodeError::UnexpectedEof)
318        ));
319        let err = WsClientError::from(Either::<EncodeError, _>::Right(io::Error::other("x")));
320        assert!(matches!(err, WsClientError::Disconnected(Some(_))));
321    }
322
323    #[test]
324    fn test_client_error_clone() {
325        let hdr = HeaderValue::from_static("v");
326        let errs = [
327            WsClientError::Config(WsConfigError::MissingHost),
328            WsClientError::InvalidRequest(EncodeError::UnexpectedEof),
329            WsClientError::InvalidResponse(DecodeError::Method),
330            WsClientError::InvalidResponseStatus(StatusCode::OK),
331            WsClientError::InvalidUpgradeHeader,
332            WsClientError::InvalidConnectionHeader(hdr.clone()),
333            WsClientError::MissingConnectionHeader,
334            WsClientError::MissingWebSocketAcceptHeader,
335            WsClientError::InvalidChallengeResponse("key".into(), hdr.clone()),
336            WsClientError::InvalidWebSocketProtocol(hdr.clone()),
337            WsClientError::UnexpectedWebSocketExtensions(hdr),
338            WsClientError::Protocol(ProtocolError::Overflow),
339            WsClientError::Timeout,
340            WsClientError::Connect(ConnectError::Unresolved),
341            WsClientError::Disconnected(None),
342            WsClientError::Disconnected(Some(io::Error::other("disconnected"))),
343        ];
344        for err in errs {
345            assert_eq!(err.clone().to_string(), err.to_string());
346            assert_eq!(format!("{:?}", err.clone()), format!("{err:?}"));
347            assert_eq!(err.signature(), "ntex-ws-client");
348        }
349    }
350}