cloudillo-core 0.8.16

Core infrastructure for the Cloudillo platform: middleware, extractors, scheduler, rate limiting, and access control
Documentation
// SPDX-FileCopyrightText: Szilárd Hajba
// SPDX-License-Identifier: LGPL-3.0-or-later

//! Rate Limiting Configuration
//!
//! Configuration structs for hierarchical rate limiting with dual-tier
//! (short-term burst + long-term sustained) limits.

use std::num::NonZeroU32;
use std::time::Duration;

/// Dual-tier rate limit configuration for a single address level
#[derive(Clone, Debug)]
pub struct RateLimitTierConfig {
	// Short-term: burst protection (per-second)
	/// Requests per second
	pub short_term_rps: NonZeroU32,
	/// Burst capacity for short-term
	pub short_term_burst: NonZeroU32,

	// Long-term: sustained abuse protection (per-hour)
	/// Requests per hour
	pub long_term_rph: NonZeroU32,
	/// Burst capacity for long-term
	pub long_term_burst: NonZeroU32,
}

impl RateLimitTierConfig {
	pub fn new(short_rps: u32, short_burst: u32, long_rph: u32, long_burst: u32) -> Self {
		Self {
			short_term_rps: NonZeroU32::new(short_rps).unwrap_or(NonZeroU32::MIN),
			short_term_burst: NonZeroU32::new(short_burst).unwrap_or(NonZeroU32::MIN),
			long_term_rph: NonZeroU32::new(long_rph).unwrap_or(NonZeroU32::MIN),
			long_term_burst: NonZeroU32::new(long_burst).unwrap_or(NonZeroU32::MIN),
		}
	}
}

/// Configuration for an endpoint category with all address levels
#[derive(Clone, Debug)]
pub struct EndpointCategoryConfig {
	/// Category name (e.g., "auth", "federation", "general")
	pub name: &'static str,
	/// IPv4 individual (/32) limits
	pub ipv4_individual: RateLimitTierConfig,
	/// IPv4 network (/24) limits
	pub ipv4_network: RateLimitTierConfig,
	/// IPv6 subnet (/64) limits
	pub ipv6_subnet: RateLimitTierConfig,
	/// IPv6 provider (/48) limits
	pub ipv6_provider: RateLimitTierConfig,
}

/// Main rate limit configuration
#[derive(Clone, Debug)]
pub struct RateLimitConfig {
	/// Auth endpoints (login, register, password reset)
	pub auth: EndpointCategoryConfig,
	/// DAV endpoints (CardDAV / CalDAV sync)
	pub dav: EndpointCategoryConfig,
	/// Federation endpoints (inbox)
	pub federation: EndpointCategoryConfig,
	/// General public endpoints (profile, refs)
	pub general: EndpointCategoryConfig,
	/// WebSocket endpoints
	pub websocket: EndpointCategoryConfig,
	/// Maximum number of IPs to track (memory limit)
	pub max_tracked_ips: usize,
	/// How long to retain entries after last access
	pub entry_ttl: Duration,
}

impl Default for RateLimitConfig {
	fn default() -> Self {
		Self {
			auth: EndpointCategoryConfig {
				name: "auth",
				// Auth: strict limits but allow a few rapid page refreshes
				ipv4_individual: RateLimitTierConfig::new(5, 10, 60, 60),
				ipv4_network: RateLimitTierConfig::new(15, 30, 200, 200),
				ipv6_subnet: RateLimitTierConfig::new(5, 10, 60, 60),
				ipv6_provider: RateLimitTierConfig::new(15, 30, 200, 200),
			},
			dav: EndpointCategoryConfig {
				name: "dav",
				// DAV: authenticated CardDAV / CalDAV sync. Clients like DAVx5 fire a
				// burst of PROPFIND / REPORT per sync cycle (principal + each home set +
				// each collection), and can poll every few minutes. Has to be generous
				// enough for routine sync traffic while still bounded in case a token
				// leaks — rate limiting runs before auth so this is our only brute-force
				// defense on the DAV router.
				ipv4_individual: RateLimitTierConfig::new(30, 60, 2000, 500),
				ipv4_network: RateLimitTierConfig::new(60, 120, 5000, 1000),
				ipv6_subnet: RateLimitTierConfig::new(30, 60, 2000, 500),
				ipv6_provider: RateLimitTierConfig::new(60, 120, 5000, 1000),
			},
			federation: EndpointCategoryConfig {
				name: "federation",
				// Federation: moderate limits for inter-instance communication
				ipv4_individual: RateLimitTierConfig::new(100, 200, 1000, 100),
				ipv4_network: RateLimitTierConfig::new(500, 750, 5000, 500),
				ipv6_subnet: RateLimitTierConfig::new(100, 200, 1000, 100),
				ipv6_provider: RateLimitTierConfig::new(500, 750, 5000, 500),
			},
			general: EndpointCategoryConfig {
				name: "general",
				// General: relaxed limits for normal browsing
				ipv4_individual: RateLimitTierConfig::new(300, 500, 5000, 500),
				ipv4_network: RateLimitTierConfig::new(600, 1000, 50000, 5000),
				ipv6_subnet: RateLimitTierConfig::new(300, 500, 5000, 500),
				ipv6_provider: RateLimitTierConfig::new(600, 1000, 50000, 5000),
			},
			websocket: EndpointCategoryConfig {
				name: "websocket",
				// WebSocket: relaxed limits for collaborative scenarios (connections are long-lived)
				ipv4_individual: RateLimitTierConfig::new(100, 200, 1000, 500),
				ipv4_network: RateLimitTierConfig::new(100, 200, 1000, 500),
				ipv6_subnet: RateLimitTierConfig::new(100, 200, 1000, 500),
				ipv6_provider: RateLimitTierConfig::new(100, 200, 1000, 500),
			},
			max_tracked_ips: 100_000,
			entry_ttl: Duration::from_hours(1),
		}
	}
}

/// Proof-of-Work counter configuration
#[derive(Clone, Debug)]
pub struct PowConfig {
	/// Maximum counter value (caps PoW difficulty)
	pub max_counter: u32,
	/// Counter decay: decrease by 1 every N seconds of no violations
	pub decay_interval_secs: u64,
	/// LRU cache size for individual IPs
	pub max_individual_entries: usize,
	/// LRU cache size for networks
	pub max_network_entries: usize,
}

impl Default for PowConfig {
	fn default() -> Self {
		Self {
			max_counter: 10,           // Max "AAAAAAAAAA" required
			decay_interval_secs: 3600, // 1 hour decay
			max_individual_entries: 50_000,
			max_network_entries: 10_000,
		}
	}
}

// vim: ts=4