work
This commit is contained in:
@@ -1,478 +1 @@
|
||||
//! Implements all functionality related to encoding header blocks using
|
||||
//! HPACK.
|
||||
//!
|
||||
//! Clients should use the `Encoder` struct as the API for performing HPACK
|
||||
//! encoding.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Encodes a header using a literal encoding.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use hpack::Encoder;
|
||||
//!
|
||||
//! let mut encoder = Encoder::new();
|
||||
//!
|
||||
//! let headers = vec![
|
||||
//! (&b"custom-key"[..], &b"custom-value"[..]),
|
||||
//! ];
|
||||
//! // First encoding...
|
||||
//! let result = encoder.encode(headers);
|
||||
//! // The result is a literal encoding of the header name and value, with an
|
||||
//! // initial byte representing the type of the encoding
|
||||
//! // (incremental indexing).
|
||||
//! assert_eq!(
|
||||
//! vec![0x40,
|
||||
//! 10, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y',
|
||||
//! 12, b'c', b'u', b's', b't', b'o', b'm', b'-', b'v', b'a', b'l',
|
||||
//! b'u', b'e'],
|
||||
//! result);
|
||||
//! ```
|
||||
//!
|
||||
//! Encodes some pseudo-headers that are already found in the static table.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use hpack::Encoder;
|
||||
//!
|
||||
//! let mut encoder = Encoder::new();
|
||||
//! let headers = vec![
|
||||
//! (&b":method"[..], &b"GET"[..]),
|
||||
//! (&b":path"[..], &b"/"[..]),
|
||||
//! ];
|
||||
//!
|
||||
//! // The headers are encoded by providing their index (with a bit flag
|
||||
//! // indicating that the indexed representation is used).
|
||||
//! assert_eq!(encoder.encode(headers), vec![2 | 0x80, 4 | 0x80]);
|
||||
//! ```
|
||||
use std::io;
|
||||
use std::num::Wrapping;
|
||||
|
||||
use super::STATIC_TABLE;
|
||||
use super::HeaderTable;
|
||||
|
||||
/// Encode an integer to the representation defined by HPACK, writing it into the provider
|
||||
/// `io::Write` instance. Also allows the caller to specify the leading bits of the first
|
||||
/// octet. Any bits that are already set within the last `prefix_size` bits will be cleared
|
||||
/// and overwritten by the integer's representation (in other words, only the first
|
||||
/// `8 - prefix_size` bits from the `leading_bits` octet are reflected in the first octet
|
||||
/// emitted by the function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use hpack::encoder::encode_integer_into;
|
||||
///
|
||||
/// {
|
||||
/// // No bits specified in the 3 most significant bits of the first octet
|
||||
/// let mut vec = Vec::new();
|
||||
/// encode_integer_into(10, 5, 0, &mut vec);
|
||||
/// assert_eq!(vec, vec![10]);
|
||||
/// }
|
||||
/// {
|
||||
/// // The most significant bit should be set; i.e. the 3 most significant
|
||||
/// // bits are 100.
|
||||
/// let mut vec = Vec::new();
|
||||
/// encode_integer_into(10, 5, 0x80, &mut vec);
|
||||
/// assert_eq!(vec, vec![0x8A]);
|
||||
/// }
|
||||
/// {
|
||||
/// // The most leading bits number has a bit set within the last prefix-size
|
||||
/// // bits -- they are ignored by the function
|
||||
/// // bits are 100.
|
||||
/// let mut vec = Vec::new();
|
||||
/// encode_integer_into(10, 5, 0x10, &mut vec);
|
||||
/// assert_eq!(vec, vec![0x0A]);
|
||||
/// }
|
||||
/// {
|
||||
/// let mut vec = Vec::new();
|
||||
/// encode_integer_into(1337, 5, 0, &mut vec);
|
||||
/// assert_eq!(vec, vec![31, 154, 10]);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn encode_integer_into<W: io::Write>(
|
||||
mut value: usize,
|
||||
prefix_size: u8,
|
||||
leading_bits: u8,
|
||||
writer: &mut W)
|
||||
-> io::Result<()> {
|
||||
let Wrapping(mask) = if prefix_size >= 8 {
|
||||
Wrapping(0xFF)
|
||||
} else {
|
||||
Wrapping(1u8 << prefix_size) - Wrapping(1)
|
||||
};
|
||||
// Clear any bits within the last `prefix_size` bits of the provided `leading_bits`.
|
||||
// Failing to do so might lead to an incorrect encoding of the integer.
|
||||
let leading_bits = leading_bits & (!mask);
|
||||
let mask = mask as usize;
|
||||
if value < mask {
|
||||
try!(writer.write_all(&[leading_bits | value as u8]));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
try!(writer.write_all(&[leading_bits | mask as u8]));
|
||||
value -= mask;
|
||||
while value >= 128 {
|
||||
try!(writer.write_all(&[((value % 128) + 128) as u8]));
|
||||
value = value / 128;
|
||||
}
|
||||
try!(writer.write_all(&[value as u8]));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encode an integer to the representation defined by HPACK.
|
||||
///
|
||||
/// Returns a newly allocated `Vec` containing the encoded bytes.
|
||||
/// Only `prefix_size` lowest-order bits of the first byte in the
|
||||
/// array are guaranteed to be used.
|
||||
pub fn encode_integer(value: usize, prefix_size: u8) -> Vec<u8> {
|
||||
let mut res = Vec::new();
|
||||
encode_integer_into(value, prefix_size, 0, &mut res).unwrap();
|
||||
res
|
||||
}
|
||||
|
||||
/// Represents an HPACK encoder. Allows clients to encode arbitrary header sets
|
||||
/// and tracks the encoding context. That is, encoding subsequent header sets
|
||||
/// will use the context built by previous encode calls.
|
||||
///
|
||||
/// This is the main API for performing HPACK encoding of headers.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Encoding a header two times in a row produces two different
|
||||
/// representations, due to the utilization of HPACK compression.
|
||||
///
|
||||
/// ```rust
|
||||
/// use hpack::Encoder;
|
||||
///
|
||||
/// let mut encoder = Encoder::new();
|
||||
///
|
||||
/// let headers = vec![
|
||||
/// (b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||
/// ];
|
||||
/// // First encoding...
|
||||
/// let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
/// // The result is a literal encoding of the header name and value, with an
|
||||
/// // initial byte representing the type of the encoding
|
||||
/// // (incremental indexing).
|
||||
/// assert_eq!(
|
||||
/// vec![0x40,
|
||||
/// 10, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y',
|
||||
/// 12, b'c', b'u', b's', b't', b'o', b'm', b'-', b'v', b'a', b'l',
|
||||
/// b'u', b'e'],
|
||||
/// result);
|
||||
///
|
||||
/// // Encode the same headers again!
|
||||
/// let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
/// // The result is simply the index of the header in the header table (62),
|
||||
/// // with a flag representing that the decoder should use the index.
|
||||
/// assert_eq!(vec![0x80 | 62], result);
|
||||
/// ```
|
||||
pub struct Encoder<'a> {
|
||||
/// The header table represents the encoder's context
|
||||
header_table: HeaderTable<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Encoder<'a> {
|
||||
/// Creates a new `Encoder` with a default static table, as defined by the
|
||||
/// HPACK spec (Appendix A).
|
||||
pub fn new() -> Encoder<'a> {
|
||||
Encoder {
|
||||
header_table: HeaderTable::with_static_table(STATIC_TABLE),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a new maximum dynamic table size for the decoder.
|
||||
pub fn set_max_table_size(&mut self, new_max_size: usize) {
|
||||
self.header_table.dynamic_table.set_max_table_size(new_max_size);
|
||||
}
|
||||
|
||||
/// Encodes the given headers using the HPACK rules and returns a newly
|
||||
/// allocated `Vec` containing the bytes representing the encoded header
|
||||
/// set.
|
||||
///
|
||||
/// The encoder so far supports only a single, extremely simple encoding
|
||||
/// strategy, whereby each header is represented as an indexed header if
|
||||
/// already found in the header table and a literal otherwise. When a
|
||||
/// header isn't found in the table, it is added if the header name wasn't
|
||||
/// found either (i.e. there are never two header names with different
|
||||
/// values in the produced header table). Strings are always encoded as
|
||||
/// literals (Huffman encoding is not used).
|
||||
pub fn encode<'b, I>(&mut self, headers: I) -> Vec<u8>
|
||||
where I: IntoIterator<Item=(&'b [u8], &'b [u8])> {
|
||||
let mut encoded: Vec<u8> = Vec::new();
|
||||
self.encode_into(headers, &mut encoded).unwrap();
|
||||
encoded
|
||||
}
|
||||
|
||||
/// Encodes the given headers into the given `io::Write` instance. If the io::Write raises an
|
||||
/// Error at any point, this error is propagated out. Any changes to the internal state of the
|
||||
/// encoder will not be rolled back, though, so care should be taken to ensure that the paired
|
||||
/// decoder also ends up seeing the same state updates or that their pairing is cancelled.
|
||||
pub fn encode_into<'b, I, W>(&mut self, headers: I, writer: &mut W) -> io::Result<()>
|
||||
where I: IntoIterator<Item=(&'b [u8], &'b [u8])>,
|
||||
W: io::Write {
|
||||
for header in headers {
|
||||
try!(self.encode_header_into(header, writer));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encodes a single given header into the given `io::Write` instance.
|
||||
///
|
||||
/// Any errors are propagated, similarly to the `encode_into` method, and it is the callers
|
||||
/// responsiblity to make sure that the paired encoder sees them too.
|
||||
pub fn encode_header_into<W: io::Write>(
|
||||
&mut self,
|
||||
header: (&[u8], &[u8]),
|
||||
writer: &mut W)
|
||||
-> io::Result<()> {
|
||||
match self.header_table.find_header(header) {
|
||||
None => {
|
||||
// The name of the header is in no tables: need to encode
|
||||
// it with both a literal name and value.
|
||||
try!(self.encode_literal(&header, true, writer));
|
||||
self.header_table.add_header(header.0.to_vec(), header.1.to_vec());
|
||||
},
|
||||
Some((index, false)) => {
|
||||
// The name of the header is at the given index, but the
|
||||
// value does not match the current one: need to encode
|
||||
// only the value as a literal.
|
||||
try!(self.encode_indexed_name((index, header.1), false, writer));
|
||||
},
|
||||
Some((index, true)) => {
|
||||
// The full header was found in one of the tables, so we
|
||||
// just encode the index.
|
||||
try!(self.encode_indexed(index, writer));
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encodes a header as a literal (i.e. both the name and the value are
|
||||
/// encoded as a string literal) and places the result in the given buffer
|
||||
/// `buf`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `header` - the header to be encoded
|
||||
/// - `should_index` - indicates whether the given header should be indexed, i.e.
|
||||
/// inserted into the dynamic table
|
||||
/// - `buf` - The buffer into which the result is placed
|
||||
///
|
||||
fn encode_literal<W: io::Write>(
|
||||
&mut self,
|
||||
header: &(&[u8], &[u8]),
|
||||
should_index: bool,
|
||||
buf: &mut W)
|
||||
-> io::Result<()> {
|
||||
let mask = if should_index {
|
||||
0x40
|
||||
} else {
|
||||
0x0
|
||||
};
|
||||
|
||||
try!(buf.write_all(&[mask]));
|
||||
try!(self.encode_string_literal(&header.0, buf));
|
||||
try!(self.encode_string_literal(&header.1, buf));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encodes a string literal and places the result in the given buffer
|
||||
/// `buf`.
|
||||
///
|
||||
/// The function does not consider Huffman encoding for now, but always
|
||||
/// produces a string literal representations, according to the HPACK spec
|
||||
/// section 5.2.
|
||||
fn encode_string_literal<W: io::Write>(
|
||||
&mut self,
|
||||
octet_str: &[u8],
|
||||
buf: &mut W)
|
||||
-> io::Result<()> {
|
||||
try!(encode_integer_into(octet_str.len(), 7, 0, buf));
|
||||
try!(buf.write_all(octet_str));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encodes a header whose name is indexed and places the result in the
|
||||
/// given buffer `buf`.
|
||||
fn encode_indexed_name<W: io::Write>(
|
||||
&mut self,
|
||||
header: (usize, &[u8]),
|
||||
should_index: bool,
|
||||
buf: &mut W)
|
||||
-> io::Result<()> {
|
||||
let (mask, prefix) = if should_index {
|
||||
(0x40, 6)
|
||||
} else {
|
||||
(0x0, 4)
|
||||
};
|
||||
|
||||
try!(encode_integer_into(header.0, prefix, mask, buf));
|
||||
// So far, we rely on just one strategy for encoding string literals.
|
||||
try!(self.encode_string_literal(&header.1, buf));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encodes an indexed header (a header that is fully in the header table)
|
||||
/// and places the result in the given buffer `buf`.
|
||||
///
|
||||
/// The encoding is according to the rules of the HPACK spec, section 6.1.
|
||||
fn encode_indexed<W: io::Write>(&self, index: usize, buf: &mut W) -> io::Result<()> {
|
||||
// We need to set the most significant bit, since the bit-pattern is
|
||||
// `1xxxxxxx` for indexed headers.
|
||||
try!(encode_integer_into(index, 7, 0x80, buf));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::encode_integer;
|
||||
use super::Encoder;
|
||||
|
||||
use super::super::Decoder;
|
||||
|
||||
#[test]
|
||||
fn test_encode_integer() {
|
||||
assert_eq!(encode_integer(10, 5), [10]);
|
||||
assert_eq!(encode_integer(1337, 5), [31, 154, 10]);
|
||||
assert_eq!(encode_integer(127, 7), [127, 0]);
|
||||
assert_eq!(encode_integer(255, 8), [255, 0]);
|
||||
assert_eq!(encode_integer(254, 8), [254]);
|
||||
assert_eq!(encode_integer(1, 8), [1]);
|
||||
assert_eq!(encode_integer(0, 8), [0]);
|
||||
assert_eq!(encode_integer(255, 7), [127, 128, 1]);
|
||||
}
|
||||
|
||||
/// A helper function that checks whether the given buffer can be decoded
|
||||
/// into a set of headers that corresponds to the given `headers` list.
|
||||
/// Relies on using the `hpack::decoder::Decoder`` struct for
|
||||
/// performing the decoding.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `bool` indicating whether such a decoding can be performed.
|
||||
fn is_decodable(buf: &Vec<u8>, headers: &Vec<(Vec<u8>, Vec<u8>)>) -> bool {
|
||||
let mut decoder = Decoder::new();
|
||||
match decoder.decode(buf).ok() {
|
||||
Some(h) => h == *headers,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that encoding only the `:method` header works.
|
||||
#[test]
|
||||
fn test_encode_only_method() {
|
||||
let mut encoder: Encoder = Encoder::new();
|
||||
let headers = vec![
|
||||
(b":method".to_vec(), b"GET".to_vec()),
|
||||
];
|
||||
|
||||
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
|
||||
debug!("{:?}", result);
|
||||
assert!(is_decodable(&result, &headers));
|
||||
}
|
||||
|
||||
/// Tests that when a single custom header is sent it gets indexed by the
|
||||
/// coder.
|
||||
#[test]
|
||||
fn test_custom_header_gets_indexed() {
|
||||
let mut encoder: Encoder = Encoder::new();
|
||||
let headers = vec![
|
||||
(b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||
];
|
||||
|
||||
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
assert!(is_decodable(&result, &headers));
|
||||
// The header is in the encoder's dynamic table.
|
||||
assert_eq!(encoder.header_table.dynamic_table.to_vec(), headers);
|
||||
// ...but also indicated as such in the output.
|
||||
assert!(0x40 == (0x40 & result[0]));
|
||||
debug!("{:?}", result);
|
||||
}
|
||||
|
||||
/// Tests that when a header gets added to the dynamic table, the encoder
|
||||
/// will use the index, instead of the literal representation on the next
|
||||
/// encoding of the same header.
|
||||
#[test]
|
||||
fn test_uses_index_on_second_iteration() {
|
||||
let mut encoder: Encoder = Encoder::new();
|
||||
let headers = vec![
|
||||
(b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||
];
|
||||
// First encoding...
|
||||
let _ = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
|
||||
// Encode the same headers again!
|
||||
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
|
||||
// The header is in the encoder's dynamic table.
|
||||
assert_eq!(encoder.header_table.dynamic_table.to_vec(), headers);
|
||||
// The output is a single index byte?
|
||||
assert_eq!(result.len(), 1);
|
||||
// The index is correctly encoded:
|
||||
// - The most significant bit is set
|
||||
assert_eq!(0x80 & result[0], 0x80);
|
||||
// - The other 7 bits decode to an integer giving the index in the full
|
||||
// header address space.
|
||||
assert_eq!(result[0] ^ 0x80, 62);
|
||||
// The header table actually contains the header at that index?
|
||||
assert_eq!(
|
||||
encoder.header_table.get_from_table(62).unwrap(),
|
||||
(&headers[0].0[..], &headers[0].1[..]));
|
||||
}
|
||||
|
||||
/// Tests that when a header name is indexed, but the value isn't, the
|
||||
/// header is represented by an index (for the name) and a literal (for
|
||||
/// the value).
|
||||
#[test]
|
||||
fn test_name_indexed_value_not() {
|
||||
{
|
||||
let mut encoder: Encoder = Encoder::new();
|
||||
// `:method` is in the static table, but only for GET and POST
|
||||
let headers = vec![
|
||||
(b":method", b"PUT"),
|
||||
];
|
||||
|
||||
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
|
||||
// The first byte represents the index in the header table: last
|
||||
// occurrence of `:method` is at index 3.
|
||||
assert_eq!(result[0], 3);
|
||||
// The rest of it correctly represents PUT?
|
||||
assert_eq!(&result[1..], &[3, b'P', b'U', b'T']);
|
||||
}
|
||||
{
|
||||
let mut encoder: Encoder = Encoder::new();
|
||||
// `:method` is in the static table, but only for GET and POST
|
||||
let headers = vec![
|
||||
(b":authority".to_vec(), b"example.com".to_vec()),
|
||||
];
|
||||
|
||||
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
|
||||
assert_eq!(result[0], 1);
|
||||
// The rest of it correctly represents PUT?
|
||||
assert_eq!(
|
||||
&result[1..],
|
||||
&[11, b'e', b'x', b'a', b'm', b'p', b'l', b'e', b'.', b'c', b'o', b'm'])
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that multiple headers are correctly encoded (i.e. can be decoded
|
||||
/// back to their original representation).
|
||||
#[test]
|
||||
fn test_multiple_headers_encoded() {
|
||||
let mut encoder = Encoder::new();
|
||||
let headers = vec![
|
||||
(b"custom-key".to_vec(), b"custom-value".to_vec()),
|
||||
(b":method".to_vec(), b"GET".to_vec()),
|
||||
(b":path".to_vec(), b"/some/path".to_vec()),
|
||||
];
|
||||
|
||||
let result = encoder.encode(headers.iter().map(|h| (&h.0[..], &h.1[..])));
|
||||
|
||||
assert!(is_decodable(&result, &headers));
|
||||
}
|
||||
}
|
||||
pub struct Encoder;
|
||||
|
||||
Reference in New Issue
Block a user