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 ctx = self
677 .stream_map
678 .get_mut(&stream_id)
679 .filter(|c| !c.fin_or_reset_recv);
680 let Some(ctx) = ctx else {
681 // Stream is already finished, nothing to do
682 return Ok(());
683 };
684
685 ctx.fin_or_reset_recv = true;
686 ctx.audit_stats
687 .set_recvd_stream_fin(StreamClosureKind::Explicit);
688
689 // It's important to send this H3Event before process_h3_data so that
690 // a server can (potentially) generate the control response before the
691 // corresponding receiver drops.
692 let event = H3Event::BodyBytesReceived {
693 stream_id,
694 num_bytes: 0,
695 fin: true,
696 };
697 let _ = self.h3_event_sender.send(event.into());
698
699 // Communicate fin to upstream. Since `ctx.fin_recv` is true now,
700 // there can't be a recursive loop.
701 self.process_h3_data(qconn, stream_id)
702 }
703
704 /// Processes a single [`quiche::h3::Event`] received from the underlying
705 /// [`quiche::h3::Connection`]. Some events are dispatched to helper
706 /// methods.
707 fn process_read_event(
708 &mut self, qconn: &mut QuicheConnection, stream_id: u64, event: h3::Event,
709 ) -> H3ConnectionResult<()> {
710 self.forward_settings()?;
711
712 match event {
713 // Requests/responses are exclusively handled by hooks.
714 h3::Event::Headers { list, more_frames } =>
715 H::headers_received(self, qconn, InboundHeaders {
716 stream_id,
717 headers: list,
718 has_body: more_frames,
719 }),
720
721 h3::Event::Data => self.process_h3_data(qconn, stream_id),
722 h3::Event::Finished => self.process_h3_fin(qconn, stream_id),
723
724 h3::Event::Reset(code) => {
725 if let Some(ctx) = self.stream_map.get_mut(&stream_id) {
726 ctx.handle_recvd_reset(code);
727 // Close the channel if this stream is waiting. Otherwise,
728 // `handle_recvd_reset()` already closed it.
729 for pending in self.waiting_streams.iter_mut() {
730 match pending {
731 WaitForStream::Upstream(
732 WaitForUpstreamCapacity {
733 stream_id: id,
734 chan: Some(chan),
735 },
736 ) if stream_id == *id => {
737 chan.close();
738 },
739 _ => {},
740 }
741 }
742
743 self.h3_event_sender
744 .send(H3Event::ResetStream { stream_id }.into())
745 .map_err(|_| H3ConnectionError::ControllerWentAway)?;
746 if ctx.both_directions_done() {
747 return self.cleanup_stream(qconn, stream_id);
748 }
749 }
750
751 // TODO: if we don't have the stream in our map: should we
752 // send the H3Event::ResetStream?
753 Ok(())
754 },
755
756 h3::Event::PriorityUpdate => Ok(()),
757 h3::Event::GoAway => {
758 self.h3_event_sender
759 .send(H3Event::GoAway { id: stream_id }.into())
760 .map_err(|_| H3ConnectionError::ControllerWentAway)?;
761 Ok(())
762 },
763 }
764 }
765
766 /// The SETTINGS frame can be received at any point, so we
767 /// need to check `peer_settings_raw` to decide if we've received it.
768 ///
769 /// Settings should only be sent once, so we generate a single event
770 /// when `peer_settings_raw` transitions from None to Some.
771 fn forward_settings(&mut self) -> H3ConnectionResult<()> {
772 if self.settings_received_and_forwarded {
773 return Ok(());
774 }
775
776 // capture the peer settings and forward it
777 if let Some(settings) = self.conn_mut()?.peer_settings_raw() {
778 let incoming_settings = H3Event::IncomingSettings {
779 settings: settings.to_vec(),
780 };
781
782 self.h3_event_sender
783 .send(incoming_settings.into())
784 .map_err(|_| H3ConnectionError::ControllerWentAway)?;
785
786 self.settings_received_and_forwarded = true;
787 }
788 Ok(())
789 }
790
791 /// Send an individual frame to the underlying [`quiche::h3::Connection`] to
792 /// be flushed at a later time.
793 ///
794 /// `Self::process_writes` will iterate over all writable streams and call
795 /// this method in a loop for each stream to send all writable packets.
796 fn process_write_frame(
797 conn: &mut h3::Connection, qconn: &mut QuicheConnection,
798 ctx: &mut StreamCtx,
799 ) -> h3::Result<()> {
800 let Some(frame) = &mut ctx.queued_frame else {
801 return Ok(());
802 };
803
804 let audit_stats = &ctx.audit_stats;
805 let stream_id = audit_stats.stream_id();
806
807 match frame {
808 OutboundFrame::Headers(headers, priority) => {
809 let prio = priority.as_ref().unwrap_or(&DEFAULT_PRIO);
810
811 let res = if ctx.initial_headers_sent {
812 // Initial headers were already sent, send additional
813 // headers now.
814 conn.send_additional_headers_with_priority(
815 qconn, stream_id, headers, prio, false, false,
816 )
817 } else {
818 // Send initial headers.
819 conn.send_response_with_priority(
820 qconn, stream_id, headers, prio, false,
821 )
822 .inspect(|_| ctx.initial_headers_sent = true)
823 };
824
825 if let Err(h3::Error::StreamBlocked) = res {
826 ctx.first_full_headers_flush_fail_time
827 .get_or_insert(Instant::now());
828 }
829
830 if res.is_ok() {
831 if let Some(first) =
832 ctx.first_full_headers_flush_fail_time.take()
833 {
834 ctx.audit_stats.add_header_flush_duration(
835 Instant::now().duration_since(first),
836 );
837 }
838 }
839
840 res
841 },
842
843 OutboundFrame::Body(body, fin) => {
844 let len = body.len();
845 if len == 0 && !*fin {
846 // quiche doesn't allow sending an empty body when the fin
847 // flag is not set
848 return Ok(());
849 }
850 if *fin {
851 // If this is the last body frame, drop the receiver in the
852 // stream map to signal that we shouldn't receive any more
853 // frames. NOTE: we can't use `mpsc::Receiver::close()`
854 // due to an inconsistency in how tokio handles reading
855 // from a closed mpsc channel https://github.com/tokio-rs/tokio/issues/7631
856 ctx.recv = None;
857 }
858 let n = conn.send_body_zc(qconn, stream_id, body, *fin)?;
859
860 audit_stats.add_downstream_bytes_sent(n as _);
861 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 Ok(())
876 }
877 },
878
879 OutboundFrame::Trailers(headers, priority) => {
880 let prio = priority.as_ref().unwrap_or(&DEFAULT_PRIO);
881
882 // trailers always set fin=true
883 let res = conn.send_additional_headers_with_priority(
884 qconn, stream_id, headers, prio, true, true,
885 );
886
887 if res.is_ok() {
888 Self::on_fin_sent(ctx)?;
889 }
890 res
891 },
892
893 OutboundFrame::PeerStreamError => Err(h3::Error::MessageError),
894
895 OutboundFrame::FlowShutdown { .. } => {
896 unreachable!("Only flows send shutdowns")
897 },
898
899 OutboundFrame::Datagram(..) => {
900 unreachable!("Only flows send datagrams")
901 },
902 }
903 }
904
905 fn on_fin_sent(ctx: &mut StreamCtx) -> h3::Result<()> {
906 ctx.recv = None;
907 ctx.fin_or_reset_sent = true;
908 ctx.audit_stats
909 .set_sent_stream_fin(StreamClosureKind::Explicit);
910 if ctx.fin_or_reset_recv {
911 // Return a TransportError to trigger stream cleanup
912 // instead of h3::Error::Done
913 Err(h3::Error::TransportError(quiche::Error::Done))
914 } else {
915 Ok(())
916 }
917 }
918
919 /// Resumes reads or writes to the connection when a stream channel becomes
920 /// unblocked.
921 ///
922 /// If we were waiting for more data from a channel, we resume writing to
923 /// the connection. Otherwise, we were blocked on channel capacity and
924 /// continue reading from the connection. `Upstream` in this context is
925 /// the consumer of the stream.
926 fn upstream_ready(
927 &mut self, qconn: &mut QuicheConnection, ready: StreamReady,
928 ) -> H3ConnectionResult<()> {
929 match ready {
930 StreamReady::Downstream(r) => self.upstream_read_ready(qconn, r),
931 StreamReady::Upstream(r) => self.upstream_write_ready(qconn, r),
932 }
933 }
934
935 fn upstream_read_ready(
936 &mut self, qconn: &mut QuicheConnection,
937 read_ready: ReceivedDownstreamData,
938 ) -> H3ConnectionResult<()> {
939 let ReceivedDownstreamData {
940 stream_id,
941 chan,
942 data,
943 } = read_ready;
944
945 match self.stream_map.get_mut(&stream_id) {
946 None => Ok(()),
947 Some(stream) => {
948 stream.recv = Some(chan);
949 stream.queued_frame = data;
950 self.process_writable_stream(qconn, stream_id)
951 },
952 }
953 }
954
955 fn upstream_write_ready(
956 &mut self, qconn: &mut QuicheConnection,
957 write_ready: HaveUpstreamCapacity,
958 ) -> H3ConnectionResult<()> {
959 let HaveUpstreamCapacity {
960 stream_id,
961 mut chan,
962 } = write_ready;
963
964 match self.stream_map.get_mut(&stream_id) {
965 None => Ok(()),
966 Some(stream) => {
967 // Release the associated permit before retaining the channel.
968 chan.abort_send();
969 stream.send = Some(chan);
970 self.process_h3_data(qconn, stream_id)
971 },
972 }
973 }
974
975 /// Processes all queued outbound datagrams from the `dgram_recv` channel.
976 fn dgram_ready(
977 &mut self, qconn: &mut QuicheConnection, frame: OutboundFrame,
978 ) -> H3ConnectionResult<()> {
979 let mut frame = Ok(frame);
980
981 loop {
982 match frame {
983 Ok(OutboundFrame::Datagram(dgram, flow_id)) => {
984 // Drop datagrams if there is no capacity
985 let _ = datagram::send_h3_dgram(qconn, flow_id, dgram);
986 },
987 Ok(OutboundFrame::FlowShutdown { flow_id, stream_id }) => {
988 self.shutdown_stream(
989 qconn,
990 stream_id,
991 StreamShutdown::Both {
992 read_error_code: WireErrorCode::NoError as u64,
993 write_error_code: WireErrorCode::NoError as u64,
994 },
995 )?;
996 self.flow_map.remove(&flow_id);
997 self.close_if_idle(qconn);
998 break;
999 },
1000 Ok(_) => unreachable!("Flows can't send frame of other types"),
1001 Err(TryRecvError::Empty) => break,
1002 Err(TryRecvError::Disconnected) =>
1003 return Err(H3ConnectionError::ControllerWentAway),
1004 }
1005
1006 frame = self.dgram_recv.try_recv();
1007 }
1008
1009 Ok(())
1010 }
1011
1012 /// Return a mutable reference to the driver's HTTP/3 connection.
1013 ///
1014 /// If the connection doesn't exist yet, this function returns
1015 /// a `Self::connection_not_present()` error.
1016 fn conn_mut(&mut self) -> H3ConnectionResult<&mut h3::Connection> {
1017 self.conn.as_mut().ok_or(Self::connection_not_present())
1018 }
1019
1020 /// Alias for [`quiche::Error::TlsFail`], which is used in the case where
1021 /// this driver doesn't have an established HTTP/3 connection attached
1022 /// to it yet.
1023 const fn connection_not_present() -> H3ConnectionError {
1024 H3ConnectionError::H3(h3::Error::TransportError(quiche::Error::TlsFail))
1025 }
1026
1027 /// Cleans up internal state for the indicated HTTP/3 stream.
1028 ///
1029 /// This function removes the stream from the stream map, closes any pending
1030 /// futures, removes associated DATAGRAM flows, and sends a
1031 /// [`H3Event::StreamClosed`] event (for servers).
1032 fn cleanup_stream(
1033 &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1034 ) -> H3ConnectionResult<()> {
1035 let Some(stream_ctx) = self.stream_map.remove(&stream_id) else {
1036 return Ok(());
1037 };
1038
1039 // Find if the stream also has any pending futures associated with it
1040 for pending in self.waiting_streams.iter_mut() {
1041 match pending {
1042 WaitForStream::Downstream(WaitForDownstreamData {
1043 stream_id: id,
1044 chan: Some(chan),
1045 }) if stream_id == *id => {
1046 chan.close();
1047 },
1048 WaitForStream::Upstream(WaitForUpstreamCapacity {
1049 stream_id: id,
1050 chan: Some(chan),
1051 }) if stream_id == *id => {
1052 chan.close();
1053 },
1054 _ => {},
1055 }
1056 }
1057
1058 // Close any DATAGRAM-proxying channels associated with the stream.
1059 if let Some(mapped_flow_id) = stream_ctx.associated_dgram_flow_id {
1060 self.flow_map.remove(&mapped_flow_id);
1061 }
1062
1063 if qconn.is_server() {
1064 // Signal the server to remove the stream from its map.
1065 let _ = self
1066 .h3_event_sender
1067 .send(H3Event::StreamClosed { stream_id }.into());
1068 }
1069
1070 self.close_if_idle(qconn);
1071
1072 Ok(())
1073 }
1074
1075 /// Handles connection cleanup once no streams or flows remain.
1076 ///
1077 /// Releases the body receive buffer (it is reallocated lazily on the next
1078 /// body read; body bytes only flow on active streams, so an empty stream
1079 /// map means it is unused) and closes the connection with `NoError` if the
1080 /// H3 event receiver has been dropped.
1081 fn close_if_idle(&mut self, qconn: &mut QuicheConnection) {
1082 if self.stream_map.is_empty() && self.flow_map.is_empty() {
1083 self.body_recv_buf = None;
1084
1085 if self.h3_event_receiver_dropped {
1086 let _ = qconn.close(
1087 true,
1088 quiche::h3::WireErrorCode::NoError as u64,
1089 &[],
1090 );
1091 }
1092 }
1093 }
1094
1095 /// Shuts down the indicated HTTP/3 stream by sending frames and cleaning
1096 /// up then cleans up internal state by calling
1097 /// [`Self::cleanup_stream`].
1098 fn shutdown_stream(
1099 &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1100 shutdown: StreamShutdown,
1101 ) -> H3ConnectionResult<()> {
1102 let Some(stream_ctx) = self.stream_map.get(&stream_id) else {
1103 return Ok(());
1104 };
1105
1106 let audit_stats = &stream_ctx.audit_stats;
1107
1108 match shutdown {
1109 StreamShutdown::Read { error_code } => {
1110 audit_stats.set_sent_stop_sending_error_code(error_code as _);
1111 let _ = qconn.stream_shutdown(
1112 stream_id,
1113 quiche::Shutdown::Read,
1114 error_code,
1115 );
1116 },
1117 StreamShutdown::Write { error_code } => {
1118 audit_stats.set_sent_reset_stream_error_code(error_code as _);
1119 let _ = qconn.stream_shutdown(
1120 stream_id,
1121 quiche::Shutdown::Write,
1122 error_code,
1123 );
1124 },
1125 StreamShutdown::Both {
1126 read_error_code,
1127 write_error_code,
1128 } => {
1129 audit_stats
1130 .set_sent_stop_sending_error_code(read_error_code as _);
1131 let _ = qconn.stream_shutdown(
1132 stream_id,
1133 quiche::Shutdown::Read,
1134 read_error_code,
1135 );
1136 audit_stats
1137 .set_sent_reset_stream_error_code(write_error_code as _);
1138 let _ = qconn.stream_shutdown(
1139 stream_id,
1140 quiche::Shutdown::Write,
1141 write_error_code,
1142 );
1143 },
1144 }
1145
1146 self.cleanup_stream(qconn, stream_id)
1147 }
1148
1149 /// Handles a regular [`H3Command`]. May be called internally by
1150 /// [DriverHooks] for non-endpoint-specific [`H3Command`]s.
1151 fn handle_core_command(
1152 &mut self, qconn: &mut QuicheConnection, cmd: H3Command,
1153 ) -> H3ConnectionResult<()> {
1154 match cmd {
1155 H3Command::QuicCmd(cmd) => cmd.execute(qconn),
1156 H3Command::GoAway => {
1157 let max_id = self.max_stream_seen;
1158 self.conn_mut()
1159 .expect("connection should be established")
1160 .send_goaway(qconn, max_id)?;
1161 },
1162 H3Command::ShutdownStream {
1163 stream_id,
1164 shutdown,
1165 } => {
1166 self.shutdown_stream(qconn, stream_id, shutdown)?;
1167 },
1168 }
1169 Ok(())
1170 }
1171}
1172
1173impl<H: DriverHooks> H3Driver<H> {
1174 /// Reads all buffered datagrams out of `qconn` and distributes them to
1175 /// their flow channels.
1176 fn process_available_dgrams(
1177 &mut self, qconn: &mut QuicheConnection,
1178 ) -> H3ConnectionResult<()> {
1179 loop {
1180 match datagram::receive_h3_dgram(qconn) {
1181 Ok((flow_id, dgram))
1182 if !qconn.is_server() ||
1183 self.hooks.extended_connect_enabled() =>
1184 {
1185 self.get_or_insert_flow(flow_id)?.send_best_effort(dgram);
1186 },
1187 Ok(_) => {},
1188 Err(quiche::Error::Done) => return Ok(()),
1189 Err(err) => return Err(H3ConnectionError::from(err)),
1190 }
1191 }
1192 }
1193
1194 /// Flushes any queued-up frames for `stream_id` into `qconn` until either
1195 /// there is no more capacity in `qconn` or no more frames to send.
1196 fn process_writable_stream(
1197 &mut self, qconn: &mut QuicheConnection, stream_id: u64,
1198 ) -> H3ConnectionResult<()> {
1199 // Split self borrow between conn and stream_map
1200 let conn = self.conn.as_mut().ok_or(Self::connection_not_present())?;
1201 let Some(ctx) = self.stream_map.get_mut(&stream_id) else {
1202 return Ok(()); // Unknown stream_id
1203 };
1204
1205 loop {
1206 // Process each writable frame, queue the next frame for processing
1207 // and shut down any errored streams.
1208 match Self::process_write_frame(conn, qconn, ctx) {
1209 Ok(()) => ctx.queued_frame = None,
1210 Err(h3::Error::StreamBlocked | h3::Error::Done) => break,
1211 Err(h3::Error::MessageError) => {
1212 return self.shutdown_stream(
1213 qconn,
1214 stream_id,
1215 StreamShutdown::Both {
1216 read_error_code: WireErrorCode::MessageError as u64,
1217 write_error_code: WireErrorCode::MessageError as u64,
1218 },
1219 );
1220 },
1221 Err(h3::Error::TransportError(quiche::Error::StreamStopped(
1222 e,
1223 ))) => {
1224 ctx.handle_recvd_stop_sending(e);
1225 if ctx.both_directions_done() {
1226 return self.cleanup_stream(qconn, stream_id);
1227 } else {
1228 return Ok(());
1229 }
1230 },
1231 Err(h3::Error::TransportError(
1232 quiche::Error::InvalidStreamState(stream),
1233 )) => {
1234 return self.cleanup_stream(qconn, stream);
1235 },
1236 Err(_) => {
1237 return self.cleanup_stream(qconn, stream_id);
1238 },
1239 }
1240
1241 let Some(recv) = ctx.recv.as_mut() else {
1242 // This stream is already waiting for data or we wrote a fin and
1243 // closed the channel.
1244 debug_assert!(
1245 ctx.queued_frame.is_none(),
1246 "We MUST NOT have a queued frame if we are already waiting on
1247 more data from the channel"
1248 );
1249 return Ok(());
1250 };
1251
1252 // Attempt to queue the next frame for processing. The corresponding
1253 // sender is created at the same time as the `StreamCtx`
1254 // and ultimately ends up in an `H3Body`. The body then
1255 // determines which frames to send to the peer via
1256 // this processing loop.
1257 match recv.try_recv() {
1258 Ok(frame) => ctx.queued_frame = Some(frame),
1259 Err(TryRecvError::Disconnected) => {
1260 if !ctx.fin_or_reset_sent &&
1261 ctx.associated_dgram_flow_id.is_none()
1262 // The channel might be closed if the stream was used to
1263 // initiate a datagram exchange.
1264 // TODO: ideally, the application would still shut down the
1265 // stream properly. Once applications code
1266 // is fixed, we can remove this check.
1267 {
1268 // No FIN was written before closure. Send RESET_STREAM
1269 // because no more data will follow.
1270 let err = h3::WireErrorCode::RequestCancelled as u64;
1271 let _ = qconn.stream_shutdown(
1272 stream_id,
1273 quiche::Shutdown::Write,
1274 err,
1275 );
1276 ctx.handle_sent_reset(err);
1277 if ctx.both_directions_done() {
1278 return self.cleanup_stream(qconn, stream_id);
1279 }
1280 }
1281 break;
1282 },
1283 Err(TryRecvError::Empty) => {
1284 self.waiting_streams.push(ctx.wait_for_recv(stream_id));
1285 break;
1286 },
1287 }
1288 }
1289
1290 Ok(())
1291 }
1292
1293 /// Tests `qconn` for either a local or peer error and increments
1294 /// the associated HTTP/3 or QUIC error counter.
1295 fn record_quiche_error(qconn: &mut QuicheConnection, metrics: &impl Metrics) {
1296 // split metrics between local/peer and QUIC/HTTP/3 level errors
1297 if let Some(err) = qconn.local_error() {
1298 if err.is_app {
1299 metrics.local_h3_conn_close_error_count(err.error_code.into())
1300 } else {
1301 metrics.local_quic_conn_close_error_count(err.error_code.into())
1302 }
1303 .inc();
1304 } else if let Some(err) = qconn.peer_error() {
1305 if err.is_app {
1306 metrics.peer_h3_conn_close_error_count(err.error_code.into())
1307 } else {
1308 metrics.peer_quic_conn_close_error_count(err.error_code.into())
1309 }
1310 .inc();
1311 }
1312 }
1313}
1314
1315impl<H: DriverHooks> ApplicationOverQuic for H3Driver<H> {
1316 fn on_conn_established(
1317 &mut self, quiche_conn: &mut QuicheConnection,
1318 handshake_info: &HandshakeInfo,
1319 ) -> QuicResult<()> {
1320 let conn = h3::Connection::with_transport(quiche_conn, &self.h3_config)?;
1321 self.conn = Some(conn);
1322
1323 H::conn_established(self, quiche_conn, handshake_info)?;
1324 Ok(())
1325 }
1326
1327 #[inline]
1328 fn should_act(&self) -> bool {
1329 self.conn.is_some()
1330 }
1331
1332 /// Poll the underlying [`quiche::h3::Connection`] for
1333 /// [`quiche::h3::Event`]s and DATAGRAMs, delegating processing to
1334 /// `Self::process_read_event`.
1335 ///
1336 /// If a DATAGRAM is found, it is sent to the receiver on its channel.
1337 fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {
1338 loop {
1339 match self.conn_mut()?.poll(qconn) {
1340 Ok((stream_id, event)) =>
1341 self.process_read_event(qconn, stream_id, event)?,
1342 Err(h3::Error::Done) => break,
1343 Err(err) => {
1344 // Don't bubble error up, instead keep the worker loop going
1345 // until quiche reports the connection is
1346 // closed.
1347 log::debug!("connection closed due to h3 protocol error"; "error"=>?err);
1348 return Ok(());
1349 },
1350 };
1351 }
1352
1353 self.process_available_dgrams(qconn)?;
1354 Ok(())
1355 }
1356
1357 /// Write as much data as possible into the [`quiche::h3::Connection`] from
1358 /// all sources. This will attempt to write any queued frames into their
1359 /// respective streams, if writable.
1360 fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {
1361 while let Some(stream_id) = qconn.stream_writable_next() {
1362 self.process_writable_stream(qconn, stream_id)?;
1363 }
1364
1365 // Also optimistically check for any ready streams
1366 while let Some(Some(ready)) = self.waiting_streams.next().now_or_never() {
1367 self.upstream_ready(qconn, ready)?;
1368 }
1369
1370 Ok(())
1371 }
1372
1373 /// Reports connection-level error metrics and forwards
1374 /// IOWorker errors to the associated [H3Controller].
1375 fn on_conn_close<M: Metrics>(
1376 &mut self, quiche_conn: &mut QuicheConnection, metrics: &M,
1377 work_loop_result: &QuicResult<()>,
1378 ) {
1379 let max_stream_seen = self.max_stream_seen;
1380 metrics
1381 .maximum_writable_streams()
1382 .observe(max_stream_seen as f64);
1383
1384 Self::record_quiche_error(quiche_conn, metrics);
1385
1386 let Err(work_loop_error) = work_loop_result else {
1387 return;
1388 };
1389
1390 let Some(h3_err) = work_loop_error.downcast_ref::<H3ConnectionError>()
1391 else {
1392 log::error!("Found non-H3ConnectionError"; "error" => %work_loop_error);
1393 return;
1394 };
1395
1396 if matches!(h3_err, H3ConnectionError::ControllerWentAway) {
1397 // Inform client that we won't (can't) respond anymore
1398 let _ = quiche_conn.close(true, WireErrorCode::NoError as u64, &[]);
1399 return;
1400 }
1401
1402 if let Some(ev) = H3Event::from_error(h3_err) {
1403 let _ = self.h3_event_sender.send(ev.into());
1404 #[expect(clippy::needless_return)]
1405 return; // avoid accidental fallthrough in the future
1406 }
1407 }
1408
1409 /// Wait for incoming data from the [H3Controller]. The next iteration of
1410 /// the I/O loop commences when one of the `select!`ed futures triggers.
1411 #[inline]
1412 async fn wait_for_data(
1413 &mut self, qconn: &mut QuicheConnection,
1414 ) -> QuicResult<()> {
1415 select! {
1416 biased;
1417 Some(ready) = self.waiting_streams.next() => self.upstream_ready(qconn, ready),
1418 Some(dgram) = self.dgram_recv.recv() => self.dgram_ready(qconn, dgram),
1419 Some(cmd) = self.cmd_recv.recv() => H::conn_command(self, qconn, cmd),
1420 r = self.hooks.wait_for_action(qconn), if H::has_wait_action(self) => r,
1421 _ = self.h3_event_sender.closed(), if !self.h3_event_receiver_dropped => {
1422 self.h3_event_receiver_dropped = true;
1423 self.close_if_idle(qconn);
1424 Ok(())
1425 }
1426 }?;
1427
1428 // Make sure controller is not starved, but also not prioritized in the
1429 // biased select. So poll it last, however also perform a try_recv
1430 // each iteration.
1431 if let Ok(cmd) = self.cmd_recv.try_recv() {
1432 H::conn_command(self, qconn, cmd)?;
1433 }
1434
1435 Ok(())
1436 }
1437}
1438
1439impl<H: DriverHooks> Drop for H3Driver<H> {
1440 fn drop(&mut self) {
1441 for stream in self.stream_map.values() {
1442 stream
1443 .audit_stats
1444 .set_recvd_stream_fin(StreamClosureKind::Implicit);
1445 }
1446 }
1447}
1448
1449/// [`H3Command`]s are sent by the [H3Controller] to alter the [H3Driver]'s
1450/// state.
1451///
1452/// Both [ServerH3Driver] and [ClientH3Driver] may extend this enum with
1453/// endpoint-specific variants.
1454#[derive(Debug)]
1455pub enum H3Command {
1456 /// A connection-level command that executes directly on the
1457 /// [`quiche::Connection`].
1458 QuicCmd(QuicCommand),
1459 /// Send a GOAWAY frame to the peer to initiate a graceful connection
1460 /// shutdown.
1461 GoAway,
1462 /// Shuts down a stream in the specified direction(s) and removes it from
1463 /// local state.
1464 ///
1465 /// This removes the stream from local state and sends a `RESET_STREAM`
1466 /// frame (for write direction) and/or a `STOP_SENDING` frame (for read
1467 /// direction) to the peer. See [`quiche::Connection::stream_shutdown`]
1468 /// for details.
1469 ShutdownStream {
1470 stream_id: u64,
1471 shutdown: StreamShutdown,
1472 },
1473}
1474
1475/// Specifies which direction(s) of a stream to shut down.
1476///
1477/// Used with [`H3Controller::shutdown_stream`] and the internal
1478/// `shutdown_stream` function to control whether to send a `STOP_SENDING` frame
1479/// (read direction), and/or a `RESET_STREAM` frame (write direction)
1480///
1481/// Note: Despite its name, "shutdown" here refers to signaling the peer about
1482/// stream termination, not sending a FIN flag. `STOP_SENDING` asks the peer to
1483/// stop sending data, while `RESET_STREAM` abruptly terminates the write side.
1484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1485pub enum StreamShutdown {
1486 /// Shut down only the read direction (sends `STOP_SENDING` frame with the
1487 /// given error code).
1488 Read { error_code: u64 },
1489 /// Shut down only the write direction (sends `RESET_STREAM` frame with the
1490 /// given error code).
1491 Write { error_code: u64 },
1492 /// Shut down both directions (sends both `STOP_SENDING` and `RESET_STREAM`
1493 /// frames).
1494 Both {
1495 read_error_code: u64,
1496 write_error_code: u64,
1497 },
1498}
1499
1500/// Sends [`H3Command`]s to an [H3Driver]. The sender is typed and internally
1501/// wraps instances of `T` in the appropriate `H3Command` variant.
1502pub struct RequestSender<C, T> {
1503 sender: UnboundedSender<C>,
1504 // Required to work around dangling type parameter
1505 _r: PhantomData<fn() -> T>,
1506}
1507
1508impl<C, T: Into<C>> RequestSender<C, T> {
1509 /// Send a request to the [H3Driver]. This can only fail if the driver is
1510 /// gone.
1511 #[inline(always)]
1512 pub fn send(&self, v: T) -> Result<(), mpsc::error::SendError<C>> {
1513 self.sender.send(v.into())
1514 }
1515}
1516
1517impl<C, T> Clone for RequestSender<C, T> {
1518 fn clone(&self) -> Self {
1519 Self {
1520 sender: self.sender.clone(),
1521 _r: Default::default(),
1522 }
1523 }
1524}
1525
1526/// Interface to communicate with a paired [H3Driver].
1527///
1528/// An [H3Controller] receives [`H3Event`]s from its driver, which must be
1529/// consumed by the application built on top of the driver to react to incoming
1530/// events. The controller also allows the application to send ad-hoc
1531/// [`H3Command`]s to the driver, which will be processed when the driver waits
1532/// for incoming data.
1533pub struct H3Controller<H: DriverHooks> {
1534 /// Sends [`H3Command`]s to the [H3Driver], like [`QuicCommand`]s or
1535 /// outbound HTTP requests.
1536 cmd_sender: UnboundedSender<H::Command>,
1537 /// Receives [`H3Event`]s from the [H3Driver]. Can be extracted and
1538 /// used independently of the [H3Controller].
1539 h3_event_recv: Option<UnboundedReceiver<H::Event>>,
1540}
1541
1542impl<H: DriverHooks> H3Controller<H> {
1543 /// Gets a mut reference to the [`H3Event`] receiver for the paired
1544 /// [H3Driver].
1545 pub fn event_receiver_mut(&mut self) -> &mut UnboundedReceiver<H::Event> {
1546 self.h3_event_recv
1547 .as_mut()
1548 .expect("No event receiver on H3Controller")
1549 }
1550
1551 /// Takes the [`H3Event`] receiver for the paired [H3Driver].
1552 pub fn take_event_receiver(&mut self) -> UnboundedReceiver<H::Event> {
1553 self.h3_event_recv
1554 .take()
1555 .expect("No event receiver on H3Controller")
1556 }
1557
1558 /// Creates a [`QuicCommand`] sender for the paired [H3Driver].
1559 pub fn cmd_sender(&self) -> RequestSender<H::Command, QuicCommand> {
1560 RequestSender {
1561 sender: self.cmd_sender.clone(),
1562 _r: Default::default(),
1563 }
1564 }
1565
1566 /// Sends a GOAWAY frame to initiate a graceful connection shutdown.
1567 pub fn send_goaway(&self) {
1568 let _ = self.cmd_sender.send(H3Command::GoAway.into());
1569 }
1570
1571 /// Creates an [`H3Command`] sender for the paired [H3Driver].
1572 pub fn h3_cmd_sender(&self) -> RequestSender<H::Command, H3Command> {
1573 RequestSender {
1574 sender: self.cmd_sender.clone(),
1575 _r: Default::default(),
1576 }
1577 }
1578
1579 /// Shuts down a stream in the specified direction(s) and removes it from
1580 /// local state.
1581 ///
1582 /// This removes the stream from local state and sends a `RESET_STREAM`
1583 /// frame (for write direction) and/or a `STOP_SENDING` frame (for read
1584 /// direction) to the peer, depending on the [`StreamShutdown`] variant.
1585 pub fn shutdown_stream(&self, stream_id: u64, shutdown: StreamShutdown) {
1586 let _ = self.cmd_sender.send(
1587 H3Command::ShutdownStream {
1588 stream_id,
1589 shutdown,
1590 }
1591 .into(),
1592 );
1593 }
1594}