Skip to main content

net_io/
limits.rs

1//! Every bound net-io enforces, in one value.
2
3use std::time::Duration;
4
5use crate::addr::PrefixPolicy;
6use crate::driver::DriverKind;
7use crate::error::{ConfigReason, NetError};
8use crate::info::Lane;
9
10/// Lanes that can hold a connection reservation (0 to 7).
11const RESERVED_LANES: usize = 8;
12
13/// A minimum transfer rate, checked per direction while that direction is active.
14///
15/// # Examples
16///
17/// ```
18/// use std::time::Duration;
19/// let rate = net_io::MinRate::new(512, Duration::from_secs(10));
20/// assert_eq!(rate.grace, Duration::from_secs(5));
21/// assert!(net_io::MinRate::off().is_off());
22/// ```
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct MinRate {
25    /// Bytes per second the peer must reach over the window.
26    pub bytes_per_sec: u32,
27    /// The sliding window.
28    pub window: Duration,
29    /// How long after a direction becomes active the check starts.
30    pub grace: Duration,
31    /// How often the rate is checked.
32    pub check: Duration,
33}
34
35impl MinRate {
36    /// No minimum rate.
37    #[must_use]
38    pub const fn off() -> MinRate {
39        MinRate {
40            bytes_per_sec: 0,
41            window: Duration::from_secs(10),
42            grace: Duration::from_secs(5),
43            check: Duration::from_secs(1),
44        }
45    }
46
47    /// `bytes_per_sec` over `window`, after a 5 s grace, checked every second.
48    #[must_use]
49    pub const fn new(bytes_per_sec: u32, window: Duration) -> MinRate {
50        MinRate {
51            bytes_per_sec,
52            window,
53            grace: Duration::from_secs(5),
54            check: Duration::from_secs(1),
55        }
56    }
57
58    /// Whether the rate check is off.
59    #[must_use]
60    pub const fn is_off(&self) -> bool {
61        self.bytes_per_sec == 0
62    }
63}
64
65/// Adaptive deadlines: past `high` of `max_connections`, the handshake and idle
66/// deadlines of new and waiting connections are multiplied by `factor`, down to 1 s,
67/// until the count falls below `low`. No cost below the threshold beyond one check
68/// per connection opened or closed.
69///
70/// ```
71/// let p = net_io::Pressure::default();
72/// assert!(p.high > p.low);
73/// ```
74#[derive(Clone, Copy, Debug, PartialEq)]
75pub struct Pressure {
76    /// Fraction of `max_connections` above which deadlines shrink.
77    pub high: f32,
78    /// Fraction below which they return to normal.
79    pub low: f32,
80    /// The multiplier applied to handshake and idle timeouts.
81    pub factor: f32,
82}
83
84impl Default for Pressure {
85    fn default() -> Pressure {
86        Pressure {
87            high: 0.8,
88            low: 0.6,
89            factor: 0.25,
90        }
91    }
92}
93
94/// A buffer size class: a power of two from 1 KiB to 1 MiB.
95///
96/// ```
97/// use net_io::SizeClass;
98/// assert_eq!(SizeClass::MEDIUM.bytes(), 16 * 1024);
99/// assert!(SizeClass::new(3000).is_err());
100/// assert_eq!(SizeClass::new(2048).unwrap().bytes(), 2048);
101/// ```
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
103pub struct SizeClass(u32);
104
105impl SizeClass {
106    /// 4 KiB.
107    pub const SMALL: SizeClass = SizeClass(4 * 1024);
108    /// 16 KiB.
109    pub const MEDIUM: SizeClass = SizeClass(16 * 1024);
110    /// 64 KiB.
111    pub const LARGE: SizeClass = SizeClass(64 * 1024);
112
113    /// A class of `bytes`.
114    ///
115    /// # Errors
116    ///
117    /// `InvalidConfig { field: "size_class" }` unless `bytes` is a power of two from 1 KiB to 1 MiB.
118    pub const fn new(bytes: u32) -> Result<SizeClass, NetError> {
119        if !bytes.is_power_of_two() {
120            return Err(NetError::InvalidConfig {
121                field: "size_class",
122                reason: ConfigReason::Inconsistent,
123            });
124        }
125        if bytes < 1024 {
126            return Err(NetError::InvalidConfig {
127                field: "size_class",
128                reason: ConfigReason::TooSmall { min: 1024 },
129            });
130        }
131        if bytes > 1024 * 1024 {
132            return Err(NetError::InvalidConfig {
133                field: "size_class",
134                reason: ConfigReason::TooLarge { max: 1024 * 1024 },
135            });
136        }
137        Ok(SizeClass(bytes))
138    }
139
140    /// The size in bytes.
141    #[must_use]
142    pub const fn bytes(self) -> u32 {
143        self.0
144    }
145}
146
147/// Every bound net-io enforces. Unset values take the defaults of the driver
148/// that runs the connection.
149///
150/// # Examples
151///
152/// ```
153/// use net_io::{DriverKind, Limits, MinRate};
154/// use std::time::Duration;
155///
156/// let limits = Limits::builder()
157///     .max_connections(20_000)
158///     .idle_timeout(Duration::from_secs(15))
159///     .min_rate(MinRate::new(512, Duration::from_secs(10)))
160///     .build()?;
161/// assert_eq!(limits.max_connections(), Some(20_000));
162/// assert_eq!(limits.read_timeout(), None);
163/// assert_eq!(limits.resolve(DriverKind::Blocking).read_timeout(), Some(Duration::from_secs(30)));
164/// # Ok::<(), net_io::NetError>(())
165/// ```
166#[non_exhaustive]
167#[derive(Clone, Debug, Default, PartialEq)]
168pub struct Limits {
169    max_connections: Option<u32>,
170    max_per_listener: Option<u32>,
171    max_per_source: Option<(u32, PrefixPolicy)>,
172    backlog: Option<u32>,
173    accept_batch: Option<u16>,
174    handshake_timeout: Option<Duration>,
175    idle_timeout: Option<Duration>,
176    read_timeout: Option<Duration>,
177    write_timeout: Option<Duration>,
178    park_timeout: Option<Duration>,
179    min_rate: Option<MinRate>,
180    proxy_header_max: Option<u16>,
181    read_class: Option<(SizeClass, SizeClass)>,
182    output_budget: Option<u32>,
183    completion_queue: Option<u32>,
184    buffer_budget: Option<u64>,
185    drain_timeout: Option<Duration>,
186    graceful_linger: Option<(Duration, u32)>,
187    refusal_bytes_max: Option<u16>,
188    tick: Option<Duration>,
189    pressure: Option<Pressure>,
190    lane_reserve: [Option<u32>; RESERVED_LANES],
191}
192
193/// The resolved values a running connection uses.
194#[derive(Clone, Copy, Debug, PartialEq)]
195pub(crate) struct Effective {
196    pub max_connections: u32,
197    pub max_per_listener: u32,
198    pub max_per_source: u32,
199    pub source_policy: PrefixPolicy,
200    pub backlog: u32,
201    pub accept_batch: u16,
202    pub handshake_timeout: Duration,
203    pub idle_timeout: Duration,
204    pub read_timeout: Duration,
205    pub write_timeout: Duration,
206    pub park_timeout: Duration,
207    pub min_rate: MinRate,
208    pub proxy_header_max: u16,
209    pub read_class: SizeClass,
210    pub read_class_max: SizeClass,
211    pub output_budget: u32,
212    pub completion_queue: u32,
213    pub buffer_budget: u64,
214    pub drain_timeout: Duration,
215    pub graceful_linger: Duration,
216    pub graceful_linger_bytes: u32,
217    pub refusal_bytes_max: u16,
218    pub tick: Duration,
219    pub pressure: Option<Pressure>,
220    /// Slots each of the most important lanes keeps free from the less important.
221    pub lane_reserve: [u32; RESERVED_LANES],
222}
223
224impl Effective {
225    /// The slots the lanes more important than `lane` keep free.
226    pub(crate) fn reserved_above(&self, lane: Lane) -> u32 {
227        self.lane_reserve
228            .iter()
229            .take(usize::from(lane.0))
230            .fold(0u32, |sum, r| sum.saturating_add(*r))
231    }
232
233    /// The defaults of `driver` (blocking, or the reactor).
234    pub(crate) fn defaults(driver: DriverKind) -> Effective {
235        let blocking = driver.resolve() == DriverKind::Blocking;
236        let max_connections = if blocking { 128 } else { 10_000 };
237        Effective {
238            max_connections,
239            max_per_listener: max_connections,
240            max_per_source: if blocking { 16 } else { 256 },
241            source_policy: PrefixPolicy::DEFAULT,
242            backlog: 1024,
243            accept_batch: if blocking { 16 } else { 64 },
244            handshake_timeout: Duration::from_secs(10),
245            idle_timeout: Duration::from_secs(if blocking { 5 } else { 60 }),
246            read_timeout: Duration::from_secs(30),
247            write_timeout: Duration::from_secs(30),
248            park_timeout: Duration::from_secs(30),
249            min_rate: MinRate::new(256, Duration::from_secs(10)),
250            proxy_header_max: 1024,
251            read_class: SizeClass::MEDIUM,
252            read_class_max: SizeClass::LARGE,
253            output_budget: 64 * 1024,
254            completion_queue: if blocking { 1 } else { 1024 },
255            buffer_budget: if blocking { 64 << 20 } else { 512 << 20 },
256            drain_timeout: Duration::from_secs(30),
257            graceful_linger: Duration::from_secs(2),
258            graceful_linger_bytes: 64 * 1024,
259            refusal_bytes_max: 512,
260            tick: Duration::from_millis(if blocking { 10 } else { 1 }),
261            pressure: None,
262            lane_reserve: [0; RESERVED_LANES],
263        }
264    }
265}
266
267impl Limits {
268    /// A builder with every value unset.
269    ///
270    /// ```
271    /// let limits = net_io::Limits::builder().max_connections(100).build()?;
272    /// assert_eq!(limits.max_per_source(), None);
273    /// # Ok::<(), net_io::NetError>(())
274    /// ```
275    pub fn builder() -> LimitsBuilder {
276        LimitsBuilder::default()
277    }
278
279    /// Every value set to the reactor's defaults.
280    ///
281    /// ```
282    /// assert_eq!(net_io::Limits::reactor_defaults().max_connections(), Some(10_000));
283    /// ```
284    #[must_use]
285    pub fn reactor_defaults() -> Limits {
286        Limits::from_effective(&Effective::defaults(DriverKind::Reactor))
287    }
288
289    /// Every value set to the blocking driver's defaults.
290    ///
291    /// ```
292    /// assert_eq!(net_io::Limits::blocking_defaults().idle_timeout(), Some(std::time::Duration::from_secs(5)));
293    /// ```
294    #[must_use]
295    pub fn blocking_defaults() -> Limits {
296        Limits::from_effective(&Effective::defaults(DriverKind::Blocking))
297    }
298
299    /// A builder starting from these values.
300    ///
301    /// Every value carried over counts as set, so lowering `max_connections`
302    /// below a carried-over `max_per_listener` fails validation. Start from
303    /// [`Limits::builder`] to let dependent values follow `max_connections`.
304    ///
305    /// ```
306    /// let limits = net_io::Limits::blocking_defaults().to_builder().max_connections(256).build()?;
307    /// assert_eq!(limits.max_connections(), Some(256));
308    /// # Ok::<(), net_io::NetError>(())
309    /// ```
310    pub fn to_builder(&self) -> LimitsBuilder {
311        LimitsBuilder {
312            limits: self.clone(),
313        }
314    }
315
316    /// These limits with unset values filled from `driver`'s defaults.
317    ///
318    /// ```
319    /// let limits = net_io::Limits::builder().max_connections(10).build()?;
320    /// let resolved = limits.resolve(net_io::DriverKind::Blocking);
321    /// assert_eq!(resolved.max_per_source().map(|(n, _)| n), Some(10));
322    /// # Ok::<(), net_io::NetError>(())
323    /// ```
324    #[must_use]
325    pub fn resolve(&self, driver: DriverKind) -> Limits {
326        let mut out = Limits::from_effective(&self.effective(driver));
327        out.pressure = self.pressure;
328        out.lane_reserve = self.lane_reserve;
329        out
330    }
331
332    /// Checks the resolved values against each other, the worker count, and the
333    /// process's descriptor limit.
334    ///
335    /// # Errors
336    ///
337    /// `InvalidConfig { field, reason }` for the first rule broken.
338    ///
339    /// ```
340    /// let limits = net_io::Limits::builder().max_connections(1_000_000).build()?;
341    /// assert!(limits.validate(4, Some(1024)).is_err());
342    /// # Ok::<(), net_io::NetError>(())
343    /// ```
344    pub fn validate(&self, workers: u16, descriptor_limit: Option<u64>) -> Result<(), NetError> {
345        // Rules that need only the values themselves.
346        LimitsBuilder {
347            limits: self.clone(),
348        }
349        .check()?;
350        let e = self.effective(DriverKind::Blocking);
351        let need_budget = u64::from(workers)
352            .saturating_mul(u64::from(e.read_class_max.bytes()))
353            .saturating_mul(2);
354        if e.buffer_budget < need_budget {
355            return Err(invalid(
356                "buffer_budget",
357                ConfigReason::BudgetBelowWorkers { need: need_budget },
358            ));
359        }
360        if let Some(have) = descriptor_limit {
361            let need = descriptors_needed(e.max_connections, workers);
362            if need > have {
363                return Err(invalid(
364                    "max_connections",
365                    ConfigReason::DescriptorLimit { have, need },
366                ));
367            }
368        }
369        Ok(())
370    }
371
372    /// The resolved values for `driver`.
373    pub(crate) fn effective(&self, driver: DriverKind) -> Effective {
374        let d = Effective::defaults(driver);
375        let max_connections = self.max_connections.unwrap_or(d.max_connections);
376        let (max_per_source, source_policy) = self
377            .max_per_source
378            .unwrap_or((d.max_per_source.min(max_connections), d.source_policy));
379        let (read_class, read_class_max) =
380            self.read_class.unwrap_or((d.read_class, d.read_class_max));
381        let (graceful_linger, graceful_linger_bytes) = self
382            .graceful_linger
383            .unwrap_or((d.graceful_linger, d.graceful_linger_bytes));
384        Effective {
385            max_connections,
386            max_per_listener: self.max_per_listener.unwrap_or(max_connections),
387            max_per_source,
388            source_policy,
389            backlog: self.backlog.unwrap_or(d.backlog),
390            accept_batch: self.accept_batch.unwrap_or(d.accept_batch),
391            handshake_timeout: self.handshake_timeout.unwrap_or(d.handshake_timeout),
392            idle_timeout: self.idle_timeout.unwrap_or(d.idle_timeout),
393            read_timeout: self.read_timeout.unwrap_or(d.read_timeout),
394            write_timeout: self.write_timeout.unwrap_or(d.write_timeout),
395            park_timeout: self.park_timeout.unwrap_or(d.park_timeout),
396            min_rate: self.min_rate.unwrap_or(d.min_rate),
397            proxy_header_max: self.proxy_header_max.unwrap_or(d.proxy_header_max),
398            read_class,
399            read_class_max,
400            output_budget: self.output_budget.unwrap_or(d.output_budget),
401            completion_queue: self.completion_queue.unwrap_or(d.completion_queue),
402            buffer_budget: self.buffer_budget.unwrap_or(d.buffer_budget),
403            drain_timeout: self.drain_timeout.unwrap_or(d.drain_timeout),
404            graceful_linger,
405            graceful_linger_bytes,
406            refusal_bytes_max: self.refusal_bytes_max.unwrap_or(d.refusal_bytes_max),
407            tick: self.tick.unwrap_or(d.tick),
408            pressure: self.pressure,
409            lane_reserve: self.lane_reserve.map(|v| v.unwrap_or(0)),
410        }
411    }
412
413    /// Values from `other` where it sets them, otherwise from `self` (listener overrides).
414    pub(crate) fn overlay(&self, other: &Limits) -> Limits {
415        macro_rules! pick {
416            ($($f:ident),*) => { Limits { $($f: other.$f.or(self.$f),)* lane_reserve: {
417                let mut r = self.lane_reserve;
418                for (i, v) in other.lane_reserve.iter().enumerate() { if v.is_some() { r[i] = *v; } }
419                r
420            } } };
421        }
422        pick!(
423            max_connections,
424            max_per_listener,
425            max_per_source,
426            backlog,
427            accept_batch,
428            handshake_timeout,
429            idle_timeout,
430            read_timeout,
431            write_timeout,
432            park_timeout,
433            min_rate,
434            proxy_header_max,
435            read_class,
436            output_budget,
437            completion_queue,
438            buffer_budget,
439            drain_timeout,
440            graceful_linger,
441            refusal_bytes_max,
442            tick,
443            pressure
444        )
445    }
446
447    fn from_effective(e: &Effective) -> Limits {
448        Limits {
449            max_connections: Some(e.max_connections),
450            max_per_listener: Some(e.max_per_listener),
451            max_per_source: Some((e.max_per_source, e.source_policy)),
452            backlog: Some(e.backlog),
453            accept_batch: Some(e.accept_batch),
454            handshake_timeout: Some(e.handshake_timeout),
455            idle_timeout: Some(e.idle_timeout),
456            read_timeout: Some(e.read_timeout),
457            write_timeout: Some(e.write_timeout),
458            park_timeout: Some(e.park_timeout),
459            min_rate: Some(e.min_rate),
460            proxy_header_max: Some(e.proxy_header_max),
461            read_class: Some((e.read_class, e.read_class_max)),
462            output_budget: Some(e.output_budget),
463            completion_queue: Some(e.completion_queue),
464            buffer_budget: Some(e.buffer_budget),
465            drain_timeout: Some(e.drain_timeout),
466            graceful_linger: Some((e.graceful_linger, e.graceful_linger_bytes)),
467            refusal_bytes_max: Some(e.refusal_bytes_max),
468            tick: Some(e.tick),
469            pressure: None,
470            lane_reserve: [None; RESERVED_LANES],
471        }
472    }
473
474    /// The global connection cap.
475    #[must_use]
476    pub fn max_connections(&self) -> Option<u32> {
477        self.max_connections
478    }
479    /// The per-listener connection cap.
480    #[must_use]
481    pub fn max_per_listener(&self) -> Option<u32> {
482        self.max_per_listener
483    }
484    /// The per-source concurrency cap and how sources are grouped. Each listener
485    /// applies its own value to its own connections. Every open connection counts,
486    /// including one finishing a graceful close, so a client at the cap that
487    /// reconnects at once can be refused until its last connection is closed.
488    #[must_use]
489    pub fn max_per_source(&self) -> Option<(u32, PrefixPolicy)> {
490        self.max_per_source
491    }
492    /// The listen backlog.
493    #[must_use]
494    pub fn backlog(&self) -> Option<u32> {
495        self.backlog
496    }
497    /// Connections accepted per wake-up.
498    #[must_use]
499    pub fn accept_batch(&self) -> Option<u16> {
500        self.accept_batch
501    }
502    /// Time for the PROXY header and TLS handshake together.
503    #[must_use]
504    pub fn handshake_timeout(&self) -> Option<Duration> {
505        self.handshake_timeout
506    }
507    /// Time to wait for the next exchange.
508    #[must_use]
509    pub fn idle_timeout(&self) -> Option<Duration> {
510        self.idle_timeout
511    }
512    /// Time to wait for bytes during an exchange.
513    #[must_use]
514    pub fn read_timeout(&self) -> Option<Duration> {
515        self.read_timeout
516    }
517    /// Time to wait for the peer to take bytes.
518    #[must_use]
519    pub fn write_timeout(&self) -> Option<Duration> {
520        self.write_timeout
521    }
522    /// Time a parked connection may wait for its completion.
523    #[must_use]
524    pub fn park_timeout(&self) -> Option<Duration> {
525        self.park_timeout
526    }
527    /// The minimum transfer rate.
528    #[must_use]
529    pub fn min_rate(&self) -> Option<MinRate> {
530        self.min_rate
531    }
532    /// The PROXY v2 address-and-TLV cap.
533    #[must_use]
534    pub fn proxy_header_max(&self) -> Option<u16> {
535        self.proxy_header_max
536    }
537    /// The initial and maximum read buffer class.
538    #[must_use]
539    pub fn read_class(&self) -> Option<(SizeClass, SizeClass)> {
540        self.read_class
541    }
542    /// Pool memory a connection's output may hold.
543    #[must_use]
544    pub fn output_budget(&self) -> Option<u32> {
545        self.output_budget
546    }
547    /// A worker's completion queue length.
548    #[must_use]
549    pub fn completion_queue(&self) -> Option<u32> {
550        self.completion_queue
551    }
552    /// Total bytes of buffers all workers may allocate.
553    #[must_use]
554    pub fn buffer_budget(&self) -> Option<u64> {
555        self.buffer_budget
556    }
557    /// The drain deadline.
558    #[must_use]
559    pub fn drain_timeout(&self) -> Option<Duration> {
560        self.drain_timeout
561    }
562    /// The graceful-close linger: time and bytes read and discarded.
563    #[must_use]
564    pub fn graceful_linger(&self) -> Option<(Duration, u32)> {
565        self.graceful_linger
566    }
567    /// The largest refusal reply.
568    #[must_use]
569    pub fn refusal_bytes_max(&self) -> Option<u16> {
570        self.refusal_bytes_max
571    }
572    /// The timer resolution.
573    #[must_use]
574    pub fn tick(&self) -> Option<Duration> {
575        self.tick
576    }
577    /// Adaptive deadlines under pressure.
578    #[must_use]
579    pub fn pressure(&self) -> Option<Pressure> {
580        self.pressure
581    }
582    /// The connection slots reserved for `lane` (lanes 0 to 7).
583    #[must_use]
584    pub fn lane_reserve(&self, lane: Lane) -> Option<u32> {
585        self.lane_reserve
586            .get(usize::from(lane.0))
587            .copied()
588            .flatten()
589    }
590}
591
592/// Descriptors a configuration needs: connections, two wake descriptors per
593/// worker, and a fixed reserve for listeners and the spare descriptor.
594pub(crate) fn descriptors_needed(max_connections: u32, workers: u16) -> u64 {
595    u64::from(max_connections) + 2 * u64::from(workers) + 32
596}
597
598fn invalid(field: &'static str, reason: ConfigReason) -> NetError {
599    NetError::InvalidConfig { field, reason }
600}
601
602/// Builds [`Limits`]; every setter is optional.
603///
604/// ```
605/// use net_io::{ConfigReason, Limits, NetError};
606/// let err = Limits::builder().max_connections(100).max_per_listener(200).build().unwrap_err();
607/// assert!(matches!(err, NetError::InvalidConfig { field: "max_per_listener", reason: ConfigReason::AboveGlobal }));
608/// assert!(Limits::builder().max_connections(100).build().is_ok());
609/// ```
610#[must_use]
611#[derive(Clone, Debug, Default)]
612pub struct LimitsBuilder {
613    limits: Limits,
614}
615
616macro_rules! setter {
617    ($(#[$m:meta])* $name:ident, $t:ty) => {
618        $(#[$m])*
619        pub fn $name(mut self, value: $t) -> Self {
620            self.limits.$name = Some(value);
621            self
622        }
623    };
624}
625
626impl LimitsBuilder {
627    setter!(/// The global connection cap.
628        max_connections, u32);
629    setter!(/// The per-listener connection cap.
630        max_per_listener, u32);
631    setter!(/// The listen backlog.
632        backlog, u32);
633    setter!(/// Connections accepted per wake-up.
634        accept_batch, u16);
635    setter!(/// Time for the PROXY header and TLS handshake together.
636        handshake_timeout, Duration);
637    setter!(/// Time to wait for the next exchange.
638        idle_timeout, Duration);
639    setter!(/// Time to wait for bytes during an exchange.
640        read_timeout, Duration);
641    setter!(/// Time to wait for the peer to take bytes.
642        write_timeout, Duration);
643    setter!(/// Time a parked connection may wait for its completion.
644        park_timeout, Duration);
645    setter!(/// The minimum transfer rate ([`MinRate::off`] to disable).
646        min_rate, MinRate);
647    setter!(/// Pool memory a connection's output may hold.
648        output_budget, u32);
649    setter!(/// Total bytes of buffers all workers may allocate.
650        buffer_budget, u64);
651    setter!(/// The drain deadline.
652        drain_timeout, Duration);
653    setter!(/// The largest refusal reply (at most 512).
654        refusal_bytes_max, u16);
655    setter!(/// The timer resolution (1 ms to 100 ms).
656        tick, Duration);
657    setter!(/// Adaptive deadlines under pressure.
658        pressure, Pressure);
659
660    /// The per-source concurrency cap and how sources are grouped.
661    pub fn max_per_source(mut self, n: u32, policy: PrefixPolicy) -> Self {
662        self.limits.max_per_source = Some((n, policy));
663        self
664    }
665
666    /// The PROXY v2 address-and-TLV cap (up to 65,519).
667    pub fn proxy_header_max(mut self, v2_body_bytes: u16) -> Self {
668        self.limits.proxy_header_max = Some(v2_body_bytes);
669        self
670    }
671
672    /// The initial read buffer class and the largest a session may grow it to.
673    pub fn read_class(mut self, initial: SizeClass, max: SizeClass) -> Self {
674        self.limits.read_class = Some((initial, max));
675        self
676    }
677
678    /// A worker's completion queue length, rounded up to a power of two.
679    pub fn completion_queue(mut self, n: u32) -> Self {
680        self.limits.completion_queue = Some(n);
681        self
682    }
683
684    /// The graceful-close linger: time and bytes read and discarded.
685    pub fn graceful_linger(mut self, time: Duration, bytes: u32) -> Self {
686        self.limits.graceful_linger = Some((time, bytes));
687        self
688    }
689
690    /// Connection slots reserved for `lane` (lanes 0 to 7; applied from `0.6.0`).
691    pub fn lane_reserve(mut self, lane: Lane, slots: u32) -> Self {
692        match self.limits.lane_reserve.get_mut(usize::from(lane.0)) {
693            Some(slot) => *slot = Some(slots),
694            // Rejected by `build` (field "lane_reserve").
695            None => self.limits.lane_reserve[RESERVED_LANES - 1] = Some(u32::MAX),
696        }
697        self
698    }
699
700    /// Checks the values that were set and returns the limits.
701    ///
702    /// # Errors
703    ///
704    /// `InvalidConfig { field, reason }` for the first rule broken, in the order
705    /// documented in `docs/API.md`.
706    pub fn build(self) -> Result<Limits, NetError> {
707        self.check()?;
708        let mut limits = self.limits;
709        if let Some(n) = limits.completion_queue {
710            limits.completion_queue = Some(n.next_power_of_two());
711        }
712        Ok(limits)
713    }
714
715    fn check(&self) -> Result<(), NetError> {
716        let l = &self.limits;
717        // Zero where zero is meaningless.
718        let zeros: [(&'static str, bool); 6] = [
719            ("max_connections", l.max_connections == Some(0)),
720            ("backlog", l.backlog == Some(0)),
721            ("accept_batch", l.accept_batch == Some(0)),
722            ("max_per_source", matches!(l.max_per_source, Some((0, _)))),
723            ("completion_queue", l.completion_queue == Some(0)),
724            ("output_budget", l.output_budget == Some(0)),
725        ];
726        for (field, zero) in zeros {
727            if zero {
728                return Err(invalid(field, ConfigReason::Zero));
729            }
730        }
731        if let Some(max) = l.max_connections {
732            if l.max_per_listener.is_some_and(|n| n > max) {
733                return Err(invalid("max_per_listener", ConfigReason::AboveGlobal));
734            }
735            if l.max_per_source.is_some_and(|(n, _)| n > max) {
736                return Err(invalid("max_per_source", ConfigReason::AboveGlobal));
737            }
738        }
739        if let Some(tick) = l.tick {
740            if tick < Duration::from_millis(1) {
741                return Err(invalid("tick", ConfigReason::TooSmall { min: 1 }));
742            }
743            if tick > Duration::from_millis(100) {
744                return Err(invalid("tick", ConfigReason::TooLarge { max: 100 }));
745            }
746        }
747        let tick = l.tick.unwrap_or(Duration::from_millis(1));
748        let timeouts: [(&'static str, Option<Duration>); 6] = [
749            ("handshake_timeout", l.handshake_timeout),
750            ("idle_timeout", l.idle_timeout),
751            ("read_timeout", l.read_timeout),
752            ("write_timeout", l.write_timeout),
753            ("park_timeout", l.park_timeout),
754            ("drain_timeout", l.drain_timeout),
755        ];
756        for (field, value) in timeouts {
757            if value.is_some_and(|d| d < tick) {
758                return Err(invalid(field, ConfigReason::BelowTick));
759            }
760        }
761        if let Some(rate) = l.min_rate {
762            if !rate.is_off() {
763                if rate.check.is_zero() || rate.grace.is_zero() {
764                    return Err(invalid("min_rate", ConfigReason::Zero));
765                }
766                if rate.window < rate.check {
767                    return Err(invalid("min_rate", ConfigReason::WindowBelowCheck));
768                }
769            }
770        }
771        if let Some((initial, max)) = l.read_class {
772            if max < initial {
773                return Err(invalid("read_class", ConfigReason::Inconsistent));
774            }
775        }
776        if l.refusal_bytes_max.is_some_and(|n| n > 512) {
777            return Err(invalid(
778                "refusal_bytes_max",
779                ConfigReason::TooLarge { max: 512 },
780            ));
781        }
782        if l.proxy_header_max.is_some_and(|n| n > 65_519) {
783            return Err(invalid(
784                "proxy_header_max",
785                ConfigReason::TooLarge { max: 65_519 },
786            ));
787        }
788        if l.completion_queue.is_some_and(|n| n > 1 << 20) {
789            return Err(invalid(
790                "completion_queue",
791                ConfigReason::TooLarge { max: 1 << 20 },
792            ));
793        }
794        if let Some(p) = l.pressure {
795            let factor_ok = p.factor > 0.0 && p.factor <= 1.0;
796            let ordered = p.high.partial_cmp(&p.low) == Some(std::cmp::Ordering::Greater);
797            if !ordered
798                || !factor_ok
799                || !(0.0..=1.0).contains(&p.high)
800                || !(0.0..=1.0).contains(&p.low)
801            {
802                return Err(invalid("pressure", ConfigReason::Inconsistent));
803            }
804        }
805        if l.lane_reserve[RESERVED_LANES - 1] == Some(u32::MAX) {
806            return Err(invalid("lane_reserve", ConfigReason::TooLarge { max: 7 }));
807        }
808        let reserved: u64 = l.lane_reserve.iter().flatten().map(|n| u64::from(*n)).sum();
809        if let Some(max) = l.max_connections {
810            if reserved > u64::from(max) {
811                return Err(invalid("lane_reserve", ConfigReason::AboveGlobal));
812            }
813        }
814        Ok(())
815    }
816}
817
818#[cfg(test)]
819mod tests {
820    use super::*;
821
822    #[test]
823    fn test_unset_values_take_driver_defaults() {
824        let l = Limits::builder().build().unwrap();
825        assert_eq!(
826            l.effective(DriverKind::Blocking).idle_timeout,
827            Duration::from_secs(5)
828        );
829        assert_eq!(
830            l.effective(DriverKind::Reactor).idle_timeout,
831            Duration::from_secs(60)
832        );
833    }
834
835    #[test]
836    fn test_dependent_defaults_follow_max_connections() {
837        let l = Limits::builder().max_connections(10).build().unwrap();
838        let e = l.effective(DriverKind::Reactor);
839        assert_eq!(e.max_per_listener, 10);
840        assert_eq!(e.max_per_source, 10);
841    }
842
843    #[test]
844    fn test_zero_values_rejected() {
845        for b in [
846            Limits::builder().max_connections(0),
847            Limits::builder().backlog(0),
848            Limits::builder().accept_batch(0),
849            Limits::builder().max_per_source(0, PrefixPolicy::DEFAULT),
850            Limits::builder().completion_queue(0),
851            Limits::builder().output_budget(0),
852        ] {
853            assert!(matches!(
854                b.build(),
855                Err(NetError::InvalidConfig {
856                    reason: ConfigReason::Zero,
857                    ..
858                })
859            ));
860        }
861    }
862
863    #[test]
864    fn test_timeout_below_tick_rejected() {
865        let err = Limits::builder()
866            .tick(Duration::from_millis(100))
867            .read_timeout(Duration::from_millis(50))
868            .build()
869            .unwrap_err();
870        assert!(matches!(
871            err,
872            NetError::InvalidConfig {
873                field: "read_timeout",
874                reason: ConfigReason::BelowTick
875            }
876        ));
877    }
878
879    #[test]
880    fn test_min_rate_window_below_check_rejected() {
881        let rate = MinRate {
882            bytes_per_sec: 10,
883            window: Duration::from_millis(500),
884            grace: Duration::from_secs(1),
885            check: Duration::from_secs(1),
886        };
887        assert!(matches!(
888            Limits::builder().min_rate(rate).build(),
889            Err(NetError::InvalidConfig {
890                reason: ConfigReason::WindowBelowCheck,
891                ..
892            })
893        ));
894        assert!(Limits::builder().min_rate(MinRate::off()).build().is_ok());
895    }
896
897    #[test]
898    fn test_bounds_too_large() {
899        assert!(Limits::builder().refusal_bytes_max(513).build().is_err());
900        assert!(Limits::builder().refusal_bytes_max(512).build().is_ok());
901        assert!(Limits::builder().proxy_header_max(65_520).build().is_err());
902        assert!(
903            Limits::builder()
904                .completion_queue((1 << 20) + 1)
905                .build()
906                .is_err()
907        );
908    }
909
910    #[test]
911    fn test_completion_queue_rounded_to_power_of_two() {
912        let l = Limits::builder().completion_queue(1000).build().unwrap();
913        assert_eq!(l.completion_queue(), Some(1024));
914    }
915
916    #[test]
917    fn test_pressure_and_lane_reserve_validation() {
918        let bad = Pressure {
919            high: 0.5,
920            low: 0.6,
921            factor: 0.5,
922        };
923        assert!(Limits::builder().pressure(bad).build().is_err());
924        assert!(Limits::builder().lane_reserve(Lane(8), 1).build().is_err());
925        assert!(
926            Limits::builder()
927                .max_connections(10)
928                .lane_reserve(Lane(0), 6)
929                .lane_reserve(Lane(1), 5)
930                .build()
931                .is_err()
932        );
933    }
934
935    #[test]
936    fn test_validate_descriptor_limit_and_budget() {
937        let l = Limits::builder().max_connections(1000).build().unwrap();
938        assert!(matches!(
939            l.validate(4, Some(500)),
940            Err(NetError::InvalidConfig {
941                reason: ConfigReason::DescriptorLimit { have: 500, .. },
942                ..
943            })
944        ));
945        assert!(l.validate(4, Some(100_000)).is_ok());
946        let small = Limits::builder().buffer_budget(1024).build().unwrap();
947        assert!(matches!(
948            small.validate(4, None),
949            Err(NetError::InvalidConfig {
950                reason: ConfigReason::BudgetBelowWorkers { .. },
951                ..
952            })
953        ));
954    }
955
956    #[test]
957    fn test_overlay_takes_listener_values() {
958        let base = Limits::builder()
959            .max_connections(100)
960            .idle_timeout(Duration::from_secs(9))
961            .build()
962            .unwrap();
963        let over = Limits::builder()
964            .idle_timeout(Duration::from_secs(1))
965            .build()
966            .unwrap();
967        let merged = base.overlay(&over);
968        assert_eq!(merged.max_connections(), Some(100));
969        assert_eq!(merged.idle_timeout(), Some(Duration::from_secs(1)));
970    }
971
972    #[test]
973    fn test_resolve_sets_every_value() {
974        let r = Limits::default().resolve(DriverKind::Blocking);
975        assert!(
976            r.max_connections().is_some() && r.tick().is_some() && r.graceful_linger().is_some()
977        );
978    }
979}