df_hyperliquid_core/client/native/
socket.rs1use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8use crate::live::{Event, FeedError, PING, Post, Refused, Session, Subscription, Tick};
9use futures_util::{SinkExt, StreamExt};
10use tokio::{
11 net::TcpStream,
12 sync::{mpsc, oneshot},
13 task::JoinHandle,
14 time::{Instant, sleep_until},
15};
16use tokio_tungstenite::{
17 MaybeTlsStream, WebSocketStream, connect_async,
18 tungstenite::{Message, Utf8Bytes},
19};
20
21use crate::client::native::clock;
22
23enum Command {
24 Subscribe(Subscription, oneshot::Sender<Result<(), Refused>>),
25 Unsubscribe(Subscription),
26 Post(Post, oneshot::Sender<Result<u64, Refused>>),
27}
28
29type Delivered = (Event, Option<Utf8Bytes>);
31
32pub struct Feed {
34 commands: mpsc::UnboundedSender<Command>,
35 events: mpsc::UnboundedReceiver<Delivered>,
36 task: JoinHandle<()>,
37}
38
39impl Feed {
40 #[must_use]
42 pub fn start(session: Session) -> Feed {
43 let url = session.url();
44 Feed::start_at(url, session)
45 }
46
47 #[must_use]
49 pub fn start_at(url: impl Into<String>, session: Session) -> Feed {
50 let (commands, inbox) = mpsc::unbounded_channel();
51 let (outbox, events) = mpsc::unbounded_channel();
52 let task = tokio::spawn(run(url.into(), session, inbox, outbox));
53 Feed { commands, events, task }
54 }
55
56 pub async fn subscribe(&self, subscription: Subscription) -> Result<(), FeedError> {
59 let (reply, answer) = oneshot::channel();
60 self.commands.send(Command::Subscribe(subscription, reply)).map_err(|_| FeedError::Stopped)?;
61 Ok(answer.await.map_err(|_| FeedError::Stopped)??)
62 }
63
64 pub fn unsubscribe(&self, subscription: Subscription) -> Result<(), FeedError> {
65 self.commands.send(Command::Unsubscribe(subscription)).map_err(|_| FeedError::Stopped)
66 }
67
68 pub async fn post(&self, request: Post) -> Result<u64, FeedError> {
71 let (reply, answer) = oneshot::channel();
72 self.commands.send(Command::Post(request, reply)).map_err(|_| FeedError::Stopped)?;
73 Ok(answer.await.map_err(|_| FeedError::Stopped)??)
74 }
75
76 pub async fn next(&mut self) -> Option<Event> {
78 self.events.recv().await.map(|(event, _)| event)
79 }
80
81 pub async fn next_with_text(&mut self) -> Option<(Event, Option<String>)> {
84 let (event, text) = self.events.recv().await?;
85 Some((event, text.map(|text| text.as_str().to_owned())))
86 }
87
88 pub fn close(self) {
90 drop(self);
91 }
92}
93
94impl Drop for Feed {
95 fn drop(&mut self) {
96 self.task.abort();
97 }
98}
99
100type Socket = WebSocketStream<MaybeTlsStream<TcpStream>>;
101
102enum Outcome {
104 Lost(String),
105 Stop,
106}
107
108async fn run(
109 url: String,
110 mut session: Session,
111 mut commands: mpsc::UnboundedReceiver<Command>,
112 events: mpsc::UnboundedSender<Delivered>,
113) {
114 loop {
115 let dial = connect_async(url.as_str());
118 tokio::pin!(dial);
119 let dialled = loop {
120 tokio::select! {
121 dialled = &mut dial => break dialled,
122 command = commands.recv() => match command {
123 Some(command) => {
124 handle(&mut session, command);
125 }
126 None => return,
127 },
128 }
129 };
130 let reason = match dialled {
131 Ok((mut socket, _)) => match open(&mut socket, &mut session, &mut commands, &events).await {
132 Outcome::Lost(reason) => reason,
133 Outcome::Stop => return,
134 },
135 Err(error) => error.to_string(),
136 };
137 let retry = session.closed(clock(), random());
138 let until = Instant::now() + Duration::from_millis(retry.after_ms);
139 if events.send((Event::Closed { reason, retry }, None)).is_err() {
140 return;
141 }
142 loop {
144 tokio::select! {
145 () = sleep_until(until) => break,
146 command = commands.recv() => match command {
147 Some(command) => {
148 handle(&mut session, command);
149 }
150 None => return,
151 },
152 }
153 }
154 }
155}
156
157async fn open(
158 socket: &mut Socket,
159 session: &mut Session,
160 commands: &mut mpsc::UnboundedReceiver<Command>,
161 events: &mpsc::UnboundedSender<Delivered>,
162) -> Outcome {
163 for frame in session.opened(clock()) {
164 if let Err(error) = socket.send(Message::text(frame)).await {
165 return Outcome::Lost(error.to_string());
166 }
167 }
168 if events.send((Event::Opened, None)).is_err() {
169 return Outcome::Stop;
170 }
171 loop {
172 let wake = session.due().map(|at| Instant::now() + Duration::from_millis(at.saturating_sub(clock())));
173 tokio::select! {
174 message = socket.next() => match message {
175 Some(Ok(Message::Text(text))) => {
176 let event = match session.received(clock(), &text) {
177 Ok(frame) => Event::Frame(Box::new(frame)),
178 Err(bad) => Event::Unreadable(bad),
179 };
180 if events.send((event, Some(text))).is_err() {
181 return Outcome::Stop;
182 }
183 }
184 Some(Ok(Message::Close(frame))) => {
185 return Outcome::Lost(frame.map_or_else(|| "the venue closed the socket".to_owned(), |frame| frame.reason.to_string()));
186 }
187 Some(Ok(_)) => {}
189 Some(Err(error)) => return Outcome::Lost(error.to_string()),
190 None => return Outcome::Lost("the socket ended".to_owned()),
191 },
192 command = commands.recv() => match command {
193 Some(command) => {
194 if let Some(frame) = handle(session, command)
195 && let Err(error) = socket.send(Message::text(frame)).await
196 {
197 return Outcome::Lost(error.to_string());
198 }
199 }
200 None => {
201 let _ = socket.close(None).await;
202 return Outcome::Stop;
203 }
204 },
205 () = until(wake) => match session.tick(clock()) {
206 Tick::Wait => {}
207 Tick::Ping => {
208 if let Err(error) = socket.send(Message::text(PING)).await {
209 return Outcome::Lost(error.to_string());
210 }
211 }
212 Tick::Reconnect => {
213 let _ = socket.close(None).await;
214 return Outcome::Lost("nothing arrived for too long".to_owned());
215 }
216 },
217 }
218 }
219}
220
221fn handle(session: &mut Session, command: Command) -> Option<String> {
224 match command {
225 Command::Subscribe(subscription, reply) => match session.subscribe(subscription) {
226 Ok(frame) => {
227 let _ = reply.send(Ok(()));
228 frame
229 }
230 Err(refused) => {
231 let _ = reply.send(Err(refused));
232 None
233 }
234 },
235 Command::Unsubscribe(subscription) => session.unsubscribe(&subscription),
236 Command::Post(request, reply) => match session.post(&request) {
237 Ok((id, frame)) => {
238 let _ = reply.send(Ok(id));
239 Some(frame)
240 }
241 Err(refused) => {
242 let _ = reply.send(Err(refused));
243 None
244 }
245 },
246 }
247}
248
249async fn until(wake: Option<Instant>) {
250 match wake {
251 Some(at) => sleep_until(at).await,
252 None => std::future::pending().await,
253 }
254}
255
256fn random() -> u64 {
259 let nanos = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |since| u64::from(since.subsec_nanos()));
260 nanos.wrapping_mul(0x9e37_79b9_7f4a_7c15) ^ clock().rotate_left(29)
261}
262
263#[cfg(test)]
264#[path = "tests/socket.rs"]
265mod tests;