From 84701a7d5937f5d3c0819059a28062d727b48319 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Tue, 28 Mar 2023 22:40:55 -0400 Subject: [PATCH 01/97] add cert generator --- examples/generate_certs.sh | 7 +++++++ h3/src/proto/frame.rs | 25 +++++++++++++++++++++++++ h3/src/proto/stream.rs | 1 + h3/src/server.rs | 7 +++++-- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100755 examples/generate_certs.sh diff --git a/examples/generate_certs.sh b/examples/generate_certs.sh new file mode 100755 index 00000000..d80ecfd3 --- /dev/null +++ b/examples/generate_certs.sh @@ -0,0 +1,7 @@ + +# use mkcert to generate certs for localhost +mkcert localhost + +# convert certs from pem to der +openssl x509 -outform der -in localhost.pem -out localhost.crt +openssl rsa -outform der -in localhost-key.pem -out localhost.key diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 340a9598..1b9da3d5 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -46,6 +46,7 @@ pub enum Frame { PushPromise(PushPromise), Goaway(VarInt), MaxPushId(PushId), + WebTransportStream(WebTransportStream), Grease, } @@ -80,6 +81,7 @@ impl Frame { } let mut payload = buf.take(len as usize); + trace!("frame ty: {:?}", ty); let frame = match ty { FrameType::HEADERS => Ok(Frame::Headers(payload.copy_to_bytes(len as usize))), FrameType::SETTINGS => Ok(Frame::Settings(Settings::decode(&mut payload)?)), @@ -87,6 +89,7 @@ impl Frame { FrameType::PUSH_PROMISE => Ok(Frame::PushPromise(PushPromise::decode(&mut payload)?)), FrameType::GOAWAY => Ok(Frame::Goaway(VarInt::decode(&mut payload)?)), FrameType::MAX_PUSH_ID => Ok(Frame::MaxPushId(payload.get_var()?.try_into()?)), + FrameType::WEBTRANSPORT_STREAM =>Ok(Frame::WebTransportStream(WebTransportStream { buffer: payload.copy_to_bytes(len as usize)})), FrameType::H2_PRIORITY | FrameType::H2_PING | FrameType::H2_WINDOW_UPDATE @@ -132,6 +135,7 @@ where buf.write_var(6); buf.put_slice(b"grease"); } + Frame::WebTransportStream(_) => todo!(), } } } @@ -189,6 +193,7 @@ impl fmt::Debug for Frame { Frame::Goaway(id) => write!(f, "GoAway({})", id), Frame::MaxPushId(id) => write!(f, "MaxPushId({})", id), Frame::Grease => write!(f, "Grease()"), + Frame::WebTransportStream(_) => todo!(), } } } @@ -207,6 +212,7 @@ where Frame::Goaway(id) => write!(f, "GoAway({})", id), Frame::MaxPushId(id) => write!(f, "MaxPushId({})", id), Frame::Grease => write!(f, "Grease()"), + Frame::WebTransportStream(_) => write!(f, "WebTransportStream()"), } } } @@ -226,6 +232,9 @@ impl PartialEq> for Frame { Frame::Goaway(x) => matches!(other, Frame::Goaway(y) if x == y), Frame::MaxPushId(x) => matches!(other, Frame::MaxPushId(y) if x == y), Frame::Grease => matches!(other, Frame::Grease), + Frame::WebTransportStream(x) => { + matches!(other, Frame::WebTransportStream(y) if x == y) + } } } } @@ -257,6 +266,7 @@ frame_types! { H2_WINDOW_UPDATE = 0x8, H2_CONTINUATION = 0x9, MAX_PUSH_ID = 0xD, + WEBTRANSPORT_STREAM = 0x41, } impl FrameType { @@ -297,6 +307,11 @@ pub struct PushPromise { encoded: Bytes, } +#[derive(Debug, PartialEq)] +pub struct WebTransportStream +{ + buffer: Bytes, +} impl FrameHeader for PushPromise { const TYPE: FrameType = FrameType::PUSH_PROMISE; @@ -389,6 +404,16 @@ setting_identifiers! { QPACK_MAX_TABLE_CAPACITY = 0x1, QPACK_MAX_BLOCKED_STREAMS = 0x7, MAX_HEADER_LIST_SIZE = 0x6, + QPACK_BLOCKED_STREAMS = 0x7, + // https://datatracker.ietf.org/doc/html/rfc9220#section-5 + ENABLE_CONNECT_PROTOCOL = 0x8, + // https://datatracker.ietf.org/doc/html/draft-ietf-masque-h3-datagram-05#section-9.1 + H3_DATAGRAM = 0xFFD277, + // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http2-02#section-10.1 + ENABLE_WEBTRANSPORT = 0x2B603742, + // Dummy setting to check it is correctly ignored by the peer. + // https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1 + DUMMY = 0x21, } const SETTINGS_LEN: usize = 4; diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index ad53a3d6..5ff56d37 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -26,6 +26,7 @@ stream_types! { PUSH = 0x01, ENCODER = 0x02, DECODER = 0x03, + WEBTRANSPORT = 0x54, } impl StreamType { diff --git a/h3/src/server.rs b/h3/src/server.rs index 596766ee..c525705c 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -72,7 +72,7 @@ use crate::{ quic::{self, RecvStream as _, SendStream as _}, stream, }; -use tracing::{error, trace, warn}; +use tracing::{error, trace, warn, info}; /// Create a builder of HTTP/3 server connections /// @@ -353,8 +353,11 @@ where &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { + info!("poll_accept_request"); let _ = self.poll_control(cx)?; + info!("poll_accept_request: poll_control done"); let _ = self.poll_requests_completion(cx); + info!("poll_accept_request: poll_requests_completion done"); loop { match self.inner.poll_accept_request(cx) { Poll::Ready(Err(x)) => break Poll::Ready(Err(x)), @@ -400,7 +403,7 @@ where fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll> { while let Poll::Ready(frame) = self.inner.poll_control(cx)? { match frame { - Frame::Settings(_) => trace!("Got settings"), + Frame::Settings(w) => trace!("Got settings > {:?}", w), Frame::Goaway(id) => self.inner.process_goaway(&mut self.recv_closing, id)?, f @ Frame::MaxPushId(_) | f @ Frame::CancelPush(_) => { warn!("Control frame ignored {:?}", f); From dcc248db35cf14fd5aa68bdd7132ad71bb541bb0 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Tue, 28 Mar 2023 22:41:10 -0400 Subject: [PATCH 02/97] save --- examples/Cargo.toml | 2 +- examples/server.rs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index bdbad438..f59d2171 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -19,7 +19,7 @@ rustls-native-certs = "0.6" structopt = "0.3" tokio = { version = "1.6", features = ["full"] } tracing = "0.1.10" -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter", "time", "tracing-log"] } +tracing-subscriber = { version = "0.3", default-features = true, features = ["fmt", "ansi", "env-filter", "time", "tracing-log"] } [[example]] name = "client" diff --git a/examples/server.rs b/examples/server.rs index efb840ab..9575dbf3 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -54,15 +54,13 @@ pub struct Certs { pub key: PathBuf, } -static ALPN: &[u8] = b"h3"; - #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) - .with_max_level(tracing::Level::INFO) + .with_max_level(tracing::Level::TRACE) .init(); // process cli arguments @@ -97,7 +95,8 @@ async fn main() -> Result<(), Box> { .with_single_cert(vec![cert], key)?; tls_config.max_early_data_size = u32::MAX; - tls_config.alpn_protocols = vec![ALPN.into()]; + let ALPN: Vec> = vec!(b"h3".to_vec(), b"h3-32".to_vec(), b"h3-31".to_vec(), b"h3-30".to_vec(), b"h3-29".to_vec()); + tls_config.alpn_protocols = ALPN; let server_config = quinn::ServerConfig::with_crypto(Arc::new(tls_config)); let (endpoint, mut incoming) = quinn::Endpoint::server(server_config, opt.listen)?; From 21f564d9778fce47d54fa518c2155f8ca5475c71 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 29 Mar 2023 18:59:56 -0400 Subject: [PATCH 03/97] adding SETTINGS_ENABLE_WEBTRANSPORT --- h3/src/connection.rs | 6 ++++++ h3/src/proto/frame.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/h3/src/connection.rs b/h3/src/connection.rs index abf7cd92..684570e6 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -109,6 +109,12 @@ where settings .insert(SettingId::MAX_HEADER_LIST_SIZE, max_field_section_size) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings.insert(SettingId::ENABLE_CONNECT_PROTOCOL, 1) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings.insert(SettingId::ENABLE_WEBTRANSPORT, 1) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings.insert(SettingId::H3_DATAGRAM, 1) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; if grease { // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 1b9da3d5..162fe328 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -411,6 +411,7 @@ setting_identifiers! { H3_DATAGRAM = 0xFFD277, // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http2-02#section-10.1 ENABLE_WEBTRANSPORT = 0x2B603742, + H3_SETTING_ENABLE_DATAGRAM_CHROME_SPECIFIC= 0xFFD277, // Dummy setting to check it is correctly ignored by the peer. // https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1 DUMMY = 0x21, From 22aaaa864ce2813bbaed17b8f7839f9fe96f0856 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Thu, 30 Mar 2023 08:09:29 -0400 Subject: [PATCH 04/97] revert dup setting --- h3/src/connection.rs | 7 ++++--- h3/src/proto/frame.rs | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 684570e6..3f35469b 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -7,7 +7,7 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use futures_util::{future, ready}; use http::HeaderMap; -use tracing::warn; +use tracing::{warn, trace}; use crate::{ error::{Code, Error}, @@ -113,8 +113,8 @@ where .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; settings.insert(SettingId::ENABLE_WEBTRANSPORT, 1) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - settings.insert(SettingId::H3_DATAGRAM, 1) - .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + // settings.insert(SettingId::H3_DATAGRAM, 1) + // .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; if grease { // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) @@ -162,6 +162,7 @@ where //# Endpoints MUST NOT require any data to be received from //# the peer prior to sending the SETTINGS frame; settings MUST be sent //# as soon as the transport is ready to send data. + trace!("Sending Settings frame: {:?}", settings); stream::write( &mut control_send, (StreamType::CONTROL, Frame::Settings(settings)), diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 162fe328..6078a0a9 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -404,7 +404,6 @@ setting_identifiers! { QPACK_MAX_TABLE_CAPACITY = 0x1, QPACK_MAX_BLOCKED_STREAMS = 0x7, MAX_HEADER_LIST_SIZE = 0x6, - QPACK_BLOCKED_STREAMS = 0x7, // https://datatracker.ietf.org/doc/html/rfc9220#section-5 ENABLE_CONNECT_PROTOCOL = 0x8, // https://datatracker.ietf.org/doc/html/draft-ietf-masque-h3-datagram-05#section-9.1 From 6ebd3e1f835eab8c96c54eac298670874ebf4e6a Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 10:39:36 +0200 Subject: [PATCH 05/97] chore: extract connection config to struct --- h3/src/client.rs | 23 +++++++------------- h3/src/connection.rs | 40 +++++++++++++++++++--------------- h3/src/server.rs | 51 +++++++++++++++++++++++++++++++------------- 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/h3/src/client.rs b/h3/src/client.rs index e2643aaa..5cd56357 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -16,9 +16,10 @@ use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, error::{Code, Error, ErrorLevel}, frame::FrameStream, - proto::{frame::Frame, headers::Header, push::PushId, varint::VarInt}, + proto::{frame::Frame, headers::Header, push::PushId}, qpack, quic::{self, StreamId}, + server::Config, stream, }; @@ -482,15 +483,13 @@ where /// # } /// ``` pub struct Builder { - max_field_section_size: u64, - send_grease: bool, + config: Config, } impl Builder { pub(super) fn new() -> Self { Builder { - max_field_section_size: VarInt::MAX.0, - send_grease: true, + config: Default::default(), } } @@ -500,7 +499,7 @@ impl Builder { /// /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints pub fn max_field_section_size(&mut self, value: u64) -> &mut Self { - self.max_field_section_size = value; + self.config.max_field_section_size = value; self } @@ -521,13 +520,7 @@ impl Builder { Ok(( Connection { - inner: ConnectionInner::new( - quic, - self.max_field_section_size, - conn_state.clone(), - self.send_grease, - ) - .await?, + inner: ConnectionInner::new(quic, conn_state.clone(), self.config).await?, sent_closing: None, recv_closing: None, }, @@ -535,10 +528,10 @@ impl Builder { open, conn_state, conn_waker, - max_field_section_size: self.max_field_section_size, + max_field_section_size: self.config.max_field_section_size, sender_count: Arc::new(AtomicUsize::new(1)), _buf: PhantomData, - send_grease_frame: self.send_grease, + send_grease_frame: self.config.send_grease, }, )) } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 3f35469b..40a945c9 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -7,7 +7,7 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use futures_util::{future, ready}; use http::HeaderMap; -use tracing::{warn, trace}; +use tracing::{trace, warn}; use crate::{ error::{Code, Error}, @@ -20,6 +20,7 @@ use crate::{ }, qpack, quic::{self, SendStream as _}, + server::Config, stream::{self, AcceptRecvStream, AcceptedRecvStream}, }; @@ -90,12 +91,7 @@ where C: quic::Connection, B: Buf, { - pub async fn new( - mut conn: C, - max_field_section_size: u64, - shared: SharedStateRef, - grease: bool, - ) -> Result { + pub async fn new(mut conn: C, shared: SharedStateRef, config: Config) -> Result { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2 //# Endpoints SHOULD create the HTTP control stream as well as the //# unidirectional streams required by mandatory extensions (such as the @@ -106,17 +102,27 @@ where .map_err(|e| Code::H3_STREAM_CREATION_ERROR.with_transport(e))?; let mut settings = Settings::default(); + settings - .insert(SettingId::MAX_HEADER_LIST_SIZE, max_field_section_size) - .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - settings.insert(SettingId::ENABLE_CONNECT_PROTOCOL, 1) + .insert( + SettingId::MAX_HEADER_LIST_SIZE, + config.max_field_section_size, + ) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - settings.insert(SettingId::ENABLE_WEBTRANSPORT, 1) - .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - // settings.insert(SettingId::H3_DATAGRAM, 1) - // .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - if grease { + if config.enable_webtransport { + settings + .insert(SettingId::ENABLE_CONNECT_PROTOCOL, 1) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings + .insert(SettingId::ENABLE_WEBTRANSPORT, 1) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings + .insert(SettingId::H3_DATAGRAM, 1) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + } + + if config.send_grease { // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.4.1 //# Setting identifiers of the format 0x1f * N + 0x21 for non-negative @@ -183,10 +189,10 @@ where encoder_recv: None, pending_recv_streams: Vec::with_capacity(3), got_peer_settings: false, - send_grease_frame: grease, + send_grease_frame: config.send_grease, }; // start a grease stream - if grease { + if config.send_grease { //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.8 //= type=implication //# Frame types of the format 0x1f * N + 0x21 for non-negative integer diff --git a/h3/src/server.rs b/h3/src/server.rs index c525705c..77fa05e1 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -72,7 +72,7 @@ use crate::{ quic::{self, RecvStream as _, SendStream as _}, stream, }; -use tracing::{error, trace, warn, info}; +use tracing::{error, info, trace, warn}; /// Create a builder of HTTP/3 server connections /// @@ -470,6 +470,27 @@ where } } +/// Configures the HTTP/3 connection +#[derive(Debug, Clone, Copy)] +pub struct Config { + pub(crate) send_grease: bool, + pub(crate) max_field_section_size: u64, + + //=https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 + /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled + pub(crate) enable_webtransport: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + max_field_section_size: VarInt::MAX.0, + send_grease: true, + enable_webtransport: false, + } + } +} + //= https://www.rfc-editor.org/rfc/rfc9114#section-6.1 //= type=TODO //# In order to @@ -506,16 +527,14 @@ where /// } /// ``` pub struct Builder { - pub(super) max_field_section_size: u64, - pub(super) send_grease: bool, + pub(crate) config: Config, } impl Builder { /// Creates a new [`Builder`] with default settings. pub(super) fn new() -> Self { Builder { - max_field_section_size: VarInt::MAX.0, - send_grease: true, + config: Default::default(), } } /// Set the maximum header size this client is willing to accept @@ -524,14 +543,22 @@ impl Builder { /// /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints pub fn max_field_section_size(&mut self, value: u64) -> &mut Self { - self.max_field_section_size = value; + self.config.max_field_section_size = value; self } /// Send grease values to the Client. /// See [setting](https://www.rfc-editor.org/rfc/rfc9114.html#settings-parameters), [frame](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) and [stream](https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease) for more information. pub fn send_grease(&mut self, value: bool) -> &mut Self { - self.send_grease = value; + self.config.send_grease = value; + self + } + + /// Indicates to the peer that WebTransport is supported. + /// + /// See [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) + pub fn enable_webtransport(&mut self, value: bool) -> &mut Self { + self.config.enable_webtransport = value; self } } @@ -547,14 +574,8 @@ impl Builder { { let (sender, receiver) = mpsc::unbounded_channel(); Ok(Connection { - inner: ConnectionInner::new( - conn, - self.max_field_section_size, - SharedStateRef::default(), - self.send_grease, - ) - .await?, - max_field_section_size: self.max_field_section_size, + inner: ConnectionInner::new(conn, SharedStateRef::default(), self.config).await?, + max_field_section_size: self.config.max_field_section_size, request_end_send: sender, request_end_recv: receiver, ongoing_streams: HashSet::new(), From 203d59d63d6c9e94b6eb3aa65da920776d054cfe Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 14:23:57 +0200 Subject: [PATCH 06/97] chore: add some helper comments --- h3/src/connection.rs | 2 ++ h3/src/frame.rs | 1 + h3/src/proto/frame.rs | 11 +++++++---- h3/src/proto/varint.rs | 1 + h3/src/server.rs | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 40a945c9..6d64c590 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -91,6 +91,7 @@ where C: quic::Connection, B: Buf, { + /// Initiates the connection and opens a control stream pub async fn new(mut conn: C, shared: SharedStateRef, config: Config) -> Result { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2 //# Endpoints SHOULD create the HTTP control stream as well as the @@ -244,6 +245,7 @@ where } } + // Accept the request by accepting the next bidirectional stream // .into().into() converts the impl QuicError into crate::error::Error. // The `?` operator doesn't work here for some reason. self.conn.poll_accept_bidi(cx).map_err(|e| e.into().into()) diff --git a/h3/src/frame.rs b/h3/src/frame.rs index 15d3f634..58e35ed7 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -17,6 +17,7 @@ use crate::{ stream::WriteBuf, }; +/// Decodes Frames from the underlying QUIC stream pub struct FrameStream { stream: S, bufs: BufList, diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 6078a0a9..7e1a2241 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -53,7 +53,7 @@ pub enum Frame { /// Represents the available data len for a `Data` frame on a RecvStream /// /// Decoding received frames does not handle `Data` frames payload. Instead, receiving it -/// and passing it to the user is left under the responsability of `RequestStream`s. +/// and passing it to the user is left under the responsibility of `RequestStream`s. pub struct PayloadLen(pub usize); impl From for PayloadLen { @@ -65,6 +65,7 @@ impl From for PayloadLen { impl Frame { pub const MAX_ENCODED_SIZE: usize = VarInt::MAX_SIZE * 3; + /// Decodes a Frame from the stream according to https://www.rfc-editor.org/rfc/rfc9114#section-7.1 pub fn decode(buf: &mut T) -> Result { let remaining = buf.remaining(); let ty = FrameType::decode(buf).map_err(|_| FrameError::Incomplete(remaining + 1))?; @@ -89,7 +90,9 @@ impl Frame { FrameType::PUSH_PROMISE => Ok(Frame::PushPromise(PushPromise::decode(&mut payload)?)), FrameType::GOAWAY => Ok(Frame::Goaway(VarInt::decode(&mut payload)?)), FrameType::MAX_PUSH_ID => Ok(Frame::MaxPushId(payload.get_var()?.try_into()?)), - FrameType::WEBTRANSPORT_STREAM =>Ok(Frame::WebTransportStream(WebTransportStream { buffer: payload.copy_to_bytes(len as usize)})), + FrameType::WEBTRANSPORT_STREAM => Ok(Frame::WebTransportStream(WebTransportStream { + buffer: payload.copy_to_bytes(len as usize), + })), FrameType::H2_PRIORITY | FrameType::H2_PING | FrameType::H2_WINDOW_UPDATE @@ -266,6 +269,7 @@ frame_types! { H2_WINDOW_UPDATE = 0x8, H2_CONTINUATION = 0x9, MAX_PUSH_ID = 0xD, + // Reserved frame types WEBTRANSPORT_STREAM = 0x41, } @@ -308,8 +312,7 @@ pub struct PushPromise { } #[derive(Debug, PartialEq)] -pub struct WebTransportStream -{ +pub struct WebTransportStream { buffer: Bytes, } impl FrameHeader for PushPromise { diff --git a/h3/src/proto/varint.rs b/h3/src/proto/varint.rs index 9af8e28f..41aadde5 100644 --- a/h3/src/proto/varint.rs +++ b/h3/src/proto/varint.rs @@ -172,6 +172,7 @@ impl fmt::Display for VarInt { } pub trait BufExt { + /// Decodes a VarInt from the buffer and advances it fn get_var(&mut self) -> Result; } diff --git a/h3/src/server.rs b/h3/src/server.rs index 77fa05e1..73680f62 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -337,7 +337,7 @@ where Ok(Some((req, request_stream))) } - /// Itiniate a graceful shutdown, accepting `max_request` potentially still in-flight + /// Initiate a graceful shutdown, accepting `max_request` potentially still in-flight /// /// See [connection shutdown](https://www.rfc-editor.org/rfc/rfc9114.html#connection-shutdown) for more information. pub async fn shutdown(&mut self, max_requests: usize) -> Result<(), Error> { From 54e894604f72982ac948bb3fa644eefec757f5a9 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 14:37:55 +0200 Subject: [PATCH 07/97] feat: bare WebTransportSession --- h3/src/lib.rs | 1 + h3/src/webtransport/mod.rs | 6 ++++++ h3/src/webtransport/server.rs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 h3/src/webtransport/mod.rs create mode 100644 h3/src/webtransport/server.rs diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 1cb59cd8..8be52491 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -6,6 +6,7 @@ pub mod client; pub mod error; pub mod quic; pub mod server; +pub mod webtransport; pub use error::Error; diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs new file mode 100644 index 00000000..8e9aed03 --- /dev/null +++ b/h3/src/webtransport/mod.rs @@ -0,0 +1,6 @@ +//! Provides the client and server support for WebTransport sessions. +//! +//! # Relevant Links +//! WebTransport: https://www.w3.org/TR/webtransport/#biblio-web-transport-http3 +//! WebTransport over HTTP/3: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/ +pub mod server; diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs new file mode 100644 index 00000000..899fb146 --- /dev/null +++ b/h3/src/webtransport/server.rs @@ -0,0 +1,17 @@ +//! Provides the server side WebTransport session +use bytes::Buf; + +use crate::{quic, server}; + +/// WebTransport session driver. +/// +/// Maintains the session using the underlying HTTP/3 connection. +/// +/// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. +pub struct WebTransportSession +where + C: quic::Connection, + B: Buf, +{ + conn: server::Connection, +} From e730761e27163a37b5caf71dabe1d800756642db Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 14:40:14 +0200 Subject: [PATCH 08/97] chore: update .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index c1e5bc64..973db0e1 100755 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,9 @@ target/ # macOS .DS_Store ._* + +# Example Certificates +localhost-key.pem +localhost.crt +localhost.key +localhost.pem From a1c35fdbe40d321983d606407291351dcdb29572 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 15:04:35 +0200 Subject: [PATCH 09/97] feat: creating a connection using custom config --- examples/Cargo.toml | 17 ++- examples/webtransport_server.rs | 226 ++++++++++++++++++++++++++++++++ h3/src/connection.rs | 2 + h3/src/server.rs | 46 ++++++- 4 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 examples/webtransport_server.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f59d2171..d1b23109 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,14 +12,23 @@ futures = "0.3" h3 = { path = "../h3" } h3-quinn = { path = "../h3-quinn" } http = "0.2" -quinn = { version = "0.8", default-features = false, features = ["tls-rustls", "ring"] } +quinn = { version = "0.8", default-features = false, features = [ + "tls-rustls", + "ring", +] } rcgen = { version = "0.9" } rustls = { version = "0.20", features = ["dangerous_configuration"] } rustls-native-certs = "0.6" structopt = "0.3" tokio = { version = "1.6", features = ["full"] } tracing = "0.1.10" -tracing-subscriber = { version = "0.3", default-features = true, features = ["fmt", "ansi", "env-filter", "time", "tracing-log"] } +tracing-subscriber = { version = "0.3", default-features = true, features = [ + "fmt", + "ansi", + "env-filter", + "time", + "tracing-log", +] } [[example]] name = "client" @@ -28,3 +37,7 @@ path = "client.rs" [[example]] name = "server" path = "server.rs" + +[[example]] +name = "webtransport_server" +path = "webtransport_server.rs" diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs new file mode 100644 index 00000000..237d9e15 --- /dev/null +++ b/examples/webtransport_server.rs @@ -0,0 +1,226 @@ +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; + +use bytes::{Bytes, BytesMut}; +use futures::StreamExt; +use http::{Request, StatusCode}; +use rustls::{Certificate, PrivateKey}; +use structopt::StructOpt; +use tokio::{fs::File, io::AsyncReadExt}; +use tracing::{error, info, trace_span}; + +use h3::{ + error::ErrorLevel, + quic::BidiStream, + server::{Config, RequestStream}, +}; +use h3_quinn::quinn; + +#[derive(StructOpt, Debug)] +#[structopt(name = "server")] +struct Opt { + #[structopt( + name = "dir", + short, + long, + help = "Root directory of the files to serve. \ + If omitted, server will respond OK." + )] + pub root: Option, + + #[structopt( + short, + long, + default_value = "[::1]:4433", + help = "What address:port to listen for new connections" + )] + pub listen: SocketAddr, + + #[structopt(flatten)] + pub certs: Certs, +} + +#[derive(StructOpt, Debug)] +pub struct Certs { + #[structopt( + long, + short, + default_value = "examples/server.cert", + help = "Certificate for TLS. If present, `--key` is mandatory." + )] + pub cert: PathBuf, + + #[structopt( + long, + short, + default_value = "examples/server.key", + help = "Private key for the certificate." + )] + pub key: PathBuf, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) + .with_writer(std::io::stderr) + .with_max_level(tracing::Level::TRACE) + .init(); + + // process cli arguments + + let opt = Opt::from_args(); + + let root = if let Some(root) = opt.root { + if !root.is_dir() { + return Err(format!("{}: is not a readable directory", root.display()).into()); + } else { + info!("serving {}", root.display()); + Arc::new(Some(root)) + } + } else { + Arc::new(None) + }; + + let Certs { cert, key } = opt.certs; + + // create quinn server endpoint and bind UDP socket + + // both cert and key must be DER-encoded + let cert = Certificate(std::fs::read(cert)?); + let key = PrivateKey(std::fs::read(key)?); + + let mut tls_config = rustls::ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_no_client_auth() + .with_single_cert(vec![cert], key)?; + + tls_config.max_early_data_size = u32::MAX; + let alpn: Vec> = vec![ + b"h3".to_vec(), + b"h3-32".to_vec(), + b"h3-31".to_vec(), + b"h3-30".to_vec(), + b"h3-29".to_vec(), + ]; + tls_config.alpn_protocols = alpn; + + let server_config = quinn::ServerConfig::with_crypto(Arc::new(tls_config)); + let (endpoint, mut incoming) = quinn::Endpoint::server(server_config, opt.listen)?; + + info!("listening on {}", opt.listen); + + // handle incoming connections and requests + + let mut h3_config = Config::new(); + h3_config.enable_webtransport(true); + + while let Some(new_conn) = incoming.next().await { + trace_span!("New connection being attempted"); + + let root = root.clone(); + + tokio::spawn(async move { + match new_conn.await { + Ok(conn) => { + info!("new connection established"); + + let mut h3_conn = h3::server::Connection::with_config( + h3_quinn::Connection::new(conn), + h3_config, + ) + .await + .unwrap(); + + loop { + match h3_conn.accept().await { + Ok(Some((req, stream))) => { + info!("new request: {:#?}", req); + + let root = root.clone(); + + tokio::spawn(async { + if let Err(e) = handle_request(req, stream, root).await { + error!("handling request failed: {}", e); + } + }); + } + + // indicating no more streams to be received + Ok(None) => { + break; + } + + Err(err) => { + error!("error on accept {}", err); + match err.get_error_level() { + ErrorLevel::ConnectionError => break, + ErrorLevel::StreamError => continue, + } + } + } + } + } + Err(err) => { + error!("accepting connection failed: {:?}", err); + } + } + }); + } + + // shut down gracefully + // wait for connections to be closed before exiting + endpoint.wait_idle().await; + + Ok(()) +} + +async fn handle_request( + req: Request<()>, + mut stream: RequestStream, + serve_root: Arc>, +) -> Result<(), Box> +where + T: BidiStream, +{ + let (status, to_serve) = match serve_root.as_deref() { + None => (StatusCode::OK, None), + Some(_) if req.uri().path().contains("..") => (StatusCode::NOT_FOUND, None), + Some(root) => { + let to_serve = root.join(req.uri().path().strip_prefix('/').unwrap_or("")); + match File::open(&to_serve).await { + Ok(file) => (StatusCode::OK, Some(file)), + Err(e) => { + error!("failed to open: \"{}\": {}", to_serve.to_string_lossy(), e); + (StatusCode::NOT_FOUND, None) + } + } + } + }; + + let resp = http::Response::builder().status(status).body(()).unwrap(); + + match stream.send_response(resp).await { + Ok(_) => { + info!("successfully respond to connection"); + } + Err(err) => { + error!("unable to send response to connection peer: {:?}", err); + } + } + + if let Some(mut file) = to_serve { + loop { + let mut buf = BytesMut::with_capacity(4096 * 10); + if file.read_buf(&mut buf).await? == 0 { + break; + } + stream.send_data(buf.freeze()).await?; + } + } + + Ok(stream.finish().await?) +} diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 6d64c590..53101d7d 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -112,6 +112,7 @@ where .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; if config.enable_webtransport { + tracing::debug!("Enabling WebTransport"); settings .insert(SettingId::ENABLE_CONNECT_PROTOCOL, 1) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; @@ -124,6 +125,7 @@ where } if config.send_grease { + tracing::debug!("Enabling send grease"); // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.4.1 //# Setting identifiers of the format 0x1f * N + 0x21 for non-negative diff --git a/h3/src/server.rs b/h3/src/server.rs index 73680f62..da1e5c47 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -126,12 +126,17 @@ where { /// Create a new HTTP/3 server connection with default settings /// - /// Use a custom [`Builder`] with [`builder()`] to create a connection + /// Use [`Self::with_config`] or a custom [`Builder`] with [`builder()`] to create a connection /// with different settings. /// Provide a Connection which implements [`quic::Connection`]. pub async fn new(conn: C) -> Result { builder().build(conn).await } + + /// Create a new HTTP/3 server connection using the provided settings. + pub async fn with_config(conn: C, config: Config) -> Result { + Builder { config }.build(conn).await + } } impl Connection @@ -481,6 +486,38 @@ pub struct Config { pub(crate) enable_webtransport: bool, } +impl Config { + /// Creates a new HTTP/3 config with default settings + pub fn new() -> Self { + Self::default() + } + + /// Set the maximum header size this client is willing to accept + /// + /// See [header size constraints] section of the specification for details. + /// + /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints + #[inline] + pub fn max_field_section_size(&mut self, value: u64) { + self.max_field_section_size = value; + } + + /// Send grease values to the Client. + /// See [setting](https://www.rfc-editor.org/rfc/rfc9114.html#settings-parameters), [frame](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) and [stream](https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease) for more information. + #[inline] + pub fn send_grease(&mut self, value: bool) { + self.send_grease = value; + } + + /// Indicates to the peer that WebTransport is supported. + /// + /// See: [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) + #[inline] + pub fn enable_webtransport(&mut self, value: bool) { + self.enable_webtransport = value; + } +} + impl Default for Config { fn default() -> Self { Self { @@ -537,20 +574,21 @@ impl Builder { config: Default::default(), } } + /// Set the maximum header size this client is willing to accept /// /// See [header size constraints] section of the specification for details. /// /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints pub fn max_field_section_size(&mut self, value: u64) -> &mut Self { - self.config.max_field_section_size = value; + self.config.max_field_section_size(value); self } /// Send grease values to the Client. /// See [setting](https://www.rfc-editor.org/rfc/rfc9114.html#settings-parameters), [frame](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) and [stream](https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease) for more information. pub fn send_grease(&mut self, value: bool) -> &mut Self { - self.config.send_grease = value; + self.config.send_grease(value); self } @@ -558,7 +596,7 @@ impl Builder { /// /// See [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) pub fn enable_webtransport(&mut self, value: bool) -> &mut Self { - self.config.enable_webtransport = value; + self.config.enable_webtransport(value); self } } From d4812c3d6d73065b98a8937fb7a621763d72d752 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 16:49:52 +0200 Subject: [PATCH 10/97] wip: server webtransport settings negotiation --- examples/webtransport_server.rs | 63 ++++++++++--------- h3/src/client.rs | 2 +- h3/src/connection.rs | 49 +++++++++------ h3/src/server.rs | 104 +++++++++++++++++++++++++++++++- h3/src/tests/request.rs | 9 ++- h3/src/webtransport/server.rs | 22 +++---- 6 files changed, 181 insertions(+), 68 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 237d9e15..41f49850 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -11,7 +11,7 @@ use tracing::{error, info, trace_span}; use h3::{ error::ErrorLevel, quic::BidiStream, - server::{Config, RequestStream}, + server::{Config, RequestStream, WebTransportSession}, }; use h3_quinn::quinn; @@ -135,34 +135,39 @@ async fn main() -> Result<(), Box> { .await .unwrap(); - loop { - match h3_conn.accept().await { - Ok(Some((req, stream))) => { - info!("new request: {:#?}", req); - - let root = root.clone(); - - tokio::spawn(async { - if let Err(e) = handle_request(req, stream, root).await { - error!("handling request failed: {}", e); - } - }); - } - - // indicating no more streams to be received - Ok(None) => { - break; - } - - Err(err) => { - error!("error on accept {}", err); - match err.get_error_level() { - ErrorLevel::ConnectionError => break, - ErrorLevel::StreamError => continue, - } - } - } - } + tracing::info!("Establishing WebTransport session"); + let session: WebTransportSession<_, Bytes> = + WebTransportSession::new(h3_conn).await.unwrap(); + tracing::info!("Finished establishing webtransport session"); + + // loop { + // match h3_conn.accept().await { + // Ok(Some((req, stream))) => { + // info!("new request: {:#?}", req); + + // let root = root.clone(); + + // tokio::spawn(async { + // if let Err(e) = handle_request(req, stream, root).await { + // error!("handling request failed: {}", e); + // } + // }); + // } + + // // indicating no more streams to be received + // Ok(None) => { + // break; + // } + + // Err(err) => { + // error!("error on accept {}", err); + // match err.get_error_level() { + // ErrorLevel::ConnectionError => break, + // ErrorLevel::StreamError => continue, + // } + // } + // } + // } } Err(err) => { error!("accepting connection failed: {:?}", err); diff --git a/h3/src/client.rs b/h3/src/client.rs index 5cd56357..5d12eb43 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -147,7 +147,7 @@ where ) -> Result, Error> { let (peer_max_field_section_size, closing) = { let state = self.conn_state.read("send request lock state"); - (state.peer_max_field_section_size, state.closing) + (state.config.max_field_section_size, state.closing) }; if closing { diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 53101d7d..4a7957f8 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -26,8 +26,8 @@ use crate::{ #[doc(hidden)] pub struct SharedState { - // maximum size for a header we send - pub peer_max_field_section_size: u64, + // Peer settings + pub config: Config, // connection-wide error, concerns all RequestStreams and drivers pub error: Option, // Has a GOAWAY frame been sent or received? @@ -51,7 +51,7 @@ impl SharedStateRef { impl Default for SharedStateRef { fn default() -> Self { Self(Arc::new(RwLock::new(SharedState { - peer_max_field_section_size: VarInt::MAX.0, + config: Config::default(), error: None, closing: false, }))) @@ -84,6 +84,7 @@ where pending_recv_streams: Vec>, got_peer_settings: bool, pub(super) send_grease_frame: bool, + pub(super) config: Config, } impl ConnectionInner @@ -111,18 +112,23 @@ where ) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - if config.enable_webtransport { - tracing::debug!("Enabling WebTransport"); - settings - .insert(SettingId::ENABLE_CONNECT_PROTOCOL, 1) - .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - settings - .insert(SettingId::ENABLE_WEBTRANSPORT, 1) - .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - settings - .insert(SettingId::H3_DATAGRAM, 1) - .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - } + settings + .insert( + SettingId::ENABLE_CONNECT_PROTOCOL, + config.enable_connect as u64, + ) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings + .insert( + SettingId::ENABLE_WEBTRANSPORT, + config.enable_webtransport as u64, + ) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + settings + .insert(SettingId::H3_DATAGRAM, config.enable_datagram as u64) + .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + + tracing::debug!("Sending server settings: {settings:?}"); if config.send_grease { tracing::debug!("Enabling send grease"); @@ -193,6 +199,7 @@ where pending_recv_streams: Vec::with_capacity(3), got_peer_settings: false, send_grease_frame: config.send_grease, + config, }; // start a grease stream if config.send_grease { @@ -384,11 +391,14 @@ where //= type=implication //# Endpoints MUST NOT consider such settings to have //# any meaning upon receipt. - self.shared - .write("connection settings write") - .peer_max_field_section_size = settings + let mut shared = self.shared.write("connection settings write"); + shared.config.max_field_section_size = settings .get(SettingId::MAX_HEADER_LIST_SIZE) .unwrap_or(VarInt::MAX.0); + + shared.config.enable_webtransport = + settings.get(SettingId::ENABLE_WEBTRANSPORT).unwrap_or(0) != 0; + Ok(Frame::Settings(settings)) } f @ Frame::Goaway(_) => Ok(f), @@ -724,7 +734,8 @@ where let max_mem_size = self .conn_state .read("send_trailers shared state read") - .peer_max_field_section_size; + .config + .max_field_section_size; //= https://www.rfc-editor.org/rfc/rfc9114#section-4.2.2 //# An implementation that diff --git a/h3/src/server.rs b/h3/src/server.rs index da1e5c47..3fa497d7 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -94,6 +94,7 @@ where C: quic::Connection, B: Buf, { + /// TODO: find a better way to manage the connection inner: ConnectionInner, max_field_section_size: u64, // List of all incoming streams that are currently running. @@ -405,7 +406,7 @@ where } } - fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll> { + pub(crate) fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll> { while let Poll::Ready(frame) = self.inner.poll_control(cx)? { match frame { Frame::Settings(w) => trace!("Got settings > {:?}", w), @@ -484,6 +485,9 @@ pub struct Config { //=https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled pub(crate) enable_webtransport: bool, + pub(crate) enable_connect: bool, + pub(crate) enable_datagram: bool, + pub(crate) max_webtransport_sessions: u64, } impl Config { @@ -512,10 +516,32 @@ impl Config { /// Indicates to the peer that WebTransport is supported. /// /// See: [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) + /// + /// + /// **Server**: + /// Supporting for webtransport also requires setting `enable_connect` `enable_datagram` + /// and `max_webtransport_sessions`. #[inline] pub fn enable_webtransport(&mut self, value: bool) { self.enable_webtransport = value; } + + /// Enables the CONNECT protocol + pub fn enable_connect(&mut self, value: bool) { + self.enable_connect = value; + } + + /// Limits the maximum number of WebTransport sessions + pub fn max_webtransport_sessions(&mut self, value: u64) { + self.max_webtransport_sessions = value; + } + + /// Indicates that the client or server supports HTTP/3 datagrams + /// + /// See: https://www.rfc-editor.org/rfc/rfc9297#section-2.1.1 + pub fn enable_datagram(&mut self, value: bool) { + self.enable_datagram = value; + } } impl Default for Config { @@ -524,6 +550,9 @@ impl Default for Config { max_field_section_size: VarInt::MAX.0, send_grease: true, enable_webtransport: false, + enable_connect: false, + enable_datagram: false, + max_webtransport_sessions: 0, } } } @@ -693,7 +722,8 @@ where .inner .conn_state .read("send_response") - .peer_max_field_section_size; + .config + .max_field_section_size; //= https://www.rfc-editor.org/rfc/rfc9114#section-4.2.2 //# An implementation that @@ -787,3 +817,73 @@ impl Drop for RequestEnd { } } } + +// WEBTRANSPORT +// TODO: extract server.rs to server/mod.rs and submodules + +/// WebTransport session driver. +/// +/// Maintains the session using the underlying HTTP/3 connection. +/// +/// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. +pub struct WebTransportSession +where + C: quic::Connection, + B: Buf, +{ + conn: Connection, +} + +impl WebTransportSession +where + C: quic::Connection, + B: Buf, +{ + /// Establishes a [`WebTransportSession`] using the provided HTTP/3 connection. + /// + /// Fails if the server or client do not send `SETTINGS_ENABLE_WEBTRANSPORT=1` + pub async fn new(mut conn: Connection) -> Result { + future::poll_fn(|cx| conn.poll_control(cx)).await?; + + let shared = conn.shared_state().clone(); + + { + let shared = shared.write("Read WebTransport support"); + + tracing::debug!("Client settings: {:#?}", shared.config); + if !shared.config.enable_webtransport { + return Err(conn.inner.close( + Code::H3_SETTINGS_ERROR, + "webtransport is not supported by client", + )); + } + + if !shared.config.enable_datagram { + return Err(conn.inner.close( + Code::H3_SETTINGS_ERROR, + "datagrams are not supported by client", + )); + } + } + + tracing::debug!("Validated client webtransport support"); + + // The peer is responsible for validating our side of the webtransport support. + // + // However, it is still advantageous to show a log on the server as (attempting) to + // establish a WebTransportSession without the proper h3 config is usually an error + if !conn.inner.config.enable_webtransport { + tracing::warn!("Server does not support webtransport"); + } + + if !conn.inner.config.enable_datagram { + tracing::warn!("Server does not support datagrams"); + } + + if !conn.inner.config.enable_connect { + tracing::warn!("Server does not support CONNECT"); + } + + todo!() + } +} diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index 17a174b5..0f03d863 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -381,7 +381,8 @@ async fn header_too_big_client_error() { client .shared_state() .write("client") - .peer_max_field_section_size = 12; + .config + .max_field_section_size = 12; let req = Request::get("http://localhost/salut").body(()).unwrap(); let err_kind = client @@ -432,7 +433,8 @@ async fn header_too_big_client_error_trailer() { client .shared_state() .write("client") - .peer_max_field_section_size = 200; + .config + .max_field_section_size = 200; let mut request_stream = client .send_request(Request::get("http://localhost/salut").body(()).unwrap()) @@ -541,7 +543,8 @@ async fn header_too_big_discard_from_client() { incoming_req .shared_state() .write("client") - .peer_max_field_section_size = u64::MAX; + .config + .max_field_section_size = u64::MAX; request_stream .send_response( Response::builder() diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 899fb146..a5cfd208 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,17 +1,11 @@ //! Provides the server side WebTransport session -use bytes::Buf; -use crate::{quic, server}; +use bytes::Buf; +use futures_util::future; -/// WebTransport session driver. -/// -/// Maintains the session using the underlying HTTP/3 connection. -/// -/// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. -pub struct WebTransportSession -where - C: quic::Connection, - B: Buf, -{ - conn: server::Connection, -} +use crate::{ + connection::ConnectionState, + quic, + server::{self, Connection}, + Error, +}; From 943d334204849d34d48f4d24f539a03c3b66b91e Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 30 Mar 2023 17:04:32 +0200 Subject: [PATCH 11/97] fix: cert paths --- examples/webtransport_server.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 41f49850..991765fa 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -44,7 +44,7 @@ pub struct Certs { #[structopt( long, short, - default_value = "examples/server.cert", + default_value = "examples/localhost.crt", help = "Certificate for TLS. If present, `--key` is mandatory." )] pub cert: PathBuf, @@ -52,7 +52,7 @@ pub struct Certs { #[structopt( long, short, - default_value = "examples/server.key", + default_value = "examples/localhost.key", help = "Private key for the certificate." )] pub key: PathBuf, @@ -71,6 +71,7 @@ async fn main() -> Result<(), Box> { let opt = Opt::from_args(); + tracing::info!("Opt: {opt:#?}"); let root = if let Some(root) = opt.root { if !root.is_dir() { return Err(format!("{}: is not a readable directory", root.display()).into()); From 47c312073e8e88f4913cf1b7c20e24f0b2fa1e3d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 31 Mar 2023 13:51:44 +0200 Subject: [PATCH 12/97] chore: trust self-signed cert script --- examples/generate_certs.sh | 8 ++++---- examples/launch_chrome.sh | 12 ++++++++++++ examples/webtransport_server.rs | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100755 examples/launch_chrome.sh diff --git a/examples/generate_certs.sh b/examples/generate_certs.sh index d80ecfd3..5198865e 100755 --- a/examples/generate_certs.sh +++ b/examples/generate_certs.sh @@ -1,7 +1,7 @@ - +#!/bin/sh # use mkcert to generate certs for localhost -mkcert localhost +mkcert -key-file localhost-key.pem -cert-file localhost.pem localhost 127.0.0.1 ::1 # convert certs from pem to der -openssl x509 -outform der -in localhost.pem -out localhost.crt -openssl rsa -outform der -in localhost-key.pem -out localhost.key +openssl x509 -inform pem -outform der -in localhost.pem -out localhost.crt +openssl rsa -inform pem -outform der -in localhost-key.pem -out localhost.key diff --git a/examples/launch_chrome.sh b/examples/launch_chrome.sh new file mode 100755 index 00000000..61d5b1fd --- /dev/null +++ b/examples/launch_chrome.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +SPKI=`openssl x509 -inform der -in localhost.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64` + +echo "Got cert key $SPKI" + +echo "Opening google chrome" + +sleep 2 +open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 991765fa..17684e0e 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -30,7 +30,7 @@ struct Opt { #[structopt( short, long, - default_value = "[::1]:4433", + default_value = "127.0.0.1:4433", help = "What address:port to listen for new connections" )] pub listen: SocketAddr, From f3f2a8044fcb093d24fc305ddc1137b70d5caf55 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 31 Mar 2023 14:09:16 +0200 Subject: [PATCH 13/97] chore: configure server for webtransport --- examples/webtransport_server.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 17684e0e..ce5abd75 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -118,6 +118,9 @@ async fn main() -> Result<(), Box> { let mut h3_config = Config::new(); h3_config.enable_webtransport(true); + h3_config.enable_connect(true); + h3_config.enable_datagram(true); + h3_config.max_webtransport_sessions(16); while let Some(new_conn) = incoming.next().await { trace_span!("New connection being attempted"); From 38ad758b67b8167d7ff6bbbaef279d42e083bf02 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 31 Mar 2023 14:51:46 +0200 Subject: [PATCH 14/97] feat: handle extended settings frame --- examples/Cargo.toml | 4 ++ examples/webtransport_server.rs | 17 ++++++- h3/Cargo.toml | 8 ++- h3/src/connection.rs | 12 +++++ h3/src/proto/frame.rs | 25 +++++++-- h3/src/server.rs | 89 +++++++++++++++++++-------------- 6 files changed, 110 insertions(+), 45 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d1b23109..75e12781 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -29,6 +29,10 @@ tracing-subscriber = { version = "0.3", default-features = true, features = [ "time", "tracing-log", ] } +tracing-tree = { version = "0.2" } + +[features] +tracing-tree = [] [[example]] name = "client" diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index ce5abd75..bbb88670 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, path::PathBuf, sync::Arc}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use bytes::{Bytes, BytesMut}; use futures::StreamExt; @@ -14,6 +14,7 @@ use h3::{ server::{Config, RequestStream, WebTransportSession}, }; use h3_quinn::quinn; +use tracing_subscriber::prelude::*; #[derive(StructOpt, Debug)] #[structopt(name = "server")] @@ -60,6 +61,7 @@ pub struct Certs { #[tokio::main] async fn main() -> Result<(), Box> { + #[cfg(not(feature = "tracing-tree"))] tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) @@ -67,6 +69,16 @@ async fn main() -> Result<(), Box> { .with_max_level(tracing::Level::TRACE) .init(); + #[cfg(feature = "tracing-tree")] + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::from_default_env()) + .with( + tracing_tree::HierarchicalLayer::new(4) + .with_verbose_exit(true) + .with_bracketed_fields(true), + ) + .init(); + // process cli arguments let opt = Opt::from_args(); @@ -121,6 +133,7 @@ async fn main() -> Result<(), Box> { h3_config.enable_connect(true); h3_config.enable_datagram(true); h3_config.max_webtransport_sessions(16); + h3_config.send_grease(true); while let Some(new_conn) = incoming.next().await { trace_span!("New connection being attempted"); @@ -144,6 +157,8 @@ async fn main() -> Result<(), Box> { WebTransportSession::new(h3_conn).await.unwrap(); tracing::info!("Finished establishing webtransport session"); + tokio::time::sleep(Duration::from_millis(10_000)).await; + // loop { // match h3_conn.accept().await { // Ok(Some((req, stream))) => { diff --git a/h3/Cargo.toml b/h3/Cargo.toml index f3fef363..b0a786ce 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -11,8 +11,12 @@ documentation = "https://docs.rs/h3" repository = "https://github.com/hyperium/h3" readme = "../README.md" description = "An async HTTP/3 implementation." -keywords = ["http3","quic","h3"] -categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"] +keywords = ["http3", "quic", "h3"] +categories = [ + "network-programming", + "web-programming::http-client", + "web-programming::http-server", +] [dependencies] bytes = "1" diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 4a7957f8..f2ee5ff7 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -399,6 +399,18 @@ where shared.config.enable_webtransport = settings.get(SettingId::ENABLE_WEBTRANSPORT).unwrap_or(0) != 0; + shared.config.max_webtransport_sessions = settings + .get(SettingId::WEBTRANSPORT_MAX_SESSIONS) + .unwrap_or(0); + + shared.config.enable_datagram = + settings.get(SettingId::H3_DATAGRAM).unwrap_or(0) != 0; + + shared.config.enable_connect = settings + .get(SettingId::ENABLE_CONNECT_PROTOCOL) + .unwrap_or(0) + != 0; + Ok(Frame::Settings(settings)) } f @ Frame::Goaway(_) => Ok(f), diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 7e1a2241..b792d2bc 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -63,7 +63,7 @@ impl From for PayloadLen { } impl Frame { - pub const MAX_ENCODED_SIZE: usize = VarInt::MAX_SIZE * 3; + pub const MAX_ENCODED_SIZE: usize = VarInt::MAX_SIZE * 7; /// Decodes a Frame from the stream according to https://www.rfc-editor.org/rfc/rfc9114#section-7.1 pub fn decode(buf: &mut T) -> Result { @@ -368,7 +368,11 @@ impl SettingId { self, SettingId::MAX_HEADER_LIST_SIZE | SettingId::QPACK_MAX_TABLE_CAPACITY - | SettingId::QPACK_MAX_BLOCKED_STREAMS, + | SettingId::QPACK_MAX_BLOCKED_STREAMS + | SettingId::ENABLE_CONNECT_PROTOCOL + | SettingId::ENABLE_WEBTRANSPORT + | SettingId::WEBTRANSPORT_MAX_SESSIONS + | SettingId::H3_DATAGRAM, ) } @@ -411,15 +415,18 @@ setting_identifiers! { ENABLE_CONNECT_PROTOCOL = 0x8, // https://datatracker.ietf.org/doc/html/draft-ietf-masque-h3-datagram-05#section-9.1 H3_DATAGRAM = 0xFFD277, - // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http2-02#section-10.1 + // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-8.2 ENABLE_WEBTRANSPORT = 0x2B603742, + // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-8.2 H3_SETTING_ENABLE_DATAGRAM_CHROME_SPECIFIC= 0xFFD277, + + WEBTRANSPORT_MAX_SESSIONS = 0x2b603743, // Dummy setting to check it is correctly ignored by the peer. // https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1 DUMMY = 0x21, } -const SETTINGS_LEN: usize = 4; +const SETTINGS_LEN: usize = 8; #[derive(Debug, PartialEq)] pub struct Settings { @@ -511,6 +518,8 @@ impl Settings { //# their receipt MUST be treated as a connection error of type //# H3_SETTINGS_ERROR. settings.insert(identifier, value)?; + } else { + tracing::warn!("Unsupported setting: {identifier:?}"); } } Ok(settings) @@ -622,6 +631,10 @@ mod tests { (SettingId::QPACK_MAX_TABLE_CAPACITY, 0xfad2), (SettingId::QPACK_MAX_BLOCKED_STREAMS, 0xfad3), (SettingId(95), 0), + (SettingId::NONE, 0), + (SettingId::NONE, 0), + (SettingId::NONE, 0), + (SettingId::NONE, 0), ], len: 4, }), @@ -635,6 +648,10 @@ mod tests { (SettingId::QPACK_MAX_BLOCKED_STREAMS, 0xfad3), // check without the Grease setting because this is ignored (SettingId(0), 0), + (SettingId::NONE, 0), + (SettingId::NONE, 0), + (SettingId::NONE, 0), + (SettingId::NONE, 0), ], len: 3, }), diff --git a/h3/src/server.rs b/h3/src/server.rs index 3fa497d7..e46f87ed 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -67,7 +67,12 @@ use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, error::{Code, Error, ErrorLevel}, frame::FrameStream, - proto::{frame::Frame, headers::Header, push::PushId, varint::VarInt}, + proto::{ + frame::{Frame, PayloadLen}, + headers::Header, + push::PushId, + varint::VarInt, + }, qpack, quic::{self, RecvStream as _, SendStream as _}, stream, @@ -407,40 +412,48 @@ where } pub(crate) fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll> { - while let Poll::Ready(frame) = self.inner.poll_control(cx)? { - match frame { - Frame::Settings(w) => trace!("Got settings > {:?}", w), - Frame::Goaway(id) => self.inner.process_goaway(&mut self.recv_closing, id)?, - f @ Frame::MaxPushId(_) | f @ Frame::CancelPush(_) => { - warn!("Control frame ignored {:?}", f); - - //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.3 - //= type=TODO - //# If a server receives a CANCEL_PUSH frame for a push - //# ID that has not yet been mentioned by a PUSH_PROMISE frame, this MUST - //# be treated as a connection error of type H3_ID_ERROR. - - //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.7 - //= type=TODO - //# A MAX_PUSH_ID frame cannot reduce the maximum push - //# ID; receipt of a MAX_PUSH_ID frame that contains a smaller value than - //# previously received MUST be treated as a connection error of type - //# H3_ID_ERROR. - } + while let Poll::Ready(frame) = self.poll_next_control(cx)? {} + Poll::Pending + } - //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.5 - //# A server MUST treat the - //# receipt of a PUSH_PROMISE frame as a connection error of type - //# H3_FRAME_UNEXPECTED. - frame => { - return Poll::Ready(Err(Code::H3_FRAME_UNEXPECTED.with_reason( - format!("on server control stream: {:?}", frame), - ErrorLevel::ConnectionError, - ))) - } + pub(crate) fn poll_next_control( + &mut self, + cx: &mut Context<'_>, + ) -> Poll, Error>> { + let frame = futures_util::ready!(self.inner.poll_control(cx))?; + + match &frame { + Frame::Settings(w) => trace!("Got settings > {:?}", w), + &Frame::Goaway(id) => self.inner.process_goaway(&mut self.recv_closing, id)?, + f @ Frame::MaxPushId(_) | f @ Frame::CancelPush(_) => { + warn!("Control frame ignored {:?}", f); + + //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.3 + //= type=TODO + //# If a server receives a CANCEL_PUSH frame for a push + //# ID that has not yet been mentioned by a PUSH_PROMISE frame, this MUST + //# be treated as a connection error of type H3_ID_ERROR. + + //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.7 + //= type=TODO + //# A MAX_PUSH_ID frame cannot reduce the maximum push + //# ID; receipt of a MAX_PUSH_ID frame that contains a smaller value than + //# previously received MUST be treated as a connection error of type + //# H3_ID_ERROR. + } + + //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.5 + //# A server MUST treat the + //# receipt of a PUSH_PROMISE frame as a connection error of type + //# H3_FRAME_UNEXPECTED. + frame => { + return Poll::Ready(Err(Code::H3_FRAME_UNEXPECTED.with_reason( + format!("on server control stream: {:?}", frame), + ErrorLevel::ConnectionError, + ))) } } - Poll::Pending + Poll::Ready(Ok(frame)) } fn poll_requests_completion(&mut self, cx: &mut Context<'_>) -> Poll<()> { @@ -843,22 +856,22 @@ where /// /// Fails if the server or client do not send `SETTINGS_ENABLE_WEBTRANSPORT=1` pub async fn new(mut conn: Connection) -> Result { - future::poll_fn(|cx| conn.poll_control(cx)).await?; + future::poll_fn(|cx| conn.poll_next_control(cx)).await?; let shared = conn.shared_state().clone(); { - let shared = shared.write("Read WebTransport support"); + let config = shared.write("Read WebTransport support").config; - tracing::debug!("Client settings: {:#?}", shared.config); - if !shared.config.enable_webtransport { + tracing::debug!("Client settings: {:#?}", config); + if !config.enable_webtransport { return Err(conn.inner.close( Code::H3_SETTINGS_ERROR, "webtransport is not supported by client", )); } - if !shared.config.enable_datagram { + if !config.enable_datagram { return Err(conn.inner.close( Code::H3_SETTINGS_ERROR, "datagrams are not supported by client", @@ -884,6 +897,6 @@ where tracing::warn!("Server does not support CONNECT"); } - todo!() + Ok(Self { conn }) } } From 52eb68de6c853125800833f171043b9695eb8b15 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 31 Mar 2023 17:01:58 +0200 Subject: [PATCH 15/97] feat: support CONNECT and :protocol --- examples/webtransport_server.rs | 6 +--- h3/src/client.rs | 2 +- h3/src/proto/headers.rs | 57 ++++++++++++++++++++++++++++++--- h3/src/server.rs | 17 +++++++++- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index bbb88670..65a406cf 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -72,11 +72,7 @@ async fn main() -> Result<(), Box> { #[cfg(feature = "tracing-tree")] tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::from_default_env()) - .with( - tracing_tree::HierarchicalLayer::new(4) - .with_verbose_exit(true) - .with_bracketed_fields(true), - ) + .with(tracing_tree::HierarchicalLayer::new(4).with_bracketed_fields(true)) .init(); // process cli arguments diff --git a/h3/src/client.rs b/h3/src/client.rs index 5d12eb43..9238e9d0 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -161,7 +161,7 @@ where headers, .. } = parts; - let headers = Header::request(method, uri, headers)?; + let headers = Header::request(method, uri, headers, Default::default())?; //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1 //= type=implication diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index 86f8de74..5520101a 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -8,7 +8,7 @@ use std::{ use http::{ header::{self, HeaderName, HeaderValue}, uri::{self, Authority, Parts, PathAndQuery, Scheme, Uri}, - HeaderMap, Method, StatusCode, + Extensions, HeaderMap, Method, StatusCode, }; use crate::qpack::HeaderField; @@ -22,12 +22,18 @@ pub struct Header { #[allow(clippy::len_without_is_empty)] impl Header { - pub fn request(method: Method, uri: Uri, fields: HeaderMap) -> Result { + /// Creates a new `Header` frame data suitable for sending a request + pub fn request( + method: Method, + uri: Uri, + fields: HeaderMap, + ext: Extensions, + ) -> Result { match (uri.authority(), fields.get("host")) { (None, None) => Err(HeaderError::MissingAuthority), (Some(a), Some(h)) if a.as_str() != h => Err(HeaderError::ContradictedAuthority), _ => Ok(Self { - pseudo: Pseudo::request(method, uri), + pseudo: Pseudo::request(method, uri, ext), fields, }), } @@ -221,6 +227,11 @@ impl TryFrom> for Header { Field::Header((n, v)) => { fields.append(n, v); } + Field::Protocol(p) => { + tracing::info!("Got protocol"); + pseudo.protocol = Some(p); + pseudo.len += 1; + } } } @@ -234,6 +245,7 @@ enum Field { Authority(Authority), Path(PathAndQuery), Status(StatusCode), + Protocol(Protocol), Header((HeaderName, HeaderValue)), } @@ -277,6 +289,7 @@ impl Field { StatusCode::from_bytes(value.as_ref()) .map_err(|_| HeaderError::invalid_value(name, value))?, ), + b":protocol" => Field::Protocol(try_value(name, value)?), _ => return Err(HeaderError::invalid_name(name)), }) } @@ -293,6 +306,14 @@ where R::from_str(s).map_err(|_| HeaderError::invalid_value(name, value)) } +#[derive(Copy, PartialEq, Debug, Clone)] +pub struct Protocol(ProtocolInner); + +#[derive(Copy, PartialEq, Debug, Clone)] +enum ProtocolInner { + WebTransport, +} + /// Pseudo-header fields have the same purpose as data from the first line of HTTP/1.X, /// but are conveyed along with other headers. For example ':method' and ':path' in a /// request, and ':status' in a response. They must be placed before all other fields, @@ -316,12 +337,27 @@ struct Pseudo { // Response status: Option, + protocol: Option, + len: usize, } +pub struct InvalidProtocol; + +impl FromStr for Protocol { + type Err = InvalidProtocol; + + fn from_str(s: &str) -> Result { + match s { + "webtransport" => Ok(Self(ProtocolInner::WebTransport)), + _ => Err(InvalidProtocol), + } + } +} + #[allow(clippy::len_without_is_empty)] impl Pseudo { - fn request(method: Method, uri: Uri) -> Self { + fn request(method: Method, uri: Uri, ext: Extensions) -> Self { let Parts { scheme, authority, @@ -345,7 +381,16 @@ impl Pseudo { }, ); - let len = 3 + if authority.is_some() { 1 } else { 0 }; + // If the method is connect, the `:protocol` pseudo-header MAY be defined + // + // See: [https://www.rfc-editor.org/rfc/rfc8441#section-4] + let protocol = if method == Method::CONNECT { + ext.get::().copied() + } else { + None + }; + + let len = 3 + authority.is_some() as usize + protocol.is_some() as usize; //= https://www.rfc-editor.org/rfc/rfc9114#section-4.3 //= type=implication @@ -364,6 +409,7 @@ impl Pseudo { authority, path: Some(path), status: None, + protocol, len, } } @@ -381,6 +427,7 @@ impl Pseudo { path: None, status: Some(status), len: 1, + protocol: None, } } diff --git a/h3/src/server.rs b/h3/src/server.rs index e46f87ed..1a1ceba4 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -59,7 +59,7 @@ use std::{ use bytes::{Buf, BytesMut}; use futures_util::future; -use http::{response, HeaderMap, Request, Response, StatusCode}; +use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; use tokio::sync::mpsc; @@ -897,6 +897,21 @@ where tracing::warn!("Server does not support CONNECT"); } + tracing::debug!("Waiting for the client to send a CONNECT request"); + + //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 + // TODO: can the client send other requests than CONNECT? + let req = conn.accept().await?; + if let Some((req, mut stream)) = req { + if req.method() == Method::CONNECT { + tracing::info!("Received connect request: {req:?}"); + } + + let response = Response::builder().status(StatusCode::OK).body(()).unwrap(); + stream.send_response(response).await?; + stream.finish().await?; + } + Ok(Self { conn }) } } From 7992a2b61f8258b533ac37b4d78175158d5fbd0c Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Fri, 31 Mar 2023 22:42:33 -0400 Subject: [PATCH 16/97] revert changes to original demo --- examples/server.rs | 7 ++++--- h3/src/server.rs | 28 ++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 9575dbf3..efb840ab 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -54,13 +54,15 @@ pub struct Certs { pub key: PathBuf, } +static ALPN: &[u8] = b"h3"; + #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) - .with_max_level(tracing::Level::TRACE) + .with_max_level(tracing::Level::INFO) .init(); // process cli arguments @@ -95,8 +97,7 @@ async fn main() -> Result<(), Box> { .with_single_cert(vec![cert], key)?; tls_config.max_early_data_size = u32::MAX; - let ALPN: Vec> = vec!(b"h3".to_vec(), b"h3-32".to_vec(), b"h3-31".to_vec(), b"h3-30".to_vec(), b"h3-29".to_vec()); - tls_config.alpn_protocols = ALPN; + tls_config.alpn_protocols = vec![ALPN.into()]; let server_config = quinn::ServerConfig::with_crypto(Arc::new(tls_config)); let (endpoint, mut incoming) = quinn::Endpoint::server(server_config, opt.listen)?; diff --git a/h3/src/server.rs b/h3/src/server.rs index 1a1ceba4..6b5918ea 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -58,7 +58,7 @@ use std::{ }; use bytes::{Buf, BytesMut}; -use futures_util::future; +use futures_util::{future, Future}; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; use tokio::sync::mpsc; @@ -344,7 +344,7 @@ where *req.version_mut() = http::Version::HTTP_3; // send the grease frame only once self.inner.send_grease_frame = false; - + trace!("replying with: {:?}", req); Ok(Some((req, request_stream))) } @@ -844,7 +844,7 @@ where C: quic::Connection, B: Buf, { - conn: Connection, + conn: Connection, } impl WebTransportSession @@ -907,11 +907,31 @@ where tracing::info!("Received connect request: {req:?}"); } - let response = Response::builder().status(StatusCode::OK).body(()).unwrap(); + let response = Response::builder() + .header("server", "big_daddy") + .header("sec-webtransport-http3-draft", "draft02") + .status(StatusCode::OK).body(()).unwrap(); stream.send_response(response).await?; stream.finish().await?; } Ok(Self { conn }) } + + async fn accept(&self) -> Result, RequestStream)>, Error> { + let req = self.conn.accept().await?; + if let Some((req, mut stream)) = req { + if req.method() == Method::CONNECT { + tracing::info!("Received connect request: {req:?}"); + } + + let response = Response::builder() + .header("server", "big_daddy") + .header("sec-webtransport-http3-draft", "draft02") + .status(StatusCode::OK).body(()).unwrap(); + stream.send_response(response).await?; + stream.finish().await?; + } + Ok(()) + } } From 6337f2566c101011f9fa883cb2c1a1baf7edbc22 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Fri, 31 Mar 2023 22:43:16 -0400 Subject: [PATCH 17/97] print client header fields for debugging --- h3/src/proto/headers.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index 5520101a..cc374818 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -10,6 +10,7 @@ use http::{ uri::{self, Authority, Parts, PathAndQuery, Scheme, Uri}, Extensions, HeaderMap, Method, StatusCode, }; +use tracing::trace; use crate::qpack::HeaderField; @@ -83,6 +84,7 @@ impl Header { //# If the scheme does not have a mandatory authority component and none //# is provided in the request target, the request MUST NOT contain the //# :authority pseudo-header or Host header fields. + trace!("got headers {:#?}", self.fields); match (self.pseudo.authority, self.fields.get("host")) { (None, None) => return Err(HeaderError::MissingAuthority), (Some(a), None) => uri = uri.authority(a.as_str().as_bytes()), From c4829609f699a03aac51ef6f70a96a8472f9250d Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Fri, 31 Mar 2023 22:43:48 -0400 Subject: [PATCH 18/97] delete mkcert script because it does not work and it is confusing --- examples/generate_certs.sh | 7 ------- 1 file changed, 7 deletions(-) delete mode 100755 examples/generate_certs.sh diff --git a/examples/generate_certs.sh b/examples/generate_certs.sh deleted file mode 100755 index 5198865e..00000000 --- a/examples/generate_certs.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# use mkcert to generate certs for localhost -mkcert -key-file localhost-key.pem -cert-file localhost.pem localhost 127.0.0.1 ::1 - -# convert certs from pem to der -openssl x509 -inform pem -outform der -in localhost.pem -out localhost.crt -openssl rsa -inform pem -outform der -in localhost-key.pem -out localhost.key From d48a11ababb1a8b1c7e2b57fcf0c29f09a39c14f Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Fri, 31 Mar 2023 23:29:01 -0400 Subject: [PATCH 19/97] got connect to work, now we need to connect datagrams. unidirectional and bidirectional streams --- examples/Cargo.toml | 1 + examples/webtransport_server.rs | 105 ++++++++------------------------ h3/src/server.rs | 16 ----- 3 files changed, 25 insertions(+), 97 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 75e12781..996375db 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # If you copy one of the examples into a new project, you should be using # [dependencies] instead. [dev-dependencies] +anyhow = "1.0" bytes = "1" futures = "0.3" h3 = { path = "../h3" } diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 65a406cf..a149c7cf 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; +use std::{net::SocketAddr, path::{PathBuf, Path}, sync::Arc, time::Duration}; use bytes::{Bytes, BytesMut}; use futures::StreamExt; @@ -7,6 +7,7 @@ use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; use tokio::{fs::File, io::AsyncReadExt}; use tracing::{error, info, trace_span}; +use anyhow::{Result, anyhow}; use h3::{ error::ErrorLevel, @@ -66,7 +67,6 @@ async fn main() -> Result<(), Box> { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) - .with_max_level(tracing::Level::TRACE) .init(); #[cfg(feature = "tracing-tree")] @@ -140,8 +140,7 @@ async fn main() -> Result<(), Box> { match new_conn.await { Ok(conn) => { info!("new connection established"); - - let mut h3_conn = h3::server::Connection::with_config( + let h3_conn = h3::server::Connection::with_config( h3_quinn::Connection::new(conn), h3_config, ) @@ -149,40 +148,10 @@ async fn main() -> Result<(), Box> { .unwrap(); tracing::info!("Establishing WebTransport session"); - let session: WebTransportSession<_, Bytes> = + let mut session: WebTransportSession<_, Bytes> = WebTransportSession::new(h3_conn).await.unwrap(); tracing::info!("Finished establishing webtransport session"); - - tokio::time::sleep(Duration::from_millis(10_000)).await; - - // loop { - // match h3_conn.accept().await { - // Ok(Some((req, stream))) => { - // info!("new request: {:#?}", req); - - // let root = root.clone(); - - // tokio::spawn(async { - // if let Err(e) = handle_request(req, stream, root).await { - // error!("handling request failed: {}", e); - // } - // }); - // } - - // // indicating no more streams to be received - // Ok(None) => { - // break; - // } - - // Err(err) => { - // error!("error on accept {}", err); - // match err.get_error_level() { - // ErrorLevel::ConnectionError => break, - // ErrorLevel::StreamError => continue, - // } - // } - // } - // } + // Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. } Err(err) => { error!("accepting connection failed: {:?}", err); @@ -198,49 +167,23 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_request( - req: Request<()>, - mut stream: RequestStream, - serve_root: Arc>, -) -> Result<(), Box> -where - T: BidiStream, -{ - let (status, to_serve) = match serve_root.as_deref() { - None => (StatusCode::OK, None), - Some(_) if req.uri().path().contains("..") => (StatusCode::NOT_FOUND, None), - Some(root) => { - let to_serve = root.join(req.uri().path().strip_prefix('/').unwrap_or("")); - match File::open(&to_serve).await { - Ok(file) => (StatusCode::OK, Some(file)), - Err(e) => { - error!("failed to open: \"{}\": {}", to_serve.to_string_lossy(), e); - (StatusCode::NOT_FOUND, None) - } - } - } - }; - - let resp = http::Response::builder().status(status).body(()).unwrap(); - - match stream.send_response(resp).await { - Ok(_) => { - info!("successfully respond to connection"); - } - Err(err) => { - error!("unable to send response to connection peer: {:?}", err); - } - } - - if let Some(mut file) = to_serve { - loop { - let mut buf = BytesMut::with_capacity(4096 * 10); - if file.read_buf(&mut buf).await? == 0 { - break; - } - stream.send_data(buf.freeze()).await?; - } - } - - Ok(stream.finish().await?) +async fn handle_request( + root: Arc, + (mut send, recv): (quinn::SendStream, quinn::RecvStream), +) -> Result<()> { + let req = recv + .read_to_end(64 * 1024) + .await + .map_err(|e| anyhow!("failed reading request: {}", e))?; + + // Write the response + send.write_all(&req) + .await + .map_err(|e| anyhow!("failed to send response: {}", e))?; + // Gracefully terminate the stream + send.finish() + .await + .map_err(|e| anyhow!("failed to shutdown stream: {}", e))?; + info!("complete"); + Ok(()) } diff --git a/h3/src/server.rs b/h3/src/server.rs index 6b5918ea..f42e8452 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -918,20 +918,4 @@ where Ok(Self { conn }) } - async fn accept(&self) -> Result, RequestStream)>, Error> { - let req = self.conn.accept().await?; - if let Some((req, mut stream)) = req { - if req.method() == Method::CONNECT { - tracing::info!("Received connect request: {req:?}"); - } - - let response = Response::builder() - .header("server", "big_daddy") - .header("sec-webtransport-http3-draft", "draft02") - .status(StatusCode::OK).body(()).unwrap(); - stream.send_response(response).await?; - stream.finish().await?; - } - Ok(()) - } } From d0bb808a119674d0e9d4151c988be72523599843 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Sat, 1 Apr 2023 19:29:19 -0400 Subject: [PATCH 20/97] enable chrome debugging --- examples/launch_chrome.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/launch_chrome.sh b/examples/launch_chrome.sh index 61d5b1fd..498ca502 100755 --- a/examples/launch_chrome.sh +++ b/examples/launch_chrome.sh @@ -9,4 +9,6 @@ echo "Got cert key $SPKI" echo "Opening google chrome" sleep 2 -open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI +open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 + +## Logs are stored to ~/Library/Application Support/Google/Chrome/chrome_debug.log From 3accb2d23e084ee81732789ff9e936d585163f6e Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Sat, 1 Apr 2023 19:29:43 -0400 Subject: [PATCH 21/97] checkin chrome extension to debug connect request --- examples/webtransport-debug/README.md | 3 + examples/webtransport-debug/background.js | 67 ++++++++++++++++++++++ examples/webtransport-debug/icon.png | Bin 0 -> 5981 bytes examples/webtransport-debug/index.html | 9 +++ examples/webtransport-debug/manifest.json | 20 +++++++ 5 files changed, 99 insertions(+) create mode 100644 examples/webtransport-debug/README.md create mode 100644 examples/webtransport-debug/background.js create mode 100644 examples/webtransport-debug/icon.png create mode 100644 examples/webtransport-debug/index.html create mode 100644 examples/webtransport-debug/manifest.json diff --git a/examples/webtransport-debug/README.md b/examples/webtransport-debug/README.md new file mode 100644 index 00000000..a6f1df8d --- /dev/null +++ b/examples/webtransport-debug/README.md @@ -0,0 +1,3 @@ +Extension used to debug WebTransport. + +It allows the user to intercept the connect request and to print both the CONNECT request and response headers. diff --git a/examples/webtransport-debug/background.js b/examples/webtransport-debug/background.js new file mode 100644 index 00000000..596f7361 --- /dev/null +++ b/examples/webtransport-debug/background.js @@ -0,0 +1,67 @@ +const URLS = ["https://127.0.0.1:*/", "https://echo.webtransport.day:*/"]; + +chrome.webRequest.onBeforeRequest.addListener( + function(details) { + console.log("Request intercepted:", details); + console.log("Request extraHeaders:", details.extraHeaders); + console.log("Request requestBody:", details.requestBody); + }, + {urls: URLS}, + ["extraHeaders", "requestBody"] +); + +chrome.webRequest.onBeforeSendHeaders.addListener( + function(details) { + console.log("onBeforeSendHeaders:", details); + console.log("onBeforeSendHeaders extraHeaders:", details.extraHeaders); + }, + {urls: URLS}, + ["requestHeaders"] +); + +chrome.webRequest.onSendHeaders.addListener( + function(details) { + console.log("onSendHeaders:", details); + console.log("onSendHeaders requestHeaders:", details.requestHeaders); + }, + {urls: URLS}, + ["requestHeaders"] +); + + +chrome.webRequest.onHeadersReceived.addListener( + function(details) { + console.log("onHeadersReceived:", details); + console.log("onHeadersReceived responseHeaders:", details.responseHeaders); + }, + {urls: URLS}, + ["responseHeaders"] +); + + +chrome.webRequest.onResponseStarted.addListener( + function(details) { + console.log("onResponseStarted:", details); + console.log("onResponseStarted responseHeaders:", details.responseHeaders); + }, + {urls: URLS}, + ["responseHeaders"] +); + +chrome.webRequest.onCompleted.addListener( + function(details) { + console.log("onCompleted:", details); + console.log("onCompleted responseHeaders:", details.responseHeaders); + }, + {urls: URLS}, + ["responseHeaders"] +); + + +chrome.webRequest.onErrorOccurred.addListener( + function(details) { + console.log("onErrorOccurred:", details); + }, + {urls: URLS}, + [] +); diff --git a/examples/webtransport-debug/icon.png b/examples/webtransport-debug/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..183f34e4b453b500409b5b9affc3978ce63ae1c4 GIT binary patch literal 5981 zcmcgwcT`hbvk$$64gwM&bOa><1VZmbq>D5`2#|o(KoT%`=_tK}6h(>%(yO2pfzUfD zpcIkb1e7i!<;DBmckf;A{r{b{=Il9p<~OrvX77FWIngFYaC+Lyv;Y8rUhjtXEn@3N zBqwT0;vFmb*d72N(Q(z%GSSo00-Ipa&aNIP06;9+34vhIyDHY_;DA8%4NF|1#oWIY z6%}_25j@h~(cafSh!{a6W!l=#b2817-R=Msm=a#QtJkZ0TXP^ko`pg4bI~jIThPzv zdqZT0x(0`0Ariv%Fs>UHB#W<>!=AHHQueTcZ5mr%0J^>bYIR~l5=d*;LMAtujwqRw zXgX9$Em&EWfyg;PEk%k9OQT)j(4iA@S- zV|Fb0#Kq}nDFxkW66hM2B>fo!1i}+Igf0np@U$Pt7s7@osXJD`jI_5aZeLuCE&zdB zPk_MSs*8(@KYm?Y1ZPM=p>vK1*bY5FTh74%EVSKE%&P^;O3&HQ5Fk$EsR1OBt^jf( zM?$2_L;?WFQX&9U#Fm9f+IggZOWpFw{^p&2D{7c&>FE($GbaoR<%PY6#+^Y*Y>B2O zTyI<9tPBklozR|=4oI{kN)qqs{Tl*M#w!w8PZZ7pjQ8~L!Yblb1pd@eB=WzzAp+n( zRdDVq0#=45U@bHT1(uV9NJ z|Li5ES(R29@^{HpX)lumdg`4KBe7f5l#WcI^58Jma{=3|` z5S81})|5sbNwmO;>j_*>+l?= z>(_}wJdhd;!)Ap`;Y=2-RiQ)FWpt z_Gp-LMy)-&FNs|nxn_fY>${S{zZ$edpjt^aR=uKjc3@_1AnA5FZ*(6zpbTr6@oL6w z)%04dQHBHIXAGMeTtP<1>jjAe9}4v)8en1#hohUpM$iR;u{ysn^@Og^LBFSq z%FhMTe{@iOEF)iCnQ2C~QQ zbKTbKxggZ{w?8VkJsYNqM3e&q2%^MCy9%|_ozA_(Pxh8vq%i`ZG{mw zP+;Y&Ut4v?3TxTefyT8Qo7Lf`^4(64vdG>&*NGh<7Tek`@Ni;pr;{t zAgqOPn)mqW&;7y2mUh+&u*E0=Gt3)IaUtECkSw&4?Hg~0srz$*gDrOI=r;=ydmnAk z=I>S)WYL@psbJ{5Ip+WXzxb*sZr;#-%v(<9wW85b`ZI;GiDLmW%I&isb8XpyM^h{1 zbtB?bAn*3^MmXduK22G4;q|QF45p7iGVNtkv)OfPiP&tx518+zt9U9v@-V)cwxsG@}Y43OfG&o`;0xQQ}L z%K-cnq0HlCDGADry5V-N7{PegM1k`>r79jOUOb}l;Di2Pzi6&4tGv-Fl~U{s|CBeU z*?e>*kKbf|woeb&;l1Ony3v8=@2xheHPJ4JybvZs?MxTsW03!HGKvsJ;j1?zC*}*C zap6fhvOf(IgErhan0Bvv{OenD6`&(}nN*)qy)!C@)A`)@(_KsS1F6rpM*N~y^@1Gs z2lEBNC^Vhl@S4Xciv%{(K`LFwK%jPaF&0v(vFHtB1+N|a`rc+gns1(4A?|)*WpgH7 z^|}t$CKPyjy74kMon69$ZB1#ei}(C-HW}F?W=_plPStPEh%={39`XmIlheKb3uf@z z1Y*A&9Ao=5QDHT+Ddr0?xlgWyB)NmA_u$N`6bxkx2}<8GpQHrs=`yz*2zyu4vPjE~ zsnamMwehIYX#hK0C z&6c78|C|m$x-e-gF`BBj7T=L{c%6IP$1?c&GaH7c3Oal)KFZYSk{ zfE)#OV;L^MWu2_VMBsk-d(NvT{m)y^T&BmhvxIO7hQ^#7J>swvd-=@ojtV)=^=}>XM|F-rV``BbDSO1tGGk>h!!l^l zrU$@O02{!DO_``v(=eU^r6UV!DN<3&JuK>sLG?jRj@8fFE19ky$1fQ-%SD{lZ2SLk z-D?KE(ca-UI7s612w<|cpjAGgec|W*RSWshey&|TE6P+OP0;9v$TnU0)7T7ao7{%8 zako3i!E}TEYtJm_d;|1tPTwuo9y}{N%){m9vE38e4hEDOYmbKwGaulJgWTFpw>X> za5lWq4&H7gIg- z5{k%%Lp|9DTB3H*0JtjLb#u+OeoZ9886eH8fLvg|SMLa<2i{hDXYc_M(okPk)!Nju z+rz!`BGpLzkL`NPvs?4-8b>Z8yN6b*!?T`Vv!$>V;w&j&Bms!2pZLnf8;HFKW;@spLEAaSi0@hQhBb}c8R164d>5nM$#Iu5 zra#v-??i(x9m32o`ZDK1DP`wtNj~nkKEF!8Q6xEbm9#pSpL_YzSJKfCzqw>$V1f+y zpS?Xl+1!k@)|=-cKO7`fd236XhpC(&FPY`yU?FBhhL@Ijbnv$2sIcoIomHP10?Vv%A!fq7xJEiR3;Nb-+<5P}XiXQl7%lp7TA_y~Tje6; z=i$xGI$B4u5E*`H&IR`t^Nnm={yeo4$U%APhRD>(M2t*VtTV%d77s!J4_CpCcg~Ma zguu+GatdoJ?2fc9yt?cDNqj1E0O2z_@;sPLjFl|JF>Ds!vhzEDdi-YVP6wTPJ2@R^ z{hUjR!|IiS8|uT%Y^Ni743Pw@k!tcUSZc;woroal=8{;wBd^LbP;f07DI*+3)BA@#9pWFKrMh3*l1FMqlM z*${z4G)paRr%;=U>q?JF9P(aHK|zTbA62PB()oOJxj5p9nne*^^fIoI-)0+rTmEit zSf44vbc`8TW6AVtms874ct2(Q%eV7K3&H#yT$8s!E491;r?lg#Z04+`wVzac0o7kp z908*Af+QlWxtd7D_~QS>RM_SzlK z5oiGP#>o17DtS^IxwaCd$D3cezp||C$0_95#Y0Zrdtq9X-M%G9piRue%A`gaqj&Be z--Gr1lWg_wY)7^~Qx(q&CKz+0@h59?D=$1N2gCO#QPDy`mC*omX)xfMie{2wkO59+?&`r#U}J0ddEUh*>M4w>$i+SfCe`c}bgYo8)r z&%0oh&tW;?C?Swn2#E6(uTm#Gxo_}e;7bO%y;uvmsCUmovU;Z&1FocT^@;merRC>e z2GyE3_RW}n1(F?#cl>P7tlgb-@W|R<9WHw_l{!~QJ~tJa!K)Eb#a>KKZPG5q&wACD zYL$3+vQulvMEE4+8HiAeSNls<+Hz4aUHqXf#N+ItF5OG12PP6G52daqyiag41VmwBKyZg9UhAY^J$r%2eRr|@Fub_N zxj%cuP4`1=dEWl0THCpzxDo{wf!>%powLM9%InZafRXz_7`>?9L~z1fp$}gE4AZL{;YNVx7ZMdGuow&u`UOr?6jtS zj`xL4;(GnSKj%WE+5L^ z0%o&k9D?@@QKVmGaIW4pC4_a`wlKJIWcj}hpY+QVTC9dOU;a48@wQ|G1&f2U9y@L} z^R*(za}w5X%_mgOhtn{O_-XIZ^Q~Bjg;Mm%>t|FU1Vmfktyv3be`_bMWe3Vha$#!& zF;yM4cK~T{NBqKIK+JG&>f=&gUpyUUR-Y|qB0FCVHm|c_{_@#*QP31m3ohb9+H zCiGJ`Ob$zWvhm=apmU^}q^qK5G6kZb^&?`MQcO_>31&}?ilQ)1sYYbtGHB`=u=2lUs>ys<59@f?SPgECi_ z+ovJM>aZ8NjvQQgn9;0STX4?WvFPznK@EY#t)jLe8VgTt<5Tx?dvpLOo9uP#Mby{x zgjA}Jo?n(8mUe#FQ|Bm7*YBw-pc}{c6h11YyFDnpH1(dsO?l~q<^)CQO3LomI;m`q zk8A!{(g{pI-VYagdGyUI6W>s`Mx?S*lX|?1?VaK#^?F5GPOw9Vvf2?{sM%tB{h_L?w9FP~; literal 0 HcmV?d00001 diff --git a/examples/webtransport-debug/index.html b/examples/webtransport-debug/index.html new file mode 100644 index 00000000..bebf38d3 --- /dev/null +++ b/examples/webtransport-debug/index.html @@ -0,0 +1,9 @@ + + + + Hello World Extension + + +

Hello World!

+ + diff --git a/examples/webtransport-debug/manifest.json b/examples/webtransport-debug/manifest.json new file mode 100644 index 00000000..079fc40c --- /dev/null +++ b/examples/webtransport-debug/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "WebTransport Debug", + "version": "1.0", + "description": "Displays a 'Hello World' message", + "manifest_version": 2, + "permissions": [ + "webRequest", + "webNavigation", + "" + ], + "browser_action": { + "default_icon": "icon.png", + "default_popup": "index.html" + }, + "background": { + "scripts": ["background.js"], + "persistent": true + } + } + \ No newline at end of file From f89acdd6bc919fdebbf6d98e7a664786e77ac0ea Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Sat, 1 Apr 2023 20:11:49 -0400 Subject: [PATCH 22/97] add datagrams to h3 connection, also, never drop the stream used to accept the connect request, instead hold on to it --- examples/webtransport_server.rs | 17 +++++++++---- h3-quinn/src/lib.rs | 14 +++++++++++ h3/src/connection.rs | 9 +++---- h3/src/quic.rs | 8 ++++++- h3/src/server.rs | 42 +++++++++++++++++++++++++++------ 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index a149c7cf..6630a203 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -5,7 +5,7 @@ use futures::StreamExt; use http::{Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; -use tokio::{fs::File, io::AsyncReadExt}; +use tokio::{fs::File, io::AsyncReadExt, time::sleep}; use tracing::{error, info, trace_span}; use anyhow::{Result, anyhow}; @@ -62,6 +62,7 @@ pub struct Certs { #[tokio::main] async fn main() -> Result<(), Box> { + // 0. Setup tracing #[cfg(not(feature = "tracing-tree"))] tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) @@ -122,8 +123,7 @@ async fn main() -> Result<(), Box> { info!("listening on {}", opt.listen); - // handle incoming connections and requests - + // 1. Configure h3 server, since this is a webtransport server, we need to enable webtransport, connect, and datagram let mut h3_config = Config::new(); h3_config.enable_webtransport(true); h3_config.enable_connect(true); @@ -131,6 +131,7 @@ async fn main() -> Result<(), Box> { h3_config.max_webtransport_sessions(16); h3_config.send_grease(true); + // 2. Accept new quic connections and spawn a new task to handle them while let Some(new_conn) = incoming.next().await { trace_span!("New connection being attempted"); @@ -139,7 +140,7 @@ async fn main() -> Result<(), Box> { tokio::spawn(async move { match new_conn.await { Ok(conn) => { - info!("new connection established"); + info!("new http3 established"); let h3_conn = h3::server::Connection::with_config( h3_quinn::Connection::new(conn), h3_config, @@ -148,10 +149,16 @@ async fn main() -> Result<(), Box> { .unwrap(); tracing::info!("Establishing WebTransport session"); + // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. + // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them + // to the webtransport session. let mut session: WebTransportSession<_, Bytes> = WebTransportSession::new(h3_conn).await.unwrap(); tracing::info!("Finished establishing webtransport session"); - // Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. + // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. + // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. + // session.echo_all_web_transport_requests().await; + sleep(Duration::from_secs(100)).await; } Err(err) => { error!("accepting connection failed: {:?}", err); diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 28862ddf..3efad21f 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -33,6 +33,7 @@ pub struct Connection { opening_bi: Option, incoming_uni: IncomingUniStreams, opening_uni: Option, + datagrams: quinn::Datagrams, } impl Connection { @@ -42,6 +43,7 @@ impl Connection { uni_streams, bi_streams, connection, + datagrams, .. } = new_conn; @@ -51,6 +53,7 @@ impl Connection { opening_bi: None, incoming_uni: uni_streams, opening_uni: None, + datagrams: datagrams } } } @@ -101,6 +104,17 @@ where type OpenStreams = OpenStreams; type Error = ConnectionError; + 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)), + } + } + fn poll_accept_bidi( &mut self, cx: &mut task::Context<'_>, diff --git a/h3/src/connection.rs b/h3/src/connection.rs index f2ee5ff7..aa3d216e 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,7 +1,7 @@ use std::{ convert::TryFrom, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, - task::{Context, Poll}, + task::{Context, Poll}, mem, }; use bytes::{Buf, Bytes, BytesMut}; @@ -76,7 +76,8 @@ where B: Buf, { pub(super) shared: SharedStateRef, - conn: C, + // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging + pub conn: C, control_send: C::SendStream, control_recv: Option>, decoder_recv: Option>, @@ -214,9 +215,9 @@ where Ok(conn_inner) } - /// Send GOAWAY with specified max_id, iff max_id is smaller than the previous one. - pub async fn shutdown( + + pub async fn shutdown( &mut self, sent_closing: &mut Option, max_id: T, diff --git a/h3/src/quic.rs b/h3/src/quic.rs index e4f0aaf0..434e212f 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -5,7 +5,7 @@ use std::task::{self, Poll}; -use bytes::Buf; +use bytes::{Buf, Bytes}; pub use crate::proto::stream::{InvalidStreamId, StreamId}; pub use crate::stream::WriteBuf; @@ -75,6 +75,12 @@ pub trait Connection { /// Close the connection immediately fn close(&mut self, code: crate::error::Code, reason: &[u8]); + + /// Poll the connection for incoming datagrams. + fn poll_accept_datagram( + &mut self, + cx: &mut task::Context<'_>, + ) -> Poll, Self::Error>>; } /// Trait for opening outgoing streams diff --git a/h3/src/server.rs b/h3/src/server.rs index f42e8452..4952faa7 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -58,10 +58,10 @@ use std::{ }; use bytes::{Buf, BytesMut}; -use futures_util::{future, Future}; +use futures_util::{future}; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; -use tokio::sync::mpsc; +use tokio::{sync::mpsc}; use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, @@ -145,6 +145,7 @@ where } } + impl Connection where C: quic::Connection, @@ -845,6 +846,7 @@ where B: Buf, { conn: Connection, + connect_stream: RequestStream, } impl WebTransportSession @@ -908,14 +910,40 @@ where } let response = Response::builder() - .header("server", "big_daddy") + // This is the only header that chrome cares about. .header("sec-webtransport-http3-draft", "draft02") .status(StatusCode::OK).body(()).unwrap(); stream.send_response(response).await?; - stream.finish().await?; - } - Ok(Self { conn }) + Ok(Self { conn, connect_stream:stream }) + } else { + return Err(conn.inner.close( + Code::H3_REQUEST_REJECTED, + "expected CONNECT request", + )); + } } - } + +impl WebTransportSession +where + C: quic::Connection, + B: Buf, +{ + // Test method to poll the connection for incoming requests. + // pub async fn echo_all_web_transport_requests(&self) -> JoinHandle<()> { + // let conn = self.conn; + // let poll_datagrams = tokio::spawn(async move { + // while let Ok(Some(result)) = future::poll_fn(|cx| conn.inner.conn.poll_accept_datagram(cx)).await { + // info!("Received datagram: {:?}", result); + // } + // }); + + // // let poll_bidi_streams = tokio::spawn(async move { + // // while let Ok(Some(result)) = future::poll_fn(|cx| conn.inner.conn.poll_accept_bidi(cx)).await { + // // info!("Received bidi stream"); + // // } + // // }); + // poll_datagrams + // } +} \ No newline at end of file From 7d3f1a2e7dde28a18d3e4a25a4becf1eda392401 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Sat, 1 Apr 2023 22:04:34 -0400 Subject: [PATCH 23/97] got datagrams echo mode to work --- h3/src/connection.rs | 13 +++++++------ h3/src/quic.rs | 7 +++++++ h3/src/server.rs | 44 ++++++++++++++++++++++++++------------------ 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/h3/src/connection.rs b/h3/src/connection.rs index aa3d216e..91b17151 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,6 +1,6 @@ use std::{ convert::TryFrom, - sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Mutex}, task::{Context, Poll}, mem, }; @@ -77,7 +77,7 @@ where { pub(super) shared: SharedStateRef, // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging - pub conn: C, + pub conn: Arc>, control_send: C::SendStream, control_recv: Option>, decoder_recv: Option>, @@ -190,6 +190,7 @@ where //# The //# sender MUST NOT close the control stream, and the receiver MUST NOT //# request that the sender close the control stream. + let conn = Arc::new(Mutex::new(conn)); let mut conn_inner = Self { shared, conn, @@ -258,7 +259,7 @@ where // Accept the request by accepting the next bidirectional stream // .into().into() converts the impl QuicError into crate::error::Error. // The `?` operator doesn't work here for some reason. - self.conn.poll_accept_bidi(cx).map_err(|e| e.into().into()) + self.conn.lock().unwrap().poll_accept_bidi(cx).map_err(|e| e.into().into()) } pub fn poll_accept_recv(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -267,7 +268,7 @@ where } loop { - match self.conn.poll_accept_recv(cx)? { + match self.conn.lock().unwrap().poll_accept_recv(cx)? { Poll::Ready(Some(stream)) => self .pending_recv_streams .push(AcceptRecvStream::new(stream)), @@ -515,7 +516,7 @@ where pub fn close>(&mut self, code: Code, reason: T) -> Error { self.shared.write("connection close err").error = Some(code.with_reason(reason.as_ref(), crate::error::ErrorLevel::ConnectionError)); - self.conn.close(code, reason.as_ref().as_bytes()); + self.conn.lock().unwrap().close(code, reason.as_ref().as_bytes()); code.with_reason(reason.as_ref(), crate::error::ErrorLevel::ConnectionError) } @@ -523,7 +524,7 @@ where /// https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease async fn start_grease_stream(&mut self) { // start the stream - let mut grease_stream = match future::poll_fn(|cx| self.conn.poll_open_send(cx)) + let mut grease_stream = match future::poll_fn(|cx| self.conn.lock().unwrap().poll_open_send(cx)) .await .map_err(|e| Code::H3_STREAM_CREATION_ERROR.with_transport(e)) { diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 434e212f..b52ff4f3 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -81,6 +81,13 @@ pub trait Connection { &mut self, cx: &mut task::Context<'_>, ) -> Poll, Self::Error>>; + + /// Send a datagram + fn send_datagram( + &mut self, + data: Bytes, + ) -> Result<(), Self::Error>; + } /// Trait for opening outgoing streams diff --git a/h3/src/server.rs b/h3/src/server.rs index 4952faa7..f7f90f3d 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -61,7 +61,7 @@ use bytes::{Buf, BytesMut}; use futures_util::{future}; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; -use tokio::{sync::mpsc}; +use tokio::{sync::{mpsc, Mutex}, task::JoinHandle}; use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, @@ -927,23 +927,31 @@ where impl WebTransportSession where - C: quic::Connection, + C: quic::Connection + std::marker::Send + 'static, B: Buf, { - // Test method to poll the connection for incoming requests. - // pub async fn echo_all_web_transport_requests(&self) -> JoinHandle<()> { - // let conn = self.conn; - // let poll_datagrams = tokio::spawn(async move { - // while let Ok(Some(result)) = future::poll_fn(|cx| conn.inner.conn.poll_accept_datagram(cx)).await { - // info!("Received datagram: {:?}", result); - // } - // }); - - // // let poll_bidi_streams = tokio::spawn(async move { - // // while let Ok(Some(result)) = future::poll_fn(|cx| conn.inner.conn.poll_accept_bidi(cx)).await { - // // info!("Received bidi stream"); - // // } - // // }); - // poll_datagrams - // } + /// Test method to poll the connection for incoming requests. + pub async fn echo_all_web_transport_requests(&self) -> JoinHandle<()> { + let conn = self.conn.inner.conn.clone(); + let poll_datagrams = tokio::spawn(async move { + let conn = conn.clone(); + while let Ok(Some(result)) = future::poll_fn(|cx| { + let mut conn = conn.lock().unwrap(); + conn.poll_accept_datagram(cx) + }).await { + info!("Received datagram: {:?}", result); + let mut conn = conn.lock().unwrap(); + let result = conn.send_datagram(result); + info!("Sent datagram"); + } + trace!("poll_accept_datagram finished"); + }); + + // let poll_bidi_streams = tokio::spawn(async move { + // while let Ok(Some(result)) = future::poll_fn(|cx| conn.inner.conn.poll_accept_bidi(cx)).await { + // info!("Received bidi stream"); + // } + // }); + poll_datagrams + } } \ No newline at end of file From 495e1cdf3096871097efa8db918f167f5fa14fb5 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Sat, 1 Apr 2023 22:04:48 -0400 Subject: [PATCH 24/97] got datagrams echo mode to work --- examples/webtransport_server.rs | 3 ++- h3-quinn/src/lib.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 6630a203..ac9dcd25 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -158,7 +158,8 @@ async fn main() -> Result<(), Box> { // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. // session.echo_all_web_transport_requests().await; - sleep(Duration::from_secs(100)).await; + let handle = session.echo_all_web_transport_requests().await; + let result = handle.await; } Err(err) => { error!("accepting connection failed: {:?}", err); diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 3efad21f..34dd5869 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -167,6 +167,14 @@ where Poll::Ready(Ok(Self::SendStream::new(send))) } + fn send_datagram( + &mut self, + data: Bytes, + ) -> Result<(), Self::Error> { + let _ = self.conn.send_datagram(data).unwrap(); + Ok(()) + } + fn opener(&self) -> Self::OpenStreams { OpenStreams { conn: self.conn.clone(), From 3ae8832c17109853f6fa736391f21557fc833bcb Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 3 Apr 2023 12:26:37 +0200 Subject: [PATCH 25/97] feat: use upgrade style api for wt session --- examples/client.rs | 7 +- examples/server.rs | 7 +- examples/webtransport_server.rs | 125 +++++++++++++++++++++++--------- h3/src/connection.rs | 49 ++++++++----- h3/src/lib.rs | 1 + h3/src/proto/frame.rs | 7 +- h3/src/proto/headers.rs | 13 +++- h3/src/server.rs | 84 ++++++++++++--------- 8 files changed, 199 insertions(+), 94 deletions(-) diff --git a/examples/client.rs b/examples/client.rs index 3f8d692b..83937eb2 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -30,10 +30,13 @@ struct Opt { #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .with_env_filter( + tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing::Level::INFO.into()) + .from_env_lossy(), + ) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) - .with_max_level(tracing::Level::INFO) .init(); let opt = Opt::from_args(); diff --git a/examples/server.rs b/examples/server.rs index efb840ab..548bd533 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -59,10 +59,13 @@ static ALPN: &[u8] = b"h3"; #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .with_env_filter( + tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing::Level::INFO.into()) + .from_env_lossy(), + ) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) - .with_max_level(tracing::Level::INFO) .init(); // process cli arguments diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index ac9dcd25..217a0f17 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,18 +1,24 @@ -use std::{net::SocketAddr, path::{PathBuf, Path}, sync::Arc, time::Duration}; +use std::{ + net::SocketAddr, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; -use bytes::{Bytes, BytesMut}; +use anyhow::{anyhow, Result}; +use bytes::{Buf, Bytes, BytesMut}; use futures::StreamExt; -use http::{Request, StatusCode}; +use http::{Method, Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; use tokio::{fs::File, io::AsyncReadExt, time::sleep}; use tracing::{error, info, trace_span}; -use anyhow::{Result, anyhow}; use h3::{ error::ErrorLevel, - quic::BidiStream, - server::{Config, RequestStream, WebTransportSession}, + quic::{self, BidiStream}, + server::{Config, Connection, RequestStream, WebTransportSession}, + Protocol, }; use h3_quinn::quinn; use tracing_subscriber::prelude::*; @@ -148,18 +154,24 @@ async fn main() -> Result<(), Box> { .await .unwrap(); - tracing::info!("Establishing WebTransport session"); - // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. - // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them - // to the webtransport session. - let mut session: WebTransportSession<_, Bytes> = - WebTransportSession::new(h3_conn).await.unwrap(); - tracing::info!("Finished establishing webtransport session"); - // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. - // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. - // session.echo_all_web_transport_requests().await; - let handle = session.echo_all_web_transport_requests().await; - let result = handle.await; + // tracing::info!("Establishing WebTransport session"); + // // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. + // // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them + // // to the webtransport session. + + tokio::spawn(async move { + if let Err(err) = handle_connection(h3_conn).await { + tracing::error!("Failed to handle connection: {err:?}"); + } + }); + // let mut session: WebTransportSession<_, Bytes> = + // WebTransportSession::accept(h3_conn).await.unwrap(); + // tracing::info!("Finished establishing webtransport session"); + // // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. + // // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. + // // session.echo_all_web_transport_requests().await; + // let handle = session.echo_all_web_transport_requests().await; + // let result = handle.await; } Err(err) => { error!("accepting connection failed: {:?}", err); @@ -175,23 +187,64 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_request( - root: Arc, - (mut send, recv): (quinn::SendStream, quinn::RecvStream), -) -> Result<()> { - let req = recv - .read_to_end(64 * 1024) - .await - .map_err(|e| anyhow!("failed reading request: {}", e))?; - - // Write the response - send.write_all(&req) - .await - .map_err(|e| anyhow!("failed to send response: {}", e))?; - // Gracefully terminate the stream - send.finish() - .await - .map_err(|e| anyhow!("failed to shutdown stream: {}", e))?; - info!("complete"); +async fn handle_connection(mut conn: Connection) -> Result<()> +where + C: 'static + Send + quic::Connection, +{ + // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. + // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them + // to the webtransport session. + + loop { + match conn.accept().await { + Ok(Some((req, stream))) => { + info!("new request: {:#?}", req); + + let ext = req.extensions(); + match req.method() { + &Method::CONNECT if ext.get::() == Some(&Protocol::WEB_TRANSPORT) => { + tracing::info!("Peer wants to initiate a webtransport session"); + + tracing::info!("Handing over connection to WebTransport"); + let session = WebTransportSession::accept(req, stream, conn).await?; + tracing::info!("Established webtransport session"); + // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. + // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. + // session.echo_all_web_transport_requests().await; + handle_session(session).await?; + + return Ok(()); + } + _ => { + tracing::info!(?req, "Received request"); + } + } + } + + // indicating no more streams to be received + Ok(None) => { + break; + } + + Err(err) => { + error!("Error on accept {}", err); + match err.get_error_level() { + ErrorLevel::ConnectionError => break, + ErrorLevel::StreamError => continue, + } + } + } + } + Ok(()) +} + +#[tracing::instrument(level = "info", skip(session))] +async fn handle_session(session: WebTransportSession) -> anyhow::Result<()> +where + C: 'static + Send + h3::quic::Connection, + B: Buf, +{ + session.echo_all_web_transport_requests().await; + Ok(()) } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 91b17151..7d38329e 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,7 +1,8 @@ use std::{ convert::TryFrom, - sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Mutex}, - task::{Context, Poll}, mem, + mem, + sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, + task::{Context, Poll}, }; use bytes::{Buf, Bytes, BytesMut}; @@ -129,7 +130,7 @@ where .insert(SettingId::H3_DATAGRAM, config.enable_datagram as u64) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - tracing::debug!("Sending server settings: {settings:?}"); + tracing::debug!("Sending server settings: {settings:#x?}"); if config.send_grease { tracing::debug!("Enabling send grease"); @@ -178,7 +179,7 @@ where //# Endpoints MUST NOT require any data to be received from //# the peer prior to sending the SETTINGS frame; settings MUST be sent //# as soon as the transport is ready to send data. - trace!("Sending Settings frame: {:?}", settings); + trace!("Sending Settings frame: {settings:#x?}"); stream::write( &mut control_send, (StreamType::CONTROL, Frame::Settings(settings)), @@ -217,8 +218,8 @@ where Ok(conn_inner) } /// Send GOAWAY with specified max_id, iff max_id is smaller than the previous one. - - pub async fn shutdown( + + pub async fn shutdown( &mut self, sent_closing: &mut Option, max_id: T, @@ -259,7 +260,11 @@ where // Accept the request by accepting the next bidirectional stream // .into().into() converts the impl QuicError into crate::error::Error. // The `?` operator doesn't work here for some reason. - self.conn.lock().unwrap().poll_accept_bidi(cx).map_err(|e| e.into().into()) + self.conn + .lock() + .unwrap() + .poll_accept_bidi(cx) + .map_err(|e| e.into().into()) } pub fn poll_accept_recv(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -516,7 +521,10 @@ where pub fn close>(&mut self, code: Code, reason: T) -> Error { self.shared.write("connection close err").error = Some(code.with_reason(reason.as_ref(), crate::error::ErrorLevel::ConnectionError)); - self.conn.lock().unwrap().close(code, reason.as_ref().as_bytes()); + self.conn + .lock() + .unwrap() + .close(code, reason.as_ref().as_bytes()); code.with_reason(reason.as_ref(), crate::error::ErrorLevel::ConnectionError) } @@ -524,16 +532,17 @@ where /// https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease async fn start_grease_stream(&mut self) { // start the stream - let mut grease_stream = match future::poll_fn(|cx| self.conn.lock().unwrap().poll_open_send(cx)) - .await - .map_err(|e| Code::H3_STREAM_CREATION_ERROR.with_transport(e)) - { - Err(err) => { - warn!("grease stream creation failed with {}", err); - return; - } - Ok(grease) => grease, - }; + let mut grease_stream = + match future::poll_fn(|cx| self.conn.lock().unwrap().poll_open_send(cx)) + .await + .map_err(|e| Code::H3_STREAM_CREATION_ERROR.with_transport(e)) + { + Err(err) => { + warn!("grease stream creation failed with {}", err); + return; + } + Ok(grease) => grease, + }; //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.3 //# Stream types of the format 0x1f * N + 0x21 for non-negative integer @@ -565,6 +574,10 @@ where Err(err) => warn!("grease stream error on close {}", err), }; } + + pub fn got_peer_settings(&self) -> bool { + self.got_peer_settings + } } pub struct RequestStream { diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 8be52491..292ba7c1 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -9,6 +9,7 @@ pub mod server; pub mod webtransport; pub use error::Error; +pub use proto::headers::Protocol; mod buf; mod connection; diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index b792d2bc..0363ceb9 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -1,5 +1,8 @@ use bytes::{Buf, BufMut, Bytes}; -use std::{convert::TryInto, fmt}; +use std::{ + convert::TryInto, + fmt::{self, Debug}, +}; use tracing::trace; use super::{ @@ -519,7 +522,7 @@ impl Settings { //# H3_SETTINGS_ERROR. settings.insert(identifier, value)?; } else { - tracing::warn!("Unsupported setting: {identifier:?}"); + tracing::warn!("Unsupported setting: {identifier:#x?}"); } } Ok(settings) diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index cc374818..dce073eb 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -57,7 +57,9 @@ impl Header { } } - pub fn into_request_parts(self) -> Result<(Method, Uri, HeaderMap), HeaderError> { + pub fn into_request_parts( + self, + ) -> Result<(Method, Uri, Option, HeaderMap), HeaderError> { let mut uri = Uri::builder(); if let Some(path) = self.pseudo.path { @@ -100,6 +102,7 @@ impl Header { Ok(( self.pseudo.method.ok_or(HeaderError::MissingMethod)?, uri.build().map_err(HeaderError::InvalidRequest)?, + self.pseudo.protocol, self.fields, )) } @@ -309,8 +312,16 @@ where } #[derive(Copy, PartialEq, Debug, Clone)] +/// Describes the `:protocol` pseudo-header for extended connect +/// +/// See: [https://www.rfc-editor.org/rfc/rfc8441#section-4] pub struct Protocol(ProtocolInner); +impl Protocol { + /// WebTransport protocol + pub const WEB_TRANSPORT: Protocol = Protocol(ProtocolInner::WebTransport); +} + #[derive(Copy, PartialEq, Debug, Clone)] enum ProtocolInner { WebTransport, diff --git a/h3/src/server.rs b/h3/src/server.rs index f7f90f3d..9e1a66b9 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -58,10 +58,13 @@ use std::{ }; use bytes::{Buf, BytesMut}; -use futures_util::{future}; +use futures_util::future; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; -use tokio::{sync::{mpsc, Mutex}, task::JoinHandle}; +use tokio::{ + sync::{mpsc, Mutex}, + task::JoinHandle, +}; use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, @@ -69,7 +72,7 @@ use crate::{ frame::FrameStream, proto::{ frame::{Frame, PayloadLen}, - headers::Header, + headers::{Header, Protocol}, push::PushId, varint::VarInt, }, @@ -145,7 +148,6 @@ where } } - impl Connection where C: quic::Connection, @@ -315,7 +317,7 @@ where }; // Parse the request headers - let (method, uri, headers) = match Header::try_from(fields) { + let (method, uri, protocol, headers) = match Header::try_from(fields) { Ok(header) => match header.into_request_parts() { Ok(parts) => parts, Err(err) => { @@ -337,11 +339,17 @@ where return Err(error); } }; + + tracing::info!("Protocol: {protocol:?}"); // request_stream.stop_stream(Code::H3_MESSAGE_ERROR).await; let mut req = http::Request::new(()); *req.method_mut() = method; *req.uri_mut() = uri; *req.headers_mut() = headers; + // NOTE: insert `Protocol` and not `Option` + if let Some(protocol) = protocol { + req.extensions_mut().insert(protocol); + } *req.version_mut() = http::Version::HTTP_3; // send the grease frame only once self.inner.send_grease_frame = false; @@ -413,7 +421,7 @@ where } pub(crate) fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll> { - while let Poll::Ready(frame) = self.poll_next_control(cx)? {} + while let Poll::Ready(_) = self.poll_next_control(cx)? {} Poll::Pending } @@ -845,8 +853,8 @@ where C: quic::Connection, B: Buf, { - conn: Connection, - connect_stream: RequestStream, + conn: Connection, + connect_stream: RequestStream, } impl WebTransportSession @@ -854,14 +862,17 @@ where C: quic::Connection, B: Buf, { - /// Establishes a [`WebTransportSession`] using the provided HTTP/3 connection. + /// Accepts a *CONNECT* request for establishing a WebTransport session. /// - /// Fails if the server or client do not send `SETTINGS_ENABLE_WEBTRANSPORT=1` - pub async fn new(mut conn: Connection) -> Result { - future::poll_fn(|cx| conn.poll_next_control(cx)).await?; + /// TODO: is the API or the user responsible for validating the CONNECT request? + pub async fn accept( + request: Request<()>, + mut stream: RequestStream, + mut conn: Connection, + ) -> Result { + // future::poll_fn(|cx| conn.poll_control(cx)).await?; let shared = conn.shared_state().clone(); - { let config = shared.write("Read WebTransport support").config; @@ -899,32 +910,37 @@ where tracing::warn!("Server does not support CONNECT"); } - tracing::debug!("Waiting for the client to send a CONNECT request"); + // Respond to the CONNECT request. //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 - // TODO: can the client send other requests than CONNECT? - let req = conn.accept().await?; - if let Some((req, mut stream)) = req { - if req.method() == Method::CONNECT { - tracing::info!("Received connect request: {req:?}"); - } - - let response = Response::builder() + let response = if validate_wt_connect(&request) { + Response::builder() // This is the only header that chrome cares about. .header("sec-webtransport-http3-draft", "draft02") - .status(StatusCode::OK).body(()).unwrap(); - stream.send_response(response).await?; - - Ok(Self { conn, connect_stream:stream }) + .status(StatusCode::OK) + .body(()) + .unwrap() } else { - return Err(conn.inner.close( - Code::H3_REQUEST_REJECTED, - "expected CONNECT request", - )); - } + Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(()) + .unwrap() + }; + + tracing::info!("Sending response: {response:?}"); + stream.send_response(response).await?; + + Ok(Self { + conn, + connect_stream: stream, + }) } } +fn validate_wt_connect(request: &Request<()>) -> bool { + matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) +} + impl WebTransportSession where C: quic::Connection + std::marker::Send + 'static, @@ -938,7 +954,9 @@ where while let Ok(Some(result)) = future::poll_fn(|cx| { let mut conn = conn.lock().unwrap(); conn.poll_accept_datagram(cx) - }).await { + }) + .await + { info!("Received datagram: {:?}", result); let mut conn = conn.lock().unwrap(); let result = conn.send_datagram(result); @@ -954,4 +972,4 @@ where // }); poll_datagrams } -} \ No newline at end of file +} From 57dcb9292889aa137af2d8e0d162ca23639986c9 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 3 Apr 2023 12:28:46 +0200 Subject: [PATCH 26/97] fix: future requiring double await --- examples/webtransport_server.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 217a0f17..76066aa4 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -244,7 +244,8 @@ where C: 'static + Send + h3::quic::Connection, B: Buf, { - session.echo_all_web_transport_requests().await; + session.echo_all_web_transport_requests().await.await?; + tracing::info!("Finished handling session"); Ok(()) } From 8a6dd1d9f85c252b0fe19fd3bc7e5c1f27970e1c Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 3 Apr 2023 17:28:40 +0200 Subject: [PATCH 27/97] feat: datagram api --- examples/webtransport_server.rs | 25 +++++++-- h3-quinn/src/lib.rs | 22 +++++--- h3/src/connection.rs | 8 ++- h3/src/quic.rs | 46 +++++++++++++++-- h3/src/server.rs | 89 +++++++++++++++++++-------------- 5 files changed, 137 insertions(+), 53 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 76066aa4..8c6fae81 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -6,7 +6,7 @@ use std::{ }; use anyhow::{anyhow, Result}; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::StreamExt; use http::{Method, Request, StatusCode}; use rustls::{Certificate, PrivateKey}; @@ -31,7 +31,7 @@ struct Opt { short, long, help = "Root directory of the files to serve. \ - If omitted, server will respond OK." + If omitted, server will respond OK." )] pub root: Option, @@ -244,7 +244,26 @@ where C: 'static + Send + h3::quic::Connection, B: Buf, { - session.echo_all_web_transport_requests().await.await?; + // session.echo_all_web_transport_requests().await; + loop { + tokio::select! { + datagram = session.read_datagram() => { + let datagram = datagram?; + if let Some(datagram) = datagram { + // let mut response = BytesMut::from("echo: "); + // response.put(datagram); + + tracing::info!("Responding with {datagram:?}"); + session.send_datagram(datagram).unwrap(); + tracing::info!("Finished sending datagram"); + } + } + else => { + break + } + } + } + tracing::info!("Finished handling session"); Ok(()) diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 34dd5869..e8b824ee 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -17,6 +17,7 @@ use futures_util::io::AsyncWrite as _; use futures_util::ready; use futures_util::stream::StreamExt as _; +use quinn::SendDatagramError; pub use quinn::{ self, crypto::Session, Endpoint, IncomingBiStreams, IncomingUniStreams, NewConnection, OpenBi, OpenUni, VarInt, WriteError, @@ -53,7 +54,7 @@ impl Connection { opening_bi: None, incoming_uni: uni_streams, opening_uni: None, - datagrams: datagrams + datagrams, } } } @@ -104,6 +105,7 @@ where type OpenStreams = OpenStreams; type Error = ConnectionError; + #[inline] fn poll_accept_datagram( &mut self, cx: &mut task::Context<'_>, @@ -167,12 +169,18 @@ where Poll::Ready(Ok(Self::SendStream::new(send))) } - fn send_datagram( - &mut self, - data: Bytes, - ) -> Result<(), Self::Error> { - let _ = self.conn.send_datagram(data).unwrap(); - Ok(()) + fn send_datagram(&mut self, data: Bytes) -> Result<(), quic::SendDatagramError> { + match self.conn.send_datagram(data) { + Ok(v) => Ok(v), + Err(SendDatagramError::Disabled) => Err(quic::SendDatagramError::Disabled), + Err(SendDatagramError::TooLarge) => Err(quic::SendDatagramError::TooLarge), + Err(SendDatagramError::UnsupportedByPeer) => { + Err(quic::SendDatagramError::UnsupportedByPeer) + } + Err(SendDatagramError::ConnectionLost(err)) => Err( + quic::SendDatagramError::ConnectionLost(ConnectionError::from(err).into()), + ), + } } fn opener(&self) -> Self::OpenStreams { diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 7d38329e..3f102d18 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -273,7 +273,13 @@ where } loop { - match self.conn.lock().unwrap().poll_accept_recv(cx)? { + match self + .conn + .lock() + .unwrap() + .poll_accept_recv(cx) + .map_err(|v| Error::from(v))? + { Poll::Ready(Some(stream)) => self .pending_recv_streams .push(AcceptRecvStream::new(stream)), diff --git a/h3/src/quic.rs b/h3/src/quic.rs index b52ff4f3..69041bcd 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -3,6 +3,7 @@ //! This module includes traits and types meant to allow being generic over any //! QUIC implementation. +use core::fmt; use std::task::{self, Poll}; use bytes::{Buf, Bytes}; @@ -29,6 +30,45 @@ impl<'a, E: Error + 'a> From for Box { } } +/// Types of erros that *specifically* arises 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, + } + } +} + /// Trait representing a QUIC connection. pub trait Connection { /// The type produced by `poll_accept_bidi()` @@ -83,11 +123,7 @@ pub trait Connection { ) -> Poll, Self::Error>>; /// Send a datagram - fn send_datagram( - &mut self, - data: Bytes, - ) -> Result<(), Self::Error>; - + fn send_datagram(&mut self, data: Bytes) -> Result<(), SendDatagramError>; } /// Trait for opening outgoing streams diff --git a/h3/src/server.rs b/h3/src/server.rs index 9e1a66b9..5c6da0eb 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -53,18 +53,19 @@ use std::{ collections::HashSet, convert::TryFrom, + marker::PhantomData, sync::Arc, task::{Context, Poll}, }; -use bytes::{Buf, BytesMut}; -use futures_util::future; +use bytes::{Buf, Bytes, BytesMut}; +use futures_util::{ + future::{self, Future}, + ready, +}; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; -use tokio::{ - sync::{mpsc, Mutex}, - task::JoinHandle, -}; +use tokio::sync::mpsc; use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, @@ -421,7 +422,7 @@ where } pub(crate) fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll> { - while let Poll::Ready(_) = self.poll_next_control(cx)? {} + while (self.poll_next_control(cx)?).is_ready() {} Poll::Pending } @@ -429,7 +430,7 @@ where &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { - let frame = futures_util::ready!(self.inner.poll_control(cx))?; + let frame = ready!(self.inner.poll_control(cx))?; match &frame { Frame::Settings(w) => trace!("Got settings > {:?}", w), @@ -897,7 +898,7 @@ where // The peer is responsible for validating our side of the webtransport support. // // However, it is still advantageous to show a log on the server as (attempting) to - // establish a WebTransportSession without the proper h3 config is usually an error + // establish a WebTransportSession without the proper h3 config is usually a mistake. if !conn.inner.config.enable_webtransport { tracing::warn!("Server does not support webtransport"); } @@ -935,41 +936,55 @@ where connect_stream: stream, }) } + + /// Receive a datagram from the client + pub fn read_datagram(&self) -> ReadDatagram { + ReadDatagram { + conn: &self.conn.inner.conn, + _marker: PhantomData, + } + } + + /// Sends a datagram + /// + /// TODO: maybe make async. `quinn` does not require an async send + pub fn send_datagram(&self, data: Bytes) -> Result<(), Error> { + self.conn.inner.conn.lock().unwrap().send_datagram(data)?; + tracing::info!("Sent datagram"); + + Ok(()) + } } -fn validate_wt_connect(request: &Request<()>) -> bool { - matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) +/// Future for [`WebTransportSession::read_datagram`] +pub struct ReadDatagram<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + conn: &'a std::sync::Mutex, + _marker: PhantomData, } -impl WebTransportSession +impl<'a, C, B> Future for ReadDatagram<'a, C, B> where - C: quic::Connection + std::marker::Send + 'static, + C: quic::Connection, B: Buf, { - /// Test method to poll the connection for incoming requests. - pub async fn echo_all_web_transport_requests(&self) -> JoinHandle<()> { - let conn = self.conn.inner.conn.clone(); - let poll_datagrams = tokio::spawn(async move { - let conn = conn.clone(); - while let Ok(Some(result)) = future::poll_fn(|cx| { - let mut conn = conn.lock().unwrap(); - conn.poll_accept_datagram(cx) - }) - .await - { - info!("Received datagram: {:?}", result); - let mut conn = conn.lock().unwrap(); - let result = conn.send_datagram(result); - info!("Sent datagram"); - } - trace!("poll_accept_datagram finished"); - }); + type Output = Result, Error>; - // let poll_bidi_streams = tokio::spawn(async move { - // while let Ok(Some(result)) = future::poll_fn(|cx| conn.inner.conn.poll_accept_bidi(cx)).await { - // info!("Received bidi stream"); - // } - // }); - poll_datagrams + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("poll: read_datagram"); + let res = match ready!(self.conn.lock().unwrap().poll_accept_datagram(cx)) { + Ok(v) => Ok(v), + Err(err) => Err(Error::from(err)), + }; + + tracing::info!("Got datagram: {res:?}"); + Poll::Ready(res) } } + +fn validate_wt_connect(request: &Request<()>) -> bool { + matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) +} From add97ffefb2527ad0c0986883e0b4cce1b32acce Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Mon, 3 Apr 2023 21:12:26 -0400 Subject: [PATCH 28/97] remove Google Chrome extension --- examples/webtransport-debug/README.md | 3 - examples/webtransport-debug/background.js | 67 ---------------------- examples/webtransport-debug/icon.png | Bin 5981 -> 0 bytes examples/webtransport-debug/index.html | 9 --- examples/webtransport-debug/manifest.json | 20 ------- 5 files changed, 99 deletions(-) delete mode 100644 examples/webtransport-debug/README.md delete mode 100644 examples/webtransport-debug/background.js delete mode 100644 examples/webtransport-debug/icon.png delete mode 100644 examples/webtransport-debug/index.html delete mode 100644 examples/webtransport-debug/manifest.json diff --git a/examples/webtransport-debug/README.md b/examples/webtransport-debug/README.md deleted file mode 100644 index a6f1df8d..00000000 --- a/examples/webtransport-debug/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Extension used to debug WebTransport. - -It allows the user to intercept the connect request and to print both the CONNECT request and response headers. diff --git a/examples/webtransport-debug/background.js b/examples/webtransport-debug/background.js deleted file mode 100644 index 596f7361..00000000 --- a/examples/webtransport-debug/background.js +++ /dev/null @@ -1,67 +0,0 @@ -const URLS = ["https://127.0.0.1:*/", "https://echo.webtransport.day:*/"]; - -chrome.webRequest.onBeforeRequest.addListener( - function(details) { - console.log("Request intercepted:", details); - console.log("Request extraHeaders:", details.extraHeaders); - console.log("Request requestBody:", details.requestBody); - }, - {urls: URLS}, - ["extraHeaders", "requestBody"] -); - -chrome.webRequest.onBeforeSendHeaders.addListener( - function(details) { - console.log("onBeforeSendHeaders:", details); - console.log("onBeforeSendHeaders extraHeaders:", details.extraHeaders); - }, - {urls: URLS}, - ["requestHeaders"] -); - -chrome.webRequest.onSendHeaders.addListener( - function(details) { - console.log("onSendHeaders:", details); - console.log("onSendHeaders requestHeaders:", details.requestHeaders); - }, - {urls: URLS}, - ["requestHeaders"] -); - - -chrome.webRequest.onHeadersReceived.addListener( - function(details) { - console.log("onHeadersReceived:", details); - console.log("onHeadersReceived responseHeaders:", details.responseHeaders); - }, - {urls: URLS}, - ["responseHeaders"] -); - - -chrome.webRequest.onResponseStarted.addListener( - function(details) { - console.log("onResponseStarted:", details); - console.log("onResponseStarted responseHeaders:", details.responseHeaders); - }, - {urls: URLS}, - ["responseHeaders"] -); - -chrome.webRequest.onCompleted.addListener( - function(details) { - console.log("onCompleted:", details); - console.log("onCompleted responseHeaders:", details.responseHeaders); - }, - {urls: URLS}, - ["responseHeaders"] -); - - -chrome.webRequest.onErrorOccurred.addListener( - function(details) { - console.log("onErrorOccurred:", details); - }, - {urls: URLS}, - [] -); diff --git a/examples/webtransport-debug/icon.png b/examples/webtransport-debug/icon.png deleted file mode 100644 index 183f34e4b453b500409b5b9affc3978ce63ae1c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5981 zcmcgwcT`hbvk$$64gwM&bOa><1VZmbq>D5`2#|o(KoT%`=_tK}6h(>%(yO2pfzUfD zpcIkb1e7i!<;DBmckf;A{r{b{=Il9p<~OrvX77FWIngFYaC+Lyv;Y8rUhjtXEn@3N zBqwT0;vFmb*d72N(Q(z%GSSo00-Ipa&aNIP06;9+34vhIyDHY_;DA8%4NF|1#oWIY z6%}_25j@h~(cafSh!{a6W!l=#b2817-R=Msm=a#QtJkZ0TXP^ko`pg4bI~jIThPzv zdqZT0x(0`0Ariv%Fs>UHB#W<>!=AHHQueTcZ5mr%0J^>bYIR~l5=d*;LMAtujwqRw zXgX9$Em&EWfyg;PEk%k9OQT)j(4iA@S- zV|Fb0#Kq}nDFxkW66hM2B>fo!1i}+Igf0np@U$Pt7s7@osXJD`jI_5aZeLuCE&zdB zPk_MSs*8(@KYm?Y1ZPM=p>vK1*bY5FTh74%EVSKE%&P^;O3&HQ5Fk$EsR1OBt^jf( zM?$2_L;?WFQX&9U#Fm9f+IggZOWpFw{^p&2D{7c&>FE($GbaoR<%PY6#+^Y*Y>B2O zTyI<9tPBklozR|=4oI{kN)qqs{Tl*M#w!w8PZZ7pjQ8~L!Yblb1pd@eB=WzzAp+n( zRdDVq0#=45U@bHT1(uV9NJ z|Li5ES(R29@^{HpX)lumdg`4KBe7f5l#WcI^58Jma{=3|` z5S81})|5sbNwmO;>j_*>+l?= z>(_}wJdhd;!)Ap`;Y=2-RiQ)FWpt z_Gp-LMy)-&FNs|nxn_fY>${S{zZ$edpjt^aR=uKjc3@_1AnA5FZ*(6zpbTr6@oL6w z)%04dQHBHIXAGMeTtP<1>jjAe9}4v)8en1#hohUpM$iR;u{ysn^@Og^LBFSq z%FhMTe{@iOEF)iCnQ2C~QQ zbKTbKxggZ{w?8VkJsYNqM3e&q2%^MCy9%|_ozA_(Pxh8vq%i`ZG{mw zP+;Y&Ut4v?3TxTefyT8Qo7Lf`^4(64vdG>&*NGh<7Tek`@Ni;pr;{t zAgqOPn)mqW&;7y2mUh+&u*E0=Gt3)IaUtECkSw&4?Hg~0srz$*gDrOI=r;=ydmnAk z=I>S)WYL@psbJ{5Ip+WXzxb*sZr;#-%v(<9wW85b`ZI;GiDLmW%I&isb8XpyM^h{1 zbtB?bAn*3^MmXduK22G4;q|QF45p7iGVNtkv)OfPiP&tx518+zt9U9v@-V)cwxsG@}Y43OfG&o`;0xQQ}L z%K-cnq0HlCDGADry5V-N7{PegM1k`>r79jOUOb}l;Di2Pzi6&4tGv-Fl~U{s|CBeU z*?e>*kKbf|woeb&;l1Ony3v8=@2xheHPJ4JybvZs?MxTsW03!HGKvsJ;j1?zC*}*C zap6fhvOf(IgErhan0Bvv{OenD6`&(}nN*)qy)!C@)A`)@(_KsS1F6rpM*N~y^@1Gs z2lEBNC^Vhl@S4Xciv%{(K`LFwK%jPaF&0v(vFHtB1+N|a`rc+gns1(4A?|)*WpgH7 z^|}t$CKPyjy74kMon69$ZB1#ei}(C-HW}F?W=_plPStPEh%={39`XmIlheKb3uf@z z1Y*A&9Ao=5QDHT+Ddr0?xlgWyB)NmA_u$N`6bxkx2}<8GpQHrs=`yz*2zyu4vPjE~ zsnamMwehIYX#hK0C z&6c78|C|m$x-e-gF`BBj7T=L{c%6IP$1?c&GaH7c3Oal)KFZYSk{ zfE)#OV;L^MWu2_VMBsk-d(NvT{m)y^T&BmhvxIO7hQ^#7J>swvd-=@ojtV)=^=}>XM|F-rV``BbDSO1tGGk>h!!l^l zrU$@O02{!DO_``v(=eU^r6UV!DN<3&JuK>sLG?jRj@8fFE19ky$1fQ-%SD{lZ2SLk z-D?KE(ca-UI7s612w<|cpjAGgec|W*RSWshey&|TE6P+OP0;9v$TnU0)7T7ao7{%8 zako3i!E}TEYtJm_d;|1tPTwuo9y}{N%){m9vE38e4hEDOYmbKwGaulJgWTFpw>X> za5lWq4&H7gIg- z5{k%%Lp|9DTB3H*0JtjLb#u+OeoZ9886eH8fLvg|SMLa<2i{hDXYc_M(okPk)!Nju z+rz!`BGpLzkL`NPvs?4-8b>Z8yN6b*!?T`Vv!$>V;w&j&Bms!2pZLnf8;HFKW;@spLEAaSi0@hQhBb}c8R164d>5nM$#Iu5 zra#v-??i(x9m32o`ZDK1DP`wtNj~nkKEF!8Q6xEbm9#pSpL_YzSJKfCzqw>$V1f+y zpS?Xl+1!k@)|=-cKO7`fd236XhpC(&FPY`yU?FBhhL@Ijbnv$2sIcoIomHP10?Vv%A!fq7xJEiR3;Nb-+<5P}XiXQl7%lp7TA_y~Tje6; z=i$xGI$B4u5E*`H&IR`t^Nnm={yeo4$U%APhRD>(M2t*VtTV%d77s!J4_CpCcg~Ma zguu+GatdoJ?2fc9yt?cDNqj1E0O2z_@;sPLjFl|JF>Ds!vhzEDdi-YVP6wTPJ2@R^ z{hUjR!|IiS8|uT%Y^Ni743Pw@k!tcUSZc;woroal=8{;wBd^LbP;f07DI*+3)BA@#9pWFKrMh3*l1FMqlM z*${z4G)paRr%;=U>q?JF9P(aHK|zTbA62PB()oOJxj5p9nne*^^fIoI-)0+rTmEit zSf44vbc`8TW6AVtms874ct2(Q%eV7K3&H#yT$8s!E491;r?lg#Z04+`wVzac0o7kp z908*Af+QlWxtd7D_~QS>RM_SzlK z5oiGP#>o17DtS^IxwaCd$D3cezp||C$0_95#Y0Zrdtq9X-M%G9piRue%A`gaqj&Be z--Gr1lWg_wY)7^~Qx(q&CKz+0@h59?D=$1N2gCO#QPDy`mC*omX)xfMie{2wkO59+?&`r#U}J0ddEUh*>M4w>$i+SfCe`c}bgYo8)r z&%0oh&tW;?C?Swn2#E6(uTm#Gxo_}e;7bO%y;uvmsCUmovU;Z&1FocT^@;merRC>e z2GyE3_RW}n1(F?#cl>P7tlgb-@W|R<9WHw_l{!~QJ~tJa!K)Eb#a>KKZPG5q&wACD zYL$3+vQulvMEE4+8HiAeSNls<+Hz4aUHqXf#N+ItF5OG12PP6G52daqyiag41VmwBKyZg9UhAY^J$r%2eRr|@Fub_N zxj%cuP4`1=dEWl0THCpzxDo{wf!>%powLM9%InZafRXz_7`>?9L~z1fp$}gE4AZL{;YNVx7ZMdGuow&u`UOr?6jtS zj`xL4;(GnSKj%WE+5L^ z0%o&k9D?@@QKVmGaIW4pC4_a`wlKJIWcj}hpY+QVTC9dOU;a48@wQ|G1&f2U9y@L} z^R*(za}w5X%_mgOhtn{O_-XIZ^Q~Bjg;Mm%>t|FU1Vmfktyv3be`_bMWe3Vha$#!& zF;yM4cK~T{NBqKIK+JG&>f=&gUpyUUR-Y|qB0FCVHm|c_{_@#*QP31m3ohb9+H zCiGJ`Ob$zWvhm=apmU^}q^qK5G6kZb^&?`MQcO_>31&}?ilQ)1sYYbtGHB`=u=2lUs>ys<59@f?SPgECi_ z+ovJM>aZ8NjvQQgn9;0STX4?WvFPznK@EY#t)jLe8VgTt<5Tx?dvpLOo9uP#Mby{x zgjA}Jo?n(8mUe#FQ|Bm7*YBw-pc}{c6h11YyFDnpH1(dsO?l~q<^)CQO3LomI;m`q zk8A!{(g{pI-VYagdGyUI6W>s`Mx?S*lX|?1?VaK#^?F5GPOw9Vvf2?{sM%tB{h_L?w9FP~; diff --git a/examples/webtransport-debug/index.html b/examples/webtransport-debug/index.html deleted file mode 100644 index bebf38d3..00000000 --- a/examples/webtransport-debug/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - Hello World Extension - - -

Hello World!

- - diff --git a/examples/webtransport-debug/manifest.json b/examples/webtransport-debug/manifest.json deleted file mode 100644 index 079fc40c..00000000 --- a/examples/webtransport-debug/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "WebTransport Debug", - "version": "1.0", - "description": "Displays a 'Hello World' message", - "manifest_version": 2, - "permissions": [ - "webRequest", - "webNavigation", - "" - ], - "browser_action": { - "default_icon": "icon.png", - "default_popup": "index.html" - }, - "background": { - "scripts": ["background.js"], - "persistent": true - } - } - \ No newline at end of file From 6d503d68444a806def6aab5d56c8d51c2be708bb Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Mon, 3 Apr 2023 21:13:12 -0400 Subject: [PATCH 29/97] revert examples/server changes --- examples/server.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 548bd533..efb840ab 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -59,13 +59,10 @@ static ALPN: &[u8] = b"h3"; #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::Level::INFO.into()) - .from_env_lossy(), - ) + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) + .with_max_level(tracing::Level::INFO) .init(); // process cli arguments From 9c69ca10b1f6ffddf93a69946b299db6768de091 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Mon, 3 Apr 2023 21:36:37 -0400 Subject: [PATCH 30/97] remove unrelated changes on examples/client --- examples/client.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/client.rs b/examples/client.rs index 83937eb2..3f8d692b 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -30,13 +30,10 @@ struct Opt { #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::Level::INFO.into()) - .from_env_lossy(), - ) + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) + .with_max_level(tracing::Level::INFO) .init(); let opt = Opt::from_args(); From 254a0131bdabc7561408b1e265ba21ba40cd59d3 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Tue, 4 Apr 2023 01:05:14 -0400 Subject: [PATCH 31/97] got halfway done implementing unidirectional streams --- examples/webtransport_server.rs | 27 +++++++++++---------- h3-quinn/src/lib.rs | 2 +- h3/src/server.rs | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 8c6fae81..cf4640c1 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -8,6 +8,7 @@ use std::{ use anyhow::{anyhow, Result}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::StreamExt; +use futures::future::poll_fn; use http::{Method, Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; @@ -16,7 +17,7 @@ use tracing::{error, info, trace_span}; use h3::{ error::ErrorLevel, - quic::{self, BidiStream}, + quic::{self, BidiStream, RecvStream}, server::{Config, Connection, RequestStream, WebTransportSession}, Protocol, }; @@ -141,8 +142,6 @@ async fn main() -> Result<(), Box> { while let Some(new_conn) = incoming.next().await { trace_span!("New connection being attempted"); - let root = root.clone(); - tokio::spawn(async move { match new_conn.await { Ok(conn) => { @@ -169,8 +168,6 @@ async fn main() -> Result<(), Box> { // tracing::info!("Finished establishing webtransport session"); // // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. // // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. - // // session.echo_all_web_transport_requests().await; - // let handle = session.echo_all_web_transport_requests().await; // let result = handle.await; } Err(err) => { @@ -210,8 +207,7 @@ where tracing::info!("Established webtransport session"); // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. - // session.echo_all_web_transport_requests().await; - handle_session(session).await?; + handle_session_and_echo_all_inbound_messages(session).await?; return Ok(()); } @@ -238,26 +234,33 @@ where Ok(()) } +/// This method will echo all inbound datagrams, unidirectional and bidirectional streams. #[tracing::instrument(level = "info", skip(session))] -async fn handle_session(session: WebTransportSession) -> anyhow::Result<()> +async fn handle_session_and_echo_all_inbound_messages(session: WebTransportSession) -> anyhow::Result<()> where C: 'static + Send + h3::quic::Connection, B: Buf, { - // session.echo_all_web_transport_requests().await; loop { tokio::select! { datagram = session.read_datagram() => { let datagram = datagram?; if let Some(datagram) = datagram { - // let mut response = BytesMut::from("echo: "); - // response.put(datagram); - tracing::info!("Responding with {datagram:?}"); session.send_datagram(datagram).unwrap(); tracing::info!("Finished sending datagram"); } } + stream = session.read_uni_stream() => { + let mut stream = stream?.unwrap(); + // TODO: got stuck polling this future!!! + if let Ok(Some(mut bytes)) = poll_fn(|cx| stream.poll_data(cx)).await { + tracing::info!("Received unidirectional stream with {}", bytes.get_u8()); + } + } + // stream = session = session.read_bi_stream() => { + // let mut stream = stream?.unwrap(); + // } else => { break } diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index e8b824ee..8f5cce92 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -17,7 +17,7 @@ use futures_util::io::AsyncWrite as _; use futures_util::ready; use futures_util::stream::StreamExt as _; -use quinn::SendDatagramError; +use quinn::{SendDatagramError, ReadToEndError}; pub use quinn::{ self, crypto::Session, Endpoint, IncomingBiStreams, IncomingUniStreams, NewConnection, OpenBi, OpenUni, VarInt, WriteError, diff --git a/h3/src/server.rs b/h3/src/server.rs index 5c6da0eb..40e783c4 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -65,6 +65,7 @@ use futures_util::{ }; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; use quic::StreamId; +use quic::RecvStream; use tokio::sync::mpsc; use crate::{ @@ -954,6 +955,15 @@ where Ok(()) } + + /// Receive a unidirectional stream from the client, it reads the stream until EOF. + pub fn read_uni_stream(&self) -> ReadUniStream { + ReadUniStream { + conn: &self.conn.inner.conn, + _marker: PhantomData, + } + } + } /// Future for [`WebTransportSession::read_datagram`] @@ -985,6 +995,38 @@ where } } +/// Future for [`WebTransportSession::read_uni_stream`] +pub struct ReadUniStream<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + conn: &'a std::sync::Mutex, + _marker: PhantomData, +} + +impl<'a, C, B> Future for ReadUniStream<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + + type Output = Result>::RecvStream>, Error>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("poll: read_uni_stream"); + let res = match ready!(self.conn.lock().unwrap().poll_accept_recv(cx)) { + Ok(v) => Ok(v), + Err(err) => Err(Error::from(err)), + }; + + tracing::info!("Got uni stream"); + Poll::Ready(res) + } + +} + + fn validate_wt_connect(request: &Request<()>) -> bool { matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) } From 7628e3e82ad8acfb3c3ad0f4127cfb91ad5c3b03 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 4 Apr 2023 15:14:06 +0200 Subject: [PATCH 32/97] feat: proper datagram encoding/decoding --- examples/webtransport_server.rs | 15 +++-- h3-quinn/src/lib.rs | 16 +++-- h3/Cargo.toml | 1 + h3/src/error.rs | 3 + h3/src/frame.rs | 12 +++- h3/src/proto/datagram.rs | 65 ++++++++++++++++++++ h3/src/proto/mod.rs | 1 + h3/src/proto/stream.rs | 4 ++ h3/src/proto/varint.rs | 10 +++- h3/src/quic.rs | 5 +- h3/src/server.rs | 102 ++++++++++++++++++++++++-------- h3/src/tests/connection.rs | 5 +- h3/src/tests/request.rs | 11 ++-- 13 files changed, 206 insertions(+), 44 deletions(-) create mode 100644 h3/src/proto/datagram.rs diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index cf4640c1..a01992a6 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -7,8 +7,8 @@ use std::{ use anyhow::{anyhow, Result}; use bytes::{Buf, BufMut, Bytes, BytesMut}; -use futures::StreamExt; use futures::future::poll_fn; +use futures::StreamExt; use http::{Method, Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; @@ -236,7 +236,9 @@ where /// This method will echo all inbound datagrams, unidirectional and bidirectional streams. #[tracing::instrument(level = "info", skip(session))] -async fn handle_session_and_echo_all_inbound_messages(session: WebTransportSession) -> anyhow::Result<()> +async fn handle_session_and_echo_all_inbound_messages( + session: WebTransportSession, +) -> anyhow::Result<()> where C: 'static + Send + h3::quic::Connection, B: Buf, @@ -247,13 +249,18 @@ where let datagram = datagram?; if let Some(datagram) = datagram { tracing::info!("Responding with {datagram:?}"); - session.send_datagram(datagram).unwrap(); + // Put something before to make sure encoding and decoding works and don't just + // pass through + let mut resp = BytesMut::from(&b"Response: "[..]); + resp.put(datagram); + + session.send_datagram(resp).unwrap(); tracing::info!("Finished sending datagram"); } } stream = session.read_uni_stream() => { let mut stream = stream?.unwrap(); - // TODO: got stuck polling this future!!! + // TODO: got stuck polling this future!!! if let Ok(Some(mut bytes)) = poll_fn(|cx| stream.poll_data(cx)).await { tracing::info!("Received unidirectional stream with {}", bytes.get_u8()); } diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 8f5cce92..5c4d5e92 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -17,7 +17,7 @@ use futures_util::io::AsyncWrite as _; use futures_util::ready; use futures_util::stream::StreamExt as _; -use quinn::{SendDatagramError, ReadToEndError}; +use quinn::SendDatagramError; pub use quinn::{ self, crypto::Session, Endpoint, IncomingBiStreams, IncomingUniStreams, NewConnection, OpenBi, OpenUni, VarInt, WriteError, @@ -304,6 +304,10 @@ where 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 @@ -328,8 +332,8 @@ where self.send.send_data(data) } - fn id(&self) -> StreamId { - self.send.id() + fn send_id(&self) -> StreamId { + self.send.send_id() } } @@ -366,6 +370,10 @@ impl quic::RecvStream for RecvStream { .stream .stop(VarInt::from_u64(error_code).expect("invalid error_code")); } + + fn recv_id(&self) -> StreamId { + self.stream.id().0.try_into().expect("invalid stream id") + } } /// The error type for [`RecvStream`] @@ -486,7 +494,7 @@ where Ok(()) } - fn id(&self) -> StreamId { + fn send_id(&self) -> StreamId { self.stream.id().0.try_into().expect("invalid stream id") } } diff --git a/h3/Cargo.toml b/h3/Cargo.toml index b0a786ce..f07d6bab 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -25,6 +25,7 @@ http = "0.2.3" tokio = { version = "1", features = ["sync"] } tracing = "0.1.18" fastrand = "1.7.0" +pin-project = { version = "1.0", default_features = false } [dev-dependencies] assert_matches = "1.3.0" diff --git a/h3/src/error.rs b/h3/src/error.rs index 5c693cd6..36f497f2 100644 --- a/h3/src/error.rs +++ b/h3/src/error.rs @@ -110,6 +110,9 @@ macro_rules! codes { } codes! { + /// Datagram or capsule parse error + /// See: https://www.rfc-editor.org/rfc/rfc9297#section-5.2 + (0x33, H3_DATAGRAM_ERROR); /// No error. This is used when the connection or stream needs to be /// closed, but there is no error to signal. (0x100, H3_NO_ERROR); diff --git a/h3/src/frame.rs b/h3/src/frame.rs index 58e35ed7..8af14fd3 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -140,6 +140,10 @@ where } } } + + pub fn id(&self) -> StreamId { + self.stream.recv_id() + } } impl SendStream for FrameStream @@ -165,8 +169,8 @@ where self.stream.reset(reset_code) } - fn id(&self) -> StreamId { - self.stream.id() + fn send_id(&self) -> StreamId { + self.stream.send_id() } } @@ -570,6 +574,10 @@ mod tests { fn stop_sending(&mut self, _: u64) { unimplemented!() } + + fn recv_id(&self) -> StreamId { + unimplemented!() + } } #[derive(Debug)] diff --git a/h3/src/proto/datagram.rs b/h3/src/proto/datagram.rs new file mode 100644 index 00000000..035f70e4 --- /dev/null +++ b/h3/src/proto/datagram.rs @@ -0,0 +1,65 @@ +use std::convert::TryFrom; + +use bytes::{Buf, Bytes}; + +use crate::{error::Code, Error}; + +use super::{stream::StreamId, varint::VarInt}; + +/// HTTP datagram frames +/// See: https://www.rfc-editor.org/rfc/rfc9297#section-2.1 +pub struct Datagram { + /// Stream id divided by 4 + stream_id: StreamId, + /// The data contained in the datagram + pub(crate) payload: B, +} + +impl Datagram +where + B: Buf, +{ + pub fn new(stream_id: StreamId, payload: B) -> Self { + assert!( + stream_id.into_inner() % 4 == 0, + "StreamId is not divisible by 4" + ); + Self { stream_id, payload } + } + + /// Decodes a datagram frame from the QUIC datagram + pub(crate) fn decode(mut buf: B) -> Result { + let q_stream_id = VarInt::decode(&mut buf) + .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Malformed datagram frame"))?; + + //= https://www.rfc-editor.org/rfc/rfc9297#section-2.1 + // Quarter Stream ID: A variable-length integer that contains the value of the client-initiated bidirectional + // stream that this datagram is associated with divided by four (the division by four stems + // from the fact that HTTP requests are sent on client-initiated bidirectional streams, + // which have stream IDs that are divisible by four). The largest legal QUIC stream ID + // value is 262-1, so the largest legal value of the Quarter Stream ID field is 260-1. + // Receipt of an HTTP/3 Datagram that includes a larger value MUST be treated as an HTTP/3 + // connection error of type H3_DATAGRAM_ERROR (0x33). + let stream_id = StreamId::try_from(u64::from(q_stream_id) * 4) + .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Invalid stream id"))?; + + let payload = buf; + + Ok(Self { stream_id, payload }) + } + + #[inline] + pub fn stream_id(&self) -> StreamId { + self.stream_id + } + + #[inline] + pub fn payload(&self) -> &B { + &self.payload + } + + pub fn encode(self, buf: &mut D) { + (VarInt::from(self.stream_id) / 4).encode(buf); + buf.put(self.payload); + } +} diff --git a/h3/src/proto/mod.rs b/h3/src/proto/mod.rs index 8c46d5f6..900db9e1 100644 --- a/h3/src/proto/mod.rs +++ b/h3/src/proto/mod.rs @@ -1,4 +1,5 @@ pub mod coding; +pub mod datagram; #[allow(dead_code)] pub mod frame; #[allow(dead_code)] diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 5ff56d37..ac4e4a16 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -129,6 +129,10 @@ impl StreamId { Dir::Uni } } + + pub(crate) fn into_inner(self) -> u64 { + self.0 + } } impl TryFrom for StreamId { diff --git a/h3/src/proto/varint.rs b/h3/src/proto/varint.rs index 41aadde5..5484eee2 100644 --- a/h3/src/proto/varint.rs +++ b/h3/src/proto/varint.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, fmt}; +use std::{convert::TryInto, fmt, ops::Div}; use bytes::{Buf, BufMut}; @@ -12,6 +12,14 @@ pub use super::coding::UnexpectedEnd; #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct VarInt(pub(crate) u64); +impl Div for VarInt { + type Output = Self; + + fn div(self, rhs: u64) -> Self::Output { + Self(self.0 / rhs) + } +} + impl VarInt { /// The largest representable value pub const MAX: VarInt = VarInt((1 << 62) - 1); diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 69041bcd..8d437b7a 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -171,7 +171,7 @@ pub trait SendStream { fn reset(&mut self, reset_code: u64); /// Get QUIC send stream id - fn id(&self) -> StreamId; + fn send_id(&self) -> StreamId; } /// A trait describing the "receive" actions of a QUIC stream. @@ -192,6 +192,9 @@ pub trait RecvStream { /// Send a `STOP_SENDING` QUIC code. fn stop_sending(&mut self, error_code: u64); + + /// Get QUIC send stream id + fn recv_id(&self) -> StreamId; } /// Optional trait to allow "splitting" a bidirectional stream into two sides. diff --git a/h3/src/server.rs b/h3/src/server.rs index 40e783c4..a219a0f7 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -1,4 +1,4 @@ -//! This module provides methods to create a http/3 Server. +//! This mofield1ule provides methods to create a http/3 Server. //! //! It allows to accept incoming requests, and send responses. //! @@ -61,11 +61,12 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use futures_util::{ future::{self, Future}, - ready, + ready, FutureExt, }; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; -use quic::StreamId; +use pin_project::pin_project; use quic::RecvStream; +use quic::StreamId; use tokio::sync::mpsc; use crate::{ @@ -73,13 +74,14 @@ use crate::{ error::{Code, Error, ErrorLevel}, frame::FrameStream, proto::{ + datagram::Datagram, frame::{Frame, PayloadLen}, headers::{Header, Protocol}, push::PushId, varint::VarInt, }, qpack, - quic::{self, RecvStream as _, SendStream as _}, + quic::{self, SendStream as _}, stream, }; use tracing::{error, info, trace, warn}; @@ -258,7 +260,7 @@ where let mut request_stream = RequestStream { request_end: Arc::new(RequestEnd { request_end: self.request_end_send.clone(), - stream_id: stream.id(), + stream_id: stream.send_id(), }), inner: connection::RequestStream::new( stream, @@ -359,6 +361,28 @@ where Ok(Some((req, request_stream))) } + /// Reads an incoming datagram + pub fn read_datagram(&self) -> ReadDatagram { + ReadDatagram { + conn: &self.inner.conn, + _marker: PhantomData, + } + } + + /// Sends a datagram + pub fn send_datagram(&self, stream_id: StreamId, data: impl Buf) -> Result<(), Error> { + let mut buf = BytesMut::with_capacity(8 + data.remaining()); + + // Encode::encode(&Datagram::new(stream_id, data), &mut buf); + Datagram::new(stream_id, data).encode(&mut buf); + + let buf = buf.freeze(); + self.inner.conn.lock().unwrap().send_datagram(buf)?; + tracing::info!("Sent datagram"); + + Ok(()) + } + /// Initiate a graceful shutdown, accepting `max_request` potentially still in-flight /// /// See [connection shutdown](https://www.rfc-editor.org/rfc/rfc9114.html#connection-shutdown) for more information. @@ -405,7 +429,7 @@ where // incoming requests not belonging to the grace interval. It's possible that // some acceptable request streams arrive after rejected requests. if let Some(max_id) = self.sent_closing { - if s.id() > max_id { + if s.send_id() > max_id { s.stop_sending(Code::H3_REQUEST_REJECTED.value()); s.reset(Code::H3_REQUEST_REJECTED.value()); if self.poll_requests_completion(cx).is_ready() { @@ -414,8 +438,8 @@ where continue; } } - self.last_accepted_stream = Some(s.id()); - self.ongoing_streams.insert(s.id()); + self.last_accepted_stream = Some(s.send_id()); + self.ongoing_streams.insert(s.send_id()); break Poll::Ready(Ok(Some(s))); } }; @@ -721,6 +745,11 @@ where pub fn stop_sending(&mut self, error_code: crate::error::Code) { self.inner.stream.stop_sending(error_code) } + + /// Returns the underlying stream id + pub fn id(&self) -> StreamId { + self.inner.stream.id() + } } impl RequestStream @@ -939,19 +968,17 @@ where } /// Receive a datagram from the client - pub fn read_datagram(&self) -> ReadDatagram { - ReadDatagram { - conn: &self.conn.inner.conn, - _marker: PhantomData, + pub fn read_datagram(&self) -> WebTransportReadDatagram { + WebTransportReadDatagram { + inner: self.conn.read_datagram(), } } /// Sends a datagram /// /// TODO: maybe make async. `quinn` does not require an async send - pub fn send_datagram(&self, data: Bytes) -> Result<(), Error> { - self.conn.inner.conn.lock().unwrap().send_datagram(data)?; - tracing::info!("Sent datagram"); + pub fn send_datagram(&self, data: impl Buf) -> Result<(), Error> { + self.conn.send_datagram(self.connect_stream.id(), data)?; Ok(()) } @@ -963,10 +990,9 @@ where _marker: PhantomData, } } - } -/// Future for [`WebTransportSession::read_datagram`] +/// Future for [`Connection::read_datagram`] pub struct ReadDatagram<'a, C, B> where C: quic::Connection, @@ -977,6 +1003,33 @@ where } impl<'a, C, B> Future for ReadDatagram<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + type Output = Result, Error>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("poll: read_datagram"); + match ready!(self.conn.lock().unwrap().poll_accept_datagram(cx))? { + Some(v) => Poll::Ready(Ok(Some(Datagram::decode(v)?))), + None => Poll::Ready(Ok(None)), + } + } +} + +/// Future for [`Connection::read_datagram`] +#[pin_project] +pub struct WebTransportReadDatagram<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + #[pin] + inner: ReadDatagram<'a, C, B>, +} + +impl<'a, C, B> Future for WebTransportReadDatagram<'a, C, B> where C: quic::Connection, B: Buf, @@ -985,13 +1038,13 @@ where fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_datagram"); - let res = match ready!(self.conn.lock().unwrap().poll_accept_datagram(cx)) { - Ok(v) => Ok(v), - Err(err) => Err(Error::from(err)), + let mut p = self.project(); + let val = match ready!(p.inner.poll_unpin(cx))? { + Some(v) => Some(v.payload), + None => None, }; - tracing::info!("Got datagram: {res:?}"); - Poll::Ready(res) + Poll::Ready(Ok(val)) } } @@ -1010,10 +1063,9 @@ where C: quic::Connection, B: Buf, { - type Output = Result>::RecvStream>, Error>; - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); let res = match ready!(self.conn.lock().unwrap().poll_accept_recv(cx)) { Ok(v) => Ok(v), @@ -1023,10 +1075,8 @@ where tracing::info!("Got uni stream"); Poll::Ready(res) } - } - fn validate_wt_connect(request: &Request<()>) -> bool { matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) } diff --git a/h3/src/tests/connection.rs b/h3/src/tests/connection.rs index 76056965..9d36451a 100644 --- a/h3/src/tests/connection.rs +++ b/h3/src/tests/connection.rs @@ -144,7 +144,8 @@ async fn settings_exchange_client() { if client .shared_state() .read("client") - .peer_max_field_section_size + .config + .max_field_section_size == 12 { return; @@ -202,7 +203,7 @@ async fn settings_exchange_server() { let settings_change = async { for _ in 0..10 { - if state.read("setting_change").peer_max_field_section_size == 12 { + if state.read("setting_change").config.max_field_section_size == 12 { return; } tokio::time::sleep(Duration::from_millis(2)).await; diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index 0f03d863..3d2638d0 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -630,7 +630,8 @@ async fn header_too_big_discard_from_client_trailers() { incoming_req .shared_state() .write("server") - .peer_max_field_section_size = u64::MAX; + .config + .max_field_section_size = u64::MAX; request_stream .send_response( @@ -701,7 +702,8 @@ async fn header_too_big_server_error() { incoming_req .shared_state() .write("server") - .peer_max_field_section_size = 12; + .config + .max_field_section_size = 12; let err_kind = request_stream .send_response( @@ -781,7 +783,8 @@ async fn header_too_big_server_error_trailers() { incoming_req .shared_state() .write("write") - .peer_max_field_section_size = 200; + .config + .max_field_section_size = 200; let mut trailers = HeaderMap::new(); trailers.insert("trailer", "value".repeat(100).parse().unwrap()); @@ -1335,7 +1338,7 @@ fn request_encode(buf: &mut B, req: http::Request<()>) { headers, .. } = parts; - let headers = Header::request(method, uri, headers).unwrap(); + let headers = Header::request(method, uri, headers, Default::default()).unwrap(); let mut block = BytesMut::new(); qpack::encode_stateless(&mut block, headers).unwrap(); Frame::headers(block).encode_with_payload(buf); From ff4d51ad04518d2422e2b375705577eb55062102 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 4 Apr 2023 17:30:22 +0200 Subject: [PATCH 33/97] chore: move WebTransportSession --- examples/webtransport_server.rs | 5 +- h3/src/connection.rs | 6 +- h3/src/proto/stream.rs | 3 +- h3/src/server.rs | 206 ++++---------------------------- h3/src/stream.rs | 1 + h3/src/webtransport/server.rs | 198 +++++++++++++++++++++++++++++- 6 files changed, 224 insertions(+), 195 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index a01992a6..004fb11e 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -18,7 +18,8 @@ use tracing::{error, info, trace_span}; use h3::{ error::ErrorLevel, quic::{self, BidiStream, RecvStream}, - server::{Config, Connection, RequestStream, WebTransportSession}, + server::{Config, Connection, RequestStream}, + webtransport::server::WebTransportSession, Protocol, }; use h3_quinn::quinn; @@ -258,7 +259,7 @@ where tracing::info!("Finished sending datagram"); } } - stream = session.read_uni_stream() => { + stream = session.accept_uni() => { let mut stream = stream?.unwrap(); // TODO: got stuck polling this future!!! if let Ok(Some(mut bytes)) = poll_fn(|cx| stream.poll_data(cx)).await { diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 3f102d18..9d598989 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,6 +1,5 @@ use std::{ convert::TryFrom, - mem, sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, task::{Context, Poll}, }; @@ -78,7 +77,8 @@ where { pub(super) shared: SharedStateRef, // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging - pub conn: Arc>, + // TODO: Remove Mutex as *every* operation congests on this + pub(super) conn: Arc>, control_send: C::SendStream, control_recv: Option>, decoder_recv: Option>, @@ -267,6 +267,7 @@ where .map_err(|e| e.into().into()) } + /// Processes *all* incoming uni streams pub fn poll_accept_recv(&mut self, cx: &mut Context<'_>) -> Poll> { if let Some(ref e) = self.shared.read("poll_accept_request").error { return Poll::Ready(Err(e.clone())); @@ -318,6 +319,7 @@ where //# receipt of a second stream claiming to be a control stream MUST be //# treated as a connection error of type H3_STREAM_CREATION_ERROR. AcceptedRecvStream::Control(s) => { + tracing::debug!("Received control stream"); if self.control_recv.is_some() { return Poll::Ready(Err( self.close(Code::H3_STREAM_CREATION_ERROR, "got two control streams") diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index ac4e4a16..99a2e954 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -26,7 +26,7 @@ stream_types! { PUSH = 0x01, ENCODER = 0x02, DECODER = 0x03, - WEBTRANSPORT = 0x54, + WEBTRANSPORT_UNI = 0x54, } impl StreamType { @@ -60,6 +60,7 @@ impl fmt::Display for StreamType { &StreamType::CONTROL => write!(f, "Control"), &StreamType::ENCODER => write!(f, "Encoder"), &StreamType::DECODER => write!(f, "Decoder"), + &StreamType::WEBTRANSPORT_UNI => write!(f, "WebTransportUni"), x => write!(f, "StreamType({})", x.0), } } diff --git a/h3/src/server.rs b/h3/src/server.rs index a219a0f7..ab0e6478 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -54,6 +54,8 @@ use std::{ collections::HashSet, convert::TryFrom, marker::PhantomData, + option::Option, + result::Result, sync::Arc, task::{Context, Poll}, }; @@ -106,8 +108,8 @@ where C: quic::Connection, B: Buf, { - /// TODO: find a better way to manage the connection - inner: ConnectionInner, + /// TODO: temporarily break encapsulation for `WebTransportSession` + pub(crate) inner: ConnectionInner, max_field_section_size: u64, // List of all incoming streams that are currently running. ongoing_streams: HashSet, @@ -150,6 +152,11 @@ where pub async fn with_config(conn: C, config: Config) -> Result { Builder { config }.build(conn).await } + + /// Closes the connection with a code and a reason. + pub(crate) fn close>(&mut self, code: Code, reason: T) -> Error { + self.inner.close(code, reason) + } } impl Connection @@ -491,6 +498,17 @@ where Poll::Ready(Ok(frame)) } + /// Accepts an incoming recv stream + fn poll_accept_uni( + &self, + cx: &mut Context<'_>, + ) -> Poll>::RecvStream>, Error>> { + todo!() + // let recv = ready!(self.inner.poll_accept_recv(cx))?; + + // Poll::Ready(Ok(recv)) + } + fn poll_requests_completion(&mut self, cx: &mut Context<'_>) -> Poll<()> { loop { match self.request_end_recv.poll_recv(cx) { @@ -871,127 +889,6 @@ impl Drop for RequestEnd { } } -// WEBTRANSPORT -// TODO: extract server.rs to server/mod.rs and submodules - -/// WebTransport session driver. -/// -/// Maintains the session using the underlying HTTP/3 connection. -/// -/// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. -pub struct WebTransportSession -where - C: quic::Connection, - B: Buf, -{ - conn: Connection, - connect_stream: RequestStream, -} - -impl WebTransportSession -where - C: quic::Connection, - B: Buf, -{ - /// Accepts a *CONNECT* request for establishing a WebTransport session. - /// - /// TODO: is the API or the user responsible for validating the CONNECT request? - pub async fn accept( - request: Request<()>, - mut stream: RequestStream, - mut conn: Connection, - ) -> Result { - // future::poll_fn(|cx| conn.poll_control(cx)).await?; - - let shared = conn.shared_state().clone(); - { - let config = shared.write("Read WebTransport support").config; - - tracing::debug!("Client settings: {:#?}", config); - if !config.enable_webtransport { - return Err(conn.inner.close( - Code::H3_SETTINGS_ERROR, - "webtransport is not supported by client", - )); - } - - if !config.enable_datagram { - return Err(conn.inner.close( - Code::H3_SETTINGS_ERROR, - "datagrams are not supported by client", - )); - } - } - - tracing::debug!("Validated client webtransport support"); - - // The peer is responsible for validating our side of the webtransport support. - // - // However, it is still advantageous to show a log on the server as (attempting) to - // establish a WebTransportSession without the proper h3 config is usually a mistake. - if !conn.inner.config.enable_webtransport { - tracing::warn!("Server does not support webtransport"); - } - - if !conn.inner.config.enable_datagram { - tracing::warn!("Server does not support datagrams"); - } - - if !conn.inner.config.enable_connect { - tracing::warn!("Server does not support CONNECT"); - } - - // Respond to the CONNECT request. - - //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 - let response = if validate_wt_connect(&request) { - Response::builder() - // This is the only header that chrome cares about. - .header("sec-webtransport-http3-draft", "draft02") - .status(StatusCode::OK) - .body(()) - .unwrap() - } else { - Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(()) - .unwrap() - }; - - tracing::info!("Sending response: {response:?}"); - stream.send_response(response).await?; - - Ok(Self { - conn, - connect_stream: stream, - }) - } - - /// Receive a datagram from the client - pub fn read_datagram(&self) -> WebTransportReadDatagram { - WebTransportReadDatagram { - inner: self.conn.read_datagram(), - } - } - - /// Sends a datagram - /// - /// TODO: maybe make async. `quinn` does not require an async send - pub fn send_datagram(&self, data: impl Buf) -> Result<(), Error> { - self.conn.send_datagram(self.connect_stream.id(), data)?; - - Ok(()) - } - - /// Receive a unidirectional stream from the client, it reads the stream until EOF. - pub fn read_uni_stream(&self) -> ReadUniStream { - ReadUniStream { - conn: &self.conn.inner.conn, - _marker: PhantomData, - } - } -} - /// Future for [`Connection::read_datagram`] pub struct ReadDatagram<'a, C, B> where @@ -1017,66 +914,3 @@ where } } } - -/// Future for [`Connection::read_datagram`] -#[pin_project] -pub struct WebTransportReadDatagram<'a, C, B> -where - C: quic::Connection, - B: Buf, -{ - #[pin] - inner: ReadDatagram<'a, C, B>, -} - -impl<'a, C, B> Future for WebTransportReadDatagram<'a, C, B> -where - C: quic::Connection, - B: Buf, -{ - type Output = Result, Error>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - tracing::trace!("poll: read_datagram"); - let mut p = self.project(); - let val = match ready!(p.inner.poll_unpin(cx))? { - Some(v) => Some(v.payload), - None => None, - }; - - Poll::Ready(Ok(val)) - } -} - -/// Future for [`WebTransportSession::read_uni_stream`] -pub struct ReadUniStream<'a, C, B> -where - C: quic::Connection, - B: Buf, -{ - conn: &'a std::sync::Mutex, - _marker: PhantomData, -} - -impl<'a, C, B> Future for ReadUniStream<'a, C, B> -where - C: quic::Connection, - B: Buf, -{ - type Output = Result>::RecvStream>, Error>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - tracing::trace!("poll: read_uni_stream"); - let res = match ready!(self.conn.lock().unwrap().poll_accept_recv(cx)) { - Ok(v) => Ok(v), - Err(err) => Err(Error::from(err)), - }; - - tracing::info!("Got uni stream"); - Poll::Ready(res) - } -} - -fn validate_wt_connect(request: &Request<()>) -> bool { - matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) -} diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 0314867d..7068317c 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -167,6 +167,7 @@ where Push(u64, FrameStream), Encoder(S), Decoder(S), + WebSocketUni(S), Reserved, } diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index a5cfd208..da08973d 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,11 +1,201 @@ //! Provides the server side WebTransport session -use bytes::Buf; -use futures_util::future; +use std::{ + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::{Buf, Bytes}; +use futures_util::{future, ready, Future}; +use http::{Method, Request, Response, StatusCode}; +use pin_project::pin_project; use crate::{ connection::ConnectionState, + error::Code, quic, - server::{self, Connection}, - Error, + server::{self, Connection, ReadDatagram, RequestStream}, + Error, Protocol, }; + +/// WebTransport session driver. +/// +/// Maintains the session using the underlying HTTP/3 connection. +/// +/// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. +pub struct WebTransportSession +where + C: quic::Connection, + B: Buf, +{ + conn: Connection, + connect_stream: RequestStream, +} + +impl WebTransportSession +where + C: quic::Connection, + B: Buf, +{ + /// Accepts a *CONNECT* request for establishing a WebTransport session. + /// + /// TODO: is the API or the user responsible for validating the CONNECT request? + pub async fn accept( + request: Request<()>, + mut stream: RequestStream, + mut conn: Connection, + ) -> Result { + // future::poll_fn(|cx| conn.poll_control(cx)).await?; + + let shared = conn.shared_state().clone(); + { + let config = shared.write("Read WebTransport support").config; + + tracing::debug!("Client settings: {:#?}", config); + if !config.enable_webtransport { + return Err(conn.close( + Code::H3_SETTINGS_ERROR, + "webtransport is not supported by client", + )); + } + + if !config.enable_datagram { + return Err(conn.close( + Code::H3_SETTINGS_ERROR, + "datagrams are not supported by client", + )); + } + } + + tracing::debug!("Validated client webtransport support"); + + // The peer is responsible for validating our side of the webtransport support. + // + // However, it is still advantageous to show a log on the server as (attempting) to + // establish a WebTransportSession without the proper h3 config is usually a mistake. + if !conn.inner.config.enable_webtransport { + tracing::warn!("Server does not support webtransport"); + } + + if !conn.inner.config.enable_datagram { + tracing::warn!("Server does not support datagrams"); + } + + if !conn.inner.config.enable_connect { + tracing::warn!("Server does not support CONNECT"); + } + + // Respond to the CONNECT request. + + //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 + let response = if validate_wt_connect(&request) { + Response::builder() + // This is the only header that chrome cares about. + .header("sec-webtransport-http3-draft", "draft02") + .status(StatusCode::OK) + .body(()) + .unwrap() + } else { + Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(()) + .unwrap() + }; + + tracing::info!("Sending response: {response:?}"); + stream.send_response(response).await?; + + Ok(Self { + conn, + connect_stream: stream, + }) + } + + /// Receive a datagram from the client + pub fn read_datagram(&self) -> WebTransportReadDatagram { + WebTransportReadDatagram { + inner: self.conn.read_datagram(), + } + } + + /// Sends a datagram + /// + /// TODO: maybe make async. `quinn` does not require an async send + pub fn send_datagram(&self, data: impl Buf) -> Result<(), Error> { + self.conn.send_datagram(self.connect_stream.id(), data)?; + + Ok(()) + } + + /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. + pub fn accept_uni(&self) -> AcceptUni { + AcceptUni { + conn: &self.conn.inner.conn, + _marker: PhantomData, + } + } +} + +/// Future for [`Connection::read_datagram`] +#[pin_project] +pub struct WebTransportReadDatagram<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + #[pin] + inner: ReadDatagram<'a, C, B>, +} + +impl<'a, C, B> Future for WebTransportReadDatagram<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("poll: read_datagram"); + let mut p = self.project(); + let val = match ready!(p.inner.poll(cx))? { + Some(v) => Some(v.payload), + None => None, + }; + + Poll::Ready(Ok(val)) + } +} + +/// Future for [`WebTransportSession::accept_uni`] +pub struct AcceptUni<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + conn: &'a std::sync::Mutex, + _marker: PhantomData, +} + +impl<'a, C, B> Future for AcceptUni<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + type Output = Result>::RecvStream>, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("poll: read_uni_stream"); + let res = match ready!(self.conn.lock().unwrap().poll_accept_recv(cx)) { + Ok(v) => Ok(v), + Err(err) => Err(Error::from(err)), + }; + + tracing::info!("Got uni stream"); + Poll::Ready(res) + } +} + +fn validate_wt_connect(request: &Request<()>) -> bool { + matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) +} From a00976544d50946b9f62bfd06eed88fb3d359e99 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 5 Apr 2023 15:51:29 +0200 Subject: [PATCH 34/97] fix: don't discard incoming recv streams --- h3/src/connection.rs | 110 ++++++++++++++++++---------------- h3/src/stream.rs | 55 +++++++++++++++-- h3/src/webtransport/server.rs | 71 ++++++++++++---------- 3 files changed, 149 insertions(+), 87 deletions(-) diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 9d598989..dd6ff97b 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -5,7 +5,7 @@ use std::{ }; use bytes::{Buf, Bytes, BytesMut}; -use futures_util::{future, ready}; +use futures_util::{future, ready, stream::FuturesUnordered, StreamExt}; use http::HeaderMap; use tracing::{trace, warn}; @@ -21,7 +21,7 @@ use crate::{ qpack, quic::{self, SendStream as _}, server::Config, - stream::{self, AcceptRecvStream, AcceptedRecvStream}, + stream::{self, AcceptRecvStream, AcceptRecvStreamFuture, AcceptedRecvStream}, }; #[doc(hidden)] @@ -70,6 +70,18 @@ pub trait ConnectionState { } } +pub(crate) struct AcceptedStreams { + pub uni_streams: Vec, +} + +impl Default for AcceptedStreams { + fn default() -> Self { + Self { + uni_streams: Default::default(), + } + } +} + pub struct ConnectionInner where C: quic::Connection, @@ -83,7 +95,13 @@ where control_recv: Option>, decoder_recv: Option>, encoder_recv: Option>, - pending_recv_streams: Vec>, + /// Stores incoming uni/recv streams which have yet to be claimed. + /// + /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. + accepted_streams: AcceptedStreams, + + pending_recv_streams: FuturesUnordered>, + got_peer_settings: bool, pub(super) send_grease_frame: bool, pub(super) config: Config, @@ -199,10 +217,11 @@ where control_recv: None, decoder_recv: None, encoder_recv: None, - pending_recv_streams: Vec::with_capacity(3), + pending_recv_streams: FuturesUnordered::new(), got_peer_settings: false, send_grease_frame: config.send_grease, config, + accepted_streams: Default::default(), }; // start a grease stream if config.send_grease { @@ -267,52 +286,38 @@ where .map_err(|e| e.into().into()) } - /// Processes *all* incoming uni streams - pub fn poll_accept_recv(&mut self, cx: &mut Context<'_>) -> Poll> { + /// Polls incoming streams + /// + /// Accepted streams which are not control, decoder, or encoder streams are buffer in `accepted_recv_streams` + pub fn poll_accept_recv(&mut self, cx: &mut Context<'_>) -> Result<(), Error> { if let Some(ref e) = self.shared.read("poll_accept_request").error { - return Poll::Ready(Err(e.clone())); + return Err(e.clone()); } + // Get all currently pending streams loop { - match self - .conn - .lock() - .unwrap() - .poll_accept_recv(cx) - .map_err(|v| Error::from(v))? - { + match self.conn.lock().unwrap().poll_accept_recv(cx)? { Poll::Ready(Some(stream)) => self .pending_recv_streams - .push(AcceptRecvStream::new(stream)), + .push(AcceptRecvStreamFuture::new(AcceptRecvStream::new(stream))), Poll::Ready(None) => { - return Poll::Ready(Err(Code::H3_GENERAL_PROTOCOL_ERROR.with_reason( + return Err(Code::H3_GENERAL_PROTOCOL_ERROR.with_reason( "Connection closed unexpected", crate::error::ErrorLevel::ConnectionError, - ))) + )) } Poll::Pending => break, } } - let mut resolved = vec![]; - - for (index, pending) in self.pending_recv_streams.iter_mut().enumerate() { - match pending.poll_type(cx)? { - Poll::Ready(()) => resolved.push(index), - Poll::Pending => (), - } - } - - for (removed, index) in resolved.into_iter().enumerate() { + // Get all currently ready streams + while let Poll::Ready(Some(stream)) = self.pending_recv_streams.poll_next_unpin(cx) { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2 //= type=implication //# As certain stream types can affect connection state, a recipient //# SHOULD NOT discard data from incoming unidirectional streams prior to //# reading the stream type. - let stream = self - .pending_recv_streams - .remove(index - removed) - .into_stream()?; + let stream = stream?; match stream { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.1 //# Only one control stream per peer is permitted; @@ -321,26 +326,31 @@ where AcceptedRecvStream::Control(s) => { tracing::debug!("Received control stream"); if self.control_recv.is_some() { - return Poll::Ready(Err( + return Err( self.close(Code::H3_STREAM_CREATION_ERROR, "got two control streams") - )); + ); } self.control_recv = Some(s); } enc @ AcceptedRecvStream::Encoder(_) => { if let Some(_prev) = self.encoder_recv.replace(enc) { - return Poll::Ready(Err( + return Err( self.close(Code::H3_STREAM_CREATION_ERROR, "got two encoder streams") - )); + ); } } dec @ AcceptedRecvStream::Decoder(_) => { if let Some(_prev) = self.decoder_recv.replace(dec) { - return Poll::Ready(Err( + return Err( self.close(Code::H3_STREAM_CREATION_ERROR, "got two decoder streams") - )); + ); } } + AcceptedRecvStream::WebTransportUni(s) => { + // Store until someone else picks it up, like a webtransport session which is + // not yet established. + self.accepted_streams.uni_streams.push(s) + } //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.3 //= type=implication @@ -350,28 +360,24 @@ where } } - Poll::Pending + Ok(()) } + /// Waits for the control stream to be received and reads subsequent frames. pub fn poll_control(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { if let Some(ref e) = self.shared.read("poll_accept_request").error { return Poll::Ready(Err(e.clone())); } - loop { - match self.poll_accept_recv(cx) { - Poll::Ready(Ok(_)) => continue, - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), - Poll::Pending if self.control_recv.is_none() => return Poll::Pending, - _ => break, + let recv = loop { + // TODO + self.poll_accept_recv(cx)?; + if let Some(v) = &mut self.control_recv { + break v; } - } + }; - let recvd = ready!(self - .control_recv - .as_mut() - .expect("control_recv") - .poll_next(cx))?; + let recvd = ready!(recv.poll_next(cx))?; let res = match recvd { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.1 @@ -583,8 +589,8 @@ where }; } - pub fn got_peer_settings(&self) -> bool { - self.got_peer_settings + pub(crate) fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { + &mut self.accepted_streams } } diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 7068317c..16911df9 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -1,7 +1,11 @@ -use std::task::{Context, Poll}; +use std::{ + marker::PhantomData, + task::{Context, Poll}, +}; use bytes::{Buf, BufMut as _, Bytes}; -use futures_util::{future, ready}; +use futures_util::{future, ready, Future}; +use pin_project::pin_project; use quic::RecvStream; use crate::{ @@ -167,14 +171,24 @@ where Push(u64, FrameStream), Encoder(S), Decoder(S), - WebSocketUni(S), + WebTransportUni(S), Reserved, } -pub(super) struct AcceptRecvStream +impl AcceptedRecvStream where S: quic::RecvStream, { + /// Returns `true` if the accepted recv stream is [`WebTransportUni`]. + /// + /// [`WebTransportUni`]: AcceptedRecvStream::WebTransportUni + #[must_use] + pub fn is_web_transport_uni(&self) -> bool { + matches!(self, Self::WebTransportUni(..)) + } +} + +pub(super) struct AcceptRecvStream { stream: S, ty: Option, push_id: Option, @@ -207,6 +221,7 @@ where ), StreamType::ENCODER => AcceptedRecvStream::Encoder(self.stream), StreamType::DECODER => AcceptedRecvStream::Decoder(self.stream), + StreamType::WEBTRANSPORT_UNI => AcceptedRecvStream::WebTransportUni(self.stream), t if t.value() > 0x21 && (t.value() - 0x21) % 0x1f == 0 => AcceptedRecvStream::Reserved, //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2 @@ -285,6 +300,38 @@ where } } +/// Future for accepting a receive stream and reading the type +#[pin_project] // Allows moving out of `inner` +pub(super) struct AcceptRecvStreamFuture { + inner: Option>, + _marker: PhantomData, +} + +impl AcceptRecvStreamFuture { + pub(super) fn new(recv: AcceptRecvStream) -> Self { + Self { + inner: Some(recv), + _marker: PhantomData, + } + } +} + +impl Future for AcceptRecvStreamFuture +where + S: RecvStream, + B: Buf, +{ + type Output = Result, Error>; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let inner = self.inner.as_mut().unwrap(); + ready!(inner.poll_type(cx))?; + + let stream = self.inner.take().unwrap().into_stream()?; + Poll::Ready(Ok(stream)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index da08973d..3d3a1e18 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -3,21 +3,21 @@ use std::{ marker::PhantomData, pin::Pin, + sync::{Arc, Mutex}, task::{Context, Poll}, }; -use bytes::{Buf, Bytes}; -use futures_util::{future, ready, Future}; -use http::{Method, Request, Response, StatusCode}; -use pin_project::pin_project; - use crate::{ connection::ConnectionState, error::Code, + proto::datagram::Datagram, quic, - server::{self, Connection, ReadDatagram, RequestStream}, + server::{self, Connection, RequestStream}, Error, Protocol, }; +use bytes::{Buf, Bytes}; +use futures_util::{ready, Future}; +use http::{Method, Request, Response, StatusCode}; /// WebTransport session driver. /// @@ -29,7 +29,7 @@ where C: quic::Connection, B: Buf, { - conn: Connection, + conn: Mutex>, connect_stream: RequestStream, } @@ -107,15 +107,16 @@ where stream.send_response(response).await?; Ok(Self { - conn, + conn: Mutex::new(conn), connect_stream: stream, }) } /// Receive a datagram from the client - pub fn read_datagram(&self) -> WebTransportReadDatagram { - WebTransportReadDatagram { - inner: self.conn.read_datagram(), + pub fn read_datagram(&self) -> ReadDatagram { + ReadDatagram { + conn: self.conn.lock().unwrap().inner.conn.clone(), + _marker: PhantomData, } } @@ -123,7 +124,10 @@ where /// /// TODO: maybe make async. `quinn` does not require an async send pub fn send_datagram(&self, data: impl Buf) -> Result<(), Error> { - self.conn.send_datagram(self.connect_stream.id(), data)?; + self.conn + .lock() + .unwrap() + .send_datagram(self.connect_stream.id(), data)?; Ok(()) } @@ -131,24 +135,23 @@ where /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. pub fn accept_uni(&self) -> AcceptUni { AcceptUni { - conn: &self.conn.inner.conn, + conn: &self.conn, _marker: PhantomData, } } } /// Future for [`Connection::read_datagram`] -#[pin_project] -pub struct WebTransportReadDatagram<'a, C, B> +pub struct ReadDatagram where C: quic::Connection, B: Buf, { - #[pin] - inner: ReadDatagram<'a, C, B>, + conn: Arc>, + _marker: PhantomData, } -impl<'a, C, B> Future for WebTransportReadDatagram<'a, C, B> +impl Future for ReadDatagram where C: quic::Connection, B: Buf, @@ -157,13 +160,12 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_datagram"); - let mut p = self.project(); - let val = match ready!(p.inner.poll(cx))? { - Some(v) => Some(v.payload), - None => None, - }; - Poll::Ready(Ok(val)) + let mut conn = self.conn.lock().unwrap(); + match ready!(conn.poll_accept_datagram(cx))? { + Some(v) => Poll::Ready(Ok(Some(Datagram::decode(v)?.payload))), + None => Poll::Ready(Ok(None)), + } } } @@ -173,7 +175,7 @@ where C: quic::Connection, B: Buf, { - conn: &'a std::sync::Mutex, + conn: &'a Mutex>, _marker: PhantomData, } @@ -186,13 +188,20 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); - let res = match ready!(self.conn.lock().unwrap().poll_accept_recv(cx)) { - Ok(v) => Ok(v), - Err(err) => Err(Error::from(err)), - }; - tracing::info!("Got uni stream"); - Poll::Ready(res) + let mut conn = self.conn.lock().unwrap(); + conn.inner.poll_accept_recv(cx)?; + + // Get the currently available streams + let streams = conn.inner.accepted_streams_mut(); + if let Some(stream) = streams.uni_streams.pop() { + tracing::info!("Got uni stream"); + return Poll::Ready(Ok(Some(stream))); + } + + tracing::debug!("Waiting on incoming streams"); + + Poll::Pending } } From 126fa50fbc7c3ad12408bf76e877e8f9b7019b9d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 5 Apr 2023 16:58:51 +0200 Subject: [PATCH 35/97] chore: save session_id --- h3/src/server.rs | 5 +++++ h3/src/webtransport/server.rs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/h3/src/server.rs b/h3/src/server.rs index ab0e6478..0e5fb42f 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -849,6 +849,11 @@ where //# implementation resets the sending parts of streams and aborts reading //# on the receiving parts of streams; see Section 2.4 of //# [QUIC-TRANSPORT]. + + /// Returns the underlying stream id + pub fn send_id(&self) -> StreamId { + self.inner.stream.send_id() + } } impl RequestStream diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 3d3a1e18..ef8fbd14 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -18,6 +18,7 @@ use crate::{ use bytes::{Buf, Bytes}; use futures_util::{ready, Future}; use http::{Method, Request, Response, StatusCode}; +use quic::StreamId; /// WebTransport session driver. /// @@ -29,6 +30,8 @@ where C: quic::Connection, B: Buf, { + // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 + session_id: StreamId, conn: Mutex>, connect_stream: RequestStream, } @@ -106,7 +109,10 @@ where tracing::info!("Sending response: {response:?}"); stream.send_response(response).await?; + let session_id = stream.send_id(); + tracing::info!("Established new WebTransport session with id {session_id:?}"); Ok(Self { + session_id, conn: Mutex::new(conn), connect_stream: stream, }) From 5091639355812cd2c02ed8f2ad79a64e0cb77c40 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 6 Apr 2023 12:21:18 +0200 Subject: [PATCH 36/97] feat: resolve session id for uni streams --- examples/webtransport_server.rs | 5 ++- h3/src/connection.rs | 42 ++++++++++++------ h3/src/proto/stream.rs | 8 +++- h3/src/stream.rs | 79 +++++++++++---------------------- h3/src/webtransport/mod.rs | 51 +++++++++++++++++++++ h3/src/webtransport/server.rs | 16 ++++--- 6 files changed, 128 insertions(+), 73 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 004fb11e..fa61b708 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -248,7 +248,7 @@ where tokio::select! { datagram = session.read_datagram() => { let datagram = datagram?; - if let Some(datagram) = datagram { + if let Some((id, datagram)) = datagram { tracing::info!("Responding with {datagram:?}"); // Put something before to make sure encoding and decoding works and don't just // pass through @@ -260,7 +260,8 @@ where } } stream = session.accept_uni() => { - let mut stream = stream?.unwrap(); + let (id, mut stream) = stream?.unwrap(); + tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); // TODO: got stuck polling this future!!! if let Ok(Some(mut bytes)) = poll_fn(|cx| stream.poll_data(cx)).await { tracing::info!("Received unidirectional stream with {}", bytes.get_u8()); diff --git a/h3/src/connection.rs b/h3/src/connection.rs index dd6ff97b..1d7ae7f4 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -5,7 +5,7 @@ use std::{ }; use bytes::{Buf, Bytes, BytesMut}; -use futures_util::{future, ready, stream::FuturesUnordered, StreamExt}; +use futures_util::{future, ready}; use http::HeaderMap; use tracing::{trace, warn}; @@ -21,7 +21,8 @@ use crate::{ qpack, quic::{self, SendStream as _}, server::Config, - stream::{self, AcceptRecvStream, AcceptRecvStreamFuture, AcceptedRecvStream}, + stream::{self, AcceptRecvStream, AcceptedRecvStream}, + webtransport::SessionId, }; #[doc(hidden)] @@ -71,7 +72,7 @@ pub trait ConnectionState { } pub(crate) struct AcceptedStreams { - pub uni_streams: Vec, + pub uni_streams: Vec<(SessionId, R)>, } impl Default for AcceptedStreams { @@ -100,7 +101,7 @@ where /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. accepted_streams: AcceptedStreams, - pending_recv_streams: FuturesUnordered>, + pending_recv_streams: Vec>, got_peer_settings: bool, pub(super) send_grease_frame: bool, @@ -217,7 +218,7 @@ where control_recv: None, decoder_recv: None, encoder_recv: None, - pending_recv_streams: FuturesUnordered::new(), + pending_recv_streams: Vec::with_capacity(3), got_peer_settings: false, send_grease_frame: config.send_grease, config, @@ -299,7 +300,7 @@ where match self.conn.lock().unwrap().poll_accept_recv(cx)? { Poll::Ready(Some(stream)) => self .pending_recv_streams - .push(AcceptRecvStreamFuture::new(AcceptRecvStream::new(stream))), + .push(AcceptRecvStream::new(stream)), Poll::Ready(None) => { return Err(Code::H3_GENERAL_PROTOCOL_ERROR.with_reason( "Connection closed unexpected", @@ -310,14 +311,26 @@ where } } - // Get all currently ready streams - while let Poll::Ready(Some(stream)) = self.pending_recv_streams.poll_next_unpin(cx) { + let mut resolved = vec![]; + + for (index, pending) in self.pending_recv_streams.iter_mut().enumerate() { + match pending.poll_type(cx)? { + Poll::Ready(()) => resolved.push(index), + Poll::Pending => (), + } + } + + for (removed, index) in resolved.into_iter().enumerate() { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2 //= type=implication //# As certain stream types can affect connection state, a recipient //# SHOULD NOT discard data from incoming unidirectional streams prior to //# reading the stream type. - let stream = stream?; + let stream = self + .pending_recv_streams + .remove(index - removed) + .into_stream()?; + match stream { //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.1 //# Only one control stream per peer is permitted; @@ -346,10 +359,10 @@ where ); } } - AcceptedRecvStream::WebTransportUni(s) => { + AcceptedRecvStream::WebTransportUni(id, s) => { // Store until someone else picks it up, like a webtransport session which is // not yet established. - self.accepted_streams.uni_streams.push(s) + self.accepted_streams.uni_streams.push((id, s)) } //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.3 @@ -369,11 +382,14 @@ where return Poll::Ready(Err(e.clone())); } - let recv = loop { + let recv = { // TODO self.poll_accept_recv(cx)?; if let Some(v) = &mut self.control_recv { - break v; + v + } else { + // Try later + return Poll::Pending; } }; diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 99a2e954..18e4ddce 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -160,7 +160,7 @@ impl From for VarInt { /// Invalid StreamId, for example because it's too large #[derive(Debug, PartialEq)] -pub struct InvalidStreamId(u64); +pub struct InvalidStreamId(pub(crate) u64); impl Display for InvalidStreamId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -187,6 +187,12 @@ impl Add for StreamId { } } +impl From for StreamId { + fn from(value: crate::webtransport::SessionId) -> Self { + Self(value.into_inner()) + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Side { /// The initiator of a connection diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 16911df9..d45da75a 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -1,12 +1,7 @@ -use std::{ - marker::PhantomData, - task::{Context, Poll}, -}; +use std::task::{Context, Poll}; use bytes::{Buf, BufMut as _, Bytes}; -use futures_util::{future, ready, Future}; -use pin_project::pin_project; -use quic::RecvStream; +use futures_util::{future, ready}; use crate::{ buf::BufList, @@ -18,7 +13,8 @@ use crate::{ stream::StreamType, varint::VarInt, }, - quic::{self, SendStream}, + quic::{self, RecvStream, SendStream}, + webtransport::SessionId, Error, }; @@ -171,7 +167,7 @@ where Push(u64, FrameStream), Encoder(S), Decoder(S), - WebTransportUni(S), + WebTransportUni(SessionId, S), Reserved, } @@ -188,10 +184,12 @@ where } } +/// Resolves an incoming streams type as well as `PUSH_ID`s and `SESSION_ID`s pub(super) struct AcceptRecvStream { stream: S, ty: Option, - push_id: Option, + /// push_id or session_id + id: Option, buf: BufList, expected: Option, } @@ -204,7 +202,7 @@ where Self { stream, ty: None, - push_id: None, + id: None, buf: BufList::new(), expected: None, } @@ -216,12 +214,15 @@ where AcceptedRecvStream::Control(FrameStream::with_bufs(self.stream, self.buf)) } StreamType::PUSH => AcceptedRecvStream::Push( - self.push_id.expect("Push ID not resolved yet"), + self.id.expect("Push ID not resolved yet").into_inner(), FrameStream::with_bufs(self.stream, self.buf), ), StreamType::ENCODER => AcceptedRecvStream::Encoder(self.stream), StreamType::DECODER => AcceptedRecvStream::Decoder(self.stream), - StreamType::WEBTRANSPORT_UNI => AcceptedRecvStream::WebTransportUni(self.stream), + StreamType::WEBTRANSPORT_UNI => AcceptedRecvStream::WebTransportUni( + SessionId::from_varint(self.id.expect("Session ID not resolved yet")), + self.stream, + ), t if t.value() > 0x21 && (t.value() - 0x21) % 0x1f == 0 => AcceptedRecvStream::Reserved, //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2 @@ -249,11 +250,16 @@ where pub fn poll_type(&mut self, cx: &mut Context) -> Poll> { loop { - match (self.ty.as_ref(), self.push_id) { - // When accepting a Push stream, we want to parse two VarInts: [StreamType, PUSH_ID] - (Some(&StreamType::PUSH), Some(_)) | (Some(_), _) => return Poll::Ready(Ok(())), - _ => (), - } + // Return if all identification data is met + match self.ty { + Some(StreamType::PUSH | StreamType::WEBTRANSPORT_UNI) => { + if self.id.is_some() { + return Poll::Ready(Ok(())); + } + } + Some(_) => return Poll::Ready(Ok(())), + None => (), + }; match ready!(self.stream.poll_data(cx))? { Some(mut b) => self.buf.push_bytes(&mut b), @@ -277,6 +283,7 @@ where continue; } + // Parse ty and then id if self.ty.is_none() { // Parse StreamType self.ty = Some(StreamType::decode(&mut self.buf).map_err(|_| { @@ -289,9 +296,9 @@ where self.expected = None; } else { // Parse PUSH_ID - self.push_id = Some(self.buf.get_var().map_err(|_| { + self.id = Some(VarInt::decode(&mut self.buf).map_err(|_| { Code::H3_INTERNAL_ERROR.with_reason( - "Unexpected end parsing stream type", + "Unexpected end parsing push or session id", ErrorLevel::ConnectionError, ) })?); @@ -300,38 +307,6 @@ where } } -/// Future for accepting a receive stream and reading the type -#[pin_project] // Allows moving out of `inner` -pub(super) struct AcceptRecvStreamFuture { - inner: Option>, - _marker: PhantomData, -} - -impl AcceptRecvStreamFuture { - pub(super) fn new(recv: AcceptRecvStream) -> Self { - Self { - inner: Some(recv), - _marker: PhantomData, - } - } -} - -impl Future for AcceptRecvStreamFuture -where - S: RecvStream, - B: Buf, -{ - type Output = Result, Error>; - - fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let inner = self.inner.as_mut().unwrap(); - ready!(inner.poll_type(cx))?; - - let stream = self.inner.take().unwrap().into_stream()?; - Poll::Ready(Ok(stream)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 8e9aed03..5774b890 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -3,4 +3,55 @@ //! # Relevant Links //! WebTransport: https://www.w3.org/TR/webtransport/#biblio-web-transport-http3 //! WebTransport over HTTP/3: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/ + +use std::convert::TryFrom; + +use crate::proto::{ + coding::{Decode, Encode}, + stream::{InvalidStreamId, StreamId}, + varint::VarInt, +}; pub mod server; + +/// Identifies a WebTransport session +/// +/// The session id is the same as the stream id of the CONNECT request. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct SessionId(u64); +impl SessionId { + pub(crate) fn from_varint(id: VarInt) -> SessionId { + Self(id.0) + } + + pub(crate) fn into_inner(self) -> u64 { + self.0 + } +} + +impl TryFrom for SessionId { + type Error = InvalidStreamId; + fn try_from(v: u64) -> Result { + if v > VarInt::MAX.0 { + return Err(InvalidStreamId(v)); + } + Ok(Self(v)) + } +} + +impl Encode for SessionId { + fn encode(&self, buf: &mut B) { + VarInt::from_u64(self.0).unwrap().encode(buf); + } +} + +impl Decode for SessionId { + fn decode(buf: &mut B) -> crate::proto::coding::Result { + Ok(Self(VarInt::decode(buf)?.into_inner())) + } +} + +impl From for SessionId { + fn from(value: StreamId) -> Self { + Self(value.into_inner()) + } +} diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index ef8fbd14..54fed092 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -20,6 +20,8 @@ use futures_util::{ready, Future}; use http::{Method, Request, Response, StatusCode}; use quic::StreamId; +use super::SessionId; + /// WebTransport session driver. /// /// Maintains the session using the underlying HTTP/3 connection. @@ -162,14 +164,18 @@ where C: quic::Connection, B: Buf, { - type Output = Result, Error>; + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_datagram"); let mut conn = self.conn.lock().unwrap(); match ready!(conn.poll_accept_datagram(cx))? { - Some(v) => Poll::Ready(Ok(Some(Datagram::decode(v)?.payload))), + // Some(v) => Poll::Ready(Ok(Some(Datagram::decode(v)?.payload))), + Some(v) => { + let datagram = Datagram::decode(v)?; + Poll::Ready(Ok(Some((datagram.stream_id().into(), datagram.payload)))) + } None => Poll::Ready(Ok(None)), } } @@ -190,7 +196,7 @@ where C: quic::Connection, B: Buf, { - type Output = Result>::RecvStream>, Error>; + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); @@ -200,9 +206,9 @@ where // Get the currently available streams let streams = conn.inner.accepted_streams_mut(); - if let Some(stream) = streams.uni_streams.pop() { + if let Some(v) = streams.uni_streams.pop() { tracing::info!("Got uni stream"); - return Poll::Ready(Ok(Some(stream))); + return Poll::Ready(Ok(Some(v))); } tracing::debug!("Waiting on incoming streams"); From 0fde7d442c4322b0235eae43e7de6634d516b2ee Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 6 Apr 2023 16:40:11 +0200 Subject: [PATCH 37/97] wip: partial read in uni stream --- h3/src/buf.rs | 4 +++ h3/src/connection.rs | 4 +-- h3/src/stream.rs | 6 ++-- h3/src/webtransport/mod.rs | 2 ++ h3/src/webtransport/server.rs | 4 +-- h3/src/webtransport/stream.rs | 53 +++++++++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 h3/src/webtransport/stream.rs diff --git a/h3/src/buf.rs b/h3/src/buf.rs index 99d12d56..2fa7985f 100644 --- a/h3/src/buf.rs +++ b/h3/src/buf.rs @@ -32,6 +32,10 @@ impl BufList { } impl BufList { + pub fn take_first_chunk(&mut self) -> Option { + self.bufs.pop_front() + } + pub fn take_chunk(&mut self, max_len: usize) -> Option { let chunk = self .bufs diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 1d7ae7f4..fb04729f 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -22,7 +22,7 @@ use crate::{ quic::{self, SendStream as _}, server::Config, stream::{self, AcceptRecvStream, AcceptedRecvStream}, - webtransport::SessionId, + webtransport::{self, SessionId}, }; #[doc(hidden)] @@ -72,7 +72,7 @@ pub trait ConnectionState { } pub(crate) struct AcceptedStreams { - pub uni_streams: Vec<(SessionId, R)>, + pub uni_streams: Vec<(SessionId, webtransport::stream::RecvStream)>, } impl Default for AcceptedStreams { diff --git a/h3/src/stream.rs b/h3/src/stream.rs index d45da75a..9a59bbd6 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -14,7 +14,7 @@ use crate::{ varint::VarInt, }, quic::{self, RecvStream, SendStream}, - webtransport::SessionId, + webtransport::{self, SessionId}, Error, }; @@ -167,7 +167,7 @@ where Push(u64, FrameStream), Encoder(S), Decoder(S), - WebTransportUni(SessionId, S), + WebTransportUni(SessionId, webtransport::stream::RecvStream), Reserved, } @@ -221,7 +221,7 @@ where StreamType::DECODER => AcceptedRecvStream::Decoder(self.stream), StreamType::WEBTRANSPORT_UNI => AcceptedRecvStream::WebTransportUni( SessionId::from_varint(self.id.expect("Session ID not resolved yet")), - self.stream, + webtransport::stream::RecvStream::new(self.buf, self.stream), ), t if t.value() > 0x21 && (t.value() - 0x21) % 0x1f == 0 => AcceptedRecvStream::Reserved, diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 5774b890..bdf68836 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -12,6 +12,8 @@ use crate::proto::{ varint::VarInt, }; pub mod server; +/// Send and Receive streams +pub mod stream; /// Identifies a WebTransport session /// diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 54fed092..9f6ed8ed 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -20,7 +20,7 @@ use futures_util::{ready, Future}; use http::{Method, Request, Response, StatusCode}; use quic::StreamId; -use super::SessionId; +use super::{stream::RecvStream, SessionId}; /// WebTransport session driver. /// @@ -196,7 +196,7 @@ where C: quic::Connection, B: Buf, { - type Output = Result, Error>; + type Output = Result)>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs new file mode 100644 index 00000000..2d78770a --- /dev/null +++ b/h3/src/webtransport/stream.rs @@ -0,0 +1,53 @@ +use std::task::Poll; + +use bytes::{Buf, Bytes}; +use futures_util::ready; + +use crate::{buf::BufList, quic}; + +/// WebTransport receive stream +pub struct RecvStream { + buf: BufList, + stream: S, +} + +impl RecvStream { + pub(crate) fn new(buf: BufList, stream: S) -> Self { + Self { buf, stream } + } +} + +impl quic::RecvStream for RecvStream +where + S: quic::RecvStream, +{ + type Buf = Bytes; + + type Error = S::Error; + + fn poll_data( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>> { + match ready!(self.stream.poll_data(cx)?) { + Some(mut buf) => self.buf.push_bytes(&mut buf), + None => return Poll::Ready(Ok(None)), + } + + if let Some(chunk) = self.buf.take_first_chunk() { + if chunk.remaining() > 0 { + return Poll::Ready(Ok(Some(chunk))); + } + } + + Poll::Pending + } + + fn stop_sending(&mut self, error_code: u64) { + self.stream.stop_sending(error_code) + } + + fn recv_id(&self) -> quic::StreamId { + self.stream.recv_id() + } +} From cc7dea2b453ac739fb9e3c48dbb7361d2668620a Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 11 Apr 2023 16:58:30 +0200 Subject: [PATCH 38/97] feat: bidirectional streams albeit quite hacky --- examples/webtransport_server.rs | 33 +++++--- h3/src/buf.rs | 1 + h3/src/connection.rs | 20 +++-- h3/src/frame.rs | 4 + h3/src/proto/frame.rs | 27 ++++--- h3/src/quic.rs | 4 +- h3/src/server.rs | 25 +++++- h3/src/webtransport/server.rs | 132 +++++++++++++++++++++++++++++--- h3/src/webtransport/stream.rs | 67 +++++++++++++--- 9 files changed, 262 insertions(+), 51 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index fa61b708..8a466d3c 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -5,10 +5,10 @@ use std::{ time::Duration, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use bytes::{Buf, BufMut, Bytes, BytesMut}; -use futures::future::poll_fn; -use futures::StreamExt; +use futures::{future, StreamExt}; +use futures::{future::poll_fn, AsyncRead}; use http::{Method, Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; @@ -17,7 +17,7 @@ use tracing::{error, info, trace_span}; use h3::{ error::ErrorLevel, - quic::{self, BidiStream, RecvStream}, + quic::{self, BidiStream, RecvStream, SendStream}, server::{Config, Connection, RequestStream}, webtransport::server::WebTransportSession, Protocol, @@ -248,7 +248,7 @@ where tokio::select! { datagram = session.read_datagram() => { let datagram = datagram?; - if let Some((id, datagram)) = datagram { + if let Some((_, datagram)) = datagram { tracing::info!("Responding with {datagram:?}"); // Put something before to make sure encoding and decoding works and don't just // pass through @@ -262,14 +262,25 @@ where stream = session.accept_uni() => { let (id, mut stream) = stream?.unwrap(); tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); - // TODO: got stuck polling this future!!! - if let Ok(Some(mut bytes)) = poll_fn(|cx| stream.poll_data(cx)).await { - tracing::info!("Received unidirectional stream with {}", bytes.get_u8()); + if let Some(bytes) = poll_fn(|cx| stream.poll_data(cx)).await? { + tracing::info!("Received data {:?}", bytes); } } - // stream = session = session.read_bi_stream() => { - // let mut stream = stream?.unwrap(); - // } + stream = session.accept_bi() => { + let (session_id, mut send, mut recv) = stream?.unwrap(); + tracing::info!("Got bi stream"); + let mut message = BytesMut::new(); + while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { + tracing::info!("Received data {:?}", bytes); + message.put(bytes); + + } + + tracing::info!("Got message: {message:?}"); + + // send.send_data(message.freeze()).context("Failed to send response"); + // future::poll_fn(|cx| send.poll_ready(cx)).await?; + } else => { break } diff --git a/h3/src/buf.rs b/h3/src/buf.rs index 2fa7985f..819c3bd0 100644 --- a/h3/src/buf.rs +++ b/h3/src/buf.rs @@ -3,6 +3,7 @@ use std::io::IoSlice; use bytes::{Buf, Bytes}; +#[derive(Debug)] pub(crate) struct BufList { bufs: VecDeque, } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index fb04729f..1bf432a2 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -71,11 +71,19 @@ pub trait ConnectionState { } } -pub(crate) struct AcceptedStreams { - pub uni_streams: Vec<(SessionId, webtransport::stream::RecvStream)>, +pub(crate) struct AcceptedStreams +where + C: quic::Connection, + B: Buf, +{ + pub uni_streams: Vec<(SessionId, webtransport::stream::RecvStream)>, } -impl Default for AcceptedStreams { +impl Default for AcceptedStreams +where + C: quic::Connection, + B: bytes::Buf, +{ fn default() -> Self { Self { uni_streams: Default::default(), @@ -90,7 +98,6 @@ where { pub(super) shared: SharedStateRef, // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging - // TODO: Remove Mutex as *every* operation congests on this pub(super) conn: Arc>, control_send: C::SendStream, control_recv: Option>, @@ -99,7 +106,7 @@ where /// Stores incoming uni/recv streams which have yet to be claimed. /// /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. - accepted_streams: AcceptedStreams, + accepted_streams: AcceptedStreams, pending_recv_streams: Vec>, @@ -237,6 +244,7 @@ where Ok(conn_inner) } + /// Send GOAWAY with specified max_id, iff max_id is smaller than the previous one. pub async fn shutdown( @@ -605,7 +613,7 @@ where }; } - pub(crate) fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { + pub(crate) fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { &mut self.accepted_streams } } diff --git a/h3/src/frame.rs b/h3/src/frame.rs index 8af14fd3..135fdd3d 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -43,6 +43,10 @@ impl FrameStream { _phantom_buffer: PhantomData, } } + + pub(crate) fn into_inner(self) -> (S, BufList) { + (self.stream, self.bufs) + } } impl FrameStream diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 0363ceb9..063079f9 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -5,8 +5,10 @@ use std::{ }; use tracing::trace; +use crate::webtransport::SessionId; + use super::{ - coding::Encode, + coding::{Decode, Encode}, push::{InvalidPushId, PushId}, stream::InvalidStreamId, varint::{BufExt, BufMutExt, UnexpectedEnd, VarInt}, @@ -49,7 +51,10 @@ pub enum Frame { PushPromise(PushPromise), Goaway(VarInt), MaxPushId(PushId), - WebTransportStream(WebTransportStream), + /// A webtransport frame is a frame without a length. + /// + /// The data is streaming + WebTransportStream(SessionId), Grease, } @@ -72,6 +77,15 @@ impl Frame { pub fn decode(buf: &mut T) -> Result { let remaining = buf.remaining(); let ty = FrameType::decode(buf).map_err(|_| FrameError::Incomplete(remaining + 1))?; + + // Webtransport streams need special handling as they have no length. + // + // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.2 + if ty == FrameType::WEBTRANSPORT_STREAM { + tracing::trace!("webtransport frame"); + return Ok(Frame::WebTransportStream(SessionId::decode(buf)?)); + } + let len = buf .get_var() .map_err(|_| FrameError::Incomplete(remaining + 1))?; @@ -93,18 +107,17 @@ impl Frame { FrameType::PUSH_PROMISE => Ok(Frame::PushPromise(PushPromise::decode(&mut payload)?)), FrameType::GOAWAY => Ok(Frame::Goaway(VarInt::decode(&mut payload)?)), FrameType::MAX_PUSH_ID => Ok(Frame::MaxPushId(payload.get_var()?.try_into()?)), - FrameType::WEBTRANSPORT_STREAM => Ok(Frame::WebTransportStream(WebTransportStream { - buffer: payload.copy_to_bytes(len as usize), - })), FrameType::H2_PRIORITY | FrameType::H2_PING | FrameType::H2_WINDOW_UPDATE | FrameType::H2_CONTINUATION => Err(FrameError::UnsupportedFrame(ty.0)), + FrameType::WEBTRANSPORT_STREAM | FrameType::DATA => unreachable!(), _ => { buf.advance(len as usize); Err(FrameError::UnknownFrame(ty.0)) } }; + if let Ok(frame) = &frame { trace!( "got frame {:?}, len: {}, remaining: {}", @@ -314,10 +327,6 @@ pub struct PushPromise { encoded: Bytes, } -#[derive(Debug, PartialEq)] -pub struct WebTransportStream { - buffer: Bytes, -} impl FrameHeader for PushPromise { const TYPE: FrameType = FrameType::PUSH_PROMISE; diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 8d437b7a..1cba9486 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -72,7 +72,7 @@ impl Error for SendDatagramError { /// Trait representing a QUIC connection. pub trait Connection { /// The type produced by `poll_accept_bidi()` - type BidiStream: SendStream + RecvStream; + type BidiStream: BidiStream; /// The type of the sending part of `BidiStream` type SendStream: SendStream; /// The type produced by `poll_accept_recv()` @@ -179,7 +179,7 @@ pub trait RecvStream { /// The type of `Buf` for data received on this stream. type Buf: Buf; /// The error type that can occur when receiving data. - type Error: Into>; + type Error: 'static + Error; /// Poll the stream for more data. /// diff --git a/h3/src/server.rs b/h3/src/server.rs index 0e5fb42f..31c63d4b 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -62,6 +62,7 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use futures_util::{ + future::ready, future::{self, Future}, ready, FutureExt, }; @@ -74,7 +75,7 @@ use tokio::sync::mpsc; use crate::{ connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, error::{Code, Error, ErrorLevel}, - frame::FrameStream, + frame::{FrameStream, FrameStreamError}, proto::{ datagram::Datagram, frame::{Frame, PayloadLen}, @@ -200,7 +201,20 @@ where }; let frame = future::poll_fn(|cx| stream.poll_next(cx)).await; + self.accept_with_frame(stream, frame).await + } + /// Accepts an http request where the first frame has already been read and decoded. + /// + /// + /// This is needed as a bidirectional stream may be read as part of incoming webtransport + /// bi-streams. If it turns out that the stream is *not* a `WEBTRANSPORT_STREAM` the request + /// may still want to be handled and passed to the user. + pub(crate) async fn accept_with_frame( + &mut self, + mut stream: FrameStream, + frame: Result>, FrameStreamError>, + ) -> Result, RequestStream)>, Error> { let mut encoded = match frame { Ok(Some(Frame::Headers(h))) => h, @@ -213,7 +227,7 @@ where return Err(self.inner.close( Code::H3_REQUEST_INCOMPLETE, "request stream closed before headers", - )) + )); } //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1 @@ -291,6 +305,7 @@ where .expect("header too big response"), ) .await?; + return Err(Error::header_too_big( cancel_size, self.max_field_section_size, @@ -402,7 +417,11 @@ where self.inner.shutdown(&mut self.sent_closing, max_id).await } - fn poll_accept_request( + /// Accepts an incoming bidirectional stream. + /// + /// This could be either a *Request* or a *WebTransportBiStream*, the first frame's type + /// decides. + pub(crate) fn poll_accept_request( &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 9f6ed8ed..aa3e6dec 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -9,18 +9,22 @@ use std::{ use crate::{ connection::ConnectionState, - error::Code, - proto::datagram::Datagram, - quic, + error::{Code, ErrorLevel}, + frame::FrameStream, + proto::{datagram::Datagram, frame::Frame}, + quic::{self, BidiStream, SendStream}, server::{self, Connection, RequestStream}, Error, Protocol, }; use bytes::{Buf, Bytes}; -use futures_util::{ready, Future}; +use futures_util::{future::poll_fn, ready, Future}; use http::{Method, Request, Response, StatusCode}; use quic::StreamId; -use super::{stream::RecvStream, SessionId}; +use super::{ + stream::{self, RecvStream}, + SessionId, +}; /// WebTransport session driver. /// @@ -142,9 +146,115 @@ where /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. pub fn accept_uni(&self) -> AcceptUni { - AcceptUni { - conn: &self.conn, - _marker: PhantomData, + AcceptUni { conn: &self.conn } + } + + /// Accepts an incoming bidirectional stream + /// TODO: should this return an enum of BiStream/Request to allow ordinary HTTP/3 requests to + /// pass through? + /// + /// Currently, they are ignored, and the function loops until it receives a *webtransport* + /// stream. + pub async fn accept_bi( + &self, + ) -> Result< + Option<( + SessionId, + stream::SendStream, + stream::RecvStream, + )>, + Error, + > { + loop { + // Get the next stream + // Accept the incoming stream + let stream = poll_fn(|cx| { + let mut conn = self.conn.lock().unwrap(); + conn.poll_accept_request(cx) + }) + .await; + + tracing::debug!("Received biderectional stream"); + + let mut stream = match stream { + Ok(Some(s)) => FrameStream::new(s), + Ok(None) => { + // We always send a last GoAway frame to the client, so it knows which was the last + // non-rejected request. + // self.shutdown(0).await?; + todo!("shutdown"); + // return Ok(None); + } + Err(err) => { + match err.inner.kind { + crate::error::Kind::Closed => return Ok(None), + crate::error::Kind::Application { + code, + reason, + level: ErrorLevel::ConnectionError, + } => { + return Err(self.conn.lock().unwrap().close( + code, + reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + )) + } + _ => return Err(err), + }; + } + }; + + tracing::debug!("Reading first frame"); + // Read the first frame. + // + // This wil determine if it is a webtransport bi-stream or a request stream + let frame = poll_fn(|cx| stream.poll_next(cx)).await; + + let mut conn = self.conn.lock().unwrap(); + match frame { + Ok(None) => return Ok(None), + Ok(Some(Frame::WebTransportStream(session_id))) => { + tracing::info!("Got webtransport stream"); + // Take the stream out of the framed reader + let (stream, buf) = stream.into_inner(); + let (send, recv) = stream.split(); + // Don't lose already read data + let recv = stream::RecvStream::new(buf, recv); + let send = stream::SendStream::new(send); + return Ok(Some((session_id, send, recv))); + } + // Not a webtransport stream, discard + // + // Find a workaround to return this request, in order to accept more sessions + Ok(Some(_frame)) => continue, + Err(e) => { + let err: Error = e.into(); + if err.is_closed() { + return Ok(None); + } + match err.inner.kind { + crate::error::Kind::Closed => return Ok(None), + crate::error::Kind::Application { + code, + reason, + level: ErrorLevel::ConnectionError, + } => { + return Err(conn.close( + code, + reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + )) + } + crate::error::Kind::Application { + code, + reason: _, + level: ErrorLevel::StreamError, + } => { + stream.reset(code.into()); + return Err(err); + } + _ => return Err(err), + }; + } + } } } } @@ -188,7 +298,6 @@ where B: Buf, { conn: &'a Mutex>, - _marker: PhantomData, } impl<'a, C, B> Future for AcceptUni<'a, C, B> @@ -196,7 +305,7 @@ where C: quic::Connection, B: Buf, { - type Output = Result)>, Error>; + type Output = Result)>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); @@ -218,5 +327,6 @@ where } fn validate_wt_connect(request: &Request<()>) -> bool { - matches!((request.method(), request.extensions().get::()), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) + let protocol = request.extensions().get::(); + matches!((request.method(), protocol), (&Method::CONNECT, Some(p)) if p == &Protocol::WEB_TRANSPORT) } diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 2d78770a..12913ae8 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -1,9 +1,15 @@ -use std::task::Poll; +use std::{marker::PhantomData, task::Poll}; use bytes::{Buf, Bytes}; -use futures_util::ready; +use futures_util::{ready, AsyncRead}; -use crate::{buf::BufList, quic}; +use crate::{ + buf::BufList, + proto::varint::UnexpectedEnd, + quic::{self}, +}; + +use super::SessionId; /// WebTransport receive stream pub struct RecvStream { @@ -25,22 +31,22 @@ where type Error = S::Error; + #[tracing::instrument(level = "info", skip_all)] fn poll_data( &mut self, cx: &mut std::task::Context<'_>, ) -> Poll, Self::Error>> { - match ready!(self.stream.poll_data(cx)?) { - Some(mut buf) => self.buf.push_bytes(&mut buf), - None => return Poll::Ready(Ok(None)), - } - + tracing::info!("Polling RecvStream"); if let Some(chunk) = self.buf.take_first_chunk() { if chunk.remaining() > 0 { return Poll::Ready(Ok(Some(chunk))); } } - Poll::Pending + match ready!(self.stream.poll_data(cx)?) { + Some(mut buf) => Poll::Ready(Ok(Some(buf.copy_to_bytes(buf.remaining())))), + None => Poll::Ready(Ok(None)), + } } fn stop_sending(&mut self, error_code: u64) { @@ -51,3 +57,46 @@ where self.stream.recv_id() } } + +/// WebTransport send stream +pub struct SendStream { + stream: S, + _marker: PhantomData, +} + +impl SendStream { + pub(crate) fn new(stream: S) -> Self { + Self { + stream, + _marker: PhantomData, + } + } +} + +impl quic::SendStream for SendStream +where + S: quic::SendStream, + B: Buf, +{ + type Error = S::Error; + + fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + self.stream.poll_ready(cx) + } + + fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { + self.stream.send_data(data) + } + + fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + self.stream.poll_finish(cx) + } + + fn reset(&mut self, reset_code: u64) { + self.stream.reset(reset_code) + } + + fn send_id(&self) -> quic::StreamId { + self.stream.send_id() + } +} From 7f2bc2db3bad4fb793eac4d4fbe448da2d91a192 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 12 Apr 2023 14:10:48 +0200 Subject: [PATCH 39/97] feat: make SendStream allow writing arbitrary data like io::Write This changes the `quic::SendStream` trait to closer mimic the `AsyncWrite` trait, but using a `impl Buf` rather than `&[u8]` which removes the need to allocate and copy the byte slice to store it if needed. [s2n-quic::SendStream](https://github.com/aws/s2n-quic/blob/bf20c6dd148153802929a2514b444dcf5dd37fd1/quic/s2n-quic-h3/src/s2n_quic.rs#L364) uses this to enqueue the bytes for sending, which would require allocating if `&[u8]` was used. Issue #78 discusses this API change which would remove the need for intermediate buffering. See: https://github.com/hyperium/h3/issues/78#issuecomment-1032899617 --- examples/webtransport_server.rs | 4 ++ h3-quinn/src/lib.rs | 103 +++++++++++++++++++++++--------- h3/src/connection.rs | 4 +- h3/src/frame.rs | 18 ++++++ h3/src/proto/frame.rs | 16 +++-- h3/src/quic.rs | 13 ++++ h3/src/stream.rs | 7 ++- h3/src/webtransport/stream.rs | 40 +++++++++++-- 8 files changed, 160 insertions(+), 45 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 8a466d3c..e00ce0f7 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -188,6 +188,8 @@ async fn main() -> Result<(), Box> { async fn handle_connection(mut conn: Connection) -> Result<()> where C: 'static + Send + quic::Connection, + >::Error: + 'static + std::error::Error + Send + Sync, { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them @@ -242,6 +244,7 @@ async fn handle_session_and_echo_all_inbound_messages( ) -> anyhow::Result<()> where C: 'static + Send + h3::quic::Connection, + >::Error: 'static + std::error::Error + Send + Sync, B: Buf, { loop { @@ -278,6 +281,7 @@ where tracing::info!("Got message: {message:?}"); + send.write_all(&mut format!("I got your message: {message:?}").as_bytes()).await.context("Failed to respond")?; // send.send_data(message.freeze()).context("Failed to send response"); // future::poll_fn(|cx| send.poll_ready(cx)).await?; } diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 5c4d5e92..dce8bd5e 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -320,6 +320,18 @@ where self.send.poll_ready(cx) } + fn send_data>>(&mut self, data: D) -> Result<(), Self::Error> { + self.send.send_data(data) + } + + fn poll_send( + &mut self, + cx: &mut task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.send.poll_send(cx, buf) + } + fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll> { self.send.poll_finish(cx) } @@ -328,10 +340,6 @@ where 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() } @@ -447,33 +455,70 @@ where { type Error = SendStreamError; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { - if let Some(ref mut data) = self.writing { - while data.has_remaining() { - match ready!(Pin::new(&mut self.stream).poll_write(cx, data.chunk())) { - Ok(cnt) => data.advance(cnt), - 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]) -> Write<'a, S>. - // - // This is why we have to unpack the error from io::Error below. This should not - // panic as long as quinn's AsyncWrite impl doesn't change. - return Poll::Ready(Err(SendStreamError::Write( - err.into_inner() - .expect("write stream returned an empty error") - .downcast_ref::() - .expect( - "write stream returned an error which type is not WriteError", - ) - .clone(), - ))); - } - } + fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll> { + unimplemented!() + //if let Some(ref mut data) = self.writing { + // while data.has_remaining() { + // match ready!(Pin::new(&mut self.stream).poll_write(cx, data.chunk())) { + // Ok(cnt) => data.advance(cnt), + // 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]) -> Write<'a, S>. + // // + // // This is why we have to unpack the error from io::Error below. This should not + // // panic as long as quinn's AsyncWrite impl doesn't change. + // return Poll::Ready(Err(SendStreamError::Write( + // err.into_inner() + // .expect("write stream returned an empty error") + // .downcast_ref::() + // .expect( + // "write stream returned an error which type is not WriteError", + // ) + // .clone(), + // ))); + // } + // } + // } + //} + //self.writing = None; + //Poll::Ready(Ok(())) + } + + fn poll_send( + &mut self, + cx: &mut task::Context<'_>, + buf: &mut D, + ) -> Poll> { + if self.writing.is_some() { + return Poll::Ready(Err(Self::Error::NotReady)); + } + + let s = Pin::new(&mut self.stream); + + let res = ready!(s.poll_write(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))) } } - self.writing = None; - Poll::Ready(Ok(())) } fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll> { diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 1bf432a2..f940c5e9 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -830,9 +830,7 @@ where .map_err(|e| self.maybe_conn_err(e))?; self.send_grease_frame = false; } - future::poll_fn(|cx| self.stream.poll_ready(cx)) - .await - .map_err(|e| self.maybe_conn_err(e))?; + future::poll_fn(|cx| self.stream.poll_finish(cx)) .await .map_err(|e| self.maybe_conn_err(e)) diff --git a/h3/src/frame.rs b/h3/src/frame.rs index 135fdd3d..f2aa7049 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -20,6 +20,7 @@ use crate::{ /// Decodes Frames from the underlying QUIC stream pub struct FrameStream { stream: S, + // Already read data from the stream bufs: BufList, decoder: FrameDecoder, remaining_data: usize, @@ -70,6 +71,10 @@ where self.remaining_data = len; Poll::Ready(Ok(Some(Frame::Data(PayloadLen(len))))) } + frame @ Some(Frame::WebTransportStream(_)) => { + self.remaining_data = usize::MAX; + Poll::Ready(Ok(frame)) + } Some(frame) => Poll::Ready(Ok(Some(frame))), None => match end { // Received a chunk but frame is incomplete, poll until we get `Pending`. @@ -89,6 +94,10 @@ where } } + /// Retrieves the next piece of data in an incoming data packet or webtransport stream + /// + /// + /// WebTransport bidirectional payload has no finite length and is processed until the end of the stream. pub fn poll_data( &mut self, cx: &mut Context<'_>, @@ -115,6 +124,7 @@ where } } + /// Stops the underlying stream with the provided error code pub(crate) fn stop_sending(&mut self, error_code: crate::error::Code) { self.stream.stop_sending(error_code.into()); } @@ -176,6 +186,14 @@ where fn send_id(&self) -> StreamId { self.stream.send_id() } + + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) + } } impl FrameStream diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 063079f9..dc3cf805 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -51,9 +51,13 @@ pub enum Frame { PushPromise(PushPromise), Goaway(VarInt), MaxPushId(PushId), - /// A webtransport frame is a frame without a length. + /// Describes the header for a webtransport stream. /// - /// The data is streaming + /// The payload is sent streaming until the stream is closed + /// + /// Unwrap the framed streamer and read the inner stream until the end. + /// + /// Conversely, when sending, send this frame and unwrap the stream WebTransportStream(SessionId), Grease, } @@ -81,7 +85,7 @@ impl Frame { // Webtransport streams need special handling as they have no length. // // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.2 - if ty == FrameType::WEBTRANSPORT_STREAM { + if ty == FrameType::WEBTRANSPORT_BI_STREAM { tracing::trace!("webtransport frame"); return Ok(Frame::WebTransportStream(SessionId::decode(buf)?)); } @@ -111,7 +115,7 @@ impl Frame { | FrameType::H2_PING | FrameType::H2_WINDOW_UPDATE | FrameType::H2_CONTINUATION => Err(FrameError::UnsupportedFrame(ty.0)), - FrameType::WEBTRANSPORT_STREAM | FrameType::DATA => unreachable!(), + FrameType::WEBTRANSPORT_BI_STREAM | FrameType::DATA => unreachable!(), _ => { buf.advance(len as usize); Err(FrameError::UnknownFrame(ty.0)) @@ -212,7 +216,7 @@ impl fmt::Debug for Frame { Frame::Goaway(id) => write!(f, "GoAway({})", id), Frame::MaxPushId(id) => write!(f, "MaxPushId({})", id), Frame::Grease => write!(f, "Grease()"), - Frame::WebTransportStream(_) => todo!(), + Frame::WebTransportStream(session) => write!(f, "WebTransportStream({session:?})"), } } } @@ -286,7 +290,7 @@ frame_types! { H2_CONTINUATION = 0x9, MAX_PUSH_ID = 0xD, // Reserved frame types - WEBTRANSPORT_STREAM = 0x41, + WEBTRANSPORT_BI_STREAM = 0x41, } impl FrameType { diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 1cba9486..1ebc1985 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -164,6 +164,19 @@ pub trait SendStream { /// Send more data on the stream. fn send_data>>(&mut self, data: T) -> Result<(), Self::Error>; + /// Attempts write data into the stream. + /// + /// Returns the number of bytes written. + /// + /// `buf` is advanced by the number of bytes written. + /// + /// This allows writing arbitrary data to the stream as well as complete encoded frames. + fn poll_send( + &mut self, + cx: &mut task::Context<'_>, + buf: &mut D, + ) -> Poll>; + /// Poll to finish the sending side of the stream. fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll>; diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 9a59bbd6..917515a2 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -19,14 +19,17 @@ use crate::{ }; #[inline] +/// Transmits data by encoding in wire format. pub(crate) async fn write(stream: &mut S, data: D) -> Result<(), Error> where S: SendStream, D: Into>, B: Buf, { - stream.send_data(data)?; - future::poll_fn(|cx| stream.poll_ready(cx)).await?; + let mut write_buf = data.into(); + while write_buf.has_remaining() { + future::poll_fn(|cx| stream.poll_send(cx, &mut write_buf)).await?; + } Ok(()) } diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 12913ae8..0d6a2895 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -1,12 +1,12 @@ use std::{marker::PhantomData, task::Poll}; use bytes::{Buf, Bytes}; -use futures_util::{ready, AsyncRead}; +use futures_util::{future, ready, AsyncRead}; use crate::{ buf::BufList, proto::varint::UnexpectedEnd, - quic::{self}, + quic::{self, SendStream as QSendStream}, }; use super::SessionId; @@ -73,19 +73,49 @@ impl SendStream { } } -impl quic::SendStream for SendStream +impl SendStream where S: quic::SendStream, B: Buf, +{ + /// Write bytes to the stream. + /// + /// Returns the number of bytes written + pub async fn write(&mut self, buf: &mut impl Buf) -> Result { + future::poll_fn(|cx| quic::SendStream::poll_send(self, cx, buf)).await + } + + /// Writes the entire buffer to the stream + pub async fn write_all(&mut self, mut buf: impl Buf) -> Result<(), S::Error> { + while buf.has_remaining() { + self.write(&mut buf).await?; + } + + Ok(()) + } +} + +impl QSendStream for SendStream +where + S: QSendStream, + B: Buf, { type Error = S::Error; fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - self.stream.poll_ready(cx) + todo!() } fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { - self.stream.send_data(data) + todo!() + } + + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) } fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { From 16eead4aa95551087cd60ac1da1e1f00fc2c99b4 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 12 Apr 2023 17:07:53 +0200 Subject: [PATCH 40/97] fix: remove generic for SendStream This makes the SendStream significantly more flexible as it is no longer constrained to sending only a single type of bytes. Both `quinn` and `s2n` implemented SendStream for *any* type which implemented `Buf`, rather than a specific type. See: https://github.com/aws/s2n-quic/blob/bf20c6dd148153802929a2514b444dcf5dd37fd1/quic/s2n-quic-h3/src/s2n_quic.rs#L358 Use of SendStream was mixed throughout the code and supplying a type for `B` was always necessary even at times where sending was not necessary, which lead to the use of `PhantomData`, such as the `RecvStream` impl for FrameStream. Often a `()` was used to indicate this. This leaked all the way up to the user facing api, where the user has to decide upfront what type of buffer they will use for sending the request, most often `Bytes` (see examples). This is unnecessary and needlessly restrictive, and that `B` is in most cases a `PhantomData` where it is used. The new system moves the specific buffer to a generic to the send function. --- examples/server.rs | 11 ++-- examples/webtransport_server.rs | 5 +- h3-quinn/src/lib.rs | 107 +++++--------------------------- h3/src/client.rs | 79 ++++++++++------------- h3/src/connection.rs | 52 ++++++++-------- h3/src/frame.rs | 46 +++++--------- h3/src/quic.rs | 24 +++---- h3/src/server.rs | 39 +++++------- h3/src/stream.rs | 14 ++--- h3/src/tests/connection.rs | 12 ++-- h3/src/tests/request.rs | 6 +- h3/src/webtransport/server.rs | 8 +-- h3/src/webtransport/stream.rs | 101 +++++++++++++++++++++++------- 13 files changed, 220 insertions(+), 284 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index efb840ab..a379c2c2 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -116,9 +116,10 @@ async fn main() -> Result<(), Box> { Ok(conn) => { info!("new connection established"); - let mut h3_conn = h3::server::Connection::new(h3_quinn::Connection::new(conn)) - .await - .unwrap(); + let mut h3_conn: h3::server::Connection<_, Bytes> = + h3::server::Connection::new(h3_quinn::Connection::new(conn)) + .await + .unwrap(); loop { match h3_conn.accept().await { @@ -165,11 +166,11 @@ async fn main() -> Result<(), Box> { async fn handle_request( req: Request<()>, - mut stream: RequestStream, + mut stream: RequestStream, serve_root: Arc>, ) -> Result<(), Box> where - T: BidiStream, + T: BidiStream, { let (status, to_serve) = match serve_root.as_deref() { None => (StatusCode::OK, None), diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index e00ce0f7..05bfe08e 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -188,8 +188,7 @@ async fn main() -> Result<(), Box> { async fn handle_connection(mut conn: Connection) -> Result<()> where C: 'static + Send + quic::Connection, - >::Error: - 'static + std::error::Error + Send + Sync, + ::Error: 'static + std::error::Error + Send + Sync, { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them @@ -244,7 +243,7 @@ async fn handle_session_and_echo_all_inbound_messages( ) -> anyhow::Result<()> where C: 'static + Send + h3::quic::Connection, - >::Error: 'static + std::error::Error + Send + Sync, + ::Error: 'static + std::error::Error + Send + Sync, B: Buf, { loop { diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index dce8bd5e..ab2d6933 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -99,9 +99,9 @@ impl quic::Connection for Connection where B: Buf, { - type SendStream = SendStream; + type SendStream = SendStream; type RecvStream = RecvStream; - type BidiStream = BidiStream; + type BidiStream = BidiStream; type OpenStreams = OpenStreams; type Error = ConnectionError; @@ -209,13 +209,10 @@ pub struct OpenStreams { opening_uni: Option, } -impl quic::OpenStreams for OpenStreams -where - B: Buf, -{ +impl quic::OpenStreams for OpenStreams { type RecvStream = RecvStream; - type SendStream = SendStream; - type BidiStream = BidiStream; + type SendStream = SendStream; + type BidiStream = BidiStream; type Error = ConnectionError; fn poll_open_bidi( @@ -267,19 +264,13 @@ impl Clone for OpenStreams { /// /// 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, +pub struct BidiStream { + send: SendStream, recv: RecvStream, } -impl quic::BidiStream for BidiStream -where - B: Buf, -{ - type SendStream = SendStream; +impl quic::BidiStream for BidiStream { + type SendStream = SendStream; type RecvStream = RecvStream; fn split(self) -> (Self::SendStream, Self::RecvStream) { @@ -287,10 +278,7 @@ where } } -impl quic::RecvStream for BidiStream -where - B: Buf, -{ +impl quic::RecvStream for BidiStream { type Buf = Bytes; type Error = ReadError; @@ -310,20 +298,9 @@ where } } -impl quic::SendStream for BidiStream -where - B: Buf, -{ +impl quic::SendStream for BidiStream { type Error = SendStreamError; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { - self.send.poll_ready(cx) - } - - fn send_data>>(&mut self, data: D) -> Result<(), Self::Error> { - self.send.send_data(data) - } - fn poll_send( &mut self, cx: &mut task::Context<'_>, @@ -432,68 +409,24 @@ impl Error for ReadError { /// Quinn-backed send stream /// /// Implements a [`quic::SendStream`] backed by a [`quinn::SendStream`]. -pub struct SendStream { +pub struct SendStream { stream: quinn::SendStream, - writing: Option>, } -impl SendStream -where - B: Buf, -{ - fn new(stream: quinn::SendStream) -> SendStream { - Self { - stream, - writing: None, - } +impl SendStream { + fn new(stream: quinn::SendStream) -> SendStream { + Self { stream } } } -impl quic::SendStream for SendStream -where - B: Buf, -{ +impl quic::SendStream for SendStream { type Error = SendStreamError; - fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll> { - unimplemented!() - //if let Some(ref mut data) = self.writing { - // while data.has_remaining() { - // match ready!(Pin::new(&mut self.stream).poll_write(cx, data.chunk())) { - // Ok(cnt) => data.advance(cnt), - // 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]) -> Write<'a, S>. - // // - // // This is why we have to unpack the error from io::Error below. This should not - // // panic as long as quinn's AsyncWrite impl doesn't change. - // return Poll::Ready(Err(SendStreamError::Write( - // err.into_inner() - // .expect("write stream returned an empty error") - // .downcast_ref::() - // .expect( - // "write stream returned an error which type is not WriteError", - // ) - // .clone(), - // ))); - // } - // } - // } - //} - //self.writing = None; - //Poll::Ready(Ok(())) - } - fn poll_send( &mut self, cx: &mut task::Context<'_>, buf: &mut D, ) -> Poll> { - if self.writing.is_some() { - return Poll::Ready(Err(Self::Error::NotReady)); - } - let s = Pin::new(&mut self.stream); let res = ready!(s.poll_write(cx, buf.chunk())); @@ -531,14 +464,6 @@ where .reset(VarInt::from_u64(reset_code).unwrap_or(VarInt::MAX)); } - 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(()) - } - fn send_id(&self) -> StreamId { self.stream.id().0.try_into().expect("invalid stream id") } diff --git a/h3/src/client.rs b/h3/src/client.rs index 9238e9d0..ef800fcd 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -29,10 +29,10 @@ pub fn builder() -> Builder { } /// Create a new HTTP/3 client with default settings -pub async fn new(conn: C) -> Result<(Connection, SendRequest), Error> +pub async fn new(conn: C) -> Result<(Connection, SendRequest), Error> where C: quic::Connection, - O: quic::OpenStreams, + O: quic::OpenStreams, { //= https://www.rfc-editor.org/rfc/rfc9114#section-3.3 //= type=implication @@ -65,7 +65,7 @@ where /// # use h3::{quic, client::*}; /// # use http::{Request, Response}; /// # use bytes::Buf; -/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> +/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> /// # where /// # T: quic::OpenStreams, /// # B: Buf, @@ -91,7 +91,7 @@ where /// # use h3::{quic, client::*}; /// # use http::{Request, Response, HeaderMap}; /// # use bytes::{Buf, Bytes}; -/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> +/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> /// # where /// # T: quic::OpenStreams, /// # { @@ -120,10 +120,9 @@ where /// [`send_request()`]: struct.SendRequest.html#method.send_request /// [`RequestStream`]: struct.RequestStream.html /// [`RequestStream::finish()`]: struct.RequestStream.html#method.finish -pub struct SendRequest +pub struct SendRequest where - T: quic::OpenStreams, - B: Buf, + T: quic::OpenStreams, { open: T, conn_state: SharedStateRef, @@ -131,20 +130,18 @@ where // counts instances of SendRequest to close the connection when the last is dropped. sender_count: Arc, conn_waker: Option, - _buf: PhantomData, send_grease_frame: bool, } -impl SendRequest +impl SendRequest where - T: quic::OpenStreams, - B: Buf, + T: quic::OpenStreams, { /// Send a HTTP/3 request to the server pub async fn send_request( &mut self, req: http::Request<()>, - ) -> Result, Error> { + ) -> Result, Error> { let (peer_max_field_section_size, closing) = { let state = self.conn_state.read("send request lock state"); (state.config.max_field_section_size, state.closing) @@ -194,7 +191,7 @@ where return Err(Error::header_too_big(mem_size, peer_max_field_section_size)); } - stream::write(&mut stream, Frame::Headers(block.freeze())) + stream::write::<_, _, Bytes>(&mut stream, Frame::Headers(block.freeze())) .await .map_err(|e| self.maybe_conn_err(e))?; @@ -212,20 +209,18 @@ where } } -impl ConnectionState for SendRequest +impl ConnectionState for SendRequest where - T: quic::OpenStreams, - B: Buf, + T: quic::OpenStreams, { fn shared_state(&self) -> &SharedStateRef { &self.conn_state } } -impl Clone for SendRequest +impl Clone for SendRequest where - T: quic::OpenStreams + Clone, - B: Buf, + T: quic::OpenStreams + Clone, { fn clone(&self) -> Self { self.sender_count @@ -237,16 +232,14 @@ where max_field_section_size: self.max_field_section_size, sender_count: self.sender_count.clone(), conn_waker: self.conn_waker.clone(), - _buf: PhantomData, send_grease_frame: self.send_grease_frame, } } } -impl Drop for SendRequest +impl Drop for SendRequest where - T: quic::OpenStreams, - B: Buf, + T: quic::OpenStreams, { fn drop(&mut self) { if self @@ -284,7 +277,7 @@ where /// # use futures_util::future; /// # use h3::{client::*, quic}; /// # use tokio::task::JoinHandle; -/// # async fn doc(mut connection: Connection) +/// # async fn doc(mut connection: Connection) /// # -> JoinHandle>> /// # where /// # C: quic::Connection + Send + 'static, @@ -307,7 +300,7 @@ where /// # use futures_util::future; /// # use h3::{client::*, quic}; /// # use tokio::{self, sync::oneshot, task::JoinHandle}; -/// # async fn doc(mut connection: Connection) +/// # async fn doc(mut connection: Connection) /// # -> Result<(), Box> /// # where /// # C: quic::Connection + Send + 'static, @@ -469,7 +462,7 @@ where /// # Examples /// ```rust /// # use h3::quic; -/// # async fn doc(quic: C) +/// # async fn doc(quic: C) /// # where /// # C: quic::Connection, /// # O: quic::OpenStreams, @@ -507,10 +500,10 @@ impl Builder { pub async fn build( &mut self, quic: C, - ) -> Result<(Connection, SendRequest), Error> + ) -> Result<(Connection, SendRequest), Error> where C: quic::Connection, - O: quic::OpenStreams, + O: quic::OpenStreams, B: Buf, { let open = quic.opener(); @@ -530,7 +523,6 @@ impl Builder { conn_waker, max_field_section_size: self.config.max_field_section_size, sender_count: Arc::new(AtomicUsize::new(1)), - _buf: PhantomData, send_grease_frame: self.config.send_grease, }, )) @@ -561,7 +553,7 @@ impl Builder { /// # use http::{Request, Response}; /// # use bytes::Buf; /// # use tokio::io::AsyncWriteExt; -/// # async fn doc(mut req_stream: RequestStream) -> Result<(), Box> +/// # async fn doc(mut req_stream: RequestStream) -> Result<(), Box> /// # where /// # T: quic::RecvStream, /// # B: Buf, @@ -590,17 +582,17 @@ impl Builder { /// [`recv_trailers()`]: #method.recv_trailers /// [`finish()`]: #method.finish /// [`stop_sending()`]: #method.stop_sending -pub struct RequestStream { - inner: connection::RequestStream, +pub struct RequestStream { + inner: connection::RequestStream, } -impl ConnectionState for RequestStream { +impl ConnectionState for RequestStream { fn shared_state(&self) -> &SharedStateRef { &self.inner.conn_state } } -impl RequestStream +impl RequestStream where S: quic::RecvStream, { @@ -691,13 +683,12 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::SendStream, - B: Buf, + S: quic::SendStream, { /// Send some data on the request body. - pub async fn send_data(&mut self, buf: B) -> Result<(), Error> { + pub async fn send_data(&mut self, buf: impl Buf) -> Result<(), Error> { self.inner.send_data(buf).await } @@ -728,18 +719,12 @@ where //# [QUIC-TRANSPORT]. } -impl RequestStream +impl RequestStream where - S: quic::BidiStream, - B: Buf, + S: quic::BidiStream, { /// Split this stream into two halves that can be driven independently. - pub fn split( - self, - ) -> ( - RequestStream, - RequestStream, - ) { + pub fn split(self) -> (RequestStream, RequestStream) { let (send, recv) = self.inner.split(); (RequestStream { inner: send }, RequestStream { inner: recv }) } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index f940c5e9..ff6f6af1 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,5 +1,6 @@ use std::{ convert::TryFrom, + marker::PhantomData, sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, task::{Context, Poll}, }; @@ -100,9 +101,9 @@ where // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging pub(super) conn: Arc>, control_send: C::SendStream, - control_recv: Option>, - decoder_recv: Option>, - encoder_recv: Option>, + control_recv: Option>, + decoder_recv: Option>, + encoder_recv: Option>, /// Stores incoming uni/recv streams which have yet to be claimed. /// /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. @@ -113,6 +114,7 @@ where got_peer_settings: bool, pub(super) send_grease_frame: bool, pub(super) config: Config, + _marker: PhantomData, } impl ConnectionInner @@ -206,7 +208,7 @@ where //# the peer prior to sending the SETTINGS frame; settings MUST be sent //# as soon as the transport is ready to send data. trace!("Sending Settings frame: {settings:#x?}"); - stream::write( + stream::write::<_, _, B>( &mut control_send, (StreamType::CONTROL, Frame::Settings(settings)), ) @@ -230,6 +232,7 @@ where send_grease_frame: config.send_grease, config, accepted_streams: Default::default(), + _marker: PhantomData, }; // start a grease stream if config.send_grease { @@ -271,7 +274,7 @@ where //# (Section 5.2) so that both endpoints can reliably determine whether //# previously sent frames have been processed and gracefully complete or //# terminate any necessary remaining tasks. - stream::write(&mut self.control_send, Frame::Goaway(max_id.into())).await + stream::write::<_, _, B>(&mut self.control_send, Frame::Goaway(max_id.into())).await } pub fn poll_accept_request( @@ -588,7 +591,9 @@ where //# types be ignored. These streams have no semantics, and they can be //# sent when application-layer padding is desired. They MAY also be //# sent on connections where no data is currently being transferred. - match stream::write(&mut grease_stream, (StreamType::grease(), Frame::Grease)).await { + match stream::write::<_, _, B>(&mut grease_stream, (StreamType::grease(), Frame::Grease)) + .await + { Ok(()) => (), Err(err) => { warn!("write data on grease stream failed with {}", err); @@ -618,17 +623,17 @@ where } } -pub struct RequestStream { - pub(super) stream: FrameStream, +pub struct RequestStream { + pub(super) stream: FrameStream, pub(super) trailers: Option, pub(super) conn_state: SharedStateRef, pub(super) max_field_section_size: u64, send_grease_frame: bool, } -impl RequestStream { +impl RequestStream { pub fn new( - stream: FrameStream, + stream: FrameStream, max_field_section_size: u64, conn_state: SharedStateRef, grease: bool, @@ -643,13 +648,13 @@ impl RequestStream { } } -impl ConnectionState for RequestStream { +impl ConnectionState for RequestStream { fn shared_state(&self) -> &SharedStateRef { &self.conn_state } } -impl RequestStream +impl RequestStream where S: quic::RecvStream, { @@ -772,13 +777,12 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::SendStream, - B: Buf, + S: quic::SendStream, { /// Send some data on the response body. - pub async fn send_data(&mut self, buf: B) -> Result<(), Error> { + pub async fn send_data(&mut self, buf: impl Buf) -> Result<(), Error> { let frame = Frame::Data(buf); stream::write(&mut self.stream, frame) @@ -810,7 +814,7 @@ where if mem_size > max_mem_size { return Err(Error::header_too_big(mem_size, max_mem_size)); } - stream::write(&mut self.stream, Frame::Headers(block.freeze())) + stream::write::<_, _, Bytes>(&mut self.stream, Frame::Headers(block.freeze())) .await .map_err(|e| self.maybe_conn_err(e))?; @@ -825,7 +829,7 @@ where pub async fn finish(&mut self) -> Result<(), Error> { if self.send_grease_frame { // send a grease frame once per Connection - stream::write(&mut self.stream, Frame::Grease) + stream::write::<_, _, Bytes>(&mut self.stream, Frame::Grease) .await .map_err(|e| self.maybe_conn_err(e))?; self.send_grease_frame = false; @@ -837,17 +841,11 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::BidiStream, - B: Buf, + S: quic::BidiStream, { - pub(crate) fn split( - self, - ) -> ( - RequestStream, - RequestStream, - ) { + pub(crate) fn split(self) -> (RequestStream, RequestStream) { let (send, recv) = self.stream.split(); ( diff --git a/h3/src/frame.rs b/h3/src/frame.rs index f2aa7049..36d7df59 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -18,7 +18,7 @@ use crate::{ }; /// Decodes Frames from the underlying QUIC stream -pub struct FrameStream { +pub struct FrameStream { stream: S, // Already read data from the stream bufs: BufList, @@ -26,10 +26,9 @@ pub struct FrameStream { remaining_data: usize, /// Set to true when `stream` reaches the end. is_eos: bool, - _phantom_buffer: PhantomData, } -impl FrameStream { +impl FrameStream { pub fn new(stream: S) -> Self { Self::with_bufs(stream, BufList::new()) } @@ -41,7 +40,6 @@ impl FrameStream { decoder: FrameDecoder::default(), remaining_data: 0, is_eos: false, - _phantom_buffer: PhantomData, } } @@ -50,7 +48,7 @@ impl FrameStream { } } -impl FrameStream +impl FrameStream where S: RecvStream, { @@ -160,20 +158,11 @@ where } } -impl SendStream for FrameStream +impl SendStream for FrameStream where - T: SendStream, - B: Buf, + T: SendStream, { - type Error = >::Error; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.stream.poll_ready(cx) - } - - fn send_data>>(&mut self, data: D) -> Result<(), Self::Error> { - self.stream.send_data(data) - } + type Error = ::Error; fn poll_finish(&mut self, cx: &mut Context<'_>) -> Poll> { self.stream.poll_finish(cx) @@ -196,12 +185,11 @@ where } } -impl FrameStream +impl FrameStream where - S: BidiStream, - B: Buf, + S: BidiStream, { - pub(crate) fn split(self) -> (FrameStream, FrameStream) { + pub(crate) fn split(self) -> (FrameStream, FrameStream) { let (send, recv) = self.stream.split(); ( FrameStream { @@ -210,7 +198,6 @@ where decoder: FrameDecoder::default(), remaining_data: 0, is_eos: false, - _phantom_buffer: PhantomData, }, FrameStream { stream: recv, @@ -218,7 +205,6 @@ where decoder: self.decoder, remaining_data: self.remaining_data, is_eos: self.is_eos, - _phantom_buffer: PhantomData, }, ) } @@ -400,7 +386,7 @@ mod tests { Frame::headers(&b"trailer"[..]).encode_with_payload(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); assert_poll_matches!(|cx| stream.poll_next(cx), Ok(Some(Frame::Headers(_)))); assert_poll_matches!( @@ -422,7 +408,7 @@ mod tests { Frame::headers(&b"header"[..]).encode_with_payload(&mut buf); let mut buf = buf.freeze(); recv.chunk(buf.split_to(buf.len() - 1)); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -441,7 +427,7 @@ mod tests { FrameType::DATA.encode(&mut buf); VarInt::from(4u32).encode(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -463,7 +449,7 @@ mod tests { let mut buf = buf.freeze(); recv.chunk(buf.split_to(buf.len() - 2)); recv.chunk(buf); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); // We get the total size of data about to be received assert_poll_matches!( @@ -492,7 +478,7 @@ mod tests { VarInt::from(4u32).encode(&mut buf); buf.put_slice(&b"b"[..]); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -524,7 +510,7 @@ mod tests { Frame::Data(Bytes::from("body")).encode_with_payload(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -546,7 +532,7 @@ mod tests { buf.put_slice(&b"bo"[..]); recv.chunk(buf.clone().freeze()); - let mut stream: FrameStream<_, ()> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(recv); assert_poll_matches!( |cx| stream.poll_next(cx), diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 1ebc1985..3a27e3c5 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -72,13 +72,13 @@ impl Error for SendDatagramError { /// Trait representing a QUIC connection. pub trait Connection { /// The type produced by `poll_accept_bidi()` - type BidiStream: BidiStream; + type BidiStream: BidiStream; /// The type of the sending part of `BidiStream` - type SendStream: SendStream; + type SendStream: SendStream; /// The type produced by `poll_accept_recv()` type RecvStream: RecvStream; /// A producer of outgoing Unidirectional and Bidirectional streams. - type OpenStreams: OpenStreams; + type OpenStreams: OpenStreams; /// Error type yielded by this trait methods type Error: Into>; @@ -127,11 +127,11 @@ pub trait Connection { } /// Trait for opening outgoing streams -pub trait OpenStreams { +pub trait OpenStreams { /// The type produced by `poll_open_bidi()` - type BidiStream: SendStream + RecvStream; + type BidiStream: SendStream + RecvStream; /// The type produced by `poll_open_send()` - type SendStream: SendStream; + type SendStream: SendStream; /// The type of the receiving part of `BidiStream` type RecvStream: RecvStream; /// Error type yielded by these trait methods @@ -154,16 +154,10 @@ pub trait OpenStreams { } /// A trait describing the "send" actions of a QUIC stream. -pub trait SendStream { +pub trait SendStream { /// The error type returned by fallible send methods. type Error: Into>; - /// Polls if the stream can send more data. - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; - - /// Send more data on the stream. - fn send_data>>(&mut self, data: T) -> Result<(), Self::Error>; - /// Attempts write data into the stream. /// /// Returns the number of bytes written. @@ -211,9 +205,9 @@ pub trait RecvStream { } /// Optional trait to allow "splitting" a bidirectional stream into two sides. -pub trait BidiStream: SendStream + RecvStream { +pub trait BidiStream: SendStream + RecvStream { /// The type for the send half. - type SendStream: SendStream; + type SendStream: SendStream; /// The type for the receive half. type RecvStream: RecvStream; diff --git a/h3/src/server.rs b/h3/src/server.rs index 31c63d4b..2b884b7d 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -172,7 +172,7 @@ where /// The [`RequestStream`] can be used to send the response. pub async fn accept( &mut self, - ) -> Result, RequestStream)>, Error> { + ) -> Result, RequestStream)>, Error> { // Accept the incoming stream let mut stream = match future::poll_fn(|cx| self.poll_accept_request(cx)).await { Ok(Some(s)) => FrameStream::new(s), @@ -212,9 +212,9 @@ where /// may still want to be handled and passed to the user. pub(crate) async fn accept_with_frame( &mut self, - mut stream: FrameStream, + mut stream: FrameStream, frame: Result>, FrameStreamError>, - ) -> Result, RequestStream)>, Error> { + ) -> Result, RequestStream)>, Error> { let mut encoded = match frame { Ok(Some(Frame::Headers(h))) => h, @@ -747,24 +747,24 @@ struct RequestEnd { /// /// The [`RequestStream`] struct is used to send and/or receive /// information from the client. -pub struct RequestStream { - inner: connection::RequestStream, +pub struct RequestStream { + inner: connection::RequestStream, request_end: Arc, } -impl AsMut> for RequestStream { - fn as_mut(&mut self) -> &mut connection::RequestStream { +impl AsMut> for RequestStream { + fn as_mut(&mut self) -> &mut connection::RequestStream { &mut self.inner } } -impl ConnectionState for RequestStream { +impl ConnectionState for RequestStream { fn shared_state(&self) -> &SharedStateRef { &self.inner.conn_state } } -impl RequestStream +impl RequestStream where S: quic::RecvStream, { @@ -789,10 +789,9 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::SendStream, - B: Buf, + S: quic::SendStream, { /// Send the HTTP/3 response /// @@ -824,7 +823,7 @@ where return Err(Error::header_too_big(mem_size, max_mem_size)); } - stream::write(&mut self.inner.stream, Frame::Headers(block.freeze())) + stream::write::<_, _, Bytes>(&mut self.inner.stream, Frame::Headers(block.freeze())) .await .map_err(|e| self.maybe_conn_err(e))?; @@ -832,7 +831,7 @@ where } /// Send some data on the response body. - pub async fn send_data(&mut self, buf: B) -> Result<(), Error> { + pub async fn send_data(&mut self, buf: Bytes) -> Result<(), Error> { self.inner.send_data(buf).await } @@ -875,19 +874,13 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::BidiStream, - B: Buf, + S: quic::BidiStream, { /// Splits the Request-Stream into send and receive. /// This can be used the send and receive data on different tasks. - pub fn split( - self, - ) -> ( - RequestStream, - RequestStream, - ) { + pub fn split(self) -> (RequestStream, RequestStream) { let (send, recv) = self.inner.split(); ( RequestStream { diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 917515a2..c4e3238d 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -8,7 +8,7 @@ use crate::{ error::{Code, ErrorLevel}, frame::FrameStream, proto::{ - coding::{BufExt, Decode as _, Encode}, + coding::{Decode as _, Encode}, frame::Frame, stream::StreamType, varint::VarInt, @@ -22,7 +22,7 @@ use crate::{ /// Transmits data by encoding in wire format. pub(crate) async fn write(stream: &mut S, data: D) -> Result<(), Error> where - S: SendStream, + S: SendStream, D: Into>, B: Buf, { @@ -162,19 +162,19 @@ where } } -pub(super) enum AcceptedRecvStream +pub(super) enum AcceptedRecvStream where S: quic::RecvStream, { - Control(FrameStream), - Push(u64, FrameStream), + Control(FrameStream), + Push(u64, FrameStream), Encoder(S), Decoder(S), WebTransportUni(SessionId, webtransport::stream::RecvStream), Reserved, } -impl AcceptedRecvStream +impl AcceptedRecvStream where S: quic::RecvStream, { @@ -211,7 +211,7 @@ where } } - pub fn into_stream(self) -> Result, Error> { + pub fn into_stream(self) -> Result, Error> { Ok(match self.ty.expect("Stream type not resolved yet") { StreamType::CONTROL => { AcceptedRecvStream::Control(FrameStream::with_bufs(self.stream, self.buf)) diff --git a/h3/src/tests/connection.rs b/h3/src/tests/connection.rs index 9d36451a..8d2f3772 100644 --- a/h3/src/tests/connection.rs +++ b/h3/src/tests/connection.rs @@ -683,11 +683,10 @@ async fn graceful_shutdown_client() { tokio::join!(server_fut, client_fut); } -async fn request(mut send_request: T) -> Result, Error> +async fn request(mut send_request: T) -> Result, Error> where - T: BorrowMut>, - O: quic::OpenStreams, - B: Buf, + T: BorrowMut>, + O: quic::OpenStreams, { let mut request_stream = send_request .borrow_mut() @@ -696,10 +695,9 @@ where request_stream.recv_response().await } -async fn response(mut stream: server::RequestStream) +async fn response(mut stream: server::RequestStream) where - S: quic::RecvStream + SendStream, - B: Buf, + S: quic::RecvStream + SendStream, { stream .send_response( diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index 3d2638d0..2d08046b 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -214,7 +214,7 @@ async fn post() { .expect("request"); request_stream - .send_data("wonderful json".into()) + .send_data("wonderful json".as_bytes()) .await .expect("send_data"); request_stream.finish().await.expect("client finish"); @@ -319,7 +319,7 @@ async fn header_too_big_response_from_server_trailers() { .await .expect("request"); request_stream - .send_data("wonderful json".into()) + .send_data("wonderful json".as_bytes()) .await .expect("send_data"); @@ -441,7 +441,7 @@ async fn header_too_big_client_error_trailer() { .await .expect("request"); request_stream - .send_data("wonderful json".into()) + .send_data("wonderful json".as_bytes()) .await .expect("send_data"); diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index aa3e6dec..ba7d0a75 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -22,7 +22,7 @@ use http::{Method, Request, Response, StatusCode}; use quic::StreamId; use super::{ - stream::{self, RecvStream}, + stream::{self}, SessionId, }; @@ -39,7 +39,7 @@ where // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: StreamId, conn: Mutex>, - connect_stream: RequestStream, + connect_stream: RequestStream, } impl WebTransportSession @@ -52,7 +52,7 @@ where /// TODO: is the API or the user responsible for validating the CONNECT request? pub async fn accept( request: Request<()>, - mut stream: RequestStream, + mut stream: RequestStream, mut conn: Connection, ) -> Result { // future::poll_fn(|cx| conn.poll_control(cx)).await?; @@ -160,7 +160,7 @@ where ) -> Result< Option<( SessionId, - stream::SendStream, + stream::SendStream, stream::RecvStream, )>, Error, diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 0d6a2895..38b71342 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -6,7 +6,7 @@ use futures_util::{future, ready, AsyncRead}; use crate::{ buf::BufList, proto::varint::UnexpectedEnd, - quic::{self, SendStream as QSendStream}, + quic::{self, RecvStream as _, SendStream as _}, }; use super::SessionId; @@ -59,24 +59,19 @@ where } /// WebTransport send stream -pub struct SendStream { +pub struct SendStream { stream: S, - _marker: PhantomData, } -impl SendStream { +impl SendStream { pub(crate) fn new(stream: S) -> Self { - Self { - stream, - _marker: PhantomData, - } + Self { stream } } } -impl SendStream +impl SendStream where - S: quic::SendStream, - B: Buf, + S: quic::SendStream, { /// Write bytes to the stream. /// @@ -95,21 +90,12 @@ where } } -impl QSendStream for SendStream +impl quic::SendStream for SendStream where - S: QSendStream, - B: Buf, + S: quic::SendStream, { type Error = S::Error; - fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - todo!() - } - - fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { - todo!() - } - fn poll_send( &mut self, cx: &mut std::task::Context<'_>, @@ -130,3 +116,74 @@ where self.stream.send_id() } } + +/// A biderectional webtransport stream. +/// +/// May be split into a sender and a receiver part +pub struct BidiStream { + send: SendStream, + recv: RecvStream, +} + +impl quic::BidiStream for BidiStream +where + S: quic::SendStream + quic::RecvStream, +{ + type SendStream = SendStream; + type RecvStream = RecvStream; + + fn split(self) -> (Self::SendStream, Self::RecvStream) { + (self.send, self.recv) + } +} + +impl quic::SendStream for BidiStream +where + S: quic::SendStream, +{ + type Error = S::Error; + + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.send.poll_send(cx, buf) + } + + fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + self.send.poll_finish(cx) + } + + fn reset(&mut self, reset_code: u64) { + self.send.reset(reset_code) + } + + fn send_id(&self) -> quic::StreamId { + self.send.send_id() + } +} + +impl quic::RecvStream for BidiStream +where + S: quic::RecvStream, +{ + type Buf = Bytes; + + type Error = S::Error; + + fn poll_data( + &mut self, + cx: &mut std::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) -> quic::StreamId { + self.recv.recv_id() + } +} From e07286cb59b83408d75b3fc6090e409caeb6bca4 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Thu, 13 Apr 2023 00:55:31 -0400 Subject: [PATCH 41/97] remove print statements from poll accept request and add keep alive interval to quinn config so that webtransport connection does not close --- examples/webtransport_server.rs | 5 ++++- h3/src/server.rs | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 05bfe08e..c93e0732 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -126,7 +126,10 @@ async fn main() -> Result<(), Box> { ]; tls_config.alpn_protocols = alpn; - let server_config = quinn::ServerConfig::with_crypto(Arc::new(tls_config)); + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(tls_config)); + let mut transport_config = quinn::TransportConfig::default(); + transport_config.keep_alive_interval(Some(Duration::from_secs(2))); + server_config.transport = Arc::new(transport_config); let (endpoint, mut incoming) = quinn::Endpoint::server(server_config, opt.listen)?; info!("listening on {}", opt.listen); diff --git a/h3/src/server.rs b/h3/src/server.rs index 2b884b7d..ac1bb769 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -425,11 +425,8 @@ where &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { - info!("poll_accept_request"); let _ = self.poll_control(cx)?; - info!("poll_accept_request: poll_control done"); let _ = self.poll_requests_completion(cx); - info!("poll_accept_request: poll_requests_completion done"); loop { match self.inner.poll_accept_request(cx) { Poll::Ready(Err(x)) => break Poll::Ready(Err(x)), From 92ca2a797811cfba82e22be6d812be13b09c9c2d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 13 Apr 2023 11:10:28 +0200 Subject: [PATCH 42/97] feat: make Connection agnostic to underlying send buffer implementation This also removes the "phantom" generic on many types which is often `Bytes` for an internal buffer type on the send streams --- examples/server.rs | 7 ++--- examples/webtransport_server.rs | 22 +++---------- h3-quinn/src/lib.rs | 7 ++--- h3/src/client.rs | 25 ++++++--------- h3/src/connection.rs | 56 ++++++++++++++++++++------------- h3/src/quic.rs | 2 +- h3/src/server.rs | 52 +++++++++++++----------------- h3/src/tests/connection.rs | 4 +-- h3/src/tests/mod.rs | 2 +- h3/src/tests/request.rs | 4 +-- h3/src/webtransport/server.rs | 42 ++++++++++--------------- 11 files changed, 97 insertions(+), 126 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 09bb7f37..955bbda9 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -115,10 +115,9 @@ async fn main() -> Result<(), Box> { Ok(conn) => { info!("new connection established"); - let mut h3_conn: h3::server::Connection<_, Bytes> = - h3::server::Connection::new(h3_quinn::Connection::new(conn)) - .await - .unwrap(); + let mut h3_conn = h3::server::Connection::new(h3_quinn::Connection::new(conn)) + .await + .unwrap(); loop { match h3_conn.accept().await { diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index d70d17d8..e80e9f54 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -89,17 +89,6 @@ async fn main() -> Result<(), Box> { let opt = Opt::from_args(); tracing::info!("Opt: {opt:#?}"); - let root = if let Some(root) = opt.root { - if !root.is_dir() { - return Err(format!("{}: is not a readable directory", root.display()).into()); - } else { - info!("serving {}", root.display()); - Arc::new(Some(root)) - } - } else { - Arc::new(None) - }; - let Certs { cert, key } = opt.certs; // create quinn server endpoint and bind UDP socket @@ -188,9 +177,9 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_connection(mut conn: Connection) -> Result<()> +async fn handle_connection(mut conn: Connection) -> Result<()> where - C: 'static + Send + quic::Connection, + C: 'static + Send + quic::Connection, ::Error: 'static + std::error::Error + Send + Sync, { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. @@ -241,13 +230,12 @@ where /// This method will echo all inbound datagrams, unidirectional and bidirectional streams. #[tracing::instrument(level = "info", skip(session))] -async fn handle_session_and_echo_all_inbound_messages( - session: WebTransportSession, +async fn handle_session_and_echo_all_inbound_messages( + session: WebTransportSession, ) -> anyhow::Result<()> where - C: 'static + Send + h3::quic::Connection, + C: 'static + Send + h3::quic::Connection, ::Error: 'static + std::error::Error + Send + Sync, - B: Buf, { loop { tokio::select! { diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 880119da..c0427af8 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -24,7 +24,7 @@ pub use quinn::{ }; use quinn::{ReadDatagram, SendDatagramError}; -use h3::quic::{self, Error, StreamId, WriteBuf}; +use h3::quic::{self, Error, StreamId}; use tokio_util::sync::ReusableBoxFuture; /// A QUIC connection backed by Quinn @@ -95,10 +95,7 @@ impl From for ConnectionError { } } -impl quic::Connection for Connection -where - B: Buf, -{ +impl quic::Connection for Connection { type SendStream = SendStream; type RecvStream = RecvStream; type BidiStream = BidiStream; diff --git a/h3/src/client.rs b/h3/src/client.rs index ef800fcd..5f3c457d 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -2,7 +2,6 @@ use std::{ convert::TryFrom, - marker::PhantomData, sync::{atomic::AtomicUsize, Arc}, task::{Context, Poll, Waker}, }; @@ -29,9 +28,9 @@ pub fn builder() -> Builder { } /// Create a new HTTP/3 client with default settings -pub async fn new(conn: C) -> Result<(Connection, SendRequest), Error> +pub async fn new(conn: C) -> Result<(Connection, SendRequest), Error> where - C: quic::Connection, + C: quic::Connection, O: quic::OpenStreams, { //= https://www.rfc-editor.org/rfc/rfc9114#section-3.3 @@ -338,22 +337,20 @@ where /// ``` /// [`poll_close()`]: struct.Connection.html#method.poll_close /// [`shutdown()`]: struct.Connection.html#method.shutdown -pub struct Connection +pub struct Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { - inner: ConnectionInner, + inner: ConnectionInner, // Has a GOAWAY frame been sent? If so, this PushId is the last we are willing to accept. sent_closing: Option, // Has a GOAWAY frame been received? If so, this is StreamId the last the remote will accept. recv_closing: Option, } -impl Connection +impl Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { /// Initiate a graceful shutdown, accepting `max_push` potentially in-flight server pushes pub async fn shutdown(&mut self, _max_push: usize) -> Result<(), Error> { @@ -497,14 +494,10 @@ impl Builder { } /// Create a new HTTP/3 client from a `quic` connection - pub async fn build( - &mut self, - quic: C, - ) -> Result<(Connection, SendRequest), Error> + pub async fn build(&mut self, quic: C) -> Result<(Connection, SendRequest), Error> where - C: quic::Connection, + C: quic::Connection, O: quic::OpenStreams, - B: Buf, { let open = quic.opener(); let conn_state = SharedStateRef::default(); diff --git a/h3/src/connection.rs b/h3/src/connection.rs index ff6f6af1..f85a4113 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,6 +1,5 @@ use std::{ convert::TryFrom, - marker::PhantomData, sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, task::{Context, Poll}, }; @@ -72,18 +71,16 @@ pub trait ConnectionState { } } -pub(crate) struct AcceptedStreams +pub(crate) struct AcceptedStreams where - C: quic::Connection, - B: Buf, + C: quic::Connection, { pub uni_streams: Vec<(SessionId, webtransport::stream::RecvStream)>, } -impl Default for AcceptedStreams +impl Default for AcceptedStreams where - C: quic::Connection, - B: bytes::Buf, + C: quic::Connection, { fn default() -> Self { Self { @@ -92,10 +89,9 @@ where } } -pub struct ConnectionInner +pub struct ConnectionInner where - C: quic::Connection, - B: Buf, + C: quic::Connection, { pub(super) shared: SharedStateRef, // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging @@ -104,23 +100,37 @@ where control_recv: Option>, decoder_recv: Option>, encoder_recv: Option>, - /// Stores incoming uni/recv streams which have yet to be claimed. + /// Buffers incoming uni/recv streams which have yet to be claimed. /// /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. - accepted_streams: AcceptedStreams, + /// + /// See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.5 + /// + /// In WebTransport over HTTP/3, the client MAY send its SETTINGS frame, as well as + /// multiple WebTransport CONNECT requests, WebTransport data streams and WebTransport + /// datagrams, all within a single flight. As those can arrive out of order, a WebTransport + /// server could be put into a situation where it receives a stream or a datagram without a + /// corresponding session. Similarly, a client may receive a server-initiated stream or a + /// datagram before receiving the CONNECT response headers from the server.To handle this + /// case, WebTransport endpoints SHOULD buffer streams and datagrams until those can be + /// associated with an established session. To avoid resource exhaustion, the endpoints + /// MUST limit the number of buffered streams and datagrams. When the number of buffered + /// streams is exceeded, a stream SHALL be closed by sending a RESET_STREAM and/or + /// STOP_SENDING with the H3_WEBTRANSPORT_BUFFERED_STREAM_REJECTED error code. When the + /// number of buffered datagrams is exceeded, a datagram SHALL be dropped. It is up to an + /// implementation to choose what stream or datagram to discard. + accepted_streams: AcceptedStreams, pending_recv_streams: Vec>, got_peer_settings: bool, pub(super) send_grease_frame: bool, pub(super) config: Config, - _marker: PhantomData, } -impl ConnectionInner +impl ConnectionInner where - C: quic::Connection, - B: Buf, + C: quic::Connection, { /// Initiates the connection and opens a control stream pub async fn new(mut conn: C, shared: SharedStateRef, config: Config) -> Result { @@ -208,7 +218,7 @@ where //# the peer prior to sending the SETTINGS frame; settings MUST be sent //# as soon as the transport is ready to send data. trace!("Sending Settings frame: {settings:#x?}"); - stream::write::<_, _, B>( + stream::write::<_, _, Bytes>( &mut control_send, (StreamType::CONTROL, Frame::Settings(settings)), ) @@ -232,7 +242,6 @@ where send_grease_frame: config.send_grease, config, accepted_streams: Default::default(), - _marker: PhantomData, }; // start a grease stream if config.send_grease { @@ -274,7 +283,7 @@ where //# (Section 5.2) so that both endpoints can reliably determine whether //# previously sent frames have been processed and gracefully complete or //# terminate any necessary remaining tasks. - stream::write::<_, _, B>(&mut self.control_send, Frame::Goaway(max_id.into())).await + stream::write::<_, _, Bytes>(&mut self.control_send, Frame::Goaway(max_id.into())).await } pub fn poll_accept_request( @@ -591,8 +600,11 @@ where //# types be ignored. These streams have no semantics, and they can be //# sent when application-layer padding is desired. They MAY also be //# sent on connections where no data is currently being transferred. - match stream::write::<_, _, B>(&mut grease_stream, (StreamType::grease(), Frame::Grease)) - .await + match stream::write::<_, _, Bytes>( + &mut grease_stream, + (StreamType::grease(), Frame::Grease), + ) + .await { Ok(()) => (), Err(err) => { @@ -618,7 +630,7 @@ where }; } - pub(crate) fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { + pub(crate) fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { &mut self.accepted_streams } } diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 3a27e3c5..013f9633 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -70,7 +70,7 @@ impl Error for SendDatagramError { } /// Trait representing a QUIC connection. -pub trait Connection { +pub trait Connection { /// The type produced by `poll_accept_bidi()` type BidiStream: BidiStream; /// The type of the sending part of `BidiStream` diff --git a/h3/src/server.rs b/h3/src/server.rs index ac1bb769..8472f31e 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -8,8 +8,8 @@ //! ```rust //! async fn doc(conn: C) //! where -//! C: h3::quic::Connection, -//! >::BidiStream: Send + 'static +//! C: h3::quic::Connection, +//! ::BidiStream: Send + 'static //! { //! let mut server_builder = h3::server::builder(); //! // Build the Connection @@ -104,13 +104,12 @@ pub fn builder() -> Builder { /// Create a new Instance with [`Connection::new()`]. /// Accept incoming requests with [`Connection::accept()`]. /// And shutdown a connection with [`Connection::shutdown()`]. -pub struct Connection +pub struct Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { /// TODO: temporarily break encapsulation for `WebTransportSession` - pub(crate) inner: ConnectionInner, + pub(crate) inner: ConnectionInner, max_field_section_size: u64, // List of all incoming streams that are currently running. ongoing_streams: HashSet, @@ -125,20 +124,18 @@ where last_accepted_stream: Option, } -impl ConnectionState for Connection +impl ConnectionState for Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { fn shared_state(&self) -> &SharedStateRef { &self.inner.shared } } -impl Connection +impl Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { /// Create a new HTTP/3 server connection with default settings /// @@ -160,10 +157,9 @@ where } } -impl Connection +impl Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { /// Accept an incoming request. /// @@ -384,10 +380,9 @@ where } /// Reads an incoming datagram - pub fn read_datagram(&self) -> ReadDatagram { + pub fn read_datagram(&self) -> ReadDatagram { ReadDatagram { conn: &self.inner.conn, - _marker: PhantomData, } } @@ -518,7 +513,7 @@ where fn poll_accept_uni( &self, cx: &mut Context<'_>, - ) -> Poll>::RecvStream>, Error>> { + ) -> Poll::RecvStream>, Error>> { todo!() // let recv = ready!(self.inner.poll_accept_recv(cx))?; @@ -548,10 +543,9 @@ where } } -impl Drop for Connection +impl Drop for Connection where - C: quic::Connection, - B: Buf, + C: quic::Connection, { fn drop(&mut self) { self.inner.close(Code::H3_NO_ERROR, ""); @@ -716,10 +710,9 @@ impl Builder { /// Build an HTTP/3 connection from a QUIC connection /// /// This method creates a [`Connection`] instance with the settings in the [`Builder`]. - pub async fn build(&self, conn: C) -> Result, Error> + pub async fn build(&self, conn: C) -> Result, Error> where - C: quic::Connection, - B: Buf, + C: quic::Connection, { let (sender, receiver) = mpsc::unbounded_channel(); Ok(Connection { @@ -904,19 +897,16 @@ impl Drop for RequestEnd { } /// Future for [`Connection::read_datagram`] -pub struct ReadDatagram<'a, C, B> +pub struct ReadDatagram<'a, C> where - C: quic::Connection, - B: Buf, + C: quic::Connection, { conn: &'a std::sync::Mutex, - _marker: PhantomData, } -impl<'a, C, B> Future for ReadDatagram<'a, C, B> +impl<'a, C> Future for ReadDatagram<'a, C> where - C: quic::Connection, - B: Buf, + C: quic::Connection, { type Output = Result, Error>; diff --git a/h3/src/tests/connection.rs b/h3/src/tests/connection.rs index 0d6dde41..d8816a09 100644 --- a/h3/src/tests/connection.rs +++ b/h3/src/tests/connection.rs @@ -4,7 +4,7 @@ use std::{borrow::BorrowMut, time::Duration}; use assert_matches::assert_matches; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use futures_util::future; use http::{Request, Response, StatusCode}; @@ -184,7 +184,7 @@ async fn settings_exchange_server() { let client_fut = async { let (mut conn, _client) = client::builder() .max_field_section_size(12) - .build::<_, _, Bytes>(pair.client().await) + .build(pair.client().await) .await .expect("client init"); let drive = async move { diff --git a/h3/src/tests/mod.rs b/h3/src/tests/mod.rs index 655ab16a..f3533cd3 100644 --- a/h3/src/tests/mod.rs +++ b/h3/src/tests/mod.rs @@ -130,7 +130,7 @@ pub struct Server { } impl Server { - pub async fn next(&mut self) -> impl quic::Connection { + pub async fn next(&mut self) -> impl quic::Connection { Connection::new(self.endpoint.accept().await.unwrap().await.unwrap()) } } diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index 8936cad0..91e3c0fc 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -508,7 +508,7 @@ async fn header_too_big_discard_from_client() { // Do not poll driver so client doesn't know about server's max_field section size setting let (_conn, mut client) = client::builder() .max_field_section_size(12) - .build::<_, _, Bytes>(pair.client().await) + .build(pair.client().await) .await .expect("client init"); let mut request_stream = client @@ -594,7 +594,7 @@ async fn header_too_big_discard_from_client_trailers() { // Do not poll driver so client doesn't know about server's max_field section size setting let (mut driver, mut client) = client::builder() .max_field_section_size(200) - .build::<_, _, Bytes>(pair.client().await) + .build(pair.client().await) .await .expect("client init"); let drive_fut = async { future::poll_fn(|cx| driver.poll_close(cx)).await }; diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index ba7d0a75..a436c970 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -31,21 +31,19 @@ use super::{ /// Maintains the session using the underlying HTTP/3 connection. /// /// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. -pub struct WebTransportSession +pub struct WebTransportSession where - C: quic::Connection, - B: Buf, + C: quic::Connection, { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: StreamId, - conn: Mutex>, + conn: Mutex>, connect_stream: RequestStream, } -impl WebTransportSession +impl WebTransportSession where - C: quic::Connection, - B: Buf, + C: quic::Connection, { /// Accepts a *CONNECT* request for establishing a WebTransport session. /// @@ -53,7 +51,7 @@ where pub async fn accept( request: Request<()>, mut stream: RequestStream, - mut conn: Connection, + mut conn: Connection, ) -> Result { // future::poll_fn(|cx| conn.poll_control(cx)).await?; @@ -125,10 +123,9 @@ where } /// Receive a datagram from the client - pub fn read_datagram(&self) -> ReadDatagram { + pub fn read_datagram(&self) -> ReadDatagram { ReadDatagram { conn: self.conn.lock().unwrap().inner.conn.clone(), - _marker: PhantomData, } } @@ -145,7 +142,7 @@ where } /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. - pub fn accept_uni(&self) -> AcceptUni { + pub fn accept_uni(&self) -> AcceptUni { AcceptUni { conn: &self.conn } } @@ -260,19 +257,16 @@ where } /// Future for [`Connection::read_datagram`] -pub struct ReadDatagram +pub struct ReadDatagram where - C: quic::Connection, - B: Buf, + C: quic::Connection, { conn: Arc>, - _marker: PhantomData, } -impl Future for ReadDatagram +impl Future for ReadDatagram where - C: quic::Connection, - B: Buf, + C: quic::Connection, { type Output = Result, Error>; @@ -292,18 +286,16 @@ where } /// Future for [`WebTransportSession::accept_uni`] -pub struct AcceptUni<'a, C, B> +pub struct AcceptUni<'a, C> where - C: quic::Connection, - B: Buf, + C: quic::Connection, { - conn: &'a Mutex>, + conn: &'a Mutex>, } -impl<'a, C, B> Future for AcceptUni<'a, C, B> +impl<'a, C> Future for AcceptUni<'a, C> where - C: quic::Connection, - B: Buf, + C: quic::Connection, { type Output = Result)>, Error>; From 0c42603fa2fe50623a586919af42e2afd6b2f296 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 13 Apr 2023 17:02:32 +0200 Subject: [PATCH 43/97] feat: generalize peeked reading of streams Introduced `BufRecvStream` which holds onto the already read data. This prevents data loss when streams are transitioned from one type to another, such as when reading the type and then constructing a decoded. This was previously done manually through `AcceptedRecvStream::into_stream` => `FrameStream::with_buf`, but was error prone as the buffer could easily be forgotten when deconstructing the frame stream later. --- h3/src/buf.rs | 1 + h3/src/client.rs | 6 +- h3/src/connection.rs | 2 +- h3/src/frame.rs | 64 +++++--------- h3/src/lib.rs | 1 + h3/src/server.rs | 4 +- h3/src/stream.rs | 190 ++++++++++++++++++++++++++++++++++------ h3/src/tests/request.rs | 1 + 8 files changed, 198 insertions(+), 71 deletions(-) diff --git a/h3/src/buf.rs b/h3/src/buf.rs index 819c3bd0..c6c5617e 100644 --- a/h3/src/buf.rs +++ b/h3/src/buf.rs @@ -42,6 +42,7 @@ impl BufList { .bufs .front_mut() .map(|chunk| chunk.split_to(usize::min(max_len, chunk.remaining()))); + if let Some(front) = self.bufs.front() { if front.remaining() == 0 { let _ = self.bufs.pop_front(); diff --git a/h3/src/client.rs b/h3/src/client.rs index 5f3c457d..0b4089e8 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -19,7 +19,7 @@ use crate::{ qpack, quic::{self, StreamId}, server::Config, - stream, + stream::{self, BufRecvStream}, }; /// Start building a new HTTP/3 client @@ -196,7 +196,7 @@ where let request_stream = RequestStream { inner: connection::RequestStream::new( - FrameStream::new(stream), + FrameStream::new(BufRecvStream::new(stream)), self.max_field_section_size, self.conn_state.clone(), self.send_grease_frame, @@ -246,7 +246,7 @@ where .fetch_sub(1, std::sync::atomic::Ordering::AcqRel) == 1 { - if let Some(w) = self.conn_waker.take() { + if let Some(w) = Option::take(&mut self.conn_waker) { w.wake() } self.shared_state().write("SendRequest drop").error = Some(Error::closed()); diff --git a/h3/src/connection.rs b/h3/src/connection.rs index f85a4113..de3b9282 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -21,7 +21,7 @@ use crate::{ qpack, quic::{self, SendStream as _}, server::Config, - stream::{self, AcceptRecvStream, AcceptedRecvStream}, + stream::{self, AcceptRecvStream, AcceptedRecvStream, BufRecvStream}, webtransport::{self, SessionId}, }; diff --git a/h3/src/frame.rs b/h3/src/frame.rs index 36d7df59..560b8962 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -6,6 +6,7 @@ use bytes::{Buf, Bytes}; use futures_util::ready; use tracing::trace; +use crate::stream::BufRecvStream; use crate::{ buf::BufList, error::TransportError, @@ -19,32 +20,25 @@ use crate::{ /// Decodes Frames from the underlying QUIC stream pub struct FrameStream { - stream: S, + stream: BufRecvStream, // Already read data from the stream - bufs: BufList, decoder: FrameDecoder, remaining_data: usize, - /// Set to true when `stream` reaches the end. - is_eos: bool, } impl FrameStream { - pub fn new(stream: S) -> Self { - Self::with_bufs(stream, BufList::new()) - } - - pub(crate) fn with_bufs(stream: S, bufs: BufList) -> Self { + pub(crate) fn new(stream: BufRecvStream) -> Self { Self { stream, - bufs, decoder: FrameDecoder::default(), remaining_data: 0, - is_eos: false, } } - pub(crate) fn into_inner(self) -> (S, BufList) { - (self.stream, self.bufs) + /// Unwraps the Framed streamer and returns the underlying stream **without** data loss for + /// partially received/read frames. + pub(crate) fn into_inner(self) -> BufRecvStream { + self.stream } } @@ -64,7 +58,7 @@ where loop { let end = self.try_recv(cx)?; - return match self.decoder.decode(&mut self.bufs)? { + return match self.decoder.decode(&mut self.stream.buf_mut())? { Some(Frame::Data(PayloadLen(len))) => { self.remaining_data = len; Poll::Ready(Ok(Some(Frame::Data(PayloadLen(len))))) @@ -79,7 +73,7 @@ where Poll::Ready(false) => continue, Poll::Pending => Poll::Pending, Poll::Ready(true) => { - if self.bufs.has_remaining() { + if self.stream.buf_mut().has_remaining() { // Reached the end of receive stream, but there is still some data: // The frame is incomplete. Poll::Ready(Err(FrameStreamError::UnexpectedEnd)) @@ -105,13 +99,14 @@ where }; let end = ready!(self.try_recv(cx))?; - let data = self.bufs.take_chunk(self.remaining_data); + let data = self.stream.buf_mut().take_chunk(self.remaining_data); match (data, end) { (None, true) => Poll::Ready(Ok(None)), (None, false) => Poll::Pending, (Some(d), true) - if d.remaining() < self.remaining_data && !self.bufs.has_remaining() => + if d.remaining() < self.remaining_data + && !self.stream.buf_mut().has_remaining() => { Poll::Ready(Err(FrameStreamError::UnexpectedEnd)) } @@ -132,24 +127,17 @@ where } pub(crate) fn is_eos(&self) -> bool { - self.is_eos && !self.bufs.has_remaining() + self.stream.is_eos() && !self.stream.buf().has_remaining() } fn try_recv(&mut self, cx: &mut Context<'_>) -> Poll> { - if self.is_eos { + if self.stream.is_eos() { return Poll::Ready(Ok(true)); } - match self.stream.poll_data(cx) { + match self.stream.poll_read(cx) { Poll::Ready(Err(e)) => Poll::Ready(Err(FrameStreamError::Quic(e.into()))), Poll::Pending => Poll::Pending, - Poll::Ready(Ok(None)) => { - self.is_eos = true; - Poll::Ready(Ok(true)) - } - Poll::Ready(Ok(Some(mut d))) => { - self.bufs.push_bytes(&mut d); - Poll::Ready(Ok(false)) - } + Poll::Ready(Ok(eos)) => Poll::Ready(Ok(eos)), } } @@ -194,17 +182,13 @@ where ( FrameStream { stream: send, - bufs: BufList::new(), decoder: FrameDecoder::default(), remaining_data: 0, - is_eos: false, }, FrameStream { stream: recv, - bufs: self.bufs, decoder: self.decoder, remaining_data: self.remaining_data, - is_eos: self.is_eos, }, ) } @@ -386,7 +370,7 @@ mod tests { Frame::headers(&b"trailer"[..]).encode_with_payload(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!(|cx| stream.poll_next(cx), Ok(Some(Frame::Headers(_)))); assert_poll_matches!( @@ -408,7 +392,7 @@ mod tests { Frame::headers(&b"header"[..]).encode_with_payload(&mut buf); let mut buf = buf.freeze(); recv.chunk(buf.split_to(buf.len() - 1)); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -427,7 +411,7 @@ mod tests { FrameType::DATA.encode(&mut buf); VarInt::from(4u32).encode(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -449,7 +433,7 @@ mod tests { let mut buf = buf.freeze(); recv.chunk(buf.split_to(buf.len() - 2)); recv.chunk(buf); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); // We get the total size of data about to be received assert_poll_matches!( @@ -478,7 +462,7 @@ mod tests { VarInt::from(4u32).encode(&mut buf); buf.put_slice(&b"b"[..]); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -510,7 +494,7 @@ mod tests { Frame::Data(Bytes::from("body")).encode_with_payload(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -532,7 +516,7 @@ mod tests { buf.put_slice(&b"bo"[..]); recv.chunk(buf.clone().freeze()); - let mut stream: FrameStream<_> = FrameStream::new(recv); + let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -541,7 +525,7 @@ mod tests { buf.truncate(0); buf.put_slice(&b"dy"[..]); - stream.bufs.push_bytes(&mut buf.freeze()); + stream.stream.buf_mut().push_bytes(&mut buf.freeze()); assert_poll_matches!( |cx| to_bytes(stream.poll_data(cx)), diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 292ba7c1..6dfbd2a1 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -5,6 +5,7 @@ pub mod client; pub mod error; pub mod quic; +pub(crate) mod request; pub mod server; pub mod webtransport; diff --git a/h3/src/server.rs b/h3/src/server.rs index 8472f31e..1bfca7e3 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -85,7 +85,7 @@ use crate::{ }, qpack, quic::{self, SendStream as _}, - stream, + stream::{self, BufRecvStream}, }; use tracing::{error, info, trace, warn}; @@ -171,7 +171,7 @@ where ) -> Result, RequestStream)>, Error> { // Accept the incoming stream let mut stream = match future::poll_fn(|cx| self.poll_accept_request(cx)).await { - Ok(Some(s)) => FrameStream::new(s), + Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), Ok(None) => { // We always send a last GoAway frame to the client, so it knows which was the last // non-rejected request. diff --git a/h3/src/stream.rs b/h3/src/stream.rs index c4e3238d..74694dcb 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -13,7 +13,7 @@ use crate::{ stream::StreamType, varint::VarInt, }, - quic::{self, RecvStream, SendStream}, + quic::{self, BidiStream, RecvStream, SendStream}, webtransport::{self, SessionId}, Error, }; @@ -168,8 +168,8 @@ where { Control(FrameStream), Push(u64, FrameStream), - Encoder(S), - Decoder(S), + Encoder(BufRecvStream), + Decoder(BufRecvStream), WebTransportUni(SessionId, webtransport::stream::RecvStream), Reserved, } @@ -189,11 +189,10 @@ where /// Resolves an incoming streams type as well as `PUSH_ID`s and `SESSION_ID`s pub(super) struct AcceptRecvStream { - stream: S, + stream: BufRecvStream, ty: Option, /// push_id or session_id id: Option, - buf: BufList, expected: Option, } @@ -203,28 +202,25 @@ where { pub fn new(stream: S) -> Self { Self { - stream, + stream: BufRecvStream::new(stream), ty: None, id: None, - buf: BufList::new(), expected: None, } } pub fn into_stream(self) -> Result, Error> { Ok(match self.ty.expect("Stream type not resolved yet") { - StreamType::CONTROL => { - AcceptedRecvStream::Control(FrameStream::with_bufs(self.stream, self.buf)) - } + StreamType::CONTROL => AcceptedRecvStream::Control(FrameStream::new(self.stream)), StreamType::PUSH => AcceptedRecvStream::Push( self.id.expect("Push ID not resolved yet").into_inner(), - FrameStream::with_bufs(self.stream, self.buf), + FrameStream::new(self.stream), ), StreamType::ENCODER => AcceptedRecvStream::Encoder(self.stream), StreamType::DECODER => AcceptedRecvStream::Decoder(self.stream), StreamType::WEBTRANSPORT_UNI => AcceptedRecvStream::WebTransportUni( SessionId::from_varint(self.id.expect("Session ID not resolved yet")), - webtransport::stream::RecvStream::new(self.buf, self.stream), + webtransport::stream::RecvStream::new(self.stream), ), t if t.value() > 0x21 && (t.value() - 0x21) % 0x1f == 0 => AcceptedRecvStream::Reserved, @@ -264,22 +260,21 @@ where None => (), }; - match ready!(self.stream.poll_data(cx))? { - Some(mut b) => self.buf.push_bytes(&mut b), - None => { - return Poll::Ready(Err(Code::H3_STREAM_CREATION_ERROR.with_reason( - "Stream closed before type received", - ErrorLevel::ConnectionError, - ))); - } + if ready!(self.stream.poll_read(cx))? { + return Poll::Ready(Err(Code::H3_STREAM_CREATION_ERROR.with_reason( + "Stream closed before type received", + ErrorLevel::ConnectionError, + ))); }; - if self.expected.is_none() && self.buf.remaining() >= 1 { - self.expected = Some(VarInt::encoded_size(self.buf.chunk()[0])); + let mut buf = self.stream.buf_mut(); + if self.expected.is_none() && buf.remaining() >= 1 { + self.expected = Some(VarInt::encoded_size(buf.chunk()[0])); } if let Some(expected) = self.expected { - if self.buf.remaining() < expected { + // Poll for more data + if buf.remaining() < expected { continue; } } else { @@ -289,7 +284,7 @@ where // Parse ty and then id if self.ty.is_none() { // Parse StreamType - self.ty = Some(StreamType::decode(&mut self.buf).map_err(|_| { + self.ty = Some(StreamType::decode(&mut buf).map_err(|_| { Code::H3_INTERNAL_ERROR.with_reason( "Unexpected end parsing stream type", ErrorLevel::ConnectionError, @@ -299,7 +294,7 @@ where self.expected = None; } else { // Parse PUSH_ID - self.id = Some(VarInt::decode(&mut self.buf).map_err(|_| { + self.id = Some(VarInt::decode(&mut buf).map_err(|_| { Code::H3_INTERNAL_ERROR.with_reason( "Unexpected end parsing push or session id", ErrorLevel::ConnectionError, @@ -310,6 +305,151 @@ where } } +/// A stream which allows partial reading of the data without data loss. +/// +/// This fixes the problem where `poll_data` returns more than the needed amount of bytes, +/// requiring correct implementations to hold on to that extra data and return it later. +/// +/// # Usage +/// +/// Implements `quic::RecvStream` which will first return buffered data, and then read from the +/// stream +pub(crate) struct BufRecvStream { + buf: BufList, + /// Indicates that the end of the stream has been reached + /// + /// Data may still be available as buffered + eos: bool, + stream: S, +} + +impl BufRecvStream { + pub(crate) fn new(stream: S) -> Self { + Self { + buf: BufList::new(), + eos: false, + stream, + } + } + + pub(crate) fn with_bufs(stream: S, bufs: BufList) -> Self { + Self { + buf: bufs, + eos: false, + stream, + } + } + + /// Reads more data into the buffer, returning the number of bytes read. + /// + /// Returns `true` if the end of the stream is reached. + pub fn poll_read(&mut self, cx: &mut Context<'_>) -> Poll> { + let data = ready!(self.stream.poll_data(cx))?; + + if let Some(mut data) = data { + self.buf.push_bytes(&mut data); + Poll::Ready(Ok(false)) + } else { + self.eos = true; + Poll::Ready(Ok(true)) + } + } + + /// Returns the currently buffered data, allowing it to be partially read + #[inline] + pub fn buf_mut(&mut self) -> &mut BufList { + &mut self.buf + } + + #[inline] + pub(crate) fn buf(&self) -> &BufList { + &self.buf + } + + pub fn is_eos(&self) -> bool { + self.eos + } +} + +impl RecvStream for BufRecvStream { + type Buf = Bytes; + + type Error = S::Error; + + fn poll_data( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>> { + // There is data buffered, return that immediately + if let Some(chunk) = self.buf.take_first_chunk() { + return Poll::Ready(Ok(Some(chunk))); + } + + if let Some(mut data) = ready!(self.stream.poll_data(cx))? { + Poll::Ready(Ok(Some(data.copy_to_bytes(data.remaining())))) + } else { + self.eos = true; + Poll::Ready(Ok(None)) + } + } + + fn stop_sending(&mut self, error_code: u64) { + self.stream.stop_sending(error_code) + } + + fn recv_id(&self) -> quic::StreamId { + self.stream.recv_id() + } +} + +impl SendStream for BufRecvStream { + type Error = S::Error; + + #[inline] + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) + } + + fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + self.stream.poll_finish(cx) + } + + fn reset(&mut self, reset_code: u64) { + self.stream.reset(reset_code) + } + + fn send_id(&self) -> quic::StreamId { + self.stream.send_id() + } +} + +impl BidiStream for BufRecvStream { + type SendStream = BufRecvStream; + + type RecvStream = BufRecvStream; + + fn split(self) -> (Self::SendStream, Self::RecvStream) { + let (send, recv) = self.stream.split(); + ( + BufRecvStream { + // Sending is not buffered + buf: BufList::new(), + eos: self.eos, + stream: send, + }, + BufRecvStream { + buf: self.buf, + eos: self.eos, + stream: recv, + }, + ) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index 91e3c0fc..c662bb95 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -597,6 +597,7 @@ async fn header_too_big_discard_from_client_trailers() { .build(pair.client().await) .await .expect("client init"); + let drive_fut = async { future::poll_fn(|cx| driver.poll_close(cx)).await }; let req_fut = async { let mut request_stream = client From 51d66c3fd5e2b783e4c77b2f453e41afa22e8f7a Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 13 Apr 2023 17:06:54 +0200 Subject: [PATCH 44/97] feat: allow requests to pass through WebTransportSession --- examples/webtransport_server.rs | 12 +- h3/src/request.rs | 97 ++++++++++++++++ h3/src/server.rs | 137 +++++++++-------------- h3/src/webtransport/server.rs | 188 ++++++++++++++------------------ h3/src/webtransport/stream.rs | 94 ++-------------- 5 files changed, 245 insertions(+), 283 deletions(-) create mode 100644 h3/src/request.rs diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index e80e9f54..b1e33e47 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -260,7 +260,8 @@ where } } stream = session.accept_bi() => { - let (session_id, mut send, mut recv) = stream?.unwrap(); + if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { + tracing::info!("Got bi stream"); let mut message = BytesMut::new(); while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { @@ -271,9 +272,12 @@ where tracing::info!("Got message: {message:?}"); - send.write_all(&mut format!("I got your message: {message:?}").as_bytes()).await.context("Failed to respond")?; - // send.send_data(message.freeze()).context("Failed to send response"); - // future::poll_fn(|cx| send.poll_ready(cx)).await?; + let mut response = BytesMut::new(); + response.put(format!("I got your message of {} bytes: ", message.len()).as_bytes()); + response.put(message); + + send.write_all(response).await.context("Failed to respond")?; + } } else => { break diff --git a/h3/src/request.rs b/h3/src/request.rs new file mode 100644 index 00000000..f0ed9b21 --- /dev/null +++ b/h3/src/request.rs @@ -0,0 +1,97 @@ +use std::convert::TryFrom; + +use bytes::Bytes; +use http::{Request, StatusCode}; + +use crate::{ + error::Code, + proto::headers::Header, + qpack, quic, + server::{Connection, RequestStream}, + Error, +}; + +pub(crate) struct ResolveRequest { + request_stream: RequestStream, + // Ok or `REQUEST_HEADER_FIELDS_TO_LARGE` which neeeds to be sent + decoded: Result<(qpack::Decoded), u64>, + max_field_section_size: u64, +} + +impl ResolveRequest { + pub fn new( + request_stream: RequestStream, + decoded: Result<(qpack::Decoded), u64>, + max_field_section_size: u64, + ) -> Self { + Self { + request_stream, + decoded, + max_field_section_size, + } + } + + /// Finishes the resolution of the request + pub async fn resolve(mut self) -> Result<(Request<()>, RequestStream), Error> { + let fields = match self.decoded { + Ok(v) => v.fields, + Err(cancel_size) => { + // Send and await the error response + self.request_stream + .send_response( + http::Response::builder() + .status(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE) + .body(()) + .expect("header too big response"), + ) + .await?; + + return Err(Error::header_too_big( + cancel_size, + self.max_field_section_size, + )); + } + }; + + // Parse the request headers + let (method, uri, protocol, headers) = match Header::try_from(fields) { + Ok(header) => match header.into_request_parts() { + Ok(parts) => parts, + Err(err) => { + //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1.2 + //# Malformed requests or responses that are + //# detected MUST be treated as a stream error of type H3_MESSAGE_ERROR. + let error: Error = err.into(); + self.request_stream + .stop_stream(error.try_get_code().unwrap_or(Code::H3_MESSAGE_ERROR)); + return Err(error); + } + }, + Err(err) => { + //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1.2 + //# Malformed requests or responses that are + //# detected MUST be treated as a stream error of type H3_MESSAGE_ERROR. + let error: Error = err.into(); + self.request_stream + .stop_stream(error.try_get_code().unwrap_or(Code::H3_MESSAGE_ERROR)); + return Err(error); + } + }; + + tracing::info!("Protocol: {protocol:?}"); + // request_stream.stop_stream(Code::H3_MESSAGE_ERROR).await; + let mut req = http::Request::new(()); + *req.method_mut() = method; + *req.uri_mut() = uri; + *req.headers_mut() = headers; + // NOTE: insert `Protocol` and not `Option` + if let Some(protocol) = protocol { + req.extensions_mut().insert(protocol); + } + *req.version_mut() = http::Version::HTTP_3; + // send the grease frame only once + // self.inner.send_grease_frame = false; + tracing::trace!("replying with: {:?}", req); + Ok((req, self.request_stream)) + } +} diff --git a/h3/src/server.rs b/h3/src/server.rs index 1bfca7e3..bfd508b0 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -85,6 +85,7 @@ use crate::{ }, qpack, quic::{self, SendStream as _}, + request::ResolveRequest, stream::{self, BufRecvStream}, }; use tracing::{error, info, trace, warn}; @@ -197,7 +198,12 @@ where }; let frame = future::poll_fn(|cx| stream.poll_next(cx)).await; - self.accept_with_frame(stream, frame).await + let req = self.accept_with_frame(stream, frame)?; + if let Some(req) = req { + Ok(Some(req.resolve().await?)) + } else { + Ok(None) + } } /// Accepts an http request where the first frame has already been read and decoded. @@ -206,11 +212,11 @@ where /// This is needed as a bidirectional stream may be read as part of incoming webtransport /// bi-streams. If it turns out that the stream is *not* a `WEBTRANSPORT_STREAM` the request /// may still want to be handled and passed to the user. - pub(crate) async fn accept_with_frame( + pub(crate) fn accept_with_frame( &mut self, mut stream: FrameStream, frame: Result>, FrameStreamError>, - ) -> Result, RequestStream)>, Error> { + ) -> Result>, Error> { let mut encoded = match frame { Ok(Some(Frame::Headers(h))) => h, @@ -287,96 +293,51 @@ where ), }; - let qpack::Decoded { fields, .. } = - match qpack::decode_stateless(&mut encoded, self.max_field_section_size) { - //= https://www.rfc-editor.org/rfc/rfc9114#section-4.2.2 - //# An HTTP/3 implementation MAY impose a limit on the maximum size of - //# the message header it will accept on an individual HTTP message. - Err(qpack::DecoderError::HeaderTooLong(cancel_size)) => { - request_stream - .send_response( - http::Response::builder() - .status(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE) - .body(()) - .expect("header too big response"), - ) - .await?; - - return Err(Error::header_too_big( - cancel_size, - self.max_field_section_size, - )); + let decoded = match qpack::decode_stateless(&mut encoded, self.max_field_section_size) { + //= https://www.rfc-editor.org/rfc/rfc9114#section-4.2.2 + //# An HTTP/3 implementation MAY impose a limit on the maximum size of + //# the message header it will accept on an individual HTTP message. + Err(qpack::DecoderError::HeaderTooLong(cancel_size)) => Err(cancel_size), + Ok(decoded) => { + // send the grease frame only once + self.inner.send_grease_frame = false; + Ok(decoded) + } + Err(e) => { + let err: Error = e.into(); + if err.is_closed() { + return Ok(None); } - Ok(decoded) => decoded, - Err(e) => { - let err: Error = e.into(); - if err.is_closed() { - return Ok(None); - } - match err.inner.kind { - crate::error::Kind::Closed => return Ok(None), - crate::error::Kind::Application { - code, - reason, - level: ErrorLevel::ConnectionError, - } => { - return Err(self.inner.close( - code, - reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), - )) - } - crate::error::Kind::Application { + match err.inner.kind { + crate::error::Kind::Closed => return Ok(None), + crate::error::Kind::Application { + code, + reason, + level: ErrorLevel::ConnectionError, + } => { + return Err(self.inner.close( code, - reason: _, - level: ErrorLevel::StreamError, - } => { - request_stream.stop_stream(code); - return Err(err); - } - _ => return Err(err), - }; - } - }; - - // Parse the request headers - let (method, uri, protocol, headers) = match Header::try_from(fields) { - Ok(header) => match header.into_request_parts() { - Ok(parts) => parts, - Err(err) => { - //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1.2 - //# Malformed requests or responses that are - //# detected MUST be treated as a stream error of type H3_MESSAGE_ERROR. - let error: Error = err.into(); - request_stream - .stop_stream(error.try_get_code().unwrap_or(Code::H3_MESSAGE_ERROR)); - return Err(error); - } - }, - Err(err) => { - //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1.2 - //# Malformed requests or responses that are - //# detected MUST be treated as a stream error of type H3_MESSAGE_ERROR. - let error: Error = err.into(); - request_stream.stop_stream(error.try_get_code().unwrap_or(Code::H3_MESSAGE_ERROR)); - return Err(error); + reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + )) + } + crate::error::Kind::Application { + code, + reason: _, + level: ErrorLevel::StreamError, + } => { + request_stream.stop_stream(code); + return Err(err); + } + _ => return Err(err), + }; } }; - tracing::info!("Protocol: {protocol:?}"); - // request_stream.stop_stream(Code::H3_MESSAGE_ERROR).await; - let mut req = http::Request::new(()); - *req.method_mut() = method; - *req.uri_mut() = uri; - *req.headers_mut() = headers; - // NOTE: insert `Protocol` and not `Option` - if let Some(protocol) = protocol { - req.extensions_mut().insert(protocol); - } - *req.version_mut() = http::Version::HTTP_3; - // send the grease frame only once - self.inner.send_grease_frame = false; - trace!("replying with: {:?}", req); - Ok(Some((req, request_stream))) + Ok(Some(ResolveRequest::new( + request_stream, + decoded, + self.max_field_section_size, + ))) } /// Reads an incoming datagram diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index a436c970..02b3e6cc 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -12,8 +12,9 @@ use crate::{ error::{Code, ErrorLevel}, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, - quic::{self, BidiStream, SendStream}, + quic::{self, BidiStream as _, SendStream as _}, server::{self, Connection, RequestStream}, + stream::BufRecvStream, Error, Protocol, }; use bytes::{Buf, Bytes}; @@ -22,7 +23,7 @@ use http::{Method, Request, Response, StatusCode}; use quic::StreamId; use super::{ - stream::{self}, + stream::{self, RecvStream, SendStream}, SessionId, }; @@ -146,116 +147,95 @@ where AcceptUni { conn: &self.conn } } - /// Accepts an incoming bidirectional stream - /// TODO: should this return an enum of BiStream/Request to allow ordinary HTTP/3 requests to - /// pass through? - /// - /// Currently, they are ignored, and the function loops until it receives a *webtransport* - /// stream. - pub async fn accept_bi( - &self, - ) -> Result< - Option<( - SessionId, - stream::SendStream, - stream::RecvStream, - )>, - Error, - > { - loop { - // Get the next stream - // Accept the incoming stream - let stream = poll_fn(|cx| { - let mut conn = self.conn.lock().unwrap(); - conn.poll_accept_request(cx) - }) - .await; - - tracing::debug!("Received biderectional stream"); - - let mut stream = match stream { - Ok(Some(s)) => FrameStream::new(s), - Ok(None) => { - // We always send a last GoAway frame to the client, so it knows which was the last - // non-rejected request. - // self.shutdown(0).await?; - todo!("shutdown"); - // return Ok(None); - } - Err(err) => { - match err.inner.kind { - crate::error::Kind::Closed => return Ok(None), - crate::error::Kind::Application { - code, - reason, - level: ErrorLevel::ConnectionError, - } => { - return Err(self.conn.lock().unwrap().close( - code, - reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), - )) - } - _ => return Err(err), - }; - } - }; - - tracing::debug!("Reading first frame"); - // Read the first frame. - // - // This wil determine if it is a webtransport bi-stream or a request stream - let frame = poll_fn(|cx| stream.poll_next(cx)).await; - + /// Accepts an incoming bidirectional stream or request + pub async fn accept_bi(&self) -> Result>, Error> { + // Get the next stream + // Accept the incoming stream + let stream = poll_fn(|cx| { let mut conn = self.conn.lock().unwrap(); - match frame { - Ok(None) => return Ok(None), - Ok(Some(Frame::WebTransportStream(session_id))) => { - tracing::info!("Got webtransport stream"); - // Take the stream out of the framed reader - let (stream, buf) = stream.into_inner(); - let (send, recv) = stream.split(); - // Don't lose already read data - let recv = stream::RecvStream::new(buf, recv); - let send = stream::SendStream::new(send); - return Ok(Some((session_id, send, recv))); - } - // Not a webtransport stream, discard - // - // Find a workaround to return this request, in order to accept more sessions - Ok(Some(_frame)) => continue, - Err(e) => { - let err: Error = e.into(); - if err.is_closed() { - return Ok(None); - } - match err.inner.kind { - crate::error::Kind::Closed => return Ok(None), - crate::error::Kind::Application { - code, - reason, - level: ErrorLevel::ConnectionError, - } => { - return Err(conn.close( - code, - reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), - )) - } - crate::error::Kind::Application { + conn.poll_accept_request(cx) + }) + .await; + + tracing::debug!("Received biderectional stream"); + + let mut stream = match stream { + Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), + Ok(None) => { + // We always send a last GoAway frame to the client, so it knows which was the last + // non-rejected request. + // self.shutdown(0).await?; + todo!("shutdown"); + // return Ok(None); + } + Err(err) => { + match err.inner.kind { + crate::error::Kind::Closed => return Ok(None), + crate::error::Kind::Application { + code, + reason, + level: ErrorLevel::ConnectionError, + } => { + return Err(self.conn.lock().unwrap().close( code, - reason: _, - level: ErrorLevel::StreamError, - } => { - stream.reset(code.into()); - return Err(err); - } - _ => return Err(err), - }; + reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + )) + } + _ => return Err(err), + }; + } + }; + + tracing::debug!("Reading first frame"); + // Read the first frame. + // + // This will determine if it is a webtransport bi-stream or a request stream + let frame = poll_fn(|cx| stream.poll_next(cx)).await; + + match frame { + Ok(None) => Ok(None), + Ok(Some(Frame::WebTransportStream(session_id))) => { + tracing::info!("Got webtransport stream"); + // Take the stream out of the framed reader and split it in half like Paul Allen + let (send, recv) = stream.into_inner().split(); + let send = SendStream::new(send); + let recv = RecvStream::new(recv); + + Ok(Some(AcceptedBi::BidiStream(session_id, send, recv))) + } + // Make the underlying HTTP/3 connection handle the rest + frame => { + let req = { + let mut conn = self.conn.lock().unwrap(); + conn.accept_with_frame(stream, frame)? + }; + if let Some(req) = req { + let (req, resp) = req.resolve().await?; + Ok(Some(AcceptedBi::Request(req, resp))) + } else { + Ok(None) } } } } } +/// An accepted incoming bidirectional stream. +/// +/// Since +pub enum AcceptedBi { + /// An incoming bidirectional stream + BidiStream( + SessionId, + SendStream, + RecvStream, + ), + /// An incoming HTTP/3 request, passed through a webtransport session. + /// + /// This makes it possible to respond to multiple CONNECT requests + Request(Request<()>, RequestStream), +} + /// Future for [`Connection::read_datagram`] pub struct ReadDatagram where diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 38b71342..617fbb8e 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -7,19 +7,19 @@ use crate::{ buf::BufList, proto::varint::UnexpectedEnd, quic::{self, RecvStream as _, SendStream as _}, + stream::BufRecvStream, }; use super::SessionId; /// WebTransport receive stream pub struct RecvStream { - buf: BufList, - stream: S, + stream: BufRecvStream, } impl RecvStream { - pub(crate) fn new(buf: BufList, stream: S) -> Self { - Self { buf, stream } + pub(crate) fn new(stream: BufRecvStream) -> Self { + Self { stream } } } @@ -37,16 +37,7 @@ where cx: &mut std::task::Context<'_>, ) -> Poll, Self::Error>> { tracing::info!("Polling RecvStream"); - if let Some(chunk) = self.buf.take_first_chunk() { - if chunk.remaining() > 0 { - return Poll::Ready(Ok(Some(chunk))); - } - } - - match ready!(self.stream.poll_data(cx)?) { - Some(mut buf) => Poll::Ready(Ok(Some(buf.copy_to_bytes(buf.remaining())))), - None => Poll::Ready(Ok(None)), - } + self.stream.poll_data(cx) } fn stop_sending(&mut self, error_code: u64) { @@ -60,11 +51,11 @@ where /// WebTransport send stream pub struct SendStream { - stream: S, + stream: BufRecvStream, } impl SendStream { - pub(crate) fn new(stream: S) -> Self { + pub(crate) fn new(stream: BufRecvStream) -> Self { Self { stream } } } @@ -116,74 +107,3 @@ where self.stream.send_id() } } - -/// A biderectional webtransport stream. -/// -/// May be split into a sender and a receiver part -pub struct BidiStream { - send: SendStream, - recv: RecvStream, -} - -impl quic::BidiStream for BidiStream -where - S: quic::SendStream + quic::RecvStream, -{ - type SendStream = SendStream; - type RecvStream = RecvStream; - - fn split(self) -> (Self::SendStream, Self::RecvStream) { - (self.send, self.recv) - } -} - -impl quic::SendStream for BidiStream -where - S: quic::SendStream, -{ - type Error = S::Error; - - fn poll_send( - &mut self, - cx: &mut std::task::Context<'_>, - buf: &mut D, - ) -> Poll> { - self.send.poll_send(cx, buf) - } - - fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - self.send.poll_finish(cx) - } - - fn reset(&mut self, reset_code: u64) { - self.send.reset(reset_code) - } - - fn send_id(&self) -> quic::StreamId { - self.send.send_id() - } -} - -impl quic::RecvStream for BidiStream -where - S: quic::RecvStream, -{ - type Buf = Bytes; - - type Error = S::Error; - - fn poll_data( - &mut self, - cx: &mut std::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) -> quic::StreamId { - self.recv.recv_id() - } -} From 9af9ad573d66bb363188efb0762e4ce6ec480686 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 13 Apr 2023 17:24:26 +0200 Subject: [PATCH 45/97] feat: implement AsyncWrite for wt SendStream --- examples/webtransport_server.rs | 10 +++++++--- h3-quinn/src/lib.rs | 10 ++++++++++ h3/src/webtransport/stream.rs | 30 +++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index b1e33e47..d3bf7b0f 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -180,7 +180,9 @@ async fn main() -> Result<(), Box> { async fn handle_connection(mut conn: Connection) -> Result<()> where C: 'static + Send + quic::Connection, - ::Error: 'static + std::error::Error + Send + Sync, + ::Error: + 'static + std::error::Error + Send + Sync + Into, + C::SendStream: Unpin, { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them @@ -235,7 +237,9 @@ async fn handle_session_and_echo_all_inbound_messages( ) -> anyhow::Result<()> where C: 'static + Send + h3::quic::Connection, - ::Error: 'static + std::error::Error + Send + Sync, + ::Error: + 'static + std::error::Error + Send + Sync + Into, + C::SendStream: Unpin, { loop { tokio::select! { @@ -276,7 +280,7 @@ where response.put(format!("I got your message of {} bytes: ", message.len()).as_bytes()); response.put(message); - send.write_all(response).await.context("Failed to respond")?; + futures::AsyncWriteExt::write_all(&mut send, &response).await.context("Failed to respond")?; } } else => { diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index c0427af8..bf1b946a 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -514,6 +514,16 @@ pub enum SendStreamError { NotReady, } +impl From for std::io::Error { + fn from(value: SendStreamError) -> Self { + match value { + SendStreamError::Write(err) => err.into(), + // TODO: remove + SendStreamError::NotReady => unreachable!(), + } + } +} + impl std::error::Error for SendStreamError {} impl Display for SendStreamError { diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 617fbb8e..9fbb59d0 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -1,7 +1,7 @@ use std::{marker::PhantomData, task::Poll}; use bytes::{Buf, Bytes}; -use futures_util::{future, ready, AsyncRead}; +use futures_util::{future, ready, AsyncRead, AsyncWrite}; use crate::{ buf::BufList, @@ -107,3 +107,31 @@ where self.stream.send_id() } } + +impl AsyncWrite for SendStream +where + S: Unpin + quic::SendStream, + S::Error: Into, +{ + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + mut buf: &[u8], + ) -> Poll> { + self.poll_send(cx, &mut buf).map_err(Into::into) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.poll_finish(cx).map_err(Into::into) + } +} From 687e3eba53d80afbc4a19adfcc44fec688cb905c Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 14 Apr 2023 09:32:26 +0200 Subject: [PATCH 46/97] fix: (some) msrv --- .github/workflows/CI.yml | 2 +- examples/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 045eacf3..b548503d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -66,7 +66,7 @@ jobs: with: command: clippy - msrv: + gsrv: name: Check MSRV needs: [style] runs-on: ubuntu-latest diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4d5e8af3..beeb1491 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -35,7 +35,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ tracing-tree = { version = "0.2" } [features] -tracing-tree = [] +tree = [] [[example]] name = "client" From 869add42bde4a9c79808fd0d339f76b16ca2c9db Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 14 Apr 2023 10:56:05 +0200 Subject: [PATCH 47/97] fix: doc tests --- h3/src/client.rs | 23 +++++++++-------------- h3/src/server.rs | 5 ++--- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/h3/src/client.rs b/h3/src/client.rs index 0b4089e8..13bbe6d2 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -66,14 +66,13 @@ where /// # use bytes::Buf; /// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> /// # where -/// # T: quic::OpenStreams, -/// # B: Buf, +/// # T: quic::OpenStreams, /// # { /// // Prepare the HTTP request to send to the server /// let request = Request::get("https://www.example.com/").body(())?; /// /// // Send the request to the server -/// let mut req_stream: RequestStream<_, _> = send_request.send_request(request).await?; +/// let mut req_stream: RequestStream<_> = send_request.send_request(request).await?; /// // Don't forget to end up the request by finishing the send stream. /// req_stream.finish().await?; /// // Receive the response @@ -90,9 +89,9 @@ where /// # use h3::{quic, client::*}; /// # use http::{Request, Response, HeaderMap}; /// # use bytes::{Buf, Bytes}; -/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> +/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> /// # where -/// # T: quic::OpenStreams, +/// # T: quic::OpenStreams, /// # { /// // Prepare the HTTP request to send to the server /// let request = Request::get("https://www.example.com/").body(())?; @@ -100,7 +99,7 @@ where /// // Send the request to the server /// let mut req_stream = send_request.send_request(request).await?; /// // Send some data -/// req_stream.send_data("body".into()).await?; +/// req_stream.send_data("body".as_bytes()).await?; /// // Prepare the trailers /// let mut trailers = HeaderMap::new(); /// trailers.insert("trailer", "value".parse()?); @@ -279,10 +278,9 @@ where /// # async fn doc(mut connection: Connection) /// # -> JoinHandle>> /// # where -/// # C: quic::Connection + Send + 'static, +/// # C: quic::Connection + Send + 'static, /// # C::SendStream: Send + 'static, /// # C::RecvStream: Send + 'static, -/// # B: Buf + Send + 'static, /// # { /// // Run the driver on a different task /// tokio::spawn(async move { @@ -302,10 +300,9 @@ where /// # async fn doc(mut connection: Connection) /// # -> Result<(), Box> /// # where -/// # C: quic::Connection + Send + 'static, +/// # C: quic::Connection + Send + 'static, /// # C::SendStream: Send + 'static, /// # C::RecvStream: Send + 'static, -/// # B: Buf + Send + 'static, /// # { /// // Prepare a channel to stop the driver thread /// let (shutdown_tx, shutdown_rx) = oneshot::channel(); @@ -461,9 +458,8 @@ where /// # use h3::quic; /// # async fn doc(quic: C) /// # where -/// # C: quic::Connection, -/// # O: quic::OpenStreams, -/// # B: bytes::Buf, +/// # C: quic::Connection, +/// # O: quic::OpenStreams, /// # { /// let h3_conn = h3::client::builder() /// .max_field_section_size(8192) @@ -549,7 +545,6 @@ impl Builder { /// # async fn doc(mut req_stream: RequestStream) -> Result<(), Box> /// # where /// # T: quic::RecvStream, -/// # B: Buf, /// # { /// // Prepare the HTTP request to send to the server /// let request = Request::get("https://www.example.com/").body(())?; diff --git a/h3/src/server.rs b/h3/src/server.rs index bfd508b0..b18d6063 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -615,10 +615,9 @@ impl Default for Config { /// # Example /// /// ```rust -/// fn doc(conn: C) +/// fn doc(conn: C) /// where -/// C: h3::quic::Connection, -/// B: bytes::Buf, +/// C: h3::quic::Connection, /// { /// let mut server_builder = h3::server::builder(); /// // Set the maximum header size From 669bee6e916577fe3f5804e946dbac0e6039e32b Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Fri, 14 Apr 2023 16:44:17 +0200 Subject: [PATCH 48/97] wip: open bi --- examples/webtransport_server.rs | 40 +++++++++++------ h3/src/proto/frame.rs | 6 ++- h3/src/quic.rs | 8 +++- h3/src/stream.rs | 9 ++-- h3/src/webtransport/server.rs | 79 ++++++++++++++++++++++++++++++--- 5 files changed, 115 insertions(+), 27 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index d3bf7b0f..b69dd4f0 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -7,12 +7,12 @@ use std::{ use anyhow::{anyhow, Context, Result}; use bytes::{Buf, BufMut, Bytes, BytesMut}; -use futures::{future, StreamExt}; +use futures::{future, AsyncWriteExt, StreamExt}; use futures::{future::poll_fn, AsyncRead}; use http::{Method, Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; -use tokio::{fs::File, io::AsyncReadExt, time::sleep}; +use tokio::{fs::File, io::AsyncReadExt, pin, time::sleep}; use tracing::{error, info, trace_span}; use h3::{ @@ -71,14 +71,14 @@ pub struct Certs { #[tokio::main] async fn main() -> Result<(), Box> { // 0. Setup tracing - #[cfg(not(feature = "tracing-tree"))] + #[cfg(not(feature = "tree"))] tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_writer(std::io::stderr) .init(); - #[cfg(feature = "tracing-tree")] + #[cfg(feature = "tree")] tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::from_default_env()) .with(tracing_tree::HierarchicalLayer::new(4).with_bracketed_fields(true)) @@ -241,6 +241,10 @@ where 'static + std::error::Error + Send + Sync + Into, C::SendStream: Unpin, { + let open_bi_timer = sleep(Duration::from_millis(10000)); + pin!(open_bi_timer); + let session_id = session.session_id(); + loop { tokio::select! { datagram = session.read_datagram() => { @@ -263,22 +267,30 @@ where tracing::info!("Received data {:?}", bytes); } } + _ = &mut open_bi_timer => { + tracing::info!("Opening bidirectional stream"); + let (mut send, recv) = session.open_bi(session_id).await?; + + send.write_all("Hello, World!".as_bytes()).await?; + tracing::info!("Sent data"); + + } stream = session.accept_bi() => { if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { - tracing::info!("Got bi stream"); - let mut message = BytesMut::new(); - while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { - tracing::info!("Received data {:?}", bytes); - message.put(bytes); + tracing::info!("Got bi stream"); + let mut message = BytesMut::new(); + while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { + tracing::info!("Received data {:?}", bytes); + message.put(bytes); - } + } - tracing::info!("Got message: {message:?}"); + tracing::info!("Got message: {message:?}"); - let mut response = BytesMut::new(); - response.put(format!("I got your message of {} bytes: ", message.len()).as_bytes()); - response.put(message); + let mut response = BytesMut::new(); + response.put(format!("I got your message of {} bytes: ", message.len()).as_bytes()); + response.put(message); futures::AsyncWriteExt::write_all(&mut send, &response).await.context("Failed to respond")?; } diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index dc3cf805..531131a7 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -158,7 +158,11 @@ where buf.write_var(6); buf.put_slice(b"grease"); } - Frame::WebTransportStream(_) => todo!(), + Frame::WebTransportStream(id) => { + FrameType::WEBTRANSPORT_BI_STREAM.encode(buf); + id.encode(buf); + // rest of the data is sent streaming + } } } } diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 013f9633..7657fd09 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -78,7 +78,11 @@ pub trait Connection { /// The type produced by `poll_accept_recv()` type RecvStream: RecvStream; /// A producer of outgoing Unidirectional and Bidirectional streams. - type OpenStreams: OpenStreams; + type OpenStreams: OpenStreams< + SendStream = Self::SendStream, + RecvStream = Self::RecvStream, + BidiStream = Self::BidiStream, + >; /// Error type yielded by this trait methods type Error: Into>; @@ -129,7 +133,7 @@ pub trait Connection { /// Trait for opening outgoing streams pub trait OpenStreams { /// The type produced by `poll_open_bidi()` - type BidiStream: SendStream + RecvStream; + type BidiStream: BidiStream; /// The type produced by `poll_open_send()` type SendStream: SendStream; /// The type of the receiving part of `BidiStream` diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 74694dcb..4c22a58a 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -46,10 +46,7 @@ const WRITE_BUF_ENCODE_SIZE: usize = StreamType::MAX_ENCODED_SIZE + Frame::MAX_E /// data is necessary (say, in `quic::SendStream::send_data`). It also has a public API ergonomy /// advantage: `WriteBuf` doesn't have to appear in public associated types. On the other hand, /// QUIC implementers have to call `into()`, which will encode the header in `Self::buf`. -pub struct WriteBuf -where - B: Buf, -{ +pub struct WriteBuf { buf: [u8; WRITE_BUF_ENCODE_SIZE], len: usize, pos: usize, @@ -323,7 +320,7 @@ pub(crate) struct BufRecvStream { stream: S, } -impl BufRecvStream { +impl BufRecvStream { pub(crate) fn new(stream: S) -> Self { Self { buf: BufList::new(), @@ -339,7 +336,9 @@ impl BufRecvStream { stream, } } +} +impl BufRecvStream { /// Reads more data into the buffer, returning the number of bytes read. /// /// Returns `true` if the end of the stream is reached. diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 02b3e6cc..1ebbd698 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,7 +1,6 @@ //! Provides the server side WebTransport session use std::{ - marker::PhantomData, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, @@ -12,7 +11,7 @@ use crate::{ error::{Code, ErrorLevel}, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, - quic::{self, BidiStream as _, SendStream as _}, + quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, server::{self, Connection, RequestStream}, stream::BufRecvStream, Error, Protocol, @@ -20,7 +19,6 @@ use crate::{ use bytes::{Buf, Bytes}; use futures_util::{future::poll_fn, ready, Future}; use http::{Method, Request, Response, StatusCode}; -use quic::StreamId; use super::{ stream::{self, RecvStream, SendStream}, @@ -37,9 +35,10 @@ where C: quic::Connection, { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 - session_id: StreamId, + session_id: SessionId, conn: Mutex>, connect_stream: RequestStream, + opener: Mutex, } impl WebTransportSession @@ -114,10 +113,15 @@ where tracing::info!("Sending response: {response:?}"); stream.send_response(response).await?; - let session_id = stream.send_id(); + let session_id = stream.send_id().into(); tracing::info!("Established new WebTransport session with id {session_id:?}"); + let conn_inner = conn.inner.conn.lock().unwrap(); + let opener = Mutex::new(conn_inner.opener()); + drop(conn_inner); + Ok(Self { session_id, + opener, conn: Mutex::new(conn), connect_stream: stream, }) @@ -218,6 +222,71 @@ where } } } + + /// Open a new bidirectional stream + pub fn open_bi(&self, session_id: SessionId) -> OpenBi { + OpenBi { + opener: &self.opener, + stream: None, + session_id, + } + } + + /// Returns the session id + pub fn session_id(&self) -> SessionId { + self.session_id + } +} + +/// Streams are opened, but the initial webtransport header has not been sent +type PendingStreams = ( + SendStream<::SendStream>, + RecvStream<::RecvStream>, + WriteBuf<&'static [u8]>, +); + +#[pin_project::pin_project] +/// Future for opening a bidi stream +pub struct OpenBi<'a, C: quic::Connection> { + opener: &'a Mutex, + stream: Option>, + session_id: SessionId, +} + +impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { + type Output = Result<(SendStream, RecvStream), Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut p = self.project(); + loop { + match &mut p.stream { + Some((send, _, buf)) => { + while buf.has_remaining() { + ready!(send.poll_send(cx, buf))?; + } + + tracing::debug!("Finished sending header frame"); + + let (send, recv, _) = p.stream.take().unwrap(); + return Poll::Ready(Ok((send, recv))); + } + None => { + let mut opener = (*p.opener).lock().unwrap(); + // Open the stream first + let res = ready!(opener.poll_open_bidi(cx))?; + let (send, recv) = BufRecvStream::new(res).split(); + + let send: SendStream = SendStream::new(send); + let recv = RecvStream::new(recv); + *p.stream = Some(( + send, + recv, + WriteBuf::from(Frame::WebTransportStream(*p.session_id)), + )); + } + } + } + } } /// An accepted incoming bidirectional stream. From 0e178e2edc12d0688c67c2865f5671a63de03347 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Tue, 18 Apr 2023 15:34:12 -0400 Subject: [PATCH 49/97] read unidirectional stream until the end --- examples/webtransport_server.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index b69dd4f0..c19504b0 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -263,18 +263,27 @@ where stream = session.accept_uni() => { let (id, mut stream) = stream?.unwrap(); tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); - if let Some(bytes) = poll_fn(|cx| stream.poll_data(cx)).await? { - tracing::info!("Received data {:?}", bytes); + // create a buffer + let mut message = BytesMut::new(); + while let Some(bytes) = poll_fn(|cx| stream.poll_data(cx)).await? { + tracing::info!("Received chunk {:?}", &bytes); + message.put(bytes); } - } - _ = &mut open_bi_timer => { - tracing::info!("Opening bidirectional stream"); - let (mut send, recv) = session.open_bi(session_id).await?; - - send.write_all("Hello, World!".as_bytes()).await?; - tracing::info!("Sent data"); - - } + // send the message back + tracing::info!("Got message: {message:?}"); + // TODO: commented this because it's not working yet + // let mut send = session.open_uni(session_id).await?; + // send.write_all(&message).await?; + } + // TODO: commented this because it's not working yet + // _ = &mut open_bi_timer => { + // tracing::info!("Opening bidirectional stream"); + // let (mut send, recv) = session.open_bi(session_id).await?; + + // send.write_all("Hello, World!".as_bytes()).await?; + // tracing::info!("Sent data"); + + // } stream = session.accept_bi() => { if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { From d8053b4d5565624d34d113df7adafc7e3f4853b5 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 19 Apr 2023 00:49:03 -0400 Subject: [PATCH 50/97] got system to echo datagrams and bidi_streams, we can receive uni_streams but I sending server side uni_streams does not work --- examples/webtransport_server.rs | 63 ++++++++++++++------------------- h3-quinn/src/lib.rs | 2 +- h3/src/quic.rs | 2 +- h3/src/webtransport/server.rs | 56 ++++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index c19504b0..09282913 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,29 +1,28 @@ use std::{ net::SocketAddr, - path::{Path, PathBuf}, + path::{PathBuf}, sync::Arc, - time::Duration, + time::Duration }; -use anyhow::{anyhow, Context, Result}; -use bytes::{Buf, BufMut, Bytes, BytesMut}; -use futures::{future, AsyncWriteExt, StreamExt}; -use futures::{future::poll_fn, AsyncRead}; -use http::{Method, Request, StatusCode}; +use anyhow::{Result}; +use bytes::{BufMut, BytesMut}; +use futures::{AsyncWriteExt}; +use futures::{future::poll_fn}; +use http::{Method}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; -use tokio::{fs::File, io::AsyncReadExt, pin, time::sleep}; +use tokio::{pin, time::sleep}; use tracing::{error, info, trace_span}; use h3::{ error::ErrorLevel, - quic::{self, BidiStream, RecvStream, SendStream}, - server::{Config, Connection, RequestStream}, + quic::{self, RecvStream}, + server::{Config, Connection}, webtransport::server::WebTransportSession, Protocol, }; use h3_quinn::quinn; -use tracing_subscriber::prelude::*; #[derive(StructOpt, Debug)] #[structopt(name = "server")] @@ -247,7 +246,7 @@ where loop { tokio::select! { - datagram = session.read_datagram() => { + datagram = session.accept_datagram() => { let datagram = datagram?; if let Some((_, datagram)) = datagram { tracing::info!("Responding with {datagram:?}"); @@ -260,48 +259,38 @@ where tracing::info!("Finished sending datagram"); } } - stream = session.accept_uni() => { - let (id, mut stream) = stream?.unwrap(); + uni_stream = session.accept_uni() => { + let (id, mut stream) = uni_stream?.unwrap(); tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); - // create a buffer + let mut message = BytesMut::new(); while let Some(bytes) = poll_fn(|cx| stream.poll_data(cx)).await? { tracing::info!("Received chunk {:?}", &bytes); - message.put(bytes); + message.put(bytes.clone()); } - // send the message back + tracing::info!("Got message: {message:?}"); - // TODO: commented this because it's not working yet - // let mut send = session.open_uni(session_id).await?; - // send.write_all(&message).await?; + let mut send = session.open_uni(session_id).await?; + let message = message.to_vec(); + let message = message.as_slice(); + futures::AsyncWriteExt::write_all(&mut send, message).await.context("Failed to respond")?; } // TODO: commented this because it's not working yet // _ = &mut open_bi_timer => { // tracing::info!("Opening bidirectional stream"); - // let (mut send, recv) = session.open_bi(session_id).await?; - - // send.write_all("Hello, World!".as_bytes()).await?; - // tracing::info!("Sent data"); + // let (mut send, recv) = session.open_bi(session_id).await?; + // send.write_all("Hello, World!".as_bytes()).await?; + // tracing::info!("Sent data"); // } stream = session.accept_bi() => { if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { - tracing::info!("Got bi stream"); - let mut message = BytesMut::new(); while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { - tracing::info!("Received data {:?}", bytes); - message.put(bytes); - + tracing::info!("Received data {:?}", &bytes); + futures::AsyncWriteExt::write_all(&mut send, &bytes).await.context("Failed to respond")?; } - - tracing::info!("Got message: {message:?}"); - - let mut response = BytesMut::new(); - response.put(format!("I got your message of {} bytes: ", message.len()).as_bytes()); - response.put(message); - - futures::AsyncWriteExt::write_all(&mut send, &response).await.context("Failed to respond")?; + send.close().await?; } } else => { diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index bf1b946a..fd061665 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -235,7 +235,7 @@ impl quic::OpenStreams for OpenStreams { })) } - fn poll_open_send( + fn poll_open_uni( &mut self, cx: &mut task::Context<'_>, ) -> Poll> { diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 7657fd09..bd8eea0f 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -148,7 +148,7 @@ pub trait OpenStreams { ) -> Poll>; /// Poll the connection to create a new unidirectional stream. - fn poll_open_send( + fn poll_open_uni( &mut self, cx: &mut task::Context<'_>, ) -> Poll>; diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 1ebbd698..8cbdfd84 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -128,7 +128,7 @@ where } /// Receive a datagram from the client - pub fn read_datagram(&self) -> ReadDatagram { + pub fn accept_datagram(&self) -> ReadDatagram { ReadDatagram { conn: self.conn.lock().unwrap().inner.conn.clone(), } @@ -232,6 +232,15 @@ where } } + /// Open a new unidirectional stream + pub fn open_uni(&self, session_id: SessionId) -> OpenUni { + OpenUni { + opener: &self.opener, + stream: None, + session_id, + } + } + /// Returns the session id pub fn session_id(&self) -> SessionId { self.session_id @@ -245,6 +254,12 @@ type PendingStreams = ( WriteBuf<&'static [u8]>, ); +/// Streams are opened, but the initial webtransport header has not been sent +type PendingUniStreams = ( + SendStream<::SendStream>, + WriteBuf<&'static [u8]>, +); + #[pin_project::pin_project] /// Future for opening a bidi stream pub struct OpenBi<'a, C: quic::Connection> { @@ -289,6 +304,45 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { } } +#[pin_project::pin_project] +/// Future for opening a uni stream +pub struct OpenUni<'a, C: quic::Connection> { + opener: &'a Mutex, + stream: Option>, + session_id: SessionId, +} + +impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut p = self.project(); + loop { + match &mut p.stream { + Some((send, buf)) => { + while buf.has_remaining() { + ready!(send.poll_send(cx, buf))?; + } + tracing::debug!("Finished sending header frame"); + let (send, _) = p.stream.take().unwrap(); + return Poll::Ready(Ok(send)); + } + None => { + let mut opener = (*p.opener).lock().unwrap(); + let send = ready!(opener.poll_open_uni(cx))?; + let send = BufRecvStream::new(send); + let send = SendStream::new(send); + *p.stream = Some( + (send, + WriteBuf::from(Frame::WebTransportStream(*p.session_id))), + ); + } + } + } + } +} + + /// An accepted incoming bidirectional stream. /// /// Since From 1d8339df9b9abf029b9cfa21a34865f6ab4c54b5 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 19 Apr 2023 00:50:22 -0400 Subject: [PATCH 51/97] add missing import --- examples/webtransport_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 09282913..fc421814 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, time::Duration }; - +use anyhow::Context; use anyhow::{Result}; use bytes::{BufMut, BytesMut}; use futures::{AsyncWriteExt}; From 4441c682196b8899dac0624126e1adb9b9273ae2 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 19 Apr 2023 11:01:56 -0400 Subject: [PATCH 52/97] send frame type and session id --- examples/Cargo.toml | 1 + examples/webtransport_server.rs | 23 +++++++++++++++++++++++ h3/src/webtransport/mod.rs | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index beeb1491..fea2b3a2 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -31,6 +31,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ "time", "tracing-log", ] } +octets = "0.2.0" tracing-tree = { version = "0.2" } diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index fc421814..f7dc5b63 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -67,6 +67,10 @@ pub struct Certs { pub key: PathBuf, } +// TODO: Move this to h3::webtransport::server +const WEBTRANSPORT_UNI_STREAM_TYPE: u64 = 0x54; +const WEBTRANSPORT_BIDI_FRAME_TYPE: u64 = 0x41; + #[tokio::main] async fn main() -> Result<(), Box> { // 0. Setup tracing @@ -273,7 +277,26 @@ where let mut send = session.open_uni(session_id).await?; let message = message.to_vec(); let message = message.as_slice(); + + // TODO: move this into the library + // send stream type + { + let mut data = [0u8; 8]; + let mut buf = octets::OctetsMut::with_slice(&mut data); + futures::AsyncWriteExt::write(&mut send, buf.put_varint(WEBTRANSPORT_UNI_STREAM_TYPE).unwrap()).await.context("Failed to respond")?; + } + + // Send session id + { + let mut data = [0u8; 8]; + let mut buf = octets::OctetsMut::with_slice(&mut data); + buf.put_varint(session_id.0).unwrap(); + let data = buf.buf(); + futures::AsyncWriteExt::write(&mut send, &data[..buf.off()]).await.context("Failed to respond")?; + } + // Send actual data. futures::AsyncWriteExt::write_all(&mut send, message).await.context("Failed to respond")?; + send.close().await?; } // TODO: commented this because it's not working yet // _ = &mut open_bi_timer => { diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index bdf68836..72232ea4 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -19,7 +19,7 @@ pub mod stream; /// /// The session id is the same as the stream id of the CONNECT request. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct SessionId(u64); +pub struct SessionId(pub u64); impl SessionId { pub(crate) fn from_varint(id: VarInt) -> SessionId { Self(id.0) From fa150c81a4d799a64812a46e85f021e503090ad8 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 19 Apr 2023 11:14:35 -0400 Subject: [PATCH 53/97] almost? --- examples/webtransport_server.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index f7dc5b63..3c72b7fd 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -276,14 +276,15 @@ where tracing::info!("Got message: {message:?}"); let mut send = session.open_uni(session_id).await?; let message = message.to_vec(); - let message = message.as_slice(); + let mut message = message.as_slice(); // TODO: move this into the library // send stream type { let mut data = [0u8; 8]; let mut buf = octets::OctetsMut::with_slice(&mut data); - futures::AsyncWriteExt::write(&mut send, buf.put_varint(WEBTRANSPORT_UNI_STREAM_TYPE).unwrap()).await.context("Failed to respond")?; + buf.put_varint(WEBTRANSPORT_UNI_STREAM_TYPE).unwrap(); + send.write(&mut buf.buf()).await.context("Failed to respond")?; } // Send session id @@ -291,11 +292,10 @@ where let mut data = [0u8; 8]; let mut buf = octets::OctetsMut::with_slice(&mut data); buf.put_varint(session_id.0).unwrap(); - let data = buf.buf(); - futures::AsyncWriteExt::write(&mut send, &data[..buf.off()]).await.context("Failed to respond")?; + send.write(&mut buf.buf()).await.context("Failed to respond")?; } // Send actual data. - futures::AsyncWriteExt::write_all(&mut send, message).await.context("Failed to respond")?; + send.write(&mut message).await.context("Failed to respond")?; send.close().await?; } // TODO: commented this because it's not working yet From d5ef8d82a13d45c27f366a0438e4d9fa008abd54 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 19 Apr 2023 17:15:18 +0200 Subject: [PATCH 54/97] fix: unidirectional stream header encoding --- examples/launch_chrome.sh | 1 - examples/webtransport_server.rs | 75 ++++++++++++++-------------- h3-quinn/src/lib.rs | 43 ++++++++--------- h3/Cargo.toml | 1 + h3/src/connection.rs | 6 ++- h3/src/frame.rs | 2 +- h3/src/proto/frame.rs | 2 +- h3/src/proto/stream.rs | 2 +- h3/src/stream.rs | 86 ++++++++++++++++++++++++++++----- h3/src/webtransport/mod.rs | 2 +- h3/src/webtransport/server.rs | 23 +++++---- h3/src/webtransport/stream.rs | 48 +++++++++++++++++- 12 files changed, 199 insertions(+), 92 deletions(-) diff --git a/examples/launch_chrome.sh b/examples/launch_chrome.sh index 498ca502..251079da 100755 --- a/examples/launch_chrome.sh +++ b/examples/launch_chrome.sh @@ -8,7 +8,6 @@ echo "Got cert key $SPKI" echo "Opening google chrome" -sleep 2 open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 ## Logs are stored to ~/Library/Application Support/Google/Chrome/chrome_debug.log diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 3c72b7fd..c763efd8 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,19 +1,17 @@ -use std::{ - net::SocketAddr, - path::{PathBuf}, - sync::Arc, - time::Duration -}; use anyhow::Context; -use anyhow::{Result}; +use anyhow::Result; +use bytes::Bytes; use bytes::{BufMut, BytesMut}; -use futures::{AsyncWriteExt}; -use futures::{future::poll_fn}; -use http::{Method}; +use futures::future::poll_fn; +use futures::AsyncReadExt; +use futures::AsyncWriteExt; +use http::Method; use rustls::{Certificate, PrivateKey}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use structopt::StructOpt; use tokio::{pin, time::sleep}; use tracing::{error, info, trace_span}; +use tracing_subscriber::prelude::*; use h3::{ error::ErrorLevel, @@ -185,7 +183,11 @@ where C: 'static + Send + quic::Connection, ::Error: 'static + std::error::Error + Send + Sync + Into, - C::SendStream: Unpin, + ::Error: + 'static + std::error::Error + Send + Sync + Into, + C::SendStream: Send + Unpin, + C::RecvStream: Send + Unpin, + C::OpenStreams: Send, { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them @@ -242,12 +244,20 @@ where C: 'static + Send + h3::quic::Connection, ::Error: 'static + std::error::Error + Send + Sync + Into, - C::SendStream: Unpin, + ::Error: + 'static + std::error::Error + Send + Sync + Into, + C::SendStream: Send + Unpin, + C::RecvStream: Send + Unpin, { let open_bi_timer = sleep(Duration::from_millis(10000)); pin!(open_bi_timer); let session_id = session.session_id(); + // { + // let mut stream = session.open_uni(session_id).await?; + // stream.write_all("Enrsetnersntensn".as_bytes()).await?; + // } + loop { tokio::select! { datagram = session.accept_datagram() => { @@ -267,37 +277,22 @@ where let (id, mut stream) = uni_stream?.unwrap(); tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); - let mut message = BytesMut::new(); - while let Some(bytes) = poll_fn(|cx| stream.poll_data(cx)).await? { - tracing::info!("Received chunk {:?}", &bytes); - message.put(bytes.clone()); - } + let mut message = Vec::new(); + + AsyncReadExt::read_to_end(&mut stream, &mut message).await.context("Failed to read message")?; + let message = Bytes::from(message); - tracing::info!("Got message: {message:?}"); + tracing::info!("Got message: {message:?}"); let mut send = session.open_uni(session_id).await?; - let message = message.to_vec(); - let mut message = message.as_slice(); - - // TODO: move this into the library - // send stream type - { - let mut data = [0u8; 8]; - let mut buf = octets::OctetsMut::with_slice(&mut data); - buf.put_varint(WEBTRANSPORT_UNI_STREAM_TYPE).unwrap(); - send.write(&mut buf.buf()).await.context("Failed to respond")?; - } + tracing::info!("Opened unidirectional stream"); - // Send session id - { - let mut data = [0u8; 8]; - let mut buf = octets::OctetsMut::with_slice(&mut data); - buf.put_varint(session_id.0).unwrap(); - send.write(&mut buf.buf()).await.context("Failed to respond")?; + for byte in message { + tokio::time::sleep(Duration::from_millis(100)).await; + send.write_all(&[byte][..]).await?; } - // Send actual data. - send.write(&mut message).await.context("Failed to respond")?; - send.close().await?; - } + // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; + tracing::info!("Wrote response"); + } // TODO: commented this because it's not working yet // _ = &mut open_bi_timer => { // tracing::info!("Opening bidirectional stream"); @@ -313,7 +308,7 @@ where tracing::info!("Received data {:?}", &bytes); futures::AsyncWriteExt::write_all(&mut send, &bytes).await.context("Failed to respond")?; } - send.close().await?; + send.close().await?; } } else => { diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index fd061665..232a5e9f 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -400,7 +400,17 @@ impl quic::RecvStream for RecvStream { #[derive(Debug)] pub struct ReadError(quinn::ReadError); -impl std::error::Error for 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 { @@ -482,7 +492,7 @@ impl quic::SendStream for SendStream { .downcast::() .expect("write stream returned an error which type is not WriteError"); - Poll::Ready(Err(SendStreamError::Write(*err))) + Poll::Ready(Err(SendStreamError(*err))) } } } @@ -506,21 +516,11 @@ impl quic::SendStream 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, -} +pub struct SendStreamError(WriteError); impl From for std::io::Error { fn from(value: SendStreamError) -> Self { - match value { - SendStreamError::Write(err) => err.into(), - // TODO: remove - SendStreamError::NotReady => unreachable!(), - } + value.0.into() } } @@ -534,7 +534,7 @@ impl Display for SendStreamError { impl From for SendStreamError { fn from(e: WriteError) -> Self { - Self::Write(e) + Self(e) } } @@ -542,7 +542,7 @@ impl Error for SendStreamError { fn is_timeout(&self) -> bool { matches!( self, - Self::Write(quinn::WriteError::ConnectionLost( + Self(quinn::WriteError::ConnectionLost( quinn::ConnectionError::TimedOut )) ) @@ -550,13 +550,10 @@ impl Error for SendStreamError { 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(quinn_proto::ApplicationClose { - error_code, - .. - }), - )) => Some(error_code.into_inner()), + Self(quinn::WriteError::Stopped(error_code)) => Some(error_code.into_inner()), + Self(quinn::WriteError::ConnectionLost(quinn::ConnectionError::ApplicationClosed( + quinn_proto::ApplicationClose { error_code, .. }, + ))) => Some(error_code.into_inner()), _ => None, } } diff --git a/h3/Cargo.toml b/h3/Cargo.toml index d5babfcc..104a6ab5 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -33,6 +33,7 @@ assert_matches = "1.5.0" futures-util = { version = "0.3", default-features = false, features = ["io"] } proptest = "1" quinn = { version = "0.9.3", default-features = false, features = [ + "futures-io", "runtime-tokio", "tls-rustls", "ring", diff --git a/h3/src/connection.rs b/h3/src/connection.rs index de3b9282..07143e7b 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -7,6 +7,7 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use futures_util::{future, ready}; use http::HeaderMap; +use stream::WriteBuf; use tracing::{trace, warn}; use crate::{ @@ -21,7 +22,7 @@ use crate::{ qpack, quic::{self, SendStream as _}, server::Config, - stream::{self, AcceptRecvStream, AcceptedRecvStream, BufRecvStream}, + stream::{self, AcceptRecvStream, AcceptedRecvStream, BufRecvStream, UniStreamHeader}, webtransport::{self, SessionId}, }; @@ -220,7 +221,8 @@ where trace!("Sending Settings frame: {settings:#x?}"); stream::write::<_, _, Bytes>( &mut control_send, - (StreamType::CONTROL, Frame::Settings(settings)), + // (StreamType::CONTROL, Frame::Settings(settings)), + WriteBuf::from(UniStreamHeader::Control(settings)), ) .await?; diff --git a/h3/src/frame.rs b/h3/src/frame.rs index 560b8962..cb089694 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -58,7 +58,7 @@ where loop { let end = self.try_recv(cx)?; - return match self.decoder.decode(&mut self.stream.buf_mut())? { + return match self.decoder.decode(self.stream.buf_mut())? { Some(Frame::Data(PayloadLen(len))) => { self.remaining_data = len; Poll::Ready(Ok(Some(Frame::Data(PayloadLen(len))))) diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 531131a7..f497c586 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -501,7 +501,7 @@ impl Settings { None } - pub(super) fn encode(&self, buf: &mut T) { + pub(crate) fn encode(&self, buf: &mut T) { self.encode_header(buf); for (id, val) in self.entries[..self.len].iter() { id.encode(buf); diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 18e4ddce..9302c479 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -118,7 +118,7 @@ impl StreamId { } /// Distinguishes streams of the same initiator and directionality - fn index(self) -> u64 { + pub fn index(self) -> u64 { self.0 >> 2 } diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 4c22a58a..da0476fd 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -1,6 +1,6 @@ use std::task::{Context, Poll}; -use bytes::{Buf, BufMut as _, Bytes}; +use bytes::{Buf, BufMut, Bytes}; use futures_util::{future, ready}; use crate::{ @@ -9,7 +9,7 @@ use crate::{ frame::FrameStream, proto::{ coding::{Decode as _, Encode}, - frame::Frame, + frame::{Frame, Settings}, stream::StreamType, varint::VarInt, }, @@ -59,10 +59,17 @@ where { fn encode_stream_type(&mut self, ty: StreamType) { let mut buf_mut = &mut self.buf[self.len..]; + ty.encode(&mut buf_mut); self.len = WRITE_BUF_ENCODE_SIZE - buf_mut.remaining_mut(); } + fn encode_value(&mut self, value: impl Encode) { + let mut buf_mut = &mut self.buf[self.len..]; + value.encode(&mut buf_mut); + self.len = WRITE_BUF_ENCODE_SIZE - buf_mut.remaining_mut(); + } + fn encode_frame_header(&mut self) { if let Some(frame) = self.frame.as_ref() { let mut buf_mut = &mut self.buf[self.len..]; @@ -88,6 +95,44 @@ where } } +impl From for WriteBuf +where + B: Buf, +{ + fn from(header: UniStreamHeader) -> Self { + let mut this = Self { + buf: [0; WRITE_BUF_ENCODE_SIZE], + len: 0, + pos: 0, + frame: None, + }; + + this.encode_value(header); + tracing::debug!("encoded header: {:?} len: {}", this.buf, this.len); + this + } +} + +pub enum UniStreamHeader { + Control(Settings), + WebTransportUni(SessionId), +} + +impl Encode for UniStreamHeader { + fn encode(&self, buf: &mut B) { + match self { + Self::Control(settings) => { + StreamType::CONTROL.encode(buf); + settings.encode(buf); + } + Self::WebTransportUni(session_id) => { + StreamType::WEBTRANSPORT_UNI.encode(buf); + session_id.encode(buf); + } + } + } +} + impl From> for WriteBuf where B: Buf, @@ -116,7 +161,7 @@ where pos: 0, frame: Some(frame), }; - me.encode_stream_type(ty); + me.encode_value(ty); me.encode_frame_header(); me } @@ -320,6 +365,16 @@ pub(crate) struct BufRecvStream { stream: S, } +impl std::fmt::Debug for BufRecvStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BufRecvStream") + .field("buf", &self.buf) + .field("eos", &self.eos) + .field("stream", &"...") + .finish() + } +} + impl BufRecvStream { pub(crate) fn new(stream: S) -> Self { Self { @@ -328,14 +383,6 @@ impl BufRecvStream { stream, } } - - pub(crate) fn with_bufs(stream: S, bufs: BufList) -> Self { - Self { - buf: bufs, - eos: false, - stream, - } - } } impl BufRecvStream { @@ -451,8 +498,25 @@ impl BidiStream for BufRecvStream { #[cfg(test)] mod tests { + use quic::StreamId; + use quinn_proto::coding::BufExt; + use super::*; + #[test] + fn write_wt_uni_header() { + let mut w = WriteBuf::::from(UniStreamHeader::WebTransportUni( + SessionId::from_varint(VarInt(5)), + )); + + let ty = w.get_var().unwrap(); + println!("Got type: {ty} {ty:#x}"); + assert_eq!(ty, 0x54); + + let id = w.get_var().unwrap(); + println!("Got id: {id}"); + } + #[test] fn write_buf_encode_streamtype() { let wbuf = WriteBuf::::from(StreamType::ENCODER); diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 72232ea4..38864135 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -54,6 +54,6 @@ impl Decode for SessionId { impl From for SessionId { fn from(value: StreamId) -> Self { - Self(value.into_inner()) + Self(value.index()) } } diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 8cbdfd84..66c5df7d 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -13,7 +13,7 @@ use crate::{ proto::{datagram::Datagram, frame::Frame}, quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, server::{self, Connection, RequestStream}, - stream::BufRecvStream, + stream::{BufRecvStream, UniStreamHeader}, Error, Protocol, }; use bytes::{Buf, Bytes}; @@ -113,6 +113,7 @@ where tracing::info!("Sending response: {response:?}"); stream.send_response(response).await?; + tracing::info!("Stream id: {}", stream.id()); let session_id = stream.send_id().into(); tracing::info!("Established new WebTransport session with id {session_id:?}"); let conn_inner = conn.inner.conn.lock().unwrap(); @@ -277,7 +278,8 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { match &mut p.stream { Some((send, _, buf)) => { while buf.has_remaining() { - ready!(send.poll_send(cx, buf))?; + let n = ready!(send.poll_send(cx, buf))?; + tracing::debug!("Wrote {n} bytes"); } tracing::debug!("Finished sending header frame"); @@ -320,29 +322,32 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { loop { match &mut p.stream { Some((send, buf)) => { + tracing::debug!("Sending uni stream header"); + tracing::debug!("Sending buffer: {send:?}"); while buf.has_remaining() { - ready!(send.poll_send(cx, buf))?; + let n = ready!(send.poll_send(cx, buf))?; + tracing::debug!("Wrote {n} bytes"); } tracing::debug!("Finished sending header frame"); - let (send, _) = p.stream.take().unwrap(); + let (send, buf) = p.stream.take().unwrap(); + assert!(!buf.has_remaining()); return Poll::Ready(Ok(send)); } None => { + tracing::debug!("Opening stream"); let mut opener = (*p.opener).lock().unwrap(); let send = ready!(opener.poll_open_uni(cx))?; let send = BufRecvStream::new(send); let send = SendStream::new(send); - *p.stream = Some( - (send, - WriteBuf::from(Frame::WebTransportStream(*p.session_id))), - ); + + let buf = WriteBuf::from(UniStreamHeader::WebTransportUni(*p.session_id)); + *p.stream = Some((send, buf)); } } } } } - /// An accepted incoming bidirectional stream. /// /// Since diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 9fbb59d0..ad90399a 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -49,11 +49,52 @@ where } } +impl AsyncRead for RecvStream +where + S: Unpin + quic::RecvStream, + S::Error: Into, +{ + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut [u8], + ) -> Poll> { + // If the buffer i empty, poll for more data + if !self.stream.buf_mut().has_remaining() { + let res = ready!(self.stream.poll_read(cx).map_err(Into::into))?; + if res { + return Poll::Ready(Ok(0)); + }; + } + + let bytes = self.stream.buf_mut(); + + // Do not overfill + if let Some(chunk) = bytes.take_chunk(buf.len()) { + assert!(chunk.len() <= buf.len()); + let len = chunk.len().min(buf.len()); + buf[..len].copy_from_slice(&chunk); + + Poll::Ready(Ok(len)) + } else { + Poll::Ready(Ok(0)) + } + } +} + /// WebTransport send stream pub struct SendStream { stream: BufRecvStream, } +impl std::fmt::Debug for SendStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SendStream") + .field("stream", &self.stream) + .finish() + } +} + impl SendStream { pub(crate) fn new(stream: BufRecvStream) -> Self { Self { stream } @@ -118,12 +159,15 @@ where cx: &mut std::task::Context<'_>, mut buf: &[u8], ) -> Poll> { - self.poll_send(cx, &mut buf).map_err(Into::into) + let res = self.poll_send(cx, &mut buf).map_err(Into::into); + + tracing::debug!("poll_write {res:?}"); + res } fn poll_flush( self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + _: &mut std::task::Context<'_>, ) -> Poll> { Poll::Ready(Ok(())) } From 8597893e0706ad31e44d65d18fabddda7c4137cf Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 19 Apr 2023 17:28:49 +0200 Subject: [PATCH 55/97] chore: cleanup logs --- examples/webtransport_server.rs | 1 + h3/src/stream.rs | 1 - h3/src/webtransport/server.rs | 5 ----- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index c763efd8..b59d365d 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -288,6 +288,7 @@ where for byte in message { tokio::time::sleep(Duration::from_millis(100)).await; + tracing::info!("Sending {byte:?}"); send.write_all(&[byte][..]).await?; } // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; diff --git a/h3/src/stream.rs b/h3/src/stream.rs index da0476fd..b0002253 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -108,7 +108,6 @@ where }; this.encode_value(header); - tracing::debug!("encoded header: {:?} len: {}", this.buf, this.len); this } } diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 66c5df7d..96330b81 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -322,19 +322,14 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { loop { match &mut p.stream { Some((send, buf)) => { - tracing::debug!("Sending uni stream header"); - tracing::debug!("Sending buffer: {send:?}"); while buf.has_remaining() { let n = ready!(send.poll_send(cx, buf))?; - tracing::debug!("Wrote {n} bytes"); } - tracing::debug!("Finished sending header frame"); let (send, buf) = p.stream.take().unwrap(); assert!(!buf.has_remaining()); return Poll::Ready(Ok(send)); } None => { - tracing::debug!("Opening stream"); let mut opener = (*p.opener).lock().unwrap(); let send = ready!(opener.poll_open_uni(cx))?; let send = BufRecvStream::new(send); From cde007d2a9572fa4fd12d3c69b3bd8b097b2a9dc Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 19 Apr 2023 21:58:30 -0400 Subject: [PATCH 56/97] added BIDI stream type, removed unused constants --- examples/webtransport_server.rs | 4 ---- h3/src/proto/stream.rs | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index c763efd8..9a073adc 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -65,10 +65,6 @@ pub struct Certs { pub key: PathBuf, } -// TODO: Move this to h3::webtransport::server -const WEBTRANSPORT_UNI_STREAM_TYPE: u64 = 0x54; -const WEBTRANSPORT_BIDI_FRAME_TYPE: u64 = 0x41; - #[tokio::main] async fn main() -> Result<(), Box> { // 0. Setup tracing diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 9302c479..17925052 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -26,6 +26,7 @@ stream_types! { PUSH = 0x01, ENCODER = 0x02, DECODER = 0x03, + WEBTRANSPORT_BIDI = 0x41, WEBTRANSPORT_UNI = 0x54, } From 644f991eb7cd2db4d951d86ae275aa4264c47ff9 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 19 Apr 2023 23:33:24 -0400 Subject: [PATCH 57/97] got server initiated uni-streams to work --- examples/webtransport_server.rs | 24 ++++++++------------- h3/src/connection.rs | 1 - h3/src/stream.rs | 37 +++++++++++++++++++++++++++++++++ h3/src/webtransport/server.rs | 13 ++++-------- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index dd9ac898..cf50f722 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -9,7 +9,6 @@ use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use structopt::StructOpt; -use tokio::{pin, time::sleep}; use tracing::{error, info, trace_span}; use tracing_subscriber::prelude::*; @@ -245,14 +244,17 @@ where C::SendStream: Send + Unpin, C::RecvStream: Send + Unpin, { - let open_bi_timer = sleep(Duration::from_millis(10000)); - pin!(open_bi_timer); let session_id = session.session_id(); - // { - // let mut stream = session.open_uni(session_id).await?; - // stream.write_all("Enrsetnersntensn".as_bytes()).await?; - // } + // This will open a bidirectional stream and send a message to the client right after connecting! + let (mut send, _read_bidi) = session.open_bi(session_id).await.unwrap(); + tracing::info!("Opening bidirectional stream"); + let bytes = Bytes::from("Hello from server"); + futures::AsyncWriteExt::write_all(&mut send, &bytes) + .await + .context("Failed to respond") + .unwrap(); + tokio::time::sleep(Duration::from_millis(1000)).await; loop { tokio::select! { @@ -290,14 +292,6 @@ where // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; tracing::info!("Wrote response"); } - // TODO: commented this because it's not working yet - // _ = &mut open_bi_timer => { - // tracing::info!("Opening bidirectional stream"); - // let (mut send, recv) = session.open_bi(session_id).await?; - - // send.write_all("Hello, World!".as_bytes()).await?; - // tracing::info!("Sent data"); - // } stream = session.accept_bi() => { if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { tracing::info!("Got bi stream"); diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 07143e7b..91388a1a 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -221,7 +221,6 @@ where trace!("Sending Settings frame: {settings:#x?}"); stream::write::<_, _, Bytes>( &mut control_send, - // (StreamType::CONTROL, Frame::Settings(settings)), WriteBuf::from(UniStreamHeader::Control(settings)), ) .await?; diff --git a/h3/src/stream.rs b/h3/src/stream.rs index b0002253..140893a9 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -132,6 +132,43 @@ impl Encode for UniStreamHeader { } } +impl From for WriteBuf +where + B: Buf, +{ + fn from(header: BidiStreamHeader) -> Self { + let mut this = Self { + buf: [0; WRITE_BUF_ENCODE_SIZE], + len: 0, + pos: 0, + frame: None, + }; + + this.encode_value(header); + this + } +} + +pub enum BidiStreamHeader { + Control(Settings), + WebTransportBidi(SessionId), +} + +impl Encode for BidiStreamHeader { + fn encode(&self, buf: &mut B) { + match self { + Self::Control(settings) => { + StreamType::CONTROL.encode(buf); + settings.encode(buf); + } + Self::WebTransportBidi(session_id) => { + StreamType::WEBTRANSPORT_BIDI.encode(buf); + session_id.encode(buf); + } + } + } +} + impl From> for WriteBuf where B: Buf, diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 96330b81..12ae37c9 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -13,7 +13,7 @@ use crate::{ proto::{datagram::Datagram, frame::Frame}, quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, server::{self, Connection, RequestStream}, - stream::{BufRecvStream, UniStreamHeader}, + stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, Error, Protocol, }; use bytes::{Buf, Bytes}; @@ -53,8 +53,6 @@ where mut stream: RequestStream, mut conn: Connection, ) -> Result { - // future::poll_fn(|cx| conn.poll_control(cx)).await?; - let shared = conn.shared_state().clone(); { let config = shared.write("Read WebTransport support").config; @@ -295,11 +293,9 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { let send: SendStream = SendStream::new(send); let recv = RecvStream::new(recv); - *p.stream = Some(( - send, - recv, - WriteBuf::from(Frame::WebTransportStream(*p.session_id)), - )); + + let buf = WriteBuf::from(BidiStreamHeader::WebTransportBidi(*p.session_id)); + *p.stream = Some((send, recv, buf)); } } } @@ -378,7 +374,6 @@ where let mut conn = self.conn.lock().unwrap(); match ready!(conn.poll_accept_datagram(cx))? { - // Some(v) => Poll::Ready(Ok(Some(Datagram::decode(v)?.payload))), Some(v) => { let datagram = Datagram::decode(v)?; Poll::Ready(Ok(Some((datagram.stream_id().into(), datagram.payload)))) From 9ec801279d2cb436b0072c4b0373036c06efb43a Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 20 Apr 2023 16:11:47 +0200 Subject: [PATCH 58/97] wip: multi session support --- h3/src/proto/frame.rs | 10 +--- h3/src/proto/stream.rs | 2 +- h3/src/server.rs | 19 ++---- h3/src/webtransport/mod.rs | 1 + h3/src/webtransport/server.rs | 105 ++++++++++++++++++++++++++++++++-- 5 files changed, 109 insertions(+), 28 deletions(-) diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index f497c586..6d7391ce 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -82,14 +82,6 @@ impl Frame { let remaining = buf.remaining(); let ty = FrameType::decode(buf).map_err(|_| FrameError::Incomplete(remaining + 1))?; - // Webtransport streams need special handling as they have no length. - // - // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.2 - if ty == FrameType::WEBTRANSPORT_BI_STREAM { - tracing::trace!("webtransport frame"); - return Ok(Frame::WebTransportStream(SessionId::decode(buf)?)); - } - let len = buf .get_var() .map_err(|_| FrameError::Incomplete(remaining + 1))?; @@ -115,7 +107,7 @@ impl Frame { | FrameType::H2_PING | FrameType::H2_WINDOW_UPDATE | FrameType::H2_CONTINUATION => Err(FrameError::UnsupportedFrame(ty.0)), - FrameType::WEBTRANSPORT_BI_STREAM | FrameType::DATA => unreachable!(), + FrameType::DATA => unreachable!(), _ => { buf.advance(len as usize); Err(FrameError::UnknownFrame(ty.0)) diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 17925052..3f68f992 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -204,7 +204,7 @@ pub enum Side { /// Whether a stream communicates data in both directions or only from the initiator #[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum Dir { +pub(crate) enum Dir { /// Data flows in both directions Bi = 0, /// Data flows only from the stream's initiator diff --git a/h3/src/server.rs b/h3/src/server.rs index b18d6063..915497bc 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -171,7 +171,7 @@ where &mut self, ) -> Result, RequestStream)>, Error> { // Accept the incoming stream - let mut stream = match future::poll_fn(|cx| self.poll_accept_request(cx)).await { + let mut stream = match future::poll_fn(|cx| self.poll_accept_bi(cx)).await { Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), Ok(None) => { // We always send a last GoAway frame to the client, so it knows which was the last @@ -377,12 +377,13 @@ where /// /// This could be either a *Request* or a *WebTransportBiStream*, the first frame's type /// decides. - pub(crate) fn poll_accept_request( + pub(crate) fn poll_accept_bi( &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { let _ = self.poll_control(cx)?; let _ = self.poll_requests_completion(cx); + loop { match self.inner.poll_accept_request(cx) { Poll::Ready(Err(x)) => break Poll::Ready(Err(x)), @@ -470,17 +471,7 @@ where Poll::Ready(Ok(frame)) } - /// Accepts an incoming recv stream - fn poll_accept_uni( - &self, - cx: &mut Context<'_>, - ) -> Poll::RecvStream>, Error>> { - todo!() - // let recv = ready!(self.inner.poll_accept_recv(cx))?; - - // Poll::Ready(Ok(recv)) - } - + /// Polls until all ongoing requests have completed fn poll_requests_completion(&mut self, cx: &mut Context<'_>) -> Poll<()> { loop { match self.request_end_recv.poll_recv(cx) { @@ -492,7 +483,7 @@ where } Poll::Pending => { if self.ongoing_streams.is_empty() { - // Tell the caller there is not more ongoing requests. + // Tell the caller there are no more ongoing requests. // Still, the completion of future requests will wake us. return Poll::Ready(()); } else { diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 38864135..98fd8161 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -11,6 +11,7 @@ use crate::proto::{ stream::{InvalidStreamId, StreamId}, varint::VarInt, }; +mod accept; pub mod server; /// Send and Receive streams pub mod stream; diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 12ae37c9..8a00fdbb 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,6 +1,7 @@ //! Provides the server side WebTransport session use std::{ + collections::HashMap, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, @@ -10,21 +11,117 @@ use crate::{ connection::ConnectionState, error::{Code, ErrorLevel}, frame::FrameStream, - proto::{datagram::Datagram, frame::Frame}, + proto::{datagram::Datagram, frame::Frame, stream::Dir}, quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, + request::ResolveRequest, server::{self, Connection, RequestStream}, stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, Error, Protocol, }; use bytes::{Buf, Bytes}; -use futures_util::{future::poll_fn, ready, Future}; +use futures_util::{future::poll_fn, ready, stream::FuturesUnordered, Future, StreamExt}; use http::{Method, Request, Response, StatusCode}; +use tokio::sync::mpsc; use super::{ + accept::AcceptBi, stream::{self, RecvStream, SendStream}, SessionId, }; +enum Event { + Datagram(Bytes), + /// A new stream was opened for this session + Stream(Dir), +} + +/// Manages multiple connected WebTransport sessions. +pub struct WebTransportServer { + conn: Connection, + + sessions: HashMap>, + + /// Bidirectional streams which have not yet read the first frame + /// This determines if it is a webtransport stream or a request stream + pending_bi: FuturesUnordered>, + + accepted_requests: Vec>, +} + +impl WebTransportServer { + /// Creates a new WebTransport server from an HTTP/3 connection. + pub fn new(conn: Connection) -> Self { + Self { + conn, + sessions: HashMap::new(), + pending_bi: Default::default(), + accepted_requests: todo!(), + } + } + + /// Accepts the next request. + /// + /// *CONNECT* requests for the WebTransport protocol are intercepted. Use [`Self::accept_session`] + pub async fn accept_request(&self) -> (Request<()>, RequestStream) { + todo!() + } + + /// Accepts the next WebTransport session. + pub fn accept_session( + &self, + request: &Request<()>, + mut stream: RequestStream, + ) -> Result, Error> { + todo!() + } + + /// Polls requests and incoming `CONNECT` requests + fn poll_requests(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { + loop { + let conn = &mut self.conn; + // Handle any pending requests + while let Poll::Ready(Some((frame, mut stream))) = self.pending_bi.poll_next_unpin(cx) { + match frame { + Ok(Some(Frame::WebTransportStream(session_id))) => { + tracing::debug!("Got webtransport stream for {session_id:?}"); + } + frame => { + match conn.accept_with_frame(stream, frame) { + Ok(Some(request)) => { + self.accepted_requests.push(request); + } + Ok(None) => { + // Connection is closed + return Poll::Ready(Ok(None)); + } + Err(err) => { + tracing::debug!("Error accepting request: {err}"); + return Poll::Ready(Err(err)); + } + } + } + } + } + + // Accept *new* incoming bidirectional stream + // + // This could be an HTTP/3 request, or a webtransport bidi stream. + let stream = ready!(self.conn.poll_accept_bi(cx)?); + + if let Some(stream) = stream { + let stream = FrameStream::new(BufRecvStream::new(stream)); + self.pending_bi.push(AcceptBi::new(stream)); + // Go back around and poll the streams + } else { + // Connection is closed + return Poll::Ready(Ok(None)); + } + } + + // Peek the first varint to determine the type + } +} + /// WebTransport session driver. /// /// Maintains the session using the underlying HTTP/3 connection. @@ -156,7 +253,7 @@ where // Accept the incoming stream let stream = poll_fn(|cx| { let mut conn = self.conn.lock().unwrap(); - conn.poll_accept_request(cx) + conn.poll_accept_bi(cx) }) .await; @@ -319,7 +416,7 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { match &mut p.stream { Some((send, buf)) => { while buf.has_remaining() { - let n = ready!(send.poll_send(cx, buf))?; + ready!(send.poll_send(cx, buf))?; } let (send, buf) = p.stream.take().unwrap(); assert!(!buf.has_remaining()); From 0d49d38a8e0a235b351677072b6a5847fe27c3a4 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 20 Apr 2023 17:08:56 +0200 Subject: [PATCH 59/97] wip: new server and session api --- examples/webtransport_server.rs | 107 +++----- h3/src/connection.rs | 2 - h3/src/webtransport/accept.rs | 91 +++++++ h3/src/webtransport/server.rs | 441 +++++++++++++++++--------------- 4 files changed, 365 insertions(+), 276 deletions(-) create mode 100644 h3/src/webtransport/accept.rs diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index cf50f722..ad23b147 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -5,6 +5,7 @@ use bytes::{BufMut, BytesMut}; use futures::future::poll_fn; use futures::AsyncReadExt; use futures::AsyncWriteExt; +use h3::webtransport::server::WebTransportServer; use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; @@ -188,29 +189,12 @@ where // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them // to the webtransport session. + let conn = WebTransportServer::new(conn); + loop { - match conn.accept().await { - Ok(Some((req, stream))) => { - info!("new request: {:#?}", req); - - let ext = req.extensions(); - match req.method() { - &Method::CONNECT if ext.get::() == Some(&Protocol::WEB_TRANSPORT) => { - tracing::info!("Peer wants to initiate a webtransport session"); - - tracing::info!("Handing over connection to WebTransport"); - let session = WebTransportSession::accept(req, stream, conn).await?; - tracing::info!("Established webtransport session"); - // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. - // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. - handle_session_and_echo_all_inbound_messages(session).await?; - - return Ok(()); - } - _ => { - tracing::info!(?req, "Received request"); - } - } + match conn.accept_session().await { + Ok(Some((session))) => { + handle_session_and_echo_all_inbound_messages(session).await?; } // indicating no more streams to be received @@ -245,55 +229,48 @@ where C::RecvStream: Send + Unpin, { let session_id = session.session_id(); + tracing::info!("Handling session: {session_id:?}"); // This will open a bidirectional stream and send a message to the client right after connecting! - let (mut send, _read_bidi) = session.open_bi(session_id).await.unwrap(); - tracing::info!("Opening bidirectional stream"); - let bytes = Bytes::from("Hello from server"); - futures::AsyncWriteExt::write_all(&mut send, &bytes) - .await - .context("Failed to respond") - .unwrap(); - tokio::time::sleep(Duration::from_millis(1000)).await; loop { tokio::select! { - datagram = session.accept_datagram() => { - let datagram = datagram?; - if let Some((_, datagram)) = datagram { - tracing::info!("Responding with {datagram:?}"); - // Put something before to make sure encoding and decoding works and don't just - // pass through - let mut resp = BytesMut::from(&b"Response: "[..]); - resp.put(datagram); - - session.send_datagram(resp).unwrap(); - tracing::info!("Finished sending datagram"); - } - } - uni_stream = session.accept_uni() => { - let (id, mut stream) = uni_stream?.unwrap(); - tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); - - let mut message = Vec::new(); - - AsyncReadExt::read_to_end(&mut stream, &mut message).await.context("Failed to read message")?; - let message = Bytes::from(message); - - tracing::info!("Got message: {message:?}"); - let mut send = session.open_uni(session_id).await?; - tracing::info!("Opened unidirectional stream"); - - for byte in message { - tokio::time::sleep(Duration::from_millis(100)).await; - tracing::info!("Sending {byte:?}"); - send.write_all(&[byte][..]).await?; - } - // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; - tracing::info!("Wrote response"); - } + // datagram = session.accept_datagram() => { + // let datagram = datagram?; + // if let Some((_, datagram)) = datagram { + // tracing::info!("Responding with {datagram:?}"); + // // Put something before to make sure encoding and decoding works and don't just + // // pass through + // let mut resp = BytesMut::from(&b"Response: "[..]); + // resp.put(datagram); + + // session.send_datagram(resp).unwrap(); + // tracing::info!("Finished sending datagram"); + // } + // } + // uni_stream = session.accept_uni() => { + // let (id, mut stream) = uni_stream?.unwrap(); + // tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); + + // let mut message = Vec::new(); + + // AsyncReadExt::read_to_end(&mut stream, &mut message).await.context("Failed to read message")?; + // let message = Bytes::from(message); + + // tracing::info!("Got message: {message:?}"); + // let mut send = session.open_uni(session_id).await?; + // tracing::info!("Opened unidirectional stream"); + + // for byte in message { + // tokio::time::sleep(Duration::from_millis(100)).await; + // tracing::info!("Sending {byte:?}"); + // send.write_all(&[byte][..]).await?; + // } + // // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; + // tracing::info!("Wrote response"); + // } stream = session.accept_bi() => { - if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { + if let Some((mut send, mut recv)) = stream? { tracing::info!("Got bi stream"); while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { tracing::info!("Received data {:?}", &bytes); diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 91388a1a..2c2eb1f6 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -169,8 +169,6 @@ where .insert(SettingId::H3_DATAGRAM, config.enable_datagram as u64) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - tracing::debug!("Sending server settings: {settings:#x?}"); - if config.send_grease { tracing::debug!("Enabling send grease"); // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) diff --git a/h3/src/webtransport/accept.rs b/h3/src/webtransport/accept.rs new file mode 100644 index 00000000..54e91423 --- /dev/null +++ b/h3/src/webtransport/accept.rs @@ -0,0 +1,91 @@ +use bytes::Buf; +use futures_util::ready; +use futures_util::Future; +use std::mem; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use crate::frame::FrameStream; +use crate::frame::FrameStreamError; +use crate::proto::coding::BufExt; +use crate::proto::frame::Frame; +use crate::proto::frame::PayloadLen; +use crate::proto::stream::StreamType; +use crate::quic::BidiStream; +use crate::{ + buf::Cursor, + error::{Code, ErrorLevel}, + proto::varint::VarInt, + quic::Connection, + stream::BufRecvStream, + Error, +}; + +use super::stream::RecvStream; +use super::SessionId; + +/// Resolves to a request or a webtransport bi stream +#[pin_project::pin_project] +pub(crate) struct AcceptBi { + stream: Option>, +} + +impl AcceptBi { + pub(crate) fn new(stream: FrameStream) -> Self { + Self { + stream: Some(stream), + } + } +} + +const WEBTRANSPORT_BI: u64 = 0x41; + +impl Future for AcceptBi { + type Output = ( + Result>, FrameStreamError>, + FrameStream, + ); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let stream = self.stream.as_mut().unwrap(); + + let frame = ready!(stream.poll_next(cx)); + + let stream = Option::take(&mut self.stream).unwrap(); + + match frame { + Ok(Some(frame)) => Poll::Ready(((Ok(Some(frame))), stream)), + //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1 + //# If a client-initiated + //# stream terminates without enough of the HTTP message to provide a + //# complete response, the server SHOULD abort its response stream with + //# the error code H3_REQUEST_INCOMPLETE. + Ok(None) => Poll::Ready((Ok(None), stream)), + Err(err) => Poll::Ready(((Err(err)), stream)), + } + } +} + +enum DecodedStreamType { + WebTransport(SessionId), + Framed, +} + +/// Decodes the stream +fn decode_stream(mut buf: impl Buf) -> Result { + use crate::proto::coding::Decode; + let rem = buf.remaining() + 1; + + // Get the type + let ty = buf.get_var().map_err(|_| rem + 1)?; + + if ty == WEBTRANSPORT_BI { + let session_id = SessionId::decode(&mut buf).map_err(|_| rem + 2)?; + + Ok(DecodedStreamType::WebTransport(session_id)) + } else { + Ok(DecodedStreamType::Framed) + } +} diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 8a00fdbb..9521b3c8 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,9 +1,9 @@ //! Provides the server side WebTransport session use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, pin::Pin, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, MutexGuard}, task::{Context, Poll}, }; @@ -12,7 +12,7 @@ use crate::{ error::{Code, ErrorLevel}, frame::FrameStream, proto::{datagram::Datagram, frame::Frame, stream::Dir}, - quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, + quic::{self, BidiStream as _, OpenStreams, RecvStream as _, SendStream as _, WriteBuf}, request::ResolveRequest, server::{self, Connection, RequestStream}, stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, @@ -24,41 +24,160 @@ use http::{Method, Request, Response, StatusCode}; use tokio::sync::mpsc; use super::{ - accept::AcceptBi, + accept::AcceptBi as AcceptIncomingBi, stream::{self, RecvStream, SendStream}, SessionId, }; -enum Event { - Datagram(Bytes), - /// A new stream was opened for this session - Stream(Dir), -} - -/// Manages multiple connected WebTransport sessions. +/// Accepts and manages WebTransport sessions pub struct WebTransportServer { - conn: Connection, - - sessions: HashMap>, - - /// Bidirectional streams which have not yet read the first frame - /// This determines if it is a webtransport stream or a request stream - pending_bi: FuturesUnordered>, - - accepted_requests: Vec>, + inner: Arc>>, } impl WebTransportServer { /// Creates a new WebTransport server from an HTTP/3 connection. pub fn new(conn: Connection) -> Self { - Self { + // The peer is responsible for validating our side of the webtransport support. + // + // However, it is still advantageous to show a log on the server as (attempting) to + // establish a WebTransportSession without the proper h3 config is usually a mistake. + if !conn.inner.config.enable_webtransport { + tracing::warn!("Server does not support webtransport"); + } + + if !conn.inner.config.enable_datagram { + tracing::warn!("Server does not support datagrams"); + } + + if !conn.inner.config.enable_connect { + tracing::warn!("Server does not support CONNECT"); + } + + let opener = conn.inner.conn.lock().unwrap().opener(); + + let inner = WConn { conn, - sessions: HashMap::new(), + sessions: Default::default(), pending_bi: Default::default(), - accepted_requests: todo!(), + pending_requests: Default::default(), + bi_streams: Default::default(), + opener, + }; + + Self { + inner: Arc::new(Mutex::new(inner)), } } + /// Accepts the next WebTransport session. + pub async fn accept_session(&self) -> Result>, Error> { + loop { + let req = self.inner().pending_requests.pop(); + if let Some(req) = req { + let (request, mut stream) = req.resolve().await?; + + if request.method() != Method::CONNECT { + // TODO buffer this request for someone else + } + + { + let mut inner = self.inner(); + + let shared = inner.conn.shared_state().clone(); + { + let config = shared.write("Read WebTransport support").config; + + tracing::debug!("Client settings: {:#?}", config); + if !config.enable_webtransport { + return Err(inner.conn.close( + Code::H3_SETTINGS_ERROR, + "webtransport is not supported by client", + )); + } + + if !config.enable_datagram { + return Err(inner.conn.close( + Code::H3_SETTINGS_ERROR, + "datagrams are not supported by client", + )); + } + } + + // Create a channel to communicate with the session + + let session_id = stream.send_id().into(); + + // Ensure the session is inserted **before** sending and awaiting the response + // + // This is because streams can be opened by the client before the response it received + inner.sessions.insert(session_id); + } + // Respond to the CONNECT request. + + //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 + let response = if validate_wt_connect(&request) { + Response::builder() + // This is the only header that chrome cares about. + .header("sec-webtransport-http3-draft", "draft02") + .status(StatusCode::OK) + .body(()) + .unwrap() + } else { + Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(()) + .unwrap() + }; + + tracing::info!("Sending response: {response:?}"); + stream.send_response(response).await?; + + tracing::info!("Stream id: {}", stream.id()); + let session_id = stream.send_id().into(); + tracing::info!("Established new WebTransport session with id {session_id:?}"); + + let session = WebTransportSession { + session_id, + conn: self.inner.clone(), + connect_stream: stream, + }; + + return Ok(Some(session)); + } else { + match poll_fn(|cx| self.inner().poll_incoming_bidi(cx)).await { + Ok(Some(())) => {} + Ok(None) => return Ok(None), + Err(err) => return Err(err), + } + } + } + } + + fn inner(&self) -> MutexGuard<'_, WConn> { + self.inner.lock().unwrap() + } +} + +/// Manages multiple connected WebTransport sessions. +struct WConn { + conn: Connection, + + sessions: HashSet, + + /// Bidirectional streams which have not yet read the first frame + /// This determines if it is a webtransport stream or a request stream + pending_bi: FuturesUnordered>, + + pending_requests: Vec>, + bi_streams: Vec<( + SessionId, + SendStream, + RecvStream, + )>, + opener: C::OpenStreams, +} + +impl WConn { /// Accepts the next request. /// /// *CONNECT* requests for the WebTransport protocol are intercepted. Use [`Self::accept_session`] @@ -66,29 +185,32 @@ impl WebTransportServer { todo!() } - /// Accepts the next WebTransport session. - pub fn accept_session( - &self, - request: &Request<()>, - mut stream: RequestStream, - ) -> Result, Error> { - todo!() - } - - /// Polls requests and incoming `CONNECT` requests - fn poll_requests(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { + /// Polls for the next incoming request or bidi stream + fn poll_incoming_bidi(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { loop { let conn = &mut self.conn; // Handle any pending requests - while let Poll::Ready(Some((frame, mut stream))) = self.pending_bi.poll_next_unpin(cx) { + if let Poll::Ready(Some((frame, stream))) = self.pending_bi.poll_next_unpin(cx) { match frame { Ok(Some(Frame::WebTransportStream(session_id))) => { tracing::debug!("Got webtransport stream for {session_id:?}"); + + if let Some(session) = self.sessions.get(&session_id) { + let (send, recv) = stream.into_inner().split(); + self.bi_streams.push(( + session_id, + SendStream::new(send), + RecvStream::new(recv), + )); + } + + return Poll::Ready(Ok(Some(()))); } frame => { match conn.accept_with_frame(stream, frame) { Ok(Some(request)) => { - self.accepted_requests.push(request); + self.pending_requests.push(request); + return Poll::Ready(Ok(Some(()))); } Ok(None) => { // Connection is closed @@ -110,7 +232,7 @@ impl WebTransportServer { if let Some(stream) = stream { let stream = FrameStream::new(BufRecvStream::new(stream)); - self.pending_bi.push(AcceptBi::new(stream)); + self.pending_bi.push(AcceptIncomingBi::new(stream)); // Go back around and poll the streams } else { // Connection is closed @@ -133,101 +255,20 @@ where { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: SessionId, - conn: Mutex>, + conn: Arc>>, connect_stream: RequestStream, - opener: Mutex, } impl WebTransportSession where C: quic::Connection, { - /// Accepts a *CONNECT* request for establishing a WebTransport session. - /// - /// TODO: is the API or the user responsible for validating the CONNECT request? - pub async fn accept( - request: Request<()>, - mut stream: RequestStream, - mut conn: Connection, - ) -> Result { - let shared = conn.shared_state().clone(); - { - let config = shared.write("Read WebTransport support").config; - - tracing::debug!("Client settings: {:#?}", config); - if !config.enable_webtransport { - return Err(conn.close( - Code::H3_SETTINGS_ERROR, - "webtransport is not supported by client", - )); - } - - if !config.enable_datagram { - return Err(conn.close( - Code::H3_SETTINGS_ERROR, - "datagrams are not supported by client", - )); - } - } - - tracing::debug!("Validated client webtransport support"); - - // The peer is responsible for validating our side of the webtransport support. - // - // However, it is still advantageous to show a log on the server as (attempting) to - // establish a WebTransportSession without the proper h3 config is usually a mistake. - if !conn.inner.config.enable_webtransport { - tracing::warn!("Server does not support webtransport"); - } - - if !conn.inner.config.enable_datagram { - tracing::warn!("Server does not support datagrams"); - } - - if !conn.inner.config.enable_connect { - tracing::warn!("Server does not support CONNECT"); - } - - // Respond to the CONNECT request. - - //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 - let response = if validate_wt_connect(&request) { - Response::builder() - // This is the only header that chrome cares about. - .header("sec-webtransport-http3-draft", "draft02") - .status(StatusCode::OK) - .body(()) - .unwrap() - } else { - Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(()) - .unwrap() - }; - - tracing::info!("Sending response: {response:?}"); - stream.send_response(response).await?; - - tracing::info!("Stream id: {}", stream.id()); - let session_id = stream.send_id().into(); - tracing::info!("Established new WebTransport session with id {session_id:?}"); - let conn_inner = conn.inner.conn.lock().unwrap(); - let opener = Mutex::new(conn_inner.opener()); - drop(conn_inner); - - Ok(Self { - session_id, - opener, - conn: Mutex::new(conn), - connect_stream: stream, - }) - } - /// Receive a datagram from the client pub fn accept_datagram(&self) -> ReadDatagram { - ReadDatagram { - conn: self.conn.lock().unwrap().inner.conn.clone(), - } + todo!() + // ReadDatagram { + // conn: self.conn.lock().unwrap().inner.conn.clone(), + // } } /// Sends a datagram @@ -237,6 +278,7 @@ where self.conn .lock() .unwrap() + .conn .send_datagram(self.connect_stream.id(), data)?; Ok(()) @@ -244,85 +286,22 @@ where /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. pub fn accept_uni(&self) -> AcceptUni { - AcceptUni { conn: &self.conn } + todo!() + // AcceptUni { conn: &self.conn } } /// Accepts an incoming bidirectional stream or request - pub async fn accept_bi(&self) -> Result>, Error> { - // Get the next stream - // Accept the incoming stream - let stream = poll_fn(|cx| { - let mut conn = self.conn.lock().unwrap(); - conn.poll_accept_bi(cx) - }) - .await; - - tracing::debug!("Received biderectional stream"); - - let mut stream = match stream { - Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), - Ok(None) => { - // We always send a last GoAway frame to the client, so it knows which was the last - // non-rejected request. - // self.shutdown(0).await?; - todo!("shutdown"); - // return Ok(None); - } - Err(err) => { - match err.inner.kind { - crate::error::Kind::Closed => return Ok(None), - crate::error::Kind::Application { - code, - reason, - level: ErrorLevel::ConnectionError, - } => { - return Err(self.conn.lock().unwrap().close( - code, - reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), - )) - } - _ => return Err(err), - }; - } - }; - - tracing::debug!("Reading first frame"); - // Read the first frame. - // - // This will determine if it is a webtransport bi-stream or a request stream - let frame = poll_fn(|cx| stream.poll_next(cx)).await; - - match frame { - Ok(None) => Ok(None), - Ok(Some(Frame::WebTransportStream(session_id))) => { - tracing::info!("Got webtransport stream"); - // Take the stream out of the framed reader and split it in half like Paul Allen - let (send, recv) = stream.into_inner().split(); - let send = SendStream::new(send); - let recv = RecvStream::new(recv); - - Ok(Some(AcceptedBi::BidiStream(session_id, send, recv))) - } - // Make the underlying HTTP/3 connection handle the rest - frame => { - let req = { - let mut conn = self.conn.lock().unwrap(); - conn.accept_with_frame(stream, frame)? - }; - if let Some(req) = req { - let (req, resp) = req.resolve().await?; - Ok(Some(AcceptedBi::Request(req, resp))) - } else { - Ok(None) - } - } + pub fn accept_bi(&self) -> AcceptBi { + AcceptBi { + conn: &self.conn, + session_id: self.session_id, } } /// Open a new bidirectional stream pub fn open_bi(&self, session_id: SessionId) -> OpenBi { OpenBi { - opener: &self.opener, + conn: &self.conn, stream: None, session_id, } @@ -331,7 +310,7 @@ where /// Open a new unidirectional stream pub fn open_uni(&self, session_id: SessionId) -> OpenUni { OpenUni { - opener: &self.opener, + conn: &self.conn, stream: None, session_id, } @@ -359,7 +338,7 @@ type PendingUniStreams = ( #[pin_project::pin_project] /// Future for opening a bidi stream pub struct OpenBi<'a, C: quic::Connection> { - opener: &'a Mutex, + conn: &'a Mutex>, stream: Option>, session_id: SessionId, } @@ -383,7 +362,8 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { return Poll::Ready(Ok((send, recv))); } None => { - let mut opener = (*p.opener).lock().unwrap(); + let mut conn = (*p.conn).lock().unwrap(); + let opener = &mut conn.opener; // Open the stream first let res = ready!(opener.poll_open_bidi(cx))?; let (send, recv) = BufRecvStream::new(res).split(); @@ -402,7 +382,7 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { #[pin_project::pin_project] /// Future for opening a uni stream pub struct OpenUni<'a, C: quic::Connection> { - opener: &'a Mutex, + conn: &'a Mutex>, stream: Option>, session_id: SessionId, } @@ -423,7 +403,9 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { return Poll::Ready(Ok(send)); } None => { - let mut opener = (*p.opener).lock().unwrap(); + let mut conn = (*p.conn).lock().unwrap(); + let opener = &mut conn.opener; + let send = ready!(opener.poll_open_uni(cx))?; let send = BufRecvStream::new(send); let send = SendStream::new(send); @@ -480,12 +462,52 @@ where } } +/// Accepts the next incoming bidirectional stream +pub struct AcceptBi<'a, C> +where + C: quic::Connection, +{ + conn: &'a Mutex>, + + session_id: SessionId, +} + +impl<'a, C> Future for AcceptBi<'a, C> +where + C: quic::Connection, +{ + type Output = Result, RecvStream)>, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let mut inner = self.conn.lock().unwrap(); + + if let Some(idx) = inner + .bi_streams + .iter() + .position(|(id, _, _)| *id == self.session_id) + { + let (_, send, recv) = inner.bi_streams.remove(idx); + return Poll::Ready(Ok(Some((send, recv)))); + } + + match ready!(inner.poll_incoming_bidi(cx)) { + Ok(Some(())) => {} + Ok(None) => return Poll::Ready(Ok(None)), + Err(err) => return Poll::Ready(Err(err)), + } + } + } +} + /// Future for [`WebTransportSession::accept_uni`] pub struct AcceptUni<'a, C> where C: quic::Connection, { - conn: &'a Mutex>, + conn: &'a Mutex>, + + session_id: SessionId, } impl<'a, C> Future for AcceptUni<'a, C> @@ -497,19 +519,20 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); - let mut conn = self.conn.lock().unwrap(); - conn.inner.poll_accept_recv(cx)?; + todo!() + // let mut conn = self.conn.lock().unwrap(); + // conn.poll_accept_recv(cx)?; - // Get the currently available streams - let streams = conn.inner.accepted_streams_mut(); - if let Some(v) = streams.uni_streams.pop() { - tracing::info!("Got uni stream"); - return Poll::Ready(Ok(Some(v))); - } + // // Get the currently available streams + // let streams = conn.inner.accepted_streams_mut(); + // if let Some(v) = streams.uni_streams.pop() { + // tracing::info!("Got uni stream"); + // return Poll::Ready(Ok(Some(v))); + // } - tracing::debug!("Waiting on incoming streams"); + // tracing::debug!("Waiting on incoming streams"); - Poll::Pending + // Poll::Pending } } From 6a355a9b72d04a3e1f10232d50768b0f95ee49ce Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Thu, 20 Apr 2023 21:06:08 -0400 Subject: [PATCH 60/97] Revert "wip: new server and session api" This reverts commit 0d49d38a8e0a235b351677072b6a5847fe27c3a4. --- examples/webtransport_server.rs | 107 +++++--- h3/src/connection.rs | 2 + h3/src/webtransport/accept.rs | 91 ------- h3/src/webtransport/server.rs | 441 +++++++++++++++----------------- 4 files changed, 276 insertions(+), 365 deletions(-) delete mode 100644 h3/src/webtransport/accept.rs diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index ad23b147..cf50f722 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -5,7 +5,6 @@ use bytes::{BufMut, BytesMut}; use futures::future::poll_fn; use futures::AsyncReadExt; use futures::AsyncWriteExt; -use h3::webtransport::server::WebTransportServer; use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; @@ -189,12 +188,29 @@ where // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them // to the webtransport session. - let conn = WebTransportServer::new(conn); - loop { - match conn.accept_session().await { - Ok(Some((session))) => { - handle_session_and_echo_all_inbound_messages(session).await?; + match conn.accept().await { + Ok(Some((req, stream))) => { + info!("new request: {:#?}", req); + + let ext = req.extensions(); + match req.method() { + &Method::CONNECT if ext.get::() == Some(&Protocol::WEB_TRANSPORT) => { + tracing::info!("Peer wants to initiate a webtransport session"); + + tracing::info!("Handing over connection to WebTransport"); + let session = WebTransportSession::accept(req, stream, conn).await?; + tracing::info!("Established webtransport session"); + // 4. Get datagrams, bidirectional streams, and unidirectional streams and wait for client requests here. + // h3_conn needs to handover the datagrams, bidirectional streams, and unidirectional streams to the webtransport session. + handle_session_and_echo_all_inbound_messages(session).await?; + + return Ok(()); + } + _ => { + tracing::info!(?req, "Received request"); + } + } } // indicating no more streams to be received @@ -229,48 +245,55 @@ where C::RecvStream: Send + Unpin, { let session_id = session.session_id(); - tracing::info!("Handling session: {session_id:?}"); // This will open a bidirectional stream and send a message to the client right after connecting! + let (mut send, _read_bidi) = session.open_bi(session_id).await.unwrap(); + tracing::info!("Opening bidirectional stream"); + let bytes = Bytes::from("Hello from server"); + futures::AsyncWriteExt::write_all(&mut send, &bytes) + .await + .context("Failed to respond") + .unwrap(); + tokio::time::sleep(Duration::from_millis(1000)).await; loop { tokio::select! { - // datagram = session.accept_datagram() => { - // let datagram = datagram?; - // if let Some((_, datagram)) = datagram { - // tracing::info!("Responding with {datagram:?}"); - // // Put something before to make sure encoding and decoding works and don't just - // // pass through - // let mut resp = BytesMut::from(&b"Response: "[..]); - // resp.put(datagram); - - // session.send_datagram(resp).unwrap(); - // tracing::info!("Finished sending datagram"); - // } - // } - // uni_stream = session.accept_uni() => { - // let (id, mut stream) = uni_stream?.unwrap(); - // tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); - - // let mut message = Vec::new(); - - // AsyncReadExt::read_to_end(&mut stream, &mut message).await.context("Failed to read message")?; - // let message = Bytes::from(message); - - // tracing::info!("Got message: {message:?}"); - // let mut send = session.open_uni(session_id).await?; - // tracing::info!("Opened unidirectional stream"); - - // for byte in message { - // tokio::time::sleep(Duration::from_millis(100)).await; - // tracing::info!("Sending {byte:?}"); - // send.write_all(&[byte][..]).await?; - // } - // // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; - // tracing::info!("Wrote response"); - // } + datagram = session.accept_datagram() => { + let datagram = datagram?; + if let Some((_, datagram)) = datagram { + tracing::info!("Responding with {datagram:?}"); + // Put something before to make sure encoding and decoding works and don't just + // pass through + let mut resp = BytesMut::from(&b"Response: "[..]); + resp.put(datagram); + + session.send_datagram(resp).unwrap(); + tracing::info!("Finished sending datagram"); + } + } + uni_stream = session.accept_uni() => { + let (id, mut stream) = uni_stream?.unwrap(); + tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); + + let mut message = Vec::new(); + + AsyncReadExt::read_to_end(&mut stream, &mut message).await.context("Failed to read message")?; + let message = Bytes::from(message); + + tracing::info!("Got message: {message:?}"); + let mut send = session.open_uni(session_id).await?; + tracing::info!("Opened unidirectional stream"); + + for byte in message { + tokio::time::sleep(Duration::from_millis(100)).await; + tracing::info!("Sending {byte:?}"); + send.write_all(&[byte][..]).await?; + } + // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; + tracing::info!("Wrote response"); + } stream = session.accept_bi() => { - if let Some((mut send, mut recv)) = stream? { + if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { tracing::info!("Got bi stream"); while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { tracing::info!("Received data {:?}", &bytes); diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 2c2eb1f6..91388a1a 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -169,6 +169,8 @@ where .insert(SettingId::H3_DATAGRAM, config.enable_datagram as u64) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; + tracing::debug!("Sending server settings: {settings:#x?}"); + if config.send_grease { tracing::debug!("Enabling send grease"); // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) diff --git a/h3/src/webtransport/accept.rs b/h3/src/webtransport/accept.rs deleted file mode 100644 index 54e91423..00000000 --- a/h3/src/webtransport/accept.rs +++ /dev/null @@ -1,91 +0,0 @@ -use bytes::Buf; -use futures_util::ready; -use futures_util::Future; -use std::mem; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - -use crate::frame::FrameStream; -use crate::frame::FrameStreamError; -use crate::proto::coding::BufExt; -use crate::proto::frame::Frame; -use crate::proto::frame::PayloadLen; -use crate::proto::stream::StreamType; -use crate::quic::BidiStream; -use crate::{ - buf::Cursor, - error::{Code, ErrorLevel}, - proto::varint::VarInt, - quic::Connection, - stream::BufRecvStream, - Error, -}; - -use super::stream::RecvStream; -use super::SessionId; - -/// Resolves to a request or a webtransport bi stream -#[pin_project::pin_project] -pub(crate) struct AcceptBi { - stream: Option>, -} - -impl AcceptBi { - pub(crate) fn new(stream: FrameStream) -> Self { - Self { - stream: Some(stream), - } - } -} - -const WEBTRANSPORT_BI: u64 = 0x41; - -impl Future for AcceptBi { - type Output = ( - Result>, FrameStreamError>, - FrameStream, - ); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let stream = self.stream.as_mut().unwrap(); - - let frame = ready!(stream.poll_next(cx)); - - let stream = Option::take(&mut self.stream).unwrap(); - - match frame { - Ok(Some(frame)) => Poll::Ready(((Ok(Some(frame))), stream)), - //= https://www.rfc-editor.org/rfc/rfc9114#section-4.1 - //# If a client-initiated - //# stream terminates without enough of the HTTP message to provide a - //# complete response, the server SHOULD abort its response stream with - //# the error code H3_REQUEST_INCOMPLETE. - Ok(None) => Poll::Ready((Ok(None), stream)), - Err(err) => Poll::Ready(((Err(err)), stream)), - } - } -} - -enum DecodedStreamType { - WebTransport(SessionId), - Framed, -} - -/// Decodes the stream -fn decode_stream(mut buf: impl Buf) -> Result { - use crate::proto::coding::Decode; - let rem = buf.remaining() + 1; - - // Get the type - let ty = buf.get_var().map_err(|_| rem + 1)?; - - if ty == WEBTRANSPORT_BI { - let session_id = SessionId::decode(&mut buf).map_err(|_| rem + 2)?; - - Ok(DecodedStreamType::WebTransport(session_id)) - } else { - Ok(DecodedStreamType::Framed) - } -} diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 9521b3c8..8a00fdbb 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,9 +1,9 @@ //! Provides the server side WebTransport session use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, pin::Pin, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex}, task::{Context, Poll}, }; @@ -12,7 +12,7 @@ use crate::{ error::{Code, ErrorLevel}, frame::FrameStream, proto::{datagram::Datagram, frame::Frame, stream::Dir}, - quic::{self, BidiStream as _, OpenStreams, RecvStream as _, SendStream as _, WriteBuf}, + quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, request::ResolveRequest, server::{self, Connection, RequestStream}, stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, @@ -24,160 +24,41 @@ use http::{Method, Request, Response, StatusCode}; use tokio::sync::mpsc; use super::{ - accept::AcceptBi as AcceptIncomingBi, + accept::AcceptBi, stream::{self, RecvStream, SendStream}, SessionId, }; -/// Accepts and manages WebTransport sessions -pub struct WebTransportServer { - inner: Arc>>, -} - -impl WebTransportServer { - /// Creates a new WebTransport server from an HTTP/3 connection. - pub fn new(conn: Connection) -> Self { - // The peer is responsible for validating our side of the webtransport support. - // - // However, it is still advantageous to show a log on the server as (attempting) to - // establish a WebTransportSession without the proper h3 config is usually a mistake. - if !conn.inner.config.enable_webtransport { - tracing::warn!("Server does not support webtransport"); - } - - if !conn.inner.config.enable_datagram { - tracing::warn!("Server does not support datagrams"); - } - - if !conn.inner.config.enable_connect { - tracing::warn!("Server does not support CONNECT"); - } - - let opener = conn.inner.conn.lock().unwrap().opener(); - - let inner = WConn { - conn, - sessions: Default::default(), - pending_bi: Default::default(), - pending_requests: Default::default(), - bi_streams: Default::default(), - opener, - }; - - Self { - inner: Arc::new(Mutex::new(inner)), - } - } - - /// Accepts the next WebTransport session. - pub async fn accept_session(&self) -> Result>, Error> { - loop { - let req = self.inner().pending_requests.pop(); - if let Some(req) = req { - let (request, mut stream) = req.resolve().await?; - - if request.method() != Method::CONNECT { - // TODO buffer this request for someone else - } - - { - let mut inner = self.inner(); - - let shared = inner.conn.shared_state().clone(); - { - let config = shared.write("Read WebTransport support").config; - - tracing::debug!("Client settings: {:#?}", config); - if !config.enable_webtransport { - return Err(inner.conn.close( - Code::H3_SETTINGS_ERROR, - "webtransport is not supported by client", - )); - } - - if !config.enable_datagram { - return Err(inner.conn.close( - Code::H3_SETTINGS_ERROR, - "datagrams are not supported by client", - )); - } - } - - // Create a channel to communicate with the session - - let session_id = stream.send_id().into(); - - // Ensure the session is inserted **before** sending and awaiting the response - // - // This is because streams can be opened by the client before the response it received - inner.sessions.insert(session_id); - } - // Respond to the CONNECT request. - - //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 - let response = if validate_wt_connect(&request) { - Response::builder() - // This is the only header that chrome cares about. - .header("sec-webtransport-http3-draft", "draft02") - .status(StatusCode::OK) - .body(()) - .unwrap() - } else { - Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(()) - .unwrap() - }; - - tracing::info!("Sending response: {response:?}"); - stream.send_response(response).await?; - - tracing::info!("Stream id: {}", stream.id()); - let session_id = stream.send_id().into(); - tracing::info!("Established new WebTransport session with id {session_id:?}"); - - let session = WebTransportSession { - session_id, - conn: self.inner.clone(), - connect_stream: stream, - }; - - return Ok(Some(session)); - } else { - match poll_fn(|cx| self.inner().poll_incoming_bidi(cx)).await { - Ok(Some(())) => {} - Ok(None) => return Ok(None), - Err(err) => return Err(err), - } - } - } - } - - fn inner(&self) -> MutexGuard<'_, WConn> { - self.inner.lock().unwrap() - } +enum Event { + Datagram(Bytes), + /// A new stream was opened for this session + Stream(Dir), } /// Manages multiple connected WebTransport sessions. -struct WConn { +pub struct WebTransportServer { conn: Connection, - sessions: HashSet, + sessions: HashMap>, /// Bidirectional streams which have not yet read the first frame /// This determines if it is a webtransport stream or a request stream - pending_bi: FuturesUnordered>, + pending_bi: FuturesUnordered>, - pending_requests: Vec>, - bi_streams: Vec<( - SessionId, - SendStream, - RecvStream, - )>, - opener: C::OpenStreams, + accepted_requests: Vec>, } -impl WConn { +impl WebTransportServer { + /// Creates a new WebTransport server from an HTTP/3 connection. + pub fn new(conn: Connection) -> Self { + Self { + conn, + sessions: HashMap::new(), + pending_bi: Default::default(), + accepted_requests: todo!(), + } + } + /// Accepts the next request. /// /// *CONNECT* requests for the WebTransport protocol are intercepted. Use [`Self::accept_session`] @@ -185,32 +66,29 @@ impl WConn { todo!() } - /// Polls for the next incoming request or bidi stream - fn poll_incoming_bidi(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { + /// Accepts the next WebTransport session. + pub fn accept_session( + &self, + request: &Request<()>, + mut stream: RequestStream, + ) -> Result, Error> { + todo!() + } + + /// Polls requests and incoming `CONNECT` requests + fn poll_requests(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { loop { let conn = &mut self.conn; // Handle any pending requests - if let Poll::Ready(Some((frame, stream))) = self.pending_bi.poll_next_unpin(cx) { + while let Poll::Ready(Some((frame, mut stream))) = self.pending_bi.poll_next_unpin(cx) { match frame { Ok(Some(Frame::WebTransportStream(session_id))) => { tracing::debug!("Got webtransport stream for {session_id:?}"); - - if let Some(session) = self.sessions.get(&session_id) { - let (send, recv) = stream.into_inner().split(); - self.bi_streams.push(( - session_id, - SendStream::new(send), - RecvStream::new(recv), - )); - } - - return Poll::Ready(Ok(Some(()))); } frame => { match conn.accept_with_frame(stream, frame) { Ok(Some(request)) => { - self.pending_requests.push(request); - return Poll::Ready(Ok(Some(()))); + self.accepted_requests.push(request); } Ok(None) => { // Connection is closed @@ -232,7 +110,7 @@ impl WConn { if let Some(stream) = stream { let stream = FrameStream::new(BufRecvStream::new(stream)); - self.pending_bi.push(AcceptIncomingBi::new(stream)); + self.pending_bi.push(AcceptBi::new(stream)); // Go back around and poll the streams } else { // Connection is closed @@ -255,20 +133,101 @@ where { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: SessionId, - conn: Arc>>, + conn: Mutex>, connect_stream: RequestStream, + opener: Mutex, } impl WebTransportSession where C: quic::Connection, { + /// Accepts a *CONNECT* request for establishing a WebTransport session. + /// + /// TODO: is the API or the user responsible for validating the CONNECT request? + pub async fn accept( + request: Request<()>, + mut stream: RequestStream, + mut conn: Connection, + ) -> Result { + let shared = conn.shared_state().clone(); + { + let config = shared.write("Read WebTransport support").config; + + tracing::debug!("Client settings: {:#?}", config); + if !config.enable_webtransport { + return Err(conn.close( + Code::H3_SETTINGS_ERROR, + "webtransport is not supported by client", + )); + } + + if !config.enable_datagram { + return Err(conn.close( + Code::H3_SETTINGS_ERROR, + "datagrams are not supported by client", + )); + } + } + + tracing::debug!("Validated client webtransport support"); + + // The peer is responsible for validating our side of the webtransport support. + // + // However, it is still advantageous to show a log on the server as (attempting) to + // establish a WebTransportSession without the proper h3 config is usually a mistake. + if !conn.inner.config.enable_webtransport { + tracing::warn!("Server does not support webtransport"); + } + + if !conn.inner.config.enable_datagram { + tracing::warn!("Server does not support datagrams"); + } + + if !conn.inner.config.enable_connect { + tracing::warn!("Server does not support CONNECT"); + } + + // Respond to the CONNECT request. + + //= https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3 + let response = if validate_wt_connect(&request) { + Response::builder() + // This is the only header that chrome cares about. + .header("sec-webtransport-http3-draft", "draft02") + .status(StatusCode::OK) + .body(()) + .unwrap() + } else { + Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(()) + .unwrap() + }; + + tracing::info!("Sending response: {response:?}"); + stream.send_response(response).await?; + + tracing::info!("Stream id: {}", stream.id()); + let session_id = stream.send_id().into(); + tracing::info!("Established new WebTransport session with id {session_id:?}"); + let conn_inner = conn.inner.conn.lock().unwrap(); + let opener = Mutex::new(conn_inner.opener()); + drop(conn_inner); + + Ok(Self { + session_id, + opener, + conn: Mutex::new(conn), + connect_stream: stream, + }) + } + /// Receive a datagram from the client pub fn accept_datagram(&self) -> ReadDatagram { - todo!() - // ReadDatagram { - // conn: self.conn.lock().unwrap().inner.conn.clone(), - // } + ReadDatagram { + conn: self.conn.lock().unwrap().inner.conn.clone(), + } } /// Sends a datagram @@ -278,7 +237,6 @@ where self.conn .lock() .unwrap() - .conn .send_datagram(self.connect_stream.id(), data)?; Ok(()) @@ -286,22 +244,85 @@ where /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. pub fn accept_uni(&self) -> AcceptUni { - todo!() - // AcceptUni { conn: &self.conn } + AcceptUni { conn: &self.conn } } /// Accepts an incoming bidirectional stream or request - pub fn accept_bi(&self) -> AcceptBi { - AcceptBi { - conn: &self.conn, - session_id: self.session_id, + pub async fn accept_bi(&self) -> Result>, Error> { + // Get the next stream + // Accept the incoming stream + let stream = poll_fn(|cx| { + let mut conn = self.conn.lock().unwrap(); + conn.poll_accept_bi(cx) + }) + .await; + + tracing::debug!("Received biderectional stream"); + + let mut stream = match stream { + Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), + Ok(None) => { + // We always send a last GoAway frame to the client, so it knows which was the last + // non-rejected request. + // self.shutdown(0).await?; + todo!("shutdown"); + // return Ok(None); + } + Err(err) => { + match err.inner.kind { + crate::error::Kind::Closed => return Ok(None), + crate::error::Kind::Application { + code, + reason, + level: ErrorLevel::ConnectionError, + } => { + return Err(self.conn.lock().unwrap().close( + code, + reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + )) + } + _ => return Err(err), + }; + } + }; + + tracing::debug!("Reading first frame"); + // Read the first frame. + // + // This will determine if it is a webtransport bi-stream or a request stream + let frame = poll_fn(|cx| stream.poll_next(cx)).await; + + match frame { + Ok(None) => Ok(None), + Ok(Some(Frame::WebTransportStream(session_id))) => { + tracing::info!("Got webtransport stream"); + // Take the stream out of the framed reader and split it in half like Paul Allen + let (send, recv) = stream.into_inner().split(); + let send = SendStream::new(send); + let recv = RecvStream::new(recv); + + Ok(Some(AcceptedBi::BidiStream(session_id, send, recv))) + } + // Make the underlying HTTP/3 connection handle the rest + frame => { + let req = { + let mut conn = self.conn.lock().unwrap(); + conn.accept_with_frame(stream, frame)? + }; + if let Some(req) = req { + let (req, resp) = req.resolve().await?; + Ok(Some(AcceptedBi::Request(req, resp))) + } else { + Ok(None) + } + } } } /// Open a new bidirectional stream pub fn open_bi(&self, session_id: SessionId) -> OpenBi { OpenBi { - conn: &self.conn, + opener: &self.opener, stream: None, session_id, } @@ -310,7 +331,7 @@ where /// Open a new unidirectional stream pub fn open_uni(&self, session_id: SessionId) -> OpenUni { OpenUni { - conn: &self.conn, + opener: &self.opener, stream: None, session_id, } @@ -338,7 +359,7 @@ type PendingUniStreams = ( #[pin_project::pin_project] /// Future for opening a bidi stream pub struct OpenBi<'a, C: quic::Connection> { - conn: &'a Mutex>, + opener: &'a Mutex, stream: Option>, session_id: SessionId, } @@ -362,8 +383,7 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { return Poll::Ready(Ok((send, recv))); } None => { - let mut conn = (*p.conn).lock().unwrap(); - let opener = &mut conn.opener; + let mut opener = (*p.opener).lock().unwrap(); // Open the stream first let res = ready!(opener.poll_open_bidi(cx))?; let (send, recv) = BufRecvStream::new(res).split(); @@ -382,7 +402,7 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { #[pin_project::pin_project] /// Future for opening a uni stream pub struct OpenUni<'a, C: quic::Connection> { - conn: &'a Mutex>, + opener: &'a Mutex, stream: Option>, session_id: SessionId, } @@ -403,9 +423,7 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { return Poll::Ready(Ok(send)); } None => { - let mut conn = (*p.conn).lock().unwrap(); - let opener = &mut conn.opener; - + let mut opener = (*p.opener).lock().unwrap(); let send = ready!(opener.poll_open_uni(cx))?; let send = BufRecvStream::new(send); let send = SendStream::new(send); @@ -462,52 +480,12 @@ where } } -/// Accepts the next incoming bidirectional stream -pub struct AcceptBi<'a, C> -where - C: quic::Connection, -{ - conn: &'a Mutex>, - - session_id: SessionId, -} - -impl<'a, C> Future for AcceptBi<'a, C> -where - C: quic::Connection, -{ - type Output = Result, RecvStream)>, Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - let mut inner = self.conn.lock().unwrap(); - - if let Some(idx) = inner - .bi_streams - .iter() - .position(|(id, _, _)| *id == self.session_id) - { - let (_, send, recv) = inner.bi_streams.remove(idx); - return Poll::Ready(Ok(Some((send, recv)))); - } - - match ready!(inner.poll_incoming_bidi(cx)) { - Ok(Some(())) => {} - Ok(None) => return Poll::Ready(Ok(None)), - Err(err) => return Poll::Ready(Err(err)), - } - } - } -} - /// Future for [`WebTransportSession::accept_uni`] pub struct AcceptUni<'a, C> where C: quic::Connection, { - conn: &'a Mutex>, - - session_id: SessionId, + conn: &'a Mutex>, } impl<'a, C> Future for AcceptUni<'a, C> @@ -519,20 +497,19 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); - todo!() - // let mut conn = self.conn.lock().unwrap(); - // conn.poll_accept_recv(cx)?; + let mut conn = self.conn.lock().unwrap(); + conn.inner.poll_accept_recv(cx)?; - // // Get the currently available streams - // let streams = conn.inner.accepted_streams_mut(); - // if let Some(v) = streams.uni_streams.pop() { - // tracing::info!("Got uni stream"); - // return Poll::Ready(Ok(Some(v))); - // } + // Get the currently available streams + let streams = conn.inner.accepted_streams_mut(); + if let Some(v) = streams.uni_streams.pop() { + tracing::info!("Got uni stream"); + return Poll::Ready(Ok(Some(v))); + } - // tracing::debug!("Waiting on incoming streams"); + tracing::debug!("Waiting on incoming streams"); - // Poll::Pending + Poll::Pending } } From 08e39eb3e211731bd7a3726f2a07192746ddcc85 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Thu, 20 Apr 2023 21:06:29 -0400 Subject: [PATCH 61/97] Revert "wip: multi session support" This reverts commit 9ec801279d2cb436b0072c4b0373036c06efb43a. --- h3/src/proto/frame.rs | 10 +++- h3/src/proto/stream.rs | 2 +- h3/src/server.rs | 19 ++++-- h3/src/webtransport/mod.rs | 1 - h3/src/webtransport/server.rs | 105 ++-------------------------------- 5 files changed, 28 insertions(+), 109 deletions(-) diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 6d7391ce..f497c586 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -82,6 +82,14 @@ impl Frame { let remaining = buf.remaining(); let ty = FrameType::decode(buf).map_err(|_| FrameError::Incomplete(remaining + 1))?; + // Webtransport streams need special handling as they have no length. + // + // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.2 + if ty == FrameType::WEBTRANSPORT_BI_STREAM { + tracing::trace!("webtransport frame"); + return Ok(Frame::WebTransportStream(SessionId::decode(buf)?)); + } + let len = buf .get_var() .map_err(|_| FrameError::Incomplete(remaining + 1))?; @@ -107,7 +115,7 @@ impl Frame { | FrameType::H2_PING | FrameType::H2_WINDOW_UPDATE | FrameType::H2_CONTINUATION => Err(FrameError::UnsupportedFrame(ty.0)), - FrameType::DATA => unreachable!(), + FrameType::WEBTRANSPORT_BI_STREAM | FrameType::DATA => unreachable!(), _ => { buf.advance(len as usize); Err(FrameError::UnknownFrame(ty.0)) diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 3f68f992..17925052 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -204,7 +204,7 @@ pub enum Side { /// Whether a stream communicates data in both directions or only from the initiator #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub(crate) enum Dir { +enum Dir { /// Data flows in both directions Bi = 0, /// Data flows only from the stream's initiator diff --git a/h3/src/server.rs b/h3/src/server.rs index 915497bc..b18d6063 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -171,7 +171,7 @@ where &mut self, ) -> Result, RequestStream)>, Error> { // Accept the incoming stream - let mut stream = match future::poll_fn(|cx| self.poll_accept_bi(cx)).await { + let mut stream = match future::poll_fn(|cx| self.poll_accept_request(cx)).await { Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), Ok(None) => { // We always send a last GoAway frame to the client, so it knows which was the last @@ -377,13 +377,12 @@ where /// /// This could be either a *Request* or a *WebTransportBiStream*, the first frame's type /// decides. - pub(crate) fn poll_accept_bi( + pub(crate) fn poll_accept_request( &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { let _ = self.poll_control(cx)?; let _ = self.poll_requests_completion(cx); - loop { match self.inner.poll_accept_request(cx) { Poll::Ready(Err(x)) => break Poll::Ready(Err(x)), @@ -471,7 +470,17 @@ where Poll::Ready(Ok(frame)) } - /// Polls until all ongoing requests have completed + /// Accepts an incoming recv stream + fn poll_accept_uni( + &self, + cx: &mut Context<'_>, + ) -> Poll::RecvStream>, Error>> { + todo!() + // let recv = ready!(self.inner.poll_accept_recv(cx))?; + + // Poll::Ready(Ok(recv)) + } + fn poll_requests_completion(&mut self, cx: &mut Context<'_>) -> Poll<()> { loop { match self.request_end_recv.poll_recv(cx) { @@ -483,7 +492,7 @@ where } Poll::Pending => { if self.ongoing_streams.is_empty() { - // Tell the caller there are no more ongoing requests. + // Tell the caller there is not more ongoing requests. // Still, the completion of future requests will wake us. return Poll::Ready(()); } else { diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 98fd8161..38864135 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -11,7 +11,6 @@ use crate::proto::{ stream::{InvalidStreamId, StreamId}, varint::VarInt, }; -mod accept; pub mod server; /// Send and Receive streams pub mod stream; diff --git a/h3/src/webtransport/server.rs b/h3/src/webtransport/server.rs index 8a00fdbb..12ae37c9 100644 --- a/h3/src/webtransport/server.rs +++ b/h3/src/webtransport/server.rs @@ -1,7 +1,6 @@ //! Provides the server side WebTransport session use std::{ - collections::HashMap, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, @@ -11,117 +10,21 @@ use crate::{ connection::ConnectionState, error::{Code, ErrorLevel}, frame::FrameStream, - proto::{datagram::Datagram, frame::Frame, stream::Dir}, + proto::{datagram::Datagram, frame::Frame}, quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, - request::ResolveRequest, server::{self, Connection, RequestStream}, stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, Error, Protocol, }; use bytes::{Buf, Bytes}; -use futures_util::{future::poll_fn, ready, stream::FuturesUnordered, Future, StreamExt}; +use futures_util::{future::poll_fn, ready, Future}; use http::{Method, Request, Response, StatusCode}; -use tokio::sync::mpsc; use super::{ - accept::AcceptBi, stream::{self, RecvStream, SendStream}, SessionId, }; -enum Event { - Datagram(Bytes), - /// A new stream was opened for this session - Stream(Dir), -} - -/// Manages multiple connected WebTransport sessions. -pub struct WebTransportServer { - conn: Connection, - - sessions: HashMap>, - - /// Bidirectional streams which have not yet read the first frame - /// This determines if it is a webtransport stream or a request stream - pending_bi: FuturesUnordered>, - - accepted_requests: Vec>, -} - -impl WebTransportServer { - /// Creates a new WebTransport server from an HTTP/3 connection. - pub fn new(conn: Connection) -> Self { - Self { - conn, - sessions: HashMap::new(), - pending_bi: Default::default(), - accepted_requests: todo!(), - } - } - - /// Accepts the next request. - /// - /// *CONNECT* requests for the WebTransport protocol are intercepted. Use [`Self::accept_session`] - pub async fn accept_request(&self) -> (Request<()>, RequestStream) { - todo!() - } - - /// Accepts the next WebTransport session. - pub fn accept_session( - &self, - request: &Request<()>, - mut stream: RequestStream, - ) -> Result, Error> { - todo!() - } - - /// Polls requests and incoming `CONNECT` requests - fn poll_requests(&mut self, cx: &mut Context<'_>) -> Poll, Error>> { - loop { - let conn = &mut self.conn; - // Handle any pending requests - while let Poll::Ready(Some((frame, mut stream))) = self.pending_bi.poll_next_unpin(cx) { - match frame { - Ok(Some(Frame::WebTransportStream(session_id))) => { - tracing::debug!("Got webtransport stream for {session_id:?}"); - } - frame => { - match conn.accept_with_frame(stream, frame) { - Ok(Some(request)) => { - self.accepted_requests.push(request); - } - Ok(None) => { - // Connection is closed - return Poll::Ready(Ok(None)); - } - Err(err) => { - tracing::debug!("Error accepting request: {err}"); - return Poll::Ready(Err(err)); - } - } - } - } - } - - // Accept *new* incoming bidirectional stream - // - // This could be an HTTP/3 request, or a webtransport bidi stream. - let stream = ready!(self.conn.poll_accept_bi(cx)?); - - if let Some(stream) = stream { - let stream = FrameStream::new(BufRecvStream::new(stream)); - self.pending_bi.push(AcceptBi::new(stream)); - // Go back around and poll the streams - } else { - // Connection is closed - return Poll::Ready(Ok(None)); - } - } - - // Peek the first varint to determine the type - } -} - /// WebTransport session driver. /// /// Maintains the session using the underlying HTTP/3 connection. @@ -253,7 +156,7 @@ where // Accept the incoming stream let stream = poll_fn(|cx| { let mut conn = self.conn.lock().unwrap(); - conn.poll_accept_bi(cx) + conn.poll_accept_request(cx) }) .await; @@ -416,7 +319,7 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { match &mut p.stream { Some((send, buf)) => { while buf.has_remaining() { - ready!(send.poll_send(cx, buf))?; + let n = ready!(send.poll_send(cx, buf))?; } let (send, buf) = p.stream.take().unwrap(); assert!(!buf.has_remaining()); From 374a137137dbb3f02b7e2aa3f23f81d7897303ad Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Thu, 20 Apr 2023 22:43:19 -0400 Subject: [PATCH 62/97] took a stab at breaking webtransport into it's own crate --- Cargo.toml | 1 + examples/Cargo.toml | 1 + examples/webtransport_server.rs | 5 +- h3-webtransport/Cargo.toml | 14 +++++ h3-webtransport/src/lib.rs | 14 +++++ .../src}/server.rs | 38 ++++++------ h3/src/buf.rs | 2 +- h3/src/connection.rs | 28 ++++++--- h3/src/error.rs | 9 ++- h3/src/frame.rs | 6 +- h3/src/lib.rs | 15 +++-- h3/src/proto/datagram.rs | 4 +- h3/src/proto/frame.rs | 2 +- h3/src/proto/stream.rs | 6 +- h3/src/request.rs | 2 +- h3/src/server.rs | 39 ++++++++---- h3/src/stream.rs | 6 +- h3/src/webtransport/mod.rs | 61 +------------------ h3/src/webtransport/session_id.rs | 50 +++++++++++++++ h3/src/webtransport/stream.rs | 12 ++-- 20 files changed, 187 insertions(+), 128 deletions(-) create mode 100644 h3-webtransport/Cargo.toml create mode 100644 h3-webtransport/src/lib.rs rename {h3/src/webtransport => h3-webtransport/src}/server.rs (94%) create mode 100644 h3/src/webtransport/session_id.rs diff --git a/Cargo.toml b/Cargo.toml index 89a45c59..93763f5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "h3", "h3-quinn", + "h3-webtransport", # Internal "examples", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index fea2b3a2..1f94687e 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,6 +12,7 @@ bytes = "1" futures = "0.3" h3 = { path = "../h3" } h3-quinn = { path = "../h3-quinn" } +h3-webtransport = { path = "../h3-webtransport" } http = "0.2" quinn = { version = "0.9", default-features = false, features = [ "runtime-tokio", diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index cf50f722..40748619 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -5,6 +5,7 @@ use bytes::{BufMut, BytesMut}; use futures::future::poll_fn; use futures::AsyncReadExt; use futures::AsyncWriteExt; +use h3_webtransport::server; use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; @@ -16,10 +17,10 @@ use h3::{ error::ErrorLevel, quic::{self, RecvStream}, server::{Config, Connection}, - webtransport::server::WebTransportSession, Protocol, }; use h3_quinn::quinn; +use h3_webtransport::server::WebTransportSession; #[derive(StructOpt, Debug)] #[structopt(name = "server")] @@ -293,7 +294,7 @@ where tracing::info!("Wrote response"); } stream = session.accept_bi() => { - if let Some(h3::webtransport::server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { + if let Some(server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { tracing::info!("Got bi stream"); while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { tracing::info!("Received data {:?}", &bytes); diff --git a/h3-webtransport/Cargo.toml b/h3-webtransport/Cargo.toml new file mode 100644 index 00000000..ec753abb --- /dev/null +++ b/h3-webtransport/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "h3-webtransport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "1" +futures-util = { version = "0.3", default-features = false } +h3 = { version = "0.0.2", path = "../h3" } +http = "0.2.9" +pin-project = { version = "1.0", default_features = false } +tracing = "0.1.37" \ No newline at end of file diff --git a/h3-webtransport/src/lib.rs b/h3-webtransport/src/lib.rs new file mode 100644 index 00000000..38a3de23 --- /dev/null +++ b/h3-webtransport/src/lib.rs @@ -0,0 +1,14 @@ +//! Provides the client and server support for WebTransport sessions. +//! +//! # Relevant Links +//! WebTransport: https://www.w3.org/TR/webtransport/#biblio-web-transport-http3 +//! WebTransport over HTTP/3: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/ + +use std::convert::TryFrom; + +use h3::proto::{ + coding::{Decode, Encode}, + stream::{InvalidStreamId, StreamId}, + varint::VarInt, +}; +pub mod server; diff --git a/h3/src/webtransport/server.rs b/h3-webtransport/src/server.rs similarity index 94% rename from h3/src/webtransport/server.rs rename to h3-webtransport/src/server.rs index 12ae37c9..dadd7e17 100644 --- a/h3/src/webtransport/server.rs +++ b/h3-webtransport/src/server.rs @@ -6,23 +6,23 @@ use std::{ task::{Context, Poll}, }; -use crate::{ +use bytes::{Buf, Bytes}; +use futures_util::{future::poll_fn, ready, Future}; +use h3::stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}; +use h3::{ connection::ConnectionState, error::{Code, ErrorLevel}, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, server::{self, Connection, RequestStream}, - stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, Error, Protocol, }; -use bytes::{Buf, Bytes}; -use futures_util::{future::poll_fn, ready, Future}; use http::{Method, Request, Response, StatusCode}; -use super::{ +use h3::webtransport::{ + session_id::SessionId, stream::{self, RecvStream, SendStream}, - SessionId, }; /// WebTransport session driver. @@ -36,7 +36,7 @@ where { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: SessionId, - conn: Mutex>, + pub conn: Mutex>, connect_stream: RequestStream, opener: Mutex, } @@ -87,7 +87,7 @@ where tracing::warn!("Server does not support datagrams"); } - if !conn.inner.config.enable_connect { + if !conn.inner.config.enable_extended_connect { tracing::warn!("Server does not support CONNECT"); } @@ -173,17 +173,17 @@ where } Err(err) => { match err.inner.kind { - crate::error::Kind::Closed => return Ok(None), - crate::error::Kind::Application { - code, - reason, - level: ErrorLevel::ConnectionError, - } => { - return Err(self.conn.lock().unwrap().close( - code, - reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), - )) - } + h3::error::Kind::Closed => return Ok(None), + // h3::error::Kind::Application { + // code, + // reason, + // level: ErrorLevel::ConnectionError, + // } => { + // return Err(self.conn.lock().unwrap().close( + // code, + // reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + // )) + // }, _ => return Err(err), }; } diff --git a/h3/src/buf.rs b/h3/src/buf.rs index c6c5617e..d63880db 100644 --- a/h3/src/buf.rs +++ b/h3/src/buf.rs @@ -4,7 +4,7 @@ use std::io::IoSlice; use bytes::{Buf, Bytes}; #[derive(Debug)] -pub(crate) struct BufList { +pub struct BufList { bufs: VecDeque, } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 91388a1a..4d787340 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -23,7 +23,7 @@ use crate::{ quic::{self, SendStream as _}, server::Config, stream::{self, AcceptRecvStream, AcceptedRecvStream, BufRecvStream, UniStreamHeader}, - webtransport::{self, SessionId}, + webtransport::{self, session_id::SessionId}, }; #[doc(hidden)] @@ -60,6 +60,7 @@ impl Default for SharedStateRef { } } +#[allow(missing_docs)] pub trait ConnectionState { fn shared_state(&self) -> &SharedStateRef; @@ -72,10 +73,12 @@ pub trait ConnectionState { } } -pub(crate) struct AcceptedStreams +#[allow(missing_docs)] +pub struct AcceptedStreams where C: quic::Connection, { + #[allow(missing_docs)] pub uni_streams: Vec<(SessionId, webtransport::stream::RecvStream)>, } @@ -90,13 +93,14 @@ where } } +#[allow(missing_docs)] pub struct ConnectionInner where C: quic::Connection, { pub(super) shared: SharedStateRef, - // TODO: breaking encapsulation just to see if we can get this to work, will fix before merging - pub(super) conn: Arc>, + /// TODO: breaking encapsulation just to see if we can get this to work, will fix before merging + pub conn: Arc>, control_send: C::SendStream, control_recv: Option>, decoder_recv: Option>, @@ -125,8 +129,8 @@ where pending_recv_streams: Vec>, got_peer_settings: bool, - pub(super) send_grease_frame: bool, - pub(super) config: Config, + pub send_grease_frame: bool, + pub config: Config, } impl ConnectionInner @@ -156,7 +160,7 @@ where settings .insert( SettingId::ENABLE_CONNECT_PROTOCOL, - config.enable_connect as u64, + config.enable_extended_connect as u64, ) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; settings @@ -287,6 +291,7 @@ where stream::write::<_, _, Bytes>(&mut self.control_send, Frame::Goaway(max_id.into())).await } + #[allow(missing_docs)] pub fn poll_accept_request( &mut self, cx: &mut Context<'_>, @@ -464,7 +469,7 @@ where shared.config.enable_datagram = settings.get(SettingId::H3_DATAGRAM).unwrap_or(0) != 0; - shared.config.enable_connect = settings + shared.config.enable_extended_connect = settings .get(SettingId::ENABLE_CONNECT_PROTOCOL) .unwrap_or(0) != 0; @@ -631,11 +636,13 @@ where }; } - pub(crate) fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { + #[allow(missing_docs)] + pub fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { &mut self.accepted_streams } } +#[allow(missing_docs)] pub struct RequestStream { pub(super) stream: FrameStream, pub(super) trailers: Option, @@ -645,6 +652,7 @@ pub struct RequestStream { } impl RequestStream { + #[allow(missing_docs)] pub fn new( stream: FrameStream, max_field_section_size: u64, @@ -785,6 +793,7 @@ where Ok(Some(Header::try_from(fields)?.into_fields())) } + #[allow(missing_docs)] pub fn stop_sending(&mut self, err_code: Code) { self.stream.stop_sending(err_code); } @@ -839,6 +848,7 @@ where self.stream.reset(code.into()); } + #[allow(missing_docs)] pub async fn finish(&mut self) -> Result<(), Error> { if self.send_grease_frame { // send a grease frame once per Connection diff --git a/h3/src/error.rs b/h3/src/error.rs index 36f497f2..7991db4c 100644 --- a/h3/src/error.rs +++ b/h3/src/error.rs @@ -12,7 +12,8 @@ pub(crate) type TransportError = Box; /// A general error that can occur when handling the HTTP/3 protocol. #[derive(Clone)] pub struct Error { - pub(crate) inner: Box, + /// The error kind. + pub inner: Box, } /// An HTTP/3 "application error code". @@ -37,9 +38,11 @@ impl PartialEq for Code { } } +/// The error kind. #[derive(Clone)] -pub(crate) struct ErrorImpl { - pub(crate) kind: Kind, +pub struct ErrorImpl { + /// The error kind. + pub kind: Kind, cause: Option>, } diff --git a/h3/src/frame.rs b/h3/src/frame.rs index cb089694..be0ff8d3 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -20,14 +20,14 @@ use crate::{ /// Decodes Frames from the underlying QUIC stream pub struct FrameStream { - stream: BufRecvStream, + pub stream: BufRecvStream, // Already read data from the stream decoder: FrameDecoder, remaining_data: usize, } impl FrameStream { - pub(crate) fn new(stream: BufRecvStream) -> Self { + pub fn new(stream: BufRecvStream) -> Self { Self { stream, decoder: FrameDecoder::default(), @@ -37,7 +37,7 @@ impl FrameStream { /// Unwraps the Framed streamer and returns the underlying stream **without** data loss for /// partially received/read frames. - pub(crate) fn into_inner(self) -> BufRecvStream { + pub fn into_inner(self) -> BufRecvStream { self.stream } } diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 6dfbd2a1..623f671d 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -7,18 +7,23 @@ pub mod error; pub mod quic; pub(crate) mod request; pub mod server; -pub mod webtransport; pub use error::Error; pub use proto::headers::Protocol; mod buf; -mod connection; -mod frame; -mod proto; +#[allow(missing_docs)] +pub mod connection; +#[allow(missing_docs)] +pub mod frame; +#[allow(missing_docs)] +pub mod proto; #[allow(dead_code)] mod qpack; -mod stream; +#[allow(missing_docs)] +pub mod stream; +#[allow(missing_docs)] +pub mod webtransport; #[cfg(test)] mod tests; diff --git a/h3/src/proto/datagram.rs b/h3/src/proto/datagram.rs index 035f70e4..15a97cc3 100644 --- a/h3/src/proto/datagram.rs +++ b/h3/src/proto/datagram.rs @@ -12,7 +12,7 @@ pub struct Datagram { /// Stream id divided by 4 stream_id: StreamId, /// The data contained in the datagram - pub(crate) payload: B, + pub payload: B, } impl Datagram @@ -28,7 +28,7 @@ where } /// Decodes a datagram frame from the QUIC datagram - pub(crate) fn decode(mut buf: B) -> Result { + pub fn decode(mut buf: B) -> Result { let q_stream_id = VarInt::decode(&mut buf) .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Malformed datagram frame"))?; diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index f497c586..51f65fd3 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -5,7 +5,7 @@ use std::{ }; use tracing::trace; -use crate::webtransport::SessionId; +use crate::webtransport::session_id::SessionId; use super::{ coding::{Decode, Encode}, diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 17925052..1a62ae8a 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -5,6 +5,8 @@ use std::{ ops::Add, }; +use crate::webtransport::session_id::SessionId; + use super::{ coding::{BufExt, BufMutExt, Decode, Encode, UnexpectedEnd}, varint::VarInt, @@ -188,8 +190,8 @@ impl Add for StreamId { } } -impl From for StreamId { - fn from(value: crate::webtransport::SessionId) -> Self { +impl From for StreamId { + fn from(value: SessionId) -> Self { Self(value.into_inner()) } } diff --git a/h3/src/request.rs b/h3/src/request.rs index f0ed9b21..ac84ef27 100644 --- a/h3/src/request.rs +++ b/h3/src/request.rs @@ -11,7 +11,7 @@ use crate::{ Error, }; -pub(crate) struct ResolveRequest { +pub struct ResolveRequest { request_stream: RequestStream, // Ok or `REQUEST_HEADER_FIELDS_TO_LARGE` which neeeds to be sent decoded: Result<(qpack::Decoded), u64>, diff --git a/h3/src/server.rs b/h3/src/server.rs index b18d6063..2cae9f17 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -110,7 +110,7 @@ where C: quic::Connection, { /// TODO: temporarily break encapsulation for `WebTransportSession` - pub(crate) inner: ConnectionInner, + pub inner: ConnectionInner, max_field_section_size: u64, // List of all incoming streams that are currently running. ongoing_streams: HashSet, @@ -153,7 +153,7 @@ where } /// Closes the connection with a code and a reason. - pub(crate) fn close>(&mut self, code: Code, reason: T) -> Error { + pub fn close>(&mut self, code: Code, reason: T) -> Error { self.inner.close(code, reason) } } @@ -212,7 +212,7 @@ where /// This is needed as a bidirectional stream may be read as part of incoming webtransport /// bi-streams. If it turns out that the stream is *not* a `WEBTRANSPORT_STREAM` the request /// may still want to be handled and passed to the user. - pub(crate) fn accept_with_frame( + pub fn accept_with_frame( &mut self, mut stream: FrameStream, frame: Result>, FrameStreamError>, @@ -377,7 +377,7 @@ where /// /// This could be either a *Request* or a *WebTransportBiStream*, the first frame's type /// decides. - pub(crate) fn poll_accept_request( + pub fn poll_accept_request( &mut self, cx: &mut Context<'_>, ) -> Poll, Error>> { @@ -516,15 +516,30 @@ where /// Configures the HTTP/3 connection #[derive(Debug, Clone, Copy)] pub struct Config { - pub(crate) send_grease: bool, - pub(crate) max_field_section_size: u64, + /// Just like in HTTP/2, HTTP/3 also uses the concept of "grease" + /// to prevent potential interoperability issues in the future. + /// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve + /// and accommodate future changes without breaking existing implementations. + pub send_grease: bool, + /// The MAX_FIELD_SECTION_SIZE in HTTP/3 refers to the maximum size of the dynamic table used in HPACK compression. + /// HPACK is the compression algorithm used in HTTP/3 to reduce the size of the header fields in HTTP requests and responses. + + /// In HTTP/3, the MAX_FIELD_SECTION_SIZE is set to 12. + /// This means that the dynamic table used for HPACK compression can have a maximum size of 2^12 bytes, which is 4KB. + pub max_field_section_size: u64, //=https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled - pub(crate) enable_webtransport: bool, - pub(crate) enable_connect: bool, - pub(crate) enable_datagram: bool, - pub(crate) max_webtransport_sessions: u64, + pub enable_webtransport: bool, + /// https://www.rfc-editor.org/info/rfc8441 defines an extended CONNECT method in Section 4, + /// enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL parameter. + /// That parameter is only defined for HTTP/2. + /// for extended CONNECT in HTTP/3; instead, the SETTINGS_ENABLE_WEBTRANSPORT setting implies that an endpoint supports extended CONNECT. + pub enable_extended_connect: bool, + /// Enable HTTP Datagrams, see https://datatracker.ietf.org/doc/rfc9297/ for details + pub enable_datagram: bool, + /// The maximum number of concurrent streams that can be opened by the peer. + pub max_webtransport_sessions: u64, } impl Config { @@ -565,7 +580,7 @@ impl Config { /// Enables the CONNECT protocol pub fn enable_connect(&mut self, value: bool) { - self.enable_connect = value; + self.enable_extended_connect = value; } /// Limits the maximum number of WebTransport sessions @@ -587,7 +602,7 @@ impl Default for Config { max_field_section_size: VarInt::MAX.0, send_grease: true, enable_webtransport: false, - enable_connect: false, + enable_extended_connect: false, enable_datagram: false, max_webtransport_sessions: 0, } diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 140893a9..24f1e3b4 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -14,7 +14,7 @@ use crate::{ varint::VarInt, }, quic::{self, BidiStream, RecvStream, SendStream}, - webtransport::{self, SessionId}, + webtransport::{self, session_id::SessionId}, Error, }; @@ -392,7 +392,7 @@ where /// /// Implements `quic::RecvStream` which will first return buffered data, and then read from the /// stream -pub(crate) struct BufRecvStream { +pub struct BufRecvStream { buf: BufList, /// Indicates that the end of the stream has been reached /// @@ -412,7 +412,7 @@ impl std::fmt::Debug for BufRecvStream { } impl BufRecvStream { - pub(crate) fn new(stream: S) -> Self { + pub fn new(stream: S) -> Self { Self { buf: BufList::new(), eos: false, diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 38864135..bec15e5a 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -1,59 +1,4 @@ -//! Provides the client and server support for WebTransport sessions. -//! -//! # Relevant Links -//! WebTransport: https://www.w3.org/TR/webtransport/#biblio-web-transport-http3 -//! WebTransport over HTTP/3: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/ - -use std::convert::TryFrom; - -use crate::proto::{ - coding::{Decode, Encode}, - stream::{InvalidStreamId, StreamId}, - varint::VarInt, -}; -pub mod server; -/// Send and Receive streams +/// WebTransport session_id +pub mod session_id; +/// WebTransport stream pub mod stream; - -/// Identifies a WebTransport session -/// -/// The session id is the same as the stream id of the CONNECT request. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct SessionId(pub u64); -impl SessionId { - pub(crate) fn from_varint(id: VarInt) -> SessionId { - Self(id.0) - } - - pub(crate) fn into_inner(self) -> u64 { - self.0 - } -} - -impl TryFrom for SessionId { - type Error = InvalidStreamId; - fn try_from(v: u64) -> Result { - if v > VarInt::MAX.0 { - return Err(InvalidStreamId(v)); - } - Ok(Self(v)) - } -} - -impl Encode for SessionId { - fn encode(&self, buf: &mut B) { - VarInt::from_u64(self.0).unwrap().encode(buf); - } -} - -impl Decode for SessionId { - fn decode(buf: &mut B) -> crate::proto::coding::Result { - Ok(Self(VarInt::decode(buf)?.into_inner())) - } -} - -impl From for SessionId { - fn from(value: StreamId) -> Self { - Self(value.index()) - } -} diff --git a/h3/src/webtransport/session_id.rs b/h3/src/webtransport/session_id.rs new file mode 100644 index 00000000..af4ae041 --- /dev/null +++ b/h3/src/webtransport/session_id.rs @@ -0,0 +1,50 @@ +use std::convert::TryFrom; + +use crate::proto::{ + coding::{Decode, Encode}, + stream::{InvalidStreamId, StreamId}, + varint::VarInt, +}; + +/// Identifies a WebTransport session +/// +/// The session id is the same as the stream id of the CONNECT request. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct SessionId(pub u64); +impl SessionId { + pub(crate) fn from_varint(id: VarInt) -> SessionId { + Self(id.0) + } + + pub(crate) fn into_inner(self) -> u64 { + self.0 + } +} + +impl TryFrom for SessionId { + type Error = InvalidStreamId; + fn try_from(v: u64) -> Result { + if v > VarInt::MAX.0 { + return Err(InvalidStreamId(v)); + } + Ok(Self(v)) + } +} + +impl Encode for SessionId { + fn encode(&self, buf: &mut B) { + VarInt::from_u64(self.0).unwrap().encode(buf); + } +} + +impl Decode for SessionId { + fn decode(buf: &mut B) -> crate::proto::coding::Result { + Ok(Self(VarInt::decode(buf)?.into_inner())) + } +} + +impl From for SessionId { + fn from(value: StreamId) -> Self { + Self(value.index()) + } +} diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index ad90399a..9388f399 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -1,24 +1,21 @@ -use std::{marker::PhantomData, task::Poll}; +use std::task::Poll; use bytes::{Buf, Bytes}; use futures_util::{future, ready, AsyncRead, AsyncWrite}; use crate::{ - buf::BufList, - proto::varint::UnexpectedEnd, quic::{self, RecvStream as _, SendStream as _}, stream::BufRecvStream, }; -use super::SessionId; - /// WebTransport receive stream pub struct RecvStream { stream: BufRecvStream, } impl RecvStream { - pub(crate) fn new(stream: BufRecvStream) -> Self { + #[allow(missing_docs)] + pub fn new(stream: BufRecvStream) -> Self { Self { stream } } } @@ -96,7 +93,8 @@ impl std::fmt::Debug for SendStream { } impl SendStream { - pub(crate) fn new(stream: BufRecvStream) -> Self { + #[allow(missing_docs)] + pub fn new(stream: BufRecvStream) -> Self { Self { stream } } } From e385d6d7a5c2f375b80ab1205b313624b4993c44 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Thu, 20 Apr 2023 23:13:42 -0400 Subject: [PATCH 63/97] add allow_access_to_core feature so that h3 users need to go out of their way to access mods that used to be private like connection, streams, etc --- h3-webtransport/Cargo.toml | 8 ++++++-- h3/Cargo.toml | 3 +++ h3/src/lib.rs | 22 +++++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/h3-webtransport/Cargo.toml b/h3-webtransport/Cargo.toml index ec753abb..b9121c41 100644 --- a/h3-webtransport/Cargo.toml +++ b/h3-webtransport/Cargo.toml @@ -8,7 +8,11 @@ edition = "2021" [dependencies] bytes = "1" futures-util = { version = "0.3", default-features = false } -h3 = { version = "0.0.2", path = "../h3" } http = "0.2.9" pin-project = { version = "1.0", default_features = false } -tracing = "0.1.37" \ No newline at end of file +tracing = "0.1.37" + +[dependencies.h3] +version = "0.0.2" +path = "../h3" +features = ["allow_access_to_core"] \ No newline at end of file diff --git a/h3/Cargo.toml b/h3/Cargo.toml index 104a6ab5..a337cd63 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -19,6 +19,9 @@ categories = [ "web-programming::http-server", ] +[features] +allow_access_to_core = [] + [dependencies] bytes = "1" futures-util = { version = "0.3", default-features = false } diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 623f671d..7d8d7cb1 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -12,21 +12,37 @@ pub use error::Error; pub use proto::headers::Protocol; mod buf; + +#[cfg(feature="allow_access_to_core")] #[allow(missing_docs)] pub mod connection; +#[cfg(feature="allow_access_to_core")] #[allow(missing_docs)] pub mod frame; +#[cfg(feature="allow_access_to_core")] #[allow(missing_docs)] pub mod proto; -#[allow(dead_code)] -mod qpack; +#[cfg(feature="allow_access_to_core")] #[allow(missing_docs)] pub mod stream; +#[cfg(feature="allow_access_to_core")] #[allow(missing_docs)] pub mod webtransport; +#[cfg(not(feature = "allow_access_to_core"))] +mod connection; +#[cfg(not(feature = "allow_access_to_core"))] +mod frame; +#[cfg(not(feature = "allow_access_to_core"))] +mod proto; +#[cfg(not(feature = "allow_access_to_core"))] +mod stream; +#[cfg(not(feature = "allow_access_to_core"))] +mod webtransport; + +#[allow(dead_code)] +mod qpack; #[cfg(test)] mod tests; - #[cfg(test)] extern crate self as h3; From 0540c1d9919cc6c0e5aad741350045429643e7a8 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 24 Apr 2023 17:28:20 +0200 Subject: [PATCH 64/97] chore: concurrent handling message in webtransport example --- examples/webtransport_server.rs | 79 ++++++++++++++++++++++----------- h3/src/webtransport/stream.rs | 17 +++---- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 40748619..609e69ea 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -3,8 +3,13 @@ use anyhow::Result; use bytes::Bytes; use bytes::{BufMut, BytesMut}; use futures::future::poll_fn; +use futures::AsyncRead; use futures::AsyncReadExt; +use futures::AsyncWrite; use futures::AsyncWriteExt; +use h3::webtransport::session_id::SessionId; +use h3::webtransport::stream::RecvStream; +use h3::webtransport::stream::SendStream; use h3_webtransport::server; use http::Method; use rustls::{Certificate, PrivateKey}; @@ -15,7 +20,7 @@ use tracing_subscriber::prelude::*; use h3::{ error::ErrorLevel, - quic::{self, RecvStream}, + quic::{self, RecvStream as _}, server::{Config, Connection}, Protocol, }; @@ -231,6 +236,46 @@ where Ok(()) } +macro_rules! log_result { + ($expr:expr) => { + if let Err(err) = $expr { + tracing::error!("{err:?}"); + } + }; +} + +async fn echo_stream( + mut send: SendStream, + mut recv: RecvStream, +) -> anyhow::Result<()> +where + C: quic::Connection, + SendStream: AsyncWrite, + RecvStream: AsyncRead, +{ + tracing::info!("Got stream"); + let mut buf = Vec::new(); + recv.read_to_end(&mut buf).await?; + + let message = Bytes::from(buf); + + send_chunked(send, message).await?; + + Ok(()) +} + +// Used to test that all chunks arrive properly as it is easy to write an impl which only reads and +// writes the first chunk. +async fn send_chunked(mut send: impl AsyncWrite + Unpin, data: Bytes) -> anyhow::Result<()> { + for chunk in data.chunks(4) { + tokio::time::sleep(Duration::from_millis(100)).await; + tracing::info!("Sending {chunk:?}"); + send.write_all(chunk).await?; + } + + Ok(()) +} + /// This method will echo all inbound datagrams, unidirectional and bidirectional streams. #[tracing::instrument(level = "info", skip(session))] async fn handle_session_and_echo_all_inbound_messages( @@ -268,39 +313,19 @@ where let mut resp = BytesMut::from(&b"Response: "[..]); resp.put(datagram); - session.send_datagram(resp).unwrap(); + session.send_datagram(resp)?; tracing::info!("Finished sending datagram"); } } uni_stream = session.accept_uni() => { - let (id, mut stream) = uni_stream?.unwrap(); - tracing::info!("Received uni stream {:?} for {id:?}", stream.recv_id()); + let (id, stream) = uni_stream?.unwrap(); - let mut message = Vec::new(); - - AsyncReadExt::read_to_end(&mut stream, &mut message).await.context("Failed to read message")?; - let message = Bytes::from(message); - - tracing::info!("Got message: {message:?}"); - let mut send = session.open_uni(session_id).await?; - tracing::info!("Opened unidirectional stream"); - - for byte in message { - tokio::time::sleep(Duration::from_millis(100)).await; - tracing::info!("Sending {byte:?}"); - send.write_all(&[byte][..]).await?; - } - // futures::AsyncWriteExt::write_all(&mut send, &message).await.context("Failed to respond")?; - tracing::info!("Wrote response"); + let send = session.open_uni(id).await?; + tokio::spawn( async move { log_result!(echo_stream::(send, stream).await); }); } stream = session.accept_bi() => { - if let Some(server::AcceptedBi::BidiStream(_, mut send, mut recv)) = stream? { - tracing::info!("Got bi stream"); - while let Some(bytes) = poll_fn(|cx| recv.poll_data(cx)).await? { - tracing::info!("Received data {:?}", &bytes); - futures::AsyncWriteExt::write_all(&mut send, &bytes).await.context("Failed to respond")?; - } - send.close().await?; + if let Some(server::AcceptedBi::BidiStream(_, send, recv)) = stream? { + tokio::spawn( async move { log_result!(echo_stream::(send, recv).await); }); } } else => { diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index 9388f399..e4e55895 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -4,11 +4,12 @@ use bytes::{Buf, Bytes}; use futures_util::{future, ready, AsyncRead, AsyncWrite}; use crate::{ - quic::{self, RecvStream as _, SendStream as _}, + quic::{self, SendStream as _}, stream::BufRecvStream, }; /// WebTransport receive stream +#[pin_project::pin_project] pub struct RecvStream { stream: BufRecvStream, } @@ -48,7 +49,7 @@ where impl AsyncRead for RecvStream where - S: Unpin + quic::RecvStream, + S: quic::RecvStream, S::Error: Into, { fn poll_read( @@ -80,6 +81,7 @@ where } /// WebTransport send stream +#[pin_project::pin_project] pub struct SendStream { stream: BufRecvStream, } @@ -109,15 +111,6 @@ where pub async fn write(&mut self, buf: &mut impl Buf) -> Result { future::poll_fn(|cx| quic::SendStream::poll_send(self, cx, buf)).await } - - /// Writes the entire buffer to the stream - pub async fn write_all(&mut self, mut buf: impl Buf) -> Result<(), S::Error> { - while buf.has_remaining() { - self.write(&mut buf).await?; - } - - Ok(()) - } } impl quic::SendStream for SendStream @@ -149,7 +142,7 @@ where impl AsyncWrite for SendStream where - S: Unpin + quic::SendStream, + S: quic::SendStream, S::Error: Into, { fn poll_write( From 0c3a07caa95fb76ab29a1f7324d3962f017d5c32 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Tue, 25 Apr 2023 11:04:01 +0200 Subject: [PATCH 65/97] chore: use pin-project --- h3-webtransport/Cargo.toml | 5 +++-- h3-webtransport/src/server.rs | 32 ++++++++++++++++++-------------- h3/Cargo.toml | 2 +- h3/src/server.rs | 1 - h3/src/webtransport/stream.rs | 19 +++++++++++-------- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/h3-webtransport/Cargo.toml b/h3-webtransport/Cargo.toml index b9121c41..4794589c 100644 --- a/h3-webtransport/Cargo.toml +++ b/h3-webtransport/Cargo.toml @@ -9,10 +9,11 @@ edition = "2021" bytes = "1" futures-util = { version = "0.3", default-features = false } http = "0.2.9" -pin-project = { version = "1.0", default_features = false } +pin-project-lite = { version = "0.2", default_features = false } tracing = "0.1.37" [dependencies.h3] version = "0.0.2" path = "../h3" -features = ["allow_access_to_core"] \ No newline at end of file +features = ["allow_access_to_core"] + diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index dadd7e17..9658748b 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -11,7 +11,7 @@ use futures_util::{future::poll_fn, ready, Future}; use h3::stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}; use h3::{ connection::ConnectionState, - error::{Code, ErrorLevel}, + error::Code, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, @@ -24,6 +24,7 @@ use h3::webtransport::{ session_id::SessionId, stream::{self, RecvStream, SendStream}, }; +use pin_project_lite::pin_project; /// WebTransport session driver. /// @@ -259,12 +260,13 @@ type PendingUniStreams = ( WriteBuf<&'static [u8]>, ); -#[pin_project::pin_project] -/// Future for opening a bidi stream -pub struct OpenBi<'a, C: quic::Connection> { - opener: &'a Mutex, - stream: Option>, - session_id: SessionId, +pin_project! { + /// Future for opening a bidi stream + pub struct OpenBi<'a, C: quic::Connection> { + opener: &'a Mutex, + stream: Option>, + session_id: SessionId, + } } impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { @@ -302,12 +304,14 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { } } -#[pin_project::pin_project] -/// Future for opening a uni stream -pub struct OpenUni<'a, C: quic::Connection> { - opener: &'a Mutex, - stream: Option>, - session_id: SessionId, +pin_project! { + /// Opens a unidirectional stream + pub struct OpenUni<'a, C: quic::Connection> { + opener: &'a Mutex, + stream: Option>, + // Future for opening a uni stream + session_id: SessionId, + } } impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { @@ -319,7 +323,7 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { match &mut p.stream { Some((send, buf)) => { while buf.has_remaining() { - let n = ready!(send.poll_send(cx, buf))?; + ready!(send.poll_send(cx, buf))?; } let (send, buf) = p.stream.take().unwrap(); assert!(!buf.has_remaining()); diff --git a/h3/Cargo.toml b/h3/Cargo.toml index a337cd63..595862d5 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -27,7 +27,7 @@ bytes = "1" futures-util = { version = "0.3", default-features = false } http = "0.2.9" tokio = { version = "1", features = ["sync"] } -pin-project = { version = "1.0", default_features = false } +pin-project-lite = { version = "0.2", default_features = false } tracing = "0.1.37" fastrand = "1.9.0" diff --git a/h3/src/server.rs b/h3/src/server.rs index 2cae9f17..cb9c8179 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -67,7 +67,6 @@ use futures_util::{ ready, FutureExt, }; use http::{response, HeaderMap, Method, Request, Response, StatusCode}; -use pin_project::pin_project; use quic::RecvStream; use quic::StreamId; use tokio::sync::mpsc; diff --git a/h3/src/webtransport/stream.rs b/h3/src/webtransport/stream.rs index e4e55895..0aa4e080 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3/src/webtransport/stream.rs @@ -2,16 +2,18 @@ use std::task::Poll; use bytes::{Buf, Bytes}; use futures_util::{future, ready, AsyncRead, AsyncWrite}; +use pin_project_lite::pin_project; use crate::{ quic::{self, SendStream as _}, stream::BufRecvStream, }; -/// WebTransport receive stream -#[pin_project::pin_project] -pub struct RecvStream { - stream: BufRecvStream, +pin_project! { + /// WebTransport receive stream + pub struct RecvStream { + stream: BufRecvStream, + } } impl RecvStream { @@ -80,10 +82,11 @@ where } } -/// WebTransport send stream -#[pin_project::pin_project] -pub struct SendStream { - stream: BufRecvStream, +pin_project! { + /// WebTransport send stream + pub struct SendStream { + stream: BufRecvStream, + } } impl std::fmt::Debug for SendStream { From 32b156022311c65f4deb83f1dd1e0583bf5d3cc0 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Tue, 25 Apr 2023 14:57:58 +0200 Subject: [PATCH 66/97] chore: move streams to h3-webtransport --- examples/launch_chrome.sh | 5 ++++- examples/webtransport_server.rs | 5 ++--- h3-webtransport/src/lib.rs | 13 ++++++------- h3-webtransport/src/server.rs | 15 +++++++-------- .../src}/stream.rs | 5 ++--- h3/src/connection.rs | 4 ++-- h3/src/lib.rs | 10 +++++----- h3/src/proto/frame.rs | 2 +- h3/src/proto/stream.rs | 2 +- h3/src/stream.rs | 6 +++--- h3/src/webtransport/mod.rs | 6 ++---- 11 files changed, 35 insertions(+), 38 deletions(-) rename {h3/src/webtransport => h3-webtransport/src}/stream.rs (99%) diff --git a/examples/launch_chrome.sh b/examples/launch_chrome.sh index 251079da..97acd7ee 100755 --- a/examples/launch_chrome.sh +++ b/examples/launch_chrome.sh @@ -8,6 +8,9 @@ echo "Got cert key $SPKI" echo "Opening google chrome" -open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 +case `uname` in + (*Linux*) google-chrome --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 ;; + (*Darwin*) open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 ;; +esac ## Logs are stored to ~/Library/Application Support/Google/Chrome/chrome_debug.log diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 609e69ea..aa623284 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -7,10 +7,9 @@ use futures::AsyncRead; use futures::AsyncReadExt; use futures::AsyncWrite; use futures::AsyncWriteExt; -use h3::webtransport::session_id::SessionId; -use h3::webtransport::stream::RecvStream; -use h3::webtransport::stream::SendStream; use h3_webtransport::server; +use h3_webtransport::stream::RecvStream; +use h3_webtransport::stream::SendStream; use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; diff --git a/h3-webtransport/src/lib.rs b/h3-webtransport/src/lib.rs index 38a3de23..9900311a 100644 --- a/h3-webtransport/src/lib.rs +++ b/h3-webtransport/src/lib.rs @@ -3,12 +3,11 @@ //! # Relevant Links //! WebTransport: https://www.w3.org/TR/webtransport/#biblio-web-transport-http3 //! WebTransport over HTTP/3: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/ +#![deny(missing_docs)] -use std::convert::TryFrom; - -use h3::proto::{ - coding::{Decode, Encode}, - stream::{InvalidStreamId, StreamId}, - varint::VarInt, -}; +/// Server side WebTransport session support pub mod server; +/// Webtransport stream types +pub mod stream; + +pub use h3::webtransport::SessionId; diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 9658748b..8ab3cdac 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -20,12 +20,11 @@ use h3::{ }; use http::{Method, Request, Response, StatusCode}; -use h3::webtransport::{ - session_id::SessionId, - stream::{self, RecvStream, SendStream}, -}; +use h3::webtransport::SessionId; use pin_project_lite::pin_project; +use crate::stream::{RecvStream, SendStream}; + /// WebTransport session driver. /// /// Maintains the session using the underlying HTTP/3 connection. @@ -37,7 +36,7 @@ where { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: SessionId, - pub conn: Mutex>, + conn: Mutex>, connect_stream: RequestStream, opener: Mutex, } @@ -399,7 +398,7 @@ impl<'a, C> Future for AcceptUni<'a, C> where C: quic::Connection, { - type Output = Result)>, Error>; + type Output = Result)>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_uni_stream"); @@ -409,9 +408,9 @@ where // Get the currently available streams let streams = conn.inner.accepted_streams_mut(); - if let Some(v) = streams.uni_streams.pop() { + if let Some((id, stream)) = streams.uni_streams.pop() { tracing::info!("Got uni stream"); - return Poll::Ready(Ok(Some(v))); + return Poll::Ready(Ok(Some((id, RecvStream::new(stream))))); } tracing::debug!("Waiting on incoming streams"); diff --git a/h3/src/webtransport/stream.rs b/h3-webtransport/src/stream.rs similarity index 99% rename from h3/src/webtransport/stream.rs rename to h3-webtransport/src/stream.rs index 0aa4e080..cdbd0eed 100644 --- a/h3/src/webtransport/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -2,12 +2,11 @@ use std::task::Poll; use bytes::{Buf, Bytes}; use futures_util::{future, ready, AsyncRead, AsyncWrite}; -use pin_project_lite::pin_project; - -use crate::{ +use h3::{ quic::{self, SendStream as _}, stream::BufRecvStream, }; +use pin_project_lite::pin_project; pin_project! { /// WebTransport receive stream diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 4d787340..6c5f7d44 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -23,7 +23,7 @@ use crate::{ quic::{self, SendStream as _}, server::Config, stream::{self, AcceptRecvStream, AcceptedRecvStream, BufRecvStream, UniStreamHeader}, - webtransport::{self, session_id::SessionId}, + webtransport::SessionId, }; #[doc(hidden)] @@ -79,7 +79,7 @@ where C: quic::Connection, { #[allow(missing_docs)] - pub uni_streams: Vec<(SessionId, webtransport::stream::RecvStream)>, + pub uni_streams: Vec<(SessionId, BufRecvStream)>, } impl Default for AcceptedStreams diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 7d8d7cb1..95edf315 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -13,19 +13,19 @@ pub use proto::headers::Protocol; mod buf; -#[cfg(feature="allow_access_to_core")] +#[cfg(feature = "allow_access_to_core")] #[allow(missing_docs)] pub mod connection; -#[cfg(feature="allow_access_to_core")] +#[cfg(feature = "allow_access_to_core")] #[allow(missing_docs)] pub mod frame; -#[cfg(feature="allow_access_to_core")] +#[cfg(feature = "allow_access_to_core")] #[allow(missing_docs)] pub mod proto; -#[cfg(feature="allow_access_to_core")] +#[cfg(feature = "allow_access_to_core")] #[allow(missing_docs)] pub mod stream; -#[cfg(feature="allow_access_to_core")] +#[cfg(feature = "allow_access_to_core")] #[allow(missing_docs)] pub mod webtransport; diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 51f65fd3..f497c586 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -5,7 +5,7 @@ use std::{ }; use tracing::trace; -use crate::webtransport::session_id::SessionId; +use crate::webtransport::SessionId; use super::{ coding::{Decode, Encode}, diff --git a/h3/src/proto/stream.rs b/h3/src/proto/stream.rs index 1a62ae8a..2d525167 100644 --- a/h3/src/proto/stream.rs +++ b/h3/src/proto/stream.rs @@ -5,7 +5,7 @@ use std::{ ops::Add, }; -use crate::webtransport::session_id::SessionId; +use crate::webtransport::SessionId; use super::{ coding::{BufExt, BufMutExt, Decode, Encode, UnexpectedEnd}, diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 24f1e3b4..8e92fc59 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -14,7 +14,7 @@ use crate::{ varint::VarInt, }, quic::{self, BidiStream, RecvStream, SendStream}, - webtransport::{self, session_id::SessionId}, + webtransport::{self, SessionId}, Error, }; @@ -248,7 +248,7 @@ where Push(u64, FrameStream), Encoder(BufRecvStream), Decoder(BufRecvStream), - WebTransportUni(SessionId, webtransport::stream::RecvStream), + WebTransportUni(SessionId, BufRecvStream), Reserved, } @@ -298,7 +298,7 @@ where StreamType::DECODER => AcceptedRecvStream::Decoder(self.stream), StreamType::WEBTRANSPORT_UNI => AcceptedRecvStream::WebTransportUni( SessionId::from_varint(self.id.expect("Session ID not resolved yet")), - webtransport::stream::RecvStream::new(self.stream), + self.stream, ), t if t.value() > 0x21 && (t.value() - 0x21) % 0x1f == 0 => AcceptedRecvStream::Reserved, diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index bec15e5a..1b6d8224 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -1,4 +1,2 @@ -/// WebTransport session_id -pub mod session_id; -/// WebTransport stream -pub mod stream; +mod session_id; +pub use session_id::*; From 7b6a2607afdcf2b138418ab909104fe299f41562 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Tue, 25 Apr 2023 16:21:53 +0200 Subject: [PATCH 67/97] chore: fix warnings --- examples/server.rs | 2 +- examples/webtransport_server.rs | 17 ++++------------- h3/src/frame.rs | 6 ++---- h3/src/request.rs | 13 +++---------- h3/src/server.rs | 22 ++++------------------ h3/src/stream.rs | 16 +--------------- h3/src/tests/mod.rs | 1 - 7 files changed, 15 insertions(+), 62 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 955bbda9..e90c2159 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,6 +1,6 @@ use std::{net::SocketAddr, path::PathBuf, sync::Arc}; -use bytes::{Bytes, BytesMut}; +use bytes::BytesMut; use http::{Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index aa623284..37b179b3 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -2,7 +2,6 @@ use anyhow::Context; use anyhow::Result; use bytes::Bytes; use bytes::{BufMut, BytesMut}; -use futures::future::poll_fn; use futures::AsyncRead; use futures::AsyncReadExt; use futures::AsyncWrite; @@ -15,11 +14,10 @@ use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use structopt::StructOpt; use tracing::{error, info, trace_span}; -use tracing_subscriber::prelude::*; use h3::{ error::ErrorLevel, - quic::{self, RecvStream as _}, + quic, server::{Config, Connection}, Protocol, }; @@ -29,15 +27,6 @@ use h3_webtransport::server::WebTransportSession; #[derive(StructOpt, Debug)] #[structopt(name = "server")] struct Opt { - #[structopt( - name = "dir", - short, - long, - help = "Root directory of the files to serve. \ - If omitted, server will respond OK." - )] - pub root: Option, - #[structopt( short, long, @@ -79,6 +68,8 @@ async fn main() -> Result<(), Box> { .with_writer(std::io::stderr) .init(); + #[cfg(feature = "tree")] + use tracing_subscriber::prelude::*; #[cfg(feature = "tree")] tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::from_default_env()) @@ -244,7 +235,7 @@ macro_rules! log_result { } async fn echo_stream( - mut send: SendStream, + send: SendStream, mut recv: RecvStream, ) -> anyhow::Result<()> where diff --git a/h3/src/frame.rs b/h3/src/frame.rs index be0ff8d3..b95dc6fc 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -1,7 +1,6 @@ -use std::marker::PhantomData; use std::task::{Context, Poll}; -use bytes::{Buf, Bytes}; +use bytes::Buf; use futures_util::ready; use tracing::trace; @@ -15,7 +14,6 @@ use crate::{ stream::StreamId, }, quic::{BidiStream, RecvStream, SendStream}, - stream::WriteBuf, }; /// Decodes Frames from the underlying QUIC stream @@ -263,7 +261,7 @@ mod tests { use super::*; use assert_matches::assert_matches; - use bytes::{BufMut, BytesMut}; + use bytes::{BufMut, Bytes, BytesMut}; use futures_util::future::poll_fn; use std::{collections::VecDeque, fmt, sync::Arc}; diff --git a/h3/src/request.rs b/h3/src/request.rs index ac84ef27..558a1168 100644 --- a/h3/src/request.rs +++ b/h3/src/request.rs @@ -1,27 +1,20 @@ use std::convert::TryFrom; -use bytes::Bytes; use http::{Request, StatusCode}; -use crate::{ - error::Code, - proto::headers::Header, - qpack, quic, - server::{Connection, RequestStream}, - Error, -}; +use crate::{error::Code, proto::headers::Header, qpack, quic, server::RequestStream, Error}; pub struct ResolveRequest { request_stream: RequestStream, // Ok or `REQUEST_HEADER_FIELDS_TO_LARGE` which neeeds to be sent - decoded: Result<(qpack::Decoded), u64>, + decoded: Result, max_field_section_size: u64, } impl ResolveRequest { pub fn new( request_stream: RequestStream, - decoded: Result<(qpack::Decoded), u64>, + decoded: Result, max_field_section_size: u64, ) -> Self { Self { diff --git a/h3/src/server.rs b/h3/src/server.rs index cb9c8179..1f6e6d32 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -52,8 +52,6 @@ use std::{ collections::HashSet, - convert::TryFrom, - marker::PhantomData, option::Option, result::Result, sync::Arc, @@ -62,11 +60,10 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use futures_util::{ - future::ready, future::{self, Future}, - ready, FutureExt, + ready, }; -use http::{response, HeaderMap, Method, Request, Response, StatusCode}; +use http::{response, HeaderMap, Request, Response}; use quic::RecvStream; use quic::StreamId; use tokio::sync::mpsc; @@ -78,7 +75,7 @@ use crate::{ proto::{ datagram::Datagram, frame::{Frame, PayloadLen}, - headers::{Header, Protocol}, + headers::Header, push::PushId, varint::VarInt, }, @@ -87,7 +84,7 @@ use crate::{ request::ResolveRequest, stream::{self, BufRecvStream}, }; -use tracing::{error, info, trace, warn}; +use tracing::{error, trace, warn}; /// Create a builder of HTTP/3 server connections /// @@ -469,17 +466,6 @@ where Poll::Ready(Ok(frame)) } - /// Accepts an incoming recv stream - fn poll_accept_uni( - &self, - cx: &mut Context<'_>, - ) -> Poll::RecvStream>, Error>> { - todo!() - // let recv = ready!(self.inner.poll_accept_recv(cx))?; - - // Poll::Ready(Ok(recv)) - } - fn poll_requests_completion(&mut self, cx: &mut Context<'_>) -> Poll<()> { loop { match self.request_end_recv.poll_recv(cx) { diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 8e92fc59..e56491af 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -14,7 +14,7 @@ use crate::{ varint::VarInt, }, quic::{self, BidiStream, RecvStream, SendStream}, - webtransport::{self, SessionId}, + webtransport::SessionId, Error, }; @@ -252,19 +252,6 @@ where Reserved, } -impl AcceptedRecvStream -where - S: quic::RecvStream, -{ - /// Returns `true` if the accepted recv stream is [`WebTransportUni`]. - /// - /// [`WebTransportUni`]: AcceptedRecvStream::WebTransportUni - #[must_use] - pub fn is_web_transport_uni(&self) -> bool { - matches!(self, Self::WebTransportUni(..)) - } -} - /// Resolves an incoming streams type as well as `PUSH_ID`s and `SESSION_ID`s pub(super) struct AcceptRecvStream { stream: BufRecvStream, @@ -534,7 +521,6 @@ impl BidiStream for BufRecvStream { #[cfg(test)] mod tests { - use quic::StreamId; use quinn_proto::coding::BufExt; use super::*; diff --git a/h3/src/tests/mod.rs b/h3/src/tests/mod.rs index f3533cd3..03380be1 100644 --- a/h3/src/tests/mod.rs +++ b/h3/src/tests/mod.rs @@ -18,7 +18,6 @@ use std::{ time::Duration, }; -use bytes::Bytes; use rustls::{Certificate, PrivateKey}; use crate::quic; From 57576b914fdda77e00f2fb68c848079df76e5d49 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Tue, 25 Apr 2023 17:00:24 +0200 Subject: [PATCH 68/97] chore: cleanup --- .github/workflows/CI.yml | 2 +- h3-webtransport/src/server.rs | 26 +++----------------------- h3-webtransport/src/stream.rs | 7 +------ h3/src/client.rs | 2 +- h3/src/connection.rs | 18 ++++++++---------- h3/src/error.rs | 10 ++++++++-- h3/src/proto/headers.rs | 1 - h3/src/proto/varint.rs | 1 - h3/src/request.rs | 1 - h3/src/server.rs | 2 +- h3/src/tests/connection.rs | 9 +++++++-- h3/src/tests/request.rs | 12 ++++++------ 12 files changed, 36 insertions(+), 55 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b548503d..045eacf3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -66,7 +66,7 @@ jobs: with: command: clippy - gsrv: + msrv: name: Check MSRV needs: [style] runs-on: ubuntu-latest diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 8ab3cdac..6802f267 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -55,9 +55,8 @@ where ) -> Result { let shared = conn.shared_state().clone(); { - let config = shared.write("Read WebTransport support").config; + let config = shared.write("Read WebTransport support").peer_config; - tracing::debug!("Client settings: {:#?}", config); if !config.enable_webtransport { return Err(conn.close( Code::H3_SETTINGS_ERROR, @@ -73,8 +72,6 @@ where } } - tracing::debug!("Validated client webtransport support"); - // The peer is responsible for validating our side of the webtransport support. // // However, it is still advantageous to show a log on the server as (attempting) to @@ -108,12 +105,9 @@ where .unwrap() }; - tracing::info!("Sending response: {response:?}"); stream.send_response(response).await?; - tracing::info!("Stream id: {}", stream.id()); let session_id = stream.send_id().into(); - tracing::info!("Established new WebTransport session with id {session_id:?}"); let conn_inner = conn.inner.conn.lock().unwrap(); let opener = Mutex::new(conn_inner.opener()); drop(conn_inner); @@ -160,8 +154,6 @@ where }) .await; - tracing::debug!("Received biderectional stream"); - let mut stream = match stream { Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), Ok(None) => { @@ -172,7 +164,7 @@ where // return Ok(None); } Err(err) => { - match err.inner.kind { + match err.inner.kind() { h3::error::Kind::Closed => return Ok(None), // h3::error::Kind::Application { // code, @@ -189,7 +181,6 @@ where } }; - tracing::debug!("Reading first frame"); // Read the first frame. // // This will determine if it is a webtransport bi-stream or a request stream @@ -198,7 +189,6 @@ where match frame { Ok(None) => Ok(None), Ok(Some(Frame::WebTransportStream(session_id))) => { - tracing::info!("Got webtransport stream"); // Take the stream out of the framed reader and split it in half like Paul Allen let (send, recv) = stream.into_inner().split(); let send = SendStream::new(send); @@ -277,12 +267,9 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { match &mut p.stream { Some((send, _, buf)) => { while buf.has_remaining() { - let n = ready!(send.poll_send(cx, buf))?; - tracing::debug!("Wrote {n} bytes"); + ready!(send.poll_send(cx, buf))?; } - tracing::debug!("Finished sending header frame"); - let (send, recv, _) = p.stream.take().unwrap(); return Poll::Ready(Ok((send, recv))); } @@ -373,8 +360,6 @@ where type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - tracing::trace!("poll: read_datagram"); - let mut conn = self.conn.lock().unwrap(); match ready!(conn.poll_accept_datagram(cx))? { Some(v) => { @@ -401,20 +386,15 @@ where type Output = Result)>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - tracing::trace!("poll: read_uni_stream"); - let mut conn = self.conn.lock().unwrap(); conn.inner.poll_accept_recv(cx)?; // Get the currently available streams let streams = conn.inner.accepted_streams_mut(); if let Some((id, stream)) = streams.uni_streams.pop() { - tracing::info!("Got uni stream"); return Poll::Ready(Ok(Some((id, RecvStream::new(stream))))); } - tracing::debug!("Waiting on incoming streams"); - Poll::Pending } } diff --git a/h3-webtransport/src/stream.rs b/h3-webtransport/src/stream.rs index cdbd0eed..fd4aa824 100644 --- a/h3-webtransport/src/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -30,12 +30,10 @@ where type Error = S::Error; - #[tracing::instrument(level = "info", skip_all)] fn poll_data( &mut self, cx: &mut std::task::Context<'_>, ) -> Poll, Self::Error>> { - tracing::info!("Polling RecvStream"); self.stream.poll_data(cx) } @@ -152,10 +150,7 @@ where cx: &mut std::task::Context<'_>, mut buf: &[u8], ) -> Poll> { - let res = self.poll_send(cx, &mut buf).map_err(Into::into); - - tracing::debug!("poll_write {res:?}"); - res + self.poll_send(cx, &mut buf).map_err(Into::into) } fn poll_flush( diff --git a/h3/src/client.rs b/h3/src/client.rs index 13bbe6d2..fc555478 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -142,7 +142,7 @@ where ) -> Result, Error> { let (peer_max_field_section_size, closing) = { let state = self.conn_state.read("send request lock state"); - (state.config.max_field_section_size, state.closing) + (state.peer_config.max_field_section_size, state.closing) }; if closing { diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 6c5f7d44..2eb13aed 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -29,7 +29,7 @@ use crate::{ #[doc(hidden)] pub struct SharedState { // Peer settings - pub config: Config, + pub peer_config: Config, // connection-wide error, concerns all RequestStreams and drivers pub error: Option, // Has a GOAWAY frame been sent or received? @@ -53,7 +53,7 @@ impl SharedStateRef { impl Default for SharedStateRef { fn default() -> Self { Self(Arc::new(RwLock::new(SharedState { - config: Config::default(), + peer_config: Config::default(), error: None, closing: false, }))) @@ -176,7 +176,6 @@ where tracing::debug!("Sending server settings: {settings:#x?}"); if config.send_grease { - tracing::debug!("Enabling send grease"); // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.4.1 //# Setting identifiers of the format 0x1f * N + 0x21 for non-negative @@ -363,7 +362,6 @@ where //# receipt of a second stream claiming to be a control stream MUST be //# treated as a connection error of type H3_STREAM_CREATION_ERROR. AcceptedRecvStream::Control(s) => { - tracing::debug!("Received control stream"); if self.control_recv.is_some() { return Err( self.close(Code::H3_STREAM_CREATION_ERROR, "got two control streams") @@ -455,21 +453,21 @@ where //# Endpoints MUST NOT consider such settings to have //# any meaning upon receipt. let mut shared = self.shared.write("connection settings write"); - shared.config.max_field_section_size = settings + shared.peer_config.max_field_section_size = settings .get(SettingId::MAX_HEADER_LIST_SIZE) .unwrap_or(VarInt::MAX.0); - shared.config.enable_webtransport = + shared.peer_config.enable_webtransport = settings.get(SettingId::ENABLE_WEBTRANSPORT).unwrap_or(0) != 0; - shared.config.max_webtransport_sessions = settings + shared.peer_config.max_webtransport_sessions = settings .get(SettingId::WEBTRANSPORT_MAX_SESSIONS) .unwrap_or(0); - shared.config.enable_datagram = + shared.peer_config.enable_datagram = settings.get(SettingId::H3_DATAGRAM).unwrap_or(0) != 0; - shared.config.enable_extended_connect = settings + shared.peer_config.enable_extended_connect = settings .get(SettingId::ENABLE_CONNECT_PROTOCOL) .unwrap_or(0) != 0; @@ -825,7 +823,7 @@ where let max_mem_size = self .conn_state .read("send_trailers shared state read") - .config + .peer_config .max_field_section_size; //= https://www.rfc-editor.org/rfc/rfc9114#section-4.2.2 diff --git a/h3/src/error.rs b/h3/src/error.rs index 7991db4c..2d217053 100644 --- a/h3/src/error.rs +++ b/h3/src/error.rs @@ -41,11 +41,17 @@ impl PartialEq for Code { /// The error kind. #[derive(Clone)] pub struct ErrorImpl { - /// The error kind. - pub kind: Kind, + pub(crate) kind: Kind, cause: Option>, } +impl ErrorImpl { + /// Returns the error kind + pub fn kind(&self) -> &Kind { + &self.kind + } +} + /// Some errors affect the whole connection, others only one Request or Stream. /// See [errors](https://www.rfc-editor.org/rfc/rfc9114.html#errors) for mor details. #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index dce073eb..26aa733e 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -233,7 +233,6 @@ impl TryFrom> for Header { fields.append(n, v); } Field::Protocol(p) => { - tracing::info!("Got protocol"); pseudo.protocol = Some(p); pseudo.len += 1; } diff --git a/h3/src/proto/varint.rs b/h3/src/proto/varint.rs index 5484eee2..60cdacf5 100644 --- a/h3/src/proto/varint.rs +++ b/h3/src/proto/varint.rs @@ -180,7 +180,6 @@ impl fmt::Display for VarInt { } pub trait BufExt { - /// Decodes a VarInt from the buffer and advances it fn get_var(&mut self) -> Result; } diff --git a/h3/src/request.rs b/h3/src/request.rs index 558a1168..f2a0217e 100644 --- a/h3/src/request.rs +++ b/h3/src/request.rs @@ -71,7 +71,6 @@ impl ResolveRequest { } }; - tracing::info!("Protocol: {protocol:?}"); // request_stream.stop_stream(Code::H3_MESSAGE_ERROR).await; let mut req = http::Request::new(()); *req.method_mut() = method; diff --git a/h3/src/server.rs b/h3/src/server.rs index 1f6e6d32..527d01ef 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -761,7 +761,7 @@ where .inner .conn_state .read("send_response") - .config + .peer_config .max_field_section_size; //= https://www.rfc-editor.org/rfc/rfc9114#section-4.2.2 diff --git a/h3/src/tests/connection.rs b/h3/src/tests/connection.rs index d8816a09..f57bd1a8 100644 --- a/h3/src/tests/connection.rs +++ b/h3/src/tests/connection.rs @@ -144,7 +144,7 @@ async fn settings_exchange_client() { if client .shared_state() .read("client") - .config + .peer_config .max_field_section_size == 12 { @@ -203,7 +203,12 @@ async fn settings_exchange_server() { let settings_change = async { for _ in 0..10 { - if state.read("setting_change").config.max_field_section_size == 12 { + if state + .read("setting_change") + .peer_config + .max_field_section_size + == 12 + { return; } tokio::time::sleep(Duration::from_millis(2)).await; diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index c662bb95..488b5f24 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -381,7 +381,7 @@ async fn header_too_big_client_error() { client .shared_state() .write("client") - .config + .peer_config .max_field_section_size = 12; let req = Request::get("http://localhost/salut").body(()).unwrap(); @@ -433,7 +433,7 @@ async fn header_too_big_client_error_trailer() { client .shared_state() .write("client") - .config + .peer_config .max_field_section_size = 200; let mut request_stream = client @@ -543,7 +543,7 @@ async fn header_too_big_discard_from_client() { incoming_req .shared_state() .write("client") - .config + .peer_config .max_field_section_size = u64::MAX; request_stream .send_response( @@ -631,7 +631,7 @@ async fn header_too_big_discard_from_client_trailers() { incoming_req .shared_state() .write("server") - .config + .peer_config .max_field_section_size = u64::MAX; request_stream @@ -703,7 +703,7 @@ async fn header_too_big_server_error() { incoming_req .shared_state() .write("server") - .config + .peer_config .max_field_section_size = 12; let err_kind = request_stream @@ -784,7 +784,7 @@ async fn header_too_big_server_error_trailers() { incoming_req .shared_state() .write("write") - .config + .peer_config .max_field_section_size = 200; let mut trailers = HeaderMap::new(); From cf2044bc1bda9874ffe0eb966ad9e1812062fad7 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Tue, 25 Apr 2023 17:02:55 +0200 Subject: [PATCH 69/97] fix: don't always buffer webtransport streams --- h3-webtransport/src/server.rs | 2 +- h3/src/connection.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 6802f267..36a6b7c4 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -391,7 +391,7 @@ where // Get the currently available streams let streams = conn.inner.accepted_streams_mut(); - if let Some((id, stream)) = streams.uni_streams.pop() { + if let Some((id, stream)) = streams.wt_uni_streams.pop() { return Poll::Ready(Ok(Some((id, RecvStream::new(stream))))); } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 2eb13aed..b8f5cc82 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -79,7 +79,7 @@ where C: quic::Connection, { #[allow(missing_docs)] - pub uni_streams: Vec<(SessionId, BufRecvStream)>, + pub wt_uni_streams: Vec<(SessionId, BufRecvStream)>, } impl Default for AcceptedStreams @@ -88,7 +88,7 @@ where { fn default() -> Self { Self { - uni_streams: Default::default(), + wt_uni_streams: Default::default(), } } } @@ -383,10 +383,10 @@ where ); } } - AcceptedRecvStream::WebTransportUni(id, s) => { + AcceptedRecvStream::WebTransportUni(id, s) if self.config.enable_webtransport => { // Store until someone else picks it up, like a webtransport session which is // not yet established. - self.accepted_streams.uni_streams.push((id, s)) + self.accepted_streams.wt_uni_streams.push((id, s)) } //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.3 From 64c1da7c207bc9e0865f7f574e6e1f5b0c187ef9 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 26 Apr 2023 09:42:29 +0200 Subject: [PATCH 70/97] fix: don't log header fields --- h3/src/proto/headers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index 26aa733e..18107dc3 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -86,7 +86,6 @@ impl Header { //# If the scheme does not have a mandatory authority component and none //# is provided in the request target, the request MUST NOT contain the //# :authority pseudo-header or Host header fields. - trace!("got headers {:#?}", self.fields); match (self.pseudo.authority, self.fields.get("host")) { (None, None) => return Err(HeaderError::MissingAuthority), (Some(a), None) => uri = uri.authority(a.as_str().as_bytes()), From f4e83fb9be28ef3ea99e5feed725550f0169e5c2 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 26 Apr 2023 11:58:50 +0200 Subject: [PATCH 71/97] feat: webtransport combined bidi streams --- examples/webtransport_server.rs | 73 +++++++------ h3-webtransport/src/server.rs | 39 +++---- h3-webtransport/src/stream.rs | 180 +++++++++++++++++++++++++------- h3/src/proto/headers.rs | 1 - h3/src/quic.rs | 2 +- 5 files changed, 199 insertions(+), 96 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 37b179b3..7c88c056 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -7,12 +7,12 @@ use futures::AsyncReadExt; use futures::AsyncWrite; use futures::AsyncWriteExt; use h3_webtransport::server; -use h3_webtransport::stream::RecvStream; -use h3_webtransport::stream::SendStream; +use h3_webtransport::stream; use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use structopt::StructOpt; +use tokio::pin; use tracing::{error, info, trace_span}; use h3::{ @@ -169,17 +169,7 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_connection(mut conn: Connection) -> Result<()> -where - C: 'static + Send + quic::Connection, - ::Error: - 'static + std::error::Error + Send + Sync + Into, - ::Error: - 'static + std::error::Error + Send + Sync + Into, - C::SendStream: Send + Unpin, - C::RecvStream: Send + Unpin, - C::OpenStreams: Send, -{ +async fn handle_connection(mut conn: Connection) -> Result<()> { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them // to the webtransport session. @@ -234,15 +224,14 @@ macro_rules! log_result { }; } -async fn echo_stream( - send: SendStream, - mut recv: RecvStream, -) -> anyhow::Result<()> +async fn echo_stream(send: T, recv: R) -> anyhow::Result<()> where - C: quic::Connection, - SendStream: AsyncWrite, - RecvStream: AsyncRead, + T: AsyncWrite, + R: AsyncRead, { + pin!(send); + pin!(recv); + tracing::info!("Got stream"); let mut buf = Vec::new(); recv.read_to_end(&mut buf).await?; @@ -266,6 +255,26 @@ async fn send_chunked(mut send: impl AsyncWrite + Unpin, data: Bytes) -> anyhow: Ok(()) } +async fn open_bidi_test(mut stream: S) -> anyhow::Result<()> +where + S: Unpin + AsyncRead + AsyncWrite, +{ + tracing::info!("Opening bidirectional stream"); + + stream + .write_all(b"Hello from a server initiated bidi stream") + .await + .context("Failed to respond")?; + + let mut resp = Vec::new(); + stream.close().await?; + stream.read_to_end(&mut resp).await?; + + tracing::info!("Got response from client: {resp:?}"); + + Ok(()) +} + /// This method will echo all inbound datagrams, unidirectional and bidirectional streams. #[tracing::instrument(level = "info", skip(session))] async fn handle_session_and_echo_all_inbound_messages( @@ -277,20 +286,21 @@ where 'static + std::error::Error + Send + Sync + Into, ::Error: 'static + std::error::Error + Send + Sync + Into, + stream::BidiStream: quic::BidiStream + Unpin + AsyncWrite + AsyncRead, + as quic::BidiStream>::SendStream: + Unpin + AsyncWrite + Send + Sync, + as quic::BidiStream>::RecvStream: + Unpin + AsyncRead + Send + Sync, C::SendStream: Send + Unpin, C::RecvStream: Send + Unpin, + C::BidiStream: Send + Unpin, { let session_id = session.session_id(); // This will open a bidirectional stream and send a message to the client right after connecting! - let (mut send, _read_bidi) = session.open_bi(session_id).await.unwrap(); - tracing::info!("Opening bidirectional stream"); - let bytes = Bytes::from("Hello from server"); - futures::AsyncWriteExt::write_all(&mut send, &bytes) - .await - .context("Failed to respond") - .unwrap(); - tokio::time::sleep(Duration::from_millis(1000)).await; + let stream = session.open_bi(session_id).await?; + + tokio::spawn(async move { log_result!(open_bidi_test(stream).await) }); loop { tokio::select! { @@ -311,11 +321,12 @@ where let (id, stream) = uni_stream?.unwrap(); let send = session.open_uni(id).await?; - tokio::spawn( async move { log_result!(echo_stream::(send, stream).await); }); + tokio::spawn( async move { log_result!(echo_stream(send, stream).await); }); } stream = session.accept_bi() => { - if let Some(server::AcceptedBi::BidiStream(_, send, recv)) = stream? { - tokio::spawn( async move { log_result!(echo_stream::(send, recv).await); }); + if let Some(server::AcceptedBi::BidiStream(_, stream)) = stream? { + let (send, recv) = quic::BidiStream::split(stream); + tokio::spawn( async move { log_result!(echo_stream(send, recv).await); }); } } else => { diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 36a6b7c4..029d5216 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -14,7 +14,7 @@ use h3::{ error::Code, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, - quic::{self, BidiStream as _, OpenStreams, SendStream as _, WriteBuf}, + quic::{self, OpenStreams, SendStream as _, WriteBuf}, server::{self, Connection, RequestStream}, Error, Protocol, }; @@ -23,7 +23,7 @@ use http::{Method, Request, Response, StatusCode}; use h3::webtransport::SessionId; use pin_project_lite::pin_project; -use crate::stream::{RecvStream, SendStream}; +use crate::stream::{BidiStream, RecvStream, SendStream}; /// WebTransport session driver. /// @@ -190,11 +190,12 @@ where Ok(None) => Ok(None), Ok(Some(Frame::WebTransportStream(session_id))) => { // Take the stream out of the framed reader and split it in half like Paul Allen - let (send, recv) = stream.into_inner().split(); - let send = SendStream::new(send); - let recv = RecvStream::new(recv); + let stream = stream.into_inner(); - Ok(Some(AcceptedBi::BidiStream(session_id, send, recv))) + Ok(Some(AcceptedBi::BidiStream( + session_id, + BidiStream::new(stream), + ))) } // Make the underlying HTTP/3 connection handle the rest frame => { @@ -238,8 +239,7 @@ where /// Streams are opened, but the initial webtransport header has not been sent type PendingStreams = ( - SendStream<::SendStream>, - RecvStream<::RecvStream>, + BidiStream<::BidiStream>, WriteBuf<&'static [u8]>, ); @@ -259,31 +259,28 @@ pin_project! { } impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { - type Output = Result<(SendStream, RecvStream), Error>; + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut p = self.project(); loop { match &mut p.stream { - Some((send, _, buf)) => { + Some((stream, buf)) => { while buf.has_remaining() { - ready!(send.poll_send(cx, buf))?; + ready!(stream.poll_send(cx, buf))?; } - let (send, recv, _) = p.stream.take().unwrap(); - return Poll::Ready(Ok((send, recv))); + let (stream, _) = p.stream.take().unwrap(); + return Poll::Ready(Ok(stream)); } None => { let mut opener = (*p.opener).lock().unwrap(); // Open the stream first let res = ready!(opener.poll_open_bidi(cx))?; - let (send, recv) = BufRecvStream::new(res).split(); - - let send: SendStream = SendStream::new(send); - let recv = RecvStream::new(recv); + let stream = BidiStream::new(BufRecvStream::new(res)); let buf = WriteBuf::from(BidiStreamHeader::WebTransportBidi(*p.session_id)); - *p.stream = Some((send, recv, buf)); + *p.stream = Some((stream, buf)); } } } @@ -334,11 +331,7 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { /// Since pub enum AcceptedBi { /// An incoming bidirectional stream - BidiStream( - SessionId, - SendStream, - RecvStream, - ), + BidiStream(SessionId, BidiStream), /// An incoming HTTP/3 request, passed through a webtransport session. /// /// This makes it possible to respond to multiple CONNECT requests diff --git a/h3-webtransport/src/stream.rs b/h3-webtransport/src/stream.rs index fd4aa824..61fa1f09 100644 --- a/h3-webtransport/src/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -46,37 +46,69 @@ where } } -impl AsyncRead for RecvStream -where - S: quic::RecvStream, - S::Error: Into, -{ - fn poll_read( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &mut [u8], - ) -> Poll> { - // If the buffer i empty, poll for more data - if !self.stream.buf_mut().has_remaining() { - let res = ready!(self.stream.poll_read(cx).map_err(Into::into))?; - if res { - return Poll::Ready(Ok(0)); - }; +macro_rules! async_read { + ($buf: ty) => { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: $buf, + ) -> Poll> { + // If the buffer i empty, poll for more data + if !self.stream.buf_mut().has_remaining() { + let res = ready!(self.stream.poll_read(cx).map_err(Into::into))?; + if res { + return Poll::Ready(Ok(0)); + }; + } + + let bytes = self.stream.buf_mut(); + + // Do not overfill + if let Some(chunk) = bytes.take_chunk(buf.len()) { + assert!(chunk.len() <= buf.len()); + let len = chunk.len().min(buf.len()); + buf[..len].copy_from_slice(&chunk); + + Poll::Ready(Ok(len)) + } else { + Poll::Ready(Ok(0)) + } } + }; +} - let bytes = self.stream.buf_mut(); +macro_rules! async_write { + ($buf: ty) => { + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + mut buf: $buf, + ) -> Poll> { + self.poll_send(cx, &mut buf).map_err(Into::into) + } - // Do not overfill - if let Some(chunk) = bytes.take_chunk(buf.len()) { - assert!(chunk.len() <= buf.len()); - let len = chunk.len().min(buf.len()); - buf[..len].copy_from_slice(&chunk); + fn poll_flush( + self: std::pin::Pin<&mut Self>, + _: &mut std::task::Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } - Poll::Ready(Ok(len)) - } else { - Poll::Ready(Ok(0)) + fn poll_close( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.poll_finish(cx).map_err(Into::into) } - } + }; +} + +impl AsyncRead for RecvStream +where + S: quic::RecvStream, + S::Error: Into, +{ + async_read!(&mut [u8]); } pin_project! { @@ -145,25 +177,93 @@ where S: quic::SendStream, S::Error: Into, { - fn poll_write( - mut self: std::pin::Pin<&mut Self>, + async_write!(&[u8]); +} + +pin_project! { + /// Combined send and receive stream. + /// + /// Can be split into a [`RecvStream`] and [`SendStream`] if the underlying QUIC implementation + /// supports it. + pub struct BidiStream { + stream: BufRecvStream, + } +} + +impl BidiStream { + pub(crate) fn new(stream: BufRecvStream) -> Self { + Self { stream } + } +} + +impl quic::SendStream for BidiStream { + type Error = S::Error; + + fn poll_send( + &mut self, cx: &mut std::task::Context<'_>, - mut buf: &[u8], - ) -> Poll> { - self.poll_send(cx, &mut buf).map_err(Into::into) + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) } - fn poll_flush( - self: std::pin::Pin<&mut Self>, - _: &mut std::task::Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) + fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + self.stream.poll_finish(cx) } - fn poll_close( - mut self: std::pin::Pin<&mut Self>, + fn reset(&mut self, reset_code: u64) { + self.stream.reset(reset_code) + } + + fn send_id(&self) -> quic::StreamId { + self.stream.send_id() + } +} + +impl quic::RecvStream for BidiStream { + type Buf = Bytes; + + type Error = S::Error; + + fn poll_data( + &mut self, cx: &mut std::task::Context<'_>, - ) -> Poll> { - self.poll_finish(cx).map_err(Into::into) + ) -> Poll, Self::Error>> { + self.stream.poll_data(cx) } + + fn stop_sending(&mut self, error_code: u64) { + self.stream.stop_sending(error_code) + } + + fn recv_id(&self) -> quic::StreamId { + self.stream.recv_id() + } +} + +impl quic::BidiStream for BidiStream { + type SendStream = SendStream; + + type RecvStream = RecvStream; + + fn split(self) -> (Self::SendStream, Self::RecvStream) { + let (send, recv) = self.stream.split(); + (SendStream::new(send), RecvStream::new(recv)) + } +} + +impl AsyncRead for BidiStream +where + S: quic::RecvStream, + S::Error: Into, +{ + async_read!(&mut [u8]); +} + +impl AsyncWrite for BidiStream +where + S: quic::SendStream, + S::Error: Into, +{ + async_write!(&[u8]); } diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index 18107dc3..a1c34f0f 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -10,7 +10,6 @@ use http::{ uri::{self, Authority, Parts, PathAndQuery, Scheme, Uri}, Extensions, HeaderMap, Method, StatusCode, }; -use tracing::trace; use crate::qpack::HeaderField; diff --git a/h3/src/quic.rs b/h3/src/quic.rs index bd8eea0f..d8ec4328 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -72,7 +72,7 @@ impl Error for SendDatagramError { /// Trait representing a QUIC connection. pub trait Connection { /// The type produced by `poll_accept_bidi()` - type BidiStream: BidiStream; + type BidiStream: SendStream + RecvStream; /// The type of the sending part of `BidiStream` type SendStream: SendStream; /// The type produced by `poll_accept_recv()` From 71415212e435f712b636bc3fb9bf4a3aa2ce66c9 Mon Sep 17 00:00:00 2001 From: Dario Lencina Date: Wed, 26 Apr 2023 08:40:23 -0400 Subject: [PATCH 72/97] remove DUMMY setting --- h3/src/proto/frame.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 51f65fd3..a63904c8 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -441,9 +441,6 @@ setting_identifiers! { H3_SETTING_ENABLE_DATAGRAM_CHROME_SPECIFIC= 0xFFD277, WEBTRANSPORT_MAX_SESSIONS = 0x2b603743, - // Dummy setting to check it is correctly ignored by the peer. - // https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1 - DUMMY = 0x21, } const SETTINGS_LEN: usize = 8; From 24e6e00e98b1cd830c1362b457ab893c46c005a3 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 3 May 2023 09:14:22 +0200 Subject: [PATCH 73/97] fix: typo --- h3/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/server.rs b/h3/src/server.rs index 527d01ef..c9beb455 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -1,4 +1,4 @@ -//! This mofield1ule provides methods to create a http/3 Server. +//! This module provides methods to create a http/3 Server. //! //! It allows to accept incoming requests, and send responses. //! From 63388af69e1ba51b3c2b7ce78072bef2c3015755 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 3 May 2023 17:25:00 +0200 Subject: [PATCH 74/97] fix: relax OpenStream bounds --- h3/src/quic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/quic.rs b/h3/src/quic.rs index d8ec4328..ef1c2976 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -133,7 +133,7 @@ pub trait Connection { /// Trait for opening outgoing streams pub trait OpenStreams { /// The type produced by `poll_open_bidi()` - type BidiStream: BidiStream; + type BidiStream: SendStream + RecvStream; /// The type produced by `poll_open_send()` type SendStream: SendStream; /// The type of the receiving part of `BidiStream` From 9ff3c2e78d92df0b5ba38abb99d7f72f67b5fd60 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 4 May 2023 14:34:31 +0200 Subject: [PATCH 75/97] chore: revert quic traits --- examples/server.rs | 6 +- examples/webtransport_server.rs | 15 +-- h3-quinn/src/lib.rs | 191 +++++++++++++++++++++----------- h3-webtransport/src/server.rs | 93 +++++++++------- h3-webtransport/src/stream.rs | 119 ++++++++++++-------- h3/src/client.rs | 89 +++++++++------ h3/src/connection.rs | 81 +++++++------- h3/src/frame.rs | 58 +++++----- h3/src/quic.rs | 55 +++++---- h3/src/request.rs | 13 ++- h3/src/server.rs | 94 +++++++++------- h3/src/stream.rs | 82 ++++++++------ h3/src/tests/connection.rs | 16 +-- h3/src/tests/mod.rs | 3 +- h3/src/tests/request.rs | 10 +- 15 files changed, 547 insertions(+), 378 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index e90c2159..339e7e25 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,6 +1,6 @@ use std::{net::SocketAddr, path::PathBuf, sync::Arc}; -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use http::{Request, StatusCode}; use rustls::{Certificate, PrivateKey}; use structopt::StructOpt; @@ -164,11 +164,11 @@ async fn main() -> Result<(), Box> { async fn handle_request( req: Request<()>, - mut stream: RequestStream, + mut stream: RequestStream, serve_root: Arc>, ) -> Result<(), Box> where - T: BidiStream, + T: BidiStream, { let (status, to_serve) = match serve_root.as_deref() { None => (StatusCode::OK, None), diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 7c88c056..f5d49514 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -169,7 +169,7 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_connection(mut conn: Connection) -> Result<()> { +async fn handle_connection(mut conn: Connection) -> Result<()> { // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. // if this is a webtransport session, then h3 needs to stop handing the datagrams, bidirectional streams, and unidirectional streams and give them // to the webtransport session. @@ -278,18 +278,19 @@ where /// This method will echo all inbound datagrams, unidirectional and bidirectional streams. #[tracing::instrument(level = "info", skip(session))] async fn handle_session_and_echo_all_inbound_messages( - session: WebTransportSession, + session: WebTransportSession, ) -> anyhow::Result<()> where - C: 'static + Send + h3::quic::Connection, - ::Error: + C: 'static + Send + h3::quic::Connection, + >::Error: 'static + std::error::Error + Send + Sync + Into, ::Error: 'static + std::error::Error + Send + Sync + Into, - stream::BidiStream: quic::BidiStream + Unpin + AsyncWrite + AsyncRead, - as quic::BidiStream>::SendStream: + stream::BidiStream: + quic::BidiStream + Unpin + AsyncWrite + AsyncRead, + as quic::BidiStream>::SendStream: Unpin + AsyncWrite + Send + Sync, - as quic::BidiStream>::RecvStream: + as quic::BidiStream>::RecvStream: Unpin + AsyncRead + Send + Sync, C::SendStream: Send + Unpin, C::RecvStream: Send + Unpin, diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 232a5e9f..203472b2 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -7,7 +7,6 @@ use std::{ convert::TryInto, fmt::{self, Display}, future::Future, - pin::Pin, sync::Arc, task::{self, Poll}, }; @@ -17,14 +16,17 @@ use bytes::{Buf, Bytes}; use futures::{ ready, stream::{self, BoxStream}, - AsyncWrite, StreamExt, + StreamExt, }; pub use quinn::{ self, crypto::Session, AcceptBi, AcceptUni, Endpoint, OpenBi, OpenUni, VarInt, WriteError, }; use quinn::{ReadDatagram, SendDatagramError}; -use h3::quic::{self, Error, StreamId}; +use h3::{ + quic::{self, Error, StreamId}, + stream::WriteBuf, +}; use tokio_util::sync::ReusableBoxFuture; /// A QUIC connection backed by Quinn @@ -95,10 +97,13 @@ impl From for ConnectionError { } } -impl quic::Connection for Connection { - type SendStream = SendStream; +impl quic::Connection for Connection +where + B: Buf, +{ + type SendStream = SendStream; type RecvStream = RecvStream; - type BidiStream = BidiStream; + type BidiStream = BidiStream; type OpenStreams = OpenStreams; type Error = ConnectionError; @@ -211,10 +216,13 @@ pub struct OpenStreams { opening_uni: Option as Future>::Output>>, } -impl quic::OpenStreams for OpenStreams { +impl quic::OpenStreams for OpenStreams +where + B: Buf, +{ type RecvStream = RecvStream; - type SendStream = SendStream; - type BidiStream = BidiStream; + type SendStream = SendStream; + type BidiStream = BidiStream; type Error = ConnectionError; fn poll_open_bidi( @@ -235,7 +243,7 @@ impl quic::OpenStreams for OpenStreams { })) } - fn poll_open_uni( + fn poll_open_send( &mut self, cx: &mut task::Context<'_>, ) -> Poll> { @@ -271,13 +279,19 @@ impl Clone for OpenStreams { /// /// Implements [`quic::BidiStream`] which allows the stream to be split /// into two structs each implementing one direction. -pub struct BidiStream { - send: SendStream, +pub struct BidiStream +where + B: Buf, +{ + send: SendStream, recv: RecvStream, } -impl quic::BidiStream for BidiStream { - type SendStream = SendStream; +impl quic::BidiStream for BidiStream +where + B: Buf, +{ + type SendStream = SendStream; type RecvStream = RecvStream; fn split(self) -> (Self::SendStream, Self::RecvStream) { @@ -285,7 +299,7 @@ impl quic::BidiStream for BidiStream { } } -impl quic::RecvStream for BidiStream { +impl quic::RecvStream for BidiStream { type Buf = Bytes; type Error = ReadError; @@ -305,15 +319,14 @@ impl quic::RecvStream for BidiStream { } } -impl quic::SendStream for BidiStream { +impl quic::SendStream for BidiStream +where + B: Buf, +{ type Error = SendStreamError; - fn poll_send( - &mut self, - cx: &mut task::Context<'_>, - buf: &mut D, - ) -> Poll> { - self.send.poll_send(cx, buf) + 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> { @@ -324,6 +337,10 @@ impl quic::SendStream for BidiStream { 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() } @@ -452,63 +469,91 @@ impl Error for ReadError { /// Quinn-backed send stream /// /// Implements a [`quic::SendStream`] backed by a [`quinn::SendStream`]. -pub struct SendStream { - stream: quinn::SendStream, +pub struct SendStream { + stream: Option, + writing: Option>, + write_fut: WriteFuture, } -impl SendStream { - fn new(stream: quinn::SendStream) -> SendStream { - Self { stream } +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 { +impl quic::SendStream for SendStream +where + B: Buf, +{ type Error = SendStreamError; - fn poll_send( - &mut self, - cx: &mut task::Context<'_>, - buf: &mut D, - ) -> Poll> { - let s = Pin::new(&mut self.stream); - - let res = ready!(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(*err))) + 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(())) } fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll> { - self.stream.poll_finish(cx).map_err(Into::into) + 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>>(&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.id().0.try_into().expect("invalid stream id") + self.stream + .as_ref() + .unwrap() + .id() + .0 + .try_into() + .expect("invalid stream id") } } @@ -516,11 +561,22 @@ impl quic::SendStream for SendStream { /// /// Wraps errors that can happen writing to or polling a send stream. #[derive(Debug)] -pub struct SendStreamError(WriteError); +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 for std::io::Error { fn from(value: SendStreamError) -> Self { - value.0.into() + match value { + SendStreamError::Write(err) => err.into(), + SendStreamError::NotReady => { + std::io::Error::new(std::io::ErrorKind::Other, "send stream is not ready") + } + } } } @@ -534,7 +590,7 @@ impl Display for SendStreamError { impl From for SendStreamError { fn from(e: WriteError) -> Self { - Self(e) + Self::Write(e) } } @@ -542,7 +598,7 @@ impl Error for SendStreamError { fn is_timeout(&self) -> bool { matches!( self, - Self(quinn::WriteError::ConnectionLost( + Self::Write(quinn::WriteError::ConnectionLost( quinn::ConnectionError::TimedOut )) ) @@ -550,10 +606,13 @@ impl Error for SendStreamError { fn err_code(&self) -> Option { match self { - Self(quinn::WriteError::Stopped(error_code)) => Some(error_code.into_inner()), - Self(quinn::WriteError::ConnectionLost(quinn::ConnectionError::ApplicationClosed( - quinn_proto::ApplicationClose { error_code, .. }, - ))) => Some(error_code.into_inner()), + 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, } } diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 029d5216..901e8248 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -1,6 +1,7 @@ //! Provides the server side WebTransport session use std::{ + marker::PhantomData, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, @@ -30,28 +31,30 @@ use crate::stream::{BidiStream, RecvStream, SendStream}; /// Maintains the session using the underlying HTTP/3 connection. /// /// Similar to [`crate::Connection`] it is generic over the QUIC implementation and Buffer. -pub struct WebTransportSession +pub struct WebTransportSession where - C: quic::Connection, + C: quic::Connection, + B: Buf, { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: SessionId, - conn: Mutex>, - connect_stream: RequestStream, + conn: Mutex>, + connect_stream: RequestStream, opener: Mutex, } -impl WebTransportSession +impl WebTransportSession where - C: quic::Connection, + C: quic::Connection, + B: Buf, { /// Accepts a *CONNECT* request for establishing a WebTransport session. /// /// TODO: is the API or the user responsible for validating the CONNECT request? pub async fn accept( request: Request<()>, - mut stream: RequestStream, - mut conn: Connection, + mut stream: RequestStream, + mut conn: Connection, ) -> Result { let shared = conn.shared_state().clone(); { @@ -121,9 +124,10 @@ where } /// Receive a datagram from the client - pub fn accept_datagram(&self) -> ReadDatagram { + pub fn accept_datagram(&self) -> ReadDatagram { ReadDatagram { conn: self.conn.lock().unwrap().inner.conn.clone(), + _marker: PhantomData, } } @@ -140,12 +144,12 @@ where } /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. - pub fn accept_uni(&self) -> AcceptUni { + pub fn accept_uni(&self) -> AcceptUni { AcceptUni { conn: &self.conn } } /// Accepts an incoming bidirectional stream or request - pub async fn accept_bi(&self) -> Result>, Error> { + pub async fn accept_bi(&self) -> Result>, Error> { // Get the next stream // Accept the incoming stream let stream = poll_fn(|cx| { @@ -214,7 +218,7 @@ where } /// Open a new bidirectional stream - pub fn open_bi(&self, session_id: SessionId) -> OpenBi { + pub fn open_bi(&self, session_id: SessionId) -> OpenBi { OpenBi { opener: &self.opener, stream: None, @@ -223,7 +227,7 @@ where } /// Open a new unidirectional stream - pub fn open_uni(&self, session_id: SessionId) -> OpenUni { + pub fn open_uni(&self, session_id: SessionId) -> OpenUni { OpenUni { opener: &self.opener, stream: None, @@ -238,28 +242,28 @@ where } /// Streams are opened, but the initial webtransport header has not been sent -type PendingStreams = ( - BidiStream<::BidiStream>, +type PendingStreams = ( + BidiStream<>::BidiStream, B>, WriteBuf<&'static [u8]>, ); /// Streams are opened, but the initial webtransport header has not been sent -type PendingUniStreams = ( - SendStream<::SendStream>, +type PendingUniStreams = ( + SendStream<>::SendStream, B>, WriteBuf<&'static [u8]>, ); pin_project! { /// Future for opening a bidi stream - pub struct OpenBi<'a, C: quic::Connection> { + pub struct OpenBi<'a, C:quic::Connection, B:Buf> { opener: &'a Mutex, - stream: Option>, + stream: Option>, session_id: SessionId, } } -impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { - type Output = Result, Error>; +impl<'a, B: Buf, C: quic::Connection> Future for OpenBi<'a, C, B> { + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut p = self.project(); @@ -267,7 +271,8 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { match &mut p.stream { Some((stream, buf)) => { while buf.has_remaining() { - ready!(stream.poll_send(cx, buf))?; + todo!() + // ready!(stream.poll_send(cx, buf))?; } let (stream, _) = p.stream.take().unwrap(); @@ -289,16 +294,16 @@ impl<'a, C: quic::Connection> Future for OpenBi<'a, C> { pin_project! { /// Opens a unidirectional stream - pub struct OpenUni<'a, C: quic::Connection> { + pub struct OpenUni<'a, C: quic::Connection, B:Buf> { opener: &'a Mutex, - stream: Option>, + stream: Option>, // Future for opening a uni stream session_id: SessionId, } } -impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { - type Output = Result, Error>; +impl<'a, C: quic::Connection, B: Buf> Future for OpenUni<'a, C, B> { + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut p = self.project(); @@ -306,7 +311,8 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { match &mut p.stream { Some((send, buf)) => { while buf.has_remaining() { - ready!(send.poll_send(cx, buf))?; + todo!() + // ready!(send.poll_send(cx, buf))?; } let (send, buf) = p.stream.take().unwrap(); assert!(!buf.has_remaining()); @@ -314,7 +320,7 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { } None => { let mut opener = (*p.opener).lock().unwrap(); - let send = ready!(opener.poll_open_uni(cx))?; + let send = ready!(opener.poll_open_send(cx))?; let send = BufRecvStream::new(send); let send = SendStream::new(send); @@ -329,26 +335,25 @@ impl<'a, C: quic::Connection> Future for OpenUni<'a, C> { /// An accepted incoming bidirectional stream. /// /// Since -pub enum AcceptedBi { +pub enum AcceptedBi, B: Buf> { /// An incoming bidirectional stream - BidiStream(SessionId, BidiStream), + BidiStream(SessionId, BidiStream), /// An incoming HTTP/3 request, passed through a webtransport session. /// /// This makes it possible to respond to multiple CONNECT requests - Request(Request<()>, RequestStream), + Request(Request<()>, RequestStream), } /// Future for [`Connection::read_datagram`] -pub struct ReadDatagram -where - C: quic::Connection, -{ +pub struct ReadDatagram { conn: Arc>, + _marker: PhantomData, } -impl Future for ReadDatagram +impl Future for ReadDatagram where - C: quic::Connection, + C: quic::Connection, + B: Buf, { type Output = Result, Error>; @@ -365,18 +370,20 @@ where } /// Future for [`WebTransportSession::accept_uni`] -pub struct AcceptUni<'a, C> +pub struct AcceptUni<'a, C, B> where - C: quic::Connection, + C: quic::Connection, + B: Buf, { - conn: &'a Mutex>, + conn: &'a Mutex>, } -impl<'a, C> Future for AcceptUni<'a, C> +impl<'a, C, B> Future for AcceptUni<'a, C, B> where - C: quic::Connection, + C: quic::Connection, + B: Buf, { - type Output = Result)>, Error>; + type Output = Result)>, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut conn = self.conn.lock().unwrap(); diff --git a/h3-webtransport/src/stream.rs b/h3-webtransport/src/stream.rs index 61fa1f09..deb06766 100644 --- a/h3-webtransport/src/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -10,21 +10,22 @@ use pin_project_lite::pin_project; pin_project! { /// WebTransport receive stream - pub struct RecvStream { - stream: BufRecvStream, + pub struct RecvStream { + stream: BufRecvStream, } } -impl RecvStream { +impl RecvStream { #[allow(missing_docs)] - pub fn new(stream: BufRecvStream) -> Self { + pub fn new(stream: BufRecvStream) -> Self { Self { stream } } } -impl quic::RecvStream for RecvStream +impl quic::RecvStream for RecvStream where S: quic::RecvStream, + B: Buf, { type Buf = Bytes; @@ -84,7 +85,12 @@ macro_rules! async_write { cx: &mut std::task::Context<'_>, mut buf: $buf, ) -> Poll> { - self.poll_send(cx, &mut buf).map_err(Into::into) + // self.send_data(buf.into())?; + + // ready!(self.poll_ready(cx)?); + + let len = buf.len(); + Poll::Ready(Ok(len)) } fn poll_flush( @@ -103,22 +109,23 @@ macro_rules! async_write { }; } -impl AsyncRead for RecvStream +impl AsyncRead for RecvStream where S: quic::RecvStream, S::Error: Into, + B: Buf, { async_read!(&mut [u8]); } pin_project! { /// WebTransport send stream - pub struct SendStream { - stream: BufRecvStream, + pub struct SendStream { + stream: BufRecvStream, } } -impl std::fmt::Debug for SendStream { +impl std::fmt::Debug for SendStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SendStream") .field("stream", &self.stream) @@ -126,39 +133,36 @@ impl std::fmt::Debug for SendStream { } } -impl SendStream { +impl SendStream { #[allow(missing_docs)] - pub fn new(stream: BufRecvStream) -> Self { + pub(crate) fn new(stream: BufRecvStream) -> Self { Self { stream } } } -impl SendStream +impl SendStream where - S: quic::SendStream, + S: quic::SendStream, + B: Buf, { /// Write bytes to the stream. /// /// Returns the number of bytes written - pub async fn write(&mut self, buf: &mut impl Buf) -> Result { - future::poll_fn(|cx| quic::SendStream::poll_send(self, cx, buf)).await + pub async fn write(&mut self, buf: impl Buf) -> Result<(), S::Error> { + todo!(); + // self.send_data(buf.into())?; + future::poll_fn(|cx| quic::SendStream::poll_ready(self, cx)).await?; + Ok(()) } } -impl quic::SendStream for SendStream +impl quic::SendStream for SendStream where - S: quic::SendStream, + S: quic::SendStream, + B: Buf, { type Error = S::Error; - fn poll_send( - &mut self, - cx: &mut std::task::Context<'_>, - buf: &mut D, - ) -> Poll> { - self.stream.poll_send(cx, buf) - } - fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { self.stream.poll_finish(cx) } @@ -170,11 +174,20 @@ where fn send_id(&self) -> quic::StreamId { self.stream.send_id() } + + fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { + todo!() + } + + fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + todo!() + } } -impl AsyncWrite for SendStream +impl AsyncWrite for SendStream where - S: quic::SendStream, + S: quic::SendStream, + B: Buf, S::Error: Into, { async_write!(&[u8]); @@ -185,28 +198,24 @@ pin_project! { /// /// Can be split into a [`RecvStream`] and [`SendStream`] if the underlying QUIC implementation /// supports it. - pub struct BidiStream { - stream: BufRecvStream, + pub struct BidiStream { + stream: BufRecvStream, } } -impl BidiStream { - pub(crate) fn new(stream: BufRecvStream) -> Self { +impl BidiStream { + pub(crate) fn new(stream: BufRecvStream) -> Self { Self { stream } } } -impl quic::SendStream for BidiStream { +impl quic::SendStream for BidiStream +where + S: quic::SendStream, + B: Buf, +{ type Error = S::Error; - fn poll_send( - &mut self, - cx: &mut std::task::Context<'_>, - buf: &mut D, - ) -> Poll> { - self.stream.poll_send(cx, buf) - } - fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { self.stream.poll_finish(cx) } @@ -218,9 +227,17 @@ impl quic::SendStream for BidiStream { fn send_id(&self) -> quic::StreamId { self.stream.send_id() } + + fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + todo!() + } + + fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { + todo!() + } } -impl quic::RecvStream for BidiStream { +impl quic::RecvStream for BidiStream { type Buf = Bytes; type Error = S::Error; @@ -241,10 +258,14 @@ impl quic::RecvStream for BidiStream { } } -impl quic::BidiStream for BidiStream { - type SendStream = SendStream; +impl quic::BidiStream for BidiStream +where + S: quic::BidiStream, + B: Buf, +{ + type SendStream = SendStream; - type RecvStream = RecvStream; + type RecvStream = RecvStream; fn split(self) -> (Self::SendStream, Self::RecvStream) { let (send, recv) = self.stream.split(); @@ -252,18 +273,20 @@ impl quic::BidiStream for BidiStream { } } -impl AsyncRead for BidiStream +impl AsyncRead for BidiStream where S: quic::RecvStream, S::Error: Into, + B: Buf, { async_read!(&mut [u8]); } -impl AsyncWrite for BidiStream +impl AsyncWrite for BidiStream where - S: quic::SendStream, + S: quic::SendStream, S::Error: Into, + B: Buf, { async_write!(&[u8]); } diff --git a/h3/src/client.rs b/h3/src/client.rs index fc555478..b3bdf695 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -2,6 +2,7 @@ use std::{ convert::TryFrom, + marker::PhantomData, sync::{atomic::AtomicUsize, Arc}, task::{Context, Poll, Waker}, }; @@ -28,10 +29,10 @@ pub fn builder() -> Builder { } /// Create a new HTTP/3 client with default settings -pub async fn new(conn: C) -> Result<(Connection, SendRequest), Error> +pub async fn new(conn: C) -> Result<(Connection, SendRequest), Error> where - C: quic::Connection, - O: quic::OpenStreams, + C: quic::Connection, + O: quic::OpenStreams, { //= https://www.rfc-editor.org/rfc/rfc9114#section-3.3 //= type=implication @@ -118,9 +119,10 @@ where /// [`send_request()`]: struct.SendRequest.html#method.send_request /// [`RequestStream`]: struct.RequestStream.html /// [`RequestStream::finish()`]: struct.RequestStream.html#method.finish -pub struct SendRequest +pub struct SendRequest where - T: quic::OpenStreams, + T: quic::OpenStreams, + B: Buf, { open: T, conn_state: SharedStateRef, @@ -128,18 +130,20 @@ where // counts instances of SendRequest to close the connection when the last is dropped. sender_count: Arc, conn_waker: Option, + _buf: PhantomData, send_grease_frame: bool, } -impl SendRequest +impl SendRequest where - T: quic::OpenStreams, + T: quic::OpenStreams, + B: Buf, { /// Send a HTTP/3 request to the server pub async fn send_request( &mut self, req: http::Request<()>, - ) -> Result, Error> { + ) -> Result, Error> { let (peer_max_field_section_size, closing) = { let state = self.conn_state.read("send request lock state"); (state.peer_config.max_field_section_size, state.closing) @@ -189,7 +193,7 @@ where return Err(Error::header_too_big(mem_size, peer_max_field_section_size)); } - stream::write::<_, _, Bytes>(&mut stream, Frame::Headers(block.freeze())) + stream::write(&mut stream, Frame::Headers(block.freeze())) .await .map_err(|e| self.maybe_conn_err(e))?; @@ -207,18 +211,20 @@ where } } -impl ConnectionState for SendRequest +impl ConnectionState for SendRequest where - T: quic::OpenStreams, + T: quic::OpenStreams, + B: Buf, { fn shared_state(&self) -> &SharedStateRef { &self.conn_state } } -impl Clone for SendRequest +impl Clone for SendRequest where - T: quic::OpenStreams + Clone, + T: quic::OpenStreams + Clone, + B: Buf, { fn clone(&self) -> Self { self.sender_count @@ -230,14 +236,16 @@ where max_field_section_size: self.max_field_section_size, sender_count: self.sender_count.clone(), conn_waker: self.conn_waker.clone(), + _buf: PhantomData, send_grease_frame: self.send_grease_frame, } } } -impl Drop for SendRequest +impl Drop for SendRequest where - T: quic::OpenStreams, + T: quic::OpenStreams, + B: Buf, { fn drop(&mut self) { if self @@ -334,20 +342,22 @@ where /// ``` /// [`poll_close()`]: struct.Connection.html#method.poll_close /// [`shutdown()`]: struct.Connection.html#method.shutdown -pub struct Connection +pub struct Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { - inner: ConnectionInner, + inner: ConnectionInner, // Has a GOAWAY frame been sent? If so, this PushId is the last we are willing to accept. sent_closing: Option, // Has a GOAWAY frame been received? If so, this is StreamId the last the remote will accept. recv_closing: Option, } -impl Connection +impl Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { /// Initiate a graceful shutdown, accepting `max_push` potentially in-flight server pushes pub async fn shutdown(&mut self, _max_push: usize) -> Result<(), Error> { @@ -490,10 +500,14 @@ impl Builder { } /// Create a new HTTP/3 client from a `quic` connection - pub async fn build(&mut self, quic: C) -> Result<(Connection, SendRequest), Error> + pub async fn build( + &mut self, + quic: C, + ) -> Result<(Connection, SendRequest), Error> where - C: quic::Connection, - O: quic::OpenStreams, + C: quic::Connection, + O: quic::OpenStreams, + B: Buf, { let open = quic.opener(); let conn_state = SharedStateRef::default(); @@ -513,6 +527,7 @@ impl Builder { max_field_section_size: self.config.max_field_section_size, sender_count: Arc::new(AtomicUsize::new(1)), send_grease_frame: self.config.send_grease, + _buf: PhantomData, }, )) } @@ -570,19 +585,20 @@ impl Builder { /// [`recv_trailers()`]: #method.recv_trailers /// [`finish()`]: #method.finish /// [`stop_sending()`]: #method.stop_sending -pub struct RequestStream { - inner: connection::RequestStream, +pub struct RequestStream { + inner: connection::RequestStream, } -impl ConnectionState for RequestStream { +impl ConnectionState for RequestStream { fn shared_state(&self) -> &SharedStateRef { &self.inner.conn_state } } -impl RequestStream +impl RequestStream where S: quic::RecvStream, + B: Buf, { /// Receive the HTTP/3 response /// @@ -671,12 +687,13 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::SendStream, + S: quic::SendStream, + B: Buf, { /// Send some data on the request body. - pub async fn send_data(&mut self, buf: impl Buf) -> Result<(), Error> { + pub async fn send_data(&mut self, buf: B) -> Result<(), Error> { self.inner.send_data(buf).await } @@ -707,12 +724,18 @@ where //# [QUIC-TRANSPORT]. } -impl RequestStream +impl RequestStream where - S: quic::BidiStream, + S: quic::BidiStream, + B: Buf, { /// Split this stream into two halves that can be driven independently. - pub fn split(self) -> (RequestStream, RequestStream) { + pub fn split( + self, + ) -> ( + RequestStream, + RequestStream, + ) { let (send, recv) = self.inner.split(); (RequestStream { inner: send }, RequestStream { inner: recv }) } diff --git a/h3/src/connection.rs b/h3/src/connection.rs index b8f5cc82..27f32a33 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -74,17 +74,19 @@ pub trait ConnectionState { } #[allow(missing_docs)] -pub struct AcceptedStreams +pub struct AcceptedStreams where - C: quic::Connection, + C: quic::Connection, + B: Buf, { #[allow(missing_docs)] - pub wt_uni_streams: Vec<(SessionId, BufRecvStream)>, + pub wt_uni_streams: Vec<(SessionId, BufRecvStream)>, } -impl Default for AcceptedStreams +impl Default for AcceptedStreams where - C: quic::Connection, + C: quic::Connection, + B: Buf, { fn default() -> Self { Self { @@ -94,17 +96,18 @@ where } #[allow(missing_docs)] -pub struct ConnectionInner +pub struct ConnectionInner where - C: quic::Connection, + C: quic::Connection, + B: Buf, { pub(super) shared: SharedStateRef, /// TODO: breaking encapsulation just to see if we can get this to work, will fix before merging pub conn: Arc>, control_send: C::SendStream, - control_recv: Option>, - decoder_recv: Option>, - encoder_recv: Option>, + control_recv: Option>, + decoder_recv: Option>, + encoder_recv: Option>, /// Buffers incoming uni/recv streams which have yet to be claimed. /// /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. @@ -124,18 +127,19 @@ where /// STOP_SENDING with the H3_WEBTRANSPORT_BUFFERED_STREAM_REJECTED error code. When the /// number of buffered datagrams is exceeded, a datagram SHALL be dropped. It is up to an /// implementation to choose what stream or datagram to discard. - accepted_streams: AcceptedStreams, + accepted_streams: AcceptedStreams, - pending_recv_streams: Vec>, + pending_recv_streams: Vec>, got_peer_settings: bool, pub send_grease_frame: bool, pub config: Config, } -impl ConnectionInner +impl ConnectionInner where - C: quic::Connection, + C: quic::Connection, + B: Buf, { /// Initiates the connection and opens a control stream pub async fn new(mut conn: C, shared: SharedStateRef, config: Config) -> Result { @@ -222,7 +226,7 @@ where //# the peer prior to sending the SETTINGS frame; settings MUST be sent //# as soon as the transport is ready to send data. trace!("Sending Settings frame: {settings:#x?}"); - stream::write::<_, _, Bytes>( + stream::write( &mut control_send, WriteBuf::from(UniStreamHeader::Control(settings)), ) @@ -287,7 +291,7 @@ where //# (Section 5.2) so that both endpoints can reliably determine whether //# previously sent frames have been processed and gracefully complete or //# terminate any necessary remaining tasks. - stream::write::<_, _, Bytes>(&mut self.control_send, Frame::Goaway(max_id.into())).await + stream::write(&mut self.control_send, Frame::Goaway(max_id.into())).await } #[allow(missing_docs)] @@ -604,12 +608,7 @@ where //# types be ignored. These streams have no semantics, and they can be //# sent when application-layer padding is desired. They MAY also be //# sent on connections where no data is currently being transferred. - match stream::write::<_, _, Bytes>( - &mut grease_stream, - (StreamType::grease(), Frame::Grease), - ) - .await - { + match stream::write(&mut grease_stream, (StreamType::grease(), Frame::Grease)).await { Ok(()) => (), Err(err) => { warn!("write data on grease stream failed with {}", err); @@ -635,24 +634,24 @@ where } #[allow(missing_docs)] - pub fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { + pub fn accepted_streams_mut(&mut self) -> &mut AcceptedStreams { &mut self.accepted_streams } } #[allow(missing_docs)] -pub struct RequestStream { - pub(super) stream: FrameStream, +pub struct RequestStream { + pub(super) stream: FrameStream, pub(super) trailers: Option, pub(super) conn_state: SharedStateRef, pub(super) max_field_section_size: u64, send_grease_frame: bool, } -impl RequestStream { +impl RequestStream { #[allow(missing_docs)] pub fn new( - stream: FrameStream, + stream: FrameStream, max_field_section_size: u64, conn_state: SharedStateRef, grease: bool, @@ -667,15 +666,16 @@ impl RequestStream { } } -impl ConnectionState for RequestStream { +impl ConnectionState for RequestStream { fn shared_state(&self) -> &SharedStateRef { &self.conn_state } } -impl RequestStream +impl RequestStream where S: quic::RecvStream, + B: Buf, { /// Receive some of the request body. pub async fn recv_data(&mut self) -> Result, Error> { @@ -797,12 +797,13 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::SendStream, + S: quic::SendStream, + B: Buf, { /// Send some data on the response body. - pub async fn send_data(&mut self, buf: impl Buf) -> Result<(), Error> { + pub async fn send_data(&mut self, buf: B) -> Result<(), Error> { let frame = Frame::Data(buf); stream::write(&mut self.stream, frame) @@ -834,7 +835,7 @@ where if mem_size > max_mem_size { return Err(Error::header_too_big(mem_size, max_mem_size)); } - stream::write::<_, _, Bytes>(&mut self.stream, Frame::Headers(block.freeze())) + stream::write(&mut self.stream, Frame::Headers(block.freeze())) .await .map_err(|e| self.maybe_conn_err(e))?; @@ -850,7 +851,7 @@ where pub async fn finish(&mut self) -> Result<(), Error> { if self.send_grease_frame { // send a grease frame once per Connection - stream::write::<_, _, Bytes>(&mut self.stream, Frame::Grease) + stream::write(&mut self.stream, Frame::Grease) .await .map_err(|e| self.maybe_conn_err(e))?; self.send_grease_frame = false; @@ -862,11 +863,17 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::BidiStream, + S: quic::BidiStream, + B: Buf, { - pub(crate) fn split(self) -> (RequestStream, RequestStream) { + pub(crate) fn split( + self, + ) -> ( + RequestStream, + RequestStream, + ) { let (send, recv) = self.stream.split(); ( diff --git a/h3/src/frame.rs b/h3/src/frame.rs index b95dc6fc..3c8a58dc 100644 --- a/h3/src/frame.rs +++ b/h3/src/frame.rs @@ -5,7 +5,7 @@ use bytes::Buf; use futures_util::ready; use tracing::trace; -use crate::stream::BufRecvStream; +use crate::stream::{BufRecvStream, WriteBuf}; use crate::{ buf::BufList, error::TransportError, @@ -17,15 +17,15 @@ use crate::{ }; /// Decodes Frames from the underlying QUIC stream -pub struct FrameStream { - pub stream: BufRecvStream, +pub struct FrameStream { + pub stream: BufRecvStream, // Already read data from the stream decoder: FrameDecoder, remaining_data: usize, } -impl FrameStream { - pub fn new(stream: BufRecvStream) -> Self { +impl FrameStream { + pub fn new(stream: BufRecvStream) -> Self { Self { stream, decoder: FrameDecoder::default(), @@ -35,12 +35,12 @@ impl FrameStream { /// Unwraps the Framed streamer and returns the underlying stream **without** data loss for /// partially received/read frames. - pub fn into_inner(self) -> BufRecvStream { + pub fn into_inner(self) -> BufRecvStream { self.stream } } -impl FrameStream +impl FrameStream where S: RecvStream, { @@ -144,11 +144,20 @@ where } } -impl SendStream for FrameStream +impl SendStream for FrameStream where - T: SendStream, + T: SendStream, + B: Buf, { - type Error = ::Error; + type Error = >::Error; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.stream.poll_ready(cx) + } + + fn send_data>>(&mut self, data: D) -> Result<(), Self::Error> { + self.stream.send_data(data) + } fn poll_finish(&mut self, cx: &mut Context<'_>) -> Poll> { self.stream.poll_finish(cx) @@ -161,21 +170,14 @@ where fn send_id(&self) -> StreamId { self.stream.send_id() } - - fn poll_send( - &mut self, - cx: &mut std::task::Context<'_>, - buf: &mut D, - ) -> Poll> { - self.stream.poll_send(cx, buf) - } } -impl FrameStream +impl FrameStream where - S: BidiStream, + S: BidiStream, + B: Buf, { - pub(crate) fn split(self) -> (FrameStream, FrameStream) { + pub(crate) fn split(self) -> (FrameStream, FrameStream) { let (send, recv) = self.stream.split(); ( FrameStream { @@ -368,7 +370,7 @@ mod tests { Frame::headers(&b"trailer"[..]).encode_with_payload(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!(|cx| stream.poll_next(cx), Ok(Some(Frame::Headers(_)))); assert_poll_matches!( @@ -390,7 +392,7 @@ mod tests { Frame::headers(&b"header"[..]).encode_with_payload(&mut buf); let mut buf = buf.freeze(); recv.chunk(buf.split_to(buf.len() - 1)); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -409,7 +411,7 @@ mod tests { FrameType::DATA.encode(&mut buf); VarInt::from(4u32).encode(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -431,7 +433,7 @@ mod tests { let mut buf = buf.freeze(); recv.chunk(buf.split_to(buf.len() - 2)); recv.chunk(buf); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); // We get the total size of data about to be received assert_poll_matches!( @@ -460,7 +462,7 @@ mod tests { VarInt::from(4u32).encode(&mut buf); buf.put_slice(&b"b"[..]); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -492,7 +494,7 @@ mod tests { Frame::Data(Bytes::from("body")).encode_with_payload(&mut buf); recv.chunk(buf.freeze()); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), @@ -514,7 +516,7 @@ mod tests { buf.put_slice(&b"bo"[..]); recv.chunk(buf.clone().freeze()); - let mut stream: FrameStream<_> = FrameStream::new(BufRecvStream::new(recv)); + let mut stream: FrameStream<_, ()> = FrameStream::new(BufRecvStream::new(recv)); assert_poll_matches!( |cx| stream.poll_next(cx), diff --git a/h3/src/quic.rs b/h3/src/quic.rs index ef1c2976..be37c7ae 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -70,15 +70,16 @@ impl Error for SendDatagramError { } /// Trait representing a QUIC connection. -pub trait Connection { +pub trait Connection { /// The type produced by `poll_accept_bidi()` - type BidiStream: SendStream + RecvStream; + type BidiStream: SendStream + RecvStream; /// The type of the sending part of `BidiStream` - type SendStream: SendStream; + type SendStream: SendStream; /// The type produced by `poll_accept_recv()` type RecvStream: RecvStream; /// A producer of outgoing Unidirectional and Bidirectional streams. type OpenStreams: OpenStreams< + B, SendStream = Self::SendStream, RecvStream = Self::RecvStream, BidiStream = Self::BidiStream, @@ -131,11 +132,11 @@ pub trait Connection { } /// Trait for opening outgoing streams -pub trait OpenStreams { +pub trait OpenStreams { /// The type produced by `poll_open_bidi()` - type BidiStream: SendStream + RecvStream; + type BidiStream: SendStream + RecvStream; /// The type produced by `poll_open_send()` - type SendStream: SendStream; + type SendStream: SendStream; /// The type of the receiving part of `BidiStream` type RecvStream: RecvStream; /// Error type yielded by these trait methods @@ -148,7 +149,7 @@ pub trait OpenStreams { ) -> Poll>; /// Poll the connection to create a new unidirectional stream. - fn poll_open_uni( + fn poll_open_send( &mut self, cx: &mut task::Context<'_>, ) -> Poll>; @@ -158,22 +159,15 @@ pub trait OpenStreams { } /// A trait describing the "send" actions of a QUIC stream. -pub trait SendStream { +pub trait SendStream { /// The error type returned by fallible send methods. type Error: Into>; - /// Attempts write data into the stream. - /// - /// Returns the number of bytes written. - /// - /// `buf` is advanced by the number of bytes written. - /// - /// This allows writing arbitrary data to the stream as well as complete encoded frames. - fn poll_send( - &mut self, - cx: &mut task::Context<'_>, - buf: &mut D, - ) -> Poll>; + /// Polls if the stream can send more data. + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; + + /// Send more data on the stream. + fn send_data>>(&mut self, data: T) -> Result<(), Self::Error>; /// Poll to finish the sending side of the stream. fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll>; @@ -185,6 +179,23 @@ pub trait SendStream { fn send_id(&self) -> StreamId; } +/// Allows sending unframed bytes to a stream +pub trait SendStreamUnframed { + /// The error type returned by fallible send methods. + type Error: Into>; + + /// Attempts write data into the stream. + /// + /// Returns the number of bytes written. + /// + /// `buf` is advanced by the number of bytes written. + fn poll_send( + &mut self, + cx: &mut task::Context<'_>, + buf: &mut B, + ) -> Poll>; +} + /// A trait describing the "receive" actions of a QUIC stream. pub trait RecvStream { /// The type of `Buf` for data received on this stream. @@ -209,9 +220,9 @@ pub trait RecvStream { } /// Optional trait to allow "splitting" a bidirectional stream into two sides. -pub trait BidiStream: SendStream + RecvStream { +pub trait BidiStream: SendStream + RecvStream { /// The type for the send half. - type SendStream: SendStream; + type SendStream: SendStream; /// The type for the receive half. type RecvStream: RecvStream; diff --git a/h3/src/request.rs b/h3/src/request.rs index f2a0217e..f705efbc 100644 --- a/h3/src/request.rs +++ b/h3/src/request.rs @@ -1,19 +1,20 @@ use std::convert::TryFrom; +use bytes::Buf; use http::{Request, StatusCode}; use crate::{error::Code, proto::headers::Header, qpack, quic, server::RequestStream, Error}; -pub struct ResolveRequest { - request_stream: RequestStream, +pub struct ResolveRequest, B: Buf> { + request_stream: RequestStream, // Ok or `REQUEST_HEADER_FIELDS_TO_LARGE` which neeeds to be sent decoded: Result, max_field_section_size: u64, } -impl ResolveRequest { +impl> ResolveRequest { pub fn new( - request_stream: RequestStream, + request_stream: RequestStream, decoded: Result, max_field_section_size: u64, ) -> Self { @@ -25,7 +26,9 @@ impl ResolveRequest { } /// Finishes the resolution of the request - pub async fn resolve(mut self) -> Result<(Request<()>, RequestStream), Error> { + pub async fn resolve( + mut self, + ) -> Result<(Request<()>, RequestStream), Error> { let fields = match self.decoded { Ok(v) => v.fields, Err(cancel_size) => { diff --git a/h3/src/server.rs b/h3/src/server.rs index c9beb455..975a0626 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -52,13 +52,14 @@ use std::{ collections::HashSet, + marker::PhantomData, option::Option, result::Result, sync::Arc, task::{Context, Poll}, }; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Buf, BytesMut}; use futures_util::{ future::{self, Future}, ready, @@ -101,12 +102,13 @@ pub fn builder() -> Builder { /// Create a new Instance with [`Connection::new()`]. /// Accept incoming requests with [`Connection::accept()`]. /// And shutdown a connection with [`Connection::shutdown()`]. -pub struct Connection +pub struct Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { /// TODO: temporarily break encapsulation for `WebTransportSession` - pub inner: ConnectionInner, + pub inner: ConnectionInner, max_field_section_size: u64, // List of all incoming streams that are currently running. ongoing_streams: HashSet, @@ -121,18 +123,20 @@ where last_accepted_stream: Option, } -impl ConnectionState for Connection +impl ConnectionState for Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { fn shared_state(&self) -> &SharedStateRef { &self.inner.shared } } -impl Connection +impl Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { /// Create a new HTTP/3 server connection with default settings /// @@ -154,9 +158,10 @@ where } } -impl Connection +impl Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { /// Accept an incoming request. /// @@ -165,7 +170,7 @@ where /// The [`RequestStream`] can be used to send the response. pub async fn accept( &mut self, - ) -> Result, RequestStream)>, Error> { + ) -> Result, RequestStream)>, Error> { // Accept the incoming stream let mut stream = match future::poll_fn(|cx| self.poll_accept_request(cx)).await { Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), @@ -210,9 +215,9 @@ where /// may still want to be handled and passed to the user. pub fn accept_with_frame( &mut self, - mut stream: FrameStream, + mut stream: FrameStream, frame: Result>, FrameStreamError>, - ) -> Result>, Error> { + ) -> Result>, Error> { let mut encoded = match frame { Ok(Some(Frame::Headers(h))) => h, @@ -337,9 +342,10 @@ where } /// Reads an incoming datagram - pub fn read_datagram(&self) -> ReadDatagram { + pub fn read_datagram(&self) -> ReadDatagram { ReadDatagram { conn: &self.inner.conn, + _marker: PhantomData, } } @@ -489,9 +495,10 @@ where } } -impl Drop for Connection +impl Drop for Connection where - C: quic::Connection, + C: quic::Connection, + B: Buf, { fn drop(&mut self) { self.inner.close(Code::H3_NO_ERROR, ""); @@ -615,9 +622,10 @@ impl Default for Config { /// # Example /// /// ```rust -/// fn doc(conn: C) +/// fn doc(conn: C) /// where -/// C: h3::quic::Connection, +/// C: h3::quic::Connection, +/// B: bytes::Buf, /// { /// let mut server_builder = h3::server::builder(); /// // Set the maximum header size @@ -670,9 +678,10 @@ impl Builder { /// Build an HTTP/3 connection from a QUIC connection /// /// This method creates a [`Connection`] instance with the settings in the [`Builder`]. - pub async fn build(&self, conn: C) -> Result, Error> + pub async fn build(&self, conn: C) -> Result, Error> where - C: quic::Connection, + C: quic::Connection, + B: Buf, { let (sender, receiver) = mpsc::unbounded_channel(); Ok(Connection { @@ -697,26 +706,27 @@ struct RequestEnd { /// /// The [`RequestStream`] struct is used to send and/or receive /// information from the client. -pub struct RequestStream { - inner: connection::RequestStream, +pub struct RequestStream { + inner: connection::RequestStream, request_end: Arc, } -impl AsMut> for RequestStream { - fn as_mut(&mut self) -> &mut connection::RequestStream { +impl AsMut> for RequestStream { + fn as_mut(&mut self) -> &mut connection::RequestStream { &mut self.inner } } -impl ConnectionState for RequestStream { +impl ConnectionState for RequestStream { fn shared_state(&self) -> &SharedStateRef { &self.inner.conn_state } } -impl RequestStream +impl RequestStream where S: quic::RecvStream, + B: Buf, { /// Receive data sent from the client pub async fn recv_data(&mut self) -> Result, Error> { @@ -739,9 +749,10 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::SendStream, + S: quic::SendStream, + B: Buf, { /// Send the HTTP/3 response /// @@ -773,7 +784,7 @@ where return Err(Error::header_too_big(mem_size, max_mem_size)); } - stream::write::<_, _, Bytes>(&mut self.inner.stream, Frame::Headers(block.freeze())) + stream::write(&mut self.inner.stream, Frame::Headers(block.freeze())) .await .map_err(|e| self.maybe_conn_err(e))?; @@ -781,7 +792,7 @@ where } /// Send some data on the response body. - pub async fn send_data(&mut self, buf: Bytes) -> Result<(), Error> { + pub async fn send_data(&mut self, buf: B) -> Result<(), Error> { self.inner.send_data(buf).await } @@ -824,13 +835,19 @@ where } } -impl RequestStream +impl RequestStream where - S: quic::BidiStream, + S: quic::BidiStream, + B: Buf, { /// Splits the Request-Stream into send and receive. /// This can be used the send and receive data on different tasks. - pub fn split(self) -> (RequestStream, RequestStream) { + pub fn split( + self, + ) -> ( + RequestStream, + RequestStream, + ) { let (send, recv) = self.inner.split(); ( RequestStream { @@ -857,16 +874,15 @@ impl Drop for RequestEnd { } /// Future for [`Connection::read_datagram`] -pub struct ReadDatagram<'a, C> -where - C: quic::Connection, -{ +pub struct ReadDatagram<'a, C, B> { conn: &'a std::sync::Mutex, + _marker: PhantomData, } -impl<'a, C> Future for ReadDatagram<'a, C> +impl<'a, C, B> Future for ReadDatagram<'a, C, B> where - C: quic::Connection, + C: quic::Connection, + B: Buf, { type Output = Result, Error>; diff --git a/h3/src/stream.rs b/h3/src/stream.rs index e56491af..2d7bc757 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -1,4 +1,7 @@ -use std::task::{Context, Poll}; +use std::{ + marker::PhantomData, + task::{Context, Poll}, +}; use bytes::{Buf, BufMut, Bytes}; use futures_util::{future, ready}; @@ -22,14 +25,12 @@ use crate::{ /// Transmits data by encoding in wire format. pub(crate) async fn write(stream: &mut S, data: D) -> Result<(), Error> where - S: SendStream, + S: SendStream, D: Into>, B: Buf, { - let mut write_buf = data.into(); - while write_buf.has_remaining() { - future::poll_fn(|cx| stream.poll_send(cx, &mut write_buf)).await?; - } + stream.send_data(data)?; + future::poll_fn(|cx| stream.poll_ready(cx)).await?; Ok(()) } @@ -240,30 +241,32 @@ where } } -pub(super) enum AcceptedRecvStream +pub(super) enum AcceptedRecvStream where S: quic::RecvStream, + B: Buf, { - Control(FrameStream), - Push(u64, FrameStream), - Encoder(BufRecvStream), - Decoder(BufRecvStream), - WebTransportUni(SessionId, BufRecvStream), + Control(FrameStream), + Push(u64, FrameStream), + Encoder(BufRecvStream), + Decoder(BufRecvStream), + WebTransportUni(SessionId, BufRecvStream), Reserved, } /// Resolves an incoming streams type as well as `PUSH_ID`s and `SESSION_ID`s -pub(super) struct AcceptRecvStream { - stream: BufRecvStream, +pub(super) struct AcceptRecvStream { + stream: BufRecvStream, ty: Option, /// push_id or session_id id: Option, expected: Option, } -impl AcceptRecvStream +impl AcceptRecvStream where S: RecvStream, + B: Buf, { pub fn new(stream: S) -> Self { Self { @@ -274,7 +277,7 @@ where } } - pub fn into_stream(self) -> Result, Error> { + pub fn into_stream(self) -> Result, Error> { Ok(match self.ty.expect("Stream type not resolved yet") { StreamType::CONTROL => AcceptedRecvStream::Control(FrameStream::new(self.stream)), StreamType::PUSH => AcceptedRecvStream::Push( @@ -379,16 +382,17 @@ where /// /// Implements `quic::RecvStream` which will first return buffered data, and then read from the /// stream -pub struct BufRecvStream { +pub struct BufRecvStream { buf: BufList, /// Indicates that the end of the stream has been reached /// /// Data may still be available as buffered eos: bool, stream: S, + _marker: PhantomData, } -impl std::fmt::Debug for BufRecvStream { +impl std::fmt::Debug for BufRecvStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufRecvStream") .field("buf", &self.buf) @@ -398,17 +402,18 @@ impl std::fmt::Debug for BufRecvStream { } } -impl BufRecvStream { +impl BufRecvStream { pub fn new(stream: S) -> Self { Self { buf: BufList::new(), eos: false, stream, + _marker: PhantomData, } } } -impl BufRecvStream { +impl BufRecvStream { /// Reads more data into the buffer, returning the number of bytes read. /// /// Returns `true` if the end of the stream is reached. @@ -440,7 +445,7 @@ impl BufRecvStream { } } -impl RecvStream for BufRecvStream { +impl RecvStream for BufRecvStream { type Buf = Bytes; type Error = S::Error; @@ -471,18 +476,13 @@ impl RecvStream for BufRecvStream { } } -impl SendStream for BufRecvStream { +impl SendStream for BufRecvStream +where + B: Buf, + S: SendStream, +{ type Error = S::Error; - #[inline] - fn poll_send( - &mut self, - cx: &mut std::task::Context<'_>, - buf: &mut D, - ) -> Poll> { - self.stream.poll_send(cx, buf) - } - fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { self.stream.poll_finish(cx) } @@ -494,12 +494,24 @@ impl SendStream for BufRecvStream { fn send_id(&self) -> quic::StreamId { self.stream.send_id() } + + fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + self.stream.poll_ready(cx) + } + + fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { + self.stream.send_data(data) + } } -impl BidiStream for BufRecvStream { - type SendStream = BufRecvStream; +impl BidiStream for BufRecvStream +where + B: Buf, + S: BidiStream, +{ + type SendStream = BufRecvStream; - type RecvStream = BufRecvStream; + type RecvStream = BufRecvStream; fn split(self) -> (Self::SendStream, Self::RecvStream) { let (send, recv) = self.stream.split(); @@ -509,11 +521,13 @@ impl BidiStream for BufRecvStream { buf: BufList::new(), eos: self.eos, stream: send, + _marker: PhantomData, }, BufRecvStream { buf: self.buf, eos: self.eos, stream: recv, + _marker: PhantomData, }, ) } diff --git a/h3/src/tests/connection.rs b/h3/src/tests/connection.rs index f57bd1a8..458db214 100644 --- a/h3/src/tests/connection.rs +++ b/h3/src/tests/connection.rs @@ -4,7 +4,7 @@ use std::{borrow::BorrowMut, time::Duration}; use assert_matches::assert_matches; -use bytes::{Bytes, BytesMut}; +use bytes::{Buf, Bytes, BytesMut}; use futures_util::future; use http::{Request, Response, StatusCode}; @@ -184,7 +184,7 @@ async fn settings_exchange_server() { let client_fut = async { let (mut conn, _client) = client::builder() .max_field_section_size(12) - .build(pair.client().await) + .build::<_, _, Bytes>(pair.client().await) .await .expect("client init"); let drive = async move { @@ -687,10 +687,11 @@ async fn graceful_shutdown_client() { tokio::join!(server_fut, client_fut); } -async fn request(mut send_request: T) -> Result, Error> +async fn request(mut send_request: T) -> Result, Error> where - T: BorrowMut>, - O: quic::OpenStreams, + T: BorrowMut>, + O: quic::OpenStreams, + B: Buf, { let mut request_stream = send_request .borrow_mut() @@ -699,9 +700,10 @@ where request_stream.recv_response().await } -async fn response(mut stream: server::RequestStream) +async fn response(mut stream: server::RequestStream) where - S: quic::RecvStream + SendStream, + S: quic::RecvStream + SendStream, + B: Buf, { stream .send_response( diff --git a/h3/src/tests/mod.rs b/h3/src/tests/mod.rs index 03380be1..655ab16a 100644 --- a/h3/src/tests/mod.rs +++ b/h3/src/tests/mod.rs @@ -18,6 +18,7 @@ use std::{ time::Duration, }; +use bytes::Bytes; use rustls::{Certificate, PrivateKey}; use crate::quic; @@ -129,7 +130,7 @@ pub struct Server { } impl Server { - pub async fn next(&mut self) -> impl quic::Connection { + pub async fn next(&mut self) -> impl quic::Connection { Connection::new(self.endpoint.accept().await.unwrap().await.unwrap()) } } diff --git a/h3/src/tests/request.rs b/h3/src/tests/request.rs index 488b5f24..cb2296ca 100644 --- a/h3/src/tests/request.rs +++ b/h3/src/tests/request.rs @@ -214,7 +214,7 @@ async fn post() { .expect("request"); request_stream - .send_data("wonderful json".as_bytes()) + .send_data("wonderful json".into()) .await .expect("send_data"); request_stream.finish().await.expect("client finish"); @@ -319,7 +319,7 @@ async fn header_too_big_response_from_server_trailers() { .await .expect("request"); request_stream - .send_data("wonderful json".as_bytes()) + .send_data("wonderful json".into()) .await .expect("send_data"); @@ -441,7 +441,7 @@ async fn header_too_big_client_error_trailer() { .await .expect("request"); request_stream - .send_data("wonderful json".as_bytes()) + .send_data("wonderful json".into()) .await .expect("send_data"); @@ -508,7 +508,7 @@ async fn header_too_big_discard_from_client() { // Do not poll driver so client doesn't know about server's max_field section size setting let (_conn, mut client) = client::builder() .max_field_section_size(12) - .build(pair.client().await) + .build::<_, _, Bytes>(pair.client().await) .await .expect("client init"); let mut request_stream = client @@ -594,7 +594,7 @@ async fn header_too_big_discard_from_client_trailers() { // Do not poll driver so client doesn't know about server's max_field section size setting let (mut driver, mut client) = client::builder() .max_field_section_size(200) - .build(pair.client().await) + .build::<_, _, Bytes>(pair.client().await) .await .expect("client init"); From 14fba6b102f969eb59aede553545c19d27b04015 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 4 May 2023 17:50:32 +0200 Subject: [PATCH 76/97] feat: separate trait for unframed stream data --- examples/webtransport_server.rs | 6 ++++ h3-quinn/Cargo.toml | 5 ++- h3-quinn/src/lib.rs | 55 +++++++++++++++++++++++++++++++++ h3-webtransport/src/server.rs | 27 +++++++++++----- h3-webtransport/src/stream.rs | 55 +++++++++++++++++++-------------- h3/src/client.rs | 33 +++++++++++--------- h3/src/connection.rs | 1 - h3/src/quic.rs | 11 +++---- h3/src/server.rs | 4 +-- h3/src/stream.rs | 16 +++++++++- 10 files changed, 152 insertions(+), 61 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index f5d49514..be966d75 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -6,6 +6,7 @@ use futures::AsyncRead; use futures::AsyncReadExt; use futures::AsyncWrite; use futures::AsyncWriteExt; +use h3::quic::SendStreamUnframed; use h3_webtransport::server; use h3_webtransport::stream; use http::Method; @@ -281,6 +282,8 @@ async fn handle_session_and_echo_all_inbound_messages( session: WebTransportSession, ) -> anyhow::Result<()> where + // Use trait bounds to ensure we only happen to use implementation that are only for the quinn + // backend. C: 'static + Send + h3::quic::Connection, >::Error: 'static + std::error::Error + Send + Sync + Into, @@ -295,6 +298,9 @@ where C::SendStream: Send + Unpin, C::RecvStream: Send + Unpin, C::BidiStream: Send + Unpin, + stream::SendStream: AsyncWrite, + C::BidiStream: SendStreamUnframed, + C::SendStream: SendStreamUnframed, { let session_id = session.session_id(); diff --git a/h3-quinn/Cargo.toml b/h3-quinn/Cargo.toml index 646787c7..0462865a 100644 --- a/h3-quinn/Cargo.toml +++ b/h3-quinn/Cargo.toml @@ -15,9 +15,8 @@ license = "MIT" [dependencies] h3 = { version = "0.0.2", path = "../h3" } bytes = "1" -quinn = { version = "0.9.3", default-features = false, features = [ - "futures-io", -] } +quinn = { version = "0.9.3", default-features = false, features = [] } quinn-proto = { version = "0.9.2", default-features = false } tokio-util = { version = "0.7.7" } futures = { version = "0.3.27" } +tokio = { version = "1.28", features = ["io-util"], default-features = false } diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 203472b2..ac21c30e 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -7,6 +7,7 @@ use std::{ convert::TryInto, fmt::{self, Display}, future::Future, + pin::Pin, sync::Arc, task::{self, Poll}, }; @@ -345,6 +346,18 @@ where 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 /// @@ -557,6 +570,48 @@ where } } +impl quic::SendStreamUnframed for SendStream +where + B: Buf, +{ + 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!(tokio::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. diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 901e8248..c1d7202f 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -9,16 +9,19 @@ use std::{ use bytes::{Buf, Bytes}; use futures_util::{future::poll_fn, ready, Future}; -use h3::stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}; use h3::{ connection::ConnectionState, error::Code, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, - quic::{self, OpenStreams, SendStream as _, WriteBuf}, + quic::{self, OpenStreams, WriteBuf}, server::{self, Connection, RequestStream}, Error, Protocol, }; +use h3::{ + quic::SendStreamUnframed, + stream::{BidiStreamHeader, BufRecvStream, UniStreamHeader}, +}; use http::{Method, Request, Response, StatusCode}; use h3::webtransport::SessionId; @@ -262,7 +265,12 @@ pin_project! { } } -impl<'a, B: Buf, C: quic::Connection> Future for OpenBi<'a, C, B> { +impl<'a, B, C> Future for OpenBi<'a, C, B> +where + C: quic::Connection, + B: Buf, + C::BidiStream: SendStreamUnframed, +{ type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -271,8 +279,7 @@ impl<'a, B: Buf, C: quic::Connection> Future for OpenBi<'a, C, B> { match &mut p.stream { Some((stream, buf)) => { while buf.has_remaining() { - todo!() - // ready!(stream.poll_send(cx, buf))?; + ready!(stream.poll_send(cx, buf))?; } let (stream, _) = p.stream.take().unwrap(); @@ -302,7 +309,12 @@ pin_project! { } } -impl<'a, C: quic::Connection, B: Buf> Future for OpenUni<'a, C, B> { +impl<'a, C, B> Future for OpenUni<'a, C, B> +where + C: quic::Connection, + B: Buf, + C::SendStream: SendStreamUnframed, +{ type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -311,8 +323,7 @@ impl<'a, C: quic::Connection, B: Buf> Future for OpenUni<'a, C, B> { match &mut p.stream { Some((send, buf)) => { while buf.has_remaining() { - todo!() - // ready!(send.poll_send(cx, buf))?; + ready!(send.poll_send(cx, buf))?; } let (send, buf) = p.stream.take().unwrap(); assert!(!buf.has_remaining()); diff --git a/h3-webtransport/src/stream.rs b/h3-webtransport/src/stream.rs index deb06766..78d8adde 100644 --- a/h3-webtransport/src/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -1,9 +1,9 @@ use std::task::Poll; use bytes::{Buf, Bytes}; -use futures_util::{future, ready, AsyncRead, AsyncWrite}; +use futures_util::{ready, AsyncRead, AsyncWrite}; use h3::{ - quic::{self, SendStream as _}, + quic::{self, SendStream as _, SendStreamUnframed}, stream::BufRecvStream, }; use pin_project_lite::pin_project; @@ -85,12 +85,7 @@ macro_rules! async_write { cx: &mut std::task::Context<'_>, mut buf: $buf, ) -> Poll> { - // self.send_data(buf.into())?; - - // ready!(self.poll_ready(cx)?); - - let len = buf.len(); - Poll::Ready(Ok(len)) + self.poll_send(cx, &mut buf).map_err(Into::into) } fn poll_flush( @@ -140,19 +135,17 @@ impl SendStream { } } -impl SendStream +impl quic::SendStreamUnframed for SendStream where - S: quic::SendStream, + S: quic::SendStreamUnframed, B: Buf, { - /// Write bytes to the stream. - /// - /// Returns the number of bytes written - pub async fn write(&mut self, buf: impl Buf) -> Result<(), S::Error> { - todo!(); - // self.send_data(buf.into())?; - future::poll_fn(|cx| quic::SendStream::poll_ready(self, cx)).await?; - Ok(()) + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) } } @@ -176,17 +169,17 @@ where } fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { - todo!() + self.stream.send_data(data) } fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - todo!() + self.stream.poll_ready(cx) } } impl AsyncWrite for SendStream where - S: quic::SendStream, + S: quic::SendStream + SendStreamUnframed, B: Buf, S::Error: Into, { @@ -229,11 +222,25 @@ where } fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - todo!() + self.stream.poll_ready(cx) } fn send_data>>(&mut self, data: T) -> Result<(), Self::Error> { - todo!() + self.stream.send_data(data) + } +} + +impl quic::SendStreamUnframed for BidiStream +where + S: quic::SendStreamUnframed, + B: Buf, +{ + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) } } @@ -284,7 +291,7 @@ where impl AsyncWrite for BidiStream where - S: quic::SendStream, + S: SendStreamUnframed, S::Error: Into, B: Buf, { diff --git a/h3/src/client.rs b/h3/src/client.rs index b3bdf695..4458100c 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -65,15 +65,16 @@ where /// # use h3::{quic, client::*}; /// # use http::{Request, Response}; /// # use bytes::Buf; -/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> +/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> /// # where -/// # T: quic::OpenStreams, +/// # T: quic::OpenStreams, +/// # B: Buf, /// # { /// // Prepare the HTTP request to send to the server /// let request = Request::get("https://www.example.com/").body(())?; /// /// // Send the request to the server -/// let mut req_stream: RequestStream<_> = send_request.send_request(request).await?; +/// let mut req_stream: RequestStream<_, _> = send_request.send_request(request).await?; /// // Don't forget to end up the request by finishing the send stream. /// req_stream.finish().await?; /// // Receive the response @@ -90,9 +91,9 @@ where /// # use h3::{quic, client::*}; /// # use http::{Request, Response, HeaderMap}; /// # use bytes::{Buf, Bytes}; -/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> +/// # async fn doc(mut send_request: SendRequest) -> Result<(), Box> /// # where -/// # T: quic::OpenStreams, +/// # T: quic::OpenStreams, /// # { /// // Prepare the HTTP request to send to the server /// let request = Request::get("https://www.example.com/").body(())?; @@ -100,7 +101,7 @@ where /// // Send the request to the server /// let mut req_stream = send_request.send_request(request).await?; /// // Send some data -/// req_stream.send_data("body".as_bytes()).await?; +/// req_stream.send_data("body".into()).await?; /// // Prepare the trailers /// let mut trailers = HeaderMap::new(); /// trailers.insert("trailer", "value".parse()?); @@ -283,12 +284,13 @@ where /// # use futures_util::future; /// # use h3::{client::*, quic}; /// # use tokio::task::JoinHandle; -/// # async fn doc(mut connection: Connection) +/// # async fn doc(mut connection: Connection) /// # -> JoinHandle>> /// # where -/// # C: quic::Connection + Send + 'static, +/// # C: quic::Connection + Send + 'static, /// # C::SendStream: Send + 'static, /// # C::RecvStream: Send + 'static, +/// # B: Buf + Send + 'static, /// # { /// // Run the driver on a different task /// tokio::spawn(async move { @@ -305,12 +307,13 @@ where /// # use futures_util::future; /// # use h3::{client::*, quic}; /// # use tokio::{self, sync::oneshot, task::JoinHandle}; -/// # async fn doc(mut connection: Connection) +/// # async fn doc(mut connection: Connection) /// # -> Result<(), Box> /// # where -/// # C: quic::Connection + Send + 'static, +/// # C: quic::Connection + Send + 'static, /// # C::SendStream: Send + 'static, /// # C::RecvStream: Send + 'static, +/// # B: Buf + Send + 'static, /// # { /// // Prepare a channel to stop the driver thread /// let (shutdown_tx, shutdown_rx) = oneshot::channel(); @@ -466,10 +469,11 @@ where /// # Examples /// ```rust /// # use h3::quic; -/// # async fn doc(quic: C) +/// # async fn doc(quic: C) /// # where -/// # C: quic::Connection, -/// # O: quic::OpenStreams, +/// # C: quic::Connection, +/// # O: quic::OpenStreams, +/// # B: bytes::Buf, /// # { /// let h3_conn = h3::client::builder() /// .max_field_section_size(8192) @@ -557,7 +561,7 @@ impl Builder { /// # use http::{Request, Response}; /// # use bytes::Buf; /// # use tokio::io::AsyncWriteExt; -/// # async fn doc(mut req_stream: RequestStream) -> Result<(), Box> +/// # async fn doc(mut req_stream: RequestStream) -> Result<(), Box> /// # where /// # T: quic::RecvStream, /// # { @@ -598,7 +602,6 @@ impl ConnectionState for RequestStream { impl RequestStream where S: quic::RecvStream, - B: Buf, { /// Receive the HTTP/3 response /// diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 27f32a33..dd6aee5a 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -675,7 +675,6 @@ impl ConnectionState for RequestStream { impl RequestStream where S: quic::RecvStream, - B: Buf, { /// Receive some of the request body. pub async fn recv_data(&mut self) -> Result, Error> { diff --git a/h3/src/quic.rs b/h3/src/quic.rs index be37c7ae..60aefd22 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -179,20 +179,17 @@ pub trait SendStream { fn send_id(&self) -> StreamId; } -/// Allows sending unframed bytes to a stream -pub trait SendStreamUnframed { - /// The error type returned by fallible send methods. - type Error: Into>; - +/// Allows sending unframed pure bytes to a stream. Similar to [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html) +pub trait SendStreamUnframed: SendStream { /// Attempts write data into the stream. /// /// Returns the number of bytes written. /// /// `buf` is advanced by the number of bytes written. - fn poll_send( + fn poll_send( &mut self, cx: &mut task::Context<'_>, - buf: &mut B, + buf: &mut D, ) -> Poll>; } diff --git a/h3/src/server.rs b/h3/src/server.rs index 975a0626..dbe12b3f 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -8,8 +8,8 @@ //! ```rust //! async fn doc(conn: C) //! where -//! C: h3::quic::Connection, -//! ::BidiStream: Send + 'static +//! C: h3::quic::Connection, +//! >::BidiStream: Send + 'static //! { //! let mut server_builder = h3::server::builder(); //! // Build the Connection diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 2d7bc757..2ac8ea6c 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -16,7 +16,7 @@ use crate::{ stream::StreamType, varint::VarInt, }, - quic::{self, BidiStream, RecvStream, SendStream}, + quic::{self, BidiStream, RecvStream, SendStream, SendStreamUnframed}, webtransport::SessionId, Error, }; @@ -504,6 +504,20 @@ where } } +impl SendStreamUnframed for BufRecvStream +where + B: Buf, + S: SendStreamUnframed, +{ + fn poll_send( + &mut self, + cx: &mut std::task::Context<'_>, + buf: &mut D, + ) -> Poll> { + self.stream.poll_send(cx, buf) + } +} + impl BidiStream for BufRecvStream where B: Buf, From 3708407c8a19827c6abae39a4286598bd689430d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 10:11:08 +0200 Subject: [PATCH 77/97] fix: import --- h3-quinn/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index ac21c30e..339c144f 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -24,10 +24,7 @@ pub use quinn::{ }; use quinn::{ReadDatagram, SendDatagramError}; -use h3::{ - quic::{self, Error, StreamId}, - stream::WriteBuf, -}; +use h3::quic::{self, Error, StreamId, WriteBuf}; use tokio_util::sync::ReusableBoxFuture; /// A QUIC connection backed by Quinn From 757dedc5f63cbab4af031209916e148b49ac9c7f Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 10:11:17 +0200 Subject: [PATCH 78/97] feat: ext module --- examples/webtransport_server.rs | 2 +- h3-webtransport/src/server.rs | 3 ++- h3/src/ext.rs | 33 +++++++++++++++++++++++++++++++++ h3/src/lib.rs | 2 +- h3/src/proto/headers.rs | 31 +------------------------------ 5 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 h3/src/ext.rs diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index be966d75..c9f207a9 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -18,9 +18,9 @@ use tracing::{error, info, trace_span}; use h3::{ error::ErrorLevel, + ext::Protocol, quic, server::{Config, Connection}, - Protocol, }; use h3_quinn::quinn; use h3_webtransport::server::WebTransportSession; diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index c1d7202f..05fd8384 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -12,11 +12,12 @@ use futures_util::{future::poll_fn, ready, Future}; use h3::{ connection::ConnectionState, error::Code, + ext::Protocol, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, quic::{self, OpenStreams, WriteBuf}, server::{self, Connection, RequestStream}, - Error, Protocol, + Error, }; use h3::{ quic::SendStreamUnframed, diff --git a/h3/src/ext.rs b/h3/src/ext.rs new file mode 100644 index 00000000..d38bc980 --- /dev/null +++ b/h3/src/ext.rs @@ -0,0 +1,33 @@ +//! Extensions for the HTTP/3 protocol. + +use std::str::FromStr; + +/// Describes the `:protocol` pseudo-header for extended connect +/// +/// See: [https://www.rfc-editor.org/rfc/rfc8441#section-4] +#[derive(Copy, PartialEq, Debug, Clone)] +pub struct Protocol(ProtocolInner); + +impl Protocol { + /// WebTransport protocol + pub const WEB_TRANSPORT: Protocol = Protocol(ProtocolInner::WebTransport); +} + +#[derive(Copy, PartialEq, Debug, Clone)] +enum ProtocolInner { + WebTransport, +} + +/// Error when parsing the protocol +pub struct InvalidProtocol; + +impl FromStr for Protocol { + type Err = InvalidProtocol; + + fn from_str(s: &str) -> Result { + match s { + "webtransport" => Ok(Self(ProtocolInner::WebTransport)), + _ => Err(InvalidProtocol), + } + } +} diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 95edf315..cd65883b 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -4,12 +4,12 @@ pub mod client; pub mod error; +pub mod ext; pub mod quic; pub(crate) mod request; pub mod server; pub use error::Error; -pub use proto::headers::Protocol; mod buf; diff --git a/h3/src/proto/headers.rs b/h3/src/proto/headers.rs index a1c34f0f..5a4bda13 100644 --- a/h3/src/proto/headers.rs +++ b/h3/src/proto/headers.rs @@ -11,7 +11,7 @@ use http::{ Extensions, HeaderMap, Method, StatusCode, }; -use crate::qpack::HeaderField; +use crate::{ext::Protocol, qpack::HeaderField}; #[derive(Debug)] #[cfg_attr(test, derive(PartialEq, Clone))] @@ -308,22 +308,6 @@ where R::from_str(s).map_err(|_| HeaderError::invalid_value(name, value)) } -#[derive(Copy, PartialEq, Debug, Clone)] -/// Describes the `:protocol` pseudo-header for extended connect -/// -/// See: [https://www.rfc-editor.org/rfc/rfc8441#section-4] -pub struct Protocol(ProtocolInner); - -impl Protocol { - /// WebTransport protocol - pub const WEB_TRANSPORT: Protocol = Protocol(ProtocolInner::WebTransport); -} - -#[derive(Copy, PartialEq, Debug, Clone)] -enum ProtocolInner { - WebTransport, -} - /// Pseudo-header fields have the same purpose as data from the first line of HTTP/1.X, /// but are conveyed along with other headers. For example ':method' and ':path' in a /// request, and ':status' in a response. They must be placed before all other fields, @@ -352,19 +336,6 @@ struct Pseudo { len: usize, } -pub struct InvalidProtocol; - -impl FromStr for Protocol { - type Err = InvalidProtocol; - - fn from_str(s: &str) -> Result { - match s { - "webtransport" => Ok(Self(ProtocolInner::WebTransport)), - _ => Err(InvalidProtocol), - } - } -} - #[allow(clippy::len_without_is_empty)] impl Pseudo { fn request(method: Method, uri: Uri, ext: Extensions) -> Self { From fa7785c6b9ac44ce6059c40452263dfee23c476f Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 10:15:13 +0200 Subject: [PATCH 79/97] chore: revert 'static + Error --- h3/src/quic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 60aefd22..115c3193 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -198,7 +198,7 @@ pub trait RecvStream { /// The type of `Buf` for data received on this stream. type Buf: Buf; /// The error type that can occur when receiving data. - type Error: 'static + Error; + type Error: Into>; /// Poll the stream for more data. /// From 74935b5496bd2f87223e8f389c82bc782903170b Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 10:25:42 +0200 Subject: [PATCH 80/97] chore: fix public error internals --- h3-webtransport/src/server.rs | 25 +++++++++++++------------ h3/src/error.rs | 3 +-- h3/src/webtransport/mod.rs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 05fd8384..9eea6628 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -11,7 +11,7 @@ use bytes::{Buf, Bytes}; use futures_util::{future::poll_fn, ready, Future}; use h3::{ connection::ConnectionState, - error::Code, + error::{Code, ErrorLevel}, ext::Protocol, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, @@ -172,18 +172,19 @@ where // return Ok(None); } Err(err) => { - match err.inner.kind() { + match err.kind() { h3::error::Kind::Closed => return Ok(None), - // h3::error::Kind::Application { - // code, - // reason, - // level: ErrorLevel::ConnectionError, - // } => { - // return Err(self.conn.lock().unwrap().close( - // code, - // reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), - // )) - // }, + h3::error::Kind::Application { + code, + reason, + level: ErrorLevel::ConnectionError, + .. + } => { + return Err(self.conn.lock().unwrap().close( + code, + reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), + )) + } _ => return Err(err), }; } diff --git a/h3/src/error.rs b/h3/src/error.rs index 2d217053..a86b8386 100644 --- a/h3/src/error.rs +++ b/h3/src/error.rs @@ -13,7 +13,7 @@ pub(crate) type TransportError = Box; #[derive(Clone)] pub struct Error { /// The error kind. - pub inner: Box, + pub(crate) inner: Box, } /// An HTTP/3 "application error code". @@ -284,7 +284,6 @@ impl Error { matches!(&self.inner.kind, Kind::HeaderTooBig { .. }) } - #[cfg(test)] #[doc(hidden)] pub fn kind(&self) -> Kind { self.inner.kind.clone() diff --git a/h3/src/webtransport/mod.rs b/h3/src/webtransport/mod.rs index 1b6d8224..74ddc906 100644 --- a/h3/src/webtransport/mod.rs +++ b/h3/src/webtransport/mod.rs @@ -1,2 +1,2 @@ mod session_id; -pub use session_id::*; +pub use session_id::SessionId; From 3dd7732de46f7b1ce3e3e5194721c06c2e3096dc Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 11:06:44 +0200 Subject: [PATCH 81/97] fix: session id exposed field --- h3/src/webtransport/session_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/webtransport/session_id.rs b/h3/src/webtransport/session_id.rs index af4ae041..b6f4424d 100644 --- a/h3/src/webtransport/session_id.rs +++ b/h3/src/webtransport/session_id.rs @@ -10,7 +10,7 @@ use crate::proto::{ /// /// The session id is the same as the stream id of the CONNECT request. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct SessionId(pub u64); +pub struct SessionId(u64); impl SessionId { pub(crate) fn from_varint(id: VarInt) -> SessionId { Self(id.0) From d781040852f3473669548b2259d09e71a4d63f71 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 13:13:57 +0200 Subject: [PATCH 82/97] feat: split datagram support into traits --- examples/webtransport_server.rs | 10 ++- h3-quinn/src/lib.rs | 121 ++++++++++++++++++++++++-------- h3-webtransport/src/server.rs | 11 +-- h3/src/quic.rs | 69 +++++++----------- h3/src/server.rs | 60 +++++++++------- 5 files changed, 166 insertions(+), 105 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index c9f207a9..16bbc15e 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -6,6 +6,8 @@ use futures::AsyncRead; use futures::AsyncReadExt; use futures::AsyncWrite; use futures::AsyncWriteExt; +use h3::quic::RecvDatagramExt; +use h3::quic::SendDatagramExt; use h3::quic::SendStreamUnframed; use h3_webtransport::server; use h3_webtransport::stream; @@ -284,7 +286,11 @@ async fn handle_session_and_echo_all_inbound_messages( where // Use trait bounds to ensure we only happen to use implementation that are only for the quinn // backend. - C: 'static + Send + h3::quic::Connection, + C: 'static + + Send + + h3::quic::Connection + + RecvDatagramExt + + SendDatagramExt, >::Error: 'static + std::error::Error + Send + Sync + Into, ::Error: @@ -320,7 +326,7 @@ where let mut resp = BytesMut::from(&b"Response: "[..]); resp.put(datagram); - session.send_datagram(resp)?; + session.send_datagram(resp.freeze())?; tracing::info!("Finished sending datagram"); } } diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index 339c144f..dc7eb7e5 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -12,19 +12,22 @@ use std::{ task::{self, Poll}, }; -use bytes::{Buf, Bytes}; +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 quinn::{ReadDatagram, SendDatagramError}; -use h3::quic::{self, Error, StreamId, WriteBuf}; +use h3::{ + proto::datagram::Datagram, + quic::{self, Error, StreamId, WriteBuf}, +}; use tokio_util::sync::ReusableBoxFuture; /// A QUIC connection backed by Quinn @@ -95,6 +98,58 @@ impl From for ConnectionError { } } +/// 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, @@ -105,18 +160,6 @@ where type OpenStreams = OpenStreams; type Error = ConnectionError; - #[inline] - 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)), - } - } - fn poll_accept_bidi( &mut self, cx: &mut task::Context<'_>, @@ -174,20 +217,6 @@ where Poll::Ready(Ok(Self::SendStream::new(send))) } - fn send_datagram(&mut self, data: Bytes) -> Result<(), quic::SendDatagramError> { - match self.conn.send_datagram(data) { - Ok(v) => Ok(v), - Err(SendDatagramError::Disabled) => Err(quic::SendDatagramError::Disabled), - Err(SendDatagramError::TooLarge) => Err(quic::SendDatagramError::TooLarge), - Err(SendDatagramError::UnsupportedByPeer) => { - Err(quic::SendDatagramError::UnsupportedByPeer) - } - Err(SendDatagramError::ConnectionLost(err)) => Err( - quic::SendDatagramError::ConnectionLost(ConnectionError::from(err).into()), - ), - } - } - fn opener(&self) -> Self::OpenStreams { OpenStreams { conn: self.conn.clone(), @@ -204,6 +233,40 @@ where } } +impl quic::SendDatagramExt for Connection +where + B: Buf, +{ + type Error = SendDatagramError; + + 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] + 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`], diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 9eea6628..4494b5cb 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -15,7 +15,7 @@ use h3::{ ext::Protocol, frame::FrameStream, proto::{datagram::Datagram, frame::Frame}, - quic::{self, OpenStreams, WriteBuf}, + quic::{self, OpenStreams, RecvDatagramExt, SendDatagramExt, WriteBuf}, server::{self, Connection, RequestStream}, Error, }; @@ -138,7 +138,10 @@ where /// Sends a datagram /// /// TODO: maybe make async. `quinn` does not require an async send - pub fn send_datagram(&self, data: impl Buf) -> Result<(), Error> { + pub fn send_datagram(&self, data: B) -> Result<(), Error> + where + C: SendDatagramExt, + { self.conn .lock() .unwrap() @@ -365,10 +368,10 @@ pub struct ReadDatagram { impl Future for ReadDatagram where - C: quic::Connection, + C: quic::Connection + RecvDatagramExt, B: Buf, { - type Output = Result, Error>; + type Output = Result, Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut conn = self.conn.lock().unwrap(); diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 115c3193..2cc55980 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -3,11 +3,11 @@ //! This module includes traits and types meant to allow being generic over any //! QUIC implementation. -use core::fmt; use std::task::{self, Poll}; -use bytes::{Buf, Bytes}; +use bytes::Buf; +use crate::proto::datagram::Datagram; pub use crate::proto::stream::{InvalidStreamId, StreamId}; pub use crate::stream::WriteBuf; @@ -30,45 +30,6 @@ impl<'a, E: Error + 'a> From for Box { } } -/// Types of erros that *specifically* arises 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, - } - } -} - /// Trait representing a QUIC connection. pub trait Connection { /// The type produced by `poll_accept_bidi()` @@ -120,15 +81,33 @@ pub trait Connection { /// Close the connection immediately fn close(&mut self, code: crate::error::Code, reason: &[u8]); +} + +/// Extends the `Connection` trait for sending datagrams +/// +/// See: https://www.rfc-editor.org/rfc/rfc9297 +pub trait SendDatagramExt { + /// The error type that can occur when sending a datagram + type Error: Into>; + + /// Send a datagram + fn send_datagram(&mut self, data: Datagram) -> Result<(), Self::Error>; +} + +/// Extends the `Connection` trait for receiving datagrams +/// +/// See: https://www.rfc-editor.org/rfc/rfc9297 +pub trait RecvDatagramExt { + /// The type of `Buf` for *raw* datagrams (without the stream_id decoded) + type Buf: Buf; + /// The error type that can occur when receiving a datagram + type Error: Into>; /// Poll the connection for incoming datagrams. fn poll_accept_datagram( &mut self, cx: &mut task::Context<'_>, - ) -> Poll, Self::Error>>; - - /// Send a datagram - fn send_datagram(&mut self, data: Bytes) -> Result<(), SendDatagramError>; + ) -> Poll, Self::Error>>; } /// Trait for opening outgoing streams diff --git a/h3/src/server.rs b/h3/src/server.rs index dbe12b3f..45b0c412 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -81,7 +81,7 @@ use crate::{ varint::VarInt, }, qpack, - quic::{self, SendStream as _}, + quic::{self, RecvDatagramExt, SendDatagramExt, SendStream as _}, request::ResolveRequest, stream::{self, BufRecvStream}, }; @@ -341,28 +341,6 @@ where ))) } - /// Reads an incoming datagram - pub fn read_datagram(&self) -> ReadDatagram { - ReadDatagram { - conn: &self.inner.conn, - _marker: PhantomData, - } - } - - /// Sends a datagram - pub fn send_datagram(&self, stream_id: StreamId, data: impl Buf) -> Result<(), Error> { - let mut buf = BytesMut::with_capacity(8 + data.remaining()); - - // Encode::encode(&Datagram::new(stream_id, data), &mut buf); - Datagram::new(stream_id, data).encode(&mut buf); - - let buf = buf.freeze(); - self.inner.conn.lock().unwrap().send_datagram(buf)?; - tracing::info!("Sent datagram"); - - Ok(()) - } - /// Initiate a graceful shutdown, accepting `max_request` potentially still in-flight /// /// See [connection shutdown](https://www.rfc-editor.org/rfc/rfc9114.html#connection-shutdown) for more information. @@ -495,6 +473,38 @@ where } } +impl Connection +where + C: quic::Connection + SendDatagramExt, + B: Buf, +{ + /// Sends a datagram + pub fn send_datagram(&self, stream_id: StreamId, data: B) -> Result<(), Error> { + self.inner + .conn + .lock() + .unwrap() + .send_datagram(Datagram::new(stream_id, data))?; + tracing::info!("Sent datagram"); + + Ok(()) + } +} + +impl Connection +where + C: quic::Connection + RecvDatagramExt, + B: Buf, +{ + /// Reads an incoming datagram + pub fn read_datagram(&self) -> ReadDatagram { + ReadDatagram { + conn: &self.inner.conn, + _marker: PhantomData, + } + } +} + impl Drop for Connection where C: quic::Connection, @@ -881,10 +891,10 @@ pub struct ReadDatagram<'a, C, B> { impl<'a, C, B> Future for ReadDatagram<'a, C, B> where - C: quic::Connection, + C: quic::Connection + RecvDatagramExt, B: Buf, { - type Output = Result, Error>; + type Output = Result>, Error>; fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_datagram"); From 0bc780f76d005c32f5368af5c958c2a3f8a9401d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 11 May 2023 13:33:01 +0200 Subject: [PATCH 83/97] fix: make BufList private be exposing relevant method in BufRecvStream --- h3-webtransport/src/server.rs | 2 +- h3-webtransport/src/stream.rs | 6 +++--- h3/src/buf.rs | 2 +- h3/src/stream.rs | 14 +++++++++++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 4494b5cb..c6f9d422 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -7,7 +7,7 @@ use std::{ task::{Context, Poll}, }; -use bytes::{Buf, Bytes}; +use bytes::Buf; use futures_util::{future::poll_fn, ready, Future}; use h3::{ connection::ConnectionState, diff --git a/h3-webtransport/src/stream.rs b/h3-webtransport/src/stream.rs index 78d8adde..07a926ad 100644 --- a/h3-webtransport/src/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -55,17 +55,17 @@ macro_rules! async_read { buf: $buf, ) -> Poll> { // If the buffer i empty, poll for more data - if !self.stream.buf_mut().has_remaining() { + if !self.stream.has_remaining() { let res = ready!(self.stream.poll_read(cx).map_err(Into::into))?; if res { return Poll::Ready(Ok(0)); }; } - let bytes = self.stream.buf_mut(); + let chunk = self.stream.take_chunk(buf.len()); // Do not overfill - if let Some(chunk) = bytes.take_chunk(buf.len()) { + if let Some(chunk) = chunk { assert!(chunk.len() <= buf.len()); let len = chunk.len().min(buf.len()); buf[..len].copy_from_slice(&chunk); diff --git a/h3/src/buf.rs b/h3/src/buf.rs index d63880db..c6c5617e 100644 --- a/h3/src/buf.rs +++ b/h3/src/buf.rs @@ -4,7 +4,7 @@ use std::io::IoSlice; use bytes::{Buf, Bytes}; #[derive(Debug)] -pub struct BufList { +pub(crate) struct BufList { bufs: VecDeque, } diff --git a/h3/src/stream.rs b/h3/src/stream.rs index 2ac8ea6c..a086112f 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -431,10 +431,22 @@ impl BufRecvStream { /// Returns the currently buffered data, allowing it to be partially read #[inline] - pub fn buf_mut(&mut self) -> &mut BufList { + pub(crate) fn buf_mut(&mut self) -> &mut BufList { &mut self.buf } + /// Returns the next chunk of data from the stream + /// + /// Return `None` when there is no more buffered data; use [`Self::poll_read`]. + pub fn take_chunk(&mut self, limit: usize) -> Option { + self.buf.take_chunk(limit) + } + + /// Returns true if there is remaining buffered data + pub fn has_remaining(&mut self) -> bool { + self.buf.has_remaining() + } + #[inline] pub(crate) fn buf(&self) -> &BufList { &self.buf From 3cb795546f50961c3bd7030d7015333e4a55525b Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 16 May 2023 10:54:44 +0200 Subject: [PATCH 84/97] fix: duplicate method to construct server connection --- examples/webtransport_server.rs | 59 ++++++++------------- h3/src/server.rs | 94 +++++++++++---------------------- 2 files changed, 54 insertions(+), 99 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 16bbc15e..1dd9131d 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,16 +1,17 @@ -use anyhow::Context; -use anyhow::Result; -use bytes::Bytes; -use bytes::{BufMut, BytesMut}; -use futures::AsyncRead; -use futures::AsyncReadExt; -use futures::AsyncWrite; -use futures::AsyncWriteExt; -use h3::quic::RecvDatagramExt; -use h3::quic::SendDatagramExt; -use h3::quic::SendStreamUnframed; -use h3_webtransport::server; -use h3_webtransport::stream; +use anyhow::{Context, Result}; +use bytes::{BufMut, Bytes, BytesMut}; +use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use h3::{ + error::ErrorLevel, + ext::Protocol, + quic::{self, RecvDatagramExt, SendDatagramExt, SendStreamUnframed}, + server::Connection, +}; +use h3_quinn::quinn; +use h3_webtransport::{ + server::{self, WebTransportSession}, + stream, +}; use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; @@ -18,15 +19,6 @@ use structopt::StructOpt; use tokio::pin; use tracing::{error, info, trace_span}; -use h3::{ - error::ErrorLevel, - ext::Protocol, - quic, - server::{Config, Connection}, -}; -use h3_quinn::quinn; -use h3_webtransport::server::WebTransportSession; - #[derive(StructOpt, Debug)] #[structopt(name = "server")] struct Opt { @@ -118,14 +110,6 @@ async fn main() -> Result<(), Box> { info!("listening on {}", opt.listen); - // 1. Configure h3 server, since this is a webtransport server, we need to enable webtransport, connect, and datagram - let mut h3_config = Config::new(); - h3_config.enable_webtransport(true); - h3_config.enable_connect(true); - h3_config.enable_datagram(true); - h3_config.max_webtransport_sessions(16); - h3_config.send_grease(true); - // 2. Accept new quic connections and spawn a new task to handle them while let Some(new_conn) = endpoint.accept().await { trace_span!("New connection being attempted"); @@ -134,12 +118,15 @@ async fn main() -> Result<(), Box> { match new_conn.await { Ok(conn) => { info!("new http3 established"); - let h3_conn = h3::server::Connection::with_config( - h3_quinn::Connection::new(conn), - h3_config, - ) - .await - .unwrap(); + let h3_conn = h3::server::builder() + .enable_webtransport(true) + .enable_connect(true) + .enable_datagram(true) + .max_webtransport_sessions(1) + .send_grease(true) + .build(h3_quinn::Connection::new(conn)) + .await + .unwrap(); // tracing::info!("Establishing WebTransport session"); // // 3. TODO: Conditionally, if the client indicated that this is a webtransport session, we should accept it here, else use regular h3. diff --git a/h3/src/server.rs b/h3/src/server.rs index 45b0c412..361ff2f7 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -147,11 +147,6 @@ where builder().build(conn).await } - /// Create a new HTTP/3 server connection using the provided settings. - pub async fn with_config(conn: C, config: Config) -> Result { - Builder { config }.build(conn).await - } - /// Closes the connection with a code and a reason. pub fn close>(&mut self, code: Code, reason: T) -> Error { self.inner.close(code, reason) @@ -544,60 +539,6 @@ pub struct Config { pub max_webtransport_sessions: u64, } -impl Config { - /// Creates a new HTTP/3 config with default settings - pub fn new() -> Self { - Self::default() - } - - /// Set the maximum header size this client is willing to accept - /// - /// See [header size constraints] section of the specification for details. - /// - /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints - #[inline] - pub fn max_field_section_size(&mut self, value: u64) { - self.max_field_section_size = value; - } - - /// Send grease values to the Client. - /// See [setting](https://www.rfc-editor.org/rfc/rfc9114.html#settings-parameters), [frame](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) and [stream](https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease) for more information. - #[inline] - pub fn send_grease(&mut self, value: bool) { - self.send_grease = value; - } - - /// Indicates to the peer that WebTransport is supported. - /// - /// See: [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) - /// - /// - /// **Server**: - /// Supporting for webtransport also requires setting `enable_connect` `enable_datagram` - /// and `max_webtransport_sessions`. - #[inline] - pub fn enable_webtransport(&mut self, value: bool) { - self.enable_webtransport = value; - } - - /// Enables the CONNECT protocol - pub fn enable_connect(&mut self, value: bool) { - self.enable_extended_connect = value; - } - - /// Limits the maximum number of WebTransport sessions - pub fn max_webtransport_sessions(&mut self, value: u64) { - self.max_webtransport_sessions = value; - } - - /// Indicates that the client or server supports HTTP/3 datagrams - /// - /// See: https://www.rfc-editor.org/rfc/rfc9297#section-2.1.1 - pub fn enable_datagram(&mut self, value: bool) { - self.enable_datagram = value; - } -} - impl Default for Config { fn default() -> Self { Self { @@ -664,22 +605,49 @@ impl Builder { /// /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints pub fn max_field_section_size(&mut self, value: u64) -> &mut Self { - self.config.max_field_section_size(value); + self.config.max_field_section_size = value; self } /// Send grease values to the Client. /// See [setting](https://www.rfc-editor.org/rfc/rfc9114.html#settings-parameters), [frame](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) and [stream](https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease) for more information. + #[inline] pub fn send_grease(&mut self, value: bool) -> &mut Self { - self.config.send_grease(value); + self.config.send_grease = value; self } /// Indicates to the peer that WebTransport is supported. /// - /// See [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) + /// See: [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) + /// + /// + /// **Server**: + /// Supporting for webtransport also requires setting `enable_connect` `enable_datagram` + /// and `max_webtransport_sessions`. + #[inline] pub fn enable_webtransport(&mut self, value: bool) -> &mut Self { - self.config.enable_webtransport(value); + self.config.enable_webtransport = value; + self + } + + /// Enables the CONNECT protocol + pub fn enable_connect(&mut self, value: bool) -> &mut Self { + self.config.enable_extended_connect = value; + self + } + + /// Limits the maximum number of WebTransport sessions + pub fn max_webtransport_sessions(&mut self, value: u64) -> &mut Self { + self.config.max_webtransport_sessions = value; + self + } + + /// Indicates that the client or server supports HTTP/3 datagrams + /// + /// See: https://www.rfc-editor.org/rfc/rfc9297#section-2.1.1 + pub fn enable_datagram(&mut self, value: bool) -> &mut Self { + self.config.enable_datagram = value; self } } From 4e44e045b66b60c5f55d84d0f5c49b8e10f20c70 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 16 May 2023 11:13:01 +0200 Subject: [PATCH 85/97] chore: rename `conn` --- h3-webtransport/src/server.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index c6f9d422..8952fbb3 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -42,7 +42,8 @@ where { // See: https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-2-3 session_id: SessionId, - conn: Mutex>, + /// The underlying HTTP/3 connection + server_conn: Mutex>, connect_stream: RequestStream, opener: Mutex, } @@ -122,7 +123,7 @@ where Ok(Self { session_id, opener, - conn: Mutex::new(conn), + server_conn: Mutex::new(conn), connect_stream: stream, }) } @@ -130,7 +131,7 @@ where /// Receive a datagram from the client pub fn accept_datagram(&self) -> ReadDatagram { ReadDatagram { - conn: self.conn.lock().unwrap().inner.conn.clone(), + conn: self.server_conn.lock().unwrap().inner.conn.clone(), _marker: PhantomData, } } @@ -142,7 +143,7 @@ where where C: SendDatagramExt, { - self.conn + self.server_conn .lock() .unwrap() .send_datagram(self.connect_stream.id(), data)?; @@ -152,7 +153,9 @@ where /// Accept an incoming unidirectional stream from the client, it reads the stream until EOF. pub fn accept_uni(&self) -> AcceptUni { - AcceptUni { conn: &self.conn } + AcceptUni { + conn: &self.server_conn, + } } /// Accepts an incoming bidirectional stream or request @@ -160,7 +163,7 @@ where // Get the next stream // Accept the incoming stream let stream = poll_fn(|cx| { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.server_conn.lock().unwrap(); conn.poll_accept_request(cx) }) .await; @@ -183,7 +186,7 @@ where level: ErrorLevel::ConnectionError, .. } => { - return Err(self.conn.lock().unwrap().close( + return Err(self.server_conn.lock().unwrap().close( code, reason.unwrap_or_else(|| String::into_boxed_str(String::from(""))), )) @@ -212,7 +215,7 @@ where // Make the underlying HTTP/3 connection handle the rest frame => { let req = { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.server_conn.lock().unwrap(); conn.accept_with_frame(stream, frame)? }; if let Some(req) = req { From fc67a31a449e10c49041b43e380ebb91c3c14f42 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 16 May 2023 11:30:27 +0200 Subject: [PATCH 86/97] chore: remove inner connection Mutex on `C` --- h3-webtransport/src/server.rs | 19 ++++++++++------- h3/src/connection.rs | 39 ++++++++++++++--------------------- h3/src/server.rs | 29 +++++++++++++++----------- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 8952fbb3..0cae8718 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -3,7 +3,7 @@ use std::{ marker::PhantomData, pin::Pin, - sync::{Arc, Mutex}, + sync::Mutex, task::{Context, Poll}, }; @@ -116,9 +116,8 @@ where stream.send_response(response).await?; let session_id = stream.send_id().into(); - let conn_inner = conn.inner.conn.lock().unwrap(); + let conn_inner = &mut conn.inner.conn; let opener = Mutex::new(conn_inner.opener()); - drop(conn_inner); Ok(Self { session_id, @@ -131,7 +130,7 @@ where /// Receive a datagram from the client pub fn accept_datagram(&self) -> ReadDatagram { ReadDatagram { - conn: self.server_conn.lock().unwrap().inner.conn.clone(), + conn: &self.server_conn, _marker: PhantomData, } } @@ -364,12 +363,16 @@ pub enum AcceptedBi, B: Buf> { } /// Future for [`Connection::read_datagram`] -pub struct ReadDatagram { - conn: Arc>, +pub struct ReadDatagram<'a, C, B> +where + C: quic::Connection, + B: Buf, +{ + conn: &'a Mutex>, _marker: PhantomData, } -impl Future for ReadDatagram +impl<'a, C, B> Future for ReadDatagram<'a, C, B> where C: quic::Connection + RecvDatagramExt, B: Buf, @@ -378,7 +381,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut conn = self.conn.lock().unwrap(); - match ready!(conn.poll_accept_datagram(cx))? { + match ready!(conn.inner.conn.poll_accept_datagram(cx))? { Some(v) => { let datagram = Datagram::decode(v)?; Poll::Ready(Ok(Some((datagram.stream_id().into(), datagram.payload)))) diff --git a/h3/src/connection.rs b/h3/src/connection.rs index dd6aee5a..348cb6e5 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -1,6 +1,6 @@ use std::{ convert::TryFrom, - sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, task::{Context, Poll}, }; @@ -103,7 +103,7 @@ where { pub(super) shared: SharedStateRef, /// TODO: breaking encapsulation just to see if we can get this to work, will fix before merging - pub conn: Arc>, + pub conn: C, control_send: C::SendStream, control_recv: Option>, decoder_recv: Option>, @@ -237,7 +237,6 @@ where //# The //# sender MUST NOT close the control stream, and the receiver MUST NOT //# request that the sender close the control stream. - let conn = Arc::new(Mutex::new(conn)); let mut conn_inner = Self { shared, conn, @@ -309,11 +308,7 @@ where // Accept the request by accepting the next bidirectional stream // .into().into() converts the impl QuicError into crate::error::Error. // The `?` operator doesn't work here for some reason. - self.conn - .lock() - .unwrap() - .poll_accept_bidi(cx) - .map_err(|e| e.into().into()) + self.conn.poll_accept_bidi(cx).map_err(|e| e.into().into()) } /// Polls incoming streams @@ -326,7 +321,7 @@ where // Get all currently pending streams loop { - match self.conn.lock().unwrap().poll_accept_recv(cx)? { + match self.conn.poll_accept_recv(cx)? { Poll::Ready(Some(stream)) => self .pending_recv_streams .push(AcceptRecvStream::new(stream)), @@ -579,10 +574,7 @@ where pub fn close>(&mut self, code: Code, reason: T) -> Error { self.shared.write("connection close err").error = Some(code.with_reason(reason.as_ref(), crate::error::ErrorLevel::ConnectionError)); - self.conn - .lock() - .unwrap() - .close(code, reason.as_ref().as_bytes()); + self.conn.close(code, reason.as_ref().as_bytes()); code.with_reason(reason.as_ref(), crate::error::ErrorLevel::ConnectionError) } @@ -590,17 +582,16 @@ where /// https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease async fn start_grease_stream(&mut self) { // start the stream - let mut grease_stream = - match future::poll_fn(|cx| self.conn.lock().unwrap().poll_open_send(cx)) - .await - .map_err(|e| Code::H3_STREAM_CREATION_ERROR.with_transport(e)) - { - Err(err) => { - warn!("grease stream creation failed with {}", err); - return; - } - Ok(grease) => grease, - }; + let mut grease_stream = match future::poll_fn(|cx| self.conn.poll_open_send(cx)) + .await + .map_err(|e| Code::H3_STREAM_CREATION_ERROR.with_transport(e)) + { + Err(err) => { + warn!("grease stream creation failed with {}", err); + return; + } + Ok(grease) => grease, + }; //= https://www.rfc-editor.org/rfc/rfc9114#section-6.2.3 //# Stream types of the format 0x1f * N + 0x21 for non-negative integer diff --git a/h3/src/server.rs b/h3/src/server.rs index 361ff2f7..1dc80684 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -55,7 +55,7 @@ use std::{ marker::PhantomData, option::Option, result::Result, - sync::Arc, + sync::{Arc, Mutex}, task::{Context, Poll}, }; @@ -65,6 +65,7 @@ use futures_util::{ ready, }; use http::{response, HeaderMap, Request, Response}; +use pin_project_lite::pin_project; use quic::RecvStream; use quic::StreamId; use tokio::sync::mpsc; @@ -474,11 +475,9 @@ where B: Buf, { /// Sends a datagram - pub fn send_datagram(&self, stream_id: StreamId, data: B) -> Result<(), Error> { + pub fn send_datagram(&mut self, stream_id: StreamId, data: B) -> Result<(), Error> { self.inner .conn - .lock() - .unwrap() .send_datagram(Datagram::new(stream_id, data))?; tracing::info!("Sent datagram"); @@ -492,9 +491,9 @@ where B: Buf, { /// Reads an incoming datagram - pub fn read_datagram(&self) -> ReadDatagram { + pub fn read_datagram(&mut self) -> ReadDatagram { ReadDatagram { - conn: &self.inner.conn, + conn: self, _marker: PhantomData, } } @@ -851,10 +850,16 @@ impl Drop for RequestEnd { } } -/// Future for [`Connection::read_datagram`] -pub struct ReadDatagram<'a, C, B> { - conn: &'a std::sync::Mutex, - _marker: PhantomData, +pin_project! { + /// Future for [`Connection::read_datagram`] + pub struct ReadDatagram<'a, C, B> + where + C: quic::Connection, + B: Buf, + { + conn: &'a mut Connection, + _marker: PhantomData, + } } impl<'a, C, B> Future for ReadDatagram<'a, C, B> @@ -864,9 +869,9 @@ where { type Output = Result>, Error>; - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { tracing::trace!("poll: read_datagram"); - match ready!(self.conn.lock().unwrap().poll_accept_datagram(cx))? { + match ready!(self.conn.inner.conn.poll_accept_datagram(cx))? { Some(v) => Poll::Ready(Ok(Some(Datagram::decode(v)?))), None => Poll::Ready(Ok(None)), } From 185e55d27e9c312e02694ff2a6b477ccf9d3b40d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 16 May 2023 15:13:54 +0200 Subject: [PATCH 87/97] feat: `i-implement-a-third-party-backend-and-opt-into-breaking-changes` feature --- h3-webtransport/Cargo.toml | 3 +-- h3/Cargo.toml | 2 +- h3/src/lib.rs | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/h3-webtransport/Cargo.toml b/h3-webtransport/Cargo.toml index 4794589c..c1c310cf 100644 --- a/h3-webtransport/Cargo.toml +++ b/h3-webtransport/Cargo.toml @@ -15,5 +15,4 @@ tracing = "0.1.37" [dependencies.h3] version = "0.0.2" path = "../h3" -features = ["allow_access_to_core"] - +features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes"] diff --git a/h3/Cargo.toml b/h3/Cargo.toml index 475419d9..f396c5e2 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -20,7 +20,7 @@ categories = [ ] [features] -allow_access_to_core = [] +i-implement-a-third-party-backend-and-opt-into-breaking-changes = [] [dependencies] bytes = "1" diff --git a/h3/src/lib.rs b/h3/src/lib.rs index cd65883b..21c24e39 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -13,31 +13,31 @@ pub use error::Error; mod buf; -#[cfg(feature = "allow_access_to_core")] +#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] #[allow(missing_docs)] pub mod connection; -#[cfg(feature = "allow_access_to_core")] +#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] #[allow(missing_docs)] pub mod frame; -#[cfg(feature = "allow_access_to_core")] +#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] #[allow(missing_docs)] pub mod proto; -#[cfg(feature = "allow_access_to_core")] +#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] #[allow(missing_docs)] pub mod stream; -#[cfg(feature = "allow_access_to_core")] +#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] #[allow(missing_docs)] pub mod webtransport; -#[cfg(not(feature = "allow_access_to_core"))] +#[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] mod connection; -#[cfg(not(feature = "allow_access_to_core"))] +#[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] mod frame; -#[cfg(not(feature = "allow_access_to_core"))] +#[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] mod proto; -#[cfg(not(feature = "allow_access_to_core"))] +#[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] mod stream; -#[cfg(not(feature = "allow_access_to_core"))] +#[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] mod webtransport; #[allow(dead_code)] From 6c8de6327f27756230ccf8e21d569583b5c6b25e Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 16 May 2023 15:16:43 +0200 Subject: [PATCH 88/97] fix: unnused import --- h3/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/server.rs b/h3/src/server.rs index 1dc80684..10d63fe3 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -55,7 +55,7 @@ use std::{ marker::PhantomData, option::Option, result::Result, - sync::{Arc, Mutex}, + sync::Arc, task::{Context, Poll}, }; From 6449f79d4430de7b71a63894e724aa7a3f8707e2 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 17 May 2023 14:29:21 +0200 Subject: [PATCH 89/97] fix: shutdown todo --- h3-webtransport/src/server.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 0cae8718..52667a6d 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -170,11 +170,9 @@ where let mut stream = match stream { Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), Ok(None) => { - // We always send a last GoAway frame to the client, so it knows which was the last - // non-rejected request. - // self.shutdown(0).await?; - todo!("shutdown"); - // return Ok(None); + // FIXME: is proper HTTP GoAway shutdown required? + panic!(""); + return Ok(None); } Err(err) => { match err.kind() { From 2b536b7190847b449d2481a3b7c97db0313b839a Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Wed, 17 May 2023 14:29:51 +0200 Subject: [PATCH 90/97] feat(webtransport): tokio AsyncRead/AsyncWrite --- examples/webtransport_server.rs | 4 +- h3-webtransport/Cargo.toml | 1 + h3-webtransport/src/stream.rs | 243 +++++++++++++++++++++----------- h3/src/stream.rs | 159 ++++++++++++++++++--- 4 files changed, 309 insertions(+), 98 deletions(-) diff --git a/examples/webtransport_server.rs b/examples/webtransport_server.rs index 1dd9131d..58d4ba43 100644 --- a/examples/webtransport_server.rs +++ b/examples/webtransport_server.rs @@ -1,6 +1,5 @@ use anyhow::{Context, Result}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use h3::{ error::ErrorLevel, ext::Protocol, @@ -16,6 +15,7 @@ use http::Method; use rustls::{Certificate, PrivateKey}; use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use structopt::StructOpt; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::pin; use tracing::{error, info, trace_span}; @@ -257,7 +257,7 @@ where .context("Failed to respond")?; let mut resp = Vec::new(); - stream.close().await?; + stream.shutdown().await?; stream.read_to_end(&mut resp).await?; tracing::info!("Got response from client: {resp:?}"); diff --git a/h3-webtransport/Cargo.toml b/h3-webtransport/Cargo.toml index c1c310cf..c4901844 100644 --- a/h3-webtransport/Cargo.toml +++ b/h3-webtransport/Cargo.toml @@ -11,6 +11,7 @@ futures-util = { version = "0.3", default-features = false } http = "0.2.9" pin-project-lite = { version = "0.2", default_features = false } tracing = "0.1.37" +tokio = { version = "1.28", default_features = false } [dependencies.h3] version = "0.0.2" diff --git a/h3-webtransport/src/stream.rs b/h3-webtransport/src/stream.rs index 07a926ad..4f29f6f5 100644 --- a/h3-webtransport/src/stream.rs +++ b/h3-webtransport/src/stream.rs @@ -1,16 +1,14 @@ use std::task::Poll; use bytes::{Buf, Bytes}; -use futures_util::{ready, AsyncRead, AsyncWrite}; -use h3::{ - quic::{self, SendStream as _, SendStreamUnframed}, - stream::BufRecvStream, -}; +use h3::{quic, stream::BufRecvStream}; use pin_project_lite::pin_project; +use tokio::io::ReadBuf; pin_project! { /// WebTransport receive stream pub struct RecvStream { + #[pin] stream: BufRecvStream, } } @@ -47,75 +45,38 @@ where } } -macro_rules! async_read { - ($buf: ty) => { - fn poll_read( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: $buf, - ) -> Poll> { - // If the buffer i empty, poll for more data - if !self.stream.has_remaining() { - let res = ready!(self.stream.poll_read(cx).map_err(Into::into))?; - if res { - return Poll::Ready(Ok(0)); - }; - } - - let chunk = self.stream.take_chunk(buf.len()); - - // Do not overfill - if let Some(chunk) = chunk { - assert!(chunk.len() <= buf.len()); - let len = chunk.len().min(buf.len()); - buf[..len].copy_from_slice(&chunk); - - Poll::Ready(Ok(len)) - } else { - Poll::Ready(Ok(0)) - } - } - }; -} - -macro_rules! async_write { - ($buf: ty) => { - fn poll_write( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - mut buf: $buf, - ) -> Poll> { - self.poll_send(cx, &mut buf).map_err(Into::into) - } - - fn poll_flush( - self: std::pin::Pin<&mut Self>, - _: &mut std::task::Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_close( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - self.poll_finish(cx).map_err(Into::into) - } - }; +impl futures_util::io::AsyncRead for RecvStream +where + BufRecvStream: futures_util::io::AsyncRead, +{ + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let p = self.project(); + p.stream.poll_read(cx, buf) + } } -impl AsyncRead for RecvStream +impl tokio::io::AsyncRead for RecvStream where - S: quic::RecvStream, - S::Error: Into, - B: Buf, + BufRecvStream: tokio::io::AsyncRead, { - async_read!(&mut [u8]); + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_read(cx, buf) + } } pin_project! { /// WebTransport send stream pub struct SendStream { + #[pin] stream: BufRecvStream, } } @@ -177,13 +138,64 @@ where } } -impl AsyncWrite for SendStream +impl futures_util::io::AsyncWrite for SendStream where - S: quic::SendStream + SendStreamUnframed, - B: Buf, - S::Error: Into, + BufRecvStream: futures_util::io::AsyncWrite, { - async_write!(&[u8]); + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + let p = self.project(); + p.stream.poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_flush(cx) + } + + fn poll_close( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_close(cx) + } +} + +impl tokio::io::AsyncWrite for SendStream +where + BufRecvStream: tokio::io::AsyncWrite, +{ + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + let p = self.project(); + p.stream.poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_flush(cx) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_shutdown(cx) + } } pin_project! { @@ -192,6 +204,7 @@ pin_project! { /// Can be split into a [`RecvStream`] and [`SendStream`] if the underlying QUIC implementation /// supports it. pub struct BidiStream { + #[pin] stream: BufRecvStream, } } @@ -280,20 +293,90 @@ where } } -impl AsyncRead for BidiStream +impl futures_util::io::AsyncRead for BidiStream where - S: quic::RecvStream, - S::Error: Into, - B: Buf, + BufRecvStream: futures_util::io::AsyncRead, { - async_read!(&mut [u8]); + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let p = self.project(); + p.stream.poll_read(cx, buf) + } } -impl AsyncWrite for BidiStream +impl futures_util::io::AsyncWrite for BidiStream where - S: SendStreamUnframed, - S::Error: Into, - B: Buf, + BufRecvStream: futures_util::io::AsyncWrite, { - async_write!(&[u8]); + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + let p = self.project(); + p.stream.poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_flush(cx) + } + + fn poll_close( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_close(cx) + } +} + +impl tokio::io::AsyncRead for BidiStream +where + BufRecvStream: tokio::io::AsyncRead, +{ + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_read(cx, buf) + } +} + +impl tokio::io::AsyncWrite for BidiStream +where + BufRecvStream: tokio::io::AsyncWrite, +{ + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + let p = self.project(); + p.stream.poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_flush(cx) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let p = self.project(); + p.stream.poll_shutdown(cx) + } } diff --git a/h3/src/stream.rs b/h3/src/stream.rs index a086112f..514a0390 100644 --- a/h3/src/stream.rs +++ b/h3/src/stream.rs @@ -1,10 +1,13 @@ use std::{ marker::PhantomData, + pin::Pin, task::{Context, Poll}, }; use bytes::{Buf, BufMut, Bytes}; use futures_util::{future, ready}; +use pin_project_lite::pin_project; +use tokio::io::ReadBuf; use crate::{ buf::BufList, @@ -373,23 +376,25 @@ where } } -/// A stream which allows partial reading of the data without data loss. -/// -/// This fixes the problem where `poll_data` returns more than the needed amount of bytes, -/// requiring correct implementations to hold on to that extra data and return it later. -/// -/// # Usage -/// -/// Implements `quic::RecvStream` which will first return buffered data, and then read from the -/// stream -pub struct BufRecvStream { - buf: BufList, - /// Indicates that the end of the stream has been reached +pin_project! { + /// A stream which allows partial reading of the data without data loss. + /// + /// This fixes the problem where `poll_data` returns more than the needed amount of bytes, + /// requiring correct implementations to hold on to that extra data and return it later. /// - /// Data may still be available as buffered - eos: bool, - stream: S, - _marker: PhantomData, + /// # Usage + /// + /// Implements `quic::RecvStream` which will first return buffered data, and then read from the + /// stream + pub struct BufRecvStream { + buf: BufList, + // Indicates that the end of the stream has been reached + // + // Data may still be available as buffered + eos: bool, + stream: S, + _marker: PhantomData, + } } impl std::fmt::Debug for BufRecvStream { @@ -521,6 +526,7 @@ where B: Buf, S: SendStreamUnframed, { + #[inline] fn poll_send( &mut self, cx: &mut std::task::Context<'_>, @@ -559,6 +565,127 @@ where } } +impl futures_util::io::AsyncRead for BufRecvStream +where + B: Buf, + S: RecvStream, + S::Error: Into, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let p = &mut *self; + // Poll for data if the buffer is empty + // + // If there is data available *do not* poll for more data, as that may suspend indefinitely + // if no more data is sent, causing data loss. + if !p.has_remaining() { + let eos = ready!(p.poll_read(cx).map_err(Into::into))?; + if eos { + return Poll::Ready(Ok(0)); + } + } + + let chunk = p.buf_mut().take_chunk(buf.len()); + if let Some(chunk) = chunk { + assert!(chunk.len() <= buf.len()); + let len = chunk.len().min(buf.len()); + // Write the subset into the destination + buf[..len].copy_from_slice(&chunk); + Poll::Ready(Ok(len)) + } else { + Poll::Ready(Ok(0)) + } + } +} + +impl tokio::io::AsyncRead for BufRecvStream +where + B: Buf, + S: RecvStream, + S::Error: Into, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let p = &mut *self; + // Poll for data if the buffer is empty + // + // If there is data available *do not* poll for more data, as that may suspend indefinitely + // if no more data is sent, causing data loss. + if !p.has_remaining() { + let eos = ready!(p.poll_read(cx).map_err(Into::into))?; + if eos { + return Poll::Ready(Ok(())); + } + } + + let chunk = p.buf_mut().take_chunk(buf.remaining()); + if let Some(chunk) = chunk { + assert!(chunk.len() <= buf.remaining()); + // Write the subset into the destination + buf.put_slice(&chunk); + Poll::Ready(Ok(())) + } else { + Poll::Ready(Ok(())) + } + } +} + +impl futures_util::io::AsyncWrite for BufRecvStream +where + B: Buf, + S: SendStreamUnframed, + S::Error: Into, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + mut buf: &[u8], + ) -> Poll> { + let p = &mut *self; + p.poll_send(cx, &mut buf).map_err(Into::into) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let p = &mut *self; + p.poll_finish(cx).map_err(Into::into) + } +} + +impl tokio::io::AsyncWrite for BufRecvStream +where + B: Buf, + S: SendStreamUnframed, + S::Error: Into, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + mut buf: &[u8], + ) -> Poll> { + let p = &mut *self; + p.poll_send(cx, &mut buf).map_err(Into::into) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let p = &mut *self; + p.poll_finish(cx).map_err(Into::into) + } +} + #[cfg(test)] mod tests { use quinn_proto::coding::BufExt; From 64fe30d43572655e8d87de7979e12b0e5d05003b Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Thu, 25 May 2023 20:50:01 +0200 Subject: [PATCH 91/97] fix: panic in shutdown during request --- h3-webtransport/src/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 52667a6d..b04ce984 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -171,7 +171,6 @@ where Ok(Some(s)) => FrameStream::new(BufRecvStream::new(s)), Ok(None) => { // FIXME: is proper HTTP GoAway shutdown required? - panic!(""); return Ok(None); } Err(err) => { From 48d5f1fcce7871f4d3f8d6be72af9492bde2b40a Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 29 May 2023 11:29:34 +0200 Subject: [PATCH 92/97] chore: split config into separate struct --- h3/src/client.rs | 2 +- h3/src/config.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ h3/src/connection.rs | 2 +- h3/src/lib.rs | 1 + h3/src/server.rs | 44 +------------------------------------------- 5 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 h3/src/config.rs diff --git a/h3/src/client.rs b/h3/src/client.rs index 4458100c..fb444664 100644 --- a/h3/src/client.rs +++ b/h3/src/client.rs @@ -13,13 +13,13 @@ use http::{request, HeaderMap, Response}; use tracing::{info, trace}; use crate::{ + config::Config, connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, error::{Code, Error, ErrorLevel}, frame::FrameStream, proto::{frame::Frame, headers::Header, push::PushId}, qpack, quic::{self, StreamId}, - server::Config, stream::{self, BufRecvStream}, }; diff --git a/h3/src/config.rs b/h3/src/config.rs new file mode 100644 index 00000000..81df645a --- /dev/null +++ b/h3/src/config.rs @@ -0,0 +1,43 @@ +use crate::proto::varint::VarInt; + +/// Configures the HTTP/3 connection +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// Just like in HTTP/2, HTTP/3 also uses the concept of "grease" + /// to prevent potential interoperability issues in the future. + /// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve + /// and accommodate future changes without breaking existing implementations. + pub send_grease: bool, + /// The MAX_FIELD_SECTION_SIZE in HTTP/3 refers to the maximum size of the dynamic table used in HPACK compression. + /// HPACK is the compression algorithm used in HTTP/3 to reduce the size of the header fields in HTTP requests and responses. + + /// In HTTP/3, the MAX_FIELD_SECTION_SIZE is set to 12. + /// This means that the dynamic table used for HPACK compression can have a maximum size of 2^12 bytes, which is 4KB. + pub max_field_section_size: u64, + + //=https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 + /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled + pub enable_webtransport: bool, + /// https://www.rfc-editor.org/info/rfc8441 defines an extended CONNECT method in Section 4, + /// enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL parameter. + /// That parameter is only defined for HTTP/2. + /// for extended CONNECT in HTTP/3; instead, the SETTINGS_ENABLE_WEBTRANSPORT setting implies that an endpoint supports extended CONNECT. + pub enable_extended_connect: bool, + /// Enable HTTP Datagrams, see https://datatracker.ietf.org/doc/rfc9297/ for details + pub enable_datagram: bool, + /// The maximum number of concurrent streams that can be opened by the peer. + pub max_webtransport_sessions: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { + max_field_section_size: VarInt::MAX.0, + send_grease: true, + enable_webtransport: false, + enable_extended_connect: false, + enable_datagram: false, + max_webtransport_sessions: 0, + } + } +} diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 348cb6e5..f2fe6e8e 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -11,6 +11,7 @@ use stream::WriteBuf; use tracing::{trace, warn}; use crate::{ + config::Config, error::{Code, Error}, frame::FrameStream, proto::{ @@ -21,7 +22,6 @@ use crate::{ }, qpack, quic::{self, SendStream as _}, - server::Config, stream::{self, AcceptRecvStream, AcceptedRecvStream, BufRecvStream, UniStreamHeader}, webtransport::SessionId, }; diff --git a/h3/src/lib.rs b/h3/src/lib.rs index 21c24e39..7fb6496a 100644 --- a/h3/src/lib.rs +++ b/h3/src/lib.rs @@ -3,6 +3,7 @@ #![allow(clippy::derive_partial_eq_without_eq)] pub mod client; +mod config; pub mod error; pub mod ext; pub mod quic; diff --git a/h3/src/server.rs b/h3/src/server.rs index 10d63fe3..985bc4f0 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -71,6 +71,7 @@ use quic::StreamId; use tokio::sync::mpsc; use crate::{ + config::Config, connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, error::{Code, Error, ErrorLevel}, frame::{FrameStream, FrameStreamError}, @@ -79,7 +80,6 @@ use crate::{ frame::{Frame, PayloadLen}, headers::Header, push::PushId, - varint::VarInt, }, qpack, quic::{self, RecvDatagramExt, SendDatagramExt, SendStream as _}, @@ -509,48 +509,6 @@ where } } -/// Configures the HTTP/3 connection -#[derive(Debug, Clone, Copy)] -pub struct Config { - /// Just like in HTTP/2, HTTP/3 also uses the concept of "grease" - /// to prevent potential interoperability issues in the future. - /// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve - /// and accommodate future changes without breaking existing implementations. - pub send_grease: bool, - /// The MAX_FIELD_SECTION_SIZE in HTTP/3 refers to the maximum size of the dynamic table used in HPACK compression. - /// HPACK is the compression algorithm used in HTTP/3 to reduce the size of the header fields in HTTP requests and responses. - - /// In HTTP/3, the MAX_FIELD_SECTION_SIZE is set to 12. - /// This means that the dynamic table used for HPACK compression can have a maximum size of 2^12 bytes, which is 4KB. - pub max_field_section_size: u64, - - //=https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 - /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled - pub enable_webtransport: bool, - /// https://www.rfc-editor.org/info/rfc8441 defines an extended CONNECT method in Section 4, - /// enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL parameter. - /// That parameter is only defined for HTTP/2. - /// for extended CONNECT in HTTP/3; instead, the SETTINGS_ENABLE_WEBTRANSPORT setting implies that an endpoint supports extended CONNECT. - pub enable_extended_connect: bool, - /// Enable HTTP Datagrams, see https://datatracker.ietf.org/doc/rfc9297/ for details - pub enable_datagram: bool, - /// The maximum number of concurrent streams that can be opened by the peer. - pub max_webtransport_sessions: u64, -} - -impl Default for Config { - fn default() -> Self { - Self { - max_field_section_size: VarInt::MAX.0, - send_grease: true, - enable_webtransport: false, - enable_extended_connect: false, - enable_datagram: false, - max_webtransport_sessions: 0, - } - } -} - //= https://www.rfc-editor.org/rfc/rfc9114#section-6.1 //= type=TODO //# In order to From 483eac959fdd0399dff726680c77aa97d33404ba Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 29 May 2023 11:45:30 +0200 Subject: [PATCH 93/97] fix: msrv and move datagram to exposed module --- h3-quinn/Cargo.toml | 4 +- h3-quinn/src/lib.rs | 4 +- h3-webtransport/src/server.rs | 4 +- h3/src/connection.rs | 4 +- h3/src/ext.rs | 71 +++++++++++++++++++++++++++++++++++ h3/src/proto/datagram.rs | 65 -------------------------------- h3/src/proto/frame.rs | 4 +- h3/src/proto/mod.rs | 1 - h3/src/quic.rs | 2 +- h3/src/server.rs | 2 +- 10 files changed, 84 insertions(+), 77 deletions(-) delete mode 100644 h3/src/proto/datagram.rs diff --git a/h3-quinn/Cargo.toml b/h3-quinn/Cargo.toml index 4eb82dfd..d45fe233 100644 --- a/h3-quinn/Cargo.toml +++ b/h3-quinn/Cargo.toml @@ -15,7 +15,9 @@ license = "MIT" [dependencies] h3 = { version = "0.0.2", path = "../h3" } bytes = "1" -quinn = { version = "0.10", default-features = false } +quinn = { version = "0.10", default-features = false, features = [ + "futures-io", +] } quinn-proto = { version = "0.10", default-features = false } tokio-util = { version = "0.7.7" } futures = { version = "0.3.27" } diff --git a/h3-quinn/src/lib.rs b/h3-quinn/src/lib.rs index dc7eb7e5..78696dec 100644 --- a/h3-quinn/src/lib.rs +++ b/h3-quinn/src/lib.rs @@ -25,7 +25,7 @@ pub use quinn::{ }; use h3::{ - proto::datagram::Datagram, + ext::Datagram, quic::{self, Error, StreamId, WriteBuf}, }; use tokio_util::sync::ReusableBoxFuture; @@ -646,7 +646,7 @@ where let s = Pin::new(self.stream.as_mut().unwrap()); - let res = ready!(tokio::io::AsyncWrite::poll_write(s, cx, buf.chunk())); + let res = ready!(futures::io::AsyncWrite::poll_write(s, cx, buf.chunk())); match res { Ok(written) => { buf.advance(written); diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index b04ce984..9f165082 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -12,9 +12,9 @@ use futures_util::{future::poll_fn, ready, Future}; use h3::{ connection::ConnectionState, error::{Code, ErrorLevel}, - ext::Protocol, + ext::{Datagram, Protocol}, frame::FrameStream, - proto::{datagram::Datagram, frame::Frame}, + proto::frame::Frame, quic::{self, OpenStreams, RecvDatagramExt, SendDatagramExt, WriteBuf}, server::{self, Connection, RequestStream}, Error, diff --git a/h3/src/connection.rs b/h3/src/connection.rs index f2fe6e8e..d6707303 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -177,7 +177,7 @@ where .insert(SettingId::H3_DATAGRAM, config.enable_datagram as u64) .map_err(|e| Code::H3_INTERNAL_ERROR.with_cause(e))?; - tracing::debug!("Sending server settings: {settings:#x?}"); + tracing::debug!("Sending server settings: {:#x?}", settings); if config.send_grease { // Grease Settings (https://www.rfc-editor.org/rfc/rfc9114.html#name-defined-settings-parameters) @@ -225,7 +225,7 @@ where //# Endpoints MUST NOT require any data to be received from //# the peer prior to sending the SETTINGS frame; settings MUST be sent //# as soon as the transport is ready to send data. - trace!("Sending Settings frame: {settings:#x?}"); + trace!("Sending Settings frame: {:#x?}", settings); stream::write( &mut control_send, WriteBuf::from(UniStreamHeader::Control(settings)), diff --git a/h3/src/ext.rs b/h3/src/ext.rs index d38bc980..3e77c22a 100644 --- a/h3/src/ext.rs +++ b/h3/src/ext.rs @@ -1,7 +1,16 @@ //! Extensions for the HTTP/3 protocol. +use std::convert::TryFrom; use std::str::FromStr; +use bytes::{Buf, Bytes}; + +use crate::{ + error::Code, + proto::{stream::StreamId, varint::VarInt}, + Error, +}; + /// Describes the `:protocol` pseudo-header for extended connect /// /// See: [https://www.rfc-editor.org/rfc/rfc8441#section-4] @@ -31,3 +40,65 @@ impl FromStr for Protocol { } } } + +/// HTTP datagram frames +/// See: https://www.rfc-editor.org/rfc/rfc9297#section-2.1 +pub struct Datagram { + /// Stream id divided by 4 + stream_id: StreamId, + /// The data contained in the datagram + pub payload: B, +} + +impl Datagram +where + B: Buf, +{ + /// Creates a new datagram frame + pub fn new(stream_id: StreamId, payload: B) -> Self { + assert!( + stream_id.into_inner() % 4 == 0, + "StreamId is not divisible by 4" + ); + Self { stream_id, payload } + } + + /// Decodes a datagram frame from the QUIC datagram + pub fn decode(mut buf: B) -> Result { + let q_stream_id = VarInt::decode(&mut buf) + .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Malformed datagram frame"))?; + + //= https://www.rfc-editor.org/rfc/rfc9297#section-2.1 + // Quarter Stream ID: A variable-length integer that contains the value of the client-initiated bidirectional + // stream that this datagram is associated with divided by four (the division by four stems + // from the fact that HTTP requests are sent on client-initiated bidirectional streams, + // which have stream IDs that are divisible by four). The largest legal QUIC stream ID + // value is 262-1, so the largest legal value of the Quarter Stream ID field is 260-1. + // Receipt of an HTTP/3 Datagram that includes a larger value MUST be treated as an HTTP/3 + // connection error of type H3_DATAGRAM_ERROR (0x33). + let stream_id = StreamId::try_from(u64::from(q_stream_id) * 4) + .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Invalid stream id"))?; + + let payload = buf; + + Ok(Self { stream_id, payload }) + } + + #[inline] + /// Returns the associated stream id of the datagram + pub fn stream_id(&self) -> StreamId { + self.stream_id + } + + #[inline] + /// Returns the datagram payload + pub fn payload(&self) -> &B { + &self.payload + } + + /// Encode the datagram to wire format + pub fn encode(self, buf: &mut D) { + (VarInt::from(self.stream_id) / 4).encode(buf); + buf.put(self.payload); + } +} diff --git a/h3/src/proto/datagram.rs b/h3/src/proto/datagram.rs deleted file mode 100644 index 15a97cc3..00000000 --- a/h3/src/proto/datagram.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::convert::TryFrom; - -use bytes::{Buf, Bytes}; - -use crate::{error::Code, Error}; - -use super::{stream::StreamId, varint::VarInt}; - -/// HTTP datagram frames -/// See: https://www.rfc-editor.org/rfc/rfc9297#section-2.1 -pub struct Datagram { - /// Stream id divided by 4 - stream_id: StreamId, - /// The data contained in the datagram - pub payload: B, -} - -impl Datagram -where - B: Buf, -{ - pub fn new(stream_id: StreamId, payload: B) -> Self { - assert!( - stream_id.into_inner() % 4 == 0, - "StreamId is not divisible by 4" - ); - Self { stream_id, payload } - } - - /// Decodes a datagram frame from the QUIC datagram - pub fn decode(mut buf: B) -> Result { - let q_stream_id = VarInt::decode(&mut buf) - .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Malformed datagram frame"))?; - - //= https://www.rfc-editor.org/rfc/rfc9297#section-2.1 - // Quarter Stream ID: A variable-length integer that contains the value of the client-initiated bidirectional - // stream that this datagram is associated with divided by four (the division by four stems - // from the fact that HTTP requests are sent on client-initiated bidirectional streams, - // which have stream IDs that are divisible by four). The largest legal QUIC stream ID - // value is 262-1, so the largest legal value of the Quarter Stream ID field is 260-1. - // Receipt of an HTTP/3 Datagram that includes a larger value MUST be treated as an HTTP/3 - // connection error of type H3_DATAGRAM_ERROR (0x33). - let stream_id = StreamId::try_from(u64::from(q_stream_id) * 4) - .map_err(|_| Code::H3_DATAGRAM_ERROR.with_cause("Invalid stream id"))?; - - let payload = buf; - - Ok(Self { stream_id, payload }) - } - - #[inline] - pub fn stream_id(&self) -> StreamId { - self.stream_id - } - - #[inline] - pub fn payload(&self) -> &B { - &self.payload - } - - pub fn encode(self, buf: &mut D) { - (VarInt::from(self.stream_id) / 4).encode(buf); - buf.put(self.payload); - } -} diff --git a/h3/src/proto/frame.rs b/h3/src/proto/frame.rs index 7ac56d7c..f587d01a 100644 --- a/h3/src/proto/frame.rs +++ b/h3/src/proto/frame.rs @@ -220,7 +220,7 @@ impl fmt::Debug for Frame { Frame::Goaway(id) => write!(f, "GoAway({})", id), Frame::MaxPushId(id) => write!(f, "MaxPushId({})", id), Frame::Grease => write!(f, "Grease()"), - Frame::WebTransportStream(session) => write!(f, "WebTransportStream({session:?})"), + Frame::WebTransportStream(session) => write!(f, "WebTransportStream({:?})", session), } } } @@ -536,7 +536,7 @@ impl Settings { //# H3_SETTINGS_ERROR. settings.insert(identifier, value)?; } else { - tracing::warn!("Unsupported setting: {identifier:#x?}"); + tracing::warn!("Unsupported setting: {:#x?}", identifier); } } Ok(settings) diff --git a/h3/src/proto/mod.rs b/h3/src/proto/mod.rs index 900db9e1..8c46d5f6 100644 --- a/h3/src/proto/mod.rs +++ b/h3/src/proto/mod.rs @@ -1,5 +1,4 @@ pub mod coding; -pub mod datagram; #[allow(dead_code)] pub mod frame; #[allow(dead_code)] diff --git a/h3/src/quic.rs b/h3/src/quic.rs index 2cc55980..de1ecf4c 100644 --- a/h3/src/quic.rs +++ b/h3/src/quic.rs @@ -7,7 +7,7 @@ use std::task::{self, Poll}; use bytes::Buf; -use crate::proto::datagram::Datagram; +use crate::ext::Datagram; pub use crate::proto::stream::{InvalidStreamId, StreamId}; pub use crate::stream::WriteBuf; diff --git a/h3/src/server.rs b/h3/src/server.rs index 985bc4f0..446a4282 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -74,9 +74,9 @@ use crate::{ config::Config, connection::{self, ConnectionInner, ConnectionState, SharedStateRef}, error::{Code, Error, ErrorLevel}, + ext::Datagram, frame::{FrameStream, FrameStreamError}, proto::{ - datagram::Datagram, frame::{Frame, PayloadLen}, headers::Header, push::PushId, From d68f1c013151efe8559a1d35ed2459fd25e0ee53 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Mon, 29 May 2023 13:17:49 +0200 Subject: [PATCH 94/97] fix: doctests --- h3/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/server.rs b/h3/src/server.rs index 446a4282..7416fabb 100644 --- a/h3/src/server.rs +++ b/h3/src/server.rs @@ -141,7 +141,7 @@ where { /// Create a new HTTP/3 server connection with default settings /// - /// Use [`Self::with_config`] or a custom [`Builder`] with [`builder()`] to create a connection + /// Use a custom [`Builder`] with [`builder()`] to create a connection /// with different settings. /// Provide a Connection which implements [`quic::Connection`]. pub async fn new(conn: C) -> Result { From db5c723f653911a476bfd8ffcfebf0f8f2eb980d Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Thu, 1 Jun 2023 09:08:38 +0200 Subject: [PATCH 95/97] fix: remove spec comment due to failing to find draft rfc --- h3/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/src/config.rs b/h3/src/config.rs index 81df645a..ad8463f4 100644 --- a/h3/src/config.rs +++ b/h3/src/config.rs @@ -15,7 +15,7 @@ pub struct Config { /// This means that the dynamic table used for HPACK compression can have a maximum size of 2^12 bytes, which is 4KB. pub max_field_section_size: u64, - //=https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 + /// https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled pub enable_webtransport: bool, /// https://www.rfc-editor.org/info/rfc8441 defines an extended CONNECT method in Section 4, From a45bfd45ef9e4609ce8866558553633291025a95 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 13 Jun 2023 09:16:56 +0200 Subject: [PATCH 96/97] fix: missing feature flag due to feature unification --- h3/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3/Cargo.toml b/h3/Cargo.toml index f396c5e2..bf6a48e7 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -24,7 +24,7 @@ i-implement-a-third-party-backend-and-opt-into-breaking-changes = [] [dependencies] bytes = "1" -futures-util = { version = "0.3", default-features = false } +futures-util = { version = "0.3", default-features = false, features = ["io"] } http = "0.2.9" tokio = { version = "1", features = ["sync"] } pin-project-lite = { version = "0.2", default_features = false } From fa956e0d44e66c04545741908fcb3690b0890be6 Mon Sep 17 00:00:00 2001 From: Tei Roberts Date: Tue, 13 Jun 2023 09:21:03 +0200 Subject: [PATCH 97/97] chore: address visibility concerns --- h3-webtransport/src/server.rs | 15 +++++++++------ h3/src/config.rs | 34 ++++++++++++++++++++++++++++------ h3/src/connection.rs | 1 + h3/src/error.rs | 9 +-------- h3/src/ext.rs | 7 ++++++- 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/h3-webtransport/src/server.rs b/h3-webtransport/src/server.rs index 9f165082..3a212dd1 100644 --- a/h3-webtransport/src/server.rs +++ b/h3-webtransport/src/server.rs @@ -65,14 +65,14 @@ where { let config = shared.write("Read WebTransport support").peer_config; - if !config.enable_webtransport { + if !config.enable_webtransport() { return Err(conn.close( Code::H3_SETTINGS_ERROR, "webtransport is not supported by client", )); } - if !config.enable_datagram { + if !config.enable_datagram() { return Err(conn.close( Code::H3_SETTINGS_ERROR, "datagrams are not supported by client", @@ -84,15 +84,15 @@ where // // However, it is still advantageous to show a log on the server as (attempting) to // establish a WebTransportSession without the proper h3 config is usually a mistake. - if !conn.inner.config.enable_webtransport { + if !conn.inner.config.enable_webtransport() { tracing::warn!("Server does not support webtransport"); } - if !conn.inner.config.enable_datagram { + if !conn.inner.config.enable_datagram() { tracing::warn!("Server does not support datagrams"); } - if !conn.inner.config.enable_extended_connect { + if !conn.inner.config.enable_extended_connect() { tracing::warn!("Server does not support CONNECT"); } @@ -381,7 +381,10 @@ where match ready!(conn.inner.conn.poll_accept_datagram(cx))? { Some(v) => { let datagram = Datagram::decode(v)?; - Poll::Ready(Ok(Some((datagram.stream_id().into(), datagram.payload)))) + Poll::Ready(Ok(Some(( + datagram.stream_id().into(), + datagram.into_payload(), + )))) } None => Poll::Ready(Ok(None)), } diff --git a/h3/src/config.rs b/h3/src/config.rs index ad8463f4..a1dbe89b 100644 --- a/h3/src/config.rs +++ b/h3/src/config.rs @@ -2,31 +2,53 @@ use crate::proto::varint::VarInt; /// Configures the HTTP/3 connection #[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub struct Config { /// Just like in HTTP/2, HTTP/3 also uses the concept of "grease" /// to prevent potential interoperability issues in the future. /// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve /// and accommodate future changes without breaking existing implementations. - pub send_grease: bool, + pub(crate) send_grease: bool, /// The MAX_FIELD_SECTION_SIZE in HTTP/3 refers to the maximum size of the dynamic table used in HPACK compression. /// HPACK is the compression algorithm used in HTTP/3 to reduce the size of the header fields in HTTP requests and responses. /// In HTTP/3, the MAX_FIELD_SECTION_SIZE is set to 12. /// This means that the dynamic table used for HPACK compression can have a maximum size of 2^12 bytes, which is 4KB. - pub max_field_section_size: u64, + pub(crate) max_field_section_size: u64, /// https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled - pub enable_webtransport: bool, + pub(crate) enable_webtransport: bool, /// https://www.rfc-editor.org/info/rfc8441 defines an extended CONNECT method in Section 4, /// enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL parameter. /// That parameter is only defined for HTTP/2. /// for extended CONNECT in HTTP/3; instead, the SETTINGS_ENABLE_WEBTRANSPORT setting implies that an endpoint supports extended CONNECT. - pub enable_extended_connect: bool, + pub(crate) enable_extended_connect: bool, /// Enable HTTP Datagrams, see https://datatracker.ietf.org/doc/rfc9297/ for details - pub enable_datagram: bool, + pub(crate) enable_datagram: bool, /// The maximum number of concurrent streams that can be opened by the peer. - pub max_webtransport_sessions: u64, + pub(crate) max_webtransport_sessions: u64, +} + +impl Config { + /// https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1 + /// Sets `SETTINGS_ENABLE_WEBTRANSPORT` if enabled + pub fn enable_webtransport(&self) -> bool { + self.enable_webtransport + } + + /// Enable HTTP Datagrams, see https://datatracker.ietf.org/doc/rfc9297/ for details + pub fn enable_datagram(&self) -> bool { + self.enable_datagram + } + + /// https://www.rfc-editor.org/info/rfc8441 defines an extended CONNECT method in Section 4, + /// enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL parameter. + /// That parameter is only defined for HTTP/2. + /// for extended CONNECT in HTTP/3; instead, the SETTINGS_ENABLE_WEBTRANSPORT setting implies that an endpoint supports extended CONNECT. + pub fn enable_extended_connect(&self) -> bool { + self.enable_extended_connect + } } impl Default for Config { diff --git a/h3/src/connection.rs b/h3/src/connection.rs index d6707303..8c455657 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -27,6 +27,7 @@ use crate::{ }; #[doc(hidden)] +#[non_exhaustive] pub struct SharedState { // Peer settings pub peer_config: Config, diff --git a/h3/src/error.rs b/h3/src/error.rs index a86b8386..02663f7d 100644 --- a/h3/src/error.rs +++ b/h3/src/error.rs @@ -40,18 +40,11 @@ impl PartialEq for Code { /// The error kind. #[derive(Clone)] -pub struct ErrorImpl { +pub(crate) struct ErrorImpl { pub(crate) kind: Kind, cause: Option>, } -impl ErrorImpl { - /// Returns the error kind - pub fn kind(&self) -> &Kind { - &self.kind - } -} - /// Some errors affect the whole connection, others only one Request or Stream. /// See [errors](https://www.rfc-editor.org/rfc/rfc9114.html#errors) for mor details. #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] diff --git a/h3/src/ext.rs b/h3/src/ext.rs index 3e77c22a..1ef8a14e 100644 --- a/h3/src/ext.rs +++ b/h3/src/ext.rs @@ -47,7 +47,7 @@ pub struct Datagram { /// Stream id divided by 4 stream_id: StreamId, /// The data contained in the datagram - pub payload: B, + payload: B, } impl Datagram @@ -101,4 +101,9 @@ where (VarInt::from(self.stream_id) / 4).encode(buf); buf.put(self.payload); } + + /// Returns the datagram payload + pub fn into_payload(self) -> B { + self.payload + } }