Skip to main content

tokio_quiche/http3/driver/
mod.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27mod client;
28/// Wrapper for running HTTP/3 connections.
29pub mod connection;
30mod datagram;
31// `DriverHooks` must stay private to prevent users from creating their own
32// H3Drivers.
33mod hooks;
34mod server;
35mod streams;
36#[cfg(test)]
37pub mod test_utils;
38#[cfg(test)]
39mod tests;
40
41use debug_panic::debug_panic;
42use std::collections::BTreeMap;
43use std::error::Error;
44use std::fmt;
45use std::marker::PhantomData;
46use std::sync::Arc;
47use std::time::Instant;
48
49use bytes::BufMut as _;
50use bytes::Bytes;
51use bytes::BytesMut;
52use datagram_socket::DgramBuffer;
53use datagram_socket::StreamClosureKind;
54use foundations::telemetry::log;
55use futures::FutureExt;
56use quiche::h3;
57use quiche::h3::WireErrorCode;
58use tokio::select;
59use tokio::sync::mpsc;
60use tokio::sync::mpsc::error::TryRecvError;
61use tokio::sync::mpsc::error::TrySendError;
62use tokio::sync::mpsc::UnboundedReceiver;
63use tokio::sync::mpsc::UnboundedSender;
64use tokio_util::sync::PollSender;
65
66use self::hooks::DriverHooks;
67use self::hooks::InboundHeaders;
68use self::streams::FlowCtx;
69use self::streams::ReadyStreamSender;
70use self::streams::ReadyStreams;
71use self::streams::RecvState;
72use self::streams::SendState;
73use self::streams::StreamCtx;
74use crate::http3::settings::Http3Settings;
75use crate::http3::H3AuditStats;
76use crate::metrics::Metrics;
77use crate::quic::HandshakeError;
78use crate::quic::HandshakeInfo;
79use crate::quic::QuicCommand;
80use crate::quic::QuicheConnection;
81use crate::ApplicationOverQuic;
82use crate::QuicResult;
83
84pub use self::client::ClientEventStream;
85pub use self::client::ClientH3Command;
86pub use self::client::ClientH3Controller;
87pub use self::client::ClientH3Driver;
88pub use self::client::ClientH3Event;
89pub use self::client::ClientRequestSender;
90pub use self::client::NewClientRequest;
91pub use self::server::IsInEarlyData;
92pub use self::server::RawPriorityValue;
93pub use self::server::ServerEventStream;
94pub use self::server::ServerH3Command;
95pub use self::server::ServerH3Controller;
96pub use self::server::ServerH3Driver;
97pub use self::server::ServerH3Event;
98
99// The default priority for HTTP/3 responses if the application didn't provide
100// one.
101const DEFAULT_PRIO: h3::Priority = h3::Priority::new(3, true);
102
103// For a stream use a channel with 16 entries, which works out to 16 * 64KB =
104// 1MB of max buffered data.
105#[cfg(not(any(test, debug_assertions)))]
106const STREAM_CAPACITY: usize = 16;
107// A single entry stresses `write_pending` in test and debug builds.
108#[cfg(any(test, debug_assertions))]
109const STREAM_CAPACITY: usize = 1;
110
111// For *all* flows use a shared channel with 2048 entries, which works out
112// to 3MB of max buffered data at 1500 bytes per datagram.
113const FLOW_CAPACITY: usize = 2048;
114
115// Floor for the lazily-allocated body receive buffer. The buffer is sized to
116// the amount currently readable on the stream (see [`process_h3_data`]), but we
117// never allocate below this floor, for two reasons:
118//
119// - A `Limit<BytesMut>` with a zero limit reports no remaining capacity and
120//   would make `recv_body_buf` a no-op.
121// - Sizing strictly to the readable length defeats allocation amortization: a
122//   body that trickles in a few bytes at a time (e.g. one byte per read) would
123//   reallocate the buffer on every read. Allocating at least this many bytes
124//   lets a single allocation absorb many small reads (each `split()` off)
125//   before it is exhausted and reallocated.
126//
127// The floor is a soft hint, not a hard minimum: it never overrides the
128// configured cap, so a cap smaller than the floor still bounds the allocation
129// (see [`body_recv_buf_size`]).
130const MIN_BODY_RECV_BUF_SIZE: usize = 1024;
131
132// Default cap for the body receive buffer when
133// [`Http3Settings::max_recv_body_buf_size`] is unset or zero. Chosen well below
134// `BufFactory::MAX_BUF_SIZE` (64 KiB) so a request that carries a body doesn't
135// pin a large allocation for the life of the stream; a larger streamed body
136// simply reallocates once per driver read-cycle.
137const DEFAULT_MAX_BODY_RECV_BUF_SIZE: usize = 16 * 1024;
138
139/// Computes the capacity to use for the body receive buffer given the number of
140/// bytes currently readable on the stream and the configured maximum.
141///
142/// The result is clamped to `[floor, max]`, where `floor` is
143/// [`MIN_BODY_RECV_BUF_SIZE`] capped by `max`. Reads below the floor still
144/// allocate the floor (so a trickle of tiny reads reuses one allocation instead
145/// of reallocating each time), while a single (potentially adversarial) read
146/// never allocates more than `max`. `max` is derived from
147/// [`Http3Settings::max_recv_body_buf_size`], defaulting to
148/// [`DEFAULT_MAX_BODY_RECV_BUF_SIZE`] when the setting is unset or zero.
149///
150/// `max` is the hard upper bound: when it is smaller than the floor it wins, so
151/// the range passed to `clamp` is always valid (`floor <= max`). The
152/// constructor guarantees `max >= 1`, so the result is never zero.
153fn body_recv_buf_size(readable: usize, max: usize) -> usize {
154    let floor = MIN_BODY_RECV_BUF_SIZE.min(max);
155    readable.clamp(floor, max)
156}
157
158/// Largest legal Quarter Stream ID value (RFC 9297 §2.1).
159/// Receipt of a datagram with a larger value MUST be treated as
160/// H3_DATAGRAM_ERROR (0x33).
161pub const MAX_QUARTER_STREAM_ID: u64 = (1 << 60) - 1;
162
163/// A validated RFC 9297 Quarter Stream ID or legacy `datagram-flow-id`.
164///
165/// The inner value is guaranteed not to exceed [`MAX_QUARTER_STREAM_ID`].
166///
167/// Note, that legacy flow_ids have a max value of 10^15-1, which is less
168/// than the value of [`MAX_QUARTER_STREAM_ID`]. This struct only checks
169/// against [`MAX_QUARTER_STREAM_ID`].
170#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
171pub struct DgramFlowId(u64);
172
173impl DgramFlowId {
174    /// Creates a new [`DgramFlowId`].
175    ///
176    /// Returns `None` when `id` exceeds [`MAX_QUARTER_STREAM_ID`].
177    pub fn new(id: u64) -> Option<Self> {
178        (id <= MAX_QUARTER_STREAM_ID).then_some(Self(id))
179    }
180
181    /// Returns the validated raw ID.
182    pub fn id(self) -> u64 {
183        self.0
184    }
185}
186
187impl fmt::Display for DgramFlowId {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        write!(f, "{}", self.0)
190    }
191}
192
193/// Used by a local task to send [`OutboundFrame`]s to a peer on the
194/// stream or flow associated with this channel.
195pub type OutboundFrameSender = PollSender<OutboundFrame>;
196
197/// Used internally to receive [`OutboundFrame`]s which should be sent to a peer
198/// on the stream or flow associated with this channel.
199type OutboundFrameStream = mpsc::Receiver<OutboundFrame>;
200
201/// Used internally to send [`InboundFrame`]s (data) from the peer to a local
202/// task on the stream or flow associated with this channel.
203type InboundFrameSender = PollSender<InboundFrame>;
204
205/// Used by a local task to receive [`InboundFrame`]s (data) on the stream or
206/// flow associated with this channel.
207pub type InboundFrameStream = mpsc::Receiver<InboundFrame>;
208
209/// The error type used internally in [H3Driver].
210///
211/// Note that [`ApplicationOverQuic`] errors are not exposed to users at this
212/// time. The type is public to document the failure modes in [H3Driver].
213#[derive(Debug, PartialEq, Eq)]
214#[non_exhaustive]
215pub enum H3ConnectionError {
216    /// The controller task was shut down and is no longer listening.
217    ControllerWentAway,
218    /// Other error at the connection, but not stream level.
219    H3(h3::Error),
220    /// Received data for a stream that was closed or never opened.
221    NonexistentStream,
222    /// The server's post-accept timeout was hit.
223    /// The timeout can be configured in [`Http3Settings`].
224    PostAcceptTimeout,
225}
226
227impl From<h3::Error> for H3ConnectionError {
228    fn from(err: h3::Error) -> Self {
229        H3ConnectionError::H3(err)
230    }
231}
232
233impl From<quiche::Error> for H3ConnectionError {
234    fn from(err: quiche::Error) -> Self {
235        H3ConnectionError::H3(h3::Error::TransportError(err))
236    }
237}
238
239impl Error for H3ConnectionError {}
240
241impl fmt::Display for H3ConnectionError {
242    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
243        let s: &dyn fmt::Display = match self {
244            Self::ControllerWentAway => &"controller went away",
245            Self::H3(e) => e,
246            Self::NonexistentStream => &"nonexistent stream",
247            Self::PostAcceptTimeout => &"post accept timeout hit",
248        };
249
250        write!(f, "H3ConnectionError: {s}")
251    }
252}
253
254type H3ConnectionResult<T> = Result<T, H3ConnectionError>;
255
256/// The channels and metadata for a newly established HTTP/3 DATAGRAM flow
257/// (RFC 9297). Delivered as part of [`IncomingH3Headers`] when the associated
258/// stream exchange carries DATAGRAM semantics (e.g. CONNECT-UDP / MASQUE).
259pub struct DatagramFlowInfo {
260    /// RFC 9297 Quarter Stream ID or legacy flow_id for this flow.
261    pub flow_id: DgramFlowId,
262    /// Sender for transmitting datagrams to the peer.
263    pub send: OutboundFrameSender,
264    /// Receiver for receiving datagrams from the peer.
265    pub recv: InboundFrameStream,
266}
267
268/// HTTP/3 headers that were received on a stream.
269///
270/// `recv` is used to read the message body, while `send` is used to transmit
271/// data back to the peer.
272pub struct IncomingH3Headers {
273    /// Stream ID of the frame.
274    pub stream_id: u64,
275    /// The actual [`h3::Header`]s which were received.
276    pub headers: Vec<h3::Header>,
277    /// An [`OutboundFrameSender`] for streaming body data to the peer. For
278    /// [ClientH3Driver], note that the request body can also be passed a
279    /// cloned sender via [`NewClientRequest`].
280    pub send: OutboundFrameSender,
281    /// An [`InboundFrameStream`] of body data received from the peer.
282    pub recv: InboundFrameStream,
283    /// Whether there is a body associated with the incoming headers.
284    pub read_fin: bool,
285    /// Handle to the [`H3AuditStats`] for the message's stream.
286    pub h3_audit_stats: Arc<H3AuditStats>,
287    /// If the associated stream exchange carries HTTP/3 DATAGRAM semantics
288    /// (e.g. CONNECT-UDP), this field contains the flow channels and flow_id.
289    /// `None` for ordinary streams.
290    pub dgram_flow: Option<DatagramFlowInfo>,
291}
292
293impl fmt::Debug for IncomingH3Headers {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        f.debug_struct("IncomingH3Headers")
296            .field("stream_id", &self.stream_id)
297            .field("headers", &self.headers)
298            .field("read_fin", &self.read_fin)
299            .field(
300                "dgram_flow_id",
301                &self.dgram_flow.as_ref().map(|f| f.flow_id),
302            )
303            .field("h3_audit_stats", &self.h3_audit_stats)
304            .finish()
305    }
306}
307
308/// [`H3Event`]s are produced by an [H3Driver] to describe HTTP/3 state updates.
309///
310/// Both [ServerH3Driver] and [ClientH3Driver] may extend this enum with
311/// endpoint-specific variants. The events must be consumed by users of the
312/// drivers, like a higher-level `Server` or `Client` controller.
313#[derive(Debug)]
314pub enum H3Event {
315    /// A SETTINGS frame was received.
316    IncomingSettings {
317        /// Raw HTTP/3 setting pairs, in the order received from the peer.
318        settings: Vec<(u64, u64)>,
319    },
320
321    /// A HEADERS frame was received on the given stream. This is either a
322    /// request or a response depending on the perspective of the [`H3Event`]
323    /// receiver.
324    IncomingHeaders(IncomingH3Headers),
325
326    /// A RST_STREAM frame was seen on the given `stream_id`. The user of the
327    /// driver should clean up any state allocated for this stream.
328    ResetStream { stream_id: u64 },
329    /// The connection has irrecoverably errored and is shutting down.
330    ConnectionError(h3::Error),
331    /// The connection has been shutdown, optionally due to an
332    /// [`H3ConnectionError`].
333    ConnectionShutdown(Option<H3ConnectionError>),
334    /// Body data has been received over a stream.
335    BodyBytesReceived {
336        /// Stream ID of the body data.
337        stream_id: u64,
338        /// Number of bytes received.
339        num_bytes: u64,
340        /// Whether the stream is finished and won't yield any more data.
341        fin: bool,
342    },
343    /// The stream has been closed. This is used to signal stream closures that
344    /// don't result from RST_STREAM frames, unlike the
345    /// [`H3Event::ResetStream`] variant.
346    StreamClosed { stream_id: u64 },
347    /// A GOAWAY frame was received from the peer containing `id`,
348    /// as described in https://datatracker.ietf.org/doc/html/rfc9114#section-5.2.
349    GoAway { id: u64 },
350}
351
352impl H3Event {
353    /// Generates an event from an applicable [`H3ConnectionError`].
354    fn from_error(err: &H3ConnectionError) -> Option<Self> {
355        Some(match err {
356            H3ConnectionError::H3(e) => Self::ConnectionError(*e),
357            H3ConnectionError::PostAcceptTimeout => Self::ConnectionShutdown(
358                Some(H3ConnectionError::PostAcceptTimeout),
359            ),
360            _ => return None,
361        })
362    }
363}
364
365/// An [`OutboundFrame`] is a data frame that should be sent from a local task
366/// to a peer over a [`quiche::h3::Connection`].
367///
368/// This is used, for example, to send response body data to a peer, or proxied
369/// UDP datagrams.
370#[derive(Debug)]
371pub enum OutboundFrame {
372    /// Response headers to be sent to the peer, with optional priority.
373    Headers(Vec<h3::Header>, Option<quiche::h3::Priority>),
374    /// Response body/CONNECT downstream data plus FIN flag.
375    Body(Bytes, bool),
376    /// CONNECT-UDP (DATAGRAM) downstream data plus flow ID.
377    Datagram(DgramBuffer, DgramFlowId),
378    /// Close the stream with a trailers, with optional priority.
379    Trailers(Vec<h3::Header>, Option<quiche::h3::Priority>),
380    /// An error encountered when serving the request. Stream should be closed.
381    PeerStreamError,
382    /// DATAGRAM flow explicitly closed.
383    FlowShutdown { flow_id: DgramFlowId },
384}
385
386/// An [`InboundFrame`] is a data frame that was received from the peer over a
387/// [`quiche::h3::Connection`]. This is used by peers to send body or datagrams
388/// to the local task.
389#[derive(Debug)]
390pub enum InboundFrame {
391    /// Request body/CONNECT upstream data plus FIN flag.
392    Body(BytesMut, bool),
393    /// CONNECT-UDP (DATAGRAM) upstream data.
394    Datagram(DgramBuffer),
395}
396
397/// A ready-made [`ApplicationOverQuic`] which can handle HTTP/3 and MASQUE.
398/// Depending on the `DriverHooks` in use, it powers either a client or a
399/// server.
400///
401/// Use the [ClientH3Driver] and [ServerH3Driver] aliases to access the
402/// respective driver types. The driver is passed into an I/O loop and
403/// communicates with the driver's user (e.g., an HTTP client or a server) via
404/// its associated [H3Controller]. The controller allows the application to both
405/// listen for [`H3Event`]s of note and send [`H3Command`]s into the I/O loop.
406pub struct H3Driver<H: DriverHooks> {
407    /// Configuration used to initialize `conn`. Created from [`Http3Settings`]
408    /// in the constructor.
409    h3_config: h3::Config,
410    /// The underlying HTTP/3 connection. Initialized in
411    /// `ApplicationOverQuic::on_conn_established`.
412    conn: Option<h3::Connection>,
413    /// State required by the client/server hooks.
414    hooks: H,
415    /// Sends [`H3Event`]s to the [H3Controller] paired with this driver.
416    h3_event_sender: mpsc::UnboundedSender<H::Event>,
417    /// Receives [`H3Command`]s from the [H3Controller] paired with this driver.
418    cmd_recv: mpsc::UnboundedReceiver<H::Command>,
419    /// A sender that feeds back into `cmd_recv`. Used by hooks that need to
420    /// re-queue commands (e.g. retrying blocked requests) without access to
421    /// the [H3Controller]'s copy of the sender.
422    cmd_sender: mpsc::UnboundedSender<H::Command>,
423
424    /// A map of stream IDs to their [StreamCtx]. This is mainly used to
425    /// retrieve the internal Tokio channels associated with the stream.
426    stream_map: BTreeMap<u64, StreamCtx>,
427    /// A map of flow IDs to their [FlowCtx]. This is mainly used to retrieve
428    /// the internal Tokio channels associated with the flow.
429    flow_map: BTreeMap<DgramFlowId, FlowCtx>,
430    /// Coalesced notifications from per-stream channel wakers.
431    ready_streams: ReadyStreams,
432    /// Kept open for the connection's lifetime and cloned into stream wakers.
433    ready_stream_sender: ReadyStreamSender,
434    /// Receives [`OutboundFrame`]s from all datagram flows on the connection.
435    dgram_recv: OutboundFrameStream,
436    /// Keeps the datagram channel open such that datagram flows can be created.
437    dgram_send: OutboundFrameSender,
438    /// A buffer to receive H3 body data from quiche. Lazily allocated on the
439    /// first body read and released once no streams remain, so idle
440    /// connections hold no receive buffer. We `split()` off filled parts until
441    /// we need to reallocate.
442    body_recv_buf: Option<bytes::buf::Limit<BytesMut>>,
443    /// Upper bound on the capacity of `body_recv_buf`, from
444    /// [`Http3Settings::max_recv_body_buf_size`]. Unset or `0` defaults to
445    /// [`DEFAULT_MAX_BODY_RECV_BUF_SIZE`], so this is always non-zero.
446    max_recv_body_buf_size: usize,
447
448    /// The maximum HTTP/3 stream ID seen on this connection.
449    max_stream_seen: u64,
450
451    /// Tracks whether we have forwarded the HTTP/3 SETTINGS frame
452    /// to the [H3Controller] once.
453    settings_received_and_forwarded: bool,
454    /// Tracks whether the H3 event receiver has been dropped.
455    /// Used to avoid busy-looping on `h3_event_sender.closed()`.
456    h3_event_receiver_dropped: bool,
457}
458
459impl<H: DriverHooks> H3Driver<H> {
460    /// Builds a new [H3Driver] and an associated [H3Controller].
461    ///
462    /// The driver should then be passed to
463    /// [`InitialQuicConnection`](crate::InitialQuicConnection)'s `start`
464    /// method.
465    pub fn new(http3_settings: Http3Settings) -> (Self, H3Controller<H>) {
466        let (dgram_send, dgram_recv) = mpsc::channel(FLOW_CAPACITY);
467        let (cmd_sender, cmd_recv) = mpsc::unbounded_channel();
468        let (h3_event_sender, h3_event_recv) = mpsc::unbounded_channel();
469        let (ready_stream_sender, ready_streams) = ReadyStreams::new();
470
471        (
472            H3Driver {
473                h3_config: (&http3_settings).into(),
474                conn: None,
475                hooks: H::new(&http3_settings),
476                h3_event_sender,
477                cmd_recv,
478                cmd_sender: cmd_sender.clone(),
479
480                stream_map: BTreeMap::new(),
481                flow_map: BTreeMap::new(),
482
483                dgram_recv,
484                dgram_send: PollSender::new(dgram_send),
485                max_stream_seen: 0,
486                body_recv_buf: None,
487                // `Some(0)` would make `recv_body_buf` a no-op, so treat it as
488                // unset.
489                max_recv_body_buf_size: http3_settings
490                    .max_recv_body_buf_size
491                    .filter(|&size| size > 0)
492                    .unwrap_or(DEFAULT_MAX_BODY_RECV_BUF_SIZE),
493
494                ready_streams,
495                ready_stream_sender,
496                settings_received_and_forwarded: false,
497                h3_event_receiver_dropped: false,
498            },
499            H3Controller {
500                cmd_sender,
501                h3_event_recv: Some(h3_event_recv),
502            },
503        )
504    }
505
506    /// Returns a sender that feeds back into this driver's own `cmd_recv`.
507    ///
508    /// Hooks that need to re-queue commands (e.g. retrying a request that
509    /// was temporarily blocked) can use this sender without needing access
510    /// to the paired [H3Controller].
511    pub(crate) fn self_cmd_sender(&self) -> &mpsc::UnboundedSender<H::Command> {
512        &self.cmd_sender
513    }
514
515    /// Create a new [`FlowCtx`] for `flow_id`, insert it into the flow map, and
516    /// return the channels that should be delivered to the application via
517    /// [`IncomingH3Headers::dgram_flow`].
518    /// `stream_id` is the QUIC stream that carried the request establishing
519    /// the datagram flow.
520    ///
521    /// Returns an error and closes the connection if a flow with the same
522    /// `flow_id` already exists. Panics in debug builds.
523    pub(crate) fn establish_flow(
524        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
525        flow_id: DgramFlowId,
526    ) -> H3ConnectionResult<DatagramFlowInfo> {
527        if self.flow_map.contains_key(&flow_id) {
528            debug_panic!(
529                "establish_flow called for already-established flow {}",
530                flow_id
531            );
532            let _ = qconn.close(
533                true,
534                WireErrorCode::DatagramError as u64,
535                b"duplicate flow_id",
536            );
537            return Err(H3ConnectionError::H3(h3::Error::IdError));
538        }
539        let (flow_ctx, recv) = FlowCtx::new(stream_id, FLOW_CAPACITY);
540        self.flow_map.insert(flow_id, flow_ctx);
541        Ok(DatagramFlowInfo {
542            flow_id,
543            send: self.dgram_send.clone(),
544            recv,
545        })
546    }
547
548    /// Return a mutable reference to the [`FlowCtx`] for `flow_id`, or `None`
549    /// if no flow with that ID has been established.
550    pub(crate) fn get_flow(
551        &mut self, flow_id: DgramFlowId,
552    ) -> Option<&mut FlowCtx> {
553        self.flow_map.get_mut(&flow_id)
554    }
555
556    /// Adds a [StreamCtx] to the stream map with the given `stream_id`.
557    fn insert_stream(&mut self, stream_id: u64, ctx: StreamCtx) {
558        self.stream_map.insert(stream_id, ctx);
559        self.max_stream_seen = self.max_stream_seen.max(stream_id);
560    }
561
562    /// Fetches body chunks from the [`quiche::h3::Connection`] and forwards
563    /// them to the stream's associated [`InboundFrameStream`].
564    fn process_h3_data(
565        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
566    ) -> H3ConnectionResult<()> {
567        // Split self borrow between conn and stream_map
568        let conn = self.conn.as_mut().ok_or(Self::connection_not_present())?;
569        let ctx = self
570            .stream_map
571            .get_mut(&stream_id)
572            .ok_or(H3ConnectionError::NonexistentStream)?;
573
574        /// Result of one receive-body processing pass.
575        enum StreamStatus {
576            /// No more receive work can be completed in this pass.
577            Done,
578            /// Peer FIN was queued to the application channel.
579            Fin,
580            /// Peer RESET_STREAM was received with this application error code.
581            Reset { wire_err_code: u64 },
582            /// Application-channel capacity is unavailable and must be awaited.
583            Blocked,
584            /// An unrecoverable receive error requires stream cleanup.
585            Failed,
586        }
587
588        let status = loop {
589            let Some(sender) = ctx.recv_state.inbound_sender_if_not_waiting()
590            else {
591                break StreamStatus::Done;
592            };
593
594            let try_reserve_result = sender.try_reserve();
595            let permit = match try_reserve_result {
596                Ok(permit) => permit,
597                Err(TrySendError::Closed(())) => {
598                    // The channel has closed before we delivered a fin or reset
599                    // to the application.
600                    // Release the channel borrow before updating the context.
601                    drop(try_reserve_result);
602                    match ctx.recv_state {
603                        RecvState::Open(_) => {
604                            let err = h3::WireErrorCode::RequestCancelled as u64;
605                            let _ = qconn.stream_shutdown(
606                                stream_id,
607                                quiche::Shutdown::Read,
608                                err,
609                            );
610                            ctx.handle_sent_stop_sending(err)?;
611                            // TODO: should channel abandonment emit an H3Event
612                            // for logging or auditing?
613                        },
614                        RecvState::FinPending(_) => {
615                            ctx.recv_state = RecvState::Closed;
616                        },
617                        RecvState::Closed => {},
618                    }
619                    break StreamStatus::Done;
620                },
621                Err(TrySendError::Full(())) => {
622                    match ctx.recv_state {
623                        RecvState::FinPending(_) | RecvState::Closed =>
624                            break StreamStatus::Blocked,
625                        RecvState::Open(_) =>
626                            if qconn.stream_readable(stream_id) {
627                                break StreamStatus::Blocked;
628                            },
629                    }
630                    break StreamStatus::Done;
631                },
632            };
633
634            match ctx.recv_state {
635                RecvState::FinPending(_) | RecvState::Closed => {
636                    // Signal end-of-body to upstream.
637                    permit.send(InboundFrame::Body(Default::default(), true));
638                    break StreamStatus::Fin;
639                },
640                RecvState::Open(_) => {},
641            }
642
643            // Size the buffer for currently readable, in-order data, capped at
644            // the configured maximum. The readable length includes H3 framing,
645            // so it can drain all readable body bytes. The minimum keeps the
646            // allocation nonzero and amortizes a trickle of small reads.
647            let want = body_recv_buf_size(
648                qconn.stream_readable_len(stream_id, self.max_recv_body_buf_size),
649                self.max_recv_body_buf_size,
650            );
651            // Lazily allocate the receive buffer on first use; idle
652            // connections never receive body bytes and never allocate it.
653            let body_recv_buf = self
654                .body_recv_buf
655                .get_or_insert_with(|| BytesMut::with_capacity(want).limit(want));
656            // `Limit<BytesMut>` reports only capacity below its limit. Plain
657            // `BytesMut` can reallocate and always reports available space.
658            //
659            // Reallocate when the remaining room cannot hold this read. This
660            // handles exhausted buffers and reads larger than the previous
661            // allocation. Keep capacity equal to the limit to preserve the
662            // `split()` invariant checked below.
663            if body_recv_buf.remaining_mut() < want {
664                *body_recv_buf = BytesMut::with_capacity(want).limit(want);
665            }
666            match conn.recv_body_buf(qconn, stream_id, &mut *body_recv_buf) {
667                Ok(n) => {
668                    ctx.audit_stats.add_downstream_bytes_recvd(n as u64);
669                    let event = H3Event::BodyBytesReceived {
670                        stream_id,
671                        num_bytes: n as u64,
672                        fin: false,
673                    };
674                    let _ = self.h3_event_sender.send(event.into());
675                    // Take the filled part, leave the remaining capacity
676                    let filled_body = body_recv_buf.get_mut().split();
677                    // Sanity check: the remaining spare capacity should equal
678                    // the limit.
679                    debug_assert_eq!(
680                        body_recv_buf.get_mut().spare_capacity_mut().len(),
681                        body_recv_buf.remaining_mut()
682                    );
683                    // A full split leaves only an empty shared handle, so let
684                    // the forwarded frame own the allocation.
685                    if !body_recv_buf.has_remaining_mut() {
686                        self.body_recv_buf = None;
687                    }
688                    permit.send(InboundFrame::Body(filled_body, false));
689                },
690                Err(h3::Error::Done) => break StreamStatus::Done,
691                Err(h3::Error::TransportError(quiche::Error::StreamReset(
692                    code,
693                ))) => {
694                    break StreamStatus::Reset {
695                        wire_err_code: code,
696                    };
697                },
698                Err(_) => break StreamStatus::Failed,
699            }
700        };
701
702        if let StreamStatus::Fin = status {
703            ctx.recv_state = RecvState::Closed;
704        }
705
706        match status {
707            StreamStatus::Done | StreamStatus::Fin => {
708                if ctx.fully_closed() {
709                    return self.cleanup_stream(qconn, stream_id);
710                }
711
712                // The QUIC stream is finished, manually invoke `process_h3_fin`
713                // in case `h3::poll()` is never called again.
714                //
715                // Note that this case will not conflict with StreamStatus::Done
716                // being returned due to the body channel being
717                // blocked. qconn.stream_finished() will guarantee
718                // that we've fully parsed the body as it only returns true
719                // if we've seen a Fin for the read half of the stream.
720                match ctx.recv_state {
721                    RecvState::Open(_) =>
722                        if qconn.stream_finished(stream_id) {
723                            return self.process_h3_fin(qconn, stream_id);
724                        },
725                    RecvState::FinPending(_) | RecvState::Closed => {},
726                }
727            },
728            StreamStatus::Reset { wire_err_code } => {
729                ctx.handle_recvd_reset(wire_err_code)?;
730                let fully_closed = ctx.fully_closed();
731                H::stream_recv_closed(self, stream_id);
732                self.h3_event_sender
733                    .send(H3Event::ResetStream { stream_id }.into())
734                    .map_err(|_| H3ConnectionError::ControllerWentAway)?;
735                if fully_closed {
736                    return self.cleanup_stream(qconn, stream_id);
737                }
738            },
739            StreamStatus::Blocked => {
740                ctx.wait_for_send();
741            },
742            StreamStatus::Failed => return self.cleanup_stream(qconn, stream_id),
743        }
744
745        Ok(())
746    }
747
748    /// Processes an end-of-stream event from the [`quiche::h3::Connection`].
749    fn process_h3_fin(
750        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
751    ) -> H3ConnectionResult<()> {
752        let Some(ctx) = self.stream_map.get_mut(&stream_id) else {
753            // Report a pending stop because no context remains to handle it.
754            //
755            // This removes its writable notification and permits transport
756            // cleanup.
757            let _ = qconn.stream_capacity(stream_id);
758            return Ok(());
759        };
760
761        if !ctx.recv_state.mark_fin_pending() {
762            return Ok(());
763        }
764        ctx.audit_stats
765            .set_recvd_stream_fin(StreamClosureKind::Explicit);
766        H::stream_recv_closed(self, stream_id);
767
768        // It's important to send this H3Event before process_h3_data so that
769        // a server can (potentially) generate the control response before the
770        // corresponding receiver drops.
771        let event = H3Event::BodyBytesReceived {
772            stream_id,
773            num_bytes: 0,
774            fin: true,
775        };
776        let _ = self.h3_event_sender.send(event.into());
777
778        // The state is no longer Open, so process_h3_data's finished-stream
779        // fallback cannot call process_h3_fin recursively.
780        self.process_h3_data(qconn, stream_id)
781    }
782
783    /// Processes a single [`quiche::h3::Event`] received from the underlying
784    /// [`quiche::h3::Connection`]. Some events are dispatched to helper
785    /// methods.
786    fn process_read_event(
787        &mut self, qconn: &mut QuicheConnection, stream_id: u64, event: h3::Event,
788    ) -> H3ConnectionResult<()> {
789        self.forward_settings()?;
790
791        match event {
792            // Requests/responses are exclusively handled by hooks.
793            h3::Event::Headers { list, more_frames } =>
794                H::headers_received(self, qconn, InboundHeaders {
795                    stream_id,
796                    headers: list,
797                    has_body: more_frames,
798                }),
799
800            h3::Event::Data => self.process_h3_data(qconn, stream_id),
801            h3::Event::Finished => self.process_h3_fin(qconn, stream_id),
802
803            h3::Event::Reset(code) => {
804                if let Some(ctx) = self.stream_map.get_mut(&stream_id) {
805                    ctx.handle_recvd_reset(code)?;
806
807                    let fully_closed = ctx.fully_closed();
808                    H::stream_recv_closed(self, stream_id);
809                    self.h3_event_sender
810                        .send(H3Event::ResetStream { stream_id }.into())
811                        .map_err(|_| H3ConnectionError::ControllerWentAway)?;
812                    if fully_closed {
813                        return self.cleanup_stream(qconn, stream_id);
814                    }
815                } else {
816                    // A reset can finish a stream that never had a context.
817                    let _ = qconn.stream_capacity(stream_id);
818                }
819
820                // TODO: if we don't have the stream in our map: should we
821                // send the H3Event::ResetStream?
822                Ok(())
823            },
824
825            h3::Event::PriorityUpdate => Ok(()),
826            h3::Event::GoAway => {
827                self.h3_event_sender
828                    .send(H3Event::GoAway { id: stream_id }.into())
829                    .map_err(|_| H3ConnectionError::ControllerWentAway)?;
830                Ok(())
831            },
832        }
833    }
834
835    /// The SETTINGS frame can be received at any point, so we
836    /// need to check `peer_settings_raw` to decide if we've received it.
837    ///
838    /// Settings should only be sent once, so we generate a single event
839    /// when `peer_settings_raw` transitions from None to Some.
840    fn forward_settings(&mut self) -> H3ConnectionResult<()> {
841        if self.settings_received_and_forwarded {
842            return Ok(());
843        }
844
845        // capture the peer settings and forward it
846        if let Some(settings) = self.conn_mut()?.peer_settings_raw() {
847            let incoming_settings = H3Event::IncomingSettings {
848                settings: settings.to_vec(),
849            };
850
851            self.h3_event_sender
852                .send(incoming_settings.into())
853                .map_err(|_| H3ConnectionError::ControllerWentAway)?;
854
855            self.settings_received_and_forwarded = true;
856        }
857        Ok(())
858    }
859
860    /// Send an individual frame to the underlying [`quiche::h3::Connection`] to
861    /// be flushed at a later time.
862    ///
863    /// `Self::process_writes` will iterate over all writable streams and call
864    /// this method in a loop for each stream to send all writable packets.
865    fn process_write_frame(
866        conn: &mut h3::Connection, qconn: &mut QuicheConnection,
867        ctx: &mut StreamCtx,
868    ) -> h3::Result<()> {
869        let audit_stats = &ctx.audit_stats;
870        let stream_id = audit_stats.stream_id();
871
872        // Observe STOP before H3 validates queued output (which may no longer
873        // be valid, e.g. body data before response headers). An application
874        // error must still close both directions even after the write side
875        // has stopped, so let it reach the MessageError path below.
876        if !matches!(ctx.queued_frame, Some(OutboundFrame::PeerStreamError)) {
877            if let Err(error @ quiche::Error::StreamStopped(_)) =
878                qconn.stream_capacity(stream_id)
879            {
880                return Err(h3::Error::TransportError(error));
881            }
882        }
883
884        match &mut ctx.queued_frame {
885            Some(OutboundFrame::Headers(headers, priority)) => {
886                let prio = priority.as_ref().unwrap_or(&DEFAULT_PRIO);
887
888                let res = if ctx.initial_headers_sent {
889                    // Initial headers were already sent, send additional
890                    // headers now.
891                    conn.send_additional_headers_with_priority(
892                        qconn, stream_id, headers, prio, false, false,
893                    )
894                } else {
895                    // Send initial headers.
896                    conn.send_response_with_priority(
897                        qconn, stream_id, headers, prio, false,
898                    )
899                    .inspect(|_| ctx.initial_headers_sent = true)
900                };
901
902                if let Err(h3::Error::StreamBlocked) = res {
903                    ctx.first_full_headers_flush_fail_time
904                        .get_or_insert(Instant::now());
905                }
906
907                if res.is_ok() {
908                    if let Some(first) =
909                        ctx.first_full_headers_flush_fail_time.take()
910                    {
911                        ctx.audit_stats.add_header_flush_duration(
912                            Instant::now().duration_since(first),
913                        );
914                    }
915                }
916
917                return res;
918            },
919
920            Some(OutboundFrame::Body(body, fin)) if !body.is_empty() || *fin => {
921                let len = body.len();
922
923                if *fin {
924                    // Drop the receiver on FIN to prevent more frames.
925                    // We cannot use `mpsc::Receiver::close()` because of a
926                    // Tokio inconsistency when reading a closed channel.
927                    // See https://github.com/tokio-rs/tokio/issues/7631.
928                    ctx.recv = None;
929                }
930
931                let n = conn.send_body_zc(qconn, stream_id, body, *fin)?;
932
933                audit_stats.add_downstream_bytes_sent(n as _);
934
935                return if n != len {
936                    // Couldn't write the entire body, `send_body_zc` will
937                    // have trimmed `body` accordingly. The driver keeps
938                    // the remainder of the body to send in the future.
939                    debug_assert_eq!(
940                        n + body.len(),
941                        len,
942                        "send_body_zc() should have trimmed body but did not"
943                    );
944                    Err(h3::Error::StreamBlocked)
945                } else {
946                    if *fin {
947                        Self::on_fin_sent(ctx)?;
948                    }
949
950                    Ok(())
951                };
952            },
953
954            Some(OutboundFrame::Body(..)) => {
955                // Empty non-FIN bodies need no H3 write. STOP was probed above.
956            },
957
958            Some(OutboundFrame::Trailers(headers, priority)) => {
959                let prio = priority.as_ref().unwrap_or(&DEFAULT_PRIO);
960
961                // trailers always set fin=true
962                let res = conn.send_additional_headers_with_priority(
963                    qconn, stream_id, headers, prio, true, true,
964                );
965
966                if res.is_ok() {
967                    Self::on_fin_sent(ctx)?;
968                }
969
970                return res;
971            },
972
973            Some(OutboundFrame::PeerStreamError) => {
974                return Err(h3::Error::MessageError);
975            },
976
977            Some(OutboundFrame::FlowShutdown { .. }) => {
978                unreachable!("Only flows send shutdowns")
979            },
980
981            Some(OutboundFrame::Datagram(..)) => {
982                unreachable!("Only flows send datagrams")
983            },
984
985            None => {
986                // Idle streams still observe STOP through the probe above.
987            },
988        }
989
990        Ok(())
991    }
992
993    fn on_fin_sent(ctx: &mut StreamCtx) -> h3::Result<()> {
994        match ctx.send_state {
995            SendState::Open => {},
996            SendState::Closed => {
997                debug_assert!(
998                    false,
999                    "FIN completion requires an open send direction"
1000                );
1001                return Err(h3::Error::InternalError);
1002            },
1003        }
1004        ctx.cancel_recv_wait();
1005        ctx.recv = None;
1006        ctx.send_state = SendState::Closed;
1007        ctx.audit_stats
1008            .set_sent_stream_fin(StreamClosureKind::Explicit);
1009        if ctx.fully_closed() {
1010            // Return a TransportError to trigger stream cleanup
1011            // instead of h3::Error::Done.
1012            Err(h3::Error::TransportError(quiche::Error::Done))
1013        } else {
1014            Ok(())
1015        }
1016    }
1017
1018    /// Re-polls the waiting channels for an awakened stream and resumes the
1019    /// corresponding connection reads and writes. Queue acknowledgment has
1020    /// already occurred in ReadyStreams. Stale IDs after cleanup are ignored;
1021    /// their wake metadata owns no stream endpoints.
1022    fn upstream_ready(
1023        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1024    ) -> H3ConnectionResult<()> {
1025        let Some(stream) = self.stream_map.get_mut(&stream_id) else {
1026            return Ok(());
1027        };
1028        let (recv_ready, send_ready) = stream.poll_waiting()?;
1029        // Service app-bound capacity before outbound data so pending peer FIN
1030        // reaches the app before outbound FIN can clean up the stream.
1031        if send_ready {
1032            self.process_h3_data(qconn, stream_id)?;
1033        }
1034        if recv_ready && self.stream_map.contains_key(&stream_id) {
1035            self.process_writable_stream(qconn, stream_id)?;
1036        }
1037        Ok(())
1038    }
1039
1040    /// Processes all queued outbound datagrams from the `dgram_recv` channel.
1041    fn dgram_ready(
1042        &mut self, qconn: &mut QuicheConnection, frame: OutboundFrame,
1043    ) -> H3ConnectionResult<()> {
1044        // Opportunistically purge expired pending datagrams.
1045        self.hooks.expire_pending_dgrams();
1046
1047        let mut frame = Ok(frame);
1048
1049        loop {
1050            match frame {
1051                Ok(OutboundFrame::Datagram(dgram, flow_id)) => {
1052                    // Drop datagrams if there is no capacity
1053                    let _ = datagram::send_h3_dgram(qconn, flow_id, dgram);
1054                },
1055                Ok(OutboundFrame::FlowShutdown { flow_id }) => {
1056                    self.cleanup_flow(qconn, flow_id)?;
1057                    break;
1058                },
1059                Ok(_) => unreachable!("Flows can't send frame of other types"),
1060                Err(TryRecvError::Empty) => break,
1061                Err(TryRecvError::Disconnected) =>
1062                    return Err(H3ConnectionError::ControllerWentAway),
1063            }
1064
1065            frame = self.dgram_recv.try_recv();
1066        }
1067
1068        Ok(())
1069    }
1070
1071    /// Return a mutable reference to the driver's HTTP/3 connection.
1072    ///
1073    /// If the connection doesn't exist yet, this function returns
1074    /// a `Self::connection_not_present()` error.
1075    fn conn_mut(&mut self) -> H3ConnectionResult<&mut h3::Connection> {
1076        self.conn.as_mut().ok_or(Self::connection_not_present())
1077    }
1078
1079    /// Alias for [`quiche::Error::TlsFail`], which is used in the case where
1080    /// this driver doesn't have an established HTTP/3 connection attached
1081    /// to it yet.
1082    const fn connection_not_present() -> H3ConnectionError {
1083        H3ConnectionError::H3(h3::Error::TransportError(quiche::Error::TlsFail))
1084    }
1085
1086    /// Cleans up internal state for the indicated HTTP/3 stream.
1087    ///
1088    /// This function removes the stream and its channel endpoints from the
1089    /// stream map, removes associated DATAGRAM flows, and sends a
1090    /// [`H3Event::StreamClosed`] event (for servers).
1091    fn cleanup_stream(
1092        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1093    ) -> H3ConnectionResult<()> {
1094        let Some(mut stream_ctx) = self.stream_map.remove(&stream_id) else {
1095            return Ok(());
1096        };
1097
1098        // Receive-side cleanup can precede the writable stop notification.
1099        if let Err(quiche::Error::StreamStopped(code)) =
1100            qconn.stream_capacity(stream_id)
1101        {
1102            stream_ctx.handle_recvd_stop_sending(code);
1103        }
1104
1105        // Close any DATAGRAM-proxying channels when we close the stream, if
1106        // they exist, and remove any pending buffered datagrams for that flow.
1107        if let Some(mapped_flow_id) = stream_ctx.associated_dgram_flow_id {
1108            // We can call `cleanup_flow()`. While `cleanup_flow()` transitively
1109            // calls `cleanup_stream()`, it won't lead to a loop, since
1110            // `cleanup_flow()` and `cleanup_stream()` remove the flow / stream
1111            // entry first.
1112            self.cleanup_flow(qconn, mapped_flow_id)?;
1113        }
1114
1115        H::stream_closed(self, stream_id);
1116
1117        self.close_if_idle(qconn);
1118
1119        Ok(())
1120    }
1121
1122    /// Handles connection cleanup once no streams or flows remain.
1123    ///
1124    /// Releases the body receive buffer (it is reallocated lazily on the next
1125    /// body read; body bytes only flow on active streams, so an empty stream
1126    /// map means it is unused) and closes the connection with `NoError` if the
1127    /// H3 event receiver has been dropped.
1128    fn close_if_idle(&mut self, qconn: &mut QuicheConnection) {
1129        if self.stream_map.is_empty() && self.flow_map.is_empty() {
1130            self.body_recv_buf = None;
1131
1132            if self.h3_event_receiver_dropped {
1133                let _ = qconn.close(
1134                    true,
1135                    quiche::h3::WireErrorCode::NoError as u64,
1136                    &[],
1137                );
1138            }
1139        }
1140    }
1141
1142    /// Cleans up a datagram flow and shuts down its associated HTTP/3 stream.
1143    ///
1144    /// This is intentionally idempotent: a flow might be cleaned up both by an
1145    /// explicit [`OutboundFrame::FlowShutdown`] and associated stream cleanup.
1146    /// Only the first caller performs the shutdown.
1147    fn cleanup_flow(
1148        &mut self, qconn: &mut QuicheConnection, flow_id: DgramFlowId,
1149    ) -> H3ConnectionResult<()> {
1150        let Some(flow_ctx) = self.flow_map.remove(&flow_id) else {
1151            return Ok(());
1152        };
1153
1154        self.shutdown_stream(
1155            qconn,
1156            flow_ctx.associated_stream_id,
1157            StreamShutdown::Both {
1158                read_error_code: WireErrorCode::NoError as u64,
1159                write_error_code: WireErrorCode::NoError as u64,
1160            },
1161        )?;
1162
1163        self.close_if_idle(qconn);
1164
1165        Ok(())
1166    }
1167
1168    /// Shuts down the indicated HTTP/3 stream by sending frames and cleaning
1169    /// up then cleans up internal state by calling
1170    /// [`Self::cleanup_stream`].
1171    fn shutdown_stream(
1172        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1173        shutdown: StreamShutdown,
1174    ) -> H3ConnectionResult<()> {
1175        let Some(stream_ctx) = self.stream_map.get(&stream_id) else {
1176            return Ok(());
1177        };
1178
1179        let audit_stats = &stream_ctx.audit_stats;
1180
1181        match shutdown {
1182            StreamShutdown::Read { error_code } => {
1183                audit_stats.set_sent_stop_sending_error_code(error_code as _);
1184                let _ = qconn.stream_shutdown(
1185                    stream_id,
1186                    quiche::Shutdown::Read,
1187                    error_code,
1188                );
1189            },
1190            StreamShutdown::Write { error_code } => {
1191                audit_stats.set_sent_reset_stream_error_code(error_code as _);
1192                let _ = qconn.stream_shutdown(
1193                    stream_id,
1194                    quiche::Shutdown::Write,
1195                    error_code,
1196                );
1197            },
1198            StreamShutdown::Both {
1199                read_error_code,
1200                write_error_code,
1201            } => {
1202                audit_stats
1203                    .set_sent_stop_sending_error_code(read_error_code as _);
1204                let _ = qconn.stream_shutdown(
1205                    stream_id,
1206                    quiche::Shutdown::Read,
1207                    read_error_code,
1208                );
1209                audit_stats
1210                    .set_sent_reset_stream_error_code(write_error_code as _);
1211                let _ = qconn.stream_shutdown(
1212                    stream_id,
1213                    quiche::Shutdown::Write,
1214                    write_error_code,
1215                );
1216            },
1217        }
1218
1219        self.cleanup_stream(qconn, stream_id)
1220    }
1221
1222    /// Handles a regular [`H3Command`]. May be called internally by
1223    /// [DriverHooks] for non-endpoint-specific [`H3Command`]s.
1224    fn handle_core_command(
1225        &mut self, qconn: &mut QuicheConnection, cmd: H3Command,
1226    ) -> H3ConnectionResult<()> {
1227        match cmd {
1228            H3Command::QuicCmd(cmd) => cmd.execute(qconn),
1229            H3Command::GoAway => {
1230                let max_id = self.max_stream_seen;
1231                self.conn_mut()
1232                    .expect("connection should be established")
1233                    .send_goaway(qconn, max_id)?;
1234            },
1235            H3Command::ShutdownStream {
1236                stream_id,
1237                shutdown,
1238            } => {
1239                self.shutdown_stream(qconn, stream_id, shutdown)?;
1240            },
1241        }
1242        Ok(())
1243    }
1244}
1245
1246impl<H: DriverHooks> H3Driver<H> {
1247    /// Reads all buffered datagrams out of `qconn` and hands them to the
1248    /// `H::handle_incoming_dgram()` hook for delivery, buffering, or dropping.
1249    fn process_available_dgrams(
1250        &mut self, qconn: &mut QuicheConnection,
1251    ) -> H3ConnectionResult<()> {
1252        loop {
1253            match datagram::receive_h3_dgram(qconn) {
1254                Ok((raw_flow_id, dgram)) => {
1255                    // RFC 9297 §2.1: Quarter Stream ID > 2^60-1 is a
1256                    // connection-level error.
1257                    let Some(flow_id) = DgramFlowId::new(raw_flow_id) else {
1258                        let _ = qconn.close(
1259                            true,
1260                            WireErrorCode::DatagramError as u64,
1261                            b"",
1262                        );
1263                        return Err(H3ConnectionError::H3(h3::Error::IdError));
1264                    };
1265                    H::handle_incoming_dgram(self, flow_id, dgram);
1266                },
1267                Err(quiche::Error::Done) => return Ok(()),
1268                Err(err) => return Err(H3ConnectionError::from(err)),
1269            }
1270        }
1271    }
1272
1273    /// Flushes any queued-up frames for `stream_id` into `qconn` until either
1274    /// there is no more capacity in `qconn` or no more frames to send.
1275    fn process_writable_stream(
1276        &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1277    ) -> H3ConnectionResult<()> {
1278        // Split self borrow between conn and stream_map
1279        let conn = self.conn.as_mut().ok_or(Self::connection_not_present())?;
1280        let Some(ctx) = self.stream_map.get_mut(&stream_id) else {
1281            // Keep the stop pending while headers can still create a context.
1282            if qconn.stream_finished(stream_id) {
1283                let _ = qconn.stream_capacity(stream_id);
1284            }
1285
1286            return Ok(());
1287        };
1288
1289        loop {
1290            // Process each writable frame, queue the next frame for processing
1291            // and shut down any errored streams.
1292            match Self::process_write_frame(conn, qconn, ctx) {
1293                Ok(()) => ctx.queued_frame = None,
1294                Err(h3::Error::StreamBlocked | h3::Error::Done) => break,
1295                Err(h3::Error::MessageError) => {
1296                    return self.shutdown_stream(
1297                        qconn,
1298                        stream_id,
1299                        StreamShutdown::Both {
1300                            read_error_code: WireErrorCode::MessageError as u64,
1301                            write_error_code: WireErrorCode::MessageError as u64,
1302                        },
1303                    );
1304                },
1305                Err(h3::Error::TransportError(quiche::Error::StreamStopped(
1306                    e,
1307                ))) => {
1308                    ctx.handle_recvd_stop_sending(e);
1309
1310                    // A buffered application error still closes both halves
1311                    // after STOP_SENDING has discarded ordinary outbound data.
1312                    if matches!(
1313                        ctx.queued_frame,
1314                        Some(OutboundFrame::PeerStreamError)
1315                    ) {
1316                        continue;
1317                    }
1318
1319                    if ctx.fully_closed() {
1320                        return self.cleanup_stream(qconn, stream_id);
1321                    }
1322                    return Ok(());
1323                },
1324                Err(h3::Error::TransportError(
1325                    quiche::Error::InvalidStreamState(stream),
1326                )) => {
1327                    return self.cleanup_stream(qconn, stream);
1328                },
1329                Err(_) => {
1330                    return self.cleanup_stream(qconn, stream_id);
1331                },
1332            }
1333
1334            if ctx.waiting_for_recv() {
1335                return Ok(());
1336            }
1337            let Some(recv) = ctx.recv.as_mut() else {
1338                // We wrote a fin or otherwise closed the channel.
1339                // Defensive: successful frame processing cleared queued output.
1340                if ctx.queued_frame.is_some() {
1341                    debug_assert!(
1342                        false,
1343                        "A closed outbound channel must not have a queued frame"
1344                    );
1345                    return Err(H3ConnectionError::H3(h3::Error::InternalError));
1346                }
1347                return Ok(());
1348            };
1349
1350            // Attempt to queue the next frame for processing. The corresponding
1351            // sender is created at the same time as the `StreamCtx`
1352            // and ultimately ends up in an `H3Body`. The body then
1353            // determines which frames to send to the peer via
1354            // this processing loop.
1355            match recv.try_recv() {
1356                Ok(frame) => ctx.queued_frame = Some(frame),
1357                Err(TryRecvError::Disconnected) => {
1358                    match ctx.send_state {
1359                        SendState::Open => {
1360                            // No FIN was written before channel closure. Reset
1361                            // because no more data will follow.
1362                            let err = h3::WireErrorCode::RequestCancelled as u64;
1363                            let _ = qconn.stream_shutdown(
1364                                stream_id,
1365                                quiche::Shutdown::Write,
1366                                err,
1367                            );
1368                            ctx.handle_sent_reset(err)?;
1369                            if ctx.fully_closed() {
1370                                return self.cleanup_stream(qconn, stream_id);
1371                            }
1372                        },
1373                        SendState::Closed => {},
1374                    }
1375                    break;
1376                },
1377                Err(TryRecvError::Empty) => {
1378                    ctx.wait_for_recv();
1379                    break;
1380                },
1381            }
1382        }
1383
1384        Ok(())
1385    }
1386
1387    /// Tests `qconn` for either a local or peer error and increments
1388    /// the associated HTTP/3 or QUIC error counter.
1389    fn record_quiche_error(qconn: &mut QuicheConnection, metrics: &impl Metrics) {
1390        // split metrics between local/peer and QUIC/HTTP/3 level errors
1391        if let Some(err) = qconn.local_error() {
1392            if err.is_app {
1393                metrics.local_h3_conn_close_error_count(err.error_code.into())
1394            } else {
1395                metrics.local_quic_conn_close_error_count(err.error_code.into())
1396            }
1397            .inc();
1398        } else if let Some(err) = qconn.peer_error() {
1399            if err.is_app {
1400                metrics.peer_h3_conn_close_error_count(err.error_code.into())
1401            } else {
1402                metrics.peer_quic_conn_close_error_count(err.error_code.into())
1403            }
1404            .inc();
1405        }
1406    }
1407}
1408
1409impl<H: DriverHooks> ApplicationOverQuic for H3Driver<H> {
1410    fn on_conn_established(
1411        &mut self, quiche_conn: &mut QuicheConnection,
1412        handshake_info: &HandshakeInfo,
1413    ) -> QuicResult<()> {
1414        let conn = h3::Connection::with_transport(quiche_conn, &self.h3_config)?;
1415        self.conn = Some(conn);
1416
1417        H::conn_established(self, quiche_conn, handshake_info)?;
1418        Ok(())
1419    }
1420
1421    #[inline]
1422    fn should_act(&self) -> bool {
1423        self.conn.is_some()
1424    }
1425
1426    /// Poll the underlying [`quiche::h3::Connection`] for
1427    /// [`quiche::h3::Event`]s and DATAGRAMs, delegating processing to
1428    /// `Self::process_read_event`.
1429    ///
1430    /// If a DATAGRAM is found, it is sent to the receiver on its channel.
1431    fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {
1432        loop {
1433            match self.conn_mut()?.poll(qconn) {
1434                Ok((stream_id, event)) =>
1435                    self.process_read_event(qconn, stream_id, event)?,
1436                Err(h3::Error::Done) => break,
1437                Err(err) => {
1438                    // Don't bubble error up, instead keep the worker loop going
1439                    // until quiche reports the connection is
1440                    // closed.
1441                    log::debug!("connection closed due to h3 protocol error"; "error"=>?err);
1442                    return Ok(());
1443                },
1444            };
1445        }
1446        // opportunistically expire pending datagrams based on their timeout
1447        self.hooks.expire_pending_dgrams();
1448        self.process_available_dgrams(qconn)?;
1449        Ok(())
1450    }
1451
1452    /// Write as much data as possible into the [`quiche::h3::Connection`] from
1453    /// all sources. This will attempt to write any queued frames into their
1454    /// respective streams, if writable.
1455    fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {
1456        while let Some(stream_id) = qconn.stream_writable_next() {
1457            self.process_writable_stream(qconn, stream_id)?;
1458        }
1459
1460        // Also optimistically check for any ready streams
1461        // Keep the cooperative-budget-aware recv future here. A channel poll
1462        // can yield and immediately requeue its own stream ID; try_recv would
1463        // ignore the exhausted budget and spin on that notification forever.
1464        while let Some(ready) = self.ready_streams.recv().now_or_never().flatten()
1465        {
1466            self.upstream_ready(qconn, ready)?;
1467        }
1468
1469        Ok(())
1470    }
1471
1472    /// Reports connection-level error metrics and forwards
1473    /// IOWorker errors to the associated [H3Controller].
1474    fn on_conn_close<M: Metrics>(
1475        &mut self, quiche_conn: &mut QuicheConnection, metrics: &M,
1476        work_loop_result: &QuicResult<()>,
1477    ) {
1478        let max_stream_seen = self.max_stream_seen;
1479        metrics
1480            .maximum_writable_streams()
1481            .observe(max_stream_seen as f64);
1482
1483        Self::record_quiche_error(quiche_conn, metrics);
1484
1485        let Err(work_loop_error) = work_loop_result else {
1486            return;
1487        };
1488
1489        let Some(h3_err) = work_loop_error.downcast_ref::<H3ConnectionError>()
1490        else {
1491            // Errors returned by the IoWorker rather than the driver are
1492            // expected and already counted in metrics:
1493            // - `HandshakeError`: with 0-RTT early data, the handshake can
1494            //   still fail (e.g. time out) after the application was started.
1495            // - `quiche::Error`: the connection was closed by quiche, typically
1496            //   because of a peer protocol violation.
1497            if work_loop_error.is::<HandshakeError>() ||
1498                work_loop_error.is::<quiche::Error>()
1499            {
1500                log::debug!("connection closed by IoWorker"; "error" => %work_loop_error);
1501            } else {
1502                log::error!("Found non-H3ConnectionError"; "error" => %work_loop_error);
1503            }
1504            return;
1505        };
1506
1507        if matches!(h3_err, H3ConnectionError::ControllerWentAway) {
1508            // Inform client that we won't (can't) respond anymore
1509            let _ = quiche_conn.close(true, WireErrorCode::NoError as u64, &[]);
1510            return;
1511        }
1512
1513        if let Some(ev) = H3Event::from_error(h3_err) {
1514            let _ = self.h3_event_sender.send(ev.into());
1515            #[expect(clippy::needless_return)]
1516            return; // avoid accidental fallthrough in the future
1517        }
1518    }
1519
1520    /// Wait for incoming data from the [H3Controller]. The next iteration of
1521    /// the I/O loop commences when one of the `select!`ed futures triggers.
1522    #[inline]
1523    async fn wait_for_data(
1524        &mut self, qconn: &mut QuicheConnection,
1525    ) -> QuicResult<()> {
1526        select! {
1527            biased;
1528            Some(ready) = self.ready_streams.recv() => self.upstream_ready(qconn, ready),
1529            Some(dgram) = self.dgram_recv.recv() => self.dgram_ready(qconn, dgram),
1530            Some(cmd) = self.cmd_recv.recv() => H::conn_command(self, qconn, cmd),
1531            r = self.hooks.wait_for_action(qconn), if H::has_wait_action(self) => r,
1532            _ = self.h3_event_sender.closed(), if !self.h3_event_receiver_dropped => {
1533                self.h3_event_receiver_dropped = true;
1534                self.close_if_idle(qconn);
1535                Ok(())
1536            }
1537        }?;
1538
1539        // Make sure controller is not starved, but also not prioritized in the
1540        // biased select. So poll it last, however also perform a try_recv
1541        // each iteration.
1542        if let Ok(cmd) = self.cmd_recv.try_recv() {
1543            H::conn_command(self, qconn, cmd)?;
1544        }
1545
1546        Ok(())
1547    }
1548}
1549
1550impl<H: DriverHooks> Drop for H3Driver<H> {
1551    fn drop(&mut self) {
1552        for stream in self.stream_map.values() {
1553            stream
1554                .audit_stats
1555                .set_recvd_stream_fin(StreamClosureKind::Implicit);
1556        }
1557    }
1558}
1559
1560/// [`H3Command`]s are sent by the [H3Controller] to alter the [H3Driver]'s
1561/// state.
1562///
1563/// Both [ServerH3Driver] and [ClientH3Driver] may extend this enum with
1564/// endpoint-specific variants.
1565#[derive(Debug)]
1566pub enum H3Command {
1567    /// A connection-level command that executes directly on the
1568    /// [`quiche::Connection`].
1569    QuicCmd(QuicCommand),
1570    /// Send a GOAWAY frame to the peer to initiate a graceful connection
1571    /// shutdown.
1572    GoAway,
1573    /// Shuts down a stream in the specified direction(s) and removes it from
1574    /// local state.
1575    ///
1576    /// This removes the stream from local state and sends a `RESET_STREAM`
1577    /// frame (for write direction) and/or a `STOP_SENDING` frame (for read
1578    /// direction) to the peer. See [`quiche::Connection::stream_shutdown`]
1579    /// for details.
1580    ShutdownStream {
1581        stream_id: u64,
1582        shutdown: StreamShutdown,
1583    },
1584}
1585
1586/// Specifies which direction(s) of a stream to shut down.
1587///
1588/// Used with [`H3Controller::shutdown_stream`] and the internal
1589/// `shutdown_stream` function to control whether to send a `STOP_SENDING` frame
1590/// (read direction), and/or a `RESET_STREAM` frame (write direction)
1591///
1592/// Note: Despite its name, "shutdown" here refers to signaling the peer about
1593/// stream termination, not sending a FIN flag. `STOP_SENDING` asks the peer to
1594/// stop sending data, while `RESET_STREAM` abruptly terminates the write side.
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum StreamShutdown {
1597    /// Shut down only the read direction (sends `STOP_SENDING` frame with the
1598    /// given error code).
1599    Read { error_code: u64 },
1600    /// Shut down only the write direction (sends `RESET_STREAM` frame with the
1601    /// given error code).
1602    Write { error_code: u64 },
1603    /// Shut down both directions (sends both `STOP_SENDING` and `RESET_STREAM`
1604    /// frames).
1605    Both {
1606        read_error_code: u64,
1607        write_error_code: u64,
1608    },
1609}
1610
1611/// Sends [`H3Command`]s to an [H3Driver]. The sender is typed and internally
1612/// wraps instances of `T` in the appropriate `H3Command` variant.
1613pub struct RequestSender<C, T> {
1614    sender: UnboundedSender<C>,
1615    // Required to work around dangling type parameter
1616    _r: PhantomData<fn() -> T>,
1617}
1618
1619impl<C, T: Into<C>> RequestSender<C, T> {
1620    /// Send a request to the [H3Driver]. This can only fail if the driver is
1621    /// gone.
1622    #[inline(always)]
1623    pub fn send(&self, v: T) -> Result<(), mpsc::error::SendError<C>> {
1624        self.sender.send(v.into())
1625    }
1626}
1627
1628impl<C, T> Clone for RequestSender<C, T> {
1629    fn clone(&self) -> Self {
1630        Self {
1631            sender: self.sender.clone(),
1632            _r: Default::default(),
1633        }
1634    }
1635}
1636
1637/// Interface to communicate with a paired [H3Driver].
1638///
1639/// An [H3Controller] receives [`H3Event`]s from its driver, which must be
1640/// consumed by the application built on top of the driver to react to incoming
1641/// events. The controller also allows the application to send ad-hoc
1642/// [`H3Command`]s to the driver, which will be processed when the driver waits
1643/// for incoming data.
1644pub struct H3Controller<H: DriverHooks> {
1645    /// Sends [`H3Command`]s to the [H3Driver], like [`QuicCommand`]s or
1646    /// outbound HTTP requests.
1647    cmd_sender: UnboundedSender<H::Command>,
1648    /// Receives [`H3Event`]s from the [H3Driver]. Can be extracted and
1649    /// used independently of the [H3Controller].
1650    h3_event_recv: Option<UnboundedReceiver<H::Event>>,
1651}
1652
1653impl<H: DriverHooks> H3Controller<H> {
1654    /// Gets a mut reference to the [`H3Event`] receiver for the paired
1655    /// [H3Driver].
1656    pub fn event_receiver_mut(&mut self) -> &mut UnboundedReceiver<H::Event> {
1657        self.h3_event_recv
1658            .as_mut()
1659            .expect("No event receiver on H3Controller")
1660    }
1661
1662    /// Takes the [`H3Event`] receiver for the paired [H3Driver].
1663    pub fn take_event_receiver(&mut self) -> UnboundedReceiver<H::Event> {
1664        self.h3_event_recv
1665            .take()
1666            .expect("No event receiver on H3Controller")
1667    }
1668
1669    /// Creates a [`QuicCommand`] sender for the paired [H3Driver].
1670    pub fn cmd_sender(&self) -> RequestSender<H::Command, QuicCommand> {
1671        RequestSender {
1672            sender: self.cmd_sender.clone(),
1673            _r: Default::default(),
1674        }
1675    }
1676
1677    /// Sends a GOAWAY frame to initiate a graceful connection shutdown.
1678    pub fn send_goaway(&self) {
1679        let _ = self.cmd_sender.send(H3Command::GoAway.into());
1680    }
1681
1682    /// Creates an [`H3Command`] sender for the paired [H3Driver].
1683    pub fn h3_cmd_sender(&self) -> RequestSender<H::Command, H3Command> {
1684        RequestSender {
1685            sender: self.cmd_sender.clone(),
1686            _r: Default::default(),
1687        }
1688    }
1689
1690    /// Shuts down a stream in the specified direction(s) and removes it from
1691    /// local state.
1692    ///
1693    /// This removes the stream from local state and sends a `RESET_STREAM`
1694    /// frame (for write direction) and/or a `STOP_SENDING` frame (for read
1695    /// direction) to the peer, depending on the [`StreamShutdown`] variant.
1696    pub fn shutdown_stream(&self, stream_id: u64, shutdown: StreamShutdown) {
1697        let _ = self.cmd_sender.send(
1698            H3Command::ShutdownStream {
1699                stream_id,
1700                shutdown,
1701            }
1702            .into(),
1703        );
1704    }
1705}