From ecb4616d2a3cb4ec283b27c491ff56dc5f6d6e71 Mon Sep 17 00:00:00 2001 From: Park Joon-Kyu Date: Wed, 6 Nov 2024 21:57:48 +0900 Subject: [PATCH] Implement RSV bits --- actix-http/src/ws/codec.rs | 40 ++++++++++++++++++++++++++++----- actix-http/src/ws/frame.rs | 45 +++++++++++++++++++++++--------------- actix-http/src/ws/mod.rs | 2 +- actix-http/src/ws/proto.rs | 19 ++++++++++++++++ 4 files changed, 82 insertions(+), 24 deletions(-) diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index ad487e400..fe2ca43be 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -6,7 +6,7 @@ use tracing::error; use super::{ frame::Parser, - proto::{CloseReason, OpCode}, + proto::{CloseReason, OpCode, RsvBits}, ProtocolError, }; @@ -71,6 +71,9 @@ pub enum Item { pub struct Codec { flags: Flags, max_size: usize, + + inbound_rsv_bits: Option, + outbound_rsv_bits: RsvBits, } bitflags! { @@ -88,6 +91,9 @@ impl Codec { Codec { max_size: 65_536, flags: Flags::SERVER, + + inbound_rsv_bits: None, + outbound_rsv_bits: RsvBits::empty(), } } @@ -108,6 +114,18 @@ impl Codec { self.flags.remove(Flags::SERVER); self } + + /// Get inbound RSV bits. + /// + /// Returns None if there's no received frame yet. + pub fn get_inbound_rsv_bits(&self) -> Option { + self.inbound_rsv_bits + } + + /// Set outbound RSV bits. + pub fn set_outbound_rsv_bits(&mut self, rsv_bits: RsvBits) { + self.outbound_rsv_bits = rsv_bits; + } } impl Default for Codec { @@ -125,6 +143,7 @@ impl Encoder for Codec { dst, txt, OpCode::Text, + self.outbound_rsv_bits, true, !self.flags.contains(Flags::SERVER), ), @@ -132,6 +151,7 @@ impl Encoder for Codec { dst, bin, OpCode::Binary, + self.outbound_rsv_bits, true, !self.flags.contains(Flags::SERVER), ), @@ -139,6 +159,7 @@ impl Encoder for Codec { dst, txt, OpCode::Ping, + self.outbound_rsv_bits, true, !self.flags.contains(Flags::SERVER), ), @@ -146,12 +167,16 @@ impl Encoder for Codec { dst, txt, OpCode::Pong, + self.outbound_rsv_bits, true, !self.flags.contains(Flags::SERVER), ), - Message::Close(reason) => { - Parser::write_close(dst, reason, !self.flags.contains(Flags::SERVER)) - } + Message::Close(reason) => Parser::write_close( + dst, + reason, + self.outbound_rsv_bits, + !self.flags.contains(Flags::SERVER), + ), Message::Continuation(cont) => match cont { Item::FirstText(data) => { if self.flags.contains(Flags::W_CONTINUATION) { @@ -162,6 +187,7 @@ impl Encoder for Codec { dst, &data[..], OpCode::Text, + self.outbound_rsv_bits, false, !self.flags.contains(Flags::SERVER), ) @@ -176,6 +202,7 @@ impl Encoder for Codec { dst, &data[..], OpCode::Binary, + self.outbound_rsv_bits, false, !self.flags.contains(Flags::SERVER), ) @@ -187,6 +214,7 @@ impl Encoder for Codec { dst, &data[..], OpCode::Continue, + self.outbound_rsv_bits, false, !self.flags.contains(Flags::SERVER), ) @@ -201,6 +229,7 @@ impl Encoder for Codec { dst, &data[..], OpCode::Continue, + self.outbound_rsv_bits, true, !self.flags.contains(Flags::SERVER), ) @@ -221,7 +250,8 @@ impl Decoder for Codec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { match Parser::parse(src, self.flags.contains(Flags::SERVER), self.max_size) { - Ok(Some((finished, opcode, payload))) => { + Ok(Some((finished, opcode, rsv_bits, payload))) => { + self.inbound_rsv_bits = Some(rsv_bits); // continuation is not supported if !finished { return match opcode { diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 35b3f8e66..e166f1cf5 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -5,7 +5,7 @@ use tracing::debug; use super::{ mask::apply_mask, - proto::{CloseCode, CloseReason, OpCode}, + proto::{CloseCode, CloseReason, OpCode, RsvBits}, ProtocolError, }; @@ -17,7 +17,7 @@ impl Parser { fn parse_metadata( src: &[u8], server: bool, - ) -> Result)>, ProtocolError> { + ) -> Result)>, ProtocolError> { let chunk_len = src.len(); let mut idx = 2; @@ -37,6 +37,9 @@ impl Parser { return Err(ProtocolError::MaskedFrame); } + // RSV bits + let rsv_bits = RsvBits::from_bits((first & 0x70) >> 4).unwrap_or(RsvBits::empty()); + // Op code let opcode = OpCode::from(first & 0x0F); @@ -79,7 +82,7 @@ impl Parser { None }; - Ok(Some((idx, finished, opcode, length, mask))) + Ok(Some((idx, finished, opcode, rsv_bits, length, mask))) } /// Parse the input stream into a frame. @@ -87,12 +90,13 @@ impl Parser { src: &mut BytesMut, server: bool, max_size: usize, - ) -> Result)>, ProtocolError> { + ) -> Result)>, ProtocolError> { // try to parse ws frame metadata - let (idx, finished, opcode, length, mask) = match Parser::parse_metadata(src, server)? { - None => return Ok(None), - Some(res) => res, - }; + let (idx, finished, opcode, rsv_bits, length, mask) = + match Parser::parse_metadata(src, server)? { + None => return Ok(None), + Some(res) => res, + }; // not enough data if src.len() < idx + length { @@ -115,7 +119,7 @@ impl Parser { // no need for body if length == 0 { - return Ok(Some((finished, opcode, None))); + return Ok(Some((finished, opcode, rsv_bits, None))); } let mut data = src.split_to(length); @@ -127,7 +131,7 @@ impl Parser { } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Some((true, OpCode::Close, None))); + return Ok(Some((true, OpCode::Close, rsv_bits, None))); } _ => {} } @@ -137,7 +141,7 @@ impl Parser { apply_mask(&mut data, mask); } - Ok(Some((finished, opcode, Some(data)))) + Ok(Some((finished, opcode, rsv_bits, Some(data)))) } /// Parse the payload of a close frame. @@ -161,15 +165,15 @@ impl Parser { dst: &mut BytesMut, pl: B, op: OpCode, + rsv_bits: RsvBits, fin: bool, mask: bool, ) { let payload = pl.as_ref(); - let one: u8 = if fin { - 0x80 | Into::::into(op) - } else { - op.into() - }; + let fin_bits = if fin { 0x80 } else { 0x00 }; + let rsv_bits = rsv_bits.bits() << 4; + + let one: u8 = fin_bits | rsv_bits | Into::::into(op); let payload_len = payload.len(); let (two, p_len) = if mask { (0x80, payload_len + 4) @@ -203,7 +207,12 @@ impl Parser { /// Create a new Close control frame. #[inline] - pub fn write_close(dst: &mut BytesMut, reason: Option, mask: bool) { + pub fn write_close( + dst: &mut BytesMut, + reason: Option, + rsv_bits: RsvBits, + mask: bool, + ) { let payload = match reason { None => Vec::new(), Some(reason) => { @@ -215,7 +224,7 @@ impl Parser { } }; - Parser::write_message(dst, payload, OpCode::Close, true, mask) + Parser::write_message(dst, payload, OpCode::Close, rsv_bits, true, mask) } } diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 88053b254..811e63474 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -20,7 +20,7 @@ pub use self::{ codec::{Codec, Frame, Item, Message}, dispatcher::Dispatcher, frame::Parser, - proto::{hash_key, CloseCode, CloseReason, OpCode}, + proto::{hash_key, CloseCode, CloseReason, OpCode, RsvBits}, }; /// WebSocket protocol errors. diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 27815eaf2..6941f5828 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -222,6 +222,25 @@ impl> From<(CloseCode, T)> for CloseReason { } } +bitflags::bitflags! { + /// RSV bits defined in [RFC 6455 ยง5.2]. + /// Reserved for extensions and should be set to zero if no extensions are applicable. + /// + /// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 + #[derive(Debug, Eq, PartialEq, Clone, Copy)] + pub struct RsvBits: u8 { + const RSV1 = 0b0000_0100; + const RSV2 = 0b0000_0010; + const RSV3 = 0b0000_0001; + } +} + +impl Default for RsvBits { + fn default() -> Self { + Self::empty() + } +} + /// The WebSocket GUID as stated in the spec. /// See . static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";