Skip to main content

net_wire/http3/stream/
peer.rs

1//! Bounded lifecycle state for incoming HTTP/3 peer unidirectional streams.
2
3use core::fmt;
4
5use crate::quic::varint::QuicVarIntLen;
6
7use super::super::codepoints::{Http3ErrorCode, Http3StreamType};
8use super::super::enums::stream::{Http3EndpointRole, Http3UniStreamKind};
9use super::super::ids::{Http3PushId, Http3StreamId};
10use super::uni::Http3UniStreamHeader;
11
12/// A peer unidirectional stream kind that HTTP/3 requires exactly once.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum Http3CriticalUniStreamKind {
15    /// The HTTP/3 control stream.
16    Control,
17    /// The peer's QPACK encoder stream.
18    QpackEncoder,
19    /// The peer's QPACK decoder stream.
20    QpackDecoder,
21}
22
23/// The outcome of accepting an incoming peer unidirectional stream header.
24///
25/// Critical events hand the stream remainder to its existing owner. Push events hand the Push ID
26/// to future push-lifecycle state; unknown events preserve the extension stream type for caller
27/// policy.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum Http3PeerUniStreamEvent {
30    /// The peer's unique HTTP/3 control stream.
31    Control {
32        /// QUIC stream identifier.
33        stream_id: Http3StreamId,
34    },
35    /// A server push stream received by a client.
36    Push {
37        /// QUIC stream identifier.
38        stream_id: Http3StreamId,
39        /// Push ID carried by the stream header.
40        push_id: Http3PushId,
41    },
42    /// The peer's unique QPACK encoder stream.
43    QpackEncoder {
44        /// QUIC stream identifier.
45        stream_id: Http3StreamId,
46    },
47    /// The peer's unique QPACK decoder stream.
48    QpackDecoder {
49        /// QUIC stream identifier.
50        stream_id: Http3StreamId,
51    },
52    /// An extension, unknown, or GREASE stream that remains caller-owned.
53    Unknown {
54        /// QUIC stream identifier.
55        stream_id: Http3StreamId,
56        /// Raw HTTP/3 unidirectional stream type.
57        stream_type: Http3StreamType,
58    },
59}
60
61/// Failure while registering or closing a peer HTTP/3 unidirectional stream.
62///
63/// [`Self::InvalidPeerUnidirectionalStream`] is a local integration error. The remaining
64/// variants are peer-caused HTTP/3 connection errors.
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub enum Http3PeerUniStreamError {
67    /// The supplied stream ID is not a valid peer-initiated unidirectional QUIC stream ID.
68    InvalidPeerUnidirectionalStream {
69        /// Invalid raw QUIC stream identifier.
70        stream_id: Http3StreamId,
71    },
72    /// The peer opened a second stream for a required unique critical-stream kind.
73    DuplicateCriticalStream {
74        /// Duplicated critical-stream kind.
75        kind: Http3CriticalUniStreamKind,
76        /// Previously registered peer stream identifier.
77        existing: Http3StreamId,
78        /// Newly received duplicate peer stream identifier.
79        received: Http3StreamId,
80    },
81    /// A client opened a server-only push stream.
82    ClientInitiatedPushStream {
83        /// Client-initiated QUIC stream identifier.
84        stream_id: Http3StreamId,
85        /// Push ID carried by the invalid stream header.
86        push_id: Http3PushId,
87    },
88    /// A peer closed or reset a registered critical stream.
89    ClosedCriticalStream {
90        /// Closed critical-stream kind.
91        kind: Http3CriticalUniStreamKind,
92        /// Closed peer stream identifier.
93        stream_id: Http3StreamId,
94    },
95}
96
97impl Http3PeerUniStreamError {
98    /// Returns the HTTP/3 application error code for a peer-caused failure.
99    pub const fn error_code(self) -> Option<Http3ErrorCode> {
100        match self {
101            Self::InvalidPeerUnidirectionalStream { .. } => None,
102            Self::DuplicateCriticalStream { .. } | Self::ClientInitiatedPushStream { .. } => {
103                Some(Http3ErrorCode::STREAM_CREATION_ERROR)
104            }
105            Self::ClosedCriticalStream { .. } => Some(Http3ErrorCode::CLOSED_CRITICAL_STREAM),
106        }
107    }
108}
109
110impl fmt::Display for Http3PeerUniStreamError {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            Self::InvalidPeerUnidirectionalStream { stream_id } => write!(
114                f,
115                "stream ID {} is not a peer-initiated unidirectional QUIC stream",
116                stream_id.value()
117            ),
118            Self::DuplicateCriticalStream {
119                kind,
120                existing,
121                received,
122            } => write!(
123                f,
124                "HTTP/3 {kind:?} critical stream {} duplicates stream {}",
125                received.value(),
126                existing.value()
127            ),
128            Self::ClientInitiatedPushStream { stream_id, push_id } => write!(
129                f,
130                "client-initiated HTTP/3 push stream {} carries Push ID {}",
131                stream_id.value(),
132                push_id.value()
133            ),
134            Self::ClosedCriticalStream { kind, stream_id } => write!(
135                f,
136                "HTTP/3 {kind:?} critical stream {} was closed",
137                stream_id.value()
138            ),
139        }
140    }
141}
142
143impl core::error::Error for Http3PeerUniStreamError {}
144
145/// Bounded state for incoming peer HTTP/3 unidirectional critical streams.
146///
147/// It records only the three unique critical stream IDs. Header parsing, stream I/O and
148/// reassembly, critical-stream frame sequencing, QPACK application, push lifecycle, locally
149/// opened streams, and extension policy remain owned by their respective callers.
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub struct Http3PeerUniStreamState {
152    local_role: Http3EndpointRole,
153    control: Option<Http3StreamId>,
154    qpack_encoder: Option<Http3StreamId>,
155    qpack_decoder: Option<Http3StreamId>,
156}
157
158impl Http3PeerUniStreamState {
159    /// Creates empty incoming peer unidirectional-stream state for an endpoint role.
160    pub const fn new(local_role: Http3EndpointRole) -> Self {
161        Self {
162            local_role,
163            control: None,
164            qpack_encoder: None,
165            qpack_decoder: None,
166        }
167    }
168
169    /// Returns the local endpoint role used to validate peer stream identifiers.
170    pub const fn role(self) -> Http3EndpointRole {
171        self.local_role
172    }
173
174    /// Returns the registered peer control stream identifier.
175    pub const fn control_stream_id(self) -> Option<Http3StreamId> {
176        self.control
177    }
178
179    /// Returns the registered peer QPACK encoder stream identifier.
180    pub const fn qpack_encoder_stream_id(self) -> Option<Http3StreamId> {
181        self.qpack_encoder
182    }
183
184    /// Returns the registered peer QPACK decoder stream identifier.
185    pub const fn qpack_decoder_stream_id(self) -> Option<Http3StreamId> {
186        self.qpack_decoder
187    }
188
189    /// Receives a parsed HTTP/3 unidirectional stream header from a peer stream.
190    ///
191    /// Errors leave state unchanged. A successful critical event registers exactly its matching
192    /// critical stream; push and unknown streams are deliberately not retained.
193    pub fn receive_header(
194        &mut self,
195        stream_id: Http3StreamId,
196        header: Http3UniStreamHeader<'_>,
197    ) -> Result<Http3PeerUniStreamEvent, Http3PeerUniStreamError> {
198        self.validate_peer_unidirectional_stream_id(stream_id)?;
199
200        match header.kind() {
201            Http3UniStreamKind::Control => {
202                self.receive_critical(stream_id, Http3CriticalUniStreamKind::Control)
203            }
204            Http3UniStreamKind::QpackEncoder => {
205                self.receive_critical(stream_id, Http3CriticalUniStreamKind::QpackEncoder)
206            }
207            Http3UniStreamKind::QpackDecoder => {
208                self.receive_critical(stream_id, Http3CriticalUniStreamKind::QpackDecoder)
209            }
210            Http3UniStreamKind::Push(push_id) => {
211                if self.local_role == Http3EndpointRole::Server {
212                    return Err(Http3PeerUniStreamError::ClientInitiatedPushStream {
213                        stream_id,
214                        push_id,
215                    });
216                }
217                Ok(Http3PeerUniStreamEvent::Push { stream_id, push_id })
218            }
219            Http3UniStreamKind::Unknown(stream_type) => Ok(Http3PeerUniStreamEvent::Unknown {
220                stream_id,
221                stream_type,
222            }),
223        }
224    }
225
226    /// Reports a close or reset of a retained peer critical stream.
227    ///
228    /// Closing a critical stream is terminal for the connection, so this method intentionally
229    /// takes `self` and does not perform a recoverable state transition. Closing an unregistered,
230    /// push, or extension stream is ignored, including a close received before its header.
231    pub fn receive_closed(self, stream_id: Http3StreamId) -> Result<(), Http3PeerUniStreamError> {
232        if self.control == Some(stream_id) {
233            return Err(Http3PeerUniStreamError::ClosedCriticalStream {
234                kind: Http3CriticalUniStreamKind::Control,
235                stream_id,
236            });
237        }
238        if self.qpack_encoder == Some(stream_id) {
239            return Err(Http3PeerUniStreamError::ClosedCriticalStream {
240                kind: Http3CriticalUniStreamKind::QpackEncoder,
241                stream_id,
242            });
243        }
244        if self.qpack_decoder == Some(stream_id) {
245            return Err(Http3PeerUniStreamError::ClosedCriticalStream {
246                kind: Http3CriticalUniStreamKind::QpackDecoder,
247                stream_id,
248            });
249        }
250        Ok(())
251    }
252
253    fn validate_peer_unidirectional_stream_id(
254        self,
255        stream_id: Http3StreamId,
256    ) -> Result<(), Http3PeerUniStreamError> {
257        let value = stream_id.value();
258        let expected_low_bits = match self.local_role {
259            Http3EndpointRole::Client => 0x03,
260            Http3EndpointRole::Server => 0x02,
261        };
262        if value > QuicVarIntLen::Eight.max_value() || value & 0x03 != expected_low_bits {
263            return Err(Http3PeerUniStreamError::InvalidPeerUnidirectionalStream { stream_id });
264        }
265        Ok(())
266    }
267
268    fn receive_critical(
269        &mut self,
270        stream_id: Http3StreamId,
271        kind: Http3CriticalUniStreamKind,
272    ) -> Result<Http3PeerUniStreamEvent, Http3PeerUniStreamError> {
273        let existing = match kind {
274            Http3CriticalUniStreamKind::Control => self.control,
275            Http3CriticalUniStreamKind::QpackEncoder => self.qpack_encoder,
276            Http3CriticalUniStreamKind::QpackDecoder => self.qpack_decoder,
277        };
278        if let Some(existing) = existing {
279            return Err(Http3PeerUniStreamError::DuplicateCriticalStream {
280                kind,
281                existing,
282                received: stream_id,
283            });
284        }
285
286        match kind {
287            Http3CriticalUniStreamKind::Control => {
288                self.control = Some(stream_id);
289                Ok(Http3PeerUniStreamEvent::Control { stream_id })
290            }
291            Http3CriticalUniStreamKind::QpackEncoder => {
292                self.qpack_encoder = Some(stream_id);
293                Ok(Http3PeerUniStreamEvent::QpackEncoder { stream_id })
294            }
295            Http3CriticalUniStreamKind::QpackDecoder => {
296                self.qpack_decoder = Some(stream_id);
297                Ok(Http3PeerUniStreamEvent::QpackDecoder { stream_id })
298            }
299        }
300    }
301}