Skip to main content

net_wire/http3/stream/
message.rs

1//! Incoming HTTP/3 request, response, and push-message frame sequencing.
2
3use core::fmt;
4
5use super::super::codepoints::{Http3ErrorCode, Http3FrameType};
6use super::super::enums::message::Http3HeadersKind;
7use super::super::frame::Http3Frame;
8use super::super::frame::Http3FramePayloadParseError;
9use super::super::frame::{Http3Data, Http3Headers, Http3PushPromise};
10use super::super::headers::Http3DecodedHeaderSection;
11
12/// The direction and message role of an incoming HTTP/3 stream.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum Http3MessageStreamKind {
15    /// An inbound client request on a server's request stream.
16    Request,
17    /// An inbound server response on a client's request stream.
18    Response,
19    /// An inbound server response on a client's push stream.
20    Push,
21}
22
23/// The current position in an incoming HTTP/3 message.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum Http3MessagePosition {
26    /// A request has not yet received its initial header section.
27    BeforeHeaders,
28    /// A response has not yet received its final response header section.
29    BeforeFinalResponse,
30    /// The initial or final header section was accepted and content may follow.
31    Content,
32    /// A trailing header section was accepted.
33    Trailers,
34}
35
36/// Failure to sequence a received frame on an HTTP/3 message stream.
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub enum Http3MessageStreamError {
39    /// A frame is forbidden in the current message position or stream kind.
40    UnexpectedFrame {
41        /// Forbidden frame type.
42        frame_type: Http3FrameType,
43        /// Message position at which the frame was received.
44        position: Http3MessagePosition,
45    },
46    /// An allowed known frame has a malformed intrinsic payload.
47    FramePayload {
48        /// Type of the frame with the malformed payload.
49        frame_type: Http3FrameType,
50        /// Intrinsic payload parsing failure.
51        error: Http3FramePayloadParseError,
52    },
53    /// A QPACK-decoded header section is invalid for the current message position.
54    InvalidHeaderSection {
55        /// Incoming message stream kind.
56        stream_kind: Http3MessageStreamKind,
57        /// Message position at which HEADERS was received.
58        position: Http3MessagePosition,
59        /// Validated QPACK-decoded header-section role.
60        section_kind: Http3HeadersKind,
61    },
62    /// The stream ended before its initial or final response header section was accepted.
63    IncompleteMessage {
64        /// Incoming message stream kind.
65        stream_kind: Http3MessageStreamKind,
66        /// Message position at stream end.
67        position: Http3MessagePosition,
68    },
69}
70
71impl Http3MessageStreamError {
72    /// Returns the HTTP/3 application error code required by this failure.
73    pub const fn error_code(self) -> Http3ErrorCode {
74        match self {
75            Self::UnexpectedFrame { .. } => Http3ErrorCode::FRAME_UNEXPECTED,
76            Self::FramePayload { .. } => Http3ErrorCode::FRAME_ERROR,
77            Self::InvalidHeaderSection { .. } => Http3ErrorCode::MESSAGE_ERROR,
78            Self::IncompleteMessage {
79                stream_kind: Http3MessageStreamKind::Request,
80                ..
81            } => Http3ErrorCode::REQUEST_INCOMPLETE,
82            Self::IncompleteMessage {
83                stream_kind: Http3MessageStreamKind::Response | Http3MessageStreamKind::Push,
84                ..
85            } => Http3ErrorCode::MESSAGE_ERROR,
86        }
87    }
88}
89
90impl fmt::Display for Http3MessageStreamError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            Self::UnexpectedFrame {
94                frame_type,
95                position,
96            } => write!(
97                f,
98                "HTTP/3 frame type {} is unexpected at message position {position:?}",
99                frame_type.value()
100            ),
101            Self::FramePayload { frame_type, error } => write!(
102                f,
103                "HTTP/3 message frame type {} payload: {error}",
104                frame_type.value()
105            ),
106            Self::InvalidHeaderSection {
107                stream_kind,
108                position,
109                section_kind,
110            } => write!(
111                f,
112                "HTTP/3 {section_kind:?} header section is invalid for {stream_kind:?} at message position {position:?}"
113            ),
114            Self::IncompleteMessage {
115                stream_kind,
116                position,
117            } => write!(
118                f,
119                "HTTP/3 {stream_kind:?} message is incomplete at message position {position:?}"
120            ),
121        }
122    }
123}
124
125impl core::error::Error for Http3MessageStreamError {
126    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
127        match self {
128            Self::FramePayload { error, .. } => Some(error),
129            Self::UnexpectedFrame { .. }
130            | Self::InvalidHeaderSection { .. }
131            | Self::IncompleteMessage { .. } => None,
132        }
133    }
134}
135
136/// Bounded state for receiving one incoming HTTP/3 request, response, or push message.
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138pub struct Http3MessageStreamState {
139    kind: Http3MessageStreamKind,
140    position: Http3MessagePosition,
141}
142
143impl Http3MessageStreamState {
144    /// Creates state for an incoming stream of the supplied message kind.
145    pub const fn new(kind: Http3MessageStreamKind) -> Self {
146        let position = match kind {
147            Http3MessageStreamKind::Request => Http3MessagePosition::BeforeHeaders,
148            Http3MessageStreamKind::Response | Http3MessageStreamKind::Push => {
149                Http3MessagePosition::BeforeFinalResponse
150            }
151        };
152        Self { kind, position }
153    }
154
155    /// Returns this stream's incoming message kind.
156    pub const fn kind(self) -> Http3MessageStreamKind {
157        self.kind
158    }
159
160    /// Returns the current message position.
161    pub const fn position(self) -> Http3MessagePosition {
162        self.position
163    }
164
165    /// Receives a structurally bounded frame from this message stream.
166    ///
167    /// A HEADERS event retains the mutable state borrow until its QPACK-decoded semantic role is
168    /// accepted. On every error, state is unchanged.
169    pub fn receive<'state, 'wire>(
170        &'state mut self,
171        frame: Http3Frame<'wire>,
172    ) -> Result<Http3MessageFrame<'state, 'wire>, Http3MessageStreamError> {
173        let frame_type = frame.frame_type();
174        let position = self.position;
175
176        match frame_type {
177            Http3FrameType::HEADERS => {
178                if position == Http3MessagePosition::Trailers {
179                    return Err(unexpected_frame(frame_type, position));
180                }
181                let headers = Http3Headers::from_frame(frame)
182                    .map_err(|error| frame_payload_error(frame_type, error))?;
183                Ok(Http3MessageFrame::Headers(Http3PendingHeaders {
184                    state: self,
185                    headers,
186                }))
187            }
188            Http3FrameType::DATA => {
189                if position != Http3MessagePosition::Content {
190                    return Err(unexpected_frame(frame_type, position));
191                }
192                Http3Data::from_frame(frame)
193                    .map(Http3MessageFrame::Data)
194                    .map_err(|error| frame_payload_error(frame_type, error))
195            }
196            Http3FrameType::PUSH_PROMISE => {
197                if self.kind != Http3MessageStreamKind::Response {
198                    return Err(unexpected_frame(frame_type, position));
199                }
200                Http3PushPromise::from_frame(frame)
201                    .map(Http3MessageFrame::PushPromise)
202                    .map_err(|error| frame_payload_error(frame_type, error))
203            }
204            Http3FrameType::CANCEL_PUSH
205            | Http3FrameType::SETTINGS
206            | Http3FrameType::GOAWAY
207            | Http3FrameType::MAX_PUSH_ID => Err(unexpected_frame(frame_type, position)),
208            frame_type if is_http2_reserved_frame_type(frame_type) => {
209                Err(unexpected_frame(frame_type, position))
210            }
211            _ => Ok(Http3MessageFrame::Unknown(frame)),
212        }
213    }
214
215    /// Validates that this message ended after its complete header and optional content sequence.
216    pub const fn finish(&self) -> Result<(), Http3MessageStreamError> {
217        match self.position {
218            Http3MessagePosition::Content | Http3MessagePosition::Trailers => Ok(()),
219            Http3MessagePosition::BeforeHeaders | Http3MessagePosition::BeforeFinalResponse => {
220                Err(Http3MessageStreamError::IncompleteMessage {
221                    stream_kind: self.kind,
222                    position: self.position,
223                })
224            }
225        }
226    }
227}
228
229/// An accepted incoming HTTP/3 message frame.
230#[derive(Debug)]
231pub enum Http3MessageFrame<'state, 'wire> {
232    /// An opaque DATA payload following the initial or final header section.
233    Data(Http3Data<'wire>),
234    /// A HEADERS frame awaiting QPACK-driven semantic classification.
235    Headers(Http3PendingHeaders<'state, 'wire>),
236    /// A PUSH_PROMISE on an incoming response request stream.
237    PushPromise(Http3PushPromise<'wire>),
238    /// An unknown extension or GREASE frame preserved exactly.
239    Unknown(Http3Frame<'wire>),
240}
241
242/// A HEADERS frame whose QPACK-decoded semantic role must be accepted before state advances.
243#[derive(Debug)]
244pub struct Http3PendingHeaders<'state, 'wire> {
245    state: &'state mut Http3MessageStreamState,
246    headers: Http3Headers<'wire>,
247}
248
249impl<'state, 'wire> Http3PendingHeaders<'state, 'wire> {
250    /// Returns the typed, raw-preserving HEADERS frame for QPACK decoding.
251    pub const fn headers(&self) -> Http3Headers<'wire> {
252        self.headers
253    }
254
255    /// Accepts a validated QPACK-decoded header section and commits the corresponding transition.
256    ///
257    /// The section is consumed semantically but remains caller-owned for message-content handling.
258    /// Invalid classifications leave the stream state unchanged.
259    pub fn accept(
260        self,
261        section: &Http3DecodedHeaderSection<'_>,
262    ) -> Result<Http3Headers<'wire>, Http3MessageStreamError> {
263        let section_kind = section.kind();
264        let next_position = next_position(self.state.kind, self.state.position, section_kind)?;
265        self.state.position = next_position;
266        Ok(self.headers)
267    }
268}
269
270const fn next_position(
271    stream_kind: Http3MessageStreamKind,
272    position: Http3MessagePosition,
273    section_kind: Http3HeadersKind,
274) -> Result<Http3MessagePosition, Http3MessageStreamError> {
275    match (stream_kind, position, section_kind) {
276        (
277            Http3MessageStreamKind::Request,
278            Http3MessagePosition::BeforeHeaders,
279            Http3HeadersKind::Request,
280        ) => Ok(Http3MessagePosition::Content),
281        (
282            Http3MessageStreamKind::Request,
283            Http3MessagePosition::Content,
284            Http3HeadersKind::Trailers,
285        ) => Ok(Http3MessagePosition::Trailers),
286        (
287            Http3MessageStreamKind::Response | Http3MessageStreamKind::Push,
288            Http3MessagePosition::BeforeFinalResponse,
289            Http3HeadersKind::InformationalResponse,
290        ) => Ok(Http3MessagePosition::BeforeFinalResponse),
291        (
292            Http3MessageStreamKind::Response | Http3MessageStreamKind::Push,
293            Http3MessagePosition::BeforeFinalResponse,
294            Http3HeadersKind::FinalResponse,
295        ) => Ok(Http3MessagePosition::Content),
296        (
297            Http3MessageStreamKind::Response | Http3MessageStreamKind::Push,
298            Http3MessagePosition::Content,
299            Http3HeadersKind::Trailers,
300        ) => Ok(Http3MessagePosition::Trailers),
301        _ => Err(Http3MessageStreamError::InvalidHeaderSection {
302            stream_kind,
303            position,
304            section_kind,
305        }),
306    }
307}
308
309const fn unexpected_frame(
310    frame_type: Http3FrameType,
311    position: Http3MessagePosition,
312) -> Http3MessageStreamError {
313    Http3MessageStreamError::UnexpectedFrame {
314        frame_type,
315        position,
316    }
317}
318
319const fn frame_payload_error(
320    frame_type: Http3FrameType,
321    error: Http3FramePayloadParseError,
322) -> Http3MessageStreamError {
323    Http3MessageStreamError::FramePayload { frame_type, error }
324}
325
326const fn is_http2_reserved_frame_type(frame_type: Http3FrameType) -> bool {
327    matches!(frame_type.value(), 0x02 | 0x06 | 0x08 | 0x09)
328}