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