1#![cfg(all(not(target_arch = "wasm32"), feature = "relay"))]
19
20use std::collections::HashMap;
21use std::net::SocketAddr;
22use std::sync::atomic::{AtomicU64, Ordering};
23use std::sync::Arc;
24
25use futures::{SinkExt, StreamExt};
26use tokio::net::{TcpListener, TcpStream};
27use tokio::sync::{mpsc, Mutex};
28use tokio_tungstenite::tungstenite::Message;
29
30pub use crate::relay_proto::RelayFrame;
31
32fn encode(frame: &RelayFrame) -> Message {
34 Message::Binary(frame.to_bytes())
35}
36fn decode(bytes: &[u8]) -> Option<RelayFrame> {
37 RelayFrame::from_bytes(bytes)
38}
39
40type ConnId = u64;
41
42#[derive(Clone)]
47struct Hub {
48 inner: Arc<Mutex<HashMap<String, Vec<(ConnId, mpsc::UnboundedSender<RelayFrame>)>>>>,
49 gossip_out: Option<mpsc::UnboundedSender<(String, Vec<u8>)>>,
50}
51
52impl Hub {
53 fn new(gossip_out: Option<mpsc::UnboundedSender<(String, Vec<u8>)>>) -> Self {
54 Hub { inner: Arc::new(Mutex::new(HashMap::new())), gossip_out }
55 }
56
57 async fn subscribe(&self, conn: ConnId, topic: String, tx: mpsc::UnboundedSender<RelayFrame>) {
58 let mut map = self.inner.lock().await;
59 let subs = map.entry(topic).or_default();
60 if !subs.iter().any(|(c, _)| *c == conn) {
61 subs.push((conn, tx));
62 }
63 }
64
65 async fn unsubscribe(&self, conn: ConnId, topic: &str) {
66 let mut map = self.inner.lock().await;
67 if let Some(subs) = map.get_mut(topic) {
68 subs.retain(|(c, _)| *c != conn);
69 }
70 }
71
72 async fn deliver_except(&self, topic: &str, data: &[u8], except: Option<ConnId>) -> usize {
78 let mut map = self.inner.lock().await;
79 let Some(subs) = map.get_mut(topic) else { return 0 };
80 let event = RelayFrame::Event { topic: topic.to_string(), data: data.to_vec() };
81 subs.retain(|(conn, tx)| {
82 if Some(*conn) == except {
83 return true; }
85 tx.send(event.clone()).is_ok()
86 });
87 subs.iter().filter(|(conn, _)| Some(*conn) != except).count()
88 }
89
90 async fn deliver(&self, topic: &str, data: &[u8]) -> usize {
93 self.deliver_except(topic, data, None).await
94 }
95
96 async fn publish(&self, topic: &str, data: &[u8], from: ConnId) -> usize {
99 if let Some(gossip) = &self.gossip_out {
100 let _ = gossip.send((topic.to_string(), data.to_vec()));
101 }
102 self.deliver_except(topic, data, Some(from)).await
103 }
104
105 async fn drop_conn(&self, conn: ConnId) {
106 let mut map = self.inner.lock().await;
107 for subs in map.values_mut() {
108 subs.retain(|(c, _)| *c != conn);
109 }
110 }
111}
112
113pub struct RelayServer {
115 addr: SocketAddr,
116 hub: Hub,
117 accept: tokio::task::JoinHandle<()>,
118}
119
120impl RelayServer {
121 pub fn addr(&self) -> SocketAddr {
123 self.addr
124 }
125
126 pub fn url(&self) -> String {
128 format!("ws://{}", self.addr)
129 }
130
131 pub async fn inject(&self, topic: &str, data: &[u8]) -> usize {
135 self.hub.deliver(topic, data).await
136 }
137}
138
139impl Drop for RelayServer {
140 fn drop(&mut self) {
141 self.accept.abort();
142 }
143}
144
145pub async fn serve(addr: &str) -> std::io::Result<RelayServer> {
148 serve_inner(addr, Hub::new(None)).await
149}
150
151#[cfg(feature = "networking")]
161pub async fn serve_bridged(addr: &str, topics: Vec<String>) -> std::io::Result<RelayServer> {
162 let (gtx, mut grx) = mpsc::unbounded_channel::<(String, Vec<u8>)>();
164 let server = serve_inner(addr, Hub::new(Some(gtx))).await?;
165 tokio::spawn(async move {
166 while let Some((topic, data)) = grx.recv().await {
167 let _ = crate::network::gossip::publish_raw(&topic, data).await;
168 }
169 });
170 for topic in topics {
173 let hub = server.hub.clone();
174 let mut rx = crate::network::gossip::subscribe(&topic).await;
175 tokio::spawn(async move {
176 while let Some(data) = rx.recv().await {
177 hub.deliver(&topic, &data).await;
178 }
179 });
180 }
181 Ok(server)
182}
183
184async fn serve_inner(addr: &str, hub: Hub) -> std::io::Result<RelayServer> {
185 let listener = TcpListener::bind(addr).await?;
186 let addr = listener.local_addr()?;
187 let hub_for_loop = hub.clone();
188 let next_id = Arc::new(AtomicU64::new(0));
189 let accept = tokio::spawn(async move {
190 while let Ok((stream, _peer)) = listener.accept().await {
191 let hub = hub_for_loop.clone();
192 let id = next_id.fetch_add(1, Ordering::Relaxed);
193 tokio::spawn(async move {
194 let _ = handle_conn(stream, hub, id).await;
195 });
196 }
197 });
198 Ok(RelayServer { addr, hub, accept })
199}
200
201async fn handle_conn(stream: TcpStream, hub: Hub, id: ConnId) -> Result<(), ()> {
202 let ws = tokio_tungstenite::accept_async(stream).await.map_err(|_| ())?;
203 let (mut write, mut read) = ws.split();
204
205 let (out_tx, mut out_rx) = mpsc::unbounded_channel::<RelayFrame>();
208 let writer = tokio::spawn(async move {
209 while let Some(frame) = out_rx.recv().await {
210 if write.send(encode(&frame)).await.is_err() {
211 break;
212 }
213 }
214 });
215
216 while let Some(Ok(msg)) = read.next().await {
217 let bytes = match msg {
218 Message::Binary(b) => b,
219 Message::Text(t) => t.into_bytes(),
220 Message::Close(_) => break,
221 _ => continue,
222 };
223 let Some(frame) = decode(&bytes) else { continue };
224 match frame {
225 RelayFrame::Subscribe { topic } => {
226 hub.subscribe(id, topic.clone(), out_tx.clone()).await;
227 let _ = out_tx.send(RelayFrame::SubAck { topic });
228 }
229 RelayFrame::Unsubscribe { topic } => hub.unsubscribe(id, &topic).await,
230 RelayFrame::Publish { topic, data } => {
231 hub.publish(&topic, &data, id).await;
232 }
233 RelayFrame::SubAck { .. } | RelayFrame::Event { .. } => {}
235 }
236 }
237
238 hub.drop_conn(id).await;
239 writer.abort();
240 Ok(())
241}
242
243pub struct RelayClient {
246 out: mpsc::UnboundedSender<RelayFrame>,
247 events: mpsc::UnboundedReceiver<RelayFrame>,
248 acks: mpsc::UnboundedReceiver<String>,
249 _pump: tokio::task::JoinHandle<()>,
250}
251
252impl RelayClient {
253 pub async fn connect(url: &str) -> Result<RelayClient, String> {
255 let (ws, _) = tokio_tungstenite::connect_async(url)
256 .await
257 .map_err(|e| format!("relay dial failed: {e}"))?;
258 let (mut write, mut read) = ws.split();
259
260 let (out_tx, mut out_rx) = mpsc::unbounded_channel::<RelayFrame>();
261 let (ev_tx, ev_rx) = mpsc::unbounded_channel::<RelayFrame>();
262 let (ack_tx, ack_rx) = mpsc::unbounded_channel::<String>();
263
264 let pump = tokio::spawn(async move {
267 loop {
268 tokio::select! {
269 out = out_rx.recv() => match out {
270 Some(frame) => {
271 if write.send(encode(&frame)).await.is_err() { break; }
272 }
273 None => break,
274 },
275 inb = read.next() => match inb {
276 Some(Ok(Message::Binary(b))) => {
277 if let Some(frame) = decode(&b) {
278 match frame {
279 RelayFrame::SubAck { topic } => { let _ = ack_tx.send(topic); }
280 ev @ RelayFrame::Event { .. } => { let _ = ev_tx.send(ev); }
281 _ => {}
282 }
283 }
284 }
285 Some(Ok(Message::Close(_))) | None => break,
286 Some(Ok(_)) => {}
287 Some(Err(_)) => break,
288 },
289 }
290 }
291 });
292
293 Ok(RelayClient { out: out_tx, events: ev_rx, acks: ack_rx, _pump: pump })
294 }
295
296 pub async fn subscribe(&mut self, topic: &str) -> Result<(), String> {
299 self.out
300 .send(RelayFrame::Subscribe { topic: topic.to_string() })
301 .map_err(|_| "relay connection closed".to_string())?;
302 loop {
303 match self.acks.recv().await {
304 Some(t) if t == topic => return Ok(()),
305 Some(_) => continue,
306 None => return Err("relay closed before subscribe ack".to_string()),
307 }
308 }
309 }
310
311 pub fn publish(&self, topic: &str, data: Vec<u8>) -> Result<(), String> {
313 self.out
314 .send(RelayFrame::Publish { topic: topic.to_string(), data })
315 .map_err(|_| "relay connection closed".to_string())
316 }
317
318 pub async fn next_event(&mut self) -> Option<(String, Vec<u8>)> {
320 match self.events.recv().await {
321 Some(RelayFrame::Event { topic, data }) => Some((topic, data)),
322 _ => None,
323 }
324 }
325
326 pub fn drain_events(&mut self) -> Vec<(String, Vec<u8>)> {
329 let mut out = Vec::new();
330 while let Ok(RelayFrame::Event { topic, data }) = self.events.try_recv() {
331 out.push((topic, data));
332 }
333 out
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[tokio::test]
344 async fn relay_pubsub_roundtrip() {
345 let relay = serve("127.0.0.1:0").await.expect("relay binds");
346 let url = relay.url();
347
348 let mut sub = RelayClient::connect(&url).await.expect("subscriber dials");
349 let pubr = RelayClient::connect(&url).await.expect("publisher dials");
350
351 sub.subscribe("chat").await.expect("subscribe acked");
352 pubr.publish("chat", b"hello".to_vec()).expect("publish");
353
354 let (topic, data) = tokio::time::timeout(std::time::Duration::from_secs(5), sub.next_event())
355 .await
356 .expect("event arrives in time")
357 .expect("event present");
358 assert_eq!(topic, "chat");
359 assert_eq!(data, b"hello");
360 }
361
362 #[tokio::test]
365 async fn relay_topics_are_isolated() {
366 let relay = serve("127.0.0.1:0").await.expect("relay binds");
367 let url = relay.url();
368 let mut a = RelayClient::connect(&url).await.unwrap();
369 let b = RelayClient::connect(&url).await.unwrap();
370
371 a.subscribe("alpha").await.unwrap();
372 b.publish("beta", b"nope".to_vec()).unwrap(); b.publish("alpha", b"yes".to_vec()).unwrap();
374
375 let (topic, data) = tokio::time::timeout(std::time::Duration::from_secs(5), a.next_event())
376 .await
377 .expect("event arrives")
378 .unwrap();
379 assert_eq!(topic, "alpha");
382 assert_eq!(data, b"yes");
383 }
384
385 #[tokio::test]
388 async fn relay_inject_reaches_subscribers() {
389 let relay = serve("127.0.0.1:0").await.expect("relay binds");
390 let mut sub = RelayClient::connect(&relay.url()).await.unwrap();
391 sub.subscribe("mesh").await.unwrap();
392
393 let delivered = relay.inject("mesh", b"from-gossip").await;
394 assert_eq!(delivered, 1, "one WS subscriber received the injected message");
395
396 let (topic, data) = tokio::time::timeout(std::time::Duration::from_secs(5), sub.next_event())
397 .await
398 .expect("event arrives")
399 .unwrap();
400 assert_eq!(topic, "mesh");
401 assert_eq!(data, b"from-gossip");
402 }
403}