Boundary v0: WebSocket with heartbeats, acked commands and mock state
One socket on :7700 carries hello, heartbeats both ways (5 s timeout), 5 Hz state snapshots of envelopes, and idempotent commands. 'mark' is forwarded to the recorder's control socket. --mock synthesises degrading state flagged as mock in every envelope; without it a client gets hello and heartbeats only. Peers outside loopback, RFC 1918 and the tailnet are refused. Deployed on the car Orin as yukart-gw.service. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
+58
@@ -0,0 +1,58 @@
|
||||
//! Fan-out to clients. Drop-only: each client has a bounded queue that loses its
|
||||
//! oldest message when full, so a tablet with its screen off never stalls anything.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
const QUEUE: usize = 64;
|
||||
|
||||
pub struct Outbox {
|
||||
q: Mutex<VecDeque<String>>,
|
||||
cv: Condvar,
|
||||
}
|
||||
|
||||
impl Outbox {
|
||||
pub fn push(&self, msg: String) {
|
||||
let mut q = self.q.lock().unwrap();
|
||||
if q.len() >= QUEUE {
|
||||
q.pop_front();
|
||||
}
|
||||
q.push_back(msg);
|
||||
self.cv.notify_one();
|
||||
}
|
||||
|
||||
/// Everything queued, waiting at most `wait` for something to arrive.
|
||||
pub fn drain(&self, wait: Duration) -> Vec<String> {
|
||||
let q = self.q.lock().unwrap();
|
||||
let (mut q, _) = self.cv.wait_timeout_while(q, wait, |q| q.is_empty()).unwrap();
|
||||
q.drain(..).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Hub {
|
||||
clients: Arc<Mutex<Vec<Arc<Outbox>>>>,
|
||||
}
|
||||
|
||||
impl Hub {
|
||||
pub fn join(&self) -> Arc<Outbox> {
|
||||
let o = Arc::new(Outbox { q: Mutex::new(VecDeque::new()), cv: Condvar::new() });
|
||||
self.clients.lock().unwrap().push(o.clone());
|
||||
o
|
||||
}
|
||||
|
||||
pub fn leave(&self, o: &Arc<Outbox>) {
|
||||
self.clients.lock().unwrap().retain(|c| !Arc::ptr_eq(c, o));
|
||||
}
|
||||
|
||||
pub fn broadcast(&self, msg: &str) {
|
||||
for c in self.clients.lock().unwrap().iter() {
|
||||
c.push(msg.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.clients.lock().unwrap().len()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user