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] [dependencies]
futures = "0.1" 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" } tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
bytes = { git = "https://github.com/carllerche/bytes" } bytes = "0.4"
tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" } http = { path = "/Users/carllerche/Code/Oss/Tokio/tower/http" }
log = "0.3.8"
[replace] # tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" }
"futures:0.1.10" = { git = "https://github.com/alexcrichton/futures-rs", branch = "close" }

View File

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

View File

@@ -1,6 +1,8 @@
use super::DecoderError; 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; use bytes::Bytes;
/// HPack table entry /// HPack table entry
@@ -8,12 +10,12 @@ use bytes::Bytes;
pub enum Entry { pub enum Entry {
Header { Header {
name: HeaderName, name: HeaderName,
value: Str, value: HeaderValue,
}, },
Authority(Str), Authority(ByteStr),
Method(Method), Method(Method),
Scheme(Str), Scheme(ByteStr),
Path(Str), Path(ByteStr),
Status(StatusCode), Status(StatusCode),
} }
@@ -28,6 +30,38 @@ pub enum Key<'a> {
} }
impl Entry { 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 { pub fn len(&self) -> usize {
match *self { match *self {
Entry::Header { ref name, ref value } => { Entry::Header { ref name, ref value } => {
@@ -70,25 +104,26 @@ impl<'a> Key<'a> {
Key::Header(name) => { Key::Header(name) => {
Ok(Entry::Header { Ok(Entry::Header {
name: name.clone(), name: name.clone(),
value: try!(Str::from_utf8(value)), value: try!(HeaderValue::try_from_slice(&*value)),
}) })
} }
Key::Authority => { Key::Authority => {
Ok(Entry::Authority(try!(Str::from_utf8(value)))) Ok(Entry::Authority(try!(ByteStr::from_utf8(value))))
} }
Key::Method => { Key::Method => {
Ok(Entry::Scheme(try!(Str::from_utf8(value)))) Ok(Entry::Scheme(try!(ByteStr::from_utf8(value))))
} }
Key::Scheme => { Key::Scheme => {
Ok(Entry::Scheme(try!(Str::from_utf8(value)))) Ok(Entry::Scheme(try!(ByteStr::from_utf8(value))))
} }
Key::Path => { Key::Path => {
Ok(Entry::Path(try!(Str::from_utf8(value)))) Ok(Entry::Path(try!(ByteStr::from_utf8(value))))
} }
Key::Status => { Key::Status => {
match StatusCode::parse(&value) { match StatusCode::from_slice(&value) {
Some(status) => Ok(Entry::Status(status)), Ok(status) => Ok(Entry::Status(status)),
None => Err(DecoderError::InvalidStatusCode), // TODO: better error handling
Err(_) => Err(DecoderError::InvalidStatusCode),
} }
} }
} }

View File

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