pub mod limits;
pub mod timing;
pub use limits::{
Limits, MAX_ACCOUNTS, MAX_CONNECTIONS, MAX_INFLIGHT_MESSAGES, MAX_MESSAGES_PER_MINUTE, MAX_SUBSCRIPTIONS,
MAX_SUBSCRIPTIONS_PER_CONNECTION, Refused,
};
pub use timing::{Retry, Tick, Timing};
use std::collections::BTreeMap;
use std::fmt;
use crate::client::Network;
use crate::live::{
BadFrame, Data, Frame, Gap, PONG, Subscription, decode, request::batch_fits, resubscribe, send_tx, send_tx_batch,
subscribe, unsubscribe,
};
use crate::utils::MarketId;
use crate::utils::sign::Signed;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Link {
Closed,
Open {
since: u64,
heard: u64,
talked: bool,
pinged: u64,
},
}
#[derive(Clone, PartialEq, Eq)]
struct Token(String);
impl fmt::Debug for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Token(..)")
}
}
#[derive(Debug, Clone)]
pub struct Session {
network: Network,
readonly: bool,
limits: Limits,
timing: Timing,
token: Option<Token>,
wanted: Vec<Subscription>,
link: Link,
failures: u32,
next_id: u64,
waiting: Vec<u64>,
chains: BTreeMap<MarketId, Option<u64>>,
owed: Vec<String>,
}
impl Session {
#[must_use]
pub fn new(network: Network) -> Self {
Session {
network,
readonly: false,
limits: Limits::VENUE,
timing: Timing::DEFAULT,
token: None,
wanted: Vec::new(),
link: Link::Closed,
failures: 0,
next_id: 1,
waiting: Vec::new(),
chains: BTreeMap::new(),
owed: Vec::new(),
}
}
#[must_use]
pub const fn read_only(mut self) -> Self {
self.readonly = true;
self
}
#[must_use]
pub const fn with_limits(mut self, limits: Limits) -> Self {
self.limits = limits;
self
}
#[must_use]
pub const fn with_timing(mut self, timing: Timing) -> Self {
self.timing = timing;
self
}
#[must_use]
pub fn with_auth(mut self, token: impl Into<String>) -> Self {
self.token = Some(Token(token.into()));
self
}
pub fn set_auth(&mut self, token: Option<String>) {
self.token = token.map(Token);
}
#[must_use]
pub const fn network(&self) -> Network {
self.network
}
#[must_use]
pub const fn is_read_only(&self) -> bool {
self.readonly
}
#[must_use]
pub const fn url(&self) -> &'static str {
match self.readonly {
true => self.network.ws_readonly_url(),
false => self.network.ws_url(),
}
}
#[must_use]
pub const fn is_open(&self) -> bool {
matches!(self.link, Link::Open { .. })
}
#[must_use]
pub fn wanted(&self) -> &[Subscription] {
&self.wanted
}
#[must_use]
pub fn accounts(&self) -> usize {
let mut accounts: Vec<_> = self.wanted.iter().filter_map(Subscription::account).collect();
accounts.sort_unstable();
accounts.dedup();
accounts.len()
}
#[must_use]
pub fn waiting(&self) -> &[u64] {
&self.waiting
}
pub fn subscribe(&mut self, subscription: Subscription) -> Result<Option<String>, Refused> {
if self.wanted.contains(&subscription) {
return Ok(None);
}
subscription.check(self.network)?;
if subscription.needs_auth() && self.token.is_none() {
return Err(Refused::NoToken(subscription.channel()));
}
if self.wanted.len() >= self.limits.subscriptions {
return Err(Refused::Subscriptions { limit: self.limits.subscriptions });
}
if let Some(account) = subscription.account()
&& !self.wanted.iter().any(|wanted| wanted.account() == Some(account))
&& self.accounts() >= self.limits.accounts
{
return Err(Refused::Accounts { limit: self.limits.accounts });
}
let frame = self.is_open().then(|| subscribe(&subscription, self.auth()));
self.wanted.push(subscription);
Ok(frame)
}
pub fn unsubscribe(&mut self, subscription: &Subscription) -> Option<String> {
let at = self.wanted.iter().position(|wanted| wanted == subscription)?;
let gone = self.wanted.remove(at);
if let Subscription::OrderBook { market } = gone {
self.chains.remove(&market);
}
self.is_open().then(|| unsubscribe(&gone))
}
pub fn resubscribe(&mut self, subscription: &Subscription) -> Vec<String> {
if !self.is_open() || !self.wanted.contains(subscription) {
return Vec::new();
}
if let Subscription::OrderBook { market } = subscription {
self.chains.insert(*market, None);
}
resubscribe(subscription, self.auth()).into()
}
pub fn send_tx(&mut self, signed: &Signed) -> Result<(u64, String), Refused> {
self.send_tx_info(signed.tx_type().code(), &signed.tx_info())
}
pub fn send_tx_info(&mut self, tx_type: u8, tx_info: &str) -> Result<(u64, String), Refused> {
let id = self.take_id()?;
let frame = send_tx(&id.to_string(), tx_type, tx_info).map_err(Refused::Unwritable)?;
Ok((self.sent(id), frame))
}
pub fn send_tx_batch(&mut self, signed: &[Signed]) -> Result<(u64, String), Refused> {
let txs: Vec<(u8, String)> = signed.iter().map(|one| (one.tx_type().code(), one.tx_info())).collect();
self.send_tx_batch_infos(&txs)
}
pub fn send_tx_batch_infos(&mut self, txs: &[(u8, String)]) -> Result<(u64, String), Refused> {
if !batch_fits(txs.len()) {
return Err(Refused::Batch(txs.len()));
}
let id = self.take_id()?;
let frame = send_tx_batch(&id.to_string(), txs);
Ok((self.sent(id), frame))
}
pub fn opened(&mut self, now: u64) -> Vec<String> {
self.link = Link::Open { since: now, heard: now, talked: false, pinged: now };
self.chains.clear();
self.owed.clear();
self.wanted.iter().map(|subscription| subscribe(subscription, self.auth())).collect()
}
pub fn received(&mut self, now: u64, text: &str) -> Result<Frame, BadFrame> {
let frame = decode(text);
if let Link::Open { heard, talked, .. } = &mut self.link {
*heard = (*heard).max(now);
*talked = true;
}
let frame = frame?;
if let Some(id) = frame.answers().and_then(|id| id.parse::<u64>().ok()) {
self.waiting.retain(|waiting| *waiting != id);
}
Ok(match frame {
Frame::Ping => {
self.owed.push(PONG.to_owned());
Frame::Ping
}
Frame::Snapshot(Data::OrderBook(book)) => {
self.chains.insert(book.market, Some(book.nonce));
Frame::Snapshot(Data::OrderBook(book))
}
Frame::Update(Data::OrderBook(book)) => match self.chains.get(&book.market).copied() {
Some(Some(expected)) if book.begin_nonce != expected => {
let gap = Gap { market: book.market, expected, got: book.begin_nonce };
let again = self.resubscribe(&Subscription::OrderBook { market: book.market });
self.owed.extend(again);
self.chains.insert(book.market, None);
Frame::Gap(gap)
}
Some(Some(_)) => {
self.chains.insert(book.market, Some(book.nonce));
Frame::Update(Data::OrderBook(book))
}
_ => Frame::Update(Data::OrderBook(book)),
},
other => other,
})
}
pub fn owed(&mut self) -> Vec<String> {
std::mem::take(&mut self.owed)
}
fn auth(&self) -> Option<&str> {
self.token.as_ref().map(|token| token.0.as_str())
}
fn take_id(&mut self) -> Result<u64, Refused> {
if self.readonly {
return Err(Refused::ReadOnly);
}
if !self.is_open() {
return Err(Refused::Closed);
}
if self.waiting.len() >= self.limits.transactions {
return Err(Refused::Transactions { limit: self.limits.transactions });
}
let id = self.next_id;
self.next_id = self.next_id.saturating_add(1);
Ok(id)
}
fn sent(&mut self, id: u64) -> u64 {
self.waiting.push(id);
id
}
}
#[cfg(test)]
#[path = "tests/session.rs"]
mod tests;