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