use std::collections::VecDeque;
const ENTRY_OVERHEAD: u64 = 32;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicEntry {
pub name: Vec<u8>,
pub value: Vec<u8>,
pub absolute_index: u64,
}
impl DynamicEntry {
pub fn new(name: Vec<u8>, value: Vec<u8>, absolute_index: u64) -> Self {
Self {
name,
value,
absolute_index,
}
}
#[inline]
pub fn size(&self) -> u64 {
ENTRY_OVERHEAD + self.name.len() as u64 + self.value.len() as u64
}
}
#[derive(Debug)]
pub struct DynamicTable {
entries: VecDeque<DynamicEntry>,
max_capacity: u64,
current_size: u64,
insert_count: u64,
dropped_count: u64,
eviction_limit: u64,
}
impl Default for DynamicTable {
fn default() -> Self {
Self::new()
}
}
impl DynamicTable {
pub fn new() -> Self {
Self {
entries: VecDeque::new(),
max_capacity: 0,
current_size: 0,
insert_count: 0,
dropped_count: 0,
eviction_limit: 0,
}
}
pub fn with_capacity(capacity: u64) -> Self {
Self {
entries: VecDeque::new(),
max_capacity: capacity,
current_size: 0,
insert_count: 0,
dropped_count: 0,
eviction_limit: 0,
}
}
#[inline]
pub fn max_capacity(&self) -> u64 {
self.max_capacity
}
#[inline]
pub fn current_size(&self) -> u64 {
self.current_size
}
#[inline]
pub fn insert_count(&self) -> u64 {
self.insert_count
}
#[inline]
pub fn dropped_count(&self) -> u64 {
self.dropped_count
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn set_eviction_limit(&mut self, limit: u64) {
self.eviction_limit = limit;
}
pub fn set_capacity(&mut self, capacity: u64) {
self.max_capacity = capacity;
self.evict_to_fit(0);
}
pub fn insert(&mut self, name: Vec<u8>, value: Vec<u8>) -> Option<u64> {
let entry_size = ENTRY_OVERHEAD + name.len() as u64 + value.len() as u64;
if entry_size > self.max_capacity {
return None;
}
self.evict_to_fit(entry_size);
if self.current_size + entry_size > self.max_capacity {
return None;
}
let absolute_index = self.insert_count;
let entry = DynamicEntry::new(name, value, absolute_index);
self.current_size += entry.size();
self.insert_count += 1;
self.entries.push_front(entry);
Some(absolute_index)
}
pub fn insert_with_name_ref(
&mut self,
name_index: u64,
is_static: bool,
value: Vec<u8>,
static_table: &[crate::qpack::Header],
) -> Option<u64> {
let name = if is_static {
static_table.get(name_index as usize)?.name().to_vec()
} else {
self.get_by_absolute_index(name_index)?.name.clone()
};
self.insert(name, value)
}
pub fn duplicate(&mut self, relative_index: u64) -> Option<u64> {
let entry = self.get_by_relative_index_encoder(relative_index)?;
let name = entry.name.clone();
let value = entry.value.clone();
self.insert(name, value)
}
pub fn get_by_absolute_index(&self, absolute_index: u64) -> Option<&DynamicEntry> {
if absolute_index < self.dropped_count {
return None;
}
if absolute_index >= self.insert_count {
return None;
}
let offset = (self.insert_count - 1 - absolute_index) as usize;
self.entries.get(offset)
}
pub fn get_by_relative_index_encoder(&self, relative_index: u64) -> Option<&DynamicEntry> {
self.entries.get(relative_index as usize)
}
pub fn get_by_relative_index_repr(
&self,
relative_index: u64,
base: u64,
) -> Option<&DynamicEntry> {
if relative_index >= base {
return None;
}
let absolute_index = base - relative_index - 1;
self.get_by_absolute_index(absolute_index)
}
pub fn get_by_post_base_index(&self, post_base_index: u64, base: u64) -> Option<&DynamicEntry> {
let absolute_index = base + post_base_index;
self.get_by_absolute_index(absolute_index)
}
pub fn find_entry(&self, name: &[u8], value: &[u8]) -> (Option<u64>, Option<u64>) {
let mut name_match = None;
for entry in &self.entries {
if entry.name == name {
if entry.value == value {
return (Some(entry.absolute_index), Some(entry.absolute_index));
}
if name_match.is_none() {
name_match = Some(entry.absolute_index);
}
}
}
(None, name_match)
}
fn evict_to_fit(&mut self, required_size: u64) {
while self.current_size + required_size > self.max_capacity {
if let Some(entry) = self.entries.back() {
if entry.absolute_index < self.eviction_limit {
break;
}
let size = entry.size();
self.entries.pop_back();
self.current_size -= size;
self.dropped_count += 1;
} else {
break;
}
}
}
pub fn clear(&mut self) {
self.dropped_count += self.entries.len() as u64;
self.entries.clear();
self.current_size = 0;
}
pub fn iter(&self) -> impl Iterator<Item = &DynamicEntry> {
self.entries.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_table() {
let table = DynamicTable::new();
assert_eq!(table.max_capacity(), 0);
assert_eq!(table.current_size(), 0);
assert_eq!(table.insert_count(), 0);
assert_eq!(table.len(), 0);
assert!(table.is_empty());
}
#[test]
fn test_insert_entry() {
let mut table = DynamicTable::with_capacity(1024);
let idx = table.insert(b":authority".to_vec(), b"www.example.com".to_vec());
assert_eq!(idx, Some(0));
assert_eq!(table.len(), 1);
assert_eq!(table.insert_count(), 1);
let entry = table.get_by_absolute_index(0).unwrap();
assert_eq!(entry.name, b":authority");
assert_eq!(entry.value, b"www.example.com");
}
#[test]
fn test_insert_multiple_entries() {
let mut table = DynamicTable::with_capacity(1024);
let idx1 = table.insert(b"name1".to_vec(), b"value1".to_vec());
let idx2 = table.insert(b"name2".to_vec(), b"value2".to_vec());
let idx3 = table.insert(b"name3".to_vec(), b"value3".to_vec());
assert_eq!(idx1, Some(0));
assert_eq!(idx2, Some(1));
assert_eq!(idx3, Some(2));
assert_eq!(table.len(), 3);
assert_eq!(table.get_by_absolute_index(0).unwrap().name, b"name1");
assert_eq!(table.get_by_absolute_index(1).unwrap().name, b"name2");
assert_eq!(table.get_by_absolute_index(2).unwrap().name, b"name3");
assert_eq!(
table.get_by_relative_index_encoder(0).unwrap().name,
b"name3"
);
assert_eq!(
table.get_by_relative_index_encoder(1).unwrap().name,
b"name2"
);
assert_eq!(
table.get_by_relative_index_encoder(2).unwrap().name,
b"name1"
);
}
#[test]
fn test_eviction() {
let mut table = DynamicTable::with_capacity(50);
let idx1 = table.insert(b"name1".to_vec(), b"value1".to_vec());
assert_eq!(idx1, Some(0));
assert_eq!(table.len(), 1);
let idx2 = table.insert(b"name2".to_vec(), b"value2".to_vec());
assert_eq!(idx2, Some(1));
assert_eq!(table.len(), 1);
assert_eq!(table.dropped_count(), 1);
assert!(table.get_by_absolute_index(0).is_none());
assert!(table.get_by_absolute_index(1).is_some());
}
#[test]
fn test_entry_too_large() {
let mut table = DynamicTable::with_capacity(50);
let idx = table.insert(b"very_long_name".to_vec(), b"very_long_value".to_vec());
assert_eq!(idx, None);
assert!(table.is_empty());
}
#[test]
fn test_set_capacity() {
let mut table = DynamicTable::with_capacity(200);
table.insert(b"name1".to_vec(), b"value1".to_vec());
table.insert(b"name2".to_vec(), b"value2".to_vec());
table.insert(b"name3".to_vec(), b"value3".to_vec());
assert_eq!(table.len(), 3);
table.set_capacity(50);
assert_eq!(table.len(), 1);
assert_eq!(table.dropped_count(), 2);
}
#[test]
fn test_find_entry() {
let mut table = DynamicTable::with_capacity(1024);
table.insert(b":method".to_vec(), b"GET".to_vec());
table.insert(b":method".to_vec(), b"POST".to_vec());
table.insert(b":path".to_vec(), b"/".to_vec());
let (exact, name_only) = table.find_entry(b":method", b"GET");
assert_eq!(exact, Some(0));
assert_eq!(name_only, Some(0));
let (exact, name_only) = table.find_entry(b":method", b"PUT");
assert_eq!(exact, None);
assert_eq!(name_only, Some(1));
let (exact, name_only) = table.find_entry(b"x-custom", b"value");
assert_eq!(exact, None);
assert_eq!(name_only, None);
}
#[test]
fn test_relative_index_repr() {
let mut table = DynamicTable::with_capacity(1024);
table.insert(b"name0".to_vec(), b"value0".to_vec()); table.insert(b"name1".to_vec(), b"value1".to_vec()); table.insert(b"name2".to_vec(), b"value2".to_vec()); table.insert(b"name3".to_vec(), b"value3".to_vec());
assert_eq!(
table.get_by_relative_index_repr(0, 3).unwrap().name,
b"name2"
);
assert_eq!(
table.get_by_relative_index_repr(1, 3).unwrap().name,
b"name1"
);
assert_eq!(
table.get_by_relative_index_repr(2, 3).unwrap().name,
b"name0"
);
assert!(table.get_by_relative_index_repr(3, 3).is_none());
}
#[test]
fn test_post_base_index() {
let mut table = DynamicTable::with_capacity(1024);
table.insert(b"name0".to_vec(), b"value0".to_vec()); table.insert(b"name1".to_vec(), b"value1".to_vec()); table.insert(b"name2".to_vec(), b"value2".to_vec()); table.insert(b"name3".to_vec(), b"value3".to_vec());
assert_eq!(table.get_by_post_base_index(0, 2).unwrap().name, b"name2");
assert_eq!(table.get_by_post_base_index(1, 2).unwrap().name, b"name3");
assert!(table.get_by_post_base_index(2, 2).is_none());
}
#[test]
fn test_duplicate() {
let mut table = DynamicTable::with_capacity(1024);
table.insert(b"name".to_vec(), b"value".to_vec()); table.insert(b"other".to_vec(), b"data".to_vec());
let idx = table.duplicate(1);
assert_eq!(idx, Some(2));
assert_eq!(table.len(), 3);
let entry = table.get_by_absolute_index(2).unwrap();
assert_eq!(entry.name, b"name");
assert_eq!(entry.value, b"value");
}
#[test]
fn test_clear() {
let mut table = DynamicTable::with_capacity(1024);
table.insert(b"name1".to_vec(), b"value1".to_vec());
table.insert(b"name2".to_vec(), b"value2".to_vec());
assert_eq!(table.len(), 2);
table.clear();
assert!(table.is_empty());
assert_eq!(table.current_size(), 0);
assert_eq!(table.dropped_count(), 2);
assert_eq!(table.insert_count(), 2);
}
#[test]
fn test_eviction_limit_prevents_eviction() {
let mut table = DynamicTable::with_capacity(50);
let idx1 = table.insert(b"name1".to_vec(), b"value1".to_vec());
assert_eq!(idx1, Some(0));
table.set_eviction_limit(1);
let idx2 = table.insert(b"name2".to_vec(), b"value2".to_vec());
assert_eq!(idx2, None);
assert_eq!(table.len(), 1);
assert_eq!(table.dropped_count(), 0);
table.set_eviction_limit(0);
let idx3 = table.insert(b"name3".to_vec(), b"value3".to_vec());
assert_eq!(idx3, Some(1));
assert_eq!(table.len(), 1);
assert_eq!(table.dropped_count(), 1);
}
#[test]
fn test_eviction_limit_on_set_capacity() {
let mut table = DynamicTable::with_capacity(200);
table.insert(b"name1".to_vec(), b"value1".to_vec()); table.insert(b"name2".to_vec(), b"value2".to_vec()); table.insert(b"name3".to_vec(), b"value3".to_vec()); assert_eq!(table.len(), 3);
table.set_eviction_limit(1);
table.set_capacity(50);
assert_eq!(table.len(), 3);
assert_eq!(table.dropped_count(), 0);
table.set_eviction_limit(0);
table.set_capacity(50);
assert_eq!(table.len(), 1);
assert_eq!(table.dropped_count(), 2);
}
}