Refactor errors (#46)

This patch does a bunch of refactoring, mostly around error types, but it also
paves the way to allow `Codec` to be used standalone.

* `Codec` (and `FramedRead` / `FramedWrite`) is broken out into a codec module.
* An h2-codec crate is created that re-exports the frame and codec modules.
* New error types are introduced in the internals:
  * `RecvError` represents errors caused by trying to receive a frame.
  * `SendError` represents errors caused by trying to send a frame.
  * `UserError` is an enum of potential errors caused by invalid usage
    by the user of the lib.
  * `ProtoError` is either a `Reason` or an `io::Error`. However it doesn't
    specify connection or stream level.
  * `h2::Error` is an opaque error type and is the only error type exposed
    by the public API (used to be `ConnectionError`).

There are misc code changes to enable this as well. The biggest is a new "sink"
API for `Codec`. It provides buffer which queues up a frame followed by flush
which writes everything that is queued. This departs from the `Sink` trait in
order to provide more accurate error values. For example, buffer can never fail
(but it will panic if `poll_ready` is not called first).
This commit is contained in:
Carl Lerche
2017-09-02 11:12:50 -07:00
committed by GitHub
parent 6fd9674759
commit c122e97127
37 changed files with 1043 additions and 1027 deletions

121
src/codec/error.rs Normal file
View File

@@ -0,0 +1,121 @@
use frame::{Reason, StreamId};
use std::{error, fmt, io};
/// Errors that are received
#[derive(Debug)]
pub enum RecvError {
Connection(Reason),
Stream {
id: StreamId,
reason: Reason,
},
Io(io::Error),
}
/// Errors caused by sending a message
#[derive(Debug)]
pub enum SendError {
/// User error
User(UserError),
/// I/O error
Io(io::Error),
}
/// Errors caused by users of the library
#[derive(Debug)]
pub enum UserError {
/// The stream ID is no longer accepting frames.
InactiveStreamId,
/// The stream is not currently expecting a frame of this type.
UnexpectedFrameType,
/// The payload size is too big
PayloadTooBig,
/// The application attempted to initiate too many streams to remote.
Rejected,
}
// ===== impl RecvError =====
impl From<io::Error> for RecvError {
fn from(src: io::Error) -> Self {
RecvError::Io(src)
}
}
impl error::Error for RecvError {
fn description(&self) -> &str {
use self::RecvError::*;
match *self {
Connection(ref reason) => reason.description(),
Stream { ref reason, .. } => reason.description(),
Io(ref e) => e.description(),
}
}
}
impl fmt::Display for RecvError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
// ===== impl SendError =====
impl error::Error for SendError {
fn description(&self) -> &str {
use self::SendError::*;
match *self {
User(ref e) => e.description(),
Io(ref e) => e.description(),
}
}
}
impl fmt::Display for SendError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl From<io::Error> for SendError {
fn from(src: io::Error) -> Self {
SendError::Io(src)
}
}
impl From<UserError> for SendError {
fn from(src: UserError) -> Self {
SendError::User(src)
}
}
// ===== impl UserError =====
impl error::Error for UserError {
fn description(&self) -> &str {
use self::UserError::*;
match *self {
InactiveStreamId => "inactive stream",
UnexpectedFrameType => "unexpected frame type",
PayloadTooBig => "payload too big",
Rejected => "rejected",
}
}
}
impl fmt::Display for UserError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}

248
src/codec/framed_read.rs Normal file
View File

@@ -0,0 +1,248 @@
use codec::RecvError;
use frame::{self, Frame, Kind};
use frame::DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
use frame::Reason::*;
use hpack;
use futures::*;
use bytes::BytesMut;
use tokio_io::AsyncRead;
use tokio_io::codec::length_delimited;
#[derive(Debug)]
pub struct FramedRead<T> {
inner: length_delimited::FramedRead<T>,
// hpack decoder state
hpack: hpack::Decoder,
partial: Option<Partial>,
}
/// Partially loaded headers frame
#[derive(Debug)]
struct Partial {
/// Empty frame
frame: Continuable,
/// Partial header payload
buf: BytesMut,
}
#[derive(Debug)]
enum Continuable {
Headers(frame::Headers),
// Decode the Continuation frame but ignore it...
// Ignore(StreamId),
// PushPromise(frame::PushPromise),
}
impl<T> FramedRead<T> {
pub fn new(inner: length_delimited::FramedRead<T>) -> FramedRead<T> {
FramedRead {
inner: inner,
hpack: hpack::Decoder::new(DEFAULT_SETTINGS_HEADER_TABLE_SIZE),
partial: None,
}
}
pub fn apply_remote_settings(&mut self, _settings: &frame::Settings) {
// TODO: Is this needed?
}
fn decode_frame(&mut self, mut bytes: BytesMut) -> Result<Option<Frame>, RecvError> {
use self::RecvError::*;
trace!("decoding frame from {}B", bytes.len());
// Parse the head
let head = frame::Head::parse(&bytes);
if self.partial.is_some() && head.kind() != Kind::Continuation {
return Err(Connection(ProtocolError));
}
let kind = head.kind();
trace!(" -> kind={:?}", kind);
let frame = match kind {
Kind::Settings => {
let res = frame::Settings::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::Ping => {
let res = frame::Ping::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::WindowUpdate => {
let res = frame::WindowUpdate::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::Data => {
let _ = bytes.split_to(frame::HEADER_LEN);
let res = frame::Data::load(head, bytes.freeze());
// TODO: Should this always be connection level? Probably not...
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::Headers => {
// Drop the frame header
// TODO: Change to drain: carllerche/bytes#130
let _ = bytes.split_to(frame::HEADER_LEN);
// Parse the header frame w/o parsing the payload
let (mut headers, payload) = match frame::Headers::load(head, bytes) {
Ok(res) => res,
Err(frame::Error::InvalidDependencyId) => {
// A stream cannot depend on itself. An endpoint MUST
// treat this as a stream error (Section 5.4.2) of type
// `PROTOCOL_ERROR`.
return Err(Stream {
id: head.stream_id(),
reason: ProtocolError,
});
}
_ => return Err(Connection(ProtocolError)),
};
if headers.is_end_headers() {
// Load the HPACK encoded headers & return the frame
match headers.load_hpack(payload, &mut self.hpack) {
Ok(_) => {}
Err(frame::Error::MalformedMessage) => {
return Err(Stream {
id: head.stream_id(),
reason: ProtocolError,
});
}
Err(_) => return Err(Connection(ProtocolError)),
}
headers.into()
} else {
// Defer loading the frame
self.partial = Some(Partial {
frame: Continuable::Headers(headers),
buf: payload,
});
return Ok(None);
}
}
Kind::Reset => {
let res = frame::Reset::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::GoAway => {
let res = frame::GoAway::load(&bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::PushPromise => {
let res = frame::PushPromise::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
}
Kind::Priority => {
if head.stream_id() == 0 {
// Invalid stream identifier
return Err(Connection(ProtocolError));
}
match frame::Priority::load(head, &bytes[frame::HEADER_LEN..]) {
Ok(frame) => frame.into(),
Err(frame::Error::InvalidDependencyId) => {
// A stream cannot depend on itself. An endpoint MUST
// treat this as a stream error (Section 5.4.2) of type
// `PROTOCOL_ERROR`.
return Err(Stream {
id: head.stream_id(),
reason: ProtocolError,
});
}
Err(_) => return Err(Connection(ProtocolError)),
}
}
Kind::Continuation => {
// TODO: Un-hack this
let end_of_headers = (head.flag() & 0x4) == 0x4;
let mut partial = match self.partial.take() {
Some(partial) => partial,
None => return Err(Connection(ProtocolError)),
};
// Extend the buf
partial.buf.extend_from_slice(&bytes[frame::HEADER_LEN..]);
if !end_of_headers {
self.partial = Some(partial);
return Ok(None);
}
match partial.frame {
Continuable::Headers(mut frame) => {
// The stream identifiers must match
if frame.stream_id() != head.stream_id() {
return Err(Connection(ProtocolError));
}
match frame.load_hpack(partial.buf, &mut self.hpack) {
Ok(_) => {}
Err(frame::Error::MalformedMessage) => {
return Err(Stream {
id: head.stream_id(),
reason: ProtocolError,
});
}
Err(_) => return Err(Connection(ProtocolError)),
}
frame.into()
}
}
}
Kind::Unknown => {
// Unknown frames are ignored
return Ok(None);
}
};
Ok(Some(frame))
}
pub fn get_ref(&self) -> &T {
self.inner.get_ref()
}
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut()
}
}
impl<T> Stream for FramedRead<T>
where T: AsyncRead,
{
type Item = Frame;
type Error = RecvError;
fn poll(&mut self) -> Poll<Option<Frame>, Self::Error> {
loop {
trace!("poll");
let bytes = match try_ready!(self.inner.poll()) {
Some(bytes) => bytes,
None => return Ok(Async::Ready(None)),
};
trace!("poll; bytes={}B", bytes.len());
if let Some(frame) = try!(self.decode_frame(bytes)) {
return Ok(Async::Ready(Some(frame)));
}
}
}
}

260
src/codec/framed_write.rs Normal file
View File

@@ -0,0 +1,260 @@
use codec::UserError;
use codec::UserError::*;
use frame::{self, Frame, FrameSize};
use hpack;
use futures::*;
use tokio_io::{AsyncRead, AsyncWrite};
use bytes::{BytesMut, Buf, BufMut};
use std::io::{self, Cursor};
#[derive(Debug)]
pub struct FramedWrite<T, B> {
/// Upstream `AsyncWrite`
inner: T,
/// HPACK encoder
hpack: hpack::Encoder,
/// Write buffer
///
/// TODO: Should this be a ring buffer?
buf: Cursor<BytesMut>,
/// Next frame to encode
next: Option<Next<B>>,
/// Last data frame
last_data_frame: Option<frame::Data<B>>,
/// Max frame size, this is specified by the peer
max_frame_size: FrameSize,
}
#[derive(Debug)]
enum Next<B> {
Data(frame::Data<B>),
Continuation(frame::Continuation),
}
/// Initialze the connection with this amount of write buffer.
const DEFAULT_BUFFER_CAPACITY: usize = 4 * 1_024;
/// Min buffer required to attempt to write a frame
const MIN_BUFFER_CAPACITY: usize = frame::HEADER_LEN + CHAIN_THRESHOLD;
/// Chain payloads bigger than this. The remote will never advertise a max frame
/// size less than this (well, the spec says the max frame size can't be less
/// than 16kb, so not even close).
const CHAIN_THRESHOLD: usize = 256;
// TODO: Make generic
impl<T, B> FramedWrite<T, B>
where T: AsyncWrite,
B: Buf,
{
pub fn new(inner: T) -> FramedWrite<T, B> {
FramedWrite {
inner: inner,
hpack: hpack::Encoder::default(),
buf: Cursor::new(BytesMut::with_capacity(DEFAULT_BUFFER_CAPACITY)),
next: None,
last_data_frame: None,
max_frame_size: frame::DEFAULT_MAX_FRAME_SIZE,
}
}
/// Returns `Ready` when `send` is able to accept a frame
///
/// Calling this function may result in the current contents of the buffer
/// to be flushed to `T`.
pub fn poll_ready(&mut self) -> Poll<(), io::Error> {
if !self.has_capacity() {
// Try flushing
try!(self.flush());
if !self.has_capacity() {
return Ok(Async::NotReady);
}
}
Ok(Async::Ready(()))
}
/// Buffer a frame.
///
/// `poll_ready` must be called first to ensure that a frame may be
/// accepted.
pub fn buffer(&mut self, item: Frame<B>) -> Result<(), UserError> {
// Ensure that we have enough capacity to accept the write.
assert!(self.has_capacity());
debug!("send; frame={:?}", item);
match item {
Frame::Data(mut v) => {
// Ensure that the payload is not greater than the max frame.
let len = v.payload().remaining();
if len > self.max_frame_size() {
return Err(PayloadTooBig);
}
if len >= CHAIN_THRESHOLD {
let head = v.head();
// Encode the frame head to the buffer
head.encode(len, self.buf.get_mut());
// Save the data frame
self.next = Some(Next::Data(v));
} else {
v.encode_chunk(self.buf.get_mut());
// The chunk has been fully encoded, so there is no need to
// keep it around
assert_eq!(v.payload().remaining(), 0, "chunk not fully encoded");
// Save off the last frame...
self.last_data_frame = Some(v);
}
}
Frame::Headers(v) => {
if let Some(continuation) = v.encode(&mut self.hpack, self.buf.get_mut()) {
self.next = Some(Next::Continuation(continuation));
}
}
Frame::PushPromise(v) => {
debug!("unimplemented PUSH_PROMISE write; frame={:?}", v);
unimplemented!();
}
Frame::Settings(v) => {
v.encode(self.buf.get_mut());
trace!("encoded settings; rem={:?}", self.buf.remaining());
}
Frame::GoAway(v) => {
v.encode(self.buf.get_mut());
trace!("encoded go_away; rem={:?}", self.buf.remaining());
}
Frame::Ping(v) => {
v.encode(self.buf.get_mut());
trace!("encoded ping; rem={:?}", self.buf.remaining());
}
Frame::WindowUpdate(v) => {
v.encode(self.buf.get_mut());
trace!("encoded window_update; rem={:?}", self.buf.remaining());
}
Frame::Priority(_) => {
/*
v.encode(self.buf.get_mut());
trace!("encoded priority; rem={:?}", self.buf.remaining());
*/
unimplemented!();
}
Frame::Reset(v) => {
v.encode(self.buf.get_mut());
trace!("encoded reset; rem={:?}", self.buf.remaining());
}
}
Ok(())
}
/// Flush buffered data to the wire
pub fn flush(&mut self) -> Poll<(), io::Error> {
trace!("flush");
while !self.is_empty() {
match self.next {
Some(Next::Data(ref mut frame)) => {
let mut buf = Buf::by_ref(&mut self.buf).chain(frame.payload_mut());
try_ready!(self.inner.write_buf(&mut buf));
}
_ => {
try_ready!(self.inner.write_buf(&mut self.buf));
}
}
}
// The data frame has been written, so unset it
match self.next.take() {
Some(Next::Data(frame)) => {
self.last_data_frame = Some(frame);
}
Some(Next::Continuation(_)) => {
unimplemented!();
}
None => {}
}
trace!("flushing buffer");
// Flush the upstream
try_nb!(self.inner.flush());
// Clear internal buffer
self.buf.set_position(0);
self.buf.get_mut().clear();
Ok(Async::Ready(()))
}
/// Close the codec
pub fn shutdown(&mut self) -> Poll<(), io::Error> {
try_ready!(self.flush());
self.inner.shutdown().map_err(Into::into)
}
fn has_capacity(&self) -> bool {
self.next.is_none() && self.buf.get_ref().remaining_mut() >= MIN_BUFFER_CAPACITY
}
fn is_empty(&self) -> bool {
match self.next {
Some(Next::Data(ref frame)) => !frame.payload().has_remaining(),
_ => !self.buf.has_remaining(),
}
}
}
impl<T, B> FramedWrite<T, B> {
/// Returns the max frame size that can be sent
pub fn max_frame_size(&self) -> usize {
self.max_frame_size as usize
}
/// Apply settings received by the peer
pub fn apply_remote_settings(&mut self, settings: &frame::Settings) {
if let Some(val) = settings.max_frame_size() {
self.max_frame_size = val;
}
}
/// Retrieve the last data frame that has been sent
pub fn take_last_data_frame(&mut self) -> Option<frame::Data<B>> {
self.last_data_frame.take()
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: io::Read, B> io::Read for FramedWrite<T, B> {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
self.inner.read(dst)
}
}
impl<T: AsyncRead, B> AsyncRead for FramedWrite<T, B> {
fn read_buf<B2: BufMut>(&mut self, buf: &mut B2) -> Poll<usize, io::Error>
where Self: Sized,
{
self.inner.read_buf(buf)
}
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.inner.prepare_uninitialized_buffer(buf)
}
}

148
src/codec/mod.rs Normal file
View File

@@ -0,0 +1,148 @@
mod error;
mod framed_read;
mod framed_write;
pub use self::error::{SendError, RecvError, UserError};
use self::framed_read::FramedRead;
use self::framed_write::FramedWrite;
use frame::{self, Frame, Data, Settings};
use futures::*;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::codec::length_delimited;
use bytes::Buf;
use std::io;
#[derive(Debug)]
pub struct Codec<T, B> {
inner: FramedRead<FramedWrite<T, B>>,
}
impl<T, B> Codec<T, B>
where T: AsyncRead + AsyncWrite,
B: Buf,
{
pub fn new(io: T) -> Self {
// Wrap with writer
let framed_write = FramedWrite::new(io);
// Delimit the frames
let delimited = length_delimited::Builder::new()
.big_endian()
.length_field_length(3)
.length_adjustment(9)
.num_skip(0) // Don't skip the header
// TODO: make this configurable and allow it to be changed during
// runtime.
.max_frame_length(frame::DEFAULT_MAX_FRAME_SIZE as usize)
.new_read(framed_write);
let inner = FramedRead::new(delimited);
Codec { inner }
}
}
impl<T, B> Codec<T, B> {
/// Apply a settings received from the peer
pub fn apply_remote_settings(&mut self, frame: &Settings) {
self.framed_read().apply_remote_settings(frame);
self.framed_write().apply_remote_settings(frame);
}
/// Takes the data payload value that was fully written to the socket
pub fn take_last_data_frame(&mut self) -> Option<Data<B>> {
self.framed_write().take_last_data_frame()
}
/// Returns the max frame size that can be sent to the peer
pub fn max_send_frame_size(&self) -> usize {
self.inner.get_ref().max_frame_size()
}
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut().get_mut()
}
fn framed_read(&mut self) -> &mut FramedRead<FramedWrite<T, B>> {
&mut self.inner
}
fn framed_write(&mut self) -> &mut FramedWrite<T, B> {
self.inner.get_mut()
}
}
impl<T, B> Codec<T, B>
where T: AsyncWrite,
B: Buf,
{
/// Returns `Ready` when the codec can buffer a frame
pub fn poll_ready(&mut self) -> Poll<(), io::Error> {
self.framed_write().poll_ready()
}
/// Buffer a frame.
///
/// `poll_ready` must be called first to ensure that a frame may be
/// accepted.
///
/// TODO: Rename this to avoid conflicts with Sink::buffer
pub fn buffer(&mut self, item: Frame<B>) -> Result<(), UserError>
{
self.framed_write().buffer(item)
}
/// Flush buffered data to the wire
pub fn flush(&mut self) -> Poll<(), io::Error> {
self.framed_write().flush()
}
/// Shutdown the send half
pub fn shutdown(&mut self) -> Poll<(), io::Error> {
self.framed_write().shutdown()
}
}
impl<T, B> Stream for Codec<T, B>
where T: AsyncRead,
{
type Item = Frame;
type Error = RecvError;
fn poll(&mut self) -> Poll<Option<Frame>, Self::Error> {
self.inner.poll()
}
}
impl<T, B> Sink for Codec<T, B>
where T: AsyncWrite,
B: Buf,
{
type SinkItem = Frame<B>;
type SinkError = SendError;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
if !self.poll_ready()?.is_ready() {
return Ok(AsyncSink::NotReady(item));
}
self.buffer(item)?;
Ok(AsyncSink::Ready)
}
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
self.flush()?;
Ok(Async::Ready(()))
}
fn close(&mut self) -> Poll<(), Self::SinkError> {
self.shutdown()?;
Ok(Async::Ready(()))
}
}