Get encoder & decoder working

This commit is contained in:
Carl Lerche
2017-06-07 21:54:04 -07:00
parent 7ffc574da8
commit 42f19f3006
4 changed files with 95 additions and 61 deletions

View File

@@ -9,6 +9,7 @@ pub struct Encoder {
size_update: Option<SizeUpdate>,
}
#[derive(Debug)]
pub enum EncoderError {
}
@@ -166,7 +167,9 @@ fn encode_str(val: &[u8], dst: &mut BytesMut) {
// Shift the header forward
for i in 0..huff_len {
dst[idx + head_len + (huff_len - i)] = dst[idx + 1 + (huff_len - 1)];
let src_i = idx + 1 + (huff_len - (i+1));
let dst_i = idx + head_len + (huff_len - (i+1));
dst[dst_i] = dst[src_i];
}
// Copy in the head
@@ -325,7 +328,7 @@ mod test {
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]);
assert_eq!(0x80 | (61 + (65-i)), res[0]);
}
}
@@ -419,11 +422,11 @@ mod test {
// Encode the first one again
let res = encode(&mut encoder, vec![header(name, "one")]);
assert_eq!(&[0x80 | 62], &res[..]);
assert_eq!(&[0x80 | 63], &res[..]);
// Now the second one
let res = encode(&mut encoder, vec![header(name, "two")]);
assert_eq!(&[0x80 | 63], &res[..]);
assert_eq!(&[0x80 | 62], &res[..]);
}
#[test]
@@ -440,12 +443,12 @@ mod test {
// This will evict the first header, while still referencing the header
// name
let res = encode(&mut encoder, vec![header("foo", "baz")]);
assert_eq!(&[0x40 | 62, 0x80 | 3], &res[..2]);
assert_eq!(&[0x40 | 63, 0, 0x80 | 3], &res[..3]);
assert_eq!(2, encoder.table.len());
// Try adding the same header again
let res = encode(&mut encoder, vec![header("foo", "baz")]);
assert_eq!(&[0x80 | 63], &res[..]);
assert_eq!(&[0x80 | 62], &res[..]);
assert_eq!(2, encoder.table.len());
}
@@ -547,6 +550,10 @@ mod test {
// Test hitting end at multiple points.
}
#[test]
fn test_evicted_overflow() {
// Not sure what the best way to do this is.
}
fn encode(e: &mut Encoder, hdrs: Vec<Header>) -> BytesMut {
let mut dst = BytesMut::with_capacity(1024);

View File

@@ -6,7 +6,7 @@ use http::header::{HeaderName, HeaderValue};
use bytes::Bytes;
/// HTTP/2.0 Header
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Header {
Field {
name: HeaderName,

View File

@@ -12,9 +12,7 @@ pub struct Table {
mask: usize,
indices: Vec<Option<Pos>>,
slots: VecDeque<Slot>,
// This tracks the number of evicted elements. It is expected to wrap. This
// value is used to map `Pos::index` to the actual index in the VecDeque.
evicted: usize,
inserted: usize,
// Size is in bytes
size: usize,
max_size: usize,
@@ -77,7 +75,7 @@ impl Table {
mask: 0,
indices: vec![],
slots: VecDeque::new(),
evicted: 0,
inserted: 0,
size: 0,
max_size: max_size,
}
@@ -90,7 +88,7 @@ impl Table {
mask: capacity.wrapping_sub(1),
indices: vec![None; capacity],
slots: VecDeque::with_capacity(usable_capacity(capacity)),
evicted: 0,
inserted: 0,
size: 0,
max_size: max_size,
}
@@ -113,6 +111,9 @@ impl Table {
// Don't index certain headers. This logic is borrowed from nghttp2.
if header.skip_value_index() {
// Right now, if this is true, the header name is always in the
// static table. At some point in the future, this might not be true
// and this logic will need to be updated.
return Index::new(statik, header);
}
@@ -155,7 +156,7 @@ impl Table {
// displacement.
let their_dist = probe_distance(self.mask, pos.hash, probe);
let slot_idx = pos.index.wrapping_sub(self.evicted);
let slot_idx = pos.index.wrapping_add(self.inserted);
if their_dist < dist {
// Index robinhood
@@ -184,7 +185,7 @@ impl Table {
// capacity.
loop {
// Compute the real index into the VecDeque
let real_idx = index.wrapping_sub(self.evicted);
let real_idx = index.wrapping_add(self.inserted);
if self.slots[real_idx].header.value_eq(&header) {
// We have a full match!
@@ -200,34 +201,25 @@ impl Table {
return Index::Name(real_idx + DYN_OFFSET, header);
}
self.update_size(header.len(), index);
self.update_size(header.len(), Some(index));
let new_idx = self.slots.len();
// Insert the new header
self.insert(header, hash);
// If `evicted` is greater than `index`, then the previous node in
// the linked list got evicted. The header we are about to insert is
// the new "head" of the list and `indices` has already been
// updated. So, all that is left to do is insert the header in the
// VecDeque.
//
// TODO: This logic isn't correct in the face of wrapping
if self.evicted <= index {
// Recompute `real_idx` since this could have been modified by
// entries being evicted
let real_idx = index.wrapping_sub(self.evicted);
// Recompute real_idx as it just changed.
let new_real_idx = index.wrapping_add(self.inserted);
self.slots[real_idx].next = Some(new_idx.wrapping_add(self.evicted));
// The previous node in the linked list may have gotten evicted
// while making room for this header.
if new_real_idx < self.slots.len() {
let idx = 0usize.wrapping_sub(self.inserted);
self.slots[new_real_idx].next = Some(idx);
}
self.slots.push_back(Slot {
hash: hash,
header: header,
next: None,
});
// Even if the previous header was evicted, we can still reference
// it when inserting the new one...
return Index::InsertedValue(real_idx + DYN_OFFSET, &self.slots[new_idx].header);
return Index::InsertedValue(real_idx + DYN_OFFSET, &self.slots[0].header);
}
Index::NotIndexed(header)
@@ -247,7 +239,7 @@ impl Table {
// Passing in `usize::MAX` for prev_idx since there is no previous
// header in this case.
if self.update_size(header.len(), usize::MAX) {
if self.update_size(header.len(), None) {
if dist != 0 {
let back = probe.wrapping_sub(1) & self.mask;
@@ -263,15 +255,9 @@ impl Table {
}
}
// The index is offset by the current # of evicted elements
let slot_idx = self.slots.len();
let pos_idx = slot_idx.wrapping_add(self.evicted);
self.insert(header, hash);
self.slots.push_back(Slot {
hash: hash,
header: header,
next: None,
});
let pos_idx = 0usize.wrapping_sub(self.inserted);
let mut prev = mem::replace(&mut self.indices[probe], Some(Pos {
index: pos_idx,
@@ -293,12 +279,22 @@ impl Table {
}
if let Some((n, _)) = statik {
Index::InsertedValue(n, &self.slots[slot_idx].header)
Index::InsertedValue(n, &self.slots[0].header)
} else {
Index::Inserted(&self.slots[slot_idx].header)
Index::Inserted(&self.slots[0].header)
}
}
fn insert(&mut self, header: Header, hash: HashValue) {
self.inserted = self.inserted.wrapping_add(1);
self.slots.push_front(Slot {
hash: hash,
header: header,
next: None,
});
}
pub fn resize(&mut self, size: usize) {
self.max_size = size;
@@ -310,18 +306,18 @@ impl Table {
}
self.slots.clear();
self.evicted = 0;
self.inserted = 0;
} else {
self.converge(usize::MAX);
self.converge(None);
}
}
fn update_size(&mut self, len: usize, prev_idx: usize) -> bool {
fn update_size(&mut self, len: usize, prev_idx: Option<usize>) -> bool {
self.size += len;
self.converge(prev_idx)
}
fn converge(&mut self, prev_idx: usize) -> bool {
fn converge(&mut self, prev_idx: Option<usize>) -> bool {
let mut ret = false;
while self.size > self.max_size {
@@ -332,19 +328,16 @@ impl Table {
ret
}
fn evict(&mut self, prev_idx: usize) {
debug_assert!(!self.slots.is_empty());
fn evict(&mut self, prev_idx: Option<usize>) {
let pos_idx = (self.slots.len() - 1).wrapping_sub(self.inserted);
// Remove the header
let slot = self.slots.pop_front().unwrap();
let slot = self.slots.pop_back().unwrap();
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;
// Find the associated position
probe_loop!(probe < self.indices.len(), {
let mut pos = self.indices[probe].unwrap();
@@ -353,8 +346,8 @@ impl Table {
if let Some(idx) = slot.next {
pos.index = idx;
self.indices[probe] = Some(pos);
} else if pos.index == prev_idx {
pos.index = (self.slots.len() + 1).wrapping_add(self.evicted);
} else if Some(pos.index) == prev_idx {
pos.index = 0usize.wrapping_sub(self.inserted + 1);
self.indices[probe] = Some(pos);
} else {
self.indices[probe] = None;
@@ -364,8 +357,6 @@ impl Table {
break;
}
});
self.evicted = self.evicted.wrapping_add(1);
}
// Shifts all indices that were displaced by the header that has just been

View File

@@ -1,10 +1,12 @@
extern crate bytes;
extern crate hex;
extern crate walkdir;
extern crate serde;
extern crate serde_json;
use super::{Header, Decoder};
use super::{Header, Decoder, Encoder};
use self::bytes::BytesMut;
use self::hex::FromHex;
use self::serde_json::Value;
use self::walkdir::WalkDir;
@@ -37,6 +39,14 @@ fn hpack_fixtures() {
}
}
println!("");
println!("");
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
println!("~~~ {:?} ~~~", entry.path());
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
println!("");
println!("");
test_fixture(entry.path());
}
}
@@ -86,6 +96,7 @@ fn test_story(story: Value) {
let mut decoder = Decoder::default();
// First, check decoding against the fixtures
for case in &cases {
let mut expect = case.expect.clone();
@@ -101,6 +112,31 @@ fn test_story(story: Value) {
assert_eq!(0, expect.len());
}
let mut encoder = Encoder::default();
let mut decoder = Decoder::default();
// Now, encode the headers
for case in &cases {
let mut buf = BytesMut::with_capacity(64 * 1024);
if let Some(size) = case.header_table_size {
encoder.update_max_size(size);
decoder.queue_size_update(size);
}
let mut input: Vec<_> = case.expect.iter().map(|&(ref name, ref value)| {
Header::new(name.clone().into(), value.clone().into()).unwrap()
}).collect();
encoder.encode(input.clone(), &mut buf).unwrap();
decoder.decode(&buf.into(), |e| {
assert_eq!(e, input.remove(0));
}).unwrap();
assert_eq!(0, input.len());
}
}
}