Get encoder & decoder working
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user