work
This commit is contained in:
1741
src/hpack/decoder.rs
1741
src/hpack/decoder.rs
File diff suppressed because it is too large
Load Diff
@@ -1,478 +1 @@
|
||||
//! 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));
|
||||
}
|
||||
}
|
||||
pub struct Encoder;
|
||||
|
||||
@@ -1,702 +0,0 @@
|
||||
//! 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"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
759
src/hpack/mod.rs
759
src/hpack/mod.rs
@@ -1,756 +1,7 @@
|
||||
//! A module implementing HPACK functionality. Exposes a simple API for
|
||||
//! performing the encoding and decoding of header sets, according to the
|
||||
//! HPACK spec.
|
||||
mod encoder;
|
||||
mod decoder;
|
||||
mod table;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use self::decoder::Decoder;
|
||||
pub use self::table::Entry;
|
||||
|
||||
55
src/hpack/table.rs
Normal file
55
src/hpack/table.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use tower::http::{HeaderName, StatusCode, Str};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// HPack table entry
|
||||
pub enum Entry {
|
||||
Header {
|
||||
name: HeaderName,
|
||||
value: Str,
|
||||
},
|
||||
Authority(Str),
|
||||
Scheme(Str),
|
||||
Path(Str),
|
||||
Status(StatusCode),
|
||||
}
|
||||
|
||||
/// Get an entry from the static table
|
||||
pub fn get_static(idx: usize) -> Entry {
|
||||
match idx {
|
||||
1 => unimplemented!(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub struct Table {
|
||||
entries: VecDeque<HeaderPair>,
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
pub enum Entry {
|
||||
Header {
|
||||
name: HeaderName,
|
||||
value: Str,
|
||||
},
|
||||
Authority(Str),
|
||||
Scheme(Str),
|
||||
Path(Str),
|
||||
Status(StatusCode),
|
||||
}
|
||||
impl Table {
|
||||
pub fn new(max_size: usize) -> Table {
|
||||
Table {
|
||||
entries: VecDeque::new(),
|
||||
max_size: max_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Table {
|
||||
fn default() -> Table {
|
||||
// Default maximum size from the HTTP/2.0 spec.
|
||||
Table::new(4_096)
|
||||
}
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user