Skip to main content

http3_quic/
lib.rs

1//! QUIC Transport implementation
2//!
3//! This module implements QUIC traits.
4#![deny(missing_docs)]
5
6use std::{
7    convert::TryInto,
8    future::Future,
9    pin::Pin,
10    sync::Arc,
11    task::{self, Poll, ready},
12};
13
14use bytes::{Buf, Bytes};
15use futures_util::{Stream, StreamExt, stream};
16use http3::{
17    error::Code,
18    quic::{ConnectionErrorIncoming, StreamErrorIncoming, StreamId, WriteBuf},
19};
20use quic::ReadError;
21pub use quic::{self, AcceptBi, AcceptUni, Endpoint, OpenBi, OpenUni, VarInt};
22#[cfg(feature = "tracing")]
23use tracing::instrument;
24
25#[cfg(feature = "datagram")]
26pub mod datagram;
27
28/// Boxed stream retaining the backend's `Send + Sync` guarantees.
29type BoxStreamSync<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + Sync + 'a>>;
30
31/// Boxed [`quic::SendStream::stopped`] future, created by the first `poll_stopped`.
32type Stopped =
33    Pin<Box<dyn Future<Output = Result<Option<VarInt>, quic::StoppedError>> + Send + Sync>>;
34
35/// An HTTP/3 transport backed by a QUIC connection.
36///
37/// Implements [`http3::quic::Connection`] backed by a [`quic::Connection`].
38pub struct Connection {
39    conn: quic::Connection,
40    incoming_bi: BoxStreamSync<'static, <AcceptBi<'static> as Future>::Output>,
41    opening_bi: Option<BoxStreamSync<'static, <OpenBi<'static> as Future>::Output>>,
42    incoming_uni: BoxStreamSync<'static, <AcceptUni<'static> as Future>::Output>,
43    opening_uni: Option<BoxStreamSync<'static, <OpenUni<'static> as Future>::Output>>,
44}
45
46impl Connection {
47    /// Create a [`Connection`] from a [`quic::Connection`]
48    pub fn new(conn: quic::Connection) -> Self {
49        Self {
50            conn: conn.clone(),
51            incoming_bi: Box::pin(stream::unfold(conn.clone(), |conn| async {
52                Some((conn.accept_bi().await, conn))
53            })),
54            opening_bi: None,
55            incoming_uni: Box::pin(stream::unfold(conn.clone(), |conn| async {
56                Some((conn.accept_uni().await, conn))
57            })),
58            opening_uni: None,
59        }
60    }
61}
62
63impl<B> http3::quic::Connection<B> for Connection
64where
65    B: Buf,
66{
67    type RecvStream = RecvStream;
68    type OpenStreams = OpenStreams;
69
70    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
71    fn poll_accept_bidi(
72        &mut self,
73        cx: &mut task::Context<'_>,
74    ) -> Poll<Result<Self::BidiStream, ConnectionErrorIncoming>> {
75        let (send, recv) = ready!(self.incoming_bi.poll_next_unpin(cx))
76            .expect("self.incoming_bi BoxStream never returns None")
77            .map_err(convert_connection_error)?;
78        Poll::Ready(Ok(Self::BidiStream {
79            send: Self::SendStream::new(send),
80            recv: Self::RecvStream::new(recv),
81        }))
82    }
83
84    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
85    fn poll_accept_recv(
86        &mut self,
87        cx: &mut task::Context<'_>,
88    ) -> Poll<Result<Self::RecvStream, ConnectionErrorIncoming>> {
89        let recv = ready!(self.incoming_uni.poll_next_unpin(cx))
90            .expect("self.incoming_uni BoxStream never returns None")
91            .map_err(convert_connection_error)?;
92        Poll::Ready(Ok(Self::RecvStream::new(recv)))
93    }
94
95    fn opener(&self) -> Self::OpenStreams {
96        OpenStreams {
97            conn: self.conn.clone(),
98            opening_bi: None,
99            opening_uni: None,
100        }
101    }
102}
103
104fn convert_connection_error(e: quic::ConnectionError) -> http3::quic::ConnectionErrorIncoming {
105    match e {
106        quic::ConnectionError::ApplicationClosed(application_close) => {
107            ConnectionErrorIncoming::ApplicationClose {
108                error_code: application_close.error_code.into(),
109            }
110        }
111        quic::ConnectionError::TimedOut => ConnectionErrorIncoming::Timeout,
112
113        error @ quic::ConnectionError::VersionMismatch
114        | error @ quic::ConnectionError::Reset
115        | error @ quic::ConnectionError::LocallyClosed
116        | error @ quic::ConnectionError::CidsExhausted
117        | error @ quic::ConnectionError::TransportError(_)
118        | error @ quic::ConnectionError::ConnectionClosed(_) => {
119            ConnectionErrorIncoming::Undefined(Arc::new(error))
120        }
121    }
122}
123
124impl<B> http3::quic::OpenStreams<B> for Connection
125where
126    B: Buf,
127{
128    type SendStream = SendStream<B>;
129    type BidiStream = BidiStream<B>;
130
131    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
132    fn poll_open_bidi(
133        &mut self,
134        cx: &mut task::Context<'_>,
135    ) -> Poll<Result<Self::BidiStream, StreamErrorIncoming>> {
136        let bi = self.opening_bi.get_or_insert_with(|| {
137            Box::pin(stream::unfold(self.conn.clone(), |conn| async {
138                Some((conn.open_bi().await, conn))
139            }))
140        });
141        let (send, recv) = ready!(bi.poll_next_unpin(cx))
142            .expect("BoxStream does not return None")
143            .map_err(|e| StreamErrorIncoming::ConnectionErrorIncoming {
144                connection_error: convert_connection_error(e),
145            })?;
146        Poll::Ready(Ok(Self::BidiStream {
147            send: Self::SendStream::new(send),
148            recv: RecvStream::new(recv),
149        }))
150    }
151
152    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
153    fn poll_open_send(
154        &mut self,
155        cx: &mut task::Context<'_>,
156    ) -> Poll<Result<Self::SendStream, StreamErrorIncoming>> {
157        let uni = self.opening_uni.get_or_insert_with(|| {
158            Box::pin(stream::unfold(self.conn.clone(), |conn| async {
159                Some((conn.open_uni().await, conn))
160            }))
161        });
162
163        let send = ready!(uni.poll_next_unpin(cx))
164            .expect("BoxStream does not return None")
165            .map_err(|e| StreamErrorIncoming::ConnectionErrorIncoming {
166                connection_error: convert_connection_error(e),
167            })?;
168        Poll::Ready(Ok(Self::SendStream::new(send)))
169    }
170
171    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
172    fn close(&mut self, code: Code, reason: &[u8]) {
173        self.conn.close(
174            VarInt::from_u64(code.value()).expect("error code VarInt"),
175            reason,
176        );
177    }
178}
179
180/// Stream opener backed by a QUIC connection
181///
182/// Implements [`http3::quic::OpenStreams`] using [`quic::Connection`],
183/// [`quic::OpenBi`], [`quic::OpenUni`].
184pub struct OpenStreams {
185    conn: quic::Connection,
186    opening_bi: Option<BoxStreamSync<'static, <OpenBi<'static> as Future>::Output>>,
187    opening_uni: Option<BoxStreamSync<'static, <OpenUni<'static> as Future>::Output>>,
188}
189
190impl<B> http3::quic::OpenStreams<B> for OpenStreams
191where
192    B: Buf,
193{
194    type SendStream = SendStream<B>;
195    type BidiStream = BidiStream<B>;
196
197    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
198    fn poll_open_bidi(
199        &mut self,
200        cx: &mut task::Context<'_>,
201    ) -> Poll<Result<Self::BidiStream, StreamErrorIncoming>> {
202        let bi = self.opening_bi.get_or_insert_with(|| {
203            Box::pin(stream::unfold(self.conn.clone(), |conn| async {
204                Some((conn.open_bi().await, conn))
205            }))
206        });
207
208        let (send, recv) = ready!(bi.poll_next_unpin(cx))
209            .expect("BoxStream does not return None")
210            .map_err(|e| StreamErrorIncoming::ConnectionErrorIncoming {
211                connection_error: convert_connection_error(e),
212            })?;
213        Poll::Ready(Ok(Self::BidiStream {
214            send: Self::SendStream::new(send),
215            recv: RecvStream::new(recv),
216        }))
217    }
218
219    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
220    fn poll_open_send(
221        &mut self,
222        cx: &mut task::Context<'_>,
223    ) -> Poll<Result<Self::SendStream, StreamErrorIncoming>> {
224        let uni = self.opening_uni.get_or_insert_with(|| {
225            Box::pin(stream::unfold(self.conn.clone(), |conn| async {
226                Some((conn.open_uni().await, conn))
227            }))
228        });
229
230        let send = ready!(uni.poll_next_unpin(cx))
231            .expect("BoxStream does not return None")
232            .map_err(|e| StreamErrorIncoming::ConnectionErrorIncoming {
233                connection_error: convert_connection_error(e),
234            })?;
235        Poll::Ready(Ok(Self::SendStream::new(send)))
236    }
237
238    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
239    fn close(&mut self, code: Code, reason: &[u8]) {
240        self.conn.close(
241            VarInt::from_u64(code.value()).expect("error code VarInt"),
242            reason,
243        );
244    }
245}
246
247impl Clone for OpenStreams {
248    fn clone(&self) -> Self {
249        Self {
250            conn: self.conn.clone(),
251            opening_bi: None,
252            opening_uni: None,
253        }
254    }
255}
256
257/// QUIC-backed bidirectional stream
258///
259/// Implements [`http3::quic::BidiStream`] which allows the stream to be split
260/// into two structs each implementing one direction.
261pub struct BidiStream<B>
262where
263    B: Buf,
264{
265    send: SendStream<B>,
266    recv: RecvStream,
267}
268
269impl<B> http3::quic::BidiStream<B> for BidiStream<B>
270where
271    B: Buf,
272{
273    type SendStream = SendStream<B>;
274    type RecvStream = RecvStream;
275
276    fn split(self) -> (Self::SendStream, Self::RecvStream) {
277        (self.send, self.recv)
278    }
279}
280
281impl<B: Buf> http3::quic::RecvStream for BidiStream<B> {
282    type Buf = Bytes;
283
284    fn poll_data(
285        &mut self,
286        cx: &mut task::Context<'_>,
287    ) -> Poll<Result<Option<Self::Buf>, StreamErrorIncoming>> {
288        self.recv.poll_data(cx)
289    }
290
291    fn stop_sending(&mut self, error_code: u64) {
292        self.recv.stop_sending(error_code)
293    }
294
295    fn recv_id(&self) -> StreamId {
296        self.recv.recv_id()
297    }
298}
299
300impl<B> http3::quic::SendStream<B> for BidiStream<B>
301where
302    B: Buf,
303{
304    fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), StreamErrorIncoming>> {
305        self.send.poll_ready(cx)
306    }
307
308    fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), StreamErrorIncoming>> {
309        self.send.poll_finish(cx)
310    }
311
312    fn poll_stopped(
313        &mut self,
314        cx: &mut task::Context<'_>,
315    ) -> Poll<Result<Option<u64>, StreamErrorIncoming>> {
316        self.send.poll_stopped(cx)
317    }
318
319    fn reset(&mut self, reset_code: u64) {
320        self.send.reset(reset_code)
321    }
322
323    fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), StreamErrorIncoming> {
324        self.send.send_data(data)
325    }
326
327    fn send_id(&self) -> StreamId {
328        self.send.send_id()
329    }
330}
331impl<B> http3::quic::SendStreamUnframed<B> for BidiStream<B>
332where
333    B: Buf,
334{
335    fn poll_send<D: Buf>(
336        &mut self,
337        cx: &mut task::Context<'_>,
338        buf: &mut D,
339    ) -> Poll<Result<usize, StreamErrorIncoming>> {
340        self.send.poll_send(cx, buf)
341    }
342}
343
344impl<B> http3::quic::Is0rtt for BidiStream<B>
345where
346    B: Buf,
347{
348    fn is_0rtt(&self) -> bool {
349        self.recv.is_0rtt()
350    }
351}
352
353/// QUIC-backed receive stream
354///
355/// Implements [`http3::quic::RecvStream`] backed by a [`quic::RecvStream`].
356pub struct RecvStream {
357    stream: quic::RecvStream,
358    is_0rtt: bool,
359}
360
361impl RecvStream {
362    fn new(stream: quic::RecvStream) -> Self {
363        let is_0rtt = stream.is_0rtt();
364        Self { stream, is_0rtt }
365    }
366}
367
368impl http3::quic::RecvStream for RecvStream {
369    type Buf = Bytes;
370
371    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
372    fn poll_data(
373        &mut self,
374        cx: &mut task::Context<'_>,
375    ) -> Poll<Result<Option<Self::Buf>, StreamErrorIncoming>> {
376        let mut read_chunk = std::pin::pin!(self.stream.read_chunk(usize::MAX, true));
377        let chunk = ready!(read_chunk.as_mut().poll(cx));
378        Poll::Ready(Ok(chunk
379            .map_err(convert_read_error_to_stream_error)?
380            .map(|c| c.bytes)))
381    }
382
383    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
384    fn stop_sending(&mut self, error_code: u64) {
385        let error_code = VarInt::from_u64(error_code).expect("invalid error_code");
386        let _ = self.stream.stop(error_code);
387    }
388
389    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
390    fn recv_id(&self) -> StreamId {
391        let num: u64 = self.stream.id().into();
392
393        num.try_into().expect("invalid stream id")
394    }
395}
396
397impl http3::quic::Is0rtt for RecvStream {
398    /// Check if this stream has been opened during 0-RTT.
399    ///
400    /// In which case any non-idempotent request should be considered dangerous at the application
401    /// level. Because read data is subject to replay attacks.
402    fn is_0rtt(&self) -> bool {
403        self.is_0rtt
404    }
405}
406
407fn convert_read_error_to_stream_error(error: ReadError) -> StreamErrorIncoming {
408    match error {
409        ReadError::Reset(var_int) => StreamErrorIncoming::StreamTerminated {
410            error_code: var_int.into_inner(),
411        },
412        ReadError::ConnectionLost(connection_error) => {
413            StreamErrorIncoming::ConnectionErrorIncoming {
414                connection_error: convert_connection_error(connection_error),
415            }
416        }
417        error @ ReadError::ClosedStream => StreamErrorIncoming::Unknown(Box::new(error)),
418        ReadError::IllegalOrderedRead => panic!("http3-quic only performs ordered reads"),
419        error @ ReadError::ZeroRttRejected => StreamErrorIncoming::Unknown(Box::new(error)),
420    }
421}
422
423fn convert_write_error_to_stream_error(error: quic::WriteError) -> StreamErrorIncoming {
424    match error {
425        quic::WriteError::Stopped(var_int) => StreamErrorIncoming::StreamTerminated {
426            error_code: var_int.into_inner(),
427        },
428        quic::WriteError::ConnectionLost(connection_error) => {
429            StreamErrorIncoming::ConnectionErrorIncoming {
430                connection_error: convert_connection_error(connection_error),
431            }
432        }
433        error @ quic::WriteError::ClosedStream | error @ quic::WriteError::ZeroRttRejected => {
434            StreamErrorIncoming::Unknown(Box::new(error))
435        }
436    }
437}
438
439fn convert_stopped_error_to_stream_error(error: quic::StoppedError) -> StreamErrorIncoming {
440    match error {
441        quic::StoppedError::ConnectionLost(connection_error) => {
442            StreamErrorIncoming::ConnectionErrorIncoming {
443                connection_error: convert_connection_error(connection_error),
444            }
445        }
446        error @ quic::StoppedError::ZeroRttRejected => {
447            StreamErrorIncoming::Unknown(Box::new(error))
448        }
449    }
450}
451
452/// QUIC-backed send stream
453///
454/// Implements [`http3::quic::SendStream`] backed by a [`quic::SendStream`].
455pub struct SendStream<B: Buf> {
456    stream: quic::SendStream,
457    writing: Option<WriteBuf<B>>,
458    stopped: Option<Stopped>,
459}
460
461impl<B> SendStream<B>
462where
463    B: Buf,
464{
465    fn new(stream: quic::SendStream) -> SendStream<B> {
466        Self {
467            stream,
468            writing: None,
469            stopped: None,
470        }
471    }
472}
473
474impl<B> http3::quic::SendStream<B> for SendStream<B>
475where
476    B: Buf,
477{
478    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
479    fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), StreamErrorIncoming>> {
480        if let Some(ref mut data) = self.writing {
481            while data.has_remaining() {
482                let stream = Pin::new(&mut self.stream);
483                let written = ready!(stream.poll_write(cx, data.chunk()))
484                    .map_err(convert_write_error_to_stream_error)?;
485                data.advance(written);
486            }
487        }
488        // all data is written
489        self.writing = None;
490        Poll::Ready(Ok(()))
491    }
492
493    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
494    fn poll_finish(
495        &mut self,
496        _cx: &mut task::Context<'_>,
497    ) -> Poll<Result<(), StreamErrorIncoming>> {
498        Poll::Ready(
499            self.stream
500                .finish()
501                .map_err(|e| StreamErrorIncoming::Unknown(Box::new(e))),
502        )
503    }
504
505    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
506    fn poll_stopped(
507        &mut self,
508        cx: &mut task::Context<'_>,
509    ) -> Poll<Result<Option<u64>, StreamErrorIncoming>> {
510        let stopped = self
511            .stopped
512            .get_or_insert_with(|| Box::pin(self.stream.stopped()));
513        let result = ready!(stopped.as_mut().poll(cx));
514        self.stopped = None;
515        Poll::Ready(
516            result
517                .map(|code| code.map(VarInt::into_inner))
518                .map_err(convert_stopped_error_to_stream_error),
519        )
520    }
521
522    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
523    fn reset(&mut self, reset_code: u64) {
524        let _ = self
525            .stream
526            .reset(VarInt::from_u64(reset_code).unwrap_or(VarInt::MAX));
527    }
528
529    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
530    fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), StreamErrorIncoming> {
531        if self.writing.is_some() {
532            // This can only happen if the traits are misused by http3 itself.
533            // If this happens log an error and close the connection with H3_INTERNAL_ERROR
534
535            #[cfg(feature = "tracing")]
536            tracing::error!("send_data called while send stream is not ready");
537            return Err(StreamErrorIncoming::ConnectionErrorIncoming {
538                connection_error: ConnectionErrorIncoming::InternalError(
539                    "internal error in the http stack".to_string(),
540                ),
541            });
542        }
543        self.writing = Some(data.into());
544        Ok(())
545    }
546
547    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
548    fn send_id(&self) -> StreamId {
549        let num: u64 = self.stream.id().into();
550        num.try_into().expect("invalid stream id")
551    }
552}
553
554impl<B> http3::quic::SendStreamUnframed<B> for SendStream<B>
555where
556    B: Buf,
557{
558    #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
559    fn poll_send<D: Buf>(
560        &mut self,
561        cx: &mut task::Context<'_>,
562        buf: &mut D,
563    ) -> Poll<Result<usize, StreamErrorIncoming>> {
564        if self.writing.is_some() {
565            // This signifies a bug in implementation
566            panic!("poll_send called while send stream is not ready")
567        }
568
569        let s = Pin::new(&mut self.stream);
570
571        let res = ready!(s.poll_write(cx, buf.chunk()));
572        match res {
573            Ok(written) => {
574                buf.advance(written);
575                Poll::Ready(Ok(written))
576            }
577            Err(err) => Poll::Ready(Err(convert_write_error_to_stream_error(err))),
578        }
579    }
580}