aerosocket_core/
protocol.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Opcode {
9 Continuation = 0x0,
11 Text = 0x1,
13 Binary = 0x2,
15 Reserved3 = 0x3,
17 Reserved4 = 0x4,
19 Reserved5 = 0x5,
21 Reserved6 = 0x6,
23 Reserved7 = 0x7,
25 Close = 0x8,
27 Ping = 0x9,
29 Pong = 0xA,
31 ReservedB = 0xB,
33 ReservedC = 0xC,
35 ReservedD = 0xD,
37 ReservedE = 0xE,
39 ReservedF = 0xF,
41}
42
43impl Opcode {
44 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 pub fn value(&self) -> u8 {
59 *self as u8
60 }
61
62 pub fn is_control(&self) -> bool {
64 matches!(self, Opcode::Close | Opcode::Ping | Opcode::Pong)
65 }
66
67 pub fn is_data(&self) -> bool {
69 matches!(self, Opcode::Text | Opcode::Binary | Opcode::Continuation)
70 }
71
72 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
90pub mod constants {
92 pub const WEBSOCKET_VERSION: &str = "13";
94
95 pub const HEADER_UPGRADE: &str = "upgrade";
97
98 pub const HEADER_CONNECTION: &str = "connection";
100
101 pub const HEADER_SEC_WEBSOCKET_KEY: &str = "sec-websocket-key";
103
104 pub const HEADER_SEC_WEBSOCKET_VERSION: &str = "sec-websocket-version";
106
107 pub const HEADER_SEC_WEBSOCKET_PROTOCOL: &str = "sec-websocket-protocol";
109
110 pub const HEADER_SEC_WEBSOCKET_EXTENSIONS: &str = "sec-websocket-extensions";
112
113 pub const HEADER_SEC_WEBSOCKET_ACCEPT: &str = "sec-websocket-accept";
115
116 pub const WEBSOCKET_MAGIC: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
118
119 pub const DEFAULT_MAX_FRAME_SIZE: usize = 16 * 1024 * 1024; pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024; pub const DEFAULT_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
127
128 pub const DEFAULT_IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300); pub const WEBSOCKET_KEY_LEN: usize = 16;
133
134 pub const WEBSOCKET_ACCEPT_LEN: usize = 28;
136
137 pub const MAX_HEADER_SIZE: usize = 8192; pub const MIN_CLOSE_PAYLOAD_SIZE: usize = 2;
142
143 pub const MAX_CLOSE_REASON_SIZE: usize = 123;
145}
146
147pub mod frame {
149 pub const FIN_BIT: u8 = 0x80;
151
152 pub const RSV1_BIT: u8 = 0x40;
154
155 pub const RSV2_BIT: u8 = 0x20;
157
158 pub const RSV3_BIT: u8 = 0x10;
160
161 pub const OPCODE_MASK: u8 = 0x0F;
163
164 pub const MASK_BIT: u8 = 0x80;
166
167 pub const PAYLOAD_LEN_MASK: u8 = 0x7F;
169
170 pub const PAYLOAD_LEN_16: u8 = 126;
172
173 pub const PAYLOAD_LEN_64: u8 = 127;
175
176 pub const MASKING_KEY_LEN: usize = 4;
178}
179
180pub mod http_status {
182 pub const SWITCHING_PROTOCOLS: u16 = 101;
184
185 pub const BAD_REQUEST: u16 = 400;
187
188 pub const UNAUTHORIZED: u16 = 401;
190
191 pub const FORBIDDEN: u16 = 403;
193
194 pub const METHOD_NOT_ALLOWED: u16 = 405;
196
197 pub const UPGRADE_REQUIRED: u16 = 426;
199
200 pub const INTERNAL_SERVER_ERROR: u16 = 500;
202
203 pub const SERVICE_UNAVAILABLE: u16 = 503;
205}
206
207pub mod http_method {
209 pub const GET: &str = "GET";
211
212 pub const HEAD: &str = "HEAD";
214
215 pub const POST: &str = "POST";
217
218 pub const PUT: &str = "PUT";
220
221 pub const DELETE: &str = "DELETE";
223
224 pub const CONNECT: &str = "CONNECT";
226
227 pub const OPTIONS: &str = "OPTIONS";
229
230 pub const TRACE: &str = "TRACE";
232
233 pub const PATCH: &str = "PATCH";
235}
236
237pub mod http_header {
239 pub const HOST: &str = "host";
241
242 pub const USER_AGENT: &str = "user-agent";
244
245 pub const ACCEPT: &str = "accept";
247
248 pub const CONNECTION: &str = "connection";
250
251 pub const UPGRADE: &str = "upgrade";
253
254 pub const ORIGIN: &str = "origin";
256
257 pub const SEC_WEBSOCKET_KEY: &str = "sec-websocket-key";
259
260 pub const SEC_WEBSOCKET_VERSION: &str = "sec-websocket-version";
262
263 pub const SEC_WEBSOCKET_PROTOCOL: &str = "sec-websocket-protocol";
265
266 pub const SEC_WEBSOCKET_EXTENSIONS: &str = "sec-websocket-extensions";
268
269 pub const SEC_WEBSOCKET_ACCEPT: &str = "sec-websocket-accept";
271
272 pub const AUTHORIZATION: &str = "authorization";
274
275 pub const COOKIE: &str = "cookie";
277
278 pub const SET_COOKIE: &str = "set-cookie";
280
281 pub const CONTENT_TYPE: &str = "content-type";
283
284 pub const CONTENT_LENGTH: &str = "content-length";
286
287 pub const DATE: &str = "date";
289
290 pub const SERVER: &str = "server";
292}
293
294pub mod http_value {
296 pub const WEBSOCKET: &str = "websocket";
298
299 pub const UPGRADE: &str = "Upgrade";
301
302 pub const KEEP_ALIVE: &str = "keep-alive";
304
305 pub const CLOSE: &str = "close";
307
308 pub const APPLICATION_JSON: &str = "application/json";
310
311 pub const TEXT_PLAIN: &str = "text/plain";
313
314 pub const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";
316}
317
318pub mod extensions {
320 pub const PERMESSAGE_DEFLATE: &str = "permessage-deflate";
322
323 pub const CLIENT_CONTEXT_TAKEOVER: &str = "client_context_takeover";
325
326 pub const SERVER_CONTEXT_TAKEOVER: &str = "server_context_takeover";
328
329 pub const CLIENT_MAX_WINDOW_BITS: &str = "client_max_window_bits";
331
332 pub const SERVER_MAX_WINDOW_BITS: &str = "server_max_window_bits";
334
335 pub const CLIENT_NO_CONTEXT_TAKEOVER: &str = "client_no_context_takeover";
337
338 pub const SERVER_NO_CONTEXT_TAKEOVER: &str = "server_no_context_takeover";
340}
341
342pub mod utils {
344 use base64::{engine::general_purpose, Engine as _};
345 use sha1::{Digest, Sha1};
346
347 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 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 pub fn validate_key(key: &str) -> bool {
364 key.len() == 24 && general_purpose::STANDARD.decode(key).is_ok()
365 }
366
367 pub fn validate_version(version: &str) -> bool {
369 version == super::constants::WEBSOCKET_VERSION
370 }
371
372 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=="; 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)); assert!(utils::is_valid_close_code(500)); }
426}