Get encoder & decoder working
This commit is contained in:
@@ -9,6 +9,7 @@ pub struct Encoder {
|
|||||||
size_update: Option<SizeUpdate>,
|
size_update: Option<SizeUpdate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum EncoderError {
|
pub enum EncoderError {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +167,9 @@ fn encode_str(val: &[u8], dst: &mut BytesMut) {
|
|||||||
|
|
||||||
// Shift the header forward
|
// Shift the header forward
|
||||||
for i in 0..huff_len {
|
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
|
// Copy in the head
|
||||||
@@ -325,7 +328,7 @@ mod test {
|
|||||||
for i in 1..65 {
|
for i in 1..65 {
|
||||||
let key = format!("x-hello-world-{:02}", i);
|
let key = format!("x-hello-world-{:02}", i);
|
||||||
let res = encode(&mut encoder, vec![header(&key, &key)]);
|
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
|
// Encode the first one again
|
||||||
let res = encode(&mut encoder, vec![header(name, "one")]);
|
let res = encode(&mut encoder, vec![header(name, "one")]);
|
||||||
assert_eq!(&[0x80 | 62], &res[..]);
|
assert_eq!(&[0x80 | 63], &res[..]);
|
||||||
|
|
||||||
// Now the second one
|
// Now the second one
|
||||||
let res = encode(&mut encoder, vec![header(name, "two")]);
|
let res = encode(&mut encoder, vec![header(name, "two")]);
|
||||||
assert_eq!(&[0x80 | 63], &res[..]);
|
assert_eq!(&[0x80 | 62], &res[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -440,12 +443,12 @@ mod test {
|
|||||||
// This will evict the first header, while still referencing the header
|
// This will evict the first header, while still referencing the header
|
||||||
// name
|
// name
|
||||||
let res = encode(&mut encoder, vec![header("foo", "baz")]);
|
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());
|
assert_eq!(2, encoder.table.len());
|
||||||
|
|
||||||
// Try adding the same header again
|
// Try adding the same header again
|
||||||
let res = encode(&mut encoder, vec![header("foo", "baz")]);
|
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());
|
assert_eq!(2, encoder.table.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,6 +550,10 @@ mod test {
|
|||||||
// Test hitting end at multiple points.
|
// 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 {
|
fn encode(e: &mut Encoder, hdrs: Vec<Header>) -> BytesMut {
|
||||||
let mut dst = BytesMut::with_capacity(1024);
|
let mut dst = BytesMut::with_capacity(1024);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use http::header::{HeaderName, HeaderValue};
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
/// HTTP/2.0 Header
|
/// HTTP/2.0 Header
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum Header {
|
pub enum Header {
|
||||||
Field {
|
Field {
|
||||||
name: HeaderName,
|
name: HeaderName,
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ pub struct Table {
|
|||||||
mask: usize,
|
mask: usize,
|
||||||
indices: Vec<Option<Pos>>,
|
indices: Vec<Option<Pos>>,
|
||||||
slots: VecDeque<Slot>,
|
slots: VecDeque<Slot>,
|
||||||
// This tracks the number of evicted elements. It is expected to wrap. This
|
inserted: usize,
|
||||||
// value is used to map `Pos::index` to the actual index in the VecDeque.
|
|
||||||
evicted: usize,
|
|
||||||
// Size is in bytes
|
// Size is in bytes
|
||||||
size: usize,
|
size: usize,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
@@ -77,7 +75,7 @@ impl Table {
|
|||||||
mask: 0,
|
mask: 0,
|
||||||
indices: vec![],
|
indices: vec![],
|
||||||
slots: VecDeque::new(),
|
slots: VecDeque::new(),
|
||||||
evicted: 0,
|
inserted: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
max_size: max_size,
|
max_size: max_size,
|
||||||
}
|
}
|
||||||
@@ -90,7 +88,7 @@ impl Table {
|
|||||||
mask: capacity.wrapping_sub(1),
|
mask: capacity.wrapping_sub(1),
|
||||||
indices: vec![None; capacity],
|
indices: vec![None; capacity],
|
||||||
slots: VecDeque::with_capacity(usable_capacity(capacity)),
|
slots: VecDeque::with_capacity(usable_capacity(capacity)),
|
||||||
evicted: 0,
|
inserted: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
max_size: max_size,
|
max_size: max_size,
|
||||||
}
|
}
|
||||||
@@ -113,6 +111,9 @@ impl Table {
|
|||||||
|
|
||||||
// Don't index certain headers. This logic is borrowed from nghttp2.
|
// Don't index certain headers. This logic is borrowed from nghttp2.
|
||||||
if header.skip_value_index() {
|
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);
|
return Index::new(statik, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ 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);
|
let slot_idx = pos.index.wrapping_add(self.inserted);
|
||||||
|
|
||||||
if their_dist < dist {
|
if their_dist < dist {
|
||||||
// Index robinhood
|
// Index robinhood
|
||||||
@@ -184,7 +185,7 @@ impl Table {
|
|||||||
// capacity.
|
// capacity.
|
||||||
loop {
|
loop {
|
||||||
// Compute the real index into the VecDeque
|
// 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) {
|
if self.slots[real_idx].header.value_eq(&header) {
|
||||||
// We have a full match!
|
// We have a full match!
|
||||||
@@ -200,34 +201,25 @@ impl Table {
|
|||||||
return Index::Name(real_idx + DYN_OFFSET, header);
|
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
|
// Recompute real_idx as it just changed.
|
||||||
// the linked list got evicted. The header we are about to insert is
|
let new_real_idx = index.wrapping_add(self.inserted);
|
||||||
// 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);
|
|
||||||
|
|
||||||
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
|
// Even if the previous header was evicted, we can still reference
|
||||||
// it when inserting the new one...
|
// 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)
|
Index::NotIndexed(header)
|
||||||
@@ -247,7 +239,7 @@ impl Table {
|
|||||||
|
|
||||||
// Passing in `usize::MAX` for prev_idx since there is no previous
|
// Passing in `usize::MAX` for prev_idx since there is no previous
|
||||||
// header in this case.
|
// header in this case.
|
||||||
if self.update_size(header.len(), usize::MAX) {
|
if self.update_size(header.len(), None) {
|
||||||
if dist != 0 {
|
if dist != 0 {
|
||||||
let back = probe.wrapping_sub(1) & self.mask;
|
let back = probe.wrapping_sub(1) & self.mask;
|
||||||
|
|
||||||
@@ -263,15 +255,9 @@ impl Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The index is offset by the current # of evicted elements
|
self.insert(header, hash);
|
||||||
let slot_idx = self.slots.len();
|
|
||||||
let pos_idx = slot_idx.wrapping_add(self.evicted);
|
|
||||||
|
|
||||||
self.slots.push_back(Slot {
|
let pos_idx = 0usize.wrapping_sub(self.inserted);
|
||||||
hash: hash,
|
|
||||||
header: header,
|
|
||||||
next: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut prev = mem::replace(&mut self.indices[probe], Some(Pos {
|
let mut prev = mem::replace(&mut self.indices[probe], Some(Pos {
|
||||||
index: pos_idx,
|
index: pos_idx,
|
||||||
@@ -293,12 +279,22 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some((n, _)) = statik {
|
if let Some((n, _)) = statik {
|
||||||
Index::InsertedValue(n, &self.slots[slot_idx].header)
|
Index::InsertedValue(n, &self.slots[0].header)
|
||||||
} else {
|
} 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) {
|
pub fn resize(&mut self, size: usize) {
|
||||||
self.max_size = size;
|
self.max_size = size;
|
||||||
|
|
||||||
@@ -310,18 +306,18 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.slots.clear();
|
self.slots.clear();
|
||||||
self.evicted = 0;
|
self.inserted = 0;
|
||||||
} else {
|
} 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.size += len;
|
||||||
self.converge(prev_idx)
|
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;
|
let mut ret = false;
|
||||||
|
|
||||||
while self.size > self.max_size {
|
while self.size > self.max_size {
|
||||||
@@ -332,19 +328,16 @@ impl Table {
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evict(&mut self, prev_idx: usize) {
|
fn evict(&mut self, prev_idx: Option<usize>) {
|
||||||
debug_assert!(!self.slots.is_empty());
|
let pos_idx = (self.slots.len() - 1).wrapping_sub(self.inserted);
|
||||||
|
|
||||||
// Remove the header
|
// 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);
|
let mut probe = desired_pos(self.mask, slot.hash);
|
||||||
|
|
||||||
// Update the size
|
// Update the size
|
||||||
self.size -= slot.header.len();
|
self.size -= slot.header.len();
|
||||||
|
|
||||||
// Equivalent to 0.wrapping_add(self.evicted);
|
|
||||||
let pos_idx = self.evicted;
|
|
||||||
|
|
||||||
// Find the associated position
|
// Find the associated position
|
||||||
probe_loop!(probe < self.indices.len(), {
|
probe_loop!(probe < self.indices.len(), {
|
||||||
let mut pos = self.indices[probe].unwrap();
|
let mut pos = self.indices[probe].unwrap();
|
||||||
@@ -353,8 +346,8 @@ impl Table {
|
|||||||
if let Some(idx) = slot.next {
|
if let Some(idx) = slot.next {
|
||||||
pos.index = idx;
|
pos.index = idx;
|
||||||
self.indices[probe] = Some(pos);
|
self.indices[probe] = Some(pos);
|
||||||
} else if pos.index == prev_idx {
|
} else if Some(pos.index) == prev_idx {
|
||||||
pos.index = (self.slots.len() + 1).wrapping_add(self.evicted);
|
pos.index = 0usize.wrapping_sub(self.inserted + 1);
|
||||||
self.indices[probe] = Some(pos);
|
self.indices[probe] = Some(pos);
|
||||||
} else {
|
} else {
|
||||||
self.indices[probe] = None;
|
self.indices[probe] = None;
|
||||||
@@ -364,8 +357,6 @@ impl Table {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.evicted = self.evicted.wrapping_add(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shifts all indices that were displaced by the header that has just been
|
// 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 hex;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use super::{Header, Decoder};
|
use super::{Header, Decoder, Encoder};
|
||||||
|
|
||||||
|
use self::bytes::BytesMut;
|
||||||
use self::hex::FromHex;
|
use self::hex::FromHex;
|
||||||
use self::serde_json::Value;
|
use self::serde_json::Value;
|
||||||
use self::walkdir::WalkDir;
|
use self::walkdir::WalkDir;
|
||||||
@@ -37,6 +39,14 @@ fn hpack_fixtures() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("");
|
||||||
|
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
||||||
|
println!("~~~ {:?} ~~~", entry.path());
|
||||||
|
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
||||||
|
println!("");
|
||||||
|
println!("");
|
||||||
|
|
||||||
test_fixture(entry.path());
|
test_fixture(entry.path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,6 +96,7 @@ fn test_story(story: Value) {
|
|||||||
|
|
||||||
let mut decoder = Decoder::default();
|
let mut decoder = Decoder::default();
|
||||||
|
|
||||||
|
// First, check decoding against the fixtures
|
||||||
for case in &cases {
|
for case in &cases {
|
||||||
let mut expect = case.expect.clone();
|
let mut expect = case.expect.clone();
|
||||||
|
|
||||||
@@ -101,6 +112,31 @@ fn test_story(story: Value) {
|
|||||||
|
|
||||||
assert_eq!(0, expect.len());
|
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