config.rs (25322B)
1 //! Pure configuration for the optional TCP operations listener. 2 3 use core::{fmt, str::FromStr, time::Duration}; 4 use std::error::Error; 5 use std::net::SocketAddr; 6 7 use serde::de::Error as _; 8 use serde::ser::{Error as _, SerializeStruct}; 9 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 10 11 const MAX_HEADER_COUNT: u32 = 64; 12 const MAX_HEADER_BYTES: u32 = 32 * 1024; 13 const MAX_RESPONSE_BODY_UTF8_BYTES: u32 = 1_048_576; 14 const MAX_CONCURRENT_CONNECTIONS: u32 = 64; 15 const MAX_REQUEST_DEADLINE: Duration = Duration::from_secs(30); 16 const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(60); 17 18 /// Network scope explicitly authorized for the operations listener. 19 #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] 20 #[serde(rename_all = "snake_case")] 21 pub enum OperationsBindPolicy { 22 /// Only an IPv4 or IPv6 loopback address may be selected. 23 #[default] 24 LoopbackOnly, 25 /// A non-loopback, wildcard, or loopback address may be selected. 26 Public, 27 } 28 29 /// A parsed TCP socket address with an explicit nonzero port. 30 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 31 pub struct OperationsListenAddress(SocketAddr); 32 33 impl OperationsListenAddress { 34 pub fn new(address: SocketAddr) -> Result<Self, OperationsListenAddressError> { 35 if address.port() == 0 { 36 Err(OperationsListenAddressError::PortZero) 37 } else { 38 Ok(Self(address)) 39 } 40 } 41 42 #[must_use] 43 pub const fn socket_addr(self) -> SocketAddr { 44 self.0 45 } 46 47 #[must_use] 48 pub const fn is_loopback(self) -> bool { 49 self.0.ip().is_loopback() 50 } 51 } 52 53 impl FromStr for OperationsListenAddress { 54 type Err = OperationsListenAddressError; 55 56 fn from_str(value: &str) -> Result<Self, Self::Err> { 57 let address = value 58 .parse::<SocketAddr>() 59 .map_err(|_| OperationsListenAddressError::Invalid)?; 60 Self::new(address) 61 } 62 } 63 64 impl fmt::Display for OperationsListenAddress { 65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 66 self.0.fmt(formatter) 67 } 68 } 69 70 impl Serialize for OperationsListenAddress { 71 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 72 where 73 S: Serializer, 74 { 75 serializer.serialize_str(&self.to_string()) 76 } 77 } 78 79 /// A safe parse failure for an operations listen address. 80 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 81 pub enum OperationsListenAddressError { 82 Invalid, 83 PortZero, 84 } 85 86 impl fmt::Display for OperationsListenAddressError { 87 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 88 formatter.write_str("operations listen address is invalid") 89 } 90 } 91 92 impl Error for OperationsListenAddressError {} 93 94 /// One bounded operations transport setting. 95 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 96 pub enum OperationsTransportLimitField { 97 HeaderCount, 98 HeaderBytes, 99 ResponseBodyUtf8Bytes, 100 ConcurrentConnections, 101 RequestDeadline, 102 IdleTimeout, 103 } 104 105 impl OperationsTransportLimitField { 106 /// Returns the hard maximum in items, bytes, or milliseconds as appropriate. 107 #[must_use] 108 pub const fn maximum(self) -> u64 { 109 match self { 110 Self::HeaderCount => MAX_HEADER_COUNT as u64, 111 Self::HeaderBytes => MAX_HEADER_BYTES as u64, 112 Self::ResponseBodyUtf8Bytes => MAX_RESPONSE_BODY_UTF8_BYTES as u64, 113 Self::ConcurrentConnections => MAX_CONCURRENT_CONNECTIONS as u64, 114 Self::RequestDeadline => MAX_REQUEST_DEADLINE.as_millis() as u64, 115 Self::IdleTimeout => MAX_IDLE_TIMEOUT.as_millis() as u64, 116 } 117 } 118 } 119 120 /// A safe validation failure for operations transport limits. 121 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 122 pub enum OperationsTransportLimitsError { 123 Zero { 124 field: OperationsTransportLimitField, 125 }, 126 ExceedsMaximum { 127 field: OperationsTransportLimitField, 128 }, 129 } 130 131 impl fmt::Display for OperationsTransportLimitsError { 132 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 133 formatter.write_str("operations transport limit is outside its supported positive bounds") 134 } 135 } 136 137 impl Error for OperationsTransportLimitsError {} 138 139 /// Unvalidated values read from a service-owned configuration model. 140 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 141 pub struct OperationsTransportLimitValues { 142 pub header_count: u32, 143 pub header_bytes: u32, 144 pub response_body_utf8_bytes: u32, 145 pub concurrent_connections: u32, 146 pub request_deadline: Duration, 147 pub idle_timeout: Duration, 148 } 149 150 /// Validated resource policy for the cached operations listener. 151 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 152 pub struct OperationsTransportLimits { 153 values: OperationsTransportLimitValues, 154 } 155 156 impl OperationsTransportLimits { 157 pub const DEFAULT: Self = Self { 158 values: OperationsTransportLimitValues { 159 header_count: 32, 160 header_bytes: 16 * 1024, 161 response_body_utf8_bytes: MAX_RESPONSE_BODY_UTF8_BYTES, 162 concurrent_connections: 32, 163 request_deadline: Duration::from_secs(15), 164 idle_timeout: Duration::from_secs(30), 165 }, 166 }; 167 168 pub fn new( 169 values: OperationsTransportLimitValues, 170 ) -> Result<Self, OperationsTransportLimitsError> { 171 validate_u32( 172 OperationsTransportLimitField::HeaderCount, 173 values.header_count, 174 MAX_HEADER_COUNT, 175 )?; 176 validate_u32( 177 OperationsTransportLimitField::HeaderBytes, 178 values.header_bytes, 179 MAX_HEADER_BYTES, 180 )?; 181 validate_u32( 182 OperationsTransportLimitField::ResponseBodyUtf8Bytes, 183 values.response_body_utf8_bytes, 184 MAX_RESPONSE_BODY_UTF8_BYTES, 185 )?; 186 validate_u32( 187 OperationsTransportLimitField::ConcurrentConnections, 188 values.concurrent_connections, 189 MAX_CONCURRENT_CONNECTIONS, 190 )?; 191 validate_duration( 192 OperationsTransportLimitField::RequestDeadline, 193 values.request_deadline, 194 MAX_REQUEST_DEADLINE, 195 )?; 196 validate_duration( 197 OperationsTransportLimitField::IdleTimeout, 198 values.idle_timeout, 199 MAX_IDLE_TIMEOUT, 200 )?; 201 Ok(Self { values }) 202 } 203 204 #[must_use] 205 pub const fn values(self) -> OperationsTransportLimitValues { 206 self.values 207 } 208 209 #[must_use] 210 pub const fn header_count(self) -> u32 { 211 self.values.header_count 212 } 213 214 #[must_use] 215 pub const fn header_bytes(self) -> u32 { 216 self.values.header_bytes 217 } 218 219 #[must_use] 220 pub const fn response_body_utf8_bytes(self) -> u32 { 221 self.values.response_body_utf8_bytes 222 } 223 224 #[must_use] 225 pub const fn concurrent_connections(self) -> u32 { 226 self.values.concurrent_connections 227 } 228 229 #[must_use] 230 pub const fn request_deadline(self) -> Duration { 231 self.values.request_deadline 232 } 233 234 #[must_use] 235 pub const fn idle_timeout(self) -> Duration { 236 self.values.idle_timeout 237 } 238 } 239 240 impl Default for OperationsTransportLimits { 241 fn default() -> Self { 242 Self::DEFAULT 243 } 244 } 245 246 impl Serialize for OperationsTransportLimits { 247 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 248 where 249 S: Serializer, 250 { 251 let mut state = serializer.serialize_struct("OperationsTransportLimits", 6)?; 252 state.serialize_field("header_count", &self.header_count())?; 253 state.serialize_field("header_bytes", &self.header_bytes())?; 254 state.serialize_field("response_body_utf8_bytes", &self.response_body_utf8_bytes())?; 255 state.serialize_field("concurrent_connections", &self.concurrent_connections())?; 256 let request_deadline_ms = 257 u64::try_from(self.request_deadline().as_millis()).map_err(S::Error::custom)?; 258 let idle_timeout_ms = 259 u64::try_from(self.idle_timeout().as_millis()).map_err(S::Error::custom)?; 260 state.serialize_field("request_deadline_ms", &request_deadline_ms)?; 261 state.serialize_field("idle_timeout_ms", &idle_timeout_ms)?; 262 state.end() 263 } 264 } 265 266 /// A field that is forbidden when the operations listener is disabled. 267 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 268 pub enum OperationsConfigField { 269 Listen, 270 BindPolicy, 271 Limits, 272 } 273 274 /// A safe semantic configuration failure for the operations listener. 275 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 276 pub enum OperationsConfigError { 277 MissingListen, 278 DisabledField { field: OperationsConfigField }, 279 Listen(OperationsListenAddressError), 280 PublicBindRequiresPolicy, 281 Limits(OperationsTransportLimitsError), 282 } 283 284 impl fmt::Display for OperationsConfigError { 285 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 286 formatter.write_str("operations listener configuration is invalid") 287 } 288 } 289 290 impl Error for OperationsConfigError { 291 fn source(&self) -> Option<&(dyn Error + 'static)> { 292 match self { 293 Self::Listen(error) => Some(error), 294 Self::Limits(error) => Some(error), 295 Self::MissingListen | Self::DisabledField { .. } | Self::PublicBindRequiresPolicy => { 296 None 297 } 298 } 299 } 300 } 301 302 #[derive(Clone, Copy, PartialEq, Eq)] 303 enum OperationsListenerState { 304 Disabled, 305 Enabled { 306 listen: OperationsListenAddress, 307 bind_policy: OperationsBindPolicy, 308 limits: OperationsTransportLimits, 309 }, 310 } 311 312 /// Pure, validated configuration for the optional cached operations listener. 313 #[derive(Clone, Copy, PartialEq, Eq)] 314 pub struct OperationsListenerConfig { 315 state: OperationsListenerState, 316 } 317 318 impl OperationsListenerConfig { 319 #[must_use] 320 pub const fn disabled() -> Self { 321 Self { 322 state: OperationsListenerState::Disabled, 323 } 324 } 325 326 pub fn enabled( 327 listen: OperationsListenAddress, 328 bind_policy: OperationsBindPolicy, 329 limits: OperationsTransportLimits, 330 ) -> Result<Self, OperationsConfigError> { 331 if !listen.is_loopback() && bind_policy != OperationsBindPolicy::Public { 332 return Err(OperationsConfigError::PublicBindRequiresPolicy); 333 } 334 Ok(Self { 335 state: OperationsListenerState::Enabled { 336 listen, 337 bind_policy, 338 limits, 339 }, 340 }) 341 } 342 343 #[must_use] 344 pub const fn is_enabled(self) -> bool { 345 matches!(self.state, OperationsListenerState::Enabled { .. }) 346 } 347 348 #[must_use] 349 pub const fn listen(self) -> Option<OperationsListenAddress> { 350 match self.state { 351 OperationsListenerState::Disabled => None, 352 OperationsListenerState::Enabled { listen, .. } => Some(listen), 353 } 354 } 355 356 #[must_use] 357 pub const fn bind_policy(self) -> Option<OperationsBindPolicy> { 358 match self.state { 359 OperationsListenerState::Disabled => None, 360 OperationsListenerState::Enabled { bind_policy, .. } => Some(bind_policy), 361 } 362 } 363 364 #[must_use] 365 pub const fn limits(self) -> Option<OperationsTransportLimits> { 366 match self.state { 367 OperationsListenerState::Disabled => None, 368 OperationsListenerState::Enabled { limits, .. } => Some(limits), 369 } 370 } 371 } 372 373 impl Default for OperationsListenerConfig { 374 fn default() -> Self { 375 Self::disabled() 376 } 377 } 378 379 impl fmt::Debug for OperationsListenerConfig { 380 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 381 formatter 382 .debug_struct("OperationsListenerConfig") 383 .field("enabled", &self.is_enabled()) 384 .field("listen", &self.listen().map(|_| "[redacted]")) 385 .field("bind_policy", &self.bind_policy()) 386 .field("limits", &self.limits()) 387 .finish() 388 } 389 } 390 391 impl<'de> Deserialize<'de> for OperationsListenerConfig { 392 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 393 where 394 D: Deserializer<'de>, 395 { 396 let wire = OperationsListenerWire::deserialize(deserializer)?; 397 if !wire.enabled { 398 if wire.listen.is_some() { 399 return Err(D::Error::custom(OperationsConfigError::DisabledField { 400 field: OperationsConfigField::Listen, 401 })); 402 } 403 if wire.bind_policy.is_some() { 404 return Err(D::Error::custom(OperationsConfigError::DisabledField { 405 field: OperationsConfigField::BindPolicy, 406 })); 407 } 408 if wire.limits.is_some() { 409 return Err(D::Error::custom(OperationsConfigError::DisabledField { 410 field: OperationsConfigField::Limits, 411 })); 412 } 413 return Ok(Self::disabled()); 414 } 415 416 let listen = wire 417 .listen 418 .ok_or_else(|| D::Error::custom(OperationsConfigError::MissingListen))? 419 .parse() 420 .map_err(|error| D::Error::custom(OperationsConfigError::Listen(error)))?; 421 let limits = wire 422 .limits 423 .map(OperationsTransportLimitWire::validate) 424 .transpose() 425 .map_err(|error| D::Error::custom(OperationsConfigError::Limits(error)))? 426 .unwrap_or_default(); 427 Self::enabled(listen, wire.bind_policy.unwrap_or_default(), limits) 428 .map_err(D::Error::custom) 429 } 430 } 431 432 impl Serialize for OperationsListenerConfig { 433 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 434 where 435 S: Serializer, 436 { 437 match self.state { 438 OperationsListenerState::Disabled => { 439 let mut state = serializer.serialize_struct("OperationsListenerConfig", 1)?; 440 state.serialize_field("enabled", &false)?; 441 state.end() 442 } 443 OperationsListenerState::Enabled { 444 listen, 445 bind_policy, 446 limits, 447 } => { 448 let mut state = serializer.serialize_struct("OperationsListenerConfig", 4)?; 449 state.serialize_field("enabled", &true)?; 450 state.serialize_field("listen", &listen)?; 451 state.serialize_field("bind_policy", &bind_policy)?; 452 state.serialize_field("limits", &limits)?; 453 state.end() 454 } 455 } 456 } 457 } 458 459 #[derive(Deserialize)] 460 #[serde(deny_unknown_fields)] 461 struct OperationsListenerWire { 462 enabled: bool, 463 #[serde(default)] 464 listen: Option<String>, 465 #[serde(default)] 466 bind_policy: Option<OperationsBindPolicy>, 467 #[serde(default)] 468 limits: Option<OperationsTransportLimitWire>, 469 } 470 471 #[derive(Clone, Copy, Deserialize)] 472 #[serde(default, deny_unknown_fields)] 473 struct OperationsTransportLimitWire { 474 header_count: u32, 475 header_bytes: u32, 476 response_body_utf8_bytes: u32, 477 concurrent_connections: u32, 478 request_deadline_ms: u64, 479 idle_timeout_ms: u64, 480 } 481 482 impl OperationsTransportLimitWire { 483 fn validate(self) -> Result<OperationsTransportLimits, OperationsTransportLimitsError> { 484 OperationsTransportLimits::new(OperationsTransportLimitValues { 485 header_count: self.header_count, 486 header_bytes: self.header_bytes, 487 response_body_utf8_bytes: self.response_body_utf8_bytes, 488 concurrent_connections: self.concurrent_connections, 489 request_deadline: Duration::from_millis(self.request_deadline_ms), 490 idle_timeout: Duration::from_millis(self.idle_timeout_ms), 491 }) 492 } 493 } 494 495 impl Default for OperationsTransportLimitWire { 496 fn default() -> Self { 497 let values = OperationsTransportLimits::DEFAULT.values(); 498 Self { 499 header_count: values.header_count, 500 header_bytes: values.header_bytes, 501 response_body_utf8_bytes: values.response_body_utf8_bytes, 502 concurrent_connections: values.concurrent_connections, 503 request_deadline_ms: values.request_deadline.as_millis() as u64, 504 idle_timeout_ms: values.idle_timeout.as_millis() as u64, 505 } 506 } 507 } 508 509 fn validate_u32( 510 field: OperationsTransportLimitField, 511 value: u32, 512 maximum: u32, 513 ) -> Result<(), OperationsTransportLimitsError> { 514 if value == 0 { 515 Err(OperationsTransportLimitsError::Zero { field }) 516 } else if value > maximum { 517 Err(OperationsTransportLimitsError::ExceedsMaximum { field }) 518 } else { 519 Ok(()) 520 } 521 } 522 523 fn validate_duration( 524 field: OperationsTransportLimitField, 525 value: Duration, 526 maximum: Duration, 527 ) -> Result<(), OperationsTransportLimitsError> { 528 if value.is_zero() { 529 Err(OperationsTransportLimitsError::Zero { field }) 530 } else if value > maximum { 531 Err(OperationsTransportLimitsError::ExceedsMaximum { field }) 532 } else { 533 Ok(()) 534 } 535 } 536 537 #[cfg(test)] 538 mod tests { 539 use super::*; 540 541 const FIELDS: [OperationsTransportLimitField; 6] = [ 542 OperationsTransportLimitField::HeaderCount, 543 OperationsTransportLimitField::HeaderBytes, 544 OperationsTransportLimitField::ResponseBodyUtf8Bytes, 545 OperationsTransportLimitField::ConcurrentConnections, 546 OperationsTransportLimitField::RequestDeadline, 547 OperationsTransportLimitField::IdleTimeout, 548 ]; 549 550 #[test] 551 fn valid_loopback_and_explicit_public_addresses_are_accepted() { 552 let loopback: OperationsListenerConfig = toml::from_str( 553 r#" 554 enabled = true 555 listen = "127.0.0.1:9100" 556 "#, 557 ) 558 .expect("loopback config"); 559 assert!(loopback.is_enabled()); 560 assert_eq!( 561 loopback.bind_policy(), 562 Some(OperationsBindPolicy::LoopbackOnly) 563 ); 564 assert_eq!( 565 loopback.listen().expect("listen").to_string(), 566 "127.0.0.1:9100" 567 ); 568 assert_eq!(loopback.limits(), Some(OperationsTransportLimits::DEFAULT)); 569 570 let ipv6: OperationsListenerConfig = toml::from_str( 571 r#" 572 enabled = true 573 listen = "[::1]:9100" 574 "#, 575 ) 576 .expect("IPv6 loopback config"); 577 assert!(ipv6.listen().expect("listen").is_loopback()); 578 579 let public: OperationsListenerConfig = toml::from_str( 580 r#" 581 enabled = true 582 listen = "0.0.0.0:9100" 583 bind_policy = "public" 584 "#, 585 ) 586 .expect("explicit public config"); 587 assert_eq!(public.bind_policy(), Some(OperationsBindPolicy::Public)); 588 } 589 590 #[test] 591 fn invalid_missing_zero_port_and_implicit_public_addresses_fail() { 592 for source in [ 593 "enabled = true", 594 "enabled = true\nlisten = 'not-an-address'", 595 "enabled = true\nlisten = '127.0.0.1:0'", 596 "enabled = true\nlisten = '0.0.0.0:9100'", 597 "enabled = true\nlisten = '192.0.2.10:9100'", 598 ] { 599 assert!( 600 toml::from_str::<OperationsListenerConfig>(source).is_err(), 601 "{source}" 602 ); 603 } 604 } 605 606 #[test] 607 fn disabled_mode_has_no_dormant_bind_or_limit_state() { 608 let disabled: OperationsListenerConfig = 609 toml::from_str("enabled = false").expect("disabled config"); 610 assert_eq!(disabled, OperationsListenerConfig::disabled()); 611 assert!(!disabled.is_enabled()); 612 assert_eq!(disabled.listen(), None); 613 assert_eq!(disabled.bind_policy(), None); 614 assert_eq!(disabled.limits(), None); 615 616 for source in [ 617 "enabled = false\nlisten = '127.0.0.1:9100'", 618 "enabled = false\nbind_policy = 'loopback_only'", 619 "enabled = false\n[limits]", 620 ] { 621 assert!( 622 toml::from_str::<OperationsListenerConfig>(source).is_err(), 623 "{source}" 624 ); 625 } 626 } 627 628 #[test] 629 fn unknown_or_detailed_admin_options_are_rejected() { 630 for source in [ 631 "enabled = false\ndetailed_status = true", 632 "enabled = false\nadmin_routes = true", 633 "enabled = false\nunknown = 1", 634 "enabled = true\nlisten = '127.0.0.1:9100'\n[limits]\nunknown = 1", 635 ] { 636 assert!( 637 toml::from_str::<OperationsListenerConfig>(source).is_err(), 638 "{source}" 639 ); 640 } 641 } 642 643 #[test] 644 fn limit_inventory_defaults_and_boundaries_are_exact() { 645 assert_eq!( 646 FIELDS.map(OperationsTransportLimitField::maximum), 647 [64, 32_768, 1_048_576, 64, 30_000, 60_000] 648 ); 649 assert_eq!( 650 OperationsTransportLimits::DEFAULT.values(), 651 OperationsTransportLimitValues { 652 header_count: 32, 653 header_bytes: 16_384, 654 response_body_utf8_bytes: 1_048_576, 655 concurrent_connections: 32, 656 request_deadline: Duration::from_millis(15_000), 657 idle_timeout: Duration::from_millis(30_000), 658 } 659 ); 660 assert_eq!( 661 OperationsTransportLimits::new(maximum_values()) 662 .expect("complete maximum tuple") 663 .values(), 664 maximum_values() 665 ); 666 for field in FIELDS { 667 assert_eq!( 668 OperationsTransportLimits::new(with_field(maximum_values(), field, 0,)), 669 Err(OperationsTransportLimitsError::Zero { field }) 670 ); 671 assert_eq!( 672 OperationsTransportLimits::new(with_field( 673 maximum_values(), 674 field, 675 field.maximum() + 1, 676 )), 677 Err(OperationsTransportLimitsError::ExceedsMaximum { field }) 678 ); 679 } 680 681 let mut extreme_request = maximum_values(); 682 extreme_request.request_deadline = Duration::MAX; 683 assert_eq!( 684 OperationsTransportLimits::new(extreme_request), 685 Err(OperationsTransportLimitsError::ExceedsMaximum { 686 field: OperationsTransportLimitField::RequestDeadline, 687 }) 688 ); 689 let mut extreme_idle = maximum_values(); 690 extreme_idle.idle_timeout = Duration::MAX; 691 assert_eq!( 692 OperationsTransportLimits::new(extreme_idle), 693 Err(OperationsTransportLimitsError::ExceedsMaximum { 694 field: OperationsTransportLimitField::IdleTimeout, 695 }) 696 ); 697 for field in ["request_deadline_ms", "idle_timeout_ms"] { 698 let source = format!( 699 "enabled = true\nlisten = '127.0.0.1:9100'\n[limits]\n{field} = 9223372036854775807" 700 ); 701 assert!( 702 toml::from_str::<OperationsListenerConfig>(&source).is_err(), 703 "{field}" 704 ); 705 } 706 } 707 708 #[test] 709 fn serialization_is_exact_and_debug_redacts_the_bind_address() { 710 let config: OperationsListenerConfig = toml::from_str( 711 r#" 712 enabled = true 713 listen = "127.0.0.1:9100" 714 "#, 715 ) 716 .expect("operations config"); 717 let encoded = toml::to_string(&config).expect("serialize operations config"); 718 assert_eq!( 719 encoded, 720 concat!( 721 "enabled = true\n", 722 "listen = \"127.0.0.1:9100\"\n", 723 "bind_policy = \"loopback_only\"\n", 724 "\n[limits]\n", 725 "header_count = 32\n", 726 "header_bytes = 16384\n", 727 "response_body_utf8_bytes = 1048576\n", 728 "concurrent_connections = 32\n", 729 "request_deadline_ms = 15000\n", 730 "idle_timeout_ms = 30000\n", 731 ) 732 ); 733 assert!(!format!("{config:?}").contains("127.0.0.1")); 734 assert_eq!( 735 toml::from_str::<OperationsListenerConfig>(&encoded).expect("round trip"), 736 config 737 ); 738 } 739 740 fn maximum_values() -> OperationsTransportLimitValues { 741 OperationsTransportLimitValues { 742 header_count: MAX_HEADER_COUNT, 743 header_bytes: MAX_HEADER_BYTES, 744 response_body_utf8_bytes: MAX_RESPONSE_BODY_UTF8_BYTES, 745 concurrent_connections: MAX_CONCURRENT_CONNECTIONS, 746 request_deadline: MAX_REQUEST_DEADLINE, 747 idle_timeout: MAX_IDLE_TIMEOUT, 748 } 749 } 750 751 fn with_field( 752 mut values: OperationsTransportLimitValues, 753 field: OperationsTransportLimitField, 754 value: u64, 755 ) -> OperationsTransportLimitValues { 756 match field { 757 OperationsTransportLimitField::HeaderCount => values.header_count = value as u32, 758 OperationsTransportLimitField::HeaderBytes => values.header_bytes = value as u32, 759 OperationsTransportLimitField::ResponseBodyUtf8Bytes => { 760 values.response_body_utf8_bytes = value as u32; 761 } 762 OperationsTransportLimitField::ConcurrentConnections => { 763 values.concurrent_connections = value as u32; 764 } 765 OperationsTransportLimitField::RequestDeadline => { 766 values.request_deadline = Duration::from_millis(value); 767 } 768 OperationsTransportLimitField::IdleTimeout => { 769 values.idle_timeout = Duration::from_millis(value); 770 } 771 } 772 values 773 } 774 }