Start state transition verification + refactors
This commit is contained in:
@@ -100,6 +100,20 @@ impl<T, P> Stream for Connection<T, P>
|
||||
let stream_id = v.stream_id();
|
||||
let end_of_stream = v.is_end_stream();
|
||||
|
||||
let stream_initialized = try!(self.streams.entry(stream_id)
|
||||
.or_insert(State::default())
|
||||
.recv_headers::<P>(end_of_stream));
|
||||
|
||||
if stream_initialized {
|
||||
// TODO: Ensure available capacity for a new stream
|
||||
// This won't be as simple as self.streams.len() as closed
|
||||
// connections should not be factored.
|
||||
|
||||
if !P::is_valid_remote_stream_id(stream_id) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
Frame::Headers {
|
||||
id: stream_id,
|
||||
headers: P::convert_poll_message(v),
|
||||
@@ -143,22 +157,23 @@ impl<T, P> Sink for Connection<T, P>
|
||||
|
||||
match item {
|
||||
Frame::Headers { id, headers, end_of_stream } => {
|
||||
// Ensure ID is valid
|
||||
// TODO: This check should only be done **if** this is a new
|
||||
// stream ID
|
||||
// try!(P::check_initiating_id(id));
|
||||
|
||||
// TODO: Ensure available capacity for a new stream
|
||||
// This won't be as simple as self.streams.len() as closed
|
||||
// connections should not be factored.
|
||||
|
||||
// Transition the stream state, creating a new entry if needed
|
||||
//
|
||||
// TODO: Response can send multiple headers frames before body
|
||||
// (1xx responses).
|
||||
try!(self.streams.entry(id)
|
||||
let stream_initialized = try!(self.streams.entry(id)
|
||||
.or_insert(State::default())
|
||||
.send_headers());
|
||||
.send_headers::<P>(end_of_stream));
|
||||
|
||||
if stream_initialized {
|
||||
// TODO: Ensure available capacity for a new stream
|
||||
// This won't be as simple as self.streams.len() as closed
|
||||
// connections should not be factored.
|
||||
//
|
||||
if !P::is_valid_local_stream_id(id) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
let frame = P::convert_send_message(id, headers, end_of_stream);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ConnectionError;
|
||||
use {ConnectionError, Reason, Peer};
|
||||
|
||||
/// Represents the state of an H2 stream
|
||||
///
|
||||
@@ -45,23 +45,134 @@ pub enum State {
|
||||
Idle,
|
||||
ReservedLocal,
|
||||
ReservedRemote,
|
||||
Open,
|
||||
HalfClosedLocal,
|
||||
HalfClosedRemote,
|
||||
Open {
|
||||
local: PeerState,
|
||||
remote: PeerState,
|
||||
},
|
||||
HalfClosedLocal(PeerState),
|
||||
HalfClosedRemote(PeerState),
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum PeerState {
|
||||
Headers,
|
||||
Data,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Transition the state to represent headers being received.
|
||||
///
|
||||
/// Returns true if this state transition results in iniitializing the
|
||||
/// stream id. `Err` is returned if this is an invalid state transition.
|
||||
pub fn recv_headers<P: Peer>(&mut self, eos: bool) -> Result<bool, ConnectionError> {
|
||||
use self::State::*;
|
||||
use self::PeerState::*;
|
||||
|
||||
match *self {
|
||||
Idle => {
|
||||
*self = if eos {
|
||||
HalfClosedRemote(Headers)
|
||||
} else {
|
||||
Open {
|
||||
local: Headers,
|
||||
remote: Data,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
Open { local, remote } => {
|
||||
try!(remote.check_is_headers(Reason::ProtocolError));
|
||||
|
||||
*self = if eos {
|
||||
HalfClosedRemote(local)
|
||||
} else {
|
||||
let remote = Data;
|
||||
Open { local, remote }
|
||||
};
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
HalfClosedLocal(remote) => {
|
||||
try!(remote.check_is_headers(Reason::ProtocolError));
|
||||
|
||||
*self = if eos {
|
||||
Closed
|
||||
} else {
|
||||
HalfClosedLocal(Data)
|
||||
};
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
Closed | HalfClosedRemote(..) => {
|
||||
Err(Reason::ProtocolError.into())
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition the state to represent headers being sent.
|
||||
///
|
||||
/// Returns an error if this is an invalid state transition.
|
||||
pub fn send_headers(&mut self) -> Result<(), ConnectionError> {
|
||||
if *self != State::Idle {
|
||||
unimplemented!();
|
||||
}
|
||||
/// Returns true if this state transition results in initializing the stream
|
||||
/// id. `Err` is returned if this is an invalid state transition.
|
||||
pub fn send_headers<P: Peer>(&mut self, eos: bool) -> Result<bool, ConnectionError> {
|
||||
use self::State::*;
|
||||
use self::PeerState::*;
|
||||
|
||||
*self = State::Open;
|
||||
Ok(())
|
||||
match *self {
|
||||
Idle => {
|
||||
*self = if eos {
|
||||
HalfClosedLocal(Headers)
|
||||
} else {
|
||||
Open {
|
||||
local: Data,
|
||||
remote: Headers,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
Open { local, remote } => {
|
||||
try!(local.check_is_headers(Reason::InternalError));
|
||||
|
||||
*self = if eos {
|
||||
HalfClosedLocal(remote)
|
||||
} else {
|
||||
let local = Data;
|
||||
Open { local, remote }
|
||||
};
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
HalfClosedRemote(local) => {
|
||||
try!(local.check_is_headers(Reason::InternalError));
|
||||
|
||||
*self = if eos {
|
||||
Closed
|
||||
} else {
|
||||
HalfClosedRemote(Data)
|
||||
};
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
Closed | HalfClosedLocal(..) => {
|
||||
Err(Reason::InternalError.into())
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerState {
|
||||
#[inline]
|
||||
fn check_is_headers(&self, err: Reason) -> Result<(), ConnectionError> {
|
||||
use self::PeerState::*;
|
||||
|
||||
match *self {
|
||||
Headers => Ok(()),
|
||||
_ => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user