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