use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ResourceLimitError {
#[error("Peer connection limit exceeded: {current}/{limit}")]
PeerLimitExceeded { current: usize, limit: usize },
#[error("Memory limit exceeded: {current}MB/{limit_mb}MB")]
MemoryLimitExceeded { current: usize, limit_mb: usize },
#[error("Document size too large: {size_mb}MB/{limit_mb}MB")]
DocumentTooLarge { size_mb: usize, limit_mb: usize },
#[error("Upload rate limit exceeded: {current:.2}Mbps/{max:.2}Mbps")]
UploadRateExceeded { current: f64, max: f64 },
#[error("Download rate limit exceeded: {current:.2}Mbps/{max:.2}Mbps")]
DownloadRateExceeded { current: f64, max: f64 },
#[error("Connection timeout: {0:?}")]
Timeout(Duration),
}
pub type ResourceLimitResult<T> = Result<T, ResourceLimitError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimitsConfig {
pub max_peer_connections: usize,
pub max_relay_connections: usize,
pub max_memory_mb: usize,
pub crdt_document_limit_mb: usize,
pub connection_timeout_secs: u64,
pub anti_entropy_max_interval_secs: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_upload_rate_mbps: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_download_rate_mbps: Option<u64>,
}
impl Default for ResourceLimitsConfig {
fn default() -> Self {
Self {
max_peer_connections: 50,
max_relay_connections: 3,
max_memory_mb: 2048,
crdt_document_limit_mb: 50,
connection_timeout_secs: 30,
anti_entropy_max_interval_secs: 300,
max_upload_rate_mbps: None,
max_download_rate_mbps: None,
}
}
}
#[derive(Debug, Clone)]
pub struct ResourceUsage {
pub peer_connections: usize,
pub memory_mb: usize,
pub upload_rate_mbps: f64,
pub download_rate_mbps: f64,
}
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub max_peer_connections: usize,
pub max_relay_connections: usize,
pub max_memory_mb: usize,
pub crdt_document_limit_mb: usize,
pub connection_timeout: Duration,
pub anti_entropy_max_interval: Duration,
pub upload_rate_limit_mbps: Option<u64>,
pub download_rate_limit_mbps: Option<u64>,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self::from_config(ResourceLimitsConfig::default())
}
}
impl ResourceLimits {
pub fn from_config(config: ResourceLimitsConfig) -> Self {
Self {
max_peer_connections: config.max_peer_connections,
max_relay_connections: config.max_relay_connections,
max_memory_mb: config.max_memory_mb,
crdt_document_limit_mb: config.crdt_document_limit_mb,
connection_timeout: Duration::from_secs(config.connection_timeout_secs),
anti_entropy_max_interval: Duration::from_secs(config.anti_entropy_max_interval_secs),
upload_rate_limit_mbps: config.max_upload_rate_mbps,
download_rate_limit_mbps: config.max_download_rate_mbps,
}
}
pub fn low_resource() -> Self {
Self::from_config(ResourceLimitsConfig {
max_peer_connections: 20,
max_relay_connections: 1,
max_memory_mb: 512,
crdt_document_limit_mb: 10,
connection_timeout_secs: 15,
anti_entropy_max_interval_secs: 600, max_upload_rate_mbps: Some(5),
max_download_rate_mbps: Some(20),
})
}
pub fn high_performance() -> Self {
Self::from_config(ResourceLimitsConfig {
max_peer_connections: 200,
max_relay_connections: 10,
max_memory_mb: 8192,
crdt_document_limit_mb: 200,
connection_timeout_secs: 60,
anti_entropy_max_interval_secs: 60, max_upload_rate_mbps: None,
max_download_rate_mbps: None,
})
}
pub fn enforce_peer_limit(&self, current: usize) -> ResourceLimitResult<()> {
if current >= self.max_peer_connections {
Err(ResourceLimitError::PeerLimitExceeded {
current,
limit: self.max_peer_connections,
})
} else {
Ok(())
}
}
pub fn enforce_memory_limit(&self, current_mb: usize) -> ResourceLimitResult<()> {
if current_mb > self.max_memory_mb {
Err(ResourceLimitError::MemoryLimitExceeded {
current: current_mb,
limit_mb: self.max_memory_mb,
})
} else {
Ok(())
}
}
pub fn enforce_relay_limit(&self, current: usize) -> ResourceLimitResult<()> {
if current >= self.max_relay_connections {
Err(ResourceLimitError::PeerLimitExceeded {
current,
limit: self.max_relay_connections,
})
} else {
Ok(())
}
}
pub fn enforce_document_limit(&self, size_mb: usize) -> ResourceLimitResult<()> {
if size_mb > self.crdt_document_limit_mb {
Err(ResourceLimitError::DocumentTooLarge {
size_mb,
limit_mb: self.crdt_document_limit_mb,
})
} else {
Ok(())
}
}
pub fn check_memory_usage(&self, current_mb: usize) -> ResourceLimitResult<()> {
self.enforce_memory_limit(current_mb)
}
pub fn enforce_upload_rate(&self, current_mbps: f64) -> ResourceLimitResult<()> {
if let Some(max) = self.upload_rate_limit_mbps {
let max_f64 = max as f64;
if current_mbps > max_f64 {
return Err(ResourceLimitError::UploadRateExceeded {
current: current_mbps,
max: max_f64,
});
}
}
Ok(())
}
pub fn enforce_download_rate(&self, current_mbps: f64) -> ResourceLimitResult<()> {
if let Some(max) = self.download_rate_limit_mbps {
let max_f64 = max as f64;
if current_mbps > max_f64 {
return Err(ResourceLimitError::DownloadRateExceeded {
current: current_mbps,
max: max_f64,
});
}
}
Ok(())
}
pub fn upload_rate_bytes_per_sec(&self) -> Option<u64> {
self.upload_rate_limit_mbps.map(|mbps| mbps * 125_000)
}
pub fn download_rate_bytes_per_sec(&self) -> Option<u64> {
self.download_rate_limit_mbps.map(|mbps| mbps * 125_000)
}
pub fn validate(&self) -> ResourceLimitResult<()> {
if self.max_peer_connections == 0 {
return Err(ResourceLimitError::PeerLimitExceeded {
current: 0,
limit: 0,
});
}
if self.max_memory_mb == 0 {
return Err(ResourceLimitError::MemoryLimitExceeded {
current: 0,
limit_mb: 0,
});
}
if self.crdt_document_limit_mb > self.max_memory_mb {
return Err(ResourceLimitError::MemoryLimitExceeded {
current: self.crdt_document_limit_mb,
limit_mb: self.max_memory_mb,
});
}
Ok(())
}
pub fn check_all(&self, usage: &ResourceUsage) -> ResourceLimitResult<()> {
self.enforce_peer_limit(usage.peer_connections)?;
self.enforce_memory_limit(usage.memory_mb)?;
self.enforce_upload_rate(usage.upload_rate_mbps)?;
self.enforce_download_rate(usage.download_rate_mbps)?;
Ok(())
}
pub fn measure_current_usage(&self) -> ResourceUsage {
ResourceUsage {
peer_connections: 0,
memory_mb: 0,
upload_rate_mbps: 0.0,
download_rate_mbps: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_serialization() {
let config = ResourceLimitsConfig::default();
let toml = toml::to_string(&config).expect("Serialize");
let parsed: ResourceLimitsConfig = toml::from_str(&toml).expect("Deserialize");
assert_eq!(parsed.max_peer_connections, 50);
assert_eq!(parsed.max_memory_mb, 2048);
}
#[test]
fn test_zero_limits() {
let config = ResourceLimitsConfig {
max_peer_connections: 0,
max_relay_connections: 0,
max_memory_mb: 0,
crdt_document_limit_mb: 0,
connection_timeout_secs: 1,
anti_entropy_max_interval_secs: 1,
max_upload_rate_mbps: None,
max_download_rate_mbps: None,
};
let limits = ResourceLimits::from_config(config);
assert!(limits.enforce_peer_limit(0).is_err());
assert!(limits.enforce_memory_limit(1).is_err());
}
}