Refactor errors internals (#556)

h2::Error now knows whether protocol errors happened because the user
sent them, because it was received from the remote peer, or because
the library itself emitted an error because it detected a protocol
violation.

It also keeps track of whether it came from a RST_STREAM or GO_AWAY
frame, and in the case of the latter, it includes the additional
debug data if any.

Fixes #530
This commit is contained in:
Anthony Ramine
2021-09-28 18:04:35 +02:00
committed by GitHub
parent cab307d2ed
commit 465f0337f8
26 changed files with 464 additions and 432 deletions

View File

@@ -1,7 +1,7 @@
use super::*;
use crate::codec::{RecvError, UserError};
use crate::frame::{PushPromiseHeaderError, Reason, DEFAULT_INITIAL_WINDOW_SIZE};
use crate::{frame, proto};
use crate::codec::UserError;
use crate::frame::{self, PushPromiseHeaderError, Reason, DEFAULT_INITIAL_WINDOW_SIZE};
use crate::proto::{self, Error};
use std::task::Context;
use http::{HeaderMap, Request, Response};
@@ -68,7 +68,7 @@ pub(super) enum Event {
#[derive(Debug)]
pub(super) enum RecvHeaderBlockError<T> {
Oversize(T),
State(RecvError),
State(Error),
}
#[derive(Debug)]
@@ -124,7 +124,7 @@ impl Recv {
id: StreamId,
mode: Open,
counts: &mut Counts,
) -> Result<Option<StreamId>, RecvError> {
) -> Result<Option<StreamId>, Error> {
assert!(self.refused.is_none());
counts.peer().ensure_can_open(id, mode)?;
@@ -132,7 +132,7 @@ impl Recv {
let next_id = self.next_stream_id()?;
if id < next_id {
proto_err!(conn: "id ({:?}) < next_id ({:?})", id, next_id);
return Err(RecvError::Connection(Reason::PROTOCOL_ERROR));
return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
}
self.next_stream_id = id.next_id();
@@ -176,11 +176,7 @@ impl Recv {
Ok(v) => v,
Err(()) => {
proto_err!(stream: "could not parse content-length; stream={:?}", stream.id);
return Err(RecvError::Stream {
id: stream.id,
reason: Reason::PROTOCOL_ERROR,
}
.into());
return Err(Error::library_reset(stream.id, Reason::PROTOCOL_ERROR).into());
}
};
@@ -312,16 +308,13 @@ impl Recv {
&mut self,
frame: frame::Headers,
stream: &mut store::Ptr,
) -> Result<(), RecvError> {
) -> Result<(), Error> {
// Transition the state
stream.state.recv_close()?;
if stream.ensure_content_length_zero().is_err() {
proto_err!(stream: "recv_trailers: content-length is not zero; stream={:?};", stream.id);
return Err(RecvError::Stream {
id: stream.id,
reason: Reason::PROTOCOL_ERROR,
});
return Err(Error::library_reset(stream.id, Reason::PROTOCOL_ERROR));
}
let trailers = frame.into_fields();
@@ -455,7 +448,7 @@ impl Recv {
&mut self,
settings: &frame::Settings,
store: &mut Store,
) -> Result<(), RecvError> {
) -> Result<(), proto::Error> {
let target = if let Some(val) = settings.initial_window_size() {
val
} else {
@@ -502,7 +495,7 @@ impl Recv {
stream
.recv_flow
.inc_window(inc)
.map_err(RecvError::Connection)?;
.map_err(proto::Error::library_go_away)?;
stream.recv_flow.assign_capacity(inc);
Ok(())
})
@@ -520,11 +513,7 @@ impl Recv {
stream.pending_recv.is_empty()
}
pub fn recv_data(
&mut self,
frame: frame::Data,
stream: &mut store::Ptr,
) -> Result<(), RecvError> {
pub fn recv_data(&mut self, frame: frame::Data, stream: &mut store::Ptr) -> Result<(), Error> {
let sz = frame.payload().len();
// This should have been enforced at the codec::FramedRead layer, so
@@ -542,7 +531,7 @@ impl Recv {
// Receiving a DATA frame when not expecting one is a protocol
// error.
proto_err!(conn: "unexpected DATA frame; stream={:?}", stream.id);
return Err(RecvError::Connection(Reason::PROTOCOL_ERROR));
return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
}
tracing::trace!(
@@ -557,7 +546,7 @@ impl Recv {
"recv_data; frame ignored on locally reset {:?} for some time",
stream.id,
);
return self.ignore_data(sz);
return Ok(self.ignore_data(sz)?);
}
// Ensure that there is enough capacity on the connection before acting
@@ -573,10 +562,7 @@ impl Recv {
// So, for violating the **stream** window, we can send either a
// stream or connection error. We've opted to send a stream
// error.
return Err(RecvError::Stream {
id: stream.id,
reason: Reason::FLOW_CONTROL_ERROR,
});
return Err(Error::library_reset(stream.id, Reason::FLOW_CONTROL_ERROR));
}
if stream.dec_content_length(frame.payload().len()).is_err() {
@@ -585,10 +571,7 @@ impl Recv {
stream.id,
frame.payload().len(),
);
return Err(RecvError::Stream {
id: stream.id,
reason: Reason::PROTOCOL_ERROR,
});
return Err(Error::library_reset(stream.id, Reason::PROTOCOL_ERROR));
}
if frame.is_end_stream() {
@@ -598,15 +581,12 @@ impl Recv {
stream.id,
frame.payload().len(),
);
return Err(RecvError::Stream {
id: stream.id,
reason: Reason::PROTOCOL_ERROR,
});
return Err(Error::library_reset(stream.id, Reason::PROTOCOL_ERROR));
}
if stream.state.recv_close().is_err() {
proto_err!(conn: "recv_data: failed to transition to closed state; stream={:?}", stream.id);
return Err(RecvError::Connection(Reason::PROTOCOL_ERROR));
return Err(Error::library_go_away(Reason::PROTOCOL_ERROR).into());
}
}
@@ -625,7 +605,7 @@ impl Recv {
Ok(())
}
pub fn ignore_data(&mut self, sz: WindowSize) -> Result<(), RecvError> {
pub fn ignore_data(&mut self, sz: WindowSize) -> Result<(), Error> {
// Ensure that there is enough capacity on the connection...
self.consume_connection_window(sz)?;
@@ -641,14 +621,14 @@ impl Recv {
Ok(())
}
pub fn consume_connection_window(&mut self, sz: WindowSize) -> Result<(), RecvError> {
pub fn consume_connection_window(&mut self, sz: WindowSize) -> Result<(), Error> {
if self.flow.window_size() < sz {
tracing::debug!(
"connection error FLOW_CONTROL_ERROR -- window_size ({:?}) < sz ({:?});",
self.flow.window_size(),
sz,
);
return Err(RecvError::Connection(Reason::FLOW_CONTROL_ERROR));
return Err(Error::library_go_away(Reason::FLOW_CONTROL_ERROR));
}
// Update connection level flow control
@@ -663,7 +643,7 @@ impl Recv {
&mut self,
frame: frame::PushPromise,
stream: &mut store::Ptr,
) -> Result<(), RecvError> {
) -> Result<(), Error> {
stream.state.reserve_remote()?;
if frame.is_over_size() {
// A frame is over size if the decoded header block was bigger than
@@ -682,10 +662,10 @@ impl Recv {
headers frame is over size; promised_id={:?};",
frame.promised_id(),
);
return Err(RecvError::Stream {
id: frame.promised_id(),
reason: Reason::REFUSED_STREAM,
});
return Err(Error::library_reset(
frame.promised_id(),
Reason::REFUSED_STREAM,
));
}
let promised_id = frame.promised_id();
@@ -708,10 +688,7 @@ impl Recv {
promised_id,
),
}
return Err(RecvError::Stream {
id: promised_id,
reason: Reason::PROTOCOL_ERROR,
});
return Err(Error::library_reset(promised_id, Reason::PROTOCOL_ERROR));
}
use super::peer::PollMessage::*;
@@ -741,18 +718,16 @@ impl Recv {
/// Handle remote sending an explicit RST_STREAM.
pub fn recv_reset(&mut self, frame: frame::Reset, stream: &mut Stream) {
// Notify the stream
stream
.state
.recv_reset(frame.reason(), stream.is_pending_send);
stream.state.recv_reset(frame, stream.is_pending_send);
stream.notify_send();
stream.notify_recv();
}
/// Handle a received error
pub fn recv_err(&mut self, err: &proto::Error, stream: &mut Stream) {
/// Handle a connection-level error
pub fn handle_error(&mut self, err: &proto::Error, stream: &mut Stream) {
// Receive an error
stream.state.recv_err(err);
stream.state.handle_error(err);
// If a receiver is waiting, notify it
stream.notify_send();
@@ -783,11 +758,11 @@ impl Recv {
self.max_stream_id
}
pub fn next_stream_id(&self) -> Result<StreamId, RecvError> {
pub fn next_stream_id(&self) -> Result<StreamId, Error> {
if let Ok(id) = self.next_stream_id {
Ok(id)
} else {
Err(RecvError::Connection(Reason::PROTOCOL_ERROR))
Err(Error::library_go_away(Reason::PROTOCOL_ERROR))
}
}
@@ -802,10 +777,10 @@ impl Recv {
}
/// Returns true if the remote peer can reserve a stream with the given ID.
pub fn ensure_can_reserve(&self) -> Result<(), RecvError> {
pub fn ensure_can_reserve(&self) -> Result<(), Error> {
if !self.is_push_enabled {
proto_err!(conn: "recv_push_promise: push is disabled");
return Err(RecvError::Connection(Reason::PROTOCOL_ERROR));
return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
}
Ok(())
@@ -1092,8 +1067,8 @@ impl Open {
// ===== impl RecvHeaderBlockError =====
impl<T> From<RecvError> for RecvHeaderBlockError<T> {
fn from(err: RecvError) -> Self {
impl<T> From<Error> for RecvHeaderBlockError<T> {
fn from(err: Error) -> Self {
RecvHeaderBlockError::State(err)
}
}