wip: implemented hyper-1.0 for http/1.1 and http/2. todo: http/3 and backend handler
This commit is contained in:
parent
f3e8f8445f
commit
b639e79b4d
24 changed files with 1134 additions and 1275 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit b86df1220775d13b89cead99e787944b55991b1e
|
||||
Subproject commit 5c161952b02e663f31f9b83829bafa7a047b6627
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
[package]
|
||||
name = "h3-quinn"
|
||||
version = "0.0.4"
|
||||
rust-version = "1.63"
|
||||
authors = ["Jean-Christophe BEGUE <jc.begue@pm.me>"]
|
||||
edition = "2018"
|
||||
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.3", path = "../h3/h3" }
|
||||
bytes = "1"
|
||||
quinn = { path = "../quinn/quinn/", default-features = false, features = [
|
||||
"futures-io",
|
||||
] }
|
||||
quinn-proto = { path = "../quinn/quinn-proto/", default-features = false }
|
||||
tokio-util = { version = "0.7.9" }
|
||||
futures = { version = "0.3.28" }
|
||||
tokio = { version = "1.33.0", features = ["io-util"], default-features = false }
|
||||
|
|
@ -1,740 +0,0 @@
|
|||
//! 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, BoxStream},
|
||||
StreamExt,
|
||||
};
|
||||
use quinn::ReadDatagram;
|
||||
pub use quinn::{
|
||||
self, crypto::Session, AcceptBi, AcceptUni, Endpoint, OpenBi, OpenUni, VarInt, WriteError,
|
||||
};
|
||||
|
||||
use h3::{
|
||||
ext::Datagram,
|
||||
quic::{self, Error, StreamId, WriteBuf},
|
||||
};
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
/// A QUIC connection backed by Quinn
|
||||
///
|
||||
/// Implements a [`quic::Connection`] backed by a [`quinn::Connection`].
|
||||
pub struct Connection {
|
||||
conn: quinn::Connection,
|
||||
incoming_bi: BoxStream<'static, <AcceptBi<'static> as Future>::Output>,
|
||||
opening_bi: Option<BoxStream<'static, <OpenBi<'static> as Future>::Output>>,
|
||||
incoming_uni: BoxStream<'static, <AcceptUni<'static> as Future>::Output>,
|
||||
opening_uni: Option<BoxStream<'static, <OpenUni<'static> as Future>::Output>>,
|
||||
datagrams: BoxStream<'static, <ReadDatagram<'static> as Future>::Output>,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Create a [`Connection`] from a [`quinn::NewConnection`]
|
||||
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<u64> {
|
||||
match self.0 {
|
||||
quinn::ConnectionError::ApplicationClosed(quinn_proto::ApplicationClose {
|
||||
error_code,
|
||||
..
|
||||
}) => Some(error_code.into_inner()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quinn::ConnectionError> 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<dyn Error>),
|
||||
}
|
||||
|
||||
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<u64> {
|
||||
match self {
|
||||
Self::ConnectionLost(err) => err.err_code(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quinn::SendDatagramError> 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<B> quic::Connection<B> for Connection
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type SendStream = SendStream<B>;
|
||||
type RecvStream = RecvStream;
|
||||
type BidiStream = BidiStream<B>;
|
||||
type OpenStreams = OpenStreams;
|
||||
type Error = ConnectionError;
|
||||
|
||||
fn poll_accept_bidi(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::BidiStream>, Self::Error>> {
|
||||
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),
|
||||
})))
|
||||
}
|
||||
|
||||
fn poll_accept_recv(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::RecvStream>, Self::Error>> {
|
||||
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 poll_open_bidi(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::BidiStream, Self::Error>> {
|
||||
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: Self::RecvStream::new(recv),
|
||||
}))
|
||||
}
|
||||
|
||||
fn poll_open_send(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::SendStream, Self::Error>> {
|
||||
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)))
|
||||
}
|
||||
|
||||
fn opener(&self) -> Self::OpenStreams {
|
||||
OpenStreams {
|
||||
conn: self.conn.clone(),
|
||||
opening_bi: None,
|
||||
opening_uni: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, code: h3::error::Code, reason: &[u8]) {
|
||||
self.conn.close(
|
||||
VarInt::from_u64(code.value()).expect("error code VarInt"),
|
||||
reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::SendDatagramExt<B> for Connection
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type Error = SendDatagramError;
|
||||
|
||||
fn send_datagram(&mut self, data: Datagram<B>) -> 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]
|
||||
fn poll_accept_datagram(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::Buf>, 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<BoxStream<'static, <OpenBi<'static> as Future>::Output>>,
|
||||
opening_uni: Option<BoxStream<'static, <OpenUni<'static> as Future>::Output>>,
|
||||
}
|
||||
|
||||
impl<B> quic::OpenStreams<B> for OpenStreams
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type RecvStream = RecvStream;
|
||||
type SendStream = SendStream<B>;
|
||||
type BidiStream = BidiStream<B>;
|
||||
type Error = ConnectionError;
|
||||
|
||||
fn poll_open_bidi(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::BidiStream, Self::Error>> {
|
||||
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: Self::RecvStream::new(recv),
|
||||
}))
|
||||
}
|
||||
|
||||
fn poll_open_send(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::SendStream, Self::Error>> {
|
||||
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)))
|
||||
}
|
||||
|
||||
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<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
send: SendStream<B>,
|
||||
recv: RecvStream,
|
||||
}
|
||||
|
||||
impl<B> quic::BidiStream<B> for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type SendStream = SendStream<B>;
|
||||
type RecvStream = RecvStream;
|
||||
|
||||
fn split(self) -> (Self::SendStream, Self::RecvStream) {
|
||||
(self.send, self.recv)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buf> quic::RecvStream for BidiStream<B> {
|
||||
type Buf = Bytes;
|
||||
type Error = ReadError;
|
||||
|
||||
fn poll_data(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::Buf>, 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<B> quic::SendStream<B> for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type Error = SendStreamError;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.send.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.send.poll_finish(cx)
|
||||
}
|
||||
|
||||
fn reset(&mut self, reset_code: u64) {
|
||||
self.send.reset(reset_code)
|
||||
}
|
||||
|
||||
fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), Self::Error> {
|
||||
self.send.send_data(data)
|
||||
}
|
||||
|
||||
fn send_id(&self) -> StreamId {
|
||||
self.send.send_id()
|
||||
}
|
||||
}
|
||||
impl<B> quic::SendStreamUnframed<B> for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
fn poll_send<D: Buf>(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut D,
|
||||
) -> Poll<Result<usize, Self::Error>> {
|
||||
self.send.poll_send(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Quinn-backed receive stream
|
||||
///
|
||||
/// Implements a [`quic::RecvStream`] backed by a [`quinn::RecvStream`].
|
||||
pub struct RecvStream {
|
||||
stream: Option<quinn::RecvStream>,
|
||||
read_chunk_fut: ReadChunkFuture,
|
||||
}
|
||||
|
||||
type ReadChunkFuture = ReusableBoxFuture<
|
||||
'static,
|
||||
(
|
||||
quinn::RecvStream,
|
||||
Result<Option<quinn::Chunk>, 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;
|
||||
|
||||
fn poll_data(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::Buf>, 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)))
|
||||
}
|
||||
|
||||
fn stop_sending(&mut self, error_code: u64) {
|
||||
self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.stop(VarInt::from_u64(error_code).expect("invalid error_code"))
|
||||
.ok();
|
||||
}
|
||||
|
||||
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<ReadError> 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<ReadError> for Arc<dyn Error> {
|
||||
fn from(e: ReadError) -> Self {
|
||||
Arc::new(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quinn::ReadError> 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<u64> {
|
||||
match self.0 {
|
||||
quinn::ReadError::ConnectionLost(quinn::ConnectionError::ApplicationClosed(
|
||||
quinn_proto::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<B: Buf> {
|
||||
stream: Option<quinn::SendStream>,
|
||||
writing: Option<WriteBuf<B>>,
|
||||
write_fut: WriteFuture,
|
||||
}
|
||||
|
||||
type WriteFuture =
|
||||
ReusableBoxFuture<'static, (quinn::SendStream, Result<usize, quinn::WriteError>)>;
|
||||
|
||||
impl<B> SendStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
fn new(stream: quinn::SendStream) -> SendStream<B> {
|
||||
Self {
|
||||
stream: Some(stream),
|
||||
writing: None,
|
||||
write_fut: ReusableBoxFuture::new(async { unreachable!() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::SendStream<B> for SendStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type Error = SendStreamError;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
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(()))
|
||||
}
|
||||
|
||||
fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.poll_finish(cx)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn reset(&mut self, reset_code: u64) {
|
||||
let _ = self
|
||||
.stream
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset(VarInt::from_u64(reset_code).unwrap_or(VarInt::MAX));
|
||||
}
|
||||
|
||||
fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), Self::Error> {
|
||||
if self.writing.is_some() {
|
||||
return Err(Self::Error::NotReady);
|
||||
}
|
||||
self.writing = Some(data.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_id(&self) -> StreamId {
|
||||
self.stream
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.id()
|
||||
.0
|
||||
.try_into()
|
||||
.expect("invalid stream id")
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::SendStreamUnframed<B> for SendStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
fn poll_send<D: Buf>(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut D,
|
||||
) -> Poll<Result<usize, Self::Error>> {
|
||||
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<usize, WriteError>.
|
||||
//
|
||||
// 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::<WriteError>()
|
||||
.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,
|
||||
}
|
||||
|
||||
impl From<SendStreamError> 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<WriteError> for SendStreamError {
|
||||
fn from(e: WriteError) -> Self {
|
||||
Self::Write(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SendStreamError {
|
||||
fn is_timeout(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Write(quinn::WriteError::ConnectionLost(
|
||||
quinn::ConnectionError::TimedOut
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
fn err_code(&self) -> Option<u64> {
|
||||
match self {
|
||||
Self::Write(quinn::WriteError::Stopped(error_code)) => Some(error_code.into_inner()),
|
||||
Self::Write(quinn::WriteError::ConnectionLost(
|
||||
quinn::ConnectionError::ApplicationClosed(quinn_proto::ApplicationClose {
|
||||
error_code,
|
||||
..
|
||||
}),
|
||||
)) => Some(error_code.into_inner()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SendStreamError> for Arc<dyn Error> {
|
||||
fn from(e: SendStreamError) -> Self {
|
||||
Arc::new(e)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 6d80efeeae60b96ff330ae6a70e8cc9291fcc615
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 30027eeacc7b620da62fc4825b94afd57ab0c7be
|
||||
17
submodules/s2n-quic-h3/Cargo.toml
Normal file
17
submodules/s2n-quic-h3/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "s2n-quic-h3"
|
||||
# this in an unpublished internal crate so the version should not be changed
|
||||
version = "0.1.0"
|
||||
authors = ["AWS s2n"]
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
license = "Apache-2.0"
|
||||
# this contains an http3 implementation for testing purposes and should not be published
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bytes = { version = "1", default-features = false }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
h3 = { path = "../h3/h3/" }
|
||||
s2n-quic = "1.31.0"
|
||||
s2n-quic-core = "0.31.0"
|
||||
10
submodules/s2n-quic-h3/README.md
Normal file
10
submodules/s2n-quic-h3/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# s2n-quic-h3
|
||||
|
||||
This is an internal crate used by [s2n-quic](https://github.com/aws/s2n-quic) written as a proof of concept for implementing HTTP3 on top of s2n-quic. The API is not currently stable and should not be used directly.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [Apache-2.0 License][license-url].
|
||||
|
||||
[license-badge]: https://img.shields.io/badge/license-apache-blue.svg
|
||||
[license-url]: https://aws.amazon.com/apache-2-0/
|
||||
7
submodules/s2n-quic-h3/src/lib.rs
Normal file
7
submodules/s2n-quic-h3/src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod s2n_quic;
|
||||
|
||||
pub use self::s2n_quic::*;
|
||||
pub use h3;
|
||||
506
submodules/s2n-quic-h3/src/s2n_quic.rs
Normal file
506
submodules/s2n-quic-h3/src/s2n_quic.rs
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bytes::{Buf, Bytes};
|
||||
use futures::ready;
|
||||
use h3::quic::{self, Error, StreamId, WriteBuf};
|
||||
use s2n_quic::stream::{BidirectionalStream, ReceiveStream};
|
||||
use s2n_quic_core::varint::VarInt;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fmt::{self, Display},
|
||||
sync::Arc,
|
||||
task::{self, Poll},
|
||||
};
|
||||
|
||||
pub struct Connection {
|
||||
conn: s2n_quic::connection::Handle,
|
||||
bidi_acceptor: s2n_quic::connection::BidirectionalStreamAcceptor,
|
||||
recv_acceptor: s2n_quic::connection::ReceiveStreamAcceptor,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new(new_conn: s2n_quic::Connection) -> Self {
|
||||
let (handle, acceptor) = new_conn.split();
|
||||
let (bidi, recv) = acceptor.split();
|
||||
|
||||
Self {
|
||||
conn: handle,
|
||||
bidi_acceptor: bidi,
|
||||
recv_acceptor: recv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectionError(s2n_quic::connection::Error);
|
||||
|
||||
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, s2n_quic::connection::Error::IdleTimerExpired { .. })
|
||||
}
|
||||
|
||||
fn err_code(&self) -> Option<u64> {
|
||||
match self.0 {
|
||||
s2n_quic::connection::Error::Application { error, .. } => Some(error.into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<s2n_quic::connection::Error> for ConnectionError {
|
||||
fn from(e: s2n_quic::connection::Error) -> Self {
|
||||
Self(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::Connection<B> for Connection
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type BidiStream = BidiStream<B>;
|
||||
type SendStream = SendStream<B>;
|
||||
type RecvStream = RecvStream;
|
||||
type OpenStreams = OpenStreams;
|
||||
type Error = ConnectionError;
|
||||
|
||||
fn poll_accept_recv(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::RecvStream>, Self::Error>> {
|
||||
let recv = match ready!(self.recv_acceptor.poll_accept_receive_stream(cx))? {
|
||||
Some(x) => x,
|
||||
None => return Poll::Ready(Ok(None)),
|
||||
};
|
||||
Poll::Ready(Ok(Some(Self::RecvStream::new(recv))))
|
||||
}
|
||||
|
||||
fn poll_accept_bidi(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::BidiStream>, Self::Error>> {
|
||||
let (recv, send) = match ready!(self.bidi_acceptor.poll_accept_bidirectional_stream(cx))? {
|
||||
Some(x) => x.split(),
|
||||
None => return Poll::Ready(Ok(None)),
|
||||
};
|
||||
Poll::Ready(Ok(Some(Self::BidiStream {
|
||||
send: Self::SendStream::new(send),
|
||||
recv: Self::RecvStream::new(recv),
|
||||
})))
|
||||
}
|
||||
|
||||
fn poll_open_bidi(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::BidiStream, Self::Error>> {
|
||||
let stream = ready!(self.conn.poll_open_bidirectional_stream(cx))?;
|
||||
Ok(stream.into()).into()
|
||||
}
|
||||
|
||||
fn poll_open_send(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::SendStream, Self::Error>> {
|
||||
let stream = ready!(self.conn.poll_open_send_stream(cx))?;
|
||||
Ok(stream.into()).into()
|
||||
}
|
||||
|
||||
fn opener(&self) -> Self::OpenStreams {
|
||||
OpenStreams {
|
||||
conn: self.conn.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, code: h3::error::Code, _reason: &[u8]) {
|
||||
self.conn.close(
|
||||
code.value()
|
||||
.try_into()
|
||||
.expect("s2n-quic supports error codes up to 2^62-1"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenStreams {
|
||||
conn: s2n_quic::connection::Handle,
|
||||
}
|
||||
|
||||
impl<B> quic::OpenStreams<B> for OpenStreams
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type BidiStream = BidiStream<B>;
|
||||
type SendStream = SendStream<B>;
|
||||
type RecvStream = RecvStream;
|
||||
type Error = ConnectionError;
|
||||
|
||||
fn poll_open_bidi(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::BidiStream, Self::Error>> {
|
||||
let stream = ready!(self.conn.poll_open_bidirectional_stream(cx))?;
|
||||
Ok(stream.into()).into()
|
||||
}
|
||||
|
||||
fn poll_open_send(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Self::SendStream, Self::Error>> {
|
||||
let stream = ready!(self.conn.poll_open_send_stream(cx))?;
|
||||
Ok(stream.into()).into()
|
||||
}
|
||||
|
||||
fn close(&mut self, code: h3::error::Code, _reason: &[u8]) {
|
||||
self.conn.close(
|
||||
code.value()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| VarInt::MAX.into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for OpenStreams {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
conn: self.conn.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
send: SendStream<B>,
|
||||
recv: RecvStream,
|
||||
}
|
||||
|
||||
impl<B> quic::BidiStream<B> for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type SendStream = SendStream<B>;
|
||||
type RecvStream = RecvStream;
|
||||
|
||||
fn split(self) -> (Self::SendStream, Self::RecvStream) {
|
||||
(self.send, self.recv)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::RecvStream for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type Buf = Bytes;
|
||||
type Error = ReadError;
|
||||
|
||||
fn poll_data(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::Buf>, 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.stream.id().try_into().expect("invalid stream id")
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::SendStream<B> for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type Error = SendStreamError;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.send.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.send.poll_finish(cx)
|
||||
}
|
||||
|
||||
fn reset(&mut self, reset_code: u64) {
|
||||
self.send.reset(reset_code)
|
||||
}
|
||||
|
||||
fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), Self::Error> {
|
||||
self.send.send_data(data)
|
||||
}
|
||||
|
||||
fn send_id(&self) -> StreamId {
|
||||
self.send.stream.id().try_into().expect("invalid stream id")
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<BidirectionalStream> for BidiStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
fn from(bidi: BidirectionalStream) -> Self {
|
||||
let (recv, send) = bidi.split();
|
||||
BidiStream {
|
||||
send: send.into(),
|
||||
recv: recv.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RecvStream {
|
||||
stream: s2n_quic::stream::ReceiveStream,
|
||||
}
|
||||
|
||||
impl RecvStream {
|
||||
fn new(stream: s2n_quic::stream::ReceiveStream) -> Self {
|
||||
Self { stream }
|
||||
}
|
||||
}
|
||||
|
||||
impl quic::RecvStream for RecvStream {
|
||||
type Buf = Bytes;
|
||||
type Error = ReadError;
|
||||
|
||||
fn poll_data(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Result<Option<Self::Buf>, Self::Error>> {
|
||||
let buf = ready!(self.stream.poll_receive(cx))?;
|
||||
Ok(buf).into()
|
||||
}
|
||||
|
||||
fn stop_sending(&mut self, error_code: u64) {
|
||||
let _ = self.stream.stop_sending(
|
||||
s2n_quic::application::Error::new(error_code)
|
||||
.expect("s2n-quic supports error codes up to 2^62-1"),
|
||||
);
|
||||
}
|
||||
|
||||
fn recv_id(&self) -> StreamId {
|
||||
self.stream.id().try_into().expect("invalid stream id")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReceiveStream> for RecvStream {
|
||||
fn from(recv: ReceiveStream) -> Self {
|
||||
RecvStream::new(recv)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReadError(s2n_quic::stream::Error);
|
||||
|
||||
impl std::error::Error for ReadError {}
|
||||
|
||||
impl fmt::Display for ReadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReadError> for Arc<dyn Error> {
|
||||
fn from(e: ReadError) -> Self {
|
||||
Arc::new(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<s2n_quic::stream::Error> for ReadError {
|
||||
fn from(e: s2n_quic::stream::Error) -> Self {
|
||||
Self(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ReadError {
|
||||
fn is_timeout(&self) -> bool {
|
||||
matches!(
|
||||
self.0,
|
||||
s2n_quic::stream::Error::ConnectionError {
|
||||
error: s2n_quic::connection::Error::IdleTimerExpired { .. },
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn err_code(&self) -> Option<u64> {
|
||||
match self.0 {
|
||||
s2n_quic::stream::Error::ConnectionError {
|
||||
error: s2n_quic::connection::Error::Application { error, .. },
|
||||
..
|
||||
} => Some(error.into()),
|
||||
s2n_quic::stream::Error::StreamReset { error, .. } => Some(error.into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SendStream<B: Buf> {
|
||||
stream: s2n_quic::stream::SendStream,
|
||||
chunk: Option<Bytes>,
|
||||
buf: Option<WriteBuf<B>>, // TODO: Replace with buf: PhantomData<B>
|
||||
// after https://github.com/hyperium/h3/issues/78 is resolved
|
||||
}
|
||||
|
||||
impl<B> SendStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
fn new(stream: s2n_quic::stream::SendStream) -> SendStream<B> {
|
||||
Self {
|
||||
stream,
|
||||
chunk: None,
|
||||
buf: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> quic::SendStream<B> for SendStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
type Error = SendStreamError;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
loop {
|
||||
// try to flush the current chunk if we have one
|
||||
if let Some(chunk) = self.chunk.as_mut() {
|
||||
ready!(self.stream.poll_send(chunk, cx))?;
|
||||
|
||||
// s2n-quic will take the whole chunk on send, even if it exceeds the limits
|
||||
debug_assert!(chunk.is_empty());
|
||||
self.chunk = None;
|
||||
}
|
||||
|
||||
// try to take the next chunk from the WriteBuf
|
||||
if let Some(ref mut data) = self.buf {
|
||||
let len = data.chunk().len();
|
||||
|
||||
// if the write buf is empty, then clear it and break
|
||||
if len == 0 {
|
||||
self.buf = None;
|
||||
break;
|
||||
}
|
||||
|
||||
// copy the first chunk from WriteBuf and prepare it to flush
|
||||
let chunk = data.copy_to_bytes(len);
|
||||
self.chunk = Some(chunk);
|
||||
|
||||
// loop back around to flush the chunk
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we didn't have either a chunk or WriteBuf, then we're ready
|
||||
break;
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
|
||||
// TODO: Replace with following after https://github.com/hyperium/h3/issues/78 is resolved
|
||||
// self.available_bytes = ready!(self.stream.poll_send_ready(cx))?;
|
||||
// Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), Self::Error> {
|
||||
if self.buf.is_some() {
|
||||
return Err(Self::Error::NotReady);
|
||||
}
|
||||
self.buf = Some(data.into());
|
||||
Ok(())
|
||||
|
||||
// TODO: Replace with following after https://github.com/hyperium/h3/issues/78 is resolved
|
||||
// let mut data = data.into();
|
||||
// while self.available_bytes > 0 && data.has_remaining() {
|
||||
// let len = data.chunk().len();
|
||||
// let chunk = data.copy_to_bytes(len);
|
||||
// self.stream.send_data(chunk)?;
|
||||
// self.available_bytes = self.available_bytes.saturating_sub(len);
|
||||
// }
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// ensure all chunks are flushed to the QUIC stream before finishing
|
||||
ready!(self.poll_ready(cx))?;
|
||||
self.stream.finish()?;
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
fn reset(&mut self, reset_code: u64) {
|
||||
let _ = self
|
||||
.stream
|
||||
.reset(reset_code.try_into().unwrap_or_else(|_| VarInt::MAX.into()));
|
||||
}
|
||||
|
||||
fn send_id(&self) -> StreamId {
|
||||
self.stream.id().try_into().expect("invalid stream id")
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<s2n_quic::stream::SendStream> for SendStream<B>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
fn from(send: s2n_quic::stream::SendStream) -> Self {
|
||||
SendStream::new(send)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SendStreamError {
|
||||
Write(s2n_quic::stream::Error),
|
||||
NotReady,
|
||||
}
|
||||
|
||||
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<s2n_quic::stream::Error> for SendStreamError {
|
||||
fn from(e: s2n_quic::stream::Error) -> Self {
|
||||
Self::Write(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SendStreamError {
|
||||
fn is_timeout(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Write(s2n_quic::stream::Error::ConnectionError {
|
||||
error: s2n_quic::connection::Error::IdleTimerExpired { .. },
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn err_code(&self) -> Option<u64> {
|
||||
match self {
|
||||
Self::Write(s2n_quic::stream::Error::StreamReset { error, .. }) => {
|
||||
Some((*error).into())
|
||||
}
|
||||
Self::Write(s2n_quic::stream::Error::ConnectionError {
|
||||
error: s2n_quic::connection::Error::Application { error, .. },
|
||||
..
|
||||
}) => Some((*error).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SendStreamError> for Arc<dyn Error> {
|
||||
fn from(e: SendStreamError) -> Self {
|
||||
Arc::new(e)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue