Skip to main content

polyoxide_ws/
bare.rs

1//! The bare tier: one connection as a `Stream` of frames (W11), with the
2//! server's close captured (W9).
3
4use std::{
5    collections::VecDeque,
6    pin::Pin,
7    sync::Arc,
8    task::{Context, Poll},
9};
10
11use futures_util::{SinkExt, Stream, StreamExt};
12use polyoxide_rt::{default_runtime, Instant, Runtime};
13
14use crate::{request::Requests, tungstenite::Message, BoxSocket, WsError};
15
16/// Which frames a [`Connection`] yields.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Mode {
19    /// Text and Binary frames only. Pings, pongs and raw frames are read and
20    /// skipped, the server's pings still answered, and a server close ends
21    /// the stream.
22    Data,
23    /// Every frame, pings, pongs and the server's close included, for a
24    /// caller whose liveness rule counts them. A close is the last item.
25    All,
26}
27
28impl Mode {
29    /// Whether a frame other than a close is yielded in this mode.
30    fn yields(self, message: &Message) -> bool {
31        match self {
32            Self::Data => matches!(message, Message::Text(_) | Message::Binary(_)),
33            Self::All => true,
34        }
35    }
36}
37
38/// One open connection, as a `Stream` of its frames.
39///
40/// Yields `Ok` frames as [`Mode`] selects, and ends after the server's close,
41/// after the end of the stream, or after the one `Err` a failed read yields.
42/// Frames kept while a [request](Self::request) awaited its answer come out
43/// first, in the order they arrived. The server's pings are answered as the
44/// stream is read.
45///
46/// The server's close is answered: the RFC 6455 close reply is flushed before
47/// the stream yields its last item, so it goes out even when the caller never
48/// reads again (DRIFT R3). Once the stream has ended on the close,
49/// [`closed`](Self::closed) says with what code and reason, as a
50/// [`Kind::Closed`](crate::Kind::Closed) error.
51///
52/// The basis is one socket crate's bare-tier `poll_next` (`src/client.rs`)
53/// and its frame classifier, with the queue two other socket crates' former
54/// bare tiers drained first and the former close capture of one of those two
55/// (`record_close` and `closed_error`); all five socket crates read through
56/// this type since Stories 4.6 to 4.10. Two divergence rows stay with each
57/// venue: what counts as liveness (D5), which [`Mode::All`] and
58/// [`last_inbound`](Self::last_inbound) let a venue decide, and what a Binary
59/// frame means (D12): one venue decodes it as text, and the others skip it.
60pub struct Connection {
61    socket: BoxSocket,
62    mode: Mode,
63    /// The runtime the connection's clock reads, pacing and timeouts use.
64    pub(crate) runtime: Arc<dyn Runtime>,
65    buffered: VecDeque<Message>,
66    last_inbound: Instant,
67    /// The server's close code and reason, once the stream ended on them.
68    close: Option<(Option<u16>, String)>,
69    /// Whether tungstenite holds a close reply not yet flushed.
70    replying: bool,
71    finished: bool,
72    pub(crate) requests: Requests,
73}
74
75impl std::fmt::Debug for Connection {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.debug_struct("Connection")
78            .field("mode", &self.mode)
79            .field("buffered", &self.buffered.len())
80            .field("close", &self.close)
81            .field("finished", &self.finished)
82            .finish_non_exhaustive()
83    }
84}
85
86impl Connection {
87    /// Read `socket` in `mode`, on the target's default runtime.
88    pub fn new(socket: BoxSocket, mode: Mode) -> Self {
89        Self::with_runtime(socket, mode, default_runtime())
90    }
91
92    /// Read `socket` in `mode`, on `runtime`: its clock stamps the frames, and
93    /// it paces and bounds the connection's requests.
94    pub fn with_runtime(socket: BoxSocket, mode: Mode, runtime: Arc<dyn Runtime>) -> Self {
95        Self {
96            socket,
97            mode,
98            last_inbound: runtime.now(),
99            runtime,
100            buffered: VecDeque::new(),
101            close: None,
102            replying: false,
103            finished: false,
104            requests: Requests::default(),
105        }
106    }
107
108    /// The runtime this connection runs on, for a protocol's own timers.
109    pub fn runtime(&self) -> &Arc<dyn Runtime> {
110        &self.runtime
111    }
112
113    /// Which frames this connection yields.
114    pub fn mode(&self) -> Mode {
115        self.mode
116    }
117
118    /// When the last frame of any kind arrived, pings, pongs and frames read
119    /// while awaiting an answer included; when the connection was made, if
120    /// none has.
121    pub fn last_inbound(&self) -> Instant {
122        self.last_inbound
123    }
124
125    /// The server's close, once the stream has ended on it: a
126    /// [`Kind::Closed`](crate::Kind::Closed) with the close frame's code and
127    /// reason, or with no code and an empty reason when the stream ended
128    /// without a close frame. A close frame without a code reads the same.
129    /// `None` while the connection is open, and after a failed read, whose
130    /// error the stream yielded.
131    pub fn closed(&self) -> Option<WsError> {
132        self.close
133            .as_ref()
134            .map(|(code, reason)| WsError::closed(*code, reason.as_str()))
135    }
136
137    /// Send one frame and flush it.
138    pub async fn send(&mut self, message: Message) -> Result<(), WsError> {
139        self.socket.send(message).await?;
140        Ok(())
141    }
142
143    /// Close the connection: send a close frame and flush it. On a
144    /// connection the server already closed, flush the reply if it has not
145    /// gone out yet; there is nothing else to send. A connection whose read
146    /// failed is already gone, so closing it does nothing.
147    pub async fn close(&mut self) -> Result<(), WsError> {
148        if self.close.is_some() {
149            self.flush_reply().await;
150            return Ok(());
151        }
152        if self.finished {
153            return Ok(());
154        }
155        // Sends the close frame and flushes it.
156        self.socket.send(Message::Close(None)).await?;
157        Ok(())
158    }
159
160    /// Note the server's close frame, or the end of the stream without one.
161    /// tungstenite queues the reply to a close frame as it reads it, and sends
162    /// it on the next read or flush.
163    pub(crate) fn record_close(&mut self, frame: Option<&Message>) {
164        self.replying = matches!(frame, Some(Message::Close(_)));
165        self.close = Some(match frame {
166            Some(Message::Close(Some(frame))) => {
167                (Some(u16::from(frame.code)), frame.reason.to_string())
168            }
169            _ => (None, String::new()),
170        });
171        self.finished = true;
172        tracing::debug!(close = ?self.close, "the server closed the WebSocket connection");
173    }
174
175    /// Send the queued close reply, if there is one.
176    pub(crate) async fn flush_reply(&mut self) {
177        if self.replying {
178            if let Err(err) = SinkExt::<Message>::flush(&mut self.socket).await {
179                tracing::debug!(%err, "the close reply could not be sent");
180            }
181            self.replying = false;
182        }
183    }
184
185    /// Keep a frame for the stream to yield later, if its mode yields it.
186    pub(crate) fn keep(&mut self, message: Message) {
187        if self.mode.yields(&message) {
188            self.buffered.push_back(message);
189        }
190    }
191
192    /// The underlying socket, for the request module's reads.
193    pub(crate) fn socket(&mut self) -> &mut BoxSocket {
194        &mut self.socket
195    }
196
197    /// Note that a frame arrived now.
198    pub(crate) fn touch(&mut self) {
199        self.last_inbound = self.runtime.now();
200    }
201
202    /// End the stream after a failed read.
203    pub(crate) fn fail(&mut self) {
204        self.finished = true;
205    }
206
207    /// Whether the stream has ended.
208    pub(crate) fn is_finished(&self) -> bool {
209        self.finished
210    }
211
212    /// Whether frames a request kept are waiting to be yielded.
213    pub(crate) fn has_kept(&self) -> bool {
214        !self.buffered.is_empty()
215    }
216}
217
218impl Stream for Connection {
219    type Item = Result<Message, WsError>;
220
221    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
222        let this = self.get_mut();
223        loop {
224            // RFC 6455 requires the close reply, and this stream may never be
225            // read again once it has yielded its last item.
226            if this.replying {
227                match SinkExt::<Message>::poll_flush_unpin(&mut this.socket, cx) {
228                    Poll::Pending => return Poll::Pending,
229                    Poll::Ready(Err(err)) => {
230                        tracing::debug!(%err, "the close reply could not be sent");
231                    }
232                    Poll::Ready(Ok(())) => {}
233                }
234                this.replying = false;
235            }
236            if let Some(kept) = this.buffered.pop_front() {
237                return Poll::Ready(Some(Ok(kept)));
238            }
239            if this.finished {
240                return Poll::Ready(None);
241            }
242            let message = match this.socket.poll_next_unpin(cx) {
243                Poll::Ready(Some(Ok(message))) => message,
244                Poll::Ready(Some(Err(err))) => {
245                    this.fail();
246                    return Poll::Ready(Some(Err(err.into())));
247                }
248                Poll::Ready(None) => {
249                    this.record_close(None);
250                    return Poll::Ready(None);
251                }
252                Poll::Pending => return Poll::Pending,
253            };
254            this.touch();
255            match message {
256                Message::Close(_) => {
257                    this.record_close(Some(&message));
258                    this.keep(message);
259                }
260                Message::Ping(_) if this.mode == Mode::All => {
261                    // tungstenite has queued the pong, and sends it on the
262                    // next read or flush; the caller may not read again soon.
263                    let _ = SinkExt::<Message>::poll_flush_unpin(&mut this.socket, cx);
264                    return Poll::Ready(Some(Ok(message)));
265                }
266                message if this.mode.yields(&message) => return Poll::Ready(Some(Ok(message))),
267                // Reading again is what sends the pong for a ping just read.
268                _ => continue,
269            }
270        }
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use std::time::Duration;
277
278    use tokio_tungstenite::tungstenite::protocol::{frame::coding::CloseCode, CloseFrame};
279
280    use super::*;
281    use crate::{
282        connect,
283        test_server::{Script, ScriptedServer},
284        Kind, DEFAULT_CONNECT_TIMEOUT,
285    };
286
287    const WINDOW: Duration = Duration::from_secs(3);
288
289    async fn open(server: &ScriptedServer, mode: Mode) -> Connection {
290        let socket = connect(server.url.as_str(), DEFAULT_CONNECT_TIMEOUT)
291            .await
292            .expect("connect");
293        Connection::new(socket, mode)
294    }
295
296    async fn next(connection: &mut Connection) -> Option<Result<Message, WsError>> {
297        tokio::time::timeout(WINDOW, connection.next())
298            .await
299            .expect("an item within the window")
300    }
301
302    fn frames() -> Vec<Message> {
303        vec![
304            Message::Ping(b"p".to_vec().into()),
305            Message::Pong(b"q".to_vec().into()),
306            Message::Binary(vec![1, 2, 3].into()),
307            Message::Text("data".into()),
308        ]
309    }
310
311    #[tokio::test]
312    async fn data_mode_yields_text_and_binary_and_skips_control_frames() {
313        let server = ScriptedServer::start(vec![Script::messages(frames())]).await;
314        let mut connection = open(&server, Mode::Data).await;
315        assert_eq!(
316            next(&mut connection).await.unwrap().unwrap(),
317            Message::Binary(vec![1, 2, 3].into())
318        );
319        assert_eq!(
320            next(&mut connection).await.unwrap().unwrap(),
321            Message::Text("data".into())
322        );
323    }
324
325    #[tokio::test]
326    async fn all_mode_yields_every_frame() {
327        let server = ScriptedServer::start(vec![Script::messages(frames())]).await;
328        let mut connection = open(&server, Mode::All).await;
329        let mut seen = Vec::new();
330        for _ in 0..4 {
331            seen.push(next(&mut connection).await.unwrap().unwrap());
332        }
333        assert_eq!(seen, frames());
334    }
335
336    #[tokio::test]
337    async fn a_server_ping_is_answered_in_either_mode() {
338        // Data mode answers by reading on; all mode yields the ping and still
339        // sends the pong, though the caller never reads again.
340        for mode in [Mode::Data, Mode::All] {
341            let server = ScriptedServer::start(vec![Script::messages(vec![Message::Ping(
342                b"p1".to_vec().into(),
343            )])])
344            .await;
345            let mut connection = open(&server, mode).await;
346            let reader = match mode {
347                Mode::Data => {
348                    tokio::spawn(async move { while connection.next().await.is_some() {} })
349                }
350                Mode::All => {
351                    assert_eq!(
352                        next(&mut connection).await.unwrap().unwrap(),
353                        Message::Ping(b"p1".to_vec().into())
354                    );
355                    // Held, never read again.
356                    tokio::spawn(async move {
357                        let _held = connection;
358                        std::future::pending::<()>().await;
359                    })
360                }
361            };
362            server
363                .wait_for("a pong", |s| s.pongs() == [b"p1".to_vec()])
364                .await;
365            reader.abort();
366        }
367    }
368
369    #[tokio::test]
370    async fn a_server_close_ends_the_stream_with_its_code_and_reason() {
371        for (code, reason) in [(1011, "overloaded"), (1008, "policy")] {
372            let server =
373                ScriptedServer::start(vec![Script::close_at_once().close_with(code, reason)]).await;
374            let mut connection = open(&server, Mode::Data).await;
375            assert!(connection.closed().is_none(), "open, so not closed yet");
376            assert!(next(&mut connection).await.is_none());
377            assert_eq!(
378                connection.closed().expect("closed").kind(),
379                Kind::Closed {
380                    code: Some(code),
381                    reason
382                }
383            );
384            assert!(next(&mut connection).await.is_none(), "it stays ended");
385        }
386    }
387
388    #[tokio::test]
389    async fn all_mode_yields_the_close_frame_last() {
390        let server =
391            ScriptedServer::start(vec![Script::close_at_once().close_with(1001, "bye")]).await;
392        let mut connection = open(&server, Mode::All).await;
393        assert_eq!(
394            next(&mut connection).await.unwrap().unwrap(),
395            Message::Close(Some(CloseFrame {
396                code: CloseCode::Away,
397                reason: "bye".into()
398            }))
399        );
400        assert!(next(&mut connection).await.is_none());
401        assert_eq!(
402            connection.closed().unwrap().kind(),
403            Kind::Closed {
404                code: Some(1001),
405                reason: "bye"
406            }
407        );
408    }
409
410    #[tokio::test]
411    async fn a_server_close_is_answered_though_the_stream_is_never_read_again() {
412        // RFC 6455's close reply (DRIFT R3). tungstenite queues it as it reads
413        // the close and sends it only on a later read or flush, which a
414        // stream that has ended never makes.
415        for mode in [Mode::Data, Mode::All] {
416            let server = ScriptedServer::start(vec![
417                Script::close_at_once().close_with(1011, "pong_timeout")
418            ])
419            .await;
420            let mut connection = open(&server, mode).await;
421            let last = next(&mut connection).await;
422            match mode {
423                Mode::Data => assert!(last.is_none(), "{last:?}"),
424                Mode::All => assert!(matches!(last, Some(Ok(Message::Close(_)))), "{last:?}"),
425            }
426            assert_eq!(
427                connection.closed().unwrap().kind(),
428                Kind::Closed {
429                    code: Some(1011),
430                    reason: "pong_timeout"
431                }
432            );
433            // Held open and never read again, as a caller that saw the end
434            // would leave it.
435            server
436                .wait_for("the close reply", |s| s.close_reply_count() == 1)
437                .await;
438            drop(connection);
439        }
440    }
441
442    #[tokio::test]
443    async fn close_after_the_server_closed_succeeds() {
444        let server = ScriptedServer::start(vec![Script::close_at_once()]).await;
445        let mut connection = open(&server, Mode::Data).await;
446        assert!(next(&mut connection).await.is_none());
447        connection.close().await.expect("close");
448        server
449            .wait_for("the close reply", |s| s.close_reply_count() == 1)
450            .await;
451    }
452
453    #[tokio::test]
454    async fn a_failed_read_is_yielded_once_and_ends_the_stream() {
455        // The server drops the TCP connection without a closing handshake.
456        let server = ScriptedServer::start(vec![Script::hold().then_cut()]).await;
457        let mut connection = open(&server, Mode::Data).await;
458        let err = next(&mut connection).await.unwrap().unwrap_err();
459        assert!(matches!(err.kind(), Kind::Protocol | Kind::Io), "{err:?}");
460        assert!(next(&mut connection).await.is_none());
461        assert!(connection.closed().is_none(), "an error is not a close");
462    }
463
464    #[tokio::test]
465    async fn close_after_a_failed_read_does_nothing() {
466        let server = ScriptedServer::start(vec![Script::hold().then_cut()]).await;
467        let mut connection = open(&server, Mode::Data).await;
468        assert!(next(&mut connection).await.unwrap().is_err());
469        connection
470            .close()
471            .await
472            .expect("closing a connection that is already gone succeeds");
473    }
474
475    #[tokio::test]
476    async fn close_sends_a_close_frame_the_server_receives() {
477        let server = ScriptedServer::start(vec![Script::hold()]).await;
478        let mut connection = open(&server, Mode::Data).await;
479        connection.close().await.expect("close");
480        server
481            .wait_for("a close frame", |s| s.close_count() == 1)
482            .await;
483    }
484
485    #[tokio::test]
486    async fn every_frame_refreshes_last_inbound() {
487        let server = ScriptedServer::start(vec![Script::messages(vec![Message::Pong(
488            Vec::new().into(),
489        )])])
490        .await;
491        let mut connection = open(&server, Mode::Data).await;
492        let opened = connection.last_inbound();
493        // The pong is skipped, so the read waits; it must still count.
494        let _ = tokio::time::timeout(Duration::from_millis(200), connection.next()).await;
495        assert!(connection.last_inbound() > opened);
496    }
497
498    #[test]
499    fn a_connection_can_move_between_tasks() {
500        fn assert_send_unpin<T: Send + Unpin>() {}
501        assert_send_unpin::<Connection>();
502    }
503}