More work
This commit is contained in:
@@ -8,6 +8,7 @@ futures = "0.1"
|
|||||||
tokio-io = { git = "https://github.com/alexcrichton/tokio-io" }
|
tokio-io = { git = "https://github.com/alexcrichton/tokio-io" }
|
||||||
tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
|
tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
|
||||||
bytes = { git = "https://github.com/carllerche/bytes" }
|
bytes = { git = "https://github.com/carllerche/bytes" }
|
||||||
|
tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" }
|
||||||
|
|
||||||
[replace]
|
[replace]
|
||||||
"futures:0.1.10" = { git = "https://github.com/alexcrichton/futures-rs", branch = "close" }
|
"futures:0.1.10" = { git = "https://github.com/alexcrichton/futures-rs", branch = "close" }
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ impl Head {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_len(&self) -> usize {
|
pub fn encode_len(&self) -> usize {
|
||||||
super::FRAME_HEADER_LEN
|
super::HEADER_LEN
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode<T: BufMut>(&self, payload_len: usize, dst: &mut T) -> Result<(), Error> {
|
pub fn encode<T: BufMut>(&self, payload_len: usize, dst: &mut T) -> Result<(), Error> {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
/// Header frame
|
||||||
|
///
|
||||||
|
/// This could be either a request or a response.
|
||||||
|
pub struct Headers {
|
||||||
|
stream_id: StreamId,
|
||||||
|
headers: HeaderMap,
|
||||||
|
pseudo: Pseudo,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pseudo {
|
||||||
|
// Request
|
||||||
|
method: Option<()>,
|
||||||
|
scheme: Option<()>,
|
||||||
|
authority: Option<()>,
|
||||||
|
path: Option<()>,
|
||||||
|
|
||||||
|
// Response
|
||||||
|
status: Option<()>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub use self::head::{Head, Kind, StreamId};
|
|||||||
pub use self::settings::{Settings, SettingSet};
|
pub use self::settings::{Settings, SettingSet};
|
||||||
pub use self::unknown::Unknown;
|
pub use self::unknown::Unknown;
|
||||||
|
|
||||||
const FRAME_HEADER_LEN: usize = 9;
|
pub const HEADER_LEN: usize = 9;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Frame {
|
pub enum Frame {
|
||||||
@@ -103,8 +103,7 @@ impl Frame {
|
|||||||
let head = Head::parse(&frame);
|
let head = Head::parse(&frame);
|
||||||
|
|
||||||
// Extract the payload from the frame
|
// Extract the payload from the frame
|
||||||
let _ = frame.drain_to(FRAME_HEADER_LEN);
|
let _ = frame.drain_to(HEADER_LEN);
|
||||||
|
|
||||||
|
|
||||||
match head.kind() {
|
match head.kind() {
|
||||||
Kind::Unknown => {
|
Kind::Unknown => {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(head: Head, payload: Bytes) -> Result<Settings, Error> {
|
pub fn load(head: Head, payload: &[u8]) -> Result<Settings, Error> {
|
||||||
use self::Setting::*;
|
use self::Setting::*;
|
||||||
|
|
||||||
debug_assert_eq!(head.kind(), ::frame::Kind::Settings);
|
debug_assert_eq!(head.kind(), ::frame::Kind::Settings);
|
||||||
@@ -112,7 +112,7 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_len(&self) -> usize {
|
pub fn encode_len(&self) -> usize {
|
||||||
super::FRAME_HEADER_LEN + self.payload_len()
|
super::HEADER_LEN + self.payload_len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn payload_len(&self) -> usize {
|
fn payload_len(&self) -> usize {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use frame::{Head, Error};
|
use frame::{Frame, Head, Error};
|
||||||
use bytes::{Bytes, BytesMut, BufMut};
|
use bytes::{Bytes, BytesMut, BufMut};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -25,3 +25,9 @@ impl Unknown {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Unknown> for Frame {
|
||||||
|
fn from(src: Unknown) -> Frame {
|
||||||
|
Frame::Unknown(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1594
src/hpack/decoder.rs
Normal file
1594
src/hpack/decoder.rs
Normal file
File diff suppressed because it is too large
Load Diff
478
src/hpack/encoder.rs
Normal file
478
src/hpack/encoder.rs
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
//! Implements all functionality related to encoding header blocks using
|
||||||
|
//! HPACK.
|
||||||
|
//!
|
||||||
|
//! Clients should use the `Encoder` struct as the API for performing HPACK
|
||||||
|
//! encoding.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! Encodes a header using a literal encoding.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use hpack::Encoder;
|
||||||
|
//!
|
||||||
|
//! let mut encoder = Encoder::new();
|
||||||
|
//!
|
||||||
|
//! let headers = vec![
|
||||||
|
//! (&b"custom-key"[..], &b"custom-value"[..]),
|
||||||
|
//! ];
|
||||||
|
//! // First encoding...
|
||||||
|
//! let result = encoder.encode(headers);
|
||||||
|
//! // The result is a literal encoding of the header name and value, with an
|
||||||
|
//! // initial byte representing the type of the encoding
|
||||||
|
//! // (incremental indexing).
|
||||||
|
//! assert_eq!(
|
||||||
|
//! vec![0x40,
|
||||||
|
//! 10, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y',
|
||||||
|
//! 12, b'c', b'u', b's', b't', b'o', b'm', b'-', b'v', b'a', b'l',
|
||||||
|
//! b'u', b'e'],
|
||||||
|
//! result);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Encodes some pseudo-headers that are already found in the static table.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use hpack::Encoder;
|
||||||
|
//!
|
||||||
|
//! let mut encoder = Encoder::new();
|
||||||
|
//! let headers = vec![
|
||||||
|
//! (&b":method"[..], &b"GET"[..]),
|
||||||
|
//! (&b":path"[..], &b"/"[..]),
|
||||||
|
//! ];
|
||||||
|
//!
|
||||||
|
//! // The headers are encoded by providing their index (with a bit flag
|
||||||
|
//! // indicating that the indexed representation is used).
|
||||||
|
//! assert_eq!(encoder.encode(headers), vec![2 | 0x80, 4 | 0x80]);
|
||||||
|
//! ```
|
||||||
|
use std::io;
|
||||||
|
use std::num::Wrapping;
|
||||||
|
|
||||||
|
use super::STATIC_TABLE;
|
||||||
|
use super::HeaderTable;
|
||||||
|
|
||||||
|
/// Encode an integer to the representation defined by HPACK, writing it into the provider
|
||||||
|
/// `io::Write` instance. Also allows the caller to specify the leading bits of the first
|
||||||
|
/// octet. Any bits that are already set within the last `prefix_size` bits will be cleared
|
||||||
|
/// and overwritten by the integer's representation (in other words, only the first
|
||||||
|
/// `8 - prefix_size` bits from the `leading_bits` octet are reflected in the first octet
|
||||||
|
/// emitted by the function.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use hpack::encoder::encode_integer_into;
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// // No bits specified in the 3 most significant bits of the first octet
|
||||||
|
/// let mut vec = Vec::new();
|
||||||
|
/// encode_integer_into(10, 5, 0, &mut vec);
|
||||||
|
/// assert_eq!(vec, vec![10]);
|
||||||
|
/// }
|
||||||
|
/// {
|
||||||
|
/// // The most significant bit should be set; i.e. the 3 most significant
|
||||||
|
/// // bits are 100.
|
||||||
|
/// let mut vec = Vec::new();
|
||||||
|
/// encode_integer_into(10, 5, 0x80, &mut vec);
|
||||||
|
/// assert_eq!(vec, vec![0x8A]);
|
||||||
|
/// }
|
||||||
|
/// {
|
||||||
|
/// // The most leading bits number has a bit set within the last prefix-size
|
||||||
|
/// // bits -- they are ignored by the function
|
||||||
|
/// // bits are 100.
|
||||||
|
/// let mut vec = Vec::new();
|
||||||
|
/// encode_integer_into(10, 5, 0x10, &mut vec);
|
||||||
|
/// assert_eq!(vec, vec![0x0A]);
|
||||||
|
/// }
|
||||||
|
/// {
|
||||||
|
/// let mut vec = Vec::new();
|
||||||
|
/// encode_integer_into(1337, 5, 0, &mut vec);
|
||||||
|
/// assert_eq!(vec, vec![31, 154, 10]);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn encode_integer_into<W: io::Write>(
|
||||||
|
mut value: usize,
|
||||||
|
prefix_size: u8,
|
||||||
|
leading_bits: u8,
|
||||||
|
writer: &mut W)
|
||||||
|
-> io::Result<()> {
|
||||||
|
let Wrapping(mask) = if prefix_size >= 8 {
|
||||||
|
Wrapping(0xFF)
|
||||||
|
} else {
|
||||||
|
Wrapping(1u8 << prefix_size) - Wrapping(1)
|
||||||
|
};
|
||||||
|
// Clear any bits within the last `prefix_size` bits of the provided `leading_bits`.
|
||||||
|
// Failing to do so might lead to an incorrect encoding of the integer.
|
||||||
|
let leading_bits = leading_bits & (!mask);
|
||||||
|
let mask = mask as usize;
|
||||||
|
if value < mask {
|
||||||
|
try!(writer.write_all(&[leading_bits | value as u8]));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(writer.write_all(&[leading_bits | mask as u8]));
|
||||||
|
value -= mask;
|
||||||
|
while value >= 128 {
|
||||||
|
try!(writer.write_all(&[((value % 128) + 128) as u8]));
|
||||||
|
value = value / 128;
|
||||||
|
}
|
||||||
|
try!(writer.write_all(&[value as u8]));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode an integer to the representation defined by HPACK.
|
||||||
|
///
|
||||||
|
/// Returns a newly allocated `Vec` containing the encoded bytes.
|
||||||
|
/// Only `prefix_size` lowest-order bits of the first byte in the
|
||||||
|
/// array are guaranteed to be used.
|
||||||
|
pub fn encode_integer(value: usize, prefix_size: u8) -> Vec<u8> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
encode_integer_into(value, prefix_size, 0, &mut res).unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an HPACK encoder. Allows clients to encode arbitrary header sets
|
||||||
|
/// and tracks the encoding context. That is, encoding subsequent header sets
|
||||||
|
/// will use the context built by previous encode calls.
|
||||||
|
///
|
||||||
|
/// This is the main API for performing HPACK encoding of headers.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Encoding a header two times in a row produces two different
|
||||||
|
/// representations, due to the utilization of HPACK compression.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use hpack::Encoder;
|
||||||
|
///
|
||||||
|
/// let mut encoder = Encoder::new();
|
||||||
|
///
|
||||||
|
/// let headers = vec![
|
||||||
|
/// (b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||||
|
/// ];
|
||||||
|
/// // First encoding...
|
||||||
|
/// let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
/// // The result is a literal encoding of the header name and value, with an
|
||||||
|
/// // initial byte representing the type of the encoding
|
||||||
|
/// // (incremental indexing).
|
||||||
|
/// assert_eq!(
|
||||||
|
/// vec![0x40,
|
||||||
|
/// 10, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y',
|
||||||
|
/// 12, b'c', b'u', b's', b't', b'o', b'm', b'-', b'v', b'a', b'l',
|
||||||
|
/// b'u', b'e'],
|
||||||
|
/// result);
|
||||||
|
///
|
||||||
|
/// // Encode the same headers again!
|
||||||
|
/// let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
/// // The result is simply the index of the header in the header table (62),
|
||||||
|
/// // with a flag representing that the decoder should use the index.
|
||||||
|
/// assert_eq!(vec![0x80 | 62], result);
|
||||||
|
/// ```
|
||||||
|
pub struct Encoder<'a> {
|
||||||
|
/// The header table represents the encoder's context
|
||||||
|
header_table: HeaderTable<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Encoder<'a> {
|
||||||
|
/// Creates a new `Encoder` with a default static table, as defined by the
|
||||||
|
/// HPACK spec (Appendix A).
|
||||||
|
pub fn new() -> Encoder<'a> {
|
||||||
|
Encoder {
|
||||||
|
header_table: HeaderTable::with_static_table(STATIC_TABLE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a new maximum dynamic table size for the decoder.
|
||||||
|
pub fn set_max_table_size(&mut self, new_max_size: usize) {
|
||||||
|
self.header_table.dynamic_table.set_max_table_size(new_max_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes the given headers using the HPACK rules and returns a newly
|
||||||
|
/// allocated `Vec` containing the bytes representing the encoded header
|
||||||
|
/// set.
|
||||||
|
///
|
||||||
|
/// The encoder so far supports only a single, extremely simple encoding
|
||||||
|
/// strategy, whereby each header is represented as an indexed header if
|
||||||
|
/// already found in the header table and a literal otherwise. When a
|
||||||
|
/// header isn't found in the table, it is added if the header name wasn't
|
||||||
|
/// found either (i.e. there are never two header names with different
|
||||||
|
/// values in the produced header table). Strings are always encoded as
|
||||||
|
/// literals (Huffman encoding is not used).
|
||||||
|
pub fn encode<'b, I>(&mut self, headers: I) -> Vec<u8>
|
||||||
|
where I: IntoIterator<Item=(&'b [u8], &'b [u8])> {
|
||||||
|
let mut encoded: Vec<u8> = Vec::new();
|
||||||
|
self.encode_into(headers, &mut encoded).unwrap();
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes the given headers into the given `io::Write` instance. If the io::Write raises an
|
||||||
|
/// Error at any point, this error is propagated out. Any changes to the internal state of the
|
||||||
|
/// encoder will not be rolled back, though, so care should be taken to ensure that the paired
|
||||||
|
/// decoder also ends up seeing the same state updates or that their pairing is cancelled.
|
||||||
|
pub fn encode_into<'b, I, W>(&mut self, headers: I, writer: &mut W) -> io::Result<()>
|
||||||
|
where I: IntoIterator<Item=(&'b [u8], &'b [u8])>,
|
||||||
|
W: io::Write {
|
||||||
|
for header in headers {
|
||||||
|
try!(self.encode_header_into(header, writer));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes a single given header into the given `io::Write` instance.
|
||||||
|
///
|
||||||
|
/// Any errors are propagated, similarly to the `encode_into` method, and it is the callers
|
||||||
|
/// responsiblity to make sure that the paired encoder sees them too.
|
||||||
|
pub fn encode_header_into<W: io::Write>(
|
||||||
|
&mut self,
|
||||||
|
header: (&[u8], &[u8]),
|
||||||
|
writer: &mut W)
|
||||||
|
-> io::Result<()> {
|
||||||
|
match self.header_table.find_header(header) {
|
||||||
|
None => {
|
||||||
|
// The name of the header is in no tables: need to encode
|
||||||
|
// it with both a literal name and value.
|
||||||
|
try!(self.encode_literal(&header, true, writer));
|
||||||
|
self.header_table.add_header(header.0.to_vec(), header.1.to_vec());
|
||||||
|
},
|
||||||
|
Some((index, false)) => {
|
||||||
|
// The name of the header is at the given index, but the
|
||||||
|
// value does not match the current one: need to encode
|
||||||
|
// only the value as a literal.
|
||||||
|
try!(self.encode_indexed_name((index, header.1), false, writer));
|
||||||
|
},
|
||||||
|
Some((index, true)) => {
|
||||||
|
// The full header was found in one of the tables, so we
|
||||||
|
// just encode the index.
|
||||||
|
try!(self.encode_indexed(index, writer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes a header as a literal (i.e. both the name and the value are
|
||||||
|
/// encoded as a string literal) and places the result in the given buffer
|
||||||
|
/// `buf`.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// - `header` - the header to be encoded
|
||||||
|
/// - `should_index` - indicates whether the given header should be indexed, i.e.
|
||||||
|
/// inserted into the dynamic table
|
||||||
|
/// - `buf` - The buffer into which the result is placed
|
||||||
|
///
|
||||||
|
fn encode_literal<W: io::Write>(
|
||||||
|
&mut self,
|
||||||
|
header: &(&[u8], &[u8]),
|
||||||
|
should_index: bool,
|
||||||
|
buf: &mut W)
|
||||||
|
-> io::Result<()> {
|
||||||
|
let mask = if should_index {
|
||||||
|
0x40
|
||||||
|
} else {
|
||||||
|
0x0
|
||||||
|
};
|
||||||
|
|
||||||
|
try!(buf.write_all(&[mask]));
|
||||||
|
try!(self.encode_string_literal(&header.0, buf));
|
||||||
|
try!(self.encode_string_literal(&header.1, buf));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes a string literal and places the result in the given buffer
|
||||||
|
/// `buf`.
|
||||||
|
///
|
||||||
|
/// The function does not consider Huffman encoding for now, but always
|
||||||
|
/// produces a string literal representations, according to the HPACK spec
|
||||||
|
/// section 5.2.
|
||||||
|
fn encode_string_literal<W: io::Write>(
|
||||||
|
&mut self,
|
||||||
|
octet_str: &[u8],
|
||||||
|
buf: &mut W)
|
||||||
|
-> io::Result<()> {
|
||||||
|
try!(encode_integer_into(octet_str.len(), 7, 0, buf));
|
||||||
|
try!(buf.write_all(octet_str));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes a header whose name is indexed and places the result in the
|
||||||
|
/// given buffer `buf`.
|
||||||
|
fn encode_indexed_name<W: io::Write>(
|
||||||
|
&mut self,
|
||||||
|
header: (usize, &[u8]),
|
||||||
|
should_index: bool,
|
||||||
|
buf: &mut W)
|
||||||
|
-> io::Result<()> {
|
||||||
|
let (mask, prefix) = if should_index {
|
||||||
|
(0x40, 6)
|
||||||
|
} else {
|
||||||
|
(0x0, 4)
|
||||||
|
};
|
||||||
|
|
||||||
|
try!(encode_integer_into(header.0, prefix, mask, buf));
|
||||||
|
// So far, we rely on just one strategy for encoding string literals.
|
||||||
|
try!(self.encode_string_literal(&header.1, buf));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes an indexed header (a header that is fully in the header table)
|
||||||
|
/// and places the result in the given buffer `buf`.
|
||||||
|
///
|
||||||
|
/// The encoding is according to the rules of the HPACK spec, section 6.1.
|
||||||
|
fn encode_indexed<W: io::Write>(&self, index: usize, buf: &mut W) -> io::Result<()> {
|
||||||
|
// We need to set the most significant bit, since the bit-pattern is
|
||||||
|
// `1xxxxxxx` for indexed headers.
|
||||||
|
try!(encode_integer_into(index, 7, 0x80, buf));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::encode_integer;
|
||||||
|
use super::Encoder;
|
||||||
|
|
||||||
|
use super::super::Decoder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_integer() {
|
||||||
|
assert_eq!(encode_integer(10, 5), [10]);
|
||||||
|
assert_eq!(encode_integer(1337, 5), [31, 154, 10]);
|
||||||
|
assert_eq!(encode_integer(127, 7), [127, 0]);
|
||||||
|
assert_eq!(encode_integer(255, 8), [255, 0]);
|
||||||
|
assert_eq!(encode_integer(254, 8), [254]);
|
||||||
|
assert_eq!(encode_integer(1, 8), [1]);
|
||||||
|
assert_eq!(encode_integer(0, 8), [0]);
|
||||||
|
assert_eq!(encode_integer(255, 7), [127, 128, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper function that checks whether the given buffer can be decoded
|
||||||
|
/// into a set of headers that corresponds to the given `headers` list.
|
||||||
|
/// Relies on using the `hpack::decoder::Decoder`` struct for
|
||||||
|
/// performing the decoding.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A `bool` indicating whether such a decoding can be performed.
|
||||||
|
fn is_decodable(buf: &Vec<u8>, headers: &Vec<(Vec<u8>, Vec<u8>)>) -> bool {
|
||||||
|
let mut decoder = Decoder::new();
|
||||||
|
match decoder.decode(buf).ok() {
|
||||||
|
Some(h) => h == *headers,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that encoding only the `:method` header works.
|
||||||
|
#[test]
|
||||||
|
fn test_encode_only_method() {
|
||||||
|
let mut encoder: Encoder = Encoder::new();
|
||||||
|
let headers = vec![
|
||||||
|
(b":method".to_vec(), b"GET".to_vec()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
|
||||||
|
debug!("{:?}", result);
|
||||||
|
assert!(is_decodable(&result, &headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when a single custom header is sent it gets indexed by the
|
||||||
|
/// coder.
|
||||||
|
#[test]
|
||||||
|
fn test_custom_header_gets_indexed() {
|
||||||
|
let mut encoder: Encoder = Encoder::new();
|
||||||
|
let headers = vec![
|
||||||
|
(b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
assert!(is_decodable(&result, &headers));
|
||||||
|
// The header is in the encoder's dynamic table.
|
||||||
|
assert_eq!(encoder.header_table.dynamic_table.to_vec(), headers);
|
||||||
|
// ...but also indicated as such in the output.
|
||||||
|
assert!(0x40 == (0x40 & result[0]));
|
||||||
|
debug!("{:?}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when a header gets added to the dynamic table, the encoder
|
||||||
|
/// will use the index, instead of the literal representation on the next
|
||||||
|
/// encoding of the same header.
|
||||||
|
#[test]
|
||||||
|
fn test_uses_index_on_second_iteration() {
|
||||||
|
let mut encoder: Encoder = Encoder::new();
|
||||||
|
let headers = vec![
|
||||||
|
(b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||||
|
];
|
||||||
|
// First encoding...
|
||||||
|
let _ = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
|
||||||
|
// Encode the same headers again!
|
||||||
|
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
|
||||||
|
// The header is in the encoder's dynamic table.
|
||||||
|
assert_eq!(encoder.header_table.dynamic_table.to_vec(), headers);
|
||||||
|
// The output is a single index byte?
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
// The index is correctly encoded:
|
||||||
|
// - The most significant bit is set
|
||||||
|
assert_eq!(0x80 & result[0], 0x80);
|
||||||
|
// - The other 7 bits decode to an integer giving the index in the full
|
||||||
|
// header address space.
|
||||||
|
assert_eq!(result[0] ^ 0x80, 62);
|
||||||
|
// The header table actually contains the header at that index?
|
||||||
|
assert_eq!(
|
||||||
|
encoder.header_table.get_from_table(62).unwrap(),
|
||||||
|
(&headers[0].0[..], &headers[0].1[..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when a header name is indexed, but the value isn't, the
|
||||||
|
/// header is represented by an index (for the name) and a literal (for
|
||||||
|
/// the value).
|
||||||
|
#[test]
|
||||||
|
fn test_name_indexed_value_not() {
|
||||||
|
{
|
||||||
|
let mut encoder: Encoder = Encoder::new();
|
||||||
|
// `:method` is in the static table, but only for GET and POST
|
||||||
|
let headers = vec![
|
||||||
|
(b":method", b"PUT"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
|
||||||
|
// The first byte represents the index in the header table: last
|
||||||
|
// occurrence of `:method` is at index 3.
|
||||||
|
assert_eq!(result[0], 3);
|
||||||
|
// The rest of it correctly represents PUT?
|
||||||
|
assert_eq!(&result[1..], &[3, b'P', b'U', b'T']);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut encoder: Encoder = Encoder::new();
|
||||||
|
// `:method` is in the static table, but only for GET and POST
|
||||||
|
let headers = vec![
|
||||||
|
(b":authority".to_vec(), b"example.com".to_vec()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
|
||||||
|
assert_eq!(result[0], 1);
|
||||||
|
// The rest of it correctly represents PUT?
|
||||||
|
assert_eq!(
|
||||||
|
&result[1..],
|
||||||
|
&[11, b'e', b'x', b'a', b'm', b'p', b'l', b'e', b'.', b'c', b'o', b'm'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that multiple headers are correctly encoded (i.e. can be decoded
|
||||||
|
/// back to their original representation).
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_headers_encoded() {
|
||||||
|
let mut encoder = Encoder::new();
|
||||||
|
let headers = vec![
|
||||||
|
(b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||||
|
(b":method".to_vec(), b"GET".to_vec()),
|
||||||
|
(b":path".to_vec(), b"/some/path".to_vec()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||||
|
|
||||||
|
assert!(is_decodable(&result, &headers));
|
||||||
|
}
|
||||||
|
}
|
||||||
702
src/hpack/huffman.rs
Normal file
702
src/hpack/huffman.rs
Normal file
@@ -0,0 +1,702 @@
|
|||||||
|
//! A module exposing utilities for encoding and decoding Huffman-coded octet
|
||||||
|
//! strings, under the Huffman code defined by HPACK.
|
||||||
|
//! (HPACK-draft-10, Appendix B)
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Represents a symbol that can be inserted into a Huffman-encoded octet
|
||||||
|
/// string.
|
||||||
|
enum HuffmanCodeSymbol {
|
||||||
|
/// Any octet is a valid symbol
|
||||||
|
Symbol(u8),
|
||||||
|
/// A special symbol represents the end of the string
|
||||||
|
EndOfString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HuffmanCodeSymbol {
|
||||||
|
pub fn new(symbol: usize) -> HuffmanCodeSymbol {
|
||||||
|
if symbol == 256 {
|
||||||
|
HuffmanCodeSymbol::EndOfString
|
||||||
|
} else {
|
||||||
|
// It is safe to downcast since now we know that the value
|
||||||
|
// is in the half-open interval [0, 256)
|
||||||
|
HuffmanCodeSymbol::Symbol(symbol as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the error variants that the `HuffmanDecoder` can return.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
#[derive(Copy)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum HuffmanDecoderError {
|
||||||
|
/// Any padding strictly larger than 7 bits MUST be interpreted as an error
|
||||||
|
PaddingTooLarge,
|
||||||
|
/// Any padding that does not correspond to the most significant bits of
|
||||||
|
/// EOS MUST be interpreted as an error.
|
||||||
|
InvalidPadding,
|
||||||
|
/// If EOS is ever found in the string, it causes an error.
|
||||||
|
EOSInString,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type that represents the result of the `decode` method of the
|
||||||
|
/// `HuffmanDecoder`.
|
||||||
|
pub type HuffmanDecoderResult = Result<Vec<u8>, HuffmanDecoderError>;
|
||||||
|
|
||||||
|
/// A simple implementation of a Huffman code decoder.
|
||||||
|
pub struct HuffmanDecoder {
|
||||||
|
table: HashMap<u8, HashMap<u32, HuffmanCodeSymbol>>,
|
||||||
|
// The representation of the EOS: the left-aligned code representation and
|
||||||
|
// the actual length of the codepoint, as a tuple.
|
||||||
|
eos_codepoint: (u32, u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HuffmanDecoder {
|
||||||
|
/// Constructs a new `HuffmanDecoder` using the given table of
|
||||||
|
/// (code point, code length) tuples to represent the Huffman code.
|
||||||
|
fn from_table(table: &[(u32, u8)]) -> HuffmanDecoder {
|
||||||
|
if table.len() != 257 {
|
||||||
|
panic!("Invalid Huffman code table. It must define exactly 257 symbols.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decoder_table: HashMap<u8, HashMap<u32, HuffmanCodeSymbol>> =
|
||||||
|
HashMap::new();
|
||||||
|
let mut eos_codepoint: Option<(u32, u8)> = None;
|
||||||
|
|
||||||
|
for (symbol, &(code, code_len)) in table.iter().enumerate() {
|
||||||
|
if !decoder_table.contains_key(&code_len) {
|
||||||
|
decoder_table.insert(code_len, HashMap::new());
|
||||||
|
}
|
||||||
|
let subtable = decoder_table.get_mut(&code_len).unwrap();
|
||||||
|
let huff_symbol = HuffmanCodeSymbol::new(symbol);
|
||||||
|
match huff_symbol {
|
||||||
|
HuffmanCodeSymbol::EndOfString => {
|
||||||
|
// We also remember the code point of the EOS for easier
|
||||||
|
// reference later on.
|
||||||
|
eos_codepoint = Some((code, code_len));
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
subtable.insert(code, huff_symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
HuffmanDecoder {
|
||||||
|
table: decoder_table,
|
||||||
|
eos_codepoint: eos_codepoint.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new HuffmanDecoder with the default Huffman code table, as
|
||||||
|
/// defined in the HPACK-draft-10, Appendix B.
|
||||||
|
pub fn new() -> HuffmanDecoder {
|
||||||
|
HuffmanDecoder::from_table(HUFFMAN_CODE_TABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the buffer `buf` into a newly allocated `Vec`.
|
||||||
|
///
|
||||||
|
/// It assumes that the entire buffer should be considered as the Huffman
|
||||||
|
/// encoding of an octet string and handles the padding rules
|
||||||
|
/// accordingly.
|
||||||
|
pub fn decode(&mut self, buf: &[u8]) -> HuffmanDecoderResult {
|
||||||
|
let mut current: u32 = 0;
|
||||||
|
let mut current_len: u8 = 0;
|
||||||
|
let mut result: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
for b in BitIterator::new(buf.iter()) {
|
||||||
|
current_len += 1;
|
||||||
|
current <<= 1;
|
||||||
|
if b {
|
||||||
|
current |= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.table.contains_key(¤t_len) {
|
||||||
|
let length_table = self.table.get(¤t_len).unwrap();
|
||||||
|
if length_table.contains_key(¤t) {
|
||||||
|
let decoded_symbol = match length_table.get(¤t).unwrap() {
|
||||||
|
&HuffmanCodeSymbol::Symbol(symbol) => symbol,
|
||||||
|
&HuffmanCodeSymbol::EndOfString => {
|
||||||
|
// If the EOS symbol is detected within the stream,
|
||||||
|
// we need to consider it an error.
|
||||||
|
return Err(HuffmanDecoderError::EOSInString);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
result.push(decoded_symbol);
|
||||||
|
current = 0;
|
||||||
|
current_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we need to verify that the padding is correct.
|
||||||
|
// The spec mandates that the padding must not be strictly longer than
|
||||||
|
// 7 bits and that it must represent the most significant bits of the
|
||||||
|
// EOS symbol's code.
|
||||||
|
|
||||||
|
// First: the check for the length of the padding
|
||||||
|
if current_len > 7 {
|
||||||
|
return Err(HuffmanDecoderError::PaddingTooLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second: the padding corresponds to the most-significant bits of the
|
||||||
|
// EOS symbol.
|
||||||
|
// Align both of them to have their most significant bit as the most
|
||||||
|
// significant bit of a u32.
|
||||||
|
let right_align_current = if current_len == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
current << (32 - current_len)
|
||||||
|
};
|
||||||
|
let right_align_eos = self.eos_codepoint.0 << (32 - self.eos_codepoint.1);
|
||||||
|
// Now take only the necessary amount of most significant bit of EOS.
|
||||||
|
// The mask defines a bit pattern of `current_len` leading set bits,
|
||||||
|
// followed by the rest of the bits 0.
|
||||||
|
let mask = if current_len == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
((1 << current_len) - 1) << (32 - current_len)
|
||||||
|
};
|
||||||
|
// The mask is now used to strip the unwanted bits of the EOS
|
||||||
|
let eos_mask = right_align_eos & mask;
|
||||||
|
|
||||||
|
if eos_mask != right_align_current {
|
||||||
|
return Err(HuffmanDecoderError::InvalidPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper struct that represents an iterator over individual bits of all
|
||||||
|
/// bytes found in a wrapped Iterator over bytes.
|
||||||
|
/// Bits are represented as `bool`s, where `true` corresponds to a set bit and
|
||||||
|
/// `false` to a 0 bit.
|
||||||
|
///
|
||||||
|
/// Bits are yielded in order of significance, starting from the
|
||||||
|
/// most-significant bit.
|
||||||
|
struct BitIterator<'a, I: Iterator> {
|
||||||
|
buffer_iterator: I,
|
||||||
|
current_byte: Option<&'a u8>,
|
||||||
|
/// The bit-position within the current byte
|
||||||
|
pos: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator> BitIterator<'a, I>
|
||||||
|
where I: Iterator<Item=&'a u8> {
|
||||||
|
pub fn new(iterator: I) -> BitIterator<'a, I> {
|
||||||
|
BitIterator::<'a, I> {
|
||||||
|
buffer_iterator: iterator,
|
||||||
|
current_byte: None,
|
||||||
|
pos: 7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I> Iterator for BitIterator<'a, I>
|
||||||
|
where I: Iterator<Item=&'a u8> {
|
||||||
|
type Item = bool;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<bool> {
|
||||||
|
if self.current_byte.is_none() {
|
||||||
|
self.current_byte = self.buffer_iterator.next();
|
||||||
|
self.pos = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still have `None`, it means the buffer has been exhausted
|
||||||
|
if self.current_byte.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = *self.current_byte.unwrap();
|
||||||
|
|
||||||
|
let is_set = (b & (1 << self.pos)) == (1 << self.pos);
|
||||||
|
if self.pos == 0 {
|
||||||
|
// We have exhausted all bits from the current byte -- try to get
|
||||||
|
// a new one on the next pass.
|
||||||
|
self.current_byte = None;
|
||||||
|
} else {
|
||||||
|
// Still more bits left here...
|
||||||
|
self.pos -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(is_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HUFFMAN_CODE_TABLE: &'static [(u32, u8)] = &[
|
||||||
|
(0x1ff8, 13),
|
||||||
|
(0x7fffd8, 23),
|
||||||
|
(0xfffffe2, 28),
|
||||||
|
(0xfffffe3, 28),
|
||||||
|
(0xfffffe4, 28),
|
||||||
|
(0xfffffe5, 28),
|
||||||
|
(0xfffffe6, 28),
|
||||||
|
(0xfffffe7, 28),
|
||||||
|
(0xfffffe8, 28),
|
||||||
|
(0xffffea, 24),
|
||||||
|
(0x3ffffffc, 30),
|
||||||
|
(0xfffffe9, 28),
|
||||||
|
(0xfffffea, 28),
|
||||||
|
(0x3ffffffd, 30),
|
||||||
|
(0xfffffeb, 28),
|
||||||
|
(0xfffffec, 28),
|
||||||
|
(0xfffffed, 28),
|
||||||
|
(0xfffffee, 28),
|
||||||
|
(0xfffffef, 28),
|
||||||
|
(0xffffff0, 28),
|
||||||
|
(0xffffff1, 28),
|
||||||
|
(0xffffff2, 28),
|
||||||
|
(0x3ffffffe, 30),
|
||||||
|
(0xffffff3, 28),
|
||||||
|
(0xffffff4, 28),
|
||||||
|
(0xffffff5, 28),
|
||||||
|
(0xffffff6, 28),
|
||||||
|
(0xffffff7, 28),
|
||||||
|
(0xffffff8, 28),
|
||||||
|
(0xffffff9, 28),
|
||||||
|
(0xffffffa, 28),
|
||||||
|
(0xffffffb, 28),
|
||||||
|
(0x14, 6),
|
||||||
|
(0x3f8, 10),
|
||||||
|
(0x3f9, 10),
|
||||||
|
(0xffa, 12),
|
||||||
|
(0x1ff9, 13),
|
||||||
|
(0x15, 6),
|
||||||
|
(0xf8, 8),
|
||||||
|
(0x7fa, 11),
|
||||||
|
(0x3fa, 10),
|
||||||
|
(0x3fb, 10),
|
||||||
|
(0xf9, 8),
|
||||||
|
(0x7fb, 11),
|
||||||
|
(0xfa, 8),
|
||||||
|
(0x16, 6),
|
||||||
|
(0x17, 6),
|
||||||
|
(0x18, 6),
|
||||||
|
(0x0, 5),
|
||||||
|
(0x1, 5),
|
||||||
|
(0x2, 5),
|
||||||
|
(0x19, 6),
|
||||||
|
(0x1a, 6),
|
||||||
|
(0x1b, 6),
|
||||||
|
(0x1c, 6),
|
||||||
|
(0x1d, 6),
|
||||||
|
(0x1e, 6),
|
||||||
|
(0x1f, 6),
|
||||||
|
(0x5c, 7),
|
||||||
|
(0xfb, 8),
|
||||||
|
(0x7ffc, 15),
|
||||||
|
(0x20, 6),
|
||||||
|
(0xffb, 12),
|
||||||
|
(0x3fc, 10),
|
||||||
|
(0x1ffa, 13),
|
||||||
|
(0x21, 6),
|
||||||
|
(0x5d, 7),
|
||||||
|
(0x5e, 7),
|
||||||
|
(0x5f, 7),
|
||||||
|
(0x60, 7),
|
||||||
|
(0x61, 7),
|
||||||
|
(0x62, 7),
|
||||||
|
(0x63, 7),
|
||||||
|
(0x64, 7),
|
||||||
|
(0x65, 7),
|
||||||
|
(0x66, 7),
|
||||||
|
(0x67, 7),
|
||||||
|
(0x68, 7),
|
||||||
|
(0x69, 7),
|
||||||
|
(0x6a, 7),
|
||||||
|
(0x6b, 7),
|
||||||
|
(0x6c, 7),
|
||||||
|
(0x6d, 7),
|
||||||
|
(0x6e, 7),
|
||||||
|
(0x6f, 7),
|
||||||
|
(0x70, 7),
|
||||||
|
(0x71, 7),
|
||||||
|
(0x72, 7),
|
||||||
|
(0xfc, 8),
|
||||||
|
(0x73, 7),
|
||||||
|
(0xfd, 8),
|
||||||
|
(0x1ffb, 13),
|
||||||
|
(0x7fff0, 19),
|
||||||
|
(0x1ffc, 13),
|
||||||
|
(0x3ffc, 14),
|
||||||
|
(0x22, 6),
|
||||||
|
(0x7ffd, 15),
|
||||||
|
(0x3, 5),
|
||||||
|
(0x23, 6),
|
||||||
|
(0x4, 5),
|
||||||
|
(0x24, 6),
|
||||||
|
(0x5, 5),
|
||||||
|
(0x25, 6),
|
||||||
|
(0x26, 6),
|
||||||
|
(0x27, 6),
|
||||||
|
(0x6, 5),
|
||||||
|
(0x74, 7),
|
||||||
|
(0x75, 7),
|
||||||
|
(0x28, 6),
|
||||||
|
(0x29, 6),
|
||||||
|
(0x2a, 6),
|
||||||
|
(0x7, 5),
|
||||||
|
(0x2b, 6),
|
||||||
|
(0x76, 7),
|
||||||
|
(0x2c, 6),
|
||||||
|
(0x8, 5),
|
||||||
|
(0x9, 5),
|
||||||
|
(0x2d, 6),
|
||||||
|
(0x77, 7),
|
||||||
|
(0x78, 7),
|
||||||
|
(0x79, 7),
|
||||||
|
(0x7a, 7),
|
||||||
|
(0x7b, 7),
|
||||||
|
(0x7ffe, 15),
|
||||||
|
(0x7fc, 11),
|
||||||
|
(0x3ffd, 14),
|
||||||
|
(0x1ffd, 13),
|
||||||
|
(0xffffffc, 28),
|
||||||
|
(0xfffe6, 20),
|
||||||
|
(0x3fffd2, 22),
|
||||||
|
(0xfffe7, 20),
|
||||||
|
(0xfffe8, 20),
|
||||||
|
(0x3fffd3, 22),
|
||||||
|
(0x3fffd4, 22),
|
||||||
|
(0x3fffd5, 22),
|
||||||
|
(0x7fffd9, 23),
|
||||||
|
(0x3fffd6, 22),
|
||||||
|
(0x7fffda, 23),
|
||||||
|
(0x7fffdb, 23),
|
||||||
|
(0x7fffdc, 23),
|
||||||
|
(0x7fffdd, 23),
|
||||||
|
(0x7fffde, 23),
|
||||||
|
(0xffffeb, 24),
|
||||||
|
(0x7fffdf, 23),
|
||||||
|
(0xffffec, 24),
|
||||||
|
(0xffffed, 24),
|
||||||
|
(0x3fffd7, 22),
|
||||||
|
(0x7fffe0, 23),
|
||||||
|
(0xffffee, 24),
|
||||||
|
(0x7fffe1, 23),
|
||||||
|
(0x7fffe2, 23),
|
||||||
|
(0x7fffe3, 23),
|
||||||
|
(0x7fffe4, 23),
|
||||||
|
(0x1fffdc, 21),
|
||||||
|
(0x3fffd8, 22),
|
||||||
|
(0x7fffe5, 23),
|
||||||
|
(0x3fffd9, 22),
|
||||||
|
(0x7fffe6, 23),
|
||||||
|
(0x7fffe7, 23),
|
||||||
|
(0xffffef, 24),
|
||||||
|
(0x3fffda, 22),
|
||||||
|
(0x1fffdd, 21),
|
||||||
|
(0xfffe9, 20),
|
||||||
|
(0x3fffdb, 22),
|
||||||
|
(0x3fffdc, 22),
|
||||||
|
(0x7fffe8, 23),
|
||||||
|
(0x7fffe9, 23),
|
||||||
|
(0x1fffde, 21),
|
||||||
|
(0x7fffea, 23),
|
||||||
|
(0x3fffdd, 22),
|
||||||
|
(0x3fffde, 22),
|
||||||
|
(0xfffff0, 24),
|
||||||
|
(0x1fffdf, 21),
|
||||||
|
(0x3fffdf, 22),
|
||||||
|
(0x7fffeb, 23),
|
||||||
|
(0x7fffec, 23),
|
||||||
|
(0x1fffe0, 21),
|
||||||
|
(0x1fffe1, 21),
|
||||||
|
(0x3fffe0, 22),
|
||||||
|
(0x1fffe2, 21),
|
||||||
|
(0x7fffed, 23),
|
||||||
|
(0x3fffe1, 22),
|
||||||
|
(0x7fffee, 23),
|
||||||
|
(0x7fffef, 23),
|
||||||
|
(0xfffea, 20),
|
||||||
|
(0x3fffe2, 22),
|
||||||
|
(0x3fffe3, 22),
|
||||||
|
(0x3fffe4, 22),
|
||||||
|
(0x7ffff0, 23),
|
||||||
|
(0x3fffe5, 22),
|
||||||
|
(0x3fffe6, 22),
|
||||||
|
(0x7ffff1, 23),
|
||||||
|
(0x3ffffe0, 26),
|
||||||
|
(0x3ffffe1, 26),
|
||||||
|
(0xfffeb, 20),
|
||||||
|
(0x7fff1, 19),
|
||||||
|
(0x3fffe7, 22),
|
||||||
|
(0x7ffff2, 23),
|
||||||
|
(0x3fffe8, 22),
|
||||||
|
(0x1ffffec, 25),
|
||||||
|
(0x3ffffe2, 26),
|
||||||
|
(0x3ffffe3, 26),
|
||||||
|
(0x3ffffe4, 26),
|
||||||
|
(0x7ffffde, 27),
|
||||||
|
(0x7ffffdf, 27),
|
||||||
|
(0x3ffffe5, 26),
|
||||||
|
(0xfffff1, 24),
|
||||||
|
(0x1ffffed, 25),
|
||||||
|
(0x7fff2, 19),
|
||||||
|
(0x1fffe3, 21),
|
||||||
|
(0x3ffffe6, 26),
|
||||||
|
(0x7ffffe0, 27),
|
||||||
|
(0x7ffffe1, 27),
|
||||||
|
(0x3ffffe7, 26),
|
||||||
|
(0x7ffffe2, 27),
|
||||||
|
(0xfffff2, 24),
|
||||||
|
(0x1fffe4, 21),
|
||||||
|
(0x1fffe5, 21),
|
||||||
|
(0x3ffffe8, 26),
|
||||||
|
(0x3ffffe9, 26),
|
||||||
|
(0xffffffd, 28),
|
||||||
|
(0x7ffffe3, 27),
|
||||||
|
(0x7ffffe4, 27),
|
||||||
|
(0x7ffffe5, 27),
|
||||||
|
(0xfffec, 20),
|
||||||
|
(0xfffff3, 24),
|
||||||
|
(0xfffed, 20),
|
||||||
|
(0x1fffe6, 21),
|
||||||
|
(0x3fffe9, 22),
|
||||||
|
(0x1fffe7, 21),
|
||||||
|
(0x1fffe8, 21),
|
||||||
|
(0x7ffff3, 23),
|
||||||
|
(0x3fffea, 22),
|
||||||
|
(0x3fffeb, 22),
|
||||||
|
(0x1ffffee, 25),
|
||||||
|
(0x1ffffef, 25),
|
||||||
|
(0xfffff4, 24),
|
||||||
|
(0xfffff5, 24),
|
||||||
|
(0x3ffffea, 26),
|
||||||
|
(0x7ffff4, 23),
|
||||||
|
(0x3ffffeb, 26),
|
||||||
|
(0x7ffffe6, 27),
|
||||||
|
(0x3ffffec, 26),
|
||||||
|
(0x3ffffed, 26),
|
||||||
|
(0x7ffffe7, 27),
|
||||||
|
(0x7ffffe8, 27),
|
||||||
|
(0x7ffffe9, 27),
|
||||||
|
(0x7ffffea, 27),
|
||||||
|
(0x7ffffeb, 27),
|
||||||
|
(0xffffffe, 28),
|
||||||
|
(0x7ffffec, 27),
|
||||||
|
(0x7ffffed, 27),
|
||||||
|
(0x7ffffee, 27),
|
||||||
|
(0x7ffffef, 27),
|
||||||
|
(0x7fffff0, 27),
|
||||||
|
(0x3ffffee, 26),
|
||||||
|
(0x3fffffff, 30),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::BitIterator;
|
||||||
|
use super::HuffmanDecoder;
|
||||||
|
use super::HuffmanDecoderError;
|
||||||
|
|
||||||
|
/// A helper function that converts the given slice containing values `1`
|
||||||
|
/// and `0` to a `Vec` of `bool`s, according to the number.
|
||||||
|
fn to_expected_bit_result(numbers: &[u8]) -> Vec<bool> {
|
||||||
|
numbers.iter().map(|b| -> bool {
|
||||||
|
*b == 1
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bit_iterator_single_byte() {
|
||||||
|
let expected_result = to_expected_bit_result(
|
||||||
|
&[0, 0, 0, 0, 1, 0, 1, 0]);
|
||||||
|
|
||||||
|
let mut res: Vec<bool> = Vec::new();
|
||||||
|
for b in BitIterator::new(vec![10u8].iter()) {
|
||||||
|
res.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(res, expected_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bit_iterator_multiple_bytes() {
|
||||||
|
let expected_result = to_expected_bit_result(
|
||||||
|
&[0, 0, 0, 0, 1, 0, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 1, 0, 1, 0, 1, 0]);
|
||||||
|
|
||||||
|
let mut res: Vec<bool> = Vec::new();
|
||||||
|
for b in BitIterator::new(vec![10u8, 255, 128, 1, 0, 170].iter()) {
|
||||||
|
res.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(res, expected_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple tests for the Huffman decoder -- whether it can decode a single
|
||||||
|
/// character code represented additionally with only a single byte.
|
||||||
|
#[test]
|
||||||
|
fn test_huffman_code_single_byte() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
// (The + (2^n - 1) at the final byte is to add the correct expected
|
||||||
|
// padding: 1s)
|
||||||
|
{
|
||||||
|
// We need to shift it by 3, since we need the top-order bytes to
|
||||||
|
// start the code point.
|
||||||
|
let hex_buffer = [(0x7 << 3) + 7];
|
||||||
|
let expected_result = vec![b'o'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let hex_buffer = [0x0 + 7];
|
||||||
|
let expected_result = vec![b'0'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// The length of the codepoint is 6, so we shift by two
|
||||||
|
let hex_buffer = [(0x21 << 2) + 3];
|
||||||
|
let expected_result = vec![b'A'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that the Huffman decoder can decode a single character made of
|
||||||
|
/// multiple bytes.
|
||||||
|
#[test]
|
||||||
|
fn test_huffman_code_single_char_multiple_byte() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
// (The + (2^n - 1) at the final byte is to add the correct expected
|
||||||
|
// padding: 1s)
|
||||||
|
{
|
||||||
|
let hex_buffer = [255, 160 + 15];
|
||||||
|
let expected_result = vec![b'#'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let hex_buffer = [255, 200 + 7];
|
||||||
|
let expected_result = vec![b'$'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let hex_buffer = [255, 255, 255, 240 + 3];
|
||||||
|
let expected_result = vec![10];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_huffman_code_multiple_chars() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
{
|
||||||
|
let hex_buffer = [254, 1];
|
||||||
|
let expected_result = vec![b'!', b'0'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let hex_buffer = [(0x14 << 2) | 0x3, 248];
|
||||||
|
let expected_result = vec![b' ', b'!'];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer).ok().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that if we find the EOS symbol in the stream, we consider it an
|
||||||
|
/// error.
|
||||||
|
#[test]
|
||||||
|
fn test_eos_is_error() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
{
|
||||||
|
// The EOS is an all-ones pattern of length 30
|
||||||
|
let hex_buffer = [0xFF, 0xFF, 0xFF, 0xFF];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer);
|
||||||
|
|
||||||
|
assert_eq!(HuffmanDecoderError::EOSInString, match result {
|
||||||
|
Err(e) => e,
|
||||||
|
_ => panic!("Expected error due to EOS symbol in string"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Full EOS after a valid character.
|
||||||
|
let hex_buffer = [0x3F, 0xFF, 0xFF, 0xFF, 0xFF];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer);
|
||||||
|
|
||||||
|
assert_eq!(HuffmanDecoderError::EOSInString, match result {
|
||||||
|
Err(e) => e,
|
||||||
|
_ => panic!("Expected error due to EOS symbol in string"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when there are 7 or less padding bits, the string is
|
||||||
|
/// correctly decoded.
|
||||||
|
#[test]
|
||||||
|
fn test_short_padding_okay() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
let hex_buffer = [0x3F];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer);
|
||||||
|
|
||||||
|
assert_eq!(b"o".to_vec(), result.ok().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when there are more than 7 padding bits, we get an error.
|
||||||
|
#[test]
|
||||||
|
fn test_padding_too_long() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
let hex_buffer = [0x3F, 0xFF];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer);
|
||||||
|
|
||||||
|
assert_eq!(HuffmanDecoderError::PaddingTooLarge, match result {
|
||||||
|
Err(e) => e,
|
||||||
|
_ => panic!("Expected `PaddingTooLarge` error"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when there is a certain number of padding bits that deviate
|
||||||
|
/// from the most significant bits of the EOS symbol, we get an error.
|
||||||
|
#[test]
|
||||||
|
fn test_padding_invalid() {
|
||||||
|
let mut decoder = HuffmanDecoder::new();
|
||||||
|
{
|
||||||
|
let hex_buffer = [0x3E];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer);
|
||||||
|
|
||||||
|
assert_eq!(HuffmanDecoderError::InvalidPadding, match result {
|
||||||
|
Err(e) => e,
|
||||||
|
_ => panic!("Expected `InvalidPadding` error"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let hex_buffer = [254, 0];
|
||||||
|
|
||||||
|
let result = decoder.decode(&hex_buffer);
|
||||||
|
|
||||||
|
assert_eq!(HuffmanDecoderError::InvalidPadding, match result {
|
||||||
|
Err(e) => e,
|
||||||
|
_ => panic!("Expected `InvalidPadding` error"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
756
src/hpack/mod.rs
Normal file
756
src/hpack/mod.rs
Normal file
@@ -0,0 +1,756 @@
|
|||||||
|
//! A module implementing HPACK functionality. Exposes a simple API for
|
||||||
|
//! performing the encoding and decoding of header sets, according to the
|
||||||
|
//! HPACK spec.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter;
|
||||||
|
use std::slice;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::collections::vec_deque;
|
||||||
|
|
||||||
|
// Re-export the main HPACK API entry points.
|
||||||
|
pub use self::decoder::Decoder;
|
||||||
|
pub use self::encoder::Encoder;
|
||||||
|
|
||||||
|
pub mod encoder;
|
||||||
|
pub mod decoder;
|
||||||
|
pub mod huffman;
|
||||||
|
|
||||||
|
/// An `Iterator` through elements of the `DynamicTable`.
|
||||||
|
///
|
||||||
|
/// The implementation of the iterator itself is very tightly coupled
|
||||||
|
/// to the implementation of the `DynamicTable`.
|
||||||
|
///
|
||||||
|
/// This iterator returns tuples of slices. The tuples themselves are
|
||||||
|
/// constructed as new instances, containing a borrow from the `Vec`s
|
||||||
|
/// representing the underlying Headers.
|
||||||
|
struct DynamicTableIter<'a> {
|
||||||
|
/// Stores an iterator through the underlying structure that the
|
||||||
|
/// `DynamicTable` uses
|
||||||
|
inner: vec_deque::Iter<'a, (Vec<u8>, Vec<u8>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DynamicTableIter<'a> {
|
||||||
|
type Item = (&'a [u8], &'a [u8]);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(&'a [u8], &'a [u8])> {
|
||||||
|
match self.inner.next() {
|
||||||
|
Some(ref header) => Some((&header.0, &header.1)),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct representing the dynamic table that needs to be maintained by the
|
||||||
|
/// coder.
|
||||||
|
///
|
||||||
|
/// The dynamic table contains a number of recently used headers. The size of
|
||||||
|
/// the table is constrained to a certain number of octets. If on insertion of
|
||||||
|
/// a new header into the table, the table would exceed the maximum size,
|
||||||
|
/// headers are evicted in a FIFO fashion until there is enough room for the
|
||||||
|
/// new header to be inserted. (Therefore, it is possible that though all
|
||||||
|
/// elements end up being evicted, there is still not enough space for the new
|
||||||
|
/// header: when the size of this individual header exceeds the maximum size of
|
||||||
|
/// the table.)
|
||||||
|
///
|
||||||
|
/// The current size of the table is calculated, based on the IETF definition,
|
||||||
|
/// as the sum of sizes of each header stored within the table, where the size
|
||||||
|
/// of an individual header is
|
||||||
|
/// `len_in_octets(header_name) + len_in_octets(header_value) + 32`.
|
||||||
|
///
|
||||||
|
/// Note: the maximum size of the dynamic table does not have to be equal to
|
||||||
|
/// the maximum header table size as defined by a "higher level" protocol
|
||||||
|
/// (such as the `SETTINGS_HEADER_TABLE_SIZE` setting in HTTP/2), since HPACK
|
||||||
|
/// can choose to modify the dynamic table size on the fly (as long as it keeps
|
||||||
|
/// it below the maximum value set by the pce
|
||||||
|
/// rotocol). So, the `DynamicTable`
|
||||||
|
/// only cares about the maximum size as set by the HPACK {en,de}coder and lets
|
||||||
|
/// *it* worry about making certain that the changes are valid according to
|
||||||
|
/// the (current) constraints of the protocol.
|
||||||
|
struct DynamicTable {
|
||||||
|
table: VecDeque<(Vec<u8>, Vec<u8>)>,
|
||||||
|
size: usize,
|
||||||
|
max_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynamicTable {
|
||||||
|
/// Creates a new empty dynamic table with a default size.
|
||||||
|
fn new() -> DynamicTable {
|
||||||
|
// The default maximum size corresponds to the default HTTP/2
|
||||||
|
// setting
|
||||||
|
DynamicTable::with_size(4096)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new empty dynamic table with the given maximum size.
|
||||||
|
fn with_size(max_size: usize) -> DynamicTable {
|
||||||
|
DynamicTable {
|
||||||
|
table: VecDeque::new(),
|
||||||
|
size: 0,
|
||||||
|
max_size: max_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current size of the table in octets, as defined by the IETF
|
||||||
|
/// HPACK spec.
|
||||||
|
fn get_size(&self) -> usize {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an `Iterator` through the headers stored in the `DynamicTable`.
|
||||||
|
///
|
||||||
|
/// The iterator will yield elements of type `(&[u8], &[u8])`,
|
||||||
|
/// corresponding to a single header name and value. The name and value
|
||||||
|
/// slices are borrowed from their representations in the `DynamicTable`
|
||||||
|
/// internal implementation, which means that it is possible only to
|
||||||
|
/// iterate through the headers, not mutate them.
|
||||||
|
fn iter(&self) -> DynamicTableIter {
|
||||||
|
DynamicTableIter {
|
||||||
|
inner: self.table.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the new maximum table size.
|
||||||
|
///
|
||||||
|
/// If the current size of the table is larger than the new maximum size,
|
||||||
|
/// existing headers are evicted in a FIFO fashion until the size drops
|
||||||
|
/// below the new maximum.
|
||||||
|
fn set_max_table_size(&mut self, new_max_size: usize) {
|
||||||
|
self.max_size = new_max_size;
|
||||||
|
// Make the table size fit within the new constraints.
|
||||||
|
self.consolidate_table();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the maximum size of the table in octets.
|
||||||
|
fn get_max_table_size(&self) -> usize {
|
||||||
|
self.max_size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new header to the dynamic table.
|
||||||
|
///
|
||||||
|
/// The table automatically gets resized, if necessary.
|
||||||
|
///
|
||||||
|
/// Do note that, under the HPACK rules, it is possible the given header
|
||||||
|
/// is not found in the dynamic table after this operation finishes, in
|
||||||
|
/// case the total size of the given header exceeds the maximum size of the
|
||||||
|
/// dynamic table.
|
||||||
|
fn add_header(&mut self, name: Vec<u8>, value: Vec<u8>) {
|
||||||
|
// This is how the HPACK spec makes us calculate the size. The 32 is
|
||||||
|
// a magic number determined by them (under reasonable assumptions of
|
||||||
|
// how the table is stored).
|
||||||
|
self.size += name.len() + value.len() + 32;
|
||||||
|
// Now add it to the internal buffer
|
||||||
|
self.table.push_front((name, value));
|
||||||
|
// ...and make sure we're not over the maximum size.
|
||||||
|
self.consolidate_table();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consolidates the table entries so that the table size is below the
|
||||||
|
/// maximum allowed size, by evicting headers from the table in a FIFO
|
||||||
|
/// fashion.
|
||||||
|
fn consolidate_table(&mut self) {
|
||||||
|
while self.size > self.max_size {
|
||||||
|
{
|
||||||
|
let last_header = match self.table.back() {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
// Can never happen as the size of the table must reach
|
||||||
|
// 0 by the time we've exhausted all elements.
|
||||||
|
panic!("Size of table != 0, but no headers left!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.size -= last_header.0.len() + last_header.1.len() + 32;
|
||||||
|
}
|
||||||
|
self.table.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of headers in the dynamic table.
|
||||||
|
///
|
||||||
|
/// This is different than the size of the dynamic table.
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.table.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the current state of the table to a `Vec`
|
||||||
|
fn to_vec(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let mut ret: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
||||||
|
for elem in self.table.iter() {
|
||||||
|
ret.push(elem.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the header at the given index, if found in the
|
||||||
|
/// dynamic table.
|
||||||
|
fn get(&self, index: usize) -> Option<&(Vec<u8>, Vec<u8>)> {
|
||||||
|
self.table.get(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for DynamicTable {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "{:?}", self.table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the type of the static table, as defined by the HPACK spec.
|
||||||
|
type StaticTable<'a> = &'a [(&'a [u8], &'a [u8])];
|
||||||
|
|
||||||
|
/// Implements an iterator through the entire `HeaderTable`.
|
||||||
|
///
|
||||||
|
/// Yields first the elements from the static table, followed by elements from
|
||||||
|
/// the dynamic table, with each element being of type `(&[u8], &[u8])`.
|
||||||
|
///
|
||||||
|
/// This struct is tightly coupled to the implementation of the `HeaderTable`,
|
||||||
|
/// but its clients are shielded from all that and have a convenient (and
|
||||||
|
/// standardized) interface to iterate through all headers of the table.
|
||||||
|
///
|
||||||
|
/// The declaration of the inner iterator that is wrapped by this struct is a
|
||||||
|
/// monstrosity, that is required because "abstract return types" don't exist
|
||||||
|
/// yet ([https://github.com/rust-lang/rfcs/pull/105]).
|
||||||
|
struct HeaderTableIter<'a> {
|
||||||
|
// Represents a chain of static-table -> dynamic-table elements.
|
||||||
|
// The mapper is required to transform the elements yielded from the static
|
||||||
|
// table to a type that matches the elements yielded from the dynamic table.
|
||||||
|
inner: iter::Chain<
|
||||||
|
iter::Map<
|
||||||
|
slice::Iter<'a, (&'a [u8], &'a [u8])>,
|
||||||
|
fn((&'a (&'a [u8], &'a [u8]))) -> (&'a [u8], &'a [u8])>,
|
||||||
|
DynamicTableIter<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for HeaderTableIter<'a> {
|
||||||
|
type Item = (&'a [u8], &'a [u8]);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(&'a [u8], &'a [u8])> {
|
||||||
|
// Simply delegates to the wrapped iterator that is constructed by the
|
||||||
|
// `HeaderTable` and passed into the `HeaderTableIter`.
|
||||||
|
self.inner.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper function that maps a borrowed tuple containing two borrowed slices
|
||||||
|
/// to just a tuple of two borrowed slices.
|
||||||
|
///
|
||||||
|
/// This helper function is needed because in order to define the type
|
||||||
|
/// `HeaderTableIter` we need to be able to refer to a real type for the Fn
|
||||||
|
/// template parameter, which means that when instantiating an instance, a
|
||||||
|
/// closure cannot be passed, since it cannot be named.
|
||||||
|
fn static_table_mapper<'a>(h: &'a (&'a [u8], &'a [u8]))
|
||||||
|
-> (&'a [u8], &'a [u8]) {
|
||||||
|
*h
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The struct represents the header table obtained by merging the static and
|
||||||
|
/// dynamic tables into a single index address space, as described in section
|
||||||
|
/// `2.3.3.` of the HPACK spec.
|
||||||
|
struct HeaderTable<'a> {
|
||||||
|
static_table: StaticTable<'a>,
|
||||||
|
dynamic_table: DynamicTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> HeaderTable<'a> {
|
||||||
|
/// Creates a new header table where the static part is initialized with
|
||||||
|
/// the given static table.
|
||||||
|
pub fn with_static_table(static_table: StaticTable<'a>) -> HeaderTable<'a> {
|
||||||
|
HeaderTable {
|
||||||
|
static_table: static_table,
|
||||||
|
dynamic_table: DynamicTable::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator through *all* headers stored in the header table,
|
||||||
|
/// i.e. it includes both the ones found in the static table and the
|
||||||
|
/// dynamic table, in the order of their indices in the single address
|
||||||
|
/// space (first the headers in the static table, followed by headers in
|
||||||
|
/// the dynamic table).
|
||||||
|
///
|
||||||
|
/// The type yielded by the iterator is `(&[u8], &[u8])`, where the tuple
|
||||||
|
/// corresponds to the header name, value pairs in the described order.
|
||||||
|
pub fn iter(&'a self) -> HeaderTableIter<'a> {
|
||||||
|
HeaderTableIter {
|
||||||
|
inner: self.static_table.iter()
|
||||||
|
.map(static_table_mapper as
|
||||||
|
fn((&'a (&'a [u8], &'a [u8]))) -> (&'a [u8], &'a [u8]))
|
||||||
|
.chain(self.dynamic_table.iter()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the given header to the table. Of course, this means that the new
|
||||||
|
/// header is added to the dynamic part of the table.
|
||||||
|
///
|
||||||
|
/// If the size of the new header is larger than the current maximum table
|
||||||
|
/// size of the dynamic table, the effect will be that the dynamic table
|
||||||
|
/// gets emptied and the new header does *not* get inserted into it.
|
||||||
|
#[inline]
|
||||||
|
pub fn add_header(&mut self, name: Vec<u8>, value: Vec<u8>) {
|
||||||
|
self.dynamic_table.add_header(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the header (a `(name, value)` pair) with the
|
||||||
|
/// given index in the table.
|
||||||
|
///
|
||||||
|
/// The table is 1-indexed and constructed in such a way that the first
|
||||||
|
/// entries belong to the static table, followed by entries in the dynamic
|
||||||
|
/// table. They are merged into a single index address space, though.
|
||||||
|
///
|
||||||
|
/// This is according to the [HPACK spec, section 2.3.3.]
|
||||||
|
/// (http://http2.github.io/http2-spec/compression.html#index.address.space)
|
||||||
|
pub fn get_from_table(&self, index: usize)
|
||||||
|
-> Option<(&[u8], &[u8])> {
|
||||||
|
// The IETF defined table indexing as 1-based.
|
||||||
|
// So, before starting, make sure the given index is within the proper
|
||||||
|
// bounds.
|
||||||
|
let real_index = if index > 0 {
|
||||||
|
index - 1
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
};
|
||||||
|
|
||||||
|
if real_index < self.static_table.len() {
|
||||||
|
// It is in the static table so just return that...
|
||||||
|
Some(self.static_table[real_index])
|
||||||
|
} else {
|
||||||
|
// Maybe it's in the dynamic table then?
|
||||||
|
let dynamic_index = real_index - self.static_table.len();
|
||||||
|
if dynamic_index < self.dynamic_table.len() {
|
||||||
|
match self.dynamic_table.get(dynamic_index) {
|
||||||
|
Some(&(ref name, ref value)) => {
|
||||||
|
Some((name, value))
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Index out of bounds!
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the given header in the header table. Tries to match both the
|
||||||
|
/// header name and value to one of the headers in the table. If no such
|
||||||
|
/// header exists, then falls back to returning one that matched only the
|
||||||
|
/// name.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// An `Option`, where `Some` corresponds to a tuple representing the index
|
||||||
|
/// of the header in the header tables (the 1-based index that HPACK uses)
|
||||||
|
/// and a `bool` indicating whether the value of the header also matched.
|
||||||
|
pub fn find_header(&self, header: (&[u8], &[u8])) -> Option<(usize, bool)> {
|
||||||
|
// Just does a simple scan of the entire table, searching for a header
|
||||||
|
// that matches both the name and the value of the given header.
|
||||||
|
// If no such header is found, then any one of the headers that had a
|
||||||
|
// matching name is returned, with the appropriate return flag set.
|
||||||
|
//
|
||||||
|
// The tables are so small that it is unlikely that the linear scan
|
||||||
|
// would be a major performance bottlneck. If it does prove to be,
|
||||||
|
// though, a more efficient lookup/header representation method could
|
||||||
|
// be devised.
|
||||||
|
let mut matching_name: Option<usize> = None;
|
||||||
|
for (i, h) in self.iter().enumerate() {
|
||||||
|
if header.0 == h.0 {
|
||||||
|
if header.1 == h.1 {
|
||||||
|
// Both name and value matched: returns it immediately
|
||||||
|
return Some((i + 1, true));
|
||||||
|
}
|
||||||
|
// If only the name was valid, we continue scanning, hoping to
|
||||||
|
// find one where both the name and value match. We remember
|
||||||
|
// this one, in case such a header isn't found after all.
|
||||||
|
matching_name = Some(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, if there's no header with a matching name and value,
|
||||||
|
// return one that matched only the name, if that *was* found.
|
||||||
|
match matching_name {
|
||||||
|
Some(i) => Some((i, false)),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The table represents the static header table defined by the HPACK spec.
|
||||||
|
/// (HPACK, Appendix A)
|
||||||
|
static STATIC_TABLE: &'static [(&'static [u8], &'static [u8])] = &[
|
||||||
|
(b":authority", b""),
|
||||||
|
(b":method", b"GET"),
|
||||||
|
(b":method", b"POST"),
|
||||||
|
(b":path", b"/"),
|
||||||
|
(b":path", b"/index.html"),
|
||||||
|
(b":scheme", b"http"),
|
||||||
|
(b":scheme", b"https"),
|
||||||
|
(b":status", b"200"),
|
||||||
|
(b":status", b"204"),
|
||||||
|
(b":status", b"206"),
|
||||||
|
(b":status", b"304"),
|
||||||
|
(b":status", b"400"),
|
||||||
|
(b":status", b"404"),
|
||||||
|
(b":status", b"500"),
|
||||||
|
(b"accept-", b""),
|
||||||
|
(b"accept-encoding", b"gzip, deflate"),
|
||||||
|
(b"accept-language", b""),
|
||||||
|
(b"accept-ranges", b""),
|
||||||
|
(b"accept", b""),
|
||||||
|
(b"access-control-allow-origin", b""),
|
||||||
|
(b"age", b""),
|
||||||
|
(b"allow", b""),
|
||||||
|
(b"authorization", b""),
|
||||||
|
(b"cache-control", b""),
|
||||||
|
(b"content-disposition", b""),
|
||||||
|
(b"content-encoding", b""),
|
||||||
|
(b"content-language", b""),
|
||||||
|
(b"content-length", b""),
|
||||||
|
(b"content-location", b""),
|
||||||
|
(b"content-range", b""),
|
||||||
|
(b"content-type", b""),
|
||||||
|
(b"cookie", b""),
|
||||||
|
(b"date", b""),
|
||||||
|
(b"etag", b""),
|
||||||
|
(b"expect", b""),
|
||||||
|
(b"expires", b""),
|
||||||
|
(b"from", b""),
|
||||||
|
(b"host", b""),
|
||||||
|
(b"if-match", b""),
|
||||||
|
(b"if-modified-since", b""),
|
||||||
|
(b"if-none-match", b""),
|
||||||
|
(b"if-range", b""),
|
||||||
|
(b"if-unmodified-since", b""),
|
||||||
|
(b"last-modified", b""),
|
||||||
|
(b"link", b""),
|
||||||
|
(b"location", b""),
|
||||||
|
(b"max-forwards", b""),
|
||||||
|
(b"proxy-authenticate", b""),
|
||||||
|
(b"proxy-authorization", b""),
|
||||||
|
(b"range", b""),
|
||||||
|
(b"referer", b""),
|
||||||
|
(b"refresh", b""),
|
||||||
|
(b"retry-after", b""),
|
||||||
|
(b"server", b""),
|
||||||
|
(b"set-cookie", b""),
|
||||||
|
(b"strict-transport-security", b""),
|
||||||
|
(b"transfer-encoding", b""),
|
||||||
|
(b"user-agent", b""),
|
||||||
|
(b"vary", b""),
|
||||||
|
(b"via", b""),
|
||||||
|
(b"www-authenticate", b""),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::DynamicTable;
|
||||||
|
use super::HeaderTable;
|
||||||
|
use super::STATIC_TABLE;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_size_calculation_simple() {
|
||||||
|
let mut table = DynamicTable::new();
|
||||||
|
// Sanity check
|
||||||
|
assert_eq!(0, table.get_size());
|
||||||
|
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
|
||||||
|
assert_eq!(32 + 2, table.get_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_size_calculation() {
|
||||||
|
let mut table = DynamicTable::new();
|
||||||
|
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
table.add_header(b"123".to_vec(), b"456".to_vec());
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
|
||||||
|
assert_eq!(3 * 32 + 2 + 6 + 2, table.get_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that the `DynamicTable` gets correctly resized (by evicting old
|
||||||
|
/// headers) if it exceeds the maximum size on an insertion.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_auto_resize() {
|
||||||
|
let mut table = DynamicTable::with_size(38);
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
assert_eq!(32 + 2, table.get_size());
|
||||||
|
|
||||||
|
table.add_header(b"123".to_vec(), b"456".to_vec());
|
||||||
|
|
||||||
|
// Resized?
|
||||||
|
assert_eq!(32 + 6, table.get_size());
|
||||||
|
// Only has the second header?
|
||||||
|
assert_eq!(table.to_vec(), vec![
|
||||||
|
(b"123".to_vec(), b"456".to_vec())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when inserting a new header whose size is larger than the
|
||||||
|
/// size of the entire table, the table is fully emptied.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_auto_resize_into_empty() {
|
||||||
|
let mut table = DynamicTable::with_size(38);
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
assert_eq!(32 + 2, table.get_size());
|
||||||
|
|
||||||
|
table.add_header(b"123".to_vec(), b"4567".to_vec());
|
||||||
|
|
||||||
|
// Resized and empty?
|
||||||
|
assert_eq!(0, table.get_size());
|
||||||
|
assert_eq!(0, table.to_vec().len());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when changing the maximum size of the `DynamicTable`, the
|
||||||
|
/// headers are correctly evicted in order to keep its size below the new
|
||||||
|
/// max.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_change_max_size() {
|
||||||
|
let mut table = DynamicTable::new();
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
table.add_header(b"123".to_vec(), b"456".to_vec());
|
||||||
|
table.add_header(b"c".to_vec(), b"d".to_vec());
|
||||||
|
assert_eq!(3 * 32 + 2 + 6 + 2, table.get_size());
|
||||||
|
|
||||||
|
table.set_max_table_size(38);
|
||||||
|
|
||||||
|
assert_eq!(32 + 2, table.get_size());
|
||||||
|
assert_eq!(table.to_vec(), vec![
|
||||||
|
(b"c".to_vec(), b"d".to_vec())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that setting the maximum table size to 0 clears the dynamic
|
||||||
|
/// table.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_clear() {
|
||||||
|
let mut table = DynamicTable::new();
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
table.add_header(b"123".to_vec(), b"456".to_vec());
|
||||||
|
table.add_header(b"c".to_vec(), b"d".to_vec());
|
||||||
|
assert_eq!(3 * 32 + 2 + 6 + 2, table.get_size());
|
||||||
|
|
||||||
|
table.set_max_table_size(0);
|
||||||
|
|
||||||
|
assert_eq!(0, table.len());
|
||||||
|
assert_eq!(0, table.to_vec().len());
|
||||||
|
assert_eq!(0, table.get_size());
|
||||||
|
assert_eq!(0, table.get_max_table_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when the initial max size of the table is 0, nothing
|
||||||
|
/// can be added to the table.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_max_size_zero() {
|
||||||
|
let mut table = DynamicTable::with_size(0);
|
||||||
|
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
|
||||||
|
assert_eq!(0, table.len());
|
||||||
|
assert_eq!(0, table.to_vec().len());
|
||||||
|
assert_eq!(0, table.get_size());
|
||||||
|
assert_eq!(0, table.get_max_table_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that the iterator through the `DynamicTable` works when there are
|
||||||
|
/// some elements in the dynamic table.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_iter_with_elems() {
|
||||||
|
let mut table = DynamicTable::new();
|
||||||
|
table.add_header(b"a".to_vec(), b"b".to_vec());
|
||||||
|
table.add_header(b"123".to_vec(), b"456".to_vec());
|
||||||
|
table.add_header(b"c".to_vec(), b"d".to_vec());
|
||||||
|
|
||||||
|
let iter_res: Vec<(&[u8], &[u8])> = table.iter().collect();
|
||||||
|
|
||||||
|
let expected: Vec<(&[u8], &[u8])> = vec![
|
||||||
|
(b"c", b"d"),
|
||||||
|
(b"123", b"456"),
|
||||||
|
(b"a", b"b"),
|
||||||
|
];
|
||||||
|
assert_eq!(iter_res, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that the iterator through the `DynamicTable` works when there are
|
||||||
|
/// no elements in the dynamic table.
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_table_iter_no_elems() {
|
||||||
|
let table = DynamicTable::new();
|
||||||
|
|
||||||
|
let iter_res: Vec<(&[u8], &[u8])> = table.iter().collect();
|
||||||
|
|
||||||
|
let expected = vec![];
|
||||||
|
assert_eq!(iter_res, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that indexing the header table with indices that correspond to
|
||||||
|
/// entries found in the static table works.
|
||||||
|
#[test]
|
||||||
|
fn test_header_table_index_static() {
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
|
||||||
|
for (index, entry) in STATIC_TABLE.iter().enumerate() {
|
||||||
|
assert_eq!(table.get_from_table(index + 1).unwrap(), *entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that when the given index is out of bounds, the `HeaderTable`
|
||||||
|
/// returns a `None`
|
||||||
|
#[test]
|
||||||
|
fn test_header_table_index_out_of_bounds() {
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
|
||||||
|
assert!(table.get_from_table(0).is_none());
|
||||||
|
assert!(table.get_from_table(STATIC_TABLE.len() + 1).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that adding entries to the dynamic table through the
|
||||||
|
/// `HeaderTable` interface works.
|
||||||
|
#[test]
|
||||||
|
fn test_header_table_add_to_dynamic() {
|
||||||
|
let mut table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let header = (b"a".to_vec(), b"b".to_vec());
|
||||||
|
|
||||||
|
table.add_header(header.0.clone(), header.1.clone());
|
||||||
|
|
||||||
|
assert_eq!(table.dynamic_table.to_vec(), vec![header]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that indexing the header table with indices that correspond to
|
||||||
|
/// entries found in the dynamic table works.
|
||||||
|
#[test]
|
||||||
|
fn test_header_table_index_dynamic() {
|
||||||
|
let mut table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let header = (b"a".to_vec(), b"b".to_vec());
|
||||||
|
|
||||||
|
table.add_header(header.0.clone(), header.1.clone());
|
||||||
|
|
||||||
|
assert_eq!(table.get_from_table(STATIC_TABLE.len() + 1).unwrap(),
|
||||||
|
((&header.0[..], &header.1[..])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that the `iter` method of the `HeaderTable` returns an iterator
|
||||||
|
/// through *all* the headers found in the header table (static and dynamic
|
||||||
|
/// tables both included)
|
||||||
|
#[test]
|
||||||
|
fn test_header_table_iter() {
|
||||||
|
let mut table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let headers: [(&[u8], &[u8]); 2] = [
|
||||||
|
(b"a", b"b"),
|
||||||
|
(b"c", b"d"),
|
||||||
|
];
|
||||||
|
for header in headers.iter() {
|
||||||
|
table.add_header(header.0.to_vec(), header.1.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
let iterated: Vec<(&[u8], &[u8])> = table.iter().collect();
|
||||||
|
|
||||||
|
assert_eq!(iterated.len(), headers.len() + STATIC_TABLE.len());
|
||||||
|
// Part of the static table correctly iterated through
|
||||||
|
for (h1, h2) in iterated.iter().zip(STATIC_TABLE.iter()) {
|
||||||
|
assert_eq!(h1, h2);
|
||||||
|
}
|
||||||
|
// Part of the dynamic table correctly iterated through: the elements
|
||||||
|
// are in reversed order of insertion in the dynamic table.
|
||||||
|
for (h1, h2) in iterated.iter().skip(STATIC_TABLE.len())
|
||||||
|
.zip(headers.iter().rev()) {
|
||||||
|
assert_eq!(h1, h2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that searching for an entry in the header table, which should be
|
||||||
|
/// fully in the static table (both name and value), works correctly.
|
||||||
|
#[test]
|
||||||
|
fn test_find_header_static_full() {
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
|
||||||
|
for (i, h) in STATIC_TABLE.iter().enumerate() {
|
||||||
|
assert_eq!(table.find_header(*h).unwrap(),
|
||||||
|
(i + 1, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that searching for an entry in the header table, which should be
|
||||||
|
/// only partially in the static table (only the name), works correctly.
|
||||||
|
#[test]
|
||||||
|
fn test_find_header_static_partial() {
|
||||||
|
{
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let h: (&[u8], &[u8]) = (b":method", b"PUT");
|
||||||
|
|
||||||
|
if let (index, false) = table.find_header(h).unwrap() {
|
||||||
|
assert_eq!(h.0, STATIC_TABLE[index - 1].0);
|
||||||
|
// The index is the last one with the corresponding name
|
||||||
|
assert_eq!(3, index);
|
||||||
|
} else {
|
||||||
|
panic!("The header should have matched only partially");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let h: (&[u8], &[u8]) = (b":status", b"333");
|
||||||
|
|
||||||
|
if let (index, false) = table.find_header(h).unwrap() {
|
||||||
|
assert_eq!(h.0, STATIC_TABLE[index - 1].0);
|
||||||
|
// The index is the last one with the corresponding name
|
||||||
|
assert_eq!(14, index);
|
||||||
|
} else {
|
||||||
|
panic!("The header should have matched only partially");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let h: (&[u8], &[u8]) = (b":authority", b"example.com");
|
||||||
|
|
||||||
|
if let (index, false) = table.find_header(h).unwrap() {
|
||||||
|
assert_eq!(h.0, STATIC_TABLE[index - 1].0);
|
||||||
|
} else {
|
||||||
|
panic!("The header should have matched only partially");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let h: (&[u8], &[u8]) = (b"www-authenticate", b"asdf");
|
||||||
|
|
||||||
|
if let (index, false) = table.find_header(h).unwrap() {
|
||||||
|
assert_eq!(h.0, STATIC_TABLE[index - 1].0);
|
||||||
|
} else {
|
||||||
|
panic!("The header should have matched only partially");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Tests that searching for an entry in the header table, which should be
|
||||||
|
/// fully in the dynamic table (both name and value), works correctly.
|
||||||
|
#[test]
|
||||||
|
fn test_find_header_dynamic_full() {
|
||||||
|
let mut table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
let h: (&[u8], &[u8]) = (b":method", b"PUT");
|
||||||
|
table.add_header(h.0.to_vec(), h.1.to_vec());
|
||||||
|
|
||||||
|
if let (index, true) = table.find_header(h).unwrap() {
|
||||||
|
assert_eq!(index, STATIC_TABLE.len() + 1);
|
||||||
|
} else {
|
||||||
|
panic!("The header should have matched fully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that searching for an entry in the header table, which should be
|
||||||
|
/// only partially in the dynamic table (only the name), works correctly.
|
||||||
|
#[test]
|
||||||
|
fn test_find_header_dynamic_partial() {
|
||||||
|
let mut table = HeaderTable::with_static_table(STATIC_TABLE);
|
||||||
|
// First add it to the dynamic table
|
||||||
|
{
|
||||||
|
let h = (b"X-Custom-Header", b"stuff");
|
||||||
|
table.add_header(h.0.to_vec(), h.1.to_vec());
|
||||||
|
}
|
||||||
|
// Prepare a search
|
||||||
|
let h: (&[u8], &[u8]) = (b"X-Custom-Header", b"different-stuff");
|
||||||
|
|
||||||
|
// It must match only partially
|
||||||
|
if let (index, false) = table.find_header(h).unwrap() {
|
||||||
|
// The index must be the first one in the dynamic table
|
||||||
|
// segment of the header table.
|
||||||
|
assert_eq!(index, STATIC_TABLE.len() + 1);
|
||||||
|
} else {
|
||||||
|
panic!("The header should have matched only partially");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tokio_io;
|
extern crate tokio_io;
|
||||||
|
|
||||||
extern crate tokio_timer;
|
extern crate tokio_timer;
|
||||||
|
|
||||||
|
// HTTP types
|
||||||
|
extern crate tower;
|
||||||
|
|
||||||
|
// Buffer utilities
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod hpack;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use ConnectionError;
|
use ConnectionError;
|
||||||
use frame::Frame;
|
use frame::{self, Frame, Kind};
|
||||||
|
|
||||||
use tokio_io::AsyncWrite;
|
use tokio_io::AsyncWrite;
|
||||||
|
|
||||||
@@ -10,6 +10,10 @@ use std::io::{self, Write};
|
|||||||
|
|
||||||
pub struct FramedRead<T> {
|
pub struct FramedRead<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
|
|
||||||
|
// hpack decoder state
|
||||||
|
// hpack: hpack::Decoder,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FramedRead<T>
|
impl<T> FramedRead<T>
|
||||||
@@ -17,7 +21,9 @@ impl<T> FramedRead<T>
|
|||||||
T: AsyncWrite,
|
T: AsyncWrite,
|
||||||
{
|
{
|
||||||
pub fn new(inner: T) -> FramedRead<T> {
|
pub fn new(inner: T) -> FramedRead<T> {
|
||||||
FramedRead { inner: inner }
|
FramedRead {
|
||||||
|
inner: inner,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,14 +34,36 @@ impl<T> Stream for FramedRead<T>
|
|||||||
type Error = ConnectionError;
|
type Error = ConnectionError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Frame>, ConnectionError> {
|
fn poll(&mut self) -> Poll<Option<Frame>, ConnectionError> {
|
||||||
match try_ready!(self.inner.poll()) {
|
let mut bytes = match try_ready!(self.inner.poll()) {
|
||||||
Some(bytes) => {
|
Some(bytes) => bytes,
|
||||||
Frame::load(bytes.freeze())
|
None => return Ok(Async::Ready(None)),
|
||||||
.map(|frame| Async::Ready(Some(frame)))
|
};
|
||||||
.map_err(ConnectionError::from)
|
|
||||||
|
// Parse the head
|
||||||
|
let head = frame::Head::parse(&bytes);
|
||||||
|
|
||||||
|
let frame = match head.kind() {
|
||||||
|
Kind::Data => unimplemented!(),
|
||||||
|
Kind::Headers => unimplemented!(),
|
||||||
|
Kind::Priority => unimplemented!(),
|
||||||
|
Kind::Reset => unimplemented!(),
|
||||||
|
Kind::Settings => {
|
||||||
|
let frame = try!(frame::Settings::load(head, &bytes[frame::HEADER_LEN..]));
|
||||||
|
frame.into()
|
||||||
}
|
}
|
||||||
None => Ok(Async::Ready(None)),
|
Kind::PushPromise => unimplemented!(),
|
||||||
}
|
Kind::Ping => unimplemented!(),
|
||||||
|
Kind::GoAway => unimplemented!(),
|
||||||
|
Kind::WindowUpdate => unimplemented!(),
|
||||||
|
Kind::Continuation => unimplemented!(),
|
||||||
|
Kind::Unknown => {
|
||||||
|
let _ = bytes.split_to(frame::HEADER_LEN);
|
||||||
|
frame::Unknown::new(head, bytes.freeze()).into()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Async::Ready(Some(frame)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user