1#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
7
8use std::fmt;
9use thiserror::Error;
10
11pub type Result<T> = std::result::Result<T, Error>;
13
14#[derive(Error, Debug)]
16pub enum Error {
17 #[error("Protocol error: {0}")]
19 Protocol(#[from] ProtocolError),
20
21 #[error("I/O error: {0}")]
23 Io(#[from] std::io::Error),
24
25 #[error("Configuration error: {0}")]
27 Config(#[from] ConfigError),
28
29 #[error("Frame error: {0}")]
31 Frame(#[from] FrameError),
32
33 #[error("Message error: {0}")]
35 Message(#[from] MessageError),
36
37 #[error("Security error: {0}")]
39 Security(#[from] SecurityError),
40
41 #[error("Timeout error: {0}")]
43 Timeout(#[from] TimeoutError),
44
45 #[error("Close error: {0}")]
47 Close(#[from] CloseError),
48
49 #[error("Connection error: {0}")]
51 Connection(String),
52
53 #[error("Error: {0}")]
55 Other(String),
56
57 #[error("Buffer capacity exceeded: {size} bytes")]
59 CapacityExceeded {
60 size: usize,
62 },
63
64 #[error("Invalid UTF-8 in text frame")]
66 InvalidUtf8,
67
68 #[error("Connection closed: {code} - {reason}")]
70 Closed {
71 code: CloseCode,
73 reason: String,
75 },
76}
77
78#[derive(Error, Debug, Clone)]
80pub enum ProtocolError {
81 #[error("Unsupported WebSocket version")]
83 UnsupportedVersion,
84
85 #[error("Invalid WebSocket upgrade request")]
87 InvalidUpgradeRequest,
88
89 #[error("Missing required header: {0}")]
91 MissingHeader(String),
92
93 #[error("Invalid header value for {header}: {value}")]
95 InvalidHeader {
96 header: String,
98 value: String,
100 },
101
102 #[error("Extension negotiation failed: {0}")]
104 ExtensionNegotiation(String),
105
106 #[error("Subprotocol negotiation failed")]
108 SubprotocolNegotiation,
109
110 #[error("Invalid frame format: {0}")]
112 InvalidFrame(String),
113
114 #[error("Control frames cannot be fragmented")]
116 FragmentedControlFrame,
117
118 #[error("Invalid close code: {0}")]
120 InvalidCloseCode(u16),
121
122 #[error("Reserved bits set in frame")]
124 ReservedBitsSet,
125
126 #[error("Invalid HTTP method: {0}")]
128 InvalidMethod(String),
129
130 #[error("Invalid format: {0}")]
132 InvalidFormat(String),
133
134 #[error("Invalid origin - expected: {expected}, received: {received}")]
136 InvalidOrigin {
137 expected: String,
139 received: String,
141 },
142
143 #[error("Unsupported WebSocket protocol: {0}")]
145 UnsupportedProtocol(String),
146
147 #[error("Unexpected HTTP status: {0}")]
149 UnexpectedStatus(u16),
150
151 #[error("Invalid WebSocket accept key - expected: {expected}, received: {received}")]
153 InvalidAcceptKey {
154 expected: String,
156 received: String,
158 },
159}
160
161#[derive(Error, Debug, Clone)]
163pub enum FrameError {
164 #[error("Insufficient data: need {needed} bytes, have {have}")]
166 InsufficientData {
167 needed: usize,
169 have: usize,
171 },
172
173 #[error("Frame too large: {size} bytes (max: {max})")]
175 TooLarge {
176 size: usize,
178 max: usize,
180 },
181
182 #[error("Invalid frame header: {0}")]
184 InvalidHeader(String),
185
186 #[error("Invalid masking: {0}")]
188 InvalidMasking(String),
189
190 #[error("Invalid opcode: {0}")]
192 InvalidOpcode(u8),
193
194 #[error("Reserved bits set in frame")]
196 ReservedBitsSet,
197
198 #[error("Decompression failed")]
200 DecompressionFailed,
201
202 #[error("Control frames cannot be fragmented")]
204 FragmentedControlFrame,
205}
206
207#[derive(Error, Debug, Clone)]
209pub enum ConfigError {
210 #[error("Invalid configuration value for {field}: {value}")]
212 InvalidValue {
213 field: String,
215 value: String,
217 },
218
219 #[error("Missing required configuration: {field}")]
221 MissingField {
222 field: String,
224 },
225
226 #[error("Configuration validation failed: {0}")]
228 Validation(String),
229}
230
231#[derive(Error, Debug, Clone)]
233pub enum MessageError {
234 #[error("Message too large: {size} bytes (max: {max})")]
236 TooLarge {
237 size: usize,
239 max: usize,
241 },
242
243 #[error("Invalid message format: {0}")]
245 InvalidFormat(String),
246
247 #[error("Control messages cannot be fragmented")]
249 FragmentedControl,
250
251 #[error("Incomplete message: missing {missing}")]
253 Incomplete {
254 missing: String,
256 },
257}
258
259#[derive(Error, Debug, Clone)]
261pub enum SecurityError {
262 #[error("Authentication failed: {0}")]
264 Authentication(String),
265
266 #[error("Authorization failed: {0}")]
268 Authorization(String),
269
270 #[error("Rate limit exceeded")]
272 RateLimit,
273
274 #[error("Connection blocked: {reason}")]
276 Blocked {
277 reason: String,
279 },
280
281 #[error("Security policy violation: {0}")]
283 PolicyViolation(String),
284}
285
286#[derive(Error, Debug, Clone)]
288pub enum TimeoutError {
289 #[error("Handshake timeout: {timeout:?}")]
291 Handshake {
292 timeout: std::time::Duration,
294 },
295
296 #[error("Read timeout: {timeout:?}")]
298 Read {
299 timeout: std::time::Duration,
301 },
302
303 #[error("Write timeout: {timeout:?}")]
305 Write {
306 timeout: std::time::Duration,
308 },
309
310 #[error("Idle timeout: {timeout:?}")]
312 Idle {
313 timeout: std::time::Duration,
315 },
316}
317
318#[derive(Error, Debug, Clone)]
320pub enum CloseError {
321 #[error("Invalid close code: {code}")]
323 InvalidCode {
324 code: u16,
326 },
327
328 #[error("Close reason too long: {len} bytes (max: {max})")]
330 ReasonTooLong {
331 len: usize,
333 max: usize,
335 },
336
337 #[error("Invalid UTF-8 in close reason")]
339 InvalidUtf8,
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344#[repr(u16)]
345pub enum CloseCode {
346 Normal = 1000,
348
349 Away = 1001,
351
352 ProtocolError = 1002,
354
355 Unsupported = 1003,
357
358 NoStatus = 1005,
360
361 Abnormal = 1006,
363
364 InvalidPayload = 1007,
366
367 PolicyViolation = 1008,
369
370 TooBig = 1009,
372
373 MandatoryExtension = 1010,
375
376 Internal = 1011,
378
379 TlsHandshake = 1015,
381
382 Application(u16) = 3000,
384}
385
386impl CloseCode {
387 pub fn from(code: u16) -> Self {
389 match code {
390 1000 => CloseCode::Normal,
391 1001 => CloseCode::Away,
392 1002 => CloseCode::ProtocolError,
393 1003 => CloseCode::Unsupported,
394 1005 => CloseCode::NoStatus,
395 1006 => CloseCode::Abnormal,
396 1007 => CloseCode::InvalidPayload,
397 1008 => CloseCode::PolicyViolation,
398 1009 => CloseCode::TooBig,
399 1010 => CloseCode::MandatoryExtension,
400 1011 => CloseCode::Internal,
401 1015 => CloseCode::TlsHandshake,
402 code if (3000..=4999).contains(&code) => CloseCode::Application(code),
403 _ => CloseCode::ProtocolError,
404 }
405 }
406
407 pub fn code(&self) -> u16 {
409 match self {
410 CloseCode::Normal => 1000,
411 CloseCode::Away => 1001,
412 CloseCode::ProtocolError => 1002,
413 CloseCode::Unsupported => 1003,
414 CloseCode::NoStatus => 1005,
415 CloseCode::Abnormal => 1006,
416 CloseCode::InvalidPayload => 1007,
417 CloseCode::PolicyViolation => 1008,
418 CloseCode::TooBig => 1009,
419 CloseCode::MandatoryExtension => 1010,
420 CloseCode::Internal => 1011,
421 CloseCode::TlsHandshake => 1015,
422 CloseCode::Application(code) => *code,
423 }
424 }
425
426 pub fn is_reserved(&self) -> bool {
428 matches!(
429 self,
430 CloseCode::NoStatus | CloseCode::Abnormal | CloseCode::TlsHandshake
431 )
432 }
433
434 pub fn is_error(&self) -> bool {
436 !matches!(self, CloseCode::Normal | CloseCode::Away)
437 }
438}
439
440impl fmt::Display for CloseCode {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 write!(f, "{} ({})", self, self.code())
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn test_close_code_conversion() {
452 assert_eq!(CloseCode::from(1000), CloseCode::Normal);
453 assert_eq!(CloseCode::from(3000), CloseCode::Application(3000));
454 assert_eq!(CloseCode::from(999), CloseCode::ProtocolError);
455 }
456
457 #[test]
458 fn test_error_display() {
459 let err = Error::Protocol(ProtocolError::UnsupportedVersion);
460 let msg = err.to_string();
461 println!("Error message: {}", msg); assert!(msg.contains("protocol") || msg.contains("version") || msg.contains("WebSocket"));
463 }
464}