From 769f3f142f08df1faf1fd51d16489499e08cb25b Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Sat, 11 Mar 2017 12:59:15 -0800 Subject: [PATCH] More work --- Cargo.toml | 1 + src/frame/head.rs | 2 +- src/frame/headers.rs | 20 + src/frame/mod.rs | 5 +- src/frame/settings.rs | 4 +- src/frame/unknown.rs | 8 +- src/hpack/decoder.rs | 1594 ++++++++++++++++++++++++++++++++++++++ src/hpack/encoder.rs | 478 ++++++++++++ src/hpack/huffman.rs | 702 +++++++++++++++++ src/hpack/mod.rs | 756 ++++++++++++++++++ src/lib.rs | 8 + src/proto/framed_read.rs | 46 +- 12 files changed, 3608 insertions(+), 16 deletions(-) create mode 100644 src/hpack/decoder.rs create mode 100644 src/hpack/encoder.rs create mode 100644 src/hpack/huffman.rs create mode 100644 src/hpack/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 26b5721..ab2dc81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ futures = "0.1" tokio-io = { git = "https://github.com/alexcrichton/tokio-io" } tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" } bytes = { git = "https://github.com/carllerche/bytes" } +tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" } [replace] "futures:0.1.10" = { git = "https://github.com/alexcrichton/futures-rs", branch = "close" } diff --git a/src/frame/head.rs b/src/frame/head.rs index 202640b..c215836 100644 --- a/src/frame/head.rs +++ b/src/frame/head.rs @@ -62,7 +62,7 @@ impl Head { } pub fn encode_len(&self) -> usize { - super::FRAME_HEADER_LEN + super::HEADER_LEN } pub fn encode(&self, payload_len: usize, dst: &mut T) -> Result<(), Error> { diff --git a/src/frame/headers.rs b/src/frame/headers.rs index e69de29..076369c 100644 --- a/src/frame/headers.rs +++ b/src/frame/headers.rs @@ -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<()>, +} diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 0f89055..dbb05ea 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -35,7 +35,7 @@ pub use self::head::{Head, Kind, StreamId}; pub use self::settings::{Settings, SettingSet}; pub use self::unknown::Unknown; -const FRAME_HEADER_LEN: usize = 9; +pub const HEADER_LEN: usize = 9; #[derive(Debug, Clone, PartialEq)] pub enum Frame { @@ -103,8 +103,7 @@ impl Frame { let head = Head::parse(&frame); // Extract the payload from the frame - let _ = frame.drain_to(FRAME_HEADER_LEN); - + let _ = frame.drain_to(HEADER_LEN); match head.kind() { Kind::Unknown => { diff --git a/src/frame/settings.rs b/src/frame/settings.rs index dc5fe5b..8e11a29 100644 --- a/src/frame/settings.rs +++ b/src/frame/settings.rs @@ -54,7 +54,7 @@ impl Settings { } } - pub fn load(head: Head, payload: Bytes) -> Result { + pub fn load(head: Head, payload: &[u8]) -> Result { use self::Setting::*; debug_assert_eq!(head.kind(), ::frame::Kind::Settings); @@ -112,7 +112,7 @@ impl Settings { } pub fn encode_len(&self) -> usize { - super::FRAME_HEADER_LEN + self.payload_len() + super::HEADER_LEN + self.payload_len() } fn payload_len(&self) -> usize { diff --git a/src/frame/unknown.rs b/src/frame/unknown.rs index 0531999..77cbb67 100644 --- a/src/frame/unknown.rs +++ b/src/frame/unknown.rs @@ -1,4 +1,4 @@ -use frame::{Head, Error}; +use frame::{Frame, Head, Error}; use bytes::{Bytes, BytesMut, BufMut}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -25,3 +25,9 @@ impl Unknown { Ok(()) } } + +impl From for Frame { + fn from(src: Unknown) -> Frame { + Frame::Unknown(src) + } +} diff --git a/src/hpack/decoder.rs b/src/hpack/decoder.rs new file mode 100644 index 0000000..42ea5fd --- /dev/null +++ b/src/hpack/decoder.rs @@ -0,0 +1,1594 @@ +//! Exposes the struct `Decoder` that allows for HPACK-encoded header blocks to +//! be decoded into a header list. +//! +//! The decoder only follows HPACK rules, without performing any additional +//! (semantic) checks on the header name/value pairs, i.e. it considers the +//! headers as opaque octets. +//! +//! # Example +//! +//! A simple example of using the decoder that demonstrates its API: +//! +//! ```rust +//! use hpack::Decoder; +//! let mut decoder = Decoder::new(); +//! +//! let header_list = decoder.decode(&[0x82, 0x84]).unwrap(); +//! +//! assert_eq!(header_list, [ +//! (b":method".to_vec(), b"GET".to_vec()), +//! (b":path".to_vec(), b"/".to_vec()), +//! ]); +//! ``` +//! +//! A more complex example where the callback API is used, providing the client a +//! borrowed representation of each header, rather than an owned representation. +//! +//! ```rust +//! use hpack::Decoder; +//! let mut decoder = Decoder::new(); +//! +//! let mut count = 0; +//! let header_list = decoder.decode_with_cb(&[0x82, 0x84], |name, value| { +//! count += 1; +//! match count { +//! 1 => { +//! assert_eq!(&name[..], &b":method"[..]); +//! assert_eq!(&value[..], &b"GET"[..]); +//! }, +//! 2 => { +//! assert_eq!(&name[..], &b":path"[..]); +//! assert_eq!(&value[..], &b"/"[..]); +//! }, +//! _ => panic!("Did not expect more than two headers!"), +//! }; +//! }); +//! ``` + +use std::cmp; +use std::num::Wrapping; +use std::borrow::Cow; + +use super::huffman::HuffmanDecoder; +use super::huffman::HuffmanDecoderError; + +use super::STATIC_TABLE; +use super::{StaticTable, HeaderTable}; + +/// Decodes an integer encoded with a given prefix size (in bits). +/// Assumes that the buffer `buf` contains the integer to be decoded, +/// with the first byte representing the octet that contains the +/// prefix. +/// +/// Returns a tuple representing the decoded integer and the number +/// of bytes from the buffer that were used. +fn decode_integer(buf: &[u8], prefix_size: u8) + -> Result<(usize, usize), DecoderError> { + if prefix_size < 1 || prefix_size > 8 { + return Err( + DecoderError::IntegerDecodingError( + IntegerDecodingError::InvalidPrefix)); + } + if buf.len() < 1 { + return Err( + DecoderError::IntegerDecodingError( + IntegerDecodingError::NotEnoughOctets)); + } + + // Make sure there's no overflow in the shift operation + let Wrapping(mask) = if prefix_size == 8 { + Wrapping(0xFF) + } else { + Wrapping(1u8 << prefix_size) - Wrapping(1) + }; + let mut value = (buf[0] & mask) as usize; + if value < (mask as usize) { + // Value fits in the prefix bits. + return Ok((value, 1)); + } + + // The value does not fit into the prefix bits, so we read as many following + // bytes as necessary to decode the integer. + // Already one byte used (the prefix) + let mut total = 1; + let mut m = 0; + // The octet limit is chosen such that the maximum allowed *value* can + // never overflow an unsigned 32-bit integer. The maximum value of any + // integer that can be encoded with 5 octets is ~2^28 + let octet_limit = 5; + + for &b in buf[1..].iter() { + total += 1; + value += ((b & 127) as usize) * (1 << m); + m += 7; + + if b & 128 != 128 { + // Most significant bit is not set => no more continuation bytes + return Ok((value, total)); + } + + if total == octet_limit { + // The spec tells us that we MUST treat situations where the + // encoded representation is too long (in octets) as an error. + return Err( + DecoderError::IntegerDecodingError( + IntegerDecodingError::TooManyOctets)) + } + } + + // If we have reached here, it means the buffer has been exhausted without + // hitting the termination condition. + Err(DecoderError::IntegerDecodingError( + IntegerDecodingError::NotEnoughOctets)) +} + +/// Decodes an octet string under HPACK rules of encoding found in the given +/// buffer `buf`. +/// +/// It is assumed that the first byte in the buffer represents the start of the +/// encoded octet string. +/// +/// Returns the decoded string in a newly allocated `Vec` and the number of +/// bytes consumed from the given buffer. +fn decode_string<'a>(buf: &'a [u8]) -> Result<(Cow<'a, [u8]>, usize), DecoderError> { + let (len, consumed) = try!(decode_integer(buf, 7)); + if consumed + len > buf.len() { + return Err( + DecoderError::StringDecodingError( + StringDecodingError::NotEnoughOctets)); + } + let raw_string = &buf[consumed..consumed + len]; + if buf[0] & 128 == 128 { + // Huffman coding used: pass the raw octets to the Huffman decoder + // and return its result. + let mut decoder = HuffmanDecoder::new(); + let decoded = match decoder.decode(raw_string) { + Err(e) => { + return Err(DecoderError::StringDecodingError( + StringDecodingError::HuffmanDecoderError(e))); + }, + Ok(res) => res, + }; + Ok((Cow::Owned(decoded), consumed + len)) + } else { + // The octets were transmitted raw + Ok((Cow::Borrowed(raw_string), consumed + len)) + } +} + +/// Different variants of how a particular header field can be represented in +/// an HPACK encoding. +enum FieldRepresentation { + Indexed, + LiteralWithIncrementalIndexing, + SizeUpdate, + LiteralNeverIndexed, + LiteralWithoutIndexing, +} + +impl FieldRepresentation { + /// Based on the given octet, returns the type of the field representation. + /// + /// The given octet should be the top-order byte of the header field that + /// is about to be decoded. + fn new(octet: u8) -> FieldRepresentation { + if octet & 128 == 128 { + // High-order bit set + FieldRepresentation::Indexed + } else if octet & 64 == 64 { + // Bit pattern `01` + FieldRepresentation::LiteralWithIncrementalIndexing + } else if octet & 32 == 32 { + // Bit pattern `001` + FieldRepresentation::SizeUpdate + } else if octet & 16 == 16 { + // Bit pattern `0001` + FieldRepresentation::LiteralNeverIndexed + } else { + // None of the top 4 bits is set => bit pattern `0000xxxx` + FieldRepresentation::LiteralWithoutIndexing + } + } +} + +/// Represents all errors that can be encountered while decoding an +/// integer. +#[derive(PartialEq)] +#[derive(Copy)] +#[derive(Clone)] +#[derive(Debug)] +pub enum IntegerDecodingError { + /// 5.1. specifies that "excessively large integer decodings" MUST be + /// considered an error (whether the size is the number of octets or + /// value). This variant corresponds to the encoding containing too many + /// octets. + TooManyOctets, + /// The variant corresponds to the case where the value of the integer + /// being decoded exceeds a certain threshold. + ValueTooLarge, + /// When a buffer from which an integer was supposed to be encoded does + /// not contain enough octets to complete the decoding. + NotEnoughOctets, + /// Only valid prefixes are [1, 8] + InvalidPrefix, +} + +/// Represents all errors that can be encountered while decoding an octet +/// string. +#[derive(PartialEq)] +#[derive(Copy)] +#[derive(Clone)] +#[derive(Debug)] +pub enum StringDecodingError { + NotEnoughOctets, + HuffmanDecoderError(HuffmanDecoderError), +} + +/// Represents all errors that can be encountered while performing the decoding +/// of an HPACK header set. +#[derive(PartialEq)] +#[derive(Copy)] +#[derive(Clone)] +#[derive(Debug)] +pub enum DecoderError { + HeaderIndexOutOfBounds, + IntegerDecodingError(IntegerDecodingError), + StringDecodingError(StringDecodingError), + /// The size of the dynamic table can never be allowed to exceed the max + /// size mandated to the decoder by the protocol. (by perfroming changes + /// made by SizeUpdate blocks). + InvalidMaxDynamicSize, + ExpectedDynamicSizeUpdate, +} + +/// The result returned by the `decode` method of the `Decoder`. +pub type DecoderResult = Result, Vec)>, DecoderError>; + +/// Decodes headers encoded using HPACK. +/// +/// For now, incremental decoding is not supported, i.e. it is necessary +/// to pass in the entire encoded representation of all headers to the +/// decoder, rather than processing it piece-by-piece. +pub struct Decoder<'a> { + // The dynamic table will own its own copy of headers + header_table: HeaderTable<'a>, + + // Protocol indicated that the max table size will update + max_size_update: Option, +} + +/// Represents a decoder of HPACK encoded headers. Maintains the state +/// necessary to correctly decode subsequent HPACK blocks. +impl<'a> Decoder<'a> { + /// Creates a new `Decoder` with all settings set to default values. + pub fn new() -> Decoder<'a> { + Decoder::with_static_table(STATIC_TABLE) + } + + /// Creates a new `Decoder` with the given slice serving as its static + /// table. + /// + /// The slice should contain tuples where the tuple coordinates represent + /// the header name and value, respectively. + /// + /// Note: in order for the final decoded content to match the encoding + /// (according to the standard, at least), this static table must be + /// the one defined in the HPACK spec. + fn with_static_table(static_table: StaticTable<'a>) -> Decoder<'a> { + Decoder { + header_table: HeaderTable::with_static_table(static_table), + max_size_update: None, + } + } + + pub fn update_max_size(&mut self, size: usize) { + let size = match self.max_size_update { + Some(v) => cmp::min(v, size), + None => size, + }; + + self.max_size_update = Some(size); + } + + /// Decodes the headers found in the given buffer `buf`. Invokes the callback `cb` for each + /// decoded header in turn, by providing it the header name and value as `Cow` byte array + /// slices. + /// + /// The callback is free to decide how to handle the emitted header, however the `Cow` cannot + /// outlive the closure body without assuming ownership or otherwise copying the contents. + /// + /// This is due to the fact that the header might be found (fully or partially) in the header + /// table of the decoder, in which case the callback will have received a borrow of its + /// contents. However, when one of the following headers is decoded, it is possible that the + /// header table might have to be modified; so the borrow is only valid until the next header + /// decoding begins, meaning until the end of the callback's body. + /// + /// If an error is encountered during the decoding of any header, decoding halts and the + /// appropriate error is returned as the `Err` variant of the `Result`. + pub fn decode_with_cb(&mut self, buf: &[u8], mut cb: F) -> Result<(), DecoderError> + where F: FnMut(Cow<[u8]>, Cow<[u8]>) + { + let mut current_octet_index = 0; + let mut can_resize = true; + let mut resized = false; + + while current_octet_index < buf.len() { + // At this point we are always at the beginning of the next block + // within the HPACK data. + // The type of the block can always be determined from the first + // byte. + let initial_octet = buf[current_octet_index]; + let buffer_leftover = &buf[current_octet_index..]; + let repr = FieldRepresentation::new(initial_octet); + + match repr { + FieldRepresentation::SizeUpdate => {} + _ => { + can_resize = false; + } + } + + let consumed = match FieldRepresentation::new(initial_octet) { + FieldRepresentation::Indexed => { + let ((name, value), consumed) = + try!(self.decode_indexed(buffer_leftover)); + cb(Cow::Borrowed(name), Cow::Borrowed(value)); + + consumed + }, + FieldRepresentation::LiteralWithIncrementalIndexing => { + let ((name, value), consumed) = { + let ((name, value), consumed) = try!( + self.decode_literal(buffer_leftover, true)); + cb(Cow::Borrowed(&name), Cow::Borrowed(&value)); + + // Since we are to add the decoded header to the header table, we need to + // convert them into owned buffers that the decoder can keep internally. + let name = name.into_owned(); + let value = value.into_owned(); + + ((name, value), consumed) + }; + // This cannot be done in the same scope as the `decode_literal` call, since + // Rust cannot figure out that the `into_owned` calls effectively drop the + // borrow on `self` that the `decode_literal` return value had. Since adding + // a header to the table requires a `&mut self`, it fails to compile. + // Manually separating it out here works around it... + self.header_table.add_header(name, value); + + consumed + }, + FieldRepresentation::LiteralWithoutIndexing => { + let ((name, value), consumed) = + try!(self.decode_literal(buffer_leftover, false)); + cb(name, value); + + consumed + }, + FieldRepresentation::LiteralNeverIndexed => { + // Same as the previous one, except if we were also a proxy + // we would need to make sure not to change the + // representation received here. We don't care about this + // for now. + let ((name, value), consumed) = + try!(self.decode_literal(buffer_leftover, false)); + cb(name, value); + + consumed + }, + FieldRepresentation::SizeUpdate => { + let proto_max = match self.max_size_update { + Some(max) if can_resize => max, + _ => { + return Err(DecoderError::InvalidMaxDynamicSize); + } + }; + + resized = true; + + // Handle the dynamic table size update... + try!(self.update_max_dynamic_size(proto_max, buffer_leftover)) + } + }; + + current_octet_index += consumed; + } + + self.max_size_update = None; + + Ok(()) + } + + /// Decode the header block found in the given buffer. + /// + /// The decoded representation is returned as a sequence of headers, where both the name and + /// value of each header is represented by an owned byte sequence (i.e. `Vec`). + /// + /// The buffer should represent the entire block that should be decoded. + /// For example, in HTTP/2, all continuation frames need to be concatenated + /// to a single buffer before passing them to the decoder. + pub fn decode(&mut self, buf: &[u8]) -> DecoderResult { + let mut header_list = Vec::new(); + + try!(self.decode_with_cb(buf, |n, v| header_list.push((n.into_owned(), v.into_owned())))); + + Ok(header_list) + } + + /// Decodes an indexed header representation. + fn decode_indexed(&self, buf: &[u8]) + -> Result<((&[u8], &[u8]), usize), DecoderError> { + let (index, consumed) = try!(decode_integer(buf, 7)); + + let (name, value) = try!(self.get_from_table(index)); + + Ok(((name, value), consumed)) + } + + /// Gets the header (name, value) pair with the given index from the table. + /// + /// In this context, the "table" references the definition of the table + /// where the static table is concatenated with the dynamic table and is + /// 1-indexed. + fn get_from_table(&self, index: usize) + -> Result<(&[u8], &[u8]), DecoderError> { + self.header_table.get_from_table(index).ok_or( + DecoderError::HeaderIndexOutOfBounds) + } + + /// Decodes a literal header representation from the given buffer. + /// + /// # Parameters + /// + /// - index: whether or not the decoded value should be indexed (i.e. + /// included in the dynamic table). + fn decode_literal<'b>(&'b self, buf: &'b [u8], index: bool) + -> Result<((Cow<[u8]>, Cow<[u8]>), usize), DecoderError> { + let prefix = if index { + 6 + } else { + 4 + }; + let (table_index, mut consumed) = try!(decode_integer(buf, prefix)); + + // First read the name appropriately + let name = if table_index == 0 { + // Read name string as literal + let (name, name_len) = try!(decode_string(&buf[consumed..])); + consumed += name_len; + name + } else { + // Read name indexed from the table + let (name, _) = try!(self.get_from_table(table_index)); + Cow::Borrowed(name) + }; + + // Now read the value as a literal... + let (value, value_len) = try!(decode_string(&buf[consumed..])); + consumed += value_len; + + Ok(((name, value), consumed)) + } + + /// Handles processing the `SizeUpdate` HPACK block: updates the maximum + /// size of the underlying dynamic table, possibly causing a number of + /// headers to be evicted from it. + /// + /// Assumes that the first byte in the given buffer `buf` is the first + /// octet in the `SizeUpdate` block. + /// + /// Returns the number of octets consumed from the given buffer. + fn update_max_dynamic_size(&mut self, proto_max: usize, buf: &[u8]) -> Result { + let (new_size, consumed) = decode_integer(buf, 5).ok().unwrap(); + + if new_size > proto_max { + return Err(DecoderError::InvalidMaxDynamicSize); + } + + self.header_table.dynamic_table.set_max_table_size(new_size); + + Ok(consumed) + } +} + +#[cfg(test)] +mod tests { + use super::{decode_integer}; + + use std::borrow::Cow; + + use super::super::encoder::encode_integer; + use super::FieldRepresentation; + use super::decode_string; + use super::Decoder; + use super::{DecoderError, DecoderResult}; + use super::{IntegerDecodingError, StringDecodingError}; + use super::super::huffman::HuffmanDecoderError; + + /// Tests that valid integer encodings are properly decoded. + #[test] + fn test_decode_integer() { + assert_eq!((10, 1), + decode_integer(&[10], 5).ok().unwrap()); + assert_eq!((1337, 3), + decode_integer(&[31, 154, 10], 5).ok().unwrap()); + assert_eq!((1337, 3), + decode_integer(&[31 + 32, 154, 10], 5).ok().unwrap()); + assert_eq!((1337, 3), + decode_integer(&[31 + 64, 154, 10], 5).ok().unwrap()); + assert_eq!((1337, 3), + decode_integer(&[31, 154, 10, 111, 22], 5).ok().unwrap()); + + assert_eq!((127, 2), decode_integer(&[255, 0], 7).ok().unwrap()); + assert_eq!((127, 2), decode_integer(&[127, 0], 7).ok().unwrap()); + assert_eq!((255, 3), decode_integer(&[127, 128, 1], 7).ok().unwrap()); + assert_eq!((255, 2), decode_integer(&[255, 0], 8).unwrap()); + assert_eq!((254, 1), decode_integer(&[254], 8).unwrap()); + assert_eq!((1, 1), decode_integer(&[1], 8).unwrap()); + assert_eq!((0, 1), decode_integer(&[0], 8).unwrap()); + // The largest allowed integer correctly gets decoded... + assert_eq!( + (268435710, 5), + decode_integer(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF - 128], 8).ok().unwrap()); + } + + /// A helper macro that asserts that a given `DecoderResult` represents + /// the given `IntegerDecodingError`. + macro_rules! assert_integer_err ( + ($err_type:expr, $decoder_result:expr) => ( + assert_eq!($err_type, match $decoder_result { + Err(DecoderError::IntegerDecodingError(e)) => e, + _ => panic!("Expected a decoding error"), + }); + ); + ); + + /// Tests that some invalid integer encodings are detected and signalled as + /// errors. + #[test] + fn test_decode_integer_errors() { + assert_integer_err!(IntegerDecodingError::NotEnoughOctets, + decode_integer(&[], 5)); + assert_integer_err!(IntegerDecodingError::NotEnoughOctets, + decode_integer(&[0xFF, 0xFF], 5)); + assert_integer_err!(IntegerDecodingError::TooManyOctets, + decode_integer(&[0xFF, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80], 1)); + assert_integer_err!(IntegerDecodingError::TooManyOctets, + decode_integer(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0], 8)); + assert_integer_err!(IntegerDecodingError::InvalidPrefix, + decode_integer(&[10], 0)); + assert_integer_err!(IntegerDecodingError::InvalidPrefix, + decode_integer(&[10], 9)); + } + + #[test] + fn test_detect_literal_without_indexing() { + assert!(match FieldRepresentation::new(0) { + FieldRepresentation::LiteralWithoutIndexing => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 4) - 1) { + FieldRepresentation::LiteralWithoutIndexing => true, + _ => false, + }); + assert!(match FieldRepresentation::new(2) { + FieldRepresentation::LiteralWithoutIndexing => true, + _ => false, + }); + } + + #[test] + fn test_detect_literal_never_indexed() { + assert!(match FieldRepresentation::new(1 << 4) { + FieldRepresentation::LiteralNeverIndexed => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 4) + 15) { + FieldRepresentation::LiteralNeverIndexed => true, + _ => false, + }); + } + + #[test] + fn test_detect_literal_incremental_indexing() { + assert!(match FieldRepresentation::new(1 << 6) { + FieldRepresentation::LiteralWithIncrementalIndexing => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 6) + (1 << 4)) { + FieldRepresentation::LiteralWithIncrementalIndexing => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 7) - 1) { + FieldRepresentation::LiteralWithIncrementalIndexing => true, + _ => false, + }); + } + + #[test] + fn test_detect_indexed() { + assert!(match FieldRepresentation::new(1 << 7) { + FieldRepresentation::Indexed => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 7) + (1 << 4)) { + FieldRepresentation::Indexed => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 7) + (1 << 5)) { + FieldRepresentation::Indexed => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 7) + (1 << 6)) { + FieldRepresentation::Indexed => true, + _ => false, + }); + assert!(match FieldRepresentation::new(255) { + FieldRepresentation::Indexed => true, + _ => false, + }); + } + + #[test] + fn test_detect_dynamic_table_size_update() { + assert!(match FieldRepresentation::new(1 << 5) { + FieldRepresentation::SizeUpdate => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 5) + (1 << 4)) { + FieldRepresentation::SizeUpdate => true, + _ => false, + }); + assert!(match FieldRepresentation::new((1 << 6) - 1) { + FieldRepresentation::SizeUpdate => true, + _ => false, + }); + } + + #[test] + fn test_decode_string_no_huffman() { + /// Checks that the result matches the expectation, but also that the `Cow` is borrowed! + fn assert_borrowed_eq<'a>(expected: (&[u8], usize), result: (Cow<'a, [u8]>, usize)) { + let (expected_str, expected_len) = expected; + let (actual_str, actual_len) = result; + assert_eq!(expected_len, actual_len); + match actual_str { + Cow::Borrowed(actual) => assert_eq!(actual, expected_str), + _ => panic!("Expected the result to be borrowed!"), + }; + } + + assert_eq!((Cow::Borrowed(&b"abc"[..]), 4), + decode_string(&[3, b'a', b'b', b'c']).ok().unwrap()); + assert_eq!((Cow::Borrowed(&b"a"[..]), 2), + decode_string(&[1, b'a']).ok().unwrap()); + assert_eq!((Cow::Borrowed(&b""[..]), 1), + decode_string(&[0, b'a']).ok().unwrap()); + + assert_borrowed_eq((&b"abc"[..], 4), + decode_string(&[3, b'a', b'b', b'c']).ok().unwrap()); + assert_borrowed_eq((&b"a"[..], 2), + decode_string(&[1, b'a']).ok().unwrap()); + assert_borrowed_eq((&b""[..], 1), + decode_string(&[0, b'a']).ok().unwrap()); + + // Buffer smaller than advertised string length + assert_eq!(StringDecodingError::NotEnoughOctets, + match decode_string(&[3, b'a', b'b']) { + Err(DecoderError::StringDecodingError(e)) => e, + _ => panic!("Expected NotEnoughOctets error!"), + } + ); + } + + /// Tests that an octet string is correctly decoded when it's length + /// is longer than what can fit into the 7-bit prefix. + #[test] + fn test_decode_string_no_huffman_long() { + { + let full_string: Vec = (0u8..200).collect(); + let mut encoded = encode_integer(full_string.len(), 7); + encoded.extend(full_string.clone().into_iter()); + + assert_eq!( + (Cow::Owned(full_string), encoded.len()), + decode_string(&encoded).ok().unwrap()); + } + { + let full_string: Vec = (0u8..127).collect(); + let mut encoded = encode_integer(full_string.len(), 7); + encoded.extend(full_string.clone().into_iter()); + + assert_eq!( + (Cow::Owned(full_string), encoded.len()), + decode_string(&encoded).ok().unwrap()); + } + } + + /// Tests that a header list with only a single header found fully in the + /// static header table is correctly decoded. + /// (example from: HPACK-draft-10, C.2.4.) + #[test] + fn test_decode_fully_in_static_table() { + let mut decoder = Decoder::new(); + + let header_list = decoder.decode(&[0x82]).ok().unwrap(); + + assert_eq!(vec![(b":method".to_vec(), b"GET".to_vec())], header_list); + } + + #[test] + fn test_decode_multiple_fully_in_static_table() { + let mut decoder = Decoder::new(); + + let header_list = decoder.decode(&[0x82, 0x86, 0x84]).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"http".to_vec()), + (b":path".to_vec(), b"/".to_vec()), + ]); + } + + /// Tests that a literal with an indexed name and literal value is correctly + /// decoded. + /// (example from: HPACK-draft-10, C.2.2.) + #[test] + fn test_decode_literal_indexed_name() { + let mut decoder = Decoder::new(); + let hex_dump = [ + 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":path".to_vec(), b"/sample/path".to_vec()), + ]); + // Nothing was added to the dynamic table + assert_eq!(decoder.header_table.dynamic_table.len(), 0); + } + + /// Tests that a header with both a literal name and value is correctly + /// decoded. + /// (example from: HPACK-draft-10, C.2.1.) + #[test] + fn test_decode_literal_both() { + let mut decoder = Decoder::new(); + let hex_dump = [ + 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, + 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b"custom-key".to_vec(), b"custom-header".to_vec()), + ]); + // The entry got added to the dynamic table? + assert_eq!(decoder.header_table.dynamic_table.len(), 1); + let expected_table = vec![ + (b"custom-key".to_vec(), b"custom-header".to_vec()) + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + + /// Tests that a header with a name indexed from the dynamic table and a + /// literal value is correctly decoded. + #[test] + fn test_decode_literal_name_in_dynamic() { + let mut decoder = Decoder::new(); + { + // Prepares the context: the dynamic table contains a custom-key. + let hex_dump = [ + 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, + 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b"custom-key".to_vec(), b"custom-header".to_vec()), + ]); + // The entry got added to the dynamic table? + assert_eq!(decoder.header_table.dynamic_table.len(), 1); + let expected_table = vec![ + (b"custom-key".to_vec(), b"custom-header".to_vec()) + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + let hex_dump = [ + 0x40 + 62, // Index 62 in the table => 1st in dynamic table + 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x2d, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b"custom-key".to_vec(), b"custom-header-".to_vec()), + ]); + // The entry got added to the dynamic table, so now we have two? + assert_eq!(decoder.header_table.dynamic_table.len(), 2); + let expected_table = vec![ + (b"custom-key".to_vec(), b"custom-header-".to_vec()), + (b"custom-key".to_vec(), b"custom-header".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + } + + /// Tests that a header with a "never indexed" type is correctly + /// decoded. + /// (example from: HPACK-draft-10, C.2.3.) + #[test] + fn test_decode_literal_field_never_indexed() { + let mut decoder = Decoder::new(); + let hex_dump = [ + 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b"password".to_vec(), b"secret".to_vec()), + ]); + // Nothing was added to the dynamic table + assert_eq!(decoder.header_table.dynamic_table.len(), 0); + } + + /// Tests that a each header list from a sequence of requests is correctly + /// decoded. + /// (example from: HPACK-draft-10, C.3.*) + #[test] + fn test_request_sequence_no_huffman() { + let mut decoder = Decoder::new(); + { + // First Request (C.3.1.) + let hex_dump = [ + 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"http".to_vec()), + (b":path".to_vec(), b"/".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + ]); + // Only one entry got added to the dynamic table? + assert_eq!(decoder.header_table.dynamic_table.len(), 1); + let expected_table = vec![ + (b":authority".to_vec(), b"www.example.com".to_vec()) + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Second Request (C.3.2.) + let hex_dump = [ + 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, + 0x61, 0x63, 0x68, 0x65, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"http".to_vec()), + (b":path".to_vec(), b"/".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + (b"cache-control".to_vec(), b"no-cache".to_vec()), + ]); + // One entry got added to the dynamic table, so we have two? + let expected_table = vec![ + (b"cache-control".to_vec(), b"no-cache".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Third Request (C.3.3.) + let hex_dump = [ + 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"https".to_vec()), + (b":path".to_vec(), b"/index.html".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + (b"custom-key".to_vec(), b"custom-value".to_vec()), + ]); + // One entry got added to the dynamic table, so we have three at + // this point...? + let expected_table = vec![ + (b"custom-key".to_vec(), b"custom-value".to_vec()), + (b"cache-control".to_vec(), b"no-cache".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + } + + /// Tests that a each header list from a sequence of responses is correctly + /// decoded. + /// (example from: HPACK-draft-10, C.5.*) + #[test] + fn response_sequence_no_huffman() { + let mut decoder = Decoder::new(); + // The example sets the max table size to 256 octets. + decoder.set_max_table_size(256); + { + // First Response (C.5.1.) + let hex_dump = [ + 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, + 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, + 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, + 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":status".to_vec(), b"302".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + ]); + // All entries in the dynamic table too? + let expected_table = vec![ + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b":status".to_vec(), b"302".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Second Response (C.5.2.) + let hex_dump = [ + 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":status".to_vec(), b"307".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + ]); + // The new status replaces the old status in the table, since it + // cannot fit without evicting something from the table. + let expected_table = vec![ + (b":status".to_vec(), b"307".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Third Response (C.5.3.) + let hex_dump = [ + 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, + 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, + 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, + 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, + 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 0x44, 0x4a, + 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, + 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, + 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, + 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + let expected_header_list = [ + (b":status".to_vec(), b"200".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"content-encoding".to_vec(), b"gzip".to_vec()), + ( + b"set-cookie".to_vec(), + b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec() + ), + ]; + assert_eq!(header_list, expected_header_list); + // The new status replaces the old status in the table, since it + // cannot fit without evicting something from the table. + let expected_table = vec![ + ( + b"set-cookie".to_vec(), + b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec() + ), + (b"content-encoding".to_vec(), b"gzip".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + } + + /// Tests that when the decoder receives an update of the max dynamic table + /// size as 0, all entries are cleared from the dynamic table. + #[test] + fn test_decoder_clear_dynamic_table() { + let mut decoder = Decoder::new(); + { + let hex_dump = [ + 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, + 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, + 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, + 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":status".to_vec(), b"302".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + ]); + // All entries in the dynamic table too? + let expected_table = vec![ + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b":status".to_vec(), b"302".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + let hex_dump = [ + 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf, + // This instructs the decoder to clear the list + // (it's doubtful that it would ever be found there in a real + // response, though...) + 0x20, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + // Headers have been correctly decoded... + assert_eq!(header_list, [ + (b":status".to_vec(), b"307".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + ]); + // Expect an empty table! + let expected_table = vec![]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + assert_eq!(0, decoder.header_table.dynamic_table.get_max_table_size()); + } + } + + /// Tests that a each header list from a sequence of requests is correctly + /// decoded, when Huffman coding is used + /// (example from: HPACK-draft-10, C.4.*) + #[test] + fn request_sequence_huffman() { + let mut decoder = Decoder::new(); + { + // First Request (B.4.1.) + let hex_dump = [ + 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, + 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"http".to_vec()), + (b":path".to_vec(), b"/".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + ]); + // Only one entry got added to the dynamic table? + assert_eq!(decoder.header_table.dynamic_table.len(), 1); + let expected_table = vec![ + (b":authority".to_vec(), b"www.example.com".to_vec()) + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Second Request (C.4.2.) + let hex_dump = [ + 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, + 0x9c, 0xbf, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"http".to_vec()), + (b":path".to_vec(), b"/".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + (b"cache-control".to_vec(), b"no-cache".to_vec()), + ]); + // One entry got added to the dynamic table, so we have two? + let expected_table = vec![ + (b"cache-control".to_vec(), b"no-cache".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Third Request (C.4.3.) + let hex_dump = [ + 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, + 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, 0xa8, 0x49, 0xe9, 0x5b, + 0xb8, 0xe8, 0xb4, 0xbf, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"https".to_vec()), + (b":path".to_vec(), b"/index.html".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + (b"custom-key".to_vec(), b"custom-value".to_vec()), + ]); + // One entry got added to the dynamic table, so we have three at + // this point...? + let expected_table = vec![ + (b"custom-key".to_vec(), b"custom-value".to_vec()), + (b"cache-control".to_vec(), b"no-cache".to_vec()), + (b":authority".to_vec(), b"www.example.com".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + } + + /// Tests that a each header list from a sequence of responses is correctly + /// decoded, when Huffman encoding is used + /// (example from: HPACK-draft-10, C.6.*) + #[test] + fn response_sequence_huffman() { + let mut decoder = Decoder::new(); + // The example sets the max table size to 256 octets. + decoder.set_max_table_size(256); + { + // First Response (C.6.1.) + let hex_dump = [ + 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, + 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, + 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, + 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, + 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, + 0x82, 0xae, 0x43, 0xd3, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":status".to_vec(), b"302".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + ]); + // All entries in the dynamic table too? + let expected_table = vec![ + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b":status".to_vec(), b"302".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Second Response (C.6.2.) + let hex_dump = [ + 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + assert_eq!(header_list, [ + (b":status".to_vec(), b"307".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + ]); + // The new status replaces the old status in the table, since it + // cannot fit without evicting something from the table. + let expected_table = vec![ + (b":status".to_vec(), b"307".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + { + // Third Response (C.6.3.) + let hex_dump = [ + 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, + 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, + 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, + 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, + 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, + 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, + 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0, + 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07, + ]; + + let header_list = decoder.decode(&hex_dump).ok().unwrap(); + + let expected_header_list = [ + (b":status".to_vec(), b"200".to_vec()), + (b"cache-control".to_vec(), b"private".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()), + (b"location".to_vec(), b"https://www.example.com".to_vec()), + (b"content-encoding".to_vec(), b"gzip".to_vec()), + ( + b"set-cookie".to_vec(), + b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec() + ), + ]; + assert_eq!(header_list, expected_header_list); + // The new status replaces the old status in the table, since it + // cannot fit without evicting something from the table. + let expected_table = vec![ + ( + b"set-cookie".to_vec(), + b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec() + ), + (b"content-encoding".to_vec(), b"gzip".to_vec()), + (b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()), + ]; + let actual = decoder.header_table.dynamic_table.to_vec(); + assert_eq!(actual, expected_table); + } + } + + /// Helper function that verifies whether the given `DecoderResult` + /// indicates the given `DecoderError` + fn is_decoder_error(err: &DecoderError, result: &DecoderResult) -> bool { + match *result { + Err(ref e) => e == err, + _ => false + } + } + + /// Tests that when a header representation indicates an indexed header + /// encoding, but the index is out of valid bounds, the appropriate error + /// is returned by the decoder. + #[test] + fn test_index_out_of_bounds() { + let mut decoder = Decoder::new(); + // Some fixtures of raw messages which definitely need to cause an + // index out of bounds error. + let raw_messages = [ + // This indicates that the index of the header is 0, which is + // invalid... + vec![0x80], + // This indicates that the index of the header is 62, which is out + // of the bounds of the header table, given that there are no + // entries in the dynamic table and the static table contains 61 + // elements. + vec![0xbe], + // Literal encoded with an indexed name where the index is out of + // bounds. + vec![126, 1, 65], + ]; + + // Check them all... + for raw_message in raw_messages.iter() { + assert!( + is_decoder_error(&DecoderError::HeaderIndexOutOfBounds, + &decoder.decode(&raw_message)), + "Expected index out of bounds"); + } + } + + /// Tests that if a header encoded using a literal string representation + /// (using Huffman encoding) contains an invalid string encoding, an error + /// is returned. + #[test] + fn test_invalid_literal_huffman_string() { + let mut decoder = Decoder::new(); + // Invalid padding introduced into the message + let hex_dump = [ + 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, + 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xfe, + ]; + + assert!(match decoder.decode(&hex_dump) { + Err(DecoderError::StringDecodingError( + StringDecodingError::HuffmanDecoderError( + HuffmanDecoderError::InvalidPadding))) => true, + _ => false, + }); + } + + /// Tests that if the message cuts short before the header key is decoded, + /// we get an appropriate error. + #[test] + fn test_literal_header_key_incomplete() { + let mut decoder = Decoder::new(); + // The message does not have the length specifier of the header value + // (cuts short after the header key is complete) + let hex_dump = [ + 0x40, 0x0a, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', + ]; + + let result = decoder.decode(&hex_dump); + + assert!(match result { + Err(DecoderError::StringDecodingError( + StringDecodingError::NotEnoughOctets)) => true, + _ => false, + }); + } + + /// Tests that when a header is encoded as a literal with both a name and + /// a value, if the value is missing, we get an error. + #[test] + fn test_literal_header_missing_value() { + let mut decoder = Decoder::new(); + // The message does not have the length specifier of the header value + // (cuts short after the header key is complete) + let hex_dump = [ + 0x40, 0x0a, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', + b'y', + ]; + + let result = decoder.decode(&hex_dump); + + assert!(match result { + Err(DecoderError::IntegerDecodingError( + IntegerDecodingError::NotEnoughOctets)) => true, + _ => false, + }); + } +} + +/// The module defines interop tests between this HPACK decoder +/// and some other encoder implementations, based on their results +/// published at +/// [http2jp/hpack-test-case](https://github.com/http2jp/hpack-test-case) +#[cfg(feature="interop_tests")] +#[cfg(test)] +mod interop_tests { + use std::io::Read; + use std::fs::{self, File}; + use std::path::{Path, PathBuf}; + use std::collections::HashMap; + + use rustc_serialize::Decoder as JsonDecoder; + use rustc_serialize::{Decodable, json}; + use rustc_serialize::hex::FromHex; + + use super::Decoder; + + /// Defines the structure of a single part of a story file. We only care + /// about the bytes and corresponding headers and ignore the rest. + struct TestFixture { + wire_bytes: Vec, + headers: Vec<(Vec, Vec)>, + } + + /// Defines the structure corresponding to a full story file. We only + /// care about the cases for now. + #[derive(RustcDecodable)] + struct TestStory { + cases: Vec, + } + + /// A custom implementation of the `rustc_serialize::Decodable` trait for + /// `TestFixture`s. This is necessary for two reasons: + /// + /// - The original story files store the raw bytes as a hex-encoded + /// *string*, so we convert it to a `Vec` at parse time + /// - The original story files store the list of headers as an array of + /// objects, where each object has a single key. We convert this to a + /// more natural representation of a `Vec` of two-tuples. + /// + /// For an example of the test story JSON structure check the + /// `test_story_parser_sanity_check` test function or one of the fixtures + /// in the directory `fixtures/hpack/interop`. + impl Decodable for TestFixture { + fn decode(d: &mut D) -> Result { + d.read_struct("root", 0, |d| Ok(TestFixture { + wire_bytes: try!(d.read_struct_field("wire", 0, |d| { + // Read the `wire` field... + Decodable::decode(d).and_then(|res: String| { + // If valid, parse out the octets from the String by + // considering it a hex encoded byte sequence. + Ok(res.from_hex().unwrap()) + }) + })), + headers: try!(d.read_struct_field("headers", 0, |d| { + // Read the `headers` field... + d.read_seq(|d, len| { + // ...since it's an array, we step into the sequence + // and read each element. + let mut ret: Vec<(Vec, Vec)> = Vec::new(); + for i in (0..len) { + // Individual elements are encoded as a simple + // JSON object with one key: value pair. + let header: HashMap = try!( + d.read_seq_elt(i, |d| Decodable::decode(d))); + // We convert it to a tuple, which is a more + // natural representation of headers. + for (name, value) in header.into_iter() { + ret.push(( + name.as_bytes().to_vec(), + value.as_bytes().to_vec() + )); + } + } + Ok(ret) + }) + })), + })) + } + } + + /// Tests that the `TestStory` can be properly read out of a JSON encoded + /// string. Sanity check for the `Decodable` implementation. + #[test] + fn test_story_parser_sanity_check() { + let raw_json = stringify!( + { + "cases": [ + { + "seqno": 0, + "wire": "82864188f439ce75c875fa5784", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "yahoo.co.jp" + }, + { + ":path": "/" + } + ] + }, + { + "seqno": 1, + "wire": "8286418cf1e3c2fe8739ceb90ebf4aff84", + "headers": [ + { + ":method": "GET" + }, + { + ":scheme": "http" + }, + { + ":authority": "www.yahoo.co.jp" + }, + { + ":path": "/" + } + ] + } + ], + "draft": 9 + } + ); + + let decoded: TestStory = json::decode(raw_json).unwrap(); + + assert_eq!(decoded.cases.len(), 2); + assert_eq!(decoded.cases[0].wire_bytes, vec![ + 0x82, 0x86, 0x41, 0x88, 0xf4, 0x39, 0xce, 0x75, 0xc8, 0x75, 0xfa, + 0x57, 0x84 + ]); + assert_eq!(decoded.cases[0].headers, vec![ + (b":method".to_vec(), b"GET".to_vec()), + (b":scheme".to_vec(), b"http".to_vec()), + (b":authority".to_vec(), b"yahoo.co.jp".to_vec()), + (b":path".to_vec(), b"/".to_vec()), + ]); + } + + /// A helper function that performs an interop test for a given story file. + /// + /// It does so by first decoding the JSON representation of the story into + /// a `TestStory` struct. After this, each subsequent block of headers is + /// passed to the same decoder instance (since each story represents one + /// coder context). The result returned by the decoder is compared to the + /// headers stored for that particular block within the story file. + fn test_story(story_file_name: PathBuf) { + // Set up the story by parsing the given file + let story: TestStory = { + let mut file = File::open(&story_file_name).unwrap(); + let mut raw_story = String::new(); + file.read_to_string(&mut raw_story).unwrap(); + json::decode(&raw_story).unwrap() + }; + // Set up the decoder + let mut decoder = Decoder::new(); + + // Now check whether we correctly decode each case + for case in story.cases.iter() { + let decoded = decoder.decode(&case.wire_bytes).unwrap(); + assert_eq!(decoded, case.headers); + } + } + + /// Tests a full fixture set, provided a path to a directory containing a + /// number of story files (and no other file types). + /// + /// It calls the `test_story` function for each file found in the given + /// directory. + fn test_fixture_set(fixture_dir: &str) { + let files = fs::read_dir(&Path::new(fixture_dir)).unwrap(); + + for fixture in files { + let file_name = fixture.unwrap().path(); + test_story(file_name); + } + } + + #[test] + fn test_nghttp2_interop() { + test_fixture_set("fixtures/hpack/interop/nghttp2"); + } + + #[test] + fn test_nghttp2_change_table_size_interop() { + test_fixture_set("fixtures/hpack/interop/nghttp2-change-table-size"); + } + + #[test] + fn test_go_hpack_interop() { + test_fixture_set("fixtures/hpack/interop/go-hpack"); + } + + #[test] + fn test_node_http2_hpack_interop() { + test_fixture_set("fixtures/hpack/interop/node-http2-hpack"); + } + + #[test] + fn test_haskell_http2_linear_huffman() { + test_fixture_set("fixtures/hpack/interop/haskell-http2-linear-huffman"); + } +} diff --git a/src/hpack/encoder.rs b/src/hpack/encoder.rs new file mode 100644 index 0000000..b7f8824 --- /dev/null +++ b/src/hpack/encoder.rs @@ -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( + 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 { + 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 + where I: IntoIterator { + let mut encoded: Vec = 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, + 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( + &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( + &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( + &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( + &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(&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, headers: &Vec<(Vec, Vec)>) -> 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)); + } +} diff --git a/src/hpack/huffman.rs b/src/hpack/huffman.rs new file mode 100644 index 0000000..c965265 --- /dev/null +++ b/src/hpack/huffman.rs @@ -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, HuffmanDecoderError>; + +/// A simple implementation of a Huffman code decoder. +pub struct HuffmanDecoder { + table: HashMap>, + // 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> = + 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 = 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 { + 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 { + type Item = bool; + + fn next(&mut self) -> Option { + 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 { + 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 = 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 = 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"), + }); + } + } +} diff --git a/src/hpack/mod.rs b/src/hpack/mod.rs new file mode 100644 index 0000000..df25005 --- /dev/null +++ b/src/hpack/mod.rs @@ -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, Vec)>, +} + +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, Vec)>, + 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, value: Vec) { + // 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, Vec)> { + let mut ret: Vec<(Vec, Vec)> = 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, Vec)> { + 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, value: Vec) { + 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 = 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"); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4994445..a3633c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,20 @@ #![allow(warnings)] extern crate futures; + #[macro_use] extern crate tokio_io; + extern crate tokio_timer; + +// HTTP types +extern crate tower; + +// Buffer utilities extern crate bytes; pub mod error; +pub mod hpack; pub mod proto; pub mod frame; diff --git a/src/proto/framed_read.rs b/src/proto/framed_read.rs index 5abe602..3c1dae8 100644 --- a/src/proto/framed_read.rs +++ b/src/proto/framed_read.rs @@ -1,5 +1,5 @@ use ConnectionError; -use frame::Frame; +use frame::{self, Frame, Kind}; use tokio_io::AsyncWrite; @@ -10,6 +10,10 @@ use std::io::{self, Write}; pub struct FramedRead { inner: T, + + // hpack decoder state + // hpack: hpack::Decoder, + } impl FramedRead @@ -17,7 +21,9 @@ impl FramedRead T: AsyncWrite, { pub fn new(inner: T) -> FramedRead { - FramedRead { inner: inner } + FramedRead { + inner: inner, + } } } @@ -28,14 +34,36 @@ impl Stream for FramedRead type Error = ConnectionError; fn poll(&mut self) -> Poll, ConnectionError> { - match try_ready!(self.inner.poll()) { - Some(bytes) => { - Frame::load(bytes.freeze()) - .map(|frame| Async::Ready(Some(frame))) - .map_err(ConnectionError::from) + let mut bytes = match try_ready!(self.inner.poll()) { + Some(bytes) => bytes, + None => return Ok(Async::Ready(None)), + }; + + // 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))) } }