use super::{
encode_integer, huffman,
table::{DEFAULT_MAX_SIZE, DYNAMIC_TABLE_START, DynamicTable, STATIC_TABLE},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Representation {
Proxy,
IncrementalIndexing,
WithoutIndexing,
NeverIndexed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Found {
Field(usize),
Name {
first: usize,
last: usize,
},
Nothing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PendingSizeUpdate {
smallest: usize,
last: usize,
}
#[derive(Debug)]
pub struct Encoder {
table: DynamicTable,
pending_size_update: Option<PendingSizeUpdate>,
}
impl Default for Encoder {
fn default() -> Self {
Self::new()
}
}
impl Encoder {
pub const fn new() -> Self {
Encoder {
table: DynamicTable::new(DEFAULT_MAX_SIZE),
pending_size_update: None,
}
}
pub fn set_max_table_size(&mut self, max_size: usize) {
self.table.set_max_size(max_size);
}
pub fn change_max_table_size(&mut self, max_size: usize) {
self.table.set_max_size(max_size);
let smallest = self
.pending_size_update
.map_or(max_size, |pending| pending.smallest.min(max_size));
self.pending_size_update = Some(PendingSizeUpdate {
smallest,
last: max_size,
});
debug_assert!(
smallest <= max_size,
"the smallest size reached never exceeds the last one"
);
debug_assert!(
self.pending_size_update
.is_some_and(|pending| pending.last == max_size),
"the change is recorded as the last one"
);
}
pub fn encode_size_updates_into(&mut self, last_size: usize, out: &mut Vec<u8>) -> usize {
let pending = self.pending_size_update.take();
debug_assert!(
pending.is_none_or(|pending| pending.last == last_size),
"the size the caller signals is the last one recorded"
);
let start = out.len();
let mut updates = 0;
if let Some(pending) = pending
&& pending.smallest < last_size
{
encode_integer(pending.smallest, 5, 0x20, out);
updates += 1;
}
encode_integer(last_size, 5, 0x20, out);
updates += 1;
debug_assert!(
(1..=2).contains(&updates),
"a block opens with at most two size updates (§4.2)"
);
debug_assert!(
out[start] & 0xe0 == 0x20 && self.pending_size_update.is_none(),
"the appended bytes open with a size update, and nothing stays recorded"
);
updates
}
pub fn reset_table(&mut self) -> usize {
let max_size = self.table.max_size();
self.change_max_table_size(0);
self.change_max_table_size(max_size);
debug_assert!(
self.table.len() == 0 && self.table.max_size() == max_size,
"the table is empty and keeps its maximum size"
);
debug_assert_eq!(
self.pending_size_update,
Some(PendingSizeUpdate {
smallest: 0,
last: max_size,
}),
"the next block opens with 0, then the maximum size"
);
max_size
}
#[cfg(test)]
pub(super) fn table(&self) -> &DynamicTable {
&self.table
}
pub fn encode_header_into(&mut self, (name, value): (&[u8], &[u8]), out: &mut Vec<u8>) {
self.encode_field(name, value, Representation::Proxy, false, out);
}
pub fn encode_into<'a, I>(&mut self, headers: I, out: &mut Vec<u8>)
where
I: IntoIterator<Item = (&'a [u8], &'a [u8])>,
{
for header in headers {
self.encode_header_into(header, out);
}
}
#[cfg(test)]
pub fn encode<'a, I>(&mut self, headers: I) -> Vec<u8>
where
I: IntoIterator<Item = (&'a [u8], &'a [u8])>,
{
let mut out = Vec::new();
self.encode_into(headers, &mut out);
out
}
pub fn encode_field(
&mut self,
name: &[u8],
value: &[u8],
representation: Representation,
huffman: bool,
out: &mut Vec<u8>,
) {
let found = self.find(name, value);
match (representation, found) {
(Representation::Proxy | Representation::IncrementalIndexing, Found::Field(index)) => {
encode_integer(index, 7, 0x80, out);
}
(Representation::Proxy, Found::Name { last, .. }) => {
encode_integer(last, 4, 0x00, out);
encode_string(value, huffman, out);
}
(Representation::Proxy | Representation::IncrementalIndexing, Found::Nothing) => {
out.push(0x40);
encode_string(name, huffman, out);
encode_string(value, huffman, out);
self.table.insert(name, value);
}
(Representation::IncrementalIndexing, Found::Name { first, .. }) => {
encode_integer(first, 6, 0x40, out);
encode_string(value, huffman, out);
self.table.insert(name, value);
}
(Representation::WithoutIndexing | Representation::NeverIndexed, found) => {
let flags = if representation == Representation::NeverIndexed {
0x10
} else {
0x00
};
match found {
Found::Field(index) | Found::Name { first: index, .. } => {
encode_integer(index, 4, flags, out);
}
Found::Nothing => {
out.push(flags);
encode_string(name, huffman, out);
}
}
encode_string(value, huffman, out);
}
}
}
fn find(&self, name: &[u8], value: &[u8]) -> Found {
let entries = DYNAMIC_TABLE_START + self.table.len();
let mut found = Found::Nothing;
let tables = STATIC_TABLE
.iter()
.copied()
.chain(self.table.iter())
.enumerate();
for (position, (entry_name, entry_value)) in tables {
if entry_name != name {
continue;
}
let index = position + 1;
if entry_value == value {
return Found::Field(index);
}
found = match found {
Found::Name { first, .. } => Found::Name { first, last: index },
_ => Found::Name {
first: index,
last: index,
},
};
}
debug_assert!(
match found {
Found::Field(index) => (1..entries).contains(&index),
Found::Name { first, last } => 0 < first && first <= last && last < entries,
Found::Nothing => true,
},
"a found index addresses an entry of one of the two tables"
);
found
}
}
fn encode_string(string: &[u8], huffman: bool, out: &mut Vec<u8>) {
if huffman {
encode_integer(huffman::encoded_len(string), 7, 0x80, out);
huffman::encode(string, out);
} else {
encode_integer(string.len(), 7, 0x00, out);
out.extend_from_slice(string);
}
}