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/// | header fields (and, separately, trailer fields) | 100 |
27/// | body | 1 MiB |
28///
29/// # Examples
30///
31/// ```
32/// use http_net::Limits;
33///
34/// let limits = Limits::new().with_max_body(64 * 1024).with_max_headers(32);
35/// assert_eq!(limits.max_body(), 64 * 1024);
36/// assert_eq!(limits.max_headers(), 32);
37/// assert_eq!(limits.max_start_line(), 8 * 1024);
38/// ```
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub struct Limits {
41 start_line: usize,
42 header_bytes: usize,
43 headers: usize,
44 body: u64,
45}
46
47impl Default for Limits {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl Limits {
54 /// The default budgets (see the table on [`Limits`]).
55 ///
56 /// # Examples
57 ///
58 /// ```
59 /// assert_eq!(http_net::Limits::new(), http_net::Limits::default());
60 /// ```
61 #[must_use]
62 pub const fn new() -> Self {
63 Self {
64 start_line: 8 * 1024,
65 header_bytes: 16 * 1024,
66 headers: 100,
67 body: 1024 * 1024,
68 }
69 }
70
71 /// Sets the longest accepted request line, status line, or chunk-size
72 /// line, in bytes including the CRLF. Clamped to `16..=16 MiB`.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// let l = http_net::Limits::new().with_max_start_line(1);
78 /// assert_eq!(l.max_start_line(), 16);
79 /// ```
80 #[must_use]
81 pub const fn with_max_start_line(mut self, bytes: usize) -> Self {
82 self.start_line = clamp(bytes, 16, HARD_CAP);
83 self
84 }
85
86 /// Sets the largest accepted header section (every field line plus the
87 /// blank line that ends it), in bytes. Clamped to `2..=16 MiB`.
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// let l = http_net::Limits::new().with_max_header_bytes(4096);
93 /// assert_eq!(l.max_header_bytes(), 4096);
94 /// ```
95 #[must_use]
96 pub const fn with_max_header_bytes(mut self, bytes: usize) -> Self {
97 self.header_bytes = clamp(bytes, 2, HARD_CAP);
98 self
99 }
100
101 /// Sets the most header fields accepted in one message (trailer fields
102 /// are counted against the same number separately). Clamped to
103 /// `0..=65536`.
104 ///
105 /// # Examples
106 ///
107 /// ```
108 /// let l = http_net::Limits::new().with_max_headers(8);
109 /// assert_eq!(l.max_headers(), 8);
110 /// ```
111 #[must_use]
112 pub const fn with_max_headers(mut self, count: usize) -> Self {
113 self.headers = clamp(count, 0, 65_536);
114 self
115 }
116
117 /// Sets the largest accepted body, in bytes after chunked decoding.
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// let l = http_net::Limits::new().with_max_body(0);
123 /// assert_eq!(l.max_body(), 0);
124 /// ```
125 #[must_use]
126 pub const fn with_max_body(mut self, bytes: u64) -> Self {
127 self.body = bytes;
128 self
129 }
130
131 /// The longest accepted start line or chunk-size line, CRLF included.
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// assert_eq!(http_net::Limits::new().max_start_line(), 8192);
137 /// ```
138 #[must_use]
139 pub const fn max_start_line(&self) -> usize {
140 self.start_line
141 }
142
143 /// The largest accepted header section, in bytes.
144 ///
145 /// # Examples
146 ///
147 /// ```
148 /// assert_eq!(http_net::Limits::new().max_header_bytes(), 16384);
149 /// ```
150 #[must_use]
151 pub const fn max_header_bytes(&self) -> usize {
152 self.header_bytes
153 }
154
155 /// The most header fields accepted in one message.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// assert_eq!(http_net::Limits::new().max_headers(), 100);
161 /// ```
162 #[must_use]
163 pub const fn max_headers(&self) -> usize {
164 self.headers
165 }
166
167 /// The largest accepted body, in bytes.
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// assert_eq!(http_net::Limits::new().max_body(), 1 << 20);
173 /// ```
174 #[must_use]
175 pub const fn max_body(&self) -> u64 {
176 self.body
177 }
178
179 /// The input buffer's hard capacity, `2 * (max_start_line +
180 /// max_header_bytes) + 16 KiB` (64 KiB with the defaults), so a driver
181 /// can plan memory per connection.
182 ///
183 /// The current message's head stays pinned while its body streams, and
184 /// the decoder may need one whole chunk-size line or trailer section on
185 /// top of it, so twice the head budget plus the body window always fits
186 /// whatever the parser is waiting to complete. A connection's memory is
187 /// this buffer plus `max_headers` field records of 16 bytes each.
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// use http_net::Limits;
193 ///
194 /// assert_eq!(Limits::new().buffer_capacity(), 64 * 1024);
195 /// let small = Limits::new().with_max_start_line(1024).with_max_header_bytes(4096);
196 /// assert_eq!(small.buffer_capacity(), 2 * (1024 + 4096) + 16 * 1024);
197 /// ```
198 #[must_use]
199 pub const fn buffer_capacity(&self) -> usize {
200 2 * (self.start_line + self.header_bytes) + BODY_WINDOW
201 }
202}
203
204const fn clamp(v: usize, lo: usize, hi: usize) -> usize {
205 if v < lo {
206 lo
207 } else if v > hi {
208 hi
209 } else {
210 v
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_limits_clamp_to_hard_bounds() {
220 let l = Limits::new()
221 .with_max_start_line(usize::MAX)
222 .with_max_header_bytes(0)
223 .with_max_headers(usize::MAX);
224 assert_eq!(l.max_start_line(), HARD_CAP);
225 assert_eq!(l.max_header_bytes(), 2);
226 assert_eq!(l.max_headers(), 65_536);
227 assert!(l.buffer_capacity() <= u32::MAX as usize);
228 }
229}