// The HTTP/2 stream lifecycle state machine (RFC 7540 §5.1) and the stream-id
// parity rules (§5.1.1). Pure and total: every legal transition is enumerated,
// and any illegal one raises rather than silently mis-stepping.
///|
/// The lifecycle state of a single HTTP/2 stream (RFC 7540 §5.1).
pub(all) enum StreamState {
Idle
ReservedLocal
ReservedRemote
Open
HalfClosedLocal
HalfClosedRemote
Closed
} derive(Eq, Debug)
///|
pub impl Show for StreamState with fn output(self, logger) {
logger.write_string(
match self {
Idle => "idle"
ReservedLocal => "reserved(local)"
ReservedRemote => "reserved(remote)"
Open => "open"
HalfClosedLocal => "half-closed(local)"
HalfClosedRemote => "half-closed(remote)"
Closed => "closed"
},
)
}
///|
/// A state-changing stream event: the arrival or departure of the frames that
/// drive §5.1 transitions. `Headers`/`Data` carry the `END_STREAM` flag; `Reserve`
/// is a PUSH_PROMISE reserving this (promised) stream. Frames that never change
/// stream state (PRIORITY, WINDOW_UPDATE, SETTINGS, PING) are intentionally absent.
pub(all) enum StreamEvent {
Headers(end_stream~ : Bool)
Data(end_stream~ : Bool)
Reserve
RstStream
} derive(Eq)
///|
/// An illegal stream transition (RFC 7540 §5.1): a frame not permitted in the
/// current state (typically a `STREAM_CLOSED` or `PROTOCOL_ERROR` condition).
pub suberror StreamError {
InvalidTransition(String)
} derive(Eq)
///|
pub impl Show for StreamError with fn output(self, logger) {
match self {
InvalidTransition(m) => logger.write_string("InvalidTransition(" + m + ")")
}
}
///|
fn bad(from : StreamState, dir : String, ev : StreamEvent) -> StreamError {
let name = match ev {
Headers(..) => "HEADERS"
Data(..) => "DATA"
Reserve => "PUSH_PROMISE"
RstStream => "RST_STREAM"
}
InvalidTransition(
dir + " " + name + " not allowed in state " + from.to_string(),
)
}
///|
/// The next state after *sending* `ev` from this state (RFC 7540 §5.1, local
/// side). Raises `InvalidTransition` for a frame illegal in the current state.
pub fn StreamState::on_send(
self : StreamState,
ev : StreamEvent,
) -> StreamState raise StreamError {
match (self, ev) {
(Idle, Headers(end_stream~)) =>
if end_stream {
HalfClosedLocal
} else {
Open
}
(Idle, Reserve) => ReservedLocal
(ReservedLocal, Headers(end_stream~)) =>
if end_stream {
Closed
} else {
HalfClosedRemote
}
(ReservedLocal, RstStream) => Closed
(ReservedRemote, RstStream) => Closed
(Open, Headers(end_stream~)) | (Open, Data(end_stream~)) =>
if end_stream {
HalfClosedLocal
} else {
Open
}
(Open, RstStream) => Closed
(HalfClosedRemote, Headers(end_stream~))
| (HalfClosedRemote, Data(end_stream~)) =>
if end_stream {
Closed
} else {
HalfClosedRemote
}
(HalfClosedRemote, RstStream) => Closed
(HalfClosedLocal, RstStream) => Closed
_ => raise bad(self, "sending", ev)
}
}
///|
/// The next state after *receiving* `ev` in this state (RFC 7540 §5.1, remote
/// side — the mirror of `on_send`). Raises `InvalidTransition` on an illegal frame.
pub fn StreamState::on_recv(
self : StreamState,
ev : StreamEvent,
) -> StreamState raise StreamError {
match (self, ev) {
(Idle, Headers(end_stream~)) =>
if end_stream {
HalfClosedRemote
} else {
Open
}
(Idle, Reserve) => ReservedRemote
(ReservedRemote, Headers(end_stream~)) =>
if end_stream {
Closed
} else {
HalfClosedLocal
}
(ReservedRemote, RstStream) => Closed
(ReservedLocal, RstStream) => Closed
(Open, Headers(end_stream~)) | (Open, Data(end_stream~)) =>
if end_stream {
HalfClosedRemote
} else {
Open
}
(Open, RstStream) => Closed
(HalfClosedLocal, Headers(end_stream~))
| (HalfClosedLocal, Data(end_stream~)) =>
if end_stream {
Closed
} else {
HalfClosedLocal
}
(HalfClosedLocal, RstStream) => Closed
(HalfClosedRemote, RstStream) => Closed
_ => raise bad(self, "receiving", ev)
}
}
///|
/// A mutable stream: its identifier and current lifecycle state. `send`/`recv`
/// advance the state in place, raising on an illegal transition.
pub(all) struct Stream {
id : Int
mut state : StreamState
}
///|
/// A fresh idle stream with the given identifier.
pub fn Stream::new(id : Int) -> Stream {
{ id, state: Idle }
}
///|
/// Advance this stream by sending `ev`, returning the new state.
pub fn Stream::send(
self : Stream,
ev : StreamEvent,
) -> StreamState raise StreamError {
self.state = self.state.on_send(ev)
self.state
}
///|
/// Advance this stream by receiving `ev`, returning the new state.
pub fn Stream::recv(
self : Stream,
ev : StreamEvent,
) -> StreamState raise StreamError {
self.state = self.state.on_recv(ev)
self.state
}
// -- stream identifier rules (RFC 7540 §5.1.1) ------------------------------
///|
/// Whether `id` is a client-initiated stream: a non-zero odd identifier.
pub fn stream_is_client_initiated(id : Int) -> Bool {
id > 0 && id % 2 == 1
}
///|
/// Whether `id` is a server-initiated (pushed) stream: a non-zero even identifier.
pub fn stream_is_server_initiated(id : Int) -> Bool {
id > 0 && id % 2 == 0
}
///|
/// Whether a peer that is a client (`by_client = true`) or server may legally
/// *open* stream `id`: clients use odd ids, servers use even ids, and `0` is the
/// connection control stream, openable by neither (RFC 7540 §5.1.1).
pub fn stream_id_valid_for_initiator(id : Int, by_client~ : Bool) -> Bool {
if by_client {
stream_is_client_initiated(id)
} else {
stream_is_server_initiated(id)
}
}