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