#![expect(dead_code)]
use std::collections::VecDeque;
use bytes::Bytes;
use crate::h3::qpack::table::DynamicTable;
use crate::h3::qpack::{static_table, QpackError};
use crate::hpack::{huffman, integer};
const SET_CAPACITY: u8 = 0b0010_0000;
const INSERT_WITH_NAME_REF: u8 = 0b1000_0000;
const INSERT_WITH_LITERAL_NAME: u8 = 0b0100_0000;
const DUPLICATE: u8 = 0b0000_0000;
const SECTION_ACK: u8 = 0x80;
const STREAM_CANCELLATION: u8 = 0x40;
const INSERT_COUNT_INCREMENT: u8 = 0x00;
const MAX_DECODER_STREAM_PENDING: usize = 64;
const INDEXED: u8 = 0b1000_0000;
const INDEXED_POST_BASE: u8 = 0b0001_0000;
const LITERAL_NAME_REF: u8 = 0b0100_0000;
const LITERAL_POST_BASE_NAME_REF: u8 = 0b0000_0000;
const LITERAL_LITERAL_NAME: u8 = 0b0010_0000;
const NEVER_INDEXED: [&[u8]; 3] = [b"authorization", b"proxy-authorization", b"cookie"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncodedSection {
pub block: Bytes,
pub encoder_stream: Bytes,
}
#[derive(Debug)]
pub struct Encoder {
dynamic: DynamicTable,
max_capacity: u64,
huffman: bool,
known_received: u64,
pending_refs: VecDeque<(u64, u64)>,
decoder_stream_pending: Vec<u8>,
}
impl Encoder {
#[inline]
pub fn new(max_capacity: u64, huffman: bool) -> Self {
Self {
dynamic: DynamicTable::new(0),
max_capacity,
huffman,
known_received: 0,
pending_refs: VecDeque::new(),
decoder_stream_pending: Vec::new(),
}
}
#[inline]
pub fn max_capacity(&self) -> u64 {
self.max_capacity
}
#[inline]
fn evictable_floor(&self) -> u64 {
let pending = self
.pending_refs
.iter()
.map(|(_, min_ref)| *min_ref)
.min()
.unwrap_or(u64::MAX);
self.known_received.min(pending)
}
#[inline]
pub fn encode_section(&mut self, stream_id: u64, headers: &[(Bytes, Bytes)]) -> EncodedSection {
self.encode_section_with_base(stream_id, headers, self.dynamic.inserted())
}
#[inline]
pub(crate) fn encode_section_with_ack_base(
&mut self,
stream_id: u64,
headers: &[(Bytes, Bytes)],
) -> EncodedSection {
self.encode_section_with_base(stream_id, headers, self.known_received)
}
#[inline]
pub(crate) fn required_insert_count(&self) -> u64 {
self.dynamic.inserted()
}
#[inline]
fn encode_section_with_base(
&mut self,
stream_id: u64,
headers: &[(Bytes, Bytes)],
base: u64,
) -> EncodedSection {
let usable = self.max_capacity >= 32;
let mut encoder_stream = Vec::new();
let mut block = Vec::new();
let mut min_rel_ref: Option<u64> = None;
let mut ric = 0u64;
for (name, value) in headers {
let sensitive = NEVER_INDEXED.contains(&name.as_ref());
if sensitive {
self.encode_literal(name, value, true, &mut block);
continue;
}
let dynamic_match = usable.then(|| self.dynamic.find_full_or_name(name, value));
if usable {
if let Some(abs) = dynamic_match.and_then(|(full, _)| full) {
self.encode_indexed(abs, base, &mut block, &mut ric, &mut min_rel_ref);
continue;
}
}
let static_match = static_table::find_full_or_name(name, value);
if let Some(idx) = static_match.0 {
integer::encode(&mut block, idx as u64, 6, INDEXED | 0x40);
continue;
}
if usable {
if let Some(abs) = dynamic_match.and_then(|(_, name)| name) {
if abs >= base {
self.encode_literal_post_base_name_ref(
abs,
base,
value,
&mut block,
&mut ric,
&mut min_rel_ref,
);
} else {
self.encode_literal_with_name_ref(
abs,
base,
value,
&mut block,
&mut ric,
&mut min_rel_ref,
);
}
continue;
}
}
if let Some(idx) = static_match.1 {
self.encode_literal_with_static_name_ref(idx, value, &mut block);
continue;
}
let size = DynamicTable::entry_size(name, value);
let boundary = min_rel_ref.unwrap_or(u64::MAX).min(self.evictable_floor());
let safe = self.dynamic.inserted() - self.dynamic.len() as u64
+ self.dynamic.would_evict(size)
<= boundary;
if usable && size <= self.max_capacity && safe {
if self.dynamic.capacity() != self.max_capacity {
integer::encode(&mut encoder_stream, self.max_capacity, 5, SET_CAPACITY);
self.dynamic.set_capacity(self.max_capacity);
}
let abs = self.dynamic.next_absolute();
self.push_string(&mut encoder_stream, name, 6, INSERT_WITH_LITERAL_NAME);
self.push_string(&mut encoder_stream, value, 8, 0);
let _ = self.dynamic.insert(name.clone(), value.clone());
self.encode_indexed(abs, base, &mut block, &mut ric, &mut min_rel_ref);
} else {
self.encode_literal(name, value, false, &mut block);
}
}
if ric > 0 {
self.pending_refs
.push_back((stream_id, min_rel_ref.unwrap_or(0)));
}
let mut prefix = Vec::new();
self.encode_prefix(&mut prefix, ric, base);
prefix.reserve(block.len());
prefix.extend_from_slice(&block);
EncodedSection {
block: Bytes::from(prefix),
encoder_stream: Bytes::from(encoder_stream),
}
}
#[inline]
fn encode_prefix(&self, out: &mut Vec<u8>, ric: u64, base: u64) {
let max_entries = self.max_capacity / 32;
let enc_ric = if ric == 0 || max_entries == 0 {
0
} else {
(ric % (2 * max_entries)) + 1
};
integer::encode(out, enc_ric, 8, 0);
if base >= ric {
integer::encode(out, base - ric, 7, 0);
} else {
integer::encode(out, ric - base - 1, 7, 0x80);
}
}
#[inline]
fn encode_indexed(
&self,
abs: u64,
base: u64,
block: &mut Vec<u8>,
ric: &mut u64,
min_rel_ref: &mut Option<u64>,
) {
*ric = (*ric).max(abs + 1);
if abs >= base {
integer::encode(block, abs - base, 4, INDEXED_POST_BASE);
} else {
integer::encode(block, base - abs - 1, 6, INDEXED);
}
*min_rel_ref = Some(min_rel_ref.map_or(abs, |m| m.min(abs)));
}
#[inline]
fn encode_literal_with_name_ref(
&self,
abs: u64,
base: u64,
value: &[u8],
block: &mut Vec<u8>,
ric: &mut u64,
min_rel_ref: &mut Option<u64>,
) {
*ric = (*ric).max(abs + 1);
let rel = base - abs - 1;
integer::encode(block, rel, 4, LITERAL_NAME_REF);
*min_rel_ref = Some(min_rel_ref.map_or(abs, |m| m.min(abs)));
self.push_string(block, value, 8, 0);
}
#[inline]
fn encode_literal_post_base_name_ref(
&self,
abs: u64,
base: u64,
value: &[u8],
block: &mut Vec<u8>,
ric: &mut u64,
min_rel_ref: &mut Option<u64>,
) {
*ric = (*ric).max(abs + 1);
integer::encode(block, abs - base, 3, LITERAL_POST_BASE_NAME_REF);
*min_rel_ref = Some(min_rel_ref.map_or(abs, |m| m.min(abs)));
self.push_string(block, value, 8, 0);
}
#[inline]
fn encode_literal_with_static_name_ref(&self, idx: usize, value: &[u8], block: &mut Vec<u8>) {
integer::encode(block, idx as u64, 4, LITERAL_NAME_REF | 0x10);
self.push_string(block, value, 8, 0);
}
#[inline]
fn encode_literal(&self, name: &[u8], value: &[u8], sensitive: bool, block: &mut Vec<u8>) {
if let Some(idx) = static_table::find_name(name) {
integer::encode(
block,
idx as u64,
4,
LITERAL_NAME_REF | 0x10 | (u8::from(sensitive) << 4),
);
} else {
self.push_string(
block,
name,
4,
LITERAL_LITERAL_NAME | (u8::from(sensitive) << 4),
);
}
self.push_string(block, value, 8, 0);
}
#[inline]
fn push_string(&self, out: &mut Vec<u8>, value: &[u8], prefix: u8, header: u8) {
let (huffman, len) = if self.huffman {
let huffman_bits = huffman::encoded_len(value);
if huffman_bits < value.len() * 8 {
(true, huffman_bits.div_ceil(8) as u64)
} else {
(false, value.len() as u64)
}
} else {
(false, value.len() as u64)
};
integer::encode(
out,
len,
prefix - 1,
header | (u8::from(huffman) << (prefix - 1)),
);
if huffman {
huffman::encode_with_len(value, out, len as usize);
} else {
out.extend_from_slice(value);
}
}
#[inline]
pub(crate) fn insert_literal(&mut self, name: &[u8], value: &[u8]) -> Option<Bytes> {
let size = DynamicTable::entry_size(name, value);
if size > self.dynamic.capacity() || self.insert_would_evict_needed(size) {
return None;
}
let mut out = Vec::new();
self.push_string(&mut out, name, 6, INSERT_WITH_LITERAL_NAME);
self.push_string(&mut out, value, 8, 0);
let res = self
.dynamic
.insert(Bytes::copy_from_slice(name), Bytes::copy_from_slice(value));
debug_assert!(res.is_ok(), "insert_literal: entry passed the size check");
res.ok()?;
Some(Bytes::from(out))
}
#[inline]
pub(crate) fn insert_with_name_ref(&mut self, name: &[u8], value: &[u8]) -> Option<Bytes> {
let size = DynamicTable::entry_size(name, value);
if size > self.dynamic.capacity() || self.insert_would_evict_needed(size) {
return None;
}
let mut out = Vec::new();
let (name_idx, pattern) = self
.dynamic
.find_name(name)
.map(|abs| (abs, INSERT_WITH_NAME_REF))
.or_else(|| {
static_table::find_name(name).map(|idx| (idx as u64, INSERT_WITH_NAME_REF | 0x40))
})?;
if pattern == INSERT_WITH_NAME_REF {
integer::encode(&mut out, self.dynamic.inserted() - name_idx - 1, 6, pattern);
} else {
integer::encode(&mut out, name_idx, 6, pattern);
}
self.push_string(&mut out, value, 8, 0);
let res = self
.dynamic
.insert(Bytes::copy_from_slice(name), Bytes::copy_from_slice(value));
debug_assert!(
res.is_ok(),
"insert_with_name_ref: entry passed the size check"
);
res.ok()?;
Some(Bytes::from(out))
}
#[inline]
pub(crate) fn duplicate(&mut self, relative: u64) -> Option<Bytes> {
if relative >= self.dynamic.len() as u64 {
return None;
}
let (name, value) = self.dynamic.entry_at(relative)?;
let size = DynamicTable::entry_size(name, value);
if size > self.dynamic.capacity() || self.insert_would_evict_needed(size) {
return None;
}
let mut out = Vec::new();
integer::encode(&mut out, relative, 5, DUPLICATE);
let res = self
.dynamic
.insert(Bytes::copy_from_slice(name), Bytes::copy_from_slice(value));
debug_assert!(res.is_ok(), "duplicate: entry passed the size check");
res.ok()?;
Some(Bytes::from(out))
}
#[inline]
pub fn set_capacity(&mut self, capacity: u64) -> Option<Bytes> {
if capacity > self.max_capacity || capacity == self.dynamic.capacity() {
return None;
}
if capacity < self.dynamic.capacity() {
let evicted = self.dynamic.evict_for_capacity(capacity);
if self.dynamic.inserted() - self.dynamic.len() as u64 + evicted
> self.evictable_floor()
{
return None;
}
}
let mut out = Vec::new();
integer::encode(&mut out, capacity, 5, SET_CAPACITY);
self.dynamic.set_capacity(capacity);
Some(Bytes::from(out))
}
#[inline]
fn insert_would_evict_needed(&self, size: u64) -> bool {
self.dynamic.inserted() - self.dynamic.len() as u64 + self.dynamic.would_evict(size)
> self.evictable_floor()
}
#[inline]
pub fn feed_decoder_stream(&mut self, buf: &[u8]) -> Result<(), QpackError> {
self.decoder_stream_pending.extend_from_slice(buf);
if self.decoder_stream_pending.len() > MAX_DECODER_STREAM_PENDING {
return Err(QpackError::DecoderStream);
}
let data = &self.decoder_stream_pending;
let mut consumed = 0;
while consumed < data.len() {
let header = data[consumed];
let prefix_bits: u8 = if header & 0x80 != 0 { 7 } else { 6 };
let Some(len) = integer::encoded_len(&data[consumed..], prefix_bits) else {
break;
};
if consumed + len > data.len() {
break;
}
let instr = &data[consumed..consumed + len];
let mut off = 0;
if header & 0x80 != 0 {
let stream_id = integer::decode(instr, &mut off, 7, header)
.map_err(|_| QpackError::DecoderStream)?;
if let Some(pos) = self
.pending_refs
.iter()
.position(|(id, _)| *id == stream_id)
{
self.pending_refs.remove(pos);
}
} else if header & 0x40 != 0 {
let stream_id = integer::decode(instr, &mut off, 6, header)
.map_err(|_| QpackError::DecoderStream)?;
self.pending_refs.retain(|(id, _)| *id != stream_id);
} else {
let increment = integer::decode(instr, &mut off, 6, header)
.map_err(|_| QpackError::DecoderStream)?;
if increment == 0
|| self.known_received.saturating_add(increment) > self.dynamic.inserted()
{
return Err(QpackError::DecoderStream);
}
self.known_received += increment;
}
consumed += len;
}
self.decoder_stream_pending.drain(..consumed);
Ok(())
}
}
#[cfg(test)]
mod tests;