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}