Skip to main content

net_wire/http3/stream/
control.rs

1//! Incoming HTTP/3 control-stream frame sequencing.
2
3use 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/// A validated incoming HTTP/3 control-stream frame.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum Http3ControlFrame<'a> {
15    /// A SETTINGS frame and its semantically validated peer settings.
16    Settings {
17        /// The typed, raw-preserving SETTINGS frame.
18        frame: Http3Settings<'a>,
19        /// Effective known settings received from the peer.
20        peer_settings: Http3PeerSettings,
21    },
22    /// A CANCEL_PUSH frame received by a server.
23    CancelPush(Http3CancelPush<'a>),
24    /// A GOAWAY frame.
25    Goaway(Http3Goaway<'a>),
26    /// A MAX_PUSH_ID frame received by a server.
27    MaxPushId(Http3MaxPushId<'a>),
28    /// An unknown extension or GREASE frame.
29    Unknown(Http3Frame<'a>),
30}
31
32/// Failure to sequence a received frame on an HTTP/3 control stream.
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum Http3ControlStreamError {
35    /// The first control-stream frame was not SETTINGS.
36    MissingSettings {
37        /// Actual first frame type.
38        actual: Http3FrameType,
39    },
40    /// A frame is forbidden in its current control-stream position or endpoint role.
41    UnexpectedFrame {
42        /// Forbidden frame type.
43        frame_type: Http3FrameType,
44    },
45    /// A known control-stream frame has a malformed intrinsic payload.
46    FramePayload {
47        /// Type of the frame with the malformed payload.
48        frame_type: Http3FrameType,
49        /// Intrinsic payload parsing failure.
50        error: Http3FramePayloadParseError,
51    },
52    /// SETTINGS failed strict semantic validation.
53    Settings(Http3SettingsSemanticError),
54    /// A client received GOAWAY with a non-client-bidirectional stream identifier.
55    InvalidGoawayStreamId {
56        /// Invalid GOAWAY identifier.
57        identifier: u64,
58    },
59    /// A later GOAWAY identifier increased.
60    IncreasedGoawayIdentifier {
61        /// Previously accepted GOAWAY identifier.
62        previous: u64,
63        /// Later, invalid larger GOAWAY identifier.
64        current: u64,
65    },
66    /// A later MAX_PUSH_ID decreased.
67    ReducedMaximumPushId {
68        /// Previously accepted maximum Push ID.
69        previous: Http3PushId,
70        /// Later, invalid smaller maximum Push ID.
71        current: Http3PushId,
72    },
73}
74
75impl Http3ControlStreamError {
76    /// Returns the HTTP/3 application error code required by this failure.
77    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/// Bounded state for receiving frames on one HTTP/3 control stream.
142#[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    /// Creates empty incoming control-stream state for an endpoint role.
152    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    /// Returns the local endpoint role used for incoming frame semantics.
162    pub const fn role(self) -> Http3EndpointRole {
163        self.local_role
164    }
165
166    /// Returns the peer SETTINGS after a valid SETTINGS frame is received.
167    pub const fn settings(self) -> Option<Http3PeerSettings> {
168        self.settings
169    }
170
171    /// Returns the last accepted GOAWAY identifier.
172    pub const fn last_goaway_identifier(self) -> Option<u64> {
173        self.last_goaway_identifier
174    }
175
176    /// Returns the largest accepted MAX_PUSH_ID.
177    pub const fn maximum_push_id(self) -> Option<Http3PushId> {
178        self.maximum_push_id
179    }
180
181    /// Receives a structurally bounded frame from this control stream.
182    ///
183    /// On error, no state is changed.
184    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}