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