diff options
Diffstat (limited to 'third_party/rust/neqo-http3')
47 files changed, 22379 insertions, 0 deletions
diff --git a/third_party/rust/neqo-http3/.cargo-checksum.json b/third_party/rust/neqo-http3/.cargo-checksum.json new file mode 100644 index 0000000000..2705291744 --- /dev/null +++ b/third_party/rust/neqo-http3/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"fe3c1114cfbb94004bf56740c0d373568cb459efdb12504e67f31923fbd436e1","src/buffered_send_stream.rs":"f45bdf9ad2a04b3828c74ff5440681d3c9d1af39b55470e4f729842dc2412295","src/client_events.rs":"e1392e7bbb62fb0505a4d8bcd27559699bbf38f3c94e7d8cae7291db82e6334c","src/conn_params.rs":"224a8ea6ef632930a7788a1cabf47ce69ad41bd4bc8dcf3053fbd998fdb38e82","src/connection.rs":"09aeb123f8dc6b903dd7d30579e5bb09ed8f70bfae563fb2fcc1871c67d604d4","src/connection_client.rs":"ed1c9ebf443f49dbf12c193953a71ec0e6b95555e1927afce813d2a8324758be","src/connection_server.rs":"ca33b50650bd1ca2a952851b72712d55ec2e48b48f1f06e4184c808b8e1e009a","src/control_stream_local.rs":"d6ecc0adc926e1d5cec9a378317f9dfcfeeb9840a0873a2afb380c2d252d8c54","src/control_stream_remote.rs":"59eb4041e366d92f9f294e8446755caa5e91fd943bba7b79b726698ba13be248","src/features/extended_connect/mod.rs":"3b02f6b18627f3855465a81b1d9b285e6f13839e75a8a6db648ed9082908d7f0","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"7e3bdd591b9c7d02f69954629f889d52bd54f13dbca11d555e614138c2a55107","src/features/extended_connect/tests/webtransport/mod.rs":"fed03f0ded21a9f17be5be99e4572e16dd0c8598e37044df3228990ea7fcc9f4","src/features/extended_connect/tests/webtransport/negotiation.rs":"98254ef8446581ec520026b04ef9549645602181b61602c9936f6660141edf0b","src/features/extended_connect/tests/webtransport/sessions.rs":"de3d836f666c2bec31e70b33bdc2669572cabbe17df2225db7282613a224a364","src/features/extended_connect/tests/webtransport/streams.rs":"8b3c34cac1b2171252a4bb53d420ac2098549a20309c327bf56e2e9ba9e33538","src/features/extended_connect/webtransport_session.rs":"a6472eca50a2d097aa6ba8a76b45ae69fe2edd2696b2953945faa3ce6e7417f9","src/features/extended_connect/webtransport_streams.rs":"a9a106eefc93a9f6e9e1c246df64904353de1c4fbcd394b338e6b117f6c677f5","src/features/mod.rs":"925aae4427ad82e4d019354802b223d53db5e5585d4a940f5417a24a9503d7ee","src/frames/hframe.rs":"726842108261c9af1e7576bc546e7bd7bea86fbef4a5804f4b45a2b4612e2679","src/frames/mod.rs":"7d0a46ca147336d14781edb8dbee8b03c2e4bcd6646f5473a9d93d31fe73fecb","src/frames/reader.rs":"4883e25064da1fb3a6ae46b5d15e6bcfec9c5bbea55a1937ecdb9465b62a93b2","src/frames/tests/hframe.rs":"01ec74eb3eb25d95042aa0263f9267f89535e6b7b8c1161fab4ba9ee5352d4a7","src/frames/tests/mod.rs":"0610609b316767a6a022837d32ee0452e37ea296fde37e51bec87e7c77e923a3","src/frames/tests/reader.rs":"2bfadc7afbc41bff9f5f930b31550259a8a92484d35f6c5d8dd8fd9acfb88f5b","src/frames/tests/wtframe.rs":"589ebe1e62ce4da63b37b7d22cde7ba572ddbf29336fdcdbbcd0a745f79dacd8","src/frames/wtframe.rs":"c80518d1569de277767c7ccb7441898aadbfc5fb2afb968c1d5105f8d175ccff","src/headers_checks.rs":"44891c16dda6b7ef742058ecb0a8d34e219c51cae1216c09c661cf72d9a5e7d5","src/lib.rs":"ed8da14e573cc5a97afb012a78af7f076eb83b5cc20cb4fe432eb7136a3ffe52","src/priority.rs":"10d9dcfcd4585f2ca256daf254c78a428297c41976c6548f19cd3ed2222b7cd2","src/push_controller.rs":"eb27c7c2a52c6108c0e4d040b021775a2b573f32d78b7ac8652ff46fd549f780","src/qlog.rs":"b1e6108b018abb077f218d1806e0a83370afa87709e26b3d51f482ae5d9b9c82","src/qpack_decoder_receiver.rs":"c927dfc3e58c71d282210ba79280f6f03e789733bc3bedc247e68bab516b9e9e","src/qpack_encoder_receiver.rs":"d0ac03cc111b6e1c555a8654d3234116f2b135b5b040edac23cefe2d640beba9","src/recv_message.rs":"06666c22101cda41de14682dc7e2e6721f2821bd45baefc22caceae4ccfcf2e0","src/request_target.rs":"6041a69a0a74969ec08bc164509c055e9bad99f53bbeb16c0aa17d108dd68b8c","src/send_message.rs":"70f8a91d85515f42a64a88bd2a9480175b12596bc082f77587cc5bcff9ce996c","src/server.rs":"ab6d4c80cb5f6c070f74d8df27e7bd62d5c8a8e7756ff9d1a31d3f9ff91327a1","src/server_connection_events.rs":"12d353ca6301467f6d475dde3b789951a5716c89ddd7dbf1383efef8082361f3","src/server_events.rs":"c96cff96d5893a9ab7165d17e3d1afaafc5492418b30003c1c26ca8f489ab7ca","src/settings.rs":"476b154b5eea4c8d69a4a790fee3e527cef4d375df1cfb5eed04ec56406fe15a","src/stream_type_reader.rs":"7a7226b7911d69f7e00ec4987c2a32a5e8a33463203398cbee1e6645d2691478","tests/httpconn.rs":"bb6927801a8c75e4f05eb6cdb1e7f2d57be69b74e68ddad2a1614f2aeed04369","tests/priority.rs":"3418be17fbdfdbcfd80dc4532f9365f405925442fabc916f2b22f90aee89629f","tests/send_message.rs":"1e893216d9252e6fb69a0fb291b4f8b8ea954847c346ff7f9347d7895618cabf","tests/webtransport.rs":"cb30d348c0ce05efb722abac3b1c524216fa4cbde8b62a1d1e3238c3fadecbe7"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/neqo-http3/Cargo.toml b/third_party/rust/neqo-http3/Cargo.toml new file mode 100644 index 0000000000..8eeb2a58bf --- /dev/null +++ b/third_party/rust/neqo-http3/Cargo.toml @@ -0,0 +1,55 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.70.0" +name = "neqo-http3" +version = "0.7.0" +authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"] +license = "MIT OR Apache-2.0" + +[dependencies] +enumset = "1.1.2" +lazy_static = "1.4" +sfv = "0.9.3" +smallvec = "1.11.1" +url = "2.5" + +[dependencies.log] +version = "0.4.17" +default-features = false + +[dependencies.neqo-common] +path = "./../neqo-common" + +[dependencies.neqo-crypto] +path = "./../neqo-crypto" + +[dependencies.neqo-qpack] +path = "./../neqo-qpack" + +[dependencies.neqo-transport] +path = "./../neqo-transport" + +[dependencies.qlog] +git = "https://github.com/cloudflare/quiche" +rev = "09ea4b244096a013071cfe2175bbf2945fb7f8d1" + +[dev-dependencies.test-fixture] +path = "../test-fixture" + +[features] +deny-warnings = [] +fuzzing = [ + "neqo-transport/fuzzing", + "neqo-crypto/fuzzing", +] diff --git a/third_party/rust/neqo-http3/src/buffered_send_stream.rs b/third_party/rust/neqo-http3/src/buffered_send_stream.rs new file mode 100644 index 0000000000..4f6761fa80 --- /dev/null +++ b/third_party/rust/neqo-http3/src/buffered_send_stream.rs @@ -0,0 +1,118 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use neqo_common::qtrace; +use neqo_transport::{Connection, StreamId}; + +use crate::Res; + +#[derive(Debug, PartialEq, Eq)] +pub enum BufferedStream { + Uninitialized, + Initialized { stream_id: StreamId, buf: Vec<u8> }, +} + +impl Default for BufferedStream { + fn default() -> Self { + Self::Uninitialized + } +} + +impl ::std::fmt::Display for BufferedStream { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "BufferedStream {:?}", Option::<StreamId>::from(self)) + } +} + +impl BufferedStream { + #[must_use] + pub fn new(stream_id: StreamId) -> Self { + Self::Initialized { + stream_id, + buf: Vec::new(), + } + } + + /// # Panics + /// + /// If the `BufferedStream` is initialized more than one it will panic. + pub fn init(&mut self, stream_id: StreamId) { + debug_assert!(&Self::Uninitialized == self); + *self = Self::Initialized { + stream_id, + buf: Vec::new(), + }; + } + + /// # Panics + /// + /// This functon cannot be called before the `BufferedStream` is initialized. + pub fn buffer(&mut self, to_buf: &[u8]) { + if let Self::Initialized { buf, .. } = self { + buf.extend_from_slice(to_buf); + } else { + debug_assert!(false, "Do not buffer date before the stream is initialized"); + } + } + + /// # Errors + /// + /// Returns `neqo_transport` errors. + pub fn send_buffer(&mut self, conn: &mut Connection) -> Res<usize> { + let label = ::neqo_common::log_subject!(::log::Level::Debug, self); + let mut sent = 0; + if let Self::Initialized { stream_id, buf } = self { + if !buf.is_empty() { + qtrace!([label], "sending data."); + sent = conn.stream_send(*stream_id, &buf[..])?; + if sent == buf.len() { + buf.clear(); + } else { + let b = buf.split_off(sent); + *buf = b; + } + } + } + Ok(sent) + } + + /// # Errors + /// + /// Returns `neqo_transport` errors. + pub fn send_atomic(&mut self, conn: &mut Connection, to_send: &[u8]) -> Res<bool> { + // First try to send anything that is in the buffer. + self.send_buffer(conn)?; + if let Self::Initialized { stream_id, buf } = self { + if buf.is_empty() { + let res = conn.stream_send_atomic(*stream_id, to_send)?; + Ok(res) + } else { + Ok(false) + } + } else { + Ok(false) + } + } + + #[must_use] + pub fn has_buffered_data(&self) -> bool { + if let Self::Initialized { buf, .. } = self { + !buf.is_empty() + } else { + false + } + } +} + +impl From<&BufferedStream> for Option<StreamId> { + fn from(stream: &BufferedStream) -> Option<StreamId> { + if let BufferedStream::Initialized { stream_id, .. } = stream { + Some(*stream_id) + } else { + None + } + } +} diff --git a/third_party/rust/neqo-http3/src/client_events.rs b/third_party/rust/neqo-http3/src/client_events.rs new file mode 100644 index 0000000000..4b2ebc6c30 --- /dev/null +++ b/third_party/rust/neqo-http3/src/client_events.rs @@ -0,0 +1,379 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::{cell::RefCell, collections::VecDeque, rc::Rc}; + +use neqo_common::{event::Provider as EventProvider, Header}; +use neqo_crypto::ResumptionToken; +use neqo_transport::{AppError, StreamId, StreamType}; + +use crate::{ + connection::Http3State, + features::extended_connect::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason}, + settings::HSettingType, + CloseType, Http3StreamInfo, HttpRecvStreamEvents, RecvStreamEvents, SendStreamEvents, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum WebTransportEvent { + Negotiated(bool), + Session { + stream_id: StreamId, + status: u16, + headers: Vec<Header>, + }, + SessionClosed { + stream_id: StreamId, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + }, + NewStream { + stream_id: StreamId, + session_id: StreamId, + }, + Datagram { + session_id: StreamId, + datagram: Vec<u8>, + }, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Http3ClientEvent { + /// Response headers are received. + HeaderReady { + stream_id: StreamId, + headers: Vec<Header>, + interim: bool, + fin: bool, + }, + /// A stream can accept new data. + DataWritable { stream_id: StreamId }, + /// New bytes available for reading. + DataReadable { stream_id: StreamId }, + /// Peer reset the stream or there was an parsing error. + Reset { + stream_id: StreamId, + error: AppError, + local: bool, + }, + /// Peer has sent a STOP_SENDING. + StopSending { + stream_id: StreamId, + error: AppError, + }, + /// A new push promise. + PushPromise { + push_id: u64, + request_stream_id: StreamId, + headers: Vec<Header>, + }, + /// A push response headers are ready. + PushHeaderReady { + push_id: u64, + headers: Vec<Header>, + interim: bool, + fin: bool, + }, + /// New bytes are available on a push stream for reading. + PushDataReadable { push_id: u64 }, + /// A push has been canceled. + PushCanceled { push_id: u64 }, + /// A push stream was been reset due to a HttpGeneralProtocol error. + /// Most common case are malformed response headers. + PushReset { push_id: u64, error: AppError }, + /// New stream can be created + RequestsCreatable, + /// Cert authentication needed + AuthenticationNeeded, + /// Encrypted client hello fallback occurred. The certificate for the + /// name `public_name` needs to be authenticated in order to get + /// an updated ECH configuration. + EchFallbackAuthenticationNeeded { public_name: String }, + /// A new resumption token. + ResumptionToken(ResumptionToken), + /// Zero Rtt has been rejected. + ZeroRttRejected, + /// Client has received a GOAWAY frame + GoawayReceived, + /// Connection state change. + StateChange(Http3State), + /// WebTransport events + WebTransport(WebTransportEvent), +} + +#[derive(Debug, Default, Clone)] +pub struct Http3ClientEvents { + events: Rc<RefCell<VecDeque<Http3ClientEvent>>>, +} + +impl RecvStreamEvents for Http3ClientEvents { + /// Add a new `DataReadable` event + fn data_readable(&self, stream_info: Http3StreamInfo) { + self.insert(Http3ClientEvent::DataReadable { + stream_id: stream_info.stream_id(), + }); + } + + /// Add a new `Reset` event. + fn recv_closed(&self, stream_info: Http3StreamInfo, close_type: CloseType) { + let stream_id = stream_info.stream_id(); + let (local, error) = match close_type { + CloseType::ResetApp(_) => { + self.remove_recv_stream_events(stream_id); + return; + } + CloseType::Done => return, + CloseType::ResetRemote(e) => { + self.remove_recv_stream_events(stream_id); + (false, e) + } + CloseType::LocalError(e) => { + self.remove_recv_stream_events(stream_id); + (true, e) + } + }; + + self.insert(Http3ClientEvent::Reset { + stream_id, + error, + local, + }); + } +} + +impl HttpRecvStreamEvents for Http3ClientEvents { + /// Add a new `HeaderReady` event. + fn header_ready( + &self, + stream_info: Http3StreamInfo, + headers: Vec<Header>, + interim: bool, + fin: bool, + ) { + self.insert(Http3ClientEvent::HeaderReady { + stream_id: stream_info.stream_id(), + headers, + interim, + fin, + }); + } +} + +impl SendStreamEvents for Http3ClientEvents { + /// Add a new `DataWritable` event. + fn data_writable(&self, stream_info: Http3StreamInfo) { + self.insert(Http3ClientEvent::DataWritable { + stream_id: stream_info.stream_id(), + }); + } + + fn send_closed(&self, stream_info: Http3StreamInfo, close_type: CloseType) { + let stream_id = stream_info.stream_id(); + self.remove_send_stream_events(stream_id); + if let CloseType::ResetRemote(error) = close_type { + self.insert(Http3ClientEvent::StopSending { stream_id, error }); + } + } +} + +impl ExtendedConnectEvents for Http3ClientEvents { + fn session_start( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + status: u16, + headers: Vec<Header>, + ) { + if connect_type == ExtendedConnectType::WebTransport { + self.insert(Http3ClientEvent::WebTransport(WebTransportEvent::Session { + stream_id, + status, + headers, + })); + } else { + unreachable!("There is only ExtendedConnectType::WebTransport."); + } + } + + fn session_end( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + ) { + if connect_type == ExtendedConnectType::WebTransport { + self.insert(Http3ClientEvent::WebTransport( + WebTransportEvent::SessionClosed { + stream_id, + reason, + headers, + }, + )); + } else { + unreachable!("There are no other types."); + } + } + + fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) { + self.insert(Http3ClientEvent::WebTransport( + WebTransportEvent::NewStream { + stream_id: stream_info.stream_id(), + session_id: stream_info.session_id().unwrap(), + }, + )); + } + + fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>) { + self.insert(Http3ClientEvent::WebTransport( + WebTransportEvent::Datagram { + session_id, + datagram, + }, + )); + } +} + +impl Http3ClientEvents { + pub fn push_promise(&self, push_id: u64, request_stream_id: StreamId, headers: Vec<Header>) { + self.insert(Http3ClientEvent::PushPromise { + push_id, + request_stream_id, + headers, + }); + } + + pub fn push_canceled(&self, push_id: u64) { + self.remove_events_for_push_id(push_id); + self.insert(Http3ClientEvent::PushCanceled { push_id }); + } + + pub fn push_reset(&self, push_id: u64, error: AppError) { + self.remove_events_for_push_id(push_id); + self.insert(Http3ClientEvent::PushReset { push_id, error }); + } + + /// Add a new `RequestCreatable` event + pub(crate) fn new_requests_creatable(&self, stream_type: StreamType) { + if stream_type == StreamType::BiDi { + self.insert(Http3ClientEvent::RequestsCreatable); + } + } + + /// Add a new `AuthenticationNeeded` event + pub(crate) fn authentication_needed(&self) { + self.insert(Http3ClientEvent::AuthenticationNeeded); + } + + /// Add a new `AuthenticationNeeded` event + pub(crate) fn ech_fallback_authentication_needed(&self, public_name: String) { + self.insert(Http3ClientEvent::EchFallbackAuthenticationNeeded { public_name }); + } + + /// Add a new resumption token event. + pub(crate) fn resumption_token(&self, token: ResumptionToken) { + self.insert(Http3ClientEvent::ResumptionToken(token)); + } + + /// Add a new `ZeroRttRejected` event. + pub(crate) fn zero_rtt_rejected(&self) { + self.insert(Http3ClientEvent::ZeroRttRejected); + } + + /// Add a new `GoawayReceived` event. + pub(crate) fn goaway_received(&self) { + self.remove(|evt| matches!(evt, Http3ClientEvent::RequestsCreatable)); + self.insert(Http3ClientEvent::GoawayReceived); + } + + pub fn insert(&self, event: Http3ClientEvent) { + self.events.borrow_mut().push_back(event); + } + + fn remove<F>(&self, f: F) + where + F: Fn(&Http3ClientEvent) -> bool, + { + self.events.borrow_mut().retain(|evt| !f(evt)); + } + + /// Add a new `StateChange` event. + pub(crate) fn connection_state_change(&self, state: Http3State) { + match state { + // If closing, existing events no longer relevant. + Http3State::Closing { .. } | Http3State::Closed(_) => self.events.borrow_mut().clear(), + Http3State::Connected => { + self.remove(|evt| { + matches!(evt, Http3ClientEvent::StateChange(Http3State::ZeroRtt)) + }); + } + _ => (), + } + self.insert(Http3ClientEvent::StateChange(state)); + } + + /// Remove all events for a stream + fn remove_recv_stream_events(&self, stream_id: StreamId) { + self.remove(|evt| { + matches!(evt, + Http3ClientEvent::HeaderReady { stream_id: x, .. } + | Http3ClientEvent::DataReadable { stream_id: x } + | Http3ClientEvent::PushPromise { request_stream_id: x, .. } + | Http3ClientEvent::Reset { stream_id: x, .. } if *x == stream_id) + }); + } + + fn remove_send_stream_events(&self, stream_id: StreamId) { + self.remove(|evt| { + matches!(evt, + Http3ClientEvent::DataWritable { stream_id: x } + | Http3ClientEvent::StopSending { stream_id: x, .. } if *x == stream_id) + }); + } + + pub fn has_push(&self, push_id: u64) -> bool { + for iter in &*self.events.borrow() { + if matches!(iter, Http3ClientEvent::PushPromise{push_id:x, ..} if *x == push_id) { + return true; + } + } + false + } + + pub fn remove_events_for_push_id(&self, push_id: u64) { + self.remove(|evt| { + matches!(evt, + Http3ClientEvent::PushPromise{ push_id: x, .. } + | Http3ClientEvent::PushHeaderReady{ push_id: x, .. } + | Http3ClientEvent::PushDataReadable{ push_id: x, .. } + | Http3ClientEvent::PushCanceled{ push_id: x, .. } if *x == push_id) + }); + } + + pub fn negotiation_done(&self, feature_type: HSettingType, succeeded: bool) { + if feature_type == HSettingType::EnableWebTransport { + self.insert(Http3ClientEvent::WebTransport( + WebTransportEvent::Negotiated(succeeded), + )); + } + } +} + +impl EventProvider for Http3ClientEvents { + type Event = Http3ClientEvent; + + /// Check if there is any event present. + fn has_events(&self) -> bool { + !self.events.borrow().is_empty() + } + + /// Take the first event. + fn next_event(&mut self) -> Option<Self::Event> { + self.events.borrow_mut().pop_front() + } +} diff --git a/third_party/rust/neqo-http3/src/conn_params.rs b/third_party/rust/neqo-http3/src/conn_params.rs new file mode 100644 index 0000000000..23a5d2cc67 --- /dev/null +++ b/third_party/rust/neqo-http3/src/conn_params.rs @@ -0,0 +1,136 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::cmp::min; + +use neqo_qpack::QpackSettings; +use neqo_transport::ConnectionParameters; + +const QPACK_MAX_TABLE_SIZE_DEFAULT: u64 = 65536; +const QPACK_TABLE_SIZE_LIMIT: u64 = (1 << 30) - 1; +const QPACK_MAX_BLOCKED_STREAMS_DEFAULT: u16 = 20; +const MAX_PUSH_STREAM_DEFAULT: u64 = 0; +const WEBTRANSPORT_DEFAULT: bool = false; +const HTTP3_DATAGRAM_DEFAULT: bool = false; + +#[derive(Debug, Clone)] +pub struct Http3Parameters { + conn_params: ConnectionParameters, + qpack_settings: QpackSettings, + max_concurrent_push_streams: u64, + webtransport: bool, + http3_datagram: bool, +} + +impl Default for Http3Parameters { + fn default() -> Self { + Self { + conn_params: ConnectionParameters::default(), + qpack_settings: QpackSettings { + max_table_size_encoder: QPACK_MAX_TABLE_SIZE_DEFAULT, + max_table_size_decoder: QPACK_MAX_TABLE_SIZE_DEFAULT, + max_blocked_streams: QPACK_MAX_BLOCKED_STREAMS_DEFAULT, + }, + max_concurrent_push_streams: MAX_PUSH_STREAM_DEFAULT, + webtransport: WEBTRANSPORT_DEFAULT, + http3_datagram: HTTP3_DATAGRAM_DEFAULT, + } + } +} + +impl Http3Parameters { + #[must_use] + pub fn get_connection_parameters(&self) -> &ConnectionParameters { + &self.conn_params + } + + #[must_use] + pub fn connection_parameters(mut self, conn_params: ConnectionParameters) -> Self { + self.conn_params = conn_params; + self + } + + /// # Panics + /// + /// The table size must be smaller than 1 << 30 by the spec. + #[must_use] + pub fn max_table_size_encoder(mut self, mut max_table: u64) -> Self { + assert!(max_table <= QPACK_TABLE_SIZE_LIMIT); + max_table = min(max_table, QPACK_TABLE_SIZE_LIMIT); + self.qpack_settings.max_table_size_encoder = max_table; + self + } + + #[must_use] + pub fn get_max_table_size_encoder(&self) -> u64 { + self.qpack_settings.max_table_size_encoder + } + + /// # Panics + /// + /// The table size must be smaller than 1 << 30 by the spec. + #[must_use] + pub fn max_table_size_decoder(mut self, mut max_table: u64) -> Self { + assert!(max_table <= QPACK_TABLE_SIZE_LIMIT); + max_table = min(max_table, QPACK_TABLE_SIZE_LIMIT); + self.qpack_settings.max_table_size_decoder = max_table; + self + } + + #[must_use] + pub fn get_max_table_size_decoder(&self) -> u64 { + self.qpack_settings.max_table_size_decoder + } + + #[must_use] + pub fn max_blocked_streams(mut self, max_blocked: u16) -> Self { + self.qpack_settings.max_blocked_streams = max_blocked; + self + } + + #[must_use] + pub fn get_max_blocked_streams(&self) -> u16 { + self.qpack_settings.max_blocked_streams + } + + #[must_use] + pub fn get_qpack_settings(&self) -> &QpackSettings { + &self.qpack_settings + } + + #[must_use] + pub fn max_concurrent_push_streams(mut self, max_push_streams: u64) -> Self { + self.max_concurrent_push_streams = max_push_streams; + self + } + + #[must_use] + pub fn get_max_concurrent_push_streams(&self) -> u64 { + self.max_concurrent_push_streams + } + + #[must_use] + pub fn webtransport(mut self, webtransport: bool) -> Self { + self.webtransport = webtransport; + self + } + + #[must_use] + pub fn get_webtransport(&self) -> bool { + self.webtransport + } + + #[must_use] + pub fn http3_datagram(mut self, http3_datagram: bool) -> Self { + self.http3_datagram = http3_datagram; + self + } + + #[must_use] + pub fn get_http3_datagram(&self) -> bool { + self.http3_datagram + } +} diff --git a/third_party/rust/neqo-http3/src/connection.rs b/third_party/rust/neqo-http3/src/connection.rs new file mode 100644 index 0000000000..bb2b5a6ce0 --- /dev/null +++ b/third_party/rust/neqo-http3/src/connection.rs @@ -0,0 +1,1639 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::{ + cell::RefCell, + collections::{BTreeSet, HashMap}, + fmt::Debug, + mem, + rc::Rc, +}; + +use neqo_common::{qdebug, qerror, qinfo, qtrace, qwarn, Decoder, Header, MessageType, Role}; +use neqo_qpack::{decoder::QPackDecoder, encoder::QPackEncoder}; +use neqo_transport::{ + streams::SendOrder, AppError, Connection, ConnectionError, DatagramTracking, State, StreamId, + StreamType, ZeroRttState, +}; + +use crate::{ + client_events::Http3ClientEvents, + control_stream_local::ControlStreamLocal, + control_stream_remote::ControlStreamRemote, + features::extended_connect::{ + webtransport_session::WebTransportSession, + webtransport_streams::{WebTransportRecvStream, WebTransportSendStream}, + ExtendedConnectEvents, ExtendedConnectFeature, ExtendedConnectType, + }, + frames::HFrame, + push_controller::PushController, + qpack_decoder_receiver::DecoderRecvStream, + qpack_encoder_receiver::EncoderRecvStream, + recv_message::{RecvMessage, RecvMessageInfo}, + request_target::{AsRequestTarget, RequestTarget}, + send_message::SendMessage, + settings::{HSettingType, HSettings, HttpZeroRttChecker}, + stream_type_reader::NewStreamHeadReader, + CloseType, Error, Http3Parameters, Http3StreamType, HttpRecvStreamEvents, NewStreamType, + Priority, PriorityHandler, ReceiveOutput, RecvStream, RecvStreamEvents, Res, SendStream, + SendStreamEvents, +}; + +pub(crate) struct RequestDescription<'b, 't, T> +where + T: AsRequestTarget<'t> + ?Sized + Debug, +{ + pub method: &'b str, + pub connect_type: Option<ExtendedConnectType>, + pub target: &'t T, + pub headers: &'b [Header], + pub priority: Priority, +} + +pub enum WebTransportSessionAcceptAction { + Accept, + Reject(Vec<Header>), +} + +impl ::std::fmt::Display for WebTransportSessionAcceptAction { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + WebTransportSessionAcceptAction::Accept => f.write_str("Accept"), + WebTransportSessionAcceptAction::Reject(_) => f.write_str("Reject"), + } + } +} + +#[derive(Debug)] +enum Http3RemoteSettingsState { + NotReceived, + Received(HSettings), + ZeroRtt(HSettings), +} + +/// States: +/// - `Initializing`: this is the state during the QUIC handshake, +/// - `ZeroRtt`: 0-RTT has been enabled and is active +/// - Connected +/// - GoingAway(StreamId): The connection has received a `GOAWAY` frame +/// - Closing(ConnectionError): The connection is closed. The closing has been initiated by this end +/// of the connection, e.g., the `CONNECTION_CLOSE` frame has been sent. In this state, the +/// connection waits a certain amount of time to retransmit the `CONNECTION_CLOSE` frame if +/// needed. +/// - Closed(ConnectionError): This is the final close state: closing has been initialized by the +/// peer and an ack for the `CONNECTION_CLOSE` frame has been sent or the closing has been +/// initiated by this end of the connection and the ack for the `CONNECTION_CLOSE` has been +/// received or the waiting time has passed. +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)] +pub enum Http3State { + Initializing, + ZeroRtt, + Connected, + GoingAway(StreamId), + Closing(ConnectionError), + Closed(ConnectionError), +} + +impl Http3State { + #[must_use] + pub fn active(&self) -> bool { + matches!( + self, + Http3State::Connected | Http3State::GoingAway(_) | Http3State::ZeroRtt + ) + } +} + +/** +# HTTP/3 core implementation + +This is the core implementation of HTTP/3 protocol. It implements most of the features of the +protocol. `Http3Client` and `Http3ServerHandler` implement only client and server side behavior. + +The API consists of: +- functions that correspond to the `Http3Client` and `Http3ServerHandler` API: + - `new` + - `close` + - `fetch` - only used by the client-side implementation + - `read_data` + - `stream_reset_send` + - `stream_stop_sending` + - `cancel_fetch` + - `stream_close_send` +- functions that correspond to [`WebTransport`](https://w3c.github.io/webtransport/) functions: + - `webtransport_create_session` - only used by the client-side implementation + - `webtransport_session_accept` - only used by the server-side implementation + - `webtransport_close_session` + - `webtransport_create_stream_local` - this function is called when an application wants to open + a new `WebTransport` stream. For example `Http3Client::webtransport_create_stream` will call + this function. + - `webtransport_create_stream_remote` - this is called when a `WebTransport` stream has been + opened by the peer and this function sets up the appropriate handler for the stream. +- functions that are called by `process_http3` + - `process_sending` - some send-streams are buffered streams(see the Streams section) and this + function is called to trigger sending of the buffer data. +- functions that are called to handle `ConnectionEvent`s: + - `add_new_stream` + - `handle_stream_readable` + - `handle_stream_reset` + - `handle_stream_stop_sending` + - `handle_state_change` + - `handle_zero_rtt_rejected` +- Additional functions: + - `set_features_listener` + - `stream_has_pending_data` + - `has_data_to_send` + - `add_streams` + - `add_recv_stream` + - `queue_control_frame` + - `queue_update_priority` + - `set_0rtt_settings` + - `get_settings` + - `state` + - `webtransport_enabled` + +## Streams + +Each `Http3Connection` holds a list of stream handlers. Each send and receive-handler is registered in +`send_streams` and `recv_streams`. Unidirectional streams are registered only on one of the lists +and bidirectional streams are registered in both lists and the 2 handlers are independent, e.g. one +can be closed and removed ane second may still be active. + +The only streams that are not registered are the local control stream, local QPACK decoder stream, +and local QPACK encoder stream. These streams are send-streams and sending data on this stream is +handled a bit differently. This is done in the `process_sending` function, i.e. the control data is +sent first and QPACK data is sent after regular stream data is sent because this stream may have +new data only after regular streams are handled (TODO we may improve this a bit to send QPACK +commands before headers.) + +There are the following types of streams: +- `Control`: there is only a receiver stream of this type and the handler is `ControlStreamRemote`. +- `Decoder`: there is only a receiver stream of this type and the handler is `DecoderRecvStream`. +- `Encoder`: there is only a receiver stream of this type and the handler is `EncoderRecvStream`. +- `NewStream`: there is only a receiver stream of this type and the handler is + `NewStreamHeadReader`. +- `Http`: `SendMessage` and `RecvMessage` handlers are responsible for this type of streams. +- `Push`: `RecvMessage` is responsible for this type of streams. +- `ExtendedConnect`: `WebTransportSession` is responsible sender and receiver handler. +- `WebTransport(StreamId)`: `WebTransportSendStream` and `WebTransportRecvStream` are responsible + sender and receiver handler. +- `Unknown`: These are all other stream types that are not unknown to the current implementation + and should be handled properly by the spec, e.g., in our implementation the streams are + reset. + +The streams are registered in `send_streams` and `recv_streams` in following ways depending if they +are local or remote: +- local streams: + - all local stream will be registered with the appropriate handler. +- remote streams: + - all new incoming streams are registered with `NewStreamHeadReader`. This is triggered by + `ConnectionEvent::NewStream` and `add_new_stream` is called. + - reading from a `NewStreamHeadReader` stream, via the `receive` function, will decode a stream + type. `NewStreamHeadReader::receive` will return `ReceiveOutput::NewStream(_)` when a stream + type has been decoded. After this point the stream: + - will be regegistered with the appropriate handler, + - will be canceled if is an unknown stream type or + - the connection will fail if it is unallowed stream type (receiveing HTTP request on the + client-side). + The output is handled in `handle_new_stream`, for control, qpack streams and partially + `WebTransport` streams, otherwise the output is handled by `Http3Client` and `Http3ServerHandler`. + + +### Receiving data + +Reading from a stream is triggered by `ConnectionEvent::RecvStreamReadable` events for the stream. +The receive handler is retrieved from `recv_streams` and its `RecvStream::receive` function is +called. + +Receiving data on `Http` streams is also triggered by the `read_data` function. +`ConnectionEvent::RecvStreamReadable` events will trigger reading `HEADERS` frame and frame headers +for `DATA` frames which will produce `Http3ClientEvent` or `Http3ServerEvent` events. The content of +`DATA` frames is read by the application using the `read_data` function. The `read_data` function +may read frame headers for consecutive `DATA` frames. + +On a `WebTransport(_)` stream data will be read only by the `read_data` function. The +`RecvStream::receive` function only produces an `Http3ClientEvent` or `Http3ServerEvent` event. + +The `receive` and `read_data` functions may detect that the stream is done, e.g. FIN received. In +this case, the stream will be removed from the `recv_stream` register, see `remove_recv_stream`. + +### Sending data + +All sender stream handlers have buffers. Data is first written into a buffer before being supplied +to the QUIC layer. All data except the `DATA` frame and `WebTransport(_)`’s payload are written +into the buffer. This includes stream type byte, e.g. `WEBTRANSPORT_STREAM` as well. In the case of +`Http` and `WebTransport(_)` applications can write directly to the QUIC layer using the +`send_data` function to avoid copying data. Sending data via the `send_data` function is only +possible if there is no buffered data. + +If a stream has buffered data it will be registered in the `streams_with_pending_data` queue and +actual sending will be performed in the `process_sending` function call. (This is done in this way, +i.e. data is buffered first and then sent, for 2 reasons: in this way, sending will happen in a +single function, therefore error handling and clean up is easier and the QUIC layer may not be +able to accept all data and being able to buffer data is required in any case.) + +The `send` and `send_data` functions may detect that the stream is closed and all outstanding data +has been transferred to the QUIC layer. In this case, the stream will be removed from the +`send_stream` register. + +### `ControlStreamRemote` + +The `ControlStreamRemote` handler uses `FrameReader` to read and decode frames received on the +control frame. The `receive` returns `ReceiveOutput::ControlFrames(_)` with a list of control +frames read (the list may be empty). The control frames are handled by `Http3Connection` and/or by +`Http3Client` and `Http3ServerHandler`. + +### `DecoderRecvStream` and `EncoderRecvStream` + +The `receive` functions of these handlers call corresponding `receive` functions of `QPackDecoder` +and `QPackDecoder`. + +`DecoderRecvStream` returns `ReceiveOutput::UnblockedStreams(_)` that may contain a list of stream +ids that are unblocked by receiving qpack decoder commands. `Http3Connection` will handle this +output by calling `receive` for the listed stream ids. + +`EncoderRecvStream` only returns `ReceiveOutput::NoOutput`. + +Both handlers may return an error that will close the connection. + +### `NewStreamHeadReader` + +A new incoming receiver stream registers a `NewStreamHeadReader` handler. This handler reads the +first bytes of a stream to detect a stream type. The `receive` function returns +`ReceiveOutput::NoOutput` if a stream type is still not known by reading the available stream data +or `ReceiveOutput::NewStream(_)`. The handling of the output is explained above. + +### `SendMessage` and `RecvMessage` + +`RecvMessage::receive` only returns `ReceiveOutput::NoOutput`. It also have an event listener of +type `HttpRecvStreamEvents`. The listener is called when headers are ready, or data is ready, etc. + +For example for `Http` stream the listener will produce `HeaderReady` and `DataReadable` events. + +### `WebTransportSession` + +A `WebTransport` session is connected to a control stream that is in essence an HTTP transaction. +Therefore, `WebTransportSession` will internally use a `SendMessage` and `RecvMessage` handler to +handle parsing and sending of HTTP part of the control stream. When HTTP headers are exchenged, +`WebTransportSession` will take over handling of stream data. `WebTransportSession` sets +`WebTransportSessionListener` as the `RecvMessage` event listener. + +`WebTransportSendStream` and `WebTransportRecvStream` are associated with a `WebTransportSession` +and they will be canceled if the session is closed. To be avle to do this `WebTransportSession` +holds a list of its active streams and clean up is done in `remove_extended_connect`. + +### `WebTransportSendStream` and `WebTransportRecvStream` + +`WebTransport` streams are associated with a session. `WebTransportSendStream` and +`WebTransportRecvStream` hold a reference to the session and are registered in the session upon + creation by `Http3Connection`. The `WebTransportSendStream` and `WebTransportRecvStream` + handlers will be unregistered from the session if they are closed, reset, or canceled. + +The call to function `receive` may produce `Http3ClientEvent::DataReadable`. Actual reading of +data is done in the `read_data` function. +*/ +#[derive(Debug)] +pub(crate) struct Http3Connection { + role: Role, + pub state: Http3State, + local_params: Http3Parameters, + control_stream_local: ControlStreamLocal, + pub qpack_encoder: Rc<RefCell<QPackEncoder>>, + pub qpack_decoder: Rc<RefCell<QPackDecoder>>, + settings_state: Http3RemoteSettingsState, + streams_with_pending_data: BTreeSet<StreamId>, + pub send_streams: HashMap<StreamId, Box<dyn SendStream>>, + pub recv_streams: HashMap<StreamId, Box<dyn RecvStream>>, + webtransport: ExtendedConnectFeature, +} + +impl ::std::fmt::Display for Http3Connection { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Http3 connection") + } +} + +impl Http3Connection { + /// Create a new connection. + pub fn new(conn_params: Http3Parameters, role: Role) -> Self { + Self { + state: Http3State::Initializing, + control_stream_local: ControlStreamLocal::new(), + qpack_encoder: Rc::new(RefCell::new(QPackEncoder::new( + conn_params.get_qpack_settings(), + true, + ))), + qpack_decoder: Rc::new(RefCell::new(QPackDecoder::new( + conn_params.get_qpack_settings(), + ))), + webtransport: ExtendedConnectFeature::new( + ExtendedConnectType::WebTransport, + conn_params.get_webtransport(), + ), + local_params: conn_params, + settings_state: Http3RemoteSettingsState::NotReceived, + streams_with_pending_data: BTreeSet::new(), + send_streams: HashMap::new(), + recv_streams: HashMap::new(), + role, + } + } + + /// This function is called when a not default feature needs to be negotiated. This is currently + /// only used for the `WebTransport` feature. The negotiation is done via the `SETTINGS` frame + /// and when the peer's `SETTINGS` frame has been received the listener will be called. + pub fn set_features_listener(&mut self, feature_listener: Http3ClientEvents) { + self.webtransport.set_listener(feature_listener); + } + + /// This function creates and initializes, i.e. send stream type, the control and qpack + /// streams. + fn initialize_http3_connection(&mut self, conn: &mut Connection) -> Res<()> { + qinfo!([self], "Initialize the http3 connection."); + self.control_stream_local.create(conn)?; + + self.send_settings(); + self.create_qpack_streams(conn)?; + Ok(()) + } + + fn send_settings(&mut self) { + qdebug!([self], "Send settings."); + self.control_stream_local.queue_frame(&HFrame::Settings { + settings: HSettings::from(&self.local_params), + }); + self.control_stream_local.queue_frame(&HFrame::Grease); + } + + /// Save settings for adding to the session ticket. + pub(crate) fn save_settings(&self) -> Vec<u8> { + HttpZeroRttChecker::save(&self.local_params) + } + + fn create_qpack_streams(&mut self, conn: &mut Connection) -> Res<()> { + qdebug!([self], "create_qpack_streams."); + self.qpack_encoder + .borrow_mut() + .add_send_stream(conn.stream_create(StreamType::UniDi)?); + self.qpack_decoder + .borrow_mut() + .add_send_stream(conn.stream_create(StreamType::UniDi)?); + Ok(()) + } + + /// Inform a `HttpConnection` that a stream has data to send and that `send` should be called + /// for the stream. + pub fn stream_has_pending_data(&mut self, stream_id: StreamId) { + self.streams_with_pending_data.insert(stream_id); + } + + /// Return true if there is a stream that needs to send data. + pub fn has_data_to_send(&self) -> bool { + !self.streams_with_pending_data.is_empty() + } + + /// This function calls the `send` function for all streams that have data to send. If a stream + /// has data to send it will be added to the `streams_with_pending_data` list. + /// + /// Control and QPACK streams are handled differently and are never added to the list. + fn send_non_control_streams(&mut self, conn: &mut Connection) -> Res<()> { + let to_send = mem::take(&mut self.streams_with_pending_data); + for stream_id in to_send { + let done = if let Some(s) = &mut self.send_streams.get_mut(&stream_id) { + s.send(conn)?; + if s.has_data_to_send() { + self.streams_with_pending_data.insert(stream_id); + } + s.done() + } else { + false + }; + if done { + self.remove_send_stream(stream_id, conn); + } + } + Ok(()) + } + + /// Call `send` for all streams that need to send data. See explanation for the main structure + /// for more details. + pub fn process_sending(&mut self, conn: &mut Connection) -> Res<()> { + // check if control stream has data to send. + self.control_stream_local + .send(conn, &mut self.recv_streams)?; + + self.send_non_control_streams(conn)?; + + self.qpack_decoder.borrow_mut().send(conn)?; + match self.qpack_encoder.borrow_mut().send_encoder_updates(conn) { + Ok(()) + | Err(neqo_qpack::Error::EncoderStreamBlocked | neqo_qpack::Error::DynamicTableFull) => { + } + Err(e) => return Err(Error::QpackError(e)), + } + Ok(()) + } + + /// We have a resumption token which remembers previous settings. Update the setting. + pub fn set_0rtt_settings(&mut self, conn: &mut Connection, settings: HSettings) -> Res<()> { + self.initialize_http3_connection(conn)?; + self.set_qpack_settings(&settings)?; + self.settings_state = Http3RemoteSettingsState::ZeroRtt(settings); + self.state = Http3State::ZeroRtt; + Ok(()) + } + + /// Returns the settings for a connection. This is used for creating a resumption token. + pub fn get_settings(&self) -> Option<HSettings> { + if let Http3RemoteSettingsState::Received(settings) = &self.settings_state { + Some(settings.clone()) + } else { + None + } + } + + /// This is called when a `ConnectionEvent::NewStream` event is received. This register the + /// stream with a `NewStreamHeadReader` handler. + pub fn add_new_stream(&mut self, stream_id: StreamId) { + qtrace!([self], "A new stream: {}.", stream_id); + self.recv_streams.insert( + stream_id, + Box::new(NewStreamHeadReader::new(stream_id, self.role)), + ); + } + + /// The function calls `receive` for a stream. It also deals with the outcome of a read by + /// calling `handle_stream_manipulation_output`. + #[allow(clippy::option_if_let_else)] // False positive as borrow scope isn't lexical here. + fn stream_receive(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<ReceiveOutput> { + qtrace!([self], "Readable stream {}.", stream_id); + + if let Some(recv_stream) = self.recv_streams.get_mut(&stream_id) { + let res = recv_stream.receive(conn); + return self + .handle_stream_manipulation_output(res, stream_id, conn) + .map(|(output, _)| output); + } + Ok(ReceiveOutput::NoOutput) + } + + fn handle_unblocked_streams( + &mut self, + unblocked_streams: Vec<StreamId>, + conn: &mut Connection, + ) -> Res<()> { + for stream_id in unblocked_streams { + qdebug!([self], "Stream {} is unblocked", stream_id); + if let Some(r) = self.recv_streams.get_mut(&stream_id) { + let res = r + .http_stream() + .ok_or(Error::HttpInternal(10))? + .header_unblocked(conn); + let res = self.handle_stream_manipulation_output(res, stream_id, conn)?; + debug_assert!(matches!(res, (ReceiveOutput::NoOutput, _))); + } + } + Ok(()) + } + + /// This function handles reading from all streams, i.e. control, qpack, request/response + /// stream and unidi stream that are still do not have a type. + /// The function cannot handle: + /// 1) a `Push(_)`, `Htttp` or `WebTransportStream(_)` stream + /// 2) frames `MaxPushId`, `PriorityUpdateRequest`, `PriorityUpdateRequestPush` or `Goaway` must + /// be handled by `Http3Client`/`Server`. + /// The function returns `ReceiveOutput`. + pub fn handle_stream_readable( + &mut self, + conn: &mut Connection, + stream_id: StreamId, + ) -> Res<ReceiveOutput> { + let mut output = self.stream_receive(conn, stream_id)?; + + if let ReceiveOutput::NewStream(stream_type) = output { + output = self.handle_new_stream(conn, stream_type, stream_id)?; + } + + #[allow(clippy::match_same_arms)] // clippy is being stupid here + match output { + ReceiveOutput::UnblockedStreams(unblocked_streams) => { + self.handle_unblocked_streams(unblocked_streams, conn)?; + Ok(ReceiveOutput::NoOutput) + } + ReceiveOutput::ControlFrames(mut control_frames) => { + let mut rest = Vec::new(); + for cf in control_frames.drain(..) { + if let Some(not_handled) = self.handle_control_frame(cf)? { + rest.push(not_handled); + } + } + Ok(ReceiveOutput::ControlFrames(rest)) + } + ReceiveOutput::NewStream( + NewStreamType::Push(_) | NewStreamType::Http | NewStreamType::WebTransportStream(_), + ) => Ok(output), + ReceiveOutput::NewStream(_) => { + unreachable!("NewStream should have been handled already") + } + ReceiveOutput::NoOutput => Ok(output), + } + } + + /// This is called when a RESET frame has been received. + pub fn handle_stream_reset( + &mut self, + stream_id: StreamId, + app_error: AppError, + conn: &mut Connection, + ) -> Res<()> { + qinfo!( + [self], + "Handle a stream reset stream_id={} app_err={}", + stream_id, + app_error + ); + + self.close_recv(stream_id, CloseType::ResetRemote(app_error), conn) + } + + pub fn handle_stream_stop_sending( + &mut self, + stream_id: StreamId, + app_error: AppError, + conn: &mut Connection, + ) -> Res<()> { + qinfo!( + [self], + "Handle stream_stop_sending stream_id={} app_err={}", + stream_id, + app_error + ); + + if self.send_stream_is_critical(stream_id) { + return Err(Error::HttpClosedCriticalStream); + } + + self.close_send(stream_id, CloseType::ResetRemote(app_error), conn); + Ok(()) + } + + /// This is called when `neqo_transport::Connection` state has been change to take proper + /// actions in the HTTP3 layer. + pub fn handle_state_change(&mut self, conn: &mut Connection, state: &State) -> Res<bool> { + qdebug!([self], "Handle state change {:?}", state); + match state { + State::Handshaking => { + if self.role == Role::Server + && conn.zero_rtt_state() == ZeroRttState::AcceptedServer + { + self.state = Http3State::ZeroRtt; + self.initialize_http3_connection(conn)?; + Ok(true) + } else { + Ok(false) + } + } + State::Connected => { + debug_assert!(matches!( + self.state, + Http3State::Initializing | Http3State::ZeroRtt + )); + if self.state == Http3State::Initializing { + self.initialize_http3_connection(conn)?; + } + self.state = Http3State::Connected; + Ok(true) + } + State::Closing { error, .. } | State::Draining { error, .. } => { + if matches!(self.state, Http3State::Closing(_) | Http3State::Closed(_)) { + Ok(false) + } else { + self.state = Http3State::Closing(error.clone()); + Ok(true) + } + } + State::Closed(error) => { + if matches!(self.state, Http3State::Closed(_)) { + Ok(false) + } else { + self.state = Http3State::Closed(error.clone()); + Ok(true) + } + } + _ => Ok(false), + } + } + + /// This is called when 0RTT has been reset to clear `send_streams`, `recv_streams` and + /// settings. + pub fn handle_zero_rtt_rejected(&mut self) -> Res<()> { + if self.state == Http3State::ZeroRtt { + self.state = Http3State::Initializing; + self.control_stream_local = ControlStreamLocal::new(); + self.qpack_encoder = Rc::new(RefCell::new(QPackEncoder::new( + self.local_params.get_qpack_settings(), + true, + ))); + self.qpack_decoder = Rc::new(RefCell::new(QPackDecoder::new( + self.local_params.get_qpack_settings(), + ))); + self.settings_state = Http3RemoteSettingsState::NotReceived; + self.streams_with_pending_data.clear(); + // TODO: investigate whether this code can automatically retry failed transactions. + self.send_streams.clear(); + self.recv_streams.clear(); + Ok(()) + } else { + debug_assert!(false, "Zero rtt rejected in the wrong state."); + Err(Error::HttpInternal(3)) + } + } + + pub fn handle_datagram(&mut self, datagram: &[u8]) { + let mut decoder = Decoder::new(datagram); + let session = decoder + .decode_varint() + .and_then(|id| self.recv_streams.get_mut(&StreamId::from(id * 4))) + .and_then(|stream| stream.webtransport()); + if let Some(s) = session { + s.borrow_mut().datagram(decoder.decode_remainder().to_vec()); + } + } + + fn check_stream_exists(&self, stream_type: Http3StreamType) -> Res<()> { + if self + .recv_streams + .values() + .any(|c| c.stream_type() == stream_type) + { + Err(Error::HttpStreamCreation) + } else { + Ok(()) + } + } + + /// If the new stream is a control or QPACK stream, this function creates a proper handler + /// and perform a read. + /// if the new stream is a `Push(_)`, `Http` or `WebTransportStream(_)` stream, the function + /// returns `ReceiveOutput::NewStream(_)` and the caller will handle it. + /// If the stream is of a unknown type the stream will be closed. + fn handle_new_stream( + &mut self, + conn: &mut Connection, + stream_type: NewStreamType, + stream_id: StreamId, + ) -> Res<ReceiveOutput> { + match stream_type { + NewStreamType::Control => { + self.check_stream_exists(Http3StreamType::Control)?; + self.recv_streams + .insert(stream_id, Box::new(ControlStreamRemote::new(stream_id))); + } + + NewStreamType::Push(push_id) => { + qinfo!( + [self], + "A new push stream {} push_id:{}.", + stream_id, + push_id + ); + } + NewStreamType::Decoder => { + qinfo!([self], "A new remote qpack encoder stream {}", stream_id); + self.check_stream_exists(Http3StreamType::Decoder)?; + self.recv_streams.insert( + stream_id, + Box::new(DecoderRecvStream::new( + stream_id, + Rc::clone(&self.qpack_decoder), + )), + ); + } + NewStreamType::Encoder => { + qinfo!([self], "A new remote qpack decoder stream {}", stream_id); + self.check_stream_exists(Http3StreamType::Encoder)?; + self.recv_streams.insert( + stream_id, + Box::new(EncoderRecvStream::new( + stream_id, + Rc::clone(&self.qpack_encoder), + )), + ); + } + NewStreamType::Http => { + qinfo!([self], "A new http stream {}.", stream_id); + } + NewStreamType::WebTransportStream(session_id) => { + let session_exists = self + .send_streams + .get(&StreamId::from(session_id)) + .map_or(false, |s| { + s.stream_type() == Http3StreamType::ExtendedConnect + }); + if !session_exists { + conn.stream_stop_sending(stream_id, Error::HttpStreamCreation.code())?; + return Ok(ReceiveOutput::NoOutput); + } + // set incoming WebTransport streams to be fair (share bandwidth) + conn.stream_fairness(stream_id, true).ok(); + qinfo!( + [self], + "A new WebTransport stream {} for session {}.", + stream_id, + session_id + ); + } + NewStreamType::Unknown => { + conn.stream_stop_sending(stream_id, Error::HttpStreamCreation.code())?; + } + }; + + match stream_type { + NewStreamType::Control | NewStreamType::Decoder | NewStreamType::Encoder => { + self.stream_receive(conn, stream_id) + } + NewStreamType::Push(_) | NewStreamType::Http | NewStreamType::WebTransportStream(_) => { + Ok(ReceiveOutput::NewStream(stream_type)) + } + NewStreamType::Unknown => Ok(ReceiveOutput::NoOutput), + } + } + + /// This is called when an application closes the connection. + pub fn close(&mut self, error: AppError) { + qinfo!([self], "Close connection error {:?}.", error); + self.state = Http3State::Closing(ConnectionError::Application(error)); + if (!self.send_streams.is_empty() || !self.recv_streams.is_empty()) && (error == 0) { + qwarn!("close(0) called when streams still active"); + } + self.send_streams.clear(); + self.recv_streams.clear(); + } + + /// This function will not handle the output of the function completely, but only + /// handle the indication that a stream is closed. There are 2 cases: + /// - an error occurred or + /// - the stream is done, i.e. the second value in `output` tuple is true if the stream is done + /// and can be removed from the `recv_streams` + /// How it is handling `output`: + /// - if the stream is done, it removes the stream from `recv_streams` + /// - if the stream is not done and there is no error, return `output` and the caller will + /// handle it. + /// - in case of an error: + /// - if it is only a stream error and the stream is not critical, send `STOP_SENDING` frame, + /// remove the stream from `recv_streams` and inform the listener that the stream has been + /// reset. + /// - otherwise this is a connection error. In this case, propagate the error to the caller + /// that will handle it properly. + fn handle_stream_manipulation_output<U>( + &mut self, + output: Res<(U, bool)>, + stream_id: StreamId, + conn: &mut Connection, + ) -> Res<(U, bool)> + where + U: Default, + { + match &output { + Ok((_, true)) => { + self.remove_recv_stream(stream_id, conn); + } + Ok((_, false)) => {} + Err(e) => { + if e.stream_reset_error() && !self.recv_stream_is_critical(stream_id) { + mem::drop(conn.stream_stop_sending(stream_id, e.code())); + self.close_recv(stream_id, CloseType::LocalError(e.code()), conn)?; + return Ok((U::default(), false)); + } + } + } + output + } + + fn create_fetch_headers<'b, 't, T>(request: &RequestDescription<'b, 't, T>) -> Res<Vec<Header>> + where + T: AsRequestTarget<'t> + ?Sized + Debug, + { + let target = request + .target + .as_request_target() + .map_err(|_| Error::InvalidRequestTarget)?; + + // Transform pseudo-header fields + let mut final_headers = vec![ + Header::new(":method", request.method), + Header::new(":scheme", target.scheme()), + Header::new(":authority", target.authority()), + Header::new(":path", target.path()), + ]; + if let Some(conn_type) = request.connect_type { + final_headers.push(Header::new(":protocol", conn_type.string())); + } + + if let Some(priority_header) = request.priority.header() { + final_headers.push(priority_header); + } + final_headers.extend_from_slice(request.headers); + Ok(final_headers) + } + + pub fn fetch<'b, 't, T>( + &mut self, + conn: &mut Connection, + send_events: Box<dyn SendStreamEvents>, + recv_events: Box<dyn HttpRecvStreamEvents>, + push_handler: Option<Rc<RefCell<PushController>>>, + request: &RequestDescription<'b, 't, T>, + ) -> Res<StreamId> + where + T: AsRequestTarget<'t> + ?Sized + Debug, + { + qinfo!( + [self], + "Fetch method={} target: {:?}", + request.method, + request.target, + ); + let id = self.create_bidi_transport_stream(conn)?; + self.fetch_with_stream(id, conn, send_events, recv_events, push_handler, request)?; + Ok(id) + } + + fn create_bidi_transport_stream(&self, conn: &mut Connection) -> Res<StreamId> { + // Requests cannot be created when a connection is in states: Initializing, GoingAway, + // Closing and Closed. + match self.state() { + Http3State::GoingAway(..) | Http3State::Closing(..) | Http3State::Closed(..) => { + return Err(Error::AlreadyClosed) + } + Http3State::Initializing => return Err(Error::Unavailable), + _ => {} + } + + let id = conn + .stream_create(StreamType::BiDi) + .map_err(|e| Error::map_stream_create_errors(&e))?; + conn.stream_keep_alive(id, true)?; + Ok(id) + } + + fn fetch_with_stream<'b, 't, T>( + &mut self, + stream_id: StreamId, + conn: &mut Connection, + send_events: Box<dyn SendStreamEvents>, + recv_events: Box<dyn HttpRecvStreamEvents>, + push_handler: Option<Rc<RefCell<PushController>>>, + request: &RequestDescription<'b, 't, T>, + ) -> Res<()> + where + T: AsRequestTarget<'t> + ?Sized + Debug, + { + let final_headers = Http3Connection::create_fetch_headers(request)?; + + let stream_type = if request.connect_type.is_some() { + Http3StreamType::ExtendedConnect + } else { + Http3StreamType::Http + }; + + let mut send_message = SendMessage::new( + MessageType::Request, + stream_type, + stream_id, + self.qpack_encoder.clone(), + send_events, + ); + + send_message + .http_stream() + .unwrap() + .send_headers(&final_headers, conn)?; + + self.add_streams( + stream_id, + Box::new(send_message), + Box::new(RecvMessage::new( + &RecvMessageInfo { + message_type: MessageType::Response, + stream_type, + stream_id, + header_frame_type_read: false, + }, + Rc::clone(&self.qpack_decoder), + recv_events, + push_handler, + PriorityHandler::new(false, request.priority), + )), + ); + + // Call immediately send so that at least headers get sent. This will make Firefox faster, + // since it can send request body immediately in most cases and does not need to do + // a complete process loop. + self.send_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .send(conn)?; + Ok(()) + } + + /// Stream data are read directly into a buffer supplied as a parameter of this function to + /// avoid copying data. + /// + /// # Errors + /// + /// It returns an error if a stream does not exist or an error happens while reading a stream, + /// e.g. early close, protocol error, etc. + pub fn read_data( + &mut self, + conn: &mut Connection, + stream_id: StreamId, + buf: &mut [u8], + ) -> Res<(usize, bool)> { + qinfo!([self], "read_data from stream {}.", stream_id); + let res = self + .recv_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .read_data(conn, buf); + self.handle_stream_manipulation_output(res, stream_id, conn) + } + + /// This is called when an application resets a stream. + /// The application reset will close both sides. + pub fn stream_reset_send( + &mut self, + conn: &mut Connection, + stream_id: StreamId, + error: AppError, + ) -> Res<()> { + qinfo!( + [self], + "Reset sending side of stream {} error={}.", + stream_id, + error + ); + + if self.send_stream_is_critical(stream_id) { + return Err(Error::InvalidStreamId); + } + + self.close_send(stream_id, CloseType::ResetApp(error), conn); + conn.stream_reset_send(stream_id, error)?; + Ok(()) + } + + pub fn stream_stop_sending( + &mut self, + conn: &mut Connection, + stream_id: StreamId, + error: AppError, + ) -> Res<()> { + qinfo!( + [self], + "Send stop sending for stream {} error={}.", + stream_id, + error + ); + if self.recv_stream_is_critical(stream_id) { + return Err(Error::InvalidStreamId); + } + + self.close_recv(stream_id, CloseType::ResetApp(error), conn)?; + + // Stream may be already be closed and we may get an error here, but we do not care. + conn.stream_stop_sending(stream_id, error)?; + Ok(()) + } + + /// Set the stream `SendOrder`. + /// + /// # Errors + /// + /// Returns `InvalidStreamId` if the stream id doesn't exist + pub fn stream_set_sendorder( + conn: &mut Connection, + stream_id: StreamId, + sendorder: Option<SendOrder>, + ) -> Res<()> { + conn.stream_sendorder(stream_id, sendorder) + .map_err(|_| Error::InvalidStreamId) + } + + /// Set the stream Fairness. Fair streams will share bandwidth with other + /// streams of the same sendOrder group (or the unordered group). Unfair streams + /// will give bandwidth preferentially to the lowest streamId with data to send. + /// + /// # Errors + /// + /// Returns `InvalidStreamId` if the stream id doesn't exist + pub fn stream_set_fairness( + conn: &mut Connection, + stream_id: StreamId, + fairness: bool, + ) -> Res<()> { + conn.stream_fairness(stream_id, fairness) + .map_err(|_| Error::InvalidStreamId) + } + + pub fn cancel_fetch( + &mut self, + stream_id: StreamId, + error: AppError, + conn: &mut Connection, + ) -> Res<()> { + qinfo!([self], "cancel_fetch {} error={}.", stream_id, error); + let send_stream = self.send_streams.get(&stream_id); + let recv_stream = self.recv_streams.get(&stream_id); + match (send_stream, recv_stream) { + (None, None) => return Err(Error::InvalidStreamId), + (Some(s), None) => { + if !matches!( + s.stream_type(), + Http3StreamType::Http | Http3StreamType::ExtendedConnect + ) { + return Err(Error::InvalidStreamId); + } + // Stream may be already be closed and we may get an error here, but we do not care. + mem::drop(self.stream_reset_send(conn, stream_id, error)); + } + (None, Some(s)) => { + if !matches!( + s.stream_type(), + Http3StreamType::Http + | Http3StreamType::Push + | Http3StreamType::ExtendedConnect + ) { + return Err(Error::InvalidStreamId); + } + + // Stream may be already be closed and we may get an error here, but we do not care. + mem::drop(self.stream_stop_sending(conn, stream_id, error)); + } + (Some(s), Some(r)) => { + debug_assert_eq!(s.stream_type(), r.stream_type()); + if !matches!( + s.stream_type(), + Http3StreamType::Http | Http3StreamType::ExtendedConnect + ) { + return Err(Error::InvalidStreamId); + } + // Stream may be already be closed and we may get an error here, but we do not care. + mem::drop(self.stream_reset_send(conn, stream_id, error)); + // Stream may be already be closed and we may get an error here, but we do not care. + mem::drop(self.stream_stop_sending(conn, stream_id, error)); + } + } + Ok(()) + } + + /// This is called when an application wants to close the sending side of a stream. + pub fn stream_close_send(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<()> { + qinfo!([self], "Close the sending side for stream {}.", stream_id); + debug_assert!(self.state.active()); + let send_stream = self + .send_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)?; + // The following function may return InvalidStreamId from the transport layer if the stream + // has been closed already. It is ok to ignore it here. + mem::drop(send_stream.close(conn)); + if send_stream.done() { + self.remove_send_stream(stream_id, conn); + } else if send_stream.has_data_to_send() { + self.streams_with_pending_data.insert(stream_id); + } + Ok(()) + } + + pub fn webtransport_create_session<'x, 't: 'x, T>( + &mut self, + conn: &mut Connection, + events: Box<dyn ExtendedConnectEvents>, + target: &'t T, + headers: &'t [Header], + ) -> Res<StreamId> + where + T: AsRequestTarget<'x> + ?Sized + Debug, + { + qinfo!([self], "Create WebTransport"); + if !self.webtransport_enabled() { + return Err(Error::Unavailable); + } + + let id = self.create_bidi_transport_stream(conn)?; + + let extended_conn = Rc::new(RefCell::new(WebTransportSession::new( + id, + events, + self.role, + Rc::clone(&self.qpack_encoder), + Rc::clone(&self.qpack_decoder), + ))); + self.add_streams( + id, + Box::new(extended_conn.clone()), + Box::new(extended_conn.clone()), + ); + + let final_headers = Http3Connection::create_fetch_headers(&RequestDescription { + method: "CONNECT", + target, + headers, + connect_type: Some(ExtendedConnectType::WebTransport), + priority: Priority::default(), + })?; + extended_conn + .borrow_mut() + .send_request(&final_headers, conn)?; + self.streams_with_pending_data.insert(id); + Ok(id) + } + + pub(crate) fn webtransport_session_accept( + &mut self, + conn: &mut Connection, + stream_id: StreamId, + events: Box<dyn ExtendedConnectEvents>, + accept_res: &WebTransportSessionAcceptAction, + ) -> Res<()> { + qtrace!( + "Respond to WebTransport session with accept={}.", + accept_res + ); + if !self.webtransport_enabled() { + return Err(Error::Unavailable); + } + let mut recv_stream = self.recv_streams.get_mut(&stream_id); + if let Some(r) = &mut recv_stream { + if !r + .http_stream() + .ok_or(Error::InvalidStreamId)? + .extended_connect_wait_for_response() + { + return Err(Error::InvalidStreamId); + } + } + + let send_stream = self.send_streams.get_mut(&stream_id); + + match (send_stream, recv_stream, accept_res) { + (None, None, _) => Err(Error::InvalidStreamId), + (None, Some(_), _) | (Some(_), None, _) => { + // TODO this needs a better error + self.cancel_fetch(stream_id, Error::HttpRequestRejected.code(), conn)?; + Err(Error::InvalidStreamId) + } + (Some(s), Some(_r), WebTransportSessionAcceptAction::Reject(headers)) => { + if s.http_stream() + .ok_or(Error::InvalidStreamId)? + .send_headers(headers, conn) + .is_ok() + { + mem::drop(self.stream_close_send(conn, stream_id)); + // TODO issue 1294: add a timer to clean up the recv_stream if the peer does not + // do that in a short time. + self.streams_with_pending_data.insert(stream_id); + } else { + self.cancel_fetch(stream_id, Error::HttpRequestRejected.code(), conn)?; + } + Ok(()) + } + (Some(s), Some(_r), WebTransportSessionAcceptAction::Accept) => { + if s.http_stream() + .ok_or(Error::InvalidStreamId)? + .send_headers(&[Header::new(":status", "200")], conn) + .is_ok() + { + let extended_conn = + Rc::new(RefCell::new(WebTransportSession::new_with_http_streams( + stream_id, + events, + self.role, + self.recv_streams.remove(&stream_id).unwrap(), + self.send_streams.remove(&stream_id).unwrap(), + ))); + self.add_streams( + stream_id, + Box::new(extended_conn.clone()), + Box::new(extended_conn), + ); + self.streams_with_pending_data.insert(stream_id); + } else { + self.cancel_fetch(stream_id, Error::HttpRequestRejected.code(), conn)?; + return Err(Error::InvalidStreamId); + } + Ok(()) + } + } + } + + pub(crate) fn webtransport_close_session( + &mut self, + conn: &mut Connection, + session_id: StreamId, + error: u32, + message: &str, + ) -> Res<()> { + qtrace!("Clos WebTransport session {:?}", session_id); + let send_stream = self + .send_streams + .get_mut(&session_id) + .ok_or(Error::InvalidStreamId)?; + if send_stream.stream_type() != Http3StreamType::ExtendedConnect { + return Err(Error::InvalidStreamId); + } + + send_stream.close_with_message(conn, error, message)?; + if send_stream.done() { + self.remove_send_stream(session_id, conn); + } else if send_stream.has_data_to_send() { + self.streams_with_pending_data.insert(session_id); + } + Ok(()) + } + + pub fn webtransport_create_stream_local( + &mut self, + conn: &mut Connection, + session_id: StreamId, + stream_type: StreamType, + send_events: Box<dyn SendStreamEvents>, + recv_events: Box<dyn RecvStreamEvents>, + ) -> Res<StreamId> { + qtrace!( + "Create new WebTransport stream session={} type={:?}", + session_id, + stream_type + ); + + let wt = self + .recv_streams + .get(&session_id) + .ok_or(Error::InvalidStreamId)? + .webtransport() + .ok_or(Error::InvalidStreamId)?; + if !wt.borrow().is_active() { + return Err(Error::InvalidStreamId); + } + + let stream_id = conn + .stream_create(stream_type) + .map_err(|e| Error::map_stream_create_errors(&e))?; + // Set outgoing WebTransport streams to be fair (share bandwidth) + // This really can't fail, panics if it does + conn.stream_fairness(stream_id, true).unwrap(); + + self.webtransport_create_stream_internal( + wt, + stream_id, + session_id, + send_events, + recv_events, + true, + ); + Ok(stream_id) + } + + pub fn webtransport_create_stream_remote( + &mut self, + session_id: StreamId, + stream_id: StreamId, + send_events: Box<dyn SendStreamEvents>, + recv_events: Box<dyn RecvStreamEvents>, + ) -> Res<()> { + qtrace!( + "Create new WebTransport stream session={} stream_id={}", + session_id, + stream_id + ); + + let wt = self + .recv_streams + .get(&session_id) + .ok_or(Error::InvalidStreamId)? + .webtransport() + .ok_or(Error::InvalidStreamId)?; + + self.webtransport_create_stream_internal( + wt, + stream_id, + session_id, + send_events, + recv_events, + false, + ); + Ok(()) + } + + fn webtransport_create_stream_internal( + &mut self, + webtransport_session: Rc<RefCell<WebTransportSession>>, + stream_id: StreamId, + session_id: StreamId, + send_events: Box<dyn SendStreamEvents>, + recv_events: Box<dyn RecvStreamEvents>, + local: bool, + ) { + // TODO conn.stream_keep_alive(stream_id, true)?; + webtransport_session.borrow_mut().add_stream(stream_id); + if stream_id.stream_type() == StreamType::UniDi { + if local { + self.send_streams.insert( + stream_id, + Box::new(WebTransportSendStream::new( + stream_id, + session_id, + send_events, + webtransport_session, + true, + )), + ); + } else { + self.recv_streams.insert( + stream_id, + Box::new(WebTransportRecvStream::new( + stream_id, + session_id, + recv_events, + webtransport_session, + )), + ); + } + } else { + self.add_streams( + stream_id, + Box::new(WebTransportSendStream::new( + stream_id, + session_id, + send_events, + webtransport_session.clone(), + local, + )), + Box::new(WebTransportRecvStream::new( + stream_id, + session_id, + recv_events, + webtransport_session, + )), + ); + } + } + + pub fn webtransport_send_datagram( + &mut self, + session_id: StreamId, + conn: &mut Connection, + buf: &[u8], + id: impl Into<DatagramTracking>, + ) -> Res<()> { + self.recv_streams + .get_mut(&session_id) + .ok_or(Error::InvalidStreamId)? + .webtransport() + .ok_or(Error::InvalidStreamId)? + .borrow_mut() + .send_datagram(conn, buf, id) + } + + /// If the control stream has received frames `MaxPushId`, `Goaway`, `PriorityUpdateRequest` or + /// `PriorityUpdateRequestPush` which handling is specific to the client and server, we must + /// give them to the specific client/server handler. + fn handle_control_frame(&mut self, f: HFrame) -> Res<Option<HFrame>> { + qinfo!([self], "Handle a control frame {:?}", f); + if !matches!(f, HFrame::Settings { .. }) + && !matches!( + self.settings_state, + Http3RemoteSettingsState::Received { .. } + ) + { + return Err(Error::HttpMissingSettings); + } + match f { + HFrame::Settings { settings } => { + self.handle_settings(settings)?; + Ok(None) + } + HFrame::Goaway { .. } + | HFrame::MaxPushId { .. } + | HFrame::CancelPush { .. } + | HFrame::PriorityUpdateRequest { .. } + | HFrame::PriorityUpdatePush { .. } => Ok(Some(f)), + _ => Err(Error::HttpFrameUnexpected), + } + } + + fn set_qpack_settings(&mut self, settings: &HSettings) -> Res<()> { + let mut qpe = self.qpack_encoder.borrow_mut(); + qpe.set_max_capacity(settings.get(HSettingType::MaxTableCapacity))?; + qpe.set_max_blocked_streams(settings.get(HSettingType::BlockedStreams))?; + Ok(()) + } + + fn handle_settings(&mut self, new_settings: HSettings) -> Res<()> { + qinfo!([self], "Handle SETTINGS frame."); + match &self.settings_state { + Http3RemoteSettingsState::NotReceived => { + self.set_qpack_settings(&new_settings)?; + self.webtransport.handle_settings(&new_settings); + self.settings_state = Http3RemoteSettingsState::Received(new_settings); + Ok(()) + } + Http3RemoteSettingsState::ZeroRtt(settings) => { + self.webtransport.handle_settings(&new_settings); + let mut qpack_changed = false; + for st in &[ + HSettingType::MaxHeaderListSize, + HSettingType::MaxTableCapacity, + HSettingType::BlockedStreams, + ] { + let zero_rtt_value = settings.get(*st); + let new_value = new_settings.get(*st); + if zero_rtt_value == new_value { + continue; + } + if zero_rtt_value > new_value { + qerror!( + [self], + "The new({}) and the old value({}) of setting {:?} do not match", + new_value, + zero_rtt_value, + st + ); + return Err(Error::HttpSettings); + } + + match st { + HSettingType::MaxTableCapacity => { + if zero_rtt_value != 0 { + return Err(Error::QpackError(neqo_qpack::Error::DecoderStream)); + } + qpack_changed = true; + } + HSettingType::BlockedStreams => qpack_changed = true, + HSettingType::MaxHeaderListSize + | HSettingType::EnableWebTransport + | HSettingType::EnableH3Datagram => (), + } + } + if qpack_changed { + qdebug!([self], "Settings after zero rtt differ."); + self.set_qpack_settings(&(new_settings))?; + } + self.settings_state = Http3RemoteSettingsState::Received(new_settings); + Ok(()) + } + Http3RemoteSettingsState::Received { .. } => Err(Error::HttpFrameUnexpected), + } + } + + /// Return the current state on `Http3Connection`. + pub fn state(&self) -> Http3State { + self.state.clone() + } + + /// Adds a new send and receive stream. + pub fn add_streams( + &mut self, + stream_id: StreamId, + send_stream: Box<dyn SendStream>, + recv_stream: Box<dyn RecvStream>, + ) { + if send_stream.has_data_to_send() { + self.streams_with_pending_data.insert(stream_id); + } + self.send_streams.insert(stream_id, send_stream); + self.recv_streams.insert(stream_id, recv_stream); + } + + /// Add a new recv stream. This is used for push streams. + pub fn add_recv_stream(&mut self, stream_id: StreamId, recv_stream: Box<dyn RecvStream>) { + self.recv_streams.insert(stream_id, recv_stream); + } + + pub fn queue_control_frame(&mut self, frame: &HFrame) { + self.control_stream_local.queue_frame(frame); + } + + pub fn queue_update_priority(&mut self, stream_id: StreamId, priority: Priority) -> Res<bool> { + let stream = self + .recv_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .http_stream() + .ok_or(Error::InvalidStreamId)?; + + if stream.maybe_update_priority(priority) { + self.control_stream_local.queue_update_priority(stream_id); + Ok(true) + } else { + Ok(false) + } + } + + fn recv_stream_is_critical(&self, stream_id: StreamId) -> bool { + if let Some(r) = self.recv_streams.get(&stream_id) { + matches!( + r.stream_type(), + Http3StreamType::Control | Http3StreamType::Encoder | Http3StreamType::Decoder + ) + } else { + false + } + } + + fn send_stream_is_critical(&self, stream_id: StreamId) -> bool { + self.qpack_encoder + .borrow() + .local_stream_id() + .iter() + .chain(self.qpack_decoder.borrow().local_stream_id().iter()) + .chain(self.control_stream_local.stream_id().iter()) + .any(|id| stream_id == *id) + } + + fn close_send(&mut self, stream_id: StreamId, close_type: CloseType, conn: &mut Connection) { + if let Some(mut s) = self.remove_send_stream(stream_id, conn) { + s.handle_stop_sending(close_type); + } + } + + fn close_recv( + &mut self, + stream_id: StreamId, + close_type: CloseType, + conn: &mut Connection, + ) -> Res<()> { + if let Some(mut s) = self.remove_recv_stream(stream_id, conn) { + s.reset(close_type)?; + } + Ok(()) + } + + fn remove_extended_connect( + &mut self, + wt: &Rc<RefCell<WebTransportSession>>, + conn: &mut Connection, + ) { + let (recv, send) = wt.borrow_mut().take_sub_streams(); + + for id in recv { + qtrace!("Remove the extended connect sub receiver stream {}", id); + // Use CloseType::ResetRemote so that an event will be sent. CloseType::LocalError would + // have the same effect. + if let Some(mut s) = self.recv_streams.remove(&id) { + mem::drop(s.reset(CloseType::ResetRemote(Error::HttpRequestCancelled.code()))); + } + mem::drop(conn.stream_stop_sending(id, Error::HttpRequestCancelled.code())); + } + for id in send { + qtrace!("Remove the extended connect sub send stream {}", id); + if let Some(mut s) = self.send_streams.remove(&id) { + s.handle_stop_sending(CloseType::ResetRemote(Error::HttpRequestCancelled.code())); + } + mem::drop(conn.stream_reset_send(id, Error::HttpRequestCancelled.code())); + } + } + + fn remove_recv_stream( + &mut self, + stream_id: StreamId, + conn: &mut Connection, + ) -> Option<Box<dyn RecvStream>> { + let stream = self.recv_streams.remove(&stream_id); + if let Some(ref s) = stream { + if s.stream_type() == Http3StreamType::ExtendedConnect { + self.send_streams.remove(&stream_id).unwrap(); + if let Some(wt) = s.webtransport() { + self.remove_extended_connect(&wt, conn); + } + } + } + stream + } + + fn remove_send_stream( + &mut self, + stream_id: StreamId, + conn: &mut Connection, + ) -> Option<Box<dyn SendStream>> { + let stream = self.send_streams.remove(&stream_id); + if let Some(ref s) = stream { + if s.stream_type() == Http3StreamType::ExtendedConnect { + if let Some(wt) = self.recv_streams.remove(&stream_id).unwrap().webtransport() { + self.remove_extended_connect(&wt, conn); + } + } + } + stream + } + + pub fn webtransport_enabled(&self) -> bool { + self.webtransport.enabled() + } +} diff --git a/third_party/rust/neqo-http3/src/connection_client.rs b/third_party/rust/neqo-http3/src/connection_client.rs new file mode 100644 index 0000000000..5cc0541c0c --- /dev/null +++ b/third_party/rust/neqo-http3/src/connection_client.rs @@ -0,0 +1,7296 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{ + cell::RefCell, + convert::TryFrom, + fmt::{Debug, Display}, + mem, + net::SocketAddr, + rc::Rc, + time::Instant, +}; + +use neqo_common::{ + event::Provider as EventProvider, hex, hex_with_len, qdebug, qinfo, qlog::NeqoQlog, qtrace, + Datagram, Decoder, Encoder, Header, MessageType, Role, +}; +use neqo_crypto::{agent::CertificateInfo, AuthenticationStatus, ResumptionToken, SecretAgentInfo}; +use neqo_qpack::Stats as QpackStats; +use neqo_transport::{ + streams::SendOrder, AppError, Connection, ConnectionEvent, ConnectionId, ConnectionIdGenerator, + DatagramTracking, Output, RecvStreamStats, SendStreamStats, Stats as TransportStats, StreamId, + StreamType, Version, ZeroRttState, +}; + +use crate::{ + client_events::{Http3ClientEvent, Http3ClientEvents}, + connection::{Http3Connection, Http3State, RequestDescription}, + frames::HFrame, + push_controller::{PushController, RecvPushEvents}, + recv_message::{RecvMessage, RecvMessageInfo}, + request_target::AsRequestTarget, + settings::HSettings, + Error, Http3Parameters, Http3StreamType, NewStreamType, Priority, PriorityHandler, + ReceiveOutput, Res, +}; + +// This is used for filtering send_streams and recv_Streams with a stream_ids greater than or equal +// a given id. Only the same type (bidirectional or unidirectionsl) streams are filtered. +fn id_gte<U>(base: StreamId) -> impl FnMut((&StreamId, &U)) -> Option<StreamId> + 'static +where + U: ?Sized, +{ + move |(id, _)| { + if *id >= base && !(id.is_bidi() ^ base.is_bidi()) { + Some(*id) + } else { + None + } + } +} + +fn alpn_from_quic_version(version: Version) -> &'static str { + match version { + Version::Version2 | Version::Version1 => "h3", + Version::Draft29 => "h3-29", + Version::Draft30 => "h3-30", + Version::Draft31 => "h3-31", + Version::Draft32 => "h3-32", + } +} + +/// # The HTTP/3 client API +/// +/// This module implements the HTTP/3 client API. The main implementation of the protocol is in +/// [connection.rs](https://github.com/mozilla/neqo/blob/main/neqo-http3/src/connection.rs) which +/// implements common behavior for the client-side and the server-side. `Http3Client` structure +/// implements the public API and set of functions that differ between the client and the server. + +/// The API is used for: +/// - create and close an endpoint: +/// - [`Http3Client::new`] +/// - [`Http3Client::new_with_conn`] +/// - [`Http3Client::close`] +/// - configuring an endpoint: +/// - [`Http3Client::authenticated`] +/// - [`Http3Client::enable_ech`] +/// - [`Http3Client::enable_resumption`] +/// - [`Http3Client::initiate_key_update`] +/// - [`Http3Client::set_qlog`] +/// - retrieving information about a connection: +/// - [`Http3Client::peer_certificate`] +/// - [`Http3Client::qpack_decoder_stats`] +/// - [`Http3Client::qpack_encoder_stats`] +/// - [`Http3Client::transport_stats`] +/// - [`Http3Client::state`] +/// - [`Http3Client::take_resumption_token`] +/// - [`Http3Client::tls_info`] +/// - driving HTTP/3 session: +/// - [`Http3Client::process_output`] +/// - [`Http3Client::process_input`] +/// - [`Http3Client::process`] +/// - create requests, send/receive data, and cancel requests: +/// - [`Http3Client::fetch`] +/// - [`Http3Client::send_data`] +/// - [`Http3Client::read_data`] +/// - [`Http3Client::stream_close_send`] +/// - [`Http3Client::cancel_fetch`] +/// - [`Http3Client::stream_reset_send`] +/// - [`Http3Client::stream_stop_sending`] +/// - [`Http3Client::set_stream_max_data`] +/// - priority feature: +/// - [`Http3Client::priority_update`] +/// - `WebTransport` feature: +/// - [`Http3Client::webtransport_create_session`] +/// - [`Http3Client::webtransport_close_session`] +/// - [`Http3Client::webtransport_create_stream`] +/// - [`Http3Client::webtransport_enabled`] +/// +/// ## Examples +/// +/// ### Fetching a resource +/// +/// ```ignore +/// let mut client = Http3Client::new(...); +/// +/// // Perform a handshake +/// ... +/// +/// let req = client +/// .fetch( +/// Instant::now(), +/// "GET", +/// &("https", "something.com", "/"), +/// &[Header::new("example1", "value1"), Header::new("example1", "value2")], +/// Priority::default(), +/// ) +/// .unwrap(); +/// +/// client.stream_close_send(req).unwrap(); +/// +/// loop { +/// // exchange packets +/// ... +/// +/// while let Some(event) = client.next_event() { +/// match event { +/// Http3ClientEvent::HeaderReady { stream_id, headers, interim, fin } => { +/// println!("New response headers received for stream {:?} [fin={?}, interim={:?}]: {:?}", +/// stream_id, +/// fin, +/// interim, +/// headers, +/// ); +/// } +/// Http3ClientEvent::DataReadable { stream_id } => { +/// println!("New data available on stream {}", stream_id); +/// let mut buf = [0; 100]; +/// let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); +/// println!("Read {:?} bytes from stream {:?} [fin={?}]", +/// amount, +/// stream_id, +/// fin, +/// ); +/// } +/// _ => { +/// println!("Unhandled event {:?}", event); +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### Creating a `WebTransport` session +/// +/// ```ignore +/// let mut client = Http3Client::new(...); +/// +/// // Perform a handshake +/// ... +/// +/// // Create a session +/// let wt_session_id = client +/// .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) +/// .unwrap(); +/// +/// loop { +/// // exchange packets +/// ... +/// +/// while let Some(event) = client.next_event() { +/// match event { +/// Http3ClientEvent::WebTransport(WebTransportEvent::Session{ +/// stream_id, +/// status, +/// .. +/// }) => { +/// println!("The response from the server: WebTransport session ID {:?} status={:?}", +/// stream_id, +/// status, +/// ); +/// } +/// _ => { +/// println!("Unhandled event {:?}", event); +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### `WebTransport`: create a stream, send and receive data on the stream +/// +/// ```ignore +/// const BUF_CLIENT: &[u8] = &[0; 10]; +/// // wt_session_id is the session ID of a newly created WebTransport session, see the example above. +/// +/// // create a stream +/// let wt_stream_id = client +/// .webtransport_create_stream(wt_session_id, StreamType::BiDi) +/// .unwrap(); +/// +/// // send data +/// let data_sent = client.send_data(wt_stream_id, BUF_CLIENT).unwrap(); +/// assert_eq!(data_sent, BUF_CLIENT.len()); +/// +/// // close stream for sending +/// client.stream_close_send(wt_stream_id).unwrap(); +/// +/// // wait for data from the server +/// loop { +/// // exchange packets +/// ... +/// +/// while let Some(event) = client.next_event() { +/// match event { +/// Http3ClientEvent::DataReadable{ stream_id } => { +/// println!("Data receivedd form the server on WebTransport stream ID {:?}", +/// stream_id, +/// ); +/// let mut buf = [0; 100]; +/// let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); +/// println!("Read {:?} bytes from stream {:?} [fin={?}]", +/// amount, +/// stream_id, +/// fin, +/// ); +/// } +/// _ => { +/// println!("Unhandled event {:?}", event); +/// } +/// } +/// } +/// } +/// ``` +/// +/// ### `WebTransport`: receive a new stream form the server +/// +/// ```ignore +/// // wt_session_id is the session ID of a newly created WebTransport session, see the example above. +/// +/// // wait for a new stream from the server +/// loop { +/// // exchange packets +/// ... +/// +/// while let Some(event) = client.next_event() { +/// match event { +/// Http3ClientEvent::WebTransport(WebTransportEvent::NewStream { +/// stream_id, +/// session_id, +/// }) => { +/// println!("New stream received on session{:?}, stream id={:?} stream type={:?}", +/// sesson_id.stream_id(), +/// stream_id.stream_id(), +/// stream_id.stream_type() +/// ); +/// } +/// Http3ClientEvent::DataReadable{ stream_id } => { +/// println!("Data receivedd form the server on WebTransport stream ID {:?}", +/// stream_id, +/// ); +/// let mut buf = [0; 100]; +/// let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); +/// println!("Read {:?} bytes from stream {:?} [fin={:?}]", +/// amount, +/// stream_id, +/// fin, +/// ); +/// } +/// _ => { +/// println!("Unhandled event {:?}", event); +/// } +/// } +/// } +/// } +/// ``` +pub struct Http3Client { + conn: Connection, + base_handler: Http3Connection, + events: Http3ClientEvents, + push_handler: Rc<RefCell<PushController>>, +} + +impl Display for Http3Client { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Http3 client") + } +} + +impl Http3Client { + /// # Errors + /// + /// Making a `neqo-transport::connection` may produce an error. This can only be a crypto error + /// if the crypto context can't be created or configured. + pub fn new( + server_name: impl Into<String>, + cid_manager: Rc<RefCell<dyn ConnectionIdGenerator>>, + local_addr: SocketAddr, + remote_addr: SocketAddr, + http3_parameters: Http3Parameters, + now: Instant, + ) -> Res<Self> { + Ok(Self::new_with_conn( + Connection::new_client( + server_name, + &[alpn_from_quic_version( + http3_parameters + .get_connection_parameters() + .get_versions() + .initial(), + )], + cid_manager, + local_addr, + remote_addr, + http3_parameters.get_connection_parameters().clone(), + now, + )?, + http3_parameters, + )) + } + + /// This is a similar function to `new`. In this case, `neqo-transport::connection` has been + /// already created. + /// + /// It is recommended to use `new` instead. + #[must_use] + pub fn new_with_conn(c: Connection, http3_parameters: Http3Parameters) -> Self { + let events = Http3ClientEvents::default(); + let webtransport = http3_parameters.get_webtransport(); + let push_streams = http3_parameters.get_max_concurrent_push_streams(); + let mut base_handler = Http3Connection::new(http3_parameters, Role::Client); + if webtransport { + base_handler.set_features_listener(events.clone()); + } + Self { + conn: c, + events: events.clone(), + push_handler: Rc::new(RefCell::new(PushController::new(push_streams, events))), + base_handler, + } + } + + #[must_use] + pub fn role(&self) -> Role { + self.conn.role() + } + + /// The function returns the current state of the connection. + #[must_use] + pub fn state(&self) -> Http3State { + self.base_handler.state() + } + + #[must_use] + pub fn tls_info(&self) -> Option<&SecretAgentInfo> { + self.conn.tls_info() + } + + /// Get the peer's certificate. + #[must_use] + pub fn peer_certificate(&self) -> Option<CertificateInfo> { + self.conn.peer_certificate() + } + + /// This called when peer certificates have been verified. + /// + /// `Http3ClientEvent::AuthenticationNeeded` event is emitted when peer’s certificates are + /// available and need to be verified. When the verification is completed this function is + /// called. To inform HTTP/3 session of the verification results. + pub fn authenticated(&mut self, status: AuthenticationStatus, now: Instant) { + self.conn.authenticated(status, now); + } + + pub fn set_qlog(&mut self, qlog: NeqoQlog) { + self.conn.set_qlog(qlog); + } + + /// Enable encrypted client hello (ECH). + /// + /// # Errors + /// + /// Fails when the configuration provided is bad. + pub fn enable_ech(&mut self, ech_config_list: impl AsRef<[u8]>) -> Res<()> { + self.conn.client_enable_ech(ech_config_list)?; + Ok(()) + } + + /// Get the connection id, which is useful for disambiguating connections to + /// the same origin. + /// + /// # Panics + /// + /// Never, because clients always have this field. + #[must_use] + pub fn connection_id(&self) -> &ConnectionId { + self.conn.odcid().expect("Client always has odcid") + } + + fn encode_resumption_token(&self, token: &ResumptionToken) -> Option<ResumptionToken> { + self.base_handler.get_settings().map(|settings| { + let mut enc = Encoder::default(); + settings.encode_frame_contents(&mut enc); + enc.encode(token.as_ref()); + ResumptionToken::new(enc.into(), token.expiration_time()) + }) + } + + /// The correct way to obtain a resumption token is to wait for the + /// `Http3ClientEvent::ResumptionToken` event. To emit the event we are waiting for a + /// resumtion token and a `NEW_TOKEN` frame to arrive. Some servers don't send `NEW_TOKEN` + /// frames and in this case, we wait for 3xPTO before emitting an event. This is especially a + /// problem for short-lived connections, where the connection is closed before any events are + /// released. This function retrieves the token, without waiting for a `NEW_TOKEN` frame to + /// arrive. + /// + /// In addition to the token, HTTP/3 settings are encoded into the token before giving it to + /// the application(`encode_resumption_token`). When the resumption token is supplied to a new + /// connection the HTTP/3 setting will be decoded and used until the setting are received from + /// the server. + pub fn take_resumption_token(&mut self, now: Instant) -> Option<ResumptionToken> { + self.conn + .take_resumption_token(now) + .and_then(|t| self.encode_resumption_token(&t)) + } + + /// This may be call if an application has a resumption token. This must be called before + /// connection starts. + /// + /// The resumption token also contains encoded HTTP/3 settings. The settings will be decoded + /// and used until the setting are received from the server. + /// + /// # Errors + /// + /// An error is return if token cannot be decoded or a connection is is a wrong state. + /// + /// # Panics + /// + /// On closing if the base handler can't handle it (debug only). + pub fn enable_resumption(&mut self, now: Instant, token: impl AsRef<[u8]>) -> Res<()> { + if self.base_handler.state != Http3State::Initializing { + return Err(Error::InvalidState); + } + let mut dec = Decoder::from(token.as_ref()); + let Some(settings_slice) = dec.decode_vvec() else { + return Err(Error::InvalidResumptionToken); + }; + qtrace!([self], " settings {}", hex_with_len(settings_slice)); + let mut dec_settings = Decoder::from(settings_slice); + let mut settings = HSettings::default(); + Error::map_error( + settings.decode_frame_contents(&mut dec_settings), + Error::InvalidResumptionToken, + )?; + let tok = dec.decode_remainder(); + qtrace!([self], " Transport token {}", hex(tok)); + self.conn.enable_resumption(now, tok)?; + if self.conn.state().closed() { + let state = self.conn.state().clone(); + let res = self + .base_handler + .handle_state_change(&mut self.conn, &state); + debug_assert_eq!(Ok(true), res); + return Err(Error::FatalError); + } + if self.conn.zero_rtt_state() == ZeroRttState::Sending { + self.base_handler + .set_0rtt_settings(&mut self.conn, settings)?; + self.events + .connection_state_change(self.base_handler.state()); + self.push_handler + .borrow_mut() + .maybe_send_max_push_id_frame(&mut self.base_handler); + } + Ok(()) + } + + /// This is call to close a connection. + pub fn close<S>(&mut self, now: Instant, error: AppError, msg: S) + where + S: AsRef<str> + Display, + { + qinfo!([self], "Close the connection error={} msg={}.", error, msg); + if !matches!( + self.base_handler.state, + Http3State::Closing(_) | Http3State::Closed(_) + ) { + self.push_handler.borrow_mut().clear(); + self.conn.close(now, error, msg); + self.base_handler.close(error); + self.events + .connection_state_change(self.base_handler.state()); + } + } + + /// Attempt to force a key update. + /// + /// # Errors + /// + /// If the connection isn't confirmed, or there is an outstanding key update, this + /// returns `Err(Error::TransportError(neqo_transport::Error::KeyUpdateBlocked))`. + pub fn initiate_key_update(&mut self) -> Res<()> { + self.conn.initiate_key_update()?; + Ok(()) + } + + // API: Request/response + + /// The function fetches a resource using `method`, `target` and `headers`. A response body + /// may be added by calling `send_data`. `stream_close_send` must be sent to finish the request + /// even if request data are not sent. + /// + /// # Errors + /// + /// If a new stream cannot be created an error will be return. + /// + /// # Panics + /// + /// `SendMessage` implements `http_stream` so it will not panic. + pub fn fetch<'x, 't: 'x, T>( + &mut self, + now: Instant, + method: &'t str, + target: &'t T, + headers: &'t [Header], + priority: Priority, + ) -> Res<StreamId> + where + T: AsRequestTarget<'x> + ?Sized + Debug, + { + let output = self.base_handler.fetch( + &mut self.conn, + Box::new(self.events.clone()), + Box::new(self.events.clone()), + Some(Rc::clone(&self.push_handler)), + &RequestDescription { + method, + connect_type: None, + target, + headers, + priority, + }, + ); + if let Err(e) = &output { + if e.connection_error() { + self.close(now, e.code(), ""); + } + } + output + } + + /// Send an [`PRIORITY_UPDATE`-frame][1] on next `Http3Client::process_output()` call. + /// Returns if the priority got changed. + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist + /// + /// [1]: https://datatracker.ietf.org/doc/html/draft-kazuho-httpbis-priority-04#section-5.2 + pub fn priority_update(&mut self, stream_id: StreamId, priority: Priority) -> Res<bool> { + self.base_handler.queue_update_priority(stream_id, priority) + } + + /// An application may cancel a stream(request). + /// Both sides, the receiviing and sending side, sending and receiving side, will be closed. + /// + /// # Errors + /// + /// An error will be return if a stream does not exist. + pub fn cancel_fetch(&mut self, stream_id: StreamId, error: AppError) -> Res<()> { + qinfo!([self], "reset_stream {} error={}.", stream_id, error); + self.base_handler + .cancel_fetch(stream_id, error, &mut self.conn) + } + + /// This is call when application is done sending a request. + /// + /// # Errors + /// + /// An error will be return if stream does not exist. + pub fn stream_close_send(&mut self, stream_id: StreamId) -> Res<()> { + qinfo!([self], "Close sending side stream={}.", stream_id); + self.base_handler + .stream_close_send(&mut self.conn, stream_id) + } + + /// # Errors + /// + /// An error will be return if a stream does not exist. + pub fn stream_reset_send(&mut self, stream_id: StreamId, error: AppError) -> Res<()> { + qinfo!([self], "stream_reset_send {} error={}.", stream_id, error); + self.base_handler + .stream_reset_send(&mut self.conn, stream_id, error) + } + + /// # Errors + /// + /// An error will be return if a stream does not exist. + pub fn stream_stop_sending(&mut self, stream_id: StreamId, error: AppError) -> Res<()> { + qinfo!([self], "stream_stop_sending {} error={}.", stream_id, error); + self.base_handler + .stream_stop_sending(&mut self.conn, stream_id, error) + } + + /// This function is used for regular HTTP requests and `WebTransport` streams. + /// In the case of regular HTTP requests, the request body is supplied using this function, and + /// headers are supplied through the `fetch` function. + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist, + /// `AlreadyClosed` if the stream has already been closed. + /// `TransportStreamDoesNotExist` if the transport stream does not exist (this may happen if + /// `process_output` has not been called when needed, and HTTP3 layer has not picked up the + /// info that the stream has been closed.) `InvalidInput` if an empty buffer has been + /// supplied. + pub fn send_data(&mut self, stream_id: StreamId, buf: &[u8]) -> Res<usize> { + qinfo!( + [self], + "send_data from stream {} sending {} bytes.", + stream_id, + buf.len() + ); + self.base_handler + .send_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .send_data(&mut self.conn, buf) + } + + /// Response data are read directly into a buffer supplied as a parameter of this function to + /// avoid copying data. + /// + /// # Errors + /// + /// It returns an error if a stream does not exist or an error happen while reading a stream, + /// e.g. early close, protocol error, etc. + pub fn read_data( + &mut self, + now: Instant, + stream_id: StreamId, + buf: &mut [u8], + ) -> Res<(usize, bool)> { + qinfo!([self], "read_data from stream {}.", stream_id); + let res = self.base_handler.read_data(&mut self.conn, stream_id, buf); + if let Err(e) = &res { + if e.connection_error() { + self.close(now, e.code(), ""); + } + } + res + } + + // API: Push streams + + /// Cancel a push + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist. + pub fn cancel_push(&mut self, push_id: u64) -> Res<()> { + self.push_handler + .borrow_mut() + .cancel(push_id, &mut self.conn, &mut self.base_handler) + } + + /// Push response data are read directly into a buffer supplied as a parameter of this function + /// to avoid copying data. + /// + /// # Errors + /// + /// It returns an error if a stream does not exist(`InvalidStreamId`) or an error has happened + /// while reading a stream, e.g. early close, protocol error, etc. + pub fn push_read_data( + &mut self, + now: Instant, + push_id: u64, + buf: &mut [u8], + ) -> Res<(usize, bool)> { + let stream_id = self + .push_handler + .borrow_mut() + .get_active_stream_id(push_id) + .ok_or(Error::InvalidStreamId)?; + self.conn.stream_keep_alive(stream_id, true)?; + self.read_data(now, stream_id, buf) + } + + // API WebTransport + // + /// # Errors + /// + /// If `WebTransport` cannot be created, e.g. the `WebTransport` support is + /// not negotiated or the HTTP/3 connection is closed. + pub fn webtransport_create_session<'x, 't: 'x, T>( + &mut self, + now: Instant, + target: &'t T, + headers: &'t [Header], + ) -> Res<StreamId> + where + T: AsRequestTarget<'x> + ?Sized + Debug, + { + let output = self.base_handler.webtransport_create_session( + &mut self.conn, + Box::new(self.events.clone()), + target, + headers, + ); + + if let Err(e) = &output { + if e.connection_error() { + self.close(now, e.code(), ""); + } + } + output + } + + /// Close `WebTransport` cleanly + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist, + /// `TransportStreamDoesNotExist` if the transport stream does not exist (this may happen if + /// `process_output` has not been called when needed, and HTTP3 layer has not picked up the + /// info that the stream has been closed.) `InvalidInput` if an empty buffer has been + /// supplied. + pub fn webtransport_close_session( + &mut self, + session_id: StreamId, + error: u32, + message: &str, + ) -> Res<()> { + self.base_handler + .webtransport_close_session(&mut self.conn, session_id, error, message) + } + + /// # Errors + /// + /// This may return an error if the particular session does not exist + /// or the connection is not in the active state. + pub fn webtransport_create_stream( + &mut self, + session_id: StreamId, + stream_type: StreamType, + ) -> Res<StreamId> { + self.base_handler.webtransport_create_stream_local( + &mut self.conn, + session_id, + stream_type, + Box::new(self.events.clone()), + Box::new(self.events.clone()), + ) + } + + /// Send `WebTransport` datagram. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + /// The function returns `TooMuchData` if the supply buffer is bigger than + /// the allowed remote datagram size. + pub fn webtransport_send_datagram( + &mut self, + session_id: StreamId, + buf: &[u8], + id: impl Into<DatagramTracking>, + ) -> Res<()> { + qtrace!("webtransport_send_datagram session:{:?}", session_id); + self.base_handler + .webtransport_send_datagram(session_id, &mut self.conn, buf, id) + } + + /// Returns the current max size of a datagram that can fit into a packet. + /// The value will change over time depending on the encoded size of the + /// packet number, ack frames, etc. + /// + /// # Errors + /// + /// The function returns `NotAvailable` if datagrams are not enabled. + /// + /// # Panics + /// + /// This cannot panic. The max varint length is 8. + pub fn webtransport_max_datagram_size(&self, session_id: StreamId) -> Res<u64> { + Ok(self.conn.max_datagram_size()? + - u64::try_from(Encoder::varint_len(session_id.as_u64())).unwrap()) + } + + /// Sets the `SendOrder` for a given stream + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + /// + /// # Panics + /// + /// This cannot panic. + pub fn webtransport_set_sendorder( + &mut self, + stream_id: StreamId, + sendorder: Option<SendOrder>, + ) -> Res<()> { + Http3Connection::stream_set_sendorder(&mut self.conn, stream_id, sendorder) + } + + /// Sets the `Fairness` for a given stream + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + /// + /// # Panics + /// + /// This cannot panic. + pub fn webtransport_set_fairness(&mut self, stream_id: StreamId, fairness: bool) -> Res<()> { + Http3Connection::stream_set_fairness(&mut self.conn, stream_id, fairness) + } + + /// Returns the current `SendStreamStats` of a `WebTransportSendStream`. + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist. + pub fn webtransport_send_stream_stats(&mut self, stream_id: StreamId) -> Res<SendStreamStats> { + self.base_handler + .send_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .stats(&mut self.conn) + } + + /// Returns the current `RecvStreamStats` of a `WebTransportRecvStream`. + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist. + pub fn webtransport_recv_stream_stats(&mut self, stream_id: StreamId) -> Res<RecvStreamStats> { + self.base_handler + .recv_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .stats(&mut self.conn) + } + + /// This function combines `process_input` and `process_output` function. + pub fn process(&mut self, dgram: Option<&Datagram>, now: Instant) -> Output { + qtrace!([self], "Process."); + if let Some(d) = dgram { + self.process_input(d, now); + } + self.process_output(now) + } + + /// The function should be called when there is a new UDP packet available. The function will + /// handle the packet payload. + /// + /// First, the payload will be handled by the QUIC layer. Afterward, `process_http3` will be + /// called to handle new [`ConnectionEvent`][1]s. + /// + /// After this function is called `process_output` should be called to check whether new + /// packets need to be sent or if a timer needs to be updated. + /// + /// [1]: ../neqo_transport/enum.ConnectionEvent.html + pub fn process_input(&mut self, dgram: &Datagram, now: Instant) { + qtrace!([self], "Process input."); + self.conn.process_input(dgram, now); + self.process_http3(now); + } + + pub fn process_multiple_input<'a, I>(&mut self, dgrams: I, now: Instant) + where + I: IntoIterator<Item = &'a Datagram>, + I::IntoIter: ExactSizeIterator, + { + let dgrams = dgrams.into_iter(); + qtrace!([self], "Process multiple datagrams, len={}", dgrams.len()); + if dgrams.len() == 0 { + return; + } + self.conn.process_multiple_input(dgrams, now); + self.process_http3(now); + } + + /// This should not be used because it gives access to functionalities that may disrupt the + /// proper functioning of the HTTP/3 session. + /// Only used by `neqo-interop`. + pub fn conn(&mut self) -> &mut Connection { + &mut self.conn + } + + /// Process HTTP3 layer. + /// When `process_output`, `process_input`, or `process` is called we must call this function + /// as well. The functions calls `Http3Client::check_connection_events` to handle events from + /// the QUC layer and calls `Http3Connection::process_sending` to ensure that HTTP/3 layer + /// data, e.g. control frames, are sent. + fn process_http3(&mut self, now: Instant) { + qtrace!([self], "Process http3 internal."); + match self.base_handler.state() { + Http3State::ZeroRtt | Http3State::Connected | Http3State::GoingAway(..) => { + let res = self.check_connection_events(); + if self.check_result(now, &res) { + return; + } + self.push_handler + .borrow_mut() + .maybe_send_max_push_id_frame(&mut self.base_handler); + let res = self.base_handler.process_sending(&mut self.conn); + self.check_result(now, &res); + } + Http3State::Closed { .. } => {} + _ => { + let res = self.check_connection_events(); + _ = self.check_result(now, &res); + } + } + } + + /// The function should be called to check if there is a new UDP packet to be sent. It should + /// be called after a new packet is received and processed and after a timer expires (QUIC + /// needs timers to handle events like PTO detection and timers are not implemented by the neqo + /// library, but instead must be driven by the application). + /// + /// `process_output` can return: + /// - a [`Output::Datagram(Datagram)`][1]: data that should be sent as a UDP payload, + /// - a [`Output::Callback(Duration)`][1]: the duration of a timer. `process_output` should be + /// called at least after the time expires, + /// - [`Output::None`][1]: this is returned when `Nttp3Client` is done and can be destroyed. + /// + /// The application should call this function repeatedly until a timer value or None is + /// returned. After that, the application should call the function again if a new UDP packet is + /// received and processed or the timer value expires. + /// + /// The HTTP/3 neqo implementation drives the HTTP/3 and QUC layers, therefore this function + /// will call both layers: + /// - First it calls HTTP/3 layer processing (`process_http3`) to make sure the layer writes + /// data to QUIC layer or cancels streams if needed. + /// - Then QUIC layer processing is called - [`Connection::process_output`][3]. This produces a + /// packet or a timer value. It may also produce ned [`ConnectionEvent`][2]s, e.g. connection + /// state-change event. + /// - Therefore the HTTP/3 layer processing (`process_http3`) is called again. + /// + /// [1]: ../neqo_transport/enum.Output.html + /// [2]: ../neqo_transport/struct.ConnectionEvents.html + /// [3]: ../neqo_transport/struct.Connection.html#method.process_output + pub fn process_output(&mut self, now: Instant) -> Output { + qtrace!([self], "Process output."); + + // Maybe send() stuff on http3-managed streams + self.process_http3(now); + + let out = self.conn.process_output(now); + + // Update H3 for any transport state changes and events + self.process_http3(now); + + out + } + + /// This function takes the provided result and check for an error. + /// An error results in closing the connection. + fn check_result<ERR>(&mut self, now: Instant, res: &Res<ERR>) -> bool { + match &res { + Err(Error::HttpGoaway) => { + qinfo!([self], "Connection error: goaway stream_id increased."); + self.close( + now, + Error::HttpGeneralProtocol.code(), + "Connection error: goaway stream_id increased", + ); + true + } + Err(e) => { + qinfo!([self], "Connection error: {}.", e); + self.close(now, e.code(), &format!("{e}")); + true + } + _ => false, + } + } + + /// This function checks [`ConnectionEvent`][2]s emitted by the QUIC layer, e.g. connection + /// change state events, new incoming stream data is available, a stream is was reset, etc. + /// The HTTP/3 layer needs to handle these events. Most of the events are handled by + /// [`Http3Connection`][1] by calling appropriate functions, e.g. `handle_state_change`, + /// `handle_stream_reset`, etc. [`Http3Connection`][1] handle functionalities that are common + /// for the client and server side. Some of the functionalities are specific to the client and + /// they are handled by `Http3Client`. For example, [`ConnectionEvent::RecvStreamReadable`][3] + /// event is handled by `Http3Client::handle_stream_readable`. The function calls + /// `Http3Connection::handle_stream_readable` and then hands the return value as appropriate + /// for the client-side. + /// + /// [1]: https://github.com/mozilla/neqo/blob/main/neqo-http3/src/connection.rs + /// [2]: ../neqo_transport/enum.ConnectionEvent.html + /// [3]: ../neqo_transport/enum.ConnectionEvent.html#variant.RecvStreamReadable + fn check_connection_events(&mut self) -> Res<()> { + qtrace!([self], "Check connection events."); + while let Some(e) = self.conn.next_event() { + qdebug!([self], "check_connection_events - event {:?}.", e); + match e { + ConnectionEvent::NewStream { stream_id } => { + // During this event we only add a new stream to the Http3Connection stream + // list, with NewStreamHeadReader stream handler. + // This function will not read from the stream and try to decode the stream. + // RecvStreamReadable will be emitted after this event and reading, i.e. + // decoding of a stream will happen during that event. + self.base_handler.add_new_stream(stream_id); + } + ConnectionEvent::SendStreamWritable { stream_id } => { + if let Some(s) = self.base_handler.send_streams.get_mut(&stream_id) { + s.stream_writable(); + } + } + ConnectionEvent::RecvStreamReadable { stream_id } => { + self.handle_stream_readable(stream_id)?; + } + ConnectionEvent::RecvStreamReset { + stream_id, + app_error, + } => self + .base_handler + .handle_stream_reset(stream_id, app_error, &mut self.conn)?, + ConnectionEvent::SendStreamStopSending { + stream_id, + app_error, + } => self.base_handler.handle_stream_stop_sending( + stream_id, + app_error, + &mut self.conn, + )?, + + ConnectionEvent::SendStreamCreatable { stream_type } => { + self.events.new_requests_creatable(stream_type); + } + ConnectionEvent::AuthenticationNeeded => self.events.authentication_needed(), + ConnectionEvent::EchFallbackAuthenticationNeeded { public_name } => { + self.events.ech_fallback_authentication_needed(public_name); + } + ConnectionEvent::StateChange(state) => { + if self + .base_handler + .handle_state_change(&mut self.conn, &state)? + { + self.events + .connection_state_change(self.base_handler.state()); + } + } + ConnectionEvent::ZeroRttRejected => { + self.base_handler.handle_zero_rtt_rejected()?; + self.events.zero_rtt_rejected(); + self.push_handler.borrow_mut().handle_zero_rtt_rejected(); + } + ConnectionEvent::ResumptionToken(token) => { + if let Some(t) = self.encode_resumption_token(&token) { + self.events.resumption_token(t); + } + } + ConnectionEvent::Datagram(dgram) => { + self.base_handler.handle_datagram(&dgram); + } + ConnectionEvent::SendStreamComplete { .. } + | ConnectionEvent::OutgoingDatagramOutcome { .. } + | ConnectionEvent::IncomingDatagramDropped => {} + } + } + Ok(()) + } + + /// This function handled new data available on a stream. It calls + /// `Http3Client::handle_stream_readable` and handles its response. Reading streams are mostly + /// handled by [`Http3Connection`][1] because most part of it is common for the client and + /// server. The following actions need to be handled by the client-specific code: + /// - `ReceiveOutput::NewStream(NewStreamType::Push(_))` - the server cannot receive a push + /// stream, + /// - `ReceiveOutput::NewStream(NewStreamType::Http)` - client cannot receive a + /// server-initiated HTTP request, + /// - `ReceiveOutput::NewStream(NewStreamType::WebTransportStream(_))` - because + /// `Http3ClientEvents`is needed and events handler is specific to the client. + /// - `ReceiveOutput::ControlFrames(control_frames)` - some control frame handling differs + /// between the client and the server: + /// - `HFrame::CancelPush` - only the client-side may receive it, + /// - `HFrame::MaxPushId { .. }`, `HFrame::PriorityUpdateRequest { .. } ` and + /// `HFrame::PriorityUpdatePush` can only be receive on the server side, + /// - `HFrame::Goaway { stream_id }` needs specific handling by the client by the protocol + /// specification. + /// + /// [1]: https://github.com/mozilla/neqo/blob/main/neqo-http3/src/connection.rs + fn handle_stream_readable(&mut self, stream_id: StreamId) -> Res<()> { + match self + .base_handler + .handle_stream_readable(&mut self.conn, stream_id)? + { + ReceiveOutput::NewStream(NewStreamType::Push(push_id)) => { + self.handle_new_push_stream(stream_id, push_id) + } + ReceiveOutput::NewStream(NewStreamType::Http) => Err(Error::HttpStreamCreation), + ReceiveOutput::NewStream(NewStreamType::WebTransportStream(session_id)) => { + self.base_handler.webtransport_create_stream_remote( + StreamId::from(session_id), + stream_id, + Box::new(self.events.clone()), + Box::new(self.events.clone()), + )?; + let res = self + .base_handler + .handle_stream_readable(&mut self.conn, stream_id)?; + debug_assert!(matches!(res, ReceiveOutput::NoOutput)); + Ok(()) + } + ReceiveOutput::ControlFrames(control_frames) => { + for f in control_frames { + match f { + HFrame::CancelPush { push_id } => self + .push_handler + .borrow_mut() + .handle_cancel_push(push_id, &mut self.conn, &mut self.base_handler), + HFrame::MaxPushId { .. } + | HFrame::PriorityUpdateRequest { .. } + | HFrame::PriorityUpdatePush { .. } => Err(Error::HttpFrameUnexpected), + HFrame::Goaway { stream_id } => self.handle_goaway(stream_id), + _ => { + unreachable!( + "we should only put MaxPushId, Goaway and PriorityUpdates into control_frames." + ); + } + }?; + } + Ok(()) + } + _ => Ok(()), + } + } + + fn handle_new_push_stream(&mut self, stream_id: StreamId, push_id: u64) -> Res<()> { + if !self.push_handler.borrow().can_receive_push() { + return Err(Error::HttpId); + } + + // Add a new push stream to `PushController`. `add_new_push_stream` may return an error + // (this will be a connection error) or a bool. + // If false is returned that means that the stream should be reset because the push has + // been already canceled (CANCEL_PUSH frame or canceling push from the application). + if !self + .push_handler + .borrow_mut() + .add_new_push_stream(push_id, stream_id)? + { + // We are not interested in the result of stream_stop_sending, we are not interested + // in this stream. + mem::drop( + self.conn + .stream_stop_sending(stream_id, Error::HttpRequestCancelled.code()), + ); + return Ok(()); + } + + self.base_handler.add_recv_stream( + stream_id, + Box::new(RecvMessage::new( + &RecvMessageInfo { + message_type: MessageType::Response, + stream_type: Http3StreamType::Push, + stream_id, + header_frame_type_read: false, + }, + Rc::clone(&self.base_handler.qpack_decoder), + Box::new(RecvPushEvents::new(push_id, Rc::clone(&self.push_handler))), + None, + // TODO: think about the right prority for the push streams. + PriorityHandler::new(true, Priority::default()), + )), + ); + let res = self + .base_handler + .handle_stream_readable(&mut self.conn, stream_id)?; + debug_assert!(matches!(res, ReceiveOutput::NoOutput)); + Ok(()) + } + + fn handle_goaway(&mut self, goaway_stream_id: StreamId) -> Res<()> { + qinfo!([self], "handle_goaway {}", goaway_stream_id); + + if goaway_stream_id.is_uni() || goaway_stream_id.is_server_initiated() { + return Err(Error::HttpId); + } + + match self.base_handler.state { + Http3State::Connected => { + self.base_handler.state = Http3State::GoingAway(goaway_stream_id); + } + Http3State::GoingAway(ref mut stream_id) => { + if goaway_stream_id > *stream_id { + return Err(Error::HttpGoaway); + } + *stream_id = goaway_stream_id; + } + Http3State::Closing(..) | Http3State::Closed(..) => {} + _ => unreachable!("Should not receive Goaway frame in this state."), + } + + // Issue reset events for streams >= goaway stream id + let send_ids: Vec<StreamId> = self + .base_handler + .send_streams + .iter() + .filter_map(id_gte(goaway_stream_id)) + .collect(); + for id in send_ids { + // We do not care about streams that are going to be closed. + mem::drop(self.base_handler.handle_stream_stop_sending( + id, + Error::HttpRequestRejected.code(), + &mut self.conn, + )); + } + + let recv_ids: Vec<StreamId> = self + .base_handler + .recv_streams + .iter() + .filter_map(id_gte(goaway_stream_id)) + .collect(); + for id in recv_ids { + // We do not care about streams that are going to be closed. + mem::drop(self.base_handler.handle_stream_reset( + id, + Error::HttpRequestRejected.code(), + &mut self.conn, + )); + } + + self.events.goaway_received(); + + Ok(()) + } + + /// Increases `max_stream_data` for a `stream_id`. + /// + /// # Errors + /// + /// Returns `InvalidStreamId` if a stream does not exist or the receiving + /// side is closed. + pub fn set_stream_max_data(&mut self, stream_id: StreamId, max_data: u64) -> Res<()> { + self.conn.set_stream_max_data(stream_id, max_data)?; + Ok(()) + } + + #[must_use] + pub fn qpack_decoder_stats(&self) -> QpackStats { + self.base_handler.qpack_decoder.borrow().stats() + } + + #[must_use] + pub fn qpack_encoder_stats(&self) -> QpackStats { + self.base_handler.qpack_encoder.borrow().stats() + } + + #[must_use] + pub fn transport_stats(&self) -> TransportStats { + self.conn.stats() + } + + #[must_use] + pub fn webtransport_enabled(&self) -> bool { + self.base_handler.webtransport_enabled() + } +} + +impl EventProvider for Http3Client { + type Event = Http3ClientEvent; + + /// Return true if there are outstanding events. + fn has_events(&self) -> bool { + self.events.has_events() + } + + /// Get events that indicate state changes on the connection. This method + /// correctly handles cases where handling one event can obsolete + /// previously-queued events, or cause new events to be generated. + fn next_event(&mut self) -> Option<Self::Event> { + self.events.next_event() + } +} + +#[cfg(test)] +mod tests { + use std::{convert::TryFrom, mem, time::Duration}; + + use neqo_common::{event::Provider, qtrace, Datagram, Decoder, Encoder}; + use neqo_crypto::{AllowZeroRtt, AntiReplay, ResumptionToken}; + use neqo_qpack::{encoder::QPackEncoder, QpackSettings}; + use neqo_transport::{ + ConnectionError, ConnectionEvent, ConnectionParameters, Output, State, StreamId, + StreamType, Version, RECV_BUFFER_SIZE, SEND_BUFFER_SIZE, + }; + use test_fixture::{ + addr, anti_replay, default_server_h3, fixture_init, new_server, now, + CountingConnectionIdGenerator, DEFAULT_ALPN_H3, DEFAULT_KEYS, DEFAULT_SERVER_NAME, + }; + + use super::{ + AuthenticationStatus, Connection, Error, HSettings, Header, Http3Client, Http3ClientEvent, + Http3Parameters, Http3State, Rc, RefCell, + }; + use crate::{ + frames::{HFrame, H3_FRAME_TYPE_SETTINGS, H3_RESERVED_FRAME_TYPES}, + qpack_encoder_receiver::EncoderRecvStream, + settings::{HSetting, HSettingType, H3_RESERVED_SETTINGS}, + Http3Server, Priority, RecvStream, + }; + + fn assert_closed(client: &Http3Client, expected: &Error) { + match client.state() { + Http3State::Closing(err) | Http3State::Closed(err) => { + assert_eq!(err, ConnectionError::Application(expected.code())); + } + _ => panic!("Wrong state {:?}", client.state()), + }; + } + + /// Create a http3 client with default configuration. + pub fn default_http3_client() -> Http3Client { + default_http3_client_param(100) + } + + pub fn default_http3_client_param(max_table_size: u64) -> Http3Client { + fixture_init(); + Http3Client::new( + DEFAULT_SERVER_NAME, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + Http3Parameters::default() + .connection_parameters( + // Disable compatible upgrade, which complicates tests. + ConnectionParameters::default() + .versions(Version::default(), vec![Version::default()]), + ) + .max_table_size_encoder(max_table_size) + .max_table_size_decoder(max_table_size) + .max_blocked_streams(100) + .max_concurrent_push_streams(5), + now(), + ) + .expect("create a default client") + } + + const CONTROL_STREAM_TYPE: &[u8] = &[0x0]; + + // Encoder stream data + const ENCODER_STREAM_DATA: &[u8] = &[0x2]; + const ENCODER_STREAM_CAP_INSTRUCTION: &[u8] = &[0x3f, 0x45]; + + // Encoder stream data with a change capacity instruction(0x3f, 0x45 = change capacity to 100) + // This data will be send when 0-RTT is used and we already have a max_table_capacity from + // resumed settings. + const ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION: &[u8] = &[0x2, 0x3f, 0x45]; + + const ENCODER_STREAM_DATA_WITH_CAP_INST_AND_ENCODING_INST: &[u8] = &[ + 0x2, 0x3f, 0x45, 0x67, 0xa7, 0xd4, 0xe5, 0x1c, 0x85, 0xb1, 0x1f, 0x86, 0xa7, 0xd7, 0x71, + 0xd1, 0x69, 0x7f, + ]; + + // Decoder stream data + const DECODER_STREAM_DATA: &[u8] = &[0x3]; + + const PUSH_STREAM_TYPE: &[u8] = &[0x1]; + + const CLIENT_SIDE_CONTROL_STREAM_ID: StreamId = StreamId::new(2); + const CLIENT_SIDE_ENCODER_STREAM_ID: StreamId = StreamId::new(6); + const CLIENT_SIDE_DECODER_STREAM_ID: StreamId = StreamId::new(10); + + struct TestServer { + settings: HFrame, + conn: Connection, + control_stream_id: Option<StreamId>, + encoder: Rc<RefCell<QPackEncoder>>, + encoder_receiver: EncoderRecvStream, + encoder_stream_id: Option<StreamId>, + decoder_stream_id: Option<StreamId>, + } + + impl TestServer { + pub fn new() -> Self { + Self::new_with_settings(&[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ]) + } + + pub fn new_with_settings(server_settings: &[HSetting]) -> Self { + fixture_init(); + let max_table_size = server_settings + .iter() + .find(|s| s.setting_type == HSettingType::MaxTableCapacity) + .map_or(100, |s| s.value); + let max_blocked_streams = u16::try_from( + server_settings + .iter() + .find(|s| s.setting_type == HSettingType::BlockedStreams) + .map_or(100, |s| s.value), + ) + .unwrap(); + let qpack = Rc::new(RefCell::new(QPackEncoder::new( + &QpackSettings { + max_table_size_encoder: max_table_size, + max_table_size_decoder: max_table_size, + max_blocked_streams, + }, + true, + ))); + Self { + settings: HFrame::Settings { + settings: HSettings::new(server_settings), + }, + conn: default_server_h3(), + control_stream_id: None, + encoder: Rc::clone(&qpack), + encoder_receiver: EncoderRecvStream::new(CLIENT_SIDE_DECODER_STREAM_ID, qpack), + encoder_stream_id: None, + decoder_stream_id: None, + } + } + + pub fn new_with_conn(conn: Connection) -> Self { + let qpack = Rc::new(RefCell::new(QPackEncoder::new( + &QpackSettings { + max_table_size_encoder: 128, + max_table_size_decoder: 128, + max_blocked_streams: 0, + }, + true, + ))); + Self { + settings: HFrame::Settings { + settings: HSettings::new(&[]), + }, + conn, + control_stream_id: None, + encoder: Rc::clone(&qpack), + encoder_receiver: EncoderRecvStream::new(CLIENT_SIDE_DECODER_STREAM_ID, qpack), + encoder_stream_id: None, + decoder_stream_id: None, + } + } + + pub fn create_qpack_streams(&mut self) { + // Create a QPACK encoder stream + self.encoder_stream_id = Some(self.conn.stream_create(StreamType::UniDi).unwrap()); + self.encoder + .borrow_mut() + .add_send_stream(self.encoder_stream_id.unwrap()); + self.encoder + .borrow_mut() + .send_encoder_updates(&mut self.conn) + .unwrap(); + + // Create decoder stream + self.decoder_stream_id = Some(self.conn.stream_create(StreamType::UniDi).unwrap()); + assert_eq!( + self.conn + .stream_send(self.decoder_stream_id.unwrap(), DECODER_STREAM_DATA) + .unwrap(), + 1 + ); + } + + pub fn create_control_stream(&mut self) { + // Create control stream + let control = self.conn.stream_create(StreamType::UniDi).unwrap(); + qtrace!(["TestServer"], "control stream: {}", control); + self.control_stream_id = Some(control); + // Send stream type on the control stream. + assert_eq!( + self.conn + .stream_send(self.control_stream_id.unwrap(), CONTROL_STREAM_TYPE) + .unwrap(), + 1 + ); + + // Encode a settings frame and send it. + let mut enc = Encoder::default(); + self.settings.encode(&mut enc); + assert_eq!( + self.conn + .stream_send(self.control_stream_id.unwrap(), enc.as_ref()) + .unwrap(), + enc.len() + ); + } + + pub fn check_client_control_qpack_streams_no_resumption(&mut self) { + self.check_client_control_qpack_streams( + ENCODER_STREAM_DATA, + EXPECTED_REQUEST_HEADER_FRAME, + false, + true, + ); + } + + pub fn check_control_qpack_request_streams_resumption( + &mut self, + expect_encoder_stream_data: &[u8], + expect_request_header: &[u8], + expect_request: bool, + ) { + self.check_client_control_qpack_streams( + expect_encoder_stream_data, + expect_request_header, + expect_request, + false, + ); + } + + // Check that server has received correct settings and qpack streams. + pub fn check_client_control_qpack_streams( + &mut self, + expect_encoder_stream_data: &[u8], + expect_request_header: &[u8], + expect_request: bool, + expect_connected: bool, + ) { + let mut connected = false; + let mut control_stream = false; + let mut qpack_decoder_stream = false; + let mut qpack_encoder_stream = false; + let mut request = false; + while let Some(e) = self.conn.next_event() { + match e { + ConnectionEvent::NewStream { stream_id } + | ConnectionEvent::SendStreamWritable { stream_id } => { + if expect_request { + assert!(matches!(stream_id.as_u64(), 2 | 6 | 10 | 0)); + } else { + assert!(matches!(stream_id.as_u64(), 2 | 6 | 10)); + } + } + ConnectionEvent::RecvStreamReadable { stream_id } => { + if stream_id == CLIENT_SIDE_CONTROL_STREAM_ID { + self.check_control_stream(); + control_stream = true; + } else if stream_id == CLIENT_SIDE_ENCODER_STREAM_ID { + // the qpack encoder stream + self.read_and_check_stream_data( + stream_id, + expect_encoder_stream_data, + false, + ); + qpack_encoder_stream = true; + } else if stream_id == CLIENT_SIDE_DECODER_STREAM_ID { + // the qpack decoder stream + self.read_and_check_stream_data(stream_id, DECODER_STREAM_DATA, false); + qpack_decoder_stream = true; + } else if stream_id == 0 { + assert!(expect_request); + self.read_and_check_stream_data(stream_id, expect_request_header, true); + request = true; + } else { + panic!("unexpected event"); + } + } + ConnectionEvent::StateChange(State::Connected) => connected = true, + ConnectionEvent::StateChange(_) + | ConnectionEvent::SendStreamCreatable { .. } => {} + _ => panic!("unexpected event"), + } + } + assert_eq!(connected, expect_connected); + assert!(control_stream); + assert!(qpack_encoder_stream); + assert!(qpack_decoder_stream); + assert_eq!(request, expect_request); + } + + // Check that the control stream contains default values. + // Expect a SETTINGS frame, some grease, and a MAX_PUSH_ID frame. + // The default test configuration uses: + // - max_table_capacity = 100 + // - max_blocked_streams = 100 + // and a maximum of 5 push streams. + fn check_control_stream(&mut self) { + let mut buf = [0_u8; 100]; + let (amount, fin) = self + .conn + .stream_recv(CLIENT_SIDE_CONTROL_STREAM_ID, &mut buf) + .unwrap(); + let mut dec = Decoder::from(&buf[..amount]); + assert_eq!(dec.decode_varint().unwrap(), 0); // control stream type + assert_eq!(dec.decode_varint().unwrap(), 4); // SETTINGS + assert_eq!( + dec.decode_vvec().unwrap(), + &[1, 0x40, 0x64, 7, 0x40, 0x64, 0xab, 0x60, 0x37, 0x42, 0x00] + ); + + assert_eq!((dec.decode_varint().unwrap() - 0x21) % 0x1f, 0); // Grease + assert!(dec.decode_vvec().unwrap().len() < 8); + + assert_eq!(dec.decode_varint().unwrap(), 0xd); // MAX_PUSH_ID + assert_eq!(dec.decode_vvec().unwrap(), &[5]); + + assert_eq!(dec.remaining(), 0); + assert!(!fin); + } + + pub fn read_and_check_stream_data( + &mut self, + stream_id: StreamId, + expected_data: &[u8], + expected_fin: bool, + ) { + let mut buf = [0_u8; 100]; + let (amount, fin) = self.conn.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(fin, expected_fin); + assert_eq!(amount, expected_data.len()); + assert_eq!(&buf[..amount], expected_data); + } + + pub fn encode_headers( + &mut self, + stream_id: StreamId, + headers: &[Header], + encoder: &mut Encoder, + ) { + let header_block = + self.encoder + .borrow_mut() + .encode_header_block(&mut self.conn, headers, stream_id); + let hframe = HFrame::Headers { + header_block: header_block.as_ref().to_vec(), + }; + hframe.encode(encoder); + } + } + + fn handshake_only(client: &mut Http3Client, server: &mut TestServer) -> Output { + assert_eq!(client.state(), Http3State::Initializing); + let out = client.process(None, now()); + assert_eq!(client.state(), Http3State::Initializing); + + assert_eq!(*server.conn.state(), State::Init); + let out = server.conn.process(out.as_dgram_ref(), now()); + assert_eq!(*server.conn.state(), State::Handshaking); + + let out = client.process(out.as_dgram_ref(), now()); + let out = server.conn.process(out.as_dgram_ref(), now()); + assert!(out.as_dgram_ref().is_none()); + + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(client.events().any(authentication_needed)); + client.authenticated(AuthenticationStatus::Ok, now()); + out + } + + // Perform only Quic transport handshake. + fn connect_only_transport_with(client: &mut Http3Client, server: &mut TestServer) { + let out = handshake_only(client, server); + + let out = client.process(out.as_dgram_ref(), now()); + let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected)); + assert!(client.events().any(connected)); + + assert_eq!(client.state(), Http3State::Connected); + server + .conn + .process_input(out.as_dgram_ref().unwrap(), now()); + assert!(server.conn.state().connected()); + } + + // Perform only Quic transport handshake. + fn connect_only_transport() -> (Http3Client, TestServer) { + let mut client = default_http3_client(); + let mut server = TestServer::new(); + connect_only_transport_with(&mut client, &mut server); + (client, server) + } + + fn send_and_receive_client_settings(client: &mut Http3Client, server: &mut TestServer) { + // send and receive client settings + let out = client.process(None, now()); + server + .conn + .process_input(out.as_dgram_ref().unwrap(), now()); + server.check_client_control_qpack_streams_no_resumption(); + } + + // Perform Quic transport handshake and exchange Http3 settings. + fn connect_with(client: &mut Http3Client, server: &mut TestServer) { + connect_only_transport_with(client, server); + + send_and_receive_client_settings(client, server); + + server.create_control_stream(); + + server.create_qpack_streams(); + // Send the server's control and qpack streams data. + let out = server.conn.process(None, now()); + client.process_input(out.as_dgram_ref().unwrap(), now()); + + // assert no error occured. + assert_eq!(client.state(), Http3State::Connected); + } + + // Perform Quic transport handshake and exchange Http3 settings. + fn connect_with_connection_parameters( + server_conn_params: ConnectionParameters, + ) -> (Http3Client, TestServer) { + // connecting with default max_table_size + let mut client = default_http3_client_param(100); + let server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN_H3, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + server_conn_params, + ) + .unwrap(); + let mut server = TestServer::new_with_conn(server); + connect_with(&mut client, &mut server); + (client, server) + } + + // Perform Quic transport handshake and exchange Http3 settings. + fn connect() -> (Http3Client, TestServer) { + let mut client = default_http3_client(); + let mut server = TestServer::new(); + connect_with(&mut client, &mut server); + (client, server) + } + + // Fetch request fetch("GET", "https", "something.com", "/", headers). + fn make_request( + client: &mut Http3Client, + close_sending_side: bool, + headers: &[Header], + ) -> StreamId { + let request_stream_id = client + .fetch( + now(), + "GET", + "https://something.com/", + headers, + Priority::default(), + ) + .unwrap(); + if close_sending_side { + client.stream_close_send(request_stream_id).unwrap(); + } + request_stream_id + } + + // For fetch request fetch("GET", "https", "something.com", "/", &[]) + // the following request header frame will be sent: + const EXPECTED_REQUEST_HEADER_FRAME: &[u8] = &[ + 0x01, 0x10, 0x00, 0x00, 0xd1, 0xd7, 0x50, 0x89, 0x41, 0xe9, 0x2a, 0x67, 0x35, 0x53, 0x2e, + 0x43, 0xd3, 0xc1, + ]; + + // For fetch request fetch("GET", "https", "something.com", "/", &[(String::from("myheaders", + // "myvalue"))]) the following request header frame will be sent: + const EXPECTED_REQUEST_HEADER_FRAME_VERSION2: &[u8] = &[ + 0x01, 0x11, 0x02, 0x80, 0xd1, 0xd7, 0x50, 0x89, 0x41, 0xe9, 0x2a, 0x67, 0x35, 0x53, 0x2e, + 0x43, 0xd3, 0xc1, 0x10, + ]; + + const HTTP_HEADER_FRAME_0: &[u8] = &[0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x30]; + + // The response header from HTTP_HEADER_FRAME (0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x30) + // are decoded into: + fn check_response_header_0(header: &[Header]) { + let expected_response_header_0 = &[ + Header::new(":status", "200"), + Header::new("content-length", "0"), + ]; + assert_eq!(header, expected_response_header_0); + } + + const HTTP_RESPONSE_1: &[u8] = &[ + // headers + 0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x37, // the first data frame + 0x0, 0x3, 0x61, 0x62, 0x63, // the second data frame + 0x0, 0x4, 0x64, 0x65, 0x66, 0x67, + ]; + + const HTTP_RESPONSE_HEADER_ONLY_1: &[u8] = &[ + // headers + 0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x37, + ]; + const HTTP_RESPONSE_DATA_FRAME_1_ONLY_1: &[u8] = &[0x0, 0x3, 0x61, 0x62, 0x63]; + + const HTTP_RESPONSE_DATA_FRAME_2_ONLY_1: &[u8] = &[0x0, 0x4, 0x64, 0x65, 0x66, 0x67]; + + // The response header from HTTP_RESPONSE_1 (0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x36) are + // decoded into: + fn check_response_header_1(header: &[Header]) { + let expected_response_header_1 = &[ + Header::new(":status", "200"), + Header::new("content-length", "7"), + ]; + assert_eq!(header, expected_response_header_1); + } + + const EXPECTED_RESPONSE_DATA_1: &[u8] = &[0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67]; + + const HTTP_RESPONSE_2: &[u8] = &[ + // headers + 0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x33, // the data frame + 0x0, 0x3, 0x61, 0x62, 0x63, + ]; + + const HTTP_RESPONSE_HEADER_ONLY_2: &[u8] = &[ + // headers + 0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x33, + ]; + + const HTTP_RESPONSE_DATA_FRAME_ONLY_2: &[u8] = &[ + // the data frame + 0x0, 0x3, 0x61, 0x62, 0x63, + ]; + + // The response header from HTTP_RESPONSE_2 (0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x36) are + // decoded into: + fn check_response_header_2(header: &[Header]) { + let expected_response_header_2 = &[ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]; + assert_eq!(header, expected_response_header_2); + } + + // The data frame payload from HTTP_RESPONSE_2 is: + const EXPECTED_RESPONSE_DATA_2_FRAME_1: &[u8] = &[0x61, 0x62, 0x63]; + + fn make_request_and_exchange_pkts( + client: &mut Http3Client, + server: &mut TestServer, + close_sending_side: bool, + ) -> StreamId { + let request_stream_id = make_request(client, close_sending_side, &[]); + + let out = client.process(None, now()); + server + .conn + .process_input(out.as_dgram_ref().unwrap(), now()); + + // find the new request/response stream and send frame v on it. + while let Some(e) = server.conn.next_event() { + match e { + ConnectionEvent::NewStream { stream_id } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(stream_id.stream_type(), StreamType::BiDi); + } + ConnectionEvent::RecvStreamReadable { stream_id } => { + if stream_id == CLIENT_SIDE_ENCODER_STREAM_ID { + server.read_and_check_stream_data( + stream_id, + ENCODER_STREAM_CAP_INSTRUCTION, + false, + ); + } else { + assert_eq!(stream_id, request_stream_id); + server.read_and_check_stream_data( + stream_id, + EXPECTED_REQUEST_HEADER_FRAME, + close_sending_side, + ); + } + } + _ => {} + } + } + let dgram = server.conn.process_output(now()).dgram(); + if let Some(d) = dgram { + client.process_input(&d, now()); + } + request_stream_id + } + + fn connect_and_send_request(close_sending_side: bool) -> (Http3Client, TestServer, StreamId) { + let (mut client, mut server) = connect(); + let request_stream_id = + make_request_and_exchange_pkts(&mut client, &mut server, close_sending_side); + assert_eq!(request_stream_id, 0); + + (client, server, request_stream_id) + } + + fn server_send_response_and_exchange_packet( + client: &mut Http3Client, + server: &mut TestServer, + stream_id: StreamId, + response: impl AsRef<[u8]>, + close_stream: bool, + ) { + _ = server + .conn + .stream_send(stream_id, response.as_ref()) + .unwrap(); + if close_stream { + server.conn.stream_close_send(stream_id).unwrap(); + } + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + } + + const PUSH_PROMISE_DATA: &[u8] = &[ + 0x00, 0x00, 0xd1, 0xd7, 0x50, 0x89, 0x41, 0xe9, 0x2a, 0x67, 0x35, 0x53, 0x2e, 0x43, 0xd3, + 0xc1, + ]; + + fn check_pushpromise_header(header: &[Header]) { + let expected_response_header_1 = &[ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/"), + ]; + assert_eq!(header, expected_response_header_1); + } + + // Send a push promise with push_id and request_stream_id. + fn send_push_promise(conn: &mut Connection, stream_id: StreamId, push_id: u64) { + let frame = HFrame::PushPromise { + push_id, + header_block: PUSH_PROMISE_DATA.to_vec(), + }; + let mut d = Encoder::default(); + frame.encode(&mut d); + _ = conn.stream_send(stream_id, d.as_ref()).unwrap(); + } + + fn send_push_data_and_exchange_packets( + client: &mut Http3Client, + server: &mut TestServer, + push_id: u8, + close_push_stream: bool, + ) -> StreamId { + let push_stream_id = send_push_data(&mut server.conn, push_id, close_push_stream); + + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + push_stream_id + } + + fn send_push_promise_and_exchange_packets( + client: &mut Http3Client, + server: &mut TestServer, + stream_id: StreamId, + push_id: u64, + ) { + send_push_promise(&mut server.conn, stream_id, push_id); + + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + } + + fn send_cancel_push_and_exchange_packets( + client: &mut Http3Client, + server: &mut TestServer, + push_id: u64, + ) { + let frame = HFrame::CancelPush { push_id }; + let mut d = Encoder::default(); + frame.encode(&mut d); + server + .conn + .stream_send(server.control_stream_id.unwrap(), d.as_ref()) + .unwrap(); + + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + } + + const PUSH_DATA: &[u8] = &[ + // headers + 0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x34, // the data frame. + 0x0, 0x4, 0x61, 0x62, 0x63, 0x64, + ]; + + // The response header from PUSH_DATA (0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x34) are + // decoded into: + fn check_push_response_header(header: &[Header]) { + let expected_push_response_header = vec![ + Header::new(":status", "200"), + Header::new("content-length", "4"), + ]; + assert_eq!(header, &expected_push_response_header[..]); + } + + // The data frame payload from PUSH_DATA is: + const EXPECTED_PUSH_RESPONSE_DATA_FRAME: &[u8] = &[0x61, 0x62, 0x63, 0x64]; + + // Send push data on a push stream: + // 1) push_stream_type PUSH_STREAM_TYPE + // 2) push_id + // 3) PUSH_DATA that contains encoded headers and a data frame. + // This function can only handle small push_id numbers that fit in a varint of length 1 byte. + fn send_data_on_push( + conn: &mut Connection, + push_stream_id: StreamId, + push_id: u8, + data: impl AsRef<[u8]>, + close_push_stream: bool, + ) { + // send data + _ = conn.stream_send(push_stream_id, PUSH_STREAM_TYPE).unwrap(); + _ = conn.stream_send(push_stream_id, &[push_id]).unwrap(); + _ = conn.stream_send(push_stream_id, data.as_ref()).unwrap(); + if close_push_stream { + conn.stream_close_send(push_stream_id).unwrap(); + } + } + + // Send push data on a push stream: + // 1) push_stream_type PUSH_STREAM_TYPE + // 2) push_id + // 3) PUSH_DATA that contains encoded headers and a data frame. + // This function can only handle small push_id numbers that fit in a varint of length 1 byte. + fn send_push_data(conn: &mut Connection, push_id: u8, close_push_stream: bool) -> StreamId { + send_push_with_data(conn, push_id, PUSH_DATA, close_push_stream) + } + + // Send push data on a push stream: + // 1) push_stream_type PUSH_STREAM_TYPE + // 2) push_id + // 3) and supplied push data. + // This function can only handle small push_id numbers that fit in a varint of length 1 byte. + fn send_push_with_data( + conn: &mut Connection, + push_id: u8, + data: &[u8], + close_push_stream: bool, + ) -> StreamId { + // create a push stream + let push_stream_id = conn.stream_create(StreamType::UniDi).unwrap(); + // send data + send_data_on_push(conn, push_stream_id, push_id, data, close_push_stream); + push_stream_id + } + + struct PushPromiseInfo { + pub push_id: u64, + pub ref_stream_id: StreamId, + } + + // Helper function: read response when a server sends: + // - HTTP_RESPONSE_2 on the request_stream_id stream, + // - a number of push promises described by a list of PushPromiseInfo. + // - and a push streams with push_id in the push_streams list. + // All push stream contain PUSH_DATA that decodes to headers (that can be checked by calling + // check_push_response_header) and EXPECTED_PUSH_RESPONSE_DATA_FRAME + fn read_response_and_push_events( + client: &mut Http3Client, + push_promises: &[PushPromiseInfo], + push_streams: &[u64], + response_stream_id: StreamId, + ) { + let mut num_push_promises = 0; + let mut num_push_stream_headers = 0; + let mut num_push_stream_data = 0; + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::PushPromise { + push_id, + request_stream_id, + headers, + } => { + assert!(push_promises + .iter() + .any(|p| p.push_id == push_id && p.ref_stream_id == request_stream_id)); + check_pushpromise_header(&headers[..]); + num_push_promises += 1; + } + Http3ClientEvent::PushHeaderReady { + push_id, + headers, + interim, + fin, + } => { + assert!(push_streams.contains(&push_id)); + check_push_response_header(&headers); + num_push_stream_headers += 1; + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::PushDataReadable { push_id } => { + assert!(push_streams.contains(&push_id)); + let mut buf = [0_u8; 100]; + let (amount, fin) = client.push_read_data(now(), push_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, EXPECTED_PUSH_RESPONSE_DATA_FRAME.len()); + assert_eq!(&buf[..amount], EXPECTED_PUSH_RESPONSE_DATA_FRAME); + num_push_stream_data += 1; + } + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, response_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, response_stream_id); + let mut buf = [0_u8; 100]; + let (amount, _) = client.read_data(now(), stream_id, &mut buf).unwrap(); + assert_eq!(amount, EXPECTED_RESPONSE_DATA_2_FRAME_1.len()); + assert_eq!(&buf[..amount], EXPECTED_RESPONSE_DATA_2_FRAME_1); + } + _ => {} + } + } + + assert_eq!(num_push_promises, push_promises.len()); + assert_eq!(num_push_stream_headers, push_streams.len()); + assert_eq!(num_push_stream_data, push_streams.len()); + } + + // Client: Test receiving a new control stream and a SETTINGS frame. + #[test] + fn test_client_connect_and_exchange_qpack_and_control_streams() { + mem::drop(connect()); + } + + // Client: Test that the connection will be closed if control stream + // has been closed. + #[test] + fn test_client_close_control_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_close_send(server.control_stream_id.unwrap()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: Test that the connection will be closed if the local control stream + // has been reset. + #[test] + fn test_client_reset_control_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_reset_send(server.control_stream_id.unwrap(), Error::HttpNoError.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: Test that the connection will be closed if the server side encoder stream + // has been reset. + #[test] + fn test_client_reset_server_side_encoder_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_reset_send(server.encoder_stream_id.unwrap(), Error::HttpNoError.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: Test that the connection will be closed if the server side decoder stream + // has been reset. + #[test] + fn test_client_reset_server_side_decoder_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_reset_send(server.decoder_stream_id.unwrap(), Error::HttpNoError.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: Test that the connection will be closed if the local control stream + // has received a stop_sending. + #[test] + fn test_client_stop_sending_control_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_stop_sending(CLIENT_SIDE_CONTROL_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: Test that the connection will be closed if the client side encoder stream + // has received a stop_sending. + #[test] + fn test_client_stop_sending_encoder_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_stop_sending(CLIENT_SIDE_ENCODER_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: Test that the connection will be closed if the client side decoder stream + // has received a stop_sending. + #[test] + fn test_client_stop_sending_decoder_stream() { + let (mut client, mut server) = connect(); + server + .conn + .stream_stop_sending(CLIENT_SIDE_DECODER_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpClosedCriticalStream); + } + + // Client: test missing SETTINGS frame + // (the first frame sent is a garbage frame). + #[test] + fn test_client_missing_settings() { + let (mut client, mut server) = connect_only_transport(); + // Create server control stream. + let control_stream = server.conn.stream_create(StreamType::UniDi).unwrap(); + // Send a HEADERS frame instead (which contains garbage). + let sent = server + .conn + .stream_send(control_stream, &[0x0, 0x1, 0x3, 0x0, 0x1, 0x2]); + assert_eq!(sent, Ok(6)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpMissingSettings); + } + + // Client: receiving SETTINGS frame twice causes connection close + // with error HTTP_UNEXPECTED_FRAME. + #[test] + fn test_client_receive_settings_twice() { + let (mut client, mut server) = connect(); + // send the second SETTINGS frame. + let sent = server.conn.stream_send( + server.control_stream_id.unwrap(), + &[0x4, 0x6, 0x1, 0x40, 0x64, 0x7, 0x40, 0x64], + ); + assert_eq!(sent, Ok(8)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpFrameUnexpected); + } + + fn test_wrong_frame_on_control_stream(v: &[u8]) { + let (mut client, mut server) = connect(); + + // send a frame that is not allowed on the control stream. + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), v) + .unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + assert_closed(&client, &Error::HttpFrameUnexpected); + } + + // send DATA frame on a cortrol stream + #[test] + fn test_data_frame_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x0, 0x2, 0x1, 0x2]); + } + + // send HEADERS frame on a cortrol stream + #[test] + fn test_headers_frame_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x1, 0x2, 0x1, 0x2]); + } + + // send PUSH_PROMISE frame on a cortrol stream + #[test] + fn test_push_promise_frame_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x5, 0x2, 0x1, 0x2]); + } + + // send PRIORITY_UPDATE frame on a control stream to the client + #[test] + fn test_priority_update_request_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x80, 0x0f, 0x07, 0x00, 0x01, 0x03]); + } + + #[test] + fn test_priority_update_push_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x80, 0x0f, 0x07, 0x01, 0x01, 0x03]); + } + + fn test_wrong_frame_on_push_stream(v: &[u8]) { + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + send_push_promise(&mut server.conn, request_stream_id, 0); + // Create a push stream + let push_stream_id = server.conn.stream_create(StreamType::UniDi).unwrap(); + + // Send the push stream type byte, push_id and frame v. + _ = server + .conn + .stream_send(push_stream_id, &[0x01, 0x0]) + .unwrap(); + _ = server.conn.stream_send(push_stream_id, v).unwrap(); + + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + assert_closed(&client, &Error::HttpFrameUnexpected); + } + + #[test] + fn test_cancel_push_frame_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x3, 0x1, 0x5]); + } + + #[test] + fn test_settings_frame_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x4, 0x4, 0x6, 0x4, 0x8, 0x4]); + } + + #[test] + fn test_push_promise_frame_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x5, 0x2, 0x1, 0x2]); + } + + #[test] + fn test_priority_update_request_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x80, 0x0f, 0x07, 0x00, 0x01, 0x03]); + } + + #[test] + fn test_priority_update_push_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x80, 0x0f, 0x07, 0x01, 0x01, 0x03]); + } + + #[test] + fn test_goaway_frame_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x7, 0x1, 0x5]); + } + + #[test] + fn test_max_push_id_frame_on_push_stream() { + test_wrong_frame_on_push_stream(&[0xd, 0x1, 0x5]); + } + + // send DATA frame before a header frame + #[test] + fn test_data_frame_on_push_stream() { + test_wrong_frame_on_push_stream(&[0x0, 0x2, 0x1, 0x2]); + } + + // Client: receive unknown stream type + // This function also tests getting stream id that does not fit into a single byte. + #[test] + fn test_client_received_unknown_stream() { + let (mut client, mut server) = connect(); + + // create a stream with unknown type. + let new_stream_id = server.conn.stream_create(StreamType::UniDi).unwrap(); + _ = server + .conn + .stream_send(new_stream_id, &[0x41, 0x19, 0x4, 0x4, 0x6, 0x0, 0x8, 0x0]) + .unwrap(); + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // check for stop-sending with Error::HttpStreamCreation. + let mut stop_sending_event_found = false; + while let Some(e) = server.conn.next_event() { + if let ConnectionEvent::SendStreamStopSending { + stream_id, + app_error, + } = e + { + stop_sending_event_found = true; + assert_eq!(stream_id, new_stream_id); + assert_eq!(app_error, Error::HttpStreamCreation.code()); + } + } + assert!(stop_sending_event_found); + assert_eq!(client.state(), Http3State::Connected); + } + + // Test wrong frame on req/rec stream + fn test_wrong_frame_on_request_stream(v: &[u8]) { + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + _ = server.conn.stream_send(request_stream_id, v).unwrap(); + + // Generate packet with the above bad h3 input + let out = server.conn.process(None, now()); + // Process bad input and close the connection. + mem::drop(client.process(out.as_dgram_ref(), now())); + + assert_closed(&client, &Error::HttpFrameUnexpected); + } + + #[test] + fn test_cancel_push_frame_on_request_stream() { + test_wrong_frame_on_request_stream(&[0x3, 0x1, 0x5]); + } + + #[test] + fn test_settings_frame_on_request_stream() { + test_wrong_frame_on_request_stream(&[0x4, 0x4, 0x6, 0x4, 0x8, 0x4]); + } + + #[test] + fn test_goaway_frame_on_request_stream() { + test_wrong_frame_on_request_stream(&[0x7, 0x1, 0x5]); + } + + #[test] + fn test_max_push_id_frame_on_request_stream() { + test_wrong_frame_on_request_stream(&[0xd, 0x1, 0x5]); + } + + #[test] + fn test_priority_update_request_on_request_stream() { + test_wrong_frame_on_request_stream(&[0x80, 0x0f, 0x07, 0x00, 0x01, 0x03]); + } + + #[test] + fn test_priority_update_push_on_request_stream() { + test_wrong_frame_on_request_stream(&[0x80, 0x0f, 0x07, 0x01, 0x01, 0x03]); + } + + // Test reading of a slowly streamed frame. bytes are received one by one + #[test] + fn test_frame_reading() { + let (mut client, mut server) = connect_only_transport(); + + // create a control stream. + let control_stream = server.conn.stream_create(StreamType::UniDi).unwrap(); + + // send the stream type + let mut sent = server.conn.stream_send(control_stream, &[0x0]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // start sending SETTINGS frame + sent = server.conn.stream_send(control_stream, &[0x4]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x4]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x6]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x0]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x8]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x0]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + assert_eq!(client.state(), Http3State::Connected); + + // Now test PushPromise + sent = server.conn.stream_send(control_stream, &[0x5]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x5]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x4]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x61]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x62]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x63]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + sent = server.conn.stream_send(control_stream, &[0x64]); + assert_eq!(sent, Ok(1)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // PUSH_PROMISE on a control stream will cause an error + assert_closed(&client, &Error::HttpFrameUnexpected); + } + + #[test] + fn fetch_basic() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // send response - 200 Content-Length: 7 + // with content: 'abcdefg'. + // The content will be send in 2 DATA frames. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_1, + true, + ); + + let http_events = client.events().collect::<Vec<_>>(); + assert_eq!(http_events.len(), 2); + for e in http_events { + match e { + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_1(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, EXPECTED_RESPONSE_DATA_1.len()); + assert_eq!(&buf[..amount], EXPECTED_RESPONSE_DATA_1); + } + _ => {} + } + } + + // after this stream will be removed from hcoon. We will check this by trying to read + // from the stream and that should fail. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + client.close(now(), 0, ""); + } + + /// Force both endpoints into an idle state. + /// Do this by opening unidirectional streams at both endpoints and sending + /// a partial unidirectional stream type (which the receiver has to buffer), + /// then delivering packets out of order. + /// This forces the receiver to create an acknowledgment, which will allow + /// the peer to become idle. + fn force_idle(client: &mut Http3Client, server: &mut TestServer) { + // Send a partial unidirectional stream ID. + // Note that this can't close the stream as that causes the receiver + // to send `MAX_STREAMS`, which would prevent it from becoming idle. + fn dgram(c: &mut Connection) -> Datagram { + let stream = c.stream_create(StreamType::UniDi).unwrap(); + _ = c.stream_send(stream, &[0xc0]).unwrap(); + c.process_output(now()).dgram().unwrap() + } + + let d1 = dgram(&mut client.conn); + let d2 = dgram(&mut client.conn); + server.conn.process_input(&d2, now()); + server.conn.process_input(&d1, now()); + let d3 = dgram(&mut server.conn); + let d4 = dgram(&mut server.conn); + client.process_input(&d4, now()); + client.process_input(&d3, now()); + let ack = client.process_output(now()).dgram(); + server.conn.process_input(&ack.unwrap(), now()); + } + + /// The client should keep a connection alive if it has unanswered requests. + #[test] + fn fetch_keep_alive() { + let (mut client, mut server, _request_stream_id) = connect_and_send_request(true); + force_idle(&mut client, &mut server); + + let idle_timeout = ConnectionParameters::default().get_idle_timeout(); + assert_eq!(client.process_output(now()).callback(), idle_timeout / 2); + } + + // Helper function: read response when a server sends HTTP_RESPONSE_2. + fn read_response( + client: &mut Http3Client, + server: &mut Connection, + request_stream_id: StreamId, + ) { + let out = server.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, EXPECTED_RESPONSE_DATA_2_FRAME_1.len()); + assert_eq!(&buf[..amount], EXPECTED_RESPONSE_DATA_2_FRAME_1); + } + _ => {} + } + } + + // after this stream will be removed from client. We will check this by trying to read + // from the stream and that should fail. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + client.close(now(), 0, ""); + } + + // Data sent with a request: + const REQUEST_BODY: &[u8] = &[0x64, 0x65, 0x66]; + // Corresponding data frame that server will receive. + const EXPECTED_REQUEST_BODY_FRAME: &[u8] = &[0x0, 0x3, 0x64, 0x65, 0x66]; + + // Send a request with the request body. + #[test] + fn fetch_with_data() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Get DataWritable for the request stream so that we can write the request body. + let data_writable = |e| matches!(e, Http3ClientEvent::DataWritable { .. }); + assert!(client.events().any(data_writable)); + let sent = client.send_data(request_stream_id, REQUEST_BODY).unwrap(); + assert_eq!(sent, REQUEST_BODY.len()); + client.stream_close_send(request_stream_id).unwrap(); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // find the new request/response stream and send response on it. + while let Some(e) = server.conn.next_event() { + match e { + ConnectionEvent::NewStream { stream_id } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(stream_id.stream_type(), StreamType::BiDi); + } + ConnectionEvent::RecvStreamReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + + // Read request body. + let mut buf = [0_u8; 100]; + let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, EXPECTED_REQUEST_BODY_FRAME.len()); + assert_eq!(&buf[..amount], EXPECTED_REQUEST_BODY_FRAME); + + // send response - 200 Content-Length: 3 + // with content: 'abc'. + _ = server.conn.stream_send(stream_id, HTTP_RESPONSE_2).unwrap(); + server.conn.stream_close_send(stream_id).unwrap(); + } + _ => {} + } + } + + read_response(&mut client, &mut server.conn, request_stream_id); + } + + // send a request with request body containing request_body. We expect to receive + // expected_data_frame_header. + fn fetch_with_data_length_xbytes(request_body: &[u8], expected_data_frame_header: &[u8]) { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Get DataWritable for the request stream so that we can write the request body. + let data_writable = |e| matches!(e, Http3ClientEvent::DataWritable { .. }); + assert!(client.events().any(data_writable)); + let sent = client.send_data(request_stream_id, request_body); + assert_eq!(sent, Ok(request_body.len())); + + // Close stream. + client.stream_close_send(request_stream_id).unwrap(); + + // We need to loop a bit until all data has been sent. + let mut out = client.process(None, now()); + for _i in 0..20 { + out = server.conn.process(out.as_dgram_ref(), now()); + out = client.process(out.as_dgram_ref(), now()); + } + + // check request body is received. + // Then send a response. + while let Some(e) = server.conn.next_event() { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + if stream_id == request_stream_id { + // Read the DATA frame. + let mut buf = vec![1_u8; RECV_BUFFER_SIZE]; + let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!( + amount, + request_body.len() + expected_data_frame_header.len() + ); + + // Check the DATA frame header + assert_eq!( + &buf[..expected_data_frame_header.len()], + expected_data_frame_header + ); + + // Check data. + assert_eq!(&buf[expected_data_frame_header.len()..amount], request_body); + + // send response - 200 Content-Length: 3 + // with content: 'abc'. + _ = server.conn.stream_send(stream_id, HTTP_RESPONSE_2).unwrap(); + server.conn.stream_close_send(stream_id).unwrap(); + } + } + } + + read_response(&mut client, &mut server.conn, request_stream_id); + } + + // send a request with 63 bytes. The DATA frame length field will still have 1 byte. + #[test] + fn fetch_with_data_length_63bytes() { + fetch_with_data_length_xbytes(&[0_u8; 63], &[0x0, 0x3f]); + } + + // send a request with 64 bytes. The DATA frame length field will need 2 byte. + #[test] + fn fetch_with_data_length_64bytes() { + fetch_with_data_length_xbytes(&[0_u8; 64], &[0x0, 0x40, 0x40]); + } + + // send a request with 16383 bytes. The DATA frame length field will still have 2 byte. + #[test] + fn fetch_with_data_length_16383bytes() { + fetch_with_data_length_xbytes(&[0_u8; 16383], &[0x0, 0x7f, 0xff]); + } + + // send a request with 16384 bytes. The DATA frame length field will need 4 byte. + #[test] + fn fetch_with_data_length_16384bytes() { + fetch_with_data_length_xbytes(&[0_u8; 16384], &[0x0, 0x80, 0x0, 0x40, 0x0]); + } + + // Send 2 data frames so that the second one cannot fit into the send_buf and it is only + // partialy sent. We check that the sent data is correct. + #[allow(clippy::useless_vec)] + fn fetch_with_two_data_frames( + first_frame: &[u8], + expected_first_data_frame_header: &[u8], + expected_second_data_frame_header: &[u8], + expected_second_data_frame: &[u8], + ) { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Get DataWritable for the request stream so that we can write the request body. + let data_writable = |e| matches!(e, Http3ClientEvent::DataWritable { .. }); + assert!(client.events().any(data_writable)); + + // Send the first frame. + let sent = client.send_data(request_stream_id, first_frame); + assert_eq!(sent, Ok(first_frame.len())); + + // The second frame cannot fit. + let sent = client.send_data(request_stream_id, &vec![0_u8; SEND_BUFFER_SIZE]); + assert_eq!(sent, Ok(expected_second_data_frame.len())); + + // Close stream. + client.stream_close_send(request_stream_id).unwrap(); + + let mut out = client.process(None, now()); + // We need to loop a bit until all data has been sent. Once for every 1K + // of data. + for _i in 0..SEND_BUFFER_SIZE / 1000 { + out = server.conn.process(out.as_dgram_ref(), now()); + out = client.process(out.as_dgram_ref(), now()); + } + + // Check received frames and send a response. + while let Some(e) = server.conn.next_event() { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + if stream_id == request_stream_id { + // Read DATA frames. + let mut buf = vec![1_u8; RECV_BUFFER_SIZE]; + let (amount, fin) = server.conn.stream_recv(stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!( + amount, + expected_first_data_frame_header.len() + + first_frame.len() + + expected_second_data_frame_header.len() + + expected_second_data_frame.len() + ); + + // Check the first DATA frame header + let end = expected_first_data_frame_header.len(); + assert_eq!(&buf[..end], expected_first_data_frame_header); + + // Check the first frame data. + let start = end; + let end = end + first_frame.len(); + assert_eq!(&buf[start..end], first_frame); + + // Check the second DATA frame header + let start2 = end; + let end2 = end + expected_second_data_frame_header.len(); + assert_eq!(&buf[start2..end2], expected_second_data_frame_header); + + // Check the second frame data. + let start3 = end2; + let end3 = end2 + expected_second_data_frame.len(); + assert_eq!(&buf[start3..end3], expected_second_data_frame); + + // send response - 200 Content-Length: 3 + // with content: 'abc'. + _ = server.conn.stream_send(stream_id, HTTP_RESPONSE_2).unwrap(); + server.conn.stream_close_send(stream_id).unwrap(); + } + } + } + + read_response(&mut client, &mut server.conn, request_stream_id); + } + + fn alloc_buffer(size: usize) -> (Vec<u8>, Vec<u8>) { + let data_frame = HFrame::Data { len: size as u64 }; + let mut enc = Encoder::default(); + data_frame.encode(&mut enc); + + (vec![0_u8; size], enc.as_ref().to_vec()) + } + + // Send 2 frames. For the second one we can only send 63 bytes. + // After the first frame there is exactly 63+2 bytes left in the send buffer. + #[test] + fn fetch_two_data_frame_second_63bytes() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 88); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]); + } + + // Send 2 frames. For the second one we can only send 63 bytes. + // After the first frame there is exactly 63+3 bytes left in the send buffer, + // but we can only send 63 bytes. + #[test] + fn fetch_two_data_frame_second_63bytes_place_for_66() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 89); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x3f], &[0_u8; 63]); + } + + // Send 2 frames. For the second one we can only send 64 bytes. + // After the first frame there is exactly 64+3 bytes left in the send buffer, + // but we can only send 64 bytes. + #[test] + fn fetch_two_data_frame_second_64bytes_place_for_67() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 90); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x40, 0x40], &[0_u8; 64]); + } + + // Send 2 frames. For the second one we can only send 16383 bytes. + // After the first frame there is exactly 16383+3 bytes left in the send buffer. + #[test] + fn fetch_two_data_frame_second_16383bytes() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16409); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]); + } + + // Send 2 frames. For the second one we can only send 16383 bytes. + // After the first frame there is exactly 16383+4 bytes left in the send buffer, but we can only + // send 16383 bytes. + #[test] + fn fetch_two_data_frame_second_16383bytes_place_for_16387() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16410); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]); + } + + // Send 2 frames. For the second one we can only send 16383 bytes. + // After the first frame there is exactly 16383+5 bytes left in the send buffer, but we can only + // send 16383 bytes. + #[test] + fn fetch_two_data_frame_second_16383bytes_place_for_16388() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16411); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x7f, 0xff], &[0_u8; 16383]); + } + + // Send 2 frames. For the second one we can send 16384 bytes. + // After the first frame there is exactly 16384+5 bytes left in the send buffer, but we can send + // 16384 bytes. + #[test] + fn fetch_two_data_frame_second_16384bytes_place_for_16389() { + let (buf, hdr) = alloc_buffer(SEND_BUFFER_SIZE - 16412); + fetch_with_two_data_frames(&buf, &hdr, &[0x0, 0x80, 0x0, 0x40, 0x0], &[0_u8; 16384]); + } + + // Test receiving STOP_SENDING with the HttpNoError error code. + #[test] + fn test_stop_sending_early_response() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Stop sending with early_response. + assert_eq!( + Ok(()), + server + .conn + .stream_stop_sending(request_stream_id, Error::HttpNoError.code()) + ); + + // send response - 200 Content-Length: 3 + // with content: 'abc'. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + let mut stop_sending = false; + let mut response_headers = false; + let mut response_body = false; + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::StopSending { stream_id, error } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpNoError.code()); + // assert that we cannot send any more request data. + assert_eq!( + Err(Error::InvalidStreamId), + client.send_data(request_stream_id, &[0_u8; 10]) + ); + stop_sending = true; + } + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + response_headers = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, EXPECTED_RESPONSE_DATA_2_FRAME_1.len()); + assert_eq!(&buf[..amount], EXPECTED_RESPONSE_DATA_2_FRAME_1); + response_body = true; + } + _ => {} + } + } + assert!(response_headers); + assert!(response_body); + assert!(stop_sending); + + // after this stream will be removed from client. We will check this by trying to read + // from the stream and that should fail. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + client.close(now(), 0, ""); + } + + // Server sends stop sending and reset. + #[test] + fn test_stop_sending_other_error_with_reset() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Stop sending with RequestRejected. + assert_eq!( + Ok(()), + server + .conn + .stream_stop_sending(request_stream_id, Error::HttpRequestRejected.code()) + ); + // also reset with RequestRejected. + assert_eq!( + Ok(()), + server + .conn + .stream_reset_send(request_stream_id, Error::HttpRequestRejected.code()) + ); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let mut reset = false; + let mut stop_sending = false; + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::StopSending { stream_id, error } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestRejected.code()); + stop_sending = true; + } + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestRejected.code()); + assert!(!local); + reset = true; + } + Http3ClientEvent::HeaderReady { .. } | Http3ClientEvent::DataReadable { .. } => { + panic!("We should not get any headers or data"); + } + _ => {} + } + } + + assert!(reset); + assert!(stop_sending); + + // after this stream will be removed from client. We will check this by trying to read + // from the stream and that should fail. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + client.close(now(), 0, ""); + } + + // Server sends stop sending with RequestRejected, but it does not send reset. + #[test] + fn test_stop_sending_other_error_wo_reset() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Stop sending with RequestRejected. + assert_eq!( + Ok(()), + server + .conn + .stream_stop_sending(request_stream_id, Error::HttpRequestRejected.code()) + ); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let mut stop_sending = false; + + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::StopSending { stream_id, error } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestRejected.code()); + stop_sending = true; + } + Http3ClientEvent::Reset { .. } => { + panic!("We should not get StopSending."); + } + Http3ClientEvent::HeaderReady { .. } | Http3ClientEvent::DataReadable { .. } => { + panic!("We should not get any headers or data"); + } + _ => {} + } + } + + assert!(stop_sending); + + // after this we can still read from a stream. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert!(res.is_ok()); + + client.close(now(), 0, ""); + } + + // Server sends stop sending and reset. We have some events for that stream already + // in client.events. The events will be removed. + #[test] + fn test_stop_sending_and_reset_other_error_with_events() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // send response - 200 Content-Length: 3 + // with content: 'abc'. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + false, + ); + // At this moment we have some new events, i.e. a HeadersReady event + + // Send a stop sending and reset. + assert_eq!( + Ok(()), + server + .conn + .stream_stop_sending(request_stream_id, Error::HttpRequestCancelled.code()) + ); + assert_eq!( + Ok(()), + server + .conn + .stream_reset_send(request_stream_id, Error::HttpRequestCancelled.code()) + ); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let mut reset = false; + + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::StopSending { stream_id, error } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestCancelled.code()); + } + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestCancelled.code()); + assert!(!local); + reset = true; + } + Http3ClientEvent::HeaderReady { .. } | Http3ClientEvent::DataReadable { .. } => { + panic!("We should not get any headers or data"); + } + _ => {} + } + } + + assert!(reset); + + // after this stream will be removed from client. We will check this by trying to read + // from the stream and that should fail. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + client.close(now(), 0, ""); + } + + // Server sends stop sending with code that is not HttpNoError. + // We have some events for that stream already in the client.events. + // The events will be removed. + #[test] + fn test_stop_sending_other_error_with_events() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // send response - 200 Content-Length: 3 + // with content: 'abc'. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + false, + ); + // At this moment we have some new event, i.e. a HeadersReady event + + // Send a stop sending. + assert_eq!( + Ok(()), + server + .conn + .stream_stop_sending(request_stream_id, Error::HttpRequestCancelled.code()) + ); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let mut stop_sending = false; + let mut header_ready = false; + + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::StopSending { stream_id, error } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestCancelled.code()); + stop_sending = true; + } + Http3ClientEvent::Reset { .. } => { + panic!("We should not get StopSending."); + } + Http3ClientEvent::HeaderReady { .. } | Http3ClientEvent::DataReadable { .. } => { + header_ready = true; + } + _ => {} + } + } + + assert!(stop_sending); + assert!(header_ready); + + // after this, we can sill read data from a sttream. + let mut buf = [0_u8; 100]; + let (amount, fin) = client + .read_data(now(), request_stream_id, &mut buf) + .unwrap(); + assert!(!fin); + assert_eq!(amount, EXPECTED_RESPONSE_DATA_2_FRAME_1.len()); + assert_eq!(&buf[..amount], EXPECTED_RESPONSE_DATA_2_FRAME_1); + + client.close(now(), 0, ""); + } + + // Server sends a reset. We will close sending side as well. + #[test] + fn test_reset_wo_stop_sending() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Send a reset. + assert_eq!( + Ok(()), + server + .conn + .stream_reset_send(request_stream_id, Error::HttpRequestCancelled.code()) + ); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let mut reset = false; + + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::StopSending { .. } => { + panic!("We should not get StopSending."); + } + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert_eq!(stream_id, request_stream_id); + assert_eq!(error, Error::HttpRequestCancelled.code()); + assert!(!local); + reset = true; + } + Http3ClientEvent::HeaderReady { .. } | Http3ClientEvent::DataReadable { .. } => { + panic!("We should not get any headers or data"); + } + _ => {} + } + } + + assert!(reset); + + // after this stream will be removed from client. We will check this by trying to read + // from the stream and that should fail. + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), request_stream_id, &mut buf); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + client.close(now(), 0, ""); + } + + fn test_incomplet_frame(buf: &[u8], error: &Error) { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + buf, + true, + ); + + while let Some(e) = client.next_event() { + if let Http3ClientEvent::DataReadable { stream_id } = e { + assert_eq!(stream_id, request_stream_id); + let mut buf_res = [0_u8; 100]; + let res = client.read_data(now(), stream_id, &mut buf_res); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::HttpFrame); + } + } + assert_closed(&client, error); + } + + // Incomplete DATA frame + #[test] + fn test_incomplet_data_frame() { + test_incomplet_frame(&HTTP_RESPONSE_2[..12], &Error::HttpFrame); + } + + // Incomplete HEADERS frame + #[test] + fn test_incomplet_headers_frame() { + test_incomplet_frame(&HTTP_RESPONSE_2[..7], &Error::HttpFrame); + } + + #[test] + fn test_incomplet_unknown_frame() { + test_incomplet_frame(&[0x21], &Error::HttpFrame); + } + + // test goaway + #[test] + fn test_goaway() { + let (mut client, mut server) = connect(); + let request_stream_id_1 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_1, 0); + let request_stream_id_2 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_2, 4); + let request_stream_id_3 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_3, 8); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x8]) + .unwrap(); + + // find the new request/response stream and send frame v on it. + while let Some(e) = server.conn.next_event() { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + let mut buf = [0_u8; 100]; + _ = server.conn.stream_recv(stream_id, &mut buf).unwrap(); + if (stream_id == request_stream_id_1) || (stream_id == request_stream_id_2) { + // send response - 200 Content-Length: 7 + // with content: 'abcdefg'. + // The content will be send in 2 DATA frames. + _ = server.conn.stream_send(stream_id, HTTP_RESPONSE_1).unwrap(); + server.conn.stream_close_send(stream_id).unwrap(); + } + } + } + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let mut stream_reset = false; + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { headers, fin, .. } => { + check_response_header_1(&headers); + assert!(!fin); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert!( + (stream_id == request_stream_id_1) || (stream_id == request_stream_id_2) + ); + let mut buf = [0_u8; 100]; + assert_eq!( + (EXPECTED_RESPONSE_DATA_1.len(), true), + client.read_data(now(), stream_id, &mut buf).unwrap() + ); + } + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert_eq!(stream_id, request_stream_id_3); + assert_eq!(error, Error::HttpRequestRejected.code()); + assert!(!local); + stream_reset = true; + } + _ => {} + } + } + + assert!(stream_reset); + assert_eq!(client.state(), Http3State::GoingAway(StreamId::new(8))); + + // Check that a new request cannot be made. + assert_eq!( + client.fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default() + ), + Err(Error::AlreadyClosed) + ); + + client.close(now(), 0, ""); + } + + #[test] + fn multiple_goaways() { + let (mut client, mut server) = connect(); + let request_stream_id_1 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_1, 0); + let request_stream_id_2 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_2, 4); + let request_stream_id_3 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_3, 8); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // First send a Goaway frame with an higher number + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x8]) + .unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Check that there is one reset for stream_id 8 + let mut stream_reset_1 = 0; + while let Some(e) = client.next_event() { + if let Http3ClientEvent::Reset { + stream_id, + error, + local, + } = e + { + assert_eq!(stream_id, request_stream_id_3); + assert_eq!(error, Error::HttpRequestRejected.code()); + assert!(!local); + stream_reset_1 += 1; + } + } + + assert_eq!(stream_reset_1, 1); + assert_eq!(client.state(), Http3State::GoingAway(StreamId::new(8))); + + // Server sends another GOAWAY frame + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x4]) + .unwrap(); + + // Send response for stream 0 + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id_1, + HTTP_RESPONSE_1, + true, + ); + + let mut stream_reset_2 = 0; + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { headers, fin, .. } => { + check_response_header_1(&headers); + assert!(!fin); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert!(stream_id == request_stream_id_1); + let mut buf = [0_u8; 100]; + assert_eq!( + (EXPECTED_RESPONSE_DATA_1.len(), true), + client.read_data(now(), stream_id, &mut buf).unwrap() + ); + } + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert_eq!(stream_id, request_stream_id_2); + assert_eq!(error, Error::HttpRequestRejected.code()); + assert!(!local); + stream_reset_2 += 1; + } + _ => {} + } + } + + assert_eq!(stream_reset_2, 1); + assert_eq!(client.state(), Http3State::GoingAway(StreamId::new(4))); + } + + #[test] + fn multiple_goaways_stream_id_increased() { + let (mut client, mut server) = connect(); + let request_stream_id_1 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_1, 0); + let request_stream_id_2 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_2, 4); + let request_stream_id_3 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_3, 8); + + // First send a Goaway frame with a smaller number + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x4]) + .unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + assert_eq!(client.state(), Http3State::GoingAway(StreamId::new(4))); + + // Now send a Goaway frame with an higher number + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x8]) + .unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + assert_closed(&client, &Error::HttpGeneralProtocol); + } + + #[test] + fn goaway_wrong_stream_id() { + let (mut client, mut server) = connect(); + + _ = server + .conn + .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x9]) + .unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + assert_closed(&client, &Error::HttpId); + } + + // Close stream before headers. + #[test] + fn test_stream_fin_wo_headers() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + // send fin before sending any data. + server.conn.stream_close_send(request_stream_id).unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Recv HeaderReady wo headers with fin. + let e = client.events().next().unwrap(); + assert_eq!( + e, + Http3ClientEvent::Reset { + stream_id: request_stream_id, + error: Error::HttpGeneralProtocolStream.code(), + local: true, + } + ); + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + // Close stream imemediately after headers. + #[test] + fn test_stream_fin_after_headers() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_HEADER_ONLY_2, + true, + ); + + // Recv HeaderReady with headers and fin. + let e = client.events().next().unwrap(); + if let Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } = e + { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(fin); + assert!(!interim); + } else { + panic!("wrong event type"); + } + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + // Send headers, read headers and than close stream. + // We should get HeaderReady and a DataReadable + #[test] + fn test_stream_fin_after_headers_are_read_wo_data_frame() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + // Send some good data wo fin + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_HEADER_ONLY_2, + false, + ); + + // Recv headers wo fin + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { .. } => { + panic!("We should not receive a DataGeadable event!"); + } + _ => {} + }; + } + + // ok NOW send fin + server.conn.stream_close_send(request_stream_id).unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Recv DataReadable wo data with fin + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { .. } => { + panic!("We should not get another HeaderReady!"); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), stream_id, &mut buf); + let (len, fin) = res.expect("should read"); + assert_eq!(0, len); + assert!(fin); + } + _ => {} + }; + } + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + // Send headers and an empty data frame, then close the stream. + #[test] + fn test_stream_fin_after_headers_and_a_empty_data_frame() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send headers. + _ = server + .conn + .stream_send(request_stream_id, HTTP_RESPONSE_HEADER_ONLY_2) + .unwrap(); + // Send an empty data frame. + _ = server + .conn + .stream_send(request_stream_id, &[0x00, 0x00]) + .unwrap(); + // ok NOW send fin + server.conn.stream_close_send(request_stream_id).unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Recv HeaderReady with fin. + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + assert_eq!(Ok((0, true)), client.read_data(now(), stream_id, &mut buf)); + } + _ => {} + }; + } + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), request_stream_id, &mut buf), + Err(Error::InvalidStreamId) + ); + } + + // Send headers and an empty data frame. Read headers and then close the stream. + // We should get a HeaderReady without fin and a DataReadable wo data and with fin. + #[test] + fn test_stream_fin_after_headers_an_empty_data_frame_are_read() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + // Send some good data wo fin + // Send headers. + _ = server + .conn + .stream_send(request_stream_id, HTTP_RESPONSE_HEADER_ONLY_2) + .unwrap(); + // Send an empty data frame. + _ = server + .conn + .stream_send(request_stream_id, &[0x00, 0x00]) + .unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Recv headers wo fin + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { .. } => { + panic!("We should not receive a DataGeadable event!"); + } + _ => {} + }; + } + + // ok NOW send fin + server.conn.stream_close_send(request_stream_id).unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Recv no data, but do get fin + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { .. } => { + panic!("We should not get another HeaderReady!"); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), stream_id, &mut buf); + let (len, fin) = res.expect("should read"); + assert_eq!(0, len); + assert!(fin); + } + _ => {} + }; + } + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + #[test] + fn test_stream_fin_after_a_data_frame() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + // Send some good data wo fin + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + false, + ); + + // Recv some good data wo fin + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } => { + assert_eq!(stream_id, request_stream_id); + check_response_header_2(&headers); + assert!(!fin); + assert!(!interim); + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let res = client.read_data(now(), stream_id, &mut buf); + let (len, fin) = res.expect("should have data"); + assert_eq!(len, EXPECTED_RESPONSE_DATA_2_FRAME_1.len()); + assert_eq!(&buf[..len], EXPECTED_RESPONSE_DATA_2_FRAME_1); + assert!(!fin); + } + _ => {} + }; + } + + // ok NOW send fin + server.conn.stream_close_send(request_stream_id).unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // fin wo data should generate DataReadable + let e = client.events().next().unwrap(); + if let Http3ClientEvent::DataReadable { stream_id } = e { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0; 100]; + let res = client.read_data(now(), stream_id, &mut buf); + let (len, fin) = res.expect("should read"); + assert_eq!(0, len); + assert!(fin); + } else { + panic!("wrong event type"); + } + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + #[test] + fn test_multiple_data_frames() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send two data frames with fin + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_1, + true, + ); + + // Read first frame + match client.events().nth(1).unwrap() { + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + assert_eq!( + (EXPECTED_RESPONSE_DATA_1.len(), true), + client.read_data(now(), stream_id, &mut buf).unwrap() + ); + } + x => { + panic!("event {:?}", x); + } + } + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + #[test] + fn test_receive_grease_before_response() { + // Construct an unknown frame. + const UNKNOWN_FRAME_LEN: usize = 832; + + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + let mut enc = Encoder::with_capacity(UNKNOWN_FRAME_LEN + 4); + enc.encode_varint(1028_u64); // Arbitrary type. + enc.encode_varint(UNKNOWN_FRAME_LEN as u64); + let mut buf: Vec<_> = enc.into(); + buf.resize(UNKNOWN_FRAME_LEN + buf.len(), 0); + _ = server.conn.stream_send(request_stream_id, &buf).unwrap(); + + // Send a headers and a data frame with fin + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + // Read first frame + match client.events().nth(1).unwrap() { + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, request_stream_id); + let mut buf = [0_u8; 100]; + let (len, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); + assert_eq!(len, EXPECTED_RESPONSE_DATA_2_FRAME_1.len()); + assert_eq!(&buf[..len], EXPECTED_RESPONSE_DATA_2_FRAME_1); + assert!(fin); + } + x => { + panic!("event {:?}", x); + } + } + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + #[test] + fn test_read_frames_header_blocked() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let headers = vec![ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Send the encoder instructions, but delay them so that the stream is blocked on decoding + // headers. + let encoder_inst_pkt = server.conn.process(None, now()); + + // Send response + let mut d = Encoder::default(); + hframe.encode(&mut d); + let d_frame = HFrame::Data { len: 3 }; + d_frame.encode(&mut d); + d.encode(&[0x61, 0x62, 0x63]); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + true, + ); + + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(!client.events().any(header_ready_event)); + + // Let client receive the encoder instructions. + mem::drop(client.process(encoder_inst_pkt.as_dgram_ref(), now())); + + let out = server.conn.process(None, now()); + mem::drop(client.process(out.as_dgram_ref(), now())); + mem::drop(client.process(None, now())); + + let mut recv_header = false; + let mut recv_data = false; + // Now the stream is unblocked and both headers and data will be consumed. + while let Some(e) = client.next_event() { + match e { + Http3ClientEvent::HeaderReady { stream_id, .. } => { + assert_eq!(stream_id, request_stream_id); + recv_header = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + recv_data = true; + assert_eq!(stream_id, request_stream_id); + } + x => { + panic!("event {:?}", x); + } + } + } + assert!(recv_header && recv_data); + } + + #[test] + fn test_read_frames_header_blocked_with_fin_after_headers() { + let (mut hconn, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut hconn, &mut server); + + let sent_headers = vec![ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "0"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &sent_headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Send the encoder instructions, but delay them so that the stream is blocked on decoding + // headers. + let encoder_inst_pkt = server.conn.process(None, now()); + + let mut d = Encoder::default(); + hframe.encode(&mut d); + + server_send_response_and_exchange_packet( + &mut hconn, + &mut server, + request_stream_id, + &d, + true, + ); + + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(!hconn.events().any(header_ready_event)); + + // Let client receive the encoder instructions. + let _out = hconn.process(encoder_inst_pkt.as_dgram_ref(), now()); + + let mut recv_header = false; + // Now the stream is unblocked. After headers we will receive a fin. + while let Some(e) = hconn.next_event() { + if let Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } = e + { + assert_eq!(stream_id, request_stream_id); + assert_eq!(headers.as_ref(), sent_headers); + assert!(fin); + assert!(!interim); + recv_header = true; + } else { + panic!("event {:?}", e); + } + } + assert!(recv_header); + } + + fn exchange_token(client: &mut Http3Client, server: &mut Connection) -> ResumptionToken { + server.send_ticket(now(), &[]).expect("can send ticket"); + let out = server.process_output(now()); + assert!(out.as_dgram_ref().is_some()); + client.process_input(out.as_dgram_ref().unwrap(), now()); + // We do not have a token so we need to wait for a resumption token timer to trigger. + client.process_output(now() + Duration::from_millis(250)); + assert_eq!(client.state(), Http3State::Connected); + client + .events() + .find_map(|e| { + if let Http3ClientEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }) + .unwrap() + } + + fn start_with_0rtt() -> (Http3Client, TestServer) { + let (mut client, mut server) = connect(); + let token = exchange_token(&mut client, &mut server.conn); + + let mut client = default_http3_client(); + + let server = TestServer::new(); + + assert_eq!(client.state(), Http3State::Initializing); + client + .enable_resumption(now(), &token) + .expect("Set resumption token."); + + assert_eq!(client.state(), Http3State::ZeroRtt); + let zerortt_event = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::ZeroRtt)); + assert!(client.events().any(zerortt_event)); + + (client, server) + } + + #[test] + fn zero_rtt_negotiated() { + let (mut client, mut server) = start_with_0rtt(); + + let out = client.process(None, now()); + + assert_eq!(client.state(), Http3State::ZeroRtt); + assert_eq!(*server.conn.state(), State::Init); + let out = server.conn.process(out.as_dgram_ref(), now()); + + // Check that control and qpack streams are received and a + // SETTINGS frame has been received. + // Also qpack encoder stream will send "change capacity" instruction because it has + // the peer settings already. + server.check_control_qpack_request_streams_resumption( + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + EXPECTED_REQUEST_HEADER_FRAME, + false, + ); + + assert_eq!(*server.conn.state(), State::Handshaking); + let out = client.process(out.as_dgram_ref(), now()); + assert_eq!(client.state(), Http3State::Connected); + + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + assert!(server.conn.state().connected()); + + assert!(client.tls_info().unwrap().resumed()); + assert!(server.conn.tls_info().unwrap().resumed()); + } + + #[test] + fn zero_rtt_send_request() { + let (mut client, mut server) = start_with_0rtt(); + + let request_stream_id = + make_request(&mut client, true, &[Header::new("myheaders", "myvalue")]); + assert_eq!(request_stream_id, 0); + + let out = client.process(None, now()); + + assert_eq!(client.state(), Http3State::ZeroRtt); + assert_eq!(*server.conn.state(), State::Init); + let out = server.conn.process(out.as_dgram_ref(), now()); + + // Check that control and qpack streams are received and a + // SETTINGS frame has been received. + // Also qpack encoder stream will send "change capacity" instruction because it has + // the peer settings already. + server.check_control_qpack_request_streams_resumption( + ENCODER_STREAM_DATA_WITH_CAP_INST_AND_ENCODING_INST, + EXPECTED_REQUEST_HEADER_FRAME_VERSION2, + true, + ); + + assert_eq!(*server.conn.state(), State::Handshaking); + let out = client.process(out.as_dgram_ref(), now()); + assert_eq!(client.state(), Http3State::Connected); + let out = server.conn.process(out.as_dgram_ref(), now()); + assert!(server.conn.state().connected()); + let out = client.process(out.as_dgram_ref(), now()); + assert!(out.as_dgram_ref().is_none()); + + // After the server has been connected, send a response. + let res = server.conn.stream_send(request_stream_id, HTTP_RESPONSE_2); + assert_eq!(res, Ok(HTTP_RESPONSE_2.len())); + server.conn.stream_close_send(request_stream_id).unwrap(); + + read_response(&mut client, &mut server.conn, request_stream_id); + + assert!(client.tls_info().unwrap().resumed()); + assert!(server.conn.tls_info().unwrap().resumed()); + } + + #[test] + fn zero_rtt_before_resumption_token() { + let mut client = default_http3_client(); + assert!(client + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default() + ) + .is_err()); + } + + #[test] + fn zero_rtt_send_reject() { + let (mut client, mut server) = connect(); + let token = exchange_token(&mut client, &mut server.conn); + + let mut client = default_http3_client(); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN_H3, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + ConnectionParameters::default(), + ) + .unwrap(); + // Using a freshly initialized anti-replay context + // should result in the server rejecting 0-RTT. + let ar = AntiReplay::new(now(), test_fixture::ANTI_REPLAY_WINDOW, 1, 3) + .expect("setup anti-replay"); + server + .server_enable_0rtt(&ar, AllowZeroRtt {}) + .expect("enable 0-RTT"); + + assert_eq!(client.state(), Http3State::Initializing); + client + .enable_resumption(now(), &token) + .expect("Set resumption token."); + let zerortt_event = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::ZeroRtt)); + assert!(client.events().any(zerortt_event)); + + // Send ClientHello. + let client_hs = client.process(None, now()); + assert!(client_hs.as_dgram_ref().is_some()); + + // Create a request + let request_stream_id = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id, 0); + + let client_0rtt = client.process(None, now()); + assert!(client_0rtt.as_dgram_ref().is_some()); + + let server_hs = server.process(client_hs.as_dgram_ref(), now()); + assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc... + let server_ignored = server.process(client_0rtt.as_dgram_ref(), now()); + assert!(server_ignored.as_dgram_ref().is_none()); + + // The server shouldn't receive that 0-RTT data. + let recvd_stream_evt = |e| matches!(e, ConnectionEvent::NewStream { .. }); + assert!(!server.events().any(recvd_stream_evt)); + + // Client should get a rejection. + let client_out = client.process(server_hs.as_dgram_ref(), now()); + assert!(client_out.as_dgram_ref().is_some()); + let recvd_0rtt_reject = |e| e == Http3ClientEvent::ZeroRttRejected; + assert!(client.events().any(recvd_0rtt_reject)); + + // ...and the client stream should be gone. + let res = client.stream_close_send(request_stream_id); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + // Client will send Setting frame and open new qpack streams. + mem::drop(server.process(client_out.as_dgram_ref(), now())); + TestServer::new_with_conn(server).check_client_control_qpack_streams_no_resumption(); + + // Check that we can send a request and that the stream_id starts again from 0. + assert_eq!(make_request(&mut client, false, &[]), 0); + } + + // Connect to a server, get token and reconnect using 0-rtt. Seerver sends new Settings. + fn zero_rtt_change_settings( + original_settings: &[HSetting], + resumption_settings: &[HSetting], + expected_client_state: &Http3State, + expected_encoder_stream_data: &[u8], + ) { + let mut client = default_http3_client(); + let mut server = TestServer::new_with_settings(original_settings); + // Connect and get a token + connect_with(&mut client, &mut server); + let token = exchange_token(&mut client, &mut server.conn); + + let mut client = default_http3_client(); + let mut server = TestServer::new_with_settings(resumption_settings); + assert_eq!(client.state(), Http3State::Initializing); + client + .enable_resumption(now(), &token) + .expect("Set resumption token."); + assert_eq!(client.state(), Http3State::ZeroRtt); + let out = client.process(None, now()); + + assert_eq!(client.state(), Http3State::ZeroRtt); + assert_eq!(*server.conn.state(), State::Init); + let out = server.conn.process(out.as_dgram_ref(), now()); + + // Check that control and qpack streams anda SETTINGS frame are received. + // Also qpack encoder stream will send "change capacity" instruction because it has + // the peer settings already. + server.check_control_qpack_request_streams_resumption( + expected_encoder_stream_data, + EXPECTED_REQUEST_HEADER_FRAME, + false, + ); + + assert_eq!(*server.conn.state(), State::Handshaking); + let out = client.process(out.as_dgram_ref(), now()); + assert_eq!(client.state(), Http3State::Connected); + + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + assert!(server.conn.state().connected()); + + assert!(client.tls_info().unwrap().resumed()); + assert!(server.conn.tls_info().unwrap().resumed()); + + // Send new settings. + let control_stream = server.conn.stream_create(StreamType::UniDi).unwrap(); + let mut enc = Encoder::default(); + server.settings.encode(&mut enc); + let mut sent = server.conn.stream_send(control_stream, CONTROL_STREAM_TYPE); + assert_eq!(sent.unwrap(), CONTROL_STREAM_TYPE.len()); + sent = server.conn.stream_send(control_stream, enc.as_ref()); + assert_eq!(sent.unwrap(), enc.len()); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + assert_eq!(&client.state(), expected_client_state); + assert!(server.conn.state().connected()); + } + + #[test] + fn zero_rtt_new_server_setting_are_the_same() { + // Send a new server settings that are the same as the old one. + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Connected, + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_omit_max_table() { + // Send a new server settings without MaxTableCapacity + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Closing(ConnectionError::Application(265)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_omit_blocked_streams() { + // Send a new server settings without BlockedStreams + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Closing(ConnectionError::Application(265)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_omit_header_list_size() { + // Send a new server settings without MaxHeaderListSize + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + ], + &Http3State::Connected, + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_max_table_size_bigger() { + // Send a new server settings MaxTableCapacity=200 + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 200), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Closing(ConnectionError::Application(514)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_max_table_size_smaller() { + // Send a new server settings MaxTableCapacity=50 + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 50), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Closing(ConnectionError::Application(265)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_blocked_streams_bigger() { + // Send a new server settings withBlockedStreams=200 + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 200), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Connected, + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_blocked_streams_smaller() { + // Send a new server settings withBlockedStreams=50 + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 50), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Closing(ConnectionError::Application(265)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_max_header_size_bigger() { + // Send a new server settings with MaxHeaderListSize=20000 + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 20000), + ], + &Http3State::Connected, + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_new_server_setting_max_headers_size_smaller() { + // Send the new server settings with MaxHeaderListSize=5000 + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 5000), + ], + &Http3State::Closing(ConnectionError::Application(265)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_max_table_size_first_omitted() { + // send server original settings without MaxTableCapacity + // send new server setting with MaxTableCapacity + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Connected, + ENCODER_STREAM_DATA, + ); + } + + #[test] + fn zero_rtt_blocked_streams_first_omitted() { + // Send server original settings without BlockedStreams + // Send the new server settings with BlockedStreams + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Connected, + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn zero_rtt_max_header_size_first_omitted() { + // Send server settings without MaxHeaderListSize + // Send new settings with MaxHeaderListSize. + zero_rtt_change_settings( + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 10000), + ], + &[ + HSetting::new(HSettingType::MaxTableCapacity, 100), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ], + &Http3State::Closing(ConnectionError::Application(265)), + ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION, + ); + } + + #[test] + fn test_trailers_with_fin_after_headers() { + // Make a new connection. + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send HEADER frame. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_HEADER_FRAME_0, + false, + ); + + // Check response headers. + let mut response_headers = false; + while let Some(e) = client.next_event() { + if let Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } = e + { + assert_eq!(stream_id, request_stream_id); + check_response_header_0(&headers); + assert!(!fin); + assert!(!interim); + response_headers = true; + } + } + assert!(response_headers); + + // Send trailers + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_HEADER_FRAME_0, + true, + ); + + let events: Vec<Http3ClientEvent> = client.events().collect(); + + // We already had HeaderReady + let header_ready: fn(&Http3ClientEvent) -> _ = + |e| matches!(*e, Http3ClientEvent::HeaderReady { .. }); + assert!(!events.iter().any(header_ready)); + + // Check that we have a DataReady event. Reading from the stream will return fin=true. + let data_readable: fn(&Http3ClientEvent) -> _ = + |e| matches!(*e, Http3ClientEvent::DataReadable { .. }); + assert!(events.iter().any(data_readable)); + let mut buf = [0_u8; 100]; + let (len, fin) = client + .read_data(now(), request_stream_id, &mut buf) + .unwrap(); + assert_eq!(0, len); + assert!(fin); + } + + #[test] + fn test_trailers_with_later_fin_after_headers() { + // Make a new connection. + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send HEADER frame. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_HEADER_FRAME_0, + false, + ); + + // Check response headers. + let mut response_headers = false; + while let Some(e) = client.next_event() { + if let Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } = e + { + assert_eq!(stream_id, request_stream_id); + check_response_header_0(&headers); + assert!(!fin); + assert!(!interim); + response_headers = true; + } + } + assert!(response_headers); + + // Send trailers + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_HEADER_FRAME_0, + false, + ); + + // Check that we do not have a DataReady event. + let data_readable = |e| matches!(e, Http3ClientEvent::DataReadable { .. }); + assert!(!client.events().any(data_readable)); + + server.conn.stream_close_send(request_stream_id).unwrap(); + + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + let events: Vec<Http3ClientEvent> = client.events().collect(); + + // We already had HeaderReady + let header_ready: fn(&Http3ClientEvent) -> _ = + |e| matches!(*e, Http3ClientEvent::HeaderReady { .. }); + assert!(!events.iter().any(header_ready)); + + // Check that we have a DataReady event. Reading from the stream will return fin=true. + let data_readable_fn: fn(&Http3ClientEvent) -> _ = + |e| matches!(*e, Http3ClientEvent::DataReadable { .. }); + assert!(events.iter().any(data_readable_fn)); + let mut buf = [0_u8; 100]; + let (len, fin) = client + .read_data(now(), request_stream_id, &mut buf) + .unwrap(); + assert_eq!(0, len); + assert!(fin); + } + + #[test] + fn test_data_after_trailers_after_headers() { + // Make a new connection. + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send HEADER frame. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_HEADER_FRAME_0, + false, + ); + + // Check response headers. + let mut response_headers = false; + while let Some(e) = client.next_event() { + if let Http3ClientEvent::HeaderReady { + stream_id, + headers, + interim, + fin, + } = e + { + assert_eq!(stream_id, request_stream_id); + check_response_header_0(&headers); + assert!(!fin); + assert!(!interim); + response_headers = true; + } + } + assert!(response_headers); + + // Send trailers + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_HEADER_FRAME_0, + false, + ); + + // Check that we do not have a DataReady event. + let data_readable = |e| matches!(e, Http3ClientEvent::DataReadable { .. }); + assert!(!client.events().any(data_readable)); + + // Send Data frame. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + [0x0, 0x3, 0x61, 0x62, 0x63], // a data frame + false, + ); + + assert_closed(&client, &Error::HttpFrameUnexpected); + } + + #[test] + fn transport_stream_readable_event_after_all_data() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(false); + + // Send headers. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + false, + ); + + // Send an empty data frame and a fin + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + [0x0, 0x0], + true, + ); + + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Ok((3, true)) + ); + + client.process(None, now()); + } + + #[test] + fn no_data_ready_events_after_fin() { + // Connect exchange headers and send a request. Also check if the correct header frame has + // been sent. + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // send response - 200 Content-Length: 7 + // with content: 'abcdefg'. + // The content will be send in 2 DATA frames. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_1, + true, + ); + + let data_readable_event = |e| matches!(e, Http3ClientEvent::DataReadable { stream_id } if stream_id == request_stream_id); + assert!(client.events().any(data_readable_event)); + + let mut buf = [0_u8; 100]; + assert_eq!( + (EXPECTED_RESPONSE_DATA_1.len(), true), + client + .read_data(now(), request_stream_id, &mut buf) + .unwrap() + ); + + assert!(!client.events().any(data_readable_event)); + } + + #[test] + fn reading_small_chunks_of_data() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // send response - 200 Content-Length: 7 + // with content: 'abcdefg'. + // The content will be send in 2 DATA frames. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_1, + true, + ); + + let data_readable_event = |e| matches!(e, Http3ClientEvent::DataReadable { stream_id } if stream_id == request_stream_id); + assert!(client.events().any(data_readable_event)); + + let mut buf1 = [0_u8; 1]; + assert_eq!( + (1, false), + client + .read_data(now(), request_stream_id, &mut buf1) + .unwrap() + ); + assert!(!client.events().any(data_readable_event)); + + // Now read only until the end of the first frame. The firs framee has 3 bytes. + let mut buf2 = [0_u8; 2]; + assert_eq!( + (2, false), + client + .read_data(now(), request_stream_id, &mut buf2) + .unwrap() + ); + assert!(!client.events().any(data_readable_event)); + + // Read a half of the second frame. + assert_eq!( + (2, false), + client + .read_data(now(), request_stream_id, &mut buf2) + .unwrap() + ); + assert!(!client.events().any(data_readable_event)); + + // Read the rest. + // Read a half of the second frame. + assert_eq!( + (2, true), + client + .read_data(now(), request_stream_id, &mut buf2) + .unwrap() + ); + assert!(!client.events().any(data_readable_event)); + } + + #[test] + fn zero_length_data_at_end() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // send response - 200 Content-Length: 7 + // with content: 'abcdefg'. + // The content will be send in 2 DATA frames. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_1, + false, + ); + // Send a zero-length frame at the end of the stream. + _ = server.conn.stream_send(request_stream_id, &[0, 0]).unwrap(); + server.conn.stream_close_send(request_stream_id).unwrap(); + let dgram = server.conn.process_output(now()).dgram(); + client.process_input(&dgram.unwrap(), now()); + + let data_readable_event = |e: &_| matches!(e, Http3ClientEvent::DataReadable { stream_id } if *stream_id == request_stream_id); + assert_eq!(client.events().filter(data_readable_event).count(), 1); + + let mut buf = [0_u8; 10]; + assert_eq!( + (7, true), + client + .read_data(now(), request_stream_id, &mut buf) + .unwrap() + ); + assert!(!client.events().any(|e| data_readable_event(&e))); + } + + #[test] + fn stream_blocked_no_remote_encoder_stream() { + let (mut client, mut server) = connect_only_transport(); + + send_and_receive_client_settings(&mut client, &mut server); + + server.create_control_stream(); + // Send the server's control stream data. + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + server.create_qpack_streams(); + let qpack_pkt1 = server.conn.process(None, now()); + // delay delivery of this packet. + + let request_stream_id = make_request(&mut client, true, &[]); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + setup_server_side_encoder(&mut client, &mut server); + + let headers = vec![ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Send the encoder instructions, + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Send response + let mut d = Encoder::default(); + hframe.encode(&mut d); + let d_frame = HFrame::Data { len: 3 }; + d_frame.encode(&mut d); + d.encode(&[0x61, 0x62, 0x63]); + _ = server + .conn + .stream_send(request_stream_id, d.as_ref()) + .unwrap(); + server.conn.stream_close_send(request_stream_id).unwrap(); + + let out = server.conn.process(None, now()); + mem::drop(client.process(out.as_dgram_ref(), now())); + + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(!client.events().any(header_ready_event)); + + // Let client receive the encoder instructions. + mem::drop(client.process(qpack_pkt1.as_dgram_ref(), now())); + + assert!(client.events().any(header_ready_event)); + } + + // Client: receive a push stream + #[test] + fn push_single() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send a push promise. + send_push_promise(&mut server.conn, request_stream_id, 0); + + // create a push stream. + _ = send_push_data(&mut server.conn, 0, true); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }], + &[0], + request_stream_id, + ); + + assert_eq!(client.state(), Http3State::Connected); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + } + + /// We can't keep the connection alive on the basis of a push promise, + /// nor do we want to if the push promise is not interesting to the client. + /// We do the next best thing, which is keep any push stream alive if the + /// client reads from it. + #[test] + fn push_keep_alive() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + let idle_timeout = ConnectionParameters::default().get_idle_timeout(); + + // Promise a push and deliver, but don't close the stream. + send_push_promise(&mut server.conn, request_stream_id, 0); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }], + &[], // No push streams yet. + request_stream_id, + ); + + // The client will become idle here. + force_idle(&mut client, &mut server); + assert_eq!(client.process_output(now()).callback(), idle_timeout); + + // Reading push data will stop the client from being idle. + _ = send_push_data(&mut server.conn, 0, false); + let out = server.conn.process_output(now()); + client.process_input(out.as_dgram_ref().unwrap(), now()); + + let mut buf = [0; 16]; + let (read, fin) = client.push_read_data(now(), 0, &mut buf).unwrap(); + assert!(read < buf.len()); + assert!(!fin); + + force_idle(&mut client, &mut server); + assert_eq!(client.process_output(now()).callback(), idle_timeout / 2); + } + + #[test] + fn push_multiple() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send a push promise. + send_push_promise(&mut server.conn, request_stream_id, 0); + send_push_promise(&mut server.conn, request_stream_id, 1); + + // create a push stream. + _ = send_push_data(&mut server.conn, 0, true); + + // create a second push stream. + _ = send_push_data(&mut server.conn, 1, true); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + read_response_and_push_events( + &mut client, + &[ + PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 1, + ref_stream_id: request_stream_id, + }, + ], + &[0, 1], + request_stream_id, + ); + + assert_eq!(client.state(), Http3State::Connected); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + assert_eq!(client.cancel_push(1), Err(Error::InvalidStreamId)); + } + + #[test] + fn push_after_headers() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send response headers + _ = server + .conn + .stream_send(request_stream_id, HTTP_RESPONSE_HEADER_ONLY_2) + .unwrap(); + + // Send a push promise. + send_push_promise(&mut server.conn, request_stream_id, 0); + + // create a push stream. + _ = send_push_data(&mut server.conn, 0, true); + + // Send response data + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_DATA_FRAME_ONLY_2, + true, + ); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }], + &[0], + request_stream_id, + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + #[test] + fn push_after_response() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send response headers and data frames + _ = server + .conn + .stream_send(request_stream_id, HTTP_RESPONSE_2) + .unwrap(); + + // Send a push promise. + send_push_promise(&mut server.conn, request_stream_id, 0); + // create a push stream. + send_push_data_and_exchange_packets(&mut client, &mut server, 0, true); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }], + &[0], + request_stream_id, + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + fn check_push_events(client: &mut Http3Client) -> bool { + let any_push_event = |e| { + matches!( + e, + Http3ClientEvent::PushPromise { .. } + | Http3ClientEvent::PushHeaderReady { .. } + | Http3ClientEvent::PushDataReadable { .. } + ) + }; + client.events().any(any_push_event) + } + + fn check_data_readable(client: &mut Http3Client) -> bool { + let any_data_event = |e| matches!(e, Http3ClientEvent::DataReadable { .. }); + client.events().any(any_data_event) + } + + fn check_header_ready(client: &mut Http3Client) -> bool { + let any_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + client.events().any(any_event) + } + + fn check_header_ready_and_push_promise(client: &mut Http3Client) -> bool { + let any_event = |e| { + matches!( + e, + Http3ClientEvent::HeaderReady { .. } | Http3ClientEvent::PushPromise { .. } + ) + }; + client.events().any(any_event) + } + + #[test] + fn push_stream_before_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // create a push stream. + send_push_data_and_exchange_packets(&mut client, &mut server, 0, true); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Now send push_promise + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 0); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }], + &[0], + request_stream_id, + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test receiving pushes out of order. + // Push_id 5 is received first, therefore Push_id 3 will be in the PushState:Init state. + // Start push_id 3 by receiving a push_promise and then a push stream with the push_id 3. + #[test] + fn push_out_of_order_1() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 3); + // Start a push stream with push_id 3. + send_push_data_and_exchange_packets(&mut client, &mut server, 3, true); + + assert_eq!(client.state(), Http3State::Connected); + + read_response_and_push_events( + &mut client, + &[ + PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 3, + ref_stream_id: request_stream_id, + }, + ], + &[3], + request_stream_id, + ); + assert_eq!(client.state(), Http3State::Connected); + } + + // Test receiving pushes out of order. + // Push_id 5 is received first, therefore Push_id 3 will be in the PushState:Init state. + // Start push_id 3 by receiving a push stream with push_id 3 and then a push_promise. + #[test] + fn push_out_of_order_2() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5); + + send_push_data_and_exchange_packets(&mut client, &mut server, 3, true); + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 3); + + read_response_and_push_events( + &mut client, + &[ + PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 3, + ref_stream_id: request_stream_id, + }, + ], + &[3], + request_stream_id, + ); + assert_eq!(client.state(), Http3State::Connected); + } + + // Test receiving pushes out of order. + // Push_id 5 is received first and read so that it is removed from the list, + // therefore Push_id 3 will be in the PushState:Init state. + // Start push_id 3 by receiving a push stream with the push_id 3 and then a push_promise. + #[test] + fn push_out_of_order_3() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5); + send_push_data_and_exchange_packets(&mut client, &mut server, 5, true); + assert_eq!(client.state(), Http3State::Connected); + + // Read push stream with push_id 5 to make it change to closed state. + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id, + }], + &[5], + request_stream_id, + ); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 3); + send_push_data_and_exchange_packets(&mut client, &mut server, 3, true); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 3, + ref_stream_id: request_stream_id, + }], + &[3], + request_stream_id, + ); + assert_eq!(client.state(), Http3State::Connected); + } + + // The next test is for receiving a second PushPromise when Push is in the PushPromise state. + #[test] + fn multiple_push_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5); + + // make a second request. + let request_stream_id_2 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_2, 4); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id_2, 5); + + read_response_and_push_events( + &mut client, + &[ + PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id_2, + }, + ], + &[], + request_stream_id, + ); + assert_eq!(client.state(), Http3State::Connected); + } + + // The next test is for receiving a second PushPromise when Push is in the Active state. + #[test] + fn multiple_push_promise_active() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5); + send_push_data_and_exchange_packets(&mut client, &mut server, 5, true); + + // make a second request. + let request_stream_id_2 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_2, 4); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id_2, 5); + + read_response_and_push_events( + &mut client, + &[ + PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id_2, + }, + ], + &[5], + request_stream_id, + ); + assert_eq!(client.state(), Http3State::Connected); + } + + // The next test is for receiving a second PushPromise when the push is already closed. + // PushPromise will be ignored for the push streams that are consumed. + #[test] + fn multiple_push_promise_closed() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5); + // Start a push stream with push_id 5. + send_push_data_and_exchange_packets(&mut client, &mut server, 5, true); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 5, + ref_stream_id: request_stream_id, + }], + &[5], + request_stream_id, + ); + + // make a second request. + let request_stream_id_2 = make_request(&mut client, false, &[]); + assert_eq!(request_stream_id_2, 4); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id_2, 5); + + // Check that we do not have a Http3ClientEvent::PushPromise. + let push_event = |e| matches!(e, Http3ClientEvent::PushPromise { .. }); + assert!(!client.events().any(push_event)); + } + + // Test that max_push_id is enforced when a push promise frame is received. + #[test] + fn exceed_max_push_id_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send a push promise. max_push_id is set to 5, to trigger an error we send push_id=6. + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 6); + + assert_closed(&client, &Error::HttpId); + } + + // Test that max_push_id is enforced when a push stream is received. + #[test] + fn exceed_max_push_id_push_stream() { + // Connect and send a request + let (mut client, mut server) = connect(); + + // Send a push stream. max_push_id is set to 5, to trigger an error we send push_id=6. + send_push_data_and_exchange_packets(&mut client, &mut server, 6, true); + + assert_closed(&client, &Error::HttpId); + } + + // Test that max_push_id is enforced when a cancel push frame is received. + #[test] + fn exceed_max_push_id_cancel_push() { + // Connect and send a request + let (mut client, mut server, _request_stream_id) = connect_and_send_request(true); + + // Send CANCEL_PUSH for push_id 6. + send_cancel_push_and_exchange_packets(&mut client, &mut server, 6); + + assert_closed(&client, &Error::HttpId); + } + + // Test that max_push_id is enforced when an app calls cancel_push. + #[test] + fn exceed_max_push_id_cancel_api() { + // Connect and send a request + let (mut client, _, _) = connect_and_send_request(true); + + assert_eq!(client.cancel_push(6), Err(Error::HttpId)); + assert_eq!(client.state(), Http3State::Connected); + } + + #[test] + fn test_max_push_id_frame_update_is_sent() { + const MAX_PUSH_ID_FRAME: &[u8] = &[0xd, 0x1, 0x8]; + + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send 3 push promises. + send_push_promise(&mut server.conn, request_stream_id, 0); + send_push_promise(&mut server.conn, request_stream_id, 1); + send_push_promise(&mut server.conn, request_stream_id, 2); + + // create 3 push streams. + send_push_data(&mut server.conn, 0, true); + send_push_data(&mut server.conn, 1, true); + send_push_data_and_exchange_packets(&mut client, &mut server, 2, true); + + read_response_and_push_events( + &mut client, + &[ + PushPromiseInfo { + push_id: 0, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 1, + ref_stream_id: request_stream_id, + }, + PushPromiseInfo { + push_id: 2, + ref_stream_id: request_stream_id, + }, + ], + &[0, 1, 2], + request_stream_id, + ); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // Check max_push_id frame has been received + let control_stream_readable = + |e| matches!(e, ConnectionEvent::RecvStreamReadable{stream_id: x} if x == 2); + assert!(server.conn.events().any(control_stream_readable)); + let mut buf = [0_u8; 100]; + let (amount, fin) = server.conn.stream_recv(StreamId::new(2), &mut buf).unwrap(); + assert!(!fin); + + assert_eq!(amount, MAX_PUSH_ID_FRAME.len()); + assert_eq!(&buf[..3], MAX_PUSH_ID_FRAME); + + // Check that we can send push_id=8 now + send_push_promise(&mut server.conn, request_stream_id, 8); + send_push_data(&mut server.conn, 8, true); + + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + assert_eq!(client.state(), Http3State::Connected); + + read_response_and_push_events( + &mut client, + &[PushPromiseInfo { + push_id: 8, + ref_stream_id: request_stream_id, + }], + &[8], + request_stream_id, + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test that 2 push streams with the same push_id are caught. + #[test] + fn duplicate_push_stream() { + // Connect and send a request + let (mut client, mut server, _request_stream_id) = connect_and_send_request(true); + + // Start a push stream with push_id 0. + send_push_data_and_exchange_packets(&mut client, &mut server, 0, true); + + // Send it again + send_push_data_and_exchange_packets(&mut client, &mut server, 0, true); + + assert_closed(&client, &Error::HttpId); + } + + // Test that 2 push streams with the same push_id are caught. + #[test] + fn duplicate_push_stream_active() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise(&mut server.conn, request_stream_id, 0); + send_push_data_and_exchange_packets(&mut client, &mut server, 0, true); + // Now the push_stream is in the PushState::Active state + + send_push_data_and_exchange_packets(&mut client, &mut server, 0, true); + + assert_closed(&client, &Error::HttpId); + } + + fn assert_stop_sending_event( + server: &mut TestServer, + push_stream_id: StreamId, + expected_error: u64, + ) { + assert!(server.conn.events().any(|e| matches!( + e, + ConnectionEvent::SendStreamStopSending { + stream_id, + app_error, + } if stream_id == push_stream_id && app_error == expected_error + ))); + } + + // Test CANCEL_PUSH frame: after cancel push any new PUSH_PROMISE or push stream will be + // ignored. + #[test] + fn cancel_push_ignore_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_cancel_push_and_exchange_packets(&mut client, &mut server, 0); + + send_push_promise(&mut server.conn, request_stream_id, 0); + // Start a push stream with push_id 0. + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + // Check that the push has been canceled by the client. + assert_stop_sending_event( + &mut server, + push_stream_id, + Error::HttpRequestCancelled.code(), + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test CANCEL_PUSH frame: after cancel push any already received PUSH_PROMISE or push stream + // events will be removed. + #[test] + fn cancel_push_removes_push_events() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise(&mut server.conn, request_stream_id, 0); + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + send_cancel_push_and_exchange_packets(&mut client, &mut server, 0); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + // Check that the push has been canceled by the client. + assert_stop_sending_event( + &mut server, + push_stream_id, + Error::HttpRequestCancelled.code(), + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test CANCEL_PUSH frame: after cancel push any already received push stream will be canceled. + #[test] + fn cancel_push_frame_after_push_stream() { + // Connect and send a request + let (mut client, mut server, _) = connect_and_send_request(true); + + // Start a push stream with push_id 0. + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + send_cancel_push_and_exchange_packets(&mut client, &mut server, 0); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + // Check that the push has been canceled by the client. + assert_stop_sending_event( + &mut server, + push_stream_id, + Error::HttpRequestCancelled.code(), + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test a push stream reset after a new PUSH_PROMISE or/and push stream. The events will be + // ignored. + #[test] + fn cancel_push_stream_after_push_promise_and_push_stream() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise(&mut server.conn, request_stream_id, 0); + // Start a push stream with push_id 0. + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + server + .conn + .stream_reset_send(push_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test that a PUSH_PROMISE will be ignored after a push stream reset. + #[test] + fn cancel_push_stream_before_push_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Start a push stream with push_id 0. + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + server + .conn + .stream_reset_send(push_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 0); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test that push_promise events will be removed after application calls cancel_push. + #[test] + fn app_cancel_push_after_push_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 0); + + assert!(client.cancel_push(0).is_ok()); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test that push_promise and push data events will be removed after application calls + // cancel_push. + #[test] + fn app_cancel_push_after_push_promise_and_push_stream() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 0); + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + assert!(client.cancel_push(0).is_ok()); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + // Check that the push has been canceled by the client. + assert_stop_sending_event( + &mut server, + push_stream_id, + Error::HttpRequestCancelled.code(), + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + // Test that push_promise events will be ignored after application calls cancel_push. + #[test] + fn app_cancel_push_before_push_promise() { + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 0); + let push_stream_id = + send_push_data_and_exchange_packets(&mut client, &mut server, 0, false); + + assert!(client.cancel_push(0).is_ok()); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 0); + + // Assert that we do not have any push event. + assert!(!check_push_events(&mut client)); + + // Check that the push has been closed, e.g. calling cancel_push should return + // InvalidStreamId. + assert_eq!(client.cancel_push(0), Err(Error::InvalidStreamId)); + + // Check that the push has been canceled by the client. + assert_stop_sending_event( + &mut server, + push_stream_id, + Error::HttpRequestCancelled.code(), + ); + + assert_eq!(client.state(), Http3State::Connected); + } + + fn setup_server_side_encoder_param( + client: &mut Http3Client, + server: &mut TestServer, + max_blocked_streams: u64, + ) { + server + .encoder + .borrow_mut() + .set_max_capacity(max_blocked_streams) + .unwrap(); + server + .encoder + .borrow_mut() + .set_max_blocked_streams(100) + .unwrap(); + server + .encoder + .borrow_mut() + .send_encoder_updates(&mut server.conn) + .unwrap(); + let out = server.conn.process(None, now()); + mem::drop(client.process(out.as_dgram_ref(), now())); + } + + fn setup_server_side_encoder(client: &mut Http3Client, server: &mut TestServer) { + setup_server_side_encoder_param(client, server, 100); + } + + fn send_push_promise_using_encoder( + client: &mut Http3Client, + server: &mut TestServer, + stream_id: StreamId, + push_id: u64, + ) -> Option<Datagram> { + send_push_promise_using_encoder_with_custom_headers( + client, + server, + stream_id, + push_id, + Header::new("my-header", "my-value"), + ) + } + + fn send_push_promise_using_encoder_with_custom_headers( + client: &mut Http3Client, + server: &mut TestServer, + stream_id: StreamId, + push_id: u64, + additional_header: Header, + ) -> Option<Datagram> { + let mut headers = vec![ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/"), + Header::new("content-length", "3"), + ]; + headers.push(additional_header); + + let encoded_headers = + server + .encoder + .borrow_mut() + .encode_header_block(&mut server.conn, &headers, stream_id); + let push_promise_frame = HFrame::PushPromise { + push_id, + header_block: encoded_headers.to_vec(), + }; + + // Send the encoder instructions, but delay them so that the stream is blocked on decoding + // headers. + let encoder_inst_pkt = server.conn.process(None, now()).dgram(); + assert!(encoder_inst_pkt.is_some()); + + let mut d = Encoder::default(); + push_promise_frame.encode(&mut d); + server_send_response_and_exchange_packet(client, server, stream_id, &d, false); + + encoder_inst_pkt + } + + #[test] + fn push_promise_header_decoder_block() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let encoder_inst_pkt = + send_push_promise_using_encoder(&mut client, &mut server, request_stream_id, 0); + + // PushPromise is blocked wathing for encoder instructions. + assert!(!check_push_events(&mut client)); + + // Let client receive the encoder instructions. + let _out = client.process(encoder_inst_pkt.as_ref(), now()); + + // PushPromise is blocked wathing for encoder instructions. + assert!(check_push_events(&mut client)); + } + + // If PushPromise is blocked, stream data can still be received. + #[test] + fn push_promise_blocked_but_stream_is_not_blocked() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + // Send response headers + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_HEADER_ONLY_1, + false, + ); + + let encoder_inst_pkt = + send_push_promise_using_encoder(&mut client, &mut server, request_stream_id, 0); + + // PushPromise is blocked wathing for encoder instructions. + assert!(!check_push_events(&mut client)); + + // Stream data can be still read + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_DATA_FRAME_1_ONLY_1, + false, + ); + + assert!(check_data_readable(&mut client)); + + // Let client receive the encoder instructions. + let _out = client.process(encoder_inst_pkt.as_ref(), now()); + + // PushPromise is blocked wathing for encoder instructions. + assert!(check_push_events(&mut client)); + + // Stream data can be still read + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_DATA_FRAME_2_ONLY_1, + false, + ); + + assert!(check_data_readable(&mut client)); + } + + // The response Headers are not block if they do not refer the dynamic table. + #[test] + fn push_promise_does_not_block_headers() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let encoder_inst_pkt = + send_push_promise_using_encoder(&mut client, &mut server, request_stream_id, 0); + + // PushPromise is blocked wathing for encoder instructions. + assert!(!check_push_events(&mut client)); + + // Send response headers + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_HEADER_ONLY_1, + false, + ); + + assert!(check_header_ready(&mut client)); + + // Let client receive the encoder instructions. + let _out = client.process(encoder_inst_pkt.as_ref(), now()); + + // PushPromise is blocked wathing for encoder instructions. + assert!(check_push_events(&mut client)); + } + + // The response Headers are blocked if they refer a dynamic table entry. + #[test] + fn push_promise_block_headers() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + // Insert an elemet into a dynamic table. + // insert "content-length: 1234 + server + .encoder + .borrow_mut() + .send_and_insert(&mut server.conn, b"content-length", b"1234") + .unwrap(); + let encoder_inst_pkt1 = server.conn.process(None, now()).dgram(); + let _out = client.process(encoder_inst_pkt1.as_ref(), now()); + + // Send a PushPromise that is blocked until encoder_inst_pkt2 is process by the client. + let encoder_inst_pkt2 = + send_push_promise_using_encoder(&mut client, &mut server, request_stream_id, 0); + + // PushPromise is blocked wathing for encoder instructions. + assert!(!check_push_events(&mut client)); + + let response_headers = vec![ + Header::new(":status", "200"), + Header::new("content-length", "1234"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &response_headers, + request_stream_id, + ); + let header_hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + let mut d = Encoder::default(); + header_hframe.encode(&mut d); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // The response headers are blocked. + assert!(!check_header_ready(&mut client)); + + // Let client receive the encoder instructions. + let _out = client.process(encoder_inst_pkt2.as_ref(), now()); + + // The response headers are blocked. + assert!(check_header_ready_and_push_promise(&mut client)); + } + + // In this test there are 2 push promises that are blocked and the response header is + // blocked as well. After a packet is received only the first push promises is unblocked. + #[test] + fn two_push_promises_and_header_block() { + let mut client = default_http3_client_param(200); + let mut server = TestServer::new_with_settings(&[ + HSetting::new(HSettingType::MaxTableCapacity, 200), + HSetting::new(HSettingType::BlockedStreams, 100), + HSetting::new(HSettingType::MaxHeaderListSize, 10000), + ]); + connect_only_transport_with(&mut client, &mut server); + server.create_control_stream(); + server.create_qpack_streams(); + setup_server_side_encoder_param(&mut client, &mut server, 200); + + let request_stream_id = make_request_and_exchange_pkts(&mut client, &mut server, true); + + // Send a PushPromise that is blocked until encoder_inst_pkt2 is process by the client. + let encoder_inst_pkt1 = send_push_promise_using_encoder_with_custom_headers( + &mut client, + &mut server, + request_stream_id, + 0, + Header::new("myn1", "myv1"), + ); + + // PushPromise is blocked wathing for encoder instructions. + assert!(!check_push_events(&mut client)); + + let encoder_inst_pkt2 = send_push_promise_using_encoder_with_custom_headers( + &mut client, + &mut server, + request_stream_id, + 1, + Header::new("myn2", "myv2"), + ); + + // PushPromise is blocked wathing for encoder instructions. + assert!(!check_push_events(&mut client)); + + let response_headers = vec![ + Header::new(":status", "200"), + Header::new("content-length", "1234"), + Header::new("myn3", "myv3"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &response_headers, + request_stream_id, + ); + let header_hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + let mut d = Encoder::default(); + header_hframe.encode(&mut d); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // The response headers are blocked. + assert!(!check_header_ready(&mut client)); + + // Let client receive the encoder instructions. + let _out = client.process(encoder_inst_pkt1.as_ref(), now()); + + assert!(check_push_events(&mut client)); + + // Let client receive the encoder instructions. + let _out = client.process(encoder_inst_pkt2.as_ref(), now()); + + assert!(check_header_ready_and_push_promise(&mut client)); + } + + // The PushPromise blocked on header decoding will be canceled if the stream is closed. + #[test] + fn blocked_push_promises_canceled() { + const STREAM_CANCELED_ID_0: &[u8] = &[0x40]; + + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + mem::drop( + send_push_promise_using_encoder(&mut client, &mut server, request_stream_id, 0) + .unwrap(), + ); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_1, + true, + ); + + // Read response that will make stream change to closed state. + assert!(check_header_ready(&mut client)); + let mut buf = [0_u8; 100]; + _ = client + .read_data(now(), request_stream_id, &mut buf) + .unwrap(); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + // Check that encoder got stream_canceled instruction. + let mut inst = [0_u8; 100]; + let (amount, fin) = server + .conn + .stream_recv(CLIENT_SIDE_DECODER_STREAM_ID, &mut inst) + .unwrap(); + assert!(!fin); + assert_eq!(amount, STREAM_CANCELED_ID_0.len()); + assert_eq!(&inst[..amount], STREAM_CANCELED_ID_0); + } + + #[test] + fn data_readable_in_decoder_blocked_state() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let headers = vec![ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "0"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Delay encoder instruction so that the stream will be blocked. + let encoder_insts = server.conn.process(None, now()); + + // Send response headers. + let mut d = Encoder::default(); + hframe.encode(&mut d); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // Headers are blocked waiting fro the encoder instructions. + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(!client.events().any(header_ready_event)); + + // Now send data frame. This will trigger DataRead event. + let mut d = Encoder::default(); + hframe.encode(&mut d); + let d_frame = HFrame::Data { len: 0 }; + d_frame.encode(&mut d); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + true, + ); + + // Now read headers. + mem::drop(client.process(encoder_insts.as_dgram_ref(), now())); + } + + #[test] + fn qpack_stream_reset() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + setup_server_side_encoder(&mut client, &mut server); + // Cancel request. + mem::drop(client.cancel_fetch(request_stream_id, Error::HttpRequestCancelled.code())); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + mem::drop(server.encoder_receiver.receive(&mut server.conn)); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 1); + } + + fn send_headers_using_encoder( + client: &mut Http3Client, + server: &mut TestServer, + request_stream_id: StreamId, + headers: &[Header], + data: &[u8], + ) -> Option<Datagram> { + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + let out = server.conn.process(None, now()); + + // Send response + let mut d = Encoder::default(); + hframe.encode(&mut d); + let d_frame = HFrame::Data { + len: u64::try_from(data.len()).unwrap(), + }; + d_frame.encode(&mut d); + d.encode(data); + server_send_response_and_exchange_packet(client, server, request_stream_id, &d, true); + + out.dgram() + } + + #[test] + fn qpack_stream_reset_recv() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + setup_server_side_encoder(&mut client, &mut server); + + // Cancel request. + server + .conn + .stream_reset_send(request_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + let out = server.conn.process(None, now()); + let out = client.process(out.as_dgram_ref(), now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + mem::drop(server.encoder_receiver.receive(&mut server.conn)); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 1); + } + + #[test] + fn qpack_stream_reset_during_header_qpack_blocked() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + mem::drop( + send_headers_using_encoder( + &mut client, + &mut server, + request_stream_id, + &[ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ], + &[0x61, 0x62, 0x63], + ) + .unwrap(), + ); + + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(!client.events().any(header_ready_event)); + + // Cancel request. + client + .cancel_fetch(request_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + mem::drop(server.encoder_receiver.receive(&mut server.conn).unwrap()); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 1); + } + + #[test] + fn qpack_no_stream_cancelled_after_fin() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let encoder_instruct = send_headers_using_encoder( + &mut client, + &mut server, + request_stream_id, + &[ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ], + &[], + ); + + // Exchange encoder instructions + mem::drop(client.process(encoder_instruct.as_ref(), now())); + + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(client.events().any(header_ready_event)); + // After this the recv_stream is in ClosePending state + + // Cancel request. + client + .cancel_fetch(request_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + mem::drop(server.encoder_receiver.receive(&mut server.conn).unwrap()); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + } + + #[test] + fn qpack_stream_reset_push_promise_header_decoder_block() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let headers = vec![ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Send the encoder instructions. + let out = server.conn.process(None, now()); + mem::drop(client.process(out.as_dgram_ref(), now())); + + // Send PushPromise that will be blocked waiting for decoder instructions. + mem::drop( + send_push_promise_using_encoder(&mut client, &mut server, request_stream_id, 0) + .unwrap(), + ); + + // Send response + let mut d = Encoder::default(); + hframe.encode(&mut d); + let d_frame = HFrame::Data { len: 0 }; + d_frame.encode(&mut d); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + true, + ); + + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(client.events().any(header_ready_event)); + + // Cancel request. + client + .cancel_fetch(request_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + mem::drop(server.encoder_receiver.receive(&mut server.conn).unwrap()); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 1); + } + + #[test] + fn qpack_stream_reset_dynamic_table_zero() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + // Cancel request. + client + .cancel_fetch(request_stream_id, Error::HttpRequestCancelled.code()) + .unwrap(); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + mem::drop(server.encoder_receiver.receive(&mut server.conn).unwrap()); + assert_eq!(server.encoder.borrow_mut().stats().stream_cancelled_recv, 0); + } + + #[test] + fn multiple_streams_in_decoder_blocked_state() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let headers = vec![ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "0"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Delay encoder instruction so that the stream will be blocked. + let encoder_insts = server.conn.process(None, now()); + + // Send response headers. + let mut d = Encoder::default(); + hframe.encode(&mut d); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + true, + ); + + // Headers are blocked waiting for the encoder instructions. + let header_ready_event = |e| matches!(e, Http3ClientEvent::HeaderReady { .. }); + assert!(!client.events().any(header_ready_event)); + + // Make another request. + let request2 = make_request_and_exchange_pkts(&mut client, &mut server, true); + // Send response headers. + server_send_response_and_exchange_packet(&mut client, &mut server, request2, &d, true); + + // Headers on the second request are blocked as well are blocked + // waiting for the encoder instructions. + assert!(!client.events().any(header_ready_event)); + + // Now make the encoder instructions available. + mem::drop(client.process(encoder_insts.as_dgram_ref(), now())); + + // Header blocks for both streams should be ready. + let mut count_responses = 0; + while let Some(e) = client.next_event() { + if let Http3ClientEvent::HeaderReady { stream_id, .. } = e { + assert!((stream_id == request_stream_id) || (stream_id == request2)); + count_responses += 1; + } + } + assert_eq!(count_responses, 2); + } + + #[test] + fn reserved_frames() { + for f in H3_RESERVED_FRAME_TYPES { + let mut enc = Encoder::default(); + enc.encode_varint(*f); + test_wrong_frame_on_control_stream(enc.as_ref()); + test_wrong_frame_on_push_stream(enc.as_ref()); + test_wrong_frame_on_request_stream(enc.as_ref()); + } + } + + #[test] + fn send_reserved_settings() { + for s in H3_RESERVED_SETTINGS { + let (mut client, mut server) = connect_only_transport(); + let control_stream = server.conn.stream_create(StreamType::UniDi).unwrap(); + // Send the control stream type(0x0). + _ = server + .conn + .stream_send(control_stream, CONTROL_STREAM_TYPE) + .unwrap(); + // Create a settings frame of length 2. + let mut enc = Encoder::default(); + enc.encode_varint(H3_FRAME_TYPE_SETTINGS); + enc.encode_varint(2_u64); + // The settings frame contains a reserved settings type and some value (0x1). + enc.encode_varint(*s); + enc.encode_varint(1_u64); + let sent = server.conn.stream_send(control_stream, enc.as_ref()); + assert_eq!(sent, Ok(4)); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpSettings); + } + } + + #[test] + fn response_w_1xx() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let mut d = Encoder::default(); + let headers1xx: &[Header] = &[Header::new(":status", "103")]; + server.encode_headers(request_stream_id, headers1xx, &mut d); + + let headers200: &[Header] = &[ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + server.encode_headers(request_stream_id, headers200, &mut d); + + // Send 1xx and 200 headers response. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // Sending response data. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_DATA_FRAME_ONLY_2, + true, + ); + + let mut events = client.events().filter_map(|e| { + if let Http3ClientEvent::HeaderReady { + stream_id, + interim, + headers, + .. + } = e + { + Some((stream_id, interim, headers)) + } else { + None + } + }); + let (stream_id_1xx_rec, interim1xx_rec, headers1xx_rec) = events.next().unwrap(); + assert_eq!( + (stream_id_1xx_rec, interim1xx_rec, headers1xx_rec.as_ref()), + (request_stream_id, true, headers1xx) + ); + + let (stream_id_200_rec, interim200_rec, headers200_rec) = events.next().unwrap(); + assert_eq!( + (stream_id_200_rec, interim200_rec, headers200_rec.as_ref()), + (request_stream_id, false, headers200) + ); + assert!(events.next().is_none()); + } + + #[test] + fn response_wo_status() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let mut d = Encoder::default(); + let headers = vec![ + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + server.encode_headers(request_stream_id, &headers, &mut d); + + // Send response + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // Stream has been reset because of the malformed headers. + let e = client.events().next().unwrap(); + assert_eq!( + e, + Http3ClientEvent::Reset { + stream_id: request_stream_id, + error: Error::InvalidHeader.code(), + local: true, + } + ); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // Check that server has received a reset. + let stop_sending_event = |e| { + matches!(e, ConnectionEvent::SendStreamStopSending { + stream_id, + app_error + } if stream_id == request_stream_id && app_error == Error::InvalidHeader.code()) + }; + assert!(server.conn.events().any(stop_sending_event)); + + // Stream should now be closed and gone + let mut buf = [0_u8; 100]; + assert_eq!( + client.read_data(now(), StreamId::new(0), &mut buf), + Err(Error::InvalidStreamId) + ); + } + + // Client: receive a push stream + #[test] + fn push_single_with_1xx() { + const FIRST_PUSH_ID: u64 = 0; + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send a push promise. + send_push_promise(&mut server.conn, request_stream_id, FIRST_PUSH_ID); + // Create a push stream + let push_stream_id = server.conn.stream_create(StreamType::UniDi).unwrap(); + + let mut d = Encoder::default(); + let headers1xx: &[Header] = &[Header::new(":status", "100")]; + server.encode_headers(push_stream_id, headers1xx, &mut d); + + let headers200: &[Header] = &[ + Header::new(":status", "200"), + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + server.encode_headers(push_stream_id, headers200, &mut d); + + // create a push stream. + send_data_on_push( + &mut server.conn, + push_stream_id, + u8::try_from(FIRST_PUSH_ID).unwrap(), + &d, + true, + ); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + let mut events = client.events().filter_map(|e| { + if let Http3ClientEvent::PushHeaderReady { + push_id, + interim, + headers, + .. + } = e + { + Some((push_id, interim, headers)) + } else { + None + } + }); + + let (push_id_1xx_rec, interim1xx_rec, headers1xx_rec) = events.next().unwrap(); + assert_eq!( + (push_id_1xx_rec, interim1xx_rec, headers1xx_rec.as_ref()), + (FIRST_PUSH_ID, true, headers1xx) + ); + + let (push_id_200_rec, interim200_rec, headers200_rec) = events.next().unwrap(); + assert_eq!( + (push_id_200_rec, interim200_rec, headers200_rec.as_ref()), + (FIRST_PUSH_ID, false, headers200) + ); + assert!(events.next().is_none()); + } + + // Client: receive a push stream + #[test] + fn push_single_wo_status() { + const FIRST_PUSH_ID: u64 = 0; + // Connect and send a request + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + // Send a push promise. + send_push_promise(&mut server.conn, request_stream_id, FIRST_PUSH_ID); + // Create a push stream + let push_stream_id = server.conn.stream_create(StreamType::UniDi).unwrap(); + + let mut d = Encoder::default(); + let headers = vec![ + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + server.encode_headers(request_stream_id, &headers, &mut d); + + send_data_on_push( + &mut server.conn, + push_stream_id, + u8::try_from(FIRST_PUSH_ID).unwrap(), + &d, + false, + ); + + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + HTTP_RESPONSE_2, + true, + ); + + // Stream has been reset because of thei malformed headers. + let push_reset_event = |e| { + matches!(e, Http3ClientEvent::PushReset { + push_id, + error, + } if push_id == FIRST_PUSH_ID && error == Error::InvalidHeader.code()) + }; + + assert!(client.events().any(push_reset_event)); + + let out = client.process(None, now()); + mem::drop(server.conn.process(out.as_dgram_ref(), now())); + + // Check that server has received a reset. + let stop_sending_event = |e| { + matches!(e, ConnectionEvent::SendStreamStopSending { + stream_id, + app_error + } if stream_id == push_stream_id && app_error == Error::InvalidHeader.code()) + }; + assert!(server.conn.events().any(stop_sending_event)); + } + + fn handshake_client_error(client: &mut Http3Client, server: &mut TestServer, error: &Error) { + let out = handshake_only(client, server); + client.process(out.as_dgram_ref(), now()); + assert_closed(client, error); + } + + /// Client fails to create a control stream, since server does not allow it. + #[test] + fn client_control_stream_create_failed() { + let mut client = default_http3_client(); + let mut server = TestServer::new_with_conn(new_server( + DEFAULT_ALPN_H3, + ConnectionParameters::default().max_streams(StreamType::UniDi, 0), + )); + handshake_client_error(&mut client, &mut server, &Error::StreamLimitError); + } + + /// 2 streams isn't enough for control and QPACK streams. + #[test] + fn client_qpack_stream_create_failed() { + let mut client = default_http3_client(); + let mut server = TestServer::new_with_conn(new_server( + DEFAULT_ALPN_H3, + ConnectionParameters::default().max_streams(StreamType::UniDi, 2), + )); + handshake_client_error(&mut client, &mut server, &Error::StreamLimitError); + } + + fn do_malformed_response_test(headers: &[Header]) { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let mut d = Encoder::default(); + server.encode_headers(request_stream_id, headers, &mut d); + + // Send response + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // Stream has been reset because of the malformed headers. + let e = client.events().next().unwrap(); + assert_eq!( + e, + Http3ClientEvent::Reset { + stream_id: request_stream_id, + error: Error::InvalidHeader.code(), + local: true, + } + ); + } + + #[test] + fn malformed_response_pseudo_header_after_regular_header() { + do_malformed_response_test(&[ + Header::new("content-type", "text/plain"), + Header::new(":status", "100"), + ]); + } + + #[test] + fn malformed_response_undefined_pseudo_header() { + do_malformed_response_test(&[Header::new(":status", "200"), Header::new(":cheese", "200")]); + } + + #[test] + fn malformed_response_duplicate_pseudo_header() { + do_malformed_response_test(&[ + Header::new(":status", "200"), + Header::new(":status", "100"), + Header::new("content-type", "text/plain"), + ]); + } + + #[test] + fn malformed_response_uppercase_header() { + do_malformed_response_test(&[ + Header::new(":status", "200"), + Header::new("content-Type", "text/plain"), + ]); + } + + #[test] + fn malformed_response_excluded_header() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let mut d = Encoder::default(); + server.encode_headers( + request_stream_id, + &[ + Header::new(":status", "200"), + Header::new("content-type", "text/plain"), + Header::new("connection", "close"), + ], + &mut d, + ); + + // Send response + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // Stream has been reset because of the malformed headers. + let e = client.events().next().unwrap(); + assert_eq!( + e, + Http3ClientEvent::HeaderReady { + stream_id: request_stream_id, + headers: vec![ + Header::new(":status", "200"), + Header::new("content-type", "text/plain") + ], + interim: false, + fin: false, + } + ); + } + + #[test] + fn malformed_response_excluded_byte_in_header() { + do_malformed_response_test(&[ + Header::new(":status", "200"), + Header::new("content:type", "text/plain"), + ]); + } + + #[test] + fn malformed_response_request_header_in_response() { + do_malformed_response_test(&[ + Header::new(":status", "200"), + Header::new(":method", "GET"), + Header::new("content-type", "text/plain"), + ]); + } + + fn maybe_authenticate(conn: &mut Http3Client) { + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + if conn.events().any(authentication_needed) { + conn.authenticated(AuthenticationStatus::Ok, now()); + } + } + + const MAX_TABLE_SIZE: u64 = 65536; + const MAX_BLOCKED_STREAMS: u16 = 5; + + fn get_resumption_token(server: &mut Http3Server) -> ResumptionToken { + let mut client = default_http3_client_param(MAX_TABLE_SIZE); + + let mut datagram = None; + let is_done = |c: &Http3Client| matches!(c.state(), Http3State::Connected); + while !is_done(&mut client) { + maybe_authenticate(&mut client); + datagram = client.process(datagram.as_ref(), now()).dgram(); + datagram = server.process(datagram.as_ref(), now()).dgram(); + } + + // exchange qpack settings, server will send a token as well. + datagram = client.process(datagram.as_ref(), now()).dgram(); + datagram = server.process(datagram.as_ref(), now()).dgram(); + mem::drop(client.process(datagram.as_ref(), now()).dgram()); + + client + .events() + .find_map(|e| { + if let Http3ClientEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }) + .unwrap() + } + + // Test that decoder stream type is always sent before any other instruction also + // in case when 0RTT is used. + // A client will send a request that uses the dynamic table. This will trigger a header-ack + // from a server. We will use stats to check that a header-ack has been received. + #[test] + fn zerortt_request_use_dynamic_table() { + let mut server = Http3Server::new( + now(), + DEFAULT_KEYS, + DEFAULT_ALPN_H3, + anti_replay(), + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + Http3Parameters::default() + .max_table_size_encoder(MAX_TABLE_SIZE) + .max_table_size_decoder(MAX_TABLE_SIZE) + .max_blocked_streams(MAX_BLOCKED_STREAMS), + None, + ) + .unwrap(); + + let token = get_resumption_token(&mut server); + // Make a new connection. + let mut client = default_http3_client_param(MAX_TABLE_SIZE); + assert_eq!(client.state(), Http3State::Initializing); + client + .enable_resumption(now(), &token) + .expect("Set resumption token."); + + assert_eq!(client.state(), Http3State::ZeroRtt); + let zerortt_event = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::ZeroRtt)); + assert!(client.events().any(zerortt_event)); + + // Make a request that uses the dynamic table. + _ = make_request(&mut client, true, &[Header::new("myheaders", "myvalue")]); + // Assert that the request has used dynamic table. That will trigger a header_ack. + assert_eq!(client.qpack_encoder_stats().dynamic_table_references, 1); + + // Exchange packets until header-ack is received. + // These many packet exchange is needed, to get a header-ack. + // TODO this may be optimize at Http3Server. + let out = client.process(None, now()); + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + mem::drop(client.process(out.as_dgram_ref(), now())); + + // The header ack for the first request has been received. + assert_eq!(client.qpack_encoder_stats().header_acks_recv, 1); + } + + fn manipulate_conrol_stream(client: &mut Http3Client, stream_id: StreamId) { + assert_eq!( + client + .cancel_fetch(stream_id, Error::HttpNoError.code()) + .unwrap_err(), + Error::InvalidStreamId + ); + assert_eq!( + client.stream_close_send(stream_id).unwrap_err(), + Error::InvalidStreamId + ); + let mut buf = [0; 2]; + assert_eq!( + client.send_data(stream_id, &buf).unwrap_err(), + Error::InvalidStreamId + ); + assert_eq!( + client.read_data(now(), stream_id, &mut buf).unwrap_err(), + Error::InvalidStreamId + ); + } + + #[test] + fn manipulate_conrol_streams() { + let (mut client, server, request_stream_id) = connect_and_send_request(false); + manipulate_conrol_stream(&mut client, CLIENT_SIDE_CONTROL_STREAM_ID); + manipulate_conrol_stream(&mut client, CLIENT_SIDE_ENCODER_STREAM_ID); + manipulate_conrol_stream(&mut client, CLIENT_SIDE_DECODER_STREAM_ID); + manipulate_conrol_stream(&mut client, server.control_stream_id.unwrap()); + manipulate_conrol_stream(&mut client, server.encoder_stream_id.unwrap()); + manipulate_conrol_stream(&mut client, server.decoder_stream_id.unwrap()); + client + .cancel_fetch(request_stream_id, Error::HttpNoError.code()) + .unwrap(); + } + + // Client: receive a push stream + #[test] + fn incomple_push_stream() { + let (mut client, mut server) = connect(); + + // Create a push stream + let push_stream_id = server.conn.stream_create(StreamType::UniDi).unwrap(); + _ = server + .conn + .stream_send(push_stream_id, PUSH_STREAM_TYPE) + .unwrap(); + _ = server.conn.stream_send(push_stream_id, &[0]).unwrap(); + server.conn.stream_close_send(push_stream_id).unwrap(); + let out = server.conn.process(None, now()); + client.process(out.as_dgram_ref(), now()); + assert_closed(&client, &Error::HttpGeneralProtocol); + } + + #[test] + fn priority_update_during_full_buffer() { + // set a lower MAX_DATA on the server side to restrict the data the client can send + let (mut client, mut server) = + connect_with_connection_parameters(ConnectionParameters::default().max_data(1200)); + + let request_stream_id = make_request_and_exchange_pkts(&mut client, &mut server, false); + let data_writable = |e| matches!(e, Http3ClientEvent::DataWritable { .. }); + assert!(client.events().any(data_writable)); + // Send a lot of data to reach the flow control limit + client.send_data(request_stream_id, &[0; 2000]).unwrap(); + + // now queue a priority_update packet for that stream + assert!(client + .priority_update(request_stream_id, Priority::new(6, false)) + .unwrap()); + + let md_before = server.conn.stats().frame_tx.max_data; + + // sending the http request and most most of the request data + let out = client.process(None, now()); + let out = server.conn.process(out.as_dgram_ref(), now()); + + // the server responses with an ack, but the max_data didn't change + assert_eq!(md_before, server.conn.stats().frame_tx.max_data); + + let out = client.process(out.as_dgram_ref(), now()); + let out = server.conn.process(out.as_dgram_ref(), now()); + + // the server increased the max_data during the second read if that isn't the case + // in the future and therefore this asserts fails, the request data on stream 0 could be + // read to cause a max_update frame + assert_eq!(md_before + 1, server.conn.stats().frame_tx.max_data); + + // make sure that the server didn't receive a priority_update on client control stream + // (stream_id 2) yet + let mut buf = [0; 32]; + assert_eq!( + server.conn.stream_recv(StreamId::new(2), &mut buf), + Ok((0, false)) + ); + + // the client now sends the priority update + let out = client.process(out.as_dgram_ref(), now()); + server + .conn + .process_input(out.as_dgram_ref().unwrap(), now()); + + // check that the priority_update arrived at the client control stream + let num_read = server.conn.stream_recv(StreamId::new(2), &mut buf).unwrap(); + assert_eq!(b"\x80\x0f\x07\x00\x04\x00\x75\x3d\x36", &buf[0..num_read.0]); + } + + #[test] + fn error_request_stream() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let headers = vec![ + Header::new(":status", "200"), + Header::new(":method", "GET"), // <- invalid + Header::new("my-header", "my-header"), + Header::new("content-length", "3"), + ]; + let encoded_headers = server.encoder.borrow_mut().encode_header_block( + &mut server.conn, + &headers, + request_stream_id, + ); + let hframe = HFrame::Headers { + header_block: encoded_headers.to_vec(), + }; + + // Send the encoder instructions, but delay them so that the stream is blocked on decoding + // headers. + let encoder_inst_pkt = server.conn.process(None, now()); + + // Send response + let mut d = Encoder::default(); + hframe.encode(&mut d); + let d_frame = HFrame::Data { len: 3 }; + d_frame.encode(&mut d); + d.encode(b"abc"); + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + true, + ); + + // Let client receive the encoder instructions. + client.process_input(encoder_inst_pkt.as_dgram_ref().unwrap(), now()); + + let reset_event = |e| matches!(e, Http3ClientEvent::Reset { stream_id, .. } if stream_id == request_stream_id); + assert!(client.events().any(reset_event)); + } + + #[test] + fn response_w_101() { + let (mut client, mut server, request_stream_id) = connect_and_send_request(true); + + setup_server_side_encoder(&mut client, &mut server); + + let mut d = Encoder::default(); + let headers1xx = &[Header::new(":status", "101")]; + server.encode_headers(request_stream_id, headers1xx, &mut d); + + // Send 101 response. + server_send_response_and_exchange_packet( + &mut client, + &mut server, + request_stream_id, + &d, + false, + ); + + // Stream has been reset because of the 101 response. + let e = client.events().next().unwrap(); + assert_eq!( + e, + Http3ClientEvent::Reset { + stream_id: request_stream_id, + error: Error::InvalidHeader.code(), + local: true, + } + ); + } +} diff --git a/third_party/rust/neqo-http3/src/connection_server.rs b/third_party/rust/neqo-http3/src/connection_server.rs new file mode 100644 index 0000000000..097209a226 --- /dev/null +++ b/third_party/rust/neqo-http3/src/connection_server.rs @@ -0,0 +1,420 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{rc::Rc, time::Instant}; + +use neqo_common::{event::Provider, qdebug, qinfo, qtrace, Header, MessageType, Role}; +use neqo_transport::{ + AppError, Connection, ConnectionEvent, DatagramTracking, StreamId, StreamType, +}; + +use crate::{ + connection::{Http3Connection, Http3State, WebTransportSessionAcceptAction}, + frames::HFrame, + recv_message::{RecvMessage, RecvMessageInfo}, + send_message::SendMessage, + server_connection_events::{Http3ServerConnEvent, Http3ServerConnEvents}, + Error, Http3Parameters, Http3StreamType, NewStreamType, Priority, PriorityHandler, + ReceiveOutput, Res, +}; + +#[derive(Debug)] +pub struct Http3ServerHandler { + base_handler: Http3Connection, + events: Http3ServerConnEvents, + needs_processing: bool, +} + +impl ::std::fmt::Display for Http3ServerHandler { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Http3 server connection") + } +} + +impl Http3ServerHandler { + pub(crate) fn new(http3_parameters: Http3Parameters) -> Self { + Self { + base_handler: Http3Connection::new(http3_parameters, Role::Server), + events: Http3ServerConnEvents::default(), + needs_processing: false, + } + } + + #[must_use] + pub fn state(&self) -> Http3State { + self.base_handler.state() + } + + /// Supply a response for a request. + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist, + /// `AlreadyClosed` if the stream has already been closed. + /// `TransportStreamDoesNotExist` if the transport stream does not exist (this may happen if + /// `process_output` has not been called when needed, and HTTP3 layer has not picked up the + /// info that the stream has been closed.) `InvalidInput` if an empty buffer has been + /// supplied. + pub(crate) fn send_data( + &mut self, + stream_id: StreamId, + data: &[u8], + conn: &mut Connection, + ) -> Res<usize> { + self.base_handler.stream_has_pending_data(stream_id); + self.needs_processing = true; + self.base_handler + .send_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .send_data(conn, data) + } + + /// Supply response heeaders for a request. + pub(crate) fn send_headers( + &mut self, + stream_id: StreamId, + headers: &[Header], + conn: &mut Connection, + ) -> Res<()> { + self.base_handler + .send_streams + .get_mut(&stream_id) + .ok_or(Error::InvalidStreamId)? + .http_stream() + .ok_or(Error::InvalidStreamId)? + .send_headers(headers, conn)?; + self.base_handler.stream_has_pending_data(stream_id); + self.needs_processing = true; + Ok(()) + } + + /// This is called when application is done sending a request. + /// + /// # Errors + /// + /// An error will be returned if stream does not exist. + pub fn stream_close_send(&mut self, stream_id: StreamId, conn: &mut Connection) -> Res<()> { + qinfo!([self], "Close sending side stream={}.", stream_id); + self.base_handler.stream_close_send(conn, stream_id)?; + self.base_handler.stream_has_pending_data(stream_id); + self.needs_processing = true; + Ok(()) + } + + /// An application may reset a stream(request). + /// Both sides, sending and receiving side, will be closed. + /// + /// # Errors + /// + /// An error will be return if a stream does not exist. + pub fn cancel_fetch( + &mut self, + stream_id: StreamId, + error: AppError, + conn: &mut Connection, + ) -> Res<()> { + qinfo!([self], "cancel_fetch {} error={}.", stream_id, error); + self.needs_processing = true; + self.base_handler.cancel_fetch(stream_id, error, conn) + } + + pub fn stream_stop_sending( + &mut self, + stream_id: StreamId, + error: AppError, + conn: &mut Connection, + ) -> Res<()> { + qinfo!([self], "stream_stop_sending {} error={}.", stream_id, error); + self.needs_processing = true; + self.base_handler + .stream_stop_sending(conn, stream_id, error) + } + + pub fn stream_reset_send( + &mut self, + stream_id: StreamId, + error: AppError, + conn: &mut Connection, + ) -> Res<()> { + qinfo!([self], "stream_reset_send {} error={}.", stream_id, error); + self.needs_processing = true; + self.base_handler.stream_reset_send(conn, stream_id, error) + } + + /// Accept a `WebTransport` Session request + pub(crate) fn webtransport_session_accept( + &mut self, + conn: &mut Connection, + stream_id: StreamId, + accept: &WebTransportSessionAcceptAction, + ) -> Res<()> { + self.needs_processing = true; + self.base_handler.webtransport_session_accept( + conn, + stream_id, + Box::new(self.events.clone()), + accept, + ) + } + + /// Close `WebTransport` cleanly + /// + /// # Errors + /// + /// `InvalidStreamId` if the stream does not exist, + /// `TransportStreamDoesNotExist` if the transport stream does not exist (this may happen if + /// `process_output` has not been called when needed, and HTTP3 layer has not picked up the + /// info that the stream has been closed.) `InvalidInput` if an empty buffer has been + /// supplied. + pub fn webtransport_close_session( + &mut self, + conn: &mut Connection, + session_id: StreamId, + error: u32, + message: &str, + ) -> Res<()> { + self.needs_processing = true; + self.base_handler + .webtransport_close_session(conn, session_id, error, message) + } + + pub fn webtransport_create_stream( + &mut self, + conn: &mut Connection, + session_id: StreamId, + stream_type: StreamType, + ) -> Res<StreamId> { + self.needs_processing = true; + self.base_handler.webtransport_create_stream_local( + conn, + session_id, + stream_type, + Box::new(self.events.clone()), + Box::new(self.events.clone()), + ) + } + + pub fn webtransport_send_datagram( + &mut self, + conn: &mut Connection, + session_id: StreamId, + buf: &[u8], + id: impl Into<DatagramTracking>, + ) -> Res<()> { + self.needs_processing = true; + self.base_handler + .webtransport_send_datagram(session_id, conn, buf, id) + } + + /// Process HTTTP3 layer. + pub fn process_http3(&mut self, conn: &mut Connection, now: Instant) { + qtrace!([self], "Process http3 internal."); + if matches!(self.base_handler.state(), Http3State::Closed(..)) { + return; + } + + let res = self.check_connection_events(conn, now); + if !self.check_result(conn, now, &res) && self.base_handler.state().active() { + let res = self.base_handler.process_sending(conn); + self.check_result(conn, now, &res); + } + } + + /// Take the next available event. + pub(crate) fn next_event(&mut self) -> Option<Http3ServerConnEvent> { + self.events.next_event() + } + + /// Whether this connection has events to process or data to send. + pub(crate) fn should_be_processed(&mut self) -> bool { + if self.needs_processing { + self.needs_processing = false; + return true; + } + self.base_handler.has_data_to_send() || self.events.has_events() + } + + // This function takes the provided result and check for an error. + // An error results in closing the connection. + fn check_result<ERR>(&mut self, conn: &mut Connection, now: Instant, res: &Res<ERR>) -> bool { + match &res { + Err(e) => { + self.close(conn, now, e); + true + } + _ => false, + } + } + + fn close(&mut self, conn: &mut Connection, now: Instant, err: &Error) { + qinfo!([self], "Connection error: {}.", err); + conn.close(now, err.code(), &format!("{err}")); + self.base_handler.close(err.code()); + self.events + .connection_state_change(self.base_handler.state()); + } + + // If this return an error the connection must be closed. + fn check_connection_events(&mut self, conn: &mut Connection, now: Instant) -> Res<()> { + qtrace!([self], "Check connection events."); + while let Some(e) = conn.next_event() { + qdebug!([self], "check_connection_events - event {e:?}."); + match e { + ConnectionEvent::NewStream { stream_id } => { + self.base_handler.add_new_stream(stream_id); + } + ConnectionEvent::RecvStreamReadable { stream_id } => { + self.handle_stream_readable(conn, stream_id)?; + } + ConnectionEvent::RecvStreamReset { + stream_id, + app_error, + } => { + self.base_handler + .handle_stream_reset(stream_id, app_error, conn)?; + } + ConnectionEvent::SendStreamStopSending { + stream_id, + app_error, + } => self + .base_handler + .handle_stream_stop_sending(stream_id, app_error, conn)?, + ConnectionEvent::StateChange(state) => { + if self.base_handler.handle_state_change(conn, &state)? { + if self.base_handler.state() == Http3State::Connected { + let settings = self.base_handler.save_settings(); + conn.send_ticket(now, &settings)?; + } + self.events + .connection_state_change(self.base_handler.state()); + } + } + ConnectionEvent::SendStreamWritable { stream_id } => { + if let Some(s) = self.base_handler.send_streams.get_mut(&stream_id) { + s.stream_writable(); + } + } + ConnectionEvent::Datagram(dgram) => self.base_handler.handle_datagram(&dgram), + ConnectionEvent::AuthenticationNeeded + | ConnectionEvent::EchFallbackAuthenticationNeeded { .. } + | ConnectionEvent::ZeroRttRejected + | ConnectionEvent::ResumptionToken(..) => return Err(Error::HttpInternal(4)), + ConnectionEvent::SendStreamComplete { .. } + | ConnectionEvent::SendStreamCreatable { .. } + | ConnectionEvent::OutgoingDatagramOutcome { .. } + | ConnectionEvent::IncomingDatagramDropped => {} + } + } + Ok(()) + } + + fn handle_stream_readable(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<()> { + match self.base_handler.handle_stream_readable(conn, stream_id)? { + ReceiveOutput::NewStream(NewStreamType::Push(_)) => Err(Error::HttpStreamCreation), + ReceiveOutput::NewStream(NewStreamType::Http) => { + self.base_handler.add_streams( + stream_id, + Box::new(SendMessage::new( + MessageType::Response, + Http3StreamType::Http, + stream_id, + self.base_handler.qpack_encoder.clone(), + Box::new(self.events.clone()), + )), + Box::new(RecvMessage::new( + &RecvMessageInfo { + message_type: MessageType::Request, + stream_type: Http3StreamType::Http, + stream_id, + header_frame_type_read: true, + }, + Rc::clone(&self.base_handler.qpack_decoder), + Box::new(self.events.clone()), + None, + PriorityHandler::new(false, Priority::default()), + )), + ); + let res = self.base_handler.handle_stream_readable(conn, stream_id)?; + assert_eq!(ReceiveOutput::NoOutput, res); + Ok(()) + } + ReceiveOutput::NewStream(NewStreamType::WebTransportStream(session_id)) => { + self.base_handler.webtransport_create_stream_remote( + StreamId::from(session_id), + stream_id, + Box::new(self.events.clone()), + Box::new(self.events.clone()), + )?; + let res = self.base_handler.handle_stream_readable(conn, stream_id)?; + assert_eq!(ReceiveOutput::NoOutput, res); + Ok(()) + } + ReceiveOutput::ControlFrames(control_frames) => { + for f in control_frames { + match f { + HFrame::MaxPushId { .. } => { + // TODO implement push + Ok(()) + } + HFrame::Goaway { .. } | HFrame::CancelPush { .. } => { + Err(Error::HttpFrameUnexpected) + } + HFrame::PriorityUpdatePush { element_id, priority } => { + // TODO: check if the element_id references a promised push stream or + // is greater than the maximum Push ID. + self.events.priority_update(StreamId::from(element_id), priority); + Ok(()) + } + HFrame::PriorityUpdateRequest { element_id, priority } => { + // check that the element_id references a request stream + // within the client-sided bidirectional stream limit + let element_stream_id = StreamId::new(element_id); + if !element_stream_id.is_bidi() + || !element_stream_id.is_client_initiated() + || !conn.is_stream_id_allowed(element_stream_id) + { + return Err(Error::HttpId) + } + + self.events.priority_update(element_stream_id, priority); + Ok(()) + } + _ => unreachable!( + "we should only put MaxPushId, Goaway and PriorityUpdates into control_frames." + ), + }?; + } + Ok(()) + } + _ => Ok(()), + } + } + + /// Response data are read directly into a buffer supplied as a parameter of this function to + /// avoid copying data. + /// + /// # Errors + /// + /// It returns an error if a stream does not exist or an error happen while reading a stream, + /// e.g. early close, protocol error, etc. + pub fn read_data( + &mut self, + conn: &mut Connection, + now: Instant, + stream_id: StreamId, + buf: &mut [u8], + ) -> Res<(usize, bool)> { + qinfo!([self], "read_data from stream {}.", stream_id); + let res = self.base_handler.read_data(conn, stream_id, buf); + if let Err(e) = &res { + if e.connection_error() { + self.close(conn, now, e); + } + } + res + } +} diff --git a/third_party/rust/neqo-http3/src/control_stream_local.rs b/third_party/rust/neqo-http3/src/control_stream_local.rs new file mode 100644 index 0000000000..62676ee391 --- /dev/null +++ b/third_party/rust/neqo-http3/src/control_stream_local.rs @@ -0,0 +1,109 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{ + collections::{HashMap, VecDeque}, + convert::TryFrom, +}; + +use neqo_common::{qtrace, Encoder}; +use neqo_transport::{Connection, StreamId, StreamType}; + +use crate::{frames::HFrame, BufferedStream, Http3StreamType, RecvStream, Res}; + +pub const HTTP3_UNI_STREAM_TYPE_CONTROL: u64 = 0x0; + +/// The local control stream, responsible for encoding frames and sending them +#[derive(Debug)] +pub(crate) struct ControlStreamLocal { + stream: BufferedStream, + /// `stream_id`s of outstanding request streams + outstanding_priority_update: VecDeque<StreamId>, +} + +impl ::std::fmt::Display for ControlStreamLocal { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Local control stream {:?}", self.stream) + } +} + +impl ControlStreamLocal { + pub fn new() -> Self { + Self { + stream: BufferedStream::default(), + outstanding_priority_update: VecDeque::new(), + } + } + + /// Add a new frame that needs to be send. + pub fn queue_frame(&mut self, f: &HFrame) { + let mut enc = Encoder::default(); + f.encode(&mut enc); + self.stream.buffer(enc.as_ref()); + } + + pub fn queue_update_priority(&mut self, stream_id: StreamId) { + self.outstanding_priority_update.push_back(stream_id); + } + + /// Send control data if available. + pub fn send( + &mut self, + conn: &mut Connection, + recv_conn: &mut HashMap<StreamId, Box<dyn RecvStream>>, + ) -> Res<()> { + self.stream.send_buffer(conn)?; + self.send_priority_update(conn, recv_conn) + } + + fn send_priority_update( + &mut self, + conn: &mut Connection, + recv_conn: &mut HashMap<StreamId, Box<dyn RecvStream>>, + ) -> Res<()> { + // send all necessary priority updates + while let Some(update_id) = self.outstanding_priority_update.pop_front() { + let Some(update_stream) = recv_conn.get_mut(&update_id) else { + continue; + }; + + // can assert and unwrap here, because priority updates can only be added to + // HttpStreams in [Http3Connection::queue_update_priority} + debug_assert!(matches!( + update_stream.stream_type(), + Http3StreamType::Http | Http3StreamType::Push + )); + let stream = update_stream.http_stream().unwrap(); + + // in case multiple priority_updates were issued, ignore now irrelevant + if let Some(hframe) = stream.priority_update_frame() { + let mut enc = Encoder::new(); + hframe.encode(&mut enc); + if self.stream.send_atomic(conn, enc.as_ref())? { + stream.priority_update_sent(); + } else { + self.outstanding_priority_update.push_front(update_id); + break; + } + } + } + Ok(()) + } + + /// Create a control stream. + pub fn create(&mut self, conn: &mut Connection) -> Res<()> { + qtrace!([self], "Create a control stream."); + self.stream.init(conn.stream_create(StreamType::UniDi)?); + self.stream + .buffer(&[u8::try_from(HTTP3_UNI_STREAM_TYPE_CONTROL).unwrap()]); + Ok(()) + } + + #[must_use] + pub fn stream_id(&self) -> Option<StreamId> { + (&self.stream).into() + } +} diff --git a/third_party/rust/neqo-http3/src/control_stream_remote.rs b/third_party/rust/neqo-http3/src/control_stream_remote.rs new file mode 100644 index 0000000000..aef4b4c0a4 --- /dev/null +++ b/third_party/rust/neqo-http3/src/control_stream_remote.rs @@ -0,0 +1,78 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use neqo_common::qdebug; +use neqo_transport::{Connection, StreamId}; + +use crate::{ + frames::{FrameReader, HFrame, StreamReaderConnectionWrapper}, + CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream, +}; + +/// The remote control stream is responsible only for reading frames. The frames are handled by +/// `Http3Connection`. +#[derive(Debug)] +pub(crate) struct ControlStreamRemote { + stream_id: StreamId, + frame_reader: FrameReader, +} + +impl ::std::fmt::Display for ControlStreamRemote { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Http3 remote control stream {:?}", self.stream_id) + } +} + +impl ControlStreamRemote { + pub fn new(stream_id: StreamId) -> Self { + Self { + stream_id, + frame_reader: FrameReader::new(), + } + } + + /// Check if a stream is the control stream and read received data. + pub fn receive_single(&mut self, conn: &mut Connection) -> Res<Option<HFrame>> { + qdebug!([self], "Receiving data."); + match self + .frame_reader + .receive(&mut StreamReaderConnectionWrapper::new( + conn, + self.stream_id, + ))? { + (_, true) => Err(Error::HttpClosedCriticalStream), + (s, false) => { + qdebug!([self], "received {:?}", s); + Ok(s) + } + } + } +} + +impl Stream for ControlStreamRemote { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::Control + } +} + +impl RecvStream for ControlStreamRemote { + fn reset(&mut self, _close_type: CloseType) -> Res<()> { + Err(Error::HttpClosedCriticalStream) + } + + #[allow(clippy::vec_init_then_push)] // Clippy fail. + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + let mut control_frames = Vec::new(); + + loop { + if let Some(f) = self.receive_single(conn)? { + control_frames.push(f); + } else { + return Ok((ReceiveOutput::ControlFrames(control_frames), false)); + } + } + } +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs b/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs new file mode 100644 index 0000000000..77655833f7 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs @@ -0,0 +1,118 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +pub(crate) mod webtransport_session; +pub(crate) mod webtransport_streams; + +use std::fmt::Debug; + +use neqo_common::Header; +use neqo_transport::{AppError, StreamId}; +pub(crate) use webtransport_session::WebTransportSession; + +use crate::{ + client_events::Http3ClientEvents, + features::NegotiationState, + settings::{HSettingType, HSettings}, + CloseType, Http3StreamInfo, Http3StreamType, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SessionCloseReason { + Error(AppError), + Status(u16), + Clean { error: u32, message: String }, +} + +impl From<CloseType> for SessionCloseReason { + fn from(close_type: CloseType) -> SessionCloseReason { + match close_type { + CloseType::ResetApp(e) | CloseType::ResetRemote(e) | CloseType::LocalError(e) => { + SessionCloseReason::Error(e) + } + CloseType::Done => SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + } + } +} + +pub(crate) trait ExtendedConnectEvents: Debug { + fn session_start( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + status: u16, + headers: Vec<Header>, + ); + fn session_end( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + ); + fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo); + fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>); +} + +#[derive(Debug, PartialEq, Copy, Clone, Eq)] +pub(crate) enum ExtendedConnectType { + WebTransport, +} + +impl ExtendedConnectType { + #[must_use] + #[allow(clippy::unused_self)] // This will change when we have more features using ExtendedConnectType. + pub fn string(&self) -> &str { + "webtransport" + } + + #[allow(clippy::unused_self)] // This will change when we have more features using ExtendedConnectType. + #[must_use] + pub fn get_stream_type(self, session_id: StreamId) -> Http3StreamType { + Http3StreamType::WebTransport(session_id) + } +} + +impl From<ExtendedConnectType> for HSettingType { + fn from(_type: ExtendedConnectType) -> Self { + // This will change when we have more features using ExtendedConnectType. + HSettingType::EnableWebTransport + } +} + +#[derive(Debug)] +pub(crate) struct ExtendedConnectFeature { + feature_negotiation: NegotiationState, +} + +impl ExtendedConnectFeature { + #[must_use] + pub fn new(connect_type: ExtendedConnectType, enable: bool) -> Self { + Self { + feature_negotiation: NegotiationState::new(enable, HSettingType::from(connect_type)), + } + } + + pub fn set_listener(&mut self, new_listener: Http3ClientEvents) { + self.feature_negotiation.set_listener(new_listener); + } + + pub fn handle_settings(&mut self, settings: &HSettings) { + self.feature_negotiation.handle_settings(settings); + } + + #[must_use] + pub fn enabled(&self) -> bool { + self.feature_negotiation.enabled() + } +} +#[cfg(test)] +mod tests; diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs new file mode 100644 index 0000000000..a21f63dbf8 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod webtransport; diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs new file mode 100644 index 0000000000..1c58596dd3 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs @@ -0,0 +1,146 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::convert::TryFrom; + +use neqo_common::Encoder; +use neqo_transport::Error as TransportError; + +use crate::{ + features::extended_connect::tests::webtransport::{ + wt_default_parameters, WtTest, DATAGRAM_SIZE, + }, + Error, Http3Parameters, WebTransportRequest, +}; + +const DGRAM: &[u8] = &[0, 100]; + +#[test] +fn no_datagrams() { + let mut wt = WtTest::new_with_params( + Http3Parameters::default().webtransport(true), + Http3Parameters::default().webtransport(true), + ); + let mut wt_session = wt.create_wt_session(); + + assert_eq!( + wt_session.max_datagram_size(), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + + assert_eq!( + wt_session.send_datagram(DGRAM, None), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + assert_eq!( + wt.send_datagram(wt_session.stream_id(), DGRAM), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + + wt.exchange_packets(); + wt.check_no_datagram_received_client(); + wt.check_no_datagram_received_server(); +} + +fn do_datagram_test(wt: &mut WtTest, wt_session: &mut WebTransportRequest) { + assert_eq!( + wt_session.max_datagram_size(), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + + assert_eq!(wt_session.send_datagram(DGRAM, None), Ok(())); + assert_eq!(wt.send_datagram(wt_session.stream_id(), DGRAM), Ok(())); + + wt.exchange_packets(); + wt.check_datagram_received_client(wt_session.stream_id(), DGRAM); + wt.check_datagram_received_server(wt_session, DGRAM); +} + +#[test] +fn datagrams() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + do_datagram_test(&mut wt, &mut wt_session); +} + +#[test] +fn datagrams_server_only() { + let mut wt = WtTest::new_with_params( + Http3Parameters::default().webtransport(true), + wt_default_parameters(), + ); + let mut wt_session = wt.create_wt_session(); + + assert_eq!( + wt_session.max_datagram_size(), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + + assert_eq!( + wt_session.send_datagram(DGRAM, None), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + assert_eq!(wt.send_datagram(wt_session.stream_id(), DGRAM), Ok(())); + + wt.exchange_packets(); + wt.check_datagram_received_server(&wt_session, DGRAM); + wt.check_no_datagram_received_client(); +} + +#[test] +fn datagrams_client_only() { + let mut wt = WtTest::new_with_params( + wt_default_parameters(), + Http3Parameters::default().webtransport(true), + ); + let mut wt_session = wt.create_wt_session(); + + assert_eq!( + wt_session.max_datagram_size(), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + + assert_eq!(wt_session.send_datagram(DGRAM, None), Ok(())); + assert_eq!( + wt.send_datagram(wt_session.stream_id(), DGRAM), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + + wt.exchange_packets(); + wt.check_datagram_received_client(wt_session.stream_id(), DGRAM); + wt.check_no_datagram_received_server(); +} + +#[test] +fn datagrams_multiple_session() { + let mut wt = WtTest::new(); + + let mut wt_session1 = wt.create_wt_session(); + do_datagram_test(&mut wt, &mut wt_session1); + + let mut wt_session_2 = wt.create_wt_session(); + do_datagram_test(&mut wt, &mut wt_session_2); +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs new file mode 100644 index 0000000000..51dc47e4c1 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs @@ -0,0 +1,661 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod datagrams; +mod negotiation; +mod sessions; +mod streams; +use std::{cell::RefCell, rc::Rc, time::Duration}; + +use neqo_common::event::Provider; +use neqo_crypto::AuthenticationStatus; +use neqo_transport::{ConnectionParameters, StreamId, StreamType}; +use test_fixture::{ + addr, anti_replay, fixture_init, now, CountingConnectionIdGenerator, DEFAULT_ALPN_H3, + DEFAULT_KEYS, DEFAULT_SERVER_NAME, +}; + +use crate::{ + features::extended_connect::SessionCloseReason, Error, Header, Http3Client, Http3ClientEvent, + Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent, Http3State, + RecvStreamStats, SendStreamStats, WebTransportEvent, WebTransportRequest, + WebTransportServerEvent, WebTransportSessionAcceptAction, +}; + +const DATAGRAM_SIZE: u64 = 1200; + +pub fn wt_default_parameters() -> Http3Parameters { + Http3Parameters::default() + .webtransport(true) + .connection_parameters(ConnectionParameters::default().datagram_size(DATAGRAM_SIZE)) +} + +pub fn default_http3_client(client_params: Http3Parameters) -> Http3Client { + fixture_init(); + Http3Client::new( + DEFAULT_SERVER_NAME, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + client_params, + now(), + ) + .expect("create a default client") +} + +pub fn default_http3_server(server_params: Http3Parameters) -> Http3Server { + Http3Server::new( + now(), + DEFAULT_KEYS, + DEFAULT_ALPN_H3, + anti_replay(), + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + server_params, + None, + ) + .expect("create a server") +} + +fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) { + let mut out = None; + loop { + out = client.process(out.as_ref(), now()).dgram(); + out = server.process(out.as_ref(), now()).dgram(); + if out.is_none() { + break; + } + } +} + +// Perform only Quic transport handshake. +fn connect_with(client: &mut Http3Client, server: &mut Http3Server) { + assert_eq!(client.state(), Http3State::Initializing); + let out = client.process(None, now()); + assert_eq!(client.state(), Http3State::Initializing); + + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + assert!(out.as_dgram_ref().is_none()); + + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(client.events().any(authentication_needed)); + client.authenticated(AuthenticationStatus::Ok, now()); + + let out = client.process(out.as_dgram_ref(), now()); + let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected)); + assert!(client.events().any(connected)); + + assert_eq!(client.state(), Http3State::Connected); + + // Exchange H3 setttings + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + std::mem::drop(client.process(out.as_dgram_ref(), now())); +} + +fn connect( + client_params: Http3Parameters, + server_params: Http3Parameters, +) -> (Http3Client, Http3Server) { + let mut client = default_http3_client(client_params); + let mut server = default_http3_server(server_params); + connect_with(&mut client, &mut server); + (client, server) +} + +struct WtTest { + client: Http3Client, + server: Http3Server, +} + +impl WtTest { + pub fn new() -> Self { + let (client, server) = connect(wt_default_parameters(), wt_default_parameters()); + Self { client, server } + } + + pub fn new_with_params(client_params: Http3Parameters, server_params: Http3Parameters) -> Self { + let (client, server) = connect(client_params, server_params); + Self { client, server } + } + + pub fn new_with(mut client: Http3Client, mut server: Http3Server) -> Self { + connect_with(&mut client, &mut server); + Self { client, server } + } + fn negotiate_wt_session( + &mut self, + accept: &WebTransportSessionAcceptAction, + ) -> (StreamId, Option<WebTransportRequest>) { + let wt_session_id = self + .client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + self.exchange_packets(); + + let mut wt_server_session = None; + while let Some(event) = self.server.next_event() { + match event { + Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + mut session, + headers, + }) => { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + session.response(accept).unwrap(); + wt_server_session = Some(session); + } + Http3ServerEvent::Data { .. } => { + panic!("There should not be any data events!"); + } + _ => {} + } + } + + self.exchange_packets(); + (wt_session_id, wt_server_session) + } + + fn create_wt_session(&mut self) -> WebTransportRequest { + let (wt_session_id, wt_server_session) = + self.negotiate_wt_session(&WebTransportSessionAcceptAction::Accept); + let wt_session_negotiated_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Session{ + stream_id, + status, + headers, + }) if ( + stream_id == wt_session_id && + status == 200 && + headers.contains(&Header::new(":status", "200")) + ) + ) + }; + assert!(self.client.events().any(wt_session_negotiated_event)); + + let wt_server_session = wt_server_session.unwrap(); + assert_eq!(wt_session_id, wt_server_session.stream_id()); + wt_server_session + } + + fn exchange_packets(&mut self) { + const RTT: Duration = Duration::from_millis(10); + let mut out = None; + let mut now = now(); + loop { + now += RTT / 2; + out = self.client.process(out.as_ref(), now).dgram(); + let client_none = out.is_none(); + now += RTT / 2; + out = self.server.process(out.as_ref(), now).dgram(); + if client_none && out.is_none() { + break; + } + } + } + + pub fn cancel_session_client(&mut self, wt_stream_id: StreamId) { + self.client + .cancel_fetch(wt_stream_id, Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn session_closed_client( + e: &Http3ClientEvent, + id: StreamId, + expected_reason: &SessionCloseReason, + expected_headers: &Option<Vec<Header>>, + ) -> bool { + if let Http3ClientEvent::WebTransport(WebTransportEvent::SessionClosed { + stream_id, + reason, + headers, + }) = e + { + *stream_id == id && reason == expected_reason && headers == expected_headers + } else { + false + } + } + + pub fn check_session_closed_event_client( + &mut self, + wt_session_id: StreamId, + expected_reason: &SessionCloseReason, + expected_headers: &Option<Vec<Header>>, + ) { + let mut event_found = false; + + while let Some(event) = self.client.next_event() { + event_found = WtTest::session_closed_client( + &event, + wt_session_id, + expected_reason, + expected_headers, + ); + if event_found { + break; + } + } + assert!(event_found); + } + + pub fn cancel_session_server(&mut self, wt_session: &mut WebTransportRequest) { + wt_session.cancel_fetch(Error::HttpNoError.code()).unwrap(); + self.exchange_packets(); + } + + fn session_closed_server( + e: &Http3ServerEvent, + id: StreamId, + expected_reason: &SessionCloseReason, + ) -> bool { + if let Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed { + session, + reason, + headers, + }) = e + { + session.stream_id() == id && reason == expected_reason && headers.is_none() + } else { + false + } + } + + pub fn check_session_closed_event_server( + &mut self, + wt_session: &mut WebTransportRequest, + expected_reeason: &SessionCloseReason, + ) { + let event = self.server.next_event().unwrap(); + assert!(WtTest::session_closed_server( + &event, + wt_session.stream_id(), + expected_reeason + )); + } + + fn create_wt_stream_client( + &mut self, + wt_session_id: StreamId, + stream_type: StreamType, + ) -> StreamId { + self.client + .webtransport_create_stream(wt_session_id, stream_type) + .unwrap() + } + + fn send_data_client(&mut self, wt_stream_id: StreamId, data: &[u8]) { + assert_eq!( + self.client.send_data(wt_stream_id, data).unwrap(), + data.len() + ); + self.exchange_packets(); + } + + fn send_stream_stats(&mut self, wt_stream_id: StreamId) -> Result<SendStreamStats, Error> { + self.client.webtransport_send_stream_stats(wt_stream_id) + } + + fn recv_stream_stats(&mut self, wt_stream_id: StreamId) -> Result<RecvStreamStats, Error> { + self.client.webtransport_recv_stream_stats(wt_stream_id) + } + + fn receive_data_client( + &mut self, + expected_stream_id: StreamId, + new_stream: bool, + expected_data: &[u8], + expected_fin: bool, + ) { + let mut new_stream_received = false; + let mut data_received = false; + while let Some(event) = self.client.next_event() { + match event { + Http3ClientEvent::WebTransport(WebTransportEvent::NewStream { + stream_id, .. + }) => { + assert_eq!(stream_id, expected_stream_id); + new_stream_received = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, expected_stream_id); + let mut buf = [0; 100]; + let (amount, fin) = self.client.read_data(now(), stream_id, &mut buf).unwrap(); + assert_eq!(fin, expected_fin); + assert_eq!(amount, expected_data.len()); + assert_eq!(&buf[..amount], expected_data); + data_received = true; + } + _ => {} + } + } + assert!(data_received); + assert_eq!(new_stream, new_stream_received); + } + + fn close_stream_sending_client(&mut self, wt_stream_id: StreamId) { + self.client.stream_close_send(wt_stream_id).unwrap(); + self.exchange_packets(); + } + + fn reset_stream_client(&mut self, wt_stream_id: StreamId) { + self.client + .stream_reset_send(wt_stream_id, Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn receive_reset_client(&mut self, expected_stream_id: StreamId) { + let wt_reset_event = |e| { + matches!( + e, + Http3ClientEvent::Reset { + stream_id, + error, + local + } if stream_id == expected_stream_id && error == Error::HttpNoError.code() && !local + ) + }; + assert!(self.client.events().any(wt_reset_event)); + } + + fn stream_stop_sending_client(&mut self, stream_id: StreamId) { + self.client + .stream_stop_sending(stream_id, Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn receive_stop_sending_client(&mut self, expected_stream_id: StreamId) { + let wt_stop_sending_event = |e| { + matches!( + e, + Http3ClientEvent::StopSending { + stream_id, + error + } if stream_id == expected_stream_id && error == Error::HttpNoError.code() + ) + }; + assert!(self.client.events().any(wt_stop_sending_event)); + } + + fn check_events_after_closing_session_client( + &mut self, + expected_reset_ids: &[StreamId], + expected_error_stream_reset: Option<u64>, + expected_stop_sending_ids: &[StreamId], + expected_error_stream_stop_sending: Option<u64>, + expected_local: bool, + expected_session_close: &Option<(StreamId, SessionCloseReason)>, + ) { + let mut reset_ids_count = 0; + let mut stop_sending_ids_count = 0; + let mut close_event = false; + while let Some(event) = self.client.next_event() { + match event { + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert!(expected_reset_ids.contains(&stream_id)); + assert_eq!(expected_error_stream_reset.unwrap(), error); + assert_eq!(expected_local, local); + reset_ids_count += 1; + } + Http3ClientEvent::StopSending { stream_id, error } => { + assert!(expected_stop_sending_ids.contains(&stream_id)); + assert_eq!(expected_error_stream_stop_sending.unwrap(), error); + stop_sending_ids_count += 1; + } + Http3ClientEvent::WebTransport(WebTransportEvent::SessionClosed { + stream_id, + reason, + headers, + }) => { + close_event = true; + assert_eq!(stream_id, expected_session_close.as_ref().unwrap().0); + assert_eq!(expected_session_close.as_ref().unwrap().1, reason); + assert!(headers.is_none()); + } + _ => {} + } + } + assert_eq!(reset_ids_count, expected_reset_ids.len()); + assert_eq!(stop_sending_ids_count, expected_stop_sending_ids.len()); + assert_eq!(close_event, expected_session_close.is_some()); + } + + fn create_wt_stream_server( + wt_server_session: &mut WebTransportRequest, + stream_type: StreamType, + ) -> Http3OrWebTransportStream { + wt_server_session.create_stream(stream_type).unwrap() + } + + fn send_data_server(&mut self, wt_stream: &mut Http3OrWebTransportStream, data: &[u8]) { + assert_eq!(wt_stream.send_data(data).unwrap(), data.len()); + self.exchange_packets(); + } + + fn receive_data_server( + &mut self, + stream_id: StreamId, + new_stream: bool, + expected_data: &[u8], + expected_fin: bool, + ) -> Http3OrWebTransportStream { + self.exchange_packets(); + let mut new_stream_received = false; + let mut data_received = false; + let mut wt_stream = None; + let mut stream_closed = false; + let mut recv_data = Vec::new(); + while let Some(event) = self.server.next_event() { + match event { + Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(request)) => { + assert_eq!(stream_id, request.stream_id()); + new_stream_received = true; + } + Http3ServerEvent::Data { + mut data, + fin, + stream, + } => { + recv_data.append(&mut data); + stream_closed = fin; + data_received = true; + wt_stream = Some(stream); + } + _ => {} + } + } + assert_eq!(&recv_data[..], expected_data); + assert!(data_received); + assert_eq!(new_stream, new_stream_received); + assert_eq!(stream_closed, expected_fin); + wt_stream.unwrap() + } + + fn close_stream_sending_server(&mut self, wt_stream: &mut Http3OrWebTransportStream) { + wt_stream.stream_close_send().unwrap(); + self.exchange_packets(); + } + + fn reset_stream_server(&mut self, wt_stream: &mut Http3OrWebTransportStream) { + wt_stream + .stream_reset_send(Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn stream_stop_sending_server(&mut self, wt_stream: &mut Http3OrWebTransportStream) { + wt_stream + .stream_stop_sending(Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn receive_reset_server(&mut self, expected_stream_id: StreamId, expected_error: u64) { + let stream_reset = |e| { + matches!( + e, + Http3ServerEvent::StreamReset { + stream, + error + } if stream.stream_id() == expected_stream_id && error == expected_error + ) + }; + assert!(self.server.events().any(stream_reset)); + } + + fn receive_stop_sending_server(&mut self, expected_stream_id: StreamId, expected_error: u64) { + let stop_sending = |e| { + matches!( + e, + Http3ServerEvent::StreamStopSending { + stream, + error + } if stream.stream_id() == expected_stream_id && error == expected_error + ) + }; + assert!(self.server.events().any(stop_sending)); + } + + fn check_events_after_closing_session_server( + &mut self, + expected_reset_ids: &[StreamId], + expected_error_stream_reset: Option<u64>, + expected_stop_sending_ids: &[StreamId], + expected_error_stream_stop_sending: Option<u64>, + expected_session_close: &Option<(StreamId, SessionCloseReason)>, + ) { + let mut reset_ids_count = 0; + let mut stop_sending_ids_count = 0; + let mut close_event = false; + while let Some(event) = self.server.next_event() { + match event { + Http3ServerEvent::StreamReset { stream, error } => { + assert!(expected_reset_ids.contains(&stream.stream_id())); + assert_eq!(expected_error_stream_reset.unwrap(), error); + reset_ids_count += 1; + } + Http3ServerEvent::StreamStopSending { stream, error } => { + assert!(expected_stop_sending_ids.contains(&stream.stream_id())); + assert_eq!(expected_error_stream_stop_sending.unwrap(), error); + stop_sending_ids_count += 1; + } + Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed { + session, + reason, + headers, + }) => { + close_event = true; + assert_eq!( + session.stream_id(), + expected_session_close.as_ref().unwrap().0 + ); + assert_eq!(expected_session_close.as_ref().unwrap().1, reason); + assert!(headers.is_none()); + } + _ => {} + } + } + assert_eq!(reset_ids_count, expected_reset_ids.len()); + assert_eq!(stop_sending_ids_count, expected_stop_sending_ids.len()); + assert_eq!(close_event, expected_session_close.is_some()); + } + + pub fn session_close_frame_client(&mut self, session_id: StreamId, error: u32, message: &str) { + self.client + .webtransport_close_session(session_id, error, message) + .unwrap(); + } + + pub fn session_close_frame_server( + wt_session: &mut WebTransportRequest, + error: u32, + message: &str, + ) { + wt_session.close_session(error, message).unwrap(); + } + + fn max_datagram_size(&self, stream_id: StreamId) -> Result<u64, Error> { + self.client.webtransport_max_datagram_size(stream_id) + } + + fn send_datagram(&mut self, stream_id: StreamId, buf: &[u8]) -> Result<(), Error> { + self.client.webtransport_send_datagram(stream_id, buf, None) + } + + fn check_datagram_received_client( + &mut self, + expected_stream_id: StreamId, + expected_dgram: &[u8], + ) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Datagram { + session_id, + datagram + }) if session_id == expected_stream_id && datagram == expected_dgram + ) + }; + assert!(self.client.events().any(wt_datagram_event)); + } + + fn check_datagram_received_server( + &mut self, + expected_session: &WebTransportRequest, + expected_dgram: &[u8], + ) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram { + session, + datagram + }) if session.stream_id() == expected_session.stream_id() && datagram == expected_dgram + ) + }; + assert!(self.server.events().any(wt_datagram_event)); + } + + fn check_no_datagram_received_client(&mut self) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Datagram { .. }) + ) + }; + assert!(!self.client.events().any(wt_datagram_event)); + } + + fn check_no_datagram_received_server(&mut self) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram { .. }) + ) + }; + assert!(!self.server.events().any(wt_datagram_event)); + } +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs new file mode 100644 index 0000000000..27f669861d --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs @@ -0,0 +1,280 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::time::Duration; + +use neqo_common::{event::Provider, Encoder}; +use neqo_crypto::AuthenticationStatus; +use neqo_transport::{Connection, ConnectionError, StreamType}; +use test_fixture::{default_server_h3, now}; + +use super::{connect, default_http3_client, default_http3_server, exchange_packets}; +use crate::{ + settings::{HSetting, HSettingType, HSettings}, + Error, HFrame, Http3Client, Http3ClientEvent, Http3Parameters, Http3Server, Http3State, + WebTransportEvent, +}; + +fn check_wt_event(client: &mut Http3Client, wt_enable_client: bool, wt_enable_server: bool) { + let wt_event = client.events().find_map(|e| { + if let Http3ClientEvent::WebTransport(WebTransportEvent::Negotiated(neg)) = e { + Some(neg) + } else { + None + } + }); + + assert_eq!(wt_event.is_some(), wt_enable_client); + if let Some(wt) = wt_event { + assert_eq!(wt, wt_enable_client && wt_enable_server); + } +} + +fn connect_wt(wt_enabled_client: bool, wt_enabled_server: bool) -> (Http3Client, Http3Server) { + connect( + Http3Parameters::default().webtransport(wt_enabled_client), + Http3Parameters::default().webtransport(wt_enabled_server), + ) +} + +#[test] +fn negotiate_wt() { + let (mut client, _server) = connect_wt(true, true); + assert!(client.webtransport_enabled()); + check_wt_event(&mut client, true, true); + + let (mut client, _server) = connect_wt(true, false); + assert!(!client.webtransport_enabled()); + check_wt_event(&mut client, true, false); + + let (mut client, _server) = connect_wt(false, true); + assert!(!client.webtransport_enabled()); + check_wt_event(&mut client, false, true); + + let (mut client, _server) = connect_wt(false, false); + assert!(!client.webtransport_enabled()); + check_wt_event(&mut client, false, false); +} + +#[derive(PartialEq, Eq)] +enum ClientState { + ClientEnabled, + ClientDisabled, +} + +#[derive(PartialEq, Eq)] +enum ServerState { + ServerEnabled, + ServerDisabled, +} + +fn zero_rtt( + client_state: &ClientState, + server_state: &ServerState, + client_resumed_state: &ClientState, + server_resumed_state: &ServerState, +) { + let client_org = ClientState::ClientEnabled.eq(client_state); + let server_org = ServerState::ServerEnabled.eq(server_state); + let client_resumed = ClientState::ClientEnabled.eq(client_resumed_state); + let server_resumed = ServerState::ServerEnabled.eq(server_resumed_state); + + let (mut client, mut server) = connect_wt(client_org, server_org); + assert_eq!(client.webtransport_enabled(), client_org && server_org); + + // exchange token + let out = server.process(None, now()); + // We do not have a token so we need to wait for a resumption token timer to trigger. + std::mem::drop(client.process(out.as_dgram_ref(), now() + Duration::from_millis(250))); + assert_eq!(client.state(), Http3State::Connected); + let token = client + .events() + .find_map(|e| { + if let Http3ClientEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }) + .unwrap(); + + let mut client = default_http3_client(Http3Parameters::default().webtransport(client_resumed)); + let mut server = default_http3_server(Http3Parameters::default().webtransport(server_resumed)); + client + .enable_resumption(now(), &token) + .expect("Set resumption token."); + assert_eq!(client.state(), Http3State::ZeroRtt); + + exchange_packets(&mut client, &mut server); + + assert_eq!(&client.state(), &Http3State::Connected); + assert_eq!( + client.webtransport_enabled(), + client_resumed && server_resumed + ); + + let mut early_data_accepted = true; + // The only case we should not do 0-RTT is when webtransport was enabled + // originally and is disabled afterwards. + if server_org && !server_resumed { + early_data_accepted = false; + } + assert_eq!( + client.tls_info().unwrap().early_data_accepted(), + early_data_accepted + ); + + check_wt_event(&mut client, client_resumed, server_resumed); +} + +#[test] +fn zero_rtt_wt_settings() { + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); + + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); +} + +fn exchange_packets2(client: &mut Http3Client, server: &mut Connection) { + let mut out = None; + loop { + out = client.process(out.as_ref(), now()).dgram(); + out = server.process(out.as_ref(), now()).dgram(); + if out.is_none() { + break; + } + } +} + +#[test] +fn wrong_setting_value() { + const CONTROL_STREAM_TYPE: &[u8] = &[0x0]; + let mut client = default_http3_client(Http3Parameters::default()); + let mut server = default_server_h3(); + + exchange_packets2(&mut client, &mut server); + client.authenticated(AuthenticationStatus::Ok, now()); + exchange_packets2(&mut client, &mut server); + + let control = server.stream_create(StreamType::UniDi).unwrap(); + server.stream_send(control, CONTROL_STREAM_TYPE).unwrap(); + // Encode a settings frame and send it. + let mut enc = Encoder::default(); + let settings = HFrame::Settings { + settings: HSettings::new(&[HSetting::new(HSettingType::EnableWebTransport, 2)]), + }; + settings.encode(&mut enc); + assert_eq!( + server.stream_send(control, enc.as_ref()).unwrap(), + enc.as_ref().len() + ); + + exchange_packets2(&mut client, &mut server); + match client.state() { + Http3State::Closing(err) | Http3State::Closed(err) => { + assert_eq!( + err, + ConnectionError::Application(Error::HttpSettings.code()) + ); + } + _ => panic!("Wrong state {:?}", client.state()), + }; +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs new file mode 100644 index 0000000000..5f929d0e4b --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs @@ -0,0 +1,456 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem; + +use neqo_common::{event::Provider, Encoder}; +use neqo_transport::StreamType; +use test_fixture::now; + +use crate::{ + features::extended_connect::{ + tests::webtransport::{ + default_http3_client, default_http3_server, wt_default_parameters, WtTest, + }, + SessionCloseReason, + }, + frames::WebTransportFrame, + Error, Header, Http3ClientEvent, Http3OrWebTransportStream, Http3Server, Http3ServerEvent, + Http3State, Priority, WebTransportEvent, WebTransportServerEvent, + WebTransportSessionAcceptAction, +}; + +#[test] +fn wt_session() { + let mut wt = WtTest::new(); + mem::drop(wt.create_wt_session()); +} + +#[test] +fn wt_session_reject() { + let mut wt = WtTest::new(); + let headers = vec![Header::new(":status", "404")]; + let accept_res = WebTransportSessionAcceptAction::Reject(headers.clone()); + let (wt_session_id, _wt_session) = wt.negotiate_wt_session(&accept_res); + + wt.check_session_closed_event_client( + wt_session_id, + &SessionCloseReason::Status(404), + &Some(headers), + ); +} + +#[test] +fn wt_session_close_client() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt.cancel_session_client(wt_session.stream_id()); + wt.check_session_closed_event_server( + &mut wt_session, + &SessionCloseReason::Error(Error::HttpNoError.code()), + ); +} + +#[test] +fn wt_session_close_server() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt.cancel_session_server(&mut wt_session); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpNoError.code()), + &None, + ); +} + +#[test] +fn wt_session_close_server_close_send() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt_session.stream_close_send().unwrap(); + wt.exchange_packets(); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + &None, + ); +} + +#[test] +fn wt_session_close_server_stop_sending() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt_session + .stream_stop_sending(Error::HttpNoError.code()) + .unwrap(); + wt.exchange_packets(); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpNoError.code()), + &None, + ); +} + +#[test] +fn wt_session_close_server_reset() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt_session + .stream_reset_send(Error::HttpNoError.code()) + .unwrap(); + wt.exchange_packets(); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpNoError.code()), + &None, + ); +} + +#[test] +fn wt_session_response_with_1xx() { + let mut wt = WtTest::new(); + + let wt_session_id = wt + .client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + wt.exchange_packets(); + + let mut wt_server_session = None; + while let Some(event) = wt.server.next_event() { + if let Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + session, + headers, + }) = event + { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + wt_server_session = Some(session); + } + } + + let mut wt_server_session = wt_server_session.unwrap(); + + // Send interim response. + wt_server_session + .send_headers(&[Header::new(":status", "111")]) + .unwrap(); + wt_server_session + .response(&WebTransportSessionAcceptAction::Accept) + .unwrap(); + + wt.exchange_packets(); + + let wt_session_negotiated_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Session{ + stream_id, + status, + headers, + }) if ( + stream_id == wt_session_id && + status == 200 && + headers.contains(&Header::new(":status", "200")) + ) + ) + }; + assert!(wt.client.events().any(wt_session_negotiated_event)); + + assert_eq!(wt_session_id, wt_server_session.stream_id()); +} + +#[test] +fn wt_session_response_with_redirect() { + let headers = [Header::new(":status", "302"), Header::new("location", "/")].to_vec(); + let mut wt = WtTest::new(); + + let accept_res = WebTransportSessionAcceptAction::Reject(headers.clone()); + + let (wt_session_id, _wt_session) = wt.negotiate_wt_session(&accept_res); + + wt.check_session_closed_event_client( + wt_session_id, + &SessionCloseReason::Status(302), + &Some(headers), + ); +} + +#[test] +fn wt_session_respone_200_with_fin() { + let mut wt = WtTest::new(); + + let wt_session_id = wt + .client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + wt.exchange_packets(); + let mut wt_server_session = None; + while let Some(event) = wt.server.next_event() { + if let Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + session, + headers, + }) = event + { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + wt_server_session = Some(session); + } + } + + let mut wt_server_session = wt_server_session.unwrap(); + wt_server_session + .response(&WebTransportSessionAcceptAction::Accept) + .unwrap(); + wt_server_session.stream_close_send().unwrap(); + + wt.exchange_packets(); + + let wt_session_close_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::SessionClosed{ + stream_id, + reason, + headers, + .. + }) if ( + stream_id == wt_session_id && + reason == SessionCloseReason::Clean{ error: 0, message: String::new()} && + headers.is_none() + ) + ) + }; + assert!(wt.client.events().any(wt_session_close_event)); + + assert_eq!(wt_session_id, wt_server_session.stream_id()); +} + +#[test] +fn wt_session_close_frame_client() { + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt.session_close_frame_client(wt_session.stream_id(), ERROR_NUM, ERROR_MESSAGE); + wt.exchange_packets(); + + wt.check_session_closed_event_server( + &mut wt_session, + &SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + ); +} + +#[test] +fn wt_session_close_frame_server() { + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + WtTest::session_close_frame_server(&mut wt_session, ERROR_NUM, ERROR_MESSAGE); + wt.exchange_packets(); + + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + &None, + ); +} + +#[test] +fn wt_unknown_session_frame_client() { + const UNKNOWN_FRAME_LEN: usize = 832; + const BUF: &[u8] = &[0; 10]; + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + // Send an unknown frame. + let mut enc = Encoder::with_capacity(UNKNOWN_FRAME_LEN + 4); + enc.encode_varint(1028_u64); // Arbitrary type. + enc.encode_varint(UNKNOWN_FRAME_LEN as u64); + let mut buf: Vec<_> = enc.into(); + buf.resize(UNKNOWN_FRAME_LEN + buf.len(), 0); + wt.client.send_data(wt_session.stream_id(), &buf).unwrap(); + wt.exchange_packets(); + + // The session is still active + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, false); + + // Now close the session. + wt.session_close_frame_client(wt_session.stream_id(), ERROR_NUM, ERROR_MESSAGE); + wt.exchange_packets(); + + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + )), + ); +} + +#[test] +fn wt_close_session_frame_broken_client() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + // Send a incorrect CloseSession frame. + let mut enc = Encoder::default(); + WebTransportFrame::CloseSession { + error: 5, + message: "Hello".to_string(), + } + .encode(&mut enc); + let mut buf: Vec<_> = enc.into(); + // Corrupt the string. + buf[9] = 0xff; + wt.client.send_data(wt_session.stream_id(), &buf).unwrap(); + wt.exchange_packets(); + + // check that the webtransport session is closed. + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpGeneralProtocolStream.code()), + &None, + ); + wt.check_session_closed_event_server( + &mut wt_session, + &SessionCloseReason::Error(Error::HttpGeneralProtocolStream.code()), + ); + + // The Http3 session is still working. + assert_eq!(wt.client.state(), Http3State::Connected); + assert_eq!(wt_session.state(), Http3State::Connected); +} + +fn receive_request(server: &mut Http3Server) -> Option<Http3OrWebTransportStream> { + while let Some(event) = server.next_event() { + if let Http3ServerEvent::Headers { stream, .. } = event { + return Some(stream); + } + } + None +} + +#[test] +// Ignoring this test as it is panicking at wt.create_wt_stream_client +// Issue # 1386 is created to track this +#[ignore] +fn wt_close_session_cannot_be_sent_at_once() { + const BUF: &[u8] = &[0; 443]; + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + + let client = default_http3_client(wt_default_parameters()); + let server = default_http3_server(wt_default_parameters()); + let mut wt = WtTest::new_with(client, server); + + let mut wt_session = wt.create_wt_session(); + + // Fill the flow control window using an unrelated http stream. + let req_id = wt + .client + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + assert_eq!(req_id, 4); + wt.exchange_packets(); + let mut req = receive_request(&mut wt.server).unwrap(); + req.send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", BUF.len().to_string()), + ]) + .unwrap(); + req.send_data(BUF).unwrap(); + + // Now close the session. + WtTest::session_close_frame_server(&mut wt_session, ERROR_NUM, ERROR_MESSAGE); + // server cannot create new streams. + assert_eq!( + wt_session.create_stream(StreamType::UniDi), + Err(Error::InvalidStreamId) + ); + + let out = wt.server.process(None, now()); + let out = wt.client.process(out.as_dgram_ref(), now()); + + // Client has not received the full CloseSession frame and it can create more streams. + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + let out = wt.server.process(out.as_dgram_ref(), now()); + let out = wt.client.process(out.as_dgram_ref(), now()); + let out = wt.server.process(out.as_dgram_ref(), now()); + let out = wt.client.process(out.as_dgram_ref(), now()); + let out = wt.server.process(out.as_dgram_ref(), now()); + let _out = wt.client.process(out.as_dgram_ref(), now()); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_client], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + )), + ); + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs new file mode 100644 index 0000000000..b898dbb31e --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs @@ -0,0 +1,1131 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem; + +use neqo_transport::StreamType; + +use crate::{ + features::extended_connect::{tests::webtransport::WtTest, SessionCloseReason}, + Error, +}; + +#[test] +fn wt_client_stream_uni() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + let send_stats = wt.send_stream_stats(wt_stream).unwrap(); + assert_eq!(send_stats.bytes_written(), 0); + assert_eq!(send_stats.bytes_sent(), 0); + assert_eq!(send_stats.bytes_acked(), 0); + + wt.send_data_client(wt_stream, BUF_CLIENT); + wt.receive_data_server(wt_stream, true, BUF_CLIENT, false); + let send_stats = wt.send_stream_stats(wt_stream).unwrap(); + assert_eq!(send_stats.bytes_written(), BUF_CLIENT.len() as u64); + assert_eq!(send_stats.bytes_sent(), BUF_CLIENT.len() as u64); + assert_eq!(send_stats.bytes_acked(), BUF_CLIENT.len() as u64); + + // Send data again to test if the stats has the expected values. + wt.send_data_client(wt_stream, BUF_CLIENT); + wt.receive_data_server(wt_stream, false, BUF_CLIENT, false); + let send_stats = wt.send_stream_stats(wt_stream).unwrap(); + assert_eq!(send_stats.bytes_written(), (BUF_CLIENT.len() * 2) as u64); + assert_eq!(send_stats.bytes_sent(), (BUF_CLIENT.len() * 2) as u64); + assert_eq!(send_stats.bytes_acked(), (BUF_CLIENT.len() * 2) as u64); + + let recv_stats = wt.recv_stream_stats(wt_stream); + assert_eq!(recv_stats.unwrap_err(), Error::InvalidStreamId); +} + +#[test] +fn wt_client_stream_bidi() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(wt_client_stream, BUF_CLIENT); + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, false); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_client_stream, false, BUF_SERVER, false); + let send_stats = wt.send_stream_stats(wt_client_stream).unwrap(); + assert_eq!(send_stats.bytes_written(), BUF_CLIENT.len() as u64); + assert_eq!(send_stats.bytes_sent(), BUF_CLIENT.len() as u64); + assert_eq!(send_stats.bytes_acked(), BUF_CLIENT.len() as u64); + + let recv_stats = wt.recv_stream_stats(wt_client_stream).unwrap(); + assert_eq!(recv_stats.bytes_received(), BUF_SERVER.len() as u64); + assert_eq!(recv_stats.bytes_read(), BUF_SERVER.len() as u64); +} + +#[test] +fn wt_server_stream_uni() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + let send_stats = wt.send_stream_stats(wt_server_stream.stream_id()); + assert_eq!(send_stats.unwrap_err(), Error::InvalidStreamId); + + let recv_stats = wt.recv_stream_stats(wt_server_stream.stream_id()).unwrap(); + assert_eq!(recv_stats.bytes_received(), BUF_SERVER.len() as u64); + assert_eq!(recv_stats.bytes_read(), BUF_SERVER.len() as u64); +} + +#[test] +fn wt_server_stream_bidi() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.send_data_client(wt_server_stream.stream_id(), BUF_CLIENT); + mem::drop(wt.receive_data_server(wt_server_stream.stream_id(), false, BUF_CLIENT, false)); + let stats = wt.send_stream_stats(wt_server_stream.stream_id()).unwrap(); + assert_eq!(stats.bytes_written(), BUF_CLIENT.len() as u64); + assert_eq!(stats.bytes_sent(), BUF_CLIENT.len() as u64); + assert_eq!(stats.bytes_acked(), BUF_CLIENT.len() as u64); + + let recv_stats = wt.recv_stream_stats(wt_server_stream.stream_id()).unwrap(); + assert_eq!(recv_stats.bytes_received(), BUF_SERVER.len() as u64); + assert_eq!(recv_stats.bytes_read(), BUF_SERVER.len() as u64); +} + +#[test] +fn wt_client_stream_uni_close() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + wt.close_stream_sending_client(wt_stream); + wt.receive_data_server(wt_stream, true, BUF_CLIENT, true); +} + +#[test] +fn wt_client_stream_bidi_close() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + + wt.send_data_client(wt_client_stream, BUF_CLIENT); + wt.close_stream_sending_client(wt_client_stream); + + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, true); + + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.close_stream_sending_server(&mut wt_server_stream); + wt.receive_data_client(wt_client_stream, false, BUF_SERVER, true); +} + +#[test] +fn wt_server_stream_uni_closed() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.close_stream_sending_server(&mut wt_server_stream); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, true); +} + +#[test] +fn wt_server_stream_bidi_close() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.close_stream_sending_server(&mut wt_server_stream); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, true); + wt.send_data_client(wt_server_stream.stream_id(), BUF_CLIENT); + wt.close_stream_sending_client(wt_server_stream.stream_id()); + mem::drop(wt.receive_data_server(wt_server_stream.stream_id(), false, BUF_CLIENT, true)); +} + +#[test] +fn wt_client_stream_uni_reset() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + mem::drop(wt.receive_data_server(wt_stream, true, BUF_CLIENT, false)); + wt.reset_stream_client(wt_stream); + wt.receive_reset_server(wt_stream, Error::HttpNoError.code()); +} + +#[test] +fn wt_server_stream_uni_reset() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.reset_stream_server(&mut wt_server_stream); + wt.receive_reset_client(wt_server_stream.stream_id()); +} + +#[test] +fn wt_client_stream_bidi_reset() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + + wt.send_data_client(wt_client_stream, BUF_CLIENT); + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, false); + + wt.reset_stream_client(wt_client_stream); + wt.receive_reset_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + + wt.reset_stream_server(&mut wt_server_stream); + wt.receive_reset_client(wt_client_stream); +} + +#[test] +fn wt_server_stream_bidi_reset() { + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + + wt.reset_stream_client(wt_server_stream.stream_id()); + wt.receive_reset_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + + wt.reset_stream_server(&mut wt_server_stream); + wt.receive_reset_client(wt_server_stream.stream_id()); +} + +#[test] +fn wt_client_stream_uni_stop_sending() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + let mut wt_server_stream = wt.receive_data_server(wt_stream, true, BUF_CLIENT, false); + wt.stream_stop_sending_server(&mut wt_server_stream); + wt.receive_stop_sending_client(wt_stream); +} + +#[test] +fn wt_server_stream_uni_stop_sending() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.stream_stop_sending_client(wt_server_stream.stream_id()); + wt.receive_stop_sending_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); +} + +#[test] +fn wt_client_stream_bidi_stop_sending() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + + wt.send_data_client(wt_client_stream, BUF_CLIENT); + + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, false); + + wt.stream_stop_sending_client(wt_client_stream); + + wt.receive_stop_sending_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + wt.stream_stop_sending_server(&mut wt_server_stream); + wt.receive_stop_sending_client(wt_server_stream.stream_id()); +} + +#[test] +fn wt_server_stream_bidi_stop_sending() { + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.stream_stop_sending_client(wt_server_stream.stream_id()); + wt.receive_stop_sending_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + wt.stream_stop_sending_server(&mut wt_server_stream); + wt.receive_stop_sending_client(wt_server_stream.stream_id()); +} + +// For the following tests the client cancels a session. The streams are in different states: +// 1) Both sides of a bidirectional client stream are opened. +// 2) A client unidirectional stream is opened. +// 3) A client unidirectional stream has been closed and both sides consumed the closing info. +// 4) A client unidirectional stream has been closed, but only the server has consumed the closing +// info. +// 5) A client unidirectional stream has been closed, but only the client has consum the closing +// info. +// 6) Both sides of a bidirectional server stream are opened. +// 7) A server unidirectional stream is opened. +// 8) A server unidirectional stream has been closed and both sides consumed the closing info. +// 9) A server unidirectional stream has been closed, but only the server has consumed the closing +// info. +// 10) A server unidirectional stream has been closed, but only the client has consumed the closing +// info. +// 11) Both sides of a bidirectional stream have been closed and consumed by both sides. +// 12) Both sides of a bidirectional stream have been closed, but not consumed by both sides. +// 13) Multiples open streams + +#[test] +fn wt_client_session_close_1() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let bidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_from_client, BUF); + std::mem::drop(wt.receive_data_server(bidi_from_client, true, BUF, false)); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_2() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + std::mem::drop(wt.receive_data_server(unidi_from_client, true, BUF, false)); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[unidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_from_client], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_3() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + std::mem::drop(wt.receive_data_server(unidi_from_client, true, BUF, false)); + wt.close_stream_sending_client(unidi_from_client); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_4() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + let mut unidi_from_client_s = wt.receive_data_server(unidi_from_client, true, BUF, false); + wt.stream_stop_sending_server(&mut unidi_from_client_s); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_from_client], + Some(Error::HttpNoError.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_5() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + mem::drop(wt.receive_data_server(unidi_from_client, true, BUF, false)); + wt.reset_stream_client(unidi_from_client); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[unidi_from_client], + Some(Error::HttpNoError.code()), + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_6() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_from_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_from_server, BUF); + wt.receive_data_client(bidi_from_server.stream_id(), true, BUF, false); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_7() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_from_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_from_server, BUF); + wt.receive_data_client(unidi_from_server.stream_id(), true, BUF, false); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[unidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_8() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, true); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_9() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.stream_stop_sending_client(unidi_server.stream_id()); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpNoError.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_10() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_11() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.receive_data_client(bidi_server.stream_id(), true, BUF, true); + wt.stream_stop_sending_server(&mut bidi_server); + wt.receive_stop_sending_client(bidi_server.stream_id()); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_12() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.stream_stop_sending_server(&mut bidi_server); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpNoError.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_13() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let bidi_client_1 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_1, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_1, true, BUF, false)); + let bidi_client_2 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_2, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_2, true, BUF, false)); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +// For the following tests the server cancels a session. The streams are in different states: +// 1) Both sides of a bidirectional client stream are opened. +// 2) A client unidirectional stream is opened. +// 3) A client unidirectional stream has been closed and consumed by both sides. +// 4) A client unidirectional stream has been closed, but not consumed by the client. +// 5) Both sides of a bidirectional server stream are opened. +// 6) A server unidirectional stream is opened. +// 7) A server unidirectional stream has been closed and consumed by both sides. +// 8) A server unidirectional stream has been closed, but not consumed by the client. +// 9) Both sides of a bidirectional stream have been closed and consumed by both sides. +// 10) Both sides of a bidirectional stream have been closed, but not consumed by the client. +// 12) Multiples open streams + +#[test] +fn wt_client_session_server_close_1() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let bidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client, BUF); + std::mem::drop(wt.receive_data_server(bidi_client, true, BUF, false)); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_client_session_server_close_2() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(unidi_client, BUF); + std::mem::drop(wt.receive_data_server(unidi_client, true, BUF, false)); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_client], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[unidi_client], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + &None, + ); +} + +#[test] +fn wt_client_session_server_close_3() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(unidi_client, BUF); + let mut unidi_client_s = wt.receive_data_server(unidi_client, true, BUF, false); + wt.stream_stop_sending_server(&mut unidi_client_s); + wt.receive_stop_sending_client(unidi_client); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[], + None, + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_4() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(unidi_client, BUF); + let mut unidi_client_s = wt.receive_data_server(unidi_client, true, BUF, false); + wt.stream_stop_sending_server(&mut unidi_client_s); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_client], + Some(Error::HttpNoError.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_5() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.receive_data_client(bidi_server.stream_id(), true, BUF, false); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_client_session_server_close_6() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, false); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_client_session_server_close_7() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, true); + + wt.cancel_session_server(&mut wt_session); + + // Already close stream will not have a reset event. + wt.check_events_after_closing_session_client( + &[], + None, + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_8() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + + wt.cancel_session_server(&mut wt_session); + + // The stream was only closed on the server side therefore it is cancelled on the client side. + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_9() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.receive_data_client(bidi_server.stream_id(), true, BUF, true); + wt.stream_stop_sending_server(&mut bidi_server); + wt.receive_stop_sending_client(bidi_server.stream_id()); + + wt.cancel_session_server(&mut wt_session); + + // Already close stream will not have a reset event. + wt.check_events_after_closing_session_client( + &[], + None, + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_10() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.stream_stop_sending_server(&mut bidi_server); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpNoError.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_11() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let bidi_client_1 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_1, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_1, true, BUF, false)); + let bidi_client_2 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_2, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_2, true, BUF, false)); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_session_close_frame_and_streams_client() { + const BUF: &[u8] = &[0; 10]; + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.exchange_packets(); + + wt.session_close_frame_client(wt_session.stream_id(), ERROR_NUM, ERROR_MESSAGE); + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); + wt.exchange_packets(); + + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + )), + ); +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs new file mode 100644 index 0000000000..adbdf07e11 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs @@ -0,0 +1,555 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::{any::Any, cell::RefCell, collections::BTreeSet, mem, rc::Rc}; + +use neqo_common::{qtrace, Encoder, Header, MessageType, Role}; +use neqo_qpack::{QPackDecoder, QPackEncoder}; +use neqo_transport::{streams::SendOrder, Connection, DatagramTracking, StreamId}; + +use super::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason}; +use crate::{ + frames::{FrameReader, StreamReaderRecvStreamWrapper, WebTransportFrame}, + recv_message::{RecvMessage, RecvMessageInfo}, + send_message::SendMessage, + CloseType, Error, HFrame, Http3StreamInfo, Http3StreamType, HttpRecvStream, + HttpRecvStreamEvents, Priority, PriorityHandler, ReceiveOutput, RecvStream, RecvStreamEvents, + Res, SendStream, SendStreamEvents, Stream, +}; + +#[derive(Debug, PartialEq)] +enum SessionState { + Negotiating, + Active, + FinPending, + Done, +} + +impl SessionState { + pub fn closing_state(&self) -> bool { + matches!(self, Self::FinPending | Self::Done) + } +} + +#[derive(Debug)] +pub(crate) struct WebTransportSession { + control_stream_recv: Box<dyn RecvStream>, + control_stream_send: Box<dyn SendStream>, + stream_event_listener: Rc<RefCell<WebTransportSessionListener>>, + session_id: StreamId, + state: SessionState, + frame_reader: FrameReader, + events: Box<dyn ExtendedConnectEvents>, + send_streams: BTreeSet<StreamId>, + recv_streams: BTreeSet<StreamId>, + role: Role, +} + +impl ::std::fmt::Display for WebTransportSession { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "WebTransportSession session={}", self.session_id,) + } +} + +impl WebTransportSession { + #[must_use] + pub fn new( + session_id: StreamId, + events: Box<dyn ExtendedConnectEvents>, + role: Role, + qpack_encoder: Rc<RefCell<QPackEncoder>>, + qpack_decoder: Rc<RefCell<QPackDecoder>>, + ) -> Self { + let stream_event_listener = Rc::new(RefCell::new(WebTransportSessionListener::default())); + Self { + control_stream_recv: Box::new(RecvMessage::new( + &RecvMessageInfo { + message_type: MessageType::Response, + stream_type: Http3StreamType::ExtendedConnect, + stream_id: session_id, + header_frame_type_read: false, + }, + qpack_decoder, + Box::new(stream_event_listener.clone()), + None, + PriorityHandler::new(false, Priority::default()), + )), + control_stream_send: Box::new(SendMessage::new( + MessageType::Request, + Http3StreamType::ExtendedConnect, + session_id, + qpack_encoder, + Box::new(stream_event_listener.clone()), + )), + stream_event_listener, + session_id, + state: SessionState::Negotiating, + frame_reader: FrameReader::new(), + events, + send_streams: BTreeSet::new(), + recv_streams: BTreeSet::new(), + role, + } + } + + /// # Panics + /// + /// This function is only called with `RecvStream` and `SendStream` that also implement + /// the http specific functions and `http_stream()` will never return `None`. + #[must_use] + pub fn new_with_http_streams( + session_id: StreamId, + events: Box<dyn ExtendedConnectEvents>, + role: Role, + mut control_stream_recv: Box<dyn RecvStream>, + mut control_stream_send: Box<dyn SendStream>, + ) -> Self { + let stream_event_listener = Rc::new(RefCell::new(WebTransportSessionListener::default())); + control_stream_recv + .http_stream() + .unwrap() + .set_new_listener(Box::new(stream_event_listener.clone())); + control_stream_send + .http_stream() + .unwrap() + .set_new_listener(Box::new(stream_event_listener.clone())); + Self { + control_stream_recv, + control_stream_send, + stream_event_listener, + session_id, + state: SessionState::Active, + frame_reader: FrameReader::new(), + events, + send_streams: BTreeSet::new(), + recv_streams: BTreeSet::new(), + role, + } + } + + /// # Errors + /// + /// The function can only fail if supplied headers are not valid http headers. + /// + /// # Panics + /// + /// `control_stream_send` implements the http specific functions and `http_stream()` + /// will never return `None`. + pub fn send_request(&mut self, headers: &[Header], conn: &mut Connection) -> Res<()> { + self.control_stream_send + .http_stream() + .unwrap() + .send_headers(headers, conn) + } + + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + qtrace!([self], "receive control data"); + let (out, _) = self.control_stream_recv.receive(conn)?; + debug_assert!(out == ReceiveOutput::NoOutput); + self.maybe_check_headers(); + self.read_control_stream(conn)?; + Ok((ReceiveOutput::NoOutput, self.state == SessionState::Done)) + } + + fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + let (out, _) = self + .control_stream_recv + .http_stream() + .unwrap() + .header_unblocked(conn)?; + debug_assert!(out == ReceiveOutput::NoOutput); + self.maybe_check_headers(); + self.read_control_stream(conn)?; + Ok((ReceiveOutput::NoOutput, self.state == SessionState::Done)) + } + + fn maybe_update_priority(&mut self, priority: Priority) -> bool { + self.control_stream_recv + .http_stream() + .unwrap() + .maybe_update_priority(priority) + } + + fn priority_update_frame(&mut self) -> Option<HFrame> { + self.control_stream_recv + .http_stream() + .unwrap() + .priority_update_frame() + } + + fn priority_update_sent(&mut self) { + self.control_stream_recv + .http_stream() + .unwrap() + .priority_update_sent(); + } + + fn send(&mut self, conn: &mut Connection) -> Res<()> { + self.control_stream_send.send(conn)?; + if self.control_stream_send.done() { + self.state = SessionState::Done; + } + Ok(()) + } + + fn has_data_to_send(&self) -> bool { + self.control_stream_send.has_data_to_send() + } + + fn done(&self) -> bool { + self.state == SessionState::Done + } + + fn close(&mut self, close_type: CloseType) { + if self.state.closing_state() { + return; + } + qtrace!("ExtendedConnect close the session"); + self.state = SessionState::Done; + if !close_type.locally_initiated() { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::from(close_type), + None, + ); + } + } + + /// # Panics + /// + /// This cannot panic because headers are checked before this function called. + pub fn maybe_check_headers(&mut self) { + if SessionState::Negotiating != self.state { + return; + } + + if let Some((headers, interim, fin)) = self.stream_event_listener.borrow_mut().get_headers() + { + qtrace!( + "ExtendedConnect response headers {:?}, fin={}", + headers, + fin + ); + + if interim { + if fin { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + Some(headers), + ); + self.state = SessionState::Done; + } + } else { + let status = headers + .iter() + .find_map(|h| { + if h.name() == ":status" { + h.value().parse::<u16>().ok() + } else { + None + } + }) + .unwrap(); + + self.state = if (200..300).contains(&status) { + if fin { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + Some(headers), + ); + SessionState::Done + } else { + self.events.session_start( + ExtendedConnectType::WebTransport, + self.session_id, + status, + headers, + ); + SessionState::Active + } + } else { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Status(status), + Some(headers), + ); + SessionState::Done + }; + } + } + } + + pub fn add_stream(&mut self, stream_id: StreamId) { + if let SessionState::Active = self.state { + if stream_id.is_bidi() { + self.send_streams.insert(stream_id); + self.recv_streams.insert(stream_id); + } else if stream_id.is_self_initiated(self.role) { + self.send_streams.insert(stream_id); + } else { + self.recv_streams.insert(stream_id); + } + + if !stream_id.is_self_initiated(self.role) { + self.events + .extended_connect_new_stream(Http3StreamInfo::new( + stream_id, + ExtendedConnectType::WebTransport.get_stream_type(self.session_id), + )); + } + } + } + + pub fn remove_recv_stream(&mut self, stream_id: StreamId) { + self.recv_streams.remove(&stream_id); + } + + pub fn remove_send_stream(&mut self, stream_id: StreamId) { + self.send_streams.remove(&stream_id); + } + + #[must_use] + pub fn is_active(&self) -> bool { + matches!(self.state, SessionState::Active) + } + + pub fn take_sub_streams(&mut self) -> (BTreeSet<StreamId>, BTreeSet<StreamId>) { + ( + mem::take(&mut self.recv_streams), + mem::take(&mut self.send_streams), + ) + } + + /// # Errors + /// + /// It may return an error if the frame is not correctly decoded. + pub fn read_control_stream(&mut self, conn: &mut Connection) -> Res<()> { + let (f, fin) = self + .frame_reader + .receive::<WebTransportFrame>(&mut StreamReaderRecvStreamWrapper::new( + conn, + &mut self.control_stream_recv, + )) + .map_err(|_| Error::HttpGeneralProtocolStream)?; + qtrace!([self], "Received frame: {:?} fin={}", f, fin); + if let Some(WebTransportFrame::CloseSession { error, message }) = f { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { error, message }, + None, + ); + self.state = if fin { + SessionState::Done + } else { + SessionState::FinPending + }; + } else if fin { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + None, + ); + self.state = SessionState::Done; + } + Ok(()) + } + + /// # Errors + /// + /// Return an error if the stream was closed on the transport layer, but that information is not + /// yet consumed on the http/3 layer. + pub fn close_session(&mut self, conn: &mut Connection, error: u32, message: &str) -> Res<()> { + self.state = SessionState::Done; + let close_frame = WebTransportFrame::CloseSession { + error, + message: message.to_string(), + }; + let mut encoder = Encoder::default(); + close_frame.encode(&mut encoder); + self.control_stream_send + .send_data_atomic(conn, encoder.as_ref())?; + self.control_stream_send.close(conn)?; + self.state = if self.control_stream_send.done() { + SessionState::Done + } else { + SessionState::FinPending + }; + Ok(()) + } + + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + self.control_stream_send.send_data(conn, buf) + } + + /// # Errors + /// + /// Returns an error if the datagram exceeds the remote datagram size limit. + pub fn send_datagram( + &self, + conn: &mut Connection, + buf: &[u8], + id: impl Into<DatagramTracking>, + ) -> Res<()> { + qtrace!([self], "send_datagram state={:?}", self.state); + if let SessionState::Active = self.state { + let mut dgram_data = Encoder::default(); + dgram_data.encode_varint(self.session_id.as_u64() / 4); + dgram_data.encode(buf); + conn.send_datagram(dgram_data.as_ref(), id)?; + } else { + debug_assert!(false); + return Err(Error::Unavailable); + } + Ok(()) + } + + pub fn datagram(&mut self, datagram: Vec<u8>) { + if let SessionState::Active = self.state { + self.events.new_datagram(self.session_id, datagram); + } + } +} + +impl Stream for Rc<RefCell<WebTransportSession>> { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::ExtendedConnect + } +} + +impl RecvStream for Rc<RefCell<WebTransportSession>> { + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.borrow_mut().receive(conn) + } + + fn reset(&mut self, close_type: CloseType) -> Res<()> { + self.borrow_mut().close(close_type); + Ok(()) + } + + fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> { + Some(self) + } + + fn webtransport(&self) -> Option<Rc<RefCell<WebTransportSession>>> { + Some(self.clone()) + } +} + +impl HttpRecvStream for Rc<RefCell<WebTransportSession>> { + fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.borrow_mut().header_unblocked(conn) + } + + fn maybe_update_priority(&mut self, priority: Priority) -> bool { + self.borrow_mut().maybe_update_priority(priority) + } + + fn priority_update_frame(&mut self) -> Option<HFrame> { + self.borrow_mut().priority_update_frame() + } + + fn priority_update_sent(&mut self) { + self.borrow_mut().priority_update_sent(); + } + + fn any(&self) -> &dyn Any { + self + } +} + +impl SendStream for Rc<RefCell<WebTransportSession>> { + fn send(&mut self, conn: &mut Connection) -> Res<()> { + self.borrow_mut().send(conn) + } + + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + self.borrow_mut().send_data(conn, buf) + } + + fn has_data_to_send(&self) -> bool { + self.borrow_mut().has_data_to_send() + } + + fn set_sendorder(&mut self, _conn: &mut Connection, _sendorder: Option<SendOrder>) -> Res<()> { + // Not relevant on session + Ok(()) + } + + fn set_fairness(&mut self, _conn: &mut Connection, _fairness: bool) -> Res<()> { + // Not relevant on session + Ok(()) + } + + fn stream_writable(&self) {} + + fn done(&self) -> bool { + self.borrow_mut().done() + } + + fn close(&mut self, conn: &mut Connection) -> Res<()> { + self.borrow_mut().close_session(conn, 0, "") + } + + fn close_with_message(&mut self, conn: &mut Connection, error: u32, message: &str) -> Res<()> { + self.borrow_mut().close_session(conn, error, message) + } + + fn handle_stop_sending(&mut self, close_type: CloseType) { + self.borrow_mut().close(close_type); + } +} + +#[derive(Debug, Default)] +struct WebTransportSessionListener { + headers: Option<(Vec<Header>, bool, bool)>, +} + +impl WebTransportSessionListener { + fn set_headers(&mut self, headers: Vec<Header>, interim: bool, fin: bool) { + self.headers = Some((headers, interim, fin)); + } + + pub fn get_headers(&mut self) -> Option<(Vec<Header>, bool, bool)> { + mem::take(&mut self.headers) + } +} + +impl RecvStreamEvents for Rc<RefCell<WebTransportSessionListener>> {} + +impl HttpRecvStreamEvents for Rc<RefCell<WebTransportSessionListener>> { + fn header_ready( + &self, + _stream_info: Http3StreamInfo, + headers: Vec<Header>, + interim: bool, + fin: bool, + ) { + if !interim || fin { + self.borrow_mut().set_headers(headers, interim, fin); + } + } +} + +impl SendStreamEvents for Rc<RefCell<WebTransportSessionListener>> {} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs new file mode 100644 index 0000000000..84dcd20618 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs @@ -0,0 +1,271 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, rc::Rc}; + +use neqo_common::Encoder; +use neqo_transport::{Connection, RecvStreamStats, SendStreamStats, StreamId}; + +use super::WebTransportSession; +use crate::{ + CloseType, Http3StreamInfo, Http3StreamType, ReceiveOutput, RecvStream, RecvStreamEvents, Res, + SendStream, SendStreamEvents, Stream, +}; + +pub const WEBTRANSPORT_UNI_STREAM: u64 = 0x54; +pub const WEBTRANSPORT_STREAM: u64 = 0x41; + +#[derive(Debug)] +pub(crate) struct WebTransportRecvStream { + stream_id: StreamId, + events: Box<dyn RecvStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + session_id: StreamId, + fin: bool, +} + +impl WebTransportRecvStream { + pub fn new( + stream_id: StreamId, + session_id: StreamId, + events: Box<dyn RecvStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + ) -> Self { + Self { + stream_id, + events, + session_id, + session, + fin: false, + } + } + + fn get_info(&self) -> Http3StreamInfo { + Http3StreamInfo::new(self.stream_id, self.stream_type()) + } +} + +impl Stream for WebTransportRecvStream { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::WebTransport(self.session_id) + } +} + +impl RecvStream for WebTransportRecvStream { + fn receive(&mut self, _conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.events.data_readable(self.get_info()); + Ok((ReceiveOutput::NoOutput, false)) + } + + fn reset(&mut self, close_type: CloseType) -> Res<()> { + if !matches!(close_type, CloseType::ResetApp(_)) { + self.events.recv_closed(self.get_info(), close_type); + } + self.session.borrow_mut().remove_recv_stream(self.stream_id); + Ok(()) + } + + fn read_data(&mut self, conn: &mut Connection, buf: &mut [u8]) -> Res<(usize, bool)> { + let (amount, fin) = conn.stream_recv(self.stream_id, buf)?; + self.fin = fin; + if fin { + self.session.borrow_mut().remove_recv_stream(self.stream_id); + } + Ok((amount, fin)) + } + + fn stats(&mut self, conn: &mut Connection) -> Res<RecvStreamStats> { + const TYPE_LEN_UNI: usize = Encoder::varint_len(WEBTRANSPORT_UNI_STREAM); + const TYPE_LEN_BIDI: usize = Encoder::varint_len(WEBTRANSPORT_STREAM); + + let stream_header_size = if self.stream_id.is_server_initiated() { + let id_len = if self.stream_id.is_uni() { + TYPE_LEN_UNI + } else { + TYPE_LEN_BIDI + }; + (id_len + Encoder::varint_len(self.session_id.as_u64())) as u64 + } else { + 0 + }; + + let stats = conn.recv_stream_stats(self.stream_id)?; + if stream_header_size == 0 { + return Ok(stats); + } + + let subtract_non_app_bytes = + |count: u64| -> u64 { count.saturating_sub(stream_header_size) }; + + let bytes_received = subtract_non_app_bytes(stats.bytes_received()); + let bytes_read = subtract_non_app_bytes(stats.bytes_read()); + + Ok(RecvStreamStats::new(bytes_received, bytes_read)) + } +} + +#[derive(Debug, PartialEq)] +enum WebTransportSenderStreamState { + SendingInit { buf: Vec<u8>, fin: bool }, + SendingData, + Done, +} + +#[derive(Debug)] +pub(crate) struct WebTransportSendStream { + stream_id: StreamId, + state: WebTransportSenderStreamState, + events: Box<dyn SendStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + session_id: StreamId, +} + +impl WebTransportSendStream { + pub fn new( + stream_id: StreamId, + session_id: StreamId, + events: Box<dyn SendStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + local: bool, + ) -> Self { + Self { + stream_id, + state: if local { + let mut d = Encoder::default(); + if stream_id.is_uni() { + d.encode_varint(WEBTRANSPORT_UNI_STREAM); + } else { + d.encode_varint(WEBTRANSPORT_STREAM); + } + d.encode_varint(session_id.as_u64()); + WebTransportSenderStreamState::SendingInit { + buf: d.into(), + fin: false, + } + } else { + WebTransportSenderStreamState::SendingData + }, + events, + session_id, + session, + } + } + + fn set_done(&mut self, close_type: CloseType) { + self.state = WebTransportSenderStreamState::Done; + self.events.send_closed(self.get_info(), close_type); + self.session.borrow_mut().remove_send_stream(self.stream_id); + } + + fn get_info(&self) -> Http3StreamInfo { + Http3StreamInfo::new(self.stream_id, self.stream_type()) + } +} + +impl Stream for WebTransportSendStream { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::WebTransport(self.session_id) + } +} + +impl SendStream for WebTransportSendStream { + fn send(&mut self, conn: &mut Connection) -> Res<()> { + if let WebTransportSenderStreamState::SendingInit { ref mut buf, fin } = self.state { + let sent = conn.stream_send(self.stream_id, &buf[..])?; + if sent == buf.len() { + if fin { + conn.stream_close_send(self.stream_id)?; + self.set_done(CloseType::Done); + } else { + self.state = WebTransportSenderStreamState::SendingData; + } + } else { + let b = buf.split_off(sent); + *buf = b; + } + } + Ok(()) + } + + fn has_data_to_send(&self) -> bool { + matches!( + self.state, + WebTransportSenderStreamState::SendingInit { .. } + ) + } + + fn stream_writable(&self) { + self.events.data_writable(self.get_info()); + } + + fn done(&self) -> bool { + self.state == WebTransportSenderStreamState::Done + } + + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + self.send(conn)?; + if self.state == WebTransportSenderStreamState::SendingData { + let sent = conn.stream_send(self.stream_id, buf)?; + Ok(sent) + } else { + Ok(0) + } + } + + fn set_sendorder(&mut self, conn: &mut Connection, sendorder: Option<i64>) -> Res<()> { + conn.stream_sendorder(self.stream_id, sendorder) + .map_err(|_| crate::Error::InvalidStreamId) + } + + fn set_fairness(&mut self, conn: &mut Connection, fairness: bool) -> Res<()> { + conn.stream_fairness(self.stream_id, fairness) + .map_err(|_| crate::Error::InvalidStreamId) + } + + fn handle_stop_sending(&mut self, close_type: CloseType) { + self.set_done(close_type); + } + + fn close(&mut self, conn: &mut Connection) -> Res<()> { + if let WebTransportSenderStreamState::SendingInit { ref mut fin, .. } = self.state { + *fin = true; + } else { + self.state = WebTransportSenderStreamState::Done; + conn.stream_close_send(self.stream_id)?; + self.set_done(CloseType::Done); + } + Ok(()) + } + + fn stats(&mut self, conn: &mut Connection) -> Res<SendStreamStats> { + const TYPE_LEN_UNI: usize = Encoder::varint_len(WEBTRANSPORT_UNI_STREAM); + const TYPE_LEN_BIDI: usize = Encoder::varint_len(WEBTRANSPORT_STREAM); + + let stream_header_size = if self.stream_id.is_client_initiated() { + let id_len = if self.stream_id.is_uni() { + TYPE_LEN_UNI + } else { + TYPE_LEN_BIDI + }; + (id_len + Encoder::varint_len(self.session_id.as_u64())) as u64 + } else { + 0 + }; + + let stats = conn.send_stream_stats(self.stream_id)?; + if stream_header_size == 0 { + return Ok(stats); + } + + let subtract_non_app_bytes = + |count: u64| -> u64 { count.saturating_sub(stream_header_size) }; + + let bytes_written = subtract_non_app_bytes(stats.bytes_written()); + let bytes_sent = subtract_non_app_bytes(stats.bytes_sent()); + let bytes_acked = subtract_non_app_bytes(stats.bytes_acked()); + Ok(SendStreamStats::new(bytes_written, bytes_sent, bytes_acked)) + } +} diff --git a/third_party/rust/neqo-http3/src/features/mod.rs b/third_party/rust/neqo-http3/src/features/mod.rs new file mode 100644 index 0000000000..34e21f50ac --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/mod.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{fmt::Debug, mem}; + +use neqo_common::qtrace; + +use crate::{ + client_events::Http3ClientEvents, + settings::{HSettingType, HSettings}, +}; + +pub mod extended_connect; + +/// States: +/// - `Disable` - it is not turned on for this connection. +/// - `Negotiating` - the feature is enabled locally, but settings from the peer have not been +/// received yet. +/// - `Negotiated` - the settings have been received and both sides support the feature. +/// - `NegotiationFailed` - the settings have been received and the peer does not support the +/// feature. +#[derive(Debug)] +pub enum NegotiationState { + Disabled, + Negotiating { + feature_type: HSettingType, + listener: Option<Http3ClientEvents>, + }, + Negotiated, + NegotiationFailed, +} + +impl NegotiationState { + #[must_use] + pub fn new(enable: bool, feature_type: HSettingType) -> Self { + if enable { + Self::Negotiating { + feature_type, + listener: None, + } + } else { + Self::Disabled + } + } + + pub fn set_listener(&mut self, new_listener: Http3ClientEvents) { + if let Self::Negotiating { listener, .. } = self { + *listener = Some(new_listener); + } + } + + pub fn handle_settings(&mut self, settings: &HSettings) { + if !self.locally_enabled() { + return; + } + + if let Self::Negotiating { + feature_type, + listener, + } = self + { + qtrace!( + "set_negotiated {:?} to {}", + feature_type, + settings.get(*feature_type) + ); + let cb = mem::take(listener); + let ft = *feature_type; + *self = if settings.get(ft) == 1 { + Self::Negotiated + } else { + Self::NegotiationFailed + }; + if let Some(l) = cb { + l.negotiation_done(ft, self.enabled()); + } + } + } + + #[must_use] + pub fn enabled(&self) -> bool { + matches!(self, &Self::Negotiated) + } + + #[must_use] + pub fn locally_enabled(&self) -> bool { + !matches!(self, &Self::Disabled) + } +} diff --git a/third_party/rust/neqo-http3/src/frames/hframe.rs b/third_party/rust/neqo-http3/src/frames/hframe.rs new file mode 100644 index 0000000000..83e69ba894 --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/hframe.rs @@ -0,0 +1,227 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{fmt::Debug, io::Write}; + +use neqo_common::{Decoder, Encoder}; +use neqo_crypto::random; +use neqo_transport::StreamId; + +use crate::{frames::reader::FrameDecoder, settings::HSettings, Error, Priority, Res}; + +pub(crate) type HFrameType = u64; + +pub const H3_FRAME_TYPE_DATA: HFrameType = 0x0; +pub const H3_FRAME_TYPE_HEADERS: HFrameType = 0x1; +pub const H3_FRAME_TYPE_CANCEL_PUSH: HFrameType = 0x3; +pub const H3_FRAME_TYPE_SETTINGS: HFrameType = 0x4; +pub const H3_FRAME_TYPE_PUSH_PROMISE: HFrameType = 0x5; +pub const H3_FRAME_TYPE_GOAWAY: HFrameType = 0x7; +pub const H3_FRAME_TYPE_MAX_PUSH_ID: HFrameType = 0xd; +pub const H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST: HFrameType = 0xf0700; +pub const H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH: HFrameType = 0xf0701; + +pub const H3_RESERVED_FRAME_TYPES: &[HFrameType] = &[0x2, 0x6, 0x8, 0x9]; + +// data for DATA frame is not read into HFrame::Data. +#[derive(PartialEq, Eq, Debug)] +pub enum HFrame { + Data { + len: u64, // length of the data + }, + Headers { + header_block: Vec<u8>, + }, + CancelPush { + push_id: u64, + }, + Settings { + settings: HSettings, + }, + PushPromise { + push_id: u64, + header_block: Vec<u8>, + }, + Goaway { + stream_id: StreamId, + }, + MaxPushId { + push_id: u64, + }, + Grease, + PriorityUpdateRequest { + element_id: u64, + priority: Priority, + }, + PriorityUpdatePush { + element_id: u64, + priority: Priority, + }, +} + +impl HFrame { + fn get_type(&self) -> HFrameType { + match self { + Self::Data { .. } => H3_FRAME_TYPE_DATA, + Self::Headers { .. } => H3_FRAME_TYPE_HEADERS, + Self::CancelPush { .. } => H3_FRAME_TYPE_CANCEL_PUSH, + Self::Settings { .. } => H3_FRAME_TYPE_SETTINGS, + Self::PushPromise { .. } => H3_FRAME_TYPE_PUSH_PROMISE, + Self::Goaway { .. } => H3_FRAME_TYPE_GOAWAY, + Self::MaxPushId { .. } => H3_FRAME_TYPE_MAX_PUSH_ID, + Self::PriorityUpdateRequest { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST, + Self::PriorityUpdatePush { .. } => H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH, + Self::Grease => { + let r = random(7); + Decoder::from(&r).decode_uint(7).unwrap() * 0x1f + 0x21 + } + } + } + + pub fn encode(&self, enc: &mut Encoder) { + enc.encode_varint(self.get_type()); + + match self { + Self::Data { len } => { + // DATA frame only encode the length here. + enc.encode_varint(*len); + } + Self::Headers { header_block } => { + enc.encode_vvec(header_block); + } + Self::CancelPush { push_id } => { + enc.encode_vvec_with(|enc_inner| { + enc_inner.encode_varint(*push_id); + }); + } + Self::Settings { settings } => { + settings.encode_frame_contents(enc); + } + Self::PushPromise { + push_id, + header_block, + } => { + enc.encode_varint((header_block.len() + (Encoder::varint_len(*push_id))) as u64); + enc.encode_varint(*push_id); + enc.encode(header_block); + } + Self::Goaway { stream_id } => { + enc.encode_vvec_with(|enc_inner| { + enc_inner.encode_varint(stream_id.as_u64()); + }); + } + Self::MaxPushId { push_id } => { + enc.encode_vvec_with(|enc_inner| { + enc_inner.encode_varint(*push_id); + }); + } + Self::Grease => { + // Encode some number of random bytes. + let r = random(8); + enc.encode_vvec(&r[1..usize::from(1 + (r[0] & 0x7))]); + } + Self::PriorityUpdateRequest { + element_id, + priority, + } + | Self::PriorityUpdatePush { + element_id, + priority, + } => { + let mut update_frame = Encoder::new(); + update_frame.encode_varint(*element_id); + + let mut priority_enc: Vec<u8> = Vec::new(); + write!(priority_enc, "{priority}").unwrap(); + + update_frame.encode(&priority_enc); + enc.encode_varint(update_frame.len() as u64); + enc.encode(update_frame.as_ref()); + } + } + } +} + +impl FrameDecoder<HFrame> for HFrame { + fn frame_type_allowed(frame_type: u64) -> Res<()> { + if H3_RESERVED_FRAME_TYPES.contains(&frame_type) { + return Err(Error::HttpFrameUnexpected); + } + Ok(()) + } + + fn decode(frame_type: u64, frame_len: u64, data: Option<&[u8]>) -> Res<Option<HFrame>> { + if frame_type == H3_FRAME_TYPE_DATA { + Ok(Some(HFrame::Data { len: frame_len })) + } else if let Some(payload) = data { + let mut dec = Decoder::from(payload); + Ok(match frame_type { + H3_FRAME_TYPE_DATA => unreachable!("DATA frame has been handled already."), + H3_FRAME_TYPE_HEADERS => Some(HFrame::Headers { + header_block: dec.decode_remainder().to_vec(), + }), + H3_FRAME_TYPE_CANCEL_PUSH => Some(HFrame::CancelPush { + push_id: dec.decode_varint().ok_or(Error::HttpFrame)?, + }), + H3_FRAME_TYPE_SETTINGS => { + let mut settings = HSettings::default(); + settings.decode_frame_contents(&mut dec).map_err(|e| { + if e == Error::HttpSettings { + e + } else { + Error::HttpFrame + } + })?; + Some(HFrame::Settings { settings }) + } + H3_FRAME_TYPE_PUSH_PROMISE => Some(HFrame::PushPromise { + push_id: dec.decode_varint().ok_or(Error::HttpFrame)?, + header_block: dec.decode_remainder().to_vec(), + }), + H3_FRAME_TYPE_GOAWAY => Some(HFrame::Goaway { + stream_id: StreamId::new(dec.decode_varint().ok_or(Error::HttpFrame)?), + }), + H3_FRAME_TYPE_MAX_PUSH_ID => Some(HFrame::MaxPushId { + push_id: dec.decode_varint().ok_or(Error::HttpFrame)?, + }), + H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST | H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH => { + let element_id = dec.decode_varint().ok_or(Error::HttpFrame)?; + let priority = dec.decode_remainder(); + let priority = Priority::from_bytes(priority)?; + if frame_type == H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST { + Some(HFrame::PriorityUpdateRequest { + element_id, + priority, + }) + } else { + Some(HFrame::PriorityUpdatePush { + element_id, + priority, + }) + } + } + _ => None, + }) + } else { + Ok(None) + } + } + + fn is_known_type(frame_type: u64) -> bool { + matches!( + frame_type, + H3_FRAME_TYPE_DATA + | H3_FRAME_TYPE_HEADERS + | H3_FRAME_TYPE_CANCEL_PUSH + | H3_FRAME_TYPE_SETTINGS + | H3_FRAME_TYPE_PUSH_PROMISE + | H3_FRAME_TYPE_GOAWAY + | H3_FRAME_TYPE_MAX_PUSH_ID + | H3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST + | H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH + ) + } +} diff --git a/third_party/rust/neqo-http3/src/frames/mod.rs b/third_party/rust/neqo-http3/src/frames/mod.rs new file mode 100644 index 0000000000..8b615fad01 --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/mod.rs @@ -0,0 +1,21 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub(crate) mod hframe; +pub(crate) mod reader; +pub(crate) mod wtframe; + +#[allow(unused_imports)] +pub(crate) use hframe::{ + HFrame, H3_FRAME_TYPE_HEADERS, H3_FRAME_TYPE_SETTINGS, H3_RESERVED_FRAME_TYPES, +}; +pub(crate) use reader::{ + FrameReader, StreamReaderConnectionWrapper, StreamReaderRecvStreamWrapper, +}; +pub(crate) use wtframe::WebTransportFrame; + +#[cfg(test)] +mod tests; diff --git a/third_party/rust/neqo-http3/src/frames/reader.rs b/third_party/rust/neqo-http3/src/frames/reader.rs new file mode 100644 index 0000000000..5017c666a4 --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/reader.rs @@ -0,0 +1,280 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::{convert::TryFrom, fmt::Debug}; + +use neqo_common::{ + hex_with_len, qtrace, Decoder, IncrementalDecoderBuffer, IncrementalDecoderIgnore, + IncrementalDecoderUint, +}; +use neqo_transport::{Connection, StreamId}; + +use crate::{Error, RecvStream, Res}; + +const MAX_READ_SIZE: usize = 4096; + +pub(crate) trait FrameDecoder<T> { + fn is_known_type(frame_type: u64) -> bool; + /// # Errors + /// + /// Returns `HttpFrameUnexpected` if frames is not alowed, i.e. is a `H3_RESERVED_FRAME_TYPES`. + fn frame_type_allowed(_frame_type: u64) -> Res<()> { + Ok(()) + } + + /// # Errors + /// + /// If a frame cannot be properly decoded. + fn decode(frame_type: u64, frame_len: u64, data: Option<&[u8]>) -> Res<Option<T>>; +} + +pub(crate) trait StreamReader { + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, protocol error, etc. + /// Return an error if the stream was closed on the transport layer, but that information is not + /// yet consumed on the http/3 layer. + fn read_data(&mut self, buf: &mut [u8]) -> Res<(usize, bool)>; +} + +pub(crate) struct StreamReaderConnectionWrapper<'a> { + conn: &'a mut Connection, + stream_id: StreamId, +} + +impl<'a> StreamReaderConnectionWrapper<'a> { + pub fn new(conn: &'a mut Connection, stream_id: StreamId) -> Self { + Self { conn, stream_id } + } +} + +impl<'a> StreamReader for StreamReaderConnectionWrapper<'a> { + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, protocol error, etc. + fn read_data(&mut self, buf: &mut [u8]) -> Res<(usize, bool)> { + let res = self.conn.stream_recv(self.stream_id, buf)?; + Ok(res) + } +} + +pub(crate) struct StreamReaderRecvStreamWrapper<'a> { + recv_stream: &'a mut Box<dyn RecvStream>, + conn: &'a mut Connection, +} + +impl<'a> StreamReaderRecvStreamWrapper<'a> { + pub fn new(conn: &'a mut Connection, recv_stream: &'a mut Box<dyn RecvStream>) -> Self { + Self { recv_stream, conn } + } +} + +impl<'a> StreamReader for StreamReaderRecvStreamWrapper<'a> { + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, protocol error, etc. + fn read_data(&mut self, buf: &mut [u8]) -> Res<(usize, bool)> { + self.recv_stream.read_data(self.conn, buf) + } +} + +#[derive(Clone, Debug)] +enum FrameReaderState { + GetType { decoder: IncrementalDecoderUint }, + GetLength { decoder: IncrementalDecoderUint }, + GetData { decoder: IncrementalDecoderBuffer }, + UnknownFrameDischargeData { decoder: IncrementalDecoderIgnore }, +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub(crate) struct FrameReader { + state: FrameReaderState, + frame_type: u64, + frame_len: u64, +} + +impl Default for FrameReader { + fn default() -> Self { + Self::new() + } +} + +impl FrameReader { + #[must_use] + pub fn new() -> Self { + Self { + state: FrameReaderState::GetType { + decoder: IncrementalDecoderUint::default(), + }, + frame_type: 0, + frame_len: 0, + } + } + + #[must_use] + pub fn new_with_type(frame_type: u64) -> Self { + Self { + state: FrameReaderState::GetLength { + decoder: IncrementalDecoderUint::default(), + }, + frame_type, + frame_len: 0, + } + } + + fn reset(&mut self) { + self.state = FrameReaderState::GetType { + decoder: IncrementalDecoderUint::default(), + }; + } + + fn min_remaining(&self) -> usize { + match &self.state { + FrameReaderState::GetType { decoder } | FrameReaderState::GetLength { decoder } => { + decoder.min_remaining() + } + FrameReaderState::GetData { decoder } => decoder.min_remaining(), + FrameReaderState::UnknownFrameDischargeData { decoder } => decoder.min_remaining(), + } + } + + fn decoding_in_progress(&self) -> bool { + if let FrameReaderState::GetType { decoder } = &self.state { + decoder.decoding_in_progress() + } else { + true + } + } + + /// returns true if quic stream was closed. + /// + /// # Errors + /// + /// May return `HttpFrame` if a frame cannot be decoded. + /// and `TransportStreamDoesNotExist` if `stream_recv` fails. + pub fn receive<T: FrameDecoder<T>>( + &mut self, + stream_reader: &mut dyn StreamReader, + ) -> Res<(Option<T>, bool)> { + loop { + let to_read = std::cmp::min(self.min_remaining(), MAX_READ_SIZE); + let mut buf = vec![0; to_read]; + let (output, read, fin) = match stream_reader + .read_data(&mut buf) + .map_err(|e| Error::map_stream_recv_errors(&e))? + { + (0, f) => (None, false, f), + (amount, f) => { + qtrace!("FrameReader::receive: reading {} byte, fin={}", amount, f); + (self.consume::<T>(Decoder::from(&buf[..amount]))?, true, f) + } + }; + + if output.is_some() { + break Ok((output, fin)); + } + + if fin { + if self.decoding_in_progress() { + break Err(Error::HttpFrame); + } + break Ok((None, fin)); + } + + if !read { + // There was no new data, exit the loop. + break Ok((None, false)); + } + } + } + + /// # Errors + /// + /// May return `HttpFrame` if a frame cannot be decoded. + fn consume<T: FrameDecoder<T>>(&mut self, mut input: Decoder) -> Res<Option<T>> { + match &mut self.state { + FrameReaderState::GetType { decoder } => { + if let Some(v) = decoder.consume(&mut input) { + qtrace!("FrameReader::receive: read frame type {}", v); + self.frame_type_decoded::<T>(v)?; + } + } + FrameReaderState::GetLength { decoder } => { + if let Some(len) = decoder.consume(&mut input) { + qtrace!( + "FrameReader::receive: frame type {} length {}", + self.frame_type, + len + ); + return self.frame_length_decoded::<T>(len); + } + } + FrameReaderState::GetData { decoder } => { + if let Some(data) = decoder.consume(&mut input) { + qtrace!( + "received frame {}: {}", + self.frame_type, + hex_with_len(&data[..]) + ); + return self.frame_data_decoded::<T>(&data); + } + } + FrameReaderState::UnknownFrameDischargeData { decoder } => { + if decoder.consume(&mut input) { + self.reset(); + } + } + } + Ok(None) + } +} + +impl FrameReader { + fn frame_type_decoded<T: FrameDecoder<T>>(&mut self, frame_type: u64) -> Res<()> { + T::frame_type_allowed(frame_type)?; + self.frame_type = frame_type; + self.state = FrameReaderState::GetLength { + decoder: IncrementalDecoderUint::default(), + }; + Ok(()) + } + + fn frame_length_decoded<T: FrameDecoder<T>>(&mut self, len: u64) -> Res<Option<T>> { + self.frame_len = len; + if let Some(f) = T::decode( + self.frame_type, + self.frame_len, + if len > 0 { None } else { Some(&[]) }, + )? { + self.reset(); + return Ok(Some(f)); + } else if T::is_known_type(self.frame_type) { + self.state = FrameReaderState::GetData { + decoder: IncrementalDecoderBuffer::new( + usize::try_from(len).or(Err(Error::HttpFrame))?, + ), + }; + } else if self.frame_len == 0 { + self.reset(); + } else { + self.state = FrameReaderState::UnknownFrameDischargeData { + decoder: IncrementalDecoderIgnore::new( + usize::try_from(len).or(Err(Error::HttpFrame))?, + ), + }; + } + Ok(None) + } + + fn frame_data_decoded<T: FrameDecoder<T>>(&mut self, data: &[u8]) -> Res<Option<T>> { + let res = T::decode(self.frame_type, self.frame_len, Some(data))?; + self.reset(); + Ok(res) + } +} diff --git a/third_party/rust/neqo-http3/src/frames/tests/hframe.rs b/third_party/rust/neqo-http3/src/frames/tests/hframe.rs new file mode 100644 index 0000000000..3da7e7fc36 --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/tests/hframe.rs @@ -0,0 +1,116 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use neqo_common::{Decoder, Encoder}; +use neqo_transport::StreamId; +use test_fixture::fixture_init; + +use super::enc_dec_hframe; +use crate::{ + frames::HFrame, + settings::{HSetting, HSettingType, HSettings}, + Priority, +}; + +#[test] +fn test_data_frame() { + let f = HFrame::Data { len: 3 }; + enc_dec_hframe(&f, "0003010203", 3); +} + +#[test] +fn test_headers_frame() { + let f = HFrame::Headers { + header_block: vec![0x01, 0x02, 0x03], + }; + enc_dec_hframe(&f, "0103010203", 0); +} + +#[test] +fn test_cancel_push_frame4() { + let f = HFrame::CancelPush { push_id: 5 }; + enc_dec_hframe(&f, "030105", 0); +} + +#[test] +fn test_settings_frame4() { + let f = HFrame::Settings { + settings: HSettings::new(&[HSetting::new(HSettingType::MaxHeaderListSize, 4)]), + }; + enc_dec_hframe(&f, "04020604", 0); +} + +#[test] +fn test_push_promise_frame4() { + let f = HFrame::PushPromise { + push_id: 4, + header_block: vec![0x61, 0x62, 0x63, 0x64], + }; + enc_dec_hframe(&f, "05050461626364", 0); +} + +#[test] +fn test_goaway_frame4() { + let f = HFrame::Goaway { + stream_id: StreamId::new(5), + }; + enc_dec_hframe(&f, "070105", 0); +} + +#[test] +fn grease() { + fn make_grease() -> u64 { + let mut enc = Encoder::default(); + HFrame::Grease.encode(&mut enc); + let mut dec = Decoder::from(&enc); + let ft = dec.decode_varint().unwrap(); + assert_eq!((ft - 0x21) % 0x1f, 0); + let body = dec.decode_vvec().unwrap(); + assert!(body.len() <= 7); + ft + } + + fixture_init(); + let t1 = make_grease(); + let t2 = make_grease(); + assert_ne!(t1, t2); +} + +#[test] +fn test_priority_update_request_default() { + let f = HFrame::PriorityUpdateRequest { + element_id: 6, + priority: Priority::default(), + }; + enc_dec_hframe(&f, "800f07000106", 0); +} + +#[test] +fn test_priority_update_request_incremental_default() { + let f = HFrame::PriorityUpdateRequest { + element_id: 7, + priority: Priority::new(6, false), + }; + enc_dec_hframe(&f, "800f07000407753d36", 0); // "u=6" +} + +#[test] +fn test_priority_update_request_urgency_default() { + let f = HFrame::PriorityUpdateRequest { + element_id: 8, + priority: Priority::new(3, true), + }; + enc_dec_hframe(&f, "800f0700020869", 0); // "i" +} + +#[test] +fn test_priority_update_push_default() { + let f = HFrame::PriorityUpdatePush { + element_id: 10, + priority: Priority::default(), + }; + enc_dec_hframe(&f, "800f0701010a", 0); +} diff --git a/third_party/rust/neqo-http3/src/frames/tests/mod.rs b/third_party/rust/neqo-http3/src/frames/tests/mod.rs new file mode 100644 index 0000000000..33eea5497a --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/tests/mod.rs @@ -0,0 +1,84 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem; + +use neqo_common::Encoder; +use neqo_crypto::AuthenticationStatus; +use neqo_transport::StreamType; +use test_fixture::{default_client, default_server, now}; + +use crate::frames::{ + reader::FrameDecoder, FrameReader, HFrame, StreamReaderConnectionWrapper, WebTransportFrame, +}; + +#[allow(clippy::many_single_char_names)] +pub(crate) fn enc_dec<T: FrameDecoder<T>>(d: &Encoder, st: &str, remaining: usize) -> T { + // For data, headers and push_promise we do not read all bytes from the buffer + let d2 = Encoder::from_hex(st); + assert_eq!(d.as_ref(), &d2.as_ref()[..d.as_ref().len()]); + + let mut conn_c = default_client(); + let mut conn_s = default_server(); + let out = conn_c.process(None, now()); + let out = conn_s.process(out.as_dgram_ref(), now()); + let out = conn_c.process(out.as_dgram_ref(), now()); + mem::drop(conn_s.process(out.as_dgram_ref(), now())); + conn_c.authenticated(AuthenticationStatus::Ok, now()); + let out = conn_c.process(None, now()); + mem::drop(conn_s.process(out.as_dgram_ref(), now())); + + // create a stream + let stream_id = conn_s.stream_create(StreamType::BiDi).unwrap(); + + let mut fr: FrameReader = FrameReader::new(); + + // conver string into u8 vector + let buf = Encoder::from_hex(st); + conn_s.stream_send(stream_id, buf.as_ref()).unwrap(); + let out = conn_s.process(None, now()); + mem::drop(conn_c.process(out.as_dgram_ref(), now())); + + let (frame, fin) = fr + .receive::<T>(&mut StreamReaderConnectionWrapper::new( + &mut conn_c, + stream_id, + )) + .unwrap(); + assert!(!fin); + assert!(frame.is_some()); + + // Check remaining data. + let mut buf = [0_u8; 100]; + let (amount, _) = conn_c.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(amount, remaining); + + frame.unwrap() +} + +pub fn enc_dec_hframe(f: &HFrame, st: &str, remaining: usize) { + let mut d = Encoder::default(); + + f.encode(&mut d); + + let frame = enc_dec::<HFrame>(&d, st, remaining); + + assert_eq!(*f, frame); +} + +pub fn enc_dec_wtframe(f: &WebTransportFrame, st: &str, remaining: usize) { + let mut d = Encoder::default(); + + f.encode(&mut d); + + let frame = enc_dec::<WebTransportFrame>(&d, st, remaining); + + assert_eq!(*f, frame); +} + +mod hframe; +mod reader; +mod wtframe; diff --git a/third_party/rust/neqo-http3/src/frames/tests/reader.rs b/third_party/rust/neqo-http3/src/frames/tests/reader.rs new file mode 100644 index 0000000000..fed1477ba4 --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/tests/reader.rs @@ -0,0 +1,518 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{fmt::Debug, mem}; + +use neqo_common::Encoder; +use neqo_transport::{Connection, StreamId, StreamType}; +use test_fixture::{connect, now}; + +use crate::{ + frames::{ + reader::FrameDecoder, FrameReader, HFrame, StreamReaderConnectionWrapper, WebTransportFrame, + }, + settings::{HSetting, HSettingType, HSettings}, + Error, +}; + +struct FrameReaderTest { + pub fr: FrameReader, + pub conn_c: Connection, + pub conn_s: Connection, + pub stream_id: StreamId, +} + +impl FrameReaderTest { + pub fn new() -> Self { + let (conn_c, mut conn_s) = connect(); + let stream_id = conn_s.stream_create(StreamType::BiDi).unwrap(); + Self { + fr: FrameReader::new(), + conn_c, + conn_s, + stream_id, + } + } + + fn process<T: FrameDecoder<T>>(&mut self, v: &[u8]) -> Option<T> { + self.conn_s.stream_send(self.stream_id, v).unwrap(); + let out = self.conn_s.process(None, now()); + mem::drop(self.conn_c.process(out.as_dgram_ref(), now())); + let (frame, fin) = self + .fr + .receive::<T>(&mut StreamReaderConnectionWrapper::new( + &mut self.conn_c, + self.stream_id, + )) + .unwrap(); + assert!(!fin); + frame + } +} + +// Test receiving byte by byte for a SETTINGS frame. +#[test] +fn test_frame_reading_with_stream_settings1() { + let mut fr = FrameReaderTest::new(); + + // Send and read settings frame 040406040804 + assert!(fr.process::<HFrame>(&[0x4]).is_none()); + assert!(fr.process::<HFrame>(&[0x4]).is_none()); + assert!(fr.process::<HFrame>(&[0x6]).is_none()); + assert!(fr.process::<HFrame>(&[0x4]).is_none()); + assert!(fr.process::<HFrame>(&[0x8]).is_none()); + let frame = fr.process(&[0x4]); + + assert!(frame.is_some()); + if let HFrame::Settings { settings } = frame.unwrap() { + assert!(settings.len() == 1); + assert!(settings[0] == HSetting::new(HSettingType::MaxHeaderListSize, 4)); + } else { + panic!("wrong frame type"); + } +} + +// Test receiving byte by byte for a SETTINGS frame with larger varints +#[test] +fn test_frame_reading_with_stream_settings2() { + let mut fr = FrameReaderTest::new(); + + // Read settings frame 400406064004084100 + for i in &[0x40, 0x04, 0x06, 0x06, 0x40, 0x04, 0x08, 0x41] { + assert!(fr.process::<HFrame>(&[*i]).is_none()); + } + let frame = fr.process(&[0x0]); + + assert!(frame.is_some()); + if let HFrame::Settings { settings } = frame.unwrap() { + assert!(settings.len() == 1); + assert!(settings[0] == HSetting::new(HSettingType::MaxHeaderListSize, 4)); + } else { + panic!("wrong frame type"); + } +} + +// Test receiving byte by byte for a PUSH_PROMISE frame. +#[test] +fn test_frame_reading_with_stream_push_promise() { + let mut fr = FrameReaderTest::new(); + + // Read push-promise frame 05054101010203 + for i in &[0x05, 0x05, 0x41, 0x01, 0x01, 0x02] { + assert!(fr.process::<HFrame>(&[*i]).is_none()); + } + let frame = fr.process(&[0x3]); + + assert!(frame.is_some()); + if let HFrame::PushPromise { + push_id, + header_block, + } = frame.unwrap() + { + assert_eq!(push_id, 257); + assert_eq!(header_block, &[0x1, 0x2, 0x3]); + } else { + panic!("wrong frame type"); + } +} + +// Test DATA +#[test] +fn test_frame_reading_with_stream_data() { + let mut fr = FrameReaderTest::new(); + + // Read data frame 0003010203 + let frame = fr.process(&[0x0, 0x3, 0x1, 0x2, 0x3]).unwrap(); + assert!(matches!(frame, HFrame::Data { len } if len == 3)); + + // payloead is still on the stream. + // assert that we have 3 bytes in the stream + let mut buf = [0_u8; 100]; + let (amount, _) = fr.conn_c.stream_recv(fr.stream_id, &mut buf).unwrap(); + assert_eq!(amount, 3); +} + +// Test an unknown frame +#[test] +fn test_unknown_frame() { + // Construct an unknown frame. + const UNKNOWN_FRAME_LEN: usize = 832; + + let mut fr = FrameReaderTest::new(); + + let mut enc = Encoder::with_capacity(UNKNOWN_FRAME_LEN + 4); + enc.encode_varint(1028_u64); // Arbitrary type. + enc.encode_varint(UNKNOWN_FRAME_LEN as u64); + let mut buf: Vec<_> = enc.into(); + buf.resize(UNKNOWN_FRAME_LEN + buf.len(), 0); + assert!(fr.process::<HFrame>(&buf).is_none()); + + // now receive a CANCEL_PUSH fram to see that frame reader is ok. + let frame = fr.process(&[0x03, 0x01, 0x05]); + assert!(frame.is_some()); + if let HFrame::CancelPush { push_id } = frame.unwrap() { + assert!(push_id == 5); + } else { + panic!("wrong frame type"); + } +} + +// Test receiving byte by byte for a WT_FRAME_CLOSE_SESSION frame. +#[test] +fn test_frame_reading_with_stream_wt_close_session() { + let mut fr = FrameReaderTest::new(); + + // Read CloseSession frame 6843090000000548656c6c6f + for i in &[ + 0x68, 0x43, 0x09, 0x00, 0x00, 0x00, 0x05, 0x48, 0x65, 0x6c, 0x6c, + ] { + assert!(fr.process::<WebTransportFrame>(&[*i]).is_none()); + } + let frame = fr.process::<WebTransportFrame>(&[0x6f]); + + assert!(frame.is_some()); + let WebTransportFrame::CloseSession { error, message } = frame.unwrap(); + assert_eq!(error, 5); + assert_eq!(message, "Hello".to_string()); +} + +// Test an unknown frame for WebTransportFrames. +#[test] +fn test_unknown_wt_frame() { + // Construct an unknown frame. + const UNKNOWN_FRAME_LEN: usize = 832; + + let mut fr = FrameReaderTest::new(); + + let mut enc = Encoder::with_capacity(UNKNOWN_FRAME_LEN + 4); + enc.encode_varint(1028_u64); // Arbitrary type. + enc.encode_varint(UNKNOWN_FRAME_LEN as u64); + let mut buf: Vec<_> = enc.into(); + buf.resize(UNKNOWN_FRAME_LEN + buf.len(), 0); + assert!(fr.process::<WebTransportFrame>(&buf).is_none()); + + // now receive a WT_FRAME_CLOSE_SESSION fram to see that frame reader is ok. + let frame = fr.process(&[ + 0x68, 0x43, 0x09, 0x00, 0x00, 0x00, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, + ]); + assert!(frame.is_some()); + let WebTransportFrame::CloseSession { error, message } = frame.unwrap(); + assert_eq!(error, 5); + assert_eq!(message, "Hello".to_string()); +} + +enum FrameReadingTestSend { + OnlyData, + DataWithFin, + DataThenFin, +} + +enum FrameReadingTestExpect { + Error, + Incomplete, + FrameComplete, + FrameAndStreamComplete, + StreamDoneWithoutFrame, +} + +fn test_reading_frame<T: FrameDecoder<T> + PartialEq + Debug>( + buf: &[u8], + test_to_send: &FrameReadingTestSend, + expected_result: &FrameReadingTestExpect, +) { + let mut fr = FrameReaderTest::new(); + + fr.conn_s.stream_send(fr.stream_id, buf).unwrap(); + if let FrameReadingTestSend::DataWithFin = test_to_send { + fr.conn_s.stream_close_send(fr.stream_id).unwrap(); + } + + let out = fr.conn_s.process(None, now()); + mem::drop(fr.conn_c.process(out.as_dgram_ref(), now())); + + if let FrameReadingTestSend::DataThenFin = test_to_send { + fr.conn_s.stream_close_send(fr.stream_id).unwrap(); + let out = fr.conn_s.process(None, now()); + mem::drop(fr.conn_c.process(out.as_dgram_ref(), now())); + } + + let rv = fr.fr.receive::<T>(&mut StreamReaderConnectionWrapper::new( + &mut fr.conn_c, + fr.stream_id, + )); + + match expected_result { + FrameReadingTestExpect::Error => assert_eq!(Err(Error::HttpFrame), rv), + FrameReadingTestExpect::Incomplete => { + assert_eq!(Ok((None, false)), rv); + } + FrameReadingTestExpect::FrameComplete => { + let (f, fin) = rv.unwrap(); + assert!(!fin); + assert!(f.is_some()); + } + FrameReadingTestExpect::FrameAndStreamComplete => { + let (f, fin) = rv.unwrap(); + assert!(fin); + assert!(f.is_some()); + } + FrameReadingTestExpect::StreamDoneWithoutFrame => { + let (f, fin) = rv.unwrap(); + assert!(fin); + assert!(f.is_none()); + } + }; +} + +#[test] +fn test_complete_and_incomplete_unknown_frame() { + // Construct an unknown frame. + const UNKNOWN_FRAME_LEN: usize = 832; + let mut enc = Encoder::with_capacity(UNKNOWN_FRAME_LEN + 4); + enc.encode_varint(1028_u64); // Arbitrary type. + enc.encode_varint(UNKNOWN_FRAME_LEN as u64); + let mut buf: Vec<_> = enc.into(); + buf.resize(UNKNOWN_FRAME_LEN + buf.len(), 0); + + let len = std::cmp::min(buf.len() - 1, 10); + for i in 1..len { + test_reading_frame::<HFrame>( + &buf[..i], + &FrameReadingTestSend::OnlyData, + &FrameReadingTestExpect::Incomplete, + ); + test_reading_frame::<HFrame>( + &buf[..i], + &FrameReadingTestSend::DataWithFin, + &FrameReadingTestExpect::Error, + ); + test_reading_frame::<HFrame>( + &buf[..i], + &FrameReadingTestSend::DataThenFin, + &FrameReadingTestExpect::Error, + ); + } + test_reading_frame::<HFrame>( + &buf, + &FrameReadingTestSend::OnlyData, + &FrameReadingTestExpect::Incomplete, + ); + test_reading_frame::<HFrame>( + &buf, + &FrameReadingTestSend::DataWithFin, + &FrameReadingTestExpect::StreamDoneWithoutFrame, + ); + test_reading_frame::<HFrame>( + &buf, + &FrameReadingTestSend::DataThenFin, + &FrameReadingTestExpect::StreamDoneWithoutFrame, + ); +} + +// if we read more than done_state bytes FrameReader will be in done state. +fn test_complete_and_incomplete_frame<T: FrameDecoder<T> + PartialEq + Debug>( + buf: &[u8], + done_state: usize, +) { + use std::cmp::Ordering; + // Let's consume partial frames. It is enough to test partal frames + // up to 10 byte. 10 byte is greater than frame type and frame + // length and bit of data. + let len = std::cmp::min(buf.len() - 1, 10); + for i in 1..len { + test_reading_frame::<T>( + &buf[..i], + &FrameReadingTestSend::OnlyData, + if i >= done_state { + &FrameReadingTestExpect::FrameComplete + } else { + &FrameReadingTestExpect::Incomplete + }, + ); + test_reading_frame::<T>( + &buf[..i], + &FrameReadingTestSend::DataWithFin, + match i.cmp(&done_state) { + Ordering::Greater => &FrameReadingTestExpect::FrameComplete, + Ordering::Equal => &FrameReadingTestExpect::FrameAndStreamComplete, + Ordering::Less => &FrameReadingTestExpect::Error, + }, + ); + test_reading_frame::<T>( + &buf[..i], + &FrameReadingTestSend::DataThenFin, + match i.cmp(&done_state) { + Ordering::Greater => &FrameReadingTestExpect::FrameComplete, + Ordering::Equal => &FrameReadingTestExpect::FrameAndStreamComplete, + Ordering::Less => &FrameReadingTestExpect::Error, + }, + ); + } + test_reading_frame::<T>( + buf, + &FrameReadingTestSend::OnlyData, + &FrameReadingTestExpect::FrameComplete, + ); + test_reading_frame::<T>( + buf, + &FrameReadingTestSend::DataWithFin, + if buf.len() == done_state { + &FrameReadingTestExpect::FrameAndStreamComplete + } else { + &FrameReadingTestExpect::FrameComplete + }, + ); + test_reading_frame::<T>( + buf, + &FrameReadingTestSend::DataThenFin, + if buf.len() == done_state { + &FrameReadingTestExpect::FrameAndStreamComplete + } else { + &FrameReadingTestExpect::FrameComplete + }, + ); +} + +#[test] +fn test_complete_and_incomplete_frames() { + const FRAME_LEN: usize = 10; + const HEADER_BLOCK: &[u8] = &[0x01, 0x02, 0x03, 0x04]; + + // H3_FRAME_TYPE_DATA len=0 + let f = HFrame::Data { len: 0 }; + let mut enc = Encoder::with_capacity(2); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, 2); + + // H3_FRAME_TYPE_DATA len=FRAME_LEN + let f = HFrame::Data { + len: FRAME_LEN as u64, + }; + let mut enc = Encoder::with_capacity(2); + f.encode(&mut enc); + let mut buf: Vec<_> = enc.into(); + buf.resize(FRAME_LEN + buf.len(), 0); + test_complete_and_incomplete_frame::<HFrame>(&buf, 2); + + // H3_FRAME_TYPE_HEADERS empty header block + let f = HFrame::Headers { + header_block: Vec::new(), + }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, 2); + + // H3_FRAME_TYPE_HEADERS + let f = HFrame::Headers { + header_block: HEADER_BLOCK.to_vec(), + }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); + + // H3_FRAME_TYPE_CANCEL_PUSH + let f = HFrame::CancelPush { push_id: 5 }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); + + // H3_FRAME_TYPE_SETTINGS + let f = HFrame::Settings { + settings: HSettings::new(&[HSetting::new(HSettingType::MaxHeaderListSize, 4)]), + }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); + + // H3_FRAME_TYPE_PUSH_PROMISE + let f = HFrame::PushPromise { + push_id: 4, + header_block: HEADER_BLOCK.to_vec(), + }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); + + // H3_FRAME_TYPE_GOAWAY + let f = HFrame::Goaway { + stream_id: StreamId::new(5), + }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); + + // H3_FRAME_TYPE_MAX_PUSH_ID + let f = HFrame::MaxPushId { push_id: 5 }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<HFrame>(&buf, buf.len()); +} + +#[test] +fn test_complete_and_incomplete_wt_frames() { + // H3_FRAME_TYPE_MAX_PUSH_ID + let f = WebTransportFrame::CloseSession { + error: 5, + message: "Hello".to_string(), + }; + let mut enc = Encoder::default(); + f.encode(&mut enc); + let buf: Vec<_> = enc.into(); + test_complete_and_incomplete_frame::<WebTransportFrame>(&buf, buf.len()); +} + +// Test closing a stream before any frame is sent should not cause an error. +#[test] +fn test_frame_reading_when_stream_is_closed_before_sending_data() { + let mut fr = FrameReaderTest::new(); + + fr.conn_s.stream_send(fr.stream_id, &[0x00]).unwrap(); + let out = fr.conn_s.process(None, now()); + mem::drop(fr.conn_c.process(out.as_dgram_ref(), now())); + + assert_eq!(Ok(()), fr.conn_c.stream_close_send(fr.stream_id)); + let out = fr.conn_c.process(None, now()); + mem::drop(fr.conn_s.process(out.as_dgram_ref(), now())); + assert_eq!( + Ok((None, true)), + fr.fr + .receive::<HFrame>(&mut StreamReaderConnectionWrapper::new( + &mut fr.conn_s, + fr.stream_id + )) + ); +} + +// Test closing a stream before any frame is sent should not cause an error. +// This is the same as the previous just for WebTransportFrame. +#[test] +fn test_wt_frame_reading_when_stream_is_closed_before_sending_data() { + let mut fr = FrameReaderTest::new(); + + fr.conn_s.stream_send(fr.stream_id, &[0x00]).unwrap(); + let out = fr.conn_s.process(None, now()); + mem::drop(fr.conn_c.process(out.as_dgram_ref(), now())); + + assert_eq!(Ok(()), fr.conn_c.stream_close_send(fr.stream_id)); + let out = fr.conn_c.process(None, now()); + mem::drop(fr.conn_s.process(out.as_dgram_ref(), now())); + assert_eq!( + Ok((None, true)), + fr.fr + .receive::<WebTransportFrame>(&mut StreamReaderConnectionWrapper::new( + &mut fr.conn_s, + fr.stream_id + )) + ); +} diff --git a/third_party/rust/neqo-http3/src/frames/tests/wtframe.rs b/third_party/rust/neqo-http3/src/frames/tests/wtframe.rs new file mode 100644 index 0000000000..b6470a89cf --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/tests/wtframe.rs @@ -0,0 +1,17 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::enc_dec_wtframe; +use crate::frames::WebTransportFrame; + +#[test] +fn test_wt_close_session() { + let f = WebTransportFrame::CloseSession { + error: 5, + message: "Hello".to_string(), + }; + enc_dec_wtframe(&f, "6843090000000548656c6c6f", 0); +} diff --git a/third_party/rust/neqo-http3/src/frames/wtframe.rs b/third_party/rust/neqo-http3/src/frames/wtframe.rs new file mode 100644 index 0000000000..deb7a026a0 --- /dev/null +++ b/third_party/rust/neqo-http3/src/frames/wtframe.rs @@ -0,0 +1,62 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::convert::TryFrom; + +use neqo_common::{Decoder, Encoder}; + +use crate::{frames::reader::FrameDecoder, Error, Res}; + +pub(crate) type WebTransportFrameType = u64; + +const WT_FRAME_CLOSE_SESSION: WebTransportFrameType = 0x2843; +const WT_FRAME_CLOSE_MAX_MESSAGE_SIZE: u64 = 1024; + +#[derive(PartialEq, Eq, Debug)] +pub enum WebTransportFrame { + CloseSession { error: u32, message: String }, +} + +impl WebTransportFrame { + pub fn encode(&self, enc: &mut Encoder) { + enc.encode_varint(WT_FRAME_CLOSE_SESSION); + let WebTransportFrame::CloseSession { error, message } = &self; + enc.encode_varint(4 + message.len() as u64); + enc.encode_uint(4, *error); + enc.encode(message.as_bytes()); + } +} + +impl FrameDecoder<WebTransportFrame> for WebTransportFrame { + fn decode( + frame_type: u64, + frame_len: u64, + data: Option<&[u8]>, + ) -> Res<Option<WebTransportFrame>> { + if let Some(payload) = data { + let mut dec = Decoder::from(payload); + if frame_type == WT_FRAME_CLOSE_SESSION { + if frame_len > WT_FRAME_CLOSE_MAX_MESSAGE_SIZE + 4 { + return Err(Error::HttpMessageError); + } + let error = + u32::try_from(dec.decode_uint(4).ok_or(Error::HttpMessageError)?).unwrap(); + let Ok(message) = String::from_utf8(dec.decode_remainder().to_vec()) else { + return Err(Error::HttpMessageError); + }; + Ok(Some(WebTransportFrame::CloseSession { error, message })) + } else { + Ok(None) + } + } else { + Ok(None) + } + } + + fn is_known_type(frame_type: u64) -> bool { + frame_type == WT_FRAME_CLOSE_SESSION + } +} diff --git a/third_party/rust/neqo-http3/src/headers_checks.rs b/third_party/rust/neqo-http3/src/headers_checks.rs new file mode 100644 index 0000000000..9bf661c8fe --- /dev/null +++ b/third_party/rust/neqo-http3/src/headers_checks.rs @@ -0,0 +1,231 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::unused_unit)] // see https://github.com/Lymia/enumset/issues/44 + +use std::convert::TryFrom; + +use enumset::{enum_set, EnumSet, EnumSetType}; +use neqo_common::Header; + +use crate::{Error, MessageType, Res}; + +#[derive(EnumSetType, Debug)] +enum PseudoHeaderState { + Status, + Method, + Scheme, + Authority, + Path, + Protocol, + Regular, +} + +impl PseudoHeaderState { + fn is_pseudo(self) -> bool { + self != Self::Regular + } +} + +impl TryFrom<(MessageType, &str)> for PseudoHeaderState { + type Error = Error; + + fn try_from(v: (MessageType, &str)) -> Res<Self> { + match v { + (MessageType::Response, ":status") => Ok(Self::Status), + (MessageType::Request, ":method") => Ok(Self::Method), + (MessageType::Request, ":scheme") => Ok(Self::Scheme), + (MessageType::Request, ":authority") => Ok(Self::Authority), + (MessageType::Request, ":path") => Ok(Self::Path), + (MessageType::Request, ":protocol") => Ok(Self::Protocol), + (_, _) => Err(Error::InvalidHeader), + } + } +} + +/// Check whether the response is informational(1xx). +/// +/// # Errors +/// +/// Returns an error if response headers do not contain +/// a status header or if the value of the header is 101 or cannot be parsed. +pub fn is_interim(headers: &[Header]) -> Res<bool> { + let status = headers.iter().take(1).find(|h| h.name() == ":status"); + if let Some(h) = status { + #[allow(clippy::map_err_ignore)] + let status_code = h.value().parse::<i32>().map_err(|_| Error::InvalidHeader)?; + if status_code == 101 { + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-http#section-4.3 + Err(Error::InvalidHeader) + } else { + Ok((100..200).contains(&status_code)) + } + } else { + Err(Error::InvalidHeader) + } +} + +fn track_pseudo( + name: &str, + result_state: &mut EnumSet<PseudoHeaderState>, + message_type: MessageType, +) -> Res<bool> { + let new_state = if name.starts_with(':') { + if result_state.contains(PseudoHeaderState::Regular) { + return Err(Error::InvalidHeader); + } + PseudoHeaderState::try_from((message_type, name))? + } else { + PseudoHeaderState::Regular + }; + + let pseudo = new_state.is_pseudo(); + if *result_state & new_state == EnumSet::empty() || !pseudo { + *result_state |= new_state; + Ok(pseudo) + } else { + Err(Error::InvalidHeader) + } +} + +/// Checks if request/response headers are well formed, i.e. contain +/// allowed pseudo headers and in a right order, etc. +/// +/// # Errors +/// +/// Returns an error if headers are not well formed. +pub fn headers_valid(headers: &[Header], message_type: MessageType) -> Res<()> { + let mut method_value: Option<&str> = None; + let mut protocol_value: Option<&str> = None; + let mut scheme_value: Option<&str> = None; + let mut pseudo_state = EnumSet::new(); + for header in headers { + let is_pseudo = track_pseudo(header.name(), &mut pseudo_state, message_type)?; + + let mut bytes = header.name().bytes(); + if is_pseudo { + if header.name() == ":method" { + method_value = Some(header.value()); + } else if header.name() == ":protocol" { + protocol_value = Some(header.value()); + } else if header.name() == ":scheme" { + scheme_value = Some(header.value()); + } + _ = bytes.next(); + } + + if bytes.any(|b| matches!(b, 0 | 0x10 | 0x13 | 0x3a | 0x41..=0x5a)) { + return Err(Error::InvalidHeader); // illegal characters. + } + } + // Clear the regular header bit, since we only check pseudo headers below. + pseudo_state.remove(PseudoHeaderState::Regular); + let pseudo_header_mask = match message_type { + MessageType::Response => enum_set!(PseudoHeaderState::Status), + MessageType::Request => { + if method_value == Some("CONNECT") { + let connect_mask = PseudoHeaderState::Method | PseudoHeaderState::Authority; + if let Some(protocol) = protocol_value { + // For a webtransport CONNECT, the :scheme field must be set to https. + if protocol == "webtransport" && scheme_value != Some("https") { + return Err(Error::InvalidHeader); + } + // The CONNECT request for with :protocol included must have the scheme, + // authority, and path set. + connect_mask | PseudoHeaderState::Scheme | PseudoHeaderState::Path + } else { + connect_mask + } + } else { + PseudoHeaderState::Method | PseudoHeaderState::Scheme | PseudoHeaderState::Path + } + } + }; + + if (MessageType::Request == message_type) + && pseudo_state.contains(PseudoHeaderState::Protocol) + && method_value != Some("CONNECT") + { + return Err(Error::InvalidHeader); + } + + if pseudo_state & pseudo_header_mask != pseudo_header_mask { + return Err(Error::InvalidHeader); + } + + Ok(()) +} + +/// Checks if trailers are well formed, i.e. pseudo headers are not +/// allowed in trailers. +/// +/// # Errors +/// +/// Returns an error if trailers are not well formed. +pub fn trailers_valid(headers: &[Header]) -> Res<()> { + for header in headers { + if header.name().starts_with(':') { + return Err(Error::InvalidHeader); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use neqo_common::Header; + + use super::headers_valid; + use crate::MessageType; + + fn create_connect_headers() -> Vec<Header> { + vec![ + Header::new(":method", "CONNECT"), + Header::new(":protocol", "webtransport"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/here"), + ] + } + + fn create_connect_headers_without_field(field: &str) -> Vec<Header> { + create_connect_headers() + .into_iter() + .filter(|header| header.name() != field) + .collect() + } + + #[test] + fn connect_with_missing_header() { + for field in &[":scheme", ":path", ":authority"] { + assert!(headers_valid( + &create_connect_headers_without_field(field), + MessageType::Request + ) + .is_err()); + } + } + + #[test] + fn invalid_scheme_webtransport_connect() { + assert!(headers_valid( + &[ + Header::new(":method", "CONNECT"), + Header::new(":protocol", "webtransport"), + Header::new(":authority", "something.com"), + Header::new(":scheme", "http"), + Header::new(":path", "/here"), + ], + MessageType::Request + ) + .is_err()); + } + + #[test] + fn valid_webtransport_connect() { + assert!(headers_valid(&create_connect_headers(), MessageType::Request).is_ok()); + } +} diff --git a/third_party/rust/neqo-http3/src/lib.rs b/third_party/rust/neqo-http3/src/lib.rs new file mode 100644 index 0000000000..635707ca7c --- /dev/null +++ b/third_party/rust/neqo-http3/src/lib.rs @@ -0,0 +1,667 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(clippy::pedantic)] + +/*! + +# The HTTP/3 protocol + +This crate implements [RFC9114](https://datatracker.ietf.org/doc/html/rfc9114). + +The implementation depends on: + - [neqo-transport](../neqo_transport/index.html) --- implements the QUIC protocol + ([RFC9000](https://www.rfc-editor.org/info/rfc9000)) and + - [neqo-qpack](../neqo_qpack/index.html) --- implements QPACK + ([RFC9204](https://www.rfc-editor.org/info/rfc9204)); + +## Features + +Both client and server-side HTTP/3 protocols are implemented, although the server-side +implementation is not meant to be used in production and its only purpose is to facilitate testing +of the client-side code. + +__`WebTransport`__ +([draft version 2](https://datatracker.ietf.org/doc/html/draft-vvv-webtransport-http3-02)) is +supported and can be enabled using [`Http3Parameters`](struct.Http3Parameters.html). + +## Interaction with an application + +### Driving HTTP/3 session + +The crate does not create an OS level UDP socket, it produces, i.e. encodes, data that should be +sent as a payload in a UDP packet and consumes data received on the UDP socket. For example, +[`std::net::UdpSocket`] or [`mio::net::UdpSocket`](https://crates.io/crates/mio) +could be used for creating UDP sockets. + +The application is responsible for creating a socket, polling the socket, and sending and receiving +data from the socket. + +In addition to receiving data HTTP/3 session’s actions may be triggered when a certain amount of +time passes, e.g. after a certain amount of time data may be considered lost and should be +retransmitted, packet pacing requires a timer, etc. The implementation does not use timers, but +instead informs the application when processing needs to be triggered. + + +The core functions for driving HTTP/3 sessions are: + - __On the client-side__ : + - [`process_output`](struct.Http3Client.html#method.process_output) used for producing UDP +payload. If a payload is not produced this function returns a callback time, e.g. the time when +[`process_output`](struct.Http3Client.html#method.process_output) should be called again. + - [`process_input`](struct.Http3Client.html#method.process_input) used consuming UDP payload. + - [`process`](struct.Http3Client.html#method.process) combines the 2 functions into one, i.e. it +consumes UDP payload if available and produces some UDP payload to be sent or returns a +callback time. +- __On the server-side__ only [`process`](struct.Http3Server.html#method.process) is +available. + +An example interaction with a socket: + +```ignore +let socket = match UdpSocket::bind(local_addr) { + Err(e) => { + eprintln!("Unable to bind UDP socket: {}", e); + } + Ok(s) => s, +}; +let mut client = Http3Client::new(...); + +... + +// process_output can return 3 values, data to be sent, time duration when process_output should +// be called, and None when Http3Client is done. +match client.process_output(Instant::now()) { + Output::Datagram(dgram) => { + // Send dgram on a socket. + socket.send_to(&dgram[..], dgram.destination()) + + } + Output::Callback(duration) => { + // the client is idle for “duration”, set read timeout on the socket to this value and + // poll the socket for reading in the meantime. + socket.set_read_timeout(Some(duration)).unwrap(); + } + Output::None => { + // client is done. + } +}; + +... + +// Reading new data coming for the network. +match socket.recv_from(&mut buf[..]) { + Ok((sz, remote)) => { + let d = Datagram::new(remote, *local_addr, &buf[..sz]); + client.process_input(d, Instant::now()); + } + Err(err) => { + eprintln!("UDP error: {}", err); + } +} + ``` + +### HTTP/3 session events + +[`Http3Client`](struct.Http3Client.html) and [`Http3Server`](struct.Http3Server.html) produce +events that can be obtain by calling +[`next_event`](neqo_common/event/trait.Provider.html#tymethod.next_event). The events are of type +[`Http3ClientEvent`](enum.Http3ClientEvent.html) and +[`Http3ServerEvent`](enum.Http3ServerEvent.html) respectively. They are informing the application +when the connection changes state, when new data is received on a stream, etc. + +```ignore +... + +while let Some(event) = client.next_event() { + match event { + Http3ClientEvent::DataReadable { stream_id } => { + println!("New data available on stream {}", stream_id); + } + Http3ClientEvent::StateChange(Http3State::Connected) => { + println!("Http3 session is in state Connected now"); + } + _ => { + println!("Unhandled event {:?}", event); + } + } +} +``` + + + +*/ + +mod buffered_send_stream; +mod client_events; +mod conn_params; +mod connection; +mod connection_client; +mod connection_server; +mod control_stream_local; +mod control_stream_remote; +pub mod features; +mod frames; +mod headers_checks; +mod priority; +mod push_controller; +mod qlog; +mod qpack_decoder_receiver; +mod qpack_encoder_receiver; +mod recv_message; +mod request_target; +mod send_message; +mod server; +mod server_connection_events; +mod server_events; +mod settings; +mod stream_type_reader; + +use std::{any::Any, cell::RefCell, fmt::Debug, rc::Rc}; + +use buffered_send_stream::BufferedStream; +pub use client_events::{Http3ClientEvent, WebTransportEvent}; +pub use conn_params::Http3Parameters; +pub use connection::{Http3State, WebTransportSessionAcceptAction}; +pub use connection_client::Http3Client; +use features::extended_connect::WebTransportSession; +use frames::HFrame; +pub use neqo_common::Header; +use neqo_common::MessageType; +use neqo_qpack::Error as QpackError; +pub use neqo_transport::{streams::SendOrder, Output, StreamId}; +use neqo_transport::{ + AppError, Connection, Error as TransportError, RecvStreamStats, SendStreamStats, +}; +pub use priority::Priority; +pub use server::Http3Server; +pub use server_events::{ + Http3OrWebTransportStream, Http3ServerEvent, WebTransportRequest, WebTransportServerEvent, +}; +use stream_type_reader::NewStreamType; + +use crate::priority::PriorityHandler; + +type Res<T> = Result<T, Error>; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + HttpNoError, + HttpGeneralProtocol, + HttpGeneralProtocolStream, /* this is the same as the above but it should only close a + * stream not a connection. */ + // When using this error, you need to provide a value that is unique, which + // will allow the specific error to be identified. This will be validated in CI. + HttpInternal(u16), + HttpStreamCreation, + HttpClosedCriticalStream, + HttpFrameUnexpected, + HttpFrame, + HttpExcessiveLoad, + HttpId, + HttpSettings, + HttpMissingSettings, + HttpRequestRejected, + HttpRequestCancelled, + HttpRequestIncomplete, + HttpConnect, + HttpVersionFallback, + HttpMessageError, + QpackError(neqo_qpack::Error), + + // Internal errors from here. + AlreadyClosed, + AlreadyInitialized, + DecodingFrame, + FatalError, + HttpGoaway, + Internal, + InvalidHeader, + InvalidInput, + InvalidRequestTarget, + InvalidResumptionToken, + InvalidState, + InvalidStreamId, + NoMoreData, + NotEnoughData, + StreamLimitError, + TransportError(TransportError), + TransportStreamDoesNotExist, + Unavailable, + Unexpected, +} + +impl Error { + #[must_use] + pub fn code(&self) -> AppError { + match self { + Self::HttpNoError => 0x100, + Self::HttpGeneralProtocol | Self::HttpGeneralProtocolStream | Self::InvalidHeader => { + 0x101 + } + Self::HttpInternal(..) => 0x102, + Self::HttpStreamCreation => 0x103, + Self::HttpClosedCriticalStream => 0x104, + Self::HttpFrameUnexpected => 0x105, + Self::HttpFrame => 0x106, + Self::HttpExcessiveLoad => 0x107, + Self::HttpId => 0x108, + Self::HttpSettings => 0x109, + Self::HttpMissingSettings => 0x10a, + Self::HttpRequestRejected => 0x10b, + Self::HttpRequestCancelled => 0x10c, + Self::HttpRequestIncomplete => 0x10d, + Self::HttpMessageError => 0x10e, + Self::HttpConnect => 0x10f, + Self::HttpVersionFallback => 0x110, + Self::QpackError(e) => e.code(), + // These are all internal errors. + _ => 3, + } + } + + #[must_use] + pub fn connection_error(&self) -> bool { + matches!( + self, + Self::HttpGeneralProtocol + | Self::HttpInternal(..) + | Self::HttpStreamCreation + | Self::HttpClosedCriticalStream + | Self::HttpFrameUnexpected + | Self::HttpFrame + | Self::HttpExcessiveLoad + | Self::HttpId + | Self::HttpSettings + | Self::HttpMissingSettings + | Self::QpackError(QpackError::EncoderStream | QpackError::DecoderStream) + ) + } + + #[must_use] + pub fn stream_reset_error(&self) -> bool { + matches!(self, Self::HttpGeneralProtocolStream | Self::InvalidHeader) + } + + /// # Panics + /// + /// On unexpected errors, in debug mode. + #[must_use] + pub fn map_stream_send_errors(err: &Error) -> Self { + match err { + Self::TransportError( + TransportError::InvalidStreamId | TransportError::FinalSizeError, + ) => Error::TransportStreamDoesNotExist, + Self::TransportError(TransportError::InvalidInput) => Error::InvalidInput, + _ => { + debug_assert!(false, "Unexpected error"); + Error::TransportStreamDoesNotExist + } + } + } + + /// # Panics + /// + /// On unexpected errors, in debug mode. + #[must_use] + pub fn map_stream_create_errors(err: &TransportError) -> Self { + match err { + TransportError::ConnectionState => Error::Unavailable, + TransportError::StreamLimitError => Error::StreamLimitError, + _ => { + debug_assert!(false, "Unexpected error"); + Error::TransportStreamDoesNotExist + } + } + } + + /// # Panics + /// + /// On unexpected errors, in debug mode. + #[must_use] + pub fn map_stream_recv_errors(err: &Error) -> Self { + match err { + Self::TransportError(TransportError::NoMoreData) => { + debug_assert!( + false, + "Do not call stream_recv if FIN has been previously read" + ); + } + Self::TransportError(TransportError::InvalidStreamId) => {} + _ => { + debug_assert!(false, "Unexpected error"); + } + }; + Error::TransportStreamDoesNotExist + } + + #[must_use] + pub fn map_set_resumption_errors(err: &TransportError) -> Self { + match err { + TransportError::ConnectionState => Error::InvalidState, + _ => Error::InvalidResumptionToken, + } + } + + /// # Errors + /// + /// Any error is mapped to the indicated type. + /// + /// # Panics + /// + /// On internal errors, in debug mode. + fn map_error<R>(r: Result<R, impl Into<Self>>, err: Self) -> Result<R, Self> { + r.map_err(|e| { + debug_assert!(!matches!(e.into(), Self::HttpInternal(..))); + debug_assert!(!matches!(err, Self::HttpInternal(..))); + err + }) + } +} + +impl From<TransportError> for Error { + fn from(err: TransportError) -> Self { + Self::TransportError(err) + } +} + +impl From<QpackError> for Error { + fn from(err: QpackError) -> Self { + match err { + QpackError::ClosedCriticalStream => Error::HttpClosedCriticalStream, + e => Self::QpackError(e), + } + } +} + +impl From<AppError> for Error { + fn from(error: AppError) -> Self { + match error { + 0x100 => Self::HttpNoError, + 0x101 => Self::HttpGeneralProtocol, + 0x103 => Self::HttpStreamCreation, + 0x104 => Self::HttpClosedCriticalStream, + 0x105 => Self::HttpFrameUnexpected, + 0x106 => Self::HttpFrame, + 0x107 => Self::HttpExcessiveLoad, + 0x108 => Self::HttpId, + 0x109 => Self::HttpSettings, + 0x10a => Self::HttpMissingSettings, + 0x10b => Self::HttpRequestRejected, + 0x10c => Self::HttpRequestCancelled, + 0x10d => Self::HttpRequestIncomplete, + 0x10f => Self::HttpConnect, + 0x110 => Self::HttpVersionFallback, + 0x200 => Self::QpackError(QpackError::DecompressionFailed), + 0x201 => Self::QpackError(QpackError::EncoderStream), + 0x202 => Self::QpackError(QpackError::DecoderStream), + _ => Self::HttpInternal(0), + } + } +} + +impl ::std::error::Error for Error { + fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { + match self { + Self::TransportError(e) => Some(e), + Self::QpackError(e) => Some(e), + _ => None, + } + } +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "HTTP/3 error: {self:?}") + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Http3StreamType { + Control, + Decoder, + Encoder, + NewStream, + Http, + Push, + ExtendedConnect, + WebTransport(StreamId), + Unknown, +} + +#[must_use] +#[derive(PartialEq, Eq, Debug)] +enum ReceiveOutput { + NoOutput, + ControlFrames(Vec<HFrame>), + UnblockedStreams(Vec<StreamId>), + NewStream(NewStreamType), +} + +impl Default for ReceiveOutput { + fn default() -> Self { + Self::NoOutput + } +} + +trait Stream: Debug { + fn stream_type(&self) -> Http3StreamType; +} + +trait RecvStream: Stream { + /// The stream reads data from the corresponding quic stream and returns `ReceiveOutput`. + /// The function also returns true as the second parameter if the stream is done and + /// could be forgotten, i.e. removed from all records. + /// + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, protocol error, etc. + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)>; + + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, etc. + fn reset(&mut self, close_type: CloseType) -> Res<()>; + + /// The function allows an app to read directly from the quic stream. The function + /// returns the number of bytes written into `buf` and true/false if the stream is + /// completely done and can be forgotten, i.e. removed from all records. + /// + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, protocol error, etc. + fn read_data(&mut self, _conn: &mut Connection, _buf: &mut [u8]) -> Res<(usize, bool)> { + Err(Error::InvalidStreamId) + } + + fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> { + None + } + + fn webtransport(&self) -> Option<Rc<RefCell<WebTransportSession>>> { + None + } + + /// This function is only implemented by `WebTransportRecvStream`. + fn stats(&mut self, _conn: &mut Connection) -> Res<RecvStreamStats> { + Err(Error::Unavailable) + } +} + +trait HttpRecvStream: RecvStream { + /// This function is similar to the receive function and has the same output, i.e. + /// a `ReceiveOutput` enum and bool. The bool is true if the stream is completely done + /// and can be forgotten, i.e. removed from all records. + /// + /// # Errors + /// + /// An error may happen while reading a stream, e.g. early close, protocol error, etc. + fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)>; + + fn maybe_update_priority(&mut self, priority: Priority) -> bool; + fn priority_update_frame(&mut self) -> Option<HFrame>; + fn priority_update_sent(&mut self); + + fn set_new_listener(&mut self, _conn_events: Box<dyn HttpRecvStreamEvents>) {} + fn extended_connect_wait_for_response(&self) -> bool { + false + } + + fn any(&self) -> &dyn Any; +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct Http3StreamInfo { + stream_id: StreamId, + stream_type: Http3StreamType, +} + +impl Http3StreamInfo { + #[must_use] + pub fn new(stream_id: StreamId, stream_type: Http3StreamType) -> Self { + Self { + stream_id, + stream_type, + } + } + + #[must_use] + pub fn stream_id(&self) -> StreamId { + self.stream_id + } + + #[must_use] + pub fn session_id(&self) -> Option<StreamId> { + if let Http3StreamType::WebTransport(session) = self.stream_type { + Some(session) + } else { + None + } + } + + #[must_use] + pub fn is_http(&self) -> bool { + self.stream_type == Http3StreamType::Http + } +} + +trait RecvStreamEvents: Debug { + fn data_readable(&self, _stream_info: Http3StreamInfo) {} + fn recv_closed(&self, _stream_info: Http3StreamInfo, _close_type: CloseType) {} +} + +trait HttpRecvStreamEvents: RecvStreamEvents { + fn header_ready( + &self, + stream_info: Http3StreamInfo, + headers: Vec<Header>, + interim: bool, + fin: bool, + ); + fn extended_connect_new_session(&self, _stream_id: StreamId, _headers: Vec<Header>) {} +} + +trait SendStream: Stream { + /// # Errors + /// + /// Error my occur during sending data, e.g. protocol error, etc. + fn send(&mut self, conn: &mut Connection) -> Res<()>; + fn has_data_to_send(&self) -> bool; + fn stream_writable(&self); + fn done(&self) -> bool; + fn set_sendorder(&mut self, conn: &mut Connection, sendorder: Option<SendOrder>) -> Res<()>; + fn set_fairness(&mut self, conn: &mut Connection, fairness: bool) -> Res<()>; + + /// # Errors + /// + /// Error my occur during sending data, e.g. protocol error, etc. + fn send_data(&mut self, _conn: &mut Connection, _buf: &[u8]) -> Res<usize>; + + /// # Errors + /// + /// It may happen that the transport stream is already close. This is unlikely. + fn close(&mut self, conn: &mut Connection) -> Res<()>; + + /// # Errors + /// + /// It may happen that the transport stream is already close. This is unlikely. + fn close_with_message( + &mut self, + _conn: &mut Connection, + _error: u32, + _message: &str, + ) -> Res<()> { + Err(Error::InvalidStreamId) + } + + /// This function is called when sending side is closed abruptly by the peer or + /// the application. + fn handle_stop_sending(&mut self, close_type: CloseType); + fn http_stream(&mut self) -> Option<&mut dyn HttpSendStream> { + None + } + + /// # Errors + /// + /// It may happen that the transport stream is already close. This is unlikely. + fn send_data_atomic(&mut self, _conn: &mut Connection, _buf: &[u8]) -> Res<()> { + Err(Error::InvalidStreamId) + } + + /// This function is only implemented by `WebTransportSendStream`. + fn stats(&mut self, _conn: &mut Connection) -> Res<SendStreamStats> { + Err(Error::Unavailable) + } +} + +trait HttpSendStream: SendStream { + /// This function is used to supply headers to a http message. The + /// function is used for request headers, response headers, 1xx response and + /// trailers. + /// + /// # Errors + /// + /// This can also return an error if the underlying stream is closed. + fn send_headers(&mut self, headers: &[Header], conn: &mut Connection) -> Res<()>; + fn set_new_listener(&mut self, _conn_events: Box<dyn SendStreamEvents>) {} + fn any(&self) -> &dyn Any; +} + +trait SendStreamEvents: Debug { + fn send_closed(&self, _stream_info: Http3StreamInfo, _close_type: CloseType) {} + fn data_writable(&self, _stream_info: Http3StreamInfo) {} +} + +/// This enum is used to mark a different type of closing a stream: +/// `ResetApp` - the application has closed the stream. +/// `ResetRemote` - the stream was closed by the peer. +/// `LocalError` - There was a stream error on the stream. The stream errors are errors +/// that do not close the complete connection, e.g. unallowed headers. +/// `Done` - the stream was closed without an error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CloseType { + ResetApp(AppError), + ResetRemote(AppError), + LocalError(AppError), + Done, +} + +impl CloseType { + #[must_use] + pub fn error(&self) -> Option<AppError> { + match self { + Self::ResetApp(error) | Self::ResetRemote(error) | Self::LocalError(error) => { + Some(*error) + } + Self::Done => None, + } + } + + #[must_use] + pub fn locally_initiated(&self) -> bool { + matches!(self, CloseType::ResetApp(_)) + } +} diff --git a/third_party/rust/neqo-http3/src/priority.rs b/third_party/rust/neqo-http3/src/priority.rs new file mode 100644 index 0000000000..f2651d3bb5 --- /dev/null +++ b/third_party/rust/neqo-http3/src/priority.rs @@ -0,0 +1,212 @@ +use std::{convert::TryFrom, fmt}; + +use neqo_transport::StreamId; +use sfv::{BareItem, Item, ListEntry, Parser}; + +use crate::{frames::HFrame, Error, Header, Res}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Priority { + urgency: u8, + incremental: bool, +} + +impl Default for Priority { + fn default() -> Self { + Priority { + urgency: 3, + incremental: false, + } + } +} + +impl Priority { + /// # Panics + /// + /// If an invalid urgency (>7 is given) + #[must_use] + pub fn new(urgency: u8, incremental: bool) -> Priority { + assert!(urgency < 8); + Priority { + urgency, + incremental, + } + } + + /// Returns a header if required to send + #[must_use] + pub fn header(self) -> Option<Header> { + match self { + Priority { + urgency: 3, + incremental: false, + } => None, + other => Some(Header::new("priority", format!("{other}"))), + } + } + + /// Constructs a priority from raw bytes (either a field value of frame content). + /// + /// # Errors + /// + /// When the contained syntax is invalid. + /// + /// # Panics + /// + /// Never, but the compiler is not smart enough to work that out. + pub fn from_bytes(bytes: &[u8]) -> Res<Priority> { + let dict = Parser::parse_dictionary(bytes).map_err(|_| Error::HttpFrame)?; + let urgency = match dict.get("u") { + Some(ListEntry::Item(Item { + bare_item: BareItem::Integer(u), + .. + })) if (0..=7).contains(u) => u8::try_from(*u).unwrap(), + _ => 3, + }; + let incremental = match dict.get("i") { + Some(ListEntry::Item(Item { + bare_item: BareItem::Boolean(i), + .. + })) => *i, + _ => false, + }; + Ok(Priority { + urgency, + incremental, + }) + } +} + +impl fmt::Display for Priority { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Priority { + urgency: 3, + incremental: false, + } => Ok(()), + Priority { + urgency: 3, + incremental: true, + } => write!(f, "i"), + Priority { + urgency, + incremental: false, + } => write!(f, "u={urgency}"), + Priority { + urgency, + incremental: true, + } => write!(f, "u={urgency},i"), + } + } +} + +#[derive(Debug)] +#[allow(clippy::module_name_repetitions)] +pub struct PriorityHandler { + push_stream: bool, + priority: Priority, + last_send_priority: Priority, +} + +impl PriorityHandler { + pub fn new(push_stream: bool, priority: Priority) -> PriorityHandler { + PriorityHandler { + push_stream, + priority, + last_send_priority: priority, + } + } + + /*pub fn priority(&self) -> Priority { + self.priority + }*/ + + /// Returns if an priority update will be issued + pub fn maybe_update_priority(&mut self, priority: Priority) -> bool { + if priority == self.priority { + false + } else { + self.priority = priority; + true + } + } + + pub fn priority_update_sent(&mut self) { + self.last_send_priority = self.priority; + } + + /// Returns `HFrame` if an priority update is outstanding + pub fn maybe_encode_frame(&self, stream_id: StreamId) -> Option<HFrame> { + if self.priority == self.last_send_priority { + None + } else if self.push_stream { + Some(HFrame::PriorityUpdatePush { + element_id: stream_id.as_u64(), + priority: self.priority, + }) + } else { + Some(HFrame::PriorityUpdateRequest { + element_id: stream_id.as_u64(), + priority: self.priority, + }) + } + } +} + +#[cfg(test)] +mod test { + use neqo_transport::StreamId; + + use crate::{priority::PriorityHandler, HFrame, Priority}; + + #[test] + fn priority_updates_ignore_same() { + let mut p = PriorityHandler::new(false, Priority::new(5, false)); + assert!(!p.maybe_update_priority(Priority::new(5, false))); + // updating with the same priority -> there should not be any priority frame sent + assert!(p.maybe_encode_frame(StreamId::new(4)).is_none()); + } + + #[test] + fn priority_updates_send_update() { + let mut p = PriorityHandler::new(false, Priority::new(5, false)); + assert!(p.maybe_update_priority(Priority::new(6, false))); + // updating with the a different priority -> there should be a priority frame sent + assert!(p.maybe_encode_frame(StreamId::new(4)).is_some()); + } + + #[test] + fn multiple_priority_updates_ignore_same() { + let mut p = PriorityHandler::new(false, Priority::new(5, false)); + assert!(p.maybe_update_priority(Priority::new(6, false))); + assert!(p.maybe_update_priority(Priority::new(5, false))); + // initial and last priority same -> there should not be any priority frame sent + assert!(p.maybe_encode_frame(StreamId::new(4)).is_none()); + } + + #[test] + fn multiple_priority_updates_send_update() { + let mut p = PriorityHandler::new(false, Priority::new(5, false)); + assert!(p.maybe_update_priority(Priority::new(6, false))); + assert!(p.maybe_update_priority(Priority::new(7, false))); + // updating two times with a different priority -> the last priority update should be in the + // next frame + let expected = HFrame::PriorityUpdateRequest { + element_id: 4, + priority: Priority::new(7, false), + }; + assert_eq!(p.maybe_encode_frame(StreamId::new(4)), Some(expected)); + } + + #[test] + fn priority_updates_incremental() { + let mut p = PriorityHandler::new(false, Priority::new(5, false)); + assert!(p.maybe_update_priority(Priority::new(5, true))); + // updating the incremental parameter -> there should be a priority frame sent + let expected = HFrame::PriorityUpdateRequest { + element_id: 4, + priority: Priority::new(5, true), + }; + assert_eq!(p.maybe_encode_frame(StreamId::new(4)), Some(expected)); + } +} diff --git a/third_party/rust/neqo-http3/src/push_controller.rs b/third_party/rust/neqo-http3/src/push_controller.rs new file mode 100644 index 0000000000..c4591991ae --- /dev/null +++ b/third_party/rust/neqo-http3/src/push_controller.rs @@ -0,0 +1,516 @@ +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{ + cell::RefCell, + collections::VecDeque, + convert::TryFrom, + fmt::{Debug, Display}, + mem, + rc::Rc, + slice::SliceIndex, +}; + +use neqo_common::{qerror, qinfo, qtrace, Header}; +use neqo_transport::{Connection, StreamId}; + +use crate::{ + client_events::{Http3ClientEvent, Http3ClientEvents}, + connection::Http3Connection, + frames::HFrame, + CloseType, Error, Http3StreamInfo, HttpRecvStreamEvents, RecvStreamEvents, Res, +}; + +/// `PushStates`: +/// `Init`: there is no push stream nor a push promise. This state is only used to keep track of +/// opened and closed push streams. +/// `PushPromise`: the push has only ever receive a pushpromise frame +/// `OnlyPushStream`: there is only a push stream. All push stream events, i.e. `PushHeaderReady` +/// and `PushDataReadable` will be delayed until a push promise is received +/// (they are kept in `events`). +/// `Active`: there is a push steam and at least one push promise frame. +/// `Close`: the push stream has been closed or reset already. +#[derive(Debug, PartialEq, Clone)] +enum PushState { + Init, + PushPromise { + headers: Vec<Header>, + }, + OnlyPushStream { + stream_id: StreamId, + events: Vec<Http3ClientEvent>, + }, + Active { + stream_id: StreamId, + headers: Vec<Header>, + }, + Closed, +} + +/// `ActivePushStreams` holds information about push streams. +/// +/// `first_push_id` holds a `push_id` of the first element in `push_streams` if it is present. +/// `push_id` smaller than `first_push_id` have been already closed. `push_id` => `first_push_id` +/// are in `push_streams` or they are not yet opened. +#[derive(Debug)] +struct ActivePushStreams { + push_streams: VecDeque<PushState>, + first_push_id: u64, +} + +impl ActivePushStreams { + pub fn new() -> Self { + Self { + push_streams: VecDeque::new(), + first_push_id: 0, + } + } + + /// Returns None if a stream has been closed already. + pub fn get_mut( + &mut self, + push_id: u64, + ) -> Option<&mut <usize as SliceIndex<[PushState]>>::Output> { + if push_id < self.first_push_id { + return None; + } + + let inx = usize::try_from(push_id - self.first_push_id).unwrap(); + if inx >= self.push_streams.len() { + self.push_streams.resize(inx + 1, PushState::Init); + } + match self.push_streams.get_mut(inx) { + Some(PushState::Closed) => None, + e => e, + } + } + + /// Returns None if a stream has been closed already. + pub fn get(&mut self, push_id: u64) -> Option<&mut PushState> { + self.get_mut(push_id) + } + + /// Returns the State of a closed push stream or None for already closed streams. + pub fn close(&mut self, push_id: u64) -> Option<PushState> { + match self.get_mut(push_id) { + None | Some(PushState::Closed) => None, + Some(s) => { + let res = mem::replace(s, PushState::Closed); + while let Some(PushState::Closed) = self.push_streams.front() { + self.push_streams.pop_front(); + self.first_push_id += 1; + } + Some(res) + } + } + } + + #[must_use] + pub fn number_done(&self) -> u64 { + self.first_push_id + + u64::try_from( + self.push_streams + .iter() + .filter(|&e| e == &PushState::Closed) + .count(), + ) + .unwrap() + } + + pub fn clear(&mut self) { + self.first_push_id = 0; + self.push_streams.clear(); + } +} + +/// `PushController` keeps information about push stream states. +/// +/// A `PushStream` calls `add_new_push_stream` that may change the push state from Init to +/// `OnlyPushStream` or from `PushPromise` to `Active`. If a stream has already been closed +/// `add_new_push_stream` returns false (the `PushStream` will close the transport stream). +/// A `PushStream` calls `push_stream_reset` if the transport stream has been canceled. +/// When a push stream is done it calls `close`. +/// +/// The `PushController` handles: +/// `PUSH_PROMISE` frame: frames may change the push state from Init to `PushPromise` and from +/// `OnlyPushStream` to `Active`. Frames for a closed steams are ignored. +/// `CANCEL_PUSH` frame: (`handle_cancel_push` will be called). If a push is in state `PushPromise` +/// or `Active`, any posted events will be removed and a `PushCanceled` event +/// will be posted. If a push is in state `OnlyPushStream` or `Active` the +/// transport stream and the `PushStream` will be closed. The frame will be +/// ignored for already closed pushes. Application calling cancel: the actions are similar to the +/// `CANCEL_PUSH` frame. The difference is that `PushCanceled` will not +/// be posted and a `CANCEL_PUSH` frame may be sent. +#[derive(Debug)] +pub(crate) struct PushController { + max_concurent_push: u64, + current_max_push_id: u64, + // push_streams holds the states of push streams. + // We keep a stream until the stream has been closed. + push_streams: ActivePushStreams, + // The keeps the next consecutive push_id that should be open. + // All push_id < next_push_id_to_open are in the push_stream lists. If they are not in the list + // they have been already closed. + conn_events: Http3ClientEvents, +} + +impl PushController { + pub fn new(max_concurent_push: u64, conn_events: Http3ClientEvents) -> Self { + PushController { + max_concurent_push, + current_max_push_id: 0, + push_streams: ActivePushStreams::new(), + conn_events, + } + } +} + +impl Display for PushController { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Push controler") + } +} + +impl PushController { + /// A new `push_promise` has been received. + /// + /// # Errors + /// + /// `HttpId` if `push_id` greater than it is allowed has been received. + pub fn new_push_promise( + &mut self, + push_id: u64, + ref_stream_id: StreamId, + new_headers: Vec<Header>, + ) -> Res<()> { + qtrace!( + [self], + "New push promise push_id={} headers={:?} max_push={}", + push_id, + new_headers, + self.max_concurent_push + ); + + self.check_push_id(push_id)?; + + match self.push_streams.get_mut(push_id) { + None => { + qtrace!("Push has been closed already {}.", push_id); + Ok(()) + } + Some(push_state) => match push_state { + PushState::Init => { + self.conn_events + .push_promise(push_id, ref_stream_id, new_headers.clone()); + *push_state = PushState::PushPromise { + headers: new_headers, + }; + Ok(()) + } + PushState::PushPromise { headers } | PushState::Active { headers, .. } => { + if new_headers != *headers { + return Err(Error::HttpGeneralProtocol); + } + self.conn_events + .push_promise(push_id, ref_stream_id, new_headers); + Ok(()) + } + PushState::OnlyPushStream { stream_id, events } => { + let stream_id_tmp = *stream_id; + self.conn_events + .push_promise(push_id, ref_stream_id, new_headers.clone()); + + for e in events.drain(..) { + self.conn_events.insert(e); + } + *push_state = PushState::Active { + stream_id: stream_id_tmp, + headers: new_headers, + }; + Ok(()) + } + PushState::Closed => unreachable!("This is only internal; it is transfer to None"), + }, + } + } + + pub fn add_new_push_stream(&mut self, push_id: u64, stream_id: StreamId) -> Res<bool> { + qtrace!( + "A new push stream with push_id={} stream_id={}", + push_id, + stream_id + ); + + self.check_push_id(push_id)?; + + match self.push_streams.get_mut(push_id) { + None => { + qinfo!("Push has been closed already."); + Ok(false) + } + Some(push_state) => match push_state { + PushState::Init => { + *push_state = PushState::OnlyPushStream { + stream_id, + events: Vec::new(), + }; + Ok(true) + } + PushState::PushPromise { headers } => { + let tmp = mem::take(headers); + *push_state = PushState::Active { + stream_id, + headers: tmp, + }; + Ok(true) + } + // The following state have already have a push stream: + // PushState::OnlyPushStream | PushState::Active + _ => { + qerror!("Duplicate push stream."); + Err(Error::HttpId) + } + }, + } + } + + fn check_push_id(&mut self, push_id: u64) -> Res<()> { + // Check if push id is greater than what we allow. + if push_id > self.current_max_push_id { + qerror!("Push id is greater than current_max_push_id."); + Err(Error::HttpId) + } else { + Ok(()) + } + } + + pub fn handle_cancel_push( + &mut self, + push_id: u64, + conn: &mut Connection, + base_handler: &mut Http3Connection, + ) -> Res<()> { + qtrace!("CANCEL_PUSH frame has been received, push_id={}", push_id); + + self.check_push_id(push_id)?; + + match self.push_streams.close(push_id) { + None => { + qtrace!("Push has already been closed (push_id={}).", push_id); + Ok(()) + } + Some(ps) => match ps { + PushState::Init => Ok(()), + PushState::PushPromise { .. } => { + self.conn_events.remove_events_for_push_id(push_id); + self.conn_events.push_canceled(push_id); + Ok(()) + } + PushState::OnlyPushStream { stream_id, .. } + | PushState::Active { stream_id, .. } => { + mem::drop(base_handler.stream_stop_sending( + conn, + stream_id, + Error::HttpRequestCancelled.code(), + )); + self.conn_events.remove_events_for_push_id(push_id); + self.conn_events.push_canceled(push_id); + Ok(()) + } + PushState::Closed => unreachable!("This is only internal; it is transfer to None"), + }, + } + } + + pub fn close(&mut self, push_id: u64) { + qtrace!("Push stream has been closed."); + if let Some(push_state) = self.push_streams.close(push_id) { + debug_assert!(matches!(push_state, PushState::Active { .. })); + } else { + debug_assert!(false, "Closing non existing push stream!"); + } + } + + pub fn cancel( + &mut self, + push_id: u64, + conn: &mut Connection, + base_handler: &mut Http3Connection, + ) -> Res<()> { + qtrace!("Cancel push_id={}", push_id); + + self.check_push_id(push_id)?; + + match self.push_streams.get(push_id) { + None => { + qtrace!("Push has already been closed."); + // If we have some events for the push_id in the event queue, the caller still does + // not not know that the push has been closed. Otherwise return + // InvalidStreamId. + if self.conn_events.has_push(push_id) { + self.conn_events.remove_events_for_push_id(push_id); + Ok(()) + } else { + Err(Error::InvalidStreamId) + } + } + Some(PushState::PushPromise { .. }) => { + self.conn_events.remove_events_for_push_id(push_id); + base_handler.queue_control_frame(&HFrame::CancelPush { push_id }); + self.push_streams.close(push_id); + Ok(()) + } + Some(PushState::Active { stream_id, .. }) => { + self.conn_events.remove_events_for_push_id(push_id); + // Cancel the stream. the transport steam may already be done, so ignore an error. + mem::drop(base_handler.stream_stop_sending( + conn, + *stream_id, + Error::HttpRequestCancelled.code(), + )); + self.push_streams.close(push_id); + Ok(()) + } + Some(_) => Err(Error::InvalidStreamId), + } + } + + pub fn push_stream_reset(&mut self, push_id: u64, close_type: CloseType) { + qtrace!("Push stream has been reset, push_id={}", push_id); + + if let Some(push_state) = self.push_streams.get(push_id) { + match push_state { + PushState::OnlyPushStream { .. } => { + self.push_streams.close(push_id); + } + PushState::Active { .. } => { + self.push_streams.close(push_id); + self.conn_events.remove_events_for_push_id(push_id); + if let CloseType::LocalError(app_error) = close_type { + self.conn_events.push_reset(push_id, app_error); + } else { + self.conn_events.push_canceled(push_id); + } + } + _ => { + debug_assert!( + false, + "Reset cannot actually happen because we do not have a stream." + ); + } + } + } + } + + pub fn get_active_stream_id(&mut self, push_id: u64) -> Option<StreamId> { + match self.push_streams.get(push_id) { + Some(PushState::Active { stream_id, .. }) => Some(*stream_id), + _ => None, + } + } + + pub fn maybe_send_max_push_id_frame(&mut self, base_handler: &mut Http3Connection) { + let push_done = self.push_streams.number_done(); + if self.max_concurent_push > 0 + && (self.current_max_push_id - push_done) <= (self.max_concurent_push / 2) + { + self.current_max_push_id = push_done + self.max_concurent_push; + base_handler.queue_control_frame(&HFrame::MaxPushId { + push_id: self.current_max_push_id, + }); + } + } + + pub fn handle_zero_rtt_rejected(&mut self) { + self.current_max_push_id = 0; + } + + pub fn clear(&mut self) { + self.push_streams.clear(); + } + + pub fn can_receive_push(&self) -> bool { + self.max_concurent_push > 0 + } + + pub fn new_stream_event(&mut self, push_id: u64, event: Http3ClientEvent) { + match self.push_streams.get_mut(push_id) { + None => { + debug_assert!(false, "Push has been closed already."); + } + Some(PushState::OnlyPushStream { events, .. }) => { + events.push(event); + } + Some(PushState::Active { .. }) => { + self.conn_events.insert(event); + } + Some(_) => { + debug_assert!(false, "No record of a stream!"); + } + } + } +} + +/// `RecvPushEvents` relays a push stream events to `PushController`. +/// It informs `PushController` when a push stream is done or canceled. +/// Also when headers or data is ready and `PushController` decide whether to post +/// `PushHeaderReady` and `PushDataReadable` events or to postpone them if +/// a `push_promise` has not been yet received for the stream. +#[derive(Debug)] +pub(crate) struct RecvPushEvents { + push_id: u64, + push_handler: Rc<RefCell<PushController>>, +} + +impl RecvPushEvents { + pub fn new(push_id: u64, push_handler: Rc<RefCell<PushController>>) -> Self { + Self { + push_id, + push_handler, + } + } +} + +impl RecvStreamEvents for RecvPushEvents { + fn data_readable(&self, _stream_info: Http3StreamInfo) { + self.push_handler.borrow_mut().new_stream_event( + self.push_id, + Http3ClientEvent::PushDataReadable { + push_id: self.push_id, + }, + ); + } + + fn recv_closed(&self, _stream_info: Http3StreamInfo, close_type: CloseType) { + match close_type { + CloseType::ResetApp(_) => {} + CloseType::ResetRemote(_) | CloseType::LocalError(_) => self + .push_handler + .borrow_mut() + .push_stream_reset(self.push_id, close_type), + CloseType::Done => self.push_handler.borrow_mut().close(self.push_id), + } + } +} + +impl HttpRecvStreamEvents for RecvPushEvents { + fn header_ready( + &self, + _stream_info: Http3StreamInfo, + headers: Vec<Header>, + interim: bool, + fin: bool, + ) { + self.push_handler.borrow_mut().new_stream_event( + self.push_id, + Http3ClientEvent::PushHeaderReady { + push_id: self.push_id, + headers, + interim, + fin, + }, + ); + } +} diff --git a/third_party/rust/neqo-http3/src/qlog.rs b/third_party/rust/neqo-http3/src/qlog.rs new file mode 100644 index 0000000000..c3a13fd19f --- /dev/null +++ b/third_party/rust/neqo-http3/src/qlog.rs @@ -0,0 +1,46 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Functions that handle capturing QLOG traces. + +use std::convert::TryFrom; + +use neqo_common::qlog::NeqoQlog; +use neqo_transport::StreamId; +use qlog::{ + self, + events::{DataRecipient, EventData}, +}; + +pub fn h3_data_moved_up(qlog: &mut NeqoQlog, stream_id: StreamId, amount: usize) { + qlog.add_event_data(|| { + let ev_data = EventData::DataMoved(qlog::events::quic::DataMoved { + stream_id: Some(stream_id.as_u64()), + offset: None, + length: Some(u64::try_from(amount).unwrap()), + from: Some(DataRecipient::Transport), + to: Some(DataRecipient::Application), + raw: None, + }); + + Some(ev_data) + }); +} + +pub fn h3_data_moved_down(qlog: &mut NeqoQlog, stream_id: StreamId, amount: usize) { + qlog.add_event_data(|| { + let ev_data = EventData::DataMoved(qlog::events::quic::DataMoved { + stream_id: Some(stream_id.as_u64()), + offset: None, + length: Some(u64::try_from(amount).unwrap()), + from: Some(DataRecipient::Application), + to: Some(DataRecipient::Transport), + raw: None, + }); + + Some(ev_data) + }); +} diff --git a/third_party/rust/neqo-http3/src/qpack_decoder_receiver.rs b/third_party/rust/neqo-http3/src/qpack_decoder_receiver.rs new file mode 100644 index 0000000000..46b9ca590b --- /dev/null +++ b/third_party/rust/neqo-http3/src/qpack_decoder_receiver.rs @@ -0,0 +1,45 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, rc::Rc}; + +use neqo_qpack::QPackDecoder; +use neqo_transport::{Connection, StreamId}; + +use crate::{CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream}; + +#[derive(Debug)] +pub(crate) struct DecoderRecvStream { + stream_id: StreamId, + decoder: Rc<RefCell<QPackDecoder>>, +} + +impl DecoderRecvStream { + pub fn new(stream_id: StreamId, decoder: Rc<RefCell<QPackDecoder>>) -> Self { + Self { stream_id, decoder } + } +} + +impl Stream for DecoderRecvStream { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::Decoder + } +} + +impl RecvStream for DecoderRecvStream { + fn reset(&mut self, _close_type: CloseType) -> Res<()> { + Err(Error::HttpClosedCriticalStream) + } + + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + Ok(( + ReceiveOutput::UnblockedStreams( + self.decoder.borrow_mut().receive(conn, self.stream_id)?, + ), + false, + )) + } +} diff --git a/third_party/rust/neqo-http3/src/qpack_encoder_receiver.rs b/third_party/rust/neqo-http3/src/qpack_encoder_receiver.rs new file mode 100644 index 0000000000..76c779bcf2 --- /dev/null +++ b/third_party/rust/neqo-http3/src/qpack_encoder_receiver.rs @@ -0,0 +1,41 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, rc::Rc}; + +use neqo_qpack::QPackEncoder; +use neqo_transport::{Connection, StreamId}; + +use crate::{CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream}; + +#[derive(Debug)] +pub(crate) struct EncoderRecvStream { + stream_id: StreamId, + encoder: Rc<RefCell<QPackEncoder>>, +} + +impl EncoderRecvStream { + pub fn new(stream_id: StreamId, encoder: Rc<RefCell<QPackEncoder>>) -> Self { + Self { stream_id, encoder } + } +} + +impl Stream for EncoderRecvStream { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::Encoder + } +} + +impl RecvStream for EncoderRecvStream { + fn reset(&mut self, _close_type: CloseType) -> Res<()> { + Err(Error::HttpClosedCriticalStream) + } + + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.encoder.borrow_mut().receive(conn, self.stream_id)?; + Ok((ReceiveOutput::NoOutput, false)) + } +} diff --git a/third_party/rust/neqo-http3/src/recv_message.rs b/third_party/rust/neqo-http3/src/recv_message.rs new file mode 100644 index 0000000000..36e8f65b19 --- /dev/null +++ b/third_party/rust/neqo-http3/src/recv_message.rs @@ -0,0 +1,501 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{ + any::Any, cell::RefCell, cmp::min, collections::VecDeque, convert::TryFrom, fmt::Debug, rc::Rc, +}; + +use neqo_common::{qdebug, qinfo, qtrace, Header}; +use neqo_qpack::decoder::QPackDecoder; +use neqo_transport::{Connection, StreamId}; + +use crate::{ + frames::{FrameReader, HFrame, StreamReaderConnectionWrapper, H3_FRAME_TYPE_HEADERS}, + headers_checks::{headers_valid, is_interim}, + priority::PriorityHandler, + push_controller::PushController, + qlog, CloseType, Error, Http3StreamInfo, Http3StreamType, HttpRecvStream, HttpRecvStreamEvents, + MessageType, Priority, ReceiveOutput, RecvStream, Res, Stream, +}; + +#[allow(clippy::module_name_repetitions)] +pub(crate) struct RecvMessageInfo { + pub message_type: MessageType, + pub stream_type: Http3StreamType, + pub stream_id: StreamId, + pub header_frame_type_read: bool, +} + +/* + * Response stream state: + * WaitingForResponseHeaders : we wait for headers. in this state we can + * also get a PUSH_PROMISE frame. + * DecodingHeaders : In this step the headers will be decoded. The stream + * may be blocked in this state on encoder instructions. + * WaitingForData : we got HEADERS, we are waiting for one or more data + * frames. In this state we can receive one or more + * PUSH_PROMIS frames or a HEADERS frame carrying trailers. + * ReadingData : we got a DATA frame, now we letting the app read payload. + * From here we will go back to WaitingForData state to wait + * for more data frames or to CLosed state + * ClosePending : waiting for app to pick up data, after that we can delete + * the TransactionClient. + * Closed + * ExtendedConnect: this request is for a WebTransport session. In this + * state RecvMessage will not be treated as a HTTP + * stream anymore. It is waiting to be transformed + * into WebTransport session or to be closed. + */ +#[derive(Debug)] +enum RecvMessageState { + WaitingForResponseHeaders { frame_reader: FrameReader }, + DecodingHeaders { header_block: Vec<u8>, fin: bool }, + WaitingForData { frame_reader: FrameReader }, + ReadingData { remaining_data_len: usize }, + WaitingForFinAfterTrailers { frame_reader: FrameReader }, + ClosePending, // Close must first be read by application + Closed, + ExtendedConnect, +} + +#[derive(Debug)] +struct PushInfo { + push_id: u64, + header_block: Vec<u8>, +} + +#[derive(Debug)] +pub(crate) struct RecvMessage { + state: RecvMessageState, + message_type: MessageType, + stream_type: Http3StreamType, + qpack_decoder: Rc<RefCell<QPackDecoder>>, + conn_events: Box<dyn HttpRecvStreamEvents>, + push_handler: Option<Rc<RefCell<PushController>>>, + stream_id: StreamId, + priority_handler: PriorityHandler, + blocked_push_promise: VecDeque<PushInfo>, +} + +impl ::std::fmt::Display for RecvMessage { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "RecvMessage stream_id:{}", self.stream_id) + } +} + +impl RecvMessage { + pub fn new( + message_info: &RecvMessageInfo, + qpack_decoder: Rc<RefCell<QPackDecoder>>, + conn_events: Box<dyn HttpRecvStreamEvents>, + push_handler: Option<Rc<RefCell<PushController>>>, + priority_handler: PriorityHandler, + ) -> Self { + Self { + state: RecvMessageState::WaitingForResponseHeaders { + frame_reader: if message_info.header_frame_type_read { + FrameReader::new_with_type(H3_FRAME_TYPE_HEADERS) + } else { + FrameReader::new() + }, + }, + message_type: message_info.message_type, + stream_type: message_info.stream_type, + qpack_decoder, + conn_events, + push_handler, + stream_id: message_info.stream_id, + priority_handler, + blocked_push_promise: VecDeque::new(), + } + } + + fn handle_headers_frame(&mut self, header_block: Vec<u8>, fin: bool) -> Res<()> { + match self.state { + RecvMessageState::WaitingForResponseHeaders {..} => { + if header_block.is_empty() { + return Err(Error::HttpGeneralProtocolStream); + } + self.state = RecvMessageState::DecodingHeaders { header_block, fin }; + } + RecvMessageState::WaitingForData { ..} => { + // TODO implement trailers, for now just ignore them. + self.state = RecvMessageState::WaitingForFinAfterTrailers{frame_reader: FrameReader::new()}; + } + RecvMessageState::WaitingForFinAfterTrailers {..} => { + return Err(Error::HttpFrameUnexpected); + } + _ => unreachable!("This functions is only called in WaitingForResponseHeaders | WaitingForData | WaitingForFinAfterTrailers state.") + } + Ok(()) + } + + fn handle_data_frame(&mut self, len: u64, fin: bool) -> Res<()> { + match self.state { + RecvMessageState::WaitingForResponseHeaders {..} | RecvMessageState::WaitingForFinAfterTrailers {..} => { + return Err(Error::HttpFrameUnexpected); + } + RecvMessageState::WaitingForData {..} => { + if len > 0 { + if fin { + return Err(Error::HttpFrame); + } + self.state = RecvMessageState::ReadingData { + remaining_data_len: usize::try_from(len).or(Err(Error::HttpFrame))?, + }; + } + } + _ => unreachable!("This functions is only called in WaitingForResponseHeaders | WaitingForData | WaitingForFinAfterTrailers state.") + } + Ok(()) + } + + fn add_headers(&mut self, mut headers: Vec<Header>, fin: bool) -> Res<()> { + qtrace!([self], "Add new headers fin={}", fin); + let interim = match self.message_type { + MessageType::Request => false, + MessageType::Response => is_interim(&headers)?, + }; + headers_valid(&headers, self.message_type)?; + if self.message_type == MessageType::Response { + headers.retain(Header::is_allowed_for_response); + } + + if fin && interim { + return Err(Error::HttpGeneralProtocolStream); + } + + let is_web_transport = self.message_type == MessageType::Request + && headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport"); + if is_web_transport { + self.conn_events + .extended_connect_new_session(self.stream_id, headers); + } else { + self.conn_events + .header_ready(self.get_stream_info(), headers, interim, fin); + } + + if fin { + self.set_closed(); + } else { + self.state = if is_web_transport { + self.stream_type = Http3StreamType::ExtendedConnect; + RecvMessageState::ExtendedConnect + } else if interim { + RecvMessageState::WaitingForResponseHeaders { + frame_reader: FrameReader::new(), + } + } else { + RecvMessageState::WaitingForData { + frame_reader: FrameReader::new(), + } + }; + } + Ok(()) + } + + fn set_state_to_close_pending(&mut self, post_readable_event: bool) -> Res<()> { + // Stream has received fin. Depending on headers state set header_ready + // or data_readable event so that app can pick up the fin. + qtrace!([self], "set_state_to_close_pending: state={:?}", self.state); + + match self.state { + RecvMessageState::WaitingForResponseHeaders { .. } => { + return Err(Error::HttpGeneralProtocolStream); + } + RecvMessageState::ReadingData { .. } => {} + RecvMessageState::WaitingForData { .. } + | RecvMessageState::WaitingForFinAfterTrailers { .. } => { + if post_readable_event { + self.conn_events.data_readable(self.get_stream_info()); + } + } + _ => unreachable!("Closing an already closed transaction."), + } + if !matches!(self.state, RecvMessageState::Closed) { + self.state = RecvMessageState::ClosePending; + } + Ok(()) + } + + fn handle_push_promise(&mut self, push_id: u64, header_block: Vec<u8>) -> Res<()> { + if self.push_handler.is_none() { + return Err(Error::HttpFrameUnexpected); + } + + if !self.blocked_push_promise.is_empty() { + self.blocked_push_promise.push_back(PushInfo { + push_id, + header_block, + }); + } else if let Some(headers) = self + .qpack_decoder + .borrow_mut() + .decode_header_block(&header_block, self.stream_id)? + { + self.push_handler + .as_ref() + .ok_or(Error::HttpFrameUnexpected)? + .borrow_mut() + .new_push_promise(push_id, self.stream_id, headers)?; + } else { + self.blocked_push_promise.push_back(PushInfo { + push_id, + header_block, + }); + } + Ok(()) + } + + fn receive_internal(&mut self, conn: &mut Connection, post_readable_event: bool) -> Res<()> { + let label = ::neqo_common::log_subject!(::log::Level::Debug, self); + loop { + qdebug!([label], "state={:?}.", self.state); + match &mut self.state { + // In the following 3 states we need to read frames. + RecvMessageState::WaitingForResponseHeaders { frame_reader } + | RecvMessageState::WaitingForData { frame_reader } + | RecvMessageState::WaitingForFinAfterTrailers { frame_reader } => { + match frame_reader.receive(&mut StreamReaderConnectionWrapper::new( + conn, + self.stream_id, + ))? { + (None, true) => { + break self.set_state_to_close_pending(post_readable_event); + } + (None, false) => break Ok(()), + (Some(frame), fin) => { + qinfo!( + [self], + "A new frame has been received: {:?}; state={:?} fin={}", + frame, + self.state, + fin, + ); + match frame { + HFrame::Headers { header_block } => { + self.handle_headers_frame(header_block, fin)?; + } + HFrame::Data { len } => self.handle_data_frame(len, fin)?, + HFrame::PushPromise { + push_id, + header_block, + } => self.handle_push_promise(push_id, header_block)?, + _ => break Err(Error::HttpFrameUnexpected), + } + if matches!(self.state, RecvMessageState::Closed) { + break Ok(()); + } + if fin + && !matches!(self.state, RecvMessageState::DecodingHeaders { .. }) + { + break self.set_state_to_close_pending(post_readable_event); + } + } + }; + } + RecvMessageState::DecodingHeaders { + ref header_block, + fin, + } => { + if self + .qpack_decoder + .borrow() + .refers_dynamic_table(header_block)? + && !self.blocked_push_promise.is_empty() + { + qinfo!( + [self], + "decoding header is blocked waiting for a push_promise header block." + ); + break Ok(()); + } + let done = *fin; + let d_headers = self + .qpack_decoder + .borrow_mut() + .decode_header_block(header_block, self.stream_id)?; + if let Some(headers) = d_headers { + self.add_headers(headers, done)?; + if matches!( + self.state, + RecvMessageState::Closed | RecvMessageState::ExtendedConnect + ) { + break Ok(()); + } + } else { + qinfo!([self], "decoding header is blocked."); + break Ok(()); + } + } + RecvMessageState::ReadingData { .. } => { + if post_readable_event { + self.conn_events.data_readable(self.get_stream_info()); + } + break Ok(()); + } + RecvMessageState::ClosePending | RecvMessageState::Closed => { + panic!("Stream readable after being closed!"); + } + RecvMessageState::ExtendedConnect => { + // Ignore read event, this request is waiting to be picked up by a new + // WebTransportSession + break Ok(()); + } + }; + } + } + + fn set_closed(&mut self) { + if !self.blocked_push_promise.is_empty() { + self.qpack_decoder + .borrow_mut() + .cancel_stream(self.stream_id); + } + self.state = RecvMessageState::Closed; + self.conn_events + .recv_closed(self.get_stream_info(), CloseType::Done); + } + + fn closing(&self) -> bool { + matches!( + self.state, + RecvMessageState::ClosePending | RecvMessageState::Closed + ) + } + + fn get_stream_info(&self) -> Http3StreamInfo { + Http3StreamInfo::new(self.stream_id, Http3StreamType::Http) + } +} + +impl Stream for RecvMessage { + fn stream_type(&self) -> Http3StreamType { + self.stream_type + } +} + +impl RecvStream for RecvMessage { + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.receive_internal(conn, true)?; + Ok(( + ReceiveOutput::NoOutput, + matches!(self.state, RecvMessageState::Closed), + )) + } + + fn reset(&mut self, close_type: CloseType) -> Res<()> { + if !self.closing() || !self.blocked_push_promise.is_empty() { + self.qpack_decoder + .borrow_mut() + .cancel_stream(self.stream_id); + } + self.conn_events + .recv_closed(self.get_stream_info(), close_type); + self.state = RecvMessageState::Closed; + Ok(()) + } + + fn read_data(&mut self, conn: &mut Connection, buf: &mut [u8]) -> Res<(usize, bool)> { + let mut written = 0; + loop { + match self.state { + RecvMessageState::ReadingData { + ref mut remaining_data_len, + } => { + let to_read = min(*remaining_data_len, buf.len() - written); + let (amount, fin) = conn + .stream_recv(self.stream_id, &mut buf[written..written + to_read]) + .map_err(|e| Error::map_stream_recv_errors(&Error::from(e)))?; + qlog::h3_data_moved_up(conn.qlog_mut(), self.stream_id, amount); + + debug_assert!(amount <= to_read); + *remaining_data_len -= amount; + written += amount; + + if fin { + if *remaining_data_len > 0 { + return Err(Error::HttpFrame); + } + self.set_closed(); + break Ok((written, fin)); + } else if *remaining_data_len == 0 { + self.state = RecvMessageState::WaitingForData { + frame_reader: FrameReader::new(), + }; + self.receive_internal(conn, false)?; + } else { + break Ok((written, false)); + } + } + RecvMessageState::ClosePending => { + self.set_closed(); + break Ok((written, true)); + } + _ => break Ok((written, false)), + } + } + } + + fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> { + Some(self) + } +} + +impl HttpRecvStream for RecvMessage { + fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + while let Some(p) = self.blocked_push_promise.front() { + if let Some(headers) = self + .qpack_decoder + .borrow_mut() + .decode_header_block(&p.header_block, self.stream_id)? + { + self.push_handler + .as_ref() + .ok_or(Error::HttpFrameUnexpected)? + .borrow_mut() + .new_push_promise(p.push_id, self.stream_id, headers)?; + self.blocked_push_promise.pop_front(); + } else { + return Ok((ReceiveOutput::NoOutput, false)); + } + } + + self.receive(conn) + } + + fn maybe_update_priority(&mut self, priority: Priority) -> bool { + self.priority_handler.maybe_update_priority(priority) + } + + fn priority_update_frame(&mut self) -> Option<HFrame> { + self.priority_handler.maybe_encode_frame(self.stream_id) + } + + fn priority_update_sent(&mut self) { + self.priority_handler.priority_update_sent(); + } + + fn set_new_listener(&mut self, conn_events: Box<dyn HttpRecvStreamEvents>) { + self.state = RecvMessageState::WaitingForData { + frame_reader: FrameReader::new(), + }; + self.conn_events = conn_events; + } + + fn extended_connect_wait_for_response(&self) -> bool { + matches!(self.state, RecvMessageState::ExtendedConnect) + } + + fn any(&self) -> &dyn Any { + self + } +} diff --git a/third_party/rust/neqo-http3/src/request_target.rs b/third_party/rust/neqo-http3/src/request_target.rs new file mode 100644 index 0000000000..28bc22ac2d --- /dev/null +++ b/third_party/rust/neqo-http3/src/request_target.rs @@ -0,0 +1,130 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::fmt::{Debug, Formatter}; + +use url::{ParseError, Url}; + +pub trait RequestTarget: Debug { + fn scheme(&self) -> &str; + fn authority(&self) -> &str; + fn path(&self) -> &str; +} + +pub struct RefRequestTarget<'s, 'a, 'p> { + scheme: &'s str, + authority: &'a str, + path: &'p str, +} + +impl RequestTarget for RefRequestTarget<'_, '_, '_> { + fn scheme(&self) -> &str { + self.scheme + } + + fn authority(&self) -> &str { + self.authority + } + + fn path(&self) -> &str { + self.path + } +} + +impl<'s, 'a, 'p> RefRequestTarget<'s, 'a, 'p> { + #[must_use] + pub fn new(scheme: &'s str, authority: &'a str, path: &'p str) -> Self { + Self { + scheme, + authority, + path, + } + } +} + +impl Debug for RefRequestTarget<'_, '_, '_> { + fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { + write!(f, "{}://{}{}", self.scheme, self.authority, self.path) + } +} + +/// `AsRequestTarget` is a trait that produces a `RequestTarget` that +/// refers to the identified object. +pub trait AsRequestTarget<'x> { + type Target: RequestTarget; + type Error; + /// Produce a `RequestTarget` that refers to `self`. + /// + /// # Errors + /// + /// This method can generate an error of type `Self::Error` + /// if the conversion is unsuccessful. + fn as_request_target(&'x self) -> Result<Self::Target, Self::Error>; +} + +impl<'x, S, A, P> AsRequestTarget<'x> for (S, A, P) +where + S: AsRef<str> + 'x, + A: AsRef<str> + 'x, + P: AsRef<str> + 'x, +{ + type Target = RefRequestTarget<'x, 'x, 'x>; + type Error = (); + fn as_request_target(&'x self) -> Result<Self::Target, Self::Error> { + Ok(RefRequestTarget::new( + self.0.as_ref(), + self.1.as_ref(), + self.2.as_ref(), + )) + } +} + +impl<'x> AsRequestTarget<'x> for Url { + type Target = RefRequestTarget<'x, 'x, 'x>; + type Error = (); + fn as_request_target(&'x self) -> Result<Self::Target, Self::Error> { + Ok(RefRequestTarget::new( + self.scheme(), + self.host_str().unwrap_or(""), + self.path(), + )) + } +} + +pub struct UrlRequestTarget { + url: Url, +} + +impl RequestTarget for UrlRequestTarget { + fn scheme(&self) -> &str { + self.url.scheme() + } + + fn authority(&self) -> &str { + self.url.host_str().unwrap_or("") + } + + fn path(&self) -> &str { + self.url.path() + } +} + +impl Debug for UrlRequestTarget { + fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { + self.url.fmt(f) + } +} + +impl<'x> AsRequestTarget<'x> for str { + type Target = UrlRequestTarget; + type Error = ParseError; + fn as_request_target(&'x self) -> Result<Self::Target, Self::Error> { + let url = Url::parse(self)?; + Ok(UrlRequestTarget { url }) + } +} diff --git a/third_party/rust/neqo-http3/src/send_message.rs b/third_party/rust/neqo-http3/src/send_message.rs new file mode 100644 index 0000000000..96156938a0 --- /dev/null +++ b/third_party/rust/neqo-http3/src/send_message.rs @@ -0,0 +1,345 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{any::Any, cell::RefCell, cmp::min, fmt::Debug, rc::Rc}; + +use neqo_common::{qdebug, qinfo, qtrace, Encoder, Header, MessageType}; +use neqo_qpack::encoder::QPackEncoder; +use neqo_transport::{streams::SendOrder, Connection, StreamId}; + +use crate::{ + frames::HFrame, + headers_checks::{headers_valid, is_interim, trailers_valid}, + qlog, BufferedStream, CloseType, Error, Http3StreamInfo, Http3StreamType, HttpSendStream, Res, + SendStream, SendStreamEvents, Stream, +}; + +const MAX_DATA_HEADER_SIZE_2: usize = (1 << 6) - 1; // Maximal amount of data with DATA frame header size 2 +const MAX_DATA_HEADER_SIZE_2_LIMIT: usize = MAX_DATA_HEADER_SIZE_2 + 3; // 63 + 3 (size of the next buffer data frame header) +const MAX_DATA_HEADER_SIZE_3: usize = (1 << 14) - 1; // Maximal amount of data with DATA frame header size 3 +const MAX_DATA_HEADER_SIZE_3_LIMIT: usize = MAX_DATA_HEADER_SIZE_3 + 5; // 16383 + 5 (size of the next buffer data frame header) +const MAX_DATA_HEADER_SIZE_5: usize = (1 << 30) - 1; // Maximal amount of data with DATA frame header size 3 +const MAX_DATA_HEADER_SIZE_5_LIMIT: usize = MAX_DATA_HEADER_SIZE_5 + 9; // 1073741823 + 9 (size of the next buffer data frame header) + +/// A HTTP message, request and response, consists of headers, optional data and an optional +/// trailer header block. This state machine does not reflect what was already sent to the +/// transport layer but only reflect what has been supplied to the `SendMessage`.It is +/// represented by the following states: +/// `WaitingForHeaders` - the headers have not been supplied yet. In this state only a +/// request/response header can be added. When headers are supplied +/// the state changes to `WaitingForData`. A response may contain +/// multiple messages only if all but the last one are informational(1xx) +/// responses. The informational responses can only contain headers, +/// therefore after an informational response is received the state +/// machine states in `WaitingForHeaders` state. +/// `WaitingForData` - in this state, data and trailers can be supplied. This state means that +/// a request or response header is already supplied. +/// `TrailersSet` - trailers have been supplied. At this stage no more data or headers can be +/// supply only a fin. +/// `Done` - in this state no more data or headers can be added. This state is entered when the +/// message is closed. + +#[derive(Debug, PartialEq)] +enum MessageState { + WaitingForHeaders, + WaitingForData, + TrailersSet, + Done, +} + +impl MessageState { + fn new_headers(&mut self, headers: &[Header], message_type: MessageType) -> Res<()> { + match &self { + Self::WaitingForHeaders => { + // This is only a debug assertion because we expect that application will + // do the right thing here and performing the check costs. + debug_assert!(headers_valid(headers, message_type).is_ok()); + match message_type { + MessageType::Request => { + *self = Self::WaitingForData; + } + MessageType::Response => { + if !is_interim(headers)? { + *self = Self::WaitingForData; + } + } + } + Ok(()) + } + Self::WaitingForData => { + trailers_valid(headers)?; + *self = Self::TrailersSet; + Ok(()) + } + Self::TrailersSet | Self::Done => Err(Error::InvalidInput), + } + } + + fn new_data(&self) -> Res<()> { + if &Self::WaitingForData == self { + Ok(()) + } else { + Err(Error::InvalidInput) + } + } + + fn fin(&mut self) -> Res<()> { + match &self { + Self::WaitingForHeaders | Self::Done => Err(Error::InvalidInput), + Self::WaitingForData | Self::TrailersSet => { + *self = Self::Done; + Ok(()) + } + } + } + + fn done(&self) -> bool { + &Self::Done == self + } +} + +#[derive(Debug)] +pub(crate) struct SendMessage { + state: MessageState, + message_type: MessageType, + stream_type: Http3StreamType, + stream: BufferedStream, + encoder: Rc<RefCell<QPackEncoder>>, + conn_events: Box<dyn SendStreamEvents>, +} + +impl SendMessage { + pub fn new( + message_type: MessageType, + stream_type: Http3StreamType, + stream_id: StreamId, + encoder: Rc<RefCell<QPackEncoder>>, + conn_events: Box<dyn SendStreamEvents>, + ) -> Self { + qinfo!("Create a request stream_id={}", stream_id); + Self { + state: MessageState::WaitingForHeaders, + message_type, + stream_type, + stream: BufferedStream::new(stream_id), + encoder, + conn_events, + } + } + + /// # Errors + /// + /// `ClosedCriticalStream` if the encoder stream is closed. + /// `InternalError` if an unexpected error occurred. + fn encode( + encoder: &mut QPackEncoder, + headers: &[Header], + conn: &mut Connection, + stream_id: StreamId, + ) -> Vec<u8> { + qdebug!("Encoding headers"); + let header_block = encoder.encode_header_block(conn, headers, stream_id); + let hframe = HFrame::Headers { + header_block: header_block.to_vec(), + }; + let mut d = Encoder::default(); + hframe.encode(&mut d); + d.into() + } + + fn stream_id(&self) -> StreamId { + Option::<StreamId>::from(&self.stream).unwrap() + } + + fn get_stream_info(&self) -> Http3StreamInfo { + Http3StreamInfo::new(self.stream_id(), Http3StreamType::Http) + } +} + +impl Stream for SendMessage { + fn stream_type(&self) -> Http3StreamType { + self.stream_type + } +} +impl SendStream for SendMessage { + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + qtrace!([self], "send_body: len={}", buf.len()); + + self.state.new_data()?; + + self.stream.send_buffer(conn)?; + if self.stream.has_buffered_data() { + return Ok(0); + } + let available = conn + .stream_avail_send_space(self.stream_id()) + .map_err(|e| Error::map_stream_send_errors(&e.into()))?; + if available <= 2 { + return Ok(0); + } + let to_send = if available <= MAX_DATA_HEADER_SIZE_2_LIMIT { + // 63 + 3 + min(min(buf.len(), available - 2), MAX_DATA_HEADER_SIZE_2) + } else if available <= MAX_DATA_HEADER_SIZE_3_LIMIT { + // 16383 + 5 + min(min(buf.len(), available - 3), MAX_DATA_HEADER_SIZE_3) + } else if available <= MAX_DATA_HEADER_SIZE_5 { + // 1073741823 + 9 + min(min(buf.len(), available - 5), MAX_DATA_HEADER_SIZE_5_LIMIT) + } else { + min(buf.len(), available - 9) + }; + + qinfo!( + [self], + "send_request_body: available={} to_send={}.", + available, + to_send + ); + + let data_frame = HFrame::Data { + len: to_send as u64, + }; + let mut enc = Encoder::default(); + data_frame.encode(&mut enc); + let sent_fh = self + .stream + .send_atomic(conn, enc.as_ref()) + .map_err(|e| Error::map_stream_send_errors(&e))?; + debug_assert!(sent_fh); + + let sent = self + .stream + .send_atomic(conn, &buf[..to_send]) + .map_err(|e| Error::map_stream_send_errors(&e))?; + debug_assert!(sent); + qlog::h3_data_moved_down(conn.qlog_mut(), self.stream_id(), to_send); + Ok(to_send) + } + + fn done(&self) -> bool { + !self.stream.has_buffered_data() && self.state.done() + } + + fn stream_writable(&self) { + if !self.stream.has_buffered_data() && !self.state.done() { + // DataWritable is just a signal for an application to try to write more data, + // if writing fails it is fine. Therefore we do not need to properly check + // whether more credits are available on the transport layer. + self.conn_events.data_writable(self.get_stream_info()); + } + } + + /// # Errors + /// + /// `InternalError` if an unexpected error occurred. + /// `InvalidStreamId` if the stream does not exist, + /// `AlreadyClosed` if the stream has already been closed. + /// `TransportStreamDoesNotExist` if the transport stream does not exist (this may happen if + /// `process_output` has not been called when needed, and HTTP3 layer has not picked up the + /// info that the stream has been closed.) + fn send(&mut self, conn: &mut Connection) -> Res<()> { + let sent = Error::map_error(self.stream.send_buffer(conn), Error::HttpInternal(5))?; + qlog::h3_data_moved_down(conn.qlog_mut(), self.stream_id(), sent); + + qtrace!([self], "{} bytes sent", sent); + if !self.stream.has_buffered_data() { + if self.state.done() { + Error::map_error( + conn.stream_close_send(self.stream_id()), + Error::HttpInternal(6), + )?; + qtrace!([self], "done sending request"); + } else { + // DataWritable is just a signal for an application to try to write more data, + // if writing fails it is fine. Therefore we do not need to properly check + // whether more credits are available on the transport layer. + self.conn_events.data_writable(self.get_stream_info()); + } + } + Ok(()) + } + + // SendMessage owns headers and sends them. It may also own data for the server side. + // This method returns if they're still being sent. Request body (if any) is sent by + // http client afterwards using `send_request_body` after receiving DataWritable event. + fn has_data_to_send(&self) -> bool { + self.stream.has_buffered_data() + } + + fn set_sendorder(&mut self, _conn: &mut Connection, _sendorder: Option<SendOrder>) -> Res<()> { + // Not relevant for SendMessage + Ok(()) + } + + fn set_fairness(&mut self, _conn: &mut Connection, _fairness: bool) -> Res<()> { + // Not relevant for SendMessage + Ok(()) + } + + fn close(&mut self, conn: &mut Connection) -> Res<()> { + self.state.fin()?; + if !self.stream.has_buffered_data() { + conn.stream_close_send(self.stream_id())?; + } + + self.conn_events + .send_closed(self.get_stream_info(), CloseType::Done); + Ok(()) + } + + fn handle_stop_sending(&mut self, close_type: CloseType) { + if !self.state.done() { + self.conn_events + .send_closed(self.get_stream_info(), close_type); + } + } + + fn http_stream(&mut self) -> Option<&mut dyn HttpSendStream> { + Some(self) + } + + fn send_data_atomic(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<()> { + let data_frame = HFrame::Data { + len: buf.len() as u64, + }; + let mut enc = Encoder::default(); + data_frame.encode(&mut enc); + self.stream.buffer(enc.as_ref()); + self.stream.buffer(buf); + _ = self.stream.send_buffer(conn)?; + Ok(()) + } +} + +impl HttpSendStream for SendMessage { + fn send_headers(&mut self, headers: &[Header], conn: &mut Connection) -> Res<()> { + self.state.new_headers(headers, self.message_type)?; + let buf = SendMessage::encode( + &mut self.encoder.borrow_mut(), + headers, + conn, + self.stream_id(), + ); + self.stream.buffer(&buf); + Ok(()) + } + + fn set_new_listener(&mut self, conn_events: Box<dyn SendStreamEvents>) { + self.stream_type = Http3StreamType::ExtendedConnect; + self.conn_events = conn_events; + } + + fn any(&self) -> &dyn Any { + self + } +} + +impl ::std::fmt::Display for SendMessage { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "SendMesage {}", self.stream_id()) + } +} diff --git a/third_party/rust/neqo-http3/src/server.rs b/third_party/rust/neqo-http3/src/server.rs new file mode 100644 index 0000000000..b29f715451 --- /dev/null +++ b/third_party/rust/neqo-http3/src/server.rs @@ -0,0 +1,1329 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::{ + cell::{RefCell, RefMut}, + collections::HashMap, + path::PathBuf, + rc::Rc, + time::Instant, +}; + +use neqo_common::{qtrace, Datagram}; +use neqo_crypto::{AntiReplay, Cipher, PrivateKey, PublicKey, ZeroRttChecker}; +use neqo_transport::{ + server::{ActiveConnectionRef, Server, ValidateAddress}, + ConnectionIdGenerator, Output, +}; + +use crate::{ + connection::Http3State, + connection_server::Http3ServerHandler, + server_connection_events::Http3ServerConnEvent, + server_events::{ + Http3OrWebTransportStream, Http3ServerEvent, Http3ServerEvents, WebTransportRequest, + }, + settings::HttpZeroRttChecker, + Http3Parameters, Http3StreamInfo, Res, +}; + +type HandlerRef = Rc<RefCell<Http3ServerHandler>>; + +const MAX_EVENT_DATA_SIZE: usize = 1024; + +pub struct Http3Server { + server: Server, + http3_parameters: Http3Parameters, + http3_handlers: HashMap<ActiveConnectionRef, HandlerRef>, + events: Http3ServerEvents, +} + +impl ::std::fmt::Display for Http3Server { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Http3 server ") + } +} + +impl Http3Server { + /// # Errors + /// + /// Making a `neqo_transport::Server` may produce an error. This can only be a crypto error if + /// the socket can't be created or configured. + pub fn new( + now: Instant, + certs: &[impl AsRef<str>], + protocols: &[impl AsRef<str>], + anti_replay: AntiReplay, + cid_manager: Rc<RefCell<dyn ConnectionIdGenerator>>, + http3_parameters: Http3Parameters, + zero_rtt_checker: Option<Box<dyn ZeroRttChecker>>, + ) -> Res<Self> { + Ok(Self { + server: Server::new( + now, + certs, + protocols, + anti_replay, + zero_rtt_checker + .unwrap_or_else(|| Box::new(HttpZeroRttChecker::new(http3_parameters.clone()))), + cid_manager, + http3_parameters.get_connection_parameters().clone(), + )?, + http3_parameters, + http3_handlers: HashMap::new(), + events: Http3ServerEvents::default(), + }) + } + + pub fn set_qlog_dir(&mut self, dir: Option<PathBuf>) { + self.server.set_qlog_dir(dir); + } + + pub fn set_validation(&mut self, v: ValidateAddress) { + self.server.set_validation(v); + } + + pub fn set_ciphers(&mut self, ciphers: impl AsRef<[Cipher]>) { + self.server.set_ciphers(ciphers); + } + + /// Enable encrypted client hello (ECH). + /// + /// # Errors + /// + /// Only when NSS can't serialize a configuration. + pub fn enable_ech( + &mut self, + config: u8, + public_name: &str, + sk: &PrivateKey, + pk: &PublicKey, + ) -> Res<()> { + self.server.enable_ech(config, public_name, sk, pk)?; + Ok(()) + } + + #[must_use] + pub fn ech_config(&self) -> &[u8] { + self.server.ech_config() + } + + pub fn process(&mut self, dgram: Option<&Datagram>, now: Instant) -> Output { + qtrace!([self], "Process."); + let out = self.server.process(dgram, now); + self.process_http3(now); + // If we do not that a dgram already try again after process_http3. + match out { + Output::Datagram(d) => { + qtrace!([self], "Send packet: {:?}", d); + Output::Datagram(d) + } + _ => self.server.process(Option::<&Datagram>::None, now), + } + } + + /// Process HTTP3 layer. + fn process_http3(&mut self, now: Instant) { + qtrace!([self], "Process http3 internal."); + let mut active_conns = self.server.active_connections(); + + // We need to find connections that needs to be process on http3 level. + let mut http3_active: Vec<ActiveConnectionRef> = self + .http3_handlers + .iter() + .filter_map(|(conn, handler)| { + if handler.borrow_mut().should_be_processed() && !active_conns.contains(conn) { + Some(conn) + } else { + None + } + }) + .cloned() + .collect(); + // For http_active connection we need to put them in neqo-transport's server + // waiting queue. + active_conns.append(&mut http3_active); + active_conns.dedup(); + active_conns + .iter() + .for_each(|conn| self.server.add_to_waiting(conn.clone())); + for mut conn in active_conns { + self.process_events(&mut conn, now); + } + } + + fn process_events(&mut self, conn: &mut ActiveConnectionRef, now: Instant) { + let mut remove = false; + let http3_parameters = &self.http3_parameters; + { + let handler = self.http3_handlers.entry(conn.clone()).or_insert_with(|| { + Rc::new(RefCell::new(Http3ServerHandler::new( + http3_parameters.clone(), + ))) + }); + handler + .borrow_mut() + .process_http3(&mut conn.borrow_mut(), now); + let mut handler_borrowed = handler.borrow_mut(); + while let Some(e) = handler_borrowed.next_event() { + match e { + Http3ServerConnEvent::Headers { + stream_info, + headers, + fin, + } => self.events.headers( + Http3OrWebTransportStream::new(conn.clone(), handler.clone(), stream_info), + headers, + fin, + ), + Http3ServerConnEvent::DataReadable { stream_info } => { + prepare_data( + stream_info, + &mut handler_borrowed, + conn, + handler, + now, + &mut self.events, + ); + } + Http3ServerConnEvent::DataWritable { stream_info } => self + .events + .data_writable(conn.clone(), handler.clone(), stream_info), + Http3ServerConnEvent::StreamReset { stream_info, error } => { + self.events + .stream_reset(conn.clone(), handler.clone(), stream_info, error); + } + Http3ServerConnEvent::StreamStopSending { stream_info, error } => { + self.events.stream_stop_sending( + conn.clone(), + handler.clone(), + stream_info, + error, + ); + } + Http3ServerConnEvent::StateChange(state) => { + self.events + .connection_state_change(conn.clone(), state.clone()); + if let Http3State::Closed { .. } = state { + remove = true; + } + } + Http3ServerConnEvent::PriorityUpdate { + stream_id, + priority, + } => { + self.events.priority_update(stream_id, priority); + } + Http3ServerConnEvent::ExtendedConnect { stream_id, headers } => { + self.events.webtransport_new_session( + WebTransportRequest::new(conn.clone(), handler.clone(), stream_id), + headers, + ); + } + Http3ServerConnEvent::ExtendedConnectClosed { + stream_id, + reason, + headers, + .. + } => self.events.webtransport_session_closed( + WebTransportRequest::new(conn.clone(), handler.clone(), stream_id), + reason, + headers, + ), + Http3ServerConnEvent::ExtendedConnectNewStream(stream_info) => self + .events + .webtransport_new_stream(Http3OrWebTransportStream::new( + conn.clone(), + handler.clone(), + stream_info, + )), + Http3ServerConnEvent::ExtendedConnectDatagram { + session_id, + datagram, + } => self.events.webtransport_datagram( + WebTransportRequest::new(conn.clone(), handler.clone(), session_id), + datagram, + ), + } + } + } + if remove { + self.http3_handlers.remove(&conn.clone()); + } + } + + /// Get all current events. Best used just in debug/testing code, use + /// `next_event` instead. + pub fn events(&mut self) -> impl Iterator<Item = Http3ServerEvent> { + self.events.events() + } + + /// Return true if there are outstanding events. + #[must_use] + pub fn has_events(&self) -> bool { + self.events.has_events() + } + + /// Get events that indicate state changes on the connection. This method + /// correctly handles cases where handling one event can obsolete + /// previously-queued events, or cause new events to be generated. + pub fn next_event(&mut self) -> Option<Http3ServerEvent> { + self.events.next_event() + } +} +fn prepare_data( + stream_info: Http3StreamInfo, + handler_borrowed: &mut RefMut<Http3ServerHandler>, + conn: &mut ActiveConnectionRef, + handler: &HandlerRef, + now: Instant, + events: &mut Http3ServerEvents, +) { + loop { + let mut data = vec![0; MAX_EVENT_DATA_SIZE]; + let res = handler_borrowed.read_data( + &mut conn.borrow_mut(), + now, + stream_info.stream_id(), + &mut data, + ); + if let Ok((amount, fin)) = res { + if amount > 0 || fin { + if amount < MAX_EVENT_DATA_SIZE { + data.resize(amount, 0); + } + + events.data(conn.clone(), handler.clone(), stream_info, data, fin); + } + if amount < MAX_EVENT_DATA_SIZE || fin { + break; + } + } else { + // Any error will closed the handler, just ignore this event, the next event must + // be a state change event. + break; + } + } +} + +#[cfg(test)] +mod tests { + use std::{ + collections::HashMap, + mem, + ops::{Deref, DerefMut}, + }; + + use neqo_common::{event::Provider, Encoder}; + use neqo_crypto::{AuthenticationStatus, ZeroRttCheckResult, ZeroRttChecker}; + use neqo_qpack::{encoder::QPackEncoder, QpackSettings}; + use neqo_transport::{ + Connection, ConnectionError, ConnectionEvent, State, StreamId, StreamType, ZeroRttState, + }; + use test_fixture::{ + anti_replay, default_client, fixture_init, now, CountingConnectionIdGenerator, + DEFAULT_ALPN, DEFAULT_KEYS, + }; + + use super::{Http3Server, Http3ServerEvent, Http3State, Rc, RefCell}; + use crate::{Error, HFrame, Header, Http3Parameters, Priority}; + + const DEFAULT_SETTINGS: QpackSettings = QpackSettings { + max_table_size_encoder: 100, + max_table_size_decoder: 100, + max_blocked_streams: 100, + }; + + fn http3params(qpack_settings: QpackSettings) -> Http3Parameters { + Http3Parameters::default() + .max_table_size_encoder(qpack_settings.max_table_size_encoder) + .max_table_size_decoder(qpack_settings.max_table_size_decoder) + .max_blocked_streams(qpack_settings.max_blocked_streams) + } + + pub fn create_server(conn_params: Http3Parameters) -> Http3Server { + fixture_init(); + Http3Server::new( + now(), + DEFAULT_KEYS, + DEFAULT_ALPN, + anti_replay(), + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + conn_params, + None, + ) + .expect("create a server") + } + + /// Create a http3 server with default configuration. + pub fn default_server() -> Http3Server { + create_server(http3params(DEFAULT_SETTINGS)) + } + + fn assert_closed(hconn: &mut Http3Server, expected: &Error) { + let err = ConnectionError::Application(expected.code()); + let closed = |e| matches!(e, Http3ServerEvent::StateChange{ state: Http3State::Closing(e) | Http3State::Closed(e), .. } if e == err); + assert!(hconn.events().any(closed)); + } + + fn assert_connected(hconn: &mut Http3Server) { + let connected = |e| { + matches!( + e, + Http3ServerEvent::StateChange { + state: Http3State::Connected, + .. + } + ) + }; + assert!(hconn.events().any(connected)); + } + + fn assert_not_closed(hconn: &mut Http3Server) { + let closed = |e| { + matches!( + e, + Http3ServerEvent::StateChange { + state: Http3State::Closing(..), + .. + } + ) + }; + assert!(!hconn.events().any(closed)); + } + + const CLIENT_SIDE_CONTROL_STREAM_ID: StreamId = StreamId::new(2); + const CLIENT_SIDE_ENCODER_STREAM_ID: StreamId = StreamId::new(6); + const CLIENT_SIDE_DECODER_STREAM_ID: StreamId = StreamId::new(10); + const SERVER_SIDE_CONTROL_STREAM_ID: StreamId = StreamId::new(3); + const SERVER_SIDE_ENCODER_STREAM_ID: StreamId = StreamId::new(7); + const SERVER_SIDE_DECODER_STREAM_ID: StreamId = StreamId::new(11); + + fn connect_transport(server: &mut Http3Server, client: &mut Connection, resume: bool) { + let c1 = client.process(None, now()); + let s1 = server.process(c1.as_dgram_ref(), now()); + let c2 = client.process(s1.as_dgram_ref(), now()); + let needs_auth = client + .events() + .any(|e| e == ConnectionEvent::AuthenticationNeeded); + let c2 = if needs_auth { + assert!(!resume); + // c2 should just be an ACK, so absorb that. + let s_ack = server.process(c2.as_dgram_ref(), now()); + assert!(s_ack.as_dgram_ref().is_none()); + + client.authenticated(AuthenticationStatus::Ok, now()); + client.process(None, now()) + } else { + assert!(resume); + c2 + }; + assert!(client.state().connected()); + let s2 = server.process(c2.as_dgram_ref(), now()); + assert_connected(server); + let c3 = client.process(s2.as_dgram_ref(), now()); + assert!(c3.as_dgram_ref().is_none()); + } + + // Start a client/server and check setting frame. + fn connect_and_receive_settings_with_server(server: &mut Http3Server) -> Connection { + const CONTROL_STREAM_DATA: &[u8] = &[0x0, 0x4, 0x6, 0x1, 0x40, 0x64, 0x7, 0x40, 0x64]; + + let mut client = default_client(); + connect_transport(server, &mut client, false); + + let mut connected = false; + while let Some(e) = client.next_event() { + match e { + ConnectionEvent::NewStream { stream_id } => { + assert!( + (stream_id == SERVER_SIDE_CONTROL_STREAM_ID) + || (stream_id == SERVER_SIDE_ENCODER_STREAM_ID) + || (stream_id == SERVER_SIDE_DECODER_STREAM_ID) + ); + assert_eq!(stream_id.stream_type(), StreamType::UniDi); + } + ConnectionEvent::RecvStreamReadable { stream_id } => { + if stream_id == CLIENT_SIDE_CONTROL_STREAM_ID + || stream_id == SERVER_SIDE_CONTROL_STREAM_ID + { + // the control stream + let mut buf = [0_u8; 100]; + let (amount, fin) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert!(!fin); + assert_eq!(amount, CONTROL_STREAM_DATA.len()); + assert_eq!(&buf[..9], CONTROL_STREAM_DATA); + } else if stream_id == CLIENT_SIDE_ENCODER_STREAM_ID + || stream_id == SERVER_SIDE_ENCODER_STREAM_ID + { + let mut buf = [0_u8; 100]; + let (amount, fin) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert!(!fin); + assert_eq!(amount, 1); + assert_eq!(buf[..1], [0x2]); + } else if stream_id == CLIENT_SIDE_DECODER_STREAM_ID + || stream_id == SERVER_SIDE_DECODER_STREAM_ID + { + let mut buf = [0_u8; 100]; + let (amount, fin) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert!(!fin); + assert_eq!(amount, 1); + assert_eq!(buf[..1], [0x3]); + } else { + panic!("unexpected event"); + } + } + ConnectionEvent::SendStreamWritable { stream_id } => { + assert!( + (stream_id == CLIENT_SIDE_CONTROL_STREAM_ID) + || (stream_id == CLIENT_SIDE_ENCODER_STREAM_ID) + || (stream_id == CLIENT_SIDE_DECODER_STREAM_ID) + ); + } + ConnectionEvent::StateChange(State::Connected) => connected = true, + ConnectionEvent::StateChange(_) | ConnectionEvent::SendStreamCreatable { .. } => (), + _ => panic!("unexpected event"), + } + } + assert!(connected); + client + } + + fn connect_and_receive_settings() -> (Http3Server, Connection) { + // Create a server and connect it to a client. + // We will have a http3 server on one side and a neqo_transport + // connection on the other side so that we can check what the http3 + // side sends and also to simulate an incorrectly behaving http3 + // client. + + let mut server = default_server(); + let client = connect_and_receive_settings_with_server(&mut server); + (server, client) + } + + // Test http3 connection inintialization. + // The server will open the control and qpack streams and send SETTINGS frame. + #[test] + fn test_server_connect() { + mem::drop(connect_and_receive_settings()); + } + + struct PeerConnection { + conn: Connection, + control_stream_id: StreamId, + } + + impl PeerConnection { + /// A shortcut for sending on the control stream. + fn control_send(&mut self, data: &[u8]) { + let res = self.conn.stream_send(self.control_stream_id, data); + assert_eq!(res, Ok(data.len())); + } + } + + impl Deref for PeerConnection { + type Target = Connection; + fn deref(&self) -> &Self::Target { + &self.conn + } + } + + impl DerefMut for PeerConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.conn + } + } + + // Connect transport, send and receive settings. + fn connect_to(server: &mut Http3Server) -> PeerConnection { + let mut neqo_trans_conn = connect_and_receive_settings_with_server(server); + let control_stream = neqo_trans_conn.stream_create(StreamType::UniDi).unwrap(); + let mut sent = neqo_trans_conn.stream_send( + control_stream, + &[0x0, 0x4, 0x6, 0x1, 0x40, 0x64, 0x7, 0x40, 0x64], + ); + assert_eq!(sent, Ok(9)); + let mut encoder = QPackEncoder::new( + &QpackSettings { + max_table_size_encoder: 100, + max_table_size_decoder: 0, + max_blocked_streams: 0, + }, + true, + ); + encoder.add_send_stream(neqo_trans_conn.stream_create(StreamType::UniDi).unwrap()); + encoder.send_encoder_updates(&mut neqo_trans_conn).unwrap(); + let decoder_stream = neqo_trans_conn.stream_create(StreamType::UniDi).unwrap(); + sent = neqo_trans_conn.stream_send(decoder_stream, &[0x3]); + assert_eq!(sent, Ok(1)); + let out1 = neqo_trans_conn.process(None, now()); + let out2 = server.process(out1.as_dgram_ref(), now()); + mem::drop(neqo_trans_conn.process(out2.as_dgram_ref(), now())); + + // assert no error occured. + assert_not_closed(server); + + PeerConnection { + conn: neqo_trans_conn, + control_stream_id: control_stream, + } + } + + fn connect() -> (Http3Server, PeerConnection) { + let mut server = default_server(); + let client = connect_to(&mut server); + (server, client) + } + + // Server: Test receiving a new control stream and a SETTINGS frame. + #[test] + fn test_server_receive_control_frame() { + mem::drop(connect()); + } + + // Server: Test that the connection will be closed if control stream + // has been closed. + #[test] + fn test_server_close_control_stream() { + let (mut hconn, mut peer_conn) = connect(); + let control = peer_conn.control_stream_id; + peer_conn.stream_close_send(control).unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + // Server: test missing SETTINGS frame + // (the first frame sent is a MAX_PUSH_ID frame). + #[test] + fn test_server_missing_settings() { + let (mut hconn, mut neqo_trans_conn) = connect_and_receive_settings(); + // Create client control stream. + let control_stream = neqo_trans_conn.stream_create(StreamType::UniDi).unwrap(); + // Send a MAX_PUSH_ID frame instead. + let sent = neqo_trans_conn.stream_send(control_stream, &[0x0, 0xd, 0x1, 0xf]); + assert_eq!(sent, Ok(4)); + let out = neqo_trans_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpMissingSettings); + } + + // Server: receiving SETTINGS frame twice causes connection close + // with error HTTP_UNEXPECTED_FRAME. + #[test] + fn test_server_receive_settings_twice() { + let (mut hconn, mut peer_conn) = connect(); + // send the second SETTINGS frame. + peer_conn.control_send(&[0x4, 0x6, 0x1, 0x40, 0x64, 0x7, 0x40, 0x64]); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpFrameUnexpected); + } + + fn priority_update_check_id(stream_id: StreamId, valid: bool) { + let (mut hconn, mut peer_conn) = connect(); + // send a priority update + let frame = HFrame::PriorityUpdateRequest { + element_id: stream_id.as_u64(), + priority: Priority::default(), + }; + let mut e = Encoder::default(); + frame.encode(&mut e); + peer_conn.control_send(e.as_ref()); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + // check if the given connection got closed on invalid stream ids + if valid { + assert_not_closed(&mut hconn); + } else { + assert_closed(&mut hconn, &Error::HttpId); + } + } + + #[test] + fn test_priority_update_valid_id_0() { + // Client-Initiated, Bidirectional + priority_update_check_id(StreamId::new(0), true); + } + #[test] + fn test_priority_update_invalid_id_1() { + // Server-Initiated, Bidirectional + priority_update_check_id(StreamId::new(1), false); + } + #[test] + fn test_priority_update_invalid_id_2() { + // Client-Initiated, Unidirectional + priority_update_check_id(StreamId::new(2), false); + } + #[test] + fn test_priority_update_invalid_id_3() { + // Server-Initiated, Unidirectional + priority_update_check_id(StreamId::new(3), false); + } + + #[test] + fn test_priority_update_invalid_large_id() { + // Server-Initiated, Unidirectional (divisible by 4) + priority_update_check_id(StreamId::new(1_000_000_000), false); + } + + fn test_wrong_frame_on_control_stream(v: &[u8]) { + let (mut hconn, mut peer_conn) = connect(); + + // receive a frame that is not allowed on the control stream. + peer_conn.control_send(v); + + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpFrameUnexpected); + } + + // send DATA frame on a control stream + #[test] + fn test_server_data_frame_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x0, 0x2, 0x1, 0x2]); + } + + // send HEADERS frame on a cortrol stream + #[test] + fn test_server_headers_frame_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x1, 0x2, 0x1, 0x2]); + } + + // send PUSH_PROMISE frame on a cortrol stream + #[test] + fn test_server_push_promise_frame_on_control_stream() { + test_wrong_frame_on_control_stream(&[0x5, 0x2, 0x1, 0x2]); + } + + // Server: receive unknown stream type + // also test getting stream id that does not fit into a single byte. + #[test] + fn test_server_received_unknown_stream() { + let (mut hconn, mut peer_conn) = connect(); + + // create a stream with unknown type. + let new_stream_id = peer_conn.stream_create(StreamType::UniDi).unwrap(); + _ = peer_conn + .stream_send(new_stream_id, &[0x41, 0x19, 0x4, 0x4, 0x6, 0x0, 0x8, 0x0]) + .unwrap(); + let out = peer_conn.process(None, now()); + let out = hconn.process(out.as_dgram_ref(), now()); + mem::drop(peer_conn.process(out.as_dgram_ref(), now())); + let out = hconn.process(None, now()); + mem::drop(peer_conn.process(out.as_dgram_ref(), now())); + + // check for stop-sending with Error::HttpStreamCreation. + let mut stop_sending_event_found = false; + while let Some(e) = peer_conn.next_event() { + if let ConnectionEvent::SendStreamStopSending { + stream_id, + app_error, + } = e + { + stop_sending_event_found = true; + assert_eq!(stream_id, new_stream_id); + assert_eq!(app_error, Error::HttpStreamCreation.code()); + } + } + assert!(stop_sending_event_found); + assert_not_closed(&mut hconn); + } + + // Server: receiving a push stream on a server should cause WrongStreamDirection + #[test] + fn test_server_received_push_stream() { + let (mut hconn, mut peer_conn) = connect(); + + // create a push stream. + let push_stream_id = peer_conn.stream_create(StreamType::UniDi).unwrap(); + _ = peer_conn.stream_send(push_stream_id, &[0x1]).unwrap(); + let out = peer_conn.process(None, now()); + let out = hconn.process(out.as_dgram_ref(), now()); + mem::drop(peer_conn.conn.process(out.as_dgram_ref(), now())); + assert_closed(&mut hconn, &Error::HttpStreamCreation); + } + + /// Test reading of a slowly streamed frame. bytes are received one by one + #[test] + fn test_server_frame_reading() { + let (mut hconn, mut peer_conn) = connect_and_receive_settings(); + + // create a control stream. + let control_stream = peer_conn.stream_create(StreamType::UniDi).unwrap(); + + // send the stream type + let mut sent = peer_conn.stream_send(control_stream, &[0x0]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + // start sending SETTINGS frame + sent = peer_conn.stream_send(control_stream, &[0x4]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x4]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x6]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x0]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x8]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x0]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + assert_not_closed(&mut hconn); + + // Now test PushPromise + sent = peer_conn.stream_send(control_stream, &[0x5]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x5]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x4]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x61]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x62]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x63]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + sent = peer_conn.stream_send(control_stream, &[0x64]); + assert_eq!(sent, Ok(1)); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + // PUSH_PROMISE on a control stream will cause an error + assert_closed(&mut hconn, &Error::HttpFrameUnexpected); + } + + // Test reading of a slowly streamed frame. bytes are received one by one + fn test_incomplete_frame(res: &[u8]) { + let (mut hconn, mut peer_conn) = connect_and_receive_settings(); + + // send an incomplete reequest. + let stream_id = peer_conn.stream_create(StreamType::BiDi).unwrap(); + peer_conn.stream_send(stream_id, res).unwrap(); + peer_conn.stream_close_send(stream_id).unwrap(); + + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + assert_closed(&mut hconn, &Error::HttpFrame); + } + + const REQUEST_WITH_BODY: &[u8] = &[ + // headers + 0x01, 0x10, 0x00, 0x00, 0xd1, 0xd7, 0x50, 0x89, 0x41, 0xe9, 0x2a, 0x67, 0x35, 0x53, 0x2e, + 0x43, 0xd3, 0xc1, // the first data frame. + 0x0, 0x3, 0x61, 0x62, 0x63, // the second data frame. + 0x0, 0x3, 0x64, 0x65, 0x66, + ]; + const REQUEST_BODY: &[u8] = &[0x61, 0x62, 0x63, 0x64, 0x65, 0x66]; + + const RESPONSE_BODY: &[u8] = &[0x67, 0x68, 0x69]; + + fn check_request_header(header: &[Header]) { + let expected_request_header = &[ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/"), + ]; + assert_eq!(header, expected_request_header); + } + + // Incomplete DATA frame + #[test] + fn test_server_incomplete_data_frame() { + test_incomplete_frame(&REQUEST_WITH_BODY[..22]); + } + + // Incomplete HEADERS frame + #[test] + fn test_server_incomplete_headers_frame() { + test_incomplete_frame(&REQUEST_WITH_BODY[..10]); + } + + #[test] + fn test_server_incomplete_unknown_frame() { + test_incomplete_frame(&[0x21]); + } + + #[test] + fn test_server_request_with_body() { + let (mut hconn, mut peer_conn) = connect(); + + let stream_id = peer_conn.stream_create(StreamType::BiDi).unwrap(); + peer_conn.stream_send(stream_id, REQUEST_WITH_BODY).unwrap(); + peer_conn.stream_close_send(stream_id).unwrap(); + + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + // Check connection event. There should be 1 Header and 2 data events. + let mut headers_frames = 0; + let mut data_received = 0; + while let Some(event) = hconn.next_event() { + match event { + Http3ServerEvent::Headers { headers, fin, .. } => { + check_request_header(&headers); + assert!(!fin); + headers_frames += 1; + } + Http3ServerEvent::Data { + mut stream, + data, + fin, + } => { + assert_eq!(data, REQUEST_BODY); + assert!(fin); + stream + .send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]) + .unwrap(); + stream.send_data(RESPONSE_BODY).unwrap(); + data_received += 1; + } + Http3ServerEvent::DataWritable { .. } + | Http3ServerEvent::StreamReset { .. } + | Http3ServerEvent::StreamStopSending { .. } + | Http3ServerEvent::StateChange { .. } + | Http3ServerEvent::PriorityUpdate { .. } + | Http3ServerEvent::WebTransport(_) => {} + } + } + assert_eq!(headers_frames, 1); + assert_eq!(data_received, 1); + } + + #[test] + fn test_server_request_with_body_send_stop_sending() { + let (mut hconn, mut peer_conn) = connect(); + + let stream_id = peer_conn.stream_create(StreamType::BiDi).unwrap(); + // Send only request headers for now. + peer_conn + .stream_send(stream_id, &REQUEST_WITH_BODY[..20]) + .unwrap(); + + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + // Check connection event. There should be 1 Header and no data events. + let mut headers_frames = 0; + while let Some(event) = hconn.next_event() { + match event { + Http3ServerEvent::Headers { + mut stream, + headers, + fin, + } => { + check_request_header(&headers); + assert!(!fin); + headers_frames += 1; + stream + .stream_stop_sending(Error::HttpNoError.code()) + .unwrap(); + stream + .send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]) + .unwrap(); + stream.send_data(RESPONSE_BODY).unwrap(); + } + Http3ServerEvent::Data { .. } => { + panic!("We should not have a Data event"); + } + Http3ServerEvent::DataWritable { .. } + | Http3ServerEvent::StreamReset { .. } + | Http3ServerEvent::StreamStopSending { .. } + | Http3ServerEvent::StateChange { .. } + | Http3ServerEvent::PriorityUpdate { .. } + | Http3ServerEvent::WebTransport(_) => {} + } + } + let out = hconn.process(None, now()); + + // Send data. + peer_conn + .stream_send(stream_id, &REQUEST_WITH_BODY[20..]) + .unwrap(); + peer_conn.stream_close_send(stream_id).unwrap(); + + let out = peer_conn.process(out.as_dgram_ref(), now()); + hconn.process(out.as_dgram_ref(), now()); + + while let Some(event) = hconn.next_event() { + match event { + Http3ServerEvent::Headers { .. } => { + panic!("We should not have a Header event"); + } + Http3ServerEvent::Data { .. } => { + panic!("We should not have a Data event"); + } + Http3ServerEvent::DataWritable { .. } + | Http3ServerEvent::StreamReset { .. } + | Http3ServerEvent::StreamStopSending { .. } + | Http3ServerEvent::StateChange { .. } + | Http3ServerEvent::PriorityUpdate { .. } + | Http3ServerEvent::WebTransport(_) => {} + } + } + assert_eq!(headers_frames, 1); + } + + #[test] + fn test_server_request_with_body_server_reset() { + let (mut hconn, mut peer_conn) = connect(); + + let request_stream_id = peer_conn.stream_create(StreamType::BiDi).unwrap(); + // Send only request headers for now. + peer_conn + .stream_send(request_stream_id, &REQUEST_WITH_BODY[..20]) + .unwrap(); + + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + // Check connection event. There should be 1 Header and no data events. + // The server will reset the stream. + let mut headers_frames = 0; + while let Some(event) = hconn.next_event() { + match event { + Http3ServerEvent::Headers { + mut stream, + headers, + fin, + } => { + check_request_header(&headers); + assert!(!fin); + headers_frames += 1; + stream + .cancel_fetch(Error::HttpRequestRejected.code()) + .unwrap(); + } + Http3ServerEvent::Data { .. } => { + panic!("We should not have a Data event"); + } + Http3ServerEvent::DataWritable { .. } + | Http3ServerEvent::StreamReset { .. } + | Http3ServerEvent::StreamStopSending { .. } + | Http3ServerEvent::StateChange { .. } + | Http3ServerEvent::PriorityUpdate { .. } + | Http3ServerEvent::WebTransport(_) => {} + } + } + let out = hconn.process(None, now()); + + let out = peer_conn.process(out.as_dgram_ref(), now()); + hconn.process(out.as_dgram_ref(), now()); + + // Check that STOP_SENDING and REET has been received. + let mut reset = 0; + let mut stop_sending = 0; + while let Some(event) = peer_conn.next_event() { + match event { + ConnectionEvent::RecvStreamReset { stream_id, .. } => { + assert_eq!(request_stream_id, stream_id); + reset += 1; + } + ConnectionEvent::SendStreamStopSending { stream_id, .. } => { + assert_eq!(request_stream_id, stream_id); + stop_sending += 1; + } + _ => {} + } + } + assert_eq!(headers_frames, 1); + assert_eq!(reset, 1); + assert_eq!(stop_sending, 1); + } + + // Server: Test that the connection will be closed if the local control stream + // has been reset. + #[test] + fn test_server_reset_control_stream() { + let (mut hconn, mut peer_conn) = connect(); + peer_conn + .stream_reset_send(CLIENT_SIDE_CONTROL_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + // Server: Test that the connection will be closed if the client side encoder stream + // has been reset. + #[test] + fn test_server_reset_client_side_encoder_stream() { + let (mut hconn, mut peer_conn) = connect(); + peer_conn + .stream_reset_send(CLIENT_SIDE_ENCODER_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + // Server: Test that the connection will be closed if the client side decoder stream + // has been reset. + #[test] + fn test_server_reset_client_side_decoder_stream() { + let (mut hconn, mut peer_conn) = connect(); + peer_conn + .stream_reset_send(CLIENT_SIDE_DECODER_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + // Server: Test that the connection will be closed if the local control stream + // has received a stop_sending. + #[test] + fn test_client_stop_sending_control_stream() { + let (mut hconn, mut peer_conn) = connect(); + + peer_conn + .stream_stop_sending(SERVER_SIDE_CONTROL_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + // Server: Test that the connection will be closed if the server side encoder stream + // has received a stop_sending. + #[test] + fn test_server_stop_sending_encoder_stream() { + let (mut hconn, mut peer_conn) = connect(); + peer_conn + .stream_stop_sending(SERVER_SIDE_ENCODER_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + // Server: Test that the connection will be closed if the server side decoder stream + // has received a stop_sending. + #[test] + fn test_server_stop_sending_decoder_stream() { + let (mut hconn, mut peer_conn) = connect(); + peer_conn + .stream_stop_sending(SERVER_SIDE_DECODER_STREAM_ID, Error::HttpNoError.code()) + .unwrap(); + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + assert_closed(&mut hconn, &Error::HttpClosedCriticalStream); + } + + /// Perform a handshake, then another with the token from the first. + /// The second should always resume, but it might not always accept early data. + fn zero_rtt_with_settings(conn_params: Http3Parameters, zero_rtt: ZeroRttState) { + let (_, mut client) = connect(); + let token = client.events().find_map(|e| { + if let ConnectionEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }); + assert!(token.is_some()); + + let mut server = create_server(conn_params); + let mut client = default_client(); + client.enable_resumption(now(), token.unwrap()).unwrap(); + + connect_transport(&mut server, &mut client, true); + assert!(client.tls_info().unwrap().resumed()); + assert_eq!(client.zero_rtt_state(), zero_rtt); + } + + #[test] + fn zero_rtt() { + zero_rtt_with_settings(http3params(DEFAULT_SETTINGS), ZeroRttState::AcceptedClient); + } + + /// A larger QPACK decoder table size isn't an impediment to 0-RTT. + #[test] + fn zero_rtt_larger_decoder_table() { + zero_rtt_with_settings( + http3params(QpackSettings { + max_table_size_decoder: DEFAULT_SETTINGS.max_table_size_decoder + 1, + ..DEFAULT_SETTINGS + }), + ZeroRttState::AcceptedClient, + ); + } + + /// A smaller QPACK decoder table size prevents 0-RTT. + #[test] + fn zero_rtt_smaller_decoder_table() { + zero_rtt_with_settings( + http3params(QpackSettings { + max_table_size_decoder: DEFAULT_SETTINGS.max_table_size_decoder - 1, + ..DEFAULT_SETTINGS + }), + ZeroRttState::Rejected, + ); + } + + /// More blocked streams does not prevent 0-RTT. + #[test] + fn zero_rtt_more_blocked_streams() { + zero_rtt_with_settings( + http3params(QpackSettings { + max_blocked_streams: DEFAULT_SETTINGS.max_blocked_streams + 1, + ..DEFAULT_SETTINGS + }), + ZeroRttState::AcceptedClient, + ); + } + + /// A lower number of blocked streams also prevents 0-RTT. + #[test] + fn zero_rtt_fewer_blocked_streams() { + zero_rtt_with_settings( + http3params(QpackSettings { + max_blocked_streams: DEFAULT_SETTINGS.max_blocked_streams - 1, + ..DEFAULT_SETTINGS + }), + ZeroRttState::Rejected, + ); + } + + /// The size of the encoder table is local and therefore doesn't prevent 0-RTT. + #[test] + fn zero_rtt_smaller_encoder_table() { + zero_rtt_with_settings( + http3params(QpackSettings { + max_table_size_encoder: DEFAULT_SETTINGS.max_table_size_encoder - 1, + ..DEFAULT_SETTINGS + }), + ZeroRttState::AcceptedClient, + ); + } + + #[test] + fn client_request_hash() { + let (mut hconn, mut peer_conn) = connect(); + + let request_stream_id_1 = peer_conn.stream_create(StreamType::BiDi).unwrap(); + // Send only request headers for now. + peer_conn + .stream_send(request_stream_id_1, REQUEST_WITH_BODY) + .unwrap(); + + let request_stream_id_2 = peer_conn.stream_create(StreamType::BiDi).unwrap(); + // Send only request headers for now. + peer_conn + .stream_send(request_stream_id_2, REQUEST_WITH_BODY) + .unwrap(); + + let out = peer_conn.process(None, now()); + hconn.process(out.as_dgram_ref(), now()); + + let mut requests = HashMap::new(); + while let Some(event) = hconn.next_event() { + match event { + Http3ServerEvent::Headers { stream, .. } => { + assert!(requests.get(&stream).is_none()); + requests.insert(stream, 0); + } + Http3ServerEvent::Data { stream, .. } => { + assert!(requests.get(&stream).is_some()); + } + Http3ServerEvent::DataWritable { .. } + | Http3ServerEvent::StreamReset { .. } + | Http3ServerEvent::StreamStopSending { .. } + | Http3ServerEvent::StateChange { .. } + | Http3ServerEvent::PriorityUpdate { .. } + | Http3ServerEvent::WebTransport(_) => {} + } + } + assert_eq!(requests.len(), 2); + } + + #[derive(Debug, Default)] + pub struct RejectZeroRtt {} + impl ZeroRttChecker for RejectZeroRtt { + fn check(&self, _token: &[u8]) -> ZeroRttCheckResult { + ZeroRttCheckResult::Reject + } + } + + #[test] + fn reject_zero_server() { + fixture_init(); + let mut server = Http3Server::new( + now(), + DEFAULT_KEYS, + DEFAULT_ALPN, + anti_replay(), + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + http3params(DEFAULT_SETTINGS), + Some(Box::<RejectZeroRtt>::default()), + ) + .expect("create a server"); + let mut client = connect_to(&mut server); + let token = client.events().find_map(|e| { + if let ConnectionEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }); + assert!(token.is_some()); + + let mut client = default_client(); + client.enable_resumption(now(), token.unwrap()).unwrap(); + + connect_transport(&mut server, &mut client, true); + assert!(client.tls_info().unwrap().resumed()); + assert_eq!(client.zero_rtt_state(), ZeroRttState::Rejected); + } +} diff --git a/third_party/rust/neqo-http3/src/server_connection_events.rs b/third_party/rust/neqo-http3/src/server_connection_events.rs new file mode 100644 index 0000000000..cbc8e6d56e --- /dev/null +++ b/third_party/rust/neqo-http3/src/server_connection_events.rs @@ -0,0 +1,196 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, collections::VecDeque, rc::Rc}; + +use neqo_common::Header; +use neqo_transport::{AppError, StreamId}; + +use crate::{ + connection::Http3State, + features::extended_connect::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason}, + CloseType, Http3StreamInfo, HttpRecvStreamEvents, Priority, RecvStreamEvents, SendStreamEvents, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub(crate) enum Http3ServerConnEvent { + /// Headers are ready. + Headers { + stream_info: Http3StreamInfo, + headers: Vec<Header>, + fin: bool, + }, + PriorityUpdate { + stream_id: StreamId, + priority: Priority, + }, + /// Request data is ready. + DataReadable { + stream_info: Http3StreamInfo, + }, + DataWritable { + stream_info: Http3StreamInfo, + }, + StreamReset { + stream_info: Http3StreamInfo, + error: AppError, + }, + StreamStopSending { + stream_info: Http3StreamInfo, + error: AppError, + }, + /// Connection state change. + StateChange(Http3State), + ExtendedConnect { + stream_id: StreamId, + headers: Vec<Header>, + }, + ExtendedConnectClosed { + connect_type: ExtendedConnectType, + stream_id: StreamId, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + }, + ExtendedConnectNewStream(Http3StreamInfo), + ExtendedConnectDatagram { + session_id: StreamId, + datagram: Vec<u8>, + }, +} + +#[derive(Debug, Default, Clone)] +pub(crate) struct Http3ServerConnEvents { + events: Rc<RefCell<VecDeque<Http3ServerConnEvent>>>, +} + +impl SendStreamEvents for Http3ServerConnEvents { + fn send_closed(&self, stream_info: Http3StreamInfo, close_type: CloseType) { + if close_type != CloseType::Done { + self.insert(Http3ServerConnEvent::StreamStopSending { + stream_info, + error: close_type.error().unwrap(), + }); + } + } + + fn data_writable(&self, stream_info: Http3StreamInfo) { + self.insert(Http3ServerConnEvent::DataWritable { stream_info }); + } +} + +impl RecvStreamEvents for Http3ServerConnEvents { + /// Add a new `DataReadable` event + fn data_readable(&self, stream_info: Http3StreamInfo) { + self.insert(Http3ServerConnEvent::DataReadable { stream_info }); + } + + fn recv_closed(&self, stream_info: Http3StreamInfo, close_type: CloseType) { + if close_type != CloseType::Done { + self.remove_events_for_stream_id(stream_info); + self.insert(Http3ServerConnEvent::StreamReset { + stream_info, + error: close_type.error().unwrap(), + }); + } + } +} + +impl HttpRecvStreamEvents for Http3ServerConnEvents { + /// Add a new `HeaderReady` event. + fn header_ready( + &self, + stream_info: Http3StreamInfo, + headers: Vec<Header>, + _interim: bool, + fin: bool, + ) { + self.insert(Http3ServerConnEvent::Headers { + stream_info, + headers, + fin, + }); + } + + fn extended_connect_new_session(&self, stream_id: StreamId, headers: Vec<Header>) { + self.insert(Http3ServerConnEvent::ExtendedConnect { stream_id, headers }); + } +} + +impl ExtendedConnectEvents for Http3ServerConnEvents { + fn session_start( + &self, + _connect_type: ExtendedConnectType, + _stream_id: StreamId, + _status: u16, + _headers: Vec<Header>, + ) { + } + + fn session_end( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + ) { + self.insert(Http3ServerConnEvent::ExtendedConnectClosed { + connect_type, + stream_id, + reason, + headers, + }); + } + + fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo) { + self.insert(Http3ServerConnEvent::ExtendedConnectNewStream(stream_info)); + } + + fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>) { + self.insert(Http3ServerConnEvent::ExtendedConnectDatagram { + session_id, + datagram, + }); + } +} + +impl Http3ServerConnEvents { + fn insert(&self, event: Http3ServerConnEvent) { + self.events.borrow_mut().push_back(event); + } + + fn remove<F>(&self, f: F) + where + F: Fn(&Http3ServerConnEvent) -> bool, + { + self.events.borrow_mut().retain(|evt| !f(evt)); + } + + pub fn has_events(&self) -> bool { + !self.events.borrow().is_empty() + } + + pub fn next_event(&self) -> Option<Http3ServerConnEvent> { + self.events.borrow_mut().pop_front() + } + + pub fn connection_state_change(&self, state: Http3State) { + self.insert(Http3ServerConnEvent::StateChange(state)); + } + + pub fn priority_update(&self, stream_id: StreamId, priority: Priority) { + self.insert(Http3ServerConnEvent::PriorityUpdate { + stream_id, + priority, + }); + } + + fn remove_events_for_stream_id(&self, stream_info: Http3StreamInfo) { + self.remove(|evt| { + matches!(evt, + Http3ServerConnEvent::Headers { stream_info: x, .. } | Http3ServerConnEvent::DataReadable { stream_info: x, .. } if *x == stream_info) + }); + } +} diff --git a/third_party/rust/neqo-http3/src/server_events.rs b/third_party/rust/neqo-http3/src/server_events.rs new file mode 100644 index 0000000000..4be48363df --- /dev/null +++ b/third_party/rust/neqo-http3/src/server_events.rs @@ -0,0 +1,601 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::{ + cell::RefCell, + collections::VecDeque, + convert::TryFrom, + ops::{Deref, DerefMut}, + rc::Rc, +}; + +use neqo_common::{qdebug, qinfo, Encoder, Header}; +use neqo_transport::{ + server::ActiveConnectionRef, AppError, Connection, DatagramTracking, StreamId, StreamType, +}; + +use crate::{ + connection::{Http3State, WebTransportSessionAcceptAction}, + connection_server::Http3ServerHandler, + features::extended_connect::SessionCloseReason, + Http3StreamInfo, Http3StreamType, Priority, Res, +}; + +#[derive(Debug, Clone)] +pub struct StreamHandler { + pub conn: ActiveConnectionRef, + pub handler: Rc<RefCell<Http3ServerHandler>>, + pub stream_info: Http3StreamInfo, +} + +impl ::std::fmt::Display for StreamHandler { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + let conn: &Connection = &self.conn.borrow(); + write!(f, "conn={} stream_info={:?}", conn, self.stream_info) + } +} + +impl std::hash::Hash for StreamHandler { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.conn.hash(state); + state.write_u64(self.stream_info.stream_id().as_u64()); + state.finish(); + } +} + +impl PartialEq for StreamHandler { + fn eq(&self, other: &Self) -> bool { + self.conn == other.conn && self.stream_info.stream_id() == other.stream_info.stream_id() + } +} + +impl Eq for StreamHandler {} + +impl StreamHandler { + pub fn stream_id(&self) -> StreamId { + self.stream_info.stream_id() + } + + /// Supply a response header to a request. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn send_headers(&mut self, headers: &[Header]) -> Res<()> { + self.handler.borrow_mut().send_headers( + self.stream_id(), + headers, + &mut self.conn.borrow_mut(), + ) + } + + /// Supply response data to a request. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn send_data(&mut self, buf: &[u8]) -> Res<usize> { + self.handler + .borrow_mut() + .send_data(self.stream_id(), buf, &mut self.conn.borrow_mut()) + } + + /// Close sending side. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn stream_close_send(&mut self) -> Res<()> { + self.handler + .borrow_mut() + .stream_close_send(self.stream_id(), &mut self.conn.borrow_mut()) + } + + /// Request a peer to stop sending a stream. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn stream_stop_sending(&mut self, app_error: AppError) -> Res<()> { + qdebug!( + [self], + "stop sending stream_id:{} error:{}.", + self.stream_info.stream_id(), + app_error + ); + self.handler.borrow_mut().stream_stop_sending( + self.stream_info.stream_id(), + app_error, + &mut self.conn.borrow_mut(), + ) + } + + /// Reset sending side of a stream. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn stream_reset_send(&mut self, app_error: AppError) -> Res<()> { + qdebug!( + [self], + "reset send stream_id:{} error:{}.", + self.stream_info.stream_id(), + app_error + ); + self.handler.borrow_mut().stream_reset_send( + self.stream_info.stream_id(), + app_error, + &mut self.conn.borrow_mut(), + ) + } + + /// Reset a stream/request. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore + pub fn cancel_fetch(&mut self, app_error: AppError) -> Res<()> { + qdebug!([self], "reset error:{}.", app_error); + self.handler.borrow_mut().cancel_fetch( + self.stream_info.stream_id(), + app_error, + &mut self.conn.borrow_mut(), + ) + } +} + +#[derive(Debug, Clone)] +pub struct Http3OrWebTransportStream { + stream_handler: StreamHandler, +} + +impl ::std::fmt::Display for Http3OrWebTransportStream { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Stream server {:?}", self.stream_handler) + } +} + +impl Http3OrWebTransportStream { + pub(crate) fn new( + conn: ActiveConnectionRef, + handler: Rc<RefCell<Http3ServerHandler>>, + stream_info: Http3StreamInfo, + ) -> Self { + Self { + stream_handler: StreamHandler { + conn, + handler, + stream_info, + }, + } + } + + /// Supply a response header to a request. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn send_headers(&mut self, headers: &[Header]) -> Res<()> { + self.stream_handler.send_headers(headers) + } + + /// Supply response data to a request. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn send_data(&mut self, data: &[u8]) -> Res<usize> { + qinfo!([self], "Set new response."); + self.stream_handler.send_data(data) + } + + /// Close sending side. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn stream_close_send(&mut self) -> Res<()> { + qinfo!([self], "Set new response."); + self.stream_handler.stream_close_send() + } +} + +impl Deref for Http3OrWebTransportStream { + type Target = StreamHandler; + #[must_use] + fn deref(&self) -> &Self::Target { + &self.stream_handler + } +} + +impl DerefMut for Http3OrWebTransportStream { + fn deref_mut(&mut self) -> &mut StreamHandler { + &mut self.stream_handler + } +} + +impl std::hash::Hash for Http3OrWebTransportStream { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.stream_handler.hash(state); + state.finish(); + } +} + +impl PartialEq for Http3OrWebTransportStream { + fn eq(&self, other: &Self) -> bool { + self.stream_handler == other.stream_handler + } +} + +impl Eq for Http3OrWebTransportStream {} + +#[derive(Debug, Clone)] +pub struct WebTransportRequest { + stream_handler: StreamHandler, +} + +impl ::std::fmt::Display for WebTransportRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "WebTransport session {}", self.stream_handler) + } +} + +impl WebTransportRequest { + pub(crate) fn new( + conn: ActiveConnectionRef, + handler: Rc<RefCell<Http3ServerHandler>>, + stream_id: StreamId, + ) -> Self { + Self { + stream_handler: StreamHandler { + conn, + handler, + stream_info: Http3StreamInfo::new(stream_id, Http3StreamType::Http), + }, + } + } + + #[must_use] + pub fn state(&self) -> Http3State { + self.stream_handler.handler.borrow().state() + } + + /// Respond to a `WebTransport` session request. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn response(&mut self, accept: &WebTransportSessionAcceptAction) -> Res<()> { + qinfo!([self], "Set a response for a WebTransport session."); + self.stream_handler + .handler + .borrow_mut() + .webtransport_session_accept( + &mut self.stream_handler.conn.borrow_mut(), + self.stream_handler.stream_info.stream_id(), + accept, + ) + } + + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + /// Also return an error if the stream was closed on the transport layer, + /// but that information is not yet consumed on the http/3 layer. + pub fn close_session(&mut self, error: u32, message: &str) -> Res<()> { + self.stream_handler + .handler + .borrow_mut() + .webtransport_close_session( + &mut self.stream_handler.conn.borrow_mut(), + self.stream_handler.stream_info.stream_id(), + error, + message, + ) + } + + #[must_use] + pub fn stream_id(&self) -> StreamId { + self.stream_handler.stream_id() + } + + /// Close sending side. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + pub fn create_stream(&mut self, stream_type: StreamType) -> Res<Http3OrWebTransportStream> { + let session_id = self.stream_handler.stream_id(); + let id = self + .stream_handler + .handler + .borrow_mut() + .webtransport_create_stream( + &mut self.stream_handler.conn.borrow_mut(), + session_id, + stream_type, + )?; + + Ok(Http3OrWebTransportStream::new( + self.stream_handler.conn.clone(), + self.stream_handler.handler.clone(), + Http3StreamInfo::new(id, Http3StreamType::WebTransport(session_id)), + )) + } + + /// Send `WebTransport` datagram. + /// + /// # Errors + /// + /// It may return `InvalidStreamId` if a stream does not exist anymore. + /// The function returns `TooMuchData` if the supply buffer is bigger than + /// the allowed remote datagram size. + pub fn send_datagram(&mut self, buf: &[u8], id: impl Into<DatagramTracking>) -> Res<()> { + let session_id = self.stream_handler.stream_id(); + self.stream_handler + .handler + .borrow_mut() + .webtransport_send_datagram( + &mut self.stream_handler.conn.borrow_mut(), + session_id, + buf, + id, + ) + } + + #[must_use] + pub fn remote_datagram_size(&self) -> u64 { + self.stream_handler.conn.borrow().remote_datagram_size() + } + + /// Returns the current max size of a datagram that can fit into a packet. + /// The value will change over time depending on the encoded size of the + /// packet number, ack frames, etc. + /// + /// # Errors + /// + /// The function returns `NotAvailable` if datagrams are not enabled. + /// + /// # Panics + /// + /// This cannot panic. The max varint length is 8. + pub fn max_datagram_size(&self) -> Res<u64> { + let max_size = self.stream_handler.conn.borrow().max_datagram_size()?; + Ok(max_size + - u64::try_from(Encoder::varint_len( + self.stream_handler.stream_id().as_u64(), + )) + .unwrap()) + } +} + +impl Deref for WebTransportRequest { + type Target = StreamHandler; + #[must_use] + fn deref(&self) -> &Self::Target { + &self.stream_handler + } +} + +impl DerefMut for WebTransportRequest { + fn deref_mut(&mut self) -> &mut StreamHandler { + &mut self.stream_handler + } +} + +impl std::hash::Hash for WebTransportRequest { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.stream_handler.hash(state); + state.finish(); + } +} + +impl PartialEq for WebTransportRequest { + fn eq(&self, other: &Self) -> bool { + self.stream_handler == other.stream_handler + } +} + +impl Eq for WebTransportRequest {} + +#[derive(Debug, Clone)] +pub enum WebTransportServerEvent { + NewSession { + session: WebTransportRequest, + headers: Vec<Header>, + }, + SessionClosed { + session: WebTransportRequest, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + }, + NewStream(Http3OrWebTransportStream), + Datagram { + session: WebTransportRequest, + datagram: Vec<u8>, + }, +} + +#[derive(Debug, Clone)] +pub enum Http3ServerEvent { + /// Headers are ready. + Headers { + stream: Http3OrWebTransportStream, + headers: Vec<Header>, + fin: bool, + }, + /// Request data is ready. + Data { + stream: Http3OrWebTransportStream, + data: Vec<u8>, + fin: bool, + }, + DataWritable { + stream: Http3OrWebTransportStream, + }, + StreamReset { + stream: Http3OrWebTransportStream, + error: AppError, + }, + StreamStopSending { + stream: Http3OrWebTransportStream, + error: AppError, + }, + /// When individual connection change state. It is only used for tests. + StateChange { + conn: ActiveConnectionRef, + state: Http3State, + }, + PriorityUpdate { + stream_id: StreamId, + priority: Priority, + }, + WebTransport(WebTransportServerEvent), +} + +#[derive(Debug, Default, Clone)] +pub struct Http3ServerEvents { + events: Rc<RefCell<VecDeque<Http3ServerEvent>>>, +} + +impl Http3ServerEvents { + fn insert(&self, event: Http3ServerEvent) { + self.events.borrow_mut().push_back(event); + } + + /// Take all events + pub fn events(&self) -> impl Iterator<Item = Http3ServerEvent> { + self.events.replace(VecDeque::new()).into_iter() + } + + /// Whether there is request pending. + pub fn has_events(&self) -> bool { + !self.events.borrow().is_empty() + } + + /// Take the next event if present. + pub fn next_event(&self) -> Option<Http3ServerEvent> { + self.events.borrow_mut().pop_front() + } + + /// Insert a `Headers` event. + pub(crate) fn headers( + &self, + request: Http3OrWebTransportStream, + headers: Vec<Header>, + fin: bool, + ) { + self.insert(Http3ServerEvent::Headers { + stream: request, + headers, + fin, + }); + } + + /// Insert a `StateChange` event. + pub(crate) fn connection_state_change(&self, conn: ActiveConnectionRef, state: Http3State) { + self.insert(Http3ServerEvent::StateChange { conn, state }); + } + + /// Insert a `Data` event. + pub(crate) fn data( + &self, + conn: ActiveConnectionRef, + handler: Rc<RefCell<Http3ServerHandler>>, + stream_info: Http3StreamInfo, + data: Vec<u8>, + fin: bool, + ) { + self.insert(Http3ServerEvent::Data { + stream: Http3OrWebTransportStream::new(conn, handler, stream_info), + data, + fin, + }); + } + + pub(crate) fn data_writable( + &self, + conn: ActiveConnectionRef, + handler: Rc<RefCell<Http3ServerHandler>>, + stream_info: Http3StreamInfo, + ) { + self.insert(Http3ServerEvent::DataWritable { + stream: Http3OrWebTransportStream::new(conn, handler, stream_info), + }); + } + + pub(crate) fn stream_reset( + &self, + conn: ActiveConnectionRef, + handler: Rc<RefCell<Http3ServerHandler>>, + stream_info: Http3StreamInfo, + error: AppError, + ) { + self.insert(Http3ServerEvent::StreamReset { + stream: Http3OrWebTransportStream::new(conn, handler, stream_info), + error, + }); + } + + pub(crate) fn stream_stop_sending( + &self, + conn: ActiveConnectionRef, + handler: Rc<RefCell<Http3ServerHandler>>, + stream_info: Http3StreamInfo, + error: AppError, + ) { + self.insert(Http3ServerEvent::StreamStopSending { + stream: Http3OrWebTransportStream::new(conn, handler, stream_info), + error, + }); + } + + pub(crate) fn priority_update(&self, stream_id: StreamId, priority: Priority) { + self.insert(Http3ServerEvent::PriorityUpdate { + stream_id, + priority, + }); + } + + pub(crate) fn webtransport_new_session( + &self, + session: WebTransportRequest, + headers: Vec<Header>, + ) { + self.insert(Http3ServerEvent::WebTransport( + WebTransportServerEvent::NewSession { session, headers }, + )); + } + + pub(crate) fn webtransport_session_closed( + &self, + session: WebTransportRequest, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + ) { + self.insert(Http3ServerEvent::WebTransport( + WebTransportServerEvent::SessionClosed { + session, + reason, + headers, + }, + )); + } + + pub(crate) fn webtransport_new_stream(&self, stream: Http3OrWebTransportStream) { + self.insert(Http3ServerEvent::WebTransport( + WebTransportServerEvent::NewStream(stream), + )); + } + + pub(crate) fn webtransport_datagram(&self, session: WebTransportRequest, datagram: Vec<u8>) { + self.insert(Http3ServerEvent::WebTransport( + WebTransportServerEvent::Datagram { session, datagram }, + )); + } +} diff --git a/third_party/rust/neqo-http3/src/settings.rs b/third_party/rust/neqo-http3/src/settings.rs new file mode 100644 index 0000000000..9cd4b994b7 --- /dev/null +++ b/third_party/rust/neqo-http3/src/settings.rs @@ -0,0 +1,299 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use std::ops::Deref; + +use neqo_common::{Decoder, Encoder}; +use neqo_crypto::{ZeroRttCheckResult, ZeroRttChecker}; + +use crate::{Error, Http3Parameters, Res}; + +type SettingsType = u64; + +/// Increment this version number if a new setting is added and that might +/// cause 0-RTT to be accepted where shouldn't be. +const SETTINGS_ZERO_RTT_VERSION: u64 = 1; + +const SETTINGS_MAX_HEADER_LIST_SIZE: SettingsType = 0x6; +const SETTINGS_QPACK_MAX_TABLE_CAPACITY: SettingsType = 0x1; +const SETTINGS_QPACK_BLOCKED_STREAMS: SettingsType = 0x7; +const SETTINGS_ENABLE_WEB_TRANSPORT: SettingsType = 0x2b60_3742; +// draft-ietf-masque-h3-datagram-04. +// We also use this old value because the current web-platform test only supports +// this value. +const SETTINGS_H3_DATAGRAM_DRAFT04: SettingsType = 0x00ff_d277; + +const SETTINGS_H3_DATAGRAM: SettingsType = 0x33; + +pub const H3_RESERVED_SETTINGS: &[SettingsType] = &[0x2, 0x3, 0x4, 0x5]; + +#[derive(Clone, PartialEq, Eq, Debug, Copy)] +pub enum HSettingType { + MaxHeaderListSize, + MaxTableCapacity, + BlockedStreams, + EnableWebTransport, + EnableH3Datagram, +} + +fn hsetting_default(setting_type: HSettingType) -> u64 { + match setting_type { + HSettingType::MaxHeaderListSize => 1 << 62, + HSettingType::MaxTableCapacity + | HSettingType::BlockedStreams + | HSettingType::EnableWebTransport + | HSettingType::EnableH3Datagram => 0, + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HSetting { + pub setting_type: HSettingType, + pub value: u64, +} + +impl HSetting { + #[must_use] + pub fn new(setting_type: HSettingType, value: u64) -> Self { + Self { + setting_type, + value, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct HSettings { + settings: Vec<HSetting>, +} + +impl HSettings { + #[must_use] + pub fn new(settings: &[HSetting]) -> Self { + Self { + settings: settings.to_vec(), + } + } + + #[must_use] + pub fn get(&self, setting: HSettingType) -> u64 { + match self.settings.iter().find(|s| s.setting_type == setting) { + Some(v) => v.value, + None => hsetting_default(setting), + } + } + + pub fn encode_frame_contents(&self, enc: &mut Encoder) { + enc.encode_vvec_with(|enc_inner| { + for iter in &self.settings { + match iter.setting_type { + HSettingType::MaxHeaderListSize => { + enc_inner.encode_varint(SETTINGS_MAX_HEADER_LIST_SIZE); + enc_inner.encode_varint(iter.value); + } + HSettingType::MaxTableCapacity => { + enc_inner.encode_varint(SETTINGS_QPACK_MAX_TABLE_CAPACITY); + enc_inner.encode_varint(iter.value); + } + HSettingType::BlockedStreams => { + enc_inner.encode_varint(SETTINGS_QPACK_BLOCKED_STREAMS); + enc_inner.encode_varint(iter.value); + } + HSettingType::EnableWebTransport => { + enc_inner.encode_varint(SETTINGS_ENABLE_WEB_TRANSPORT); + enc_inner.encode_varint(iter.value); + } + HSettingType::EnableH3Datagram => { + if iter.value == 1 { + enc_inner.encode_varint(SETTINGS_H3_DATAGRAM_DRAFT04); + enc_inner.encode_varint(iter.value); + enc_inner.encode_varint(SETTINGS_H3_DATAGRAM); + enc_inner.encode_varint(iter.value); + } + } + } + } + }); + } + + /// # Errors + /// + /// Returns an error if settings types are reserved of settings value are not permitted. + pub fn decode_frame_contents(&mut self, dec: &mut Decoder) -> Res<()> { + while dec.remaining() > 0 { + let t = dec.decode_varint(); + let v = dec.decode_varint(); + + if let Some(settings_type) = t { + if H3_RESERVED_SETTINGS.contains(&settings_type) { + return Err(Error::HttpSettings); + } + } + match (t, v) { + (Some(SETTINGS_MAX_HEADER_LIST_SIZE), Some(value)) => self + .settings + .push(HSetting::new(HSettingType::MaxHeaderListSize, value)), + (Some(SETTINGS_QPACK_MAX_TABLE_CAPACITY), Some(value)) => self + .settings + .push(HSetting::new(HSettingType::MaxTableCapacity, value)), + (Some(SETTINGS_QPACK_BLOCKED_STREAMS), Some(value)) => self + .settings + .push(HSetting::new(HSettingType::BlockedStreams, value)), + (Some(SETTINGS_ENABLE_WEB_TRANSPORT), Some(value)) => { + if value > 1 { + return Err(Error::HttpSettings); + } + self.settings + .push(HSetting::new(HSettingType::EnableWebTransport, value)); + } + (Some(SETTINGS_H3_DATAGRAM_DRAFT04), Some(value)) => { + if value > 1 { + return Err(Error::HttpSettings); + } + if !self + .settings + .iter() + .any(|s| s.setting_type == HSettingType::EnableH3Datagram) + { + self.settings + .push(HSetting::new(HSettingType::EnableH3Datagram, value)); + } + } + (Some(SETTINGS_H3_DATAGRAM), Some(value)) => { + if value > 1 { + return Err(Error::HttpSettings); + } + if !self + .settings + .iter() + .any(|s| s.setting_type == HSettingType::EnableH3Datagram) + { + self.settings + .push(HSetting::new(HSettingType::EnableH3Datagram, value)); + } + } + // other supported settings here + (Some(_), Some(_)) => {} // ignore unknown setting, it is fine. + _ => return Err(Error::NotEnoughData), + }; + } + Ok(()) + } +} + +impl Deref for HSettings { + type Target = [HSetting]; + fn deref(&self) -> &Self::Target { + &self.settings + } +} + +impl From<&Http3Parameters> for HSettings { + fn from(conn_param: &Http3Parameters) -> Self { + Self { + settings: vec![ + HSetting { + setting_type: HSettingType::MaxTableCapacity, + value: conn_param.get_max_table_size_decoder(), + }, + HSetting { + setting_type: HSettingType::BlockedStreams, + value: u64::from(conn_param.get_max_blocked_streams()), + }, + HSetting { + setting_type: HSettingType::EnableWebTransport, + value: u64::from(conn_param.get_webtransport()), + }, + HSetting { + setting_type: HSettingType::EnableH3Datagram, + value: u64::from(conn_param.get_http3_datagram()), + }, + ], + } + } +} + +#[derive(Debug)] +pub struct HttpZeroRttChecker { + settings: Http3Parameters, +} + +impl HttpZeroRttChecker { + /// Right now we only have QPACK settings, so that is all this takes. + #[must_use] + pub fn new(settings: Http3Parameters) -> Self { + Self { settings } + } + + /// Save the settings that matter for 0-RTT. + #[must_use] + pub fn save(settings: &Http3Parameters) -> Vec<u8> { + let mut enc = Encoder::new(); + enc.encode_varint(SETTINGS_ZERO_RTT_VERSION) + .encode_varint(SETTINGS_QPACK_MAX_TABLE_CAPACITY) + .encode_varint(settings.get_max_table_size_decoder()) + .encode_varint(SETTINGS_QPACK_BLOCKED_STREAMS) + .encode_varint(settings.get_max_blocked_streams()); + if settings.get_webtransport() { + enc.encode_varint(SETTINGS_ENABLE_WEB_TRANSPORT) + .encode_varint(true); + } + if settings.get_http3_datagram() { + enc.encode_varint(SETTINGS_H3_DATAGRAM).encode_varint(true); + } + enc.into() + } +} + +impl ZeroRttChecker for HttpZeroRttChecker { + fn check(&self, token: &[u8]) -> ZeroRttCheckResult { + let mut dec = Decoder::from(token); + + // Read and check the version. + if let Some(version) = dec.decode_varint() { + if version != SETTINGS_ZERO_RTT_VERSION { + return ZeroRttCheckResult::Reject; + } + } else { + return ZeroRttCheckResult::Fail; + } + + // Now treat the rest as a settings frame. + let mut settings = HSettings::new(&[]); + if settings.decode_frame_contents(&mut dec).is_err() { + return ZeroRttCheckResult::Fail; + } + if settings.iter().all(|setting| match setting.setting_type { + HSettingType::BlockedStreams => { + u64::from(self.settings.get_max_blocked_streams()) >= setting.value + } + HSettingType::MaxTableCapacity => { + self.settings.get_max_table_size_decoder() >= setting.value + } + HSettingType::EnableWebTransport => { + if setting.value > 1 { + return false; + } + let value = setting.value == 1; + self.settings.get_webtransport() || !value + } + HSettingType::EnableH3Datagram => { + if setting.value > 1 { + return false; + } + let value = setting.value == 1; + self.settings.get_http3_datagram() || !value + } + HSettingType::MaxHeaderListSize => true, + }) { + ZeroRttCheckResult::Accept + } else { + ZeroRttCheckResult::Reject + } + } +} diff --git a/third_party/rust/neqo-http3/src/stream_type_reader.rs b/third_party/rust/neqo-http3/src/stream_type_reader.rs new file mode 100644 index 0000000000..f36181d3b1 --- /dev/null +++ b/third_party/rust/neqo-http3/src/stream_type_reader.rs @@ -0,0 +1,687 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use neqo_common::{qtrace, Decoder, IncrementalDecoderUint, Role}; +use neqo_qpack::{decoder::QPACK_UNI_STREAM_TYPE_DECODER, encoder::QPACK_UNI_STREAM_TYPE_ENCODER}; +use neqo_transport::{Connection, StreamId, StreamType}; + +use crate::{ + control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL, frames::H3_FRAME_TYPE_HEADERS, CloseType, + Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream, +}; + +pub(crate) const HTTP3_UNI_STREAM_TYPE_PUSH: u64 = 0x1; +pub(crate) const WEBTRANSPORT_UNI_STREAM: u64 = 0x54; +pub(crate) const WEBTRANSPORT_STREAM: u64 = 0x41; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum NewStreamType { + Control, + Decoder, + Encoder, + Push(u64), + WebTransportStream(u64), + Http, + Unknown, +} + +impl NewStreamType { + /// Get the final `NewStreamType` from a stream type. All streams, except Push stream, + /// are identified by the type only. This function will return None for the Push stream + /// because it needs the ID besides the type. + /// + /// # Errors + /// + /// Push streams received by the server are not allowed and this function will return + /// `HttpStreamCreation` error. + fn final_stream_type( + stream_type: u64, + trans_stream_type: StreamType, + role: Role, + ) -> Res<Option<NewStreamType>> { + match (stream_type, trans_stream_type, role) { + (HTTP3_UNI_STREAM_TYPE_CONTROL, StreamType::UniDi, _) => { + Ok(Some(NewStreamType::Control)) + } + (QPACK_UNI_STREAM_TYPE_ENCODER, StreamType::UniDi, _) => { + Ok(Some(NewStreamType::Decoder)) + } + (QPACK_UNI_STREAM_TYPE_DECODER, StreamType::UniDi, _) => { + Ok(Some(NewStreamType::Encoder)) + } + (HTTP3_UNI_STREAM_TYPE_PUSH, StreamType::UniDi, Role::Client) + | (WEBTRANSPORT_UNI_STREAM, StreamType::UniDi, _) + | (WEBTRANSPORT_STREAM, StreamType::BiDi, _) => Ok(None), + (H3_FRAME_TYPE_HEADERS, StreamType::BiDi, Role::Server) => { + Ok(Some(NewStreamType::Http)) + } + (_, StreamType::BiDi, Role::Server) => Err(Error::HttpFrame), + (HTTP3_UNI_STREAM_TYPE_PUSH, StreamType::UniDi, Role::Server) + | (_, StreamType::BiDi, Role::Client) => Err(Error::HttpStreamCreation), + _ => Ok(Some(NewStreamType::Unknown)), + } + } +} + +/// `NewStreamHeadReader` reads the head of an unidirectional stream to identify the stream. +/// There are 2 type of streams: +/// - streams identified by the single type (varint encoded). Most streams belong to this category. +/// The `NewStreamHeadReader` will switch from `ReadType`to `Done` state. +/// - streams identified by the type and the ID (both varint encoded). For example, a push stream +/// is identified by the type and `PushId`. After reading the type in the `ReadType` state, +/// `NewStreamHeadReader` changes to `ReadId` state and from there to `Done` state +#[derive(Debug)] +pub(crate) enum NewStreamHeadReader { + ReadType { + role: Role, + reader: IncrementalDecoderUint, + stream_id: StreamId, + }, + ReadId { + stream_type: u64, + reader: IncrementalDecoderUint, + stream_id: StreamId, + }, + Done, +} + +impl NewStreamHeadReader { + pub fn new(stream_id: StreamId, role: Role) -> Self { + NewStreamHeadReader::ReadType { + role, + reader: IncrementalDecoderUint::default(), + stream_id, + } + } + + fn read(&mut self, conn: &mut Connection) -> Res<(Option<u64>, bool)> { + if let NewStreamHeadReader::ReadType { + reader, stream_id, .. + } + | NewStreamHeadReader::ReadId { + reader, stream_id, .. + } = self + { + loop { + let to_read = reader.min_remaining(); + let mut buf = vec![0; to_read]; + match conn.stream_recv(*stream_id, &mut buf[..])? { + (0, f) => return Ok((None, f)), + (amount, f) => { + let res = reader.consume(&mut Decoder::from(&buf[..amount])); + if res.is_some() || f { + return Ok((res, f)); + } + } + } + } + } else { + Ok((None, false)) + } + } + + pub fn get_type(&mut self, conn: &mut Connection) -> Res<Option<NewStreamType>> { + loop { + let (output, fin) = self.read(conn)?; + let Some(output) = output else { + if fin { + *self = NewStreamHeadReader::Done; + return Err(Error::HttpStreamCreation); + } + return Ok(None); + }; + + qtrace!("Decoded uint {}", output); + match self { + NewStreamHeadReader::ReadType { + role, stream_id, .. + } => { + // final_stream_type may return: + // - an error if a stream type is not allowed for the role, e.g. Push stream + // received at the server. + // - a final type if a stream is only identify by the type + // - None - if a stream is not identified by the type only, but it needs + // additional data from the header to produce the final type, e.g. a push + // stream needs pushId as well. + let final_type = + NewStreamType::final_stream_type(output, stream_id.stream_type(), *role); + match (&final_type, fin) { + (Err(_), _) => { + *self = NewStreamHeadReader::Done; + return final_type; + } + (Ok(t), true) => { + *self = NewStreamHeadReader::Done; + return Self::map_stream_fin(*t); + } + (Ok(Some(t)), false) => { + qtrace!("Decoded stream type {:?}", *t); + *self = NewStreamHeadReader::Done; + return final_type; + } + (Ok(None), false) => { + // This is a push stream and it needs more data to be decoded. + *self = NewStreamHeadReader::ReadId { + reader: IncrementalDecoderUint::default(), + stream_id: *stream_id, + stream_type: output, + } + } + } + } + NewStreamHeadReader::ReadId { stream_type, .. } => { + let is_push = *stream_type == HTTP3_UNI_STREAM_TYPE_PUSH; + *self = NewStreamHeadReader::Done; + qtrace!("New Stream stream push_id={}", output); + if fin { + return Err(Error::HttpGeneralProtocol); + } + return if is_push { + Ok(Some(NewStreamType::Push(output))) + } else { + Ok(Some(NewStreamType::WebTransportStream(output))) + }; + } + NewStreamHeadReader::Done => { + unreachable!("Cannot be in state NewStreamHeadReader::Done"); + } + } + } + } + + fn map_stream_fin(decoded: Option<NewStreamType>) -> Res<Option<NewStreamType>> { + match decoded { + Some(NewStreamType::Control | NewStreamType::Encoder | NewStreamType::Decoder) => { + Err(Error::HttpClosedCriticalStream) + } + None => Err(Error::HttpStreamCreation), + Some(NewStreamType::Http) => Err(Error::HttpFrame), + Some(NewStreamType::Unknown) => Ok(decoded), + Some(NewStreamType::Push(_) | NewStreamType::WebTransportStream(_)) => { + unreachable!("PushStream and WebTransport are mapped to None at this stage.") + } + } + } + + fn done(&self) -> bool { + matches!(self, NewStreamHeadReader::Done) + } +} + +impl Stream for NewStreamHeadReader { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::NewStream + } +} + +impl RecvStream for NewStreamHeadReader { + fn reset(&mut self, _close_type: CloseType) -> Res<()> { + *self = NewStreamHeadReader::Done; + Ok(()) + } + + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + Ok(( + self.get_type(conn)? + .map_or(ReceiveOutput::NoOutput, ReceiveOutput::NewStream), + self.done(), + )) + } +} + +#[cfg(test)] +mod tests { + use std::mem; + + use neqo_common::{Encoder, Role}; + use neqo_qpack::{ + decoder::QPACK_UNI_STREAM_TYPE_DECODER, encoder::QPACK_UNI_STREAM_TYPE_ENCODER, + }; + use neqo_transport::{Connection, StreamId, StreamType}; + use test_fixture::{connect, now}; + + use super::{ + NewStreamHeadReader, HTTP3_UNI_STREAM_TYPE_PUSH, WEBTRANSPORT_STREAM, + WEBTRANSPORT_UNI_STREAM, + }; + use crate::{ + control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL, frames::H3_FRAME_TYPE_HEADERS, + CloseType, Error, NewStreamType, ReceiveOutput, RecvStream, Res, + }; + + struct Test { + conn_c: Connection, + conn_s: Connection, + stream_id: StreamId, + decoder: NewStreamHeadReader, + } + + impl Test { + fn new(stream_type: StreamType, role: Role) -> Self { + let (mut conn_c, mut conn_s) = connect(); + // create a stream + let stream_id = conn_s.stream_create(stream_type).unwrap(); + let out = conn_s.process(None, now()); + mem::drop(conn_c.process(out.as_dgram_ref(), now())); + + Self { + conn_c, + conn_s, + stream_id, + decoder: NewStreamHeadReader::new(stream_id, role), + } + } + + fn decode_buffer( + &mut self, + enc: &[u8], + fin: bool, + outcome: &Res<(ReceiveOutput, bool)>, + done: bool, + ) { + let len = enc.len() - 1; + for i in 0..len { + self.conn_s + .stream_send(self.stream_id, &enc[i..=i]) + .unwrap(); + let out = self.conn_s.process(None, now()); + mem::drop(self.conn_c.process(out.as_dgram_ref(), now())); + assert_eq!( + self.decoder.receive(&mut self.conn_c).unwrap(), + (ReceiveOutput::NoOutput, false) + ); + assert!(!self.decoder.done()); + } + self.conn_s + .stream_send(self.stream_id, &enc[enc.len() - 1..]) + .unwrap(); + if fin { + self.conn_s.stream_close_send(self.stream_id).unwrap(); + } + let out = self.conn_s.process(None, now()); + mem::drop(self.conn_c.process(out.dgram().as_ref(), now())); + assert_eq!(&self.decoder.receive(&mut self.conn_c), outcome); + assert_eq!(self.decoder.done(), done); + } + + fn decode( + &mut self, + to_encode: &[u64], + fin: bool, + outcome: &Res<(ReceiveOutput, bool)>, + done: bool, + ) { + let mut enc = Encoder::default(); + for i in to_encode { + enc.encode_varint(*i); + } + self.decode_buffer(enc.as_ref(), fin, outcome, done); + } + } + + #[test] + fn decode_stream_decoder() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[QPACK_UNI_STREAM_TYPE_DECODER], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Encoder), true)), + true, + ); + } + + #[test] + fn decode_stream_encoder() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[QPACK_UNI_STREAM_TYPE_ENCODER], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Decoder), true)), + true, + ); + } + + #[test] + fn decode_stream_control() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[HTTP3_UNI_STREAM_TYPE_CONTROL], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Control), true)), + true, + ); + } + + #[test] + fn decode_stream_push() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[HTTP3_UNI_STREAM_TYPE_PUSH, 0xaaaa_aaaa], + false, + &Ok(( + ReceiveOutput::NewStream(NewStreamType::Push(0xaaaa_aaaa)), + true, + )), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Server); + t.decode( + &[HTTP3_UNI_STREAM_TYPE_PUSH], + false, + &Err(Error::HttpStreamCreation), + true, + ); + } + + #[test] + fn decode_stream_unknown() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[0x3fff_ffff_ffff_ffff], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Unknown), true)), + true, + ); + } + + #[test] + fn decode_stream_http() { + let mut t = Test::new(StreamType::BiDi, Role::Server); + t.decode( + &[H3_FRAME_TYPE_HEADERS], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Http), true)), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Server); + t.decode( + &[H3_FRAME_TYPE_HEADERS], /* this is the same as a HTTP3_UNI_STREAM_TYPE_PUSH which + * is not aallowed on the server side. */ + false, + &Err(Error::HttpStreamCreation), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Client); + t.decode( + &[H3_FRAME_TYPE_HEADERS], + false, + &Err(Error::HttpStreamCreation), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[H3_FRAME_TYPE_HEADERS, 0xaaaa_aaaa], /* this is the same as a + * HTTP3_UNI_STREAM_TYPE_PUSH */ + false, + &Ok(( + ReceiveOutput::NewStream(NewStreamType::Push(0xaaaa_aaaa)), + true, + )), + true, + ); + } + + #[test] + fn decode_stream_wt_bidi() { + let mut t = Test::new(StreamType::BiDi, Role::Server); + t.decode( + &[WEBTRANSPORT_STREAM, 0xaaaa_aaaa], + false, + &Ok(( + ReceiveOutput::NewStream(NewStreamType::WebTransportStream(0xaaaa_aaaa)), + true, + )), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Server); + t.decode( + &[WEBTRANSPORT_STREAM], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Unknown), true)), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Client); + t.decode( + &[WEBTRANSPORT_STREAM, 0xaaaa_aaaa], + false, + &Ok(( + ReceiveOutput::NewStream(NewStreamType::WebTransportStream(0xaaaa_aaaa)), + true, + )), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[WEBTRANSPORT_STREAM], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Unknown), true)), + true, + ); + } + + #[test] + fn decode_stream_wt_unidi() { + let mut t = Test::new(StreamType::UniDi, Role::Server); + t.decode( + &[WEBTRANSPORT_UNI_STREAM, 0xaaaa_aaaa], + false, + &Ok(( + ReceiveOutput::NewStream(NewStreamType::WebTransportStream(0xaaaa_aaaa)), + true, + )), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Server); + t.decode( + &[WEBTRANSPORT_UNI_STREAM], + false, + &Err(Error::HttpFrame), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[WEBTRANSPORT_UNI_STREAM, 0xaaaa_aaaa], + false, + &Ok(( + ReceiveOutput::NewStream(NewStreamType::WebTransportStream(0xaaaa_aaaa)), + true, + )), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Client); + t.decode( + &[WEBTRANSPORT_UNI_STREAM], + false, + &Err(Error::HttpStreamCreation), + true, + ); + } + + #[test] + fn done_decoding() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[0x3fff], + false, + &Ok((ReceiveOutput::NewStream(NewStreamType::Unknown), true)), + true, + ); + // NewStreamHeadReader is done, it will not continue reading from the stream. + t.decode( + &[QPACK_UNI_STREAM_TYPE_DECODER], + false, + &Ok((ReceiveOutput::NoOutput, true)), + true, + ); + } + + #[test] + fn decoding_truncate() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode_buffer(&[0xff], false, &Ok((ReceiveOutput::NoOutput, false)), false); + } + + #[test] + fn reset() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decoder.reset(CloseType::ResetRemote(0x100)).unwrap(); + // after a reset NewStreamHeadReader will not read more data. + t.decode( + &[QPACK_UNI_STREAM_TYPE_DECODER], + false, + &Ok((ReceiveOutput::NoOutput, true)), + true, + ); + } + + #[test] + fn stream_fin_decoder() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[QPACK_UNI_STREAM_TYPE_DECODER], + true, + &Err(Error::HttpClosedCriticalStream), + true, + ); + } + + #[test] + fn stream_fin_encoder() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[QPACK_UNI_STREAM_TYPE_ENCODER], + true, + &Err(Error::HttpClosedCriticalStream), + true, + ); + } + + #[test] + fn stream_fin_control() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[HTTP3_UNI_STREAM_TYPE_CONTROL], + true, + &Err(Error::HttpClosedCriticalStream), + true, + ); + } + + #[test] + fn stream_fin_push() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[HTTP3_UNI_STREAM_TYPE_PUSH, 0xaaaa_aaaa], + true, + &Err(Error::HttpGeneralProtocol), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[HTTP3_UNI_STREAM_TYPE_PUSH], + true, + &Err(Error::HttpStreamCreation), + true, + ); + } + + #[test] + fn stream_fin_wt() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[WEBTRANSPORT_UNI_STREAM, 0xaaaa_aaaa], + true, + &Err(Error::HttpGeneralProtocol), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[WEBTRANSPORT_UNI_STREAM], + true, + &Err(Error::HttpStreamCreation), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Server); + t.decode( + &[WEBTRANSPORT_UNI_STREAM, 0xaaaa_aaaa], + true, + &Err(Error::HttpGeneralProtocol), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Server); + t.decode( + &[WEBTRANSPORT_UNI_STREAM], + true, + &Err(Error::HttpStreamCreation), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Client); + t.decode( + &[WEBTRANSPORT_STREAM, 0xaaaa_aaaa], + true, + &Err(Error::HttpGeneralProtocol), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Client); + t.decode( + &[WEBTRANSPORT_STREAM], + true, + &Err(Error::HttpStreamCreation), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Server); + t.decode( + &[WEBTRANSPORT_STREAM, 0xaaaa_aaaa], + true, + &Err(Error::HttpGeneralProtocol), + true, + ); + + let mut t = Test::new(StreamType::BiDi, Role::Server); + t.decode( + &[WEBTRANSPORT_STREAM], + true, + &Err(Error::HttpStreamCreation), + true, + ); + } + + #[test] + fn stream_fin_uknown() { + let mut t = Test::new(StreamType::UniDi, Role::Client); + t.decode( + &[0x3fff_ffff_ffff_ffff], + true, + &Ok((ReceiveOutput::NewStream(NewStreamType::Unknown), true)), + true, + ); + + let mut t = Test::new(StreamType::UniDi, Role::Client); + // A stream ID of 0x3fff_ffff_ffff_ffff is encoded into [0xff; 8]. + // For this test the stream type is truncated. + // This should cause an error. + t.decode_buffer(&[0xff; 7], true, &Err(Error::HttpStreamCreation), true); + } +} diff --git a/third_party/rust/neqo-http3/tests/httpconn.rs b/third_party/rust/neqo-http3/tests/httpconn.rs new file mode 100644 index 0000000000..a0b2bcdb80 --- /dev/null +++ b/third_party/rust/neqo-http3/tests/httpconn.rs @@ -0,0 +1,463 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(unused_assignments)] + +use std::{ + mem, + time::{Duration, Instant}, +}; + +use neqo_common::{event::Provider, qtrace, Datagram}; +use neqo_crypto::{AuthenticationStatus, ResumptionToken}; +use neqo_http3::{ + Header, Http3Client, Http3ClientEvent, Http3OrWebTransportStream, Http3Parameters, Http3Server, + Http3ServerEvent, Http3State, Priority, +}; +use neqo_transport::{ConnectionError, ConnectionParameters, Error, Output, StreamType}; +use test_fixture::*; + +const RESPONSE_DATA: &[u8] = &[0x61, 0x62, 0x63]; + +fn receive_request(server: &mut Http3Server) -> Option<Http3OrWebTransportStream> { + while let Some(event) = server.next_event() { + if let Http3ServerEvent::Headers { + stream, + headers, + fin, + } = event + { + assert_eq!( + &headers, + &[ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/") + ] + ); + assert!(fin); + + return Some(stream); + } + } + None +} + +fn set_response(request: &mut Http3OrWebTransportStream) { + request + .send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]) + .unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + request.stream_close_send().unwrap(); +} + +fn process_server_events(server: &mut Http3Server) { + let mut request = receive_request(server).unwrap(); + set_response(&mut request); +} + +fn process_client_events(conn: &mut Http3Client) { + let mut response_header_found = false; + let mut response_data_found = false; + while let Some(event) = conn.next_event() { + match event { + Http3ClientEvent::HeaderReady { headers, fin, .. } => { + assert_eq!( + &headers, + &[ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ] + ); + assert!(!fin); + response_header_found = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + let mut buf = [0u8; 100]; + let (amount, fin) = conn.read_data(now(), stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, RESPONSE_DATA.len()); + assert_eq!(&buf[..RESPONSE_DATA.len()], RESPONSE_DATA); + response_data_found = true; + } + _ => {} + } + } + assert!(response_header_found); + assert!(response_data_found); +} + +fn connect_peers(hconn_c: &mut Http3Client, hconn_s: &mut Http3Server) -> Option<Datagram> { + assert_eq!(hconn_c.state(), Http3State::Initializing); + let out = hconn_c.process(None, now()); // Initial + let out = hconn_s.process(out.as_dgram_ref(), now()); // Initial + Handshake + let out = hconn_c.process(out.as_dgram_ref(), now()); // ACK + mem::drop(hconn_s.process(out.as_dgram_ref(), now())); // consume ACK + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(hconn_c.events().any(authentication_needed)); + hconn_c.authenticated(AuthenticationStatus::Ok, now()); + let out = hconn_c.process(None, now()); // Handshake + assert_eq!(hconn_c.state(), Http3State::Connected); + let out = hconn_s.process(out.as_dgram_ref(), now()); // Handshake + let out = hconn_c.process(out.as_dgram_ref(), now()); + let out = hconn_s.process(out.as_dgram_ref(), now()); + // assert!(hconn_s.settings_received); + let out = hconn_c.process(out.as_dgram_ref(), now()); + // assert!(hconn_c.settings_received); + + out.dgram() +} + +fn connect_peers_with_network_propagation_delay( + hconn_c: &mut Http3Client, + hconn_s: &mut Http3Server, + net_delay: u64, +) -> (Option<Datagram>, Instant) { + let net_delay = Duration::from_millis(net_delay); + assert_eq!(hconn_c.state(), Http3State::Initializing); + let mut now = now(); + let out = hconn_c.process(None, now); // Initial + now += net_delay; + let out = hconn_s.process(out.as_dgram_ref(), now); // Initial + Handshake + now += net_delay; + let out = hconn_c.process(out.as_dgram_ref(), now); // ACK + now += net_delay; + let out = hconn_s.process(out.as_dgram_ref(), now); // consume ACK + assert!(out.dgram().is_none()); + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(hconn_c.events().any(authentication_needed)); + now += net_delay; + hconn_c.authenticated(AuthenticationStatus::Ok, now); + let out = hconn_c.process(None, now); // Handshake + assert_eq!(hconn_c.state(), Http3State::Connected); + now += net_delay; + let out = hconn_s.process(out.as_dgram_ref(), now); // HANDSHAKE_DONE + now += net_delay; + let out = hconn_c.process(out.as_dgram_ref(), now); // Consume HANDSHAKE_DONE, send control streams. + now += net_delay; + let out = hconn_s.process(out.as_dgram_ref(), now); // consume and send control streams. + now += net_delay; + let out = hconn_c.process(out.as_dgram_ref(), now); // consume control streams. + (out.dgram(), now) +} + +fn connect() -> (Http3Client, Http3Server, Option<Datagram>) { + let mut hconn_c = default_http3_client(); + let mut hconn_s = default_http3_server(); + + let out = connect_peers(&mut hconn_c, &mut hconn_s); + (hconn_c, hconn_s, out) +} + +fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server, out_ex: Option<Datagram>) { + let mut out = out_ex; + loop { + out = client.process(out.as_ref(), now()).dgram(); + out = server.process(out.as_ref(), now()).dgram(); + if out.is_none() { + break; + } + } +} + +#[test] +fn test_connect() { + let (_hconn_c, _hconn_s, _d) = connect(); +} + +#[test] +fn test_fetch() { + let (mut hconn_c, mut hconn_s, dgram) = connect(); + + qtrace!("-----client"); + let req = hconn_c + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + assert_eq!(req, 0); + hconn_c.stream_close_send(req).unwrap(); + let out = hconn_c.process(dgram.as_ref(), now()); + qtrace!("-----server"); + let out = hconn_s.process(out.as_dgram_ref(), now()); + mem::drop(hconn_c.process(out.as_dgram_ref(), now())); + process_server_events(&mut hconn_s); + let out = hconn_s.process(None, now()); + + qtrace!("-----client"); + mem::drop(hconn_c.process(out.as_dgram_ref(), now())); + let out = hconn_s.process(None, now()); + mem::drop(hconn_c.process(out.as_dgram_ref(), now())); + process_client_events(&mut hconn_c); +} + +#[test] +fn test_103_response() { + let (mut hconn_c, mut hconn_s, dgram) = connect(); + + let req = hconn_c + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + assert_eq!(req, 0); + hconn_c.stream_close_send(req).unwrap(); + let out = hconn_c.process(dgram.as_ref(), now()); + + let out = hconn_s.process(out.as_dgram_ref(), now()); + mem::drop(hconn_c.process(out.as_dgram_ref(), now())); + let mut request = receive_request(&mut hconn_s).unwrap(); + + let info_headers = [ + Header::new(":status", "103"), + Header::new("link", "</style.css>; rel=preload; as=style"), + ]; + // Send 103 + request.send_headers(&info_headers).unwrap(); + let out = hconn_s.process(None, now()); + + mem::drop(hconn_c.process(out.as_dgram_ref(), now())); + + let info_headers_event = |e| { + matches!(e, Http3ClientEvent::HeaderReady { headers, + interim, + fin, .. } if !fin && interim && headers.as_ref() == info_headers) + }; + assert!(hconn_c.events().any(info_headers_event)); + + set_response(&mut request); + let out = hconn_s.process(None, now()); + mem::drop(hconn_c.process(out.as_dgram_ref(), now())); + process_client_events(&mut hconn_c); +} + +#[test] +fn test_data_writable_events() { + const STREAM_LIMIT: u64 = 5000; + const DATA_AMOUNT: usize = 10000; + + let mut hconn_c = http3_client_with_params(Http3Parameters::default().connection_parameters( + ConnectionParameters::default().max_stream_data(StreamType::BiDi, false, STREAM_LIMIT), + )); + let mut hconn_s = default_http3_server(); + + mem::drop(connect_peers(&mut hconn_c, &mut hconn_s)); + + // Create a request. + let req = hconn_c + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + hconn_c.stream_close_send(req).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s, None); + + let mut request = receive_request(&mut hconn_s).unwrap(); + + request + .send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", DATA_AMOUNT.to_string()), + ]) + .unwrap(); + + // Send a lot of data + let buf = &[1; DATA_AMOUNT]; + let mut sent = request.send_data(buf).unwrap(); + assert!(sent < DATA_AMOUNT); + + // Exchange packets and read the data on the client side. + exchange_packets(&mut hconn_c, &mut hconn_s, None); + let stream_id = request.stream_id(); + let mut recv_buf = [0_u8; DATA_AMOUNT]; + let (mut recvd, _) = hconn_c.read_data(now(), stream_id, &mut recv_buf).unwrap(); + assert_eq!(sent, recvd); + exchange_packets(&mut hconn_c, &mut hconn_s, None); + + let data_writable = |e| { + matches!( + e, + Http3ServerEvent::DataWritable { + stream + } if stream.stream_id() == stream_id + ) + }; + // Make sure we have a DataWritable event. + assert!(hconn_s.events().any(data_writable)); + // Data can be sent again. + let s = request.send_data(&buf[sent..]).unwrap(); + assert!(s > 0); + sent += s; + + // Exchange packets and read the data on the client side. + exchange_packets(&mut hconn_c, &mut hconn_s, None); + let (r, _) = hconn_c + .read_data(now(), stream_id, &mut recv_buf[recvd..]) + .unwrap(); + recvd += r; + exchange_packets(&mut hconn_c, &mut hconn_s, None); + assert_eq!(sent, recvd); + + // One more DataWritable event. + assert!(hconn_s.events().any(data_writable)); + // Send more data. + let s = request.send_data(&buf[sent..]).unwrap(); + assert!(s > 0); + sent += s; + assert_eq!(sent, DATA_AMOUNT); + + exchange_packets(&mut hconn_c, &mut hconn_s, None); + let (r, _) = hconn_c + .read_data(now(), stream_id, &mut recv_buf[recvd..]) + .unwrap(); + recvd += r; + + // Make sure all data is received by the client. + assert_eq!(recvd, DATA_AMOUNT); + assert_eq!(&recv_buf, buf); +} + +fn get_token(client: &mut Http3Client) -> ResumptionToken { + assert_eq!(client.state(), Http3State::Connected); + client + .events() + .find_map(|e| { + if let Http3ClientEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }) + .unwrap() +} + +#[test] +fn zerortt() { + let (mut hconn_c, _, dgram) = connect(); + let token = get_token(&mut hconn_c); + + // Create a new connection with a resumption token. + let mut hconn_c = default_http3_client(); + hconn_c + .enable_resumption(now(), &token) + .expect("Set resumption token."); + let mut hconn_s = default_http3_server(); + + // Create a request. + let req = hconn_c + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + hconn_c.stream_close_send(req).unwrap(); + + let out = hconn_c.process(dgram.as_ref(), now()); + let out = hconn_s.process(out.as_dgram_ref(), now()); + + let mut request_stream = None; + let mut zerortt_state_change = false; + while let Some(event) = hconn_s.next_event() { + match event { + Http3ServerEvent::Headers { + stream, + headers, + fin, + } => { + assert_eq!( + &headers, + &[ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/") + ] + ); + assert!(fin); + + request_stream = Some(stream); + } + Http3ServerEvent::StateChange { state, .. } => { + assert_eq!(state, Http3State::ZeroRtt); + zerortt_state_change = true; + } + _ => {} + } + } + assert!(zerortt_state_change); + let mut request_stream = request_stream.unwrap(); + + // Send a response + set_response(&mut request_stream); + + // Receive the response + exchange_packets(&mut hconn_c, &mut hconn_s, out.dgram()); + process_client_events(&mut hconn_c); +} + +#[test] +/// When a client has an outstanding fetch, it will send keepalives. +/// Test that it will successfully run until the connection times out. +fn fetch_noresponse_will_idletimeout() { + let mut hconn_c = default_http3_client(); + let mut hconn_s = default_http3_server(); + + let (dgram, mut now) = + connect_peers_with_network_propagation_delay(&mut hconn_c, &mut hconn_s, 10); + + qtrace!("-----client"); + let req = hconn_c + .fetch( + now, + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + assert_eq!(req, 0); + hconn_c.stream_close_send(req).unwrap(); + let _out = hconn_c.process(dgram.as_ref(), now); + qtrace!("-----server"); + + let mut done = false; + while !done { + while let Some(event) = hconn_c.next_event() { + if let Http3ClientEvent::StateChange(state) = event { + match state { + Http3State::Closing(error_code) | Http3State::Closed(error_code) => { + assert_eq!(error_code, ConnectionError::Transport(Error::IdleTimeout)); + done = true; + } + _ => {} + } + } + } + + if let Output::Callback(t) = hconn_c.process_output(now) { + now += t; + } + } +} diff --git a/third_party/rust/neqo-http3/tests/priority.rs b/third_party/rust/neqo-http3/tests/priority.rs new file mode 100644 index 0000000000..cdec161058 --- /dev/null +++ b/third_party/rust/neqo-http3/tests/priority.rs @@ -0,0 +1,148 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::time::Instant; + +use neqo_common::event::Provider; +use neqo_crypto::AuthenticationStatus; +use neqo_http3::{ + Header, Http3Client, Http3ClientEvent, Http3Server, Http3ServerEvent, Http3State, Priority, +}; +use test_fixture::*; + +fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) { + let mut out = None; + loop { + out = client.process(out.as_ref(), now()).dgram(); + let client_done = out.is_none(); + out = server.process(out.as_ref(), now()).dgram(); + if out.is_none() && client_done { + break; + } + } +} + +// Perform only Quic transport handshake. +fn connect_with(client: &mut Http3Client, server: &mut Http3Server) { + assert_eq!(client.state(), Http3State::Initializing); + let out = client.process(None, now()); + assert_eq!(client.state(), Http3State::Initializing); + + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + assert!(out.as_dgram_ref().is_none()); + + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(client.events().any(authentication_needed)); + client.authenticated(AuthenticationStatus::Ok, now()); + + let out = client.process(out.as_dgram_ref(), now()); + let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected)); + assert!(client.events().any(connected)); + + assert_eq!(client.state(), Http3State::Connected); + // Exchange H3 setttings + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + _ = server.process(out.as_dgram_ref(), now()); +} + +fn connect() -> (Http3Client, Http3Server) { + let mut client = default_http3_client(); + let mut server = default_http3_server(); + connect_with(&mut client, &mut server); + (client, server) +} + +#[test] +fn priority_update() { + let (mut client, mut server) = connect(); + let stream_id = client + .fetch( + Instant::now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::new(4, true), + ) + .unwrap(); + exchange_packets(&mut client, &mut server); + + // get event of the above request, skipping events of the connection setup + let header_event = loop { + let event = server.next_event().unwrap(); + if matches!(event, Http3ServerEvent::Headers { .. }) { + break event; + } + }; + + match header_event { + Http3ServerEvent::Headers { + stream: _, + headers, + fin, + } => { + let expected_headers = &[ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/"), + Header::new("priority", "u=4,i"), + ]; + assert_eq!(&headers, expected_headers); + assert!(!fin); + } + other => panic!("unexpected server event: {:?}", other), + } + + let update_priority = Priority::new(3, false); + client.priority_update(stream_id, update_priority).unwrap(); + exchange_packets(&mut client, &mut server); + + let found = server.events().any(|e| { + if let Http3ServerEvent::PriorityUpdate { + stream_id: update_id, + priority, + } = e + { + assert_eq!(update_id, stream_id); + assert_eq!(priority, update_priority); + true + } else { + false + } + }); + assert!(found); +} + +#[test] +fn priority_update_dont_send_for_cancelled_stream() { + let (mut client, mut server) = connect(); + let stream_id = client + .fetch( + Instant::now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::new(5, false), + ) + .unwrap(); + exchange_packets(&mut client, &mut server); + + let update_priority = Priority::new(6, false); + client.priority_update(stream_id, update_priority).unwrap(); + client.cancel_fetch(stream_id, 11).unwrap(); + exchange_packets(&mut client, &mut server); + + while let Some(event) = server.next_event() { + if matches!(event, Http3ServerEvent::PriorityUpdate { .. }) { + panic!("Priority update sent on cancelled stream"); + } + } +} diff --git a/third_party/rust/neqo-http3/tests/send_message.rs b/third_party/rust/neqo-http3/tests/send_message.rs new file mode 100644 index 0000000000..507c4bd552 --- /dev/null +++ b/third_party/rust/neqo-http3/tests/send_message.rs @@ -0,0 +1,329 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use lazy_static::lazy_static; +use neqo_common::event::Provider; +use neqo_crypto::AuthenticationStatus; +use neqo_http3::{ + Error, Header, Http3Client, Http3ClientEvent, Http3OrWebTransportStream, Http3Server, + Http3ServerEvent, Priority, +}; +use test_fixture::*; + +const RESPONSE_DATA: &[u8] = &[0x61, 0x62, 0x63]; + +lazy_static! { + static ref RESPONSE_HEADER_NO_DATA: Vec<Header> = + vec![Header::new(":status", "200"), Header::new("something", "3")]; +} + +lazy_static! { + static ref RESPONSE_HEADER_103: Vec<Header> = + vec![Header::new(":status", "103"), Header::new("link", "...")]; +} + +fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) { + let mut out = None; + loop { + out = client.process(out.as_ref(), now()).dgram(); + out = server.process(out.as_ref(), now()).dgram(); + if out.is_none() { + break; + } + } +} + +fn receive_request(server: &mut Http3Server) -> Option<Http3OrWebTransportStream> { + while let Some(event) = server.next_event() { + if let Http3ServerEvent::Headers { + stream, + headers, + fin, + } = event + { + assert_eq!( + &headers, + &[ + Header::new(":method", "GET"), + Header::new(":scheme", "https"), + Header::new(":authority", "something.com"), + Header::new(":path", "/") + ] + ); + assert!(fin); + return Some(stream); + } + } + None +} + +fn send_trailers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> { + request.send_headers(&[ + Header::new("something1", "something"), + Header::new("something2", "3"), + ]) +} + +fn send_informational_headers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> { + request.send_headers(&RESPONSE_HEADER_103) +} + +fn send_headers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> { + request.send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]) +} + +fn process_client_events(conn: &mut Http3Client) { + let mut response_header_found = false; + let mut response_data_found = false; + while let Some(event) = conn.next_event() { + match event { + Http3ClientEvent::HeaderReady { headers, fin, .. } => { + assert!( + (headers.as_ref() + == [ + Header::new(":status", "200"), + Header::new("content-length", "3"), + ]) + || (headers.as_ref() == *RESPONSE_HEADER_103) + ); + assert!(!fin); + response_header_found = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + let mut buf = [0u8; 100]; + let (amount, fin) = conn.read_data(now(), stream_id, &mut buf).unwrap(); + assert!(fin); + assert_eq!(amount, RESPONSE_DATA.len()); + assert_eq!(&buf[..RESPONSE_DATA.len()], RESPONSE_DATA); + response_data_found = true; + } + _ => {} + } + } + assert!(response_header_found); + assert!(response_data_found); +} + +fn process_client_events_no_data(conn: &mut Http3Client) { + let mut response_header_found = false; + let mut fin_received = false; + while let Some(event) = conn.next_event() { + match event { + Http3ClientEvent::HeaderReady { headers, fin, .. } => { + assert_eq!(headers.as_ref(), *RESPONSE_HEADER_NO_DATA); + fin_received = fin; + response_header_found = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + let mut buf = [0u8; 100]; + let (amount, fin) = conn.read_data(now(), stream_id, &mut buf).unwrap(); + assert!(fin); + fin_received = true; + assert_eq!(amount, 0); + } + _ => {} + } + } + assert!(response_header_found); + assert!(fin_received); +} + +fn connect_send_and_receive_request() -> (Http3Client, Http3Server, Http3OrWebTransportStream) { + let mut hconn_c = default_http3_client(); + let mut hconn_s = default_http3_server(); + + exchange_packets(&mut hconn_c, &mut hconn_s); + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(hconn_c.events().any(authentication_needed)); + hconn_c.authenticated(AuthenticationStatus::Ok, now()); + exchange_packets(&mut hconn_c, &mut hconn_s); + + let req = hconn_c + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + assert_eq!(req, 0); + hconn_c.stream_close_send(req).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + + let request = receive_request(&mut hconn_s).unwrap(); + + (hconn_c, hconn_s, request) +} + +#[test] +fn response_trailers1() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + send_trailers(&mut request).unwrap(); + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn response_trailers2() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + send_trailers(&mut request).unwrap(); + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn response_trailers3() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + send_trailers(&mut request).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn response_trailers_no_data() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + send_trailers(&mut request).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events_no_data(&mut hconn_c); +} + +#[test] +fn multiple_response_trailers() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + send_trailers(&mut request).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + + assert_eq!(send_trailers(&mut request), Err(Error::InvalidInput)); + + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn data_after_trailer() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + send_trailers(&mut request).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + + assert_eq!(request.send_data(RESPONSE_DATA), Err(Error::InvalidInput)); + + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn trailers_after_close() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + request.stream_close_send().unwrap(); + + assert_eq!(send_trailers(&mut request), Err(Error::InvalidStreamId)); + + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn multiple_response_headers() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap(); + + assert_eq!( + request.send_headers(&RESPONSE_HEADER_NO_DATA), + Err(Error::InvalidHeader) + ); + + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events_no_data(&mut hconn_c); +} + +#[test] +fn informational_after_response_headers() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap(); + + assert_eq!( + send_informational_headers(&mut request), + Err(Error::InvalidHeader) + ); + + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events_no_data(&mut hconn_c); +} + +#[test] +fn data_after_informational() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_informational_headers(&mut request).unwrap(); + + assert_eq!(request.send_data(RESPONSE_DATA), Err(Error::InvalidInput)); + + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn non_trailers_headers_after_data() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + + assert_eq!( + request.send_headers(&RESPONSE_HEADER_NO_DATA), + Err(Error::InvalidHeader) + ); + + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} + +#[test] +fn data_before_headers() { + let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request(); + assert_eq!(request.send_data(RESPONSE_DATA), Err(Error::InvalidInput)); + + send_headers(&mut request).unwrap(); + request.send_data(RESPONSE_DATA).unwrap(); + request.stream_close_send().unwrap(); + exchange_packets(&mut hconn_c, &mut hconn_s); + process_client_events(&mut hconn_c); +} diff --git a/third_party/rust/neqo-http3/tests/webtransport.rs b/third_party/rust/neqo-http3/tests/webtransport.rs new file mode 100644 index 0000000000..4e943d86cb --- /dev/null +++ b/third_party/rust/neqo-http3/tests/webtransport.rs @@ -0,0 +1,320 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, rc::Rc}; + +use neqo_common::{event::Provider, Header}; +use neqo_crypto::AuthenticationStatus; +use neqo_http3::{ + Http3Client, Http3ClientEvent, Http3OrWebTransportStream, Http3Parameters, Http3Server, + Http3ServerEvent, Http3State, WebTransportEvent, WebTransportRequest, WebTransportServerEvent, + WebTransportSessionAcceptAction, +}; +use neqo_transport::{StreamId, StreamType}; +use test_fixture::{ + addr, anti_replay, fixture_init, now, CountingConnectionIdGenerator, DEFAULT_ALPN_H3, + DEFAULT_KEYS, DEFAULT_SERVER_NAME, +}; + +fn connect() -> (Http3Client, Http3Server) { + fixture_init(); + let mut client = Http3Client::new( + DEFAULT_SERVER_NAME, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + Http3Parameters::default().webtransport(true), + now(), + ) + .expect("create a default client"); + let mut server = Http3Server::new( + now(), + DEFAULT_KEYS, + DEFAULT_ALPN_H3, + anti_replay(), + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + Http3Parameters::default().webtransport(true), + None, + ) + .expect("create a server"); + assert_eq!(client.state(), Http3State::Initializing); + let out = client.process(None, now()); + assert_eq!(client.state(), Http3State::Initializing); + + let out = server.process(out.as_dgram_ref(), now()); + let out = client.process(out.as_dgram_ref(), now()); + let out = server.process(out.as_dgram_ref(), now()); + assert!(out.as_dgram_ref().is_none()); + + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(client.events().any(authentication_needed)); + client.authenticated(AuthenticationStatus::Ok, now()); + + let mut out = client.process(out.as_dgram_ref(), now()).dgram(); + let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected)); + assert!(client.events().any(connected)); + + assert_eq!(client.state(), Http3State::Connected); + + // Exchange H3 setttings + loop { + out = server.process(out.as_ref(), now()).dgram(); + let dgram_present = out.is_some(); + out = client.process(out.as_ref(), now()).dgram(); + if out.is_none() && !dgram_present { + break; + } + } + (client, server) +} + +fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) { + let mut out = None; + loop { + out = client.process(out.as_ref(), now()).dgram(); + out = server.process(out.as_ref(), now()).dgram(); + if out.is_none() { + break; + } + } +} + +fn create_wt_session(client: &mut Http3Client, server: &mut Http3Server) -> WebTransportRequest { + let wt_session_id = client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + exchange_packets(client, server); + + let mut wt_server_session = None; + while let Some(event) = server.next_event() { + match event { + Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + mut session, + headers, + }) => { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + session + .response(&WebTransportSessionAcceptAction::Accept) + .unwrap(); + wt_server_session = Some(session); + } + Http3ServerEvent::Data { .. } => { + panic!("There should not be any data events!"); + } + _ => {} + } + } + + exchange_packets(client, server); + + let wt_session_negotiated_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Session{ + stream_id, + status, + headers, + }) if ( + stream_id == wt_session_id && + status == 200 && + headers.contains(&Header::new(":status", "200")) + ) + ) + }; + assert!(client.events().any(wt_session_negotiated_event)); + + let wt_server_session = wt_server_session.unwrap(); + assert_eq!(wt_session_id, wt_server_session.stream_id()); + wt_server_session +} + +fn send_data_client( + client: &mut Http3Client, + server: &mut Http3Server, + wt_stream_id: StreamId, + data: &[u8], +) { + assert_eq!(client.send_data(wt_stream_id, data).unwrap(), data.len()); + exchange_packets(client, server); +} + +fn send_data_server( + client: &mut Http3Client, + server: &mut Http3Server, + wt_stream: &mut Http3OrWebTransportStream, + data: &[u8], +) { + assert_eq!(wt_stream.send_data(data).unwrap(), data.len()); + exchange_packets(client, server); +} + +fn receive_data_client( + client: &mut Http3Client, + expected_stream_id: StreamId, + new_stream: bool, + expected_data: &[u8], + expected_fin: bool, +) { + let mut new_stream_received = false; + let mut data_received = false; + while let Some(event) = client.next_event() { + match event { + Http3ClientEvent::WebTransport(WebTransportEvent::NewStream { stream_id, .. }) => { + assert_eq!(stream_id, expected_stream_id); + new_stream_received = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, expected_stream_id); + let mut buf = [0; 100]; + let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap(); + assert_eq!(fin, expected_fin); + assert_eq!(amount, expected_data.len()); + assert_eq!(&buf[..amount], expected_data); + data_received = true; + } + _ => {} + } + } + assert!(data_received); + assert_eq!(new_stream, new_stream_received); +} + +fn receive_data_server( + client: &mut Http3Client, + server: &mut Http3Server, + stream_id: StreamId, + new_stream: bool, + expected_data: &[u8], + expected_fin: bool, +) -> Http3OrWebTransportStream { + exchange_packets(client, server); + let mut new_stream_received = false; + let mut data_received = false; + let mut wt_stream = None; + let mut stream_closed = false; + let mut recv_data = Vec::new(); + while let Some(event) = server.next_event() { + match event { + Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(request)) => { + assert_eq!(stream_id, request.stream_id()); + new_stream_received = true; + } + Http3ServerEvent::Data { + mut data, + fin, + stream, + } => { + recv_data.append(&mut data); + stream_closed = fin; + data_received = true; + wt_stream = Some(stream); + } + _ => {} + } + } + assert_eq!(&recv_data[..], expected_data); + assert!(data_received); + assert_eq!(new_stream, new_stream_received); + assert_eq!(stream_closed, expected_fin); + wt_stream.unwrap() +} + +#[test] +fn wt_client_stream_uni() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let (mut client, mut server) = connect(); + let wt_session = create_wt_session(&mut client, &mut server); + let wt_stream = client + .webtransport_create_stream(wt_session.stream_id(), StreamType::UniDi) + .unwrap(); + send_data_client(&mut client, &mut server, wt_stream, BUF_CLIENT); + exchange_packets(&mut client, &mut server); + receive_data_server(&mut client, &mut server, wt_stream, true, BUF_CLIENT, false); +} + +#[test] +fn wt_client_stream_bidi() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let (mut client, mut server) = connect(); + let wt_session = create_wt_session(&mut client, &mut server); + let wt_client_stream = client + .webtransport_create_stream(wt_session.stream_id(), StreamType::BiDi) + .unwrap(); + send_data_client(&mut client, &mut server, wt_client_stream, BUF_CLIENT); + let mut wt_server_stream = receive_data_server( + &mut client, + &mut server, + wt_client_stream, + true, + BUF_CLIENT, + false, + ); + send_data_server(&mut client, &mut server, &mut wt_server_stream, BUF_SERVER); + receive_data_client(&mut client, wt_client_stream, false, BUF_SERVER, false); +} + +#[test] +fn wt_server_stream_uni() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let (mut client, mut server) = connect(); + let mut wt_session = create_wt_session(&mut client, &mut server); + let mut wt_server_stream = wt_session.create_stream(StreamType::UniDi).unwrap(); + send_data_server(&mut client, &mut server, &mut wt_server_stream, BUF_SERVER); + receive_data_client( + &mut client, + wt_server_stream.stream_id(), + true, + BUF_SERVER, + false, + ); +} + +#[test] +fn wt_server_stream_bidi() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let (mut client, mut server) = connect(); + let mut wt_session = create_wt_session(&mut client, &mut server); + let mut wt_server_stream = wt_session.create_stream(StreamType::BiDi).unwrap(); + send_data_server(&mut client, &mut server, &mut wt_server_stream, BUF_SERVER); + receive_data_client( + &mut client, + wt_server_stream.stream_id(), + true, + BUF_SERVER, + false, + ); + send_data_client( + &mut client, + &mut server, + wt_server_stream.stream_id(), + BUF_CLIENT, + ); + assert_eq!( + receive_data_server( + &mut client, + &mut server, + wt_server_stream.stream_id(), + false, + BUF_CLIENT, + false + ) + .stream_id(), + wt_server_stream.stream_id() + ); +} |