Skip to main content

trillium_http/h3/
connection.rs

1mod peer_settings_wait;
2
3use super::{
4    H3Error,
5    frame::{Frame, FrameDecodeError, UniStreamType},
6    quic_varint::{self, QuicVarIntError},
7    settings::H3Settings,
8};
9use crate::{
10    Buffer, Conn, HttpContext, KnownHeaderName, Priority,
11    conn::H3FirstFrame,
12    h3::{H3ErrorCode, MAX_BUFFER_SIZE},
13    headers::qpack::{DecoderDynamicTable, EncoderDynamicTable, FieldSection},
14};
15use event_listener::Event;
16use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
17use std::{
18    future::{Future, IntoFuture},
19    io::{self, ErrorKind},
20    pin::Pin,
21    sync::{
22        Arc, OnceLock,
23        atomic::{AtomicBool, AtomicU64, Ordering},
24    },
25    task::{Context, Poll},
26};
27use swansong::{ShutdownCompletion, Swansong};
28
29/// The result of processing an HTTP/3 bidirectional stream.
30#[derive(Debug)]
31#[allow(
32    clippy::large_enum_variant,
33    reason = "Request is the hot path; boxing it would add an allocation per request"
34)]
35pub enum H3StreamResult<Transport> {
36    /// The stream carried a normal HTTP/3 request.
37    Request(Conn<Transport>),
38
39    /// The stream carries a WebTransport bidirectional data stream. The `session_id` identifies
40    /// the associated WebTransport session.
41    WebTransport {
42        /// The WebTransport session ID (stream ID of the CONNECT request).
43        session_id: u64,
44        /// The underlying transport, ready for application data.
45        transport: Transport,
46        /// Any bytes buffered after the session ID during stream negotiation.
47        buffer: Buffer,
48    },
49}
50
51/// Inner-loop result of [`H3Connection::process_inbound_uni_with_close`] before the recv
52/// stream is reattached. Decouples the inner async block (which only borrows the stream)
53/// from the caller-visible [`UniStreamResult`] (which returns the stream by value on
54/// non-`Handled` variants), so the function can keep ownership of `stream` long enough to
55/// fire its close callback before `stream` drops.
56enum UniInnerResult {
57    Handled,
58    WebTransport { session_id: u64, buffer: Buffer },
59    Unknown { stream_type: u64 },
60}
61
62/// The result of processing an HTTP/3 unidirectional stream.
63#[derive(Debug)]
64pub enum UniStreamResult<T> {
65    /// The stream was a known internal type (control, QPACK encoder/decoder) and was handled
66    /// automatically.
67    Handled,
68
69    /// A WebTransport unidirectional data stream. The `session_id` identifies the associated
70    /// WebTransport session.
71    WebTransport {
72        /// The WebTransport session ID.
73        session_id: u64,
74        /// The receive stream, ready for application data.
75        stream: T,
76        /// Any bytes buffered after the session ID during stream negotiation.
77        buffer: Buffer,
78    },
79
80    /// A stream whose type is recognized but unsupported (e.g. `Push`) or not recognized
81    /// at all by this crate.
82    ///
83    /// The caller is responsible for disposing of the stream — the in-tree consumers RST
84    /// it with `H3_STREAM_CREATION_ERROR`. `process_inbound_uni` deliberately does *not*
85    /// close the stream itself: handing it back gives a downstream extension the option to
86    /// implement a stream type trillium-http doesn't know about (a future RFC, an
87    /// experiment, etc.) without forking the codec.
88    Unknown {
89        /// The raw stream type value.
90        stream_type: u64,
91        /// The stream.
92        stream: T,
93    },
94}
95
96/// Shared state for a single HTTP/3 QUIC connection.
97///
98/// Call the appropriate methods on this type for each stream accepted from the QUIC connection.
99///
100/// # Driver shape (vs h2)
101///
102/// h2 multiplexes everything onto a single TCP byte stream, so a single
103/// [`H2Driver`][crate::h2::H2Driver] task suffices. h3 instead has the QUIC layer hand us multiple
104/// independent streams: an inbound and outbound control stream, an inbound and outbound QPACK
105/// encoder stream, an inbound and outbound QPACK decoder stream, and one bidi stream per
106/// request. There is no single "h3 driver" — each stream is driven by its own future returned from
107/// `H3Connection`'s `run_*` / `process_*` methods, and the caller decides how those futures are
108/// scheduled.
109///
110/// The trillium-http boundary is **runtime-free by design**: this crate hands out anonymous futures
111/// and lets the caller pick the executor. The in-tree consumers (`trillium-server-common`,
112/// `trillium-client`) follow a task-per-stream pattern — spawn each long-lived control / encoder /
113/// decoder future on its own task at connection setup, then spawn one task per accepted request
114/// stream. Nothing in this crate requires that pattern; a caller could in principle race all the
115/// futures on one task instead, with different perf characteristics.
116#[derive(Debug)]
117pub struct H3Connection {
118    /// Shared configuration across all protocols.
119    context: Arc<HttpContext>,
120
121    /// Connection-scoped shutdown signal. Shut down when we receive GOAWAY from the peer or when
122    /// the server-level Swansong shuts down.  Request stream tasks use this to interrupt
123    /// in-progress work.
124    swansong: Swansong,
125
126    /// The peer's H3 settings, received on their control stream.  Request streams may need to
127    /// consult these (e.g. max field section size).
128    pub(super) peer_settings: OnceLock<H3Settings>,
129
130    /// Multi-listener wake source for
131    /// [`PeerSettingsReady`][peer_settings_wait::PeerSettingsReady]. Notified by
132    /// `run_inbound_control` after applying peer SETTINGS, and again on connection
133    /// close, so any number of concurrently-parked futures all unblock together.
134    pub(super) peer_settings_event: Event,
135
136    /// The highest bidirectional stream ID we have accepted.  Used to compute the GOAWAY value
137    /// (this + 4) to tell the peer which requests we saw. None until the first stream is accepted.
138    /// Updated by the runtime adapter's accept loop via [`record_accepted_stream`].
139    max_accepted_stream_id: AtomicU64,
140
141    /// Whether we have accepted any streams yet.
142    has_accepted_stream: AtomicBool,
143
144    /// The decoder-side QPACK dynamic table for this connection.
145    decoder_dynamic_table: DecoderDynamicTable,
146
147    /// The encoder-side QPACK dynamic table for this connection.
148    encoder_dynamic_table: EncoderDynamicTable,
149
150    /// Sink for RFC 9218 priority signals, set via
151    /// [`register_priority_callback`][Self::register_priority_callback]. Unset until the runtime
152    /// adapter that owns the QUIC streams registers it.
153    priority_callback: PriorityCallback,
154}
155
156/// Boxed sink for `(stream_id, priority, is_update)` signals.
157type PriorityCallbackFn = Box<dyn Fn(u64, Priority, bool) + Send + Sync>;
158
159/// A registered sink for `(stream_id, priority, is_update)` signals. Newtype so [`H3Connection`]
160/// can keep deriving `Debug` despite holding a boxed closure.
161#[derive(Default)]
162struct PriorityCallback(OnceLock<PriorityCallbackFn>);
163
164impl std::fmt::Debug for PriorityCallback {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        f.debug_tuple("PriorityCallback")
167            .field(&self.0.get().map(|_| format_args!("..")))
168            .finish()
169    }
170}
171
172impl H3Connection {
173    /// Construct a new `H3Connection` to manage HTTP/3 for a given peer.
174    pub fn new(context: Arc<HttpContext>) -> Arc<Self> {
175        let swansong = context.swansong.child();
176        let max_table_capacity = context.config.dynamic_table_capacity;
177        let blocked_streams = context.config.h3_blocked_streams;
178        let encoder_dynamic_table = EncoderDynamicTable::new(&context);
179        Arc::new(Self {
180            context,
181            swansong,
182            peer_settings: OnceLock::new(),
183            peer_settings_event: Event::new(),
184            max_accepted_stream_id: AtomicU64::new(0),
185            has_accepted_stream: AtomicBool::new(false),
186            decoder_dynamic_table: DecoderDynamicTable::new(max_table_capacity, blocked_streams),
187            encoder_dynamic_table,
188            priority_callback: PriorityCallback::default(),
189        })
190    }
191
192    /// Register the sink for RFC 9218 priority signals on this connection.
193    ///
194    /// The callback is invoked with `(stream_id, priority, is_update)` once per request when its
195    /// initial `priority` header is parsed (`is_update = false`), and again for every
196    /// `PRIORITY_UPDATE` received afterward (`is_update = true`). `is_update` lets the receiver
197    /// honor RFC 9218 precedence: a `PRIORITY_UPDATE` outranks the request's initial header
198    /// priority regardless of arrival order, including when it arrives before the stream is
199    /// accepted.
200    ///
201    /// This crate keeps no priority state of its own and does no scheduling: it parses each
202    /// signal and hands the [`Priority`] off through this callback, leaving the receiver to apply
203    /// it to whatever owns send scheduling. Without a registered callback, priority is parsed but
204    /// never applied.
205    ///
206    /// Has no effect if a callback is already registered.
207    pub fn register_priority_callback(
208        &self,
209        callback: impl Fn(u64, Priority, bool) + Send + Sync + 'static,
210    ) {
211        let _ = self.priority_callback.0.set(Box::new(callback));
212    }
213
214    /// Emit a priority signal for a request stream to the registered callback, if any.
215    /// `is_update` distinguishes a received `PRIORITY_UPDATE` from the request's initial header
216    /// priority so the receiver can honor the precedence rule.
217    fn emit_priority(&self, stream_id: u64, priority: Priority, is_update: bool) {
218        let kind = if is_update {
219            "PRIORITY_UPDATE"
220        } else {
221            "initial"
222        };
223        match self.priority_callback.0.get() {
224            Some(callback) => {
225                log::trace!("H3 stream {stream_id}: emitting {kind} priority \"{priority}\"");
226                callback(stream_id, priority, is_update);
227            }
228            None => log::trace!(
229                "H3 stream {stream_id}: {kind} priority \"{priority}\" parsed but no callback \
230                 registered"
231            ),
232        }
233    }
234
235    /// Handle an RFC 9218 `PRIORITY_UPDATE` received on the peer's control stream. The
236    /// prioritized element id must name a client-initiated bidirectional (request) stream —
237    /// `id % 4 == 0` in QUIC — and other ids are ignored rather than erroring, since the signal
238    /// is advisory.
239    fn emit_priority_update(&self, prioritized_element_id: u64, priority: Priority) {
240        if prioritized_element_id.is_multiple_of(4) {
241            self.emit_priority(prioritized_element_id, priority, true);
242        } else {
243            log::trace!(
244                "H3: ignoring PRIORITY_UPDATE for non-request stream {prioritized_element_id}"
245            );
246        }
247    }
248
249    /// Retrieve the [`Swansong`] shutdown handle for this HTTP/3 connection. See also
250    /// [`H3Connection::shut_down`]
251    pub fn swansong(&self) -> &Swansong {
252        &self.swansong
253    }
254
255    /// Attempt graceful shutdown of this HTTP/3 connection (all streams).
256    ///
257    /// The returned [`ShutdownCompletion`] type can
258    /// either be awaited in an async context or blocked on with [`ShutdownCompletion::block`] in a
259    /// blocking context
260    ///
261    /// Note that this will NOT shut down the server. To shut down the whole server, use
262    /// [`HttpContext::shut_down`]
263    pub fn shut_down(&self) -> ShutdownCompletion {
264        // Wake any in-flight `decode_field_section` calls parked on the decoder
265        // table's `ThresholdWait` (a non-I/O future awaiting dynamic-table inserts
266        // from the peer). The encoder table's writer loop is already swansong-
267        // aware, but we mark it failed too for symmetry: any future state
268        // mutations after shutdown are no longer wire-relevant.
269        self.decoder_dynamic_table.fail(H3ErrorCode::NoError);
270        self.encoder_dynamic_table.fail(H3ErrorCode::NoError);
271        self.wake_peer_settings_waiters();
272        self.swansong.shut_down()
273    }
274
275    /// Retrieve the [`HttpContext`] for this server.
276    pub fn context(&self) -> Arc<HttpContext> {
277        self.context.clone()
278    }
279
280    /// Returns the peer's HTTP/3 settings, available once the peer's control stream has been
281    /// processed.
282    pub fn peer_settings(&self) -> Option<&H3Settings> {
283        self.peer_settings.get()
284    }
285
286    /// Record that we accepted a bidirectional stream with this ID.
287    fn record_accepted_stream(&self, stream_id: u64) {
288        self.max_accepted_stream_id
289            .fetch_max(stream_id, Ordering::Relaxed);
290        self.has_accepted_stream.store(true, Ordering::Relaxed);
291    }
292
293    /// The stream ID to send in a GOAWAY frame: one past the highest stream we accepted, or 0 if we
294    /// haven't accepted any.
295    fn goaway_id(&self) -> u64 {
296        if self.has_accepted_stream.load(Ordering::Relaxed) {
297            self.max_accepted_stream_id.load(Ordering::Relaxed) + 4
298        } else {
299            0
300        }
301    }
302
303    /// Begin processing a single HTTP/3 request-response cycle on an accepted bidirectional
304    /// stream.
305    ///
306    /// Returns a builder. Attach an optional reset hook with
307    /// [`with_reset`][H3BidiRequest::with_reset], then `.await` it to run one request/response
308    /// cycle. Awaiting resolves to [`H3StreamResult::WebTransport`] if the stream opens a
309    /// WebTransport session rather than a standard HTTP/3 request.
310    ///
311    /// Without a reset hook, a stream-level protocol error drops the transport without
312    /// resetting it; attach `with_reset` to issue the RST that RFC 9114 requires for stream
313    /// errors.
314    ///
315    /// RFC 9218 priority is delivered out of band via the callback registered with
316    /// [`register_priority_callback`][Self::register_priority_callback]: this method emits the
317    /// request's initial priority once the headers are parsed.
318    pub fn process_inbound_bidi<Transport, Handler>(
319        self: Arc<Self>,
320        transport: Transport,
321        handler: Handler,
322        stream_id: u64,
323    ) -> H3BidiRequest<Transport, Handler> {
324        H3BidiRequest {
325            h3: self,
326            transport,
327            handler,
328            stream_id,
329            reset: None,
330            reject_requests: false,
331        }
332    }
333
334    /// Process a single HTTP/3 request-response cycle on a bidirectional stream, calling
335    /// `reset` to issue a stream RST when a stream-level protocol error occurs.
336    ///
337    /// On any `H3Error::Protocol(code)` produced by first-frame processing (HEADERS decode,
338    /// pseudo-header validation, etc.), `reset` is invoked with the still-owned transport and
339    /// the error code before the error is returned. This lets callers RST both the recv and
340    /// send halves of the bidi stream — required by RFC 9114 for stream errors like
341    /// `H3_MESSAGE_ERROR`. I/O errors and successful runs do not invoke `reset`.
342    ///
343    /// `reset` is a `FnOnce` taking `(&mut Transport, H3ErrorCode)`. trillium-http does not
344    /// itself depend on any reset capability of the transport; callers wire up the actual
345    /// stream-RST mechanism (e.g. quinn's `RecvStream::stop` + `SendStream::reset`) inside
346    /// the closure.
347    ///
348    /// # Errors
349    ///
350    /// Returns an `H3Error` in case of io error or http/3 semantic error.
351    // This is not deprecated yet because it didn't make sense to release a new version of
352    // trillium-client just to avoid this deprecation, but the intention is to deprecate
353    pub async fn process_inbound_bidi_with_reset<Transport, Handler, Fut, Reset>(
354        self: Arc<Self>,
355        mut transport: Transport,
356        handler: Handler,
357        stream_id: u64,
358        reset: Reset,
359    ) -> Result<H3StreamResult<Transport>, H3Error>
360    where
361        Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
362        Handler: FnOnce(Conn<Transport>) -> Fut,
363        Fut: Future<Output = Conn<Transport>>,
364        Reset: FnOnce(&mut Transport, H3ErrorCode),
365    {
366        self.record_accepted_stream(stream_id);
367        let _guard = self.swansong.guard();
368        let mut buffer: Buffer =
369            Vec::with_capacity(self.context.config.request_buffer_initial_len).into();
370
371        let outcome =
372            Conn::process_first_frame_h3(&self, &mut transport, &mut buffer, stream_id).await;
373
374        match outcome {
375            Ok(H3FirstFrame::Request {
376                validated,
377                start_time,
378            }) => {
379                let conn =
380                    Conn::build_h3(self, transport, buffer, validated, start_time, stream_id);
381                Ok(H3StreamResult::Request(
382                    handler(conn).await.send_h3().await?,
383                ))
384            }
385            Ok(H3FirstFrame::WebTransport { session_id }) => Ok(H3StreamResult::WebTransport {
386                session_id,
387                transport,
388                buffer,
389            }),
390            Err(error) => {
391                if let H3Error::Protocol(code) = &error {
392                    reset(&mut transport, *code);
393                }
394                Err(error)
395            }
396        }
397    }
398
399    /// Decode a QPACK-encoded field section, consulting the dynamic table as needed.
400    ///
401    /// If the field section's Required Insert Count is greater than zero, waits until the
402    /// dynamic table has received enough entries. Returns an error on protocol violations or
403    /// if the encoder stream fails while waiting.
404    ///
405    /// Duplicate pseudo-headers are silently ignored (first value wins). Unknown
406    /// pseudo-headers are rejected.
407    ///
408    /// # Errors
409    ///
410    /// Returns an error if the encoded bytes cannot be parsed as a valid field section.
411    #[cfg(feature = "unstable")]
412    pub async fn decode_field_section(
413        &self,
414        encoded: &[u8],
415        stream_id: u64,
416    ) -> Result<FieldSection<'static>, H3Error> {
417        self.decoder_dynamic_table.decode(encoded, stream_id).await
418    }
419
420    #[cfg(not(feature = "unstable"))]
421    pub(crate) async fn decode_field_section(
422        &self,
423        encoded: &[u8],
424        stream_id: u64,
425    ) -> Result<FieldSection<'static>, H3Error> {
426        self.decoder_dynamic_table.decode(encoded, stream_id).await
427    }
428
429    /// Encode a QPACK field section (no HTTP/3 framing) from pseudo-headers and headers,
430    /// consulting the encoder dynamic table to emit literal-with-name-reference or indexed
431    /// representations as the table's contents allow.
432    ///
433    /// Superseded by [`encode_field_section_framed`][Self::encode_field_section_framed], which
434    /// also frames the section and enforces the peer's field-section size limit.
435    ///
436    /// # Errors
437    ///
438    /// Returns an `H3Error` in case of http/3 semantic error.
439    // Retained only so an older `trillium-client` that predates the framed method still builds
440    // against this crate; remove it at the next breaking release.
441    #[cfg(feature = "unstable")]
442    #[allow(clippy::unnecessary_wraps, reason = "future-proofing api")]
443    pub fn encode_field_section(
444        &self,
445        field_section: &FieldSection<'_>,
446        buf: &mut Vec<u8>,
447        stream_id: u64,
448    ) -> Result<(), H3Error> {
449        self.encoder_dynamic_table
450            .encode(field_section, buf, stream_id);
451        Ok(())
452    }
453
454    /// Encode `field_section` as a complete HTTP/3 HEADERS frame — the QPACK-compressed field
455    /// section prefixed with its `type` + `length` frame header — and append it to `buffer`.
456    ///
457    /// If the peer's `SETTINGS_MAX_FIELD_SECTION_SIZE` is known and the section's
458    /// [`uncompressed_len`][crate::headers::FieldSection] exceeds it, the section is rejected
459    /// before encoding and nothing is written. Until the peer's SETTINGS arrive the limit is
460    /// unknown and unenforced (RFC 9114 §4.2.2).
461    ///
462    /// # Errors
463    ///
464    /// [`io::ErrorKind::InvalidData`] if the field section exceeds the peer's advertised limit, or
465    /// the QPACK encoder's error mapped through [`io::Error::other`].
466    #[cfg(feature = "unstable")]
467    #[doc(hidden)]
468    pub fn encode_field_section_framed(
469        &self,
470        field_section: &FieldSection<'_>,
471        buffer: &mut Vec<u8>,
472        stream_id: u64,
473    ) -> io::Result<()> {
474        self.encode_field_section_framed_impl(field_section, buffer, stream_id)
475    }
476
477    #[cfg(not(feature = "unstable"))]
478    pub(crate) fn encode_field_section_framed(
479        &self,
480        field_section: &FieldSection<'_>,
481        buffer: &mut Vec<u8>,
482        stream_id: u64,
483    ) -> io::Result<()> {
484        self.encode_field_section_framed_impl(field_section, buffer, stream_id)
485    }
486
487    fn encode_field_section_framed_impl(
488        &self,
489        field_section: &FieldSection<'_>,
490        buffer: &mut Vec<u8>,
491        stream_id: u64,
492    ) -> io::Result<()> {
493        // Enforce the peer's SETTINGS_MAX_FIELD_SECTION_SIZE against the uncompressed size the
494        // limit is defined in (RFC 9114 §4.2.2), before encoding — an over-limit section is
495        // rejected without being written to the wire.
496        if let Some(max_size) = self
497            .peer_settings()
498            .and_then(H3Settings::max_field_section_size)
499        {
500            let size = field_section.uncompressed_len();
501            if size > max_size {
502                return Err(io::Error::new(
503                    ErrorKind::InvalidData,
504                    format!("field section would be longer than peer allows ({size} > {max_size})"),
505                ));
506            }
507        }
508
509        let start = buffer.len();
510        self.encoder_dynamic_table
511            .encode(field_section, buffer, stream_id);
512
513        // The HEADERS frame length is the encoded (compressed) payload length, distinct from the
514        // uncompressed size enforced above. Encode the section in place, then open a gap in front
515        // of it for the now-known frame header and shift the section right into place.
516        let section_len = buffer.len() - start;
517        let frame = Frame::Headers(section_len as u64);
518        let frame_header_len = frame.encoded_len();
519        buffer.resize(buffer.len() + frame_header_len, 0);
520        buffer.copy_within(start..start + section_len, start + frame_header_len);
521        frame.encode(&mut buffer[start..start + frame_header_len]);
522        Ok(())
523    }
524
525    /// Run this connection's HTTP/3 outbound control stream.
526    ///
527    /// Sends the initial SETTINGS frame, then sends GOAWAY when the connection shuts down.
528    /// Returns after GOAWAY is sent; keep the stream open until the QUIC connection closes
529    /// (closing a control stream is a connection error).
530    ///
531    /// Shuts the connection down ([`shut_down`][Self::shut_down]) on return, for the same reason
532    /// as [`run_encoder`][Self::run_encoder].
533    ///
534    /// # Errors
535    ///
536    /// Returns an `H3Error` in case of io error or http/3 semantic error.
537    pub async fn run_outbound_control<T>(&self, mut stream: T) -> Result<(), H3Error>
538    where
539        T: AsyncWrite + Unpin + Send,
540    {
541        let result: Result<(), H3Error> = async {
542            let mut buf = vec![0; 128];
543
544            let settings = Frame::Settings(H3Settings::from(&self.context.config));
545            log::trace!(
546                "H3 outbound control: sending SETTINGS: {:?}",
547                H3Settings::from(&self.context.config)
548            );
549
550            write(&mut buf, &mut stream, |buf| {
551                let mut written = quic_varint::encode(UniStreamType::Control, buf)?;
552                written += settings.encode(&mut buf[written..])?;
553                Some(written)
554            })
555            .await?;
556            log::trace!("H3 outbound control: SETTINGS sent");
557
558            self.swansong.clone().await;
559
560            write(&mut buf, &mut stream, |buf| {
561                Frame::Goaway(self.goaway_id()).encode(buf)
562            })
563            .await?;
564
565            Ok(())
566        }
567        .await;
568
569        self.shut_down();
570        result
571    }
572
573    /// Run the outbound QPACK encoder stream for the duration of the connection.
574    ///
575    /// Writes the stream type byte, then drains encoder-stream instructions from the encoder
576    /// dynamic table as they are enqueued. Returns when the connection shuts down or the table is
577    /// marked failed.
578    ///
579    /// Shuts the connection down ([`shut_down`][Self::shut_down]) on return. This stream is
580    /// mandatory for the connection's lifetime, so its termination — clean or errored — means the
581    /// connection can no longer function; marking it shut down lets a pooling caller evict it
582    /// rather than hand it back out. Idempotent on the clean path, which returns only after
583    /// shutdown has already begun.
584    ///
585    /// # Errors
586    ///
587    /// Returns an `H3Error` in case of io error.
588    pub async fn run_encoder<T>(&self, mut stream: T) -> Result<(), H3Error>
589    where
590        T: AsyncWrite + Unpin + Send,
591    {
592        let result = self
593            .encoder_dynamic_table
594            .run_writer(&mut stream, self.swansong.clone())
595            .await;
596        self.shut_down();
597        result
598    }
599
600    /// Run the outbound QPACK decoder stream for the duration of the connection.
601    ///
602    /// Writes the stream type byte, then loops sending Section Acknowledgement and Insert
603    /// Count Increment instructions as they become needed. Returns when the connection
604    /// shuts down.
605    ///
606    /// Shuts the connection down ([`shut_down`][Self::shut_down]) on return, for the same reason
607    /// as [`run_encoder`][Self::run_encoder].
608    ///
609    /// # Errors
610    ///
611    /// Returns an `H3Error` in case of io error or http/3 semantic error.
612    pub async fn run_decoder<T>(&self, mut stream: T) -> Result<(), H3Error>
613    where
614        T: AsyncWrite + Unpin + Send,
615    {
616        let result = self
617            .decoder_dynamic_table
618            .run_writer(&mut stream, self.swansong.clone())
619            .await;
620        self.shut_down();
621        result
622    }
623
624    /// Handle an inbound unidirectional HTTP/3 stream from the peer.
625    ///
626    /// Internal stream types (control, QPACK encoder/decoder) are handled automatically;
627    /// application streams are returned via [`UniStreamResult`] for the caller to process.
628    ///
629    /// On a connection-level protocol error, this method drops the recv stream before
630    /// the caller can react. Quinn's `RecvStream::drop` then sends `STOP_SENDING`, which
631    /// races against the caller's `connection.close` — if the peer responds with a
632    /// malformed `RESET_STREAM` (notably `final_offset = 0`) before our app close is
633    /// applied, the transport-level error overrides our app error code on the wire.
634    /// Use [`process_inbound_uni_with_close`] to thread the close call through the
635    /// function so it fires before the stream drops.
636    ///
637    /// [`process_inbound_uni_with_close`]: Self::process_inbound_uni_with_close
638    ///
639    /// # Errors
640    ///
641    /// Returns a `H3Error` in case of io error or http/3 semantic error.
642    #[deprecated(
643        since = "1.2.0",
644        note = "use `process_inbound_uni_with_close` so connection-level protocol errors close \
645                the QUIC connection before the recv stream drops, avoiding a `FINAL_SIZE_ERROR` \
646                race with the peer's response to STOP_SENDING"
647    )]
648    pub async fn process_inbound_uni<T>(&self, stream: T) -> Result<UniStreamResult<T>, H3Error>
649    where
650        T: AsyncRead + Unpin + Send,
651    {
652        self.process_inbound_uni_with_close(stream, |_| {}).await
653    }
654
655    /// Handle an inbound unidirectional HTTP/3 stream from the peer, calling `on_close` to
656    /// close the QUIC connection if a connection-level protocol error is detected.
657    ///
658    /// Identical to [`process_inbound_uni`][Self::process_inbound_uni] except that on
659    /// any `H3Error::Protocol(code)` whose code is a connection-level error (RFC 9114,
660    /// RFC 9204), `on_close` is invoked with that code while the recv stream is still alive. This
661    /// lets callers send a `CONNECTION_CLOSE` before the stream drops — if the close call sets
662    /// quinn's `conn.error`, quinn's `RecvStream::drop` skips `STOP_SENDING`, eliminating a
663    /// peer race that otherwise causes `FINAL_SIZE_ERROR` to override the app error code.
664    ///
665    /// `on_close` is a `FnOnce` taking `H3ErrorCode`. trillium-http does not itself
666    /// hold the QUIC connection; callers wire up the actual `connection.close()` call
667    /// inside the closure (e.g. quinn's `Connection::close`).
668    ///
669    /// # Errors
670    ///
671    /// Returns a `H3Error` in case of io error or http/3 semantic error.
672    pub async fn process_inbound_uni_with_close<T, OnClose>(
673        &self,
674        mut stream: T,
675        on_close: OnClose,
676    ) -> Result<UniStreamResult<T>, H3Error>
677    where
678        T: AsyncRead + Unpin + Send,
679        OnClose: FnOnce(H3ErrorCode),
680    {
681        let inner = self
682            .swansong
683            .interrupt(self.process_inbound_uni_inner(&mut stream))
684            .await
685            .unwrap_or(Ok(UniInnerResult::Handled)); // interrupted
686
687        match inner {
688            Ok(UniInnerResult::Handled) => Ok(UniStreamResult::Handled),
689            Ok(UniInnerResult::WebTransport { session_id, buffer }) => {
690                Ok(UniStreamResult::WebTransport {
691                    session_id,
692                    stream,
693                    buffer,
694                })
695            }
696            Ok(UniInnerResult::Unknown { stream_type }) => Ok(UniStreamResult::Unknown {
697                stream_type,
698                stream,
699            }),
700            Err(error) => {
701                // Fire `on_close` BEFORE returning so the caller's connection.close
702                // call sets quinn's `conn.error` while `stream` is still alive. When
703                // `stream` then drops at function return, quinn's `RecvStream::drop`
704                // skips STOP_SENDING — preventing the peer-RESET_STREAM race that
705                // otherwise replaces our app close code with FINAL_SIZE_ERROR.
706                if let H3Error::Protocol(code) = &error
707                    && code.is_connection_error()
708                {
709                    on_close(*code);
710                }
711                Err(error)
712            }
713        }
714    }
715
716    /// Inner-loop body of [`process_inbound_uni_with_close`][Self::process_inbound_uni_with_close].
717    /// Borrows `stream` so the outer function can keep ownership of it across the await,
718    /// which lets the caller's close callback fire before the recv stream drops.
719    async fn process_inbound_uni_inner<T>(&self, stream: &mut T) -> Result<UniInnerResult, H3Error>
720    where
721        T: AsyncRead + Unpin + Send,
722    {
723        let mut buf = vec![0; 128];
724        let mut filled = 0;
725
726        // Read stream type varint (decode as raw u64 to handle unknown types)
727        let stream_type = read(&mut buf, &mut filled, stream, |data| {
728            match quic_varint::decode(data) {
729                Ok(ok) => Ok(Some(ok)),
730                Err(QuicVarIntError::UnexpectedEnd) => Ok(None),
731                // this branch is unreachable because u64 is always From<u64>
732                Err(QuicVarIntError::UnknownValue { bytes, value }) => Ok(Some((value, bytes))),
733            }
734        })
735        .await?;
736
737        match UniStreamType::try_from(stream_type) {
738            Ok(UniStreamType::Control) => {
739                log::trace!("H3 inbound uni: control stream");
740                self.run_inbound_control(&mut buf, &mut filled, stream)
741                    .await?;
742                Ok(UniInnerResult::Handled)
743            }
744
745            Ok(UniStreamType::QpackEncoder) => {
746                log::trace!("H3 inbound uni: QPACK encoder stream ({filled} bytes pre-read)");
747                let mut reader = Prepended {
748                    head: &buf[..filled],
749                    tail: stream,
750                };
751
752                log::trace!("QPACK encoder stream: started");
753                self.decoder_dynamic_table.run_reader(&mut reader).await?;
754
755                Ok(UniInnerResult::Handled)
756            }
757
758            Ok(UniStreamType::QpackDecoder) => {
759                log::trace!("H3 inbound uni: QPACK decoder stream ({filled} bytes pre-read)");
760                let mut reader = Prepended {
761                    head: &buf[..filled],
762                    tail: stream,
763                };
764                self.encoder_dynamic_table.run_reader(&mut reader).await?;
765                Ok(UniInnerResult::Handled)
766            }
767
768            Ok(UniStreamType::WebTransport) => {
769                log::trace!("H3 inbound uni: WebTransport stream");
770                let session_id =
771                    read(
772                        &mut buf,
773                        &mut filled,
774                        stream,
775                        |data| match quic_varint::decode(data) {
776                            Ok(ok) => Ok(Some(ok)),
777                            Err(QuicVarIntError::UnexpectedEnd) => Ok(None),
778                            Err(QuicVarIntError::UnknownValue { bytes, value }) => {
779                                Ok(Some((value, bytes)))
780                            }
781                        },
782                    )
783                    .await?;
784
785                buf.truncate(filled);
786
787                Ok(UniInnerResult::WebTransport {
788                    session_id,
789                    buffer: buf.into(),
790                })
791            }
792
793            Ok(UniStreamType::Push) => {
794                // Trillium does not support HTTP/3 push, so we hand these back as `Unknown`
795                // identically to truly-unknown stream types — the explicit arm exists so
796                // trace output names "push stream" rather than a bare type id.
797                log::trace!("H3 inbound uni: push stream (push not supported)");
798                Ok(UniInnerResult::Unknown { stream_type })
799            }
800
801            Err(_) => {
802                log::trace!("H3 inbound uni: unknown stream type {stream_type:#x}");
803                Ok(UniInnerResult::Unknown { stream_type })
804            }
805        }
806    }
807
808    /// Handle the http/3 peer's inbound control stream.
809    ///
810    /// # Errors
811    ///
812    /// Returns a `H3Error` in case of io error or HTTP/3 semantic error.
813    async fn run_inbound_control<T>(
814        &self,
815        buf: &mut Vec<u8>,
816        filled: &mut usize,
817        stream: &mut T,
818    ) -> Result<(), H3Error>
819    where
820        T: AsyncRead + Unpin + Send,
821    {
822        // SettingsError takes priority: a SETTINGS frame whose payload is itself invalid
823        // (e.g. forbidden HTTP/2 setting IDs) is reported as SETTINGS_ERROR, not the
824        // MISSING_SETTINGS we report for everything else here.
825        let settings = read(buf, filled, stream, |data| match Frame::decode(data) {
826            Ok((Frame::Settings(s), consumed)) => Ok(Some((s, consumed))),
827            Err(FrameDecodeError::Incomplete) => Ok(None),
828            Err(FrameDecodeError::Error(H3ErrorCode::SettingsError)) => {
829                Err(H3ErrorCode::SettingsError)
830            }
831            Ok(_) | Err(FrameDecodeError::Error(_)) => Err(H3ErrorCode::MissingSettings),
832        })
833        .await
834        .map_err(map_critical_stream_eof)?;
835
836        log::trace!("H3 peer settings: {settings:?}");
837
838        self.peer_settings
839            .set(settings)
840            .map_err(|_| H3ErrorCode::FrameUnexpected)?;
841        self.wake_peer_settings_waiters();
842
843        self.encoder_dynamic_table
844            .initialize_from_peer_settings(settings);
845
846        loop {
847            let frame = self
848                .swansong
849                .interrupt(read(buf, filled, stream, |data| {
850                    match Frame::decode(data) {
851                        Ok((frame, consumed)) => Ok(Some((frame, consumed))),
852                        Err(FrameDecodeError::Incomplete) => Ok(None),
853                        Err(FrameDecodeError::Error(code)) => Err(code),
854                    }
855                }))
856                .await
857                .transpose()
858                .map_err(map_critical_stream_eof)?;
859
860            match frame {
861                None => {
862                    log::trace!("H3 control stream: interrupted by shutdown");
863                    return Ok(());
864                }
865
866                Some(Frame::Goaway(id)) => {
867                    log::trace!("H3 control stream: peer sent GOAWAY(stream_id={id})");
868                    self.swansong.shut_down();
869                    return Ok(());
870                }
871
872                Some(Frame::Unknown(n)) => {
873                    // Consume the payload bytes so the stream stays synchronized.
874                    log::trace!("H3 control stream: skipping unknown frame (payload {n} bytes)");
875                    let n = usize::try_from(n).unwrap_or(usize::MAX);
876                    let in_buf = n.min(*filled);
877                    buf.copy_within(in_buf..*filled, 0);
878                    *filled -= in_buf;
879                    let mut todo = n - in_buf;
880                    let mut scratch = [0u8; 256];
881                    while todo > 0 {
882                        let to_read = todo.min(scratch.len());
883                        let n = stream
884                            .read(&mut scratch[..to_read])
885                            .await
886                            .map_err(H3Error::Io)?;
887                        if n == 0 {
888                            return Err(H3ErrorCode::ClosedCriticalStream.into());
889                        }
890                        todo -= n;
891                    }
892                }
893
894                Some(
895                    Frame::Settings(_)
896                    | Frame::Data(_)
897                    | Frame::Headers(_)
898                    | Frame::PushPromise { .. }
899                    | Frame::WebTransport(_),
900                ) => {
901                    return Err(H3ErrorCode::FrameUnexpected.into());
902                }
903
904                Some(Frame::PriorityUpdate {
905                    prioritized_element_id,
906                    priority,
907                }) => {
908                    log::trace!(
909                        "H3 control stream: PRIORITY_UPDATE stream={prioritized_element_id} \
910                         priority=\"{priority}\""
911                    );
912                    self.emit_priority_update(prioritized_element_id, priority);
913                }
914
915                // Trillium doesn't implement push, so these are ignored rather than acted on.
916                Some(Frame::CancelPush(_) | Frame::MaxPushId(_)) => {
917                    log::trace!("H3 control stream: ignoring {frame:?}");
918                }
919            }
920        }
921    }
922}
923
924/// A pending HTTP/3 request-response cycle on one bidirectional stream, with optional
925/// per-stream hooks.
926///
927/// Built by [`H3Connection::process_inbound_bidi`]. Configure hooks with the `with_*`
928/// methods and `.await` it to run the cycle. New per-stream extension points are added as
929/// further `with_*` methods, so the entry point's required arguments never change.
930pub struct H3BidiRequest<Transport, Handler> {
931    h3: Arc<H3Connection>,
932    transport: Transport,
933    handler: Handler,
934    stream_id: u64,
935    reset: Option<ResetHook<Transport>>,
936    reject_requests: bool,
937}
938
939/// Per-stream reset hook: RST both halves with the still-owned transport on a stream-level error.
940type ResetHook<Transport> = Box<dyn FnOnce(&mut Transport, H3ErrorCode) + Send>;
941
942impl<Transport, Handler> std::fmt::Debug for H3BidiRequest<Transport, Handler> {
943    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
944        f.debug_struct("H3BidiRequest")
945            .field("stream_id", &self.stream_id)
946            .finish_non_exhaustive()
947    }
948}
949
950impl<Transport, Handler> H3BidiRequest<Transport, Handler> {
951    /// Issue a stream RST on a stream-level protocol error.
952    ///
953    /// On any `H3Error::Protocol(code)` from first-frame processing, `reset` is called with
954    /// the still-owned transport and the error code before the error is returned — letting the
955    /// caller RST both halves of the bidi stream as RFC 9114 requires. I/O errors and
956    /// successful runs do not invoke it. Without this hook, the transport is dropped without a
957    /// reset.
958    #[must_use]
959    pub fn with_reset<R>(mut self, reset: R) -> Self
960    where
961        R: FnOnce(&mut Transport, H3ErrorCode) + Send + 'static,
962    {
963        self.reset = Some(Box::new(reset));
964        self
965    }
966
967    /// Treat an HTTP request on this stream as a protocol violation instead of running the
968    /// handler.
969    ///
970    /// A peer that accepts inbound bidirectional streams only for negotiated extensions —
971    /// an HTTP/3 client, where RFC 9114 makes a server-initiated request stream a connection
972    /// error — enables this to refuse requests without responding to them. When the stream's
973    /// first frame begins an HTTP request, the handler is skipped, nothing is written to the
974    /// stream, the [`with_reset`][Self::with_reset] hook is invoked with
975    /// [`H3ErrorCode::StreamCreationError`], and awaiting resolves to that error. Closing the
976    /// connection is the caller's responsibility, typically inside the reset hook while the
977    /// stream is still alive.
978    #[must_use]
979    pub fn with_request_rejection(mut self) -> Self {
980        self.reject_requests = true;
981        self
982    }
983}
984
985impl<Transport, Handler, Fut> IntoFuture for H3BidiRequest<Transport, Handler>
986where
987    Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
988    Handler: FnOnce(Conn<Transport>) -> Fut + Send + 'static,
989    Fut: Future<Output = Conn<Transport>> + Send + 'static,
990{
991    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
992    type Output = Result<H3StreamResult<Transport>, H3Error>;
993
994    fn into_future(self) -> Self::IntoFuture {
995        Box::pin(async move {
996            let Self {
997                h3,
998                mut transport,
999                handler,
1000                stream_id,
1001                reset,
1002                reject_requests,
1003            } = self;
1004
1005            h3.record_accepted_stream(stream_id);
1006            let _guard = h3.swansong.guard();
1007            let mut buffer: Buffer =
1008                Vec::with_capacity(h3.context.config.request_buffer_initial_len).into();
1009
1010            let outcome =
1011                Conn::process_first_frame_h3(&h3, &mut transport, &mut buffer, stream_id).await;
1012
1013            match outcome {
1014                Ok(H3FirstFrame::Request { .. }) if reject_requests => {
1015                    let code = H3ErrorCode::StreamCreationError;
1016                    if let Some(reset) = reset {
1017                        reset(&mut transport, code);
1018                    }
1019                    Err(code.into())
1020                }
1021                Ok(H3FirstFrame::Request {
1022                    validated,
1023                    start_time,
1024                }) => {
1025                    let initial_priority = validated
1026                        .request_headers
1027                        .get_str(KnownHeaderName::Priority)
1028                        .map(Priority::parse)
1029                        .unwrap_or_default();
1030                    h3.emit_priority(stream_id, initial_priority, false);
1031                    let conn =
1032                        Conn::build_h3(h3, transport, buffer, validated, start_time, stream_id);
1033                    Ok(H3StreamResult::Request(
1034                        handler(conn).await.send_h3().await?,
1035                    ))
1036                }
1037                Ok(H3FirstFrame::WebTransport { session_id }) => Ok(H3StreamResult::WebTransport {
1038                    session_id,
1039                    transport,
1040                    buffer,
1041                }),
1042                Err(error) => {
1043                    if let H3Error::Protocol(code) = &error
1044                        && let Some(reset) = reset
1045                    {
1046                        reset(&mut transport, *code);
1047                    }
1048                    Err(error)
1049                }
1050            }
1051        })
1052    }
1053}
1054
1055/// Map an `UnexpectedEof` I/O error (the `read` helper's "stream FIN'd" signal) to
1056/// `H3_CLOSED_CRITICAL_STREAM`. Closure of the control stream or of either QPACK
1057/// side-channel is a connection error. Other I/O errors and any protocol error are passed
1058/// through unchanged.
1059fn map_critical_stream_eof(error: H3Error) -> H3Error {
1060    match error {
1061        H3Error::Io(e) if e.kind() == ErrorKind::UnexpectedEof => {
1062            H3ErrorCode::ClosedCriticalStream.into()
1063        }
1064        other => other,
1065    }
1066}
1067
1068async fn write(
1069    buf: &mut Vec<u8>,
1070    mut stream: impl AsyncWrite + Unpin + Send,
1071    mut f: impl FnMut(&mut [u8]) -> Option<usize>,
1072) -> io::Result<usize> {
1073    let written = loop {
1074        if let Some(w) = f(buf) {
1075            break w;
1076        }
1077        if buf.len() >= MAX_BUFFER_SIZE {
1078            return Err(io::Error::new(ErrorKind::OutOfMemory, "runaway allocation"));
1079        }
1080        buf.resize(buf.len() * 2, 0);
1081    };
1082
1083    stream.write_all(&buf[..written]).await?;
1084    stream.flush().await?;
1085    Ok(written)
1086}
1087
1088/// An `AsyncRead` adapter that drains a byte slice before reading from an inner stream.
1089///
1090/// Used to replay bytes that were read ahead while parsing a stream-type varint, before
1091/// dispatching to the inner runner that consumes the rest of the stream.
1092struct Prepended<'a, T> {
1093    head: &'a [u8],
1094    tail: T,
1095}
1096
1097impl<T: AsyncRead + Unpin> AsyncRead for Prepended<'_, T> {
1098    fn poll_read(
1099        self: Pin<&mut Self>,
1100        cx: &mut Context<'_>,
1101        out: &mut [u8],
1102    ) -> Poll<io::Result<usize>> {
1103        let this = self.get_mut();
1104        if !this.head.is_empty() {
1105            let n = this.head.len().min(out.len());
1106            out[..n].copy_from_slice(&this.head[..n]);
1107            this.head = &this.head[n..];
1108            return Poll::Ready(Ok(n));
1109        }
1110        Pin::new(&mut this.tail).poll_read(cx, out)
1111    }
1112}
1113
1114/// Read from `stream` into `buf` until `f` can decode a value.
1115///
1116/// `f` receives the filled portion of the buffer and returns:
1117/// - `Ok(Some((value, consumed)))` — success; consumed bytes are removed from the front
1118/// - `Ok(None)` — need more data; reads more bytes and retries
1119/// - `Err(e)` — unrecoverable error; propagated to caller
1120async fn read<R>(
1121    buf: &mut Vec<u8>,
1122    filled: &mut usize,
1123    stream: &mut (impl AsyncRead + Unpin + Send),
1124    f: impl Fn(&[u8]) -> Result<Option<(R, usize)>, H3ErrorCode>,
1125) -> Result<R, H3Error> {
1126    loop {
1127        if let Some((result, consumed)) = f(&buf[..*filled])? {
1128            buf.copy_within(consumed..*filled, 0);
1129            *filled -= consumed;
1130            return Ok(result);
1131        }
1132
1133        if *filled >= buf.len() {
1134            if buf.len() >= MAX_BUFFER_SIZE {
1135                return Err(io::Error::new(ErrorKind::OutOfMemory, "runaway allocation").into());
1136            }
1137            buf.resize(buf.len() * 2, 0);
1138        }
1139
1140        let n = stream.read(&mut buf[*filled..]).await?;
1141        if n == 0 {
1142            return Err(io::Error::new(ErrorKind::UnexpectedEof, "stream closed").into());
1143        }
1144        *filled += n;
1145    }
1146}
1147
1148#[cfg(test)]
1149mod tests;