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 }