use std::collections::VecDeque;
use std::sync::Arc;
use super::static_table::STATIC_DECODE_TABLE;
const ENTRY_OVERHEAD: usize = 32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum TableError {
CapacityExceedsMaximum,
EntryTooLarge,
InvalidReference,
}
#[derive(Clone)]
enum EntryName {
Static(&'static [u8]),
Shared(Arc<[u8]>),
}
impl EntryName {
fn as_bytes(&self) -> &[u8] {
match self {
EntryName::Static(name) => name,
EntryName::Shared(name) => name,
}
}
}
#[derive(Clone)]
pub(crate) struct Entry {
name: EntryName,
value: Arc<[u8]>,
size: usize,
}
impl Entry {
fn new(name: EntryName, value: Arc<[u8]>) -> Result<Entry, TableError> {
let size = name
.as_bytes()
.len()
.checked_add(value.len())
.and_then(|len| len.checked_add(ENTRY_OVERHEAD))
.ok_or(TableError::EntryTooLarge)?;
Ok(Entry { name, value, size })
}
pub(crate) fn name(&self) -> &[u8] {
self.name.as_bytes()
}
pub(crate) fn value(&self) -> &[u8] {
&self.value
}
}
pub(crate) struct DynamicTable {
entries: VecDeque<Entry>,
size: usize,
capacity: usize,
max_capacity: usize,
insert_count: u64,
}
impl DynamicTable {
pub(crate) fn new(max_capacity: usize) -> DynamicTable {
DynamicTable {
entries: VecDeque::new(),
size: 0,
capacity: 0,
max_capacity,
insert_count: 0,
}
}
fn capacity(&self) -> usize {
self.capacity
}
fn max_capacity(&self) -> usize {
self.max_capacity
}
fn size(&self) -> usize {
self.size
}
fn insert_count(&self) -> u64 {
self.insert_count
}
pub(crate) fn get_by_absolute_index(
&self, absolute_index: u64,
) -> Option<&Entry> {
let oldest = self.insert_count.checked_sub(self.entries.len() as u64)?;
let pos = absolute_index.checked_sub(oldest)?;
self.entries.get(usize::try_from(pos).ok()?)
}
fn set_dynamic_table_capacity(
&mut self, capacity: u64,
) -> Result<(), TableError> {
let capacity = usize::try_from(capacity)
.map_err(|_| TableError::CapacityExceedsMaximum)?;
if capacity > self.max_capacity {
return Err(TableError::CapacityExceedsMaximum);
}
self.evict_to(capacity);
self.capacity = capacity;
Ok(())
}
fn insert_with_static_name_reference(
&mut self, static_index: u64, value: Arc<[u8]>,
) -> Result<(), TableError> {
let index = usize::try_from(static_index)
.map_err(|_| TableError::InvalidReference)?;
let &(name, _) = STATIC_DECODE_TABLE
.get(index)
.ok_or(TableError::InvalidReference)?;
self.insert(Entry::new(EntryName::Static(name), value)?)
}
fn insert_with_dynamic_name_reference(
&mut self, encoder_relative_index: u64, value: Arc<[u8]>,
) -> Result<(), TableError> {
let name = self
.get_by_encoder_relative_index(encoder_relative_index)
.ok_or(TableError::InvalidReference)?
.name
.clone();
self.insert(Entry::new(name, value)?)
}
fn insert_with_literal_name(
&mut self, name: Arc<[u8]>, value: Arc<[u8]>,
) -> Result<(), TableError> {
self.insert(Entry::new(EntryName::Shared(name), value)?)
}
fn duplicate(
&mut self, encoder_relative_index: u64,
) -> Result<(), TableError> {
let entry = self
.get_by_encoder_relative_index(encoder_relative_index)
.ok_or(TableError::InvalidReference)?
.clone();
self.insert(entry)
}
fn get_by_encoder_relative_index(
&self, encoder_relative_index: u64,
) -> Option<&Entry> {
let absolute_index = self
.insert_count
.checked_sub(encoder_relative_index)?
.checked_sub(1)?;
self.get_by_absolute_index(absolute_index)
}
fn insert(&mut self, entry: Entry) -> Result<(), TableError> {
let limit = self
.capacity
.checked_sub(entry.size)
.ok_or(TableError::EntryTooLarge)?;
self.evict_to(limit);
self.size += entry.size;
self.entries.push_back(entry);
self.insert_count += 1;
Ok(())
}
fn evict_to(&mut self, limit: usize) {
while self.size > limit {
let entry = self
.entries
.pop_front()
.expect("nonzero size requires an entry");
self.size -= entry.size;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const MAX_CAPACITY: usize = 4096;
const SMALL_ENTRY_SIZE: usize = 4 + 4 + 32;
fn bytes(s: &str) -> Arc<[u8]> {
Arc::from(s.as_bytes())
}
fn value_of_size(name: &str, size: usize) -> String {
"x".repeat(size - name.len() - 32)
}
fn insert(
table: &mut DynamicTable, name: &str, value: &str,
) -> Result<(), TableError> {
table.insert_with_literal_name(bytes(name), bytes(value))
}
fn insert_small(table: &mut DynamicTable, range: std::ops::Range<usize>) {
assert!(
range.end <= 10,
"insert_small requires single-digit indices"
);
for i in range {
insert(table, &format!("key{i}"), &format!("val{i}")).unwrap();
}
}
fn name_str(entry: &Entry) -> &str {
std::str::from_utf8(entry.name()).unwrap()
}
fn absolute(table: &DynamicTable, index: u64) -> Option<&str> {
table.get_by_absolute_index(index).map(name_str)
}
fn encoder_relative(table: &DynamicTable, index: u64) -> Option<&str> {
table.get_by_encoder_relative_index(index).map(name_str)
}
fn contents(table: &DynamicTable) -> Vec<(&str, &str)> {
table
.entries
.iter()
.map(|e| (name_str(e), std::str::from_utf8(e.value()).unwrap()))
.collect()
}
#[derive(Debug, PartialEq)]
struct Snapshot {
capacity: usize,
size: usize,
insert_count: u64,
entries: Vec<(String, String)>,
}
fn snapshot(table: &DynamicTable) -> Snapshot {
Snapshot {
capacity: table.capacity(),
size: table.size(),
insert_count: table.insert_count(),
entries: contents(table)
.into_iter()
.map(|(name, value)| (name.to_owned(), value.to_owned()))
.collect(),
}
}
fn shared_name(entry: &Entry) -> &Arc<[u8]> {
match &entry.name {
EntryName::Shared(name) => name,
EntryName::Static(_) => panic!("expected a shared name"),
}
}
#[test]
fn entry_new_size_and_buffer_sharing() {
let name = bytes("x-name");
let value = bytes("value");
let shared_entry =
Entry::new(EntryName::Shared(name.clone()), value.clone()).unwrap();
assert_eq!(shared_entry.size, 6 + 5 + 32);
assert!(Arc::ptr_eq(shared_name(&shared_entry), &name));
assert!(Arc::ptr_eq(&shared_entry.value, &value));
let static_name = STATIC_DECODE_TABLE[0].0;
let static_entry =
Entry::new(EntryName::Static(static_name), value.clone()).unwrap();
assert_eq!(static_entry.size, 10 + 5 + 32);
assert!(std::ptr::eq(static_entry.name(), static_name));
assert!(Arc::ptr_eq(&static_entry.value, &value));
}
#[test]
fn new_table_has_zero_capacity() {
let mut table = DynamicTable::new(MAX_CAPACITY);
assert_eq!(table.max_capacity(), MAX_CAPACITY);
assert_eq!(table.capacity(), 0);
assert_eq!(table.size(), 0);
assert_eq!(table.insert_count(), 0);
assert_eq!(absolute(&table, 0), None);
assert_eq!(insert(&mut table, "", ""), Err(TableError::EntryTooLarge));
assert_eq!(table.insert_count(), 0);
}
#[test]
fn max_capacity_is_configurable() {
for max_capacity in [0, 100, 4096, 8192] {
let mut table = DynamicTable::new(max_capacity);
assert_eq!(table.max_capacity(), max_capacity);
assert_eq!(table.capacity(), 0);
assert_eq!(
table.set_dynamic_table_capacity((max_capacity + 1) as u64),
Err(TableError::CapacityExceedsMaximum)
);
assert_eq!(
table.set_dynamic_table_capacity(max_capacity as u64),
Ok(())
);
assert_eq!(table.capacity(), max_capacity);
}
let mut table = DynamicTable::new(0);
table.set_dynamic_table_capacity(0).unwrap();
assert_eq!(insert(&mut table, "", ""), Err(TableError::EntryTooLarge));
let mut table = DynamicTable::new(usize::MAX);
assert_eq!(table.set_dynamic_table_capacity(usize::MAX as u64), Ok(()));
insert_small(&mut table, 0..2);
assert_eq!(table.size(), 2 * SMALL_ENTRY_SIZE);
}
#[cfg(target_pointer_width = "32")]
#[test]
fn capacity_above_usize_max_is_rejected() {
let mut table = DynamicTable::new(usize::MAX);
assert_eq!(
table.set_dynamic_table_capacity(usize::MAX as u64 + 1),
Err(TableError::CapacityExceedsMaximum)
);
assert_eq!(table.capacity(), 0);
}
#[test]
fn set_dynamic_table_capacity() {
let mut table = DynamicTable::new(MAX_CAPACITY);
assert_eq!(
table.set_dynamic_table_capacity(MAX_CAPACITY as u64),
Ok(())
);
assert_eq!(table.capacity(), MAX_CAPACITY);
assert_eq!(
table.set_dynamic_table_capacity((3 * SMALL_ENTRY_SIZE) as u64),
Ok(())
);
insert_small(&mut table, 0..3);
let before = snapshot(&table);
assert_eq!(
table.set_dynamic_table_capacity((MAX_CAPACITY + 1) as u64),
Err(TableError::CapacityExceedsMaximum)
);
assert_eq!(
table.set_dynamic_table_capacity(u64::MAX),
Err(TableError::CapacityExceedsMaximum)
);
assert_eq!(snapshot(&table), before);
assert_eq!(
table.set_dynamic_table_capacity((3 * SMALL_ENTRY_SIZE - 1) as u64),
Ok(())
);
assert_eq!(contents(&table), [("key1", "val1"), ("key2", "val2")]);
assert_eq!(table.size(), 2 * SMALL_ENTRY_SIZE);
assert_eq!(
table.set_dynamic_table_capacity((2 * SMALL_ENTRY_SIZE) as u64),
Ok(())
);
assert_eq!(contents(&table), [("key1", "val1"), ("key2", "val2")]);
assert_eq!(table.set_dynamic_table_capacity(0), Ok(()));
assert!(contents(&table).is_empty());
assert_eq!(table.size(), 0);
assert_eq!(
table.set_dynamic_table_capacity(SMALL_ENTRY_SIZE as u64),
Ok(())
);
insert_small(&mut table, 3..4);
assert_eq!(absolute(&table, 3), Some("key3"));
}
#[test]
fn insert_exact_fit() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity(MAX_CAPACITY as u64)
.unwrap();
let value0 = value_of_size("key0", 1000);
let value1 = value_of_size("key1", MAX_CAPACITY - 1000);
assert_eq!(insert(&mut table, "key0", &value0), Ok(()));
assert_eq!(insert(&mut table, "key1", &value1), Ok(()));
assert_eq!(contents(&table), [
("key0", value0.as_str()),
("key1", value1.as_str())
]);
assert_eq!(table.size(), MAX_CAPACITY);
let value2 = value_of_size("key2", MAX_CAPACITY);
assert_eq!(insert(&mut table, "key2", &value2), Ok(()));
assert_eq!(contents(&table), [("key2", value2.as_str())]);
assert_eq!(table.size(), MAX_CAPACITY);
let before = snapshot(&table);
let value3 = value_of_size("key3", MAX_CAPACITY + 1);
assert_eq!(
insert(&mut table, "key3", &value3),
Err(TableError::EntryTooLarge)
);
assert_eq!(snapshot(&table), before);
}
#[test]
fn insert_evicts_oldest_entries_first() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity((3 * SMALL_ENTRY_SIZE) as u64)
.unwrap();
insert_small(&mut table, 0..3);
insert_small(&mut table, 3..4);
assert_eq!(contents(&table), [
("key1", "val1"),
("key2", "val2"),
("key3", "val3")
]);
assert_eq!(table.size(), 3 * SMALL_ENTRY_SIZE);
let value = value_of_size("key4", 2 * SMALL_ENTRY_SIZE);
assert_eq!(insert(&mut table, "key4", &value), Ok(()));
assert_eq!(contents(&table), [
("key3", "val3"),
("key4", value.as_str())
]);
assert_eq!(table.size(), 3 * SMALL_ENTRY_SIZE);
}
#[test]
#[should_panic(expected = "nonzero size requires an entry")]
fn evict_panics_if_size_is_nonzero_without_entries() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table.size = 40;
table.evict_to(0);
}
#[test]
fn insert_rejects_entry_larger_than_capacity() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity((3 * SMALL_ENTRY_SIZE) as u64)
.unwrap();
insert_small(&mut table, 0..2);
let before = snapshot(&table);
let too_large = 3 * SMALL_ENTRY_SIZE + 1;
assert_eq!(
insert(&mut table, "key2", &value_of_size("key2", too_large)),
Err(TableError::EntryTooLarge)
);
assert_eq!(
table.insert_with_dynamic_name_reference(
0,
bytes(&value_of_size("key1", too_large))
),
Err(TableError::EntryTooLarge)
);
assert_eq!(
table.insert_with_static_name_reference(
2,
bytes(&value_of_size("age", too_large))
),
Err(TableError::EntryTooLarge)
);
assert_eq!(snapshot(&table), before);
}
#[test]
fn absolute_and_encoder_relative_indexing() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity((3 * SMALL_ENTRY_SIZE) as u64)
.unwrap();
insert_small(&mut table, 0..4);
assert_eq!(absolute(&table, 0), None);
assert_eq!(absolute(&table, 1), Some("key1"));
assert_eq!(absolute(&table, 3), Some("key3"));
assert_eq!(absolute(&table, 4), None);
assert_eq!(absolute(&table, u64::MAX), None);
assert_eq!(encoder_relative(&table, 0), Some("key3"));
assert_eq!(encoder_relative(&table, 2), Some("key1"));
assert_eq!(encoder_relative(&table, 3), None);
assert_eq!(encoder_relative(&table, 4), None);
assert_eq!(encoder_relative(&table, u64::MAX), None);
assert_eq!(table.duplicate(2), Ok(()));
assert_eq!(encoder_relative(&table, 0), Some("key1"));
assert_eq!(encoder_relative(&table, 1), Some("key3"));
assert_eq!(encoder_relative(&table, 2), Some("key2"));
assert_eq!(absolute(&table, 1), None);
assert_eq!(absolute(&table, 3), Some("key3"));
assert_eq!(absolute(&table, 4), Some("key1"));
}
#[test]
fn insert_count_is_monotonic() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity((2 * SMALL_ENTRY_SIZE) as u64)
.unwrap();
insert_small(&mut table, 0..1);
assert_eq!(table.insert_count(), 1);
assert_eq!(
table.insert_with_static_name_reference(2, bytes("12345")),
Ok(())
);
assert_eq!(table.insert_count(), 2);
assert_eq!(
table.insert_with_dynamic_name_reference(0, bytes("val2")),
Ok(())
);
assert_eq!(table.insert_count(), 3);
assert_eq!(table.duplicate(1), Ok(()));
assert_eq!(table.insert_count(), 4);
assert_eq!(contents(&table), [("age", "val2"), ("age", "12345")]);
let too_large = value_of_size("key9", 2 * SMALL_ENTRY_SIZE + 1);
assert!(insert(&mut table, "key9", &too_large).is_err());
assert!(table.duplicate(2).is_err());
assert!(table
.insert_with_static_name_reference(99, bytes(""))
.is_err());
assert!(table
.set_dynamic_table_capacity((MAX_CAPACITY + 1) as u64)
.is_err());
assert_eq!(table.insert_count(), 4);
table.set_dynamic_table_capacity(0).unwrap();
table
.set_dynamic_table_capacity(MAX_CAPACITY as u64)
.unwrap();
assert_eq!(table.insert_count(), 4);
insert_small(&mut table, 4..5);
assert_eq!(table.insert_count(), 5);
assert_eq!(absolute(&table, 4), Some("key4"));
}
#[test]
fn invalid_references() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity((2 * SMALL_ENTRY_SIZE) as u64)
.unwrap();
assert_eq!(table.duplicate(0), Err(TableError::InvalidReference));
assert_eq!(
table.insert_with_dynamic_name_reference(0, bytes("val")),
Err(TableError::InvalidReference)
);
insert_small(&mut table, 0..3);
let before = snapshot(&table);
for index in [2, 3, u64::MAX] {
assert_eq!(table.duplicate(index), Err(TableError::InvalidReference));
assert_eq!(
table.insert_with_dynamic_name_reference(index, bytes("val")),
Err(TableError::InvalidReference)
);
}
for index in [99, u64::MAX] {
assert_eq!(
table.insert_with_static_name_reference(index, bytes("val")),
Err(TableError::InvalidReference)
);
}
assert_eq!(snapshot(&table), before);
}
#[test]
fn static_name_reference_borrows_static_name() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity(MAX_CAPACITY as u64)
.unwrap();
assert_eq!(
table.insert_with_static_name_reference(0, bytes("example.com")),
Ok(())
);
assert_eq!(
table.insert_with_static_name_reference(98, bytes("deny")),
Ok(())
);
assert_eq!(contents(&table), [
(":authority", "example.com"),
("x-frame-options", "deny")
]);
assert_eq!(table.size(), (10 + 11 + 32) + (15 + 4 + 32));
table.duplicate(1).unwrap();
table
.insert_with_dynamic_name_reference(1, bytes("sameorigin"))
.unwrap();
assert_eq!(contents(&table)[2..], [
(":authority", "example.com"),
("x-frame-options", "sameorigin")
]);
assert!(table
.entries
.iter()
.all(|e| matches!(e.name, EntryName::Static(_))));
}
#[test]
fn entries_share_buffers() {
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity(MAX_CAPACITY as u64)
.unwrap();
let name = bytes("x-name");
let value = bytes("x-value");
table
.insert_with_literal_name(name.clone(), value.clone())
.unwrap();
table.duplicate(0).unwrap();
let other_value = bytes("other");
table
.insert_with_dynamic_name_reference(0, other_value.clone())
.unwrap();
for entry in &table.entries {
assert!(Arc::ptr_eq(shared_name(entry), &name));
}
assert!(Arc::ptr_eq(&table.entries[0].value, &value));
assert!(Arc::ptr_eq(&table.entries[1].value, &value));
assert!(Arc::ptr_eq(&table.entries[2].value, &other_value));
assert_eq!(table.size(), 2 * (6 + 7 + 32) + (6 + 5 + 32));
}
#[test]
fn insert_evicting_referenced_entry_keeps_its_buffers() {
let name = bytes("key0");
let value = bytes("val0");
let mut table = DynamicTable::new(MAX_CAPACITY);
table
.set_dynamic_table_capacity(SMALL_ENTRY_SIZE as u64)
.unwrap();
table
.insert_with_literal_name(name.clone(), value.clone())
.unwrap();
assert_eq!(table.duplicate(0), Ok(()));
assert_eq!(absolute(&table, 0), None);
let entry = table.get_by_absolute_index(1).unwrap();
assert!(Arc::ptr_eq(shared_name(entry), &name));
assert!(Arc::ptr_eq(&entry.value, &value));
assert_eq!(Arc::strong_count(&name), 2);
assert_eq!(Arc::strong_count(&value), 2);
assert_eq!(
table.insert_with_dynamic_name_reference(0, bytes("val1")),
Ok(())
);
assert_eq!(absolute(&table, 1), None);
let entry = table.get_by_absolute_index(2).unwrap();
assert!(Arc::ptr_eq(shared_name(entry), &name));
assert_eq!(entry.value(), b"val1");
assert_eq!(Arc::strong_count(&name), 2);
assert_eq!(Arc::strong_count(&value), 1);
}
}