Skip to main content

trueno/brick/
rate_limit.rs

1//! DoS Prevention Rate Limiting
2//!
3//! AWP-15: Request validation and rate limiting for DoS prevention.
4
5use std::fmt;
6use std::time::Duration;
7
8// ----------------------------------------------------------------------------
9// AWP-15: DoS Prevention Limits
10// ----------------------------------------------------------------------------
11
12/// DoS prevention limits for serving.
13///
14/// # Example
15/// ```rust
16/// use trueno::brick::ServeLimits;
17///
18/// let limits = ServeLimits::default();
19/// assert!(limits.validate_request(50, 1024).is_ok());
20/// assert!(limits.validate_request(200, 1024).is_err());  // Too many headers
21/// ```
22#[derive(Debug, Clone)]
23pub struct ServeLimits {
24    /// Maximum request body size (bytes).
25    pub max_request_size: usize,
26    /// Maximum number of headers.
27    pub max_headers: usize,
28    /// Maximum header size (bytes).
29    pub max_header_size: usize,
30    /// Keep-alive timeout.
31    pub keep_alive_timeout: Duration,
32    /// Client request timeout.
33    pub client_timeout: Duration,
34    /// Maximum pipelined requests.
35    pub max_pipelined: usize,
36    /// Maximum concurrent connections.
37    pub max_connections: usize,
38}
39
40impl Default for ServeLimits {
41    fn default() -> Self {
42        Self {
43            max_request_size: 2 * 1024 * 1024, // 2MB
44            max_headers: 100,
45            max_header_size: 8 * 1024, // 8KB
46            keep_alive_timeout: Duration::from_secs(5),
47            client_timeout: Duration::from_secs(5),
48            max_pipelined: 16,
49            max_connections: 1024,
50        }
51    }
52}
53
54impl ServeLimits {
55    /// Create new limits with custom values.
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Builder: set max request size.
61    #[must_use]
62    pub fn with_max_request_size(mut self, size: usize) -> Self {
63        self.max_request_size = size;
64        self
65    }
66
67    /// Builder: set max headers.
68    #[must_use]
69    pub fn with_max_headers(mut self, count: usize) -> Self {
70        self.max_headers = count;
71        self
72    }
73
74    /// Builder: set max connections.
75    #[must_use]
76    pub fn with_max_connections(mut self, count: usize) -> Self {
77        self.max_connections = count;
78        self
79    }
80
81    /// Validate incoming request against limits.
82    pub fn validate_request(
83        &self,
84        headers_count: usize,
85        body_size: usize,
86    ) -> Result<(), LimitError> {
87        if headers_count > self.max_headers {
88            return Err(LimitError::TooManyHeaders { count: headers_count, max: self.max_headers });
89        }
90        if body_size > self.max_request_size {
91            return Err(LimitError::BodyTooLarge { size: body_size, max: self.max_request_size });
92        }
93        Ok(())
94    }
95
96    /// Validate header size.
97    pub fn validate_header_size(&self, size: usize) -> Result<(), LimitError> {
98        if size > self.max_header_size {
99            return Err(LimitError::HeaderTooLarge { size, max: self.max_header_size });
100        }
101        Ok(())
102    }
103
104    /// Validate pipelined request count.
105    pub fn validate_pipelined(&self, count: usize) -> Result<(), LimitError> {
106        if count > self.max_pipelined {
107            return Err(LimitError::TooManyPipelined { count, max: self.max_pipelined });
108        }
109        Ok(())
110    }
111
112    /// Validate connection count.
113    pub fn validate_connections(&self, current: usize) -> Result<(), LimitError> {
114        if current >= self.max_connections {
115            return Err(LimitError::ConnectionLimitReached { current, max: self.max_connections });
116        }
117        Ok(())
118    }
119}
120
121/// Error when a limit is exceeded.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum LimitError {
124    /// Too many headers in request.
125    TooManyHeaders {
126        /// Actual count.
127        count: usize,
128        /// Maximum allowed.
129        max: usize,
130    },
131    /// Request body too large.
132    BodyTooLarge {
133        /// Actual size.
134        size: usize,
135        /// Maximum allowed.
136        max: usize,
137    },
138    /// Header too large.
139    HeaderTooLarge {
140        /// Actual size.
141        size: usize,
142        /// Maximum allowed.
143        max: usize,
144    },
145    /// Too many pipelined requests.
146    TooManyPipelined {
147        /// Actual count.
148        count: usize,
149        /// Maximum allowed.
150        max: usize,
151    },
152    /// Connection limit reached.
153    ConnectionLimitReached {
154        /// Current connections.
155        current: usize,
156        /// Maximum allowed.
157        max: usize,
158    },
159}
160
161impl fmt::Display for LimitError {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            LimitError::TooManyHeaders { count, max } => {
165                write!(f, "too many headers: {} (max {})", count, max)
166            }
167            LimitError::BodyTooLarge { size, max } => {
168                write!(f, "body too large: {} bytes (max {})", size, max)
169            }
170            LimitError::HeaderTooLarge { size, max } => {
171                write!(f, "header too large: {} bytes (max {})", size, max)
172            }
173            LimitError::TooManyPipelined { count, max } => {
174                write!(f, "too many pipelined requests: {} (max {})", count, max)
175            }
176            LimitError::ConnectionLimitReached { current, max } => {
177                write!(f, "connection limit reached: {} (max {})", current, max)
178            }
179        }
180    }
181}
182
183impl std::error::Error for LimitError {}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_serve_limits_default() {
191        let limits = ServeLimits::default();
192        assert_eq!(limits.max_request_size, 2 * 1024 * 1024);
193        assert_eq!(limits.max_headers, 100);
194        assert_eq!(limits.max_header_size, 8 * 1024);
195        assert_eq!(limits.max_pipelined, 16);
196        assert_eq!(limits.max_connections, 1024);
197    }
198
199    #[test]
200    fn test_serve_limits_builder() {
201        let limits = ServeLimits::new()
202            .with_max_request_size(1024)
203            .with_max_headers(50)
204            .with_max_connections(100);
205
206        assert_eq!(limits.max_request_size, 1024);
207        assert_eq!(limits.max_headers, 50);
208        assert_eq!(limits.max_connections, 100);
209    }
210
211    #[test]
212    fn test_validate_request_ok() {
213        let limits = ServeLimits::default();
214        assert!(limits.validate_request(50, 1024).is_ok());
215    }
216
217    #[test]
218    fn test_validate_request_too_many_headers() {
219        let limits = ServeLimits::default();
220        let result = limits.validate_request(200, 1024);
221        assert!(matches!(result, Err(LimitError::TooManyHeaders { .. })));
222    }
223
224    #[test]
225    fn test_validate_request_body_too_large() {
226        let limits = ServeLimits::default();
227        let result = limits.validate_request(50, 10 * 1024 * 1024);
228        assert!(matches!(result, Err(LimitError::BodyTooLarge { .. })));
229    }
230
231    #[test]
232    fn test_validate_header_size_ok() {
233        let limits = ServeLimits::default();
234        assert!(limits.validate_header_size(1024).is_ok());
235    }
236
237    #[test]
238    fn test_validate_header_size_too_large() {
239        let limits = ServeLimits::default();
240        let result = limits.validate_header_size(16 * 1024);
241        assert!(matches!(result, Err(LimitError::HeaderTooLarge { .. })));
242    }
243
244    #[test]
245    fn test_validate_pipelined_ok() {
246        let limits = ServeLimits::default();
247        assert!(limits.validate_pipelined(10).is_ok());
248    }
249
250    #[test]
251    fn test_validate_pipelined_too_many() {
252        let limits = ServeLimits::default();
253        let result = limits.validate_pipelined(20);
254        assert!(matches!(result, Err(LimitError::TooManyPipelined { .. })));
255    }
256
257    #[test]
258    fn test_validate_connections_ok() {
259        let limits = ServeLimits::default();
260        assert!(limits.validate_connections(500).is_ok());
261    }
262
263    #[test]
264    fn test_validate_connections_limit_reached() {
265        let limits = ServeLimits::default();
266        let result = limits.validate_connections(1024);
267        assert!(matches!(result, Err(LimitError::ConnectionLimitReached { .. })));
268    }
269
270    #[test]
271    fn test_limit_error_display() {
272        let err = LimitError::TooManyHeaders { count: 150, max: 100 };
273        assert_eq!(format!("{}", err), "too many headers: 150 (max 100)");
274
275        let err = LimitError::BodyTooLarge { size: 5000000, max: 2097152 };
276        assert_eq!(format!("{}", err), "body too large: 5000000 bytes (max 2097152)");
277    }
278
279    #[test]
280    fn test_limit_error_eq() {
281        let err1 = LimitError::TooManyHeaders { count: 150, max: 100 };
282        let err2 = LimitError::TooManyHeaders { count: 150, max: 100 };
283        let err3 = LimitError::TooManyHeaders { count: 200, max: 100 };
284
285        assert_eq!(err1, err2);
286        assert_ne!(err1, err3);
287    }
288
289    /// FALSIFICATION TEST: Verify boundary conditions
290    ///
291    /// Limits must reject exactly at the boundary, not before or after.
292    #[test]
293    fn test_falsify_exact_boundaries() {
294        let limits = ServeLimits::new()
295            .with_max_headers(100)
296            .with_max_request_size(1000)
297            .with_max_connections(10);
298
299        // Headers: 100 is the max, so 100 should be OK but 101 should fail
300        assert!(limits.validate_request(100, 0).is_ok());
301        assert!(limits.validate_request(101, 0).is_err());
302
303        // Body: 1000 is the max, so 1000 should be OK but 1001 should fail
304        assert!(limits.validate_request(0, 1000).is_ok());
305        assert!(limits.validate_request(0, 1001).is_err());
306
307        // Connections: Using >=, so 10 should fail but 9 should be OK
308        assert!(limits.validate_connections(9).is_ok());
309        assert!(limits.validate_connections(10).is_err());
310    }
311
312    /// FALSIFICATION TEST: Verify all validation passes for zero values
313    #[test]
314    fn test_falsify_zero_values_pass() {
315        let limits = ServeLimits::default();
316
317        // Zero headers and zero body should always pass
318        assert!(
319            limits.validate_request(0, 0).is_ok(),
320            "FALSIFICATION FAILED: Zero values should always pass"
321        );
322        assert!(
323            limits.validate_header_size(0).is_ok(),
324            "FALSIFICATION FAILED: Zero header size should pass"
325        );
326        assert!(
327            limits.validate_pipelined(0).is_ok(),
328            "FALSIFICATION FAILED: Zero pipelined should pass"
329        );
330        assert!(
331            limits.validate_connections(0).is_ok(),
332            "FALSIFICATION FAILED: Zero connections should pass"
333        );
334    }
335}