df_hyperliquid_core/live/session/
mod.rs1pub mod limits;
6pub mod timing;
7
8pub use limits::{
9 Limits, MAX_CONNECTIONS, MAX_INFLIGHT_POSTS, MAX_MESSAGES_PER_MINUTE, MAX_NEW_CONNECTIONS_PER_MINUTE,
10 MAX_SUBSCRIPTIONS, MAX_USERS, Refused,
11};
12pub use timing::{Retry, Tick, Timing};
13
14use crate::client::Network;
15use crate::live::{BadFrame, Frame, Post, Subscription, decode, post, subscribe, unsubscribe};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum Link {
19 Closed,
20 Open {
21 since: u64,
23 heard: u64,
25 kept: u64,
29 talked: bool,
31 pinged: u64,
33 },
34}
35
36#[derive(Debug, Clone)]
38pub struct Session {
39 network: Network,
40 limits: Limits,
41 timing: Timing,
42 wanted: Vec<Subscription>,
43 link: Link,
44 failures: u32,
46 next_id: u64,
47 waiting: Vec<u64>,
48}
49
50impl Session {
51 #[must_use]
53 pub fn new(network: Network) -> Self {
54 Session {
55 network,
56 limits: Limits::VENUE,
57 timing: Timing::DEFAULT,
58 wanted: Vec::new(),
59 link: Link::Closed,
60 failures: 0,
61 next_id: 1,
62 waiting: Vec::new(),
63 }
64 }
65
66 #[must_use]
68 pub const fn with_limits(mut self, limits: Limits) -> Self {
69 self.limits = limits;
70 self
71 }
72
73 #[must_use]
74 pub const fn with_timing(mut self, timing: Timing) -> Self {
75 self.timing = timing;
76 self
77 }
78
79 #[must_use]
80 pub const fn network(&self) -> Network {
81 self.network
82 }
83
84 #[must_use]
86 pub const fn url(&self) -> &'static str {
87 self.network.ws_url()
88 }
89
90 #[must_use]
91 pub const fn is_open(&self) -> bool {
92 matches!(self.link, Link::Open { .. })
93 }
94
95 #[must_use]
97 pub fn wanted(&self) -> &[Subscription] {
98 &self.wanted
99 }
100
101 #[must_use]
103 pub fn users(&self) -> usize {
104 let mut users: Vec<_> = self.wanted.iter().filter_map(Subscription::user).collect();
105 users.sort_unstable();
106 users.dedup();
107 users.len()
108 }
109
110 #[must_use]
112 pub fn waiting(&self) -> &[u64] {
113 &self.waiting
114 }
115
116 pub fn subscribe(&mut self, subscription: Subscription) -> Result<Option<String>, Refused> {
120 if self.wanted.contains(&subscription) {
121 return Ok(None);
122 }
123 subscription.check()?;
124 if self.wanted.len() >= self.limits.subscriptions {
125 return Err(Refused::Subscriptions { limit: self.limits.subscriptions });
126 }
127 if let Some(user) = subscription.user()
128 && !self.wanted.iter().any(|wanted| wanted.user() == Some(user))
129 && self.users() >= self.limits.users
130 {
131 return Err(Refused::Users { limit: self.limits.users });
132 }
133 let frame = self.is_open().then(|| subscribe(&subscription));
134 self.wanted.push(subscription);
135 Ok(frame)
136 }
137
138 pub fn unsubscribe(&mut self, subscription: &Subscription) -> Option<String> {
141 let at = self.wanted.iter().position(|wanted| wanted == subscription)?;
142 let gone = self.wanted.remove(at);
143 self.is_open().then(|| unsubscribe(&gone))
144 }
145
146 pub fn post(&mut self, request: &Post) -> Result<(u64, String), Refused> {
149 if !self.is_open() {
150 return Err(Refused::Closed);
151 }
152 if self.waiting.len() >= self.limits.posts {
153 return Err(Refused::Posts { limit: self.limits.posts });
154 }
155 let id = self.next_id;
156 self.next_id = self.next_id.saturating_add(1);
157 self.waiting.push(id);
158 Ok((id, post(id, request)))
159 }
160
161 pub fn opened(&mut self, now: u64) -> Vec<String> {
164 self.link = Link::Open { since: now, heard: now, kept: now, talked: false, pinged: now };
165 self.wanted.iter().map(subscribe).collect()
166 }
167
168 pub fn received(&mut self, now: u64, text: &str) -> Result<Frame, BadFrame> {
172 let frame = decode(text);
173 if let Link::Open { heard, kept, talked, .. } = &mut self.link {
174 *heard = (*heard).max(now);
175 *talked = true;
176 if frame.as_ref().is_ok_and(|frame| !matches!(frame, Frame::Ack(_) | Frame::Error(_) | Frame::Post(_))) {
177 *kept = (*kept).max(now);
178 }
179 }
180 let answered = match &frame {
181 Ok(Frame::Post(reply)) => Some(reply.id),
182 Ok(Frame::Error(refusal)) => refusal.post_id(),
183 _ => None,
184 };
185 if let Some(id) = answered {
186 self.waiting.retain(|waiting| *waiting != id);
187 }
188 frame
189 }
190}
191
192#[cfg(test)]
193#[path = "tests/session.rs"]
194mod tests;