Skip to main content

rama_ws/protocol/
mod.rs

1//! Generic WebSocket message stream.
2
3#![expect(
4    clippy::unreachable,
5    reason = "vendored from upstream `tungstenite-rs`: arms gated on caller-validated WebSocket protocol state that the type system can't enforce"
6)]
7
8use rama_core::error::{BoxError, BoxErrorExt as _};
9use rama_core::extensions::{Extensions, ExtensionsRef};
10use rama_core::telemetry::tracing;
11use rama_core::telemetry::tracing::{debug, trace};
12use rama_utils::octets::kib;
13use std::{
14    fmt,
15    io::{self, Read, Write},
16};
17
18#[cfg(feature = "compression")]
19use rama_http::headers::sec_websocket_extensions;
20
21pub mod frame;
22
23mod error;
24mod message;
25
26#[cfg(feature = "compression")]
27mod per_message_deflate;
28
29pub use error::ProtocolError;
30
31#[cfg(test)]
32mod tests;
33
34use crate::protocol::{
35    frame::{
36        Frame, FrameCodec, Utf8Bytes,
37        coding::{CloseCode, OpCode, OpCodeControl, OpCodeData},
38    },
39    message::{IncompleteMessage, IncompleteMessageType},
40};
41
42#[cfg(feature = "compression")]
43use self::per_message_deflate::PerMessageDeflateState;
44
45pub use self::{frame::CloseFrame, message::Message};
46
47/// Indicates a Client or Server role of the websocket
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum Role {
50    /// This socket is a server
51    Server,
52    /// This socket is a client
53    Client,
54}
55
56/// The configuration for WebSocket connection.
57///
58/// # Example
59/// ```
60/// # use rama_ws::protocol::WebSocketConfig;
61///
62/// let conf = WebSocketConfig::default()
63///     .with_read_buffer_size(256 * 1024)
64///     .with_write_buffer_size(256 * 1024);
65/// ```
66#[derive(Debug, Clone, Copy)]
67#[non_exhaustive]
68pub struct WebSocketConfig {
69    /// Read buffer capacity. This buffer is eagerly allocated and used for receiving
70    /// messages.
71    ///
72    /// For high read load scenarios a larger buffer, e.g. 128 KiB, improves performance.
73    ///
74    /// For scenarios where you expect a lot of connections and don't need high read load
75    /// performance a smaller buffer, e.g. 4 KiB, would be appropriate to lower total
76    /// memory usage.
77    ///
78    /// The default value is 128 KiB.
79    pub read_buffer_size: usize,
80
81    /// The target minimum size of the write buffer to reach before writing the data
82    /// to the underlying stream.
83    /// The default value is 128 KiB.
84    ///
85    /// If set to `0` each message will be eagerly written to the underlying stream.
86    /// It is often more optimal to allow them to buffer a little, hence the default value.
87    ///
88    /// Note: [`flush`](WebSocket::flush) will always fully write the buffer regardless.
89    pub write_buffer_size: usize,
90
91    /// The max size of the write buffer in bytes. Setting this can provide backpressure
92    /// in the case the write buffer is filling up due to write errors.
93    /// The default value is unlimited.
94    ///
95    /// Note: The write buffer only builds up past [`write_buffer_size`](Self::write_buffer_size)
96    /// when writes to the underlying stream are failing. So the **write buffer can not
97    /// fill up if you are not observing write errors even if not flushing**.
98    ///
99    /// Note: Should always be at least [`write_buffer_size + 1 message`](Self::write_buffer_size)
100    /// and probably a little more depending on error handling strategy.
101    pub max_write_buffer_size: usize,
102
103    /// The maximum size of an incoming message. `None` means no size limit. The default value is 64 MiB
104    /// which should be reasonably big for all normal use-cases but small enough to prevent
105    /// memory eating by a malicious user.
106    pub max_message_size: Option<usize>,
107
108    /// The maximum size of a single incoming message frame. `None` means no size limit. The limit is for
109    /// frame payload NOT including the frame header. The default value is 16 MiB which should
110    /// be reasonably big for all normal use-cases but small enough to prevent memory eating
111    /// by a malicious user.
112    pub max_frame_size: Option<usize>,
113
114    /// When set to `true`, the server will accept and handle unmasked frames
115    /// from the client. According to the RFC 6455, the server must close the
116    /// connection to the client in such cases, however it seems like there are
117    /// some popular libraries that are sending unmasked frames, ignoring the RFC.
118    /// By default this option is set to `false`, i.e. according to RFC 6455.
119    pub accept_unmasked_frames: bool,
120
121    #[cfg(feature = "compression")]
122    #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
123    /// Per-message-deflate configuration, specify it
124    /// to enable per-message (de)compression using the Deflate algorithm
125    /// as specified by [`RFC7692`].
126    ///
127    /// [`RFC7692`]: https://datatracker.ietf.org/doc/html/rfc7692
128    pub per_message_deflate: Option<PerMessageDeflateConfig>,
129}
130
131#[cfg(feature = "compression")]
132#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
133/// Per-message-deflate configuration as specified in [`RFC7692`]
134///
135/// [`RFC7692`]: https://datatracker.ietf.org/doc/html/rfc7692
136#[derive(Debug, Clone, Copy)]
137pub struct PerMessageDeflateConfig {
138    /// Prevents Server Context Takeover
139    ///
140    /// This extension parameter enables a client to request that
141    /// the server forgo context takeover, thereby eliminating
142    /// the client's need to retain memory for the LZ77 sliding window between messages.
143    ///
144    /// A client's omission of this parameter indicates its capability to decompress messages
145    /// even if the server utilizes context takeover.
146    ///
147    /// Servers should support this parameter and confirm acceptance by
148    /// including it in their response;
149    /// they may even include it if not explicitly requested by the client.
150    pub server_no_context_takeover: bool,
151
152    /// Manages Client Context Takeover
153    ///
154    /// This extension parameter allows a client to indicate to
155    /// the server its intent not to use context takeover,
156    /// even if the server doesn't explicitly respond with the same parameter.
157    ///
158    /// When a server receives this, it can either ignore it or include
159    /// `client_no_context_takeover` in its response,
160    /// which prevents the client from using context
161    /// takeover and helps the server conserve memory.
162    /// If the server's response omits this parameter,
163    /// it signals its ability to decompress messages where
164    /// the client does use context takeover.
165    ///
166    /// Clients are required to support this parameter in a server's response.
167    pub client_no_context_takeover: bool,
168
169    /// Limits Server Window Size
170    ///
171    /// This extension parameter allows a client to propose
172    /// a maximum LZ77 sliding window size for the server
173    /// to use when compressing messages, specified as a base-2 logarithm (8-15).
174    ///
175    /// This helps the client reduce its memory requirements.
176    /// If a client omits this parameter,
177    /// it signals its capacity to handle messages compressed with a window up to 32,768 bytes.
178    ///
179    /// A server accepts by echoing the parameter with an equal or smaller value;
180    /// otherwise, it declines. Notably, a server may suggest a window size
181    /// even if the client didn't initially propose one.
182    pub server_max_window_bits: Option<u8>,
183
184    /// Adjusts Client Window Size
185    ///
186    /// This extension parameter allows a client to propose,
187    /// optionally with a value between 8 and 15 (base-2 logarithm),
188    /// the maximum LZ77 sliding window size it will use for compression.
189    ///
190    /// This signals to the server that the client supports this parameter in responses and,
191    /// if a value is provided, hints that the client won't exceed that window size
192    /// for its own compression, regardless of the server's response.
193    ///
194    /// A server can then include client_max_window_bits in its response
195    /// with an equal or smaller value, thereby limiting the client's window size
196    /// and reducing its own memory overhead for decompression.
197    ///
198    /// If the server's response omits this parameter,
199    /// it signifies its ability to decompress messages compressed with a client window
200    /// up to 32,768 bytes.
201    ///
202    /// Servers must not include this parameter in their response
203    /// if the client's initial offer didn't contain it.
204    pub client_max_window_bits: Option<u8>,
205}
206
207#[cfg(feature = "compression")]
208#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
209impl From<&sec_websocket_extensions::PerMessageDeflateConfig> for PerMessageDeflateConfig {
210    fn from(value: &sec_websocket_extensions::PerMessageDeflateConfig) -> Self {
211        Self {
212            server_no_context_takeover: value.server_no_context_takeover,
213            client_no_context_takeover: value.client_no_context_takeover,
214            server_max_window_bits: value.server_max_window_bits,
215            client_max_window_bits: value.client_max_window_bits,
216        }
217    }
218}
219
220#[cfg(feature = "compression")]
221#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
222impl From<sec_websocket_extensions::PerMessageDeflateConfig> for PerMessageDeflateConfig {
223    #[inline]
224    fn from(value: sec_websocket_extensions::PerMessageDeflateConfig) -> Self {
225        Self::from(&value)
226    }
227}
228
229#[cfg(feature = "compression")]
230#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
231impl From<&PerMessageDeflateConfig> for sec_websocket_extensions::PerMessageDeflateConfig {
232    fn from(value: &PerMessageDeflateConfig) -> Self {
233        Self {
234            server_no_context_takeover: value.server_no_context_takeover,
235            client_no_context_takeover: value.client_no_context_takeover,
236            server_max_window_bits: value.server_max_window_bits,
237            client_max_window_bits: value.client_max_window_bits,
238            ..Default::default()
239        }
240    }
241}
242
243#[cfg(feature = "compression")]
244#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
245impl From<PerMessageDeflateConfig> for sec_websocket_extensions::PerMessageDeflateConfig {
246    #[inline]
247    fn from(value: PerMessageDeflateConfig) -> Self {
248        Self::from(&value)
249    }
250}
251
252#[cfg(feature = "compression")]
253#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
254#[expect(clippy::derivable_impls)]
255impl Default for PerMessageDeflateConfig {
256    fn default() -> Self {
257        Self {
258            // By default, allow context takeover in both directions
259            server_no_context_takeover: false,
260            client_no_context_takeover: false,
261
262            // No limit: means default 15-bit window (32768 bytes)
263            server_max_window_bits: None,
264            client_max_window_bits: None,
265        }
266    }
267}
268
269impl Default for WebSocketConfig {
270    fn default() -> Self {
271        Self {
272            read_buffer_size: kib(128),
273            write_buffer_size: kib(128),
274            max_write_buffer_size: usize::MAX,
275            max_message_size: Some(64 << 20),
276            max_frame_size: Some(16 << 20),
277            accept_unmasked_frames: false,
278            #[cfg(feature = "compression")]
279            per_message_deflate: None,
280        }
281    }
282}
283
284impl WebSocketConfig {
285    rama_utils::macros::generate_set_and_with! {
286        /// Set [`Self::read_buffer_size`].
287        #[must_use]
288        pub fn read_buffer_size(mut self, read_buffer_size: usize) -> Self {
289            self.read_buffer_size = read_buffer_size;
290            self
291        }
292    }
293
294    rama_utils::macros::generate_set_and_with! {
295        /// Set [`Self::write_buffer_size`].
296        #[must_use]
297        pub fn write_buffer_size(mut self, write_buffer_size: usize) -> Self {
298            self.write_buffer_size = write_buffer_size;
299            self
300        }
301    }
302
303    rama_utils::macros::generate_set_and_with! {
304        /// Set [`Self::max_write_buffer_size`].
305        #[must_use]
306        pub fn max_write_buffer_size(mut self, max_write_buffer_size: usize) -> Self {
307            self.max_write_buffer_size = max_write_buffer_size;
308            self
309        }
310    }
311
312    rama_utils::macros::generate_set_and_with! {
313        /// Set [`Self::max_message_size`].
314        #[must_use]
315        pub fn max_message_size(mut self, max_message_size: Option<usize>) -> Self {
316            self.max_message_size = max_message_size;
317            self
318        }
319    }
320
321    rama_utils::macros::generate_set_and_with! {
322        /// Set [`Self::max_frame_size`].
323        #[must_use]
324        pub fn max_frame_size(mut self, max_frame_size: Option<usize>) -> Self {
325            self.max_frame_size = max_frame_size;
326            self
327        }
328    }
329
330    rama_utils::macros::generate_set_and_with! {
331        /// Set [`Self::accept_unmasked_frames`].
332        #[must_use]
333        pub fn accept_unmasked_frames(mut self, accept_unmasked_frames: bool) -> Self {
334            self.accept_unmasked_frames = accept_unmasked_frames;
335            self
336        }
337    }
338
339    #[cfg(feature = "compression")]
340    rama_utils::macros::generate_set_and_with! {
341        /// Set [`Self::per_message_deflate`] with the default config..
342        #[must_use]
343        #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
344        pub fn per_message_deflate_default(mut self) -> Self {
345            self.per_message_deflate = Some(Default::default());
346            self
347        }
348    }
349
350    #[cfg(feature = "compression")]
351    rama_utils::macros::generate_set_and_with! {
352        /// Set [`Self::per_message_deflate`].
353        #[must_use]
354        #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
355        pub fn per_message_deflate(mut self, per_message_deflate: Option<PerMessageDeflateConfig>) -> Self {
356            self.per_message_deflate = per_message_deflate;
357            self
358        }
359    }
360
361    /// Panic if values are invalid.
362    pub(crate) fn assert_valid(&self) {
363        assert!(
364            self.max_write_buffer_size > self.write_buffer_size,
365            "WebSocketConfig::max_write_buffer_size must be greater than write_buffer_size, \
366            see WebSocketConfig docs`"
367        );
368    }
369}
370
371/// WebSocket input-output stream.
372///
373/// This is THE structure you want to create to be able to speak the WebSocket protocol.
374/// It may be created by calling `connect`, `accept` or `client` functions.
375///
376/// Use [`WebSocket::read`], [`WebSocket::send`] to received and send messages.
377pub struct WebSocket<Stream> {
378    /// The underlying socket.
379    socket: Stream,
380    /// The context for managing a WebSocket.
381    context: WebSocketContext,
382}
383
384impl<Stream: fmt::Debug> fmt::Debug for WebSocket<Stream> {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        f.debug_struct("WebSocket")
387            .field("socket", &self.socket)
388            .field("context", &self.context)
389            .finish()
390    }
391}
392
393impl<Stream> WebSocket<Stream> {
394    /// Convert a raw socket into a WebSocket without performing a handshake.
395    ///
396    /// # Panics
397    /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`.
398    pub fn from_raw_socket(stream: Stream, role: Role, config: Option<WebSocketConfig>) -> Self {
399        Self {
400            socket: stream,
401            context: WebSocketContext::new(role, config),
402        }
403    }
404
405    /// Convert a raw socket into a WebSocket without performing a handshake.
406    ///
407    /// # Panics
408    /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`.
409    pub fn from_partially_read(
410        stream: Stream,
411        part: Vec<u8>,
412        role: Role,
413        config: Option<WebSocketConfig>,
414    ) -> Self {
415        Self {
416            socket: stream,
417            context: WebSocketContext::from_partially_read(part, role, config),
418        }
419    }
420
421    /// Consumes the `WebSocket` and returns the underlying stream.
422    pub(crate) fn into_inner(self) -> Stream {
423        self.socket
424    }
425
426    /// Returns a shared reference to the inner stream.
427    pub fn get_ref(&self) -> &Stream {
428        &self.socket
429    }
430    /// Returns a mutable reference to the inner stream.
431    pub fn get_mut(&mut self) -> &mut Stream {
432        &mut self.socket
433    }
434
435    /// Change the configuration.
436    ///
437    /// # Panics
438    /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`.
439    pub fn set_config(&mut self, set_func: impl FnOnce(&mut WebSocketConfig)) {
440        self.context.set_config(set_func);
441    }
442
443    /// Read the configuration.
444    pub fn get_config(&self) -> &WebSocketConfig {
445        self.context.get_config()
446    }
447
448    /// Check if it is possible to read messages.
449    ///
450    /// Reading is impossible after receiving `Message::Close`. It is still possible after
451    /// sending close frame since the peer still may send some data before confirming close.
452    pub fn can_read(&self) -> bool {
453        self.context.can_read()
454    }
455
456    /// Check if it is possible to write messages.
457    ///
458    /// Writing gets impossible immediately after sending or receiving `Message::Close`.
459    pub fn can_write(&self) -> bool {
460        self.context.can_write()
461    }
462}
463
464impl<Stream: Read + Write> WebSocket<Stream> {
465    /// Read a message from stream, if possible.
466    ///
467    /// This will also queue responses to ping and close messages. These responses
468    /// will be written and flushed on the next call to [`read`](Self::read),
469    /// [`write`](Self::write) or [`flush`](Self::flush).
470    ///
471    /// # Closing the connection
472    /// When the remote endpoint decides to close the connection this will return
473    /// the close message with an optional close frame.
474    ///
475    /// You should continue calling [`read`](Self::read), [`write`](Self::write) or
476    /// [`flush`](Self::flush) to drive the reply to the close frame until [`ProtocolError::Io`] (close)
477    /// is returned. Once that happens it is safe to drop the underlying connection.
478    pub fn read(&mut self) -> Result<Message, ProtocolError> {
479        self.context.read(&mut self.socket)
480    }
481
482    /// Writes and immediately flushes a message.
483    /// Equivalent to calling [`write`](Self::write) then [`flush`](Self::flush).
484    pub fn send(&mut self, message: Message) -> Result<(), ProtocolError> {
485        self.write(message)?;
486        self.flush()
487    }
488
489    /// Write a message to the provided stream, if possible.
490    ///
491    /// A subsequent call should be made to [`flush`](Self::flush) to flush writes.
492    ///
493    /// In the event of stream write failure the message frame will be stored
494    /// in the write buffer and will try again on the next call to [`write`](Self::write)
495    /// or [`flush`](Self::flush).
496    ///
497    /// If the write buffer would exceed the configured [`WebSocketConfig::max_write_buffer_size`]
498    /// [`Err(WriteBufferFull(msg_frame))`](ProtocolError::WriteBufferFull) is returned.
499    ///
500    /// This call will generally not flush. However, if there are queued automatic messages
501    /// they will be written and eagerly flushed.
502    ///
503    /// For example, upon receiving ping messages this crate queues pong replies automatically.
504    /// The next call to [`read`](Self::read), [`write`](Self::write) or [`flush`](Self::flush)
505    /// will write & flush the pong reply. This means you should not respond to ping frames manually.
506    ///
507    /// You can however send pong frames manually in order to indicate a unidirectional heartbeat
508    /// as described in [RFC 6455](https://tools.ietf.org/html/rfc6455#section-5.5.3). Note that
509    /// if [`read`](Self::read) returns a ping, you should [`flush`](Self::flush) before passing
510    /// a custom pong to [`write`](Self::write), otherwise the automatic queued response to the
511    /// ping will not be sent as it will be replaced by your custom pong message.
512    ///
513    /// # Errors
514    /// - If the WebSocket's write buffer is full, [`ProtocolError::WriteBufferFull`] will be returned
515    ///   along with the equivalent passed message frame.
516    /// - If the connection is closed and should be dropped, this will return [`ProtocolError::Io`] (close).
517    /// - If you try again after [`ProtocolError::Io`] (close) was returned either from here or from
518    ///   [`read`](Self::read), [`ProtocolError::Io`] with reason will be returned. This indicates a program
519    ///   error on your part.
520    /// - [`ProtocolError::Io`] is returned if the underlying connection returns an error
521    ///   (consider these fatal except for WouldBlock).
522    /// - [`ProtocolError::Io`] if your message size is bigger than the configured max message size.
523    pub fn write(&mut self, message: Message) -> Result<(), ProtocolError> {
524        self.context.write(&mut self.socket, message)
525    }
526
527    /// Flush writes.
528    ///
529    /// Ensures all messages previously passed to [`write`](Self::write) and automatic
530    /// queued pong responses are written & flushed into the underlying stream.
531    pub fn flush(&mut self) -> Result<(), ProtocolError> {
532        self.context.flush(&mut self.socket)
533    }
534
535    /// Close the connection.
536    ///
537    /// This function guarantees that the close frame will be queued.
538    /// There is no need to call it again. Calling this function is
539    /// the same as calling `write(Message::Close(..))`.
540    ///
541    /// After queuing the close frame you should continue calling [`read`](Self::read) or
542    /// [`flush`](Self::flush) to drive the close handshake to completion.
543    ///
544    /// The websocket RFC defines that the underlying connection should be closed
545    /// by the server. This crate takes care of this asymmetry for you.
546    ///
547    /// When the close handshake is finished (we have both sent and received
548    /// a close message), [`read`](Self::read) or [`flush`](Self::flush) will return
549    /// [`ProtocolError::Io`] (close) if this endpoint is the server.
550    ///
551    /// If this endpoint is a client, [`ProtocolError::Io`] (close) will only be
552    /// returned after the server has closed the underlying connection.
553    ///
554    /// It is thus safe to drop the underlying connection as soon as [`ProtocolError::Io`] (close)
555    /// is returned from [`read`](Self::read) or [`flush`](Self::flush).
556    pub fn close(&mut self, code: Option<CloseFrame>) -> Result<(), ProtocolError> {
557        self.context.close(&mut self.socket, code)
558    }
559}
560
561impl<Stream: ExtensionsRef> ExtensionsRef for WebSocket<Stream> {
562    fn extensions(&self) -> &Extensions {
563        self.socket.extensions()
564    }
565}
566
567/// A context for managing WebSocket stream.
568#[derive(Debug)]
569pub struct WebSocketContext {
570    /// Server or client?
571    role: Role,
572    /// encoder/decoder of frame.
573    frame: FrameCodec,
574    /// The state of processing, either "active" or "closing".
575    state: WebSocketState,
576    #[cfg(feature = "compression")]
577    /// The state used in function per-message compression,
578    /// only set in case the extension is enabled.
579    per_message_deflate_state: Option<PerMessageDeflateState>,
580    /// Receive: an incomplete message being processed.
581    incomplete: Option<IncompleteMessage>,
582    /// Send in addition to regular messages E.g. "pong" or "close".
583    additional_send: Option<Frame>,
584    /// True indicates there is an additional message (like a pong)
585    /// that failed to flush previously and we should try again.
586    unflushed_additional: bool,
587    /// The configuration for the websocket session.
588    config: WebSocketConfig,
589}
590
591impl WebSocketContext {
592    /// Create a WebSocket context that manages a post-handshake stream.
593    ///
594    /// # Panics
595    /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`.
596    #[must_use]
597    pub fn new(role: Role, config: Option<WebSocketConfig>) -> Self {
598        let conf = config.unwrap_or_default();
599        Self::_new(role, FrameCodec::new(conf.read_buffer_size), conf)
600    }
601
602    /// Create a WebSocket context that manages a post-handshake stream.
603    ///
604    /// # Panics
605    /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`.
606    #[must_use]
607    pub fn from_partially_read(part: Vec<u8>, role: Role, config: Option<WebSocketConfig>) -> Self {
608        let conf = config.unwrap_or_default();
609        Self::_new(
610            role,
611            FrameCodec::from_partially_read(part, conf.read_buffer_size),
612            conf,
613        )
614    }
615
616    fn _new(role: Role, mut frame: FrameCodec, config: WebSocketConfig) -> Self {
617        config.assert_valid();
618        frame.set_max_out_buffer_len(config.max_write_buffer_size);
619        frame.set_out_buffer_write_len(config.write_buffer_size);
620        Self {
621            role,
622            frame,
623            state: WebSocketState::Active,
624            #[cfg(feature = "compression")]
625            per_message_deflate_state: config
626                .per_message_deflate
627                .map(|cfg| PerMessageDeflateState::new(role, cfg)),
628            incomplete: None,
629            additional_send: None,
630            unflushed_additional: false,
631            config,
632        }
633    }
634
635    /// Change the configuration.
636    ///
637    /// # Panics
638    /// Panics if config is invalid e.g. `max_write_buffer_size <= write_buffer_size`.
639    pub fn set_config(&mut self, set_func: impl FnOnce(&mut WebSocketConfig)) {
640        set_func(&mut self.config);
641        self.config.assert_valid();
642        self.frame
643            .set_max_out_buffer_len(self.config.max_write_buffer_size);
644        self.frame
645            .set_out_buffer_write_len(self.config.write_buffer_size);
646    }
647
648    /// Read the configuration.
649    pub fn get_config(&self) -> &WebSocketConfig {
650        &self.config
651    }
652
653    /// Check if it is possible to read messages.
654    ///
655    /// Reading is impossible after receiving `Message::Close`. It is still possible after
656    /// sending close frame since the peer still may send some data before confirming close.
657    pub fn can_read(&self) -> bool {
658        self.state.can_read()
659    }
660
661    /// Check if it is possible to write messages.
662    ///
663    /// Writing gets impossible immediately after sending or receiving `Message::Close`.
664    pub fn can_write(&self) -> bool {
665        self.state.is_active()
666    }
667
668    /// Read a message from the provided stream, if possible.
669    ///
670    /// This function sends pong and close responses automatically.
671    /// However, it never blocks on write.
672    pub fn read<Stream>(&mut self, stream: &mut Stream) -> Result<Message, ProtocolError>
673    where
674        Stream: Read + Write,
675    {
676        // Do not read from already closed connections.
677        self.state.check_not_terminated()?;
678
679        loop {
680            if self.additional_send.is_some() || self.unflushed_additional {
681                // Since we may get ping or close, we need to reply to the messages even during read.
682                match self.flush(stream) {
683                    Ok(_) => {}
684                    Err(ProtocolError::Io(err)) if err.kind() == io::ErrorKind::WouldBlock => {
685                        // If blocked continue reading, but try again later
686                        self.unflushed_additional = true;
687                    }
688                    Err(err) => return Err(err),
689                }
690            } else if self.role == Role::Server && !self.state.can_read() {
691                self.state = WebSocketState::Terminated;
692                return Err(ProtocolError::Io(io::Error::new(
693                    io::ErrorKind::ConnectionAborted,
694                    BoxError::from_static_str("Connection closed normally by me-the-server"),
695                )));
696            }
697
698            // If we get here, either write blocks or we have nothing to write.
699            // Thus if read blocks, just let it return WouldBlock.
700            if let Some(message) = self.read_message_frame(stream)? {
701                trace!("Received message {message}");
702                return Ok(message);
703            }
704        }
705    }
706
707    /// Write a message to the provided stream.
708    ///
709    /// A subsequent call should be made to [`flush`](Self::flush) to flush writes.
710    ///
711    /// In the event of stream write failure the message frame will be stored
712    /// in the write buffer and will try again on the next call to [`write`](Self::write)
713    /// or [`flush`](Self::flush).
714    ///
715    /// If the write buffer would exceed the configured [`WebSocketConfig::max_write_buffer_size`]
716    /// [`Err(WriteBufferFull(msg_frame))`](ProtocolError::WriteBufferFull) is returned.
717    pub fn write<Stream>(
718        &mut self,
719        stream: &mut Stream,
720        message: Message,
721    ) -> Result<(), ProtocolError>
722    where
723        Stream: Read + Write,
724    {
725        // When terminated, return AlreadyClosed.
726        self.state.check_not_terminated()?;
727
728        // Do not write after sending a close frame.
729        if !self.state.is_active() {
730            return Err(ProtocolError::SendAfterClosing);
731        }
732
733        let frame = match message {
734            Message::Text(data) => {
735                #[cfg(feature = "compression")]
736                match self.per_message_deflate_state.as_mut() {
737                    Some(deflate_state) => {
738                        let data = match deflate_state.encoder.encode(data.as_bytes()) {
739                            Ok(data) => data,
740                            Err(err) => return Err(ProtocolError::DeflateError(err)),
741                        };
742                        let mut msg = Frame::message(data, OpCode::Data(OpCodeData::Text), true);
743                        msg.header_mut().rsv1 = true;
744                        msg
745                    }
746                    None => Frame::message(data, OpCode::Data(OpCodeData::Text), true),
747                }
748                #[cfg(not(feature = "compression"))]
749                Frame::message(data, OpCode::Data(OpCodeData::Text), true)
750            }
751            Message::Binary(data) => {
752                #[cfg(feature = "compression")]
753                match self.per_message_deflate_state.as_mut() {
754                    Some(deflate_state) => {
755                        let data = match deflate_state.encoder.encode(data.as_ref()) {
756                            Ok(data) => data,
757                            Err(err) => return Err(ProtocolError::DeflateError(err)),
758                        };
759                        let mut msg = Frame::message(data, OpCode::Data(OpCodeData::Binary), true);
760                        msg.header_mut().rsv1 = true;
761                        msg
762                    }
763                    None => Frame::message(data, OpCode::Data(OpCodeData::Binary), true),
764                }
765                #[cfg(not(feature = "compression"))]
766                Frame::message(data, OpCode::Data(OpCodeData::Binary), true)
767            }
768            Message::Ping(data) => Frame::ping(data),
769            Message::Pong(data) => {
770                self.set_additional(Frame::pong(data));
771                // Note: user pongs can be user flushed so no need to flush here
772                return self._write(stream, None).map(drop);
773            }
774            Message::Close(code) => return self.close(stream, code),
775            Message::Frame(f) => f,
776        };
777
778        let should_flush = self._write(stream, Some(frame))?;
779        if should_flush {
780            self.flush(stream)?;
781        }
782        Ok(())
783    }
784
785    /// Flush writes.
786    ///
787    /// Ensures all messages previously passed to [`write`](Self::write) and automatically
788    /// queued pong responses are written & flushed into the `stream`.
789    #[inline]
790    pub fn flush<Stream>(&mut self, stream: &mut Stream) -> Result<(), ProtocolError>
791    where
792        Stream: Read + Write,
793    {
794        self._write(stream, None)?;
795        self.frame.write_out_buffer(stream)?;
796        stream.flush()?;
797        self.unflushed_additional = false;
798        Ok(())
799    }
800
801    /// Writes any data in the out_buffer, `additional_send` and given `data`.
802    ///
803    /// Does **not** flush.
804    ///
805    /// Returns true if the write contents indicate we should flush immediately.
806    fn _write<Stream>(
807        &mut self,
808        stream: &mut Stream,
809        data: Option<Frame>,
810    ) -> Result<bool, ProtocolError>
811    where
812        Stream: Read + Write,
813    {
814        if let Some(data) = data {
815            self.buffer_frame(stream, data)?;
816        }
817
818        // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
819        // response, unless it already received a Close frame. It SHOULD
820        // respond with Pong frame as soon as is practical. (RFC 6455)
821        let should_flush = if let Some(msg) = self.additional_send.take() {
822            trace!("Sending pong/close");
823            match self.buffer_frame(stream, msg) {
824                Err(ProtocolError::WriteBufferFull(msg)) => {
825                    // if an system message would exceed the buffer put it back in
826                    // `additional_send` for retry. Otherwise returning this error
827                    // may not make sense to the user, e.g. calling `flush`.
828                    if let Message::Frame(msg) = msg {
829                        self.set_additional(msg);
830                        false
831                    } else {
832                        unreachable!();
833                    }
834                }
835                Err(err) => return Err(err),
836                Ok(_) => true,
837            }
838        } else {
839            self.unflushed_additional
840        };
841
842        // If we're closing and there is nothing to send anymore, we should close the connection.
843        if self.role == Role::Server && !self.state.can_read() {
844            // The underlying TCP connection, in most normal cases, SHOULD be closed
845            // first by the server, so that it holds the TIME_WAIT state and not the
846            // client (as this would prevent it from re-opening the connection for 2
847            // maximum segment lifetimes (2MSL), while there is no corresponding
848            // server impact as a TIME_WAIT connection is immediately reopened upon
849            // a new SYN with a higher seq number). (RFC 6455)
850            self.frame.write_out_buffer(stream)?;
851            self.state = WebSocketState::Terminated;
852            Err(ProtocolError::Io(io::Error::new(
853                io::ErrorKind::ConnectionAborted,
854                BoxError::from_static_str("Connection closed normally by me-the-server (EOF)"),
855            )))
856        } else {
857            Ok(should_flush)
858        }
859    }
860
861    /// Close the connection.
862    ///
863    /// This function guarantees that the close frame will be queued.
864    /// There is no need to call it again. Calling this function is
865    /// the same as calling `send(Message::Close(..))`.
866    pub fn close<Stream>(
867        &mut self,
868        stream: &mut Stream,
869        code: Option<CloseFrame>,
870    ) -> Result<(), ProtocolError>
871    where
872        Stream: Read + Write,
873    {
874        if self.state == WebSocketState::Active {
875            self.state = WebSocketState::ClosedByUs;
876            let frame = Frame::close(code);
877            self._write(stream, Some(frame))?;
878        }
879        self.flush(stream)
880    }
881
882    /// Try to decode one message frame. May return None.
883    fn read_message_frame(
884        &mut self,
885        stream: &mut impl Read,
886    ) -> Result<Option<Message>, ProtocolError> {
887        let Some(frame) = self.frame.read_frame(
888            stream,
889            self.config.max_frame_size,
890            matches!(self.role, Role::Server),
891            self.config.accept_unmasked_frames,
892        )?
893        else {
894            // Connection closed by peer
895            return match std::mem::replace(&mut self.state, WebSocketState::Terminated) {
896                WebSocketState::ClosedByPeer | WebSocketState::CloseAcknowledged => {
897                    Err(ProtocolError::Io(io::Error::new(
898                        io::ErrorKind::ConnectionAborted,
899                        BoxError::from_static_str("Connection closed normally by peer"),
900                    )))
901                }
902                WebSocketState::Active
903                | WebSocketState::ClosedByUs
904                | WebSocketState::Terminated => Err(ProtocolError::ResetWithoutClosingHandshake),
905            };
906        };
907
908        if !self.state.can_read() {
909            return Err(ProtocolError::ReceivedAfterClosing);
910        }
911
912        #[cfg(feature = "compression")]
913        // to ensure that this is valid in later branches,
914        // as this is not always true despite an extension active that supports it
915        let mut rsv1_set = false;
916
917        // MUST be 0 unless an extension is negotiated that defines meanings
918        // for non-zero values.  If a nonzero value is received and none of
919        // the negotiated extensions defines the meaning of such a nonzero
920        // value, the receiving endpoint MUST _Fail the WebSocket
921        // Connection_.
922        {
923            let hdr = frame.header();
924            if hdr.rsv1 {
925                #[cfg(feature = "compression")]
926                {
927                    rsv1_set = true;
928                    if self.per_message_deflate_state.is_none() {
929                        tracing::debug!(
930                            "rsv1 bit is set but PMD state is none: no use case for it"
931                        );
932                        return Err(ProtocolError::NonZeroReservedBits);
933                    }
934                }
935                #[cfg(not(feature = "compression"))]
936                {
937                    tracing::debug!("rsv1 bit is set but compression feature no enabled");
938                    return Err(ProtocolError::NonZeroReservedBits);
939                }
940            } else if hdr.rsv2 || hdr.rsv3 {
941                tracing::debug!("rsv2 or rsv3 bit set: not expected ever");
942                return Err(ProtocolError::NonZeroReservedBits);
943            }
944        }
945
946        if self.role == Role::Client && frame.is_masked() {
947            // A client MUST close a connection if it detects a masked frame. (RFC 6455)
948            return Err(ProtocolError::MaskedFrameFromServer);
949        }
950
951        match frame.header().opcode {
952            OpCode::Control(ctl) => {
953                #[cfg(feature = "compression")]
954                if rsv1_set {
955                    tracing::debug!("rsv1 bit set in control frame: not expected");
956                    return Err(ProtocolError::NonZeroReservedBits);
957                }
958
959                match ctl {
960                    // All control frames MUST have a payload length of 125 bytes or less
961                    // and MUST NOT be fragmented. (RFC 6455)
962                    _ if !frame.header().is_final => Err(ProtocolError::FragmentedControlFrame),
963                    _ if frame.payload().len() > 125 => Err(ProtocolError::ControlFrameTooBig),
964                    OpCodeControl::Close => {
965                        Ok(self.do_close(frame.into_close()?).map(Message::Close))
966                    }
967                    OpCodeControl::Reserved(i) => Err(ProtocolError::UnknownControlFrameType(i)),
968                    OpCodeControl::Ping => {
969                        let data = frame.into_payload();
970                        // No ping processing after we sent a close frame.
971                        if self.state.is_active() {
972                            self.set_additional(Frame::pong(data.clone()));
973                        }
974                        Ok(Some(Message::Ping(data)))
975                    }
976                    OpCodeControl::Pong => Ok(Some(Message::Pong(frame.into_payload()))),
977                }
978            }
979
980            OpCode::Data(data) => {
981                let fin = frame.header().is_final;
982
983                #[cfg(feature = "compression")]
984                if matches!(data, OpCodeData::Continue) && rsv1_set {
985                    tracing::debug!("rsv1 bit set in CONTINUE frame: not expected");
986                    return Err(ProtocolError::NonZeroReservedBits);
987                }
988
989                let payload = match (data, self.incomplete.as_mut()) {
990                    (OpCodeData::Continue, None) => {
991                        #[cfg(feature = "compression")]
992                        if let Some(deflate_state) = self.per_message_deflate_state.as_mut() {
993                            if fin {
994                                let (compressed_data, msg_type) =
995                                    deflate_state.decompress_incomplete_msg.fin_buffer(
996                                        frame.into_payload(),
997                                        self.config.max_message_size,
998                                    )?;
999                                return match deflate_state
1000                                    .decoder
1001                                    .decode(compressed_data.as_ref(), self.config.max_message_size)
1002                                {
1003                                    Ok(raw_data) => match msg_type {
1004                                        IncompleteMessageType::Text => {
1005                                            Ok(Some(Message::Text(Utf8Bytes::try_from(raw_data)?)))
1006                                        }
1007                                        IncompleteMessageType::Binary => {
1008                                            Ok(Some(Message::Binary(raw_data.into())))
1009                                        }
1010                                    },
1011                                    Err(err) => Err(err),
1012                                };
1013                            }
1014
1015                            deflate_state
1016                                .decompress_incomplete_msg
1017                                .extend(frame.into_payload(), self.config.max_message_size)?;
1018                            Ok(None)
1019                        } else {
1020                            return Err(ProtocolError::UnexpectedContinueFrame);
1021                        }
1022
1023                        #[cfg(not(feature = "compression"))]
1024                        return Err(ProtocolError::UnexpectedContinueFrame);
1025                    }
1026                    (OpCodeData::Continue, Some(incomplete)) => {
1027                        incomplete.extend(frame.into_payload(), self.config.max_message_size)?;
1028                        Ok(None)
1029                    }
1030                    (_, Some(_)) => Err(ProtocolError::ExpectedFragment(data)),
1031                    (OpCodeData::Text, _) => {
1032                        Ok(Some((frame.into_payload(), IncompleteMessageType::Text)))
1033                    }
1034                    (OpCodeData::Binary, _) => {
1035                        Ok(Some((frame.into_payload(), IncompleteMessageType::Binary)))
1036                    }
1037                    (OpCodeData::Reserved(i), _) => Err(ProtocolError::UnknownDataFrameType(i)),
1038                }?;
1039
1040                match (payload, fin) {
1041                    (None, true) =>
1042                    {
1043                        #[expect(
1044                            clippy::expect_used,
1045                            reason = "we can only reach here if incomplete is Some"
1046                        )]
1047                        Ok(Some(
1048                            self.incomplete
1049                                .take()
1050                                .expect("incomplete to be there")
1051                                .complete()?,
1052                        ))
1053                    }
1054                    (None, false) => Ok(None),
1055                    (Some((payload, t)), true) => {
1056                        check_max_size(payload.len(), self.config.max_message_size)?;
1057
1058                        #[cfg(feature = "compression")]
1059                        if rsv1_set {
1060                            if let Some(deflate_state) = self.per_message_deflate_state.as_mut() {
1061                                let compressed_data = payload;
1062                                let raw_data = deflate_state
1063                                    .decoder
1064                                    .decode(&compressed_data, self.config.max_message_size)?;
1065                                match t {
1066                                    IncompleteMessageType::Text => {
1067                                        Ok(Some(Message::Text(Utf8Bytes::try_from(raw_data)?)))
1068                                    }
1069                                    IncompleteMessageType::Binary => {
1070                                        Ok(Some(Message::Binary(raw_data.into())))
1071                                    }
1072                                }
1073                            } else {
1074                                tracing::debug!(
1075                                    "rsv1 bit set in text frame but deflate state is none"
1076                                );
1077                                Err(ProtocolError::NonZeroReservedBits)
1078                            }
1079                        } else {
1080                            match t {
1081                                IncompleteMessageType::Text => {
1082                                    Ok(Some(Message::Text(payload.try_into()?)))
1083                                }
1084                                IncompleteMessageType::Binary => Ok(Some(Message::Binary(payload))),
1085                            }
1086                        }
1087
1088                        #[cfg(not(feature = "compression"))]
1089                        match t {
1090                            IncompleteMessageType::Text => {
1091                                Ok(Some(Message::Text(payload.try_into()?)))
1092                            }
1093                            IncompleteMessageType::Binary => Ok(Some(Message::Binary(payload))),
1094                        }
1095                    }
1096                    (Some((payload, t)), false) => {
1097                        #[cfg(feature = "compression")]
1098                        if rsv1_set {
1099                            if let Some(deflate_state) = self.per_message_deflate_state.as_mut() {
1100                                deflate_state.decompress_incomplete_msg.reset(t);
1101                                deflate_state
1102                                    .decompress_incomplete_msg
1103                                    .extend(payload, self.config.max_message_size)?;
1104                                Ok(None)
1105                            } else {
1106                                tracing::debug!(
1107                                    "rsv1 bit set in non-fin bin/text frame but deflate state is none"
1108                                );
1109                                Err(ProtocolError::NonZeroReservedBits)
1110                            }
1111                        } else {
1112                            let mut incomplete = IncompleteMessage::new(t);
1113                            incomplete.extend(payload, self.config.max_message_size)?;
1114                            self.incomplete = Some(incomplete);
1115                            Ok(None)
1116                        }
1117                        #[cfg(not(feature = "compression"))]
1118                        {
1119                            let mut incomplete = IncompleteMessage::new(t);
1120                            incomplete.extend(payload, self.config.max_message_size)?;
1121                            self.incomplete = Some(incomplete);
1122                            Ok(None)
1123                        }
1124                    }
1125                }
1126            }
1127        } // match opcode
1128    }
1129
1130    /// Received a close frame. Tells if we need to return a close frame to the user.
1131    #[expect(clippy::option_option)]
1132    fn do_close(&mut self, close: Option<CloseFrame>) -> Option<Option<CloseFrame>> {
1133        rama_core::telemetry::tracing::trace!("Received close frame: {close:?}");
1134        match self.state {
1135            WebSocketState::Active => {
1136                self.state = WebSocketState::ClosedByPeer;
1137
1138                let close = close.map(|frame| {
1139                    if !frame.code.is_allowed() {
1140                        CloseFrame {
1141                            code: CloseCode::Protocol,
1142                            reason: Utf8Bytes::from_static("Protocol violation"),
1143                        }
1144                    } else {
1145                        frame
1146                    }
1147                });
1148
1149                let reply = Frame::close(close.clone());
1150                debug!("Replying to close with {reply:?}");
1151                self.set_additional(reply);
1152
1153                Some(close)
1154            }
1155            WebSocketState::ClosedByPeer | WebSocketState::CloseAcknowledged => {
1156                // It is already closed, just ignore.
1157                None
1158            }
1159            WebSocketState::ClosedByUs => {
1160                // We received a reply.
1161                self.state = WebSocketState::CloseAcknowledged;
1162                Some(close)
1163            }
1164            WebSocketState::Terminated => unreachable!(),
1165        }
1166    }
1167
1168    /// Write a single frame into the write-buffer.
1169    fn buffer_frame<Stream>(
1170        &mut self,
1171        stream: &mut Stream,
1172        mut frame: Frame,
1173    ) -> Result<(), ProtocolError>
1174    where
1175        Stream: Read + Write,
1176    {
1177        match self.role {
1178            Role::Server => {}
1179            Role::Client => {
1180                // 5.  If the data is being sent by the client, the frame(s) MUST be
1181                // masked as defined in Section 5.3. (RFC 6455)
1182                frame.set_random_mask();
1183            }
1184        }
1185
1186        trace!("Sending frame: {frame:?}");
1187        self.frame.buffer_frame(stream, frame)
1188    }
1189
1190    /// Replace `additional_send` if it is currently a `Pong` message.
1191    fn set_additional(&mut self, add: Frame) {
1192        let empty_or_pong = self
1193            .additional_send
1194            .as_ref()
1195            .is_none_or(|f| f.header().opcode == OpCode::Control(OpCodeControl::Pong));
1196        if empty_or_pong {
1197            self.additional_send.replace(add);
1198        }
1199    }
1200}
1201
1202fn check_max_size(size: usize, max_size: Option<usize>) -> Result<(), ProtocolError> {
1203    if let Some(max_size) = max_size
1204        && size > max_size
1205    {
1206        return Err(ProtocolError::MessageTooLong { size, max_size });
1207    }
1208    Ok(())
1209}
1210
1211/// The current connection state.
1212#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1213enum WebSocketState {
1214    /// The connection is active.
1215    Active,
1216    /// We initiated a close handshake.
1217    ClosedByUs,
1218    /// The peer initiated a close handshake.
1219    ClosedByPeer,
1220    /// The peer replied to our close handshake.
1221    CloseAcknowledged,
1222    /// The connection does not exist anymore.
1223    Terminated,
1224}
1225
1226impl WebSocketState {
1227    /// Tell if we're allowed to process normal messages.
1228    fn is_active(self) -> bool {
1229        matches!(self, Self::Active)
1230    }
1231
1232    /// Tell if we should process incoming data. Note that if we send a close frame
1233    /// but the remote hasn't confirmed, they might have sent data before they receive our
1234    /// close frame, so we should still pass those to client code, hence ClosedByUs is valid.
1235    fn can_read(self) -> bool {
1236        matches!(self, Self::Active | Self::ClosedByUs)
1237    }
1238
1239    /// Check if the state is active, return error if not.
1240    fn check_not_terminated(self) -> Result<(), ProtocolError> {
1241        match self {
1242            Self::Terminated => Err(ProtocolError::Io(io::Error::new(
1243                io::ErrorKind::NotConnected,
1244                BoxError::from_static_str("Trying to work with closed connection"),
1245            ))),
1246            Self::Active | Self::CloseAcknowledged | Self::ClosedByPeer | Self::ClosedByUs => {
1247                Ok(())
1248            }
1249        }
1250    }
1251}