Skip to main content

tokio_quiche/http3/driver/
server.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::ops::Deref;
28use std::sync::Arc;
29
30use tokio::sync::mpsc;
31
32use super::datagram;
33use super::DriverHooks;
34use super::H3Command;
35use super::H3ConnectionError;
36use super::H3ConnectionResult;
37use super::H3Controller;
38use super::H3Driver;
39use super::H3Event;
40use super::InboundHeaders;
41use super::IncomingH3Headers;
42use super::StreamCtx;
43use super::STREAM_CAPACITY;
44use crate::http3::settings::Http3Settings;
45use crate::http3::settings::Http3SettingsEnforcer;
46use crate::http3::settings::Http3TimeoutType;
47use crate::http3::settings::TimeoutKey;
48use crate::quic::HandshakeInfo;
49use crate::quic::QuicCommand;
50use crate::quic::QuicheConnection;
51
52/// An [H3Driver] for a server-side HTTP/3 connection. See [H3Driver] for
53/// details. Emits [`ServerH3Event`]s and expects [`ServerH3Command`]s for
54/// control.
55pub type ServerH3Driver = H3Driver<ServerHooks>;
56/// The [H3Controller] type paired with [ServerH3Driver]. See [H3Controller] for
57/// details.
58pub type ServerH3Controller = H3Controller<ServerHooks>;
59/// Receives [`ServerH3Event`]s from a [ServerH3Driver]. This is the control
60/// stream which describes what is happening on the connection, but does not
61/// transfer data.
62pub type ServerEventStream = mpsc::UnboundedReceiver<ServerH3Event>;
63
64#[derive(Clone, Debug)]
65pub struct RawPriorityValue(Vec<u8>);
66
67impl From<Vec<u8>> for RawPriorityValue {
68    fn from(value: Vec<u8>) -> Self {
69        RawPriorityValue(value)
70    }
71}
72
73impl Deref for RawPriorityValue {
74    type Target = [u8];
75
76    fn deref(&self) -> &Self::Target {
77        &self.0
78    }
79}
80
81/// The request was received during early data (0-RTT).
82#[derive(Clone, Debug)]
83pub struct IsInEarlyData(bool);
84
85impl IsInEarlyData {
86    fn new(is_in_early_data: bool) -> Self {
87        IsInEarlyData(is_in_early_data)
88    }
89}
90
91impl Deref for IsInEarlyData {
92    type Target = bool;
93
94    fn deref(&self) -> &Self::Target {
95        &self.0
96    }
97}
98
99/// Events produced by [ServerH3Driver].
100#[derive(Debug)]
101pub enum ServerH3Event {
102    Core(H3Event),
103
104    Headers {
105        incoming_headers: IncomingH3Headers,
106        /// The latest PRIORITY_UPDATE frame value, if any.
107        priority: Option<RawPriorityValue>,
108        is_in_early_data: IsInEarlyData,
109    },
110}
111
112impl From<H3Event> for ServerH3Event {
113    fn from(ev: H3Event) -> Self {
114        match ev {
115            H3Event::IncomingHeaders(incoming_headers) => {
116                // Server `incoming_headers` are exclusively created in
117                // `ServerHooks::handle_request`, which correctly serializes the
118                // `RawPriorityValue` and `IsInEarlyData` values.
119                //
120                // `H3Driver::process_read_event` implements this behavior.
121                Self::Headers {
122                    incoming_headers,
123                    priority: None,
124                    is_in_early_data: IsInEarlyData::new(false),
125                }
126            },
127            _ => Self::Core(ev),
128        }
129    }
130}
131
132/// Commands accepted by [ServerH3Driver].
133#[derive(Debug)]
134pub enum ServerH3Command {
135    Core(H3Command),
136}
137
138impl From<H3Command> for ServerH3Command {
139    fn from(cmd: H3Command) -> Self {
140        Self::Core(cmd)
141    }
142}
143
144impl From<QuicCommand> for ServerH3Command {
145    fn from(cmd: QuicCommand) -> Self {
146        Self::Core(H3Command::QuicCmd(cmd))
147    }
148}
149
150// Quiche urgency is an 8-bit space. Internally, quiche reserves 0 for HTTP/3
151// control streams and request are shifted up by 124. Any value in that range is
152// suitable here.
153const PRE_HEADERS_BOOSTED_PRIORITY_URGENCY: u8 = 64;
154// Non-incremental streams are served in stream ID order, matching the client
155// FIFO expectation.
156const PRE_HEADERS_BOOSTED_PRIORITY_INCREMENTAL: bool = false;
157
158pub struct ServerHooks {
159    /// Helper to enforce limits and timeouts on an HTTP/3 connection.
160    settings_enforcer: Http3SettingsEnforcer,
161    /// Tracks the number of requests that have been handled by this driver.
162    requests: u64,
163
164    /// Handle to the post-accept timeout entry. If present, the server must
165    /// receive a HEADERS frame before this timeout.
166    post_accept_timeout: Option<TimeoutKey>,
167    /// Whether the extended CONNECT protocol is enabled. When disabled,
168    /// skip DATAGRAM flow creation for `:protocol` requests.
169    extended_connect_enabled: bool,
170}
171
172impl ServerHooks {
173    /// Handles a new request, creating a stream context, checking for a
174    /// potential DATAGRAM flow (CONNECT-{UDP,IP}) and sending a relevant
175    /// [`H3Event`] to the [ServerH3Controller] for application-level
176    /// processing.
177    fn handle_request(
178        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,
179        headers: InboundHeaders,
180    ) -> H3ConnectionResult<()> {
181        let InboundHeaders {
182            stream_id,
183            headers,
184            has_body,
185        } = headers;
186
187        // Multiple HEADERS frames can be received on a single stream, but only
188        // the first one is an actual request. For now ignore any additional
189        // HEADERS (e.g. "trailers").
190        if driver.stream_map.contains_key(&stream_id) {
191            return Ok(());
192        }
193
194        let (mut stream_ctx, send, recv) = StreamCtx::new(
195            stream_id,
196            STREAM_CAPACITY,
197            driver.ready_stream_sender.clone(),
198        );
199
200        // When Extended CONNECT is enabled, extract the datagram flow ID
201        // from the request headers and register it in flow_map. This
202        // allows incoming DATAGRAMs to be routed to the correct stream.
203        // Note: flow_map entries are considered active work, so the
204        // connection will not be closed in cleanup_stream() while
205        // flows remain.
206        if driver.hooks.extended_connect_enabled() {
207            if let Some(flow_id) =
208                datagram::extract_quarter_stream_id(stream_id, &headers)
209            {
210                let _ = driver.get_or_insert_flow(flow_id)?;
211                stream_ctx.associated_dgram_flow_id = Some(flow_id);
212            }
213        }
214
215        let latest_priority_update: Option<RawPriorityValue> = driver
216            .conn_mut()?
217            .take_last_priority_update(stream_id)
218            .ok()
219            .map(|v| v.into());
220
221        // Boost the priority of the stream until we write response headers via
222        // process_write_frame(), which will set the desired priority. Since it
223        // will get set later, just swallow any error here.
224        qconn
225            .stream_priority(
226                stream_id,
227                PRE_HEADERS_BOOSTED_PRIORITY_URGENCY,
228                PRE_HEADERS_BOOSTED_PRIORITY_INCREMENTAL,
229            )
230            .ok();
231
232        let headers = IncomingH3Headers {
233            stream_id,
234            headers,
235            send,
236            recv,
237            read_fin: !has_body,
238            h3_audit_stats: Arc::clone(&stream_ctx.audit_stats),
239        };
240
241        stream_ctx.wait_for_recv();
242        driver.insert_stream(stream_id, stream_ctx);
243
244        // A STOP_SENDING received before HEADERS had no stream context for
245        // the writable pass to update. Handle it now that the context exists,
246        // before the Headers event gives the application a response sender.
247        //
248        // This ensures the sender is already closed if the peer canceled.
249        driver.process_writable_stream(qconn, stream_id)?;
250
251        driver
252            .h3_event_sender
253            .send(ServerH3Event::Headers {
254                incoming_headers: headers,
255                priority: latest_priority_update,
256                is_in_early_data: IsInEarlyData::new(qconn.is_in_early_data()),
257            })
258            .map_err(|_| H3ConnectionError::ControllerWentAway)?;
259        driver.hooks.requests += 1;
260
261        Ok(())
262    }
263}
264
265#[allow(private_interfaces)]
266impl DriverHooks for ServerHooks {
267    type Command = ServerH3Command;
268    type Event = ServerH3Event;
269
270    fn new(settings: &Http3Settings) -> Self {
271        Self {
272            settings_enforcer: settings.into(),
273            requests: 0,
274            post_accept_timeout: None,
275            extended_connect_enabled: settings.enable_extended_connect,
276        }
277    }
278
279    fn extended_connect_enabled(&self) -> bool {
280        self.extended_connect_enabled
281    }
282
283    fn conn_established(
284        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,
285        handshake_info: &HandshakeInfo,
286    ) -> H3ConnectionResult<()> {
287        assert!(
288            qconn.is_server(),
289            "ServerH3Driver requires a server-side QUIC connection"
290        );
291
292        if let Some(post_accept_timeout) =
293            driver.hooks.settings_enforcer.post_accept_timeout()
294        {
295            let remaining = post_accept_timeout
296                .checked_sub(handshake_info.elapsed())
297                .ok_or(H3ConnectionError::PostAcceptTimeout)?;
298
299            let key = driver
300                .hooks
301                .settings_enforcer
302                .add_timeout(Http3TimeoutType::PostAccept, remaining);
303            driver.hooks.post_accept_timeout = Some(key);
304        }
305
306        Ok(())
307    }
308
309    fn headers_received(
310        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,
311        headers: InboundHeaders,
312    ) -> H3ConnectionResult<()> {
313        if driver
314            .hooks
315            .settings_enforcer
316            .enforce_requests_limit(driver.hooks.requests)
317        {
318            let _ =
319                qconn.close(true, quiche::h3::WireErrorCode::NoError as u64, &[]);
320            return Ok(());
321        }
322
323        if let Some(timeout) = driver.hooks.post_accept_timeout.take() {
324            // We've seen the first Headers event for the connection,
325            // so we can abort the post-accept timeout
326            driver.hooks.settings_enforcer.cancel_timeout(timeout);
327        }
328
329        Self::handle_request(driver, qconn, headers)
330    }
331
332    fn stream_recv_closed(_driver: &mut H3Driver<Self>, _stream_id: u64) {}
333
334    fn stream_closed(driver: &mut H3Driver<Self>, stream_id: u64) {
335        let _ = driver
336            .h3_event_sender
337            .send(H3Event::StreamClosed { stream_id }.into());
338    }
339
340    fn conn_command(
341        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,
342        cmd: Self::Command,
343    ) -> H3ConnectionResult<()> {
344        let ServerH3Command::Core(cmd) = cmd;
345        driver.handle_core_command(qconn, cmd)
346    }
347
348    fn has_wait_action(driver: &mut H3Driver<Self>) -> bool {
349        driver.hooks.settings_enforcer.has_pending_timeouts()
350    }
351
352    async fn wait_for_action(
353        &mut self, qconn: &mut QuicheConnection,
354    ) -> H3ConnectionResult<()> {
355        self.settings_enforcer.enforce_timeouts(qconn).await?;
356        Err(H3ConnectionError::PostAcceptTimeout)
357    }
358}