h3-quinn-0.0.7/.cargo_vcs_info.json0000644000000001460000000000100125030ustar { "git": { "sha1": "f785541fb363b4d48790ce00f954cfe7273a989a" }, "path_in_vcs": "h3-quinn" }h3-quinn-0.0.7/Cargo.toml0000644000000024760000000000100105110ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.66" name = "h3-quinn" version = "0.0.7" authors = ["Jean-Christophe BEGUE "] description = "QUIC transport implementation based on Quinn." documentation = "https://docs.rs/h3-quinn" readme = "README.md" keywords = [ "http3", "quic", "h3", ] categories = [ "network-programming", "web-programming", ] license = "MIT" repository = "https://github.com/hyperium/h3" [dependencies.bytes] version = "1" [dependencies.futures] version = "0.3.28" [dependencies.h3] version = "0.0.6" [dependencies.quinn] version = "0.11" features = ["futures-io"] default-features = false [dependencies.tokio] version = "1" features = ["io-util"] default-features = false [dependencies.tokio-util] version = "0.7.9" [dependencies.tracing] version = "0.1.40" optional = true [features] tracing = ["dep:tracing"] h3-quinn-0.0.7/Cargo.toml.orig000064400000000000000000000014541046102023000141650ustar 00000000000000[package] name = "h3-quinn" version = "0.0.7" rust-version = "1.66" authors = ["Jean-Christophe BEGUE "] edition = "2021" documentation = "https://docs.rs/h3-quinn" repository = "https://github.com/hyperium/h3" readme = "../README.md" description = "QUIC transport implementation based on Quinn." keywords = ["http3", "quic", "h3"] categories = ["network-programming", "web-programming"] license = "MIT" [dependencies] h3 = { version = "0.0.6", path = "../h3" } bytes = "1" quinn = { version = "0.11", default-features = false, features = [ "futures-io", ] } tokio-util = { version = "0.7.9" } futures = { version = "0.3.28" } tokio = { version = "1", features = ["io-util"], default-features = false } tracing = { version = "0.1.40", optional = true } [features] tracing = ["dep:tracing"] h3-quinn-0.0.7/LICENSE000064400000000000000000000020361046102023000123000ustar 00000000000000Copyright (c) 2020 h3 authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. h3-quinn-0.0.7/README.md000064400000000000000000000121271046102023000125540ustar 00000000000000# h3 An async HTTP/3 implementation. [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![CI](https://github.com/hyperium/h3/workflows/CI/badge.svg)](https://github.com/hyperium/h3/actions?query=workflow%3ACI) [![Discord chat](https://img.shields.io/discord/500028886025895936.svg?logo=discord)](https://discord.gg/q5mVhMD) This crate provides an [HTTP/3][spec] implementation that is generic over a provided QUIC transport. This allows the project to focus on just HTTP/3, while letting users pick their QUIC implementation based on their specific needs. It includes client and server APIs. Check the original [design][] for more details. [spec]: https://www.rfc-editor.org/rfc/rfc9114 [design]: docs/PROPOSAL.md ## Status The `h3` crate is still very experimental. While the client and servers do work, there may still be bugs. And the API could change as we continue to explore. That said, we eagerly welcome contributions, trying it out in test environments, and using at your own risk. The eventual goal is to use `h3` as an internal dependency of [hyper][]. [hyper]: https://hyper.rs ### Duvet This create uses the [duvet crate][] to check compliance of the [spec][]. The generated [report][] displays the current status of the requirements of the spec. Get more information about this tool in the [contributing][] document. [duvet crate]: https://crates.io/crates/duvet [spec]: https://www.rfc-editor.org/rfc/rfc9114 [report]: https://hyper.rs/h3/ci/compliance/report.html#/ [contributing]: CONTRIBUTING.md ## Features * HTTP/3 client and server implementation * Async only API * QUIC transport abstraction via traits in the [`quic`](./h3/src/quic.rs) module * Runtime independent (h3 does not spawn tasks and works with any runtime) * Supported QUIC implementations to date are [Quinn](https://github.com/quinn-rs/quinn) ([h3-quinn](./h3-quinn/)) and [s2n-quic](https://github.com/aws/s2n-quic) ([s2n-quic-h3](https://github.com/aws/s2n-quic/tree/main/quic/s2n-quic-h3)) ## Overview * **h3** HTTP/3 implementation * **h3-quinn** QUIC transport implementation based on [Quinn](https://github.com/quinn-rs/quinn/) ## Getting Started The [examples](./examples) directory can help get started in two ways: - There are ready-to-use `client` and `server` binaries to interact with _other_ HTTP/3 peers. Check the README in that directory. - The source code of those examples can help teach how to use `h3` as either a client or a server. ### Server ```rust let (endpoint, mut incoming) = h3_quinn::quinn::Endpoint::server(server_config, "[::]:443".parse()?)?; while let Some((req, stream)) = h3_conn.accept().await? { loop { match h3_conn.accept().await { Ok(Some((req, mut stream))) => { let resp = http::Response::builder().status(Status::OK).body(())?; stream.send_response(resp).await?; stream.send_data(Bytes::new("It works!")).await?; stream.finish().await?; } Ok(None) => { break; } Err(err) => { match err.get_error_level() { ErrorLevel::ConnectionError => break, ErrorLevel::StreamError => continue, } } } } } endpoint.wait_idle(); ``` You can find a full server example in [`examples/server.rs`](./examples/server.rs) ### Client ``` rust let addr: SocketAddr = "[::1]:443".parse()?; let quic = h3_quinn::Connection::new(client_endpoint.connect(addr, "server")?.await?); let (mut driver, mut send_request) = h3::client::new(quinn_conn).await?; let drive = async move { future::poll_fn(|cx| driver.poll_close(cx)).await?; Ok::<(), Box>(()) }; let request = async move { let req = http::Request::builder().uri(dest).body(())?; let mut stream = send_request.send_request(req).await?; stream.finish().await?; let resp = stream.recv_response().await?; while let Some(mut chunk) = stream.recv_data().await? { let mut out = tokio::io::stdout(); out.write_all_buf(&mut chunk).await?; out.flush().await?; } Ok::<_, Box>(()) }; let (req_res, drive_res) = tokio::join!(request, drive); req_res?; drive_res?; client_endpoint.wait_idle().await; ``` You can find a full client example in [`examples/client.rs`](./examples/client.rs) ## QUIC Generic As mentioned, the goal of this library is to be generic over a QUIC implementation. To that effect, integrations with QUIC libraries exist: - [`h3-quinn`](./h3-quinn/): in this same repository. - [`s2n-quic-h3`](https://github.com/aws/s2n-quic/tree/main/quic/s2n-quic-h3) ## Interoperability This crate as well as the quic implementations are tested ([quinn](https://github.com/quinn-rs/quinn-interop), [s2n-quic](https://github.com/aws/s2n-quic/tree/main/scripts/interop)) for interoperability and performance in the [quic-interop-runner](https://github.com/marten-seemann/quic-interop-runner). You can see the results at (https://interop.seemann.io/). ## License h3 is provided under the MIT license. See [LICENSE](LICENSE). h3-quinn-0.0.7/src/lib.rs000064400000000000000000000542611046102023000132050ustar 00000000000000//! QUIC Transport implementation with Quinn //! //! This module implements QUIC traits with Quinn. #![deny(missing_docs)] use std::{ convert::TryInto, fmt::{self, Display}, future::Future, pin::Pin, sync::Arc, task::{self, Poll}, }; use bytes::{Buf, Bytes, BytesMut}; use futures::{ ready, stream::{self}, Stream, StreamExt, }; pub use quinn::{self, AcceptBi, AcceptUni, Endpoint, OpenBi, OpenUni, VarInt, WriteError}; use quinn::{ApplicationClose, ClosedStream, ReadDatagram}; use h3::{ ext::Datagram, quic::{self, Error, StreamId, WriteBuf}, }; use tokio_util::sync::ReusableBoxFuture; #[cfg(feature = "tracing")] use tracing::instrument; /// BoxStream with Sync trait type BoxStreamSync<'a, T> = Pin + Sync + Send + 'a>>; /// A QUIC connection backed by Quinn /// /// Implements a [`quic::Connection`] backed by a [`quinn::Connection`]. pub struct Connection { conn: quinn::Connection, incoming_bi: BoxStreamSync<'static, as Future>::Output>, opening_bi: Option as Future>::Output>>, incoming_uni: BoxStreamSync<'static, as Future>::Output>, opening_uni: Option as Future>::Output>>, datagrams: BoxStreamSync<'static, as Future>::Output>, } impl Connection { /// Create a [`Connection`] from a [`quinn::Connection`] pub fn new(conn: quinn::Connection) -> Self { Self { conn: conn.clone(), incoming_bi: Box::pin(stream::unfold(conn.clone(), |conn| async { Some((conn.accept_bi().await, conn)) })), opening_bi: None, incoming_uni: Box::pin(stream::unfold(conn.clone(), |conn| async { Some((conn.accept_uni().await, conn)) })), opening_uni: None, datagrams: Box::pin(stream::unfold(conn, |conn| async { Some((conn.read_datagram().await, conn)) })), } } } /// The error type for [`Connection`] /// /// Wraps reasons a Quinn connection might be lost. #[derive(Debug)] pub struct ConnectionError(quinn::ConnectionError); impl std::error::Error for ConnectionError {} impl fmt::Display for ConnectionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl Error for ConnectionError { fn is_timeout(&self) -> bool { matches!(self.0, quinn::ConnectionError::TimedOut) } fn err_code(&self) -> Option { match self.0 { quinn::ConnectionError::ApplicationClosed(ApplicationClose { error_code, .. }) => { Some(error_code.into_inner()) } _ => None, } } } impl From for ConnectionError { fn from(e: quinn::ConnectionError) -> Self { Self(e) } } /// Types of errors when sending a datagram. #[derive(Debug)] pub enum SendDatagramError { /// Datagrams are not supported by the peer UnsupportedByPeer, /// Datagrams are locally disabled Disabled, /// The datagram was too large to be sent. TooLarge, /// Network error ConnectionLost(Box), } impl fmt::Display for SendDatagramError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { SendDatagramError::UnsupportedByPeer => write!(f, "datagrams not supported by peer"), SendDatagramError::Disabled => write!(f, "datagram support disabled"), SendDatagramError::TooLarge => write!(f, "datagram too large"), SendDatagramError::ConnectionLost(_) => write!(f, "connection lost"), } } } impl std::error::Error for SendDatagramError {} impl Error for SendDatagramError { fn is_timeout(&self) -> bool { false } fn err_code(&self) -> Option { match self { Self::ConnectionLost(err) => err.err_code(), _ => None, } } } impl From for SendDatagramError { fn from(value: quinn::SendDatagramError) -> Self { match value { quinn::SendDatagramError::UnsupportedByPeer => Self::UnsupportedByPeer, quinn::SendDatagramError::Disabled => Self::Disabled, quinn::SendDatagramError::TooLarge => Self::TooLarge, quinn::SendDatagramError::ConnectionLost(err) => { Self::ConnectionLost(ConnectionError::from(err).into()) } } } } impl quic::Connection for Connection where B: Buf, { type RecvStream = RecvStream; type OpenStreams = OpenStreams; type AcceptError = ConnectionError; #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_accept_bidi( &mut self, cx: &mut task::Context<'_>, ) -> Poll, Self::AcceptError>> { let (send, recv) = match ready!(self.incoming_bi.poll_next_unpin(cx)) { Some(x) => x?, None => return Poll::Ready(Ok(None)), }; Poll::Ready(Ok(Some(Self::BidiStream { send: Self::SendStream::new(send), recv: Self::RecvStream::new(recv), }))) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_accept_recv( &mut self, cx: &mut task::Context<'_>, ) -> Poll, Self::AcceptError>> { let recv = match ready!(self.incoming_uni.poll_next_unpin(cx)) { Some(x) => x?, None => return Poll::Ready(Ok(None)), }; Poll::Ready(Ok(Some(Self::RecvStream::new(recv)))) } fn opener(&self) -> Self::OpenStreams { OpenStreams { conn: self.conn.clone(), opening_bi: None, opening_uni: None, } } } impl quic::OpenStreams for Connection where B: Buf, { type SendStream = SendStream; type BidiStream = BidiStream; type OpenError = ConnectionError; #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_open_bidi( &mut self, cx: &mut task::Context<'_>, ) -> Poll> { if self.opening_bi.is_none() { self.opening_bi = Some(Box::pin(stream::unfold(self.conn.clone(), |conn| async { Some((conn.clone().open_bi().await, conn)) }))); } let (send, recv) = ready!(self.opening_bi.as_mut().unwrap().poll_next_unpin(cx)).unwrap()?; Poll::Ready(Ok(Self::BidiStream { send: Self::SendStream::new(send), recv: RecvStream::new(recv), })) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_open_send( &mut self, cx: &mut task::Context<'_>, ) -> Poll> { if self.opening_uni.is_none() { self.opening_uni = Some(Box::pin(stream::unfold(self.conn.clone(), |conn| async { Some((conn.open_uni().await, conn)) }))); } let send = ready!(self.opening_uni.as_mut().unwrap().poll_next_unpin(cx)).unwrap()?; Poll::Ready(Ok(Self::SendStream::new(send))) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn close(&mut self, code: h3::error::Code, reason: &[u8]) { self.conn.close( VarInt::from_u64(code.value()).expect("error code VarInt"), reason, ); } } impl quic::SendDatagramExt for Connection where B: Buf, { type Error = SendDatagramError; #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn send_datagram(&mut self, data: Datagram) -> Result<(), SendDatagramError> { // TODO investigate static buffer from known max datagram size let mut buf = BytesMut::new(); data.encode(&mut buf); self.conn.send_datagram(buf.freeze())?; Ok(()) } } impl quic::RecvDatagramExt for Connection { type Buf = Bytes; type Error = ConnectionError; #[inline] #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_accept_datagram( &mut self, cx: &mut task::Context<'_>, ) -> Poll, Self::Error>> { match ready!(self.datagrams.poll_next_unpin(cx)) { Some(Ok(x)) => Poll::Ready(Ok(Some(x))), Some(Err(e)) => Poll::Ready(Err(e.into())), None => Poll::Ready(Ok(None)), } } } /// Stream opener backed by a Quinn connection /// /// Implements [`quic::OpenStreams`] using [`quinn::Connection`], /// [`quinn::OpenBi`], [`quinn::OpenUni`]. pub struct OpenStreams { conn: quinn::Connection, opening_bi: Option as Future>::Output>>, opening_uni: Option as Future>::Output>>, } impl quic::OpenStreams for OpenStreams where B: Buf, { type SendStream = SendStream; type BidiStream = BidiStream; type OpenError = ConnectionError; #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_open_bidi( &mut self, cx: &mut task::Context<'_>, ) -> Poll> { if self.opening_bi.is_none() { self.opening_bi = Some(Box::pin(stream::unfold(self.conn.clone(), |conn| async { Some((conn.open_bi().await, conn)) }))); } let (send, recv) = ready!(self.opening_bi.as_mut().unwrap().poll_next_unpin(cx)).unwrap()?; Poll::Ready(Ok(Self::BidiStream { send: Self::SendStream::new(send), recv: RecvStream::new(recv), })) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_open_send( &mut self, cx: &mut task::Context<'_>, ) -> Poll> { if self.opening_uni.is_none() { self.opening_uni = Some(Box::pin(stream::unfold(self.conn.clone(), |conn| async { Some((conn.open_uni().await, conn)) }))); } let send = ready!(self.opening_uni.as_mut().unwrap().poll_next_unpin(cx)).unwrap()?; Poll::Ready(Ok(Self::SendStream::new(send))) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn close(&mut self, code: h3::error::Code, reason: &[u8]) { self.conn.close( VarInt::from_u64(code.value()).expect("error code VarInt"), reason, ); } } impl Clone for OpenStreams { fn clone(&self) -> Self { Self { conn: self.conn.clone(), opening_bi: None, opening_uni: None, } } } /// Quinn-backed bidirectional stream /// /// Implements [`quic::BidiStream`] which allows the stream to be split /// into two structs each implementing one direction. pub struct BidiStream where B: Buf, { send: SendStream, recv: RecvStream, } impl quic::BidiStream for BidiStream where B: Buf, { type SendStream = SendStream; type RecvStream = RecvStream; fn split(self) -> (Self::SendStream, Self::RecvStream) { (self.send, self.recv) } } impl quic::RecvStream for BidiStream { type Buf = Bytes; type Error = ReadError; fn poll_data( &mut self, cx: &mut task::Context<'_>, ) -> Poll, Self::Error>> { self.recv.poll_data(cx) } fn stop_sending(&mut self, error_code: u64) { self.recv.stop_sending(error_code) } fn recv_id(&self) -> StreamId { self.recv.recv_id() } } impl quic::SendStream for BidiStream where B: Buf, { type Error = SendStreamError; fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { self.send.poll_ready(cx) } fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll> { self.send.poll_finish(cx) } fn reset(&mut self, reset_code: u64) { self.send.reset(reset_code) } fn send_data>>(&mut self, data: D) -> Result<(), Self::Error> { self.send.send_data(data) } fn send_id(&self) -> StreamId { self.send.send_id() } } impl quic::SendStreamUnframed for BidiStream where B: Buf, { fn poll_send( &mut self, cx: &mut task::Context<'_>, buf: &mut D, ) -> Poll> { self.send.poll_send(cx, buf) } } /// Quinn-backed receive stream /// /// Implements a [`quic::RecvStream`] backed by a [`quinn::RecvStream`]. pub struct RecvStream { stream: Option, read_chunk_fut: ReadChunkFuture, } type ReadChunkFuture = ReusableBoxFuture< 'static, ( quinn::RecvStream, Result, quinn::ReadError>, ), >; impl RecvStream { fn new(stream: quinn::RecvStream) -> Self { Self { stream: Some(stream), // Should only allocate once the first time it's used read_chunk_fut: ReusableBoxFuture::new(async { unreachable!() }), } } } impl quic::RecvStream for RecvStream { type Buf = Bytes; type Error = ReadError; #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_data( &mut self, cx: &mut task::Context<'_>, ) -> Poll, Self::Error>> { if let Some(mut stream) = self.stream.take() { self.read_chunk_fut.set(async move { let chunk = stream.read_chunk(usize::MAX, true).await; (stream, chunk) }) }; let (stream, chunk) = ready!(self.read_chunk_fut.poll(cx)); self.stream = Some(stream); Poll::Ready(Ok(chunk?.map(|c| c.bytes))) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn stop_sending(&mut self, error_code: u64) { self.stream .as_mut() .unwrap() .stop(VarInt::from_u64(error_code).expect("invalid error_code")) .ok(); } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn recv_id(&self) -> StreamId { self.stream .as_ref() .unwrap() .id() .0 .try_into() .expect("invalid stream id") } } /// The error type for [`RecvStream`] /// /// Wraps errors that occur when reading from a receive stream. #[derive(Debug)] pub struct ReadError(quinn::ReadError); impl From for std::io::Error { fn from(value: ReadError) -> Self { value.0.into() } } impl std::error::Error for ReadError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } } impl fmt::Display for ReadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl From for Arc { fn from(e: ReadError) -> Self { Arc::new(e) } } impl From for ReadError { fn from(e: quinn::ReadError) -> Self { Self(e) } } impl Error for ReadError { fn is_timeout(&self) -> bool { matches!( self.0, quinn::ReadError::ConnectionLost(quinn::ConnectionError::TimedOut) ) } fn err_code(&self) -> Option { match self.0 { quinn::ReadError::ConnectionLost(quinn::ConnectionError::ApplicationClosed( ApplicationClose { error_code, .. }, )) => Some(error_code.into_inner()), quinn::ReadError::Reset(error_code) => Some(error_code.into_inner()), _ => None, } } } /// Quinn-backed send stream /// /// Implements a [`quic::SendStream`] backed by a [`quinn::SendStream`]. pub struct SendStream { stream: Option, writing: Option>, write_fut: WriteFuture, } type WriteFuture = ReusableBoxFuture<'static, (quinn::SendStream, Result)>; impl SendStream where B: Buf, { fn new(stream: quinn::SendStream) -> SendStream { Self { stream: Some(stream), writing: None, write_fut: ReusableBoxFuture::new(async { unreachable!() }), } } } impl quic::SendStream for SendStream where B: Buf, { type Error = SendStreamError; #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { if let Some(ref mut data) = self.writing { while data.has_remaining() { if let Some(mut stream) = self.stream.take() { let chunk = data.chunk().to_owned(); // FIXME - avoid copy self.write_fut.set(async move { let ret = stream.write(&chunk).await; (stream, ret) }); } let (stream, res) = ready!(self.write_fut.poll(cx)); self.stream = Some(stream); match res { Ok(cnt) => data.advance(cnt), Err(err) => { return Poll::Ready(Err(SendStreamError::Write(err))); } } } } self.writing = None; Poll::Ready(Ok(())) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_finish(&mut self, _cx: &mut task::Context<'_>) -> Poll> { Poll::Ready(self.stream.as_mut().unwrap().finish().map_err(|e| e.into())) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn reset(&mut self, reset_code: u64) { let _ = self .stream .as_mut() .unwrap() .reset(VarInt::from_u64(reset_code).unwrap_or(VarInt::MAX)); } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn send_data>>(&mut self, data: D) -> Result<(), Self::Error> { if self.writing.is_some() { return Err(Self::Error::NotReady); } self.writing = Some(data.into()); Ok(()) } #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn send_id(&self) -> StreamId { self.stream .as_ref() .unwrap() .id() .0 .try_into() .expect("invalid stream id") } } impl quic::SendStreamUnframed for SendStream where B: Buf, { #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] fn poll_send( &mut self, cx: &mut task::Context<'_>, buf: &mut D, ) -> Poll> { if self.writing.is_some() { // This signifies a bug in implementation panic!("poll_send called while send stream is not ready") } let s = Pin::new(self.stream.as_mut().unwrap()); let res = ready!(futures::io::AsyncWrite::poll_write(s, cx, buf.chunk())); match res { Ok(written) => { buf.advance(written); Poll::Ready(Ok(written)) } Err(err) => { // We are forced to use AsyncWrite for now because we cannot store // the result of a call to: // quinn::send_stream::write<'a>(&'a mut self, buf: &'a [u8]) -> Result. // // This is why we have to unpack the error from io::Error instead of having it // returned directly. This should not panic as long as quinn's AsyncWrite impl // doesn't change. let err = err .into_inner() .expect("write stream returned an empty error") .downcast::() .expect("write stream returned an error which type is not WriteError"); Poll::Ready(Err(SendStreamError::Write(*err))) } } } } /// The error type for [`SendStream`] /// /// Wraps errors that can happen writing to or polling a send stream. #[derive(Debug)] pub enum SendStreamError { /// Errors when writing, wrapping a [`quinn::WriteError`] Write(WriteError), /// Error when the stream is not ready, because it is still sending /// data from a previous call NotReady, /// Error when the stream is closed StreamClosed(ClosedStream), } impl From for std::io::Error { fn from(value: SendStreamError) -> Self { match value { SendStreamError::Write(err) => err.into(), SendStreamError::NotReady => { std::io::Error::new(std::io::ErrorKind::Other, "send stream is not ready") } SendStreamError::StreamClosed(err) => err.into(), } } } impl std::error::Error for SendStreamError {} impl Display for SendStreamError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl From for SendStreamError { fn from(e: WriteError) -> Self { Self::Write(e) } } impl From for SendStreamError { fn from(value: ClosedStream) -> Self { Self::StreamClosed(value) } } impl Error for SendStreamError { fn is_timeout(&self) -> bool { matches!( self, Self::Write(quinn::WriteError::ConnectionLost( quinn::ConnectionError::TimedOut )) ) } fn err_code(&self) -> Option { match self { Self::Write(quinn::WriteError::Stopped(error_code)) => Some(error_code.into_inner()), Self::Write(quinn::WriteError::ConnectionLost( quinn::ConnectionError::ApplicationClosed(ApplicationClose { error_code, .. }), )) => Some(error_code.into_inner()), _ => None, } } } impl From for Arc { fn from(e: SendStreamError) -> Self { Arc::new(e) } }