1use crate::error::Http2Error;
7
8pub type StreamId = u32;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum StreamState {
14 Idle,
16 ReservedLocal,
18 ReservedRemote,
20 Open,
22 HalfClosedLocal,
24 HalfClosedRemote,
26 Closed,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum StreamEvent {
33 RecvHeaders,
35 RecvEndStream,
37 SendHeaders,
39 SendEndStream,
41 RecvPushPromise,
43 SendPushPromise,
45 Reset,
47}
48
49pub fn transition(state: StreamState, event: StreamEvent) -> Result<StreamState, Http2Error> {
54 use StreamEvent as E;
55 use StreamState as S;
56 let next = match (state, event) {
57 (_, E::Reset) => S::Closed,
58 (S::Idle, E::RecvHeaders) => S::Open,
59 (S::Idle, E::SendHeaders) => S::Open,
60 (S::Idle, E::RecvPushPromise) => S::ReservedRemote,
61 (S::Idle, E::SendPushPromise) => S::ReservedLocal,
62 (S::ReservedLocal, E::SendHeaders) => S::HalfClosedRemote,
63 (S::ReservedLocal, E::SendEndStream) => S::Closed,
64 (S::ReservedRemote, E::RecvHeaders) => S::HalfClosedLocal,
65 (S::ReservedRemote, E::RecvEndStream) => S::Closed,
66 (S::Open, E::SendEndStream) => S::HalfClosedLocal,
67 (S::Open, E::RecvEndStream) => S::HalfClosedRemote,
68 (S::Open, E::RecvHeaders | E::SendHeaders) => S::Open,
69 (S::HalfClosedLocal, E::RecvEndStream) => S::Closed,
70 (S::HalfClosedLocal, E::RecvHeaders) => S::HalfClosedLocal,
71 (S::HalfClosedRemote, E::SendEndStream) => S::Closed,
72 (S::HalfClosedRemote, E::SendHeaders) => S::HalfClosedRemote,
73 (S::Closed, _) => return Err(Http2Error::InvalidState),
74 _ => return Err(Http2Error::InvalidState),
75 };
76 Ok(next)
77}
78
79#[must_use]
81pub fn is_client_initiated(stream_id: StreamId) -> bool {
82 stream_id != 0 && stream_id % 2 == 1
83}
84
85#[must_use]
87pub fn is_server_initiated(stream_id: StreamId) -> bool {
88 stream_id != 0 && stream_id % 2 == 0
89}
90
91#[cfg(test)]
92#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn idle_to_open_via_headers() {
98 assert_eq!(
99 transition(StreamState::Idle, StreamEvent::RecvHeaders).unwrap(),
100 StreamState::Open
101 );
102 }
103
104 #[test]
105 fn open_send_end_stream_half_closes_local() {
106 assert_eq!(
107 transition(StreamState::Open, StreamEvent::SendEndStream).unwrap(),
108 StreamState::HalfClosedLocal
109 );
110 }
111
112 #[test]
113 fn half_closed_local_recv_end_stream_closes() {
114 assert_eq!(
115 transition(StreamState::HalfClosedLocal, StreamEvent::RecvEndStream).unwrap(),
116 StreamState::Closed
117 );
118 }
119
120 #[test]
121 fn reset_from_any_state_closes() {
122 for s in [
123 StreamState::Idle,
124 StreamState::Open,
125 StreamState::HalfClosedLocal,
126 StreamState::HalfClosedRemote,
127 StreamState::ReservedLocal,
128 StreamState::ReservedRemote,
129 ] {
130 assert_eq!(
131 transition(s, StreamEvent::Reset).unwrap(),
132 StreamState::Closed
133 );
134 }
135 }
136
137 #[test]
138 fn reserved_local_send_headers_half_closes_remote() {
139 assert_eq!(
140 transition(StreamState::ReservedLocal, StreamEvent::SendHeaders).unwrap(),
141 StreamState::HalfClosedRemote
142 );
143 }
144
145 #[test]
146 fn closed_state_rejects_non_reset_events() {
147 assert!(transition(StreamState::Closed, StreamEvent::SendHeaders).is_err());
148 }
149
150 #[test]
151 fn idle_send_end_stream_invalid() {
152 assert!(transition(StreamState::Idle, StreamEvent::SendEndStream).is_err());
153 }
154
155 #[test]
156 fn client_streams_are_odd() {
157 assert!(is_client_initiated(1));
158 assert!(is_client_initiated(3));
159 assert!(!is_client_initiated(0));
160 assert!(!is_client_initiated(2));
161 }
162
163 #[test]
164 fn server_streams_are_even() {
165 assert!(is_server_initiated(2));
166 assert!(is_server_initiated(4));
167 assert!(!is_server_initiated(0));
168 assert!(!is_server_initiated(1));
169 }
170
171 #[test]
172 fn open_recv_headers_stays_open_for_trailers() {
173 assert_eq!(
174 transition(StreamState::Open, StreamEvent::RecvHeaders).unwrap(),
175 StreamState::Open
176 );
177 }
178}