Skip to main content

polyoxide_binance/usdm/ws/
mod.rs

1//! USDⓈ-M market streams on `fstream.binance.com`.
2//!
3//! Binance routes streams by path: `/public` carries partial depth and book
4//! tickers, `/market` the rest of the streams here. Every [`StreamName`] knows
5//! its path, the bare [`UsdmWs`] holds one connection on one path, and the
6//! supervised tier ([`UsdmWsBuilder`]) keeps one connection per path, opened
7//! when its first stream is wanted and closed when its last leaves.
8//!
9//! Binance acknowledges every `SUBSCRIBE` and enforces its connection rules by
10//! closing the connection, so both tiers enforce them before sending: at most
11//! [`MAX_STREAMS_PER_CONNECTION`] streams, [`MAX_NAMES_PER_REQUEST`] names per
12//! request, and one request per [`MIN_REQUEST_INTERVAL`]. The measurements are
13//! in `docs/specs/binance/OBSERVED.md`.
14pub mod client;
15pub mod error;
16pub mod event;
17#[cfg(any(test, feature = "test-server"))]
18#[doc(hidden)]
19pub mod fixtures;
20pub mod stream;
21pub mod supervised;
22#[cfg(any(test, feature = "test-server"))]
23#[doc(hidden)]
24pub mod test_server;
25
26use std::{fmt, time::Duration};
27
28pub use client::UsdmWs;
29pub use error::{Recovery, UsdmWsError};
30pub use event::{
31    AggTradeEvent, BookTickerEvent, KlineBar, KlineEvent, MarkPriceEvent, PartialDepthEvent,
32    Payload, SymbolType, TickerEvent, Update,
33};
34pub use stream::{DepthLevels, DepthSpeed, InvalidStreamName, StreamName};
35pub use supervised::{DisconnectReason, Event, MembershipHandle, SupervisedUsdmWs, UsdmWsBuilder};
36
37/// The production stream host.
38pub const USDM_WS_BASE: &str = "wss://fstream.binance.com";
39
40/// Streams one connection may carry. The 1025th is answered with an error,
41/// and then the server closes the connection.
42pub const MAX_STREAMS_PER_CONNECTION: usize = 1024;
43
44/// Stream names per `SUBSCRIBE` or `UNSUBSCRIBE`. 200 were measured accepted.
45pub const MAX_NAMES_PER_REQUEST: usize = 200;
46
47/// The shortest gap between two requests on one connection: 5 per second,
48/// half the documented 10. Of 40 requests sent back to back, 15 were answered
49/// before the server closed the connection.
50pub const MIN_REQUEST_INTERVAL: Duration = Duration::from_millis(200);
51
52/// Which routed path a connection is on.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum StreamPath {
55    /// `/market`: tickers, mark prices, aggregate trades, klines.
56    Market,
57    /// `/public`: partial depth and book tickers.
58    Public,
59}
60
61impl StreamPath {
62    /// Both paths.
63    pub const ALL: &'static [Self] = &[Self::Market, Self::Public];
64
65    /// The path segment.
66    pub fn as_str(self) -> &'static str {
67        match self {
68            Self::Market => "market",
69            Self::Public => "public",
70        }
71    }
72
73    /// The combined-stream URL on `base`.
74    pub(crate) fn url(self, base: &str) -> String {
75        format!("{}/{}/stream", base.trim_end_matches('/'), self.as_str())
76    }
77}
78
79impl fmt::Display for StreamPath {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.write_str(self.as_str())
82    }
83}
84
85/// Make sure rustls has a default `CryptoProvider` before opening a connection.
86///
87/// The fifth twin of the function in `polyoxide-clob`, `-rtds`, `-perps` and
88/// `-sports`: `tokio-tungstenite` builds its TLS config from the process-wide
89/// default provider, and rustls installs one automatically only when exactly
90/// one backend is enabled. With `ring` and `aws-lc-rs` both in the graph it
91/// installs neither and panics inside `connect_async`.
92pub(crate) fn ensure_crypto_provider() {
93    static INSTALL: std::sync::Once = std::sync::Once::new();
94    INSTALL.call_once(|| {
95        let _ = rustls::crypto::ring::default_provider().install_default();
96    });
97}