netget/server/nostr/
subscriptions.rs1use super::wire::{self, Event, Filter};
10use std::collections::HashMap;
11use std::sync::{Arc, Mutex};
12use tokio::sync::mpsc;
13
14#[derive(Debug)]
16pub enum Outbound {
17 Text(String),
19 Close { code: u16, reason: String },
21 Ping(Vec<u8>),
23 Shutdown,
25}
26
27#[derive(Debug)]
28struct Subscription {
29 filters: Vec<Filter>,
30 generation: u64,
31}
32
33#[derive(Debug, Default)]
34struct Subscriptions {
35 open: HashMap<String, Subscription>,
36 next_generation: u64,
37 answering: Option<(String, u64)>,
39}
40
41#[derive(Debug)]
43pub struct ConnShared {
44 pub connection_id: u32,
45 out_tx: mpsc::UnboundedSender<Outbound>,
46 subs: Mutex<Subscriptions>,
47}
48
49impl ConnShared {
50 pub fn new(connection_id: u32, out_tx: mpsc::UnboundedSender<Outbound>) -> Self {
51 Self {
52 connection_id,
53 out_tx,
54 subs: Mutex::new(Subscriptions::default()),
55 }
56 }
57
58 fn lock(&self) -> std::sync::MutexGuard<'_, Subscriptions> {
59 self.subs
60 .lock()
61 .unwrap_or_else(|poisoned| poisoned.into_inner())
62 }
63
64 pub fn send(&self, message: String) {
66 let _ = self.out_tx.send(Outbound::Text(message));
67 }
68
69 pub fn out_tx(&self) -> &mpsc::UnboundedSender<Outbound> {
70 &self.out_tx
71 }
72
73 pub fn open(&self, subscription_id: &str, filters: Vec<Filter>) -> Result<u64, String> {
76 let mut subs = self.lock();
77 if !subs.open.contains_key(subscription_id) && subs.open.len() >= wire::MAX_SUBSCRIPTIONS {
78 return Err(format!(
79 "rate-limited: at most {} open subscriptions per connection; CLOSE one first",
80 wire::MAX_SUBSCRIPTIONS
81 ));
82 }
83 subs.next_generation += 1;
84 let generation = subs.next_generation;
85 subs.open.insert(
86 subscription_id.to_string(),
87 Subscription {
88 filters,
89 generation,
90 },
91 );
92 Ok(generation)
93 }
94
95 pub fn close(&self, subscription_id: &str) -> bool {
97 self.lock().open.remove(subscription_id).is_some()
98 }
99
100 pub fn is_open(&self, subscription_id: &str, generation: u64) -> bool {
102 self.lock()
103 .open
104 .get(subscription_id)
105 .is_some_and(|s| s.generation == generation)
106 }
107
108 pub fn open_count(&self) -> usize {
109 self.lock().open.len()
110 }
111
112 pub fn begin_answer(&self, subscription_id: &str, generation: u64) {
114 self.lock().answering = Some((subscription_id.to_string(), generation));
115 }
116
117 pub fn end_answer(&self) {
118 self.lock().answering = None;
119 }
120
121 pub fn target(&self, named: Option<&str>) -> Result<(String, Vec<Filter>, bool), String> {
125 let subs = self.lock();
126 let answering = subs.answering.clone();
127 let id = match (named.filter(|n| !n.is_empty()), &answering) {
128 (Some(n), _) => n.to_string(),
129 (None, Some((id, _))) => id.clone(),
130 (None, None) => {
131 return Err(
132 "this action can only run while answering a nostr_req, or with the \
133 subscription_id of a subscription this connection has open"
134 .to_string(),
135 )
136 }
137 };
138 let Some(sub) = subs.open.get(&id) else {
139 return Err(format!(
140 "subscription {:?} is not open on this connection",
141 crate::utils::truncate_for_log(&id, 64)
142 ));
143 };
144 let apply_limit = answering
145 .as_ref()
146 .is_some_and(|(a, generation)| *a == id && *generation == sub.generation);
147 Ok((id, sub.filters.clone(), apply_limit))
148 }
149
150 pub fn matching(&self, event: &Event) -> Vec<String> {
152 self.lock()
153 .open
154 .iter()
155 .filter(|(_, s)| s.filters.iter().any(|f| f.matches(event)))
156 .map(|(id, _)| id.clone())
157 .collect()
158 }
159}
160
161#[derive(Debug, Default)]
163pub struct Relay {
164 connections: Mutex<HashMap<u32, Arc<ConnShared>>>,
165}
166
167impl Relay {
168 pub fn new() -> Self {
169 Self::default()
170 }
171
172 fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<u32, Arc<ConnShared>>> {
173 self.connections
174 .lock()
175 .unwrap_or_else(|poisoned| poisoned.into_inner())
176 }
177
178 pub fn add(&self, conn: Arc<ConnShared>) {
179 self.lock().insert(conn.connection_id, conn);
180 }
181
182 pub fn remove(&self, connection_id: u32) {
183 self.lock().remove(&connection_id);
184 }
185
186 pub fn broadcast(&self, event: &Event) -> usize {
189 let connections: Vec<Arc<ConnShared>> = self.lock().values().cloned().collect();
190 let mut delivered = 0;
191 for conn in connections {
192 for subscription_id in conn.matching(event) {
193 conn.send(wire::event_message(&subscription_id, event));
194 delivered += 1;
195 }
196 }
197 delivered
198 }
199}