use std::collections::VecDeque;
pub(super) const STATIC_TABLE: [(&[u8], &[u8]); 61] = [
(b":authority", b""),
(b":method", b"GET"),
(b":method", b"POST"),
(b":path", b"/"),
(b":path", b"/index.html"),
(b":scheme", b"http"),
(b":scheme", b"https"),
(b":status", b"200"),
(b":status", b"204"),
(b":status", b"206"),
(b":status", b"304"),
(b":status", b"400"),
(b":status", b"404"),
(b":status", b"500"),
(b"accept-charset", b""),
(b"accept-encoding", b"gzip, deflate"),
(b"accept-language", b""),
(b"accept-ranges", b""),
(b"accept", b""),
(b"access-control-allow-origin", b""),
(b"age", b""),
(b"allow", b""),
(b"authorization", b""),
(b"cache-control", b""),
(b"content-disposition", b""),
(b"content-encoding", b""),
(b"content-language", b""),
(b"content-length", b""),
(b"content-location", b""),
(b"content-range", b""),
(b"content-type", b""),
(b"cookie", b""),
(b"date", b""),
(b"etag", b""),
(b"expect", b""),
(b"expires", b""),
(b"from", b""),
(b"host", b""),
(b"if-match", b""),
(b"if-modified-since", b""),
(b"if-none-match", b""),
(b"if-range", b""),
(b"if-unmodified-since", b""),
(b"last-modified", b""),
(b"link", b""),
(b"location", b""),
(b"max-forwards", b""),
(b"proxy-authenticate", b""),
(b"proxy-authorization", b""),
(b"range", b""),
(b"referer", b""),
(b"refresh", b""),
(b"retry-after", b""),
(b"server", b""),
(b"set-cookie", b""),
(b"strict-transport-security", b""),
(b"transfer-encoding", b""),
(b"user-agent", b""),
(b"vary", b""),
(b"via", b""),
(b"www-authenticate", b""),
];
pub(super) const DYNAMIC_TABLE_START: usize = STATIC_TABLE.len() + 1;
pub(super) const ENTRY_OVERHEAD: usize = 32;
pub(super) const DEFAULT_MAX_SIZE: usize = 4096;
const INITIAL_BYTES: usize = 512;
const INITIAL_ENTRIES: usize = 16;
#[derive(Debug, Clone, Copy)]
struct Entry {
offset: usize,
name_len: usize,
value_len: usize,
}
#[derive(Debug)]
pub(super) struct DynamicTable {
bytes: Vec<u8>,
start: usize,
entries: VecDeque<Entry>,
size: usize,
max_size: usize,
}
impl DynamicTable {
pub(super) const fn new(max_size: usize) -> Self {
DynamicTable {
bytes: Vec::new(),
start: 0,
entries: VecDeque::new(),
size: 0,
max_size,
}
}
pub(super) fn len(&self) -> usize {
self.entries.len()
}
#[cfg(test)]
pub(super) fn size(&self) -> usize {
self.size
}
pub(super) fn max_size(&self) -> usize {
self.max_size
}
pub(super) fn get(&self, index: usize) -> Option<(&[u8], &[u8])> {
let position = self.entries.len().checked_sub(index + 1)?;
let entry = self.entries[position];
let value_start = entry.offset + entry.name_len;
Some((
&self.bytes[entry.offset..value_start],
&self.bytes[value_start..value_start + entry.value_len],
))
}
pub(super) fn iter(&self) -> impl Iterator<Item = (&[u8], &[u8])> {
(0..self.entries.len()).filter_map(|index| self.get(index))
}
pub(super) fn set_max_size(&mut self, max_size: usize) {
self.max_size = max_size;
while self.size > self.max_size {
self.evict_oldest();
}
self.check_invariants();
}
pub(super) fn insert(&mut self, name: &[u8], value: &[u8]) {
let entry_size = name.len() + value.len() + ENTRY_OVERHEAD;
if entry_size > self.max_size {
self.clear();
self.check_invariants();
return;
}
while self.size + entry_size > self.max_size {
self.evict_oldest();
}
let needed = name.len() + value.len();
let live = self.bytes.len() - self.start;
if self.bytes.len() + needed > self.bytes.capacity() && self.start > 0 && self.start >= live
{
self.compact();
}
if self.bytes.capacity() == 0 {
self.bytes
.reserve(needed.max(INITIAL_BYTES.min(self.max_size)));
self.entries.reserve(INITIAL_ENTRIES);
} else {
self.bytes.reserve(needed);
}
let offset = self.bytes.len();
self.bytes.extend_from_slice(name);
self.bytes.extend_from_slice(value);
self.entries.push_back(Entry {
offset,
name_len: name.len(),
value_len: value.len(),
});
self.size += entry_size;
debug_assert!(
self.get(0) == Some((name, value)),
"the inserted field is the newest entry"
);
self.check_invariants();
}
fn evict_oldest(&mut self) {
let Some(oldest) = self.entries.pop_front() else {
debug_assert_eq!(self.size, 0, "an empty table has size 0");
self.size = 0;
return;
};
self.size -= oldest.name_len + oldest.value_len + ENTRY_OVERHEAD;
match self.entries.front() {
Some(next) => self.start = next.offset,
None => {
self.bytes.clear();
self.start = 0;
}
}
}
fn clear(&mut self) {
self.entries.clear();
self.bytes.clear();
self.start = 0;
self.size = 0;
}
fn compact(&mut self) {
let start = self.start;
self.bytes.copy_within(start.., 0);
self.bytes.truncate(self.bytes.len() - start);
for entry in &mut self.entries {
entry.offset -= start;
}
self.start = 0;
}
fn check_invariants(&self) {
if !cfg!(debug_assertions) {
return;
}
let mut end = self.start;
let mut size = 0;
for entry in &self.entries {
debug_assert_eq!(entry.offset, end, "entries are contiguous");
end += entry.name_len + entry.value_len;
size += entry.name_len + entry.value_len + ENTRY_OVERHEAD;
}
debug_assert_eq!(end, self.bytes.len(), "the newest entry ends the buffer");
debug_assert_eq!(size, self.size, "size is the sum of the entry sizes");
debug_assert!(
self.size <= self.max_size,
"the table fits its maximum size"
);
debug_assert!(
self.entries.len() <= self.max_size / ENTRY_OVERHEAD,
"every entry costs at least {ENTRY_OVERHEAD} octets"
);
}
}