use alloc::vec::Vec;
use crate::integer::encode_integer;
use crate::string::encode_string;
use crate::table::{HeaderField, Table};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncoderError {
Reserved,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Encoder {
table: Table,
pub use_huffman: bool,
}
impl Encoder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_max_size(max: usize) -> Self {
Self {
table: Table::new(max),
use_huffman: false,
}
}
#[must_use]
pub fn table(&self) -> &Table {
&self.table
}
pub fn table_mut(&mut self) -> &mut Table {
&mut self.table
}
#[must_use]
pub fn encode(&mut self, headers: &[HeaderField]) -> Vec<u8> {
let mut out = Vec::new();
for h in headers {
match self.table.find(&h.name, &h.value) {
Some((index, true)) => {
let buf = encode_integer(index as u64, 7, 0x80);
out.extend_from_slice(&buf);
}
Some((index, false)) => {
let buf = encode_integer(index as u64, 6, 0x40);
out.extend_from_slice(&buf);
out.extend_from_slice(&encode_string(&h.value, self.use_huffman));
self.table.add(h.clone());
}
None => {
out.push(0x40);
out.extend_from_slice(&encode_string(&h.name, self.use_huffman));
out.extend_from_slice(&encode_string(&h.value, self.use_huffman));
self.table.add(h.clone());
}
}
}
out
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
fn hf(n: &str, v: &str) -> HeaderField {
HeaderField {
name: n.into(),
value: v.into(),
}
}
#[test]
fn full_static_match_is_single_byte() {
let mut e = Encoder::new();
let buf = e.encode(&[hf(":method", "GET")]);
assert_eq!(buf, alloc::vec![0x82]); }
#[test]
fn name_only_static_emits_literal_value() {
let mut e = Encoder::new();
let buf = e.encode(&[hf(":method", "PATCH")]);
assert!(buf[0] & 0xc0 == 0x40);
assert_eq!(e.table().len(), 1);
}
#[test]
fn unknown_header_emits_literal_name_and_value() {
let mut e = Encoder::new();
let buf = e.encode(&[hf("custom", "value")]);
assert_eq!(buf[0], 0x40); assert_eq!(e.table().len(), 1);
}
#[test]
fn second_encode_uses_dynamic_table_match() {
let mut e = Encoder::new();
let _ = e.encode(&[hf("custom", "value")]);
let buf = e.encode(&[hf("custom", "value")]);
assert_eq!(buf[0] & 0x80, 0x80);
}
#[test]
fn huffman_flag_compresses_literal_strings() {
let mut e = Encoder::with_max_size(4096);
e.use_huffman = true;
let buf = e.encode(&[hf("custom", "value")]);
assert!(buf[1] & 0x80 == 0x80, "name string should have H-flag");
}
#[test]
fn multiple_headers_encoded_sequentially() {
let mut e = Encoder::new();
let buf = e.encode(&[hf(":method", "GET"), hf(":scheme", "https")]);
assert_eq!(buf, alloc::vec![0x82, 0x87]); }
#[test]
fn encoder_default_uses_no_huffman() {
let e = Encoder::new();
assert!(!e.use_huffman);
}
}