Skip to main content

cloudillo_core/rate_limit/
config.rs

1// SPDX-FileCopyrightText: Szilárd Hajba
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! Rate Limiting Configuration
5//!
6//! Configuration structs for hierarchical rate limiting with dual-tier
7//! (short-term burst + long-term sustained) limits.
8
9use std::num::NonZeroU32;
10use std::time::Duration;
11
12/// Dual-tier rate limit configuration for a single address level
13#[derive(Clone, Debug)]
14pub struct RateLimitTierConfig {
15	// Short-term: burst protection (per-second)
16	/// Requests per second
17	pub short_term_rps: NonZeroU32,
18	/// Burst capacity for short-term
19	pub short_term_burst: NonZeroU32,
20
21	// Long-term: sustained abuse protection (per-hour)
22	/// Requests per hour
23	pub long_term_rph: NonZeroU32,
24	/// Burst capacity for long-term
25	pub long_term_burst: NonZeroU32,
26}
27
28impl RateLimitTierConfig {
29	pub fn new(short_rps: u32, short_burst: u32, long_rph: u32, long_burst: u32) -> Self {
30		Self {
31			short_term_rps: NonZeroU32::new(short_rps).unwrap_or(NonZeroU32::MIN),
32			short_term_burst: NonZeroU32::new(short_burst).unwrap_or(NonZeroU32::MIN),
33			long_term_rph: NonZeroU32::new(long_rph).unwrap_or(NonZeroU32::MIN),
34			long_term_burst: NonZeroU32::new(long_burst).unwrap_or(NonZeroU32::MIN),
35		}
36	}
37}
38
39/// Configuration for an endpoint category with all address levels
40#[derive(Clone, Debug)]
41pub struct EndpointCategoryConfig {
42	/// Category name (e.g., "auth", "federation", "general")
43	pub name: &'static str,
44	/// IPv4 individual (/32) limits
45	pub ipv4_individual: RateLimitTierConfig,
46	/// IPv4 network (/24) limits
47	pub ipv4_network: RateLimitTierConfig,
48	/// IPv6 subnet (/64) limits
49	pub ipv6_subnet: RateLimitTierConfig,
50	/// IPv6 provider (/48) limits
51	pub ipv6_provider: RateLimitTierConfig,
52}
53
54/// Main rate limit configuration
55#[derive(Clone, Debug)]
56pub struct RateLimitConfig {
57	/// Auth endpoints (login, register, password reset)
58	pub auth: EndpointCategoryConfig,
59	/// DAV endpoints (CardDAV / CalDAV sync)
60	pub dav: EndpointCategoryConfig,
61	/// Federation endpoints (inbox)
62	pub federation: EndpointCategoryConfig,
63	/// General public endpoints (profile, refs)
64	pub general: EndpointCategoryConfig,
65	/// WebSocket endpoints
66	pub websocket: EndpointCategoryConfig,
67	/// Maximum number of IPs to track (memory limit)
68	pub max_tracked_ips: usize,
69	/// How long to retain entries after last access
70	pub entry_ttl: Duration,
71}
72
73impl Default for RateLimitConfig {
74	fn default() -> Self {
75		Self {
76			auth: EndpointCategoryConfig {
77				name: "auth",
78				// Auth: strict limits but allow a few rapid page refreshes
79				ipv4_individual: RateLimitTierConfig::new(5, 10, 60, 60),
80				ipv4_network: RateLimitTierConfig::new(15, 30, 200, 200),
81				ipv6_subnet: RateLimitTierConfig::new(5, 10, 60, 60),
82				ipv6_provider: RateLimitTierConfig::new(15, 30, 200, 200),
83			},
84			dav: EndpointCategoryConfig {
85				name: "dav",
86				// DAV: authenticated CardDAV / CalDAV sync. Clients like DAVx5 fire a
87				// burst of PROPFIND / REPORT per sync cycle (principal + each home set +
88				// each collection), and can poll every few minutes. Has to be generous
89				// enough for routine sync traffic while still bounded in case a token
90				// leaks — rate limiting runs before auth so this is our only brute-force
91				// defense on the DAV router.
92				ipv4_individual: RateLimitTierConfig::new(30, 60, 2000, 500),
93				ipv4_network: RateLimitTierConfig::new(60, 120, 5000, 1000),
94				ipv6_subnet: RateLimitTierConfig::new(30, 60, 2000, 500),
95				ipv6_provider: RateLimitTierConfig::new(60, 120, 5000, 1000),
96			},
97			federation: EndpointCategoryConfig {
98				name: "federation",
99				// Federation: moderate limits for inter-instance communication
100				ipv4_individual: RateLimitTierConfig::new(100, 200, 1000, 100),
101				ipv4_network: RateLimitTierConfig::new(500, 750, 5000, 500),
102				ipv6_subnet: RateLimitTierConfig::new(100, 200, 1000, 100),
103				ipv6_provider: RateLimitTierConfig::new(500, 750, 5000, 500),
104			},
105			general: EndpointCategoryConfig {
106				name: "general",
107				// General: relaxed limits for normal browsing
108				ipv4_individual: RateLimitTierConfig::new(300, 500, 5000, 500),
109				ipv4_network: RateLimitTierConfig::new(600, 1000, 50000, 5000),
110				ipv6_subnet: RateLimitTierConfig::new(300, 500, 5000, 500),
111				ipv6_provider: RateLimitTierConfig::new(600, 1000, 50000, 5000),
112			},
113			websocket: EndpointCategoryConfig {
114				name: "websocket",
115				// WebSocket: relaxed limits for collaborative scenarios (connections are long-lived)
116				ipv4_individual: RateLimitTierConfig::new(100, 200, 1000, 500),
117				ipv4_network: RateLimitTierConfig::new(100, 200, 1000, 500),
118				ipv6_subnet: RateLimitTierConfig::new(100, 200, 1000, 500),
119				ipv6_provider: RateLimitTierConfig::new(100, 200, 1000, 500),
120			},
121			max_tracked_ips: 100_000,
122			entry_ttl: Duration::from_hours(1),
123		}
124	}
125}
126
127/// Proof-of-Work counter configuration
128#[derive(Clone, Debug)]
129pub struct PowConfig {
130	/// Maximum counter value (caps PoW difficulty)
131	pub max_counter: u32,
132	/// Counter decay: decrease by 1 every N seconds of no violations
133	pub decay_interval_secs: u64,
134	/// LRU cache size for individual IPs
135	pub max_individual_entries: usize,
136	/// LRU cache size for networks
137	pub max_network_entries: usize,
138}
139
140impl Default for PowConfig {
141	fn default() -> Self {
142		Self {
143			max_counter: 10,           // Max "AAAAAAAAAA" required
144			decay_interval_secs: 3600, // 1 hour decay
145			max_individual_entries: 50_000,
146			max_network_entries: 10_000,
147		}
148	}
149}
150
151// vim: ts=4