1use core::fmt;
4
5use super::super::codepoints::{Http3ErrorCode, Http3FrameType};
6use super::super::enums::stream::Http3EndpointRole;
7use super::super::frame::Http3Frame;
8use super::super::frame::Http3FramePayloadParseError;
9use super::super::frame::{Http3CancelPush, Http3Goaway, Http3MaxPushId};
10use super::super::ids::Http3PushId;
11use super::super::settings::{Http3PeerSettings, Http3Settings, Http3SettingsSemanticError};
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum Http3ControlFrame<'a> {
15 Settings {
17 frame: Http3Settings<'a>,
19 peer_settings: Http3PeerSettings,
21 },
22 CancelPush(Http3CancelPush<'a>),
24 Goaway(Http3Goaway<'a>),
26 MaxPushId(Http3MaxPushId<'a>),
28 Unknown(Http3Frame<'a>),
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum Http3ControlStreamError {
35 MissingSettings {
37 actual: Http3FrameType,
39 },
40 UnexpectedFrame {
42 frame_type: Http3FrameType,
44 },
45 FramePayload {
47 frame_type: Http3FrameType,
49 error: Http3FramePayloadParseError,
51 },
52 Settings(Http3SettingsSemanticError),
54 InvalidGoawayStreamId {
56 identifier: u64,
58 },
59 IncreasedGoawayIdentifier {
61 previous: u64,
63 current: u64,
65 },
66 ReducedMaximumPushId {
68 previous: Http3PushId,
70 current: Http3PushId,
72 },
73}
74
75impl Http3ControlStreamError {
76 pub const fn error_code(self) -> Http3ErrorCode {
78 match self {
79 Self::MissingSettings { .. } => Http3ErrorCode::MISSING_SETTINGS,
80 Self::UnexpectedFrame { .. } => Http3ErrorCode::FRAME_UNEXPECTED,
81 Self::FramePayload { .. } => Http3ErrorCode::FRAME_ERROR,
82 Self::Settings(_) => Http3ErrorCode::SETTINGS_ERROR,
83 Self::InvalidGoawayStreamId { .. }
84 | Self::IncreasedGoawayIdentifier { .. }
85 | Self::ReducedMaximumPushId { .. } => Http3ErrorCode::ID_ERROR,
86 }
87 }
88}
89
90impl fmt::Display for Http3ControlStreamError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::MissingSettings { actual } => write!(
94 f,
95 "HTTP/3 control stream starts with frame type {} instead of SETTINGS",
96 actual.value()
97 ),
98 Self::UnexpectedFrame { frame_type } => write!(
99 f,
100 "HTTP/3 frame type {} is unexpected on the control stream",
101 frame_type.value()
102 ),
103 Self::FramePayload { frame_type, error } => write!(
104 f,
105 "HTTP/3 control-stream frame type {} payload: {error}",
106 frame_type.value()
107 ),
108 Self::Settings(error) => write!(f, "HTTP/3 control-stream SETTINGS: {error}"),
109 Self::InvalidGoawayStreamId { identifier } => write!(
110 f,
111 "HTTP/3 GOAWAY identifier {identifier} is not a client-initiated bidirectional stream ID"
112 ),
113 Self::IncreasedGoawayIdentifier { previous, current } => write!(
114 f,
115 "HTTP/3 GOAWAY identifier increased from {previous} to {current}"
116 ),
117 Self::ReducedMaximumPushId { previous, current } => write!(
118 f,
119 "HTTP/3 MAX_PUSH_ID decreased from {} to {}",
120 previous.value(),
121 current.value()
122 ),
123 }
124 }
125}
126
127impl core::error::Error for Http3ControlStreamError {
128 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
129 match self {
130 Self::FramePayload { error, .. } => Some(error),
131 Self::Settings(error) => Some(error),
132 Self::MissingSettings { .. }
133 | Self::UnexpectedFrame { .. }
134 | Self::InvalidGoawayStreamId { .. }
135 | Self::IncreasedGoawayIdentifier { .. }
136 | Self::ReducedMaximumPushId { .. } => None,
137 }
138 }
139}
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
143pub struct Http3ControlStreamState {
144 local_role: Http3EndpointRole,
145 settings: Option<Http3PeerSettings>,
146 last_goaway_identifier: Option<u64>,
147 maximum_push_id: Option<Http3PushId>,
148}
149
150impl Http3ControlStreamState {
151 pub const fn new(local_role: Http3EndpointRole) -> Self {
153 Self {
154 local_role,
155 settings: None,
156 last_goaway_identifier: None,
157 maximum_push_id: None,
158 }
159 }
160
161 pub const fn role(self) -> Http3EndpointRole {
163 self.local_role
164 }
165
166 pub const fn settings(self) -> Option<Http3PeerSettings> {
168 self.settings
169 }
170
171 pub const fn last_goaway_identifier(self) -> Option<u64> {
173 self.last_goaway_identifier
174 }
175
176 pub const fn maximum_push_id(self) -> Option<Http3PushId> {
178 self.maximum_push_id
179 }
180
181 pub fn receive<'a>(
185 &mut self,
186 frame: Http3Frame<'a>,
187 ) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
188 let frame_type = frame.frame_type();
189
190 if self.settings.is_none() {
191 if frame_type != Http3FrameType::SETTINGS {
192 return Err(Http3ControlStreamError::MissingSettings { actual: frame_type });
193 }
194 return self.receive_settings(frame);
195 }
196
197 match frame_type {
198 Http3FrameType::SETTINGS
199 | Http3FrameType::DATA
200 | Http3FrameType::HEADERS
201 | Http3FrameType::PUSH_PROMISE => {
202 Err(Http3ControlStreamError::UnexpectedFrame { frame_type })
203 }
204 frame_type if is_http2_reserved_frame_type(frame_type) => {
205 Err(Http3ControlStreamError::UnexpectedFrame { frame_type })
206 }
207 Http3FrameType::CANCEL_PUSH => self.receive_cancel_push(frame),
208 Http3FrameType::GOAWAY => self.receive_goaway(frame),
209 Http3FrameType::MAX_PUSH_ID => self.receive_max_push_id(frame),
210 _ => Ok(Http3ControlFrame::Unknown(frame)),
211 }
212 }
213
214 fn receive_settings<'a>(
215 &mut self,
216 frame: Http3Frame<'a>,
217 ) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
218 let settings = Http3Settings::from_frame(frame).map_err(|error| {
219 Http3ControlStreamError::FramePayload {
220 frame_type: Http3FrameType::SETTINGS,
221 error,
222 }
223 })?;
224 let peer_settings = settings
225 .validate_semantics()
226 .map_err(Http3ControlStreamError::Settings)?;
227
228 self.settings = Some(peer_settings);
229 Ok(Http3ControlFrame::Settings {
230 frame: settings,
231 peer_settings,
232 })
233 }
234
235 fn receive_cancel_push<'a>(
236 &mut self,
237 frame: Http3Frame<'a>,
238 ) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
239 if self.local_role == Http3EndpointRole::Client {
240 return Err(Http3ControlStreamError::UnexpectedFrame {
241 frame_type: Http3FrameType::CANCEL_PUSH,
242 });
243 }
244 Http3CancelPush::from_frame(frame)
245 .map(Http3ControlFrame::CancelPush)
246 .map_err(|error| frame_payload_error(Http3FrameType::CANCEL_PUSH, error))
247 }
248
249 fn receive_goaway<'a>(
250 &mut self,
251 frame: Http3Frame<'a>,
252 ) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
253 let goaway = Http3Goaway::from_frame(frame)
254 .map_err(|error| frame_payload_error(Http3FrameType::GOAWAY, error))?;
255 let identifier = goaway.identifier();
256
257 if self.local_role == Http3EndpointRole::Client && identifier & 0x03 != 0 {
258 return Err(Http3ControlStreamError::InvalidGoawayStreamId { identifier });
259 }
260 if let Some(previous) = self.last_goaway_identifier
261 && identifier > previous
262 {
263 return Err(Http3ControlStreamError::IncreasedGoawayIdentifier {
264 previous,
265 current: identifier,
266 });
267 }
268
269 self.last_goaway_identifier = Some(identifier);
270 Ok(Http3ControlFrame::Goaway(goaway))
271 }
272
273 fn receive_max_push_id<'a>(
274 &mut self,
275 frame: Http3Frame<'a>,
276 ) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
277 if self.local_role == Http3EndpointRole::Client {
278 return Err(Http3ControlStreamError::UnexpectedFrame {
279 frame_type: Http3FrameType::MAX_PUSH_ID,
280 });
281 }
282 let maximum_push_id = Http3MaxPushId::from_frame(frame)
283 .map_err(|error| frame_payload_error(Http3FrameType::MAX_PUSH_ID, error))?;
284 let current = maximum_push_id.push_id();
285
286 if let Some(previous) = self.maximum_push_id
287 && current.value() < previous.value()
288 {
289 return Err(Http3ControlStreamError::ReducedMaximumPushId { previous, current });
290 }
291
292 self.maximum_push_id = Some(current);
293 Ok(Http3ControlFrame::MaxPushId(maximum_push_id))
294 }
295}
296
297fn frame_payload_error(
298 frame_type: Http3FrameType,
299 error: Http3FramePayloadParseError,
300) -> Http3ControlStreamError {
301 Http3ControlStreamError::FramePayload { frame_type, error }
302}
303
304const fn is_http2_reserved_frame_type(frame_type: Http3FrameType) -> bool {
305 matches!(frame_type.value(), 0x02 | 0x06 | 0x08 | 0x09)
306}