pub mod limits;
pub mod timing;
pub use limits::{
Limits, MAX_CONNECTIONS, MAX_INFLIGHT_POSTS, MAX_MESSAGES_PER_MINUTE, MAX_NEW_CONNECTIONS_PER_MINUTE,
MAX_SUBSCRIPTIONS, MAX_USERS, Refused,
};
pub use timing::{Retry, Tick, Timing};
use crate::client::Network;
use crate::live::{BadFrame, Frame, Post, Subscription, decode, post, subscribe, unsubscribe};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Link {
Closed,
Open {
since: u64,
heard: u64,
kept: u64,
talked: bool,
pinged: u64,
},
}
#[derive(Debug, Clone)]
pub struct Session {
network: Network,
limits: Limits,
timing: Timing,
wanted: Vec<Subscription>,
link: Link,
failures: u32,
next_id: u64,
waiting: Vec<u64>,
}
impl Session {
#[must_use]
pub fn new(network: Network) -> Self {
Session {
network,
limits: Limits::VENUE,
timing: Timing::DEFAULT,
wanted: Vec::new(),
link: Link::Closed,
failures: 0,
next_id: 1,
waiting: Vec::new(),
}
}
#[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 const fn network(&self) -> Network {
self.network
}
#[must_use]
pub const fn url(&self) -> &'static str {
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 users(&self) -> usize {
let mut users: Vec<_> = self.wanted.iter().filter_map(Subscription::user).collect();
users.sort_unstable();
users.dedup();
users.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()?;
if self.wanted.len() >= self.limits.subscriptions {
return Err(Refused::Subscriptions { limit: self.limits.subscriptions });
}
if let Some(user) = subscription.user()
&& !self.wanted.iter().any(|wanted| wanted.user() == Some(user))
&& self.users() >= self.limits.users
{
return Err(Refused::Users { limit: self.limits.users });
}
let frame = self.is_open().then(|| subscribe(&subscription));
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);
self.is_open().then(|| unsubscribe(&gone))
}
pub fn post(&mut self, request: &Post) -> Result<(u64, String), Refused> {
if !self.is_open() {
return Err(Refused::Closed);
}
if self.waiting.len() >= self.limits.posts {
return Err(Refused::Posts { limit: self.limits.posts });
}
let id = self.next_id;
self.next_id = self.next_id.saturating_add(1);
self.waiting.push(id);
Ok((id, post(id, request)))
}
pub fn opened(&mut self, now: u64) -> Vec<String> {
self.link = Link::Open { since: now, heard: now, kept: now, talked: false, pinged: now };
self.wanted.iter().map(subscribe).collect()
}
pub fn received(&mut self, now: u64, text: &str) -> Result<Frame, BadFrame> {
let frame = decode(text);
if let Link::Open { heard, kept, talked, .. } = &mut self.link {
*heard = (*heard).max(now);
*talked = true;
if frame.as_ref().is_ok_and(|frame| !matches!(frame, Frame::Ack(_) | Frame::Error(_) | Frame::Post(_))) {
*kept = (*kept).max(now);
}
}
let answered = match &frame {
Ok(Frame::Post(reply)) => Some(reply.id),
Ok(Frame::Error(refusal)) => refusal.post_id(),
_ => None,
};
if let Some(id) = answered {
self.waiting.retain(|waiting| *waiting != id);
}
frame
}
}
#[cfg(test)]
#[path = "tests/session.rs"]
mod tests;