use crate::table::{static_name, DynTable, Name, STATIC_TABLE};
use crate::{bytes_eq, encode_integer, encode_string, field_size, DEFAULT_TABLE_SIZE};
pub fn is_sensitive(name: &[u8]) -> bool {
matches!(name, b"authorization" | b"proxy-authorization" | b"cookie" | b"set-cookie")
}
const SENSITIVE: u64 = 1 << 23 | 1 << 32 | 1 << 49 | 1 << 55;
const RARE: u64 = 1 << 21 | 1 << 28 | 1 << 30 | 1 << 34 | 1 << 39 | 1 << 40 | 1 << 41 | 1 << 44 | 1 << 46;
fn rarely_repeats(idx: usize, name: &[u8], value: &[u8]) -> bool {
match idx {
4 => value.len() > 64 || value.contains(&b'?'), 0 => matches!(name, b"x-request-id" | b"traceparent" | b"tracestate"),
i => RARE >> i & 1 != 0,
}
}
#[derive(Debug, Clone)]
pub struct Encoder {
table: DynTable,
limit: usize,
pending_min: usize,
pending: bool,
pub huffman: bool,
pub indexing: bool,
}
impl Default for Encoder {
fn default() -> Self {
Encoder::new(DEFAULT_TABLE_SIZE)
}
}
impl Encoder {
pub fn new(limit: usize) -> Self {
let size = limit.min(DEFAULT_TABLE_SIZE);
Encoder {
table: DynTable::new(size),
limit,
pending_min: size,
pending: size != DEFAULT_TABLE_SIZE,
huffman: true,
indexing: true,
}
}
pub fn set_peer_max_table_size(&mut self, peer: usize) {
let size = peer.min(self.limit);
if size != self.table.max() {
self.pending_min = self.pending_min.min(size);
self.pending = true;
self.table.set_max(size);
}
}
pub fn table_size(&self) -> usize {
self.table.size()
}
pub fn table_len(&self) -> usize {
self.table.len()
}
pub fn begin_block(&mut self, out: &mut Vec<u8>) {
if self.pending {
let max = self.table.max();
if self.pending_min < max {
encode_integer(self.pending_min, 5, 0x20, out);
}
encode_integer(max, 5, 0x20, out);
self.pending = false;
self.pending_min = max;
}
}
pub fn encode(&mut self, fields: &[(&[u8], &[u8])], out: &mut Vec<u8>) {
self.begin_block(out);
for (n, v) in fields {
self.field(n, v, out);
}
}
pub fn field(&mut self, name: &[u8], value: &[u8], out: &mut Vec<u8>) {
let mut name_idx = 0usize;
if let Some((first, count)) = static_name(name) {
name_idx = first;
for i in first..first + count {
if bytes_eq(STATIC_TABLE[i - 1].1, value) {
encode_integer(i, 7, 0x80, out);
return;
}
}
}
let static_idx = name_idx;
let sensitive = SENSITIVE >> static_idx & 1 != 0;
let (full, by_name) = self.table.find(name, value);
if let (Some(i), false) = (full, sensitive) {
encode_integer(STATIC_TABLE.len() + 1 + i, 7, 0x80, out);
return;
}
if let (0, Some(i)) = (name_idx, by_name) {
name_idx = STATIC_TABLE.len() + 1 + i;
}
let index = self.indexing
&& !sensitive
&& !rarely_repeats(static_idx, name, value)
&& field_size(name, value) <= self.table.max() / 2;
let (prefix, first) = if sensitive {
(4, 0x10)
} else if index {
(6, 0x40)
} else {
(4, 0x00)
};
encode_integer(name_idx, prefix, first, out);
if name_idx == 0 {
encode_string(name, self.huffman, out);
}
encode_string(value, self.huffman, out);
if index {
self.table.insert(Name::Bytes(name), value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn masks_match_the_names() {
let rare: [&[u8]; 9] = [b"content-length", b"content-range", b"etag", b"if-none-match", b"if-modified-since", b"if-match", b"last-modified", b"age", b"location"];
for (i, (n, _)) in STATIC_TABLE.iter().enumerate() {
let first = static_name(n).unwrap().0;
if first != i + 1 {
continue;
}
assert_eq!(SENSITIVE >> first & 1 != 0, is_sensitive(n), "{}", String::from_utf8_lossy(n));
if first != 4 {
assert_eq!(rarely_repeats(first, n, b"x"), rare.contains(n), "{}", String::from_utf8_lossy(n));
}
}
assert!(rarely_repeats(4, b":path", b"/a?b=1"));
assert!(!rarely_repeats(4, b":path", b"/a"));
for n in [&b"x-request-id"[..], b"traceparent", b"tracestate"] {
assert!(rarely_repeats(0, n, b"1"));
}
assert!(!rarely_repeats(0, b"x-other", b"1"));
}
}