lib

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

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 }