Skip to main content

http_net/
limits.rs

1//! Budgets that bound what a peer can make a connection buffer or accept.
2
3/// Upper bound for the start-line and header-section budgets. Keeps every
4/// buffer offset inside `u32` and every buffer allocation sane.
5const HARD_CAP: usize = 16 * 1024 * 1024;
6
7/// Bytes of buffer space, beyond the head budget, used to stream body data.
8const BODY_WINDOW: usize = 16 * 1024;
9
10/// The budgets a connection enforces on incoming messages.
11///
12/// Every limit is checked **before** the bytes it governs are buffered or
13/// allocated: a declared `Content-Length` above [`max_body`](Limits::max_body)
14/// is refused on the header line, a chunk size is checked against the
15/// remaining body budget before its data is read, and the connection's input
16/// buffer can never grow past [`buffer_capacity`](Limits::buffer_capacity):
17/// `head + max(head, max_trailer_bytes) + 16 KiB`, where
18/// `head = max_start_line + max_header_bytes` (64 KiB with the defaults):
19/// room for a pinned head plus one partial chunk line or trailer section.
20///
21/// The defaults are the budgets of LexerSketch Studio's localhost server:
22///
23/// | Budget | Default |
24/// |---|---|
25/// | request line / status line / chunk-size line | 8 KiB |
26/// | header section (all field lines) | 16 KiB |
27/// | one field line (headers and trailers) | 8 KiB |
28/// | header fields | 100 |
29/// | trailer section, trailer fields | the header budgets |
30/// | interim (1xx) responses per request (client) | 16 |
31/// | body | 1 MiB |
32/// | requests outstanding at once (client pipelining) | 1, clamped to `1..=32` |
33///
34/// # Examples
35///
36/// ```
37/// use http_net::Limits;
38///
39/// let limits = Limits::new().with_max_body(64 * 1024).with_max_headers(32);
40/// assert_eq!(limits.max_body(), 64 * 1024);
41/// assert_eq!(limits.max_headers(), 32);
42/// assert_eq!(limits.max_start_line(), 8 * 1024);
43/// ```
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
45pub struct Limits {
46    start_line: usize,
47    header_bytes: usize,
48    headers: usize,
49    field_line: usize,
50    /// `None`: the same as the header budget.
51    trailer_bytes: Option<usize>,
52    /// `None`: the same as the header count.
53    trailer_fields: Option<usize>,
54    interim: u16,
55    pipeline_depth: u8,
56    body: u64,
57}
58
59/// The most requests a client may have outstanding at once.
60pub(crate) const MAX_PIPELINE_DEPTH: u8 = 32;
61
62impl Default for Limits {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl Limits {
69    /// The default budgets (see the table on [`Limits`]).
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// assert_eq!(http_net::Limits::new(), http_net::Limits::default());
75    /// ```
76    #[must_use]
77    pub const fn new() -> Self {
78        Self {
79            start_line: 8 * 1024,
80            header_bytes: 16 * 1024,
81            headers: 100,
82            field_line: 8 * 1024,
83            trailer_bytes: None,
84            trailer_fields: None,
85            interim: 16,
86            pipeline_depth: 1,
87            body: 1024 * 1024,
88        }
89    }
90
91    /// Sets the longest accepted request line, status line, or chunk-size
92    /// line, in bytes including the CRLF. Clamped to `16..=16 MiB`.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// let l = http_net::Limits::new().with_max_start_line(1);
98    /// assert_eq!(l.max_start_line(), 16);
99    /// ```
100    #[must_use]
101    pub const fn with_max_start_line(mut self, bytes: usize) -> Self {
102        self.start_line = clamp(bytes, 16, HARD_CAP);
103        self
104    }
105
106    /// Sets the largest accepted header section (every field line plus the
107    /// blank line that ends it), in bytes. Clamped to `2..=16 MiB`.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// let l = http_net::Limits::new().with_max_header_bytes(4096);
113    /// assert_eq!(l.max_header_bytes(), 4096);
114    /// ```
115    #[must_use]
116    pub const fn with_max_header_bytes(mut self, bytes: usize) -> Self {
117        self.header_bytes = clamp(bytes, 2, HARD_CAP);
118        self
119    }
120
121    /// Sets the most header fields accepted in one message (trailer fields
122    /// are counted against the same number separately). Clamped to
123    /// `0..=65536`.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// let l = http_net::Limits::new().with_max_headers(8);
129    /// assert_eq!(l.max_headers(), 8);
130    /// ```
131    #[must_use]
132    pub const fn with_max_headers(mut self, count: usize) -> Self {
133        self.headers = clamp(count, 0, 65_536);
134        self
135    }
136
137    /// Sets the longest accepted single field line (name, colon, value, and
138    /// CRLF), in headers and trailers alike. Clamped to `4..=16 MiB`. A line
139    /// over it is [`HttpError::FieldLineTooLong`](crate::HttpError::FieldLineTooLong)
140    /// (431), checked before the rest of the line is buffered. The whole
141    /// section is still bounded by
142    /// [`max_header_bytes`](Self::max_header_bytes).
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// let l = http_net::Limits::new().with_max_field_line(1024);
148    /// assert_eq!(l.max_field_line(), 1024);
149    /// ```
150    #[must_use]
151    pub const fn with_max_field_line(mut self, bytes: usize) -> Self {
152        self.field_line = clamp(bytes, 4, HARD_CAP);
153        self
154    }
155
156    /// Sets the largest accepted trailer section, in bytes (every trailer
157    /// field line plus the blank line that ends it). Clamped to
158    /// `2..=16 MiB`. Until it is set it follows
159    /// [`max_header_bytes`](Self::max_header_bytes).
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// let l = http_net::Limits::new().with_max_trailer_bytes(512);
165    /// assert_eq!(l.max_trailer_bytes(), 512);
166    /// assert_eq!(http_net::Limits::new().max_trailer_bytes(), 16 * 1024);
167    /// ```
168    #[must_use]
169    pub const fn with_max_trailer_bytes(mut self, bytes: usize) -> Self {
170        self.trailer_bytes = Some(clamp(bytes, 2, HARD_CAP));
171        self
172    }
173
174    /// Sets the most trailer fields accepted in one message. Clamped to
175    /// `0..=65536`. Until it is set it follows
176    /// [`max_headers`](Self::max_headers).
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// let l = http_net::Limits::new().with_max_trailer_fields(4);
182    /// assert_eq!(l.max_trailer_fields(), 4);
183    /// ```
184    #[must_use]
185    pub const fn with_max_trailer_fields(mut self, count: usize) -> Self {
186        self.trailer_fields = Some(clamp(count, 0, 65_536));
187        self
188    }
189
190    /// Sets the most interim (1xx) responses a client accepts before the
191    /// final response to one request; one more is
192    /// [`HttpError::TooManyInterim`](crate::HttpError::TooManyInterim). The
193    /// interim heads together also count against
194    /// [`max_header_bytes`](Self::max_header_bytes).
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// let l = http_net::Limits::new().with_max_interim(2);
200    /// assert_eq!(l.max_interim(), 2);
201    /// ```
202    #[must_use]
203    pub const fn with_max_interim(mut self, count: u16) -> Self {
204        self.interim = count;
205        self
206    }
207
208    /// Sets the largest accepted body, in bytes after chunked decoding.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// let l = http_net::Limits::new().with_max_body(0);
214    /// assert_eq!(l.max_body(), 0);
215    /// ```
216    #[must_use]
217    pub const fn with_max_body(mut self, bytes: u64) -> Self {
218        self.body = bytes;
219        self
220    }
221
222    /// The longest accepted start line or chunk-size line, CRLF included.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// assert_eq!(http_net::Limits::new().max_start_line(), 8192);
228    /// ```
229    #[must_use]
230    pub const fn max_start_line(&self) -> usize {
231        self.start_line
232    }
233
234    /// The largest accepted header section, in bytes.
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// assert_eq!(http_net::Limits::new().max_header_bytes(), 16384);
240    /// ```
241    #[must_use]
242    pub const fn max_header_bytes(&self) -> usize {
243        self.header_bytes
244    }
245
246    /// The most header fields accepted in one message.
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// assert_eq!(http_net::Limits::new().max_headers(), 100);
252    /// ```
253    #[must_use]
254    pub const fn max_headers(&self) -> usize {
255        self.headers
256    }
257
258    /// The longest accepted single field line, CRLF included.
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// assert_eq!(http_net::Limits::new().max_field_line(), 8192);
264    /// ```
265    #[must_use]
266    pub const fn max_field_line(&self) -> usize {
267        self.field_line
268    }
269
270    /// The largest accepted trailer section, in bytes.
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// let l = http_net::Limits::new().with_max_header_bytes(4096);
276    /// assert_eq!(l.max_trailer_bytes(), 4096);
277    /// ```
278    #[must_use]
279    pub const fn max_trailer_bytes(&self) -> usize {
280        match self.trailer_bytes {
281            Some(n) => n,
282            None => self.header_bytes,
283        }
284    }
285
286    /// The most trailer fields accepted in one message.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// assert_eq!(http_net::Limits::new().max_trailer_fields(), 100);
292    /// ```
293    #[must_use]
294    pub const fn max_trailer_fields(&self) -> usize {
295        match self.trailer_fields {
296            Some(n) => n,
297            None => self.headers,
298        }
299    }
300
301    /// The most interim responses a client accepts per request.
302    ///
303    /// # Examples
304    ///
305    /// ```
306    /// assert_eq!(http_net::Limits::new().max_interim(), 16);
307    /// ```
308    #[must_use]
309    pub const fn max_interim(&self) -> u16 {
310        self.interim
311    }
312
313    /// Sets how many requests a client may have outstanding at once
314    /// (pipelining, RFC 9112 section 9.3.2). Clamped to `1..=32`; 1 (the
315    /// default) means one request at a time. Only idempotent requests with
316    /// full bodies are pipelined; see
317    /// [`ClientConn::can_send`](crate::ClientConn::can_send).
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// let l = http_net::Limits::new().with_pipeline_depth(8);
323    /// assert_eq!(l.pipeline_depth(), 8);
324    /// assert_eq!(http_net::Limits::new().with_pipeline_depth(0).pipeline_depth(), 1);
325    /// assert_eq!(http_net::Limits::new().with_pipeline_depth(200).pipeline_depth(), 32);
326    /// ```
327    #[must_use]
328    pub const fn with_pipeline_depth(mut self, depth: u8) -> Self {
329        self.pipeline_depth = if depth == 0 {
330            1
331        } else if depth > MAX_PIPELINE_DEPTH {
332            MAX_PIPELINE_DEPTH
333        } else {
334            depth
335        };
336        self
337    }
338
339    /// How many requests a client may have outstanding at once.
340    ///
341    /// # Examples
342    ///
343    /// ```
344    /// assert_eq!(http_net::Limits::new().pipeline_depth(), 1);
345    /// ```
346    #[must_use]
347    pub const fn pipeline_depth(&self) -> u8 {
348        self.pipeline_depth
349    }
350
351    /// The largest accepted body, in bytes.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// assert_eq!(http_net::Limits::new().max_body(), 1 << 20);
357    /// ```
358    #[must_use]
359    pub const fn max_body(&self) -> u64 {
360        self.body
361    }
362
363    /// The input buffer's hard capacity, `head + max(head,
364    /// max_trailer_bytes) + 16 KiB` where `head = max_start_line +
365    /// max_header_bytes` (64 KiB with the defaults), so a driver can plan
366    /// memory per connection.
367    ///
368    /// The current message's head stays pinned while its body streams, and
369    /// the decoder may need one whole chunk-size line or trailer section on
370    /// top of it, so this always fits whatever the parser is waiting to
371    /// complete. A connection's memory is this buffer plus `max_headers`
372    /// field records of 16 bytes each (and as many for trailers when they
373    /// are reported).
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// use http_net::Limits;
379    ///
380    /// assert_eq!(Limits::new().buffer_capacity(), 64 * 1024);
381    /// let small = Limits::new().with_max_start_line(1024).with_max_header_bytes(4096);
382    /// assert_eq!(small.buffer_capacity(), 2 * (1024 + 4096) + 16 * 1024);
383    /// ```
384    #[must_use]
385    pub const fn buffer_capacity(&self) -> usize {
386        let head = self.start_line + self.header_bytes;
387        let trailers = self.max_trailer_bytes();
388        let tail = if trailers > head { trailers } else { head };
389        head + tail + BODY_WINDOW
390    }
391}
392
393const fn clamp(v: usize, lo: usize, hi: usize) -> usize {
394    if v < lo {
395        lo
396    } else if v > hi {
397        hi
398    } else {
399        v
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[test]
408    fn test_limits_clamp_to_hard_bounds() {
409        let l = Limits::new()
410            .with_max_start_line(usize::MAX)
411            .with_max_header_bytes(0)
412            .with_max_headers(usize::MAX);
413        assert_eq!(l.max_start_line(), HARD_CAP);
414        assert_eq!(l.max_header_bytes(), 2);
415        assert_eq!(l.max_headers(), 65_536);
416        assert!(l.buffer_capacity() <= u32::MAX as usize);
417        let l = l.with_max_trailer_bytes(usize::MAX).with_max_field_line(0);
418        assert!(l.buffer_capacity() <= u32::MAX as usize);
419        assert_eq!(l.max_field_line(), 4);
420    }
421
422    #[test]
423    fn test_buffer_capacity_covers_a_larger_trailer_section() {
424        let base = Limits::new();
425        assert_eq!(base.buffer_capacity(), 64 * 1024);
426        let big = base.with_max_trailer_bytes(100 * 1024);
427        assert_eq!(big.buffer_capacity(), 24 * 1024 + 100 * 1024 + 16 * 1024);
428        let small = base.with_max_trailer_bytes(10);
429        assert_eq!(small.buffer_capacity(), 64 * 1024);
430    }
431}