Finish hpack decoding

This commit is contained in:
Carl Lerche
2017-05-30 15:41:31 -07:00
parent eb00f71caf
commit 3c850c2a34
4 changed files with 266 additions and 135 deletions

View File

@@ -5,10 +5,9 @@ authors = ["Carl Lerche <me@carllerche.com>"]
[dependencies]
futures = "0.1"
tokio-io = { git = "https://github.com/alexcrichton/tokio-io" }
tokio-io = "0.1"
tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
bytes = { git = "https://github.com/carllerche/bytes" }
tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" }
[replace]
"futures:0.1.10" = { git = "https://github.com/alexcrichton/futures-rs", branch = "close" }
bytes = "0.4"
http = { path = "/Users/carllerche/Code/Oss/Tokio/tower/http" }
log = "0.3.8"
# tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" }

View File

@@ -1,6 +1,7 @@
use super::{huffman, Entry, Key};
use util::byte_str::FromUtf8Error;
use tower::http::{status, HeaderName, StatusCode, Method, Str, FromUtf8Error};
use http::{method, header, StatusCode, Method};
use bytes::{Buf, Bytes};
use std::io::Cursor;
@@ -23,6 +24,8 @@ pub enum DecoderError {
InvalidHuffmanCode,
InvalidUtf8,
InvalidStatusCode,
InvalidPseudoheader,
InvalidMaxDynamicSize,
IntegerUnderflow,
IntegerOverflow,
StringUnderflow,
@@ -143,6 +146,7 @@ impl Decoder {
use self::Representation::*;
let mut buf = Cursor::new(src);
let mut can_resize = true;
while buf.has_remaining() {
// At this point we are always at the beginning of the next block
@@ -150,23 +154,43 @@ impl Decoder {
// determined from the first byte.
match try!(Representation::load(peek_u8(&mut buf))) {
Indexed => {
can_resize = false;
f(try!(self.decode_indexed(&mut buf)));
}
LiteralWithIndexing => {
can_resize = false;
let entry = try!(self.decode_literal(&mut buf, true));
// TODO: Add entry to the table
// Insert the header into the table
self.table.insert(entry.clone());
f(entry);
}
LiteralWithoutIndexing => {
unimplemented!();
can_resize = false;
let entry = try!(self.decode_literal(&mut buf, false));
f(entry);
}
LiteralNeverIndexed => {
unimplemented!();
can_resize = false;
let entry = try!(self.decode_literal(&mut buf, false));
// TODO: Track that this should never be indexed
f(entry);
}
SizeUpdate => {
unimplemented!();
let max = match self.max_size_update {
Some(max) if can_resize => max,
_ => {
// Resize is too big or other frames have been read
// before the resize.
return Err(DecoderError::InvalidMaxDynamicSize);
}
};
// Handle the dynamic table size update...
try!(self.process_size_update(&mut buf, max))
}
}
}
@@ -174,6 +198,23 @@ impl Decoder {
Ok(())
}
fn process_size_update(&mut self, buf: &mut Cursor<&Bytes>, max: usize)
-> Result<(), DecoderError>
{
let new_size = try!(decode_int(buf, 5));
if new_size > max {
return Err(DecoderError::InvalidMaxDynamicSize);
}
debug!("Decoder changed max table size from {} to {}",
self.table.size(), new_size);
self.table.set_max_size(new_size);
Ok(())
}
fn decode_indexed(&self, buf: &mut Cursor<&Bytes>)
-> Result<Entry, DecoderError>
{
@@ -197,8 +238,9 @@ impl Decoder {
if table_idx == 0 {
// Read the name as a literal
let name = try!(decode_string(buf));
let value = try!(decode_string(buf));
unimplemented!();
Entry::new(name, value)
} else {
let e = try!(self.table.get(table_idx));
let value = try!(decode_string(buf));
@@ -338,6 +380,9 @@ impl Table {
self.max_size
}
fn size(&self) -> usize {
self.size
}
/// Returns the entry located at the given index.
///
@@ -374,6 +419,12 @@ impl Table {
self.entries.push_front(entry);
}
fn set_max_size(&mut self, size: usize) {
self.max_size = size;
// Make the table size fit within the new constraints.
self.consolidate();
}
fn reserve(&mut self, size: usize) {
debug_assert!(size <= self.max_size);
@@ -384,6 +435,25 @@ impl Table {
self.size -= last.len();
}
}
fn consolidate(&mut self) {
while self.size > self.max_size {
{
let last = match self.entries.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.len();
}
self.entries.pop_back();
}
}
}
// ===== impl DecoderError =====
@@ -394,212 +464,234 @@ impl From<FromUtf8Error> for DecoderError {
}
}
impl From<header::InvalidValueError> for DecoderError {
fn from(src: header::InvalidValueError) -> DecoderError {
// TODO: Better error?
DecoderError::InvalidUtf8
}
}
impl From<method::FromBytesError> for DecoderError {
fn from(src: method::FromBytesError) -> DecoderError {
// TODO: Better error
DecoderError::InvalidUtf8
}
}
impl From<header::FromBytesError> for DecoderError {
fn from(src: header::FromBytesError) -> DecoderError {
DecoderError::InvalidUtf8
}
}
/// Get an entry from the static table
pub fn get_static(idx: usize) -> Entry {
use tower::http::StandardHeader::*;
use http::{status, method, header};
use http::header::HeaderValue;
use util::byte_str::ByteStr;
match idx {
1 => Entry::Authority(Str::new()),
2 => Entry::Method(Method::Get),
3 => Entry::Method(Method::Post),
4 => Entry::Path(Str::from_static("/")),
5 => Entry::Path(Str::from_static("/index.html")),
6 => Entry::Scheme(Str::from_static("http")),
7 => Entry::Scheme(Str::from_static("https")),
8 => Entry::Status(status::Ok),
9 => Entry::Status(status::NoContent),
10 => Entry::Status(status::PartialContent),
11 => Entry::Status(status::NotModified),
12 => Entry::Status(status::BadRequest),
13 => Entry::Status(status::NotFound),
14 => Entry::Status(status::InternalServerError),
1 => Entry::Authority(ByteStr::from_static("")),
2 => Entry::Method(method::GET),
3 => Entry::Method(method::POST),
4 => Entry::Path(ByteStr::from_static("/")),
5 => Entry::Path(ByteStr::from_static("/index.html")),
6 => Entry::Scheme(ByteStr::from_static("http")),
7 => Entry::Scheme(ByteStr::from_static("https")),
8 => Entry::Status(status::OK),
9 => Entry::Status(status::NO_CONTENT),
10 => Entry::Status(status::PARTIAL_CONTENT),
11 => Entry::Status(status::NOT_MODIFIED),
12 => Entry::Status(status::BAD_REQUEST),
13 => Entry::Status(status::NOT_FOUND),
14 => Entry::Status(status::INTERNAL_SERVER_ERROR),
15 => Entry::Header {
name: AcceptCharset.into(),
value: Str::new(),
name: header::ACCEPT_CHARSET,
value: HeaderValue::from_static(""),
},
16 => Entry::Header {
name: AcceptEncoding.into(),
value: Str::from_static("gzip, deflate"),
name: header::ACCEPT_ENCODING,
value: HeaderValue::from_static("gzip, deflate"),
},
17 => Entry::Header {
name: AcceptLanguage.into(),
value: Str::new(),
name: header::ACCEPT_LANGUAGE,
value: HeaderValue::from_static(""),
},
18 => Entry::Header {
name: AcceptRanges.into(),
value: Str::new(),
name: header::ACCEPT_RANGES,
value: HeaderValue::from_static(""),
},
19 => Entry::Header {
name: Accept.into(),
value: Str::new(),
name: header::ACCEPT,
value: HeaderValue::from_static(""),
},
20 => Entry::Header {
name: AccessControlAllowOrigin.into(),
value: Str::new(),
name: header::ACCESS_CONTROL_ALLOW_ORIGIN,
value: HeaderValue::from_static(""),
},
21 => Entry::Header {
name: Age.into(),
value: Str::new(),
name: header::AGE,
value: HeaderValue::from_static(""),
},
22 => Entry::Header {
name: Allow.into(),
value: Str::new(),
name: header::ALLOW,
value: HeaderValue::from_static(""),
},
23 => Entry::Header {
name: Authorization.into(),
value: Str::new(),
name: header::AUTHORIZATION,
value: HeaderValue::from_static(""),
},
24 => Entry::Header {
name: CacheControl.into(),
value: Str::new(),
name: header::CACHE_CONTROL,
value: HeaderValue::from_static(""),
},
25 => Entry::Header {
name: ContentDisposition.into(),
value: Str::new(),
name: header::CONTENT_DISPOSITION,
value: HeaderValue::from_static(""),
},
26 => Entry::Header {
name: ContentEncoding.into(),
value: Str::new(),
name: header::CONTENT_ENCODING,
value: HeaderValue::from_static(""),
},
27 => Entry::Header {
name: ContentLanguage.into(),
value: Str::new(),
name: header::CONTENT_LANGUAGE,
value: HeaderValue::from_static(""),
},
28 => Entry::Header {
name: ContentLength.into(),
value: Str::new(),
name: header::CONTENT_LENGTH,
value: HeaderValue::from_static(""),
},
29 => Entry::Header {
name: ContentLocation.into(),
value: Str::new(),
name: header::CONTENT_LOCATION,
value: HeaderValue::from_static(""),
},
30 => Entry::Header {
name: ContentRange.into(),
value: Str::new(),
name: header::CONTENT_RANGE,
value: HeaderValue::from_static(""),
},
31 => Entry::Header {
name: ContentType.into(),
value: Str::new(),
name: header::CONTENT_TYPE,
value: HeaderValue::from_static(""),
},
32 => Entry::Header {
name: Cookie.into(),
value: Str::new(),
name: header::COOKIE,
value: HeaderValue::from_static(""),
},
33 => Entry::Header {
name: Date.into(),
value: Str::new(),
name: header::DATE,
value: HeaderValue::from_static(""),
},
34 => Entry::Header {
name: Etag.into(),
value: Str::new(),
name: header::ETAG,
value: HeaderValue::from_static(""),
},
35 => Entry::Header {
name: Expect.into(),
value: Str::new(),
name: header::EXPECT,
value: HeaderValue::from_static(""),
},
36 => Entry::Header {
name: Expires.into(),
value: Str::new(),
name: header::EXPIRES,
value: HeaderValue::from_static(""),
},
37 => Entry::Header {
name: From.into(),
value: Str::new(),
name: header::FROM,
value: HeaderValue::from_static(""),
},
38 => Entry::Header {
name: Host.into(),
value: Str::new(),
name: header::HOST,
value: HeaderValue::from_static(""),
},
39 => Entry::Header {
name: IfMatch.into(),
value: Str::new(),
name: header::IF_MATCH,
value: HeaderValue::from_static(""),
},
40 => Entry::Header {
name: IfModifiedSince.into(),
value: Str::new(),
name: header::IF_MODIFIED_SINCE,
value: HeaderValue::from_static(""),
},
41 => Entry::Header {
name: IfNoneMatch.into(),
value: Str::new(),
name: header::IF_NONE_MATCH,
value: HeaderValue::from_static(""),
},
42 => Entry::Header {
name: IfRange.into(),
value: Str::new(),
name: header::IF_RANGE,
value: HeaderValue::from_static(""),
},
43 => Entry::Header {
name: IfUnmodifiedSince.into(),
value: Str::new(),
name: header::IF_UNMODIFIED_SINCE,
value: HeaderValue::from_static(""),
},
44 => Entry::Header {
name: LastModified.into(),
value: Str::new(),
name: header::LAST_MODIFIED,
value: HeaderValue::from_static(""),
},
45 => Entry::Header {
name: Link.into(),
value: Str::new(),
name: header::LINK,
value: HeaderValue::from_static(""),
},
46 => Entry::Header {
name: Location.into(),
value: Str::new(),
name: header::LOCATION,
value: HeaderValue::from_static(""),
},
47 => Entry::Header {
name: MaxForwards.into(),
value: Str::new(),
name: header::MAX_FORWARDS,
value: HeaderValue::from_static(""),
},
48 => Entry::Header {
name: ProxyAuthenticate.into(),
value: Str::new(),
name: header::PROXY_AUTHENTICATE,
value: HeaderValue::from_static(""),
},
49 => Entry::Header {
name: ProxyAuthorization.into(),
value: Str::new(),
name: header::PROXY_AUTHORIZATION,
value: HeaderValue::from_static(""),
},
50 => Entry::Header {
name: Range.into(),
value: Str::new(),
name: header::RANGE,
value: HeaderValue::from_static(""),
},
51 => Entry::Header {
name: Referer.into(),
value: Str::new(),
name: header::REFERER,
value: HeaderValue::from_static(""),
},
52 => Entry::Header {
name: Refresh.into(),
value: Str::new(),
name: header::REFRESH,
value: HeaderValue::from_static(""),
},
53 => Entry::Header {
name: RetryAfter.into(),
value: Str::new(),
name: header::RETRY_AFTER,
value: HeaderValue::from_static(""),
},
54 => Entry::Header {
name: Server.into(),
value: Str::new(),
name: header::SERVER,
value: HeaderValue::from_static(""),
},
55 => Entry::Header {
name: SetCookie.into(),
value: Str::new(),
name: header::SET_COOKIE,
value: HeaderValue::from_static(""),
},
56 => Entry::Header {
name: StrictTransportSecurity.into(),
value: Str::new(),
name: header::STRICT_TRANSPORT_SECURITY,
value: HeaderValue::from_static(""),
},
57 => Entry::Header {
name: TransferEncoding.into(),
value: Str::new(),
name: header::TRANSFER_ENCODING,
value: HeaderValue::from_static(""),
},
58 => Entry::Header {
name: UserAgent.into(),
value: Str::new(),
name: header::USER_AGENT,
value: HeaderValue::from_static(""),
},
59 => Entry::Header {
name: Vary.into(),
value: Str::new(),
name: header::VARY,
value: HeaderValue::from_static(""),
},
60 => Entry::Header {
name: Via.into(),
value: Str::new(),
name: header::VIA,
value: HeaderValue::from_static(""),
},
61 => Entry::Header {
name: WwwAuthenticate.into(),
value: Str::new(),
name: header::WWW_AUTHENTICATE,
value: HeaderValue::from_static(""),
},
_ => unreachable!(),
}

View File

@@ -1,6 +1,8 @@
use super::DecoderError;
use util::byte_str::{ByteStr, FromUtf8Error};
use tower::http::{HeaderName, Method, StatusCode, Str};
use http::{Method, StatusCode};
use http::header::{HeaderName, HeaderValue};
use bytes::Bytes;
/// HPack table entry
@@ -8,12 +10,12 @@ use bytes::Bytes;
pub enum Entry {
Header {
name: HeaderName,
value: Str,
value: HeaderValue,
},
Authority(Str),
Authority(ByteStr),
Method(Method),
Scheme(Str),
Path(Str),
Scheme(ByteStr),
Path(ByteStr),
Status(StatusCode),
}
@@ -28,6 +30,38 @@ pub enum Key<'a> {
}
impl Entry {
pub fn new(name: Bytes, value: Bytes) -> Result<Entry, DecoderError> {
if name[0] == b':' {
match &name[1..] {
b"authority" => {
let value = try!(ByteStr::from_utf8(value));
Ok(Entry::Authority(value))
}
b"method" => {
let method = try!(Method::from_bytes(&value));
Ok(Entry::Method(method))
}
b"scheme" => {
unimplemented!();
}
b"path" => {
unimplemented!();
}
b"status" => {
unimplemented!();
}
_ => {
Err(DecoderError::InvalidPseudoheader)
}
}
} else {
let name = try!(HeaderName::from_bytes(&name));
let value = try!(HeaderValue::try_from_slice(&value));
Ok(Entry::Header { name: name, value: value })
}
}
pub fn len(&self) -> usize {
match *self {
Entry::Header { ref name, ref value } => {
@@ -70,25 +104,26 @@ impl<'a> Key<'a> {
Key::Header(name) => {
Ok(Entry::Header {
name: name.clone(),
value: try!(Str::from_utf8(value)),
value: try!(HeaderValue::try_from_slice(&*value)),
})
}
Key::Authority => {
Ok(Entry::Authority(try!(Str::from_utf8(value))))
Ok(Entry::Authority(try!(ByteStr::from_utf8(value))))
}
Key::Method => {
Ok(Entry::Scheme(try!(Str::from_utf8(value))))
Ok(Entry::Scheme(try!(ByteStr::from_utf8(value))))
}
Key::Scheme => {
Ok(Entry::Scheme(try!(Str::from_utf8(value))))
Ok(Entry::Scheme(try!(ByteStr::from_utf8(value))))
}
Key::Path => {
Ok(Entry::Path(try!(Str::from_utf8(value))))
Ok(Entry::Path(try!(ByteStr::from_utf8(value))))
}
Key::Status => {
match StatusCode::parse(&value) {
Some(status) => Ok(Entry::Status(status)),
None => Err(DecoderError::InvalidStatusCode),
match StatusCode::from_slice(&value) {
Ok(status) => Ok(Entry::Status(status)),
// TODO: better error handling
Err(_) => Err(DecoderError::InvalidStatusCode),
}
}
}

View File

@@ -8,15 +8,20 @@ extern crate tokio_io;
extern crate tokio_timer;
// HTTP types
extern crate tower;
extern crate http;
// Buffer utilities
extern crate bytes;
#[macro_use]
extern crate log;
pub mod error;
pub mod hpack;
pub mod proto;
pub mod frame;
mod util;
pub use error::{ConnectionError, StreamError, Reason};
pub use proto::Connection;