notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit c4cf157a127d5090f9e39f1f3bb7cb099bad200f
parent 0f360fa22a6177a388da38616d5904ae6ec0f8d1
Author: kernelkind <kernelkind@gmail.com>
Date:   Sun,  1 Feb 2026 21:37:03 -0500

feat(outbox): add relay limit objects

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Acrates/enostr/src/relay/limits.rs | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/enostr/src/relay/mod.rs | 4++++
2 files changed, 145 insertions(+), 0 deletions(-)

diff --git a/crates/enostr/src/relay/limits.rs b/crates/enostr/src/relay/limits.rs @@ -0,0 +1,141 @@ +/// Limitations imposed by the relay +pub struct RelayLimitations { + // corresponds to NIP-11 `max_subscriptions` + pub maximum_subs: usize, + + // corresponds to NIP-11 `max_message_length` + pub max_json_bytes: usize, +} + +impl Default for RelayLimitations { + fn default() -> Self { + Self { + maximum_subs: 10, + max_json_bytes: 400_000, + } + } +} + +pub struct RelayCoordinatorLimits { + pub sub_guardian: SubPassGuardian, + pub max_json_bytes: usize, +} + +impl RelayCoordinatorLimits { + pub fn new(limits: RelayLimitations) -> Self { + Self { + max_json_bytes: limits.max_json_bytes, + sub_guardian: SubPassGuardian::new(limits.maximum_subs), + } + } + + pub fn new_total(&mut self, new_max: usize) -> Option<Vec<SubPassRevocation>> { + let old = self.sub_guardian.total_passes; + + if new_max == old { + return None; + } + + if new_max > old { + let add = new_max - old; + self.sub_guardian.spawn_passes(add); + self.sub_guardian.total_passes = new_max; + return None; + } + + // new_max < old + let remove = old - new_max; + self.sub_guardian.total_passes = new_max; + + let mut pending = Vec::new(); + + for _ in 0..remove { + let mut revocation = SubPassRevocation::new(); + if let Some(pass) = self.sub_guardian.available_passes.pop() { + // can revoke immediately -> do NOT return a revocation object for it + revocation.revocate(pass); + } else { + // can't revoke now -> return a revocation object to be fulfilled later + pending.push(revocation); + } + } + + if pending.is_empty() { + None + } else { + Some(pending) + } + } +} + +pub struct SubPassGuardian { + total_passes: usize, + available_passes: Vec<SubPass>, +} + +impl SubPassGuardian { + pub(crate) fn new(max_subs: usize) -> Self { + Self { + available_passes: (0..max_subs) + .map(|_| SubPass { _private: () }) + .collect::<Vec<_>>(), + total_passes: max_subs, + } + } + + pub fn take_pass(&mut self) -> Option<SubPass> { + self.available_passes.pop() + } + + pub fn available_passes(&self) -> usize { + self.available_passes.len() + } + + pub fn total_passes(&self) -> usize { + self.total_passes + } + + pub fn return_pass(&mut self, pass: SubPass) { + self.available_passes.push(pass); + tracing::debug!( + "Returned pass. Using {} of {} passes", + self.total_passes - self.available_passes(), + self.total_passes + ); + } + + pub(crate) fn spawn_passes(&mut self, new_passes: usize) { + for _ in 0..new_passes { + self.available_passes.push(SubPass { _private: () }); + } + } +} + +/// Annihilates an existing `SubPass`. These should only be generated from the `RelayCoordinatorLimits` +/// when there is a new total subs which is less than the existing amount +pub struct SubPassRevocation { + revoked: bool, +} + +impl SubPassRevocation { + pub fn revocate(&mut self, _: SubPass) { + self.revoked = true; + } + + pub(crate) fn new() -> Self { + Self { revoked: false } + } +} + +/// It completely breaks subscription management if we don't have strict accounting, so we crash if we fail to revocate +impl Drop for SubPassRevocation { + fn drop(&mut self) { + if !self.revoked { + panic!("The subscription pass revocator did not revoke the SubPass"); + } + } +} + +pub struct SubPass { + _private: (), +} diff --git a/crates/enostr/src/relay/mod.rs b/crates/enostr/src/relay/mod.rs @@ -1,4 +1,5 @@ mod identity; +mod limits; pub mod message; mod multicast; pub mod pool; @@ -8,6 +9,9 @@ mod websocket; pub use identity::{ NormRelayUrl, OutboxSubId, RelayId, RelayReqId, RelayReqStatus, RelayType, RelayUrlPkgs, }; +pub use limits::{ + RelayCoordinatorLimits, RelayLimitations, SubPass, SubPassGuardian, SubPassRevocation, +}; pub use websocket::{WebsocketConn, WebsocketRelay}; #[derive(Debug, Copy, Clone, PartialEq, Eq)]