## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/brick/rate_limit.rs

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