use super::{
integer, string,
table::{Header, Table},
};
const INDEXED: u8 = 0b1000_0000;
const LITERAL_WITH_INDEXING: u8 = 0b0100_0000;
const LITERAL_NEVER_INDEXED: u8 = 0b0001_0000;
const SIZE_UPDATE: u8 = 0b0010_0000;
const NEVER_INDEXED: [&[u8]; 3] = [b"authorization", b"proxy-authorization", b"cookie"];
#[derive(Debug)]
pub struct Encoder {
table: Table,
queued_size_update: Option<usize>,
use_huffman: bool,
}
impl Encoder {
#[inline]
pub fn new(max_table_size: usize) -> Self {
Encoder {
table: Table::with_max_size(max_table_size),
queued_size_update: None,
use_huffman: true,
}
}
#[inline]
pub fn queue_size_update(&mut self, size: usize) {
self.queued_size_update = Some(match self.queued_size_update {
Some(current) => current.max(size),
None => size,
});
}
#[inline]
pub fn set_use_huffman(&mut self, use_huffman: bool) {
self.use_huffman = use_huffman;
}
#[cfg(test)]
#[inline]
pub(crate) fn table(&self) -> &Table {
&self.table
}
#[inline]
pub fn encode(&mut self, headers: &[Header], out: &mut Vec<u8>) {
if let Some(size) = self.queued_size_update.take() {
self.table.set_max_size(size);
integer::encode(out, size as u64, 5, SIZE_UPDATE);
}
for header in headers {
let name = header.name();
if NEVER_INDEXED.contains(&name) {
match self.table.find_name(name) {
Some(index) => integer::encode(out, index as u64, 4, LITERAL_NEVER_INDEXED),
None => {
out.push(LITERAL_NEVER_INDEXED);
self.encode_string(out, name);
}
}
self.encode_string(out, header.value());
continue;
}
match self.table.find(name, header.value()) {
Some(index) => {
integer::encode(out, index as u64, 7, INDEXED);
}
None => {
match self.table.find_name(name) {
Some(index) => integer::encode(out, index as u64, 6, LITERAL_WITH_INDEXING),
None => {
out.push(LITERAL_WITH_INDEXING);
self.encode_string(out, name);
}
}
self.encode_string(out, header.value());
self.table.add(header.clone());
}
}
}
}
#[inline]
fn encode_string(&self, out: &mut Vec<u8>, value: &[u8]) {
let huffman_len = self
.use_huffman
.then(|| string::huffman_encoded_len_if_shorter(value))
.flatten();
string::encode(out, value, huffman_len);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::h2::hpack::decode::Decoder;
#[inline]
fn headers(list: &[(&str, &str)]) -> Vec<Header> {
list.iter()
.map(|(name, value)| Header::new(name.as_bytes().to_vec(), value.as_bytes().to_vec()))
.collect()
}
#[inline]
fn encode(encoder: &mut Encoder, list: &[(&str, &str)]) -> Vec<u8> {
let list = headers(list);
let mut out = Vec::new();
encoder.encode(&list, &mut out);
out
}
#[inline]
fn decode(encoder: &Encoder, wire: &[u8]) -> Vec<(String, String)> {
let mut decoder = Decoder::new(encoder.table().max_size());
decoder
.decode(wire, &mut 0)
.unwrap()
.into_iter()
.map(|h| {
(
String::from_utf8(h.name().to_vec()).unwrap(),
String::from_utf8(h.value().to_vec()).unwrap(),
)
})
.collect()
}
#[inline]
fn hex_to_bytes(hex: &str) -> Vec<u8> {
hex.as_bytes()
.chunks_exact(2)
.map(|pair| {
let hi = (pair[0] as char).to_digit(16).unwrap() as u8;
let lo = (pair[1] as char).to_digit(16).unwrap() as u8;
(hi << 4) | lo
})
.collect()
}
#[test]
fn literal_new_name_indexed() {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
let out = encode(&mut encoder, &[("custom-key", "custom-header")]);
assert_eq!(
out,
hex_to_bytes("400a637573746f6d2d6b65790d637573746f6d2d686561646572")
);
assert_eq!(encoder.table().dynamic_len(), 1);
}
#[test]
fn c3_request_walkthrough() {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
assert_eq!(
encode(
&mut encoder,
&[
(":method", "GET"),
(":scheme", "http"),
(":path", "/"),
(":authority", "www.example.com"),
]
),
hex_to_bytes("828684410f7777772e6578616d706c652e636f6d")
);
assert_eq!(
encode(
&mut encoder,
&[
(":method", "GET"),
(":scheme", "http"),
(":path", "/"),
(":authority", "www.example.com"),
("cache-control", "no-cache"),
]
),
hex_to_bytes("828684be58086e6f2d6361636865")
);
assert_eq!(
encode(
&mut encoder,
&[
(":method", "GET"),
(":scheme", "https"),
(":path", "/index.html"),
(":authority", "www.example.com"),
("custom-key", "custom-value"),
]
),
hex_to_bytes("828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565")
);
}
#[test]
fn c4_request_walkthrough() {
let mut encoder = Encoder::new(4096);
assert_eq!(
encode(
&mut encoder,
&[
(":method", "GET"),
(":scheme", "http"),
(":path", "/"),
(":authority", "www.example.com"),
]
),
hex_to_bytes("828684418cf1e3c2e5f23a6ba0ab90f4ff")
);
assert_eq!(
encode(
&mut encoder,
&[
(":method", "GET"),
(":scheme", "http"),
(":path", "/"),
(":authority", "www.example.com"),
("cache-control", "no-cache"),
]
),
hex_to_bytes("828684be5886a8eb10649cbf")
);
assert_eq!(
encode(
&mut encoder,
&[
(":method", "GET"),
(":scheme", "https"),
(":path", "/index.html"),
(":authority", "www.example.com"),
("custom-key", "custom-value"),
]
),
hex_to_bytes("828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf")
);
}
#[test]
fn c5_response_walkthrough() {
let mut encoder = Encoder::new(256);
encoder.set_use_huffman(false);
assert_eq!(
encode(
&mut encoder,
&[
(":status", "302"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
("location", "https://www.example.com"),
]
),
hex_to_bytes(
"4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d"
)
);
assert_eq!(encoder.table().dynamic_len(), 4);
assert_eq!(
encode(
&mut encoder,
&[
(":status", "307"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
("location", "https://www.example.com"),
]
),
hex_to_bytes("4803333037c1c0bf")
);
assert_eq!(encoder.table().dynamic_len(), 4);
assert_eq!(
encode(
&mut encoder,
&[
(":status", "200"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
("location", "https://www.example.com"),
("content-encoding", "gzip"),
(
"set-cookie",
"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
),
]
),
hex_to_bytes(
"88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31"
)
);
assert_eq!(encoder.table().dynamic_len(), 3);
}
#[test]
fn c6_response_walkthrough() {
let mut encoder = Encoder::new(256);
assert_eq!(
encode(
&mut encoder,
&[
(":status", "302"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
("location", "https://www.example.com"),
]
),
hex_to_bytes(
"488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3"
)
);
assert_eq!(encoder.table().dynamic_len(), 4);
assert_eq!(
encode(
&mut encoder,
&[
(":status", "307"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
("location", "https://www.example.com"),
]
),
hex_to_bytes("4883640effc1c0bf")
);
assert_eq!(encoder.table().dynamic_len(), 4);
assert_eq!(
encode(
&mut encoder,
&[
(":status", "200"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
("location", "https://www.example.com"),
("content-encoding", "gzip"),
(
"set-cookie",
"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
),
]
),
hex_to_bytes(
"88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007"
)
);
assert_eq!(encoder.table().dynamic_len(), 3);
}
#[test]
fn non_sensitive_is_indexed() {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
let out = encode(&mut encoder, &[("password", "secret")]);
assert_eq!(out, hex_to_bytes("400870617373776f726406736563726574"));
assert_eq!(encoder.table().dynamic_len(), 1);
}
#[test]
fn sensitive_headers_never_indexed() {
for name in ["authorization", "proxy-authorization", "cookie"] {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
let out = encode(&mut encoder, &[(name, "secret-value")]);
assert_eq!(out[0], 0x1f, "{name}");
let decoded = decode(&encoder, &out);
assert_eq!(
decoded,
vec![(name.to_string(), "secret-value".into())],
"{name}"
);
assert_eq!(encoder.table().dynamic_len(), 0, "{name}");
}
}
#[test]
fn sensitive_uses_static_name_index() {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
let out = encode(&mut encoder, &[("authorization", "Bearer xyz")]);
assert_eq!(out, hex_to_bytes("1f080a4265617265722078797a"));
assert_eq!(encoder.table().dynamic_len(), 0);
}
#[test]
fn size_update_emitted_at_block_start() {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
encoder.queue_size_update(0);
let out = encode(&mut encoder, &[(":method", "GET")]);
assert_eq!(out, hex_to_bytes("2082"));
assert_eq!(encoder.table().max_size(), 0);
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
let _ = encode(&mut encoder, &[("custom-key", "custom-value")]);
assert_eq!(encoder.table().dynamic_len(), 1);
encoder.queue_size_update(32);
let _ = encode(&mut encoder, &[(":method", "GET")]);
assert_eq!(encoder.table().dynamic_len(), 0);
}
#[test]
fn size_update_grow_reindexes() {
let mut encoder = Encoder::new(4096);
encoder.set_use_huffman(false);
encoder.queue_size_update(0);
let _ = encode(&mut encoder, &[("a", "1")]);
encoder.queue_size_update(4096);
let _ = encode(&mut encoder, &[("b", "2")]);
assert_eq!(encoder.table().get(62).unwrap().name(), b"b");
assert_eq!(encoder.table().get(63), None);
}
#[test]
fn round_trip() {
let lists: [&[(&str, &str)]; 5] = [
&[(":method", "GET"), (":scheme", "https")],
&[
(":status", "302"),
("cache-control", "private"),
("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
("location", "https://www.example.com"),
("content-encoding", "gzip"),
("authorization", "Bearer sekrit"),
("x-empty", ""),
],
&[("x-long", &"v".repeat(300))],
&[("x-huffman-ok", "custom-value"), ("cookie", "a=b; c=d")],
&[("accept-encoding", "gzip, deflate"), ("te", "trailers")],
];
for (i, list) in lists.iter().enumerate() {
for table_size in [0, 16, 256, 4096] {
for use_huffman in [false, true] {
let mut encoder = Encoder::new(table_size);
encoder.set_use_huffman(use_huffman);
let wire = encode(&mut encoder, list);
let decoded = decode(&encoder, &wire);
let expected = list
.iter()
.map(|(name, value)| (name.to_string(), value.to_string()))
.collect::<Vec<_>>();
assert_eq!(
decoded, expected,
"list {i}, table {table_size}, huffman {use_huffman}"
);
}
}
}
}
#[test]
fn empty_value_round_trip() {
let mut encoder = Encoder::new(4096);
let out = encode(&mut encoder, &[("x-empty", "")]);
assert_eq!(decode(&encoder, &out), vec![("x-empty".into(), "".into())]);
}
}