use crate::headers::{
entry_name::EntryName, field_section::FieldLineValue, header_observer::ConnectionAccumulator,
recent_pairs::RecentPairs,
};
use hashbrown::HashMap;
use std::{
borrow::Cow,
collections::VecDeque,
fmt::{self, Debug},
};
const ENTRY_OVERHEAD: usize = 32;
#[derive(Debug)]
pub(super) struct TableState {
pub(super) entries: VecDeque<Entry>,
pub(super) current_size: usize,
pub(super) max_size: usize,
pub(super) local_preferred_size: usize,
pub(super) pending_size_update: Option<usize>,
pub(super) insert_count: u64,
pub(super) by_name: HashMap<EntryName<'static>, NameIndex>,
pub(super) accum: ConnectionAccumulator,
pub(super) recent_pairs: RecentPairs,
}
#[derive(Default)]
pub(super) struct NameIndex {
pub(super) by_value: HashMap<Cow<'static, [u8]>, u64>,
pub(super) latest_any: u64,
}
impl Debug for NameIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NameIndex")
.field(
"by_value",
&fmt::from_fn(|f| {
let mut map = f.debug_map();
for (k, v) in &self.by_value {
map.entry(&format_args!("{}", String::from_utf8_lossy(k)), v);
}
map.finish()
}),
)
.field("latest_any", &self.latest_any)
.finish()
}
}
#[derive(Clone)]
pub(super) struct Entry {
pub(super) name: EntryName<'static>,
pub(super) value: Cow<'static, [u8]>,
pub(super) size: usize,
}
impl Debug for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry")
.field("name", &self.name)
.field(
"value",
&format_args!("{}", String::from_utf8_lossy(&self.value)),
)
.field("size", &self.size)
.finish()
}
}
impl TableState {
pub(super) fn new(local_preferred_size: usize, recent_pairs_size: usize) -> Self {
Self {
entries: VecDeque::new(),
current_size: 0,
max_size: 0,
local_preferred_size,
pending_size_update: None,
insert_count: 0,
by_name: HashMap::new(),
accum: ConnectionAccumulator::default(),
recent_pairs: RecentPairs::with_size(recent_pairs_size),
}
}
pub(super) fn set_protocol_max_size(&mut self, peer_advertised: usize) {
let new_max = self.local_preferred_size.min(peer_advertised);
if new_max == self.max_size {
return;
}
self.max_size = new_max;
if self.current_size > new_max {
self.evict_until_fits(0);
}
self.pending_size_update = Some(new_max);
}
fn evict_until_fits(&mut self, needed: usize) {
while self.current_size + needed > self.max_size {
let Some(entry) = self.entries.pop_back() else {
break;
};
let evicted_abs = self.insert_count - self.entries.len() as u64 - 1;
self.current_size -= entry.size;
self.remove_from_reverse_index(&entry.name, &entry.value, evicted_abs);
}
}
pub(super) fn dyn_idx_of(&self, abs_idx: u64) -> usize {
usize::try_from(self.insert_count - abs_idx).expect("dyn_idx fits in usize")
}
pub(super) fn live_dyn_idx_of(&self, abs_idx: u64) -> Option<usize> {
let oldest_abs = self.insert_count.checked_sub(self.entries.len() as u64)?;
if abs_idx < oldest_abs || abs_idx >= self.insert_count {
return None;
}
Some(self.dyn_idx_of(abs_idx))
}
pub(super) fn insert(&mut self, name: EntryName<'_>, value: FieldLineValue<'_>) {
let entry_size = name.len() + value.len() + ENTRY_OVERHEAD;
if entry_size > self.max_size {
self.entries.clear();
self.current_size = 0;
self.by_name.clear();
return;
}
self.evict_until_fits(entry_size);
let abs_idx = self.insert_count;
let name = name.into_owned();
let value = value.into_static();
let name_index = self.by_name.entry(name.clone()).or_default();
name_index.by_value.insert(value.clone(), abs_idx);
name_index.latest_any = abs_idx;
self.entries.push_front(Entry {
name,
value,
size: entry_size,
});
self.current_size += entry_size;
self.insert_count += 1;
}
fn remove_from_reverse_index(
&mut self,
name: &EntryName<'static>,
value: &[u8],
evicted_abs: u64,
) {
let Some(name_index) = self.by_name.get_mut(name) else {
return;
};
if name_index.by_value.get(value) == Some(&evicted_abs) {
name_index.by_value.remove(value);
}
let drop_name_entry = if name_index.latest_any == evicted_abs {
match name_index.by_value.values().copied().max() {
Some(newest) => {
name_index.latest_any = newest;
false
}
None => true,
}
} else {
false
};
if drop_name_entry {
self.by_name.remove(name);
}
}
}