1mod priority;
4pub mod web_transport;
5use crate::{
6 ArcHandler, ArcedQuicEndpoint, BoxedBidiStream, QuicConnection, QuicTransportReceive,
7 QuicTransportSend, RuntimeTrait, unmap_ipv4,
8};
9use priority::{PrioritizedStream, PriorityRegistry, transport_priority};
10use std::{any::Any, sync::Arc};
11use trillium::{Handler, KnownHeaderName, Listener, Transport, Upgrade};
12use trillium_http::{
13 HttpContext,
14 h3::{H3Connection, H3Error, H3ErrorCode, H3StreamResult, UniStreamResult},
15};
16use web_transport::{WebTransportDispatcher, WebTransportStream};
17
18#[derive(Clone, Copy, Debug)]
20pub struct StreamId(u64);
21impl From<StreamId> for u64 {
22 fn from(val: StreamId) -> Self {
23 val.0
24 }
25}
26
27impl From<u64> for StreamId {
28 fn from(value: u64) -> Self {
29 Self(value)
30 }
31}
32
33pub(crate) async fn run_h3(
34 quic_binding: ArcedQuicEndpoint,
35 context: Arc<HttpContext>,
36 handler: ArcHandler<impl Handler>,
37 runtime: impl RuntimeTrait,
38 listener: Option<Listener>,
39 local_alt_svc: Option<&'static str>,
40) {
41 let swansong = context.swansong();
42 while let Some(connection) = swansong.interrupt(quic_binding.accept()).await.flatten() {
43 let h3 = H3Connection::new(context.clone());
44 let handler = handler.clone();
45 let runtime = runtime.clone();
46 runtime.clone().spawn_detached(run_h3_connection(
47 connection,
48 h3,
49 handler,
50 runtime,
51 listener.as_ref().map(Listener::unshared_clone),
52 local_alt_svc,
53 ));
54 }
55}
56
57async fn run_h3_connection(
58 connection: QuicConnection,
59 h3: Arc<H3Connection>,
60 handler: ArcHandler<impl Handler>,
61 runtime: impl RuntimeTrait,
62 listener: Option<Listener>,
63 local_alt_svc: Option<&'static str>,
64) {
65 let wt_dispatcher = h3
66 .context()
67 .config()
68 .webtransport_enabled()
69 .then(WebTransportDispatcher::new);
70
71 log::trace!("new quic connection from {}", connection.remote_address());
72
73 let priorities = PriorityRegistry::default();
74 h3.register_priority_callback({
75 let priorities = priorities.clone();
76 move |stream_id, priority, is_update| {
77 priorities.apply(stream_id, transport_priority(priority), is_update)
78 }
79 });
80
81 spawn_outbound_control_stream(&connection, &h3, &runtime);
82 spawn_qpack_encoder_stream(&connection, &h3, &runtime);
83 spawn_qpack_decoder_stream(&connection, &h3, &runtime);
84 spawn_inbound_uni_streams(&connection, &h3, &runtime, &wt_dispatcher);
85 handle_inbound_bidi_streams(
89 connection,
90 h3.clone(),
91 ArcHandler::new(handler),
92 runtime,
93 wt_dispatcher,
94 listener,
95 local_alt_svc,
96 priorities,
97 )
98 .await;
99}
100
101#[allow(clippy::too_many_arguments)]
102async fn handle_inbound_bidi_streams(
103 connection: QuicConnection,
104 h3: Arc<H3Connection>,
105 handler: ArcHandler<impl Handler>,
106 runtime: impl RuntimeTrait,
107 wt_dispatcher: Option<WebTransportDispatcher>,
108 listener: Option<Listener>,
109 local_alt_svc: Option<&'static str>,
110 priorities: PriorityRegistry,
111) {
112 loop {
113 match h3.swansong().interrupt(connection.accept_bidi()).await {
114 None => {
115 log::trace!("H3 bidi accept loop: interrupted by swansong shutdown");
116 break;
117 }
118 Some(Err(e)) => {
119 log::debug!("H3 bidi accept loop: accept_bidi error: {e}");
120 break;
121 }
122 Some(Ok((stream_id, transport))) => {
123 handle_bidi_stream(
124 stream_id,
125 transport,
126 &h3,
127 &handler,
128 &connection,
129 &runtime,
130 &wt_dispatcher,
131 listener.clone(),
132 local_alt_svc,
133 &priorities,
134 );
135 }
136 }
137 }
138
139 h3.shut_down();
140}
141
142#[allow(clippy::too_many_arguments)]
143fn handle_bidi_stream(
144 stream_id: u64,
145 transport: BoxedBidiStream,
146 h3: &Arc<H3Connection>,
147 handler: &ArcHandler<impl Handler>,
148 connection: &QuicConnection,
149 runtime: &impl RuntimeTrait,
150 wt_dispatcher: &Option<WebTransportDispatcher>,
151 listener: Option<Listener>,
152 local_alt_svc: Option<&'static str>,
153 priorities: &PriorityRegistry,
154) {
155 log::trace!("H3 bidi stream {stream_id}: spawning handler task");
156 let (h3, handler, connection, wt_dispatcher, priorities) = (
157 h3.clone(),
158 handler.clone(),
159 connection.clone(),
160 wt_dispatcher.clone(),
161 priorities.clone(),
162 );
163
164 let slot = priorities.register(stream_id);
168
169 let peer_gone = transport.stopped();
172
173 let transport: Box<dyn Transport> = Box::new(RequestStream::new(transport, slot, stream_id));
177
178 runtime.spawn_detached(async move {
179 let peer_ip = unmap_ipv4(connection.remote_address().ip());
183 let quic_connection = connection.clone();
184 let wt_dispatcher = wt_dispatcher.clone();
185
186 let handler_fn = {
187 let handler = handler.clone();
188 let wt_dispatcher = wt_dispatcher.clone();
189 move |mut conn: trillium_http::Conn<_>| async move {
190 conn.set_peer_ip(Some(peer_ip));
191 conn.set_secure(true);
192
193 let state = conn.state_mut();
194 state.insert(quic_connection);
195 state.insert(StreamId(stream_id));
196 if let Some(listener) = listener {
197 if let Some(addr) = listener.socket_addr() {
198 state.insert(addr);
199 }
200 state.insert(listener);
201 }
202 if let Some(dispatcher) = wt_dispatcher {
203 state.insert(dispatcher);
204 }
205 if let Some(alt_svc) = local_alt_svc {
206 conn.response_headers_mut()
207 .try_insert(KnownHeaderName::AltSvc, alt_svc);
208 }
209
210 let conn = handler.run(conn.into()).await;
211 let conn = handler.before_send(conn).await;
212
213 conn.into_inner()
214 }
215 };
216
217 let result = h3
218 .clone()
219 .process_inbound_bidi(transport, handler_fn, stream_id)
220 .with_reset(|transport, code| {
221 if let Some(stream) = request_stream(transport) {
226 let raw = u64::from(code);
227 stream.stop(raw);
228 stream.reset(raw);
229 }
230 });
231
232 let result = match peer_gone {
233 Some(peer_gone) => result.with_peer_gone(peer_gone).await,
234 None => result.await,
235 };
236
237 match result {
238 Ok(H3StreamResult::Request(conn)) if conn.should_upgrade() => {
239 let upgrade = Upgrade::from(conn);
240 if handler.has_upgrade(&upgrade) {
241 log::debug!("upgrading h3 stream");
242 handler.upgrade(upgrade).await;
243 } else {
244 log::error!("h3 upgrade specified but no upgrade handler provided");
245 }
246 }
247
248 Ok(H3StreamResult::Request(_)) => {}
249
250 Ok(H3StreamResult::WebTransport {
251 session_id,
252 transport,
253 buffer,
254 }) => {
255 if let Some(mut stream) = into_request_stream(transport) {
256 if let Some(dispatcher) = &wt_dispatcher {
257 dispatcher.dispatch(WebTransportStream::Bidi {
258 session_id,
259 stream,
260 buffer: buffer.into(),
261 });
262 } else {
263 stream.stop(H3ErrorCode::StreamCreationError.into());
264 stream.reset(H3ErrorCode::StreamCreationError.into());
265 }
266 }
267 }
268
269 Err(error) => {
270 log::debug!("H3 bidi stream {stream_id}: error: {error}");
271 handle_h3_error(error, &connection, &h3);
272 }
273 }
274
275 priorities.deregister(stream_id);
276 });
277}
278
279type RequestStream = PrioritizedStream<BoxedBidiStream>;
281
282fn request_stream(transport: &mut Box<dyn Transport>) -> Option<&mut RequestStream> {
283 (&mut **transport as &mut dyn Any).downcast_mut()
284}
285
286fn into_request_stream(transport: Box<dyn Transport>) -> Option<Box<RequestStream>> {
287 (transport as Box<dyn Any>).downcast().ok()
288}
289
290fn spawn_inbound_uni_streams(
291 connection: &QuicConnection,
292 h3: &Arc<H3Connection>,
293 runtime: &impl RuntimeTrait,
294 wt_dispatcher: &Option<WebTransportDispatcher>,
295) {
296 let (connection, h3, runtime, wt_dispatcher) = (
297 connection.clone(),
298 h3.clone(),
299 runtime.clone(),
300 wt_dispatcher.clone(),
301 );
302 runtime.clone().spawn_detached(async move {
303 while let Some(Ok((_stream_id, recv))) =
304 h3.swansong().interrupt(connection.accept_uni()).await
305 {
306 let (connection, h3, wt_dispatcher) =
307 (connection.clone(), h3.clone(), wt_dispatcher.clone());
308
309 runtime.spawn_detached(async move {
310 let close_connection = {
318 let connection = connection.clone();
319 let h3 = h3.clone();
320 move |code: H3ErrorCode| {
321 connection.close(code.into(), code.reason().as_bytes());
322 h3.shut_down();
323 }
324 };
325 let result = h3
326 .process_inbound_uni_with_close(recv, close_connection)
327 .await;
328
329 match result {
330 Ok(UniStreamResult::Handled) => {}
331 Ok(UniStreamResult::WebTransport {
332 session_id,
333 mut stream,
334 buffer,
335 }) => {
336 if let Some(dispatcher) = &wt_dispatcher {
337 dispatcher.dispatch(WebTransportStream::Uni {
338 session_id,
339 stream: Box::new(stream),
340 buffer: buffer.into(),
341 });
342 } else {
343 stream.stop(H3ErrorCode::StreamCreationError.into());
344 }
345 }
346
347 Ok(UniStreamResult::Unknown { mut stream, .. }) => {
348 stream.stop(H3ErrorCode::StreamCreationError.into());
349 }
350
351 Err(error) => {
352 handle_h3_error(error, &connection, &h3);
356 }
357 }
358 });
359 }
360
361 h3.shut_down();
362 });
363}
364
365fn spawn_qpack_decoder_stream(
366 connection: &QuicConnection,
367 h3: &Arc<H3Connection>,
368 runtime: &impl RuntimeTrait,
369) {
370 let (connection, h3) = (connection.clone(), h3.clone());
371
372 runtime.spawn_detached(async move {
373 log::trace!("H3: opening outbound QPACK decoder stream");
374 let stream = match connection.open_uni().await {
375 Ok((_stream_id, stream)) => stream,
376 Err(err) => {
377 log::error!("H3: open_uni for QPACK decoder stream failed: {err:?}");
378 h3.shut_down();
379 return;
380 }
381 };
382
383 let result = h3.run_decoder(stream).await;
384
385 if let Err(error) = result {
386 handle_h3_error(error, &connection, &h3);
387 }
388 });
390}
391
392fn spawn_qpack_encoder_stream(
393 connection: &QuicConnection,
394 h3: &Arc<H3Connection>,
395 runtime: &impl RuntimeTrait,
396) {
397 let (connection, h3) = (connection.clone(), h3.clone());
398 runtime.spawn_detached(async move {
399 log::trace!("H3: opening outbound QPACK encoder stream");
400 let stream = match connection.open_uni().await {
401 Ok((_stream_id, stream)) => stream,
402 Err(err) => {
403 log::error!("H3: open_uni for QPACK encoder stream failed: {err:?}");
404 h3.shut_down();
405 return;
406 }
407 };
408
409 let result = h3.run_encoder(stream).await;
410
411 if let Err(error) = result {
412 handle_h3_error(error, &connection, &h3);
413 }
414 });
416}
417
418fn spawn_outbound_control_stream(
419 connection: &QuicConnection,
420 h3: &Arc<H3Connection>,
421 runtime: &impl RuntimeTrait,
422) {
423 let (connection, h3) = (connection.clone(), h3.clone());
424 runtime.spawn_detached(async move {
425 log::trace!("H3: opening outbound control stream");
426 let stream = match connection.open_uni().await {
427 Ok((_stream_id, stream)) => stream,
428 Err(err) => {
429 log::error!("H3: open_uni for outbound control stream failed: {err:?}");
430 h3.shut_down();
431 return;
432 }
433 };
434
435 let result = h3.run_outbound_control(stream).await;
436
437 if let Err(error) = result {
438 handle_h3_error(error, &connection, &h3);
439 }
440 });
442}
443
444fn handle_h3_error(error: H3Error, connection: &QuicConnection, h3: &H3Connection) {
445 log::debug!("H3 error: {error}");
446 if let H3Error::Protocol(code) = error
447 && code.is_connection_error()
448 {
449 connection.close(code.into(), code.reason().as_bytes());
450 h3.shut_down();
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::{RequestStream, into_request_stream, priority::PriorityRegistry, request_stream};
457 use crate::{BoxedBidiStream, QuicTransportBidi, QuicTransportReceive, QuicTransportSend};
458 use futures_lite::{AsyncRead, AsyncWrite};
459 use std::{
460 io,
461 pin::Pin,
462 sync::{Arc, Mutex},
463 task::{Context, Poll},
464 };
465 use trillium::Transport;
466
467 struct RecordingBidi(Arc<Mutex<Vec<String>>>);
469
470 impl AsyncRead for RecordingBidi {
471 fn poll_read(
472 self: Pin<&mut Self>,
473 _: &mut Context<'_>,
474 _: &mut [u8],
475 ) -> Poll<io::Result<usize>> {
476 Poll::Ready(Ok(0))
477 }
478 }
479
480 impl AsyncWrite for RecordingBidi {
481 fn poll_write(
482 self: Pin<&mut Self>,
483 _: &mut Context<'_>,
484 buf: &[u8],
485 ) -> Poll<io::Result<usize>> {
486 Poll::Ready(Ok(buf.len()))
487 }
488
489 fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
490 Poll::Ready(Ok(()))
491 }
492
493 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
494 Poll::Ready(Ok(()))
495 }
496 }
497
498 impl QuicTransportReceive for RecordingBidi {
499 fn stop(&mut self, code: u64) {
500 self.0.lock().unwrap().push(format!("stop {code}"));
501 }
502 }
503
504 impl QuicTransportSend for RecordingBidi {
505 fn reset(&mut self, code: u64) {
506 self.0.lock().unwrap().push(format!("reset {code}"));
507 }
508 }
509
510 impl Transport for RecordingBidi {}
511 impl QuicTransportBidi for RecordingBidi {}
512
513 #[test]
514 fn a_request_stream_is_recoverable_from_its_erased_transport() {
515 let signals = Arc::new(Mutex::new(Vec::new()));
516 let registry = PriorityRegistry::default();
517 let quic_stream: BoxedBidiStream = Box::new(RecordingBidi(Arc::clone(&signals)));
518 let mut transport: Box<dyn Transport> =
519 Box::new(RequestStream::new(quic_stream, registry.register(0), 0));
520
521 let stream = request_stream(&mut transport).expect("erased as a RequestStream");
522 stream.stop(1);
523 stream.reset(2);
524 assert_eq!(*signals.lock().unwrap(), ["stop 1", "reset 2"]);
525
526 assert!(into_request_stream(transport).is_some());
527 }
528}