lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

limits.rs (11307B)


      1 use core::{fmt, time::Duration};
      2 use std::error::Error;
      3 
      4 const MAX_HEADER_COUNT: u32 = 64;
      5 const MAX_HEADER_BYTES: u32 = 32 * 1024;
      6 const MAX_REQUEST_BODY_UTF8_BYTES: u32 = 65_536;
      7 const MAX_RESPONSE_BODY_UTF8_BYTES: u32 = 1_048_576;
      8 const MAX_CONCURRENT_CONNECTIONS: u32 = 64;
      9 const MAX_REQUEST_DEADLINE: Duration = Duration::from_secs(30);
     10 const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(60);
     11 const MAX_QUERY_ITEMS: u32 = 200;
     12 
     13 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     14 pub enum AdminTransportLimitField {
     15     HeaderCount,
     16     HeaderBytes,
     17     RequestBodyUtf8Bytes,
     18     ResponseBodyUtf8Bytes,
     19     ConcurrentConnections,
     20     RequestDeadline,
     21     IdleTimeout,
     22     QueryItems,
     23 }
     24 
     25 impl AdminTransportLimitField {
     26     /// Returns the hard maximum in items, bytes, or milliseconds as appropriate.
     27     #[must_use]
     28     pub const fn maximum(self) -> u64 {
     29         match self {
     30             Self::HeaderCount => MAX_HEADER_COUNT as u64,
     31             Self::HeaderBytes => MAX_HEADER_BYTES as u64,
     32             Self::RequestBodyUtf8Bytes => MAX_REQUEST_BODY_UTF8_BYTES as u64,
     33             Self::ResponseBodyUtf8Bytes => MAX_RESPONSE_BODY_UTF8_BYTES as u64,
     34             Self::ConcurrentConnections => MAX_CONCURRENT_CONNECTIONS as u64,
     35             Self::RequestDeadline => MAX_REQUEST_DEADLINE.as_millis() as u64,
     36             Self::IdleTimeout => MAX_IDLE_TIMEOUT.as_millis() as u64,
     37             Self::QueryItems => MAX_QUERY_ITEMS as u64,
     38         }
     39     }
     40 }
     41 
     42 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     43 pub enum AdminTransportLimitsError {
     44     Zero { field: AdminTransportLimitField },
     45     ExceedsMaximum { field: AdminTransportLimitField },
     46 }
     47 
     48 impl fmt::Display for AdminTransportLimitsError {
     49     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     50         formatter.write_str("admin transport limit is outside its supported positive bounds")
     51     }
     52 }
     53 
     54 impl Error for AdminTransportLimitsError {}
     55 
     56 /// Unvalidated values read from a service-owned configuration model.
     57 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     58 pub struct AdminTransportLimitValues {
     59     pub header_count: u32,
     60     pub header_bytes: u32,
     61     pub request_body_utf8_bytes: u32,
     62     pub response_body_utf8_bytes: u32,
     63     pub concurrent_connections: u32,
     64     pub request_deadline: Duration,
     65     pub idle_timeout: Duration,
     66     pub query_items: u32,
     67 }
     68 
     69 /// Validated local-admin resource policy.
     70 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     71 pub struct AdminTransportLimits {
     72     values: AdminTransportLimitValues,
     73 }
     74 
     75 impl AdminTransportLimits {
     76     pub const DEFAULT: Self = Self {
     77         values: AdminTransportLimitValues {
     78             header_count: 32,
     79             header_bytes: 16 * 1024,
     80             request_body_utf8_bytes: MAX_REQUEST_BODY_UTF8_BYTES,
     81             response_body_utf8_bytes: MAX_RESPONSE_BODY_UTF8_BYTES,
     82             concurrent_connections: 32,
     83             request_deadline: Duration::from_secs(15),
     84             idle_timeout: Duration::from_secs(30),
     85             query_items: 100,
     86         },
     87     };
     88 
     89     pub fn new(values: AdminTransportLimitValues) -> Result<Self, AdminTransportLimitsError> {
     90         validate_u32(
     91             AdminTransportLimitField::HeaderCount,
     92             values.header_count,
     93             MAX_HEADER_COUNT,
     94         )?;
     95         validate_u32(
     96             AdminTransportLimitField::HeaderBytes,
     97             values.header_bytes,
     98             MAX_HEADER_BYTES,
     99         )?;
    100         validate_u32(
    101             AdminTransportLimitField::RequestBodyUtf8Bytes,
    102             values.request_body_utf8_bytes,
    103             MAX_REQUEST_BODY_UTF8_BYTES,
    104         )?;
    105         validate_u32(
    106             AdminTransportLimitField::ResponseBodyUtf8Bytes,
    107             values.response_body_utf8_bytes,
    108             MAX_RESPONSE_BODY_UTF8_BYTES,
    109         )?;
    110         validate_u32(
    111             AdminTransportLimitField::ConcurrentConnections,
    112             values.concurrent_connections,
    113             MAX_CONCURRENT_CONNECTIONS,
    114         )?;
    115         validate_duration(
    116             AdminTransportLimitField::RequestDeadline,
    117             values.request_deadline,
    118             MAX_REQUEST_DEADLINE,
    119         )?;
    120         validate_duration(
    121             AdminTransportLimitField::IdleTimeout,
    122             values.idle_timeout,
    123             MAX_IDLE_TIMEOUT,
    124         )?;
    125         validate_u32(
    126             AdminTransportLimitField::QueryItems,
    127             values.query_items,
    128             MAX_QUERY_ITEMS,
    129         )?;
    130         Ok(Self { values })
    131     }
    132 
    133     #[must_use]
    134     pub const fn values(self) -> AdminTransportLimitValues {
    135         self.values
    136     }
    137 
    138     #[must_use]
    139     pub const fn header_count(self) -> u32 {
    140         self.values.header_count
    141     }
    142 
    143     #[must_use]
    144     pub const fn header_bytes(self) -> u32 {
    145         self.values.header_bytes
    146     }
    147 
    148     #[must_use]
    149     pub const fn request_body_utf8_bytes(self) -> u32 {
    150         self.values.request_body_utf8_bytes
    151     }
    152 
    153     #[must_use]
    154     pub const fn response_body_utf8_bytes(self) -> u32 {
    155         self.values.response_body_utf8_bytes
    156     }
    157 
    158     #[must_use]
    159     pub const fn concurrent_connections(self) -> u32 {
    160         self.values.concurrent_connections
    161     }
    162 
    163     #[must_use]
    164     pub const fn request_deadline(self) -> Duration {
    165         self.values.request_deadline
    166     }
    167 
    168     #[must_use]
    169     pub const fn idle_timeout(self) -> Duration {
    170         self.values.idle_timeout
    171     }
    172 
    173     #[must_use]
    174     pub const fn query_items(self) -> u32 {
    175         self.values.query_items
    176     }
    177 }
    178 
    179 impl Default for AdminTransportLimits {
    180     fn default() -> Self {
    181         Self::DEFAULT
    182     }
    183 }
    184 
    185 fn validate_u32(
    186     field: AdminTransportLimitField,
    187     value: u32,
    188     maximum: u32,
    189 ) -> Result<(), AdminTransportLimitsError> {
    190     if value == 0 {
    191         Err(AdminTransportLimitsError::Zero { field })
    192     } else if value > maximum {
    193         Err(AdminTransportLimitsError::ExceedsMaximum { field })
    194     } else {
    195         Ok(())
    196     }
    197 }
    198 
    199 fn validate_duration(
    200     field: AdminTransportLimitField,
    201     value: Duration,
    202     maximum: Duration,
    203 ) -> Result<(), AdminTransportLimitsError> {
    204     if value.is_zero() {
    205         Err(AdminTransportLimitsError::Zero { field })
    206     } else if value > maximum {
    207         Err(AdminTransportLimitsError::ExceedsMaximum { field })
    208     } else {
    209         Ok(())
    210     }
    211 }
    212 
    213 #[cfg(test)]
    214 mod tests {
    215     use super::*;
    216 
    217     const FIELDS: [AdminTransportLimitField; 8] = [
    218         AdminTransportLimitField::HeaderCount,
    219         AdminTransportLimitField::HeaderBytes,
    220         AdminTransportLimitField::RequestBodyUtf8Bytes,
    221         AdminTransportLimitField::ResponseBodyUtf8Bytes,
    222         AdminTransportLimitField::ConcurrentConnections,
    223         AdminTransportLimitField::RequestDeadline,
    224         AdminTransportLimitField::IdleTimeout,
    225         AdminTransportLimitField::QueryItems,
    226     ];
    227 
    228     fn maximum_values() -> AdminTransportLimitValues {
    229         AdminTransportLimitValues {
    230             header_count: MAX_HEADER_COUNT,
    231             header_bytes: MAX_HEADER_BYTES,
    232             request_body_utf8_bytes: MAX_REQUEST_BODY_UTF8_BYTES,
    233             response_body_utf8_bytes: MAX_RESPONSE_BODY_UTF8_BYTES,
    234             concurrent_connections: MAX_CONCURRENT_CONNECTIONS,
    235             request_deadline: MAX_REQUEST_DEADLINE,
    236             idle_timeout: MAX_IDLE_TIMEOUT,
    237             query_items: MAX_QUERY_ITEMS,
    238         }
    239     }
    240 
    241     fn with_field(
    242         mut values: AdminTransportLimitValues,
    243         field: AdminTransportLimitField,
    244         value: u64,
    245     ) -> AdminTransportLimitValues {
    246         match field {
    247             AdminTransportLimitField::HeaderCount => values.header_count = value as u32,
    248             AdminTransportLimitField::HeaderBytes => values.header_bytes = value as u32,
    249             AdminTransportLimitField::RequestBodyUtf8Bytes => {
    250                 values.request_body_utf8_bytes = value as u32;
    251             }
    252             AdminTransportLimitField::ResponseBodyUtf8Bytes => {
    253                 values.response_body_utf8_bytes = value as u32;
    254             }
    255             AdminTransportLimitField::ConcurrentConnections => {
    256                 values.concurrent_connections = value as u32;
    257             }
    258             AdminTransportLimitField::RequestDeadline => {
    259                 values.request_deadline = Duration::from_millis(value);
    260             }
    261             AdminTransportLimitField::IdleTimeout => {
    262                 values.idle_timeout = Duration::from_millis(value);
    263             }
    264             AdminTransportLimitField::QueryItems => values.query_items = value as u32,
    265         }
    266         values
    267     }
    268 
    269     #[test]
    270     fn exact_positive_boundaries_are_accepted_for_every_field() {
    271         assert_eq!(
    272             AdminTransportLimits::new(maximum_values())
    273                 .unwrap()
    274                 .values(),
    275             maximum_values()
    276         );
    277         for field in FIELDS {
    278             let minimum = with_field(maximum_values(), field, 1);
    279             assert!(AdminTransportLimits::new(minimum).is_ok(), "{field:?}");
    280         }
    281     }
    282 
    283     #[test]
    284     fn hard_maximum_inventory_is_exact() {
    285         assert_eq!(
    286             FIELDS.map(AdminTransportLimitField::maximum),
    287             [64, 32_768, 65_536, 1_048_576, 64, 30_000, 60_000, 200]
    288         );
    289     }
    290 
    291     #[test]
    292     fn zero_and_just_over_maximum_fail_for_every_field() {
    293         for field in FIELDS {
    294             assert_eq!(
    295                 AdminTransportLimits::new(with_field(maximum_values(), field, 0)),
    296                 Err(AdminTransportLimitsError::Zero { field })
    297             );
    298             assert_eq!(
    299                 AdminTransportLimits::new(
    300                     with_field(maximum_values(), field, field.maximum() + 1,)
    301                 ),
    302                 Err(AdminTransportLimitsError::ExceedsMaximum { field })
    303             );
    304         }
    305         let error = AdminTransportLimitsError::Zero {
    306             field: AdminTransportLimitField::HeaderCount,
    307         };
    308         assert!(!error.to_string().is_empty());
    309         assert!(error.source().is_none());
    310     }
    311 
    312     #[test]
    313     fn extreme_duration_and_integer_inputs_fail_without_overflow() {
    314         let mut durations = maximum_values();
    315         durations.request_deadline = Duration::MAX;
    316         assert_eq!(
    317             AdminTransportLimits::new(durations),
    318             Err(AdminTransportLimitsError::ExceedsMaximum {
    319                 field: AdminTransportLimitField::RequestDeadline,
    320             })
    321         );
    322 
    323         let mut integers = maximum_values();
    324         integers.response_body_utf8_bytes = u32::MAX;
    325         assert_eq!(
    326             AdminTransportLimits::new(integers),
    327             Err(AdminTransportLimitsError::ExceedsMaximum {
    328                 field: AdminTransportLimitField::ResponseBodyUtf8Bytes,
    329             })
    330         );
    331     }
    332 
    333     #[test]
    334     fn defaults_are_stable_safe_and_within_hard_bounds() {
    335         let limits = AdminTransportLimits::default();
    336         assert_eq!(
    337             limits.values(),
    338             AdminTransportLimitValues {
    339                 header_count: 32,
    340                 header_bytes: 16 * 1024,
    341                 request_body_utf8_bytes: 65_536,
    342                 response_body_utf8_bytes: 1_048_576,
    343                 concurrent_connections: 32,
    344                 request_deadline: Duration::from_secs(15),
    345                 idle_timeout: Duration::from_secs(30),
    346                 query_items: 100,
    347             }
    348         );
    349         assert!(AdminTransportLimits::new(limits.values()).is_ok());
    350     }
    351 }