Skip to main content

net_wire/http2/
sequence.rs

1//! Bounded HTTP/2 header-block fragment sequence validation.
2
3use core::fmt;
4
5use super::error::Http2ParseError;
6use super::frame::Http2Frame;
7use super::headers::{Http2Continuation, Http2Headers, Http2PushPromise};
8use super::types::Http2FrameType;
9
10const END_HEADERS: u8 = 0x04;
11
12/// Validates one bounded HTTP/2 header-block fragment sequence at a time.
13///
14/// This type validates only the immediate HEADERS, PUSH_PROMISE, and CONTINUATION
15/// ordering rule. It does not buffer fragments, decode HPACK, or track connection state.
16#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
17pub struct Http2HeaderBlockSequence {
18    locked_stream_id: Option<u32>,
19}
20
21impl Http2HeaderBlockSequence {
22    /// Creates an idle sequence validator.
23    pub const fn new() -> Self {
24        Self {
25            locked_stream_id: None,
26        }
27    }
28
29    /// Returns whether no header block is awaiting a CONTINUATION frame.
30    pub const fn is_idle(&self) -> bool {
31        self.locked_stream_id.is_none()
32    }
33
34    /// Returns whether a header block is awaiting a CONTINUATION frame.
35    pub const fn is_open(&self) -> bool {
36        self.locked_stream_id.is_some()
37    }
38
39    /// Returns the semantic 31-bit stream ID locked by an open header block.
40    pub const fn locked_stream_id(&self) -> Option<u32> {
41        self.locked_stream_id
42    }
43
44    /// Observes one structurally validated frame.
45    ///
46    /// Unrelated frames are ignored while idle. Accepted fragment views preserve their
47    /// raw frame metadata and borrow from the supplied frame.
48    pub fn observe<'a>(
49        &mut self,
50        frame: Http2Frame<'a>,
51    ) -> Result<Option<Http2HeaderBlockFragment<'a>>, Http2HeaderBlockSequenceError> {
52        if let Some(expected_stream_id) = self.locked_stream_id {
53            return self.observe_open(frame, expected_stream_id);
54        }
55
56        match frame.frame_type() {
57            Http2FrameType::HEADERS => {
58                let headers = Http2Headers::from_frame(frame)
59                    .map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
60                let end_headers = headers.frame().flags() & END_HEADERS != 0;
61                let stream_id = headers.frame().stream_id().value();
62                let fragment = Http2HeaderBlockFragment::Headers(headers);
63                if !end_headers {
64                    self.locked_stream_id = Some(stream_id);
65                }
66                Ok(Some(fragment))
67            }
68            Http2FrameType::PUSH_PROMISE => {
69                let push_promise = Http2PushPromise::from_frame(frame)
70                    .map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
71                let end_headers = push_promise.frame().flags() & END_HEADERS != 0;
72                let stream_id = push_promise.frame().stream_id().value();
73                let fragment = Http2HeaderBlockFragment::PushPromise(push_promise);
74                if !end_headers {
75                    self.locked_stream_id = Some(stream_id);
76                }
77                Ok(Some(fragment))
78            }
79            Http2FrameType::CONTINUATION => {
80                let continuation = Http2Continuation::from_frame(frame)
81                    .map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
82                Err(Http2HeaderBlockSequenceError::UnexpectedContinuation {
83                    actual_stream_id: continuation.frame().stream_id().value(),
84                })
85            }
86            _ => Ok(None),
87        }
88    }
89
90    fn observe_open<'a>(
91        &mut self,
92        frame: Http2Frame<'a>,
93        expected_stream_id: u32,
94    ) -> Result<Option<Http2HeaderBlockFragment<'a>>, Http2HeaderBlockSequenceError> {
95        if frame.frame_type() != Http2FrameType::CONTINUATION {
96            return Err(Http2HeaderBlockSequenceError::InterleavedFrame {
97                expected_stream_id,
98                actual_stream_id: frame.stream_id().value(),
99                actual_frame_type: frame.frame_type(),
100            });
101        }
102
103        let continuation = Http2Continuation::from_frame(frame)
104            .map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
105        let actual_stream_id = continuation.frame().stream_id().value();
106        if actual_stream_id != expected_stream_id {
107            return Err(Http2HeaderBlockSequenceError::WrongContinuationStream {
108                expected_stream_id,
109                actual_stream_id,
110            });
111        }
112
113        let end_headers = continuation.frame().flags() & END_HEADERS != 0;
114        let fragment = Http2HeaderBlockFragment::Continuation(continuation);
115        if end_headers {
116            self.locked_stream_id = None;
117        }
118        Ok(Some(fragment))
119    }
120}
121
122/// An accepted HTTP/2 header-block fragment with its typed frame view intact.
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub enum Http2HeaderBlockFragment<'a> {
125    /// A HEADERS field-block fragment.
126    Headers(Http2Headers<'a>),
127    /// A PUSH_PROMISE field-block fragment.
128    PushPromise(Http2PushPromise<'a>),
129    /// A CONTINUATION field-block fragment.
130    Continuation(Http2Continuation<'a>),
131}
132
133impl<'a> Http2HeaderBlockFragment<'a> {
134    /// Returns the validated raw frame without erasing its original metadata.
135    pub const fn frame(&self) -> Http2Frame<'a> {
136        match self {
137            Self::Headers(fragment) => fragment.frame(),
138            Self::PushPromise(fragment) => fragment.frame(),
139            Self::Continuation(fragment) => fragment.frame(),
140        }
141    }
142
143    /// Returns the exact field-block bytes represented by this fragment.
144    pub fn field_block_fragment(&self) -> &'a [u8] {
145        match self {
146            Self::Headers(fragment) => fragment.field_block_fragment(),
147            Self::PushPromise(fragment) => fragment.field_block_fragment(),
148            Self::Continuation(fragment) => fragment.field_block_fragment(),
149        }
150    }
151
152    /// Returns whether this fragment ends its header block.
153    pub fn is_end_headers(&self) -> bool {
154        self.frame().flags() & END_HEADERS != 0
155    }
156}
157
158/// Failure to preserve HTTP/2 header-block fragment ordering.
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum Http2HeaderBlockSequenceError {
161    /// A typed header-block fragment has an invalid intrinsic layout.
162    MalformedFragment(Http2ParseError),
163    /// A CONTINUATION arrived while no header block was open.
164    UnexpectedContinuation {
165        /// Semantic 31-bit stream ID on the received frame.
166        actual_stream_id: u32,
167    },
168    /// A CONTINUATION arrived on a stream other than the locked stream.
169    WrongContinuationStream {
170        /// Semantic 31-bit stream ID locked by the incomplete header block.
171        expected_stream_id: u32,
172        /// Semantic 31-bit stream ID on the received CONTINUATION.
173        actual_stream_id: u32,
174    },
175    /// A non-CONTINUATION frame interrupted an incomplete header block.
176    InterleavedFrame {
177        /// Semantic 31-bit stream ID locked by the incomplete header block.
178        expected_stream_id: u32,
179        /// Semantic 31-bit stream ID on the interrupting frame.
180        actual_stream_id: u32,
181        /// Raw type of the interrupting frame.
182        actual_frame_type: Http2FrameType,
183    },
184}
185
186impl fmt::Display for Http2HeaderBlockSequenceError {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match self {
189            Self::MalformedFragment(error) => {
190                write!(f, "malformed HTTP/2 header-block fragment: {error}")
191            }
192            Self::UnexpectedContinuation { actual_stream_id } => write!(
193                f,
194                "unexpected HTTP/2 CONTINUATION on stream {actual_stream_id}"
195            ),
196            Self::WrongContinuationStream {
197                expected_stream_id,
198                actual_stream_id,
199            } => write!(
200                f,
201                "HTTP/2 CONTINUATION stream mismatch: expected {expected_stream_id}, got {actual_stream_id}"
202            ),
203            Self::InterleavedFrame {
204                expected_stream_id,
205                actual_stream_id,
206                actual_frame_type,
207            } => write!(
208                f,
209                "HTTP/2 header block on stream {expected_stream_id} was interrupted by frame type {} on stream {actual_stream_id}",
210                actual_frame_type.raw()
211            ),
212        }
213    }
214}