Skip to main content

rustlavel_ws/
connection.rs

1//! A live WebSocket connection.
2//!
3//! [`WebSocket`] owns the read half and drives everything the protocol requires
4//! but an application should never have to think about: pongs are sent for
5//! pings, fragments are reassembled, text is validated as UTF-8, oversize
6//! messages are refused, and a close is answered with a close.
7//!
8//! Writing happens in a separate task fed by a bounded queue. That split is
9//! what makes broadcasting possible: a [`Sender`] can be cloned and handed to
10//! anything that wants to push to this client, while `recv` keeps exclusive
11//! ownership of the reader. It is also what makes backpressure expressible —
12//! the queue has a fixed depth, and a client that will not drain it is dropped
13//! rather than allowed to grow the server's memory.
14
15use crate::error::{WsError, WsResult};
16use crate::frame::{CloseCode, CloseFrame, Frame, OpCode, Role};
17use crate::message::Message;
18use rustlavel_core::{Config, Event, events};
19use rustlavel_http::Upgraded;
20use std::time::{Duration, Instant};
21use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
22use tokio::sync::mpsc;
23
24/// Limits and timers for one socket.
25#[derive(Debug, Clone)]
26pub struct WebSocketConfig {
27    /// The largest reassembled message that will be accepted.
28    pub max_message_size: usize,
29    /// The largest single frame that will be accepted. Checked against the
30    /// declared length, so an absurd announcement costs no memory.
31    pub max_frame_size: usize,
32    /// How long a connection may be silent before it is pinged. A second
33    /// silent period with no pong closes it. `None` disables the check.
34    pub idle_timeout: Option<Duration>,
35    /// How long to wait for the peer's half of the close handshake.
36    pub close_timeout: Duration,
37    /// Depth of the outgoing queue. This is the backpressure budget: a client
38    /// this far behind is dropped.
39    pub send_queue: usize,
40}
41
42impl Default for WebSocketConfig {
43    fn default() -> Self {
44        WebSocketConfig {
45            max_message_size: 1 << 20,
46            max_frame_size: 1 << 20,
47            idle_timeout: Some(Duration::from_secs(60)),
48            close_timeout: Duration::from_secs(5),
49            send_queue: 64,
50        }
51    }
52}
53
54impl WebSocketConfig {
55    /// Read the limits from the application's configuration, so an operator can
56    /// raise them in `.env` without recompiling.
57    ///
58    /// `websocket.idle_timeout` is in seconds; zero turns the check off.
59    pub fn from_config(config: &Config) -> Self {
60        let defaults = WebSocketConfig::default();
61        let idle = config.int("websocket.idle_timeout", 60).max(0) as u64;
62
63        WebSocketConfig {
64            max_message_size: config
65                .int("websocket.max_message_size", defaults.max_message_size as i64)
66                .max(1) as usize,
67            max_frame_size: config
68                .int("websocket.max_frame_size", defaults.max_frame_size as i64)
69                .max(1) as usize,
70            idle_timeout: (idle > 0).then(|| Duration::from_secs(idle)),
71            close_timeout: Duration::from_secs(
72                config.int("websocket.close_timeout", 5).max(1) as u64
73            ),
74            send_queue: config.int("websocket.send_queue", defaults.send_queue as i64).max(1)
75                as usize,
76        }
77    }
78}
79
80/// A cloneable handle for pushing messages at one client.
81///
82/// Handed to the broadcaster, to background tasks, to anything that produces
83/// events. It never blocks the reader, and it can outlive the socket: once the
84/// connection is gone every clone reports closed.
85#[derive(Clone)]
86pub struct Sender {
87    queue: mpsc::Sender<Message>,
88}
89
90impl Sender {
91    /// Queue a message, waiting for room. Use this from the connection's own
92    /// task, where waiting is what you want.
93    pub async fn send(&self, message: impl Into<Message>) -> WsResult<()> {
94        self.queue.send(message.into()).await.map_err(|_| WsError::Closed)
95    }
96
97    /// Queue a message only if there is room right now.
98    ///
99    /// This is the shape broadcasting needs: fan-out must never wait on the
100    /// slowest subscriber, so a full queue is a failure the caller acts on by
101    /// dropping the subscriber.
102    pub fn try_send(&self, message: impl Into<Message>) -> WsResult<()> {
103        match self.queue.try_send(message.into()) {
104            Ok(()) => Ok(()),
105            Err(mpsc::error::TrySendError::Full(_)) => Err(WsError::Full),
106            Err(mpsc::error::TrySendError::Closed(_)) => Err(WsError::Closed),
107        }
108    }
109
110    pub fn is_closed(&self) -> bool {
111        self.queue.is_closed()
112    }
113
114    /// How many more messages fit before this client counts as too slow.
115    pub fn room(&self) -> usize {
116        self.queue.capacity()
117    }
118}
119
120/// A sender with no socket behind it, for tests and for anything that wants to
121/// collect what would have been written.
122pub fn channel(capacity: usize) -> (Sender, mpsc::Receiver<Message>) {
123    let (queue, receiver) = mpsc::channel(capacity.max(1));
124    (Sender { queue }, receiver)
125}
126
127pub struct WebSocket {
128    reader: Box<dyn AsyncRead + Send + Unpin>,
129    /// Bytes read but not yet framed. Seeded with whatever the HTTP server had
130    /// already buffered past the request — dropping those loses the first frame.
131    buffer: Vec<u8>,
132    outgoing: Sender,
133    config: WebSocketConfig,
134    /// A message still arriving in fragments: its opcode, the bytes so far, and
135    /// how many of those bytes have already been checked as UTF-8.
136    ///
137    /// The count is what keeps validation linear. Re-validating the whole
138    /// buffer on every fragment would be quadratic — a peer sending a message
139    /// one byte at a time would cost the server n² work — so each byte is
140    /// looked at once, and only an unfinished character at the very end is
141    /// looked at again, with the fragment that completes it.
142    partial: Option<(OpCode, Vec<u8>, usize)>,
143    awaiting_pong: bool,
144    closed: bool,
145    close_frame: Option<CloseFrame>,
146    path: String,
147    protocol: Option<String>,
148    opened_at: Instant,
149    received: u64,
150}
151
152impl WebSocket {
153    /// Take over a connection the HTTP server has finished with.
154    pub fn new(upgraded: Upgraded, config: WebSocketConfig) -> WebSocket {
155        let (queue, receiver) = mpsc::channel(config.send_queue.max(1));
156        tokio::spawn(write_loop(upgraded.writer, receiver));
157
158        WebSocket {
159            reader: upgraded.reader,
160            buffer: upgraded.buffered,
161            outgoing: Sender { queue },
162            config,
163            partial: None,
164            awaiting_pong: false,
165            closed: false,
166            close_frame: None,
167            path: String::new(),
168            protocol: None,
169            opened_at: Instant::now(),
170            received: 0,
171        }
172    }
173
174    /// Label the socket for instrumentation with the route it was opened on.
175    pub fn on_path(mut self, path: impl Into<String>) -> Self {
176        self.path = path.into();
177        self
178    }
179
180    pub fn with_protocol(mut self, protocol: Option<String>) -> Self {
181        self.protocol = protocol;
182        self
183    }
184
185    /// A handle other tasks can push messages through.
186    pub fn sender(&self) -> Sender {
187        self.outgoing.clone()
188    }
189
190    pub fn path(&self) -> &str {
191        &self.path
192    }
193
194    /// The subprotocol agreed during the handshake, if any.
195    pub fn protocol(&self) -> Option<&str> {
196        self.protocol.as_deref()
197    }
198
199    pub fn is_closed(&self) -> bool {
200        self.closed
201    }
202
203    /// How the connection ended, once it has. Available after `recv` returns
204    /// `None`, which is where an application usually wants to log it.
205    pub fn close_frame(&self) -> Option<&CloseFrame> {
206        self.close_frame.as_ref()
207    }
208
209    /// How many data messages have arrived.
210    pub fn received(&self) -> u64 {
211        self.received
212    }
213
214    pub async fn send(&mut self, message: impl Into<Message>) -> WsResult<()> {
215        if self.closed {
216            return Err(WsError::Closed);
217        }
218        self.outgoing.send(message).await
219    }
220
221    /// The next message from the peer, or `None` once the connection is over.
222    ///
223    /// Only `Text` and `Binary` come out of here. Pings, pongs and closes are
224    /// answered internally, because every application would have to write the
225    /// same three replies and one of them would get it wrong.
226    pub async fn recv(&mut self) -> Option<Message> {
227        loop {
228            if self.closed {
229                return None;
230            }
231
232            let frame = match self.next_frame().await {
233                Ok(Some(frame)) => frame,
234                // The socket ended without a close frame: nothing to reply to.
235                Ok(None) => {
236                    self.mark_closed(Some(CloseFrame::new(CloseCode::ABNORMAL, "")));
237                    return None;
238                }
239                Err(error) => {
240                    self.fail(error).await;
241                    return None;
242                }
243            };
244
245            // Any frame at all proves the peer is alive, not just a pong.
246            self.awaiting_pong = false;
247
248            match frame.opcode {
249                OpCode::Ping => {
250                    // The pong must carry the ping's application data back
251                    // unchanged; that is how the peer matches them up.
252                    if self.outgoing.send(Message::Pong(frame.payload)).await.is_err() {
253                        self.mark_closed(Some(CloseFrame::new(CloseCode::ABNORMAL, "")));
254                        return None;
255                    }
256                }
257                OpCode::Pong => {}
258                OpCode::Close => {
259                    let peer = match CloseFrame::parse(&frame.payload) {
260                        Ok(close) => close,
261                        Err(error) => {
262                            self.fail(error).await;
263                            return None;
264                        }
265                    };
266                    // The other half of the close handshake: echo the status
267                    // back so the peer knows the close was seen, not lost.
268                    let echo =
269                        peer.clone().unwrap_or_else(|| CloseFrame::new(CloseCode::NORMAL, ""));
270                    let _ = self.outgoing.send(Message::Close(Some(echo))).await;
271                    self.mark_closed(
272                        peer.or_else(|| Some(CloseFrame::new(CloseCode::NO_STATUS, ""))),
273                    );
274                    return None;
275                }
276                OpCode::Text | OpCode::Binary => {
277                    if self.partial.is_some() {
278                        self.fail(WsError::protocol(
279                            "a new data frame arrived while a fragmented message was still open",
280                        ))
281                        .await;
282                        return None;
283                    }
284                    if frame.fin {
285                        return self.deliver(frame.opcode, frame.payload).await;
286                    }
287                    let mut checked = 0;
288                    if frame.opcode == OpCode::Text
289                        && !text_so_far_is_valid(&frame.payload, &mut checked)
290                    {
291                        self.fail(WsError::InvalidUtf8).await;
292                        return None;
293                    }
294                    self.partial = Some((frame.opcode, frame.payload, checked));
295                }
296                OpCode::Continuation => {
297                    let Some((opcode, mut assembled, mut checked)) = self.partial.take() else {
298                        self.fail(WsError::protocol(
299                            "a continuation frame arrived with no message to continue",
300                        ))
301                        .await;
302                        return None;
303                    };
304
305                    let total = assembled.len() + frame.payload.len();
306                    if total > self.config.max_message_size {
307                        // Checked as fragments arrive, not at the end: otherwise
308                        // a peer could spend the whole limit one frame at a time.
309                        self.fail(WsError::TooLarge {
310                            size: total,
311                            limit: self.config.max_message_size,
312                        })
313                        .await;
314                        return None;
315                    }
316
317                    assembled.extend_from_slice(&frame.payload);
318                    if frame.fin {
319                        // The whole message is validated once more on its way
320                        // out, by `Message::from_data` — which is what refuses
321                        // one that ends in the middle of a character.
322                        return self.deliver(opcode, assembled).await;
323                    }
324                    // **Fail fast.** Not waiting for the last fragment is the
325                    // strict reading of RFC 6455 §8.1, and the cheaper one: a
326                    // message already known to be invalid is not worth buffering
327                    // the rest of.
328                    if opcode == OpCode::Text && !text_so_far_is_valid(&assembled, &mut checked) {
329                        self.fail(WsError::InvalidUtf8).await;
330                        return None;
331                    }
332                    self.partial = Some((opcode, assembled, checked));
333                }
334            }
335        }
336    }
337
338    /// Close the connection politely: send a close frame, then wait — briefly —
339    /// for the peer's.
340    pub async fn close(&mut self, code: CloseCode, reason: impl Into<String>) {
341        if self.closed {
342            return;
343        }
344        let close = CloseFrame::new(code, reason);
345
346        if self.outgoing.send(Message::Close(Some(close.clone()))).await.is_err() {
347            self.mark_closed(Some(close));
348            return;
349        }
350
351        // Frames the peer already sent are drained rather than reset, which is
352        // what stops a clean close from looking like a crash on the other end.
353        // Bounded, because a peer that never answers must not pin this task.
354        let peer = tokio::time::timeout(self.config.close_timeout, self.drain_until_close())
355            .await
356            .ok()
357            .flatten();
358        self.mark_closed(peer.or(Some(close)));
359    }
360
361    /// Hand a reassembled payload to the application.
362    async fn deliver(&mut self, opcode: OpCode, payload: Vec<u8>) -> Option<Message> {
363        match Message::from_data(opcode, payload) {
364            Ok(message) => {
365                self.received += 1;
366                if events::has_subscribers() {
367                    // Size and kind only. A socket carrying chat, tokens or
368                    // order data must not spill its payloads into Telescope.
369                    Event::new("ws.message")
370                        .with("path", self.path.as_str())
371                        .with("kind", message.kind())
372                        .with("bytes", message.len())
373                        .dispatch();
374                }
375                Some(message)
376            }
377            Err(error) => {
378                self.fail(error).await;
379                None
380            }
381        }
382    }
383
384    /// Report a protocol failure to the peer and stop.
385    async fn fail(&mut self, error: WsError) {
386        let close = CloseFrame::new(error.close_code(), error.to_string());
387        let _ = self.outgoing.send(Message::Close(Some(close.clone()))).await;
388        self.mark_closed(Some(close));
389    }
390
391    fn mark_closed(&mut self, close: Option<CloseFrame>) {
392        self.closed = true;
393        if self.close_frame.is_none() {
394            self.close_frame = close;
395        }
396    }
397
398    async fn drain_until_close(&mut self) -> Option<CloseFrame> {
399        loop {
400            match self.read_frame().await {
401                Ok(Some(frame)) if frame.opcode == OpCode::Close => {
402                    return CloseFrame::parse(&frame.payload).ok().flatten();
403                }
404                Ok(Some(_)) => continue,
405                Ok(None) | Err(_) => return None,
406            }
407        }
408    }
409
410    /// One frame, with the idle timer wrapped around it.
411    async fn next_frame(&mut self) -> WsResult<Option<Frame>> {
412        let Some(idle) = self.config.idle_timeout else {
413            return self.read_frame().await;
414        };
415
416        loop {
417            // Cancelling the read is safe: bytes already read live in
418            // `self.buffer`, and a read that had not completed consumed nothing.
419            match tokio::time::timeout(idle, self.read_frame()).await {
420                Ok(result) => return result,
421                Err(_) if self.awaiting_pong => return Err(WsError::Idle),
422                Err(_) => {
423                    // Silent for a whole period: prod the peer once. A TCP
424                    // connection to a machine that has vanished stays "open"
425                    // for a long time, so silence has to be tested, not assumed.
426                    self.awaiting_pong = true;
427                    if self.outgoing.send(Message::Ping(Vec::new())).await.is_err() {
428                        return Ok(None);
429                    }
430                }
431            }
432        }
433    }
434
435    /// One frame, reading more bytes until the buffer holds a whole one.
436    /// `Ok(None)` means the peer closed the socket underneath us.
437    async fn read_frame(&mut self) -> WsResult<Option<Frame>> {
438        loop {
439            if let Some((frame, used)) =
440                Frame::decode(&self.buffer, Role::Server, self.config.max_frame_size)?
441            {
442                self.buffer.drain(..used);
443                return Ok(Some(frame));
444            }
445
446            let mut chunk = [0u8; 8192];
447            let read = self.reader.read(&mut chunk).await?;
448            if read == 0 {
449                return Ok(None);
450            }
451            self.buffer.extend_from_slice(&chunk[..read]);
452        }
453    }
454}
455
456/// Dispatched here so every socket reports exactly once, however it ended —
457/// including the ones an application drops without draining.
458impl Drop for WebSocket {
459    fn drop(&mut self) {
460        if !events::has_subscribers() {
461            return;
462        }
463        let code = self.close_frame.as_ref().map_or(CloseCode::ABNORMAL, |close| close.code);
464
465        Event::new("ws.disconnected")
466            .with("path", self.path.as_str())
467            .with("messages", self.received)
468            .with("code", code.code())
469            .took(self.opened_at.elapsed())
470            .dispatch();
471    }
472}
473
474/// Serialize everything queued for this client onto the socket.
475///
476/// One task per connection means writes are ordered and never interleave two
477/// half-written frames, which is exactly the bug an unsynchronised broadcaster
478/// would produce.
479async fn write_loop(
480    mut writer: Box<dyn AsyncWrite + Send + Unpin>,
481    mut queue: mpsc::Receiver<Message>,
482) {
483    while let Some(message) = queue.recv().await {
484        let closing = matches!(message, Message::Close(_));
485        let bytes = message.into_frame().encode();
486
487        if writer.write_all(&bytes).await.is_err() || writer.flush().await.is_err() {
488            break;
489        }
490        if closing {
491            // Nothing may follow a close frame.
492            break;
493        }
494    }
495
496    // Closing the queue makes every surviving `Sender` clone — the broadcaster
497    // holds some — report the client as gone instead of quietly filling up.
498    queue.close();
499    let _ = writer.shutdown().await;
500}
501
502/// Whether the text received so far could still become valid UTF-8, advancing
503/// `checked` past what has been confirmed.
504///
505/// `false` only when the bytes are *definitely* invalid — a byte that can never
506/// appear, an overlong form, a continuation with no start. An unfinished
507/// character at the end is **not** a failure: the next fragment may complete
508/// it, because a character is allowed to be cut anywhere. `from_utf8`
509/// distinguishes the two itself — `error_len()` is `None` exactly when the
510/// input ran out in the middle of a sequence.
511///
512/// Only the bytes after `checked` are examined, so a message of any number of
513/// fragments is validated in time linear in its length.
514fn text_so_far_is_valid(bytes: &[u8], checked: &mut usize) -> bool {
515    match std::str::from_utf8(&bytes[*checked..]) {
516        Ok(_) => {
517            *checked = bytes.len();
518            true
519        }
520        Err(error) if error.error_len().is_none() => {
521            // Valid up to an unfinished character; remember where it starts so
522            // the next fragment is checked from there.
523            *checked += error.valid_up_to();
524            true
525        }
526        Err(_) => false,
527    }
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533    use tokio::io::DuplexStream;
534
535    /// A fixed mask, so the bytes a test writes are reproducible.
536    const MASK: [u8; 4] = [0xa1, 0xb2, 0xc3, 0xd4];
537
538    /// The other end of an in-memory socket: no ports, no sockets, no sharing
539    /// between tests.
540    struct Peer {
541        stream: DuplexStream,
542        buffer: Vec<u8>,
543    }
544
545    impl Peer {
546        async fn send(&mut self, frame: Frame) {
547            self.stream.write_all(&frame.encode()).await.unwrap();
548        }
549
550        /// A client frame: always masked.
551        async fn send_masked(&mut self, opcode: OpCode, payload: &[u8]) {
552            self.send(Frame::client(opcode, payload.to_vec(), MASK)).await;
553        }
554
555        async fn send_fragment(&mut self, opcode: OpCode, payload: &[u8], fin: bool) {
556            self.send(Frame { fin, opcode, mask: Some(MASK), payload: payload.to_vec() }).await;
557        }
558
559        /// The next frame the server wrote. Panics if the socket ends first,
560        /// which in a test is the failure we want to see.
561        async fn recv(&mut self) -> Frame {
562            loop {
563                if let Some((frame, used)) =
564                    Frame::decode(&self.buffer, Role::Client, 1 << 20).unwrap()
565                {
566                    self.buffer.drain(..used);
567                    return frame;
568                }
569                let mut chunk = [0u8; 4096];
570                let read = self.stream.read(&mut chunk).await.unwrap();
571                assert!(read > 0, "the server closed without sending an expected frame");
572                self.buffer.extend_from_slice(&chunk[..read]);
573            }
574        }
575
576        async fn hang_up(&mut self) {
577            self.stream.shutdown().await.unwrap();
578        }
579    }
580
581    fn pair(config: WebSocketConfig) -> (WebSocket, Peer) {
582        let (client, server) = tokio::io::duplex(64 * 1024);
583        let (reader, writer) = tokio::io::split(server);
584        let upgraded = Upgraded {
585            reader: Box::new(reader),
586            writer: Box::new(writer),
587            buffered: Vec::new(),
588        };
589        (WebSocket::new(upgraded, config), Peer { stream: client, buffer: Vec::new() })
590    }
591
592    /// The close frame the server sent, whatever else it sent first.
593    async fn close_from_server(peer: &mut Peer) -> CloseFrame {
594        loop {
595            let frame = peer.recv().await;
596            if frame.opcode == OpCode::Close {
597                return CloseFrame::parse(&frame.payload).unwrap().unwrap();
598            }
599        }
600    }
601
602    #[tokio::test]
603    async fn echoes_a_message_the_client_sent() {
604        let (mut socket, mut peer) = pair(WebSocketConfig::default());
605
606        peer.send_masked(OpCode::Text, b"hello").await;
607        let message = socket.recv().await.unwrap();
608        assert_eq!(message, Message::text("hello"));
609
610        socket.send(message).await.unwrap();
611        let echoed = peer.recv().await;
612        assert_eq!(echoed.opcode, OpCode::Text);
613        assert_eq!(echoed.mask, None, "a server must never mask");
614        assert_eq!(echoed.payload, b"hello");
615    }
616
617    #[tokio::test]
618    async fn bytes_already_buffered_by_the_http_server_are_not_lost() {
619        let (client, server) = tokio::io::duplex(1024);
620        let (reader, writer) = tokio::io::split(server);
621        // The first frame arrived glued to the handshake request.
622        let early = Frame::client(OpCode::Text, b"first".to_vec(), MASK).encode();
623
624        let mut socket = WebSocket::new(
625            Upgraded { reader: Box::new(reader), writer: Box::new(writer), buffered: early },
626            WebSocketConfig::default(),
627        );
628
629        assert_eq!(socket.recv().await.unwrap(), Message::text("first"));
630        drop(client);
631    }
632
633    #[tokio::test]
634    async fn answers_a_ping_with_a_pong_carrying_the_same_payload() {
635        let (mut socket, mut peer) = pair(WebSocketConfig::default());
636
637        peer.send_masked(OpCode::Ping, b"keepalive").await;
638        peer.send_masked(OpCode::Text, b"after").await;
639
640        // The ping never surfaces; the message behind it does.
641        assert_eq!(socket.recv().await.unwrap(), Message::text("after"));
642
643        let pong = peer.recv().await;
644        assert_eq!(pong.opcode, OpCode::Pong);
645        assert_eq!(pong.payload, b"keepalive");
646    }
647
648    #[tokio::test]
649    async fn reassembles_a_fragmented_message_around_an_interleaved_ping() {
650        let (mut socket, mut peer) = pair(WebSocketConfig::default());
651
652        // A control frame between two fragments is legal, and is the classic
653        // place a hand-written implementation corrupts the message.
654        peer.send_fragment(OpCode::Text, b"Hel", false).await;
655        peer.send_masked(OpCode::Ping, b"mid").await;
656        peer.send_fragment(OpCode::Continuation, b"lo ", false).await;
657        peer.send_fragment(OpCode::Continuation, b"there", true).await;
658
659        assert_eq!(socket.recv().await.unwrap(), Message::text("Hello there"));
660
661        let pong = peer.recv().await;
662        assert_eq!(pong.opcode, OpCode::Pong);
663        assert_eq!(pong.payload, b"mid");
664    }
665
666    #[tokio::test]
667    async fn a_continuation_with_nothing_to_continue_closes_with_1002() {
668        let (mut socket, mut peer) = pair(WebSocketConfig::default());
669
670        peer.send_fragment(OpCode::Continuation, b"orphan", true).await;
671
672        assert!(socket.recv().await.is_none());
673        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::PROTOCOL_ERROR);
674    }
675
676    #[tokio::test]
677    async fn a_text_frame_that_is_not_utf8_closes_with_1007() {
678        let (mut socket, mut peer) = pair(WebSocketConfig::default());
679
680        peer.send_masked(OpCode::Text, &[0xf0, 0x28, 0x8c, 0x28]).await;
681
682        assert!(socket.recv().await.is_none());
683        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::INVALID_PAYLOAD);
684        assert_eq!(socket.close_frame().unwrap().code, CloseCode::INVALID_PAYLOAD);
685    }
686
687    /// The pointer is the whole point of the helper: it advances over what is
688    /// confirmed and stops at the start of an unfinished character, so the next
689    /// fragment re-examines at most three bytes rather than the whole message.
690    #[test]
691    fn the_checked_pointer_stops_at_an_unfinished_character_and_moves_on_when_it_is_finished() {
692        let mut checked = 0;
693
694        // "caf" is whole; the C3 is the start of "é" and must be looked at again.
695        let mut bytes = vec![b'c', b'a', b'f', 0xc3];
696        assert!(text_so_far_is_valid(&bytes, &mut checked));
697        assert_eq!(checked, 3, "the unfinished character was counted as checked");
698
699        // Its second byte arrives; now it is whole and everything is confirmed.
700        bytes.push(0xa9);
701        assert!(text_so_far_is_valid(&bytes, &mut checked));
702        assert_eq!(checked, 5);
703
704        // Bytes already confirmed are never examined again: corrupt one behind
705        // the pointer and the check does not notice. (Nothing real can do this —
706        // the buffer is append-only — but it pins that the work is incremental.)
707        bytes[0] = 0xff;
708        bytes.push(b'!');
709        assert!(text_so_far_is_valid(&bytes, &mut checked));
710        assert_eq!(checked, 6);
711    }
712
713    #[test]
714    fn bytes_that_can_never_become_valid_utf8_are_refused_at_once() {
715        for bad in [&[0xffu8][..], &[0xc0, 0xaf], &[0x80], &[0xed, 0xa0, 0x80], &[0xf4, 0x90, 0x80, 0x80]] {
716            let mut checked = 0;
717            assert!(!text_so_far_is_valid(bad, &mut checked), "{bad:02x?} was accepted");
718        }
719        // A start byte whose only continuations would be overlong is already
720        // doomed after one more byte, not after the message.
721        let mut checked = 0;
722        assert!(text_so_far_is_valid(&[0xe0], &mut checked));
723        assert!(!text_so_far_is_valid(&[0xe0, 0x80], &mut checked));
724    }
725
726    /// **A bad fragment ends the message at once, not when it completes.**
727    ///
728    /// RFC 6455 lets a server wait for the final fragment before validating, so
729    /// this used to be allowed — and Autobahn cases 6.4.1–6.4.4 mark it
730    /// NON-STRICT. Waiting is the worse choice anyway: a peer sending an
731    /// invalid first fragment and then a megabyte of continuations makes the
732    /// server buffer all of it for a message it can already know is doomed.
733    ///
734    /// The final fragment is deliberately never sent. If the server only
735    /// closed after seeing it, `recv` would be stuck waiting and this would
736    /// hang rather than pass — which is how it proves the close is early.
737    #[tokio::test]
738    async fn an_invalid_first_fragment_closes_with_1007_before_the_message_ends() {
739        let (mut socket, mut peer) = pair(WebSocketConfig::default());
740
741        // 0xff can never appear in UTF-8.
742        peer.send_fragment(OpCode::Text, &[b'o', b'k', 0xff], false).await;
743
744        let closed = tokio::time::timeout(Duration::from_secs(2), socket.recv())
745            .await
746            .expect("the server waited for a final fragment that was never coming");
747        assert!(closed.is_none());
748        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::INVALID_PAYLOAD);
749    }
750
751    #[tokio::test]
752    async fn an_invalid_later_fragment_closes_with_1007_when_it_arrives() {
753        let (mut socket, mut peer) = pair(WebSocketConfig::default());
754
755        peer.send_fragment(OpCode::Text, b"fine so far ", false).await;
756        peer.send_fragment(OpCode::Continuation, b"still fine", false).await;
757        peer.send_fragment(OpCode::Continuation, &[0xc0, 0xaf], false).await; // overlong
758
759        let closed = tokio::time::timeout(Duration::from_secs(2), socket.recv())
760            .await
761            .expect("the server waited for a final fragment that was never coming");
762        assert!(closed.is_none());
763        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::INVALID_PAYLOAD);
764    }
765
766    /// The reason this cannot be "validate each fragment on its own": a
767    /// character is allowed to be cut anywhere, and the pieces are not valid
768    /// UTF-8 until they are put back together. Rejecting these would break
769    /// every client that streams text in arbitrary chunks.
770    #[tokio::test]
771    async fn a_character_split_across_fragments_is_not_an_error() {
772        let (mut socket, mut peer) = pair(WebSocketConfig::default());
773
774        // "é" is C3 A9; "😀" is F0 9F 98 80, cut into three pieces.
775        peer.send_fragment(OpCode::Text, &[b'c', b'a', b'f', 0xc3], false).await;
776        peer.send_fragment(OpCode::Continuation, &[0xa9, b' ', 0xf0], false).await;
777        peer.send_fragment(OpCode::Continuation, &[0x9f, 0x98], false).await;
778        peer.send_fragment(OpCode::Continuation, &[0x80], true).await;
779
780        match socket.recv().await {
781            Some(Message::Text(text)) => assert_eq!(text, "café 😀"),
782            other => panic!("a valid message cut mid-character was refused: {other:?}"),
783        }
784    }
785
786    /// Cut mid-character and never finished: not fail-fast territory — the
787    /// bytes so far are a valid *prefix* — but the final frame must still be
788    /// refused when the message ends with the character incomplete.
789    #[tokio::test]
790    async fn a_message_that_ends_mid_character_is_still_refused() {
791        let (mut socket, mut peer) = pair(WebSocketConfig::default());
792
793        peer.send_fragment(OpCode::Text, b"caf", false).await;
794        peer.send_fragment(OpCode::Continuation, &[0xc3], true).await;
795
796        assert!(socket.recv().await.is_none());
797        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::INVALID_PAYLOAD);
798    }
799
800    /// Binary is bytes. Only text is validated, so a fragmented binary message
801    /// full of 0xff arrives intact.
802    #[tokio::test]
803    async fn a_fragmented_binary_message_is_never_validated_as_text() {
804        let (mut socket, mut peer) = pair(WebSocketConfig::default());
805
806        peer.send_fragment(OpCode::Binary, &[0xff, 0xfe], false).await;
807        peer.send_fragment(OpCode::Continuation, &[0xc0, 0xaf], true).await;
808
809        match socket.recv().await {
810            Some(Message::Binary(bytes)) => assert_eq!(bytes, vec![0xff, 0xfe, 0xc0, 0xaf]),
811            other => panic!("binary was validated as if it were text: {other:?}"),
812        }
813    }
814
815    #[tokio::test]
816    async fn an_unmasked_client_frame_closes_with_1002() {
817        let (mut socket, mut peer) = pair(WebSocketConfig::default());
818
819        // A server frame sent by a client: unmasked, and therefore illegal.
820        peer.send(Frame::server(OpCode::Text, b"naked".to_vec())).await;
821
822        assert!(socket.recv().await.is_none());
823        let close = close_from_server(&mut peer).await;
824        assert_eq!(close.code, CloseCode::PROTOCOL_ERROR);
825        assert!(close.reason.contains("must be masked"), "{}", close.reason);
826    }
827
828    #[tokio::test]
829    async fn an_oversize_frame_closes_with_1009() {
830        let config = WebSocketConfig { max_frame_size: 64, ..WebSocketConfig::default() };
831        let (mut socket, mut peer) = pair(config);
832
833        peer.send_masked(OpCode::Binary, &[7u8; 200]).await;
834
835        assert!(socket.recv().await.is_none());
836        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::TOO_LARGE);
837    }
838
839    #[tokio::test]
840    async fn fragments_that_add_up_to_more_than_the_limit_close_with_1009() {
841        let config = WebSocketConfig {
842            max_message_size: 8,
843            max_frame_size: 1024,
844            ..WebSocketConfig::default()
845        };
846        let (mut socket, mut peer) = pair(config);
847
848        // Each fragment is small; together they are over the limit.
849        peer.send_fragment(OpCode::Binary, &[0u8; 5], false).await;
850        peer.send_fragment(OpCode::Continuation, &[0u8; 5], true).await;
851
852        assert!(socket.recv().await.is_none());
853        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::TOO_LARGE);
854    }
855
856    #[tokio::test]
857    async fn a_close_from_the_peer_is_echoed_back() {
858        let (mut socket, mut peer) = pair(WebSocketConfig::default());
859
860        peer.send(Frame::client(
861            OpCode::Close,
862            CloseFrame::new(CloseCode::NORMAL, "bye").to_payload(),
863            MASK,
864        ))
865        .await;
866
867        assert!(socket.recv().await.is_none());
868
869        let echo = close_from_server(&mut peer).await;
870        assert_eq!(echo.code, CloseCode::NORMAL);
871        assert_eq!(echo.reason, "bye");
872        // The socket remembers how it ended, for the application's log line.
873        assert_eq!(socket.close_frame().unwrap().code, CloseCode::NORMAL);
874    }
875
876    #[tokio::test]
877    async fn closing_waits_for_the_peers_half_of_the_handshake() {
878        let (mut socket, mut peer) = pair(WebSocketConfig::default());
879
880        let reply = async {
881            let frame = peer.recv().await;
882            assert_eq!(frame.opcode, OpCode::Close);
883            assert_eq!(
884                CloseFrame::parse(&frame.payload).unwrap().unwrap().code,
885                CloseCode::GOING_AWAY
886            );
887            peer.send(Frame::client(
888                OpCode::Close,
889                CloseFrame::new(CloseCode(4001), "acknowledged").to_payload(),
890                MASK,
891            ))
892            .await;
893        };
894
895        tokio::join!(socket.close(CloseCode::GOING_AWAY, "restarting"), reply);
896
897        assert!(socket.is_closed());
898        // The peer's status wins: it is the more informative of the two.
899        assert_eq!(socket.close_frame().unwrap().code, CloseCode(4001));
900        assert!(socket.send(Message::text("late")).await.is_err());
901    }
902
903    #[tokio::test(start_paused = true)]
904    async fn an_idle_socket_is_pinged_and_then_closed() {
905        let config = WebSocketConfig {
906            idle_timeout: Some(Duration::from_secs(30)),
907            ..WebSocketConfig::default()
908        };
909        let (mut socket, mut peer) = pair(config);
910
911        let reading = tokio::spawn(async move {
912            let message = socket.recv().await;
913            (message, socket.close_frame().cloned())
914        });
915
916        // Nothing is sent, so the first quiet period produces a ping…
917        let ping = peer.recv().await;
918        assert_eq!(ping.opcode, OpCode::Ping);
919
920        // …and the second, with no pong in between, ends the connection.
921        assert_eq!(close_from_server(&mut peer).await.code, CloseCode::GOING_AWAY);
922
923        let (message, close) = reading.await.unwrap();
924        assert!(message.is_none());
925        assert_eq!(close.unwrap().code, CloseCode::GOING_AWAY);
926    }
927
928    #[tokio::test(start_paused = true)]
929    async fn a_pong_keeps_an_otherwise_idle_socket_open() {
930        let config = WebSocketConfig {
931            idle_timeout: Some(Duration::from_secs(30)),
932            ..WebSocketConfig::default()
933        };
934        let (mut socket, mut peer) = pair(config);
935
936        let reading = tokio::spawn(async move { socket.recv().await });
937
938        let ping = peer.recv().await;
939        assert_eq!(ping.opcode, OpCode::Ping);
940        peer.send_masked(OpCode::Pong, &ping.payload).await;
941        peer.send_masked(OpCode::Text, b"still here").await;
942
943        assert_eq!(reading.await.unwrap(), Some(Message::text("still here")));
944    }
945
946    #[tokio::test]
947    async fn a_socket_that_just_ends_reports_an_abnormal_close() {
948        let (mut socket, mut peer) = pair(WebSocketConfig::default());
949
950        peer.hang_up().await;
951
952        assert!(socket.recv().await.is_none());
953        assert_eq!(socket.close_frame().unwrap().code, CloseCode::ABNORMAL);
954    }
955
956    #[tokio::test]
957    async fn a_full_send_queue_is_reported_rather_than_waited_on() {
958        let (sender, mut receiver) = channel(2);
959
960        assert!(sender.try_send(Message::text("one")).is_ok());
961        assert!(sender.try_send(Message::text("two")).is_ok());
962        // The third has nowhere to go, and waiting for room is exactly what
963        // broadcasting must not do.
964        assert!(matches!(sender.try_send(Message::text("three")), Err(WsError::Full)));
965
966        assert_eq!(receiver.recv().await, Some(Message::text("one")));
967        assert!(sender.try_send(Message::text("three")).is_ok());
968    }
969
970    #[tokio::test]
971    async fn senders_report_closed_once_the_socket_is_gone() {
972        let (sender, receiver) = channel(2);
973        drop(receiver);
974
975        assert!(sender.is_closed());
976        assert!(matches!(sender.try_send(Message::text("x")), Err(WsError::Closed)));
977    }
978
979    #[test]
980    fn limits_come_from_the_application_configuration() {
981        let config = Config::new();
982        config.set("websocket.max_message_size", 4096);
983        config.set("websocket.idle_timeout", 0);
984        config.set("websocket.send_queue", 8);
985
986        let settings = WebSocketConfig::from_config(&config);
987
988        assert_eq!(settings.max_message_size, 4096);
989        assert_eq!(settings.send_queue, 8);
990        // Zero seconds means "never ping", which is what a test harness wants.
991        assert_eq!(settings.idle_timeout, None);
992        // Anything unset keeps the built-in default.
993        assert_eq!(settings.max_frame_size, WebSocketConfig::default().max_frame_size);
994    }
995}