macula_rust/station_link/
pubsub.rs1use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::{Arc, Mutex, Weak};
11
12use tokio::sync::mpsc;
13
14use crate::cbor::Value;
15use crate::frame::{self, PublicationSpec};
16use crate::node_key::NodeKey;
17
18use super::{now_ms, Inner, Link, LinkError};
19
20const EVENT_BUFFER: usize = 64;
23
24#[derive(Debug, Clone, PartialEq)]
27pub struct Publication {
28 pub realm: [u8; 32],
29 pub topic: String,
30 pub payload: Value,
31 pub ttl_ms: Option<u64>,
32}
33
34#[derive(Debug, Clone, PartialEq)]
38pub struct Event {
39 pub publisher: [u8; 32],
40 pub realm: [u8; 32],
41 pub topic: String,
42 pub seq: u64,
43 pub published_at: u64,
44 pub payload: Value,
45 pub delivered_via: String,
46}
47
48#[derive(Debug, Default)]
53pub struct PublicationSeq {
54 last: Mutex<u64>,
55}
56
57impl PublicationSeq {
58 pub fn next(&self) -> u64 {
60 let now = std::time::SystemTime::now()
61 .duration_since(std::time::UNIX_EPOCH)
62 .map(|d| d.as_micros() as u64)
63 .unwrap_or(0);
64 let mut last = self.last.lock().unwrap_or_else(|p| p.into_inner());
65 *last = now.max(*last + 1);
66 *last
67 }
68}
69
70#[derive(Debug, Default)]
74pub struct EventDedup {
75 seen: Mutex<HashMap<[u8; 48], u64>>,
76}
77
78impl EventDedup {
79 fn first(&self, hash: [u8; 48], expires_at: u64, now_ms: i64) -> bool {
81 let mut seen = self.seen.lock().unwrap_or_else(|p| p.into_inner());
82 if seen.contains_key(&hash) {
83 return false;
84 }
85 seen.retain(|_, until| *until >= now_ms as u64);
86 seen.insert(hash, expires_at);
87 true
88 }
89}
90
91#[derive(Debug, Clone, PartialEq)]
95pub struct SignedPublication {
96 frame: Value,
97}
98
99impl SignedPublication {
100 pub fn sign(
102 key: &NodeKey,
103 seq: &PublicationSeq,
104 p: Publication,
105 ) -> Result<SignedPublication, LinkError> {
106 let frame = frame::sign_publish(
107 &PublicationSpec {
108 realm: p.realm,
109 topic: p.topic,
110 seq: seq.next(),
111 published_at: now_ms() as u64,
112 payload: p.payload,
113 ttl_ms: p.ttl_ms,
114 },
115 key,
116 )?;
117 Ok(SignedPublication { frame })
118 }
119}
120
121static NEXT_SUBSCRIBER: AtomicU64 = AtomicU64::new(1);
122
123pub(super) struct SubscriberSlot {
125 id: u64,
126 events: mpsc::Sender<Event>,
127}
128
129pub(super) fn subscribe_refused(inner: &Inner, key: &([u8; 32], String)) {
132 inner.lock().subs.remove(key);
133}
134
135pub struct Subscription {
138 link: Weak<Inner>,
139 key: ([u8; 32], String),
140 id: u64,
141 events: mpsc::Receiver<Event>,
142 unsubscribed: bool,
143}
144
145impl Subscription {
146 pub async fn recv(&mut self) -> Option<Event> {
148 self.events.recv().await
149 }
150
151 pub async fn unsubscribe(mut self) -> Result<(), LinkError> {
154 self.unsubscribed = true;
155 let Some(inner) = self.link.upgrade() else {
156 return Ok(());
157 };
158 if drop_subscriber(&inner, &self.key, self.id) {
159 let frame =
160 frame::unsubscribe_frame(self.key.1.as_bytes(), &self.key.0, &inner.self_id)?;
161 inner.send_control(&frame).await?;
162 }
163 Ok(())
164 }
165}
166
167impl Drop for Subscription {
168 fn drop(&mut self) {
170 if self.unsubscribed {
171 return;
172 }
173 let Some(inner) = self.link.upgrade() else {
174 return;
175 };
176 if !drop_subscriber(&inner, &self.key, self.id) {
177 return;
178 }
179 let key = self.key.clone();
180 if let Ok(runtime) = tokio::runtime::Handle::try_current() {
181 runtime.spawn(unsubscribe_dropped(inner, key));
182 }
183 }
184}
185
186async fn unsubscribe_dropped(inner: Arc<Inner>, key: ([u8; 32], String)) {
189 if let Ok(frame) = frame::unsubscribe_frame(key.1.as_bytes(), &key.0, &inner.self_id) {
190 let _ = inner.send_control(&frame).await;
191 }
192}
193
194fn add_subscriber(
197 inner: &Inner,
198 key: &([u8; 32], String),
199 id: u64,
200 events: mpsc::Sender<Event>,
201) -> Result<bool, LinkError> {
202 let mut state = inner.lock();
203 if let Some(e) = &state.ended {
204 return Err(e.clone());
205 }
206 let slots = state.subs.entry(key.clone()).or_default();
207 slots.push(SubscriberSlot { id, events });
208 Ok(slots.len() == 1)
209}
210
211async fn send_subscribe(inner: &Inner, realm: &[u8; 32], topic: &str) -> Result<(), LinkError> {
213 let frame = frame::subscribe_frame(topic.as_bytes(), realm, &inner.self_id);
214 match frame {
215 Ok(frame) => inner.send_control(&frame).await,
216 Err(e) => Err(e.into()),
217 }
218}
219
220fn drop_subscriber(inner: &Inner, key: &([u8; 32], String), id: u64) -> bool {
222 let mut state = inner.lock();
223 let Some(slots) = state.subs.get_mut(key) else {
224 return false;
225 };
226 slots.retain(|s| s.id != id);
227 if slots.is_empty() {
228 state.subs.remove(key);
229 return true;
230 }
231 false
232}
233
234impl Link {
235 pub async fn publish(&self, p: Publication) -> Result<(), LinkError> {
237 let signed = SignedPublication::sign(&self.inner.key, &self.inner.publication_seq, p)?;
238 self.publish_signed(&signed).await
239 }
240
241 pub async fn publish_signed(&self, p: &SignedPublication) -> Result<(), LinkError> {
243 self.inner.send_control(&p.frame).await
244 }
245
246 pub async fn subscribe(
249 &self,
250 realm: &[u8; 32],
251 topic: &str,
252 ) -> Result<Subscription, LinkError> {
253 let key = (*realm, topic.to_string());
254 let (events_tx, events) = mpsc::channel(EVENT_BUFFER);
255 let id = NEXT_SUBSCRIBER.fetch_add(1, Ordering::Relaxed);
256 let first = add_subscriber(&self.inner, &key, id, events_tx)?;
257 let subscription = Subscription {
258 link: Arc::downgrade(&self.inner),
259 key: key.clone(),
260 id,
261 events,
262 unsubscribed: false,
263 };
264 let sent = if first {
265 send_subscribe(&self.inner, realm, topic).await
266 } else {
267 Ok(())
268 };
269 if let Err(e) = sent {
270 drop_subscriber(&self.inner, &key, id);
271 let mut subscription = subscription;
272 subscription.unsubscribed = true;
273 return Err(e);
274 }
275 Ok(subscription)
276 }
277}
278
279pub(super) fn evented(inner: &Arc<Inner>, v: &Value) {
282 let now = now_ms();
283 let Ok(publication) = frame::verify_publication(v, inner.profile, now) else {
284 inner.count("event_unverified");
285 return;
286 };
287 let delivered_via = frame::delivered_via(v).to_string();
288 if !inner
289 .dedup
290 .first(publication.publication_hash, publication.expires_at, now)
291 {
292 inner.count("event_duplicate");
293 return;
294 }
295 let event = Event {
296 publisher: publication.publisher,
297 realm: publication.realm,
298 topic: publication.topic.clone(),
299 seq: publication.seq,
300 published_at: publication.published_at,
301 payload: publication.payload,
302 delivered_via,
303 };
304 let mut state = inner.lock();
305 let Some(slots) = state.subs.get(&(publication.realm, publication.topic)) else {
306 *state
307 .unrouted
308 .entry("event_unsubscribed".into())
309 .or_default() += 1;
310 return;
311 };
312 let overflowed = slots
313 .iter()
314 .filter(|s| s.events.try_send(event.clone()).is_err())
315 .count();
316 if overflowed > 0 {
317 *state.unrouted.entry("event_overflow".into()).or_default() += overflowed as u64;
318 }
319}