http_type/websocket_frame/impl.rs
1use super::*;
2
3/// Implements the `Default` trait for `WebSocketFrame`.
4///
5/// Provides a default `WebSocketFrame` with `fin: false`, `opcode: WebSocketOpcode::Text`,
6/// `mask: false`, and an empty `payload_data`.
7impl Default for WebSocketFrame {
8 /// Returns the default `WebSocketFrame`.
9 ///
10 /// # Returns
11 ///
12 /// - `Self` - A default `WebSocketFrame` instance.
13 #[inline(always)]
14 fn default() -> Self {
15 Self {
16 fin: false,
17 opcode: WebSocketOpcode::Text,
18 mask: false,
19 payload_data: Vec::new(),
20 }
21 }
22}
23
24/// Inherent implementation of [`WebSocketOpcode`].
25impl WebSocketOpcode {
26 /// Creates a `WebSocketOpcode` from a raw u8 value.
27 ///
28 /// # Arguments
29 ///
30 /// - `u8` - The raw opcode value.
31 ///
32 /// # Returns
33 ///
34 /// - `Self` - A `WebSocketOpcode` enum variant corresponding to the raw value.
35 #[inline(always)]
36 pub fn from_u8(opcode: u8) -> Self {
37 match opcode {
38 0x0 => Self::Continuation,
39 0x1 => Self::Text,
40 0x2 => Self::Binary,
41 0x8 => Self::Close,
42 0x9 => Self::Ping,
43 0xA => Self::Pong,
44 _ => Self::Reserved(opcode),
45 }
46 }
47
48 /// Converts the `WebSocketOpcode` to its raw u8 value.
49 ///
50 /// # Returns
51 ///
52 /// - `u8` - The raw u8 value of the opcode.
53 #[inline(always)]
54 pub fn to_u8(&self) -> u8 {
55 match self {
56 Self::Continuation => 0x0,
57 Self::Text => 0x1,
58 Self::Binary => 0x2,
59 Self::Close => 0x8,
60 Self::Ping => 0x9,
61 Self::Pong => 0xA,
62 Self::Reserved(code) => *code,
63 }
64 }
65
66 /// Checks if the opcode is a control frame.
67 ///
68 /// # Returns
69 ///
70 /// - `bool` - `true` if the opcode represents a control frame (Close, Ping, Pong), otherwise `false`.
71 #[inline(always)]
72 pub fn is_control(&self) -> bool {
73 matches!(self, Self::Close | Self::Ping | Self::Pong)
74 }
75
76 /// Checks if the opcode is a data frame.
77 ///
78 /// # Returns
79 ///
80 /// - `bool` - `true` if the opcode represents a data frame (Text, Binary, Continuation), otherwise `false`.
81 #[inline(always)]
82 pub fn is_data(&self) -> bool {
83 matches!(self, Self::Text | Self::Binary | Self::Continuation)
84 }
85
86 /// Checks if the opcode is a continuation frame.
87 ///
88 /// # Returns
89 ///
90 /// - `bool` - `true` if the opcode is `Continuation`, otherwise `false`.
91 #[inline(always)]
92 pub fn is_continuation(&self) -> bool {
93 matches!(self, Self::Continuation)
94 }
95
96 /// Checks if the opcode is a text frame.
97 ///
98 /// # Returns
99 ///
100 /// - `bool` - `true` if the opcode is `Text`, otherwise `false`.
101 #[inline(always)]
102 pub fn is_text(&self) -> bool {
103 matches!(self, Self::Text)
104 }
105
106 /// Checks if the opcode is a binary frame.
107 ///
108 /// # Returns
109 ///
110 /// - `bool` - `true` if the opcode is `Binary`, otherwise `false`.
111 #[inline(always)]
112 pub fn is_binary(&self) -> bool {
113 matches!(self, Self::Binary)
114 }
115
116 /// Checks if the opcode is a close frame.
117 ///
118 /// # Returns
119 ///
120 /// - `bool` - `true` if the opcode is `Close`, otherwise `false`.
121 #[inline(always)]
122 pub fn is_close(&self) -> bool {
123 matches!(self, Self::Close)
124 }
125
126 /// Checks if the opcode is a ping frame.
127 ///
128 /// # Returns
129 ///
130 /// - `bool` - `true` if the opcode is `Ping`, otherwise `false`.
131 #[inline(always)]
132 pub fn is_ping(&self) -> bool {
133 matches!(self, Self::Ping)
134 }
135
136 /// Checks if the opcode is a pong frame.
137 ///
138 /// # Returns
139 ///
140 /// - `bool` - `true` if the opcode is `Pong`, otherwise `false`.
141 #[inline(always)]
142 pub fn is_pong(&self) -> bool {
143 matches!(self, Self::Pong)
144 }
145
146 /// Checks if the opcode is a reserved frame.
147 ///
148 /// # Returns
149 ///
150 /// - `bool` - `true` if the opcode is `Reserved(_)`, otherwise `false`.
151 #[inline(always)]
152 pub fn is_reserved(&self) -> bool {
153 matches!(self, Self::Reserved(_))
154 }
155}
156
157/// Inherent implementation of [`WebSocketFrame`].
158impl WebSocketFrame {
159 /// Decodes a WebSocket frame from the provided data slice.
160 ///
161 /// This function parses the raw bytes from a WebSocket stream according to the WebSocket protocol
162 /// specification to reconstruct a `WebSocketFrame`. It handles FIN bit, opcode, mask bit,
163 /// payload length (including extended lengths), mask key, and the payload data itself.
164 ///
165 /// # Arguments
166 ///
167 /// - `D` - The raw data to decode into a WebSocket frame, which requires the `AsRef<[u8]>` bound.
168 ///
169 /// # Returns
170 ///
171 /// - `Option<(WebSocketFrame, usize)>` - `Some((frame, consumed))` when the frame is
172 /// successfully decoded, otherwise `None` when the frame is incomplete or malformed.
173 pub fn decode_ws_frame<D>(data: D) -> Option<(WebSocketFrame, usize)>
174 where
175 D: AsRef<[u8]>,
176 {
177 let data_ref: &[u8] = data.as_ref();
178 if data_ref.len() < 2 {
179 return None;
180 }
181 let mut index: usize = 0;
182 let fin: bool = (data_ref[index] & 0b1000_0000) != 0;
183 let opcode: WebSocketOpcode = WebSocketOpcode::from_u8(data_ref[index] & 0b0000_1111);
184 index += 1;
185 let mask: bool = (data_ref[index] & 0b1000_0000) != 0;
186 let mut payload_len: usize = (data_ref[index] & 0b0111_1111) as usize;
187 index += 1;
188 if payload_len == 126 {
189 if data_ref.len() < index + 2 {
190 return None;
191 }
192 payload_len = u16::from_be_bytes(data_ref[index..index + 2].try_into().ok()?) as usize;
193 index += 2;
194 } else if payload_len == 127 {
195 if data_ref.len() < index + 8 {
196 return None;
197 }
198 payload_len = u64::from_be_bytes(data_ref[index..index + 8].try_into().ok()?) as usize;
199 index += 8;
200 }
201 let mask_key: Option<[u8; 4]> = if mask {
202 if data_ref.len() < index + 4 {
203 return None;
204 }
205 let key: [u8; 4] = data_ref[index..index + 4].try_into().ok()?;
206 index += 4;
207 Some(key)
208 } else {
209 None
210 };
211 if data_ref.len() < index + payload_len {
212 return None;
213 }
214 let mut payload: Vec<u8> = data_ref[index..index + payload_len].to_vec();
215 if let Some(mask_key) = mask_key {
216 for (byte_index, payload_byte) in payload.iter_mut().enumerate() {
217 *payload_byte ^= mask_key[byte_index % 4];
218 }
219 }
220 index += payload_len;
221 let frame: WebSocketFrame = WebSocketFrame {
222 fin,
223 opcode,
224 mask,
225 payload_data: payload,
226 };
227 Some((frame, index))
228 }
229
230 /// Creates a list of response frames from the provided body.
231 ///
232 /// This method segments the response body into WebSocket frames, respecting the maximum frame size
233 /// and handling UTF-8 character boundaries for text frames. It determines the appropriate opcode
234 /// (Text or Binary) based on the body's content.
235 ///
236 /// # Arguments
237 ///
238 /// - `D` - A reference to a response body (payload) as a byte slice, which requires the
239 /// `AsRef<[u8]>` bound.
240 ///
241 /// # Returns
242 ///
243 /// - `Vec<ResponseBody>` - A vector of `ResponseBody` (byte vectors), where each element represents a framed WebSocket message.
244 pub fn create_frame_list<D>(data: D) -> Vec<ResponseBody>
245 where
246 D: AsRef<[u8]>,
247 {
248 let data_ref: &[u8] = data.as_ref();
249 let total_len: usize = data_ref.len();
250 let mut offset: usize = 0;
251 let mut frames_list: Vec<ResponseBody> =
252 Vec::with_capacity((total_len / MAX_FRAME_SIZE) + 1);
253 let mut is_first_frame: bool = true;
254 let is_valid_utf8: bool = from_utf8(data_ref).is_ok();
255 let base_opcode: WebSocketOpcode = if is_valid_utf8 {
256 WebSocketOpcode::Text
257 } else {
258 WebSocketOpcode::Binary
259 };
260 while offset < total_len {
261 let remaining: usize = total_len - offset;
262 let mut frame_size: usize = remaining.min(MAX_FRAME_SIZE);
263 if is_valid_utf8 && frame_size < remaining {
264 while frame_size > 0 && (data_ref[offset + frame_size] & 0xC0) == 0x80 {
265 frame_size -= 1;
266 }
267 if frame_size == 0 {
268 frame_size = remaining.min(MAX_FRAME_SIZE);
269 }
270 }
271 let mut frame: ResponseBody = Vec::with_capacity(frame_size + 10);
272 let opcode: WebSocketOpcode = if is_first_frame {
273 base_opcode
274 } else {
275 WebSocketOpcode::Continuation
276 };
277 let fin: u8 = if remaining > frame_size { 0x00 } else { 0x80 };
278 let opcode_byte: u8 = opcode.to_u8() & 0x0F;
279 frame.push(fin | opcode_byte);
280 if frame_size < 126 {
281 frame.push(frame_size as u8);
282 } else if frame_size <= MAX_FRAME_SIZE {
283 frame.push(126);
284 frame.extend_from_slice(&(frame_size as u16).to_be_bytes());
285 } else {
286 frame.push(127);
287 frame.extend_from_slice(&(frame_size as u16).to_be_bytes());
288 }
289 let end: usize = offset + frame_size;
290 frame.extend_from_slice(&data_ref[offset..end]);
291 frames_list.push(frame);
292 offset = end;
293 is_first_frame = false;
294 }
295 frames_list
296 }
297
298 /// Calculates the SHA-1 hash of the input data.
299 ///
300 /// This function implements the SHA-1 cryptographic hash algorithm according to RFC 3174.
301 /// It processes the input data in 512-bit (64-byte) blocks and produces a 160-bit (20-byte) hash.
302 ///
303 /// # Arguments
304 ///
305 /// - `D` - The input data to be hashed, which requires the `AsRef<[u8]>` bound.
306 ///
307 /// # Returns
308 ///
309 /// A 20-byte array representing the SHA-1 hash of the input data.
310 pub fn sha1<D>(data: D) -> [u8; 20]
311 where
312 D: AsRef<[u8]>,
313 {
314 let data_ref: &[u8] = data.as_ref();
315 let mut hash_state: [u32; 5] = HASH_STATE;
316 let mut padded_data: Vec<u8> = Vec::from(data_ref);
317 let original_size_bits: u64 = (padded_data.len() * 8) as u64;
318 padded_data.push(0x80);
319 while !(padded_data.len() + 8).is_multiple_of(64) {
320 padded_data.push(0);
321 }
322 padded_data.extend_from_slice(&original_size_bits.to_be_bytes());
323 for block in padded_data.as_chunks::<64>().0 {
324 let mut message_schedule: [u32; 80] = [0u32; 80];
325 for (chunk_index, block_chunk) in block.as_chunks::<4>().0.iter().enumerate().take(16) {
326 message_schedule[chunk_index] = u32::from_be_bytes([
327 block_chunk[0],
328 block_chunk[1],
329 block_chunk[2],
330 block_chunk[3],
331 ]);
332 }
333 for schedule_index in 16..80 {
334 message_schedule[schedule_index] = (message_schedule[schedule_index - 3]
335 ^ message_schedule[schedule_index - 8]
336 ^ message_schedule[schedule_index - 14]
337 ^ message_schedule[schedule_index - 16])
338 .rotate_left(1);
339 }
340 let [mut hash_a, mut hash_b, mut hash_c, mut hash_d, mut hash_e] = hash_state;
341 for (round_index, &schedule_word) in message_schedule.iter().enumerate() {
342 let (round_function, round_constant): (u32, u32) = match round_index {
343 0..=19 => ((hash_b & hash_c) | (!hash_b & hash_d), 0x5A827999),
344 20..=39 => (hash_b ^ hash_c ^ hash_d, 0x6ED9EBA1),
345 40..=59 => (
346 (hash_b & hash_c) | (hash_b & hash_d) | (hash_c & hash_d),
347 0x8F1BBCDC,
348 ),
349 _ => (hash_b ^ hash_c ^ hash_d, 0xCA62C1D6),
350 };
351 let temp: u32 = hash_a
352 .rotate_left(5)
353 .wrapping_add(round_function)
354 .wrapping_add(hash_e)
355 .wrapping_add(round_constant)
356 .wrapping_add(schedule_word);
357 hash_e = hash_d;
358 hash_d = hash_c;
359 hash_c = hash_b.rotate_left(30);
360 hash_b = hash_a;
361 hash_a = temp;
362 }
363 hash_state[0] = hash_state[0].wrapping_add(hash_a);
364 hash_state[1] = hash_state[1].wrapping_add(hash_b);
365 hash_state[2] = hash_state[2].wrapping_add(hash_c);
366 hash_state[3] = hash_state[3].wrapping_add(hash_d);
367 hash_state[4] = hash_state[4].wrapping_add(hash_e);
368 }
369 let mut result: [u8; 20] = [0u8; 20];
370 for (state_index, &state_value) in hash_state.iter().enumerate() {
371 result[state_index * 4..(state_index + 1) * 4]
372 .copy_from_slice(&state_value.to_be_bytes());
373 }
374 result
375 }
376
377 /// Generates a WebSocket accept key from the client-provided key, returning an `Option<String>`.
378 ///
379 /// # Arguments
380 ///
381 /// - `K` - The client-provided key (typically from the `Sec-WebSocket-Key` header), which
382 /// requires the `AsRef<str>` bound.
383 ///
384 /// # Returns
385 ///
386 /// - `Option<String>` - An optional string representing the generated WebSocket accept key (typically for the `Sec-WebSocket-Accept` header).
387 #[inline(always)]
388 pub fn try_generate_accept_key<K>(key: K) -> Option<String>
389 where
390 K: AsRef<str>,
391 {
392 let key_ref: &str = key.as_ref();
393 let mut data: [u8; 60] = [0u8; 60];
394 data[..24].copy_from_slice(&key_ref.as_bytes()[..24.min(key_ref.len())]);
395 data[24..].copy_from_slice(GUID);
396 let hash: [u8; 20] = Self::sha1(data);
397 Self::try_base64_encode(hash)
398 }
399
400 /// Generates a WebSocket accept key from the client-provided key.
401 ///
402 /// This function is used during the WebSocket handshake to validate the client's request.
403 /// It concatenates the client's key with a specific GUID, calculates the SHA-1 hash of the result,
404 /// and then encodes the hash in base64.
405 ///
406 /// # Arguments
407 ///
408 /// - `K` - The client-provided key (typically from the `Sec-WebSocket-Key` header), which
409 /// requires the `AsRef<str>` bound.
410 ///
411 /// # Returns
412 ///
413 /// - `String` - The generated WebSocket accept key (typically for the `Sec-WebSocket-Accept` header).
414 ///
415 /// # Panics
416 ///
417 /// This function will panic if the input key cannot be converted to a UTF-8 string.
418 #[inline(always)]
419 pub fn generate_accept_key<K>(key: K) -> String
420 where
421 K: AsRef<str>,
422 {
423 let key_ref: &str = key.as_ref();
424 let mut data: [u8; 60] = [0u8; 60];
425 data[..24].copy_from_slice(&key_ref.as_bytes()[..24.min(key_ref.len())]);
426 data[24..].copy_from_slice(GUID);
427 let hash: [u8; 20] = Self::sha1(data);
428 Self::base64_encode(hash)
429 }
430
431 /// Encodes the input data as a base64 string, returning an `Option<String>`.
432 ///
433 /// # Arguments
434 ///
435 /// - `D` - The data to encode in base64, which requires the `AsRef<[u8]>` bound.
436 ///
437 /// # Returns
438 ///
439 /// - `Option<String>` - An optional string with the base64 encoded representation of the input data.
440 pub fn try_base64_encode<D>(data: D) -> Option<String>
441 where
442 D: AsRef<[u8]>,
443 {
444 let data_ref: &[u8] = data.as_ref();
445 let mut encoded_data: Vec<u8> = Vec::with_capacity(data_ref.len().div_ceil(3) * 4);
446 for chunk in data_ref.chunks(3) {
447 let mut buffer: [u8; 3] = [0u8; 3];
448 buffer[..chunk.len()].copy_from_slice(chunk);
449 let indices: [u8; 4] = [
450 buffer[0] >> 2,
451 ((buffer[0] & 0b11) << 4) | (buffer[1] >> 4),
452 ((buffer[1] & 0b1111) << 2) | (buffer[2] >> 6),
453 buffer[2] & 0b111111,
454 ];
455 for &idx in &indices[..chunk.len() + 1] {
456 encoded_data.push(BASE64_CHARSET_TABLE[idx as usize]);
457 }
458 while !encoded_data.len().is_multiple_of(4) {
459 encoded_data.push(EQUAL_BYTES[0]);
460 }
461 }
462 String::from_utf8(encoded_data).ok()
463 }
464
465 /// Encodes the input data as a base64 string.
466 ///
467 /// # Arguments
468 ///
469 /// - `D` - The data to encode in base64, which requires the `AsRef<[u8]>` bound.
470 ///
471 /// # Returns
472 ///
473 /// - `String` - A string with the base64 encoded representation of the input data.
474 ///
475 /// # Panics
476 ///
477 /// This function will panic if the input data cannot be converted to a UTF-8 string.
478 #[inline(always)]
479 pub fn base64_encode<D>(data: D) -> String
480 where
481 D: AsRef<[u8]>,
482 {
483 Self::try_base64_encode(data).unwrap()
484 }
485
486 /// Checks if the opcode is a continuation frame.
487 ///
488 /// # Returns
489 ///
490 /// - `bool` - `true` if the opcode is `Continuation`, otherwise `false`.
491 #[inline(always)]
492 pub fn is_continuation_opcode(&self) -> bool {
493 self.get_opcode().is_continuation()
494 }
495
496 /// Checks if the opcode is a text frame.
497 ///
498 /// # Returns
499 ///
500 /// - `bool` - `true` if the opcode is `Text`, otherwise `false`.
501 #[inline(always)]
502 pub fn is_text_opcode(&self) -> bool {
503 self.get_opcode().is_text()
504 }
505
506 /// Checks if the opcode is a binary frame.
507 ///
508 /// # Returns
509 ///
510 /// - `bool` - `true` if the opcode is `Binary`, otherwise `false`.
511 #[inline(always)]
512 pub fn is_binary_opcode(&self) -> bool {
513 self.get_opcode().is_binary()
514 }
515
516 /// Checks if the opcode is a close frame.
517 ///
518 /// # Returns
519 ///
520 /// - `bool` - `true` if the opcode is `Close`, otherwise `false`.
521 #[inline(always)]
522 pub fn is_close_opcode(&self) -> bool {
523 self.get_opcode().is_close()
524 }
525
526 /// Checks if the opcode is a ping frame.
527 ///
528 /// # Returns
529 ///
530 /// - `bool` - `true` if the opcode is `Ping`, otherwise `false`.
531 #[inline(always)]
532 pub fn is_ping_opcode(&self) -> bool {
533 self.get_opcode().is_ping()
534 }
535
536 /// Checks if the opcode is a pong frame.
537 ///
538 /// # Returns
539 ///
540 /// - `bool` - `true` if the opcode is `Pong`, otherwise `false`.
541 #[inline(always)]
542 pub fn is_pong_opcode(&self) -> bool {
543 self.get_opcode().is_pong()
544 }
545
546 /// Checks if the opcode is a reserved frame.
547 ///
548 /// # Returns
549 ///
550 /// - `bool` - `true` if the opcode is `Reserved(_)`, otherwise `false`.
551 #[inline(always)]
552 pub fn is_reserved_opcode(&self) -> bool {
553 self.get_opcode().is_reserved()
554 }
555
556 /// Handles a decoded WebSocket Text or Binary frame and accumulates payload data.
557 ///
558 /// # Arguments
559 ///
560 /// - `&mut Vec<u8>` - The accumulated frame data.
561 ///
562 /// # Returns
563 ///
564 /// - `Result<Option<RequestBody>, RequestError>` - Some(request) if frame is complete, None to continue, or error.
565 #[inline(always)]
566 pub(crate) fn build_full_frame(
567 &self,
568 full_frame: &mut Vec<u8>,
569 ) -> Result<Option<RequestBody>, RequestError> {
570 let payload_data: &[u8] = self.get_payload_data();
571 full_frame.extend_from_slice(payload_data);
572 if self.get_fin() {
573 return Ok(Some(mem::take(full_frame)));
574 }
575 Ok(None)
576 }
577}