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}