1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Mode {
19 Data,
23 All,
26}
27
28impl Mode {
29 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
38pub struct Connection {
61 socket: BoxSocket,
62 mode: Mode,
63 pub(crate) runtime: Arc<dyn Runtime>,
65 buffered: VecDeque<Message>,
66 last_inbound: Instant,
67 close: Option<(Option<u16>, String)>,
69 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 pub fn new(socket: BoxSocket, mode: Mode) -> Self {
89 Self::with_runtime(socket, mode, default_runtime())
90 }
91
92 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 pub fn runtime(&self) -> &Arc<dyn Runtime> {
110 &self.runtime
111 }
112
113 pub fn mode(&self) -> Mode {
115 self.mode
116 }
117
118 pub fn last_inbound(&self) -> Instant {
122 self.last_inbound
123 }
124
125 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 pub async fn send(&mut self, message: Message) -> Result<(), WsError> {
139 self.socket.send(message).await?;
140 Ok(())
141 }
142
143 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 self.socket.send(Message::Close(None)).await?;
157 Ok(())
158 }
159
160 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 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 pub(crate) fn keep(&mut self, message: Message) {
187 if self.mode.yields(&message) {
188 self.buffered.push_back(message);
189 }
190 }
191
192 pub(crate) fn socket(&mut self) -> &mut BoxSocket {
194 &mut self.socket
195 }
196
197 pub(crate) fn touch(&mut self) {
199 self.last_inbound = self.runtime.now();
200 }
201
202 pub(crate) fn fail(&mut self) {
204 self.finished = true;
205 }
206
207 pub(crate) fn is_finished(&self) -> bool {
209 self.finished
210 }
211
212 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 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 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 _ => 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 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 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 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 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 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 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}