From 1fe3a573383e1699bcf9fa63808c4b5ce987aa3d Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 9 Mar 2017 20:02:47 -0800 Subject: [PATCH] Initial commit --- .gitignore | 2 + Cargo.toml | 10 +++ src/frame/data.rs | 59 ++++++++++++++++ src/frame/head.rs | 82 ++++++++++++++++++++++ src/frame/headers.rs | 0 src/frame/mod.rs | 98 +++++++++++++++++++++++++++ src/frame/settings.rs | 153 ++++++++++++++++++++++++++++++++++++++++++ src/frame/unknown.rs | 17 +++++ src/frame/util.rs | 36 ++++++++++ src/lib.rs | 30 +++++++++ 10 files changed, 487 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/frame/data.rs create mode 100644 src/frame/head.rs create mode 100644 src/frame/headers.rs create mode 100644 src/frame/mod.rs create mode 100644 src/frame/settings.rs create mode 100644 src/frame/unknown.rs create mode 100644 src/frame/util.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6a7dddd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "h2" +version = "0.1.0" +authors = ["Carl Lerche "] + +[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" } diff --git a/src/frame/data.rs b/src/frame/data.rs new file mode 100644 index 0000000..81a4b01 --- /dev/null +++ b/src/frame/data.rs @@ -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, +} + +#[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 { + 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 + } +} diff --git a/src/frame/head.rs b/src/frame/head.rs new file mode 100644 index 0000000..49e8b42 --- /dev/null +++ b/src/frame/head.rs @@ -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, + } + } +} diff --git a/src/frame/headers.rs b/src/frame/headers.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/frame/mod.rs b/src/frame/mod.rs new file mode 100644 index 0000000..971b934 --- /dev/null +++ b/src/frame/mod.rs @@ -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!(), + } + } +} diff --git a/src/frame/settings.rs b/src/frame/settings.rs new file mode 100644 index 0000000..257df5a --- /dev/null +++ b/src/frame/settings.rs @@ -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, + enable_push: Option, + max_concurrent_streams: Option, + initial_window_size: Option, + max_frame_size: Option, + max_header_list_size: Option, +} + +/// 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 { + 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 { + 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 { + 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 + } +} diff --git a/src/frame/unknown.rs b/src/frame/unknown.rs new file mode 100644 index 0000000..200d2be --- /dev/null +++ b/src/frame/unknown.rs @@ -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, + } + } +} diff --git a/src/frame/util.rs b/src/frame/util.rs new file mode 100644 index 0000000..fa4cd2f --- /dev/null +++ b/src/frame/util.rs @@ -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 { + 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) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..66001b0 --- /dev/null +++ b/src/lib.rs @@ -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 { + inner: length_delimited::FramedRead, +} + +pub fn bind(io: T) -> Transport { + 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, + } +}