1use std::{future::Future, time::Duration};
4
5use futures_util::StreamExt;
6use tokio::time::Instant;
7use tokio_tungstenite::tungstenite::Message;
8
9use crate::{Connection, WsError};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Correlated<A> {
15 Answer(A),
17 Uncorrelated,
20 Push,
23}
24
25#[derive(Debug)]
27pub(crate) struct Requests {
28 next_id: u64,
29 min_interval: Option<Duration>,
30 answer_timeout: Option<Duration>,
31 last_request: Option<Instant>,
32}
33
34impl Default for Requests {
35 fn default() -> Self {
36 Self {
37 next_id: 1,
38 min_interval: None,
39 answer_timeout: None,
40 last_request: None,
41 }
42 }
43}
44
45impl Connection {
70 pub fn min_request_interval(mut self, interval: Duration) -> Self {
75 self.requests.min_interval = Some(interval);
76 self
77 }
78
79 pub fn answer_timeout(mut self, timeout: Duration) -> Self {
82 self.requests.answer_timeout = Some(timeout);
83 self
84 }
85
86 pub fn next_id(&self) -> u64 {
88 self.requests.next_id
89 }
90
91 pub fn take_id(&mut self) -> u64 {
95 let id = self.requests.next_id;
96 self.requests.next_id += 1;
97 id
98 }
99
100 pub async fn request<A>(
103 &mut self,
104 build: impl FnOnce(u64) -> Message,
105 mut answer: impl FnMut(u64, &Message) -> Correlated<A>,
106 ) -> Result<A, WsError> {
107 if let Some(closed) = self.closed() {
110 return Err(closed);
111 }
112 if self.is_finished() {
113 return Err(already_closed());
114 }
115 if let (Some(last), Some(interval)) =
116 (self.requests.last_request, self.requests.min_interval)
117 {
118 if let Some(at) = last.checked_add(interval) {
120 tokio::time::sleep_until(at).await;
121 }
122 }
123 let id = self.take_id();
124 let timeout = self.requests.answer_timeout;
125 within(timeout, async {
126 self.send(build(id)).await?;
127 self.requests.last_request = Some(Instant::now());
128 self.read_until(&mut |message: &Message| answer(id, message))
129 .await
130 })
131 .await
132 }
133
134 pub async fn await_answer<A>(
140 &mut self,
141 mut answer: impl FnMut(&Message) -> Correlated<A>,
142 ) -> Result<A, WsError> {
143 let timeout = self.requests.answer_timeout;
144 within(timeout, self.read_until(&mut answer)).await
145 }
146
147 async fn read_until<A>(
148 &mut self,
149 answer: &mut impl FnMut(&Message) -> Correlated<A>,
150 ) -> Result<A, WsError> {
151 loop {
152 if let Some(closed) = self.closed() {
153 return Err(closed);
154 }
155 if self.is_finished() {
156 return Err(already_closed());
157 }
158 let message = match self.socket().next().await {
159 Some(Ok(message)) => message,
160 Some(Err(err)) => {
161 self.fail();
162 return Err(err.into());
163 }
164 None => {
165 self.record_close(None);
166 continue;
167 }
168 };
169 self.touch();
170 if let Message::Close(_) = message {
171 self.record_close(Some(&message));
172 self.keep(message);
173 self.flush_reply().await;
174 continue;
175 }
176 match answer(&message) {
177 Correlated::Answer(answer) => return Ok(answer),
178 Correlated::Uncorrelated => {
179 tracing::debug!(?message, "dropped an answer nobody awaits");
180 }
181 Correlated::Push => self.keep(message),
182 }
183 }
184 }
185}
186
187async fn within<A>(
189 timeout: Option<Duration>,
190 exchange: impl Future<Output = Result<A, WsError>>,
191) -> Result<A, WsError> {
192 match timeout {
193 Some(timeout) => tokio::time::timeout(timeout, exchange)
194 .await
195 .map_err(|_| WsError::no_answer(timeout))?,
196 None => exchange.await,
197 }
198}
199
200fn already_closed() -> WsError {
202 WsError::from(tokio_tungstenite::tungstenite::Error::AlreadyClosed)
203}
204
205#[cfg(test)]
206mod tests {
207 use serde_json::{json, Value};
208 use tokio_tungstenite::tungstenite::protocol::{frame::coding::CloseCode, CloseFrame};
209
210 use super::*;
211 use crate::{
212 connect,
213 test_server::{next_item, Script, ScriptedServer},
214 Kind, Mode, DEFAULT_CONNECT_TIMEOUT,
215 };
216
217 fn text(value: Value) -> Message {
218 Message::Text(value.to_string().into())
219 }
220
221 fn parse(message: &Message) -> Option<Value> {
222 match message {
223 Message::Text(text) => serde_json::from_str(text).ok(),
224 _ => None,
225 }
226 }
227
228 async fn answering<A>(answer: A) -> ScriptedServer
231 where
232 A: FnMut(&str) -> Vec<Message> + Clone + Send + 'static,
233 {
234 ScriptedServer::start_answering(vec![Script::hold()], move |_| answer.clone()).await
235 }
236
237 async fn open(server: &ScriptedServer) -> Connection {
238 let socket = connect(server.url.as_str(), DEFAULT_CONNECT_TIMEOUT)
239 .await
240 .expect("connect");
241 Connection::new(socket, Mode::Data)
242 }
243
244 async fn next_text(connection: &mut Connection) -> Value {
245 let message = tokio::time::timeout(Duration::from_secs(2), connection.next())
246 .await
247 .expect("a frame within 2 s")
248 .expect("the stream is open")
249 .expect("not an error");
250 parse(&message).expect("a JSON text frame")
251 }
252
253 mod perps {
256 use super::*;
257
258 pub(super) fn subscribe(channels: &'static [&'static str]) -> impl FnOnce(u64) -> Message {
259 move |id| text(json!({"id": id, "req": "sub", "chs": channels}))
260 }
261
262 pub(super) fn answer(id: u64, message: &Message) -> Correlated<Value> {
263 match parse(message) {
264 Some(frame) if frame.get("ch").is_some() => Correlated::Push,
265 Some(frame) if frame["id"] == id => Correlated::Answer(frame["data"].clone()),
266 Some(frame) if frame.get("id").is_some() => Correlated::Uncorrelated,
267 _ => Correlated::Push,
268 }
269 }
270
271 pub(super) fn push(sq: u64) -> Message {
272 text(json!({"ch": "bbo::1", "ts": 1, "sq": sq, "data": {}}))
273 }
274
275 pub(super) fn statuses(request: &str) -> Message {
276 let request: Value = serde_json::from_str(request).unwrap();
277 let ok: Vec<Value> = request["chs"]
278 .as_array()
279 .unwrap()
280 .iter()
281 .map(|_| json!({"status": "ok"}))
282 .collect();
283 text(json!({"id": request["id"], "data": ok}))
284 }
285 }
286
287 mod binance {
290 use super::*;
291
292 pub(super) fn subscribe(name: &'static str) -> impl FnOnce(u64) -> Message {
293 move |id| text(json!({"method": "SUBSCRIBE", "params": [name], "id": id}))
294 }
295
296 pub(super) fn answer(id: u64, message: &Message) -> Correlated<Result<Value, i64>> {
297 match parse(message) {
298 Some(frame) if frame.get("stream").is_some() => Correlated::Push,
299 Some(frame) if frame["id"] == id => Correlated::Answer(match frame.get("error") {
300 Some(error) => Err(error["code"].as_i64().unwrap()),
301 None => Ok(frame["result"].clone()),
302 }),
303 Some(_) => Correlated::Uncorrelated,
304 None => Correlated::Push,
305 }
306 }
307
308 pub(super) fn push(n: u64) -> Message {
309 text(json!({"stream": "btcusdt@aggTrade", "data": {"a": n}}))
310 }
311
312 pub(super) fn result(request: &str) -> Message {
313 let request: Value = serde_json::from_str(request).unwrap();
314 text(json!({"result": null, "id": request["id"]}))
315 }
316 }
317
318 #[tokio::test]
319 async fn ids_count_from_one() {
320 let server = ScriptedServer::start(vec![Script::hold()]).await;
321 let mut connection = open(&server).await;
322 assert_eq!(connection.next_id(), 1);
323 assert_eq!(connection.take_id(), 1);
324 assert_eq!(connection.take_id(), 2);
325 assert_eq!(connection.next_id(), 3);
326 }
327
328 #[tokio::test]
329 async fn a_request_after_a_failed_read_sends_nothing() {
330 let server = ScriptedServer::start(vec![Script::hold().then_cut()]).await;
331 let mut connection = open(&server).await;
332 assert!(next_item(&mut connection).await.is_err());
333 let err = connection
334 .request(binance::subscribe("a@aggTrade"), binance::answer)
335 .await
336 .expect_err("the connection is gone");
337 assert_eq!(err.kind(), Kind::AlreadyClosed);
338 assert_eq!(connection.next_id(), 1, "no id was taken");
339 }
340
341 #[tokio::test]
342 async fn an_interval_too_long_to_add_is_never_waited() {
343 let server = answering(|request: &str| vec![binance::result(request)]).await;
344 let mut connection = open(&server)
345 .await
346 .min_request_interval(Duration::MAX)
347 .answer_timeout(Duration::from_secs(2));
348 for name in ["a@aggTrade", "b@aggTrade"] {
349 let result = connection
350 .request(binance::subscribe(name), binance::answer)
351 .await
352 .expect("an answer, not a panic or a wait");
353 assert_eq!(result, Ok(Value::Null));
354 }
355 }
356
357 #[tokio::test]
358 async fn a_request_refreshes_last_inbound() {
359 let server = answering(|request: &str| vec![binance::result(request)]).await;
360 let mut connection = open(&server).await;
361 let before = connection.last_inbound();
362 tokio::time::sleep(Duration::from_millis(10)).await;
363 let result = connection
364 .request(binance::subscribe("a@aggTrade"), binance::answer)
365 .await
366 .expect("an answer");
367 assert_eq!(result, Ok(Value::Null));
368 assert!(
369 connection.last_inbound() > before,
370 "the answer, read by the request, must count as inbound"
371 );
372 }
373
374 #[tokio::test]
375 async fn all_mode_yields_a_close_read_while_awaiting() {
376 let server = answering(|_: &str| {
377 vec![Message::Close(Some(CloseFrame {
378 code: CloseCode::Policy,
379 reason: "Too many requests".into(),
380 }))]
381 })
382 .await;
383 let socket = connect(server.url.as_str(), DEFAULT_CONNECT_TIMEOUT)
384 .await
385 .expect("connect");
386 let mut connection = Connection::new(socket, Mode::All);
387 let err = connection
388 .request(binance::subscribe("a@aggTrade"), binance::answer)
389 .await
390 .expect_err("closed");
391 assert!(
392 matches!(
393 err.kind(),
394 Kind::Closed {
395 code: Some(1008),
396 ..
397 }
398 ),
399 "{err:?}"
400 );
401 assert!(matches!(
402 next_item(&mut connection).await,
403 Ok(Message::Close(Some(_)))
404 ));
405 assert!(connection.next().await.is_none());
406 }
407
408 #[tokio::test]
409 async fn perps_pushes_read_before_the_answer_are_kept_and_yielded_in_order() {
410 let server = answering(|request: &str| {
411 vec![perps::push(10), perps::push(11), perps::statuses(request)]
412 })
413 .await;
414 let mut connection = open(&server).await;
415 let data = connection
416 .request(perps::subscribe(&["bbo::1", "trades::1"]), perps::answer)
417 .await
418 .expect("an answer");
419 assert_eq!(data, json!([{"status": "ok"}, {"status": "ok"}]));
420 assert_eq!(next_text(&mut connection).await["sq"], 10);
421 assert_eq!(next_text(&mut connection).await["sq"], 11);
422 }
423
424 #[tokio::test]
425 async fn perps_an_answer_to_another_request_is_dropped() {
426 let server = answering(|request: &str| {
427 vec![
428 text(json!({"id": 99, "data": {"status": "ok"}})),
429 perps::statuses(request),
430 perps::push(12),
431 ]
432 })
433 .await;
434 let mut connection = open(&server).await;
435 connection
436 .request(perps::subscribe(&["bbo::1"]), perps::answer)
437 .await
438 .expect("its own answer");
439 assert_eq!(
440 next_text(&mut connection).await["sq"],
441 12,
442 "the stray answer must not be yielded"
443 );
444 }
445
446 #[tokio::test]
447 async fn binance_requests_are_paced() {
448 let interval = Duration::from_millis(50);
449 let server = answering(|request: &str| vec![binance::result(request)]).await;
450 let mut connection = open(&server).await.min_request_interval(interval);
451 let start = Instant::now();
452 for name in ["a@aggTrade", "b@aggTrade", "c@aggTrade"] {
453 let result = connection
454 .request(binance::subscribe(name), binance::answer)
455 .await
456 .expect("an answer");
457 assert_eq!(result, Ok(Value::Null));
458 }
459 assert!(
460 start.elapsed() >= interval * 2,
461 "three requests in {:?}",
462 start.elapsed()
463 );
464 }
465
466 #[tokio::test]
467 async fn binance_an_unanswered_request_is_no_answer_and_pushes_meanwhile_are_kept() {
468 let after = Duration::from_millis(200);
469 let server = answering(|_: &str| vec![binance::push(1), binance::push(2)]).await;
470 let mut connection = open(&server).await.answer_timeout(after);
471 let err = connection
472 .request(binance::subscribe("a@aggTrade"), binance::answer)
473 .await
474 .expect_err("nothing answers it");
475 assert_eq!(err.kind(), Kind::NoAnswer { after });
476 assert_eq!(next_text(&mut connection).await["data"]["a"], 1);
477 assert_eq!(next_text(&mut connection).await["data"]["a"], 2);
478 }
479
480 #[tokio::test]
481 async fn binance_a_late_answer_is_not_taken_for_the_next() {
482 let server = ScriptedServer::start_answering(vec![Script::hold()], |_| {
486 let mut held: Option<Message> = None;
487 move |request: &str| {
488 let value: Value = serde_json::from_str(request).unwrap();
489 match value["id"].as_u64() {
490 Some(2) => {
491 held = Some(text(
492 json!({"error": {"code": 2, "msg": "Invalid request"}, "id": 2}),
493 ));
494 Vec::new()
495 }
496 Some(3) => vec![
497 binance::push(7),
498 held.take().expect("request 2's answer"),
499 binance::result(request),
500 ],
501 _ => vec![binance::result(request)],
502 }
503 }
504 })
505 .await;
506 let mut connection = open(&server)
507 .await
508 .answer_timeout(Duration::from_millis(200));
509 for (name, expected) in [
510 ("a@aggTrade", Ok(Ok(Value::Null))),
511 ("b@aggTrade", Err(())),
512 ("c@aggTrade", Ok(Ok(Value::Null))),
513 ] {
514 let result = connection
515 .request(binance::subscribe(name), binance::answer)
516 .await
517 .map_err(|err| assert!(matches!(err.kind(), Kind::NoAnswer { .. }), "{err:?}"));
518 assert_eq!(result, expected, "{name}");
519 }
520 let push = tokio::time::timeout(Duration::from_secs(2), connection.next())
521 .await
522 .expect("the push read during request 3 was dropped");
523 assert_eq!(parse(&push.unwrap().unwrap()).unwrap()["data"]["a"], 7);
524 }
525
526 #[tokio::test]
527 async fn binance_an_error_answer_is_the_callers_to_read() {
528 let server = answering(|request: &str| {
529 let value: Value = serde_json::from_str(request).unwrap();
530 vec![text(
531 json!({"error": {"code": 2, "msg": "Invalid request"}, "id": value["id"]}),
532 )]
533 })
534 .await;
535 let mut connection = open(&server).await;
536 let result = connection
537 .request(binance::subscribe("a@aggTrade"), binance::answer)
538 .await
539 .expect("an answer");
540 assert_eq!(result, Err(2));
541 }
542
543 #[tokio::test]
544 async fn a_protocol_ping_awaits_its_pong_without_taking_an_id() {
545 let server = ScriptedServer::start(vec![Script::hold()]).await;
546 let mut connection = open(&server).await.answer_timeout(Duration::from_secs(2));
547 connection
548 .send(Message::Ping(Vec::new().into()))
549 .await
550 .expect("ping");
551 connection
552 .await_answer(|message| match message {
553 Message::Pong(_) => Correlated::Answer(()),
554 _ => Correlated::Push,
555 })
556 .await
557 .expect("a pong");
558 assert_eq!(connection.next_id(), 1);
559 }
560
561 #[tokio::test]
562 async fn a_close_while_awaiting_is_answered() {
563 let server = ScriptedServer::start(vec![Script::hold()
566 .after_first_frame()
567 .then_close()
568 .close_with(1008, "Too many requests")])
569 .await;
570 let mut connection = open(&server).await;
571 let err = connection
572 .request(binance::subscribe("a@aggTrade"), binance::answer)
573 .await
574 .expect_err("closed");
575 assert!(
576 matches!(
577 err.kind(),
578 Kind::Closed {
579 code: Some(1008),
580 ..
581 }
582 ),
583 "{err:?}"
584 );
585 server
586 .wait_for("the close reply", |s| s.close_reply_count() == 1)
587 .await;
588 }
589
590 #[tokio::test]
591 async fn a_close_while_awaiting_fails_the_request_and_every_later_one() {
592 let server = answering(|_: &str| {
593 vec![Message::Close(Some(CloseFrame {
594 code: CloseCode::Policy,
595 reason: "Too many requests".into(),
596 }))]
597 })
598 .await;
599 let mut connection = open(&server).await;
600 let closed = Kind::Closed {
601 code: Some(1008),
602 reason: "Too many requests",
603 };
604 let err = connection
605 .request(binance::subscribe("a@aggTrade"), binance::answer)
606 .await
607 .expect_err("closed");
608 assert_eq!(err.kind(), closed);
609 let again = connection
610 .request(binance::subscribe("b@aggTrade"), binance::answer)
611 .await
612 .expect_err("still closed");
613 assert_eq!(again.kind(), closed);
614 assert_eq!(connection.next_id(), 2, "nothing was sent after the close");
615 assert!(connection.next().await.is_none());
616 }
617}