Skip to main content

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(full_frame.clone()));
574        }
575        Ok(None)
576    }
577}