From 59bc8a9a7e6cbe9ceda79a6f31a3fd9fe91ea7b4 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Sun, 12 Mar 2017 13:44:38 -0700 Subject: [PATCH] work --- src/hpack/decoder.rs | 1741 +++++------------------------------------- src/hpack/encoder.rs | 479 +----------- src/hpack/huffman.rs | 702 ----------------- src/hpack/mod.rs | 759 +----------------- src/hpack/table.rs | 55 ++ 5 files changed, 264 insertions(+), 3472 deletions(-) delete mode 100644 src/hpack/huffman.rs create mode 100644 src/hpack/table.rs diff --git a/src/hpack/decoder.rs b/src/hpack/decoder.rs index 42ea5fd..6ab6c62 100644 --- a/src/hpack/decoder.rs +++ b/src/hpack/decoder.rs @@ -1,1594 +1,259 @@ -//! 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 super::Entry; -use std::cmp; -use std::num::Wrapping; -use std::borrow::Cow; +use tower::http::{HeaderName, Str}; +use bytes::{Buf, Bytes}; -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>, +use std::io::Cursor; +/// Decodes headers using HPACK +pub struct Decoder { // 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) - } +/// Represents all errors that can be encountered while performing the decoding +/// of an HPACK header set. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum DecoderError { + InvalidRepresentation, + InvalidIntegerPrefix, + IntegerUnderflow, + IntegerOverflow, +} - /// Creates a new `Decoder` with the given slice serving as its static +enum Representation { + /// Indexed header field representation + /// + /// An indexed header field representation identifies an entry in either the + /// static table or the dynamic table (see Section 2.3). + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 1 | Index (7+) | + /// +---+---------------------------+ + /// ``` + Indexed, + + /// Literal Header Field with Incremental Indexing + /// + /// A literal header field with incremental indexing representation results + /// in appending a header field to the decoded header list and inserting it + /// as a new entry into the dynamic table. + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 1 | Index (6+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralWithIndexing, + + /// Literal Header Field without Indexing + /// + /// A literal header field without indexing representation results in + /// appending a header field to the decoded header list without altering the + /// dynamic table. + /// + /// # Header encoding + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 0 | Index (4+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralWithoutIndexing, + + /// Literal Header Field Never Indexed + /// + /// A literal header field never-indexed representation results in appending + /// a header field to the decoded header list without altering the dynamic + /// table. Intermediaries MUST use the same representation for encoding this + /// header field. + /// + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 1 | Index (4+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralNeverIndexed, + + /// Dynamic Table Size Update + /// + /// A dynamic table size update signals a change to the size of the dynamic /// table. /// - /// The slice should contain tuples where the tuple coordinates represent - /// the header name and value, respectively. + /// # Header encoding /// - /// 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> { + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 1 | Max size (5+) | + /// +---+---------------------------+ + /// ``` + SizeUpdate, +} + +// ===== impl Decoder ===== + +impl Decoder { + /// Creates a new `Decoder` with all settings set to default values. + pub fn new() -> Decoder { 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]>) + /// Decodes the headers found in the given buffer. + pub fn decode(&mut self, src: &Bytes, mut f: F) -> Result<(), DecoderError> + where F: FnMut(HeaderName, Str) { - let mut current_octet_index = 0; - let mut can_resize = true; - let mut resized = false; + use self::Representation::*; - while current_octet_index < buf.len() { + let mut buf = Cursor::new(src); + + while buf.has_remaining() { // 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; + // within the HPACK data. The type of the block can always be + // determined from the first byte. + match try!(Representation::load(peek_u8(&mut buf))) { + Indexed => { + unimplemented!(); + } + LiteralWithIndexing => { + unimplemented!(); + } + LiteralWithoutIndexing => { + unimplemented!(); + } + LiteralNeverIndexed => { + unimplemented!(); + } + SizeUpdate => { + unimplemented!(); } } - - 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) + fn decode_indexed(&self, buf: &mut Cursor<&Bytes>) -> Result { + let index = try!(decode_int(buf, 7)); + self.get_from_table(index) } - /// 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)) + fn get_from_table(&self, index: usize) -> Result { + unimplemented!(); + // self.header_table.get(index).o } +} - /// 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) - } +// ===== impl Representation ===== - /// 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 +impl Representation { + pub fn load(byte: u8) -> Result { + const INDEXED: u8 = 0b10000000; + const LITERAL_WITH_INDEXING: u8 = 0b01000000; + const LITERAL_WITHOUT_INDEXING: u8 = 0b11110000; + const LITERAL_NEVER_INDEXED: u8 = 0b00010000; + const SIZE_UPDATE: u8 = 0b00100000; + + if byte & INDEXED == INDEXED { + Ok(Representation::Indexed) + } else if byte & LITERAL_WITH_INDEXING == LITERAL_WITH_INDEXING { + Ok(Representation::LiteralWithIndexing) + } else if byte & LITERAL_WITHOUT_INDEXING == 0 { + Ok(Representation::LiteralWithIndexing) + } else if byte & LITERAL_NEVER_INDEXED == LITERAL_NEVER_INDEXED { + Ok(Representation::LiteralNeverIndexed) + } else if byte & SIZE_UPDATE == SIZE_UPDATE { + Ok(Representation::SizeUpdate) } 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); + Err(DecoderError::InvalidRepresentation) } - - self.header_table.dynamic_table.set_max_table_size(new_size); - - Ok(consumed) } } -#[cfg(test)] -mod tests { - use super::{decode_integer}; +// ===== Utils ===== - use std::borrow::Cow; +fn decode_int(buf: &mut B, prefix_size: u8) -> Result { + // 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 + const MAX_BYTES: usize = 5; + const VARINT_MASK: u8 = 0b01111111; + const VARINT_FLAG: u8 = 0b10000000; - 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()); + if prefix_size < 1 || prefix_size > 8 { + return Err(DecoderError::InvalidIntegerPrefix); } - /// 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)); + if !buf.has_remaining() { + return Err(DecoderError::IntegerUnderflow); } - #[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, - }); + let mask = if prefix_size == 8 { + 0xFF + } else { + (1u8 << prefix_size).wrapping_sub(1) + }; + + let mut ret = (buf.get_u8() & mask) as usize; + + if ret < mask as usize { + // Value fits in the prefix bits + return Ok(ret); } - #[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, - }); - } + // The int did not fit in the prefix bits, so continue reading. + // + // The total number of bytes used to represent the int. The first byte was + // the prefix, so start at 1. + let mut bytes = 1; - #[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, - }); - } + // The rest of the int is stored as a varint -- 7 bits for the value and 1 + // bit to indicate if it is the last byte. + let mut shift = 0; - #[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, - }); - } + while buf.has_remaining() { + let b = buf.get_u8(); - #[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, - }); - } + bytes += 1; + ret += ((b & VARINT_MASK) as usize) << shift; + shift += 7; - #[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!"), - }; + if b & VARINT_FLAG == VARINT_FLAG { + return Ok(ret); } - 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()); + if bytes == MAX_BYTES { + // The spec requires that this situation is an error + return Err(DecoderError::IntegerOverflow); } } - /// 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, - }); - } + Err(DecoderError::IntegerUnderflow) } -/// 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"); - } +fn peek_u8(buf: &mut B) -> u8 { + buf.bytes()[0] } diff --git a/src/hpack/encoder.rs b/src/hpack/encoder.rs index b7f8824..dc4f0b2 100644 --- a/src/hpack/encoder.rs +++ b/src/hpack/encoder.rs @@ -1,478 +1 @@ -//! Implements all functionality related to encoding header blocks using -//! HPACK. -//! -//! Clients should use the `Encoder` struct as the API for performing HPACK -//! encoding. -//! -//! # Examples -//! -//! Encodes a header using a literal encoding. -//! -//! ```rust -//! use hpack::Encoder; -//! -//! let mut encoder = Encoder::new(); -//! -//! let headers = vec![ -//! (&b"custom-key"[..], &b"custom-value"[..]), -//! ]; -//! // First encoding... -//! let result = encoder.encode(headers); -//! // The result is a literal encoding of the header name and value, with an -//! // initial byte representing the type of the encoding -//! // (incremental indexing). -//! assert_eq!( -//! vec![0x40, -//! 10, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y', -//! 12, b'c', b'u', b's', b't', b'o', b'm', b'-', b'v', b'a', b'l', -//! b'u', b'e'], -//! result); -//! ``` -//! -//! Encodes some pseudo-headers that are already found in the static table. -//! -//! ```rust -//! use hpack::Encoder; -//! -//! let mut encoder = Encoder::new(); -//! let headers = vec![ -//! (&b":method"[..], &b"GET"[..]), -//! (&b":path"[..], &b"/"[..]), -//! ]; -//! -//! // The headers are encoded by providing their index (with a bit flag -//! // indicating that the indexed representation is used). -//! assert_eq!(encoder.encode(headers), vec![2 | 0x80, 4 | 0x80]); -//! ``` -use std::io; -use std::num::Wrapping; - -use super::STATIC_TABLE; -use super::HeaderTable; - -/// Encode an integer to the representation defined by HPACK, writing it into the provider -/// `io::Write` instance. Also allows the caller to specify the leading bits of the first -/// octet. Any bits that are already set within the last `prefix_size` bits will be cleared -/// and overwritten by the integer's representation (in other words, only the first -/// `8 - prefix_size` bits from the `leading_bits` octet are reflected in the first octet -/// emitted by the function. -/// -/// # Example -/// -/// ```rust -/// use hpack::encoder::encode_integer_into; -/// -/// { -/// // No bits specified in the 3 most significant bits of the first octet -/// let mut vec = Vec::new(); -/// encode_integer_into(10, 5, 0, &mut vec); -/// assert_eq!(vec, vec![10]); -/// } -/// { -/// // The most significant bit should be set; i.e. the 3 most significant -/// // bits are 100. -/// let mut vec = Vec::new(); -/// encode_integer_into(10, 5, 0x80, &mut vec); -/// assert_eq!(vec, vec![0x8A]); -/// } -/// { -/// // The most leading bits number has a bit set within the last prefix-size -/// // bits -- they are ignored by the function -/// // bits are 100. -/// let mut vec = Vec::new(); -/// encode_integer_into(10, 5, 0x10, &mut vec); -/// assert_eq!(vec, vec![0x0A]); -/// } -/// { -/// let mut vec = Vec::new(); -/// encode_integer_into(1337, 5, 0, &mut vec); -/// assert_eq!(vec, vec![31, 154, 10]); -/// } -/// ``` -pub fn encode_integer_into( - 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)); - } -} +pub struct Encoder; diff --git a/src/hpack/huffman.rs b/src/hpack/huffman.rs deleted file mode 100644 index c965265..0000000 --- a/src/hpack/huffman.rs +++ /dev/null @@ -1,702 +0,0 @@ -//! A module exposing utilities for encoding and decoding Huffman-coded octet -//! strings, under the Huffman code defined by HPACK. -//! (HPACK-draft-10, Appendix B) - -use std::collections::HashMap; - -/// Represents a symbol that can be inserted into a Huffman-encoded octet -/// string. -enum HuffmanCodeSymbol { - /// Any octet is a valid symbol - Symbol(u8), - /// A special symbol represents the end of the string - EndOfString, -} - -impl HuffmanCodeSymbol { - pub fn new(symbol: usize) -> HuffmanCodeSymbol { - if symbol == 256 { - HuffmanCodeSymbol::EndOfString - } else { - // It is safe to downcast since now we know that the value - // is in the half-open interval [0, 256) - HuffmanCodeSymbol::Symbol(symbol as u8) - } - } -} - -/// Represents the error variants that the `HuffmanDecoder` can return. -#[derive(PartialEq)] -#[derive(Copy)] -#[derive(Clone)] -#[derive(Debug)] -pub enum HuffmanDecoderError { - /// Any padding strictly larger than 7 bits MUST be interpreted as an error - PaddingTooLarge, - /// Any padding that does not correspond to the most significant bits of - /// EOS MUST be interpreted as an error. - InvalidPadding, - /// If EOS is ever found in the string, it causes an error. - EOSInString, -} - -/// The type that represents the result of the `decode` method of the -/// `HuffmanDecoder`. -pub type HuffmanDecoderResult = Result, 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 index df25005..51d2398 100644 --- a/src/hpack/mod.rs +++ b/src/hpack/mod.rs @@ -1,756 +1,7 @@ -//! A module implementing HPACK functionality. Exposes a simple API for -//! performing the encoding and decoding of header sets, according to the -//! HPACK spec. +mod encoder; +mod decoder; +mod table; -use std::fmt; -use std::iter; -use std::slice; -use std::collections::VecDeque; -use std::collections::vec_deque; - -// Re-export the main HPACK API entry points. -pub use self::decoder::Decoder; pub use self::encoder::Encoder; - -pub mod encoder; -pub mod decoder; -pub mod huffman; - -/// An `Iterator` through elements of the `DynamicTable`. -/// -/// The implementation of the iterator itself is very tightly coupled -/// to the implementation of the `DynamicTable`. -/// -/// This iterator returns tuples of slices. The tuples themselves are -/// constructed as new instances, containing a borrow from the `Vec`s -/// representing the underlying Headers. -struct DynamicTableIter<'a> { - /// Stores an iterator through the underlying structure that the - /// `DynamicTable` uses - inner: vec_deque::Iter<'a, (Vec, 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"); - } - } -} +pub use self::decoder::Decoder; +pub use self::table::Entry; diff --git a/src/hpack/table.rs b/src/hpack/table.rs new file mode 100644 index 0000000..49f7a6b --- /dev/null +++ b/src/hpack/table.rs @@ -0,0 +1,55 @@ +use tower::http::{HeaderName, StatusCode, Str}; +use std::collections::VecDeque; + +/// HPack table entry +pub enum Entry { + Header { + name: HeaderName, + value: Str, + }, + Authority(Str), + Scheme(Str), + Path(Str), + Status(StatusCode), +} + +/// Get an entry from the static table +pub fn get_static(idx: usize) -> Entry { + match idx { + 1 => unimplemented!(), + _ => unimplemented!(), + } +} + +/* +pub struct Table { + entries: VecDeque, + max_size: usize, +} + +pub enum Entry { + Header { + name: HeaderName, + value: Str, + }, + Authority(Str), + Scheme(Str), + Path(Str), + Status(StatusCode), +} +impl Table { + pub fn new(max_size: usize) -> Table { + Table { + entries: VecDeque::new(), + max_size: max_size, + } + } +} + +impl Default for Table { + fn default() -> Table { + // Default maximum size from the HTTP/2.0 spec. + Table::new(4_096) + } +} +*/