summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-http3
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/neqo-http3
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/neqo-http3')
-rw-r--r--third_party/rust/neqo-http3/.cargo-checksum.json1
-rw-r--r--third_party/rust/neqo-http3/Cargo.toml53
-rw-r--r--third_party/rust/neqo-http3/src/buffered_send_stream.rs113
-rw-r--r--third_party/rust/neqo-http3/src/client_events.rs380
-rw-r--r--third_party/rust/neqo-http3/src/conn_params.rs133
-rw-r--r--third_party/rust/neqo-http3/src/connection.rs1589
-rw-r--r--third_party/rust/neqo-http3/src/connection_client.rs7137
-rw-r--r--third_party/rust/neqo-http3/src/connection_server.rs407
-rw-r--r--third_party/rust/neqo-http3/src/control_stream_local.rs107
-rw-r--r--third_party/rust/neqo-http3/src/control_stream_remote.rs74
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/mod.rs114
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs7
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs142
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs648
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs278
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs450
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs1081
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs539
-rw-r--r--third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs202
-rw-r--r--third_party/rust/neqo-http3/src/features/mod.rs91
-rw-r--r--third_party/rust/neqo-http3/src/frames/hframe.rs226
-rw-r--r--third_party/rust/neqo-http3/src/frames/mod.rs21
-rw-r--r--third_party/rust/neqo-http3/src/frames/reader.rs270
-rw-r--r--third_party/rust/neqo-http3/src/frames/tests/hframe.rs115
-rw-r--r--third_party/rust/neqo-http3/src/frames/tests/mod.rs82
-rw-r--r--third_party/rust/neqo-http3/src/frames/tests/reader.rs517
-rw-r--r--third_party/rust/neqo-http3/src/frames/tests/wtframe.rs17
-rw-r--r--third_party/rust/neqo-http3/src/frames/wtframe.rs61
-rw-r--r--third_party/rust/neqo-http3/src/headers_checks.rs150
-rw-r--r--third_party/rust/neqo-http3/src/lib.rs629
-rw-r--r--third_party/rust/neqo-http3/src/priority.rs205
-rw-r--r--third_party/rust/neqo-http3/src/push_controller.rs509
-rw-r--r--third_party/rust/neqo-http3/src/qlog.rs38
-rw-r--r--third_party/rust/neqo-http3/src/qpack_decoder_receiver.rs44
-rw-r--r--third_party/rust/neqo-http3/src/qpack_encoder_receiver.rs40
-rw-r--r--third_party/rust/neqo-http3/src/recv_message.rs502
-rw-r--r--third_party/rust/neqo-http3/src/request_target.rs127
-rw-r--r--third_party/rust/neqo-http3/src/send_message.rs337
-rw-r--r--third_party/rust/neqo-http3/src/server.rs1317
-rw-r--r--third_party/rust/neqo-http3/src/server_connection_events.rs197
-rw-r--r--third_party/rust/neqo-http3/src/server_events.rs567
-rw-r--r--third_party/rust/neqo-http3/src/settings.rs296
-rw-r--r--third_party/rust/neqo-http3/src/stream_type_reader.rs682
-rw-r--r--third_party/rust/neqo-http3/tests/httpconn.rs460
-rw-r--r--third_party/rust/neqo-http3/tests/priority.rs149
-rw-r--r--third_party/rust/neqo-http3/tests/send_message.rs329
-rw-r--r--third_party/rust/neqo-http3/tests/webtransport.rs320
47 files changed, 21753 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..4e44f45df4
--- /dev/null
+++ b/third_party/rust/neqo-http3/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"023279228126adbabbb485ff778a7b7a4d4cc5e81ab7ed97adaa1bc4410c68b3","src/buffered_send_stream.rs":"4bc45ca03252dc34ab421a2af3499191b182a619143c61d5609a46377c9a0f3d","src/client_events.rs":"5e5195b88c8bbf1612c708d41e6a55811c78f284642c09cc97cf14f1cd08c0c2","src/conn_params.rs":"409f2899475fe6dfecacf191f08ea0905b0477aebacd165424542d40bbd62991","src/connection.rs":"7a27a891be0ef0abce84b0d182abe7c26acf408bb3ae281bf30af8d65208c11f","src/connection_client.rs":"5b287d2b68f567a137e4539b1e0014efb412496cb6f047cfbcf53474c3f3e203","src/connection_server.rs":"365f246a0caa5d310da21587525b4793cd3046d973cce8f3aa62b8a1eac493a7","src/control_stream_local.rs":"b86e1f869ad59bf2663501942a1a65d94c1dbc3e8770982459e0b620be4b6cf0","src/control_stream_remote.rs":"7a261ac7df77e90a428ab0f92457a934a92a8c581462fc1818efd3de0c0ebd69","src/features/extended_connect/mod.rs":"98fd04d65ebb71a327a667ff9071f007aad4015c29af43a8c25f216b76c45276","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"8ba487a3bbd75e45b9b091397d8948e98ed8f5f8bcee033d629712833794747d","src/features/extended_connect/tests/webtransport/mod.rs":"7b17db478824d45d75f572d65902bee9de0df4baf38f298e033aeb1b63058ba7","src/features/extended_connect/tests/webtransport/negotiation.rs":"f870162ddb079028900154051624863cdbfbacfbe152b6424c0f67b5d3998881","src/features/extended_connect/tests/webtransport/sessions.rs":"53a052b52006193d5e4e3f0a74144572617737de9ff3ae00be033a246088d265","src/features/extended_connect/tests/webtransport/streams.rs":"0ef81aef536f01413b13738cf59f69c7f819a75d655de3d6b905f2f3d84f8509","src/features/extended_connect/webtransport_session.rs":"04458c32b6f631b720ae70e08774b4bd7055d566957c64ab1709b8202274f0b5","src/features/extended_connect/webtransport_streams.rs":"9562e5bf321f1338514a70ee89ccd88a15cd4ccf727ea3533b55bb1d5be27f04","src/features/mod.rs":"a981ebbd03e7bb7ea2313e883452e44f052c48f28edb7fd53a0825911b490230","src/frames/hframe.rs":"8206e1a27ad805899f7e722c05dffa92649704bbaf98ff2a70a7ca1d6a55395e","src/frames/mod.rs":"7d0a46ca147336d14781edb8dbee8b03c2e4bcd6646f5473a9d93d31fe73fecb","src/frames/reader.rs":"3d7af10a21833049aa0277caec4abe13d677d2d62526f1c83126f2defe19ee5e","src/frames/tests/hframe.rs":"33a30bb98bb512606a06ae1752e1ed9e4588b7d3f5e9439ec83bb2e779d4ac80","src/frames/tests/mod.rs":"81cac9702e9016dacd60085c2e3968093b356fe682ced33d7c9c1f3151b9201c","src/frames/tests/reader.rs":"312a3deda7b3a4bbd7afed879c94d0644fce8e34435365ef9cae1fbaa62496af","src/frames/tests/wtframe.rs":"589ebe1e62ce4da63b37b7d22cde7ba572ddbf29336fdcdbbcd0a745f79dacd8","src/frames/wtframe.rs":"0eebdf9a275cd53ee6525f7387941601d119d933203d9b2425377adf8348d425","src/headers_checks.rs":"b80c1da2d9f336fa88f7b7f2a834d8e90e826260811771c7729785fdc92b20d4","src/lib.rs":"50ac4975d3b5b5680a1f8d144da16b40f080bbe90e05679fe50d59ad5937d10b","src/priority.rs":"ae0fa461031893b4f7e0d12666072e7a4da80b1e8a1c0663ab9f9e27b3242754","src/push_controller.rs":"aa2a64180d8cb1b87682d0d8bbc42167188e8e1890261cb4cabb76de1fcc708b","src/qlog.rs":"44b6cdbb1d9d6ca47b793e9dbe531b8fdbd40147375f7e4c89aeab536c5d286b","src/qpack_decoder_receiver.rs":"50c5e7679304b227a5f86ab681396d59c5885a8d7a4b72985cce82f3f8eaa129","src/qpack_encoder_receiver.rs":"3deca0555826167bbaf8099eb1b394883e5e5c8c4ee68261f8c4816bdf686eda","src/recv_message.rs":"1f740c59676f5913108de68f41db1dfc1b8d6feeb0467c61ae652d6d26e75682","src/request_target.rs":"9182b641f7a7b55272e0e2e872d24a35b1207f35399221b08b899857c3e873ab","src/send_message.rs":"9e1b22ede2a105a79d7c02178801e1a46b06a80dc1c0d2a7d69b0eea7e89f319","src/server.rs":"c7c0c91cef9b2125722bcc366ee502a8ae113f59da772a8c920b4a9553018b66","src/server_connection_events.rs":"5fc125a13d9da8be5ad2d165206990b55fff890920e723db2314580159afa5d9","src/server_events.rs":"9f685a7bf28018dcdfc773c27f84c8307221a819b2fa27805e5462bbf9aa6c19","src/settings.rs":"a47a7e84dc745ec88d9eccead786ec1a2355b33b30d988d01a2e8a7043168d98","src/stream_type_reader.rs":"983362180f19e47c938b3b81650e71c5bc5a55500ff425b1617d74e2ec9357f5","tests/httpconn.rs":"8b62aa9a24ccc45f436aa57ff7d5b37394d844eced6b204085fc5086b1a643c7","tests/priority.rs":"a606e5fa03451e09e28c7d5f1820ee85a4567e3969a1690c979761e62123bf54","tests/send_message.rs":"673ae1d0bf2dce46c21ee8353f45f189d2cb64a2f6e137ae38da6b2262ad066e","tests/webtransport.rs":"053fbe155cf33c0cce1e5e8f7c7d44e8bf82d69cd419d0c8147e92555bf89575"},"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..4e278e17c4
--- /dev/null
+++ b/third_party/rust/neqo-http3/Cargo.toml
@@ -0,0 +1,53 @@
+# 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.57.0"
+name = "neqo-http3"
+version = "0.6.4"
+authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
+license = "MIT/Apache-2.0"
+
+[dependencies]
+enumset = "1.0.8"
+lazy_static = "1.3.0"
+qlog = "0.4.0"
+sfv = "0.9.1"
+smallvec = "1.0.0"
+url = "2.0"
+
+[dependencies.log]
+version = "0.4.0"
+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"
+
+[dev-dependencies.test-fixture]
+path = "../test-fixture"
+
+[features]
+default = ["deny-warnings"]
+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..2a7d01bb74
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/buffered_send_stream.rs
@@ -0,0 +1,113 @@
+// 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 crate::Res;
+use neqo_common::qtrace;
+use neqo_transport::{Connection, StreamId};
+
+#[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..b4fdde8e13
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/client_events.rs
@@ -0,0 +1,380 @@
+// 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 crate::connection::Http3State;
+use crate::settings::HSettingType;
+use crate::{
+ features::extended_connect::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason},
+ CloseType, Http3StreamInfo, HttpRecvStreamEvents, RecvStreamEvents, SendStreamEvents,
+};
+use neqo_common::{event::Provider as EventProvider, Header};
+use neqo_crypto::ResumptionToken;
+use neqo_transport::{AppError, StreamId, StreamType};
+
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::rc::Rc;
+
+#[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().iter() {
+ 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..1ba2a601ad
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/conn_params.rs
@@ -0,0 +1,133 @@
+// 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_qpack::QpackSettings;
+use neqo_transport::ConnectionParameters;
+use std::cmp::min;
+
+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..bb5b6451c4
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/connection.rs
@@ -0,0 +1,1589 @@
+// 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 crate::control_stream_local::ControlStreamLocal;
+use crate::control_stream_remote::ControlStreamRemote;
+use crate::features::extended_connect::{
+ webtransport_session::WebTransportSession,
+ webtransport_streams::{WebTransportRecvStream, WebTransportSendStream},
+ ExtendedConnectEvents, ExtendedConnectFeature, ExtendedConnectType,
+};
+use crate::frames::HFrame;
+use crate::push_controller::PushController;
+use crate::qpack_decoder_receiver::DecoderRecvStream;
+use crate::qpack_encoder_receiver::EncoderRecvStream;
+use crate::recv_message::{RecvMessage, RecvMessageInfo};
+use crate::request_target::{AsRequestTarget, RequestTarget};
+use crate::send_message::SendMessage;
+use crate::settings::{HSettingType, HSettings, HttpZeroRttChecker};
+use crate::stream_type_reader::NewStreamHeadReader;
+use crate::{
+ client_events::Http3ClientEvents, CloseType, Http3Parameters, Http3StreamType,
+ HttpRecvStreamEvents, NewStreamType, Priority, PriorityHandler, ReceiveOutput, RecvStream,
+ RecvStreamEvents, SendStream, SendStreamEvents,
+};
+use neqo_common::{qdebug, qerror, qinfo, qtrace, qwarn, Decoder, Header, MessageType, Role};
+use neqo_qpack::decoder::QPackDecoder;
+use neqo_qpack::encoder::QPackEncoder;
+use neqo_transport::{
+ AppError, Connection, ConnectionError, DatagramTracking, State, StreamId, StreamType,
+ ZeroRttState,
+};
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap};
+use std::fmt::Debug;
+use std::mem;
+use std::rc::Rc;
+
+use crate::{Error, Res};
+
+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 QUIIC 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 reseted 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);
+ }
+ }
+ 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 immediatly 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(())
+ }
+
+ 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))?;
+
+ 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..709a1e0d6b
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/connection_client.rs
@@ -0,0 +1,7137 @@
+// 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 crate::client_events::{Http3ClientEvent, Http3ClientEvents};
+use crate::connection::{Http3Connection, Http3State, RequestDescription};
+use crate::frames::HFrame;
+use crate::push_controller::{PushController, RecvPushEvents};
+use crate::recv_message::{RecvMessage, RecvMessageInfo};
+use crate::request_target::AsRequestTarget;
+use crate::settings::HSettings;
+use crate::{
+ Http3Parameters, Http3StreamType, NewStreamType, Priority, PriorityHandler, ReceiveOutput,
+};
+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::{
+ AppError, Connection, ConnectionEvent, ConnectionId, ConnectionIdGenerator, DatagramTracking,
+ Output, Stats as TransportStats, StreamId, StreamType, Version, ZeroRttState,
+};
+use std::cell::RefCell;
+use std::convert::TryFrom;
+use std::fmt::Debug;
+use std::fmt::Display;
+use std::mem;
+use std::net::SocketAddr;
+use std::rc::Rc;
+use std::time::Instant;
+
+use crate::{Error, 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:
+/// - [`new`](struct.Http3Client.html#method.new)
+/// - [`new_with_conn`](struct.Http3Client.html#method.new_with_conn)
+/// - [`close`](struct.Http3Client.html#method.close)
+/// - configuring an endpoint:
+/// - [`authenticated`](struct.Http3Client.html#method.authenticated)
+/// - [`enable_ech`](struct.Http3Client.html#method.enable_ech)
+/// - [`enable_resumption`](struct.Http3Client.html#method.enable_resumption)
+/// - [`initiate_key_update`](struct.Http3Client.html#method.initiate_key_update)
+/// - [`set_qlog`](struct.Http3Client.html#method.set_qlog)
+/// - retrieving information about a connection:
+/// - [`peer_certificate`](struct.Http3Client.html#method.peer_certificate)
+/// - [`qpack_decoder_stats`](struct.Http3Client.html#method.qpack_decoder_stats)
+/// - [`qpack_encoder_stats`](struct.Http3Client.html#method.qpack_encoder_stats)
+/// - [`transport_stats`](struct.Http3Client.html#method.transport_stats)
+/// - [`state`](struct.Http3Client.html#method.state)
+/// - [`take_resumption_token`](struct.Http3Client.html#method.take_resumption_token)
+/// - [`tls_inf`](struct.Http3Client.html#method.tls_info)
+/// - driving HTTP/3 session:
+/// - [`process_output`](struct.Http3Client.html#method.process_output)
+/// - [`process_input`](struct.Http3Client.html#method.process_input)
+/// - [`process`](struct.Http3Client.html#method.process)
+/// - create requests, send/receive data, and cancel requests:
+/// - [`fetch`](struct.Http3Client.html#method.fetch)
+/// - [`send_data`](struct.Http3Client.html#method.send_data)
+/// - [`read_dara`](struct.Http3Client.html#method.read_data)
+/// - [`stream_close_send`](struct.Http3Client.html#method.stream_close_send)
+/// - [`cancel_fetch`](struct.Http3Client.html#method.cancel_fetch)
+/// - [`stream_reset_send`](struct.Http3Client.html#method.stream_reset_send)
+/// - [`stream_stop_sending`](struct.Http3Client.html#method.stream_stop_sending)
+/// - [`set_stream_max_data`](struct.Http3Client.html#method.set_stream_max_data)
+/// - priority feature:
+/// - [`priority_update`](struct.Http3Client.html#method.priority_update)
+/// - `WebTransport` feature:
+/// - [`webtransport_create_session`](struct.Http3Client.html#method.webtransport_create_session)
+/// - [`webtransport_close_session`](struct.Http3Client.html#method.webtransport_close_session)
+/// - [`webtransport_create_stream`](struct.Http3Client.html#method.webtransport_create_sstream)
+/// - [`webtransport_enabled`](struct.Http3Client.html#method.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.
+ #[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 settings_slice = match dec.decode_vvec() {
+ Some(v) => v,
+ None => 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())
+ }
+
+ /// 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);
+ }
+
+ /// 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();
+ let _ = 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 super::{
+ AuthenticationStatus, Connection, Error, HSettings, Header, Http3Client, Http3ClientEvent,
+ Http3Parameters, Http3State, Rc, RefCell,
+ };
+ use crate::frames::{HFrame, H3_FRAME_TYPE_SETTINGS, H3_RESERVED_FRAME_TYPES};
+ use crate::qpack_encoder_receiver::EncoderRecvStream;
+ use crate::settings::{HSetting, HSettingType, H3_RESERVED_SETTINGS};
+ use crate::{Http3Server, Priority, RecvStream};
+ 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 std::convert::TryFrom;
+ use std::mem;
+ use std::time::Duration;
+ use test_fixture::{
+ addr, anti_replay, default_server_h3, fixture_init, new_server, now,
+ CountingConnectionIdGenerator, DEFAULT_ALPN_H3, DEFAULT_KEYS, DEFAULT_SERVER_NAME,
+ };
+
+ 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.dgram(), now());
+ assert_eq!(*server.conn.state(), State::Handshaking);
+
+ let out = client.process(out.dgram(), now());
+ let out = server.conn.process(out.dgram(), 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.dgram(), 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.dgram().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 dgram = client.process(None, now()).dgram();
+ server.conn.process_input(dgram.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 dgram = server.conn.process(None, now()).dgram();
+ client.process_input(dgram.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 dgram = client.process(None, now()).dgram();
+ server.conn.process_input(dgram.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,
+ ) {
+ let _ = 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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);
+ let _ = 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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
+ let _ = conn.stream_send(push_stream_id, PUSH_STREAM_TYPE).unwrap();
+ let _ = conn.stream_send(push_stream_id, &[push_id]).unwrap();
+ let _ = 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.
+ let _ = server
+ .conn
+ .stream_send(server.control_stream_id.unwrap(), v)
+ .unwrap();
+
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), 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.
+ let _ = server
+ .conn
+ .stream_send(push_stream_id, &[0x01, 0x0])
+ .unwrap();
+ let _ = server.conn.stream_send(push_stream_id, v).unwrap();
+
+ let out = server.conn.process(None, now());
+ let out = client.process(out.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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();
+ let _ = 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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);
+
+ let _ = 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.dgram(), 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.dgram(), 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.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x4]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x6]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x0]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x8]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x0]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), 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.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x5]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x4]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x61]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x62]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x63]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ sent = server.conn.stream_send(control_stream, &[0x64]);
+ assert_eq!(sent, Ok(1));
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), 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();
+ let _ = 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.dgram(), 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.dgram(), 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'.
+ let _ = 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.dgram(), now());
+ out = client.process(out.dgram(), 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'.
+ let _ = 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.dgram(), now());
+ out = client.process(out.dgram(), 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'.
+ let _ = 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), now()));
+
+ let _ = 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];
+ let _ = 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.
+ let _ = 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.dgram(), 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.dgram(), now()));
+
+ // First send a Goaway frame with an higher number
+ let _ = server
+ .conn
+ .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x8])
+ .unwrap();
+
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), 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
+ let _ = 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
+ let _ = server
+ .conn
+ .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x4])
+ .unwrap();
+
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ assert_eq!(client.state(), Http3State::GoingAway(StreamId::new(4)));
+
+ // Now send a Goaway frame with an higher number
+ let _ = server
+ .conn
+ .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x8])
+ .unwrap();
+
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), now());
+
+ assert_closed(&client, &Error::HttpGeneralProtocol);
+ }
+
+ #[test]
+ fn goaway_wrong_stream_id() {
+ let (mut client, mut server) = connect();
+
+ let _ = server
+ .conn
+ .stream_send(server.control_stream_id.unwrap(), &[0x7, 0x1, 0x9])
+ .unwrap();
+
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), 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.dgram(), 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.dgram(), 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.
+ let _ = server
+ .conn
+ .stream_send(request_stream_id, HTTP_RESPONSE_HEADER_ONLY_2)
+ .unwrap();
+ // Send an empty data frame.
+ let _ = 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.dgram(), 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.
+ let _ = server
+ .conn
+ .stream_send(request_stream_id, HTTP_RESPONSE_HEADER_ONLY_2)
+ .unwrap();
+ // Send an empty data frame.
+ let _ = server
+ .conn
+ .stream_send(request_stream_id, &[0x00, 0x00])
+ .unwrap();
+
+ let out = server.conn.process(None, now());
+ client.process(out.dgram(), 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.dgram(), 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.dgram(), 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);
+ let _ = 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.dgram(), now()));
+
+ let out = server.conn.process(None, now());
+ mem::drop(client.process(out.dgram(), 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.dgram(), 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.dgram().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.dgram(), 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.dgram(), now());
+ assert_eq!(client.state(), Http3State::Connected);
+
+ mem::drop(server.conn.process(out.dgram(), 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.dgram(), 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.dgram(), now());
+ assert_eq!(client.state(), Http3State::Connected);
+ let out = server.conn.process(out.dgram(), now());
+ assert!(server.conn.state().connected());
+ let out = client.process(out.dgram(), 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.dgram(), now());
+ assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc...
+ let server_ignored = server.process(client_0rtt.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), now());
+ assert_eq!(client.state(), Http3State::Connected);
+
+ mem::drop(server.conn.process(out.dgram(), 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.dgram(), 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.dgram(), 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.
+ let _ = 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.dgram(), 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.dgram(), 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.dgram(), 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]);
+ let _ = 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.dgram(), 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.dgram(), 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.
+ let _ = 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.
+ let _ = send_push_data(&mut server.conn, 0, false);
+ let dgram = server.conn.process_output(now()).dgram();
+ client.process_input(dgram.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.
+ let _ = send_push_data(&mut server.conn, 0, true);
+
+ // create a second push stream.
+ let _ = 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
+ let _ = 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.
+ let _ = 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
+ let _ = 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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()).dgram();
+ client.process(out, 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()).dgram();
+ client.process(out, 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()).dgram();
+ mem::drop(server.conn.process(out, 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()).dgram();
+ mem::drop(server.conn.process(out, 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.dgram(), 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, 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, 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, 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, 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, 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, now());
+
+ assert!(check_push_events(&mut client));
+
+ // Let client receive the encoder instructions.
+ let _out = client.process(encoder_inst_pkt2, 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];
+ let _ = client
+ .read_data(now(), request_stream_id, &mut buf)
+ .unwrap();
+
+ let out = client.process(None, now());
+ mem::drop(server.conn.process(out.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), now());
+ mem::drop(server.conn.process(out.dgram(), 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.dgram(), 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, 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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).
+ let _ = 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.dgram(), 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()).dgram();
+ mem::drop(server.conn.process(out, 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()).dgram();
+ mem::drop(server.conn.process(out, 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.dgram(), 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, now()).dgram();
+ datagram = server.process(datagram, now()).dgram();
+ }
+
+ // exchange qpack settings, server will send a token as well.
+ datagram = client.process(datagram, now()).dgram();
+ datagram = server.process(datagram, now()).dgram();
+ mem::drop(client.process(datagram, 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.
+ let _ = 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()).dgram();
+ let out = server.process(out, now()).dgram();
+ let out = client.process(out, now()).dgram();
+ let out = server.process(out, now()).dgram();
+ let out = client.process(out, now()).dgram();
+ let out = server.process(out, now()).dgram();
+ let out = client.process(out, now()).dgram();
+ let out = server.process(out, now()).dgram();
+ mem::drop(client.process(out, 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();
+ let _ = server
+ .conn
+ .stream_send(push_stream_id, PUSH_STREAM_TYPE)
+ .unwrap();
+ let _ = 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.dgram(), 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()).dgram();
+ let out = server.conn.process(out, now()).dgram();
+
+ // 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, now()).dgram();
+ let out = server.conn.process(out, now()).dgram();
+
+ // 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, now()).dgram();
+ server.conn.process_input(out.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.dgram().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..f3b1bb1412
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/connection_server.rs
@@ -0,0 +1,407 @@
+// 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 crate::connection::{Http3Connection, Http3State, WebTransportSessionAcceptAction};
+use crate::frames::HFrame;
+use crate::recv_message::{RecvMessage, RecvMessageInfo};
+use crate::send_message::SendMessage;
+use crate::server_connection_events::{Http3ServerConnEvent, Http3ServerConnEvents};
+use crate::{
+ Error, Http3Parameters, Http3StreamType, NewStreamType, Priority, PriorityHandler,
+ ReceiveOutput, Res,
+};
+use neqo_common::{event::Provider, qdebug, qinfo, qtrace, Header, MessageType, Role};
+use neqo_transport::{
+ AppError, Connection, ConnectionEvent, DatagramTracking, StreamId, StreamType,
+};
+use std::rc::Rc;
+use std::time::Instant;
+
+#[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..54c8282c2a
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/control_stream_local.rs
@@ -0,0 +1,107 @@
+// 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 crate::frames::HFrame;
+use crate::{BufferedStream, Http3StreamType, RecvStream, Res};
+use neqo_common::{qtrace, Encoder};
+use neqo_transport::{Connection, StreamId, StreamType};
+use std::collections::{HashMap, VecDeque};
+use std::convert::TryFrom;
+
+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 update_stream = match recv_conn.get_mut(&update_id) {
+ Some(update_stream) => update_stream,
+ None => 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..7b42ed2b11
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/control_stream_remote.rs
@@ -0,0 +1,74 @@
+// 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 crate::frames::{FrameReader, HFrame, StreamReaderConnectionWrapper};
+use crate::{CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream};
+use neqo_common::qdebug;
+use neqo_transport::{Connection, StreamId};
+
+/// 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..6be92dabba
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs
@@ -0,0 +1,114 @@
+// 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 crate::client_events::Http3ClientEvents;
+use crate::features::NegotiationState;
+use crate::settings::{HSettingType, HSettings};
+use crate::{CloseType, Http3StreamInfo, Http3StreamType};
+use neqo_common::Header;
+use neqo_transport::{AppError, StreamId};
+use std::fmt::Debug;
+pub(crate) use webtransport_session::WebTransportSession;
+
+#[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..1b9511b255
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs
@@ -0,0 +1,142 @@
+// 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 crate::features::extended_connect::tests::webtransport::{
+ wt_default_parameters, WtTest, DATAGRAM_SIZE,
+};
+use crate::{Error, Http3Parameters, WebTransportRequest};
+use neqo_common::Encoder;
+use neqo_transport::Error as TransportError;
+use std::convert::TryFrom;
+
+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..58ebfd8a6c
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs
@@ -0,0 +1,648 @@
+// 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 neqo_common::event::Provider;
+
+use crate::{
+ features::extended_connect::SessionCloseReason, Error, Header, Http3Client, Http3ClientEvent,
+ Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent, Http3State,
+ WebTransportEvent, WebTransportRequest, WebTransportServerEvent,
+ WebTransportSessionAcceptAction,
+};
+use neqo_crypto::AuthenticationStatus;
+use neqo_transport::{ConnectionParameters, StreamId, StreamType};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use test_fixture::{
+ addr, anti_replay, fixture_init, now, CountingConnectionIdGenerator, DEFAULT_ALPN_H3,
+ DEFAULT_KEYS, DEFAULT_SERVER_NAME,
+};
+
+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, now()).dgram();
+ out = server.process(out, 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.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ let out = server.process(out.dgram(), 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.dgram(), 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.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ let out = server.process(out.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ std::mem::drop(server.process(out.dgram(), 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) {
+ let mut out = None;
+ loop {
+ out = self.client.process(out, now()).dgram();
+ out = self.server.process(out, now()).dgram();
+ if 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 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..23784e5609
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs
@@ -0,0 +1,278 @@
+// 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::{connect, default_http3_client, default_http3_server, exchange_packets};
+use crate::{
+ settings::{HSetting, HSettingType, HSettings},
+ Error, HFrame, Http3Client, Http3ClientEvent, Http3Parameters, Http3Server, Http3State,
+ WebTransportEvent,
+};
+use neqo_common::{event::Provider, Encoder};
+use neqo_crypto::AuthenticationStatus;
+use neqo_transport::{Connection, ConnectionError, StreamType};
+use std::time::Duration;
+use test_fixture::{default_server_h3, now};
+
+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.dgram(), 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, now()).dgram();
+ out = server.process(out, 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..65572a1c2a
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs
@@ -0,0 +1,450 @@
+// 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 crate::features::extended_connect::tests::webtransport::{
+ default_http3_client, default_http3_server, wt_default_parameters, WtTest,
+};
+use crate::{
+ features::extended_connect::SessionCloseReason, frames::WebTransportFrame, Error, Header,
+ Http3ClientEvent, Http3OrWebTransportStream, Http3Server, Http3ServerEvent, Http3State,
+ Priority, WebTransportEvent, WebTransportServerEvent, WebTransportSessionAcceptAction,
+};
+use neqo_common::{event::Provider, Encoder};
+use neqo_transport::StreamType;
+use std::mem;
+use test_fixture::now;
+
+#[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()).dgram();
+ let out = wt.client.process(out, now()).dgram();
+
+ // 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, now()).dgram();
+ let out = wt.client.process(out, now()).dgram();
+ let out = wt.server.process(out, now()).dgram();
+ let out = wt.client.process(out, now()).dgram();
+ let out = wt.server.process(out, now()).dgram();
+ let _out = wt.client.process(out, now()).dgram();
+
+ 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..0eaf3ce928
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs
@@ -0,0 +1,1081 @@
+// 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 crate::features::extended_connect::tests::webtransport::WtTest;
+use crate::{features::extended_connect::SessionCloseReason, Error};
+use neqo_transport::StreamType;
+use std::mem;
+
+#[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);
+ wt.send_data_client(wt_stream, BUF_CLIENT);
+ wt.receive_data_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 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);
+}
+
+#[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);
+}
+
+#[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));
+}
+
+#[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..4a412dd27e
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs
@@ -0,0 +1,539 @@
+// 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 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,
+};
+use neqo_common::{qtrace, Encoder, Header, MessageType, Role};
+use neqo_qpack::{QPackDecoder, QPackEncoder};
+use neqo_transport::{Connection, DatagramTracking, StreamId};
+use std::any::Any;
+use std::cell::RefCell;
+use std::collections::BTreeSet;
+use std::mem;
+use std::rc::Rc;
+
+#[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 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..f08f8047d7
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs
@@ -0,0 +1,202 @@
+// 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::WebTransportSession;
+use crate::{
+ CloseType, Http3StreamInfo, Http3StreamType, ReceiveOutput, RecvStream, RecvStreamEvents, Res,
+ SendStream, SendStreamEvents, Stream,
+};
+use neqo_common::Encoder;
+use neqo_transport::{Connection, StreamId};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+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))
+ }
+}
+
+#[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 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(())
+ }
+}
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..0e045ed80b
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/features/mod.rs
@@ -0,0 +1,91 @@
+// 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 crate::{
+ client_events::Http3ClientEvents,
+ settings::{HSettingType, HSettings},
+};
+use neqo_common::qtrace;
+use std::fmt::Debug;
+use std::mem;
+
+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..86acd10f84
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/frames/hframe.rs
@@ -0,0 +1,226 @@
+// 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 crate::{frames::reader::FrameDecoder, settings::HSettings, Error, Priority, Res};
+use neqo_common::{Decoder, Encoder};
+use neqo_crypto::random;
+use neqo_transport::StreamId;
+use std::fmt::Debug;
+use std::io::Write;
+
+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..9d81f2c1c1
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/frames/reader.rs
@@ -0,0 +1,270 @@
+// 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 crate::{Error, RecvStream, Res};
+use neqo_common::{
+ hex_with_len, qtrace, Decoder, IncrementalDecoderBuffer, IncrementalDecoderIgnore,
+ IncrementalDecoderUint,
+};
+use neqo_transport::{Connection, StreamId};
+use std::convert::TryFrom;
+use std::fmt::Debug;
+
+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..54b7c94c8e
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/frames/tests/hframe.rs
@@ -0,0 +1,115 @@
+// 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_hframe;
+use crate::{
+ frames::HFrame,
+ settings::{HSetting, HSettingType, HSettings},
+ Priority,
+};
+use neqo_common::{Decoder, Encoder};
+use neqo_transport::StreamId;
+use test_fixture::fixture_init;
+
+#[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..092b3039ec
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/frames/tests/mod.rs
@@ -0,0 +1,82 @@
+// 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 crate::frames::{
+ reader::FrameDecoder, FrameReader, HFrame, StreamReaderConnectionWrapper, WebTransportFrame,
+};
+use neqo_common::Encoder;
+use neqo_crypto::AuthenticationStatus;
+use neqo_transport::StreamType;
+use std::mem;
+use test_fixture::{default_client, default_server, now};
+
+#[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.dgram(), now());
+ let out = conn_c.process(out.dgram(), now());
+ mem::drop(conn_s.process(out.dgram(), now()));
+ conn_c.authenticated(AuthenticationStatus::Ok, now());
+ let out = conn_c.process(None, now());
+ mem::drop(conn_s.process(out.dgram(), 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.dgram(), 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..f694e4dbe3
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/frames/tests/reader.rs
@@ -0,0 +1,517 @@
+// 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 crate::{
+ frames::{
+ reader::FrameDecoder, FrameReader, HFrame, StreamReaderConnectionWrapper, WebTransportFrame,
+ },
+ settings::{HSetting, HSettingType, HSettings},
+ Error,
+};
+use neqo_common::Encoder;
+use neqo_transport::{Connection, StreamId, StreamType};
+use std::fmt::Debug;
+use std::mem;
+use test_fixture::{connect, now};
+
+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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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..da44be9d15
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/frames/wtframe.rs
@@ -0,0 +1,61 @@
+// 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 crate::{frames::reader::FrameDecoder, Error, Res};
+use neqo_common::{Decoder, Encoder};
+use std::convert::TryFrom;
+
+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 message = match String::from_utf8(dec.decode_remainder().to_vec()) {
+ Ok(s) => s,
+ Err(_) => 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..1f684c5ab8
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/headers_checks.rs
@@ -0,0 +1,150 @@
+// 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::expl_impl_clone_on_copy)] // see https://github.com/Lymia/enumset/issues/28
+
+use crate::{Error, MessageType, Res};
+use enumset::{enum_set, EnumSet, EnumSetType};
+use neqo_common::Header;
+use std::convert::TryFrom;
+
+#[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 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());
+ }
+ let _ = 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") {
+ PseudoHeaderState::Method | PseudoHeaderState::Authority
+ } 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(())
+}
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..c3cda393ad
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/lib.rs
@@ -0,0 +1,629 @@
+// 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`](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 neqo_qpack::Error as QpackError;
+use neqo_transport::{AppError, Connection, Error as TransportError};
+pub use neqo_transport::{Output, StreamId};
+use std::fmt::Debug;
+
+use crate::priority::PriorityHandler;
+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;
+pub use priority::Priority;
+pub use server::Http3Server;
+pub use server_events::{
+ Http3OrWebTransportStream, Http3ServerEvent, WebTransportRequest, WebTransportServerEvent,
+};
+use std::any::Any;
+use std::cell::RefCell;
+use std::rc::Rc;
+use stream_type_reader::NewStreamType;
+
+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
+ }
+}
+
+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 occure 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;
+ /// # Errors
+ /// Error my occure 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)
+ }
+}
+
+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..87bb8ee2b4
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/priority.rs
@@ -0,0 +1,205 @@
+use crate::{frames::HFrame, Error, Header, Res};
+use neqo_transport::StreamId;
+use sfv::{BareItem, Item, ListEntry, Parser};
+use std::convert::TryFrom;
+use std::fmt;
+
+#[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={},i", urgency),
+ }
+ }
+}
+
+#[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 crate::priority::PriorityHandler;
+ use crate::{HFrame, Priority};
+ use neqo_transport::StreamId;
+
+ #[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..79ebab4efc
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/push_controller.rs
@@ -0,0 +1,509 @@
+// 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 crate::client_events::{Http3ClientEvent, Http3ClientEvents};
+use crate::connection::Http3Connection;
+use crate::frames::HFrame;
+use crate::{CloseType, Error, Http3StreamInfo, HttpRecvStreamEvents, RecvStreamEvents, Res};
+use neqo_common::{qerror, qinfo, qtrace, Header};
+use neqo_transport::{Connection, StreamId};
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::convert::TryFrom;
+use std::fmt::Debug;
+use std::fmt::Display;
+use std::mem;
+use std::rc::Rc;
+use std::slice::SliceIndex;
+
+/// `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 self.push_streams.get(0).is_some()
+ && *self.push_streams.get(0).unwrap() == PushState::Closed
+ {
+ 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..3d43a2c906
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/qlog.rs
@@ -0,0 +1,38 @@
+// 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 qlog::{self, event::Event, H3DataRecipient};
+
+use neqo_common::qlog::NeqoQlog;
+use neqo_transport::StreamId;
+
+pub fn h3_data_moved_up(qlog: &mut NeqoQlog, stream_id: StreamId, amount: usize) {
+ qlog.add_event(|| {
+ Some(Event::h3_data_moved(
+ stream_id.to_string(),
+ None,
+ Some(u64::try_from(amount).unwrap()),
+ Some(H3DataRecipient::Transport),
+ Some(H3DataRecipient::Application),
+ None,
+ ))
+ });
+}
+
+pub fn h3_data_moved_down(qlog: &mut NeqoQlog, stream_id: StreamId, amount: usize) {
+ qlog.add_event(|| {
+ Some(Event::h3_data_moved(
+ stream_id.to_string(),
+ None,
+ Some(u64::try_from(amount).unwrap()),
+ Some(H3DataRecipient::Application),
+ Some(H3DataRecipient::Transport),
+ None,
+ ))
+ });
+}
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..3cdfdf74cd
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/qpack_decoder_receiver.rs
@@ -0,0 +1,44 @@
+// 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 crate::{CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream};
+use neqo_qpack::QPackDecoder;
+use neqo_transport::{Connection, StreamId};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[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..efe234173f
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/qpack_encoder_receiver.rs
@@ -0,0 +1,40 @@
+// 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 crate::{CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream};
+use neqo_qpack::QPackEncoder;
+use neqo_transport::{Connection, StreamId};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[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..dd27c51337
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/recv_message.rs
@@ -0,0 +1,502 @@
+// 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 crate::frames::{FrameReader, HFrame, StreamReaderConnectionWrapper, H3_FRAME_TYPE_HEADERS};
+use crate::push_controller::PushController;
+use crate::{
+ headers_checks::{headers_valid, is_interim},
+ priority::PriorityHandler,
+ qlog, CloseType, Error, Http3StreamInfo, Http3StreamType, HttpRecvStream, HttpRecvStreamEvents,
+ MessageType, Priority, ReceiveOutput, RecvStream, Res, Stream,
+};
+use neqo_common::{qdebug, qinfo, qtrace, Header};
+use neqo_qpack::decoder::QPackDecoder;
+use neqo_transport::{Connection, StreamId};
+use std::any::Any;
+use std::cell::RefCell;
+use std::cmp::min;
+use std::collections::VecDeque;
+use std::convert::TryFrom;
+use std::fmt::Debug;
+use std::rc::Rc;
+
+#[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..a58445b5d7
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/request_target.rs
@@ -0,0 +1,127 @@
+// 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..3fecae4708
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/send_message.rs
@@ -0,0 +1,337 @@
+// 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 crate::frames::HFrame;
+use crate::{
+ headers_checks::{headers_valid, is_interim, trailers_valid},
+ qlog, BufferedStream, CloseType, Error, Http3StreamInfo, Http3StreamType, HttpSendStream, Res,
+ SendStream, SendStreamEvents, Stream,
+};
+
+use neqo_common::{qdebug, qinfo, qtrace, Encoder, Header, MessageType};
+use neqo_qpack::encoder::QPackEncoder;
+use neqo_transport::{Connection, StreamId};
+use std::any::Any;
+use std::cell::RefCell;
+use std::cmp::min;
+use std::fmt::Debug;
+use std::mem;
+use std::rc::Rc;
+
+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 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)
+ }
+
+ #[allow(clippy::drop_copy)]
+ 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);
+ mem::drop(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..e28b72be4b
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/server.rs
@@ -0,0 +1,1317 @@
+// 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 crate::connection::Http3State;
+use crate::connection_server::Http3ServerHandler;
+use crate::server_connection_events::Http3ServerConnEvent;
+use crate::server_events::{
+ Http3OrWebTransportStream, Http3ServerEvent, Http3ServerEvents, WebTransportRequest,
+};
+use crate::settings::HttpZeroRttChecker;
+use crate::{Http3Parameters, Http3StreamInfo, Res};
+use neqo_common::{qtrace, Datagram};
+use neqo_crypto::{AntiReplay, Cipher, PrivateKey, PublicKey, ZeroRttChecker};
+use neqo_transport::server::{ActiveConnectionRef, Server, ValidateAddress};
+use neqo_transport::{ConnectionIdGenerator, Output};
+use std::cell::RefCell;
+use std::cell::RefMut;
+use std::collections::HashMap;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::time::Instant;
+
+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(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 super::{Http3Server, Http3ServerEvent, Http3State, Rc, RefCell};
+ use crate::{Error, HFrame, Header, Http3Parameters, Priority};
+ use neqo_common::event::Provider;
+ use neqo_common::Encoder;
+ use neqo_crypto::{AuthenticationStatus, ZeroRttCheckResult, ZeroRttChecker};
+ use neqo_qpack::{encoder::QPackEncoder, QpackSettings};
+ use neqo_transport::{
+ Connection, ConnectionError, ConnectionEvent, State, StreamId, StreamType, ZeroRttState,
+ };
+ use std::collections::HashMap;
+ use std::mem;
+ use std::ops::{Deref, DerefMut};
+ use test_fixture::{
+ anti_replay, default_client, fixture_init, now, CountingConnectionIdGenerator,
+ DEFAULT_ALPN, DEFAULT_KEYS,
+ };
+
+ 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()).dgram();
+ let s1 = server.process(c1, now()).dgram();
+ let c2 = client.process(s1, now()).dgram();
+ 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, now()).dgram();
+ assert!(s_ack.is_none());
+
+ client.authenticated(AuthenticationStatus::Ok, now());
+ client.process(None, now()).dgram()
+ } else {
+ assert!(resume);
+ c2
+ };
+ assert!(client.state().connected());
+ let s2 = server.process(c2, now()).dgram();
+ assert_connected(server);
+ let c3 = client.process(s2, now()).dgram();
+ assert!(c3.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.dgram(), now());
+ mem::drop(neqo_trans_conn.process(out2.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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();
+ let _ = 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.dgram(), now());
+ mem::drop(peer_conn.process(out.dgram(), now()));
+ let out = hconn.process(None, now());
+ mem::drop(peer_conn.process(out.dgram(), 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();
+ let _ = peer_conn.stream_send(push_stream_id, &[0x1]).unwrap();
+ let out = peer_conn.process(None, now());
+ let out = hconn.process(out.dgram(), now());
+ mem::drop(peer_conn.conn.process(out.dgram(), 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.dgram(), 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.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x4]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x6]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x0]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x8]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x0]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), 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.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x5]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x4]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x61]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x62]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x63]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), now());
+
+ sent = peer_conn.stream_send(control_stream, &[0x64]);
+ assert_eq!(sent, Ok(1));
+ let out = peer_conn.process(None, now());
+ hconn.process(out.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), now());
+ hconn.process(out.dgram(), 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.dgram(), 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.dgram(), now());
+ hconn.process(out.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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.dgram(), 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..f56288e204
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/server_connection_events.rs
@@ -0,0 +1,197 @@
+// 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 crate::connection::Http3State;
+use crate::{
+ features::extended_connect::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason},
+ CloseType, Http3StreamInfo, HttpRecvStreamEvents, Priority, RecvStreamEvents, SendStreamEvents,
+};
+use neqo_common::Header;
+use neqo_transport::AppError;
+use neqo_transport::StreamId;
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::rc::Rc;
+
+#[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..e0cc84ed4c
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/server_events.rs
@@ -0,0 +1,567 @@
+// 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 crate::connection::{Http3State, WebTransportSessionAcceptAction};
+use crate::connection_server::Http3ServerHandler;
+use crate::{
+ features::extended_connect::SessionCloseReason, Http3StreamInfo, Http3StreamType, Priority, Res,
+};
+use neqo_common::{qdebug, qinfo, Encoder, Header};
+use neqo_transport::server::ActiveConnectionRef;
+use neqo_transport::{AppError, Connection, DatagramTracking, StreamId, StreamType};
+
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::convert::TryFrom;
+use std::ops::{Deref, DerefMut};
+use std::rc::Rc;
+
+#[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..1e952dae6d
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/settings.rs
@@ -0,0 +1,296 @@
+// 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 crate::{Error, Http3Parameters, Res};
+use neqo_common::{Decoder, Encoder};
+use neqo_crypto::{ZeroRttCheckResult, ZeroRttChecker};
+use std::ops::Deref;
+
+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..be5d94adeb
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/stream_type_reader.rs
@@ -0,0 +1,682 @@
+// 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 crate::control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL;
+use crate::frames::H3_FRAME_TYPE_HEADERS;
+use crate::{CloseType, Error, Http3StreamType, ReceiveOutput, RecvStream, Res, Stream};
+use neqo_common::{qtrace, Decoder, IncrementalDecoderUint, Role};
+use neqo_qpack::decoder::QPACK_UNI_STREAM_TYPE_DECODER;
+use neqo_qpack::encoder::QPACK_UNI_STREAM_TYPE_ENCODER;
+use neqo_transport::{Connection, StreamId, StreamType};
+
+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.
+ /// # Error
+ /// 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 output = if let Some(o) = output {
+ o
+ } 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 super::{
+ NewStreamHeadReader, HTTP3_UNI_STREAM_TYPE_PUSH, WEBTRANSPORT_STREAM,
+ WEBTRANSPORT_UNI_STREAM,
+ };
+ use neqo_transport::{Connection, StreamId, StreamType};
+ use std::mem;
+ use test_fixture::{connect, now};
+
+ use crate::control_stream_local::HTTP3_UNI_STREAM_TYPE_CONTROL;
+ use crate::frames::H3_FRAME_TYPE_HEADERS;
+ use crate::{CloseType, Error, NewStreamType, ReceiveOutput, RecvStream, Res};
+ use neqo_common::{Encoder, Role};
+ use neqo_qpack::decoder::QPACK_UNI_STREAM_TYPE_DECODER;
+ use neqo_qpack::encoder::QPACK_UNI_STREAM_TYPE_ENCODER;
+
+ 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.dgram(), 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.dgram(), 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(), 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..c78b3f0be8
--- /dev/null
+++ b/third_party/rust/neqo-http3/tests/httpconn.rs
@@ -0,0 +1,460 @@
+// 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 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 std::mem;
+use std::time::{Duration, Instant};
+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.dgram(), now()); // Initial + Handshake
+ let out = hconn_c.process(out.dgram(), now()); // ACK
+ mem::drop(hconn_s.process(out.dgram(), 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.dgram(), now()); // Handshake
+ let out = hconn_c.process(out.dgram(), now());
+ let out = hconn_s.process(out.dgram(), now());
+ // assert!(hconn_s.settings_received);
+ let out = hconn_c.process(out.dgram(), 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.dgram(), now); // Initial + Handshake
+ now += net_delay;
+ let out = hconn_c.process(out.dgram(), now); // ACK
+ now += net_delay;
+ let out = hconn_s.process(out.dgram(), 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.dgram(), now); // HANDSHAKE_DONE
+ now += net_delay;
+ let out = hconn_c.process(out.dgram(), now); // Consume HANDSHAKE_DONE, send control streams.
+ now += net_delay;
+ let out = hconn_s.process(out.dgram(), now); // consume and send control streams.
+ now += net_delay;
+ let out = hconn_c.process(out.dgram(), 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, now()).dgram();
+ out = server.process(out, 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, now());
+ qtrace!("-----server");
+ let out = hconn_s.process(out.dgram(), now());
+ mem::drop(hconn_c.process(out.dgram(), now()));
+ process_server_events(&mut hconn_s);
+ let out = hconn_s.process(None, now());
+
+ qtrace!("-----client");
+ mem::drop(hconn_c.process(out.dgram(), now()));
+ let out = hconn_s.process(None, now());
+ mem::drop(hconn_c.process(out.dgram(), 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, now());
+
+ let out = hconn_s.process(out.dgram(), now());
+ mem::drop(hconn_c.process(out.dgram(), 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.dgram(), 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.dgram(), 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, now());
+ let out = hconn_s.process(out.dgram(), 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, 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..1fdfac3d91
--- /dev/null
+++ b/third_party/rust/neqo-http3/tests/priority.rs
@@ -0,0 +1,149 @@
+// 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::event::Provider;
+
+use neqo_crypto::AuthenticationStatus;
+use neqo_http3::{
+ Header, Http3Client, Http3ClientEvent, Http3Server, Http3ServerEvent, Http3State, Priority,
+};
+
+use std::time::Instant;
+use test_fixture::*;
+
+fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
+ let mut out = None;
+ loop {
+ out = client.process(out, now()).dgram();
+ let client_done = out.is_none();
+ out = server.process(out, 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.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ let out = server.process(out.dgram(), 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.dgram(), 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.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ let out = server.process(out.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ let _ = server.process(out.dgram(), 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..ef4a571dff
--- /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, now()).dgram();
+ out = server.process(out, 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..e0556708f1
--- /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 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 std::cell::RefCell;
+use std::rc::Rc;
+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.dgram(), now());
+ let out = client.process(out.dgram(), now());
+ let out = server.process(out.dgram(), 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.dgram(), 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, now()).dgram();
+ let dgram_present = out.is_some();
+ out = client.process(out, 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, now()).dgram();
+ out = server.process(out, 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()
+ );
+}