Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Carl Lerche <me@carllerche.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.1"
|
||||||
|
tokio-io = { git = "https://github.com/alexcrichton/tokio-io" }
|
||||||
|
tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
|
||||||
|
bytes = { git = "https://github.com/carllerche/bytes" }
|
||||||
59
src/frame/data.rs
Normal file
59
src/frame/data.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use frame::{util, Head, Error, StreamId};
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
pub struct Data {
|
||||||
|
stream_id: StreamId,
|
||||||
|
data: Bytes,
|
||||||
|
flags: DataFlag,
|
||||||
|
pad_len: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct DataFlag(u8);
|
||||||
|
|
||||||
|
const END_STREAM: u8 = 0x1;
|
||||||
|
const PADDED: u8 = 0x8;
|
||||||
|
const ALL: u8 = END_STREAM | PADDED;
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
pub fn load(head: Head, mut payload: Bytes) -> Result<Data, Error> {
|
||||||
|
let flags = DataFlag::load(head.flag());
|
||||||
|
|
||||||
|
let pad_len = if flags.is_padded() {
|
||||||
|
let len = try!(util::strip_padding(&mut payload));
|
||||||
|
Some(len)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Data {
|
||||||
|
stream_id: head.stream_id(),
|
||||||
|
data: payload,
|
||||||
|
flags: flags,
|
||||||
|
pad_len: pad_len,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl DataFlag {
|
||||||
|
pub fn load(bits: u8) -> DataFlag {
|
||||||
|
DataFlag(bits & ALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_stream() -> DataFlag {
|
||||||
|
DataFlag(END_STREAM)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn padded() -> DataFlag {
|
||||||
|
DataFlag(PADDED)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_end_stream(&self) -> bool {
|
||||||
|
self.0 & END_STREAM == END_STREAM
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_padded(&self) -> bool {
|
||||||
|
self.0 & PADDED == PADDED
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/frame/head.rs
Normal file
82
src/frame/head.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use super::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Head {
|
||||||
|
kind: Kind,
|
||||||
|
flag: u8,
|
||||||
|
stream_id: StreamId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Kind {
|
||||||
|
Data = 0,
|
||||||
|
Headers = 1,
|
||||||
|
Priority = 2,
|
||||||
|
Reset = 3,
|
||||||
|
Settings = 4,
|
||||||
|
PushPromise = 5,
|
||||||
|
Ping = 6,
|
||||||
|
GoAway = 7,
|
||||||
|
WindowUpdate = 8,
|
||||||
|
Continuation = 9,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type StreamId = u32;
|
||||||
|
|
||||||
|
// ===== impl Head =====
|
||||||
|
|
||||||
|
impl Head {
|
||||||
|
/// Parse an HTTP/2.0 frame header
|
||||||
|
pub fn parse(header: &[u8]) -> Head {
|
||||||
|
Head {
|
||||||
|
kind: Kind::new(header[3]),
|
||||||
|
flag: header[4],
|
||||||
|
stream_id: parse_stream_id(&header[5..]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stream_id(&self) -> u32 {
|
||||||
|
self.stream_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> Kind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flag(&self) -> u8 {
|
||||||
|
self.flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the next 4 octets in the given buffer, assuming they represent an
|
||||||
|
/// HTTP/2 stream ID. This means that the most significant bit of the first
|
||||||
|
/// octet is ignored and the rest interpreted as a network-endian 31-bit
|
||||||
|
/// integer.
|
||||||
|
#[inline]
|
||||||
|
fn parse_stream_id(buf: &[u8]) -> StreamId {
|
||||||
|
let unpacked = unpack_octets_4!(buf, 0, u32);
|
||||||
|
// Now clear the most significant bit, as that is reserved and MUST be ignored when received.
|
||||||
|
unpacked & !0x80000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Kind =====
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
pub fn new(byte: u8) -> Kind {
|
||||||
|
return match byte {
|
||||||
|
0 => Kind::Data,
|
||||||
|
1 => Kind::Headers,
|
||||||
|
2 => Kind::Priority,
|
||||||
|
3 => Kind::Reset,
|
||||||
|
4 => Kind::Settings,
|
||||||
|
5 => Kind::PushPromise,
|
||||||
|
6 => Kind::Ping,
|
||||||
|
7 => Kind::GoAway,
|
||||||
|
8 => Kind::WindowUpdate,
|
||||||
|
9 => Kind::Continuation,
|
||||||
|
_ => Kind::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/frame/headers.rs
Normal file
0
src/frame/headers.rs
Normal file
98
src/frame/mod.rs
Normal file
98
src/frame/mod.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
/// A helper macro that unpacks a sequence of 4 bytes found in the buffer with
|
||||||
|
/// the given identifier, starting at the given offset, into the given integer
|
||||||
|
/// type. Obviously, the integer type should be able to support at least 4
|
||||||
|
/// bytes.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let buf: [u8; 4] = [0, 0, 0, 1];
|
||||||
|
/// assert_eq!(1u32, unpack_octets_4!(buf, 0, u32));
|
||||||
|
/// ```
|
||||||
|
#[macro_escape]
|
||||||
|
macro_rules! unpack_octets_4 {
|
||||||
|
($buf:expr, $offset:expr, $tip:ty) => (
|
||||||
|
(($buf[$offset + 0] as $tip) << 24) |
|
||||||
|
(($buf[$offset + 1] as $tip) << 16) |
|
||||||
|
(($buf[$offset + 2] as $tip) << 8) |
|
||||||
|
(($buf[$offset + 3] as $tip) << 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod data;
|
||||||
|
mod head;
|
||||||
|
mod settings;
|
||||||
|
mod unknown;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub use self::data::Data;
|
||||||
|
pub use self::head::{Head, Kind, StreamId};
|
||||||
|
pub use self::unknown::Unknown;
|
||||||
|
|
||||||
|
const FRAME_HEADER_LEN: usize = 9;
|
||||||
|
|
||||||
|
/// Errors that can occur during parsing an HTTP/2 frame.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// A full frame header was not passed.
|
||||||
|
Short,
|
||||||
|
|
||||||
|
/// An unsupported value was set for the flag value.
|
||||||
|
BadFlag,
|
||||||
|
|
||||||
|
/// An unsupported value was set for the frame kind.
|
||||||
|
BadKind,
|
||||||
|
|
||||||
|
/// The padding length was larger than the frame-header-specified
|
||||||
|
/// length of the payload.
|
||||||
|
TooMuchPadding,
|
||||||
|
|
||||||
|
/// The payload length specified by the frame header was shorter than
|
||||||
|
/// necessary for the parser settings specified and the frame type.
|
||||||
|
///
|
||||||
|
/// This happens if, for instance, the priority flag is set and the
|
||||||
|
/// header length is shorter than a stream dependency.
|
||||||
|
///
|
||||||
|
/// `PayloadLengthTooShort` should be treated as a protocol error.
|
||||||
|
PayloadLengthTooShort,
|
||||||
|
|
||||||
|
/// The payload length specified by the frame header of a settings frame
|
||||||
|
/// was not a round multiple of the size of a single setting.
|
||||||
|
PartialSettingLength,
|
||||||
|
|
||||||
|
/// The payload length specified by the frame header was not the
|
||||||
|
/// value necessary for the specific frame type.
|
||||||
|
InvalidPayloadLength
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Frame {
|
||||||
|
/*
|
||||||
|
Data(DataFrame<'a>),
|
||||||
|
HeadersFrame(HeadersFrame<'a>),
|
||||||
|
RstStreamFrame(RstStreamFrame),
|
||||||
|
SettingsFrame(SettingsFrame),
|
||||||
|
PingFrame(PingFrame),
|
||||||
|
GoawayFrame(GoawayFrame<'a>),
|
||||||
|
WindowUpdateFrame(WindowUpdateFrame),
|
||||||
|
UnknownFrame(RawFrame<'a>),
|
||||||
|
*/
|
||||||
|
Unknown(Unknown),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn load(mut frame: Bytes) -> Frame {
|
||||||
|
let head = Head::parse(&frame);
|
||||||
|
|
||||||
|
// Extract the payload from the frame
|
||||||
|
let _ = frame.drain_to(FRAME_HEADER_LEN);
|
||||||
|
|
||||||
|
|
||||||
|
match head.kind() {
|
||||||
|
Kind::Unknown => Frame::Unknown(Unknown::new(head, frame)),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/frame/settings.rs
Normal file
153
src/frame/settings.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use frame::{Error, Head};
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Settings {
|
||||||
|
flag: SettingsFlag,
|
||||||
|
// Fields
|
||||||
|
header_table_size: Option<u32>,
|
||||||
|
enable_push: Option<bool>,
|
||||||
|
max_concurrent_streams: Option<u32>,
|
||||||
|
initial_window_size: Option<u32>,
|
||||||
|
max_frame_size: Option<u32>,
|
||||||
|
max_header_list_size: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum that lists all valid settings that can be sent in a SETTINGS
|
||||||
|
/// frame.
|
||||||
|
///
|
||||||
|
/// Each setting has a value that is a 32 bit unsigned integer (6.5.1.).
|
||||||
|
pub enum Setting {
|
||||||
|
HeaderTableSize(u32),
|
||||||
|
EnablePush(u32),
|
||||||
|
MaxConcurrentStreams(u32),
|
||||||
|
InitialWindowSize(u32),
|
||||||
|
MaxFrameSize(u32),
|
||||||
|
MaxHeaderListSize(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||||
|
pub struct SettingsFlag(u8);
|
||||||
|
|
||||||
|
const ACK: u8 = 0x1;
|
||||||
|
const ALL: u8 = ACK;
|
||||||
|
|
||||||
|
// ===== impl Settings =====
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn load(head: Head, payload: Bytes) -> Result<Settings, Error> {
|
||||||
|
use self::Setting::*;
|
||||||
|
|
||||||
|
debug_assert_eq!(head.kind(), ::frame::Kind::Settings);
|
||||||
|
|
||||||
|
if head.stream_id() != 0 {
|
||||||
|
// TODO: raise ProtocolError
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the flag
|
||||||
|
let flag = SettingsFlag::load(head.flag());
|
||||||
|
|
||||||
|
if flag.is_ack() {
|
||||||
|
// Ensure that the payload is empty
|
||||||
|
if payload.len() > 0 {
|
||||||
|
// TODO: raise a FRAME_SIZE_ERROR
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the ACK frame
|
||||||
|
return Ok(Settings {
|
||||||
|
flag: flag,
|
||||||
|
.. Settings::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the payload length is correct, each setting is 6 bytes long.
|
||||||
|
if payload.len() % 6 != 0 {
|
||||||
|
return Err(Error::PartialSettingLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut settings = Settings::default();
|
||||||
|
debug_assert!(!settings.flag.is_ack());
|
||||||
|
|
||||||
|
for raw in payload.chunks(6) {
|
||||||
|
match Setting::load(raw) {
|
||||||
|
Some(HeaderTableSize(val)) => {
|
||||||
|
settings.header_table_size = Some(val);
|
||||||
|
}
|
||||||
|
Some(EnablePush(val)) => {
|
||||||
|
settings.enable_push = Some(val == 1);
|
||||||
|
}
|
||||||
|
Some(MaxConcurrentStreams(val)) => {
|
||||||
|
settings.max_concurrent_streams = Some(val);
|
||||||
|
}
|
||||||
|
Some(InitialWindowSize(val)) => {
|
||||||
|
settings.initial_window_size = Some(val);
|
||||||
|
}
|
||||||
|
Some(MaxFrameSize(val)) => {
|
||||||
|
settings.max_frame_size = Some(val);
|
||||||
|
}
|
||||||
|
Some(MaxHeaderListSize(val)) => {
|
||||||
|
settings.max_header_list_size = Some(val);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Setting =====
|
||||||
|
|
||||||
|
impl Setting {
|
||||||
|
/// Creates a new `Setting` with the correct variant corresponding to the
|
||||||
|
/// given setting id, based on the settings IDs defined in section
|
||||||
|
/// 6.5.2.
|
||||||
|
pub fn from_id(id: u16, val: u32) -> Option<Setting> {
|
||||||
|
use self::Setting::*;
|
||||||
|
|
||||||
|
match id {
|
||||||
|
1 => Some(HeaderTableSize(val)),
|
||||||
|
2 => Some(EnablePush(val)),
|
||||||
|
3 => Some(MaxConcurrentStreams(val)),
|
||||||
|
4 => Some(InitialWindowSize(val)),
|
||||||
|
5 => Some(MaxFrameSize(val)),
|
||||||
|
6 => Some(MaxHeaderListSize(val)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Setting` by parsing the given buffer of 6 bytes, which
|
||||||
|
/// contains the raw byte representation of the setting, according to the
|
||||||
|
/// "SETTINGS format" defined in section 6.5.1.
|
||||||
|
///
|
||||||
|
/// The `raw` parameter should have length at least 6 bytes, since the
|
||||||
|
/// length of the raw setting is exactly 6 bytes.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If given a buffer shorter than 6 bytes, the function will panic.
|
||||||
|
fn load(raw: &[u8]) -> Option<Setting> {
|
||||||
|
let id: u16 = ((raw[0] as u16) << 8) | (raw[1] as u16);
|
||||||
|
let val: u32 = unpack_octets_4!(raw, 2, u32);
|
||||||
|
|
||||||
|
Setting::from_id(id, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl SettingsFlag =====
|
||||||
|
|
||||||
|
impl SettingsFlag {
|
||||||
|
pub fn load(bits: u8) -> SettingsFlag {
|
||||||
|
SettingsFlag(bits & ALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ack() -> SettingsFlag {
|
||||||
|
SettingsFlag(ACK)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ack(&self) -> bool {
|
||||||
|
self.0 & ACK == ACK
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/frame/unknown.rs
Normal file
17
src/frame/unknown.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use frame::Head;
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Unknown {
|
||||||
|
head: Head,
|
||||||
|
payload: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unknown {
|
||||||
|
pub fn new(head: Head, payload: Bytes) -> Unknown {
|
||||||
|
Unknown {
|
||||||
|
head: head,
|
||||||
|
payload: payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/frame/util.rs
Normal file
36
src/frame/util.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use super::Error;
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
/// Strip padding from the given payload.
|
||||||
|
///
|
||||||
|
/// It is assumed that the frame had the padded flag set. This means that the
|
||||||
|
/// first byte is the length of the padding with that many
|
||||||
|
/// 0 bytes expected to follow the actual payload.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A slice of the given payload where the actual one is found and the length
|
||||||
|
/// of the padding.
|
||||||
|
///
|
||||||
|
/// If the padded payload is invalid (e.g. the length of the padding is equal
|
||||||
|
/// to the total length), returns `None`.
|
||||||
|
pub fn strip_padding(payload: &mut Bytes) -> Result<u8, Error> {
|
||||||
|
if payload.len() == 0 {
|
||||||
|
// If this is the case, the frame is invalid as no padding length can be
|
||||||
|
// extracted, even though the frame should be padded.
|
||||||
|
return Err(Error::TooMuchPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pad_len = payload[0] as usize;
|
||||||
|
|
||||||
|
if pad_len >= payload.len() {
|
||||||
|
// This is invalid: the padding length MUST be less than the
|
||||||
|
// total frame size.
|
||||||
|
return Err(Error::TooMuchPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = payload.split_off(pad_len);
|
||||||
|
let _ = payload.split_to(1);
|
||||||
|
|
||||||
|
Ok(pad_len as u8)
|
||||||
|
}
|
||||||
30
src/lib.rs
Normal file
30
src/lib.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#![allow(warnings)]
|
||||||
|
|
||||||
|
extern crate futures;
|
||||||
|
extern crate tokio_io;
|
||||||
|
extern crate tokio_timer;
|
||||||
|
extern crate bytes;
|
||||||
|
|
||||||
|
pub mod frame;
|
||||||
|
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use tokio_io::codec::length_delimited;
|
||||||
|
|
||||||
|
use futures::*;
|
||||||
|
|
||||||
|
pub struct Transport<T> {
|
||||||
|
inner: length_delimited::FramedRead<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind<T: AsyncRead + AsyncRead>(io: T) -> Transport<T> {
|
||||||
|
let framed = length_delimited::Builder::new()
|
||||||
|
.big_endian()
|
||||||
|
.length_field_length(3)
|
||||||
|
.length_adjustment(6)
|
||||||
|
.num_skip(0) // Don't skip the header
|
||||||
|
.new_read(io);
|
||||||
|
|
||||||
|
Transport {
|
||||||
|
inner: framed,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user