use super::wire::{self, Event, Filter};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
#[derive(Debug)]
pub enum Outbound {
Text(String),
Close { code: u16, reason: String },
Ping(Vec<u8>),
Shutdown,
}
#[derive(Debug)]
struct Subscription {
filters: Vec<Filter>,
generation: u64,
}
#[derive(Debug, Default)]
struct Subscriptions {
open: HashMap<String, Subscription>,
next_generation: u64,
answering: Option<(String, u64)>,
}
#[derive(Debug)]
pub struct ConnShared {
pub connection_id: u32,
out_tx: mpsc::UnboundedSender<Outbound>,
subs: Mutex<Subscriptions>,
}
impl ConnShared {
pub fn new(connection_id: u32, out_tx: mpsc::UnboundedSender<Outbound>) -> Self {
Self {
connection_id,
out_tx,
subs: Mutex::new(Subscriptions::default()),
}
}
fn lock(&self) -> std::sync::MutexGuard<'_, Subscriptions> {
self.subs
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub fn send(&self, message: String) {
let _ = self.out_tx.send(Outbound::Text(message));
}
pub fn out_tx(&self) -> &mpsc::UnboundedSender<Outbound> {
&self.out_tx
}
pub fn open(&self, subscription_id: &str, filters: Vec<Filter>) -> Result<u64, String> {
let mut subs = self.lock();
if !subs.open.contains_key(subscription_id) && subs.open.len() >= wire::MAX_SUBSCRIPTIONS {
return Err(format!(
"rate-limited: at most {} open subscriptions per connection; CLOSE one first",
wire::MAX_SUBSCRIPTIONS
));
}
subs.next_generation += 1;
let generation = subs.next_generation;
subs.open.insert(
subscription_id.to_string(),
Subscription {
filters,
generation,
},
);
Ok(generation)
}
pub fn close(&self, subscription_id: &str) -> bool {
self.lock().open.remove(subscription_id).is_some()
}
pub fn is_open(&self, subscription_id: &str, generation: u64) -> bool {
self.lock()
.open
.get(subscription_id)
.is_some_and(|s| s.generation == generation)
}
pub fn open_count(&self) -> usize {
self.lock().open.len()
}
pub fn begin_answer(&self, subscription_id: &str, generation: u64) {
self.lock().answering = Some((subscription_id.to_string(), generation));
}
pub fn end_answer(&self) {
self.lock().answering = None;
}
pub fn target(&self, named: Option<&str>) -> Result<(String, Vec<Filter>, bool), String> {
let subs = self.lock();
let answering = subs.answering.clone();
let id = match (named.filter(|n| !n.is_empty()), &answering) {
(Some(n), _) => n.to_string(),
(None, Some((id, _))) => id.clone(),
(None, None) => {
return Err(
"this action can only run while answering a nostr_req, or with the \
subscription_id of a subscription this connection has open"
.to_string(),
)
}
};
let Some(sub) = subs.open.get(&id) else {
return Err(format!(
"subscription {:?} is not open on this connection",
crate::utils::truncate_for_log(&id, 64)
));
};
let apply_limit = answering
.as_ref()
.is_some_and(|(a, generation)| *a == id && *generation == sub.generation);
Ok((id, sub.filters.clone(), apply_limit))
}
pub fn matching(&self, event: &Event) -> Vec<String> {
self.lock()
.open
.iter()
.filter(|(_, s)| s.filters.iter().any(|f| f.matches(event)))
.map(|(id, _)| id.clone())
.collect()
}
}
#[derive(Debug, Default)]
pub struct Relay {
connections: Mutex<HashMap<u32, Arc<ConnShared>>>,
}
impl Relay {
pub fn new() -> Self {
Self::default()
}
fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<u32, Arc<ConnShared>>> {
self.connections
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub fn add(&self, conn: Arc<ConnShared>) {
self.lock().insert(conn.connection_id, conn);
}
pub fn remove(&self, connection_id: u32) {
self.lock().remove(&connection_id);
}
pub fn broadcast(&self, event: &Event) -> usize {
let connections: Vec<Arc<ConnShared>> = self.lock().values().cloned().collect();
let mut delivered = 0;
for conn in connections {
for subscription_id in conn.matching(event) {
conn.send(wire::event_message(&subscription_id, event));
delivered += 1;
}
}
delivered
}
}