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