const HARD_CAP: usize = 16 * 1024 * 1024;
const BODY_WINDOW: usize = 16 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Limits {
start_line: usize,
header_bytes: usize,
headers: usize,
field_line: usize,
trailer_bytes: Option<usize>,
trailer_fields: Option<usize>,
interim: u16,
pipeline_depth: u8,
body: u64,
}
pub(crate) const MAX_PIPELINE_DEPTH: u8 = 32;
impl Default for Limits {
fn default() -> Self {
Self::new()
}
}
impl Limits {
#[must_use]
pub const fn new() -> Self {
Self {
start_line: 8 * 1024,
header_bytes: 16 * 1024,
headers: 100,
field_line: 8 * 1024,
trailer_bytes: None,
trailer_fields: None,
interim: 16,
pipeline_depth: 1,
body: 1024 * 1024,
}
}
#[must_use]
pub const fn with_max_start_line(mut self, bytes: usize) -> Self {
self.start_line = clamp(bytes, 16, HARD_CAP);
self
}
#[must_use]
pub const fn with_max_header_bytes(mut self, bytes: usize) -> Self {
self.header_bytes = clamp(bytes, 2, HARD_CAP);
self
}
#[must_use]
pub const fn with_max_headers(mut self, count: usize) -> Self {
self.headers = clamp(count, 0, 65_536);
self
}
#[must_use]
pub const fn with_max_field_line(mut self, bytes: usize) -> Self {
self.field_line = clamp(bytes, 4, HARD_CAP);
self
}
#[must_use]
pub const fn with_max_trailer_bytes(mut self, bytes: usize) -> Self {
self.trailer_bytes = Some(clamp(bytes, 2, HARD_CAP));
self
}
#[must_use]
pub const fn with_max_trailer_fields(mut self, count: usize) -> Self {
self.trailer_fields = Some(clamp(count, 0, 65_536));
self
}
#[must_use]
pub const fn with_max_interim(mut self, count: u16) -> Self {
self.interim = count;
self
}
#[must_use]
pub const fn with_max_body(mut self, bytes: u64) -> Self {
self.body = bytes;
self
}
#[must_use]
pub const fn max_start_line(&self) -> usize {
self.start_line
}
#[must_use]
pub const fn max_header_bytes(&self) -> usize {
self.header_bytes
}
#[must_use]
pub const fn max_headers(&self) -> usize {
self.headers
}
#[must_use]
pub const fn max_field_line(&self) -> usize {
self.field_line
}
#[must_use]
pub const fn max_trailer_bytes(&self) -> usize {
match self.trailer_bytes {
Some(n) => n,
None => self.header_bytes,
}
}
#[must_use]
pub const fn max_trailer_fields(&self) -> usize {
match self.trailer_fields {
Some(n) => n,
None => self.headers,
}
}
#[must_use]
pub const fn max_interim(&self) -> u16 {
self.interim
}
#[must_use]
pub const fn with_pipeline_depth(mut self, depth: u8) -> Self {
self.pipeline_depth = if depth == 0 {
1
} else if depth > MAX_PIPELINE_DEPTH {
MAX_PIPELINE_DEPTH
} else {
depth
};
self
}
#[must_use]
pub const fn pipeline_depth(&self) -> u8 {
self.pipeline_depth
}
#[must_use]
pub const fn max_body(&self) -> u64 {
self.body
}
#[must_use]
pub const fn buffer_capacity(&self) -> usize {
let head = self.start_line + self.header_bytes;
let trailers = self.max_trailer_bytes();
let tail = if trailers > head { trailers } else { head };
head + tail + BODY_WINDOW
}
}
const fn clamp(v: usize, lo: usize, hi: usize) -> usize {
if v < lo {
lo
} else if v > hi {
hi
} else {
v
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_limits_clamp_to_hard_bounds() {
let l = Limits::new()
.with_max_start_line(usize::MAX)
.with_max_header_bytes(0)
.with_max_headers(usize::MAX);
assert_eq!(l.max_start_line(), HARD_CAP);
assert_eq!(l.max_header_bytes(), 2);
assert_eq!(l.max_headers(), 65_536);
assert!(l.buffer_capacity() <= u32::MAX as usize);
let l = l.with_max_trailer_bytes(usize::MAX).with_max_field_line(0);
assert!(l.buffer_capacity() <= u32::MAX as usize);
assert_eq!(l.max_field_line(), 4);
}
#[test]
fn test_buffer_capacity_covers_a_larger_trailer_section() {
let base = Limits::new();
assert_eq!(base.buffer_capacity(), 64 * 1024);
let big = base.with_max_trailer_bytes(100 * 1024);
assert_eq!(big.buffer_capacity(), 24 * 1024 + 100 * 1024 + 16 * 1024);
let small = base.with_max_trailer_bytes(10);
assert_eq!(small.buffer_capacity(), 64 * 1024);
}
}