Skip to main content

actix_http/ws/
frame.rs

1use std::{cmp::min, io, str};
2
3use bytes::{Buf, BufMut, BytesMut};
4
5use super::{
6    mask::apply_mask,
7    proto::{CloseCode, CloseReason, OpCode},
8    ProtocolError,
9};
10
11/// A struct representing a WebSocket frame.
12#[derive(Debug)]
13pub struct Parser;
14
15/// FIN bitmask (RFC 6455 §5.2).
16const FIN_MASK: u8 = 0x80;
17
18/// RSV1, RSV2, and RSV3 bitmask (RFC 6455 §5.2).
19const RSV_MASK: u8 = 0b0111_0000;
20
21/// OpCode 4-bit nibble mask (RFC 6455 §5.2).
22const OPCODE_MASK: u8 = 0x0F;
23
24/// MASK bit bitmask (RFC 6455 §5.2).
25const MASK_BIT: u8 = 0x80;
26
27/// 7-bit payload length bitmask (RFC 6455 §5.2).
28const PAYLOAD_LEN_MASK: u8 = 0x7F;
29
30/// Extended payload length indicator for 16-bit length (RFC 6455 §5.2).
31const EXT_LEN_U16: u8 = 126;
32
33/// Extended payload length indicator for 64-bit length (RFC 6455 §5.2).
34const EXT_LEN_U64: u8 = 127;
35
36/// Maximum payload length allowed for control frames (RFC 6455 §5.5).
37const MAX_CONTROL_FRAME_PAYLOAD: usize = 125;
38
39/// Maximum payload length representable by a 16-bit extended length (RFC 6455 §5.2).
40const U16_PAYLOAD_MAX: usize = u16::MAX as usize;
41
42/// Client masking key length in bytes (RFC 6455 §5.3).
43const MASK_LEN: usize = 4;
44
45impl Parser {
46    fn parse_metadata(
47        src: &[u8],
48        server: bool,
49    ) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> {
50        let chunk_len = src.len();
51
52        let mut idx = 2;
53        if chunk_len < 2 {
54            return Ok(None);
55        }
56
57        let first = src[0];
58        let second = src[1];
59        let finished = first & FIN_MASK != 0;
60
61        // RSV1, RSV2, and RSV3 must be zero unless a negotiated extension defines them.
62        if first & RSV_MASK != 0 {
63            // TODO(semver-major): use InvalidReservedBits
64            return Err(ProtocolError::Io(io::Error::new(
65                io::ErrorKind::InvalidData,
66                "Received a frame with non-zero reserved bits",
67            )));
68        }
69
70        // check masking
71        let masked = second & MASK_BIT != 0;
72        if !masked && server {
73            return Err(ProtocolError::UnmaskedFrame);
74        } else if masked && !server {
75            return Err(ProtocolError::MaskedFrame);
76        }
77
78        // Op code
79        let opcode = OpCode::from(first & OPCODE_MASK);
80
81        if let OpCode::Bad = opcode {
82            return Err(ProtocolError::InvalidOpcode(first & OPCODE_MASK));
83        }
84
85        let len = second & PAYLOAD_LEN_MASK;
86        let length = if len == EXT_LEN_U16 {
87            if chunk_len < 4 {
88                return Ok(None);
89            }
90            let len = usize::from(u16::from_be_bytes(
91                TryFrom::try_from(&src[idx..idx + 2]).unwrap(),
92            ));
93            idx += 2;
94            len
95        } else if len == EXT_LEN_U64 {
96            if chunk_len < 10 {
97                return Ok(None);
98            }
99            let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap());
100            idx += 8;
101            len as usize
102        } else {
103            len as usize
104        };
105
106        let mask = if server {
107            if chunk_len < idx + MASK_LEN {
108                return Ok(None);
109            }
110
111            let mask = TryFrom::try_from(&src[idx..idx + MASK_LEN]).unwrap();
112
113            idx += MASK_LEN;
114
115            Some(mask)
116        } else {
117            None
118        };
119
120        Ok(Some((idx, finished, opcode, length, mask)))
121    }
122
123    /// Parse the input stream into a frame.
124    pub fn parse(
125        src: &mut BytesMut,
126        server: bool,
127        max_size: usize,
128    ) -> Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError> {
129        // try to parse ws frame metadata
130        let (idx, finished, opcode, length, mask) = match Parser::parse_metadata(src, server)? {
131            None => return Ok(None),
132            Some(res) => res,
133        };
134
135        let frame_len = match idx.checked_add(length) {
136            Some(len) => len,
137            None => return Err(ProtocolError::Overflow),
138        };
139
140        // not enough data
141        if src.len() < frame_len {
142            let min_length = min(length, max_size);
143            let required_cap = match idx.checked_add(min_length) {
144                Some(cap) => cap,
145                None => return Err(ProtocolError::Overflow),
146            };
147
148            if src.capacity() < required_cap {
149                src.reserve(required_cap - src.capacity());
150            }
151            return Ok(None);
152        }
153
154        // remove prefix
155        src.advance(idx);
156
157        // check for max allowed size
158        if length > max_size {
159            // drop the payload
160            src.advance(length);
161            return Err(ProtocolError::Overflow);
162        }
163
164        // no need for body
165        if length == 0 {
166            return Ok(Some((finished, opcode, None)));
167        }
168
169        let mut data = src.split_to(length);
170
171        // control frames must have length <= 125 (RFC 6455 §5.5)
172        match opcode {
173            OpCode::Ping | OpCode::Pong | OpCode::Close if length > MAX_CONTROL_FRAME_PAYLOAD => {
174                return Err(ProtocolError::InvalidLength(length));
175            }
176            _ => {}
177        }
178
179        // unmask
180        if let Some(mask) = mask {
181            apply_mask(&mut data, mask);
182        }
183
184        Ok(Some((finished, opcode, Some(data))))
185    }
186
187    /// Parse the payload of a close frame.
188    ///
189    /// This method preserves the historical behavior of accepting unknown status codes and
190    /// replacing invalid UTF-8 in the reason. Use [`Parser::try_parse_close_payload`] when the
191    /// payload must be validated.
192    #[deprecated(
193        since = "3.13.5",
194        note = "Use `Parser::try_parse_close_payload` instead."
195    )]
196    pub fn parse_close_payload(payload: &[u8]) -> Option<CloseReason> {
197        if payload.len() >= 2 {
198            let raw_code = u16::from_be_bytes(TryFrom::try_from(&payload[..2]).unwrap());
199            let code = CloseCode::from(raw_code);
200            let description = if payload.len() > 2 {
201                Some(String::from_utf8_lossy(&payload[2..]).into())
202            } else {
203                None
204            };
205            Some(CloseReason { code, description })
206        } else {
207            None
208        }
209    }
210
211    /// Parse and validate the payload of a close frame.
212    ///
213    /// # Validation
214    ///
215    /// A close payload is either empty or contains a two-byte status code followed by an optional
216    /// UTF-8 reason. This follows [RFC 6455 §5.5.1], with status code rules from [RFC 6455 §7.4]
217    /// and UTF-8 error handling from [RFC 6455 §8.1].
218    ///
219    /// [RFC 6455 §5.5.1]: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1
220    /// [RFC 6455 §7.4]: https://datatracker.ietf.org/doc/html/rfc6455#section-7.4
221    /// [RFC 6455 §8.1]: https://datatracker.ietf.org/doc/html/rfc6455#section-8.1
222    pub fn try_parse_close_payload(payload: &[u8]) -> Result<Option<CloseReason>, ProtocolError> {
223        // RFC 6455 §5.5.1 requires a two-byte status code when a close payload is not empty.
224        if payload.len() == 1 {
225            return Err(ProtocolError::InvalidLength(payload.len()));
226        }
227
228        if payload.len() >= 2 {
229            let raw_code = u16::from_be_bytes(
230                payload[..2]
231                    .try_into()
232                    .expect("Payload length should be checked before parsing"),
233            );
234
235            // RFC 6455 §7.4 reserves 1000-2999 for protocol-defined codes. Reject reserved and
236            // undefined values while allowing registered codes (3000-3999) and private-use codes
237            // (4000-4999).
238            if !matches!(raw_code, 1000..=1003 | 1007..=1014 | 3000..=4999) {
239                // TODO(semver-major): use this instead
240                // return Err(ProtocolError::InvalidCloseCode(raw_code));
241                return Err(ProtocolError::BadOpCode);
242            }
243
244            if payload.len() > 2 {
245                // RFC 6455 §5.5.1 defines the remaining bytes as a UTF-8 reason. RFC 6455 §8.1
246                // requires the connection to fail when data interpreted as UTF-8 is invalid.
247                str::from_utf8(&payload[2..]).map_err(|_| {
248                    // TODO(semver-major): use this instead
249                    // ProtocolError::InvalidCloseReason
250                    ProtocolError::BadOpCode
251                })?;
252            }
253        }
254
255        #[expect(deprecated)]
256        Ok(Self::parse_close_payload(payload))
257    }
258
259    /// Generate binary representation
260    pub fn write_message<B: AsRef<[u8]>>(
261        dst: &mut BytesMut,
262        pl: B,
263        op: OpCode,
264        fin: bool,
265        mask: bool,
266    ) {
267        let payload = pl.as_ref();
268        let one = if fin {
269            FIN_MASK | u8::from(op)
270        } else {
271            u8::from(op)
272        };
273        let payload_len = payload.len();
274        let (two, p_len) = if mask {
275            (MASK_BIT, payload_len + MASK_LEN)
276        } else {
277            (0, payload_len)
278        };
279
280        if payload_len < EXT_LEN_U16 as usize {
281            dst.reserve(p_len + 2);
282            dst.put_slice(&[one, two | payload_len as u8]);
283        } else if payload_len <= U16_PAYLOAD_MAX {
284            dst.reserve(p_len + 4);
285            dst.put_slice(&[one, two | EXT_LEN_U16]);
286            dst.put_u16(payload_len as u16);
287        } else {
288            dst.reserve(p_len + 10);
289            dst.put_slice(&[one, two | EXT_LEN_U64]);
290            dst.put_u64(payload_len as u64);
291        };
292
293        if mask {
294            let mask = rand::random::<[u8; 4]>();
295            dst.put_slice(mask.as_ref());
296            dst.put_slice(payload.as_ref());
297            let pos = dst.len() - payload_len;
298            apply_mask(&mut dst[pos..], mask);
299        } else {
300            dst.put_slice(payload.as_ref());
301        }
302    }
303
304    /// Create a new Close control frame.
305    #[inline]
306    pub fn write_close(dst: &mut BytesMut, reason: Option<CloseReason>, mask: bool) {
307        let payload = match reason {
308            None => Vec::new(),
309            Some(reason) => {
310                let mut payload = Into::<u16>::into(reason.code).to_be_bytes().to_vec();
311                if let Some(description) = reason.description {
312                    payload.extend(description.as_bytes());
313                }
314                payload
315            }
316        };
317
318        Parser::write_message(dst, payload, OpCode::Close, true, mask)
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use bytes::Bytes;
325
326    use super::*;
327
328    struct F {
329        finished: bool,
330        opcode: OpCode,
331        payload: Bytes,
332    }
333
334    fn is_none(frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> bool {
335        matches!(*frm, Ok(None))
336    }
337
338    fn extract(frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> F {
339        match frm {
340            Ok(Some((finished, opcode, payload))) => F {
341                finished,
342                opcode,
343                payload: payload
344                    .map(|b| b.freeze())
345                    .unwrap_or_else(|| Bytes::from("")),
346            },
347            _ => unreachable!("error"),
348        }
349    }
350
351    #[test]
352    fn test_parse() {
353        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
354        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));
355
356        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
357        buf.extend(b"1");
358
359        let frame = extract(Parser::parse(&mut buf, false, 1024));
360        assert!(!frame.finished);
361        assert_eq!(frame.opcode, OpCode::Text);
362        assert_eq!(frame.payload.as_ref(), &b"1"[..]);
363    }
364
365    #[test]
366    fn test_parse_length0() {
367        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]);
368        let frame = extract(Parser::parse(&mut buf, false, 1024));
369        assert!(!frame.finished);
370        assert_eq!(frame.opcode, OpCode::Text);
371        assert!(frame.payload.is_empty());
372    }
373
374    #[test]
375    fn test_parse_length2() {
376        let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
377        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));
378
379        let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
380        buf.extend(&[0u8, 4u8][..]);
381        buf.extend(b"1234");
382
383        let frame = extract(Parser::parse(&mut buf, false, 1024));
384        assert!(!frame.finished);
385        assert_eq!(frame.opcode, OpCode::Text);
386        assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
387    }
388
389    #[test]
390    fn test_parse_length4() {
391        let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
392        assert!(is_none(&Parser::parse(&mut buf, false, 1024)));
393
394        let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
395        buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
396        buf.extend(b"1234");
397
398        let frame = extract(Parser::parse(&mut buf, false, 1024));
399        assert!(!frame.finished);
400        assert_eq!(frame.opcode, OpCode::Text);
401        assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
402    }
403
404    #[test]
405    fn test_parse_frame_mask() {
406        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]);
407        buf.extend(b"0001");
408        buf.extend(b"1");
409
410        assert!(Parser::parse(&mut buf, false, 1024).is_err());
411
412        let frame = extract(Parser::parse(&mut buf, true, 1024));
413        assert!(!frame.finished);
414        assert_eq!(frame.opcode, OpCode::Text);
415        assert_eq!(frame.payload, Bytes::from(vec![1u8]));
416    }
417
418    #[test]
419    fn test_parse_frame_no_mask() {
420        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
421        buf.extend([1u8]);
422
423        assert!(Parser::parse(&mut buf, true, 1024).is_err());
424
425        let frame = extract(Parser::parse(&mut buf, false, 1024));
426        assert!(!frame.finished);
427        assert_eq!(frame.opcode, OpCode::Text);
428        assert_eq!(frame.payload, Bytes::from(vec![1u8]));
429    }
430
431    #[test]
432    fn test_parse_frame_with_rsv1_set() {
433        // https://github.com/actix/actix-web/issues/1579
434
435        // Final, masked text frame with RSV1 set and no negotiated extension.
436        let mut buf = BytesMut::from(
437            &[
438                0b1100_0001u8, // FIN + RSV1 + text
439                0b1000_0001u8, // MASK + payload length
440                0,             // masking key byte 1
441                0,             // masking key byte 2
442                0,             // masking key byte 3
443                0,             // masking key byte 4
444                b'a',          // payload
445            ][..],
446        );
447
448        Parser::parse(&mut buf, true, 1024)
449            .expect_err("Should reject set RSV1 bit when no extension is negotiated");
450    }
451
452    #[test]
453    fn test_parse_frame_max_size() {
454        let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]);
455        buf.extend([1u8, 1u8]);
456
457        assert!(Parser::parse(&mut buf, true, 1).is_err());
458
459        if let Err(ProtocolError::Overflow) = Parser::parse(&mut buf, false, 0) {
460        } else {
461            unreachable!("error");
462        }
463    }
464
465    #[test]
466    fn test_parse_frame_max_size_recoverability() {
467        let mut buf = BytesMut::new();
468        // The first text frame with length == 2, payload doesn't matter.
469        buf.extend([0b0000_0001u8, 0b0000_0010u8, 0b0000_0000u8, 0b0000_0000u8]);
470        // Next binary frame with length == 2 and payload == `[0x1111_1111u8, 0x1111_1111u8]`.
471        buf.extend([0b0000_0010u8, 0b0000_0010u8, 0b1111_1111u8, 0b1111_1111u8]);
472
473        assert_eq!(buf.len(), 8);
474        assert!(matches!(
475            Parser::parse(&mut buf, false, 1),
476            Err(ProtocolError::Overflow)
477        ));
478        assert_eq!(buf.len(), 4);
479        let frame = extract(Parser::parse(&mut buf, false, 2));
480        assert!(!frame.finished);
481        assert_eq!(frame.opcode, OpCode::Binary);
482        assert_eq!(
483            frame.payload,
484            Bytes::from(vec![0b1111_1111u8, 0b1111_1111u8])
485        );
486        assert_eq!(buf.len(), 0);
487    }
488
489    #[test]
490    fn test_ping_frame() {
491        let mut buf = BytesMut::new();
492        Parser::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false);
493
494        let mut v = vec![137u8, 4u8];
495        v.extend(b"data");
496        assert_eq!(&buf[..], &v[..]);
497    }
498
499    #[test]
500    fn test_pong_frame() {
501        let mut buf = BytesMut::new();
502        Parser::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false);
503
504        let mut v = vec![138u8, 4u8];
505        v.extend(b"data");
506        assert_eq!(&buf[..], &v[..]);
507    }
508
509    #[test]
510    fn test_close_frame() {
511        let mut buf = BytesMut::new();
512        let reason = (CloseCode::Normal, "data");
513        Parser::write_close(&mut buf, Some(reason.into()), false);
514
515        let mut v = vec![136u8, 6u8, 3u8, 232u8];
516        v.extend(b"data");
517        assert_eq!(&buf[..], &v[..]);
518    }
519
520    #[test]
521    fn test_empty_close_frame() {
522        let mut buf = BytesMut::new();
523        Parser::write_close(&mut buf, None, false);
524        assert_eq!(&buf[..], &vec![0x88, 0x00][..]);
525    }
526
527    #[test]
528    fn try_parse_close_payload_validates_payload() {
529        assert!(matches!(
530            Parser::try_parse_close_payload(&[0x03, 0xe8, 0xff]).unwrap_err(),
531            ProtocolError::BadOpCode
532        ));
533        assert!(matches!(
534            Parser::try_parse_close_payload(&[0, 0]).unwrap_err(),
535            ProtocolError::BadOpCode
536        ));
537    }
538
539    #[test]
540    fn test_oversized_control_frames() {
541        // Ping frame with 126 bytes payload (exceeding 125 limit)
542        let mut buf = BytesMut::from(&[0x89u8, 126u8, 0x00, 126][..]);
543        buf.extend(vec![0u8; 126]);
544        assert!(matches!(
545            Parser::parse(&mut buf, false, 65536),
546            Err(ProtocolError::InvalidLength(126))
547        ));
548
549        // Close frame with 126 bytes payload (exceeding 125 limit)
550        let mut buf = BytesMut::from(&[0x88u8, 126u8, 0x00, 126][..]);
551        buf.extend(vec![0u8; 126]);
552        assert!(matches!(
553            Parser::parse(&mut buf, false, 65536),
554            Err(ProtocolError::InvalidLength(126))
555        ));
556    }
557
558    #[test]
559    fn test_parse_length_overflow() {
560        let buf: [u8; 14] = [
561            0x0a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0x0e, 0x8f,
562        ];
563        let mut buf = BytesMut::from(&buf[..]);
564        let result = Parser::parse(&mut buf, true, 65536);
565        assert!(matches!(result, Err(ProtocolError::Overflow)));
566    }
567}