use super::{EncoderDynamicTable, SectionRefs, state::TableState};
use crate::headers::{
entry_name::EntryName,
qpack::{
FieldLineValue, FieldSection, HeaderObserver,
instruction::field_section::{FieldLineInstruction, FieldSectionPrefix},
static_table::static_table_lookup,
},
recent_pairs::RecentPairs,
static_hit::StaticHit,
};
fn to_u32(n: usize) -> u32 {
u32::try_from(n).unwrap_or(u32::MAX)
}
impl EncoderDynamicTable {
pub(crate) fn encode(
&self,
field_section: &FieldSection<'_>,
buf: &mut Vec<u8>,
stream_id: u64,
) {
self.encode_field_lines(&field_section.field_lines(), buf, stream_id);
}
pub(in crate::headers) fn encode_field_lines(
&self,
field_lines: &[(EntryName<'_>, FieldLineValue<'_>, bool)],
buf: &mut Vec<u8>,
stream_id: u64,
) {
let plan;
let max_capacity;
let made_inserts;
let enc_stream_bytes;
let krc_at_encode;
{
let mut state = self.state.lock().unwrap();
max_capacity = state.max_capacity;
krc_at_encode = state.known_received_count;
let mut planner = Planner::new(&mut state, stream_id, &self.observer);
for (name, value, never_indexed) in field_lines {
planner.plan_header_line(name, value.reborrow(), *never_indexed);
}
made_inserts = planner.made_inserts;
enc_stream_bytes = planner.enc_stream_bytes;
plan = planner.finish();
if plan.section_ric > 0 {
state
.outstanding_sections
.entry(stream_id)
.or_default()
.push_back(SectionRefs {
required_insert_count: plan.section_ric,
min_ref_abs_idx: plan.min_ref_abs_idx,
});
}
}
if made_inserts {
self.event.notify(usize::MAX);
}
let section_start = buf.len();
emit_section_prefix(plan.section_ric, max_capacity, buf);
for emission in &plan.emissions {
emit(emission, plan.section_ric, buf);
}
let header_block_bytes = u32::try_from(buf.len() - section_start).unwrap_or(u32::MAX);
let dynamic_refs: Vec<u64> = plan
.emissions
.iter()
.filter_map(|emission| match emission {
Emission::IndexedDynamicPreBase(abs_idx)
| Emission::LiteralDynamicNameRefPreBase { abs_idx, .. } => Some(*abs_idx),
_ => None,
})
.collect();
self.metrics.record_section(
header_block_bytes,
enc_stream_bytes,
&dynamic_refs,
krc_at_encode,
);
}
}
#[derive(Debug)]
enum Emission<'lines, 'names> {
IndexedStatic(u8),
IndexedDynamicPreBase(u64),
LiteralStaticNameRef {
name_index: u8,
value: FieldLineValue<'lines>,
never_indexed: bool,
},
LiteralDynamicNameRefPreBase {
abs_idx: u64,
value: FieldLineValue<'lines>,
never_indexed: bool,
},
LiteralLiteralName {
name: &'lines EntryName<'names>,
value: FieldLineValue<'lines>,
never_indexed: bool,
},
}
struct Plan<'lines, 'names> {
emissions: Vec<Emission<'lines, 'names>>,
section_ric: u64,
min_ref_abs_idx: Option<u64>,
}
struct Planner<'state, 'lines, 'names> {
state: &'state mut TableState,
observer: &'state HeaderObserver,
emissions: Vec<Emission<'lines, 'names>>,
section_ric: u64,
min_ref_abs_idx: Option<u64>,
can_block_section: bool,
made_inserts: bool,
enc_stream_bytes: u32,
}
impl<'state, 'lines, 'names> Planner<'state, 'lines, 'names> {
fn new(
state: &'state mut TableState,
stream_id: u64,
observer: &'state HeaderObserver,
) -> Self {
let can_block_section = state.is_stream_blocking(stream_id)
|| state.currently_blocked_streams() < state.max_blocked_streams;
Self {
state,
observer,
emissions: Vec::new(),
section_ric: 0,
min_ref_abs_idx: None,
can_block_section,
made_inserts: false,
enc_stream_bytes: 0,
}
}
fn finish(self) -> Plan<'lines, 'names> {
Plan {
emissions: self.emissions,
section_ric: self.section_ric,
min_ref_abs_idx: self.min_ref_abs_idx,
}
}
fn plan_header_line(
&mut self,
name: &'lines EntryName<'names>,
value: FieldLineValue<'lines>,
never_indexed: bool,
) {
if !never_indexed {
self.state.accum.observe(name, &value);
}
let uncacheable = name.has_uncacheable_value() || never_indexed;
let hash = (!uncacheable).then(|| RecentPairs::hash(name.as_bytes(), value.as_bytes()));
let should_index = hash.is_some_and(|h| self.state.recent_pairs.seen(h));
let emission = self.plan_emission(name, value, should_index, never_indexed);
self.emissions.push(emission);
if let Some(h) = hash {
self.state.recent_pairs.remember(h);
}
}
fn plan_emission(
&mut self,
name: &'lines EntryName<'names>,
value: FieldLineValue<'lines>,
should_index: bool,
never_indexed: bool,
) -> Emission<'lines, 'names> {
let static_match = static_table_lookup(name, Some(value.as_bytes()));
if !never_indexed && let StaticHit::Full(i) = static_match {
return Emission::IndexedStatic(i);
}
let name_lookup = self.state.by_name.get(name);
let dyn_full = name_lookup.and_then(|i| i.by_value.get(value.as_bytes()).copied());
if !never_indexed
&& let Some(abs_idx) = dyn_full
&& self.can_ref(abs_idx)
{
self.record_ref(abs_idx);
return Emission::IndexedDynamicPreBase(abs_idx);
}
let dyn_name_pre = name_lookup.map(|i| i.latest_any);
if should_index {
self.refresh_hot_tail();
let pre_count = self.state.pending_ops.len();
let _ = self
.state
.insert(name.reborrow(), value.reborrow(), self.min_ref_abs_idx);
if self.state.pending_ops.len() > pre_count {
self.made_inserts = true;
if let Some(wire) = self.state.pending_ops.back() {
self.enc_stream_bytes =
self.enc_stream_bytes.saturating_add(to_u32(wire.len()));
}
}
}
let static_name_index = match static_match {
StaticHit::Name(i) => Some(i),
StaticHit::Full(i) if never_indexed => Some(i),
_ => None,
};
if let Some(i) = static_name_index {
return Emission::LiteralStaticNameRef {
name_index: i,
value,
never_indexed,
};
}
if let Some(abs_idx) = dyn_name_pre
&& self.state.entry_at_abs(abs_idx).is_some()
&& self.can_ref(abs_idx)
{
self.record_ref(abs_idx);
return Emission::LiteralDynamicNameRefPreBase {
abs_idx,
value,
never_indexed,
};
}
Emission::LiteralLiteralName {
name,
value,
never_indexed,
}
}
fn refresh_hot_tail(&mut self) {
if self.state.primed_bytes == 0 {
return;
}
let headroom = self.state.capacity.saturating_sub(self.state.current_size);
if headroom >= self.state.primed_bytes {
return;
}
let (oldest_abs, name, value_opt) = {
let Some(oldest) = self.state.entries.back() else {
return;
};
let oldest_abs = self
.state
.insert_count
.saturating_sub(self.state.entries.len() as u64);
let name = oldest.name.clone();
let value_opt = if oldest.value.is_empty() {
None
} else {
Some(FieldLineValue::Owned(oldest.value.to_vec()))
};
(oldest_abs, name, value_opt)
};
if !self.observer.is_hot(&name, value_opt.as_ref()) {
return;
}
let pre_count = self.state.pending_ops.len();
if self
.state
.duplicate(oldest_abs, self.min_ref_abs_idx)
.is_ok()
&& self.state.pending_ops.len() > pre_count
{
self.made_inserts = true;
if let Some(wire) = self.state.pending_ops.back() {
self.enc_stream_bytes = self.enc_stream_bytes.saturating_add(to_u32(wire.len()));
}
}
}
fn can_ref(&self, abs_idx: u64) -> bool {
abs_idx < self.state.known_received_count || self.can_block_section
}
fn record_ref(&mut self, abs_idx: u64) {
let needed_ric = abs_idx + 1;
if needed_ric > self.section_ric {
self.section_ric = needed_ric;
}
self.min_ref_abs_idx = Some(match self.min_ref_abs_idx {
Some(cur) => cur.min(abs_idx),
None => abs_idx,
});
}
}
fn emit_section_prefix(section_ric: u64, max_capacity: usize, buf: &mut Vec<u8>) {
let encoded_required_insert_count = if section_ric == 0 {
0
} else {
encode_required_insert_count(section_ric, max_capacity)
};
FieldSectionPrefix {
encoded_required_insert_count,
base_is_negative: false,
delta_base: 0,
}
.encode(buf);
}
fn emit(emission: &Emission<'_, '_>, section_ric: u64, buf: &mut Vec<u8>) {
let instruction = match emission {
Emission::IndexedStatic(index) => FieldLineInstruction::IndexedStatic {
index: usize::from(*index),
},
Emission::IndexedDynamicPreBase(abs_idx) => {
debug_assert!(
*abs_idx < section_ric,
"IndexedDynamicPreBase: abs_idx {abs_idx} >= base {section_ric}"
);
FieldLineInstruction::IndexedDynamic {
relative_index: usize::try_from(section_ric - 1 - abs_idx).unwrap_or(usize::MAX),
}
}
Emission::LiteralStaticNameRef {
name_index,
value,
never_indexed,
} => FieldLineInstruction::LiteralStaticNameRef {
name_index: usize::from(*name_index),
value: value.reborrow(),
never_indexed: *never_indexed,
},
Emission::LiteralDynamicNameRefPreBase {
abs_idx,
value,
never_indexed,
} => {
debug_assert!(
*abs_idx < section_ric,
"LiteralDynamicNameRefPreBase: abs_idx {abs_idx} >= base {section_ric}"
);
FieldLineInstruction::LiteralDynamicNameRef {
relative_index: usize::try_from(section_ric - 1 - *abs_idx).unwrap_or(usize::MAX),
value: value.reborrow(),
never_indexed: *never_indexed,
}
}
Emission::LiteralLiteralName {
name,
value,
never_indexed,
} => FieldLineInstruction::LiteralLiteralName {
name: name.reborrow(),
value: value.reborrow(),
never_indexed: *never_indexed,
},
};
instruction.encode(buf);
}
pub(super) fn encode_required_insert_count(ric: u64, max_capacity: usize) -> usize {
if ric == 0 {
return 0;
}
let max_entries = (max_capacity / 32) as u64;
debug_assert!(
max_entries > 0,
"encode_required_insert_count: ric>0 with max_capacity<32"
);
let full_range = 2 * max_entries;
usize::try_from((ric % full_range) + 1)
.expect("RIC fits in usize because max_capacity is a usize")
}