Initial commit

This commit is contained in:
Carl Lerche
2017-03-09 20:02:47 -08:00
commit 1fe3a57338
10 changed files with 487 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target
Cargo.lock

10
Cargo.toml Normal file
View 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
View 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
View 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
View File

98
src/frame/mod.rs Normal file
View 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
View 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
View 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
View 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
View 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,
}
}