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, PeerGone, 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            peer_gone: None,
332        }
333    }
334
335    /// Process a single HTTP/3 request-response cycle on a bidirectional stream, calling
336    /// `reset` to issue a stream RST when a stream-level protocol error occurs.
337    ///
338    /// On any `H3Error::Protocol(code)` produced by first-frame processing (HEADERS decode,
339    /// pseudo-header validation, etc.), `reset` is invoked with the still-owned transport and
340    /// the error code before the error is returned. This lets callers RST both the recv and
341    /// send halves of the bidi stream — required by RFC 9114 for stream errors like
342    /// `H3_MESSAGE_ERROR`. I/O errors and successful runs do not invoke `reset`.
343    ///
344    /// `reset` is a `FnOnce` taking `(&mut Transport, H3ErrorCode)`. trillium-http does not
345    /// itself depend on any reset capability of the transport; callers wire up the actual
346    /// stream-RST mechanism (e.g. quinn's `RecvStream::stop` + `SendStream::reset`) inside
347    /// the closure.
348    ///
349    /// # Errors
350    ///
351    /// Returns an `H3Error` in case of io error or http/3 semantic error.
352    // This is not deprecated yet because it didn't make sense to release a new version of
353    // trillium-client just to avoid this deprecation, but the intention is to deprecate
354    pub async fn process_inbound_bidi_with_reset<Transport, Handler, Fut, Reset>(
355        self: Arc<Self>,
356        mut transport: Transport,
357        handler: Handler,
358        stream_id: u64,
359        reset: Reset,
360    ) -> Result<H3StreamResult<Transport>, H3Error>
361    where
362        Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
363        Handler: FnOnce(Conn<Transport>) -> Fut,
364        Fut: Future<Output = Conn<Transport>>,
365        Reset: FnOnce(&mut Transport, H3ErrorCode),
366    {
367        self.record_accepted_stream(stream_id);
368        let _guard = self.swansong.guard();
369        let mut buffer: Buffer =
370            Vec::with_capacity(self.context.config.request_buffer_initial_len).into();
371
372        let outcome =
373            Conn::process_first_frame_h3(&self, &mut transport, &mut buffer, stream_id).await;
374
375        match outcome {
376            Ok(H3FirstFrame::Request {
377                validated,
378                start_time,
379            }) => {
380                let conn = Conn::build_h3(
381                    self, transport, buffer, validated, start_time, stream_id, None,
382                );
383                Ok(H3StreamResult::Request(
384                    handler(conn).await.send_h3().await?,
385                ))
386            }
387            Ok(H3FirstFrame::WebTransport { session_id }) => Ok(H3StreamResult::WebTransport {
388                session_id,
389                transport,
390                buffer,
391            }),
392            Err(error) => {
393                if let H3Error::Protocol(code) = &error {
394                    reset(&mut transport, *code);
395                }
396                Err(error)
397            }
398        }
399    }
400
401    /// Decode a QPACK-encoded field section, consulting the dynamic table as needed.
402    ///
403    /// If the field section's Required Insert Count is greater than zero, waits until the
404    /// dynamic table has received enough entries. Returns an error on protocol violations or
405    /// if the encoder stream fails while waiting.
406    ///
407    /// Duplicate pseudo-headers are silently ignored (first value wins). Unknown
408    /// pseudo-headers are rejected.
409    ///
410    /// # Errors
411    ///
412    /// Returns an error if the encoded bytes cannot be parsed as a valid field section.
413    #[cfg(feature = "unstable")]
414    pub async fn decode_field_section(
415        &self,
416        encoded: &[u8],
417        stream_id: u64,
418    ) -> Result<FieldSection<'static>, H3Error> {
419        self.decoder_dynamic_table.decode(encoded, stream_id).await
420    }
421
422    #[cfg(not(feature = "unstable"))]
423    pub(crate) async fn decode_field_section(
424        &self,
425        encoded: &[u8],
426        stream_id: u64,
427    ) -> Result<FieldSection<'static>, H3Error> {
428        self.decoder_dynamic_table.decode(encoded, stream_id).await
429    }
430
431    /// Encode a QPACK field section (no HTTP/3 framing) from pseudo-headers and headers,
432    /// consulting the encoder dynamic table to emit literal-with-name-reference or indexed
433    /// representations as the table's contents allow.
434    ///
435    /// Superseded by [`encode_field_section_framed`][Self::encode_field_section_framed], which
436    /// also frames the section and enforces the peer's field-section size limit.
437    ///
438    /// # Errors
439    ///
440    /// Returns an `H3Error` in case of http/3 semantic error.
441    // Retained only so an older `trillium-client` that predates the framed method still builds
442    // against this crate; remove it at the next breaking release.
443    #[cfg(feature = "unstable")]
444    #[allow(clippy::unnecessary_wraps, reason = "future-proofing api")]
445    pub fn encode_field_section(
446        &self,
447        field_section: &FieldSection<'_>,
448        buf: &mut Vec<u8>,
449        stream_id: u64,
450    ) -> Result<(), H3Error> {
451        self.encoder_dynamic_table
452            .encode(field_section, buf, stream_id);
453        Ok(())
454    }
455
456    /// Encode `field_section` as a complete HTTP/3 HEADERS frame — the QPACK-compressed field
457    /// section prefixed with its `type` + `length` frame header — and append it to `buffer`.
458    ///
459    /// If the peer's `SETTINGS_MAX_FIELD_SECTION_SIZE` is known and the section's
460    /// [`uncompressed_len`][crate::headers::FieldSection] exceeds it, the section is rejected
461    /// before encoding and nothing is written. Until the peer's SETTINGS arrive the limit is
462    /// unknown and unenforced (RFC 9114 §4.2.2).
463    ///
464    /// # Errors
465    ///
466    /// [`io::ErrorKind::InvalidData`] if the field section exceeds the peer's advertised limit, or
467    /// the QPACK encoder's error mapped through [`io::Error::other`].
468    #[cfg(feature = "unstable")]
469    #[doc(hidden)]
470    pub fn encode_field_section_framed(
471        &self,
472        field_section: &FieldSection<'_>,
473        buffer: &mut Vec<u8>,
474        stream_id: u64,
475    ) -> io::Result<()> {
476        self.encode_field_section_framed_impl(field_section, buffer, stream_id)
477    }
478
479    #[cfg(not(feature = "unstable"))]
480    pub(crate) fn encode_field_section_framed(
481        &self,
482        field_section: &FieldSection<'_>,
483        buffer: &mut Vec<u8>,
484        stream_id: u64,
485    ) -> io::Result<()> {
486        self.encode_field_section_framed_impl(field_section, buffer, stream_id)
487    }
488
489    fn encode_field_section_framed_impl(
490        &self,
491        field_section: &FieldSection<'_>,
492        buffer: &mut Vec<u8>,
493        stream_id: u64,
494    ) -> io::Result<()> {
495        // Enforce the peer's SETTINGS_MAX_FIELD_SECTION_SIZE against the uncompressed size the
496        // limit is defined in (RFC 9114 §4.2.2), before encoding — an over-limit section is
497        // rejected without being written to the wire.
498        if let Some(max_size) = self
499            .peer_settings()
500            .and_then(H3Settings::max_field_section_size)
501        {
502            let size = field_section.uncompressed_len();
503            if size > max_size {
504                return Err(io::Error::new(
505                    ErrorKind::InvalidData,
506                    format!("field section would be longer than peer allows ({size} > {max_size})"),
507                ));
508            }
509        }
510
511        let start = buffer.len();
512        self.encoder_dynamic_table
513            .encode(field_section, buffer, stream_id);
514
515        // The HEADERS frame length is the encoded (compressed) payload length, distinct from the
516        // uncompressed size enforced above. Encode the section in place, then open a gap in front
517        // of it for the now-known frame header and shift the section right into place.
518        let section_len = buffer.len() - start;
519        let frame = Frame::Headers(section_len as u64);
520        let frame_header_len = frame.encoded_len();
521        buffer.resize(buffer.len() + frame_header_len, 0);
522        buffer.copy_within(start..start + section_len, start + frame_header_len);
523        frame.encode(&mut buffer[start..start + frame_header_len]);
524        Ok(())
525    }
526
527    /// Run this connection's HTTP/3 outbound control stream.
528    ///
529    /// Sends the initial SETTINGS frame, then sends GOAWAY when the connection shuts down.
530    /// Returns after GOAWAY is sent; keep the stream open until the QUIC connection closes
531    /// (closing a control stream is a connection error).
532    ///
533    /// Shuts the connection down ([`shut_down`][Self::shut_down]) on return, for the same reason
534    /// as [`run_encoder`][Self::run_encoder].
535    ///
536    /// # Errors
537    ///
538    /// Returns an `H3Error` in case of io error or http/3 semantic error.
539    pub async fn run_outbound_control<T>(&self, mut stream: T) -> Result<(), H3Error>
540    where
541        T: AsyncWrite + Unpin + Send,
542    {
543        let result: Result<(), H3Error> = async {
544            let mut buf = vec![0; 128];
545
546            let settings = Frame::Settings(H3Settings::from(&self.context.config));
547            log::trace!(
548                "H3 outbound control: sending SETTINGS: {:?}",
549                H3Settings::from(&self.context.config)
550            );
551
552            write(&mut buf, &mut stream, |buf| {
553                let mut written = quic_varint::encode(UniStreamType::Control, buf)?;
554                written += settings.encode(&mut buf[written..])?;
555                Some(written)
556            })
557            .await?;
558            log::trace!("H3 outbound control: SETTINGS sent");
559
560            self.swansong.clone().await;
561
562            write(&mut buf, &mut stream, |buf| {
563                Frame::Goaway(self.goaway_id()).encode(buf)
564            })
565            .await?;
566
567            Ok(())
568        }
569        .await;
570
571        self.shut_down();
572        result
573    }
574
575    /// Run the outbound QPACK encoder stream for the duration of the connection.
576    ///
577    /// Writes the stream type byte, then drains encoder-stream instructions from the encoder
578    /// dynamic table as they are enqueued. Returns when the connection shuts down or the table is
579    /// marked failed.
580    ///
581    /// Shuts the connection down ([`shut_down`][Self::shut_down]) on return. This stream is
582    /// mandatory for the connection's lifetime, so its termination — clean or errored — means the
583    /// connection can no longer function; marking it shut down lets a pooling caller evict it
584    /// rather than hand it back out. Idempotent on the clean path, which returns only after
585    /// shutdown has already begun.
586    ///
587    /// # Errors
588    ///
589    /// Returns an `H3Error` in case of io error.
590    pub async fn run_encoder<T>(&self, mut stream: T) -> Result<(), H3Error>
591    where
592        T: AsyncWrite + Unpin + Send,
593    {
594        let result = self
595            .encoder_dynamic_table
596            .run_writer(&mut stream, self.swansong.clone())
597            .await;
598        self.shut_down();
599        result
600    }
601
602    /// Run the outbound QPACK decoder stream for the duration of the connection.
603    ///
604    /// Writes the stream type byte, then loops sending Section Acknowledgement and Insert
605    /// Count Increment instructions as they become needed. Returns when the connection
606    /// shuts down.
607    ///
608    /// Shuts the connection down ([`shut_down`][Self::shut_down]) on return, for the same reason
609    /// as [`run_encoder`][Self::run_encoder].
610    ///
611    /// # Errors
612    ///
613    /// Returns an `H3Error` in case of io error or http/3 semantic error.
614    pub async fn run_decoder<T>(&self, mut stream: T) -> Result<(), H3Error>
615    where
616        T: AsyncWrite + Unpin + Send,
617    {
618        let result = self
619            .decoder_dynamic_table
620            .run_writer(&mut stream, self.swansong.clone())
621            .await;
622        self.shut_down();
623        result
624    }
625
626    /// Handle an inbound unidirectional HTTP/3 stream from the peer.
627    ///
628    /// Internal stream types (control, QPACK encoder/decoder) are handled automatically;
629    /// application streams are returned via [`UniStreamResult`] for the caller to process.
630    ///
631    /// On a connection-level protocol error, this method drops the recv stream before
632    /// the caller can react. Quinn's `RecvStream::drop` then sends `STOP_SENDING`, which
633    /// races against the caller's `connection.close` — if the peer responds with a
634    /// malformed `RESET_STREAM` (notably `final_offset = 0`) before our app close is
635    /// applied, the transport-level error overrides our app error code on the wire.
636    /// Use [`process_inbound_uni_with_close`] to thread the close call through the
637    /// function so it fires before the stream drops.
638    ///
639    /// [`process_inbound_uni_with_close`]: Self::process_inbound_uni_with_close
640    ///
641    /// # Errors
642    ///
643    /// Returns a `H3Error` in case of io error or http/3 semantic error.
644    #[deprecated(
645        since = "1.2.0",
646        note = "use `process_inbound_uni_with_close` so connection-level protocol errors close \
647                the QUIC connection before the recv stream drops, avoiding a `FINAL_SIZE_ERROR` \
648                race with the peer's response to STOP_SENDING"
649    )]
650    pub async fn process_inbound_uni<T>(&self, stream: T) -> Result<UniStreamResult<T>, H3Error>
651    where
652        T: AsyncRead + Unpin + Send,
653    {
654        self.process_inbound_uni_with_close(stream, |_| {}).await
655    }
656
657    /// Handle an inbound unidirectional HTTP/3 stream from the peer, calling `on_close` to
658    /// close the QUIC connection if a connection-level protocol error is detected.
659    ///
660    /// Identical to [`process_inbound_uni`][Self::process_inbound_uni] except that on
661    /// any `H3Error::Protocol(code)` whose code is a connection-level error (RFC 9114,
662    /// RFC 9204), `on_close` is invoked with that code while the recv stream is still alive. This
663    /// lets callers send a `CONNECTION_CLOSE` before the stream drops — if the close call sets
664    /// quinn's `conn.error`, quinn's `RecvStream::drop` skips `STOP_SENDING`, eliminating a
665    /// peer race that otherwise causes `FINAL_SIZE_ERROR` to override the app error code.
666    ///
667    /// `on_close` is a `FnOnce` taking `H3ErrorCode`. trillium-http does not itself
668    /// hold the QUIC connection; callers wire up the actual `connection.close()` call
669    /// inside the closure (e.g. quinn's `Connection::close`).
670    ///
671    /// # Errors
672    ///
673    /// Returns a `H3Error` in case of io error or http/3 semantic error.
674    pub async fn process_inbound_uni_with_close<T, OnClose>(
675        &self,
676        mut stream: T,
677        on_close: OnClose,
678    ) -> Result<UniStreamResult<T>, H3Error>
679    where
680        T: AsyncRead + Unpin + Send,
681        OnClose: FnOnce(H3ErrorCode),
682    {
683        let inner = self
684            .swansong
685            .interrupt(self.process_inbound_uni_inner(&mut stream))
686            .await
687            .unwrap_or(Ok(UniInnerResult::Handled)); // interrupted
688
689        match inner {
690            Ok(UniInnerResult::Handled) => Ok(UniStreamResult::Handled),
691            Ok(UniInnerResult::WebTransport { session_id, buffer }) => {
692                Ok(UniStreamResult::WebTransport {
693                    session_id,
694                    stream,
695                    buffer,
696                })
697            }
698            Ok(UniInnerResult::Unknown { stream_type }) => Ok(UniStreamResult::Unknown {
699                stream_type,
700                stream,
701            }),
702            Err(error) => {
703                // Fire `on_close` BEFORE returning so the caller's connection.close
704                // call sets quinn's `conn.error` while `stream` is still alive. When
705                // `stream` then drops at function return, quinn's `RecvStream::drop`
706                // skips STOP_SENDING — preventing the peer-RESET_STREAM race that
707                // otherwise replaces our app close code with FINAL_SIZE_ERROR.
708                if let H3Error::Protocol(code) = &error
709                    && code.is_connection_error()
710                {
711                    on_close(*code);
712                }
713                Err(error)
714            }
715        }
716    }
717
718    /// Inner-loop body of [`process_inbound_uni_with_close`][Self::process_inbound_uni_with_close].
719    /// Borrows `stream` so the outer function can keep ownership of it across the await,
720    /// which lets the caller's close callback fire before the recv stream drops.
721    async fn process_inbound_uni_inner<T>(&self, stream: &mut T) -> Result<UniInnerResult, H3Error>
722    where
723        T: AsyncRead + Unpin + Send,
724    {
725        let mut buf = vec![0; 128];
726        let mut filled = 0;
727
728        // Read stream type varint (decode as raw u64 to handle unknown types)
729        let stream_type = read(&mut buf, &mut filled, stream, |data| {
730            match quic_varint::decode(data) {
731                Ok(ok) => Ok(Some(ok)),
732                Err(QuicVarIntError::UnexpectedEnd) => Ok(None),
733                // this branch is unreachable because u64 is always From<u64>
734                Err(QuicVarIntError::UnknownValue { bytes, value }) => Ok(Some((value, bytes))),
735            }
736        })
737        .await?;
738
739        match UniStreamType::try_from(stream_type) {
740            Ok(UniStreamType::Control) => {
741                log::trace!("H3 inbound uni: control stream");
742                self.run_inbound_control(&mut buf, &mut filled, stream)
743                    .await?;
744                Ok(UniInnerResult::Handled)
745            }
746
747            Ok(UniStreamType::QpackEncoder) => {
748                log::trace!("H3 inbound uni: QPACK encoder stream ({filled} bytes pre-read)");
749                let mut reader = Prepended {
750                    head: &buf[..filled],
751                    tail: stream,
752                };
753
754                log::trace!("QPACK encoder stream: started");
755                self.decoder_dynamic_table.run_reader(&mut reader).await?;
756
757                Ok(UniInnerResult::Handled)
758            }
759
760            Ok(UniStreamType::QpackDecoder) => {
761                log::trace!("H3 inbound uni: QPACK decoder stream ({filled} bytes pre-read)");
762                let mut reader = Prepended {
763                    head: &buf[..filled],
764                    tail: stream,
765                };
766                self.encoder_dynamic_table.run_reader(&mut reader).await?;
767                Ok(UniInnerResult::Handled)
768            }
769
770            Ok(UniStreamType::WebTransport) => {
771                log::trace!("H3 inbound uni: WebTransport stream");
772                let session_id =
773                    read(
774                        &mut buf,
775                        &mut filled,
776                        stream,
777                        |data| match quic_varint::decode(data) {
778                            Ok(ok) => Ok(Some(ok)),
779                            Err(QuicVarIntError::UnexpectedEnd) => Ok(None),
780                            Err(QuicVarIntError::UnknownValue { bytes, value }) => {
781                                Ok(Some((value, bytes)))
782                            }
783                        },
784                    )
785                    .await?;
786
787                buf.truncate(filled);
788
789                Ok(UniInnerResult::WebTransport {
790                    session_id,
791                    buffer: buf.into(),
792                })
793            }
794
795            Ok(UniStreamType::Push) => {
796                // Trillium does not support HTTP/3 push, so we hand these back as `Unknown`
797                // identically to truly-unknown stream types — the explicit arm exists so
798                // trace output names "push stream" rather than a bare type id.
799                log::trace!("H3 inbound uni: push stream (push not supported)");
800                Ok(UniInnerResult::Unknown { stream_type })
801            }
802
803            Err(_) => {
804                log::trace!("H3 inbound uni: unknown stream type {stream_type:#x}");
805                Ok(UniInnerResult::Unknown { stream_type })
806            }
807        }
808    }
809
810    /// Handle the http/3 peer's inbound control stream.
811    ///
812    /// # Errors
813    ///
814    /// Returns a `H3Error` in case of io error or HTTP/3 semantic error.
815    async fn run_inbound_control<T>(
816        &self,
817        buf: &mut Vec<u8>,
818        filled: &mut usize,
819        stream: &mut T,
820    ) -> Result<(), H3Error>
821    where
822        T: AsyncRead + Unpin + Send,
823    {
824        // SettingsError takes priority: a SETTINGS frame whose payload is itself invalid
825        // (e.g. forbidden HTTP/2 setting IDs) is reported as SETTINGS_ERROR, not the
826        // MISSING_SETTINGS we report for everything else here.
827        let settings = read(buf, filled, stream, |data| match Frame::decode(data) {
828            Ok((Frame::Settings(s), consumed)) => Ok(Some((s, consumed))),
829            Err(FrameDecodeError::Incomplete) => Ok(None),
830            Err(FrameDecodeError::Error(H3ErrorCode::SettingsError)) => {
831                Err(H3ErrorCode::SettingsError)
832            }
833            Ok(_) | Err(FrameDecodeError::Error(_)) => Err(H3ErrorCode::MissingSettings),
834        })
835        .await
836        .map_err(map_critical_stream_eof)?;
837
838        log::trace!("H3 peer settings: {settings:?}");
839
840        self.peer_settings
841            .set(settings)
842            .map_err(|_| H3ErrorCode::FrameUnexpected)?;
843        self.wake_peer_settings_waiters();
844
845        self.encoder_dynamic_table
846            .initialize_from_peer_settings(settings);
847
848        loop {
849            let frame = self
850                .swansong
851                .interrupt(read(buf, filled, stream, |data| {
852                    match Frame::decode(data) {
853                        Ok((frame, consumed)) => Ok(Some((frame, consumed))),
854                        Err(FrameDecodeError::Incomplete) => Ok(None),
855                        Err(FrameDecodeError::Error(code)) => Err(code),
856                    }
857                }))
858                .await
859                .transpose()
860                .map_err(map_critical_stream_eof)?;
861
862            match frame {
863                None => {
864                    log::trace!("H3 control stream: interrupted by shutdown");
865                    return Ok(());
866                }
867
868                Some(Frame::Goaway(id)) => {
869                    log::trace!("H3 control stream: peer sent GOAWAY(stream_id={id})");
870                    self.swansong.shut_down();
871                    return Ok(());
872                }
873
874                Some(Frame::Unknown(n)) => {
875                    // Consume the payload bytes so the stream stays synchronized.
876                    log::trace!("H3 control stream: skipping unknown frame (payload {n} bytes)");
877                    let n = usize::try_from(n).unwrap_or(usize::MAX);
878                    let in_buf = n.min(*filled);
879                    buf.copy_within(in_buf..*filled, 0);
880                    *filled -= in_buf;
881                    let mut todo = n - in_buf;
882                    let mut scratch = [0u8; 256];
883                    while todo > 0 {
884                        let to_read = todo.min(scratch.len());
885                        let n = stream
886                            .read(&mut scratch[..to_read])
887                            .await
888                            .map_err(H3Error::Io)?;
889                        if n == 0 {
890                            return Err(H3ErrorCode::ClosedCriticalStream.into());
891                        }
892                        todo -= n;
893                    }
894                }
895
896                Some(
897                    Frame::Settings(_)
898                    | Frame::Data(_)
899                    | Frame::Headers(_)
900                    | Frame::PushPromise { .. }
901                    | Frame::WebTransport(_),
902                ) => {
903                    return Err(H3ErrorCode::FrameUnexpected.into());
904                }
905
906                Some(Frame::PriorityUpdate {
907                    prioritized_element_id,
908                    priority,
909                }) => {
910                    log::trace!(
911                        "H3 control stream: PRIORITY_UPDATE stream={prioritized_element_id} \
912                         priority=\"{priority}\""
913                    );
914                    self.emit_priority_update(prioritized_element_id, priority);
915                }
916
917                // Trillium doesn't implement push, so these are ignored rather than acted on.
918                Some(Frame::CancelPush(_) | Frame::MaxPushId(_)) => {
919                    log::trace!("H3 control stream: ignoring {frame:?}");
920                }
921            }
922        }
923    }
924}
925
926/// A pending HTTP/3 request-response cycle on one bidirectional stream, with optional
927/// per-stream hooks.
928///
929/// Built by [`H3Connection::process_inbound_bidi`]. Configure hooks with the `with_*`
930/// methods and `.await` it to run the cycle. New per-stream extension points are added as
931/// further `with_*` methods, so the entry point's required arguments never change.
932pub struct H3BidiRequest<Transport, Handler> {
933    h3: Arc<H3Connection>,
934    transport: Transport,
935    handler: Handler,
936    stream_id: u64,
937    reset: Option<ResetHook<Transport>>,
938    reject_requests: bool,
939    peer_gone: Option<PeerGone>,
940}
941
942/// Per-stream reset hook: RST both halves with the still-owned transport on a stream-level error.
943type ResetHook<Transport> = Box<dyn FnOnce(&mut Transport, H3ErrorCode) + Send>;
944
945impl<Transport, Handler> std::fmt::Debug for H3BidiRequest<Transport, Handler> {
946    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
947        f.debug_struct("H3BidiRequest")
948            .field("stream_id", &self.stream_id)
949            .finish_non_exhaustive()
950    }
951}
952
953impl<Transport, Handler> H3BidiRequest<Transport, Handler> {
954    /// Issue a stream RST on a stream-level protocol error.
955    ///
956    /// On any `H3Error::Protocol(code)` from first-frame processing, `reset` is called with
957    /// the still-owned transport and the error code before the error is returned — letting the
958    /// caller RST both halves of the bidi stream as RFC 9114 requires. I/O errors and
959    /// successful runs do not invoke it. Without this hook, the transport is dropped without a
960    /// reset.
961    #[must_use]
962    pub fn with_reset<R>(mut self, reset: R) -> Self
963    where
964        R: FnOnce(&mut Transport, H3ErrorCode) + Send + 'static,
965    {
966        self.reset = Some(Box::new(reset));
967        self
968    }
969
970    /// Report peer abandonment of this stream — `STOP_SENDING`, stream reset, or connection
971    /// loss — to [`Conn::is_disconnected`], [`Conn::cancel_on_disconnect`], and
972    /// [`Upgrade::poll_closed`][crate::Upgrade::poll_closed].
973    ///
974    /// QUIC signals departure out-of-band rather than through the byte stream, and an h3
975    /// client half-closes its send side as soon as the request is complete, so reading the
976    /// transport can neither detect abandonment nor be used as a proxy for it. Without this
977    /// hook those three APIs never report a disconnection on HTTP/3.
978    ///
979    /// The future must resolve only on abandonment: an h3 request stream is not finished by
980    /// the peer under normal operation, so a future that also resolves on orderly completion
981    /// (as `quinn::SendStream::stopped` does) is only correct if it is derived from the send
982    /// half of a stream this connection has not yet finished.
983    #[must_use]
984    pub fn with_peer_gone(mut self, peer_gone: PeerGone) -> Self {
985        self.peer_gone = Some(peer_gone);
986        self
987    }
988
989    /// Treat an HTTP request on this stream as a protocol violation instead of running the
990    /// handler.
991    ///
992    /// A peer that accepts inbound bidirectional streams only for negotiated extensions —
993    /// an HTTP/3 client, where RFC 9114 makes a server-initiated request stream a connection
994    /// error — enables this to refuse requests without responding to them. When the stream's
995    /// first frame begins an HTTP request, the handler is skipped, nothing is written to the
996    /// stream, the [`with_reset`][Self::with_reset] hook is invoked with
997    /// [`H3ErrorCode::StreamCreationError`], and awaiting resolves to that error. Closing the
998    /// connection is the caller's responsibility, typically inside the reset hook while the
999    /// stream is still alive.
1000    #[must_use]
1001    pub fn with_request_rejection(mut self) -> Self {
1002        self.reject_requests = true;
1003        self
1004    }
1005}
1006
1007impl<Transport, Handler, Fut> IntoFuture for H3BidiRequest<Transport, Handler>
1008where
1009    Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
1010    Handler: FnOnce(Conn<Transport>) -> Fut + Send + 'static,
1011    Fut: Future<Output = Conn<Transport>> + Send + 'static,
1012{
1013    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
1014    type Output = Result<H3StreamResult<Transport>, H3Error>;
1015
1016    fn into_future(self) -> Self::IntoFuture {
1017        Box::pin(async move {
1018            let Self {
1019                h3,
1020                mut transport,
1021                handler,
1022                stream_id,
1023                reset,
1024                reject_requests,
1025                peer_gone,
1026            } = self;
1027
1028            h3.record_accepted_stream(stream_id);
1029            let _guard = h3.swansong.guard();
1030            let mut buffer: Buffer =
1031                Vec::with_capacity(h3.context.config.request_buffer_initial_len).into();
1032
1033            let outcome =
1034                Conn::process_first_frame_h3(&h3, &mut transport, &mut buffer, stream_id).await;
1035
1036            match outcome {
1037                Ok(H3FirstFrame::Request { .. }) if reject_requests => {
1038                    let code = H3ErrorCode::StreamCreationError;
1039                    if let Some(reset) = reset {
1040                        reset(&mut transport, code);
1041                    }
1042                    Err(code.into())
1043                }
1044                Ok(H3FirstFrame::Request {
1045                    validated,
1046                    start_time,
1047                }) => {
1048                    let initial_priority = validated
1049                        .request_headers
1050                        .get_str(KnownHeaderName::Priority)
1051                        .map(Priority::parse)
1052                        .unwrap_or_default();
1053                    h3.emit_priority(stream_id, initial_priority, false);
1054                    let conn = Conn::build_h3(
1055                        h3, transport, buffer, validated, start_time, stream_id, peer_gone,
1056                    );
1057                    Ok(H3StreamResult::Request(
1058                        handler(conn).await.send_h3().await?,
1059                    ))
1060                }
1061                Ok(H3FirstFrame::WebTransport { session_id }) => Ok(H3StreamResult::WebTransport {
1062                    session_id,
1063                    transport,
1064                    buffer,
1065                }),
1066                Err(error) => {
1067                    if let H3Error::Protocol(code) = &error
1068                        && let Some(reset) = reset
1069                    {
1070                        reset(&mut transport, *code);
1071                    }
1072                    Err(error)
1073                }
1074            }
1075        })
1076    }
1077}
1078
1079/// Map an `UnexpectedEof` I/O error (the `read` helper's "stream FIN'd" signal) to
1080/// `H3_CLOSED_CRITICAL_STREAM`. Closure of the control stream or of either QPACK
1081/// side-channel is a connection error. Other I/O errors and any protocol error are passed
1082/// through unchanged.
1083fn map_critical_stream_eof(error: H3Error) -> H3Error {
1084    match error {
1085        H3Error::Io(e) if e.kind() == ErrorKind::UnexpectedEof => {
1086            H3ErrorCode::ClosedCriticalStream.into()
1087        }
1088        other => other,
1089    }
1090}
1091
1092async fn write(
1093    buf: &mut Vec<u8>,
1094    mut stream: impl AsyncWrite + Unpin + Send,
1095    mut f: impl FnMut(&mut [u8]) -> Option<usize>,
1096) -> io::Result<usize> {
1097    let written = loop {
1098        if let Some(w) = f(buf) {
1099            break w;
1100        }
1101        if buf.len() >= MAX_BUFFER_SIZE {
1102            return Err(io::Error::new(ErrorKind::OutOfMemory, "runaway allocation"));
1103        }
1104        buf.resize(buf.len() * 2, 0);
1105    };
1106
1107    stream.write_all(&buf[..written]).await?;
1108    stream.flush().await?;
1109    Ok(written)
1110}
1111
1112/// An `AsyncRead` adapter that drains a byte slice before reading from an inner stream.
1113///
1114/// Used to replay bytes that were read ahead while parsing a stream-type varint, before
1115/// dispatching to the inner runner that consumes the rest of the stream.
1116struct Prepended<'a, T> {
1117    head: &'a [u8],
1118    tail: T,
1119}
1120
1121impl<T: AsyncRead + Unpin> AsyncRead for Prepended<'_, T> {
1122    fn poll_read(
1123        self: Pin<&mut Self>,
1124        cx: &mut Context<'_>,
1125        out: &mut [u8],
1126    ) -> Poll<io::Result<usize>> {
1127        let this = self.get_mut();
1128        if !this.head.is_empty() {
1129            let n = this.head.len().min(out.len());
1130            out[..n].copy_from_slice(&this.head[..n]);
1131            this.head = &this.head[n..];
1132            return Poll::Ready(Ok(n));
1133        }
1134        Pin::new(&mut this.tail).poll_read(cx, out)
1135    }
1136}
1137
1138/// Read from `stream` into `buf` until `f` can decode a value.
1139///
1140/// `f` receives the filled portion of the buffer and returns:
1141/// - `Ok(Some((value, consumed)))` — success; consumed bytes are removed from the front
1142/// - `Ok(None)` — need more data; reads more bytes and retries
1143/// - `Err(e)` — unrecoverable error; propagated to caller
1144async fn read<R>(
1145    buf: &mut Vec<u8>,
1146    filled: &mut usize,
1147    stream: &mut (impl AsyncRead + Unpin + Send),
1148    f: impl Fn(&[u8]) -> Result<Option<(R, usize)>, H3ErrorCode>,
1149) -> Result<R, H3Error> {
1150    loop {
1151        if let Some((result, consumed)) = f(&buf[..*filled])? {
1152            buf.copy_within(consumed..*filled, 0);
1153            *filled -= consumed;
1154            return Ok(result);
1155        }
1156
1157        if *filled >= buf.len() {
1158            if buf.len() >= MAX_BUFFER_SIZE {
1159                return Err(io::Error::new(ErrorKind::OutOfMemory, "runaway allocation").into());
1160            }
1161            buf.resize(buf.len() * 2, 0);
1162        }
1163
1164        let n = stream.read(&mut buf[*filled..]).await?;
1165        if n == 0 {
1166            return Err(io::Error::new(ErrorKind::UnexpectedEof, "stream closed").into());
1167        }
1168        *filled += n;
1169    }
1170}
1171
1172#[cfg(test)]
1173mod tests;