use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Weak};
use tokio::sync::mpsc;
use crate::cbor::Value;
use crate::frame::{self, PublicationSpec};
use crate::node_key::NodeKey;
use super::{now_ms, Inner, Link, LinkError};
const EVENT_BUFFER: usize = 64;
#[derive(Debug, Clone, PartialEq)]
pub struct Publication {
pub realm: [u8; 32],
pub topic: String,
pub payload: Value,
pub ttl_ms: Option<u64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
pub publisher: [u8; 32],
pub realm: [u8; 32],
pub topic: String,
pub seq: u64,
pub published_at: u64,
pub payload: Value,
pub delivered_via: String,
}
#[derive(Debug, Default)]
pub struct PublicationSeq {
last: Mutex<u64>,
}
impl PublicationSeq {
pub fn next(&self) -> u64 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or(0);
let mut last = self.last.lock().unwrap_or_else(|p| p.into_inner());
*last = now.max(*last + 1);
*last
}
}
#[derive(Debug, Default)]
pub struct EventDedup {
seen: Mutex<HashMap<[u8; 48], u64>>,
}
impl EventDedup {
fn first(&self, hash: [u8; 48], expires_at: u64, now_ms: i64) -> bool {
let mut seen = self.seen.lock().unwrap_or_else(|p| p.into_inner());
if seen.contains_key(&hash) {
return false;
}
seen.retain(|_, until| *until >= now_ms as u64);
seen.insert(hash, expires_at);
true
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SignedPublication {
frame: Value,
}
impl SignedPublication {
pub fn sign(
key: &NodeKey,
seq: &PublicationSeq,
p: Publication,
) -> Result<SignedPublication, LinkError> {
let frame = frame::sign_publish(
&PublicationSpec {
realm: p.realm,
topic: p.topic,
seq: seq.next(),
published_at: now_ms() as u64,
payload: p.payload,
ttl_ms: p.ttl_ms,
},
key,
)?;
Ok(SignedPublication { frame })
}
}
static NEXT_SUBSCRIBER: AtomicU64 = AtomicU64::new(1);
pub(super) struct SubscriberSlot {
id: u64,
events: mpsc::Sender<Event>,
}
pub struct Subscription {
link: Weak<Inner>,
key: ([u8; 32], String),
id: u64,
events: mpsc::Receiver<Event>,
unsubscribed: bool,
}
impl Subscription {
pub async fn recv(&mut self) -> Option<Event> {
self.events.recv().await
}
pub async fn unsubscribe(mut self) -> Result<(), LinkError> {
self.unsubscribed = true;
let Some(inner) = self.link.upgrade() else {
return Ok(());
};
if drop_subscriber(&inner, &self.key, self.id) {
let frame =
frame::unsubscribe_frame(self.key.1.as_bytes(), &self.key.0, &inner.self_id)?;
inner.send_control(&frame).await?;
}
Ok(())
}
}
impl Drop for Subscription {
fn drop(&mut self) {
if self.unsubscribed {
return;
}
let Some(inner) = self.link.upgrade() else {
return;
};
if !drop_subscriber(&inner, &self.key, self.id) {
return;
}
let key = self.key.clone();
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
runtime.spawn(async move {
if let Ok(frame) =
frame::unsubscribe_frame(key.1.as_bytes(), &key.0, &inner.self_id)
{
let _ = inner.send_control(&frame).await;
}
});
}
}
}
fn drop_subscriber(inner: &Inner, key: &([u8; 32], String), id: u64) -> bool {
let mut state = inner.lock();
let Some(slots) = state.subs.get_mut(key) else {
return false;
};
slots.retain(|s| s.id != id);
if slots.is_empty() {
state.subs.remove(key);
return true;
}
false
}
impl Link {
pub async fn publish(&self, p: Publication) -> Result<(), LinkError> {
let signed = SignedPublication::sign(&self.inner.key, &self.inner.publication_seq, p)?;
self.publish_signed(&signed).await
}
pub async fn publish_signed(&self, p: &SignedPublication) -> Result<(), LinkError> {
self.inner.write_control(&p.frame).await
}
pub async fn subscribe(
&self,
realm: &[u8; 32],
topic: &str,
) -> Result<Subscription, LinkError> {
let key = (*realm, topic.to_string());
let (events_tx, events) = mpsc::channel(EVENT_BUFFER);
let id = NEXT_SUBSCRIBER.fetch_add(1, Ordering::Relaxed);
let first = {
let mut state = self.inner.lock();
if let Some(e) = &state.ended {
return Err(e.clone());
}
let slots = state.subs.entry(key.clone()).or_default();
slots.push(SubscriberSlot {
id,
events: events_tx,
});
slots.len() == 1
};
let subscription = Subscription {
link: Arc::downgrade(&self.inner),
key: key.clone(),
id,
events,
unsubscribed: false,
};
if first {
let frame = frame::subscribe_frame(topic.as_bytes(), realm, &self.inner.self_id);
let sent = match frame {
Ok(frame) => self.inner.send_control(&frame).await,
Err(e) => Err(e.into()),
};
if let Err(e) = sent {
drop_subscriber(&self.inner, &key, id);
let mut subscription = subscription;
subscription.unsubscribed = true;
return Err(e);
}
}
Ok(subscription)
}
}
pub(super) fn evented(inner: &Arc<Inner>, v: &Value) {
let now = now_ms();
let Ok(publication) = frame::verify_publication(v, inner.profile, now) else {
inner.count("event_unverified");
return;
};
let delivered_via = match v.get("delivered_via") {
Some(Value::Text(t)) => t.clone(),
_ => String::new(),
};
if !inner
.dedup
.first(publication.publication_hash, publication.expires_at, now)
{
inner.count("event_duplicate");
return;
}
let event = Event {
publisher: publication.publisher,
realm: publication.realm,
topic: publication.topic.clone(),
seq: publication.seq,
published_at: publication.published_at,
payload: publication.payload,
delivered_via,
};
let mut state = inner.lock();
let Some(slots) = state.subs.get(&(publication.realm, publication.topic)) else {
*state
.unrouted
.entry("event_unsubscribed".into())
.or_default() += 1;
return;
};
let overflowed = slots
.iter()
.filter(|s| s.events.try_send(event.clone()).is_err())
.count();
if overflowed > 0 {
*state.unrouted.entry("event_overflow".into()).or_default() += overflowed as u64;
}
}