1use serde::{Deserialize, Serialize};
11use std::time::Duration;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
16pub enum ResourceLimitError {
17 #[error("Peer connection limit exceeded: {current}/{limit}")]
18 PeerLimitExceeded { current: usize, limit: usize },
19
20 #[error("Memory limit exceeded: {current}MB/{limit_mb}MB")]
21 MemoryLimitExceeded { current: usize, limit_mb: usize },
22
23 #[error("Document size too large: {size_mb}MB/{limit_mb}MB")]
24 DocumentTooLarge { size_mb: usize, limit_mb: usize },
25
26 #[error("Upload rate limit exceeded: {current:.2}Mbps/{max:.2}Mbps")]
27 UploadRateExceeded { current: f64, max: f64 },
28
29 #[error("Download rate limit exceeded: {current:.2}Mbps/{max:.2}Mbps")]
30 DownloadRateExceeded { current: f64, max: f64 },
31
32 #[error("Connection timeout: {0:?}")]
33 Timeout(Duration),
34}
35
36pub type ResourceLimitResult<T> = Result<T, ResourceLimitError>;
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ResourceLimitsConfig {
42 pub max_peer_connections: usize,
44
45 pub max_relay_connections: usize,
47
48 pub max_memory_mb: usize,
50
51 pub crdt_document_limit_mb: usize,
53
54 pub connection_timeout_secs: u64,
56
57 pub anti_entropy_max_interval_secs: u64,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub max_upload_rate_mbps: Option<u64>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub max_download_rate_mbps: Option<u64>,
67}
68
69impl Default for ResourceLimitsConfig {
70 fn default() -> Self {
71 Self {
72 max_peer_connections: 50,
73 max_relay_connections: 3,
74 max_memory_mb: 2048,
75 crdt_document_limit_mb: 50,
76 connection_timeout_secs: 30,
77 anti_entropy_max_interval_secs: 300,
78 max_upload_rate_mbps: None,
79 max_download_rate_mbps: None,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct ResourceUsage {
87 pub peer_connections: usize,
88 pub memory_mb: usize,
89 pub upload_rate_mbps: f64,
90 pub download_rate_mbps: f64,
91}
92
93#[derive(Debug, Clone)]
101pub struct ResourceLimits {
102 pub max_peer_connections: usize,
103 pub max_relay_connections: usize,
104 pub max_memory_mb: usize,
105 pub crdt_document_limit_mb: usize,
106 pub connection_timeout: Duration,
107 pub anti_entropy_max_interval: Duration,
108 pub upload_rate_limit_mbps: Option<u64>,
109 pub download_rate_limit_mbps: Option<u64>,
110}
111
112impl Default for ResourceLimits {
113 fn default() -> Self {
114 Self::from_config(ResourceLimitsConfig::default())
115 }
116}
117
118impl ResourceLimits {
119 pub fn from_config(config: ResourceLimitsConfig) -> Self {
121 Self {
122 max_peer_connections: config.max_peer_connections,
123 max_relay_connections: config.max_relay_connections,
124 max_memory_mb: config.max_memory_mb,
125 crdt_document_limit_mb: config.crdt_document_limit_mb,
126 connection_timeout: Duration::from_secs(config.connection_timeout_secs),
127 anti_entropy_max_interval: Duration::from_secs(config.anti_entropy_max_interval_secs),
128 upload_rate_limit_mbps: config.max_upload_rate_mbps,
129 download_rate_limit_mbps: config.max_download_rate_mbps,
130 }
131 }
132
133 pub fn low_resource() -> Self {
135 Self::from_config(ResourceLimitsConfig {
136 max_peer_connections: 20,
137 max_relay_connections: 1,
138 max_memory_mb: 512,
139 crdt_document_limit_mb: 10,
140 connection_timeout_secs: 15,
141 anti_entropy_max_interval_secs: 600, max_upload_rate_mbps: Some(5),
143 max_download_rate_mbps: Some(20),
144 })
145 }
146
147 pub fn high_performance() -> Self {
149 Self::from_config(ResourceLimitsConfig {
150 max_peer_connections: 200,
151 max_relay_connections: 10,
152 max_memory_mb: 8192,
153 crdt_document_limit_mb: 200,
154 connection_timeout_secs: 60,
155 anti_entropy_max_interval_secs: 60, max_upload_rate_mbps: None,
157 max_download_rate_mbps: None,
158 })
159 }
160
161 pub fn enforce_peer_limit(&self, current: usize) -> ResourceLimitResult<()> {
163 if current >= self.max_peer_connections {
164 Err(ResourceLimitError::PeerLimitExceeded {
165 current,
166 limit: self.max_peer_connections,
167 })
168 } else {
169 Ok(())
170 }
171 }
172
173 pub fn enforce_memory_limit(&self, current_mb: usize) -> ResourceLimitResult<()> {
175 if current_mb > self.max_memory_mb {
176 Err(ResourceLimitError::MemoryLimitExceeded {
177 current: current_mb,
178 limit_mb: self.max_memory_mb,
179 })
180 } else {
181 Ok(())
182 }
183 }
184
185 pub fn enforce_relay_limit(&self, current: usize) -> ResourceLimitResult<()> {
187 if current >= self.max_relay_connections {
188 Err(ResourceLimitError::PeerLimitExceeded {
189 current,
190 limit: self.max_relay_connections,
191 })
192 } else {
193 Ok(())
194 }
195 }
196
197 pub fn enforce_document_limit(&self, size_mb: usize) -> ResourceLimitResult<()> {
199 if size_mb > self.crdt_document_limit_mb {
200 Err(ResourceLimitError::DocumentTooLarge {
201 size_mb,
202 limit_mb: self.crdt_document_limit_mb,
203 })
204 } else {
205 Ok(())
206 }
207 }
208
209 pub fn check_memory_usage(&self, current_mb: usize) -> ResourceLimitResult<()> {
211 self.enforce_memory_limit(current_mb)
212 }
213
214 pub fn enforce_upload_rate(&self, current_mbps: f64) -> ResourceLimitResult<()> {
216 if let Some(max) = self.upload_rate_limit_mbps {
217 let max_f64 = max as f64;
218 if current_mbps > max_f64 {
219 return Err(ResourceLimitError::UploadRateExceeded {
220 current: current_mbps,
221 max: max_f64,
222 });
223 }
224 }
225 Ok(())
226 }
227
228 pub fn enforce_download_rate(&self, current_mbps: f64) -> ResourceLimitResult<()> {
230 if let Some(max) = self.download_rate_limit_mbps {
231 let max_f64 = max as f64;
232 if current_mbps > max_f64 {
233 return Err(ResourceLimitError::DownloadRateExceeded {
234 current: current_mbps,
235 max: max_f64,
236 });
237 }
238 }
239 Ok(())
240 }
241
242 pub fn upload_rate_bytes_per_sec(&self) -> Option<u64> {
244 self.upload_rate_limit_mbps.map(|mbps| mbps * 125_000)
245 }
246
247 pub fn download_rate_bytes_per_sec(&self) -> Option<u64> {
249 self.download_rate_limit_mbps.map(|mbps| mbps * 125_000)
250 }
251
252 pub fn validate(&self) -> ResourceLimitResult<()> {
254 if self.max_peer_connections == 0 {
256 return Err(ResourceLimitError::PeerLimitExceeded {
257 current: 0,
258 limit: 0,
259 });
260 }
261
262 if self.max_memory_mb == 0 {
264 return Err(ResourceLimitError::MemoryLimitExceeded {
265 current: 0,
266 limit_mb: 0,
267 });
268 }
269
270 if self.crdt_document_limit_mb > self.max_memory_mb {
272 return Err(ResourceLimitError::MemoryLimitExceeded {
273 current: self.crdt_document_limit_mb,
274 limit_mb: self.max_memory_mb,
275 });
276 }
277
278 Ok(())
279 }
280
281 pub fn check_all(&self, usage: &ResourceUsage) -> ResourceLimitResult<()> {
283 self.enforce_peer_limit(usage.peer_connections)?;
284 self.enforce_memory_limit(usage.memory_mb)?;
285 self.enforce_upload_rate(usage.upload_rate_mbps)?;
286 self.enforce_download_rate(usage.download_rate_mbps)?;
287 Ok(())
288 }
289
290 pub fn measure_current_usage(&self) -> ResourceUsage {
292 ResourceUsage {
293 peer_connections: 0,
294 memory_mb: 0,
295 upload_rate_mbps: 0.0,
296 download_rate_mbps: 0.0,
297 }
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_config_serialization() {
307 let config = ResourceLimitsConfig::default();
308 let toml = toml::to_string(&config).expect("Serialize");
309 let parsed: ResourceLimitsConfig = toml::from_str(&toml).expect("Deserialize");
310
311 assert_eq!(parsed.max_peer_connections, 50);
312 assert_eq!(parsed.max_memory_mb, 2048);
313 }
314
315 #[test]
316 fn test_zero_limits() {
317 let config = ResourceLimitsConfig {
318 max_peer_connections: 0,
319 max_relay_connections: 0,
320 max_memory_mb: 0,
321 crdt_document_limit_mb: 0,
322 connection_timeout_secs: 1,
323 anti_entropy_max_interval_secs: 1,
324 max_upload_rate_mbps: None,
325 max_download_rate_mbps: None,
326 };
327
328 let limits = ResourceLimits::from_config(config);
329
330 assert!(limits.enforce_peer_limit(0).is_err());
331 assert!(limits.enforce_memory_limit(1).is_err());
332 }
333}