use std::collections::VecDeque;
use bytes::Bytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InsertError {
EntryTooLarge,
}
pub(crate) struct DynamicTable {
entries: VecDeque<(Bytes, Bytes)>,
capacity: u64,
size: u64,
inserted: u64,
}
impl std::fmt::Debug for DynamicTable {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynamicTable")
.field("entries", &self.entries.len())
.field("capacity", &self.capacity)
.field("size", &self.size)
.field("inserted", &self.inserted)
.finish()
}
}
impl DynamicTable {
#[inline]
pub(crate) fn new(capacity: u64) -> Self {
Self {
entries: VecDeque::new(),
capacity,
size: 0,
inserted: 0,
}
}
#[inline]
pub(crate) fn capacity(&self) -> u64 {
self.capacity
}
#[cfg(test)]
#[inline]
pub(crate) fn size(&self) -> u64 {
self.size
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub(crate) fn last_absolute(&self) -> u64 {
self.inserted.saturating_sub(1)
}
#[inline]
pub(crate) fn inserted(&self) -> u64 {
self.inserted
}
#[inline]
pub(crate) fn next_absolute(&self) -> u64 {
self.inserted
}
#[inline]
pub(crate) fn entry_size(name: &[u8], value: &[u8]) -> u64 {
name.len() as u64 + value.len() as u64 + 32
}
#[inline]
pub(crate) fn set_capacity(&mut self, capacity: u64) {
self.capacity = capacity;
self.evict_to_fit(capacity);
}
#[inline]
pub(crate) fn would_evict(&self, size: u64) -> u64 {
let need_freed = size.saturating_sub(self.capacity - self.size);
if need_freed == 0 {
return 0;
}
let mut freed = 0u64;
let mut evicted = 0u64;
for (name, value) in self.entries.iter().rev() {
freed += Self::entry_size(name, value);
evicted += 1;
if freed >= need_freed {
break;
}
}
evicted
}
#[inline]
pub(crate) fn evict_for_capacity(&self, target: u64) -> u64 {
let mut size = 0u64;
let mut survivors = 0u64;
for (name, value) in self.entries.iter() {
let entry_size = Self::entry_size(name, value);
if size + entry_size > target {
break;
}
size += entry_size;
survivors += 1;
}
self.len() as u64 - survivors
}
#[inline]
pub(crate) fn find_full_or_name(
&self,
name: &[u8],
value: &[u8],
) -> (Option<u64>, Option<u64>) {
let mut name_match = None;
for (i, (entry_name, entry_value)) in self.entries.iter().enumerate() {
if entry_name != name {
continue;
}
let abs = self.inserted - 1 - i as u64;
name_match.get_or_insert(abs);
if entry_value == value {
return (Some(abs), name_match);
}
}
(None, name_match)
}
#[inline]
pub(crate) fn find_name(&self, name: &[u8]) -> Option<u64> {
self.entries
.iter()
.position(|(n, _)| n == name)
.map(|i| self.inserted - 1 - i as u64)
}
#[inline]
pub(crate) fn insert(&mut self, name: Bytes, value: Bytes) -> Result<(), InsertError> {
let entry_size = Self::entry_size(&name, &value);
if entry_size > self.capacity {
return Err(InsertError::EntryTooLarge);
}
self.evict_to_fit(self.capacity - entry_size);
self.entries.push_front((name, value));
self.size += entry_size;
self.inserted += 1;
Ok(())
}
#[cfg(test)]
#[inline]
pub(crate) fn get_absolute(&self, abs: u64) -> Option<(&[u8], &[u8])> {
let last = self.last_absolute();
if abs > last {
return None;
}
self.entry_at(last - abs)
}
#[cfg(test)]
#[inline]
pub(crate) fn get_relative(&self, index: u64) -> Option<(&[u8], &[u8])> {
self.entry_at(index)
}
#[cfg(test)]
#[inline]
pub(crate) fn get_base_relative(&self, base: u64, index: u64) -> Option<(&[u8], &[u8])> {
if index >= base {
return None;
}
let abs = base - 1 - index;
self.get_absolute(abs)
}
#[cfg(test)]
#[inline]
pub(crate) fn get_post_base(&self, base: u64, index: u64) -> Option<(&[u8], &[u8])> {
base.checked_add(index)
.and_then(|abs| self.get_absolute(abs))
}
#[inline]
pub(crate) fn entry_at(&self, i: u64) -> Option<(&[u8], &[u8])> {
let i = usize::try_from(i).ok()?;
let (name, value) = self.entries.get(i)?;
Some((name.as_ref(), value.as_ref()))
}
#[inline]
pub(crate) fn entry_bytes_at(&self, i: u64) -> Option<(Bytes, Bytes)> {
let i = usize::try_from(i).ok()?;
let (name, value) = self.entries.get(i)?;
Some((name.clone(), value.clone()))
}
#[inline]
pub(crate) fn get_absolute_bytes(&self, abs: u64) -> Option<(Bytes, Bytes)> {
let last = self.last_absolute();
if abs > last {
return None;
}
self.entry_bytes_at(last - abs)
}
#[inline]
pub(crate) fn get_relative_bytes(&self, index: u64) -> Option<(Bytes, Bytes)> {
self.entry_bytes_at(index)
}
#[inline]
pub(crate) fn get_base_relative_bytes(&self, base: u64, index: u64) -> Option<(Bytes, Bytes)> {
if index >= base {
return None;
}
self.get_absolute_bytes(base - 1 - index)
}
#[inline]
pub(crate) fn get_post_base_bytes(&self, base: u64, index: u64) -> Option<(Bytes, Bytes)> {
base.checked_add(index)
.and_then(|abs| self.get_absolute_bytes(abs))
}
#[inline]
fn evict_to_fit(&mut self, max_size: u64) {
while self.size > max_size {
let (name, value) = match self.entries.pop_back() {
Some(entry) => entry,
None => break,
};
self.size -= Self::entry_size(&name, &value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[inline]
fn insert(table: &mut DynamicTable, name: &str, value: &str) -> Result<(), InsertError> {
table.insert(
Bytes::copy_from_slice(name.as_bytes()),
Bytes::copy_from_slice(value.as_bytes()),
)
}
#[test]
fn entry_size_is_name_plus_value_plus_32() {
assert_eq!(DynamicTable::entry_size(b"foo", b"bar"), 38);
assert_eq!(DynamicTable::entry_size(b"", b""), 32);
}
#[test]
fn insert_assigns_increasing_absolute_indices() {
let mut table = DynamicTable::new(1000);
assert_eq!(table.len(), 0);
assert_eq!(table.inserted(), 0);
assert_eq!(table.next_absolute(), 0);
insert(&mut table, ":method", "GET").unwrap();
assert_eq!(table.inserted(), 1);
assert_eq!(table.last_absolute(), 0);
insert(&mut table, ":path", "/").unwrap();
assert_eq!(table.inserted(), 2);
assert_eq!(table.last_absolute(), 1);
assert_eq!(table.get_absolute(0), Some((&b":method"[..], &b"GET"[..])));
assert_eq!(table.get_absolute(1), Some((&b":path"[..], &b"/"[..])));
assert_eq!(table.get_absolute(2), None);
}
#[test]
fn relative_index_zero_is_most_recent() {
let mut table = DynamicTable::new(1000);
insert(&mut table, "a", "1").unwrap();
insert(&mut table, "b", "2").unwrap();
insert(&mut table, "c", "3").unwrap();
assert_eq!(table.get_relative(0), Some((&b"c"[..], &b"3"[..])));
assert_eq!(table.get_relative(1), Some((&b"b"[..], &b"2"[..])));
assert_eq!(table.get_relative(2), Some((&b"a"[..], &b"1"[..])));
assert_eq!(table.get_relative(3), None);
}
#[test]
fn insert_evicts_oldest_first() {
let mut table = DynamicTable::new(100);
insert(&mut table, "a", "a").unwrap();
insert(&mut table, "b", "b").unwrap();
assert_eq!(table.size(), 68);
assert_eq!(table.len(), 2);
insert(&mut table, "c", "c").unwrap();
assert_eq!(table.size(), 68);
assert_eq!(table.len(), 2);
assert_eq!(table.get_absolute(0), None, "oldest entry evicted");
assert_eq!(table.get_absolute(1), Some((&b"b"[..], &b"b"[..])));
assert_eq!(table.get_absolute(2), Some((&b"c"[..], &b"c"[..])));
assert_eq!(table.inserted(), 3);
}
#[test]
fn insert_rejects_oversized_entry() {
let mut table = DynamicTable::new(10);
assert_eq!(
insert(&mut table, "a", "a"),
Err(InsertError::EntryTooLarge)
);
assert_eq!(table.len(), 0);
assert_eq!(table.inserted(), 0);
}
#[test]
fn set_capacity_evicts_and_can_clear() {
let mut table = DynamicTable::new(1000);
for i in 0..5 {
insert(&mut table, &format!("h{i}"), "v").unwrap();
}
assert_eq!(table.len(), 5);
table.set_capacity(80);
assert!(table.size() <= 80);
assert_eq!(table.len(), 2);
table.set_capacity(0);
assert_eq!(table.len(), 0);
assert_eq!(table.size(), 0);
table.set_capacity(1000);
insert(&mut table, "fresh", "entry").unwrap();
assert_eq!(table.len(), 1);
assert_eq!(table.get_absolute(5), Some((&b"fresh"[..], &b"entry"[..])));
}
#[test]
fn field_section_relative_and_post_base_indexing() {
let mut table = DynamicTable::new(1000);
for i in 0..10u8 {
let bytes = Bytes::copy_from_slice(&[b'a' + i]);
table.insert(bytes.clone(), bytes).unwrap();
}
table.set_capacity(7 * 34);
table.set_capacity(1000);
assert_eq!(table.len(), 7);
assert_eq!(table.last_absolute(), 9);
let base = 8;
assert_eq!(table.get_base_relative(base, 0), table.get_absolute(7));
assert_eq!(table.get_base_relative(base, 3), table.get_absolute(4));
assert_eq!(table.get_base_relative(base, 5), table.get_absolute(2));
assert_eq!(table.get_base_relative(base, 5), None, "evicted entry");
assert_eq!(table.get_base_relative(base, 8), None, "index >= base");
assert_eq!(table.get_post_base(base, 0), table.get_absolute(8));
assert_eq!(table.get_post_base(base, 1), table.get_absolute(9));
assert_eq!(table.get_post_base(base, 2), None, "beyond newest entry");
}
#[test]
fn duplicate_entries_are_allowed() {
let mut table = DynamicTable::new(1000);
insert(&mut table, "cookie", "a=b").unwrap();
insert(&mut table, "cookie", "a=b").unwrap();
assert_eq!(table.len(), 2);
assert_eq!(table.get_absolute(0), Some((&b"cookie"[..], &b"a=b"[..])));
assert_eq!(table.get_absolute(1), Some((&b"cookie"[..], &b"a=b"[..])));
}
#[test]
fn empty_table_lookups_return_none() {
let table = DynamicTable::new(1000);
assert_eq!(table.get_absolute(0), None);
assert_eq!(table.get_relative(0), None);
assert_eq!(table.get_base_relative(0, 0), None);
assert_eq!(table.get_post_base(0, 0), None);
}
}