1use std::{
5 collections::VecDeque,
6 pin::Pin,
7 task::{Context, Poll},
8};
9
10use futures_util::{SinkExt, Stream, StreamExt};
11use tokio::time::Instant;
12use tokio_tungstenite::tungstenite::Message;
13
14use crate::{request::Requests, Socket, 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: Socket,
62 mode: Mode,
63 buffered: VecDeque<Message>,
64 last_inbound: Instant,
65 close: Option<(Option<u16>, String)>,
67 replying: bool,
69 finished: bool,
70 pub(crate) requests: Requests,
71}
72
73impl std::fmt::Debug for Connection {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("Connection")
76 .field("mode", &self.mode)
77 .field("buffered", &self.buffered.len())
78 .field("close", &self.close)
79 .field("finished", &self.finished)
80 .finish_non_exhaustive()
81 }
82}
83
84impl Connection {
85 pub fn new(socket: Socket, mode: Mode) -> Self {
87 Self {
88 socket,
89 mode,
90 buffered: VecDeque::new(),
91 last_inbound: Instant::now(),
92 close: None,
93 replying: false,
94 finished: false,
95 requests: Requests::default(),
96 }
97 }
98
99 pub fn mode(&self) -> Mode {
101 self.mode
102 }
103
104 pub fn last_inbound(&self) -> Instant {
108 self.last_inbound
109 }
110
111 pub fn closed(&self) -> Option<WsError> {
118 self.close
119 .as_ref()
120 .map(|(code, reason)| WsError::closed(*code, reason.as_str()))
121 }
122
123 pub async fn send(&mut self, message: Message) -> Result<(), WsError> {
125 self.socket.send(message).await?;
126 Ok(())
127 }
128
129 pub async fn close(&mut self) -> Result<(), WsError> {
134 if self.close.is_some() {
135 self.flush_reply().await;
136 return Ok(());
137 }
138 if self.finished {
139 return Ok(());
140 }
141 self.socket.close(None).await?;
143 Ok(())
144 }
145
146 pub(crate) fn record_close(&mut self, frame: Option<&Message>) {
150 self.replying = matches!(frame, Some(Message::Close(_)));
151 self.close = Some(match frame {
152 Some(Message::Close(Some(frame))) => {
153 (Some(u16::from(frame.code)), frame.reason.to_string())
154 }
155 _ => (None, String::new()),
156 });
157 self.finished = true;
158 tracing::debug!(close = ?self.close, "the server closed the WebSocket connection");
159 }
160
161 pub(crate) async fn flush_reply(&mut self) {
163 if self.replying {
164 if let Err(err) = self.socket.flush().await {
165 tracing::debug!(%err, "the close reply could not be sent");
166 }
167 self.replying = false;
168 }
169 }
170
171 pub(crate) fn keep(&mut self, message: Message) {
173 if self.mode.yields(&message) {
174 self.buffered.push_back(message);
175 }
176 }
177
178 pub(crate) fn socket(&mut self) -> &mut Socket {
180 &mut self.socket
181 }
182
183 pub(crate) fn touch(&mut self) {
185 self.last_inbound = Instant::now();
186 }
187
188 pub(crate) fn fail(&mut self) {
190 self.finished = true;
191 }
192
193 pub(crate) fn is_finished(&self) -> bool {
195 self.finished
196 }
197
198 pub(crate) fn has_kept(&self) -> bool {
200 !self.buffered.is_empty()
201 }
202}
203
204impl Stream for Connection {
205 type Item = Result<Message, WsError>;
206
207 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
208 let this = self.get_mut();
209 loop {
210 if this.replying {
213 match this.socket.poll_flush_unpin(cx) {
214 Poll::Pending => return Poll::Pending,
215 Poll::Ready(Err(err)) => {
216 tracing::debug!(%err, "the close reply could not be sent");
217 }
218 Poll::Ready(Ok(())) => {}
219 }
220 this.replying = false;
221 }
222 if let Some(kept) = this.buffered.pop_front() {
223 return Poll::Ready(Some(Ok(kept)));
224 }
225 if this.finished {
226 return Poll::Ready(None);
227 }
228 let message = match this.socket.poll_next_unpin(cx) {
229 Poll::Ready(Some(Ok(message))) => message,
230 Poll::Ready(Some(Err(err))) => {
231 this.fail();
232 return Poll::Ready(Some(Err(err.into())));
233 }
234 Poll::Ready(None) => {
235 this.record_close(None);
236 return Poll::Ready(None);
237 }
238 Poll::Pending => return Poll::Pending,
239 };
240 this.touch();
241 match message {
242 Message::Close(_) => {
243 this.record_close(Some(&message));
244 this.keep(message);
245 }
246 Message::Ping(_) if this.mode == Mode::All => {
247 let _ = this.socket.poll_flush_unpin(cx);
250 return Poll::Ready(Some(Ok(message)));
251 }
252 message if this.mode.yields(&message) => return Poll::Ready(Some(Ok(message))),
253 _ => continue,
255 }
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use std::time::Duration;
263
264 use tokio_tungstenite::tungstenite::protocol::{frame::coding::CloseCode, CloseFrame};
265
266 use super::*;
267 use crate::{
268 connect,
269 test_server::{Script, ScriptedServer},
270 Kind, DEFAULT_CONNECT_TIMEOUT,
271 };
272
273 const WINDOW: Duration = Duration::from_secs(3);
274
275 async fn open(server: &ScriptedServer, mode: Mode) -> Connection {
276 let socket = connect(server.url.as_str(), DEFAULT_CONNECT_TIMEOUT)
277 .await
278 .expect("connect");
279 Connection::new(socket, mode)
280 }
281
282 async fn next(connection: &mut Connection) -> Option<Result<Message, WsError>> {
283 tokio::time::timeout(WINDOW, connection.next())
284 .await
285 .expect("an item within the window")
286 }
287
288 fn frames() -> Vec<Message> {
289 vec![
290 Message::Ping(b"p".to_vec().into()),
291 Message::Pong(b"q".to_vec().into()),
292 Message::Binary(vec![1, 2, 3].into()),
293 Message::Text("data".into()),
294 ]
295 }
296
297 #[tokio::test]
298 async fn data_mode_yields_text_and_binary_and_skips_control_frames() {
299 let server = ScriptedServer::start(vec![Script::messages(frames())]).await;
300 let mut connection = open(&server, Mode::Data).await;
301 assert_eq!(
302 next(&mut connection).await.unwrap().unwrap(),
303 Message::Binary(vec![1, 2, 3].into())
304 );
305 assert_eq!(
306 next(&mut connection).await.unwrap().unwrap(),
307 Message::Text("data".into())
308 );
309 }
310
311 #[tokio::test]
312 async fn all_mode_yields_every_frame() {
313 let server = ScriptedServer::start(vec![Script::messages(frames())]).await;
314 let mut connection = open(&server, Mode::All).await;
315 let mut seen = Vec::new();
316 for _ in 0..4 {
317 seen.push(next(&mut connection).await.unwrap().unwrap());
318 }
319 assert_eq!(seen, frames());
320 }
321
322 #[tokio::test]
323 async fn a_server_ping_is_answered_in_either_mode() {
324 for mode in [Mode::Data, Mode::All] {
327 let server = ScriptedServer::start(vec![Script::messages(vec![Message::Ping(
328 b"p1".to_vec().into(),
329 )])])
330 .await;
331 let mut connection = open(&server, mode).await;
332 let reader = match mode {
333 Mode::Data => {
334 tokio::spawn(async move { while connection.next().await.is_some() {} })
335 }
336 Mode::All => {
337 assert_eq!(
338 next(&mut connection).await.unwrap().unwrap(),
339 Message::Ping(b"p1".to_vec().into())
340 );
341 tokio::spawn(async move {
343 let _held = connection;
344 std::future::pending::<()>().await;
345 })
346 }
347 };
348 server
349 .wait_for("a pong", |s| s.pongs() == [b"p1".to_vec()])
350 .await;
351 reader.abort();
352 }
353 }
354
355 #[tokio::test]
356 async fn a_server_close_ends_the_stream_with_its_code_and_reason() {
357 for (code, reason) in [(1011, "overloaded"), (1008, "policy")] {
358 let server =
359 ScriptedServer::start(vec![Script::close_at_once().close_with(code, reason)]).await;
360 let mut connection = open(&server, Mode::Data).await;
361 assert!(connection.closed().is_none(), "open, so not closed yet");
362 assert!(next(&mut connection).await.is_none());
363 assert_eq!(
364 connection.closed().expect("closed").kind(),
365 Kind::Closed {
366 code: Some(code),
367 reason
368 }
369 );
370 assert!(next(&mut connection).await.is_none(), "it stays ended");
371 }
372 }
373
374 #[tokio::test]
375 async fn all_mode_yields_the_close_frame_last() {
376 let server =
377 ScriptedServer::start(vec![Script::close_at_once().close_with(1001, "bye")]).await;
378 let mut connection = open(&server, Mode::All).await;
379 assert_eq!(
380 next(&mut connection).await.unwrap().unwrap(),
381 Message::Close(Some(CloseFrame {
382 code: CloseCode::Away,
383 reason: "bye".into()
384 }))
385 );
386 assert!(next(&mut connection).await.is_none());
387 assert_eq!(
388 connection.closed().unwrap().kind(),
389 Kind::Closed {
390 code: Some(1001),
391 reason: "bye"
392 }
393 );
394 }
395
396 #[tokio::test]
397 async fn a_server_close_is_answered_though_the_stream_is_never_read_again() {
398 for mode in [Mode::Data, Mode::All] {
402 let server = ScriptedServer::start(vec![
403 Script::close_at_once().close_with(1011, "pong_timeout")
404 ])
405 .await;
406 let mut connection = open(&server, mode).await;
407 let last = next(&mut connection).await;
408 match mode {
409 Mode::Data => assert!(last.is_none(), "{last:?}"),
410 Mode::All => assert!(matches!(last, Some(Ok(Message::Close(_)))), "{last:?}"),
411 }
412 assert_eq!(
413 connection.closed().unwrap().kind(),
414 Kind::Closed {
415 code: Some(1011),
416 reason: "pong_timeout"
417 }
418 );
419 server
422 .wait_for("the close reply", |s| s.close_reply_count() == 1)
423 .await;
424 drop(connection);
425 }
426 }
427
428 #[tokio::test]
429 async fn close_after_the_server_closed_succeeds() {
430 let server = ScriptedServer::start(vec![Script::close_at_once()]).await;
431 let mut connection = open(&server, Mode::Data).await;
432 assert!(next(&mut connection).await.is_none());
433 connection.close().await.expect("close");
434 server
435 .wait_for("the close reply", |s| s.close_reply_count() == 1)
436 .await;
437 }
438
439 #[tokio::test]
440 async fn a_failed_read_is_yielded_once_and_ends_the_stream() {
441 let server = ScriptedServer::start(vec![Script::hold().then_cut()]).await;
443 let mut connection = open(&server, Mode::Data).await;
444 let err = next(&mut connection).await.unwrap().unwrap_err();
445 assert!(matches!(err.kind(), Kind::Protocol | Kind::Io), "{err:?}");
446 assert!(next(&mut connection).await.is_none());
447 assert!(connection.closed().is_none(), "an error is not a close");
448 }
449
450 #[tokio::test]
451 async fn close_after_a_failed_read_does_nothing() {
452 let server = ScriptedServer::start(vec![Script::hold().then_cut()]).await;
453 let mut connection = open(&server, Mode::Data).await;
454 assert!(next(&mut connection).await.unwrap().is_err());
455 connection
456 .close()
457 .await
458 .expect("closing a connection that is already gone succeeds");
459 }
460
461 #[tokio::test]
462 async fn close_sends_a_close_frame_the_server_receives() {
463 let server = ScriptedServer::start(vec![Script::hold()]).await;
464 let mut connection = open(&server, Mode::Data).await;
465 connection.close().await.expect("close");
466 server
467 .wait_for("a close frame", |s| s.close_count() == 1)
468 .await;
469 }
470
471 #[tokio::test]
472 async fn every_frame_refreshes_last_inbound() {
473 let server = ScriptedServer::start(vec![Script::messages(vec![Message::Pong(
474 Vec::new().into(),
475 )])])
476 .await;
477 let mut connection = open(&server, Mode::Data).await;
478 let opened = connection.last_inbound();
479 let _ = tokio::time::timeout(Duration::from_millis(200), connection.next()).await;
481 assert!(connection.last_inbound() > opened);
482 }
483
484 #[test]
485 fn a_connection_can_move_between_tasks() {
486 fn assert_send_unpin<T: Send + Unpin>() {}
487 assert_send_unpin::<Connection>();
488 }
489}