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