use exfiltrate_internal::command::Response;
use exfiltrate_internal::rpc::Event;
use std::collections::VecDeque;
use std::sync::LazyLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use wasm_lite_std::Mutex;
static SUBSCRIPTION_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
struct Subscriber {
id: u64,
topics: Vec<String>,
queue: VecDeque<Event>,
capacity: usize,
dropped: u64,
next_seq: u64,
}
static SUBSCRIBERS: LazyLock<Mutex<Vec<Subscriber>>> = LazyLock::new(|| Mutex::new(Vec::new()));
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn attach(capacity: usize) -> u64 {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed) as u64;
SUBSCRIBERS.with_mut_sync(|subscribers| {
subscribers.push(Subscriber {
id,
topics: Vec::new(),
queue: VecDeque::new(),
capacity: capacity.max(1),
dropped: 0,
next_seq: 0,
})
});
id
}
pub(crate) fn detach(id: u64) {
SUBSCRIBERS.with_mut_sync(|subscribers| {
if let Some(index) = subscribers.iter().position(|s| s.id == id) {
let removed = subscribers.remove(index);
SUBSCRIPTION_COUNT.fetch_sub(removed.topics.len(), Ordering::Relaxed);
}
});
}
pub(crate) fn subscribe(id: u64, topic: &str) -> Result<(), String> {
if topic.is_empty() {
return Err("a subscription topic must not be empty".to_string());
}
SUBSCRIBERS.with_mut_sync(|subscribers| {
let Some(subscriber) = subscribers.iter_mut().find(|s| s.id == id) else {
return Err("this connection is not attached to the event system".to_string());
};
if subscriber.topics.iter().any(|held| held == topic) {
return Err(format!("already subscribed to {topic:?}"));
}
subscriber.topics.push(topic.to_string());
SUBSCRIPTION_COUNT.fetch_add(1, Ordering::Relaxed);
Ok(())
})
}
pub(crate) fn unsubscribe(id: u64, topic: &str) -> Result<(), String> {
SUBSCRIBERS.with_mut_sync(|subscribers| {
let Some(subscriber) = subscribers.iter_mut().find(|s| s.id == id) else {
return Err("this connection is not attached to the event system".to_string());
};
let Some(index) = subscriber.topics.iter().position(|held| held == topic) else {
return Err(format!("not subscribed to {topic:?}"));
};
subscriber.topics.remove(index);
SUBSCRIPTION_COUNT.fetch_sub(1, Ordering::Relaxed);
Ok(())
})
}
pub(crate) fn anyone_listening() -> bool {
SUBSCRIPTION_COUNT.load(Ordering::Relaxed) > 0
}
pub(crate) fn publish(topic: &str, payload: Response) -> usize {
SUBSCRIBERS.with_mut_sync(|subscribers| {
let mut delivered = 0;
for subscriber in subscribers.iter_mut() {
if !subscriber
.topics
.iter()
.any(|pattern| exfiltrate_internal::topic::matches(pattern, topic))
{
continue;
}
while subscriber.queue.len() >= subscriber.capacity {
subscriber.queue.pop_front();
subscriber.dropped += 1;
}
let seq = subscriber.next_seq;
subscriber.next_seq += 1;
subscriber.queue.push_back(Event {
topic: topic.to_string(),
seq,
payload: payload.clone(),
dropped: subscriber.dropped,
});
delivered += 1;
}
delivered
})
}
pub(crate) fn drain(id: u64) -> Vec<Event> {
SUBSCRIBERS.with_mut_sync(
|subscribers| match subscribers.iter_mut().find(|s| s.id == id) {
Some(subscriber) => subscriber.queue.drain(..).collect(),
None => Vec::new(),
},
)
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn serialised() -> std::sync::MutexGuard<'static, ()> {
TEST_LOCK.lock().unwrap_or_else(|error| error.into_inner())
}
struct Attached(u64);
impl Attached {
fn new(capacity: usize) -> Attached {
Attached(attach(capacity))
}
}
impl Drop for Attached {
fn drop(&mut self) {
detach(self.0);
}
}
#[test]
fn nothing_is_delivered_to_a_connection_that_did_not_subscribe() {
let _serialised = serialised();
let connection = Attached::new(8);
publish("render.frame", Response::String("x".into()));
assert!(drain(connection.0).is_empty());
}
#[test]
fn a_prefix_subscription_takes_the_subtree_and_nothing_else() {
let _serialised = serialised();
let connection = Attached::new(8);
subscribe(connection.0, "render.*").unwrap();
publish("render.frame", Response::String("a".into()));
publish("renderer.stats", Response::String("b".into()));
publish("net.request", Response::String("c".into()));
let events = drain(connection.0);
assert_eq!(events.len(), 1);
assert_eq!(events[0].topic, "render.frame");
assert_eq!(events[0].seq, 0);
}
#[test]
fn a_full_queue_drops_the_oldest_and_says_how_many() {
let _serialised = serialised();
let connection = Attached::new(2);
subscribe(connection.0, "*").unwrap();
for index in 0..5 {
publish("t", Response::String(index.to_string()));
}
let events = drain(connection.0);
assert_eq!(events.len(), 2);
assert_eq!(events[0].payload, Response::String("3".into()));
assert_eq!(events[1].payload, Response::String("4".into()));
assert_eq!(events[1].dropped, 3);
}
#[test]
fn sequence_numbers_count_every_event_including_dropped_ones() {
let _serialised = serialised();
let connection = Attached::new(1);
subscribe(connection.0, "*").unwrap();
publish("t", Response::default());
publish("t", Response::default());
let events = drain(connection.0);
assert_eq!(events.len(), 1);
assert_eq!(events[0].seq, 1, "the surviving event is the second one");
}
#[test]
fn the_listening_flag_tracks_subscribe_unsubscribe_and_detach() {
let _serialised = serialised();
let connection = Attached::new(4);
let base = SUBSCRIPTION_COUNT.load(Ordering::Relaxed);
{
subscribe(connection.0, "a").unwrap();
subscribe(connection.0, "b").unwrap();
assert!(anyone_listening());
assert_eq!(SUBSCRIPTION_COUNT.load(Ordering::Relaxed), base + 2);
unsubscribe(connection.0, "a").unwrap();
assert_eq!(SUBSCRIPTION_COUNT.load(Ordering::Relaxed), base + 1);
}
drop(connection);
assert_eq!(SUBSCRIPTION_COUNT.load(Ordering::Relaxed), base);
}
#[test]
fn subscribing_twice_and_unsubscribing_from_nothing_are_both_reported() {
let _serialised = serialised();
let connection = Attached::new(4);
subscribe(connection.0, "dup").unwrap();
assert!(
subscribe(connection.0, "dup")
.unwrap_err()
.contains("already")
);
assert!(
unsubscribe(connection.0, "never")
.unwrap_err()
.contains("not subscribed")
);
}
#[test]
fn two_connections_get_independent_copies() {
let _serialised = serialised();
let first = Attached::new(8);
let second = Attached::new(8);
subscribe(first.0, "*").unwrap();
subscribe(second.0, "*").unwrap();
assert_eq!(publish("t", Response::String("shared".into())), 2);
assert_eq!(drain(first.0).len(), 1);
assert_eq!(drain(second.0).len(), 1);
}
}