Skip to main content

trillium_http/
upgrade.rs

1use crate::{
2    Buffer, Conn, Headers, HttpContext, KnownHeaderName, Method, PeerGone, ProtocolSession,
3    ReceivedBody, Status, TypeSet, Version,
4    h2::H2Connection,
5    h3::{Frame, H3Connection},
6    headers::qpack::{FieldSection, PseudoHeaders},
7    received_body::{H3TrailerFuture, ReceivedBodyState, write_chunk},
8    util::encoding,
9};
10use encoding_rs::Encoding;
11use fieldwork::Fieldwork;
12use futures_lite::{
13    AsyncWriteExt,
14    io::{AsyncRead, AsyncWrite},
15};
16use std::{
17    borrow::Cow,
18    fmt::{self, Debug, Formatter},
19    io::{self, IoSlice, Write},
20    net::IpAddr,
21    pin::Pin,
22    str,
23    sync::Arc,
24    task::{Context, Poll, ready},
25    time::Instant,
26};
27
28/// Per-protocol outbound framing state for an [`Upgrade`], chosen at the upgrade
29/// transition.
30#[derive(Debug)]
31pub(crate) enum WriteState {
32    /// No framing on the `AsyncWrite` path. HTTP/1.1 without chunked encoding (raw
33    /// passthrough) and HTTP/2 (framed at the connection layer).
34    Raw,
35    /// HTTP/1.1 chunked transfer-encoding.
36    H1Chunked(H1ChunkedState),
37    /// HTTP/3 DATA-frame encoding.
38    H3Framed(H3FramedState),
39}
40
41#[derive(Debug, Default)]
42pub(crate) struct H1ChunkedState {
43    pub(crate) pending: Vec<u8>,
44    pub(crate) terminator_written: bool,
45}
46
47#[derive(Debug, Default)]
48pub(crate) struct H3FramedState {
49    pub(crate) pending: Vec<u8>,
50    pub(crate) terminator_written: bool,
51}
52
53/// Pick outbound framing from http version and the outbound headers' `Transfer-Encoding`.
54/// h3 is always DATA-framed; h1 chunks only when the headers request it; h2 is framed by
55/// the connection driver, so the `AsyncWrite` path stays raw.
56fn compute_write_state(version: Version, outbound_headers: &Headers) -> WriteState {
57    match version {
58        Version::Http1_0 | Version::Http1_1 if has_chunked_encoding(outbound_headers) => {
59            WriteState::H1Chunked(H1ChunkedState::default())
60        }
61        Version::Http3 => WriteState::H3Framed(H3FramedState::default()),
62        _ => WriteState::Raw,
63    }
64}
65
66/// True if `Transfer-Encoding` includes `chunked`. Tolerant of multi-codings like
67/// `gzip, chunked`; no ordering enforcement.
68fn has_chunked_encoding(headers: &Headers) -> bool {
69    headers
70        .token_iter(KnownHeaderName::TransferEncoding)
71        .any(|coding| coding.eq_ignore_ascii_case("chunked"))
72}
73
74/// Parse the inbound `Content-Length`. `None` for chunked, missing, or malformed.
75fn parse_content_length(inbound_headers: &Headers) -> Option<u64> {
76    if inbound_headers.has_header(KnownHeaderName::TransferEncoding) {
77        return None;
78    }
79    inbound_headers.content_length()
80}
81
82/// Drain `pending` to `transport`, returning `Pending` if the transport blocks.
83fn poll_drain_pending<T: AsyncWrite + Unpin>(
84    pending: &mut Vec<u8>,
85    cx: &mut Context<'_>,
86    transport: &mut T,
87) -> Poll<io::Result<()>> {
88    while !pending.is_empty() {
89        match Pin::new(&mut *transport).poll_write(cx, pending) {
90            Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())),
91            Poll::Ready(Ok(n)) => {
92                pending.drain(..n);
93            }
94            Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
95            Poll::Pending => return Poll::Pending,
96        }
97    }
98    Poll::Ready(Ok(()))
99}
100
101/// Drain `pending` until the transport blocks or `pending` is empty, without yielding
102/// `Pending`. The next call resumes the drain.
103fn best_effort_drain<T: AsyncWrite + Unpin>(
104    pending: &mut Vec<u8>,
105    cx: &mut Context<'_>,
106    transport: &mut T,
107) -> io::Result<()> {
108    while !pending.is_empty() {
109        match Pin::new(&mut *transport).poll_write(cx, pending) {
110            Poll::Ready(Ok(0)) => return Err(io::ErrorKind::WriteZero.into()),
111            Poll::Ready(Ok(n)) => {
112                pending.drain(..n);
113            }
114            Poll::Ready(Err(e)) => return Err(e),
115            Poll::Pending => break,
116        }
117    }
118    Ok(())
119}
120
121/// Append an HTTP/3 DATA frame header for `payload_len` bytes to `out`. Caller appends
122/// the payload immediately after.
123fn encode_h3_data_header(out: &mut Vec<u8>, payload_len: u64) {
124    let frame = Frame::Data(payload_len);
125    let header_len = frame.encoded_len();
126    let start = out.len();
127    out.resize(start + header_len, 0);
128    frame.encode(&mut out[start..]);
129}
130
131/// An HTTP upgrade — owns the underlying transport along with all the data from the
132/// originating [`Conn`].
133///
134/// **Reading the transport directly**: drain `buffer` first if it has bytes in it. Reading
135/// via the [`AsyncRead`] impl on `Upgrade` handles this automatically.
136#[derive(Fieldwork)]
137#[fieldwork(get, get_mut, set, with, take, into_field, rename_predicates)]
138pub struct Upgrade<Transport> {
139    /// The http headers the peer sent to us
140    #[field(deprecate(was = "request_headers", since = "1.3.0"))]
141    pub(crate) received_headers: Headers,
142
143    /// The http headers as set before the upgrade was negotiated and sent
144    /// to the peer.
145    #[field(deprecate(was = "response_headers", since = "1.3.0"))]
146    pub(crate) sent_headers: Headers,
147
148    /// The request path
149    #[field(get = false)]
150    pub(crate) path: Cow<'static, str>,
151
152    /// The http request method
153    #[field(copy)]
154    pub(crate) method: Method,
155
156    /// Any state that has been accumulated on the Conn before negotiating the upgrade
157    pub(crate) state: TypeSet,
158
159    /// The underlying io (often a `TcpStream` or similar)
160    pub(crate) transport: Transport,
161
162    /// Any bytes that have been read from the underlying transport already.
163    ///
164    /// It is your responsibility to process these bytes before reading directly from the
165    /// transport.
166    #[field(
167        deref = "[u8]",
168        into_field = false,
169        set = false,
170        with = false,
171        get_mut = false
172    )]
173    pub(crate) buffer: Buffer,
174
175    /// The [`HttpContext`] shared for this server
176    #[field(deref = false)]
177    pub(crate) context: Arc<HttpContext>,
178
179    /// the ip address of the connection, if available
180    #[field(copy)]
181    pub(crate) peer_ip: Option<IpAddr>,
182
183    /// the wall-clock time at which the underlying [`Conn`] was constructed
184    #[field(copy)]
185    pub(crate) start_time: Instant,
186
187    /// the :authority http/3 pseudo-header
188    pub(crate) authority: Option<Cow<'static, str>>,
189
190    /// the :scheme http/3 pseudo-header
191    pub(crate) scheme: Option<Cow<'static, str>>,
192
193    /// the [`ProtocolSession`] for this upgrade — h2/h3 connection driver + stream id
194    /// where applicable; `Http1` for upgrades from h1 or synthetic conns.
195    #[field = false]
196    pub(crate) protocol_session: ProtocolSession,
197
198    /// the :protocol http/3 pseudo-header
199    pub(crate) protocol: Option<Cow<'static, str>>,
200
201    /// the http version
202    #[field = "http_version"]
203    pub(crate) version: Version,
204
205    /// the http response status set on the underlying [`Conn`] before the upgrade
206    /// (typically `101 Switching Protocols`, or `200 OK` for CONNECT). `None` if unset.
207    #[field(copy)]
208    pub(crate) status: Option<Status>,
209
210    /// whether this connection was deemed secure by the handler stack
211    pub(crate) secure: bool,
212
213    /// Inbound framing state carried across the upgrade so the inbound state machine
214    /// resumes where the pre-upgrade handler left off. Request-body state on server
215    /// upgrades; response-body state on client upgrades.
216    #[field = false]
217    pub(crate) received_body_state: ReceivedBodyState,
218
219    /// Inbound trailers, populated either by a fully-consumed pre-upgrade body or by
220    /// the post-upgrade inbound state machine. `Some` only when non-empty.
221    #[field(get, get_mut, take, set = false, with = false, into_field = false)]
222    pub(crate) received_trailers: Option<Headers>,
223
224    /// Pre-parsed inbound `Content-Length`. `None` for chunked, missing, or malformed.
225    #[field = false]
226    pub(crate) content_length_in: Option<u64>,
227
228    /// Per-protocol outbound framing state. Decided at the upgrade transition.
229    #[field = false]
230    pub(crate) write_state: WriteState,
231
232    /// Charset of the inbound body, parsed from the inbound `Content-Type`'s `charset`
233    /// parameter at the upgrade transition.
234    #[field = false]
235    pub(crate) inbound_encoding: &'static Encoding,
236
237    /// In-flight QPACK trailer-decode future for inbound h3 trailing HEADERS. Held here
238    /// so its registered waker survives across `poll_read` calls — dropping the future
239    /// would drop the waker the QPACK decoder is parked on, hanging the reader.
240    #[field = false]
241    pub(crate) h3_trailer_decode_in: Option<H3TrailerFuture>,
242
243    /// Accumulator for inbound h3 trailing-HEADERS payload bytes pre-QPACK-decode.
244    /// Separate from [`Self::buffer`] so the inbound state machine doesn't recycle
245    /// accumulated trailer bytes back through the frame decoder and double-count them.
246    #[field = false]
247    pub(crate) h3_trailer_payload_in: Vec<u8>,
248
249    /// Resolves when the peer abandons this stream. HTTP/3 only; see [`PeerGone`].
250    #[field = false]
251    pub(crate) peer_gone: Option<PeerGone>,
252}
253
254impl<Transport> Upgrade<Transport> {
255    #[doc(hidden)]
256    pub fn new(
257        received_headers: Headers,
258        path: impl Into<Cow<'static, str>>,
259        method: Method,
260        transport: Transport,
261        buffer: Buffer,
262        version: Version,
263    ) -> Self {
264        Self {
265            received_headers,
266            sent_headers: Headers::new(),
267            path: path.into(),
268            method,
269            transport,
270            buffer,
271            state: TypeSet::new(),
272            context: Arc::default(),
273            peer_ip: None,
274            start_time: Instant::now(),
275            authority: None,
276            scheme: None,
277            protocol_session: ProtocolSession::Http1,
278            protocol: None,
279            secure: false,
280            version,
281            status: None,
282            received_body_state: ReceivedBodyState::Raw { total: 0 },
283            received_trailers: None,
284            content_length_in: None,
285            write_state: WriteState::Raw,
286            inbound_encoding: encoding_rs::UTF_8,
287            h3_trailer_decode_in: None,
288            h3_trailer_payload_in: Vec::new(),
289            peer_gone: None,
290        }
291    }
292
293    #[cfg(feature = "unstable")]
294    #[doc(hidden)]
295    #[allow(clippy::too_many_arguments)]
296    pub fn from_parts(
297        received_headers: Headers,
298        sent_headers: Headers,
299        path: Cow<'static, str>,
300        method: Method,
301        transport: Transport,
302        buffer: Buffer,
303        state: TypeSet,
304        context: Arc<HttpContext>,
305        peer_ip: Option<IpAddr>,
306        authority: Option<Cow<'static, str>>,
307        scheme: Option<Cow<'static, str>>,
308        protocol_session: ProtocolSession,
309        protocol: Option<Cow<'static, str>>,
310        version: Version,
311        status: Option<Status>,
312        secure: bool,
313        received_body_state: ReceivedBodyState,
314        received_trailers: Option<Headers>,
315    ) -> Self {
316        let write_state = compute_write_state(version, &sent_headers);
317        let content_length_in = parse_content_length(&received_headers);
318        let inbound_encoding = encoding(&received_headers);
319
320        Self {
321            // Client-side upgrades have no peer-departure hook; on h3 that leaves
322            // `poll_closed` permanently pending. Client h3 resets surface as read errors.
323            peer_gone: None,
324            received_headers,
325            sent_headers,
326            path,
327            method,
328            state,
329            transport,
330            buffer,
331            context,
332            peer_ip,
333            start_time: Instant::now(),
334            authority,
335            scheme,
336            protocol_session,
337            protocol,
338            version,
339            status,
340            secure,
341            received_body_state,
342            received_trailers,
343            content_length_in,
344            write_state,
345            inbound_encoding,
346            h3_trailer_decode_in: None,
347            h3_trailer_payload_in: Vec::new(),
348        }
349    }
350
351    /// the [`H2Connection`] driver for this upgrade, if it originated from an HTTP/2 stream
352    pub fn h2_connection(&self) -> Option<&Arc<H2Connection>> {
353        self.protocol_session.h2_connection()
354    }
355
356    /// the h2 stream id for this upgrade, if it originated from an HTTP/2 stream
357    pub fn h2_stream_id(&self) -> Option<u32> {
358        self.protocol_session.h2_stream_id()
359    }
360
361    /// the [`H3Connection`] driver for this upgrade, if it originated from an HTTP/3 stream
362    pub fn h3_connection(&self) -> Option<&Arc<H3Connection>> {
363        self.protocol_session.h3_connection()
364    }
365
366    /// the h3 stream id for this upgrade, if it originated from an HTTP/3 stream
367    pub fn h3_stream_id(&self) -> Option<u64> {
368        self.protocol_session.h3_stream_id()
369    }
370
371    /// Take any buffered bytes
372    pub fn take_buffer(&mut self) -> Vec<u8> {
373        std::mem::take(&mut self.buffer).into()
374    }
375
376    /// Mutably borrow any bytes that have already been read from the underlying transport.
377    ///
378    /// It is your responsibility to process these bytes before reading directly from the
379    /// transport.
380    pub fn buffer_mut(&mut self) -> &mut [u8] {
381        self.buffer.live_mut()
382    }
383
384    #[doc(hidden)]
385    pub fn buffer_and_transport_mut(&mut self) -> (&mut Buffer, &mut Transport) {
386        (&mut self.buffer, &mut self.transport)
387    }
388
389    /// borrow the shared state [`TypeSet`] for this application
390    pub fn shared_state(&self) -> &TypeSet {
391        self.context.shared_state()
392    }
393
394    /// the http request path up to but excluding any query component
395    pub fn path(&self) -> &str {
396        match self.path.split_once('?') {
397            Some((path, _)) => path,
398            None => &self.path,
399        }
400    }
401
402    /// retrieves the query component of the path
403    pub fn querystring(&self) -> &str {
404        self.path
405            .split_once('?')
406            .map(|(_, query)| query)
407            .unwrap_or_default()
408    }
409
410    /// Modify the transport type of this upgrade.
411    ///
412    /// This is useful for boxing the transport in order to erase the type argument.
413    pub fn map_transport<T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static>(
414        self,
415        f: impl Fn(Transport) -> T,
416    ) -> Upgrade<T> {
417        // Manual respread: rustc rejects `..self` across a type parameter change without
418        // the unstable `type_changing_struct_update` feature. New fields on `Upgrade`
419        // need to be added here, in `Conn::map_transport`, and in `From<Conn> for Upgrade`.
420        Upgrade {
421            transport: f(self.transport),
422            path: self.path,
423            method: self.method,
424            state: self.state,
425            buffer: self.buffer,
426            received_headers: self.received_headers,
427            sent_headers: self.sent_headers,
428            context: self.context,
429            peer_ip: self.peer_ip,
430            start_time: self.start_time,
431            authority: self.authority,
432            scheme: self.scheme,
433            protocol_session: self.protocol_session,
434            protocol: self.protocol,
435            version: self.version,
436            status: self.status,
437            secure: self.secure,
438            received_body_state: self.received_body_state,
439            received_trailers: self.received_trailers,
440            content_length_in: self.content_length_in,
441            write_state: self.write_state,
442            inbound_encoding: self.inbound_encoding,
443            h3_trailer_decode_in: self.h3_trailer_decode_in,
444            h3_trailer_payload_in: self.h3_trailer_payload_in,
445            peer_gone: self.peer_gone,
446        }
447    }
448}
449
450impl<Transport: AsyncRead + Unpin> Upgrade<Transport> {
451    /// Resolves when the peer has abandoned this upgrade — connection closed, stream
452    /// reset, or transport error.
453    ///
454    /// This is a liveness probe for upgrades the peer is not expected to speak into,
455    /// such as a server-sent event stream. If the peer may legitimately send data, read
456    /// it instead: on HTTP/1.x this probe detects closure *by reading*, treating inbound
457    /// bytes as incidental. Probed bytes accumulate on the internal buffer — where the
458    /// [`AsyncRead`] impl yields them before touching the transport — until the buffer
459    /// holds `read_allowance` bytes, after which the probe goes dormant, returning
460    /// `Poll::Pending` without scheduling a wake until reads drain the buffer.
461    ///
462    /// Per-protocol behavior:
463    /// - HTTP/1.x: reads the transport; end-of-file or a transport error resolves. A half-closed
464    ///   peer that shut down its write side but still reads is indistinguishable from a departed
465    ///   one and counts as closed.
466    /// - HTTP/2: resolves when the stream is reset or fully closed, or the connection is torn down.
467    ///   Never reads the transport; `read_allowance` is unused.
468    /// - HTTP/3: resolves on `STOP_SENDING`, stream reset, or connection loss. Never reads the
469    ///   transport; `read_allowance` is unused. Requires the runtime adapter to have supplied
470    ///   [`H3BidiRequest::with_peer_gone`][crate::h3::H3BidiRequest::with_peer_gone]; without it,
471    ///   this never resolves.
472    ///
473    /// A client that vanishes without signalling — a killed process, a severed network — is
474    /// only detected once the transport notices. On HTTP/3 that is QUIC's idle timeout, which
475    /// is always negotiated and typically under a minute. On HTTP/1.x and HTTP/2 it depends on
476    /// the TCP configuration and can be considerably longer.
477    pub fn poll_closed(
478        self: Pin<&mut Self>,
479        cx: &mut Context<'_>,
480        read_allowance: usize,
481    ) -> Poll<()> {
482        let Self {
483            protocol_session,
484            version,
485            buffer,
486            transport,
487            peer_gone,
488            ..
489        } = self.get_mut();
490
491        crate::liveness::poll_peer_gone(
492            protocol_session,
493            *version,
494            buffer,
495            transport,
496            peer_gone.as_mut(),
497            read_allowance,
498            cx,
499        )
500    }
501}
502
503impl<Transport: AsyncWrite + Unpin> Upgrade<Transport> {
504    /// Emit trailing headers and finish the outbound stream. Consumes `self`; further
505    /// writes are statically prevented.
506    ///
507    /// Per-protocol behavior:
508    /// - HTTP/1.1 with `Transfer-Encoding: chunked`: writes the last-chunk marker (`0\r\n`), the
509    ///   trailer section, and a final CRLF, then closes the transport.
510    /// - HTTP/2: enqueues a trailing `HEADERS` frame with `END_STREAM` via the connection driver
511    ///   and returns. The driver finishes the stream after draining any pending DATA frames.
512    /// - HTTP/3: encodes a trailing `HEADERS` frame via QPACK, writes it to the stream, then closes
513    ///   the stream (QUIC `FIN`).
514    /// - HTTP/1.1 without chunked encoding (raw upgrade, CONNECT tunnel, websocket-over-h1):
515    ///   trailers can't be expressed on the wire; dropped with a `log::warn!` and `Ok(())`
516    ///   returned.
517    ///
518    /// # Errors
519    ///
520    /// Returns the underlying [`io::Error`] when the wire write fails, `BrokenPipe` if
521    /// the stream has already been closed, and `NotConnected` if the carried
522    /// `ProtocolSession` is missing the expected driver for h2/h3.
523    pub async fn send_trailers(self, trailers: Headers) -> io::Result<()> {
524        let Self {
525            mut transport,
526            mut write_state,
527            context,
528            protocol_session,
529            ..
530        } = self;
531
532        match &mut write_state {
533            WriteState::H1Chunked(state) => {
534                if state.terminator_written {
535                    return Err(io::ErrorKind::BrokenPipe.into());
536                }
537                state.pending.extend_from_slice(b"0\r\n");
538                crate::conn::write_headers_or_trailers(&mut state.pending, &trailers, &context)
539                    .map_err(io::Error::other)?;
540                state.pending.extend_from_slice(b"\r\n");
541                state.terminator_written = true;
542
543                transport.write_all(&state.pending).await?;
544                state.pending.clear();
545                transport.close().await
546            }
547            WriteState::H3Framed(state) => {
548                if state.terminator_written {
549                    return Err(io::ErrorKind::BrokenPipe.into());
550                }
551                let Some((h3, stream_id)) = protocol_session.as_h3() else {
552                    return Err(io::ErrorKind::NotConnected.into());
553                };
554                let field_section = FieldSection::new(PseudoHeaders::default(), &trailers);
555                h3.encode_field_section_framed(&field_section, &mut state.pending, stream_id)?;
556                state.terminator_written = true;
557
558                transport.write_all(&state.pending).await?;
559                state.pending.clear();
560                transport.close().await
561            }
562            WriteState::Raw => {
563                if let Some((h2, stream_id)) = protocol_session.as_h2() {
564                    h2.submit_trailers(stream_id, trailers)
565                } else {
566                    log::warn!(
567                        "Upgrade::send_trailers called on a raw upgrade with no per-stream \
568                         framing; trailers dropped. Set `Transfer-Encoding: chunked` on the \
569                         outbound headers if you intend to emit trailers over HTTP/1.1."
570                    );
571                    Ok(())
572                }
573            }
574        }
575    }
576}
577
578impl<Transport> Debug for Upgrade<Transport> {
579    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
580        f.debug_struct(&format!("Upgrade<{}>", std::any::type_name::<Transport>()))
581            .field("received_headers", &self.received_headers)
582            .field("sent_headers", &self.sent_headers)
583            .field("path", &self.path)
584            .field("method", &self.method)
585            .field("buffer", &self.buffer)
586            .field("context", &self.context)
587            .field("state", &self.state)
588            .field("transport", &format_args!(".."))
589            .field("peer_ip", &self.peer_ip)
590            .field("start_time", &self.start_time)
591            .field("authority", &self.authority)
592            .field("scheme", &self.scheme)
593            .field("protocol_session", &self.protocol_session)
594            .field("protocol", &self.protocol)
595            .field("version", &self.version)
596            .field("status", &self.status)
597            .field("secure", &self.secure)
598            .field("received_body_state", &self.received_body_state)
599            .field("received_trailers", &self.received_trailers)
600            .field("content_length_in", &self.content_length_in)
601            .field("write_state", &self.write_state)
602            .field("inbound_encoding", &self.inbound_encoding.name())
603            .field(
604                "h3_trailer_decode_in",
605                &self
606                    .h3_trailer_decode_in
607                    .as_ref()
608                    .map(|_| format_args!("..")),
609            )
610            .field(
611                "h3_trailer_payload_in_len",
612                &self.h3_trailer_payload_in.len(),
613            )
614            .field(
615                "peer_gone",
616                &self.peer_gone.as_ref().map(|_| format_args!("..")),
617            )
618            .finish()
619    }
620}
621
622impl<Transport> From<Conn<Transport>> for Upgrade<Transport> {
623    fn from(conn: Conn<Transport>) -> Self {
624        // Exhaustive destructure so new fields on `Conn` force a deliberate carry-vs-drop
625        // decision. Shared drift hazard with `Conn::map_transport` and `Upgrade::map_transport`.
626        let Conn {
627            request_headers,
628            response_headers,
629            path,
630            method,
631            state,
632            transport,
633            buffer,
634            context,
635            peer_ip,
636            start_time,
637            authority,
638            scheme,
639            protocol_session,
640            protocol,
641            version,
642            status,
643            secure,
644            request_body_state,
645            request_trailers,
646            response_body,
647            // post-send hooks no longer apply; `upgrade` is the marker that brought us here
648            after_send: _,
649            upgrade: _,
650            peer_gone,
651        } = conn;
652
653        if let Some(body) = &response_body
654            && !body.is_empty()
655        {
656            log::warn!(
657                "Conn::upgrade() and a non-empty response body are both set; body is being \
658                 discarded. The upgrade path is mutually exclusive with serving a response body."
659            );
660        }
661
662        // Server-side roles: outbound = response_headers, inbound = request_headers.
663        let write_state = compute_write_state(version, &response_headers);
664        let content_length_in = parse_content_length(&request_headers);
665        let inbound_encoding = encoding(&request_headers);
666        // An h1 request with no framing headers parses to `End` — correct for the request
667        // body, but inherited across the upgrade it would EOF the first read of a live raw
668        // stream (a browser websocket handshake is exactly this shape). Declared framing
669        // does carry over: a chunked request keeps chunked inbound framing.
670        let received_body_state = if matches!(version, Version::Http1_0 | Version::Http1_1)
671            && !request_headers.has_header(KnownHeaderName::TransferEncoding)
672            && !request_headers.has_header(KnownHeaderName::ContentLength)
673        {
674            ReceivedBodyState::Raw { total: 0 }
675        } else {
676            request_body_state
677        };
678        let received_trailers = request_trailers.filter(|t| !t.is_empty());
679
680        Self {
681            received_headers: request_headers,
682            sent_headers: response_headers,
683            path,
684            method,
685            state,
686            transport,
687            buffer,
688            context,
689            peer_ip,
690            start_time,
691            authority,
692            scheme,
693            protocol_session,
694            protocol,
695            version,
696            status,
697            secure,
698            received_body_state,
699            received_trailers,
700            content_length_in,
701            write_state,
702            inbound_encoding,
703            h3_trailer_decode_in: None,
704            h3_trailer_payload_in: Vec::new(),
705            peer_gone,
706        }
707    }
708}
709
710#[cfg(test)]
711mod tests;
712
713impl<Transport> AsyncRead for Upgrade<Transport>
714where
715    Transport: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
716{
717    fn poll_read(
718        mut self: Pin<&mut Self>,
719        cx: &mut Context<'_>,
720        buf: &mut [u8],
721    ) -> Poll<io::Result<usize>> {
722        let Self {
723            transport,
724            buffer,
725            received_body_state,
726            content_length_in,
727            context,
728            protocol_session,
729            received_trailers,
730            h3_trailer_decode_in,
731            h3_trailer_payload_in,
732            inbound_encoding,
733            ..
734        } = &mut *self;
735
736        let protocol_session = protocol_session.clone();
737        let mut body: ReceivedBody<'_, Transport> = ReceivedBody::new_with_config(
738            *content_length_in,
739            buffer,
740            transport,
741            received_body_state,
742            None,
743            inbound_encoding,
744            &context.config,
745        )
746        .with_trailers(received_trailers)
747        .with_protocol_session(protocol_session)
748        .with_h3_trailer_future(h3_trailer_decode_in)
749        .with_h3_trailer_payload_buffer(h3_trailer_payload_in);
750
751        Pin::new(&mut body).poll_read(cx, buf)
752    }
753}
754
755impl<Transport: AsyncWrite + Unpin> AsyncWrite for Upgrade<Transport> {
756    fn poll_write(
757        mut self: Pin<&mut Self>,
758        cx: &mut Context<'_>,
759        buf: &[u8],
760    ) -> Poll<io::Result<usize>> {
761        let Self {
762            transport,
763            write_state,
764            ..
765        } = &mut *self;
766        match write_state {
767            WriteState::Raw => Pin::new(transport).poll_write(cx, buf),
768            WriteState::H1Chunked(state) => {
769                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
770
771                // Empty buf must not become a chunk: `0\r\n` IS the last-chunk marker.
772                if buf.is_empty() {
773                    return Poll::Ready(Ok(0));
774                }
775
776                if state.terminator_written {
777                    return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
778                }
779
780                write_chunk(&mut state.pending, buf);
781                best_effort_drain(&mut state.pending, cx, transport)?;
782                Poll::Ready(Ok(buf.len()))
783            }
784            WriteState::H3Framed(state) => {
785                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
786
787                if buf.is_empty() {
788                    return Poll::Ready(Ok(0));
789                }
790
791                if state.terminator_written {
792                    return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
793                }
794
795                encode_h3_data_header(&mut state.pending, buf.len() as u64);
796                state.pending.extend_from_slice(buf);
797                best_effort_drain(&mut state.pending, cx, transport)?;
798                Poll::Ready(Ok(buf.len()))
799            }
800        }
801    }
802
803    fn poll_write_vectored(
804        mut self: Pin<&mut Self>,
805        cx: &mut Context<'_>,
806        bufs: &[IoSlice<'_>],
807    ) -> Poll<io::Result<usize>> {
808        let Self {
809            transport,
810            write_state,
811            ..
812        } = &mut *self;
813        match write_state {
814            WriteState::Raw => Pin::new(transport).poll_write_vectored(cx, bufs),
815            WriteState::H1Chunked(state) => {
816                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
817                let total: usize = bufs.iter().map(|b| b.len()).sum();
818                if total == 0 {
819                    return Poll::Ready(Ok(0));
820                }
821                if state.terminator_written {
822                    return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
823                }
824                // One chunk per vectored batch — the default impl would emit one chunk per
825                // iobuf, which is wasteful when the caller meant them as one logical write.
826                let _ = write!(state.pending, "{total:X}\r\n");
827                for b in bufs {
828                    state.pending.extend_from_slice(b);
829                }
830                state.pending.extend_from_slice(b"\r\n");
831                best_effort_drain(&mut state.pending, cx, transport)?;
832                Poll::Ready(Ok(total))
833            }
834            WriteState::H3Framed(state) => {
835                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
836                let total: usize = bufs.iter().map(|b| b.len()).sum();
837                if total == 0 {
838                    return Poll::Ready(Ok(0));
839                }
840                if state.terminator_written {
841                    return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
842                }
843                // One DATA frame per vectored batch — collapses `[length_prefix, payload]`
844                // pairs into a single frame.
845                encode_h3_data_header(&mut state.pending, total as u64);
846                for b in bufs {
847                    state.pending.extend_from_slice(b);
848                }
849                best_effort_drain(&mut state.pending, cx, transport)?;
850                Poll::Ready(Ok(total))
851            }
852        }
853    }
854
855    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
856        let Self {
857            transport,
858            write_state,
859            ..
860        } = &mut *self;
861        match write_state {
862            WriteState::Raw => Pin::new(transport).poll_flush(cx),
863            WriteState::H1Chunked(state) => {
864                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
865                Pin::new(transport).poll_flush(cx)
866            }
867            WriteState::H3Framed(state) => {
868                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
869                Pin::new(transport).poll_flush(cx)
870            }
871        }
872    }
873
874    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
875        let Self {
876            transport,
877            write_state,
878            ..
879        } = &mut *self;
880        match write_state {
881            WriteState::Raw => Pin::new(transport).poll_close(cx),
882            WriteState::H1Chunked(state) => {
883                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
884                if !state.terminator_written {
885                    state.pending.extend_from_slice(b"0\r\n\r\n");
886                    // Flag set before the drain so a re-poll after Pending doesn't re-append.
887                    state.terminator_written = true;
888                }
889                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
890                Pin::new(transport).poll_close(cx)
891            }
892            WriteState::H3Framed(state) => {
893                // h3 stream-end is the QUIC FIN — no separate terminator frame.
894                ready!(poll_drain_pending(&mut state.pending, cx, transport))?;
895                state.terminator_written = true;
896                Pin::new(transport).poll_close(cx)
897            }
898        }
899    }
900}