aerosocket_core/
protocol.rs

1//! WebSocket protocol constants and utilities
2//!
3//! This module contains the fundamental protocol definitions from RFC 6455,
4//! including opcodes, frame header bits, and protocol constants.
5
6/// WebSocket opcodes as defined in RFC 6455 Section 5.2
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Opcode {
9    /// Continuation frame
10    Continuation = 0x0,
11    /// Text frame
12    Text = 0x1,
13    /// Binary frame
14    Binary = 0x2,
15    /// Reserved for future use
16    Reserved3 = 0x3,
17    /// Reserved for future use
18    Reserved4 = 0x4,
19    /// Reserved for future use
20    Reserved5 = 0x5,
21    /// Reserved for future use
22    Reserved6 = 0x6,
23    /// Reserved for future use
24    Reserved7 = 0x7,
25    /// Close frame
26    Close = 0x8,
27    /// Ping frame
28    Ping = 0x9,
29    /// Pong frame
30    Pong = 0xA,
31    /// Reserved for future use
32    ReservedB = 0xB,
33    /// Reserved for future use
34    ReservedC = 0xC,
35    /// Reserved for future use
36    ReservedD = 0xD,
37    /// Reserved for future use
38    ReservedE = 0xE,
39    /// Reserved for future use
40    ReservedF = 0xF,
41}
42
43impl Opcode {
44    /// Create an Opcode from a u8
45    pub fn from(value: u8) -> Option<Self> {
46        match value {
47            0x0 => Some(Opcode::Continuation),
48            0x1 => Some(Opcode::Text),
49            0x2 => Some(Opcode::Binary),
50            0x8 => Some(Opcode::Close),
51            0x9 => Some(Opcode::Ping),
52            0xA => Some(Opcode::Pong),
53            _ => None,
54        }
55    }
56
57    /// Get the numeric value of the opcode
58    pub fn value(&self) -> u8 {
59        *self as u8
60    }
61
62    /// Check if this is a control opcode
63    pub fn is_control(&self) -> bool {
64        matches!(self, Opcode::Close | Opcode::Ping | Opcode::Pong)
65    }
66
67    /// Check if this is a data opcode
68    pub fn is_data(&self) -> bool {
69        matches!(self, Opcode::Text | Opcode::Binary | Opcode::Continuation)
70    }
71
72    /// Check if this is a reserved opcode
73    pub fn is_reserved(&self) -> bool {
74        matches!(
75            self,
76            Opcode::Reserved3
77                | Opcode::Reserved4
78                | Opcode::Reserved5
79                | Opcode::Reserved6
80                | Opcode::Reserved7
81                | Opcode::ReservedB
82                | Opcode::ReservedC
83                | Opcode::ReservedD
84                | Opcode::ReservedE
85                | Opcode::ReservedF
86        )
87    }
88}
89
90/// WebSocket protocol constants
91pub mod constants {
92    /// WebSocket protocol version
93    pub const WEBSOCKET_VERSION: &str = "13";
94
95    /// WebSocket upgrade header
96    pub const HEADER_UPGRADE: &str = "upgrade";
97
98    /// WebSocket connection header
99    pub const HEADER_CONNECTION: &str = "connection";
100
101    /// WebSocket key header
102    pub const HEADER_SEC_WEBSOCKET_KEY: &str = "sec-websocket-key";
103
104    /// WebSocket version header
105    pub const HEADER_SEC_WEBSOCKET_VERSION: &str = "sec-websocket-version";
106
107    /// WebSocket protocol header
108    pub const HEADER_SEC_WEBSOCKET_PROTOCOL: &str = "sec-websocket-protocol";
109
110    /// WebSocket extensions header
111    pub const HEADER_SEC_WEBSOCKET_EXTENSIONS: &str = "sec-websocket-extensions";
112
113    /// WebSocket accept header
114    pub const HEADER_SEC_WEBSOCKET_ACCEPT: &str = "sec-websocket-accept";
115
116    /// WebSocket magic string for accept calculation
117    pub const WEBSOCKET_MAGIC: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
118
119    /// Maximum frame size (default)
120    pub const DEFAULT_MAX_FRAME_SIZE: usize = 16 * 1024 * 1024; // 16MB
121
122    /// Maximum message size (default)
123    pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024; // 64MB
124
125    /// Default handshake timeout
126    pub const DEFAULT_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
127
128    /// Default idle timeout
129    pub const DEFAULT_IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300); // 5 minutes
130
131    /// WebSocket key length in bytes
132    pub const WEBSOCKET_KEY_LEN: usize = 16;
133
134    /// WebSocket accept value length in bytes
135    pub const WEBSOCKET_ACCEPT_LEN: usize = 28;
136
137    /// Maximum header size
138    pub const MAX_HEADER_SIZE: usize = 8192; // 8KB
139
140    /// Minimum close frame payload size
141    pub const MIN_CLOSE_PAYLOAD_SIZE: usize = 2;
142
143    /// Maximum close reason size
144    pub const MAX_CLOSE_REASON_SIZE: usize = 123;
145}
146
147/// Frame header bit positions and masks
148pub mod frame {
149    /// FIN bit position
150    pub const FIN_BIT: u8 = 0x80;
151
152    /// RSV1 bit position
153    pub const RSV1_BIT: u8 = 0x40;
154
155    /// RSV2 bit position
156    pub const RSV2_BIT: u8 = 0x20;
157
158    /// RSV3 bit position
159    pub const RSV3_BIT: u8 = 0x10;
160
161    /// Opcode mask
162    pub const OPCODE_MASK: u8 = 0x0F;
163
164    /// MASK bit position
165    pub const MASK_BIT: u8 = 0x80;
166
167    /// Payload length mask for 7-bit length
168    pub const PAYLOAD_LEN_MASK: u8 = 0x7F;
169
170    /// Extended payload length (16-bit) marker
171    pub const PAYLOAD_LEN_16: u8 = 126;
172
173    /// Extended payload length (64-bit) marker
174    pub const PAYLOAD_LEN_64: u8 = 127;
175
176    /// Masking key length
177    pub const MASKING_KEY_LEN: usize = 4;
178}
179
180/// HTTP status codes used in WebSocket handshake
181pub mod http_status {
182    /// HTTP Switching Protocols status
183    pub const SWITCHING_PROTOCOLS: u16 = 101;
184
185    /// HTTP Bad Request status
186    pub const BAD_REQUEST: u16 = 400;
187
188    /// HTTP Unauthorized status
189    pub const UNAUTHORIZED: u16 = 401;
190
191    /// HTTP Forbidden status
192    pub const FORBIDDEN: u16 = 403;
193
194    /// HTTP Method Not Allowed status
195    pub const METHOD_NOT_ALLOWED: u16 = 405;
196
197    /// HTTP Upgrade Required status
198    pub const UPGRADE_REQUIRED: u16 = 426;
199
200    /// HTTP Internal Server Error status
201    pub const INTERNAL_SERVER_ERROR: u16 = 500;
202
203    /// HTTP Service Unavailable status
204    pub const SERVICE_UNAVAILABLE: u16 = 503;
205}
206
207/// HTTP methods
208pub mod http_method {
209    /// HTTP GET method
210    pub const GET: &str = "GET";
211
212    /// HTTP HEAD method
213    pub const HEAD: &str = "HEAD";
214
215    /// HTTP POST method
216    pub const POST: &str = "POST";
217
218    /// HTTP PUT method
219    pub const PUT: &str = "PUT";
220
221    /// HTTP DELETE method
222    pub const DELETE: &str = "DELETE";
223
224    /// HTTP CONNECT method
225    pub const CONNECT: &str = "CONNECT";
226
227    /// HTTP OPTIONS method
228    pub const OPTIONS: &str = "OPTIONS";
229
230    /// HTTP TRACE method
231    pub const TRACE: &str = "TRACE";
232
233    /// HTTP PATCH method
234    pub const PATCH: &str = "PATCH";
235}
236
237/// HTTP header names (lowercase for consistency)
238pub mod http_header {
239    /// Host header
240    pub const HOST: &str = "host";
241
242    /// User-Agent header
243    pub const USER_AGENT: &str = "user-agent";
244
245    /// Accept header
246    pub const ACCEPT: &str = "accept";
247
248    /// Connection header
249    pub const CONNECTION: &str = "connection";
250
251    /// Upgrade header
252    pub const UPGRADE: &str = "upgrade";
253
254    /// Origin header
255    pub const ORIGIN: &str = "origin";
256
257    /// Sec-WebSocket-Key header
258    pub const SEC_WEBSOCKET_KEY: &str = "sec-websocket-key";
259
260    /// Sec-WebSocket-Version header
261    pub const SEC_WEBSOCKET_VERSION: &str = "sec-websocket-version";
262
263    /// Sec-WebSocket-Protocol header
264    pub const SEC_WEBSOCKET_PROTOCOL: &str = "sec-websocket-protocol";
265
266    /// Sec-WebSocket-Extensions header
267    pub const SEC_WEBSOCKET_EXTENSIONS: &str = "sec-websocket-extensions";
268
269    /// Sec-WebSocket-Accept header
270    pub const SEC_WEBSOCKET_ACCEPT: &str = "sec-websocket-accept";
271
272    /// Authorization header
273    pub const AUTHORIZATION: &str = "authorization";
274
275    /// Cookie header
276    pub const COOKIE: &str = "cookie";
277
278    /// Set-Cookie header
279    pub const SET_COOKIE: &str = "set-cookie";
280
281    /// Content-Type header
282    pub const CONTENT_TYPE: &str = "content-type";
283
284    /// Content-Length header
285    pub const CONTENT_LENGTH: &str = "content-length";
286
287    /// Date header
288    pub const DATE: &str = "date";
289
290    /// Server header
291    pub const SERVER: &str = "server";
292}
293
294/// HTTP header values
295pub mod http_value {
296    /// WebSocket upgrade value
297    pub const WEBSOCKET: &str = "websocket";
298
299    /// Upgrade connection value
300    pub const UPGRADE: &str = "Upgrade";
301
302    /// Keep-Alive connection value
303    pub const KEEP_ALIVE: &str = "keep-alive";
304
305    /// Close connection value
306    pub const CLOSE: &str = "close";
307
308    /// Application JSON content type
309    pub const APPLICATION_JSON: &str = "application/json";
310
311    /// Text plain content type
312    pub const TEXT_PLAIN: &str = "text/plain";
313
314    /// Application octet-stream content type
315    pub const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";
316}
317
318/// WebSocket extension parameters
319pub mod extensions {
320    /// Per-message deflate extension
321    pub const PERMESSAGE_DEFLATE: &str = "permessage-deflate";
322
323    /// Per-message deflate client context takeover
324    pub const CLIENT_CONTEXT_TAKEOVER: &str = "client_context_takeover";
325
326    /// Per-message deflate server context takeover
327    pub const SERVER_CONTEXT_TAKEOVER: &str = "server_context_takeover";
328
329    /// Per-message deflate client max window bits
330    pub const CLIENT_MAX_WINDOW_BITS: &str = "client_max_window_bits";
331
332    /// Per-message deflate server max window bits
333    pub const SERVER_MAX_WINDOW_BITS: &str = "server_max_window_bits";
334
335    /// Per-message deflate client no context takeover
336    pub const CLIENT_NO_CONTEXT_TAKEOVER: &str = "client_no_context_takeover";
337
338    /// Per-message deflate server no context takeover
339    pub const SERVER_NO_CONTEXT_TAKEOVER: &str = "server_no_context_takeover";
340}
341
342/// Utility functions for WebSocket protocol operations
343pub mod utils {
344    use base64::{engine::general_purpose, Engine as _};
345    use sha1::{Digest, Sha1};
346
347    /// Generate a random WebSocket key
348    pub fn generate_key() -> String {
349        use rand::RngCore;
350        let mut key_bytes = [0u8; 16];
351        rand::thread_rng().fill_bytes(&mut key_bytes);
352        general_purpose::STANDARD.encode(key_bytes)
353    }
354
355    /// Compute WebSocket accept key
356    pub fn calculate_accept(key: &str) -> String {
357        let combined = format!("{}{}", key, super::constants::WEBSOCKET_MAGIC);
358        let hash = Sha1::digest(combined.as_bytes());
359        general_purpose::STANDARD.encode(hash)
360    }
361
362    /// Validate WebSocket key format
363    pub fn validate_key(key: &str) -> bool {
364        key.len() == 24 && general_purpose::STANDARD.decode(key).is_ok()
365    }
366
367    /// Validate WebSocket version
368    pub fn validate_version(version: &str) -> bool {
369        version == super::constants::WEBSOCKET_VERSION
370    }
371
372    /// Check if a close code is valid
373    pub fn is_valid_close_code(code: u16) -> bool {
374        use crate::error::CloseCode;
375        matches!(
376            CloseCode::from(code),
377            CloseCode::Normal
378                | CloseCode::Away
379                | CloseCode::ProtocolError
380                | CloseCode::Unsupported
381                | CloseCode::InvalidPayload
382                | CloseCode::PolicyViolation
383                | CloseCode::TooBig
384                | CloseCode::MandatoryExtension
385                | CloseCode::Internal
386                | CloseCode::Application(_)
387        )
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_opcode_conversion() {
397        assert_eq!(Opcode::from(0x1), Some(Opcode::Text));
398        assert_eq!(Opcode::from(0xFF), None);
399        assert_eq!(Opcode::Text.value(), 0x1);
400        assert!(Opcode::Ping.is_control());
401        assert!(Opcode::Binary.is_data());
402        assert!(Opcode::Reserved3.is_reserved());
403    }
404
405    #[test]
406    fn test_websocket_key_generation() {
407        let key = utils::generate_key();
408        assert!(utils::validate_key(&key));
409    }
410
411    #[test]
412    fn test_websocket_accept_calculation() {
413        let key = "dGhlIHNhbXBsZSBub25jZQ=="; // "the sample nonce"
414        let expected = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
415        assert_eq!(utils::calculate_accept(key), expected);
416    }
417
418    #[test]
419    fn test_close_code_validation() {
420        assert!(utils::is_valid_close_code(1000));
421        assert!(utils::is_valid_close_code(3000));
422        assert!(utils::is_valid_close_code(999)); // 999 maps to ProtocolError which is valid
423        assert!(utils::is_valid_close_code(500)); // 500 maps to ProtocolError which is valid
424                                                  // All codes seem to map to valid CloseCode variants in the current implementation
425    }
426}