use super::{HpackEncoder, state::TableState};
use crate::headers::{
entry_name::EntryName,
field_section::{FieldLineValue, FieldSection},
header_observer::HeaderObserver,
hpack::static_table::static_table_lookup,
huffman, integer_prefix,
recent_pairs::RecentPairs,
static_hit::StaticHit,
};
impl HpackEncoder {
pub fn encode(&mut self, field_section: &FieldSection<'_>, out: &mut Vec<u8>) {
if let Some(new_size) = self.state.pending_size_update.take() {
let len_before = out.len();
integer_prefix::encode_into(new_size, 5, out);
out[len_before] |= 0b0010_0000;
}
for (name, value, never_indexed) in field_section.field_lines() {
encode_line(
&self.observer,
&mut self.state,
&name,
value,
never_indexed,
out,
);
}
}
}
fn encode_line(
observer: &HeaderObserver,
state: &mut TableState,
name: &EntryName<'_>,
value: FieldLineValue<'_>,
never_indexed: bool,
out: &mut Vec<u8>,
) {
if let Some(name_key) = name.name_key() {
let static_value = if name.has_uncacheable_value() || never_indexed {
None
} else if let FieldLineValue::Static(s) = &value {
Some(*s)
} else {
None
};
state.accum.record(name_key, static_value);
}
let value_bytes = value.as_bytes();
let static_match = static_table_lookup(name, value_bytes);
if !never_indexed && let StaticHit::Full(i) = static_match {
emit_indexed(usize::from(i), out);
return;
}
let dyn_full_abs = state
.by_name
.get(name)
.and_then(|ni| ni.by_value.get(value_bytes).copied());
if !never_indexed && let Some(abs_idx) = dyn_full_abs {
let dyn_idx = state
.live_dyn_idx_of(abs_idx)
.expect("by_name reverse index out of sync with live entries");
emit_indexed(61 + dyn_idx, out);
return;
}
let dyn_name_abs = state.by_name.get(name).map(|ni| ni.latest_any);
let uncacheable = name.has_uncacheable_value() || never_indexed;
let hash = (!uncacheable).then(|| RecentPairs::hash(name.as_bytes(), value_bytes));
let observer_hot = !uncacheable && observer.is_hot(name, Some(&value));
let should_index = hash.is_some_and(|h| state.recent_pairs.seen(h)) || observer_hot;
if let Some(h) = hash {
state.recent_pairs.remember(h);
}
let name_ref_idx = match static_match {
StaticHit::Name(i) => Some(usize::from(i)),
StaticHit::Full(i) if never_indexed => Some(usize::from(i)),
StaticHit::Full(_) => unreachable!("step 1 returned for Full when not never_indexed"),
StaticHit::None => dyn_name_abs
.and_then(|abs| state.live_dyn_idx_of(abs))
.map(|d| 61 + d),
};
if never_indexed {
if let Some(idx) = name_ref_idx {
emit_literal_never_indexed_name_ref(idx, value_bytes, out);
} else {
emit_literal_never_indexed_literal_name(name.as_bytes(), value_bytes, out);
}
} else if should_index {
if let Some(idx) = name_ref_idx {
emit_literal_with_indexing_name_ref(idx, value_bytes, out);
} else {
emit_literal_with_indexing_literal_name(name.as_bytes(), value_bytes, out);
}
state.insert(name.reborrow(), value);
} else {
if let Some(idx) = name_ref_idx {
emit_literal_without_indexing_name_ref(idx, value_bytes, out);
} else {
emit_literal_without_indexing_literal_name(name.as_bytes(), value_bytes, out);
}
}
}
fn emit_indexed(index: usize, out: &mut Vec<u8>) {
let start = out.len();
integer_prefix::encode_into(index, 7, out);
out[start] |= 0b1000_0000;
}
fn emit_literal_with_indexing_name_ref(name_index: usize, value: &[u8], out: &mut Vec<u8>) {
let start = out.len();
integer_prefix::encode_into(name_index, 6, out);
out[start] |= 0b0100_0000;
encode_string(value, out);
}
fn emit_literal_with_indexing_literal_name(name: &[u8], value: &[u8], out: &mut Vec<u8>) {
out.push(0b0100_0000);
encode_string(name, out);
encode_string(value, out);
}
fn emit_literal_without_indexing_name_ref(name_index: usize, value: &[u8], out: &mut Vec<u8>) {
integer_prefix::encode_into(name_index, 4, out);
encode_string(value, out);
}
fn emit_literal_without_indexing_literal_name(name: &[u8], value: &[u8], out: &mut Vec<u8>) {
out.push(0);
encode_string(name, out);
encode_string(value, out);
}
fn emit_literal_never_indexed_name_ref(name_index: usize, value: &[u8], out: &mut Vec<u8>) {
let start = out.len();
integer_prefix::encode_into(name_index, 4, out);
out[start] |= 0b0001_0000;
encode_string(value, out);
}
fn emit_literal_never_indexed_literal_name(name: &[u8], value: &[u8], out: &mut Vec<u8>) {
out.push(0b0001_0000);
encode_string(name, out);
encode_string(value, out);
}
fn encode_string(s: &[u8], buf: &mut Vec<u8>) {
let start = buf.len();
if let Some(huffman_len) = huffman::encoded_length_if_shorter(s) {
integer_prefix::encode_into(huffman_len, 7, buf);
buf[start] |= 0b1000_0000;
huffman::encode_into(s, buf);
} else {
integer_prefix::encode_into(s.len(), 7, buf);
buf.extend_from_slice(s);
}
}