Skip to main content

zincio_http/h3/
mod.rs

1//! Native HTTP/3 server (RFC 9114) over the [`transport`] abstraction.
2//!
3//! A single connection task owns the control plane ([`control`]) and
4//! accept loops; each accepted request stream is handed to its own task
5//! through an async [`tokio::sync::Mutex`], sharing the connection's
6//! QPACK codecs ([`stream::SharedCodecs`]) with the driver. Requests and
7//! responses are streamed with trailers; `100 Continue` and `103 Early
8//! Hints` interim responses are supported, as are `Date` header caching
9//! and graceful shutdown via a [`CancellationToken`].
10
11mod control;
12mod date;
13mod error;
14mod frame;
15mod options;
16pub mod qpack;
17#[cfg(feature = "h3-quinn")]
18pub mod quinn;
19mod settings;
20mod stream;
21pub mod transport;
22mod upgrade;
23
24pub use error::{H3Error, TransportError};
25pub use frame::{Frame, FrameDecoder, FrameError, Settings};
26pub use options::*;
27
28use std::{
29    pin::Pin,
30    rc::Rc,
31    sync::{
32        atomic::{AtomicBool, Ordering},
33        Arc,
34    },
35    task::{Context, Poll},
36    time::Instant,
37};
38
39use bytes::Bytes;
40use futures_util::stream::FuturesUnordered;
41use futures_util::{ready, Future, FutureExt, StreamExt};
42use http::{Request, Response, StatusCode};
43use http_body::{Body, Frame as BodyFrame};
44use http_body_util::BodyExt;
45use tokio_util::sync::CancellationToken;
46
47use crate::{
48    h3::{
49        control::{ControlEvent, ControlStreams},
50        date::DateCache,
51        stream::{RequestStream, SharedCodecs, StreamError},
52    },
53    EarlyHints, HttpProtocol, Incoming, Upgrade, Upgraded,
54};
55
56/// Application error codes from RFC 9114 Section 8.1 used by the driver.
57const H3_NO_ERROR: u64 = 0x0100;
58const H3_REQUEST_REJECTED: u64 = 0x010b;
59
60/// Per-connection budgets for the resets a hostile peer can force the
61/// server to send or observe (RFC 9114 Section 10.5); `None` disables a
62/// budget.
63#[derive(Debug, Clone, Copy)]
64pub(super) struct ResetLimits {
65    pub(super) max_local_error_resets: Option<usize>,
66    pub(super) max_pending_accept_resets: Option<usize>,
67}
68
69/// Connection-level reset accounting, shared between the driver and the
70/// per-request tasks (which cannot themselves close the QUIC connection).
71#[derive(Debug)]
72struct ConnResetState {
73    limits: ResetLimits,
74    /// RESET_STREAM frames this endpoint has sent for the peer's protocol
75    /// errors (bounded by `limits.max_local_error_resets`).
76    local_error_resets: usize,
77    /// Streams the peer terminated (RESET_STREAM or STOP_SENDING) before
78    /// this endpoint accepted them (bounded by
79    /// `limits.max_pending_accept_resets`).
80    pending_accept_resets: usize,
81    /// Application error code the connection must close with; the driver
82    /// drains it on its next turn.
83    close_code: Option<u64>,
84}
85
86impl ConnResetState {
87    /// Records a locally sent protocol-error reset; returns the code the
88    /// connection must close with when the budget is exceeded.
89    #[inline]
90    fn note_local_error_reset(&mut self) -> Option<u64> {
91        match self.limits.max_local_error_resets {
92            Some(max) if self.local_error_resets >= max => Some(H3Error::ExcessiveLoad.code()),
93            _ => {
94                self.local_error_resets += 1;
95                None
96            }
97        }
98    }
99
100    /// Records a peer-terminated, never-accepted stream; returns the code
101    /// the connection must close with when the budget is exceeded.
102    #[inline]
103    fn note_pending_accept_reset(&mut self) -> Option<u64> {
104        match self.limits.max_pending_accept_resets {
105            Some(max) if self.pending_accept_resets >= max => Some(H3Error::ExcessiveLoad.code()),
106            _ => {
107                self.pending_accept_resets += 1;
108                None
109            }
110        }
111    }
112}
113
114/// The shared handle on a request stream: the connection task, the request
115/// task, the response body, and a possible upgrade all work through it.
116type SharedRequest = Arc<tokio::sync::Mutex<RequestStream>>;
117
118static HTTP3_INVALID_HEADERS: [http::header::HeaderName; 5] = [
119    http::header::HeaderName::from_static("keep-alive"),
120    http::header::HeaderName::from_static("proxy-connection"),
121    http::header::CONNECTION,
122    http::header::TRANSFER_ENCODING,
123    http::header::UPGRADE,
124];
125
126/// The read half of a shared request stream, as a [`Body`].
127pub(crate) struct H3Body {
128    stream: SharedRequest,
129    data_done: bool,
130    send_continue_body: Option<Arc<AtomicBool>>,
131}
132
133impl H3Body {
134    #[inline]
135    fn new(stream: SharedRequest, send_continue_body: Option<Arc<AtomicBool>>) -> Self {
136        Self {
137            stream,
138            data_done: false,
139            send_continue_body,
140        }
141    }
142}
143
144impl Body for H3Body {
145    type Data = Bytes;
146    type Error = std::io::Error;
147
148    #[inline]
149    fn poll_frame(
150        self: Pin<&mut Self>,
151        cx: &mut Context<'_>,
152    ) -> Poll<Option<Result<BodyFrame<Self::Data>, Self::Error>>> {
153        // Safety: H3Body is Unpin (all fields are Unpin), so we can get &mut Self.
154        let this = unsafe { self.get_unchecked_mut() };
155
156        if !this.data_done {
157            loop {
158                let mut stream = match std::pin::pin!(this.stream.lock()).poll_unpin(cx) {
159                    Poll::Ready(stream) => stream,
160                    Poll::Pending => return Poll::Pending,
161                };
162                match stream.poll_recv_data(cx) {
163                    Poll::Ready(Ok(Some(data))) => {
164                        if data.is_empty() {
165                            continue;
166                        }
167                        return Poll::Ready(Some(Ok(BodyFrame::data(data))));
168                    }
169                    Poll::Ready(Ok(None)) => {
170                        drop(stream);
171                        this.data_done = true;
172                        break;
173                    }
174                    Poll::Ready(Err(err)) => {
175                        return Poll::Ready(Some(Err(h3_stream_error_to_io(err))));
176                    }
177                    Poll::Pending => {
178                        if let Some(scb) = this.send_continue_body.as_ref() {
179                            scb.store(true, std::sync::atomic::Ordering::Relaxed);
180                        }
181                        return Poll::Pending;
182                    }
183                };
184            }
185        }
186
187        let mut stream = match std::pin::pin!(this.stream.lock()).poll_unpin(cx) {
188            Poll::Ready(stream) => stream,
189            Poll::Pending => {
190                if let Some(scb) = this.send_continue_body.as_ref() {
191                    scb.store(true, std::sync::atomic::Ordering::Relaxed);
192                }
193                return Poll::Pending;
194            }
195        };
196        match stream.poll_recv_trailers(cx) {
197            Poll::Ready(Ok(Some(trailers))) => Poll::Ready(Some(Ok(BodyFrame::trailers(trailers)))),
198            Poll::Ready(Ok(None)) => Poll::Ready(None),
199            Poll::Ready(Err(err)) => Poll::Ready(Some(Err(h3_stream_error_to_io(err)))),
200            Poll::Pending => {
201                if let Some(scb) = this.send_continue_body.as_ref() {
202                    scb.store(true, std::sync::atomic::Ordering::Relaxed);
203                }
204                Poll::Pending
205            }
206        }
207    }
208}
209
210#[inline]
211fn h3_control_error_to_io(error: control::ControlError) -> std::io::Error {
212    std::io::Error::other(error)
213}
214
215#[inline]
216fn h3_transport_error_to_io(error: TransportError) -> std::io::Error {
217    std::io::Error::other(error)
218}
219
220#[inline]
221fn h3_stream_error_to_io(error: stream::StreamError) -> std::io::Error {
222    std::io::Error::other(error)
223}
224
225#[inline]
226fn remove_invalid_http3_headers(headers: &mut http::HeaderMap) {
227    for header in &HTTP3_INVALID_HEADERS {
228        headers.remove(header);
229    }
230    if headers
231        .get(http::header::TE)
232        .is_some_and(|v| v != "trailers")
233    {
234        headers.remove(http::header::TE);
235    }
236}
237
238/// Waits until the peer's SETTINGS bound the QPACK encoder, so field
239/// sections can be encoded (RFC 9204 Section 5).
240///
241/// The control plane wakes this task via `encoder_notify` when the
242/// SETTINGS frame arrives; no per-stream waker map is needed.
243#[inline]
244async fn wait_for_encoder(shared: &Arc<SharedCodecs>, _stream_id: u64) {
245    loop {
246        if shared.encoder.lock().is_some() {
247            return;
248        }
249        shared.encoder_notify.notified().await;
250    }
251}
252
253/// Writes an interim (1xx) response HEADERS frame.
254#[inline]
255async fn send_interim_response(
256    stream: &SharedRequest,
257    status: StatusCode,
258) -> Result<(), std::io::Error> {
259    let mut guard = stream.lock().await;
260    std::future::poll_fn(|cx| guard.poll_send_response(cx, status, &http::HeaderMap::new()))
261        .await
262        .map_err(h3_stream_error_to_io)
263}
264
265/// Writes the response HEADERS frame for `status`/`headers`, waiting for
266/// the peer's SETTINGS first.
267#[inline]
268async fn send_response(
269    stream: &SharedRequest,
270    shared: &Arc<SharedCodecs>,
271    stream_id: u64,
272    status: StatusCode,
273    headers: &http::HeaderMap,
274) -> Result<(), std::io::Error> {
275    wait_for_encoder(shared, stream_id).await;
276    let mut guard = stream.lock().await;
277    let res = std::future::poll_fn(|cx| guard.poll_send_response(cx, status, headers))
278        .await
279        .map_err(h3_stream_error_to_io);
280    res
281}
282
283/// Writes one response DATA frame.
284#[inline]
285async fn send_data(stream: &SharedRequest, data: Bytes) -> Result<(), std::io::Error> {
286    let mut guard = stream.lock().await;
287    std::future::poll_fn(|cx| guard.poll_send_data(cx, data.clone()))
288        .await
289        .map_err(h3_stream_error_to_io)
290}
291
292/// Writes the response trailers HEADERS frame.
293#[inline]
294async fn send_trailers(
295    stream: &SharedRequest,
296    trailers: &http::HeaderMap,
297) -> Result<(), std::io::Error> {
298    let mut guard = stream.lock().await;
299    std::future::poll_fn(|cx| guard.poll_send_trailers(cx, trailers))
300        .await
301        .map_err(h3_stream_error_to_io)
302}
303
304/// Finishes the response (`FIN`).
305#[inline]
306async fn send_finish(stream: &SharedRequest) -> Result<(), std::io::Error> {
307    let mut guard = stream.lock().await;
308    let result = std::future::poll_fn(|cx| guard.poll_finish(cx))
309        .await
310        .map_err(h3_stream_error_to_io);
311    let result2 = std::future::poll_fn(|cx| guard.poll_stopped(cx))
312        .await
313        .map_err(h3_stream_error_to_io);
314    result.or(result2)
315}
316
317/// A request task's end is observed by the connection driver through the
318/// oneshot completion channel it holds in its `FuturesUnordered`; the
319/// sender is dropped when the task finishes.
320///
321/// Drives one accepted request stream to completion.
322#[allow(clippy::type_complexity)]
323#[allow(clippy::too_many_arguments)]
324async fn handle_request<F, Fut, ResB, ResBE, ResE>(
325    stream: SharedRequest,
326    shared: Arc<SharedCodecs>,
327    stream_id: u64,
328    request_fn: Rc<F>,
329    date_cache: DateCache,
330    send_continue_response: bool,
331    send_date_header: bool,
332    conn_state: Arc<parking_lot::Mutex<ConnResetState>>,
333) where
334    F: Fn(Request<Incoming>) -> Fut,
335    Fut: std::future::Future<Output = Result<Response<ResB>, ResE>>,
336    ResB: Body<Data = Bytes, Error = ResBE> + Unpin,
337    ResE: std::error::Error,
338    ResBE: std::error::Error,
339{
340    // Read the request.
341    let request_headers = {
342        let mut guard = stream.lock().await;
343        std::future::poll_fn(|cx| guard.poll_headers(cx)).await
344    };
345    let request = match request_headers {
346        Ok(Some(request)) => request,
347        // The stream ended without a request: nothing to respond to.
348        Ok(None) => return,
349        Err(err) => {
350            // The peer terminated the stream (RESET_STREAM or
351            // STOP_SENDING) before its request was read: a reset for a
352            // stream that never reached the handler. Bound how many of
353            // these a peer may churn through (RFC 9114 Section 10.5).
354            if err.is_stream_scoped() {
355                let mut state = conn_state.lock();
356                if let Some(code) = state.note_pending_accept_reset() {
357                    state.close_code = Some(code);
358                }
359                return;
360            }
361            // A malformed request message: abort the stream with
362            // `H3_MESSAGE_ERROR` rather than the whole connection (RFC
363            // 9114 Section 4.1.2), bounded by the local-reset budget.
364            if matches!(err, StreamError::Message) {
365                let mut guard = stream.lock().await;
366                let code = err.h3_code();
367                let _ = std::future::poll_fn(|cx| guard.poll_reset(cx, code)).await;
368                let _ = std::future::poll_fn(|cx| guard.poll_stop_sending(cx, code)).await;
369                drop(guard);
370                let mut state = conn_state.lock();
371                if let Some(code) = state.note_local_error_reset() {
372                    state.close_code = Some(code);
373                }
374                return;
375            }
376            // A connection-scoped protocol violation (a malformed frame,
377            // an invalid frame sequence, or a QPACK error): force the
378            // connection to close with the matching H3 code.
379            conn_state.lock().close_code = Some(err.h3_code());
380            return;
381        }
382    };
383
384    // 100 Continue
385    let is_100_continue = send_continue_response
386        && request
387            .headers()
388            .get(http::header::EXPECT)
389            .and_then(|v| v.to_str().ok())
390            .is_some_and(|v| v.eq_ignore_ascii_case("100-continue"));
391
392    let send_continue_body = is_100_continue.then(|| Arc::new(AtomicBool::new(false)));
393    let (request_parts, _) = request.into_parts();
394    let (request_body, upgrade) = if request_parts.method == http::Method::CONNECT {
395        (Incoming::Empty, Some(stream.clone()))
396    } else {
397        (
398            Incoming::Boxed(Box::pin(H3Body::new(
399                stream.clone(),
400                send_continue_body.clone(),
401            ))),
402            None,
403        )
404    };
405    let mut request = Request::from_parts(request_parts, request_body);
406
407    // Install early hints
408    let (early_hints, mut early_hints_rx) = EarlyHints::new_lazy();
409    request.extensions_mut().insert(early_hints);
410
411    // Install HTTP upgrade
412    let upgrade = if let Some(recv_stream) = upgrade {
413        let (upgrade_tx, upgrade_rx) = oneshot::async_channel();
414        let upgrade = Upgrade::new(upgrade_rx);
415        let upgraded = upgrade.upgraded.clone();
416        request.extensions_mut().insert(upgrade);
417        Some((upgrade_tx, upgraded, recv_stream))
418    } else {
419        None
420    };
421
422    let mut response_fut = std::pin::pin!(request_fn(request));
423    let mut early_hints_open = true;
424    let mut continue_sent = false;
425    let response_result = loop {
426        if !early_hints_open {
427            break response_fut.as_mut().await;
428        }
429
430        let next = std::future::poll_fn(|cx| {
431            if let Poll::Ready(res) = response_fut.as_mut().poll(cx) {
432                return Poll::Ready(Some(futures_util::future::Either::Left(res)));
433            }
434
435            match early_hints_rx.poll_recv(cx) {
436                Poll::Ready(Some(msg)) => {
437                    return Poll::Ready(Some(futures_util::future::Either::Right(Ok(msg))))
438                }
439                Poll::Ready(None) => {
440                    return Poll::Ready(Some(futures_util::future::Either::Right(Err(()))))
441                }
442                Poll::Pending => {}
443            }
444
445            if !continue_sent
446                && is_100_continue
447                && send_continue_body
448                    .as_ref()
449                    .is_some_and(|b| b.load(Ordering::Relaxed))
450            {
451                continue_sent = true;
452                return Poll::Ready(None);
453            }
454
455            Poll::Pending
456        })
457        .await;
458
459        match next {
460            // HTTP response
461            Some(futures_util::future::Either::Left(response_result)) => {
462                break response_result;
463            }
464            // 103 Early Hints
465            Some(futures_util::future::Either::Right(Ok((headers, sender)))) => {
466                sender
467                    .into_inner()
468                    .send(
469                        send_response(
470                            &stream,
471                            &shared,
472                            stream_id,
473                            StatusCode::EARLY_HINTS,
474                            &headers,
475                        )
476                        .await,
477                    )
478                    .ok();
479            }
480            Some(futures_util::future::Either::Right(Err(()))) => {
481                early_hints_open = false;
482            }
483            // 100 Continue
484            None => {
485                if send_interim_response(&stream, StatusCode::CONTINUE)
486                    .await
487                    .is_err()
488                {
489                    return;
490                }
491            }
492        }
493    };
494
495    let Ok(mut response) = response_result else {
496        // Return early if the request handler returns an error
497        return;
498    };
499
500    {
501        let response_headers = response.headers_mut();
502        if send_date_header {
503            if let Some(http_date) = date_cache.get_date_header_value() {
504                response_headers
505                    .entry(http::header::DATE)
506                    .or_insert(http_date);
507            }
508        }
509        remove_invalid_http3_headers(response_headers);
510    }
511
512    let response_is_end_stream = response.body().is_end_stream();
513    if !response_is_end_stream {
514        if let Some(content_length) = response.body().size_hint().exact() {
515            if !response
516                .headers()
517                .contains_key(http::header::CONTENT_LENGTH)
518            {
519                response
520                    .headers_mut()
521                    .insert(http::header::CONTENT_LENGTH, content_length.into());
522            }
523        }
524    }
525
526    if is_100_continue
527        && !continue_sent
528        && !response.status().is_client_error()
529        && !response.status().is_server_error()
530        && send_interim_response(&stream, StatusCode::CONTINUE)
531            .await
532            .is_err()
533    {
534        return;
535    }
536
537    let (response_parts, mut response_body) = response.into_parts();
538    if send_response(
539        &stream,
540        &shared,
541        stream_id,
542        response_parts.status,
543        &response_parts.headers,
544    )
545    .await
546    .is_err()
547    {
548        return;
549    }
550
551    if let Some((upgrade_tx, upgraded, recv_stream)) = upgrade {
552        if upgraded.load(Ordering::Relaxed) {
553            let (upgraded, task) = self::upgrade::pair(recv_stream);
554            let _ = upgrade_tx.send(Upgraded::new(upgraded, None));
555            task.await;
556            return;
557        }
558    }
559
560    if !response_is_end_stream {
561        while let Some(chunk) = response_body.frame().await {
562            match chunk {
563                Ok(frame) => {
564                    if frame.is_data() {
565                        match frame.into_data() {
566                            Ok(data) => {
567                                if data.is_empty() {
568                                    // Don't waste bandwidth using empty frames...
569                                    continue;
570                                }
571                                if send_data(&stream, data).await.is_err() {
572                                    return;
573                                }
574                            }
575                            Err(_) => {
576                                return;
577                            }
578                        }
579                    } else if frame.is_trailers() {
580                        match frame.into_trailers() {
581                            Ok(mut trailers) => {
582                                remove_invalid_http3_headers(&mut trailers);
583                                if send_trailers(&stream, &trailers).await.is_err() {
584                                    return;
585                                }
586                                break;
587                            }
588                            Err(_) => {
589                                return;
590                            }
591                        }
592                    }
593                }
594                Err(_) => {
595                    return;
596                }
597            }
598        }
599    }
600
601    let _ = send_finish(&stream).await;
602}
603
604/// An HTTP/3 connection handler.
605///
606/// `Http3` wraps a QUIC connection (`Io`) and drives the HTTP/3 server
607/// connection over the native transport stack. It supports:
608///
609/// - Concurrent request stream handling
610/// - Streaming request/response bodies and trailers
611/// - Automatic `100 Continue` and `103 Early Hints` interim responses
612/// - Per-connection `Date` header caching
613/// - Graceful shutdown via a [`CancellationToken`]
614///
615/// # Construction
616///
617/// ```rust,ignore
618/// let http3 = Http3::new(quic_connection, Http3Options::default());
619/// ```
620///
621/// # Serving requests
622///
623/// Use the [`HttpProtocol`] trait methods ([`handle`](HttpProtocol::handle) /
624/// [`handle_with_error_fn`](HttpProtocol::handle_with_error_fn)) to drive the
625/// connection to completion.
626pub struct Http3<Io> {
627    io_to_handshake: Option<Io>,
628    date_header_value_cached: DateCache,
629    options: Http3Options,
630    cancel_token: Option<CancellationToken>,
631}
632
633impl<Io> Http3<Io>
634where
635    Io: transport::Connection + Unpin + 'static,
636{
637    /// Creates a new `Http3` connection handler wrapping the given QUIC
638    /// connection.
639    ///
640    /// The `options` value controls HTTP/3 protocol configuration, connection
641    /// setup and accept timeouts, and optional behaviour such as automatic
642    /// `100 Continue` responses; see [`Http3Options`] for details.
643    ///
644    /// # Example
645    ///
646    /// ```rust,ignore
647    /// let http3 = Http3::new(quic_connection, Http3Options::default());
648    /// ```
649    #[inline]
650    pub fn new(io: Io, options: Http3Options) -> Self {
651        Self {
652            io_to_handshake: Some(io),
653            date_header_value_cached: DateCache::default(),
654            options,
655            cancel_token: None,
656        }
657    }
658
659    /// Attaches a [`CancellationToken`] for graceful shutdown.
660    ///
661    /// When the token is cancelled, the handler sends an HTTP/3 graceful
662    /// shutdown signal (GOAWAY), stops accepting new request streams, and
663    /// exits cleanly once the in-flight requests have drained.
664    #[inline]
665    pub fn graceful_shutdown_token(mut self, token: CancellationToken) -> Self {
666        self.cancel_token = Some(token);
667        self
668    }
669}
670
671impl<Io> HttpProtocol for Http3<Io>
672where
673    Io: transport::Connection + Unpin + 'static,
674{
675    #[allow(clippy::manual_async_fn)]
676    #[inline]
677    fn handle<F, Fut, ResB, ResBE, ResE>(
678        self,
679        request_fn: F,
680    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
681    where
682        F: Fn(Request<super::Incoming>) -> Fut + 'static,
683        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
684        ResB: http_body::Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
685        ResE: std::error::Error + 'static,
686        ResBE: std::error::Error + 'static,
687    {
688        async move {
689            let request_fn = Rc::new(request_fn);
690            let Http3 {
691                mut io_to_handshake,
692                date_header_value_cached,
693                options,
694                cancel_token,
695            } = self;
696            let mut conn = io_to_handshake
697                .take()
698                .ok_or_else(|| std::io::Error::other("no io to handshake"))?;
699            let date_cache = date_header_value_cached;
700            let send_continue_response = options.send_continue_response;
701            let send_date_header = options.send_date_header;
702
703            // The QUIC handshake may not be complete yet (0-RTT); wait for
704            // it, bounded by the handshake timeout. Server-side QUIC
705            // connections are already complete when handed over.
706            if let Some(timeout) = options.handshake_timeout {
707                zincio::time::timeout(timeout, async {
708                    while !conn.is_handshake_complete() {
709                        zincio::time::sleep(std::time::Duration::from_millis(1)).await;
710                    }
711                })
712                .await
713                .map_err(|_| {
714                    std::io::Error::new(std::io::ErrorKind::TimedOut, "handshake timeout")
715                })?;
716            } else {
717                while !conn.is_handshake_complete() {
718                    zincio::time::sleep(std::time::Duration::from_millis(1)).await;
719                }
720            }
721
722            let mut controls = ControlStreams::new(options.local_settings.clone());
723            let shared = controls.shared().clone();
724            // Request-stream tasks record connection-scoped errors and reset
725            // accounting here; the driver closes the connection with the
726            // recorded H3 code (a request task cannot itself close the QUIC
727            // connection).
728            let conn_state: Arc<parking_lot::Mutex<ConnResetState>> =
729                Arc::new(parking_lot::Mutex::new(ConnResetState {
730                    limits: ResetLimits {
731                        max_local_error_resets: options.max_local_error_reset_streams,
732                        max_pending_accept_resets: options.max_pending_accept_reset_streams,
733                    },
734                    local_error_resets: 0,
735                    pending_accept_resets: 0,
736                    close_code: None,
737                }));
738            let mut ongoing: FuturesUnordered<oneshot::AsyncReceiver<()>> = FuturesUnordered::new();
739            let mut cancel_fut: Option<Pin<Box<dyn std::future::Future<Output = ()> + Send>>> =
740                None;
741            if let Some(token) = cancel_token.as_ref() {
742                cancel_fut = Some(Box::pin(token.cancelled()));
743            }
744            let mut accept_sleep: Option<zincio::time::Sleep> = None;
745            let mut shutdown_sleep: Option<zincio::time::Sleep> = None;
746            // Once graceful shutdown has drained every in-flight request we
747            // must not issue `CONNECTION_CLOSE` immediately: quinn's
748            // `close()` sends exactly one frame and then stops transmitting,
749            // discarding any response bytes still buffered in the send
750            // scheduler. A short grace window lets the background transmit
751            // flush those bytes so the peer observes the response, not the
752            // close.
753            let mut drain_grace: Option<zincio::time::Sleep> = None;
754            let mut shutdown = false;
755            let mut control_dead = false;
756            // When set, the connection is being torn down by a protocol error
757            // and must close with this H3 code (rather than the graceful
758            // GOAWAY + H3_NO_ERROR path).
759            let mut closing_with: Option<u64> = None;
760            let mut outcome: Option<Result<(), std::io::Error>> = None;
761            let mut last_request_id = 0u64;
762
763            // Bring up the control plane (control stream plus QPACK
764            // encoder/decoder streams) and write the initial SETTINGS.
765            std::future::poll_fn(|cx| -> Poll<Result<(), std::io::Error>> {
766                ready!(controls
767                    .poll_init(&mut conn, cx)
768                    .map_err(h3_control_error_to_io))?;
769                ready!(controls.poll_flush(cx).map_err(h3_control_error_to_io))?;
770                Poll::Ready(Ok(()))
771            })
772            .await?;
773
774            std::future::poll_fn(|cx| loop {
775                // A request-stream task hit a connection-scoped error: close
776                // the connection with the H3 code it recorded.
777                if let Some(code) = conn_state.lock().close_code.take() {
778                    if closing_with.is_none() {
779                        closing_with = Some(code);
780                        shutdown = true;
781                        control_dead = true;
782                    }
783                }
784
785                // Accept-timeout window: refreshed whenever a request
786                // stream is accepted; it bounds waiting for the next one.
787                let mut timeout_fired = false;
788                if let Some(sleep) = accept_sleep.as_mut() {
789                    if let Poll::Ready(()) = Pin::new(&mut *sleep).poll(cx) {
790                        accept_sleep = None;
791                        timeout_fired = true;
792                    }
793                } else if let Some(accept_timeout) = options.accept_timeout {
794                    accept_sleep = Some(zincio::time::sleep(accept_timeout));
795                    continue;
796                }
797
798                // Shutdown backstop: while graceful shutdown is pending on
799                // in-flight requests, re-poll periodically so the close
800                // with `H3_NO_ERROR` cannot be starved by a lost wake-up.
801                if let Some(sleep) = shutdown_sleep.as_mut() {
802                    if let Poll::Ready(()) = Pin::new(&mut *sleep).poll(cx) {
803                        sleep.reset(Instant::now() + std::time::Duration::from_millis(10));
804                    }
805                }
806
807                // Graceful shutdown trigger.
808                let mut cancel_fired = false;
809                if let Some(fut) = cancel_fut.as_mut() {
810                    if let Poll::Ready(()) = fut.as_mut().poll(cx) {
811                        cancel_fired = true;
812                    }
813                }
814                if !shutdown {
815                    if cancel_fired {
816                        shutdown = true;
817                        outcome = Some(Ok(()));
818                    } else if timeout_fired {
819                        shutdown = true;
820                        outcome = Some(Err(std::io::Error::new(
821                            std::io::ErrorKind::TimedOut,
822                            "accept timeout",
823                        )));
824                    }
825                }
826
827                // Hand the request streams' queued QPACK encoder
828                // instructions to the control plane.
829                controls.queue_encoder_streams_from_shared(&shared);
830
831                // Write the control plane's outbound streams. If the peer tore
832                // it down while we were shutting down (e.g. an h3 0.0.8 client
833                // resets its receive side once it sees GOAWAY), we stop trying
834                // to flush — the connection is already draining — but we must
835                // NOT close yet: in-flight requests still need to be served so
836                // the peer receives their responses before the application
837                // close. `control_dead` records that state so we neither spin
838                // on a terminal error nor send a close prematurely.
839                if !control_dead {
840                    match controls.poll_flush(cx) {
841                        Poll::Ready(Ok(())) => {}
842                        Poll::Ready(Err(err)) => {
843                            if shutdown {
844                                control_dead = true;
845                            } else {
846                                closing_with = Some(err.h3_code());
847                                shutdown = true;
848                                control_dead = true;
849                            }
850                        }
851                        Poll::Pending => {}
852                    }
853                }
854
855                // Read the peer's control plane and react to its events.
856                if !control_dead {
857                    loop {
858                        match controls.poll_read(&mut conn, cx) {
859                            Poll::Ready(Ok(Some(ControlEvent::Goaway { .. }))) => {
860                                // The client is going away: stop accepting new
861                                // request streams and close once the in-flight
862                                // ones drain.
863                                if !shutdown {
864                                    shutdown = true;
865                                    outcome = Some(Ok(()));
866                                }
867                            }
868                            Poll::Ready(Ok(Some(_))) => {}
869                            Poll::Ready(Ok(None)) => {}
870                            Poll::Ready(Err(err)) => {
871                                if shutdown {
872                                    control_dead = true;
873                                    break;
874                                }
875                                closing_with = Some(err.h3_code());
876                                shutdown = true;
877                                control_dead = true;
878                                break;
879                            }
880                            Poll::Pending => break,
881                        }
882                    }
883                }
884
885                // Shutdown: either a protocol error (close immediately with
886                // the recorded H3 code) or a graceful drain (GOAWAY, then
887                // H3_NO_ERROR once every in-flight request has drained).
888                if shutdown {
889                    if let Some(code) = closing_with {
890                        ready!(conn
891                            .poll_shutdown(cx, code)
892                            .map_err(h3_transport_error_to_io))?;
893                        return Poll::Ready(outcome.take().unwrap_or(Ok(())));
894                    }
895                    if controls.goaway_sent().is_none() {
896                        controls.send_goaway(last_request_id);
897                    }
898                    if ongoing.is_empty() {
899                        // Give the send scheduler a grace window to flush
900                        // the last response before we close. See the comment
901                        // on `drain_grace` above.
902                        if let Some(grace) = drain_grace.as_mut() {
903                            if Pin::new(&mut *grace).poll(cx).is_ready() {
904                                ready!(conn
905                                    .poll_shutdown(cx, H3_NO_ERROR)
906                                    .map_err(h3_transport_error_to_io))?;
907                                return Poll::Ready(outcome.take().unwrap_or(Ok(())));
908                            }
909                        } else {
910                            drain_grace =
911                                Some(zincio::time::sleep(std::time::Duration::from_millis(50)));
912                        }
913                    } else if shutdown_sleep.is_none() {
914                        shutdown_sleep =
915                            Some(zincio::time::sleep(std::time::Duration::from_millis(10)));
916                    }
917                }
918
919                // Accept request streams.
920                loop {
921                    match conn.poll_accept(cx) {
922                        Poll::Ready(Ok(Some(stream))) => {
923                            let id = stream.id();
924                            last_request_id = last_request_id.max(id);
925                            if let Some(sleep) = accept_sleep.as_mut() {
926                                if let Some(timeout) = options.accept_timeout {
927                                    sleep.reset(Instant::now() + timeout);
928                                } else {
929                                    accept_sleep = None;
930                                }
931                            } else {
932                                accept_sleep = None;
933                            }
934                            if shutdown
935                                && (controls.goaway_sent().is_none()
936                                    || id > controls.goaway_sent().unwrap_or(u64::MAX))
937                            {
938                                // A request after our GOAWAY: reject it
939                                // (RFC 9114 Section 5.2).
940                                let mut rejected = RequestStream::new(stream, shared.clone());
941                                let _ = rejected.poll_reset(cx, H3_REQUEST_REJECTED);
942                            } else {
943                                let (end_tx, end_rx) = oneshot::async_channel();
944                                ongoing.push(end_rx);
945                                let request_stream = Arc::new(tokio::sync::Mutex::new(
946                                    RequestStream::new(stream, shared.clone()),
947                                ));
948                                let request_fn = request_fn.clone();
949                                let date_cache = date_cache.clone();
950                                let shared = shared.clone();
951                                let conn_state_for_task = conn_state.clone();
952                                zincio::spawn(async move {
953                                    let _end = end_tx;
954                                    handle_request(
955                                        request_stream,
956                                        shared.clone(),
957                                        id,
958                                        request_fn,
959                                        date_cache,
960                                        send_continue_response,
961                                        send_date_header,
962                                        conn_state_for_task,
963                                    )
964                                    .await;
965                                });
966                            }
967                        }
968                        // The connection closed: nothing left to do.
969                        Poll::Ready(Ok(None)) => {
970                            return Poll::Ready(Ok(()));
971                        }
972                        Poll::Ready(Err(err)) => {
973                            return Poll::Ready(Err(h3_transport_error_to_io(err)));
974                        }
975                        Poll::Pending => {
976                            break;
977                        }
978                    }
979                }
980
981                // Collect finished request tasks. Their completion
982                // receivers were registered on first poll, so parking
983                // below wakes whenever one finishes; a completion can
984                // race the registration, so re-check before parking. An
985                // empty set yields `None` from `poll_next` (there are no
986                // completions to observe).
987                match ongoing.poll_next_unpin(cx) {
988                    Poll::Ready(Some(Ok(()))) => continue,
989                    Poll::Ready(Some(Err(_))) => continue,
990                    Poll::Ready(None) => {}
991                    Poll::Pending => {}
992                }
993                if ongoing.is_empty() && shutdown {
994                    continue;
995                }
996
997                return Poll::Pending;
998            })
999            .await
1000        }
1001    }
1002}