Skip to main content

logicaffeine_system/
relay_proto.rs

1//! The relay wire protocol — shared verbatim by the native server/client
2//! (`super::relay`) and the browser client (`super::relay_browser`). Both
3//! targets serialize the SAME [`RelayFrame`] with `bincode`, so a browser tab and
4//! a native node speak an identical language across the WebSocket.
5//!
6//! This module is target-agnostic (no `tokio`, no `web-sys`) so it compiles into
7//! both the native binary and the wasm bundle.
8
9use serde::{Deserialize, Serialize};
10
11/// A frame on the relay WebSocket. Client→relay: `Subscribe`/`Unsubscribe`/
12/// `Publish`. Relay→client: `SubAck`/`Event`.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum RelayFrame {
15    /// Client → relay: start receiving `Event`s for `topic`.
16    Subscribe { topic: String },
17    /// Client → relay: stop receiving `topic`.
18    Unsubscribe { topic: String },
19    /// Client → relay: deliver `data` to every subscriber of `topic`.
20    Publish { topic: String, data: Vec<u8> },
21    /// Relay → client: acknowledges a `Subscribe` is registered (so a publish
22    /// that races a subscribe cannot be missed — the client awaits this).
23    SubAck { topic: String },
24    /// Relay → client: a published message on a topic the client subscribes to.
25    Event { topic: String, data: Vec<u8> },
26}
27
28impl RelayFrame {
29    /// Serialize to the bytes that cross the WebSocket (a binary message).
30    pub fn to_bytes(&self) -> Vec<u8> {
31        bincode::serialize(self).expect("relay frame is serializable")
32    }
33
34    /// Deserialize a frame from WebSocket bytes; `None` on a malformed frame.
35    pub fn from_bytes(bytes: &[u8]) -> Option<RelayFrame> {
36        bincode::deserialize(bytes).ok()
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn frames_round_trip_through_bytes() {
46        for frame in [
47            RelayFrame::Subscribe { topic: "chat".into() },
48            RelayFrame::Unsubscribe { topic: "chat".into() },
49            RelayFrame::Publish { topic: "chat".into(), data: vec![1, 2, 3] },
50            RelayFrame::SubAck { topic: "chat".into() },
51            RelayFrame::Event { topic: "chat".into(), data: vec![9] },
52        ] {
53            let bytes = frame.to_bytes();
54            assert_eq!(RelayFrame::from_bytes(&bytes), Some(frame));
55        }
56    }
57}