25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1 | use std::collections::HashSet; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 2 | use std::fs::read_to_string; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 3 | use std::net::SocketAddr; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 4 | use std::path::{Path, PathBuf}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 5 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 6 | use nostr::key::PublicKey; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 7 | use serde::Deserialize; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 8 | use tokio::sync::Semaphore; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 9 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 10 | fn default_listen() -> SocketAddr { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 11 | SocketAddr::from(([127, 0, 0, 1], 7777)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 12 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 13 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 14 | fn default_database() -> PathBuf { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 15 | PathBuf::from("vineyard.sqlite") |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 16 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 17 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 18 | const fn default_max_connections() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 19 | 32 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 20 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 21 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 22 | const fn default_max_connections_per_ip() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 23 | 8 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 24 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 25 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 26 | const fn default_max_subscriptions() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 27 | 16 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 28 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 29 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 30 | const fn default_max_event_bytes() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 31 | 2_096_896 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 32 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 33 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 34 | const fn default_max_message_bytes() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 35 | 2_097_152 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 36 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 37 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 38 | const fn default_max_tags() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 39 | 128 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 40 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 41 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 42 | const fn default_max_filters() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 43 | 8 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 44 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 45 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 46 | const fn default_max_filter_bytes() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 47 | 32_768 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 48 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 49 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 50 | const fn default_max_query_results() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 51 | 500 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 52 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 53 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 54 | const fn default_max_repositories() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 55 | 4096 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 56 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 57 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 58 | const fn default_max_discovered_peers() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 59 | 32 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 60 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 61 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 62 | const fn default_max_query_bytes() -> usize { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 63 | 16_777_216 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 64 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 65 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 66 | const fn default_events_per_minute() -> u32 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 67 | 120 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 68 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 69 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 70 | const fn default_queries_per_minute() -> u32 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 71 | 120 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 72 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 73 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 74 | const fn default_max_database_bytes() -> u64 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 75 | 1_073_741_824 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 76 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 77 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 78 | const fn default_min_free_bytes() -> u64 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 79 | 67_108_864 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 80 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 81 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 82 | const fn default_write_timeout_seconds() -> u64 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 83 | 10 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 84 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 85 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 86 | #[derive(Debug, Clone, Deserialize)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 87 | #[serde(deny_unknown_fields)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 88 | pub struct Config { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 89 | #[serde(default = "default_listen")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 90 | pub listen: SocketAddr, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 91 | pub public_url: String, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 92 | #[serde(default = "default_database")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 93 | pub database: PathBuf, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 94 | pub forge_pubkeys: Vec<String>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 95 | #[serde(default)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 96 | pub peers: Vec<Peer>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 97 | #[serde(default)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 98 | pub allow_private_discovery: bool, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 99 | #[serde(default)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 100 | pub limits: Limits, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 101 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 102 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 103 | #[derive(Debug, Clone, Deserialize)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 104 | #[serde(deny_unknown_fields)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 105 | pub struct Peer { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 106 | pub url: String, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 107 | #[serde(default = "default_max_query_results")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 108 | pub query_limit: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 109 | pub rescan_from: Option<u64>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 110 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 111 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 112 | #[derive(Debug, Clone, Deserialize)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 113 | #[serde(deny_unknown_fields)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 114 | pub struct Limits { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 115 | #[serde(default = "default_max_repositories")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 116 | pub max_repositories: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 117 | #[serde(default = "default_max_discovered_peers")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 118 | pub max_discovered_peers: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 119 | #[serde(default = "default_max_connections")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 120 | pub max_connections: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 121 | #[serde(default = "default_max_connections_per_ip")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 122 | pub max_connections_per_ip: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 123 | #[serde(default = "default_max_subscriptions")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 124 | pub max_subscriptions: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 125 | #[serde(default = "default_max_event_bytes")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 126 | pub max_event_bytes: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 127 | #[serde(default = "default_max_message_bytes")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 128 | pub max_message_bytes: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 129 | #[serde(default = "default_max_tags")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 130 | pub max_tags: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 131 | #[serde(default = "default_max_filters")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 132 | pub max_filters: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 133 | #[serde(default = "default_max_filter_bytes")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 134 | pub max_filter_bytes: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 135 | #[serde(default = "default_max_query_results")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 136 | pub max_query_results: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 137 | #[serde(default = "default_max_query_bytes")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 138 | pub max_query_bytes: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 139 | #[serde(default = "default_events_per_minute")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 140 | pub events_per_minute: u32, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 141 | #[serde(default = "default_queries_per_minute")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 142 | pub queries_per_minute: u32, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 143 | #[serde(default = "default_max_database_bytes")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 144 | pub max_database_bytes: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 145 | #[serde(default = "default_min_free_bytes")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 146 | pub min_free_bytes: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 147 | #[serde(default = "default_write_timeout_seconds")] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 148 | pub write_timeout_seconds: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 149 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 150 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 151 | impl Default for Limits { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 152 | fn default() -> Self { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 153 | Self { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 154 | max_repositories: default_max_repositories(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 155 | max_discovered_peers: default_max_discovered_peers(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 156 | max_connections: default_max_connections(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 157 | max_connections_per_ip: default_max_connections_per_ip(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 158 | max_subscriptions: default_max_subscriptions(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 159 | max_event_bytes: default_max_event_bytes(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 160 | max_message_bytes: default_max_message_bytes(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 161 | max_tags: default_max_tags(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 162 | max_filters: default_max_filters(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 163 | max_filter_bytes: default_max_filter_bytes(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 164 | max_query_results: default_max_query_results(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 165 | max_query_bytes: default_max_query_bytes(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 166 | events_per_minute: default_events_per_minute(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 167 | queries_per_minute: default_queries_per_minute(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 168 | max_database_bytes: default_max_database_bytes(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 169 | min_free_bytes: default_min_free_bytes(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 170 | write_timeout_seconds: default_write_timeout_seconds(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 171 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 172 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 173 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 174 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 175 | impl Config { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 176 | pub fn load(path: &Path) -> anyhow::Result<Self> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 177 | let text = read_to_string(path).map_err(|error| { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 178 | anyhow::anyhow!("failed to read config {}: {error}", path.display()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 179 | })?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 180 | let mut cfg: Self = toml::from_str(&text).map_err(|error| { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 181 | anyhow::anyhow!("failed to parse config {}: {error}", path.display()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 182 | })?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 183 | if cfg.database.is_relative() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 184 | if let Some(parent) = path.parent() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 185 | if !parent.as_os_str().is_empty() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 186 | cfg.database = parent.join(&cfg.database); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 187 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 188 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 189 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 190 | cfg.validate()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 191 | Ok(cfg) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 192 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 193 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 194 | pub fn validate(&self) -> anyhow::Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 195 | let url = relay_url(&self.public_url) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 196 | .ok_or_else(|| anyhow::anyhow!("invalid public_url {}", self.public_url))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 197 | self.forge_keys()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 198 | if self.peers.len() > 16 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 199 | anyhow::bail!("at most 16 peer relays are supported"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 200 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 201 | let mut peers = HashSet::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 202 | for peer in &self.peers { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 203 | let peer_url = relay_url(&peer.url).ok_or_else(|| { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 204 | anyhow::anyhow!( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 205 | "peer urls must be ws or wss without credentials, queries or fragments" |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 206 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 207 | })?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 208 | if peer_url == url || !peers.insert(peer_url) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 209 | anyhow::bail!("peer urls must be unique and distinct from public_url"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 210 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 211 | if !(2..10000).contains(&peer.query_limit) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 212 | anyhow::bail!("peer query_limit must be between 2 and 9999"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 213 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 214 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 215 | if self.limits.max_connections == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 216 | || self.limits.max_connections_per_ip == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 217 | || self.limits.max_subscriptions == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 218 | || self.limits.max_event_bytes == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 219 | || self.limits.max_message_bytes == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 220 | || self.limits.max_tags == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 221 | || self.limits.max_filters == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 222 | || self.limits.max_filter_bytes == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 223 | || self.limits.max_query_results == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 224 | || self.limits.max_query_bytes == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 225 | || self.limits.events_per_minute == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 226 | || self.limits.queries_per_minute == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 227 | || self.limits.max_database_bytes == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 228 | || self.limits.min_free_bytes == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 229 | || self.limits.write_timeout_seconds == 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 230 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 231 | anyhow::bail!("limits must all be positive"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 232 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 233 | if self.limits.max_connections_per_ip > self.limits.max_connections { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 234 | anyhow::bail!("max_connections_per_ip must not exceed max_connections"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 235 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 236 | let min_message = self |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 237 | .limits |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 238 | .max_event_bytes |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 239 | .checked_add(256) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 240 | .ok_or_else(|| anyhow::anyhow!("max_event_bytes too large"))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 241 | if min_message > self.limits.max_message_bytes { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 242 | anyhow::bail!("max_message_bytes must fit max_event_bytes plus 256 bytes of overhead"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 243 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 244 | if self.limits.max_event_bytes > self.limits.max_query_bytes { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 245 | anyhow::bail!("max_query_bytes must fit one maximum size event"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 246 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 247 | if self.limits.max_repositories == 0 || self.limits.max_repositories >= 10000 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 248 | anyhow::bail!("max_repositories must be between 1 and 9999"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 249 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 250 | if self.limits.max_discovered_peers == 0 || self.limits.max_discovered_peers > 256 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 251 | anyhow::bail!("max_discovered_peers must be between 1 and 256"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 252 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 253 | if self.limits.max_query_results >= 10000 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 254 | anyhow::bail!("max_query_results must be below 10000"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 255 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 256 | if self.limits.max_connections > Semaphore::MAX_PERMITS { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 257 | anyhow::bail!("max_connections exceeds the semaphore limit"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 258 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 259 | self.limits |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 260 | .max_filter_bytes |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 261 | .checked_mul(self.limits.max_subscriptions) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 262 | .ok_or_else(|| anyhow::anyhow!("subscription byte budget is too large"))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 263 | if self.websocket_path()? == "/healthz" { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 264 | anyhow::bail!("public_url must not use the health endpoint"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 265 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 266 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 267 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 268 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 269 | pub fn forge_keys(&self) -> anyhow::Result<HashSet<PublicKey>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 270 | if self.forge_pubkeys.is_empty() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 271 | anyhow::bail!("forge_pubkeys must not be empty"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 272 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 273 | let mut set = HashSet::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 274 | for raw in &self.forge_pubkeys { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 275 | let trimmed = raw.trim(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 276 | if trimmed.len() != 64 || !trimmed.bytes().all(|byte| byte.is_ascii_hexdigit()) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 277 | anyhow::bail!("invalid forge public key: {raw}"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 278 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 279 | let key: PublicKey = trimmed |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 280 | .parse() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 281 | .map_err(|error| anyhow::anyhow!("invalid forge public key {raw}: {error}"))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 282 | if !set.insert(key) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 283 | anyhow::bail!("duplicate forge public key: {raw}"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 284 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 285 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 286 | Ok(set) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 287 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 288 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 289 | pub fn websocket_path(&self) -> anyhow::Result<String> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 290 | let url = url::Url::parse(&self.public_url) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 291 | .map_err(|error| anyhow::anyhow!("invalid public_url {}: {error}", self.public_url))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 292 | let path = url.path(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 293 | if path.is_empty() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 294 | Ok("/".to_owned()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 295 | } else if path.starts_with('/') { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 296 | Ok(path.to_owned()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 297 | } else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 298 | Ok(format!("/{path}")) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 299 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 300 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 301 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 302 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 303 | pub(crate) fn relay_url(raw: &str) -> Option<url::Url> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 304 | let url = url::Url::parse(raw).ok()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 305 | if !matches!(url.scheme(), "ws" | "wss") |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 306 | || url.host_str().is_none() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 307 | || !url.username().is_empty() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 308 | || url.password().is_some() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 309 | || url.query().is_some() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 310 | || url.fragment().is_some() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 311 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 312 | return None; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 313 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 314 | Some(url) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 315 | } |