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