liminal_sdk/remote/websocket/
subscription.rs1use alloc::format;
14use alloc::string::ToString;
15use alloc::sync::Arc;
16use alloc::vec::Vec;
17
18use core::time::Duration;
19
20use std::net::TcpStream;
21use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender};
22use std::thread::JoinHandle;
23
24use liminal::protocol::{Frame, SchemaId, decode};
25use liminal_protocol::outcome::ReconnectState;
26use spin::Mutex;
27
28use crate::SdkError;
29
30use super::binding::{AttemptFateOutcome, OpenRequestDecision, WebSocketAuthorityBinding};
31use super::connection_error;
32use super::core::{
33 DriverOutput, FrameCorrelation, ResponseExpectation, SocketCommand, SocketEvent,
34 WebSocketFrameDriver,
35};
36use super::liminal_ws_message_bound;
37use super::std_socket::{SocketRead, WsSocket};
38
39const CLIENT_MIN_VERSION: liminal::protocol::ProtocolVersion =
41 liminal::protocol::ProtocolVersion::new(1, 0);
42const CLIENT_MAX_VERSION: liminal::protocol::ProtocolVersion =
44 liminal::protocol::ProtocolVersion::new(1, 0);
45const SUBSCRIPTION_STREAM_ID: u32 = 1;
47const SUBSCRIBE_MAX_IN_FLIGHT: u32 = 1024;
49
50#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct WebSocketDeliveredMessage {
57 delivery_seq: u64,
58 schema_id: SchemaId,
59 payload: Vec<u8>,
60}
61
62impl WebSocketDeliveredMessage {
63 #[must_use]
65 pub const fn delivery_seq(&self) -> u64 {
66 self.delivery_seq
67 }
68
69 #[must_use]
71 pub const fn schema_id(&self) -> SchemaId {
72 self.schema_id
73 }
74
75 #[must_use]
77 pub fn payload(&self) -> &[u8] {
78 &self.payload
79 }
80
81 #[must_use]
83 pub fn into_payload(self) -> Vec<u8> {
84 self.payload
85 }
86}
87
88#[derive(Debug)]
91pub struct WebSocketSubscriptionStream {
92 shutdown: TcpStream,
94 subscription_id: u64,
95 inbound: Receiver<WebSocketDeliveredMessage>,
96 binding: Arc<Mutex<WebSocketAuthorityBinding>>,
97 reader: Option<JoinHandle<()>>,
98}
99
100impl WebSocketSubscriptionStream {
101 pub fn open(
113 address: &str,
114 channel: &str,
115 accepted_schemas: Vec<SchemaId>,
116 ) -> Result<Self, SdkError> {
117 let message_bound = liminal_ws_message_bound()?;
118 let mut binding = WebSocketAuthorityBinding::new();
119 match binding.request_open() {
120 OpenRequestDecision::Authorized { .. } => {}
121 OpenRequestDecision::Refused(refusal) => {
122 return Err(connection_error(&format!(
123 "client authority refused the subscription open: {refusal:?}"
124 )));
125 }
126 }
127 match Self::open_link(address, channel, accepted_schemas, message_bound) {
128 Ok((socket, driver, subscription_id, pending)) => {
129 match binding.connection_established() {
130 AttemptFateOutcome::Recorded { .. } => {}
131 AttemptFateOutcome::Refused(refusal) => {
132 return Err(SdkError::Protocol {
133 description: format!(
134 "client authority refused the Connected fate for the \
135 subscription open: {refusal:?}"
136 ),
137 });
138 }
139 }
140 Self::start(socket, driver, binding, subscription_id, pending)
141 }
142 Err(error) => match binding.open_failed() {
143 AttemptFateOutcome::Recorded { .. } => Err(error),
144 AttemptFateOutcome::Refused(refusal) => Err(SdkError::Protocol {
145 description: format!(
146 "subscription open failed ({error}) and the client authority \
147 refused the Failed fate: {refusal:?}"
148 ),
149 }),
150 },
151 }
152 }
153
154 pub fn recv_timeout(&self, timeout: Duration) -> Result<WebSocketDeliveredMessage, SdkError> {
161 self.inbound.recv_timeout(timeout).map_err(|error| {
162 let detail = match error {
163 RecvTimeoutError::Timeout => "no delivery arrived within the timeout",
164 RecvTimeoutError::Disconnected => {
165 "the subscription reader stopped before a delivery arrived"
166 }
167 };
168 connection_error(&format!("websocket subscription receive failed: {detail}"))
169 })
170 }
171
172 #[must_use]
174 pub const fn subscription_id(&self) -> u64 {
175 self.subscription_id
176 }
177
178 #[must_use]
180 pub fn reconnect_state(&self) -> ReconnectState {
181 self.binding.lock().reconnect_state()
182 }
183
184 fn open_link(
186 address: &str,
187 channel: &str,
188 accepted_schemas: Vec<SchemaId>,
189 message_bound: usize,
190 ) -> Result<
191 (
192 WsSocket,
193 WebSocketFrameDriver,
194 u64,
195 Vec<WebSocketDeliveredMessage>,
196 ),
197 SdkError,
198 > {
199 let mut driver = WebSocketFrameDriver::new();
200 let command = driver
201 .command_open()
202 .map_err(|refusal| SdkError::Protocol {
203 description: format!("subscription driver refused its first open: {refusal:?}"),
204 })?;
205 if command != SocketCommand::Open {
206 return Err(SdkError::Protocol {
207 description: "subscription driver emitted a non-open first command".to_string(),
208 });
209 }
210 let mut socket = WsSocket::connect(address, message_bound)?;
211 let step = driver.handle_event(SocketEvent::Opened);
212 if step.output != DriverOutput::Opened {
213 return Err(SdkError::Protocol {
214 description: format!("subscription driver refused the opened socket: {step:?}"),
215 });
216 }
217
218 let mut pending = Vec::new();
219 let connect = Frame::Connect {
220 flags: 0,
221 min_version: CLIENT_MIN_VERSION,
222 max_version: CLIENT_MAX_VERSION,
223 auth_token: Vec::new(),
224 };
225 match setup_exchange(&mut socket, &mut driver, &connect, &mut pending)? {
226 Frame::ConnectAck { .. } => {}
227 Frame::ConnectError {
228 reason_code,
229 message,
230 ..
231 } => {
232 return Err(connection_error(&format!(
233 "server rejected subscription connection (reason {reason_code}): {}",
234 message.unwrap_or_else(|| "no detail".to_string())
235 )));
236 }
237 other => {
238 return Err(unexpected_setup_frame("ConnectAck", &other));
239 }
240 }
241
242 let subscribe = Frame::Subscribe {
243 flags: 0,
244 stream_id: SUBSCRIPTION_STREAM_ID,
245 channel: channel.to_string(),
246 accepted_schemas,
247 max_in_flight: SUBSCRIBE_MAX_IN_FLIGHT,
248 };
249 let subscription_id =
250 match setup_exchange(&mut socket, &mut driver, &subscribe, &mut pending)? {
251 Frame::SubscribeAck {
252 subscription_id, ..
253 } => subscription_id,
254 Frame::SubscribeError {
255 reason_code,
256 message,
257 ..
258 } => {
259 return Err(SdkError::Protocol {
260 description: format!(
261 "server rejected subscribe (reason {reason_code}): {}",
262 message.unwrap_or_else(|| "no detail".to_string())
263 ),
264 });
265 }
266 other => {
267 return Err(unexpected_setup_frame("SubscribeAck", &other));
268 }
269 };
270 Ok((socket, driver, subscription_id, pending))
271 }
272
273 fn start(
275 socket: WsSocket,
276 driver: WebSocketFrameDriver,
277 binding: WebSocketAuthorityBinding,
278 subscription_id: u64,
279 pending: Vec<WebSocketDeliveredMessage>,
280 ) -> Result<Self, SdkError> {
281 socket.set_read_timeout(None)?;
284 let shutdown = socket.try_clone_stream()?;
285 let binding = Arc::new(Mutex::new(binding));
286 let reader_binding = Arc::clone(&binding);
287 let (sender, inbound) = mpsc::channel();
288 let reader = std::thread::Builder::new()
289 .name("liminal-ws-subscription-reader".to_string())
290 .spawn(move || run_reader(socket, driver, &reader_binding, pending, &sender))
291 .map_err(|source| SdkError::Protocol {
292 description: format!(
293 "failed to start websocket subscription reader thread: {source}"
294 ),
295 })?;
296 Ok(Self {
297 shutdown,
298 subscription_id,
299 inbound,
300 binding,
301 reader: Some(reader),
302 })
303 }
304}
305
306impl Drop for WebSocketSubscriptionStream {
307 fn drop(&mut self) {
308 self.shutdown.shutdown(std::net::Shutdown::Both).ok();
313 if let Some(reader) = self.reader.take() {
314 reader.join().ok();
315 }
316 }
317}
318
319fn setup_exchange(
322 socket: &mut WsSocket,
323 driver: &mut WebSocketFrameDriver,
324 request: &Frame,
325 pending: &mut Vec<WebSocketDeliveredMessage>,
326) -> Result<Frame, SdkError> {
327 let bytes = super::encode_frame(request)?;
328 let command = driver
329 .command_send(bytes, ResponseExpectation::Correlated)
330 .map_err(|refusal| SdkError::Protocol {
331 description: format!("subscription driver refused the setup send: {refusal:?}"),
332 })?;
333 let SocketCommand::SendBinary(payload) = command else {
334 return Err(SdkError::Protocol {
335 description: "subscription driver emitted a non-send command for a send".to_string(),
336 });
337 };
338 if let Err(failure) = socket.send_binary(payload) {
339 let step = driver.handle_event(SocketEvent::Failed(failure));
340 if step.command == Some(SocketCommand::Close) {
341 socket.execute_close();
342 }
343 return Err(connection_error(&format!(
344 "failed to send subscription setup frame: {}",
345 socket
346 .last_failure_detail()
347 .unwrap_or("websocket send failed")
348 )));
349 }
350 loop {
351 let event = match socket.read_event() {
352 SocketRead::TimedOut => {
353 return Err(connection_error(
354 "subscription connection timed out waiting for a control-frame reply",
355 ));
356 }
357 SocketRead::Event(event) => event,
358 };
359 let step = driver.handle_event(event);
360 if step.command == Some(SocketCommand::Close) {
361 socket.execute_close();
362 }
363 match step.output {
364 DriverOutput::Frame { bytes, correlation } => {
365 let frame = decode_message(&bytes)?;
366 match correlation {
367 FrameCorrelation::UnsolicitedDelivery => {
368 if let Some(message) = delivered_message(frame) {
369 pending.push(message);
370 }
371 }
372 FrameCorrelation::CorrelatedResponse | FrameCorrelation::UnsolicitedFrame => {
373 return Ok(frame);
374 }
375 }
376 }
377 DriverOutput::Terminal(terminal) => {
378 return Err(connection_error(&format!(
379 "subscription connection terminated during setup: {terminal:?}"
380 )));
381 }
382 DriverOutput::Opened
383 | DriverOutput::PostTerminalIgnored(_)
384 | DriverOutput::Refused(_) => {
385 return Err(SdkError::Protocol {
386 description: format!(
387 "subscription driver produced an unexpected setup output: {:?}",
388 step.output
389 ),
390 });
391 }
392 }
393 }
394}
395
396fn run_reader(
400 mut socket: WsSocket,
401 mut driver: WebSocketFrameDriver,
402 binding: &Mutex<WebSocketAuthorityBinding>,
403 pending: Vec<WebSocketDeliveredMessage>,
404 sender: &Sender<WebSocketDeliveredMessage>,
405) {
406 for message in pending {
407 if sender.send(message).is_err() {
408 close_link(&mut socket, &mut driver);
409 return;
410 }
411 }
412 loop {
413 let event = match socket.read_event() {
414 SocketRead::TimedOut => continue,
418 SocketRead::Event(event) => event,
419 };
420 let step = driver.handle_event(event);
421 if step.command == Some(SocketCommand::Close) {
422 socket.execute_close();
423 }
424 match step.output {
425 DriverOutput::Frame { bytes, correlation } => match correlation {
426 FrameCorrelation::UnsolicitedDelivery => {
427 let Ok(frame) = decode_message(&bytes) else {
428 close_link(&mut socket, &mut driver);
430 continue;
431 };
432 if let Some(message) = delivered_message(frame) {
433 if sender.send(message).is_err() {
434 close_link(&mut socket, &mut driver);
435 return;
436 }
437 }
438 }
439 FrameCorrelation::CorrelatedResponse | FrameCorrelation::UnsolicitedFrame => {
440 match decode_message(&bytes) {
441 Ok(Frame::Disconnect { .. }) => {
442 close_link(&mut socket, &mut driver);
446 }
447 Ok(_) => {}
451 Err(_) => {
452 close_link(&mut socket, &mut driver);
453 }
454 }
455 }
456 },
457 DriverOutput::Terminal(terminal) => {
458 let _outcome = binding.lock().established_terminal(&terminal);
461 return;
462 }
463 DriverOutput::PostTerminalIgnored(_) => return,
464 DriverOutput::Opened | DriverOutput::Refused(_) => {}
465 }
466 }
467}
468
469fn close_link(socket: &mut WsSocket, driver: &mut WebSocketFrameDriver) {
471 if driver.command_close().is_ok() {
472 socket.execute_close();
473 }
474}
475
476fn decode_message(bytes: &[u8]) -> Result<Frame, SdkError> {
478 match decode(bytes) {
479 Ok((frame, consumed)) if consumed == bytes.len() => Ok(frame),
480 Ok((_, consumed)) => Err(SdkError::Protocol {
481 description: format!(
482 "subscription decode consumed {consumed} of {} message bytes",
483 bytes.len()
484 ),
485 }),
486 Err(error) => Err(SdkError::Protocol {
487 description: format!("subscription wire codec error: {error}"),
488 }),
489 }
490}
491
492fn delivered_message(frame: Frame) -> Option<WebSocketDeliveredMessage> {
494 match frame {
495 Frame::Deliver {
496 delivery_seq,
497 envelope,
498 ..
499 } => Some(WebSocketDeliveredMessage {
500 delivery_seq,
501 schema_id: envelope.schema_id,
502 payload: envelope.payload,
503 }),
504 _ => None,
505 }
506}
507
508fn unexpected_setup_frame(expected: &str, actual: &Frame) -> SdkError {
510 SdkError::Protocol {
511 description: format!(
512 "expected {expected} during subscription setup, received {:?}",
513 actual.frame_type()
514 ),
515 }
516}