More HPACK encoding work
This commit is contained in:
@@ -16,9 +16,9 @@ pub enum EncoderError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder {
|
impl Encoder {
|
||||||
pub fn new() -> Encoder {
|
pub fn new(max_size: usize, capacity: usize) -> Encoder {
|
||||||
Encoder {
|
Encoder {
|
||||||
table: Table::with_capacity(0),
|
table: Table::new(max_size, capacity),
|
||||||
max_size_update: None,
|
max_size_update: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,29 +41,39 @@ impl Encoder {
|
|||||||
fn encode_header(&mut self, header: Header, dst: &mut BytesMut)
|
fn encode_header(&mut self, header: Header, dst: &mut BytesMut)
|
||||||
-> Result<(), EncoderError>
|
-> Result<(), EncoderError>
|
||||||
{
|
{
|
||||||
if header.is_sensitive() {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.table.index(header) {
|
match self.table.index(header) {
|
||||||
Index::Indexed(idx, _header) => {
|
Index::Indexed(idx, header) => {
|
||||||
|
assert!(!header.is_sensitive());
|
||||||
encode_int(idx, 7, 0x80, dst);
|
encode_int(idx, 7, 0x80, dst);
|
||||||
}
|
}
|
||||||
Index::Name(idx, header) => {
|
Index::Name(idx, header) => {
|
||||||
|
if header.is_sensitive() {
|
||||||
|
encode_int(idx, 4, 0b10000, dst);
|
||||||
|
} else {
|
||||||
encode_int(idx, 4, 0, dst);
|
encode_int(idx, 4, 0, dst);
|
||||||
|
}
|
||||||
|
|
||||||
encode_str(header.value_slice(), dst);
|
encode_str(header.value_slice(), dst);
|
||||||
}
|
}
|
||||||
Index::Inserted(header) => {
|
Index::Inserted(header) => {
|
||||||
|
assert!(!header.is_sensitive());
|
||||||
dst.put_u8(0b01000000);
|
dst.put_u8(0b01000000);
|
||||||
encode_str(header.name().as_slice(), dst);
|
encode_str(header.name().as_slice(), dst);
|
||||||
encode_str(header.value_slice(), dst);
|
encode_str(header.value_slice(), dst);
|
||||||
}
|
}
|
||||||
Index::InsertedValue(idx, header) => {
|
Index::InsertedValue(idx, header) => {
|
||||||
|
assert!(!header.is_sensitive());
|
||||||
|
|
||||||
encode_int(idx, 6, 0b01000000, dst);
|
encode_int(idx, 6, 0b01000000, dst);
|
||||||
encode_str(header.value_slice(), dst);
|
encode_str(header.value_slice(), dst);
|
||||||
}
|
}
|
||||||
Index::NotIndexed(header) => {
|
Index::NotIndexed(header) => {
|
||||||
|
if header.is_sensitive() {
|
||||||
|
dst.put_u8(0b10000);
|
||||||
|
} else {
|
||||||
dst.put_u8(0);
|
dst.put_u8(0);
|
||||||
|
}
|
||||||
|
|
||||||
encode_str(header.name().as_slice(), dst);
|
encode_str(header.name().as_slice(), dst);
|
||||||
encode_str(header.value_slice(), dst);
|
encode_str(header.value_slice(), dst);
|
||||||
}
|
}
|
||||||
@@ -73,6 +83,12 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Encoder {
|
||||||
|
fn default() -> Encoder {
|
||||||
|
Encoder::new(4096, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn encode_str(val: &[u8], dst: &mut BytesMut) {
|
fn encode_str(val: &[u8], dst: &mut BytesMut) {
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
@@ -153,3 +169,241 @@ fn encode_int<B: BufMut>(
|
|||||||
fn encode_int_one_byte(value: usize, prefix_bits: usize) -> bool {
|
fn encode_int_one_byte(value: usize, prefix_bits: usize) -> bool {
|
||||||
value < (1 << prefix_bits) - 1
|
value < (1 << prefix_bits) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use hpack::Header;
|
||||||
|
use http::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_method_get() {
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![method("GET")]);
|
||||||
|
assert_eq!(*res, [0x80 | 2]);
|
||||||
|
assert_eq!(encoder.table.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_method_post() {
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![method("POST")]);
|
||||||
|
assert_eq!(*res, [0x80 | 3]);
|
||||||
|
assert_eq!(encoder.table.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_method_patch() {
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![method("PATCH")]);
|
||||||
|
|
||||||
|
assert_eq!(res[0], 0b01000000 | 2); // Incremental indexing w/ name pulled from table
|
||||||
|
assert_eq!(res[1], 0x80 | 5); // header value w/ huffman coding
|
||||||
|
|
||||||
|
assert_eq!("PATCH", huff_decode(&res[2..7]));
|
||||||
|
assert_eq!(encoder.table.len(), 1);
|
||||||
|
|
||||||
|
let res = encode(&mut encoder, vec![method("PATCH")]);
|
||||||
|
|
||||||
|
assert_eq!(1 << 7 | 62, res[0]);
|
||||||
|
assert_eq!(1, res.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_repeated_headers_are_indexed() {
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![header("foo", "hello")]);
|
||||||
|
|
||||||
|
assert_eq!(&[0b01000000, 0x80 | 2], &res[0..2]);
|
||||||
|
assert_eq!("foo", huff_decode(&res[2..4]));
|
||||||
|
assert_eq!(0x80 | 4, res[4]);
|
||||||
|
assert_eq!("hello", huff_decode(&res[5..]));
|
||||||
|
assert_eq!(9, res.len());
|
||||||
|
|
||||||
|
assert_eq!(1, encoder.table.len());
|
||||||
|
|
||||||
|
let res = encode(&mut encoder, vec![header("foo", "hello")]);
|
||||||
|
assert_eq!([0x80 | 62], *res);
|
||||||
|
|
||||||
|
assert_eq!(encoder.table.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_evicting_headers() {
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
|
||||||
|
// Fill the table
|
||||||
|
for i in 0..64 {
|
||||||
|
let key = format!("x-hello-world-{:02}", i);
|
||||||
|
let res = encode(&mut encoder, vec![header(&key, &key)]);
|
||||||
|
|
||||||
|
assert_eq!(&[0b01000000, 0x80 | 12], &res[0..2]);
|
||||||
|
assert_eq!(key, huff_decode(&res[2..14]));
|
||||||
|
assert_eq!(0x80 | 12, res[14]);
|
||||||
|
assert_eq!(key, huff_decode(&res[15..]));
|
||||||
|
assert_eq!(27, res.len());
|
||||||
|
|
||||||
|
// Make sure the header can be found...
|
||||||
|
let res = encode(&mut encoder, vec![header(&key, &key)]);
|
||||||
|
|
||||||
|
// Only check that it is found
|
||||||
|
assert_eq!(0x80, res[0] & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(4096, encoder.table.size());
|
||||||
|
assert_eq!(64, encoder.table.len());
|
||||||
|
|
||||||
|
// Find existing headers
|
||||||
|
for i in 0..64 {
|
||||||
|
let key = format!("x-hello-world-{:02}", i);
|
||||||
|
let res = encode(&mut encoder, vec![header(&key, &key)]);
|
||||||
|
assert_eq!(0x80, res[0] & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a new header
|
||||||
|
let key = "x-hello-world-64";
|
||||||
|
let res = encode(&mut encoder, vec![header(key, key)]);
|
||||||
|
|
||||||
|
assert_eq!(&[0b01000000, 0x80 | 12], &res[0..2]);
|
||||||
|
assert_eq!(key, huff_decode(&res[2..14]));
|
||||||
|
assert_eq!(0x80 | 12, res[14]);
|
||||||
|
assert_eq!(key, huff_decode(&res[15..]));
|
||||||
|
assert_eq!(27, res.len());
|
||||||
|
|
||||||
|
assert_eq!(64, encoder.table.len());
|
||||||
|
|
||||||
|
// Now try encoding entries that should exist in the table
|
||||||
|
for i in 1..65 {
|
||||||
|
let key = format!("x-hello-world-{:02}", i);
|
||||||
|
let res = encode(&mut encoder, vec![header(&key, &key)]);
|
||||||
|
assert_eq!(0x80 | (i + 61), res[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_large_headers_are_not_indexed() {
|
||||||
|
let mut encoder = Encoder::new(128, 0);
|
||||||
|
let key = "hello-world-hello-world-HELLO-zzz";
|
||||||
|
|
||||||
|
let res = encode(&mut encoder, vec![header(key, key)]);
|
||||||
|
|
||||||
|
assert_eq!(&[0, 0x80 | 25], &res[..2]);
|
||||||
|
|
||||||
|
assert_eq!(0, encoder.table.len());
|
||||||
|
assert_eq!(0, encoder.table.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sensitive_headers_are_never_indexed() {
|
||||||
|
use http::header::{HeaderName, HeaderValue};
|
||||||
|
|
||||||
|
let name = "my-password".parse().unwrap();
|
||||||
|
let mut value = HeaderValue::try_from_bytes(b"12345").unwrap();
|
||||||
|
value.set_sensitive(true);
|
||||||
|
|
||||||
|
let header = Header::Field { name: name, value: value };
|
||||||
|
|
||||||
|
// Now, try to encode the sensitive header
|
||||||
|
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![header]);
|
||||||
|
|
||||||
|
assert_eq!(&[0b10000, 0x80 | 8], &res[..2]);
|
||||||
|
assert_eq!("my-password", huff_decode(&res[2..10]));
|
||||||
|
assert_eq!(0x80 | 4, res[10]);
|
||||||
|
assert_eq!("12345", huff_decode(&res[11..]));
|
||||||
|
|
||||||
|
// Now, try to encode a sensitive header w/ a name in the static table
|
||||||
|
let name = "authorization".parse().unwrap();
|
||||||
|
let mut value = HeaderValue::try_from_bytes(b"12345").unwrap();
|
||||||
|
value.set_sensitive(true);
|
||||||
|
|
||||||
|
let header = Header::Field { name: name, value: value };
|
||||||
|
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![header]);
|
||||||
|
|
||||||
|
assert_eq!(&[0b11111, 8], &res[..2]);
|
||||||
|
assert_eq!(0x80 | 4, res[2]);
|
||||||
|
assert_eq!("12345", huff_decode(&res[3..]));
|
||||||
|
|
||||||
|
// Using the name component of a previously indexed header (without
|
||||||
|
// sensitive flag set)
|
||||||
|
|
||||||
|
let _ = encode(&mut encoder, vec![self::header("my-password", "not-so-secret")]);
|
||||||
|
|
||||||
|
let name = "my-password".parse().unwrap();
|
||||||
|
let mut value = HeaderValue::try_from_bytes(b"12345").unwrap();
|
||||||
|
value.set_sensitive(true);
|
||||||
|
|
||||||
|
let header = Header::Field { name: name, value: value };
|
||||||
|
let res = encode(&mut encoder, vec![header]);
|
||||||
|
|
||||||
|
assert_eq!(&[0b11111, 47], &res[..2]);
|
||||||
|
assert_eq!(0x80 | 4, res[2]);
|
||||||
|
assert_eq!("12345", huff_decode(&res[3..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_content_length_value_not_indexed() {
|
||||||
|
let mut encoder = Encoder::default();
|
||||||
|
let res = encode(&mut encoder, vec![header("content-length", "1234")]);
|
||||||
|
|
||||||
|
assert_eq!(&[15, 13, 0x80 | 3], &res[0..3]);
|
||||||
|
assert_eq!("1234", huff_decode(&res[3..]));
|
||||||
|
assert_eq!(6, res.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_at_most_two_values_per_name_indexed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_index_header_with_duplicate_name_does_not_evict() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_size_zero() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_increasing_table_size() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decreasing_table_size_without_eviction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decreasing_table_size_with_eviction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encoding_into_undersized_buf() {
|
||||||
|
// Test hitting end at multiple points.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn encode(e: &mut Encoder, hdrs: Vec<Header>) -> BytesMut {
|
||||||
|
let mut dst = BytesMut::with_capacity(1024);
|
||||||
|
e.encode(hdrs, &mut dst);
|
||||||
|
dst
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(s: &str) -> Header {
|
||||||
|
Header::Method(Method::from_bytes(s.as_bytes()).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header(name: &str, val: &str) -> Header {
|
||||||
|
use http::header::{HeaderName, HeaderValue};
|
||||||
|
|
||||||
|
let name = HeaderName::from_bytes(name.as_bytes()).unwrap();
|
||||||
|
let value = HeaderValue::try_from_bytes(val.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
Header::Field { name: name, value: value }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn huff_decode(src: &[u8]) -> BytesMut {
|
||||||
|
huffman::decode(src).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl Header {
|
|||||||
Ok(Header::Path(value))
|
Ok(Header::Path(value))
|
||||||
}
|
}
|
||||||
b"status" => {
|
b"status" => {
|
||||||
let status = try!(StatusCode::from_slice(&value));
|
let status = try!(StatusCode::from_bytes(&value));
|
||||||
Ok(Header::Status(status))
|
Ok(Header::Status(status))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -65,7 +65,7 @@ impl Header {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let name = try!(HeaderName::from_bytes(&name));
|
let name = try!(HeaderName::from_bytes(&name));
|
||||||
let value = try!(HeaderValue::try_from_slice(&value));
|
let value = try!(HeaderValue::try_from_bytes(&value));
|
||||||
|
|
||||||
Ok(Header::Field { name: name, value: value })
|
Ok(Header::Field { name: name, value: value })
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,11 @@ impl Header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_sensitive(&self) -> bool {
|
pub fn is_sensitive(&self) -> bool {
|
||||||
false
|
match *self {
|
||||||
|
Header::Field { ref value, .. } => value.is_sensitive(),
|
||||||
|
// TODO: Technically these other header values can be sensitive too.
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_value_index(&self) -> bool {
|
pub fn skip_value_index(&self) -> bool {
|
||||||
@@ -193,7 +197,7 @@ impl<'a> Name<'a> {
|
|||||||
Name::Field(name) => {
|
Name::Field(name) => {
|
||||||
Ok(Header::Field {
|
Ok(Header::Field {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
value: try!(HeaderValue::try_from_slice(&*value)),
|
value: try!(HeaderValue::try_from_bytes(&*value)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Name::Authority => {
|
Name::Authority => {
|
||||||
@@ -209,7 +213,7 @@ impl<'a> Name<'a> {
|
|||||||
Ok(Header::Path(try!(ByteStr::from_utf8(value))))
|
Ok(Header::Path(try!(ByteStr::from_utf8(value))))
|
||||||
}
|
}
|
||||||
Name::Status => {
|
Name::Status => {
|
||||||
match StatusCode::from_slice(&value) {
|
match StatusCode::from_bytes(&value) {
|
||||||
Ok(status) => Ok(Header::Status(status)),
|
Ok(status) => Ok(Header::Status(status)),
|
||||||
// TODO: better error handling
|
// TODO: better error handling
|
||||||
Err(_) => Err(DecoderError::InvalidStatusCode),
|
Err(_) => Err(DecoderError::InvalidStatusCode),
|
||||||
|
|||||||
@@ -67,40 +67,6 @@ pub fn encode<B: BufMut>(src: &[u8], dst: &mut B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
static size_t encode_huffman(uint8_t *_dst, const uint8_t *src, size_t len)
|
|
||||||
{
|
|
||||||
uint8_t *dst = _dst, *dst_end = dst + len;
|
|
||||||
const uint8_t *src_end = src + len;
|
|
||||||
uint64_t bits = 0;
|
|
||||||
int bits_left = 40;
|
|
||||||
|
|
||||||
while (src != src_end) {
|
|
||||||
const nghttp2_huff_sym *sym = huff_sym_table + *src++;
|
|
||||||
bits |= (uint64_t)sym->code << (bits_left - sym->nbits);
|
|
||||||
bits_left -= sym->nbits;
|
|
||||||
while (bits_left <= 32) {
|
|
||||||
*dst++ = bits >> 32;
|
|
||||||
bits <<= 8;
|
|
||||||
bits_left += 8;
|
|
||||||
if (dst == dst_end) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bits_left != 40) {
|
|
||||||
bits |= ((uint64_t)1 << bits_left) - 1;
|
|
||||||
*dst++ = bits >> 32;
|
|
||||||
}
|
|
||||||
if (dst == dst_end) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dst - _dst;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
impl Decoder {
|
impl Decoder {
|
||||||
fn new() -> Decoder {
|
fn new() -> Decoder {
|
||||||
Decoder {
|
Decoder {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use fnv::FnvHasher;
|
|||||||
use http::method;
|
use http::method;
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
|
|
||||||
use std::mem;
|
use std::{cmp, mem};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ pub enum Index<'a> {
|
|||||||
Inserted(&'a Header),
|
Inserted(&'a Header),
|
||||||
|
|
||||||
// Only the value has been inserted
|
// Only the value has been inserted
|
||||||
InsertedValue(usize, Header),
|
InsertedValue(usize, &'a Header),
|
||||||
|
|
||||||
// The header is not indexed by this table
|
// The header is not indexed by this table
|
||||||
NotIndexed(Header),
|
NotIndexed(Header),
|
||||||
@@ -75,19 +75,28 @@ macro_rules! probe_loop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Table {
|
impl Table {
|
||||||
pub fn with_capacity(n: usize) -> Table {
|
pub fn new(max_size: usize, capacity: usize) -> Table {
|
||||||
if n == 0 {
|
if capacity == 0 {
|
||||||
unimplemented!();
|
Table {
|
||||||
|
mask: 0,
|
||||||
|
indices: vec![],
|
||||||
|
slots: VecDeque::new(),
|
||||||
|
evicted: 0,
|
||||||
|
size: 0,
|
||||||
|
max_size: max_size,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let capacity = to_raw_capacity(n).next_power_of_two();
|
let capacity = cmp::max(
|
||||||
|
to_raw_capacity(capacity).next_power_of_two(),
|
||||||
|
8);
|
||||||
|
|
||||||
Table {
|
Table {
|
||||||
mask: capacity.wrapping_sub(1),
|
mask: capacity.wrapping_sub(1),
|
||||||
indices: vec![None; capacity],
|
indices: vec![None; capacity],
|
||||||
slots: VecDeque::with_capacity(n),
|
slots: VecDeque::with_capacity(usable_capacity(capacity)),
|
||||||
evicted: 0,
|
evicted: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
max_size: 4096,
|
max_size: max_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +130,17 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn index_dynamic(&mut self, header: Header, statik: Option<(usize, bool)>) -> Index {
|
fn index_dynamic(&mut self, header: Header, statik: Option<(usize, bool)>) -> Index {
|
||||||
|
if header.len() + self.size < self.max_size || !header.is_sensitive() {
|
||||||
|
// Only grow internal storage if needed
|
||||||
self.reserve_one();
|
self.reserve_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.indices.is_empty() {
|
||||||
|
// If `indices` is not empty, then it is impossible for all
|
||||||
|
// `indices` entries to be `Some`. So, we only need to check for the
|
||||||
|
// empty case.
|
||||||
|
return Index::new(statik, header);
|
||||||
|
}
|
||||||
|
|
||||||
let hash = hash_header(&header);
|
let hash = hash_header(&header);
|
||||||
|
|
||||||
@@ -136,15 +155,17 @@ impl Table {
|
|||||||
// displacement.
|
// displacement.
|
||||||
let their_dist = probe_distance(self.mask, pos.hash, probe);
|
let their_dist = probe_distance(self.mask, pos.hash, probe);
|
||||||
|
|
||||||
|
let slot_idx = pos.index.wrapping_sub(self.evicted);
|
||||||
|
|
||||||
if their_dist < dist {
|
if their_dist < dist {
|
||||||
// Index robinhood
|
// Index robinhood
|
||||||
return self.index_vacant(header, hash, desired_pos, probe);
|
return self.index_vacant(header, hash, dist, probe, statik);
|
||||||
} else if pos.hash == hash && self.slots[pos.index].header.name() == header.name() {
|
} else if pos.hash == hash && self.slots[slot_idx].header.name() == header.name() {
|
||||||
// Matching name, check values
|
// Matching name, check values
|
||||||
return self.index_occupied(header, pos.index, statik);
|
return self.index_occupied(header, pos.index, statik);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return self.index_vacant(header, hash, desired_pos, probe);
|
return self.index_vacant(header, hash, dist, probe, statik);
|
||||||
}
|
}
|
||||||
|
|
||||||
dist += 1;
|
dist += 1;
|
||||||
@@ -157,19 +178,59 @@ impl Table {
|
|||||||
// There already is a match for the given header name. Check if a value
|
// There already is a match for the given header name. Check if a value
|
||||||
// matches. The header will also only be inserted if the table is not at
|
// matches. The header will also only be inserted if the table is not at
|
||||||
// capacity.
|
// capacity.
|
||||||
|
let mut n = 0;
|
||||||
|
|
||||||
|
while n < MAX_VALUES_PER_NAME {
|
||||||
|
// Compute the real index into the VecDeque
|
||||||
|
let real_idx = index.wrapping_sub(self.evicted);
|
||||||
|
|
||||||
|
if self.slots[real_idx].header.value_eq(&header) {
|
||||||
|
// We have a full match!
|
||||||
|
return Index::Indexed(real_idx + DYN_OFFSET, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(next) = self.slots[real_idx].next {
|
||||||
|
n = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.is_sensitive() {
|
||||||
|
return Index::Name(real_idx + DYN_OFFSET, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe index
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Index::NotIndexed(header)
|
||||||
|
}
|
||||||
|
|
||||||
fn index_vacant(&mut self,
|
fn index_vacant(&mut self,
|
||||||
header: Header,
|
header: Header,
|
||||||
hash: HashValue,
|
hash: HashValue,
|
||||||
desired: usize,
|
dist: usize,
|
||||||
probe: usize)
|
mut probe: usize,
|
||||||
|
statik: Option<(usize, bool)>)
|
||||||
-> Index
|
-> Index
|
||||||
{
|
{
|
||||||
if self.maybe_evict(header.len()) {
|
if header.is_sensitive() {
|
||||||
// Maybe step back
|
return Index::new(statik, header);
|
||||||
unimplemented!();
|
}
|
||||||
|
|
||||||
|
if self.update_size(header.len()) {
|
||||||
|
if dist != 0 {
|
||||||
|
let back = probe.wrapping_sub(1) & self.mask;
|
||||||
|
|
||||||
|
if let Some(pos) = self.indices[probe] {
|
||||||
|
let their_dist = probe_distance(self.mask, pos.hash, probe);
|
||||||
|
|
||||||
|
if their_dist < dist {
|
||||||
|
probe = back;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
probe = back;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The index is offset by the current # of evicted elements
|
// The index is offset by the current # of evicted elements
|
||||||
@@ -193,7 +254,6 @@ impl Table {
|
|||||||
|
|
||||||
probe_loop!(probe < self.indices.len(), {
|
probe_loop!(probe < self.indices.len(), {
|
||||||
let pos = &mut self.indices[probe as usize];
|
let pos = &mut self.indices[probe as usize];
|
||||||
let p = mem::replace(pos, Some(prev));
|
|
||||||
|
|
||||||
prev = match mem::replace(pos, Some(prev)) {
|
prev = match mem::replace(pos, Some(prev)) {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
@@ -202,14 +262,18 @@ impl Table {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((n, _)) = statik {
|
||||||
|
Index::InsertedValue(n, &self.slots[slot_idx].header)
|
||||||
|
} else {
|
||||||
Index::Inserted(&self.slots[slot_idx].header)
|
Index::Inserted(&self.slots[slot_idx].header)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn maybe_evict(&mut self, len: usize) -> bool {
|
fn update_size(&mut self, len: usize) -> bool {
|
||||||
let target = self.max_size - len;
|
self.size += len;
|
||||||
let mut ret = false;
|
let mut ret = false;
|
||||||
|
|
||||||
while self.size > target {
|
while self.size > self.max_size {
|
||||||
ret = true;
|
ret = true;
|
||||||
self.evict();
|
self.evict();
|
||||||
}
|
}
|
||||||
@@ -224,6 +288,10 @@ impl Table {
|
|||||||
let slot = self.slots.pop_front().unwrap();
|
let slot = self.slots.pop_front().unwrap();
|
||||||
let mut probe = desired_pos(self.mask, slot.hash);
|
let mut probe = desired_pos(self.mask, slot.hash);
|
||||||
|
|
||||||
|
// Update the size
|
||||||
|
self.size -= slot.header.len();
|
||||||
|
|
||||||
|
// Equivalent to 0.wrapping_add(self.evicted);
|
||||||
let pos_idx = self.evicted;
|
let pos_idx = self.evicted;
|
||||||
|
|
||||||
// Find the associated position
|
// Find the associated position
|
||||||
@@ -328,6 +396,19 @@ impl Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Table {
|
||||||
|
/// Returns the number of headers in the table
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.slots.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the table size
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Index<'a> {
|
impl<'a> Index<'a> {
|
||||||
fn new(v: Option<(usize, bool)>, e: Header) -> Index<'a> {
|
fn new(v: Option<(usize, bool)>, e: Header) -> Index<'a> {
|
||||||
match v {
|
match v {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ extern crate walkdir;
|
|||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use super::{Entry, Decoder};
|
use super::{Header, Decoder};
|
||||||
|
|
||||||
use self::hex::FromHex;
|
use self::hex::FromHex;
|
||||||
use self::serde_json::Value;
|
use self::serde_json::Value;
|
||||||
@@ -111,24 +111,24 @@ struct Case {
|
|||||||
header_table_size: Option<usize>,
|
header_table_size: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_str(e: &Entry) -> &str {
|
fn key_str(e: &Header) -> &str {
|
||||||
match *e {
|
match *e {
|
||||||
Entry::Header { ref name, .. } => name.as_str(),
|
Header::Field { ref name, .. } => name.as_str(),
|
||||||
Entry::Authority(..) => ":authority",
|
Header::Authority(..) => ":authority",
|
||||||
Entry::Method(..) => ":method",
|
Header::Method(..) => ":method",
|
||||||
Entry::Scheme(..) => ":scheme",
|
Header::Scheme(..) => ":scheme",
|
||||||
Entry::Path(..) => ":path",
|
Header::Path(..) => ":path",
|
||||||
Entry::Status(..) => ":status",
|
Header::Status(..) => ":status",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_str(e: &Entry) -> &str {
|
fn value_str(e: &Header) -> &str {
|
||||||
match *e {
|
match *e {
|
||||||
Entry::Header { ref value, .. } => value.to_str().unwrap(),
|
Header::Field { ref value, .. } => value.to_str().unwrap(),
|
||||||
Entry::Authority(ref v) => &**v,
|
Header::Authority(ref v) => &**v,
|
||||||
Entry::Method(ref m) => m.as_str(),
|
Header::Method(ref m) => m.as_str(),
|
||||||
Entry::Scheme(ref v) => &**v,
|
Header::Scheme(ref v) => &**v,
|
||||||
Entry::Path(ref v) => &**v,
|
Header::Path(ref v) => &**v,
|
||||||
Entry::Status(ref v) => v.as_str(),
|
Header::Status(ref v) => v.as_str(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user