summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-http3/tests
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/neqo-http3/tests')
-rw-r--r--third_party/rust/neqo-http3/tests/httpconn.rs463
-rw-r--r--third_party/rust/neqo-http3/tests/priority.rs148
-rw-r--r--third_party/rust/neqo-http3/tests/send_message.rs329
-rw-r--r--third_party/rust/neqo-http3/tests/webtransport.rs320
4 files changed, 1260 insertions, 0 deletions
diff --git a/third_party/rust/neqo-http3/tests/httpconn.rs b/third_party/rust/neqo-http3/tests/httpconn.rs
new file mode 100644
index 0000000000..a0b2bcdb80
--- /dev/null
+++ b/third_party/rust/neqo-http3/tests/httpconn.rs
@@ -0,0 +1,463 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(unused_assignments)]
+
+use std::{
+ mem,
+ time::{Duration, Instant},
+};
+
+use neqo_common::{event::Provider, qtrace, Datagram};
+use neqo_crypto::{AuthenticationStatus, ResumptionToken};
+use neqo_http3::{
+ Header, Http3Client, Http3ClientEvent, Http3OrWebTransportStream, Http3Parameters, Http3Server,
+ Http3ServerEvent, Http3State, Priority,
+};
+use neqo_transport::{ConnectionError, ConnectionParameters, Error, Output, StreamType};
+use test_fixture::*;
+
+const RESPONSE_DATA: &[u8] = &[0x61, 0x62, 0x63];
+
+fn receive_request(server: &mut Http3Server) -> Option<Http3OrWebTransportStream> {
+ while let Some(event) = server.next_event() {
+ if let Http3ServerEvent::Headers {
+ stream,
+ headers,
+ fin,
+ } = event
+ {
+ assert_eq!(
+ &headers,
+ &[
+ Header::new(":method", "GET"),
+ Header::new(":scheme", "https"),
+ Header::new(":authority", "something.com"),
+ Header::new(":path", "/")
+ ]
+ );
+ assert!(fin);
+
+ return Some(stream);
+ }
+ }
+ None
+}
+
+fn set_response(request: &mut Http3OrWebTransportStream) {
+ request
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("content-length", "3"),
+ ])
+ .unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ request.stream_close_send().unwrap();
+}
+
+fn process_server_events(server: &mut Http3Server) {
+ let mut request = receive_request(server).unwrap();
+ set_response(&mut request);
+}
+
+fn process_client_events(conn: &mut Http3Client) {
+ let mut response_header_found = false;
+ let mut response_data_found = false;
+ while let Some(event) = conn.next_event() {
+ match event {
+ Http3ClientEvent::HeaderReady { headers, fin, .. } => {
+ assert_eq!(
+ &headers,
+ &[
+ Header::new(":status", "200"),
+ Header::new("content-length", "3"),
+ ]
+ );
+ assert!(!fin);
+ response_header_found = true;
+ }
+ Http3ClientEvent::DataReadable { stream_id } => {
+ let mut buf = [0u8; 100];
+ let (amount, fin) = conn.read_data(now(), stream_id, &mut buf).unwrap();
+ assert!(fin);
+ assert_eq!(amount, RESPONSE_DATA.len());
+ assert_eq!(&buf[..RESPONSE_DATA.len()], RESPONSE_DATA);
+ response_data_found = true;
+ }
+ _ => {}
+ }
+ }
+ assert!(response_header_found);
+ assert!(response_data_found);
+}
+
+fn connect_peers(hconn_c: &mut Http3Client, hconn_s: &mut Http3Server) -> Option<Datagram> {
+ assert_eq!(hconn_c.state(), Http3State::Initializing);
+ let out = hconn_c.process(None, now()); // Initial
+ let out = hconn_s.process(out.as_dgram_ref(), now()); // Initial + Handshake
+ let out = hconn_c.process(out.as_dgram_ref(), now()); // ACK
+ mem::drop(hconn_s.process(out.as_dgram_ref(), now())); // consume ACK
+ let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
+ assert!(hconn_c.events().any(authentication_needed));
+ hconn_c.authenticated(AuthenticationStatus::Ok, now());
+ let out = hconn_c.process(None, now()); // Handshake
+ assert_eq!(hconn_c.state(), Http3State::Connected);
+ let out = hconn_s.process(out.as_dgram_ref(), now()); // Handshake
+ let out = hconn_c.process(out.as_dgram_ref(), now());
+ let out = hconn_s.process(out.as_dgram_ref(), now());
+ // assert!(hconn_s.settings_received);
+ let out = hconn_c.process(out.as_dgram_ref(), now());
+ // assert!(hconn_c.settings_received);
+
+ out.dgram()
+}
+
+fn connect_peers_with_network_propagation_delay(
+ hconn_c: &mut Http3Client,
+ hconn_s: &mut Http3Server,
+ net_delay: u64,
+) -> (Option<Datagram>, Instant) {
+ let net_delay = Duration::from_millis(net_delay);
+ assert_eq!(hconn_c.state(), Http3State::Initializing);
+ let mut now = now();
+ let out = hconn_c.process(None, now); // Initial
+ now += net_delay;
+ let out = hconn_s.process(out.as_dgram_ref(), now); // Initial + Handshake
+ now += net_delay;
+ let out = hconn_c.process(out.as_dgram_ref(), now); // ACK
+ now += net_delay;
+ let out = hconn_s.process(out.as_dgram_ref(), now); // consume ACK
+ assert!(out.dgram().is_none());
+ let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
+ assert!(hconn_c.events().any(authentication_needed));
+ now += net_delay;
+ hconn_c.authenticated(AuthenticationStatus::Ok, now);
+ let out = hconn_c.process(None, now); // Handshake
+ assert_eq!(hconn_c.state(), Http3State::Connected);
+ now += net_delay;
+ let out = hconn_s.process(out.as_dgram_ref(), now); // HANDSHAKE_DONE
+ now += net_delay;
+ let out = hconn_c.process(out.as_dgram_ref(), now); // Consume HANDSHAKE_DONE, send control streams.
+ now += net_delay;
+ let out = hconn_s.process(out.as_dgram_ref(), now); // consume and send control streams.
+ now += net_delay;
+ let out = hconn_c.process(out.as_dgram_ref(), now); // consume control streams.
+ (out.dgram(), now)
+}
+
+fn connect() -> (Http3Client, Http3Server, Option<Datagram>) {
+ let mut hconn_c = default_http3_client();
+ let mut hconn_s = default_http3_server();
+
+ let out = connect_peers(&mut hconn_c, &mut hconn_s);
+ (hconn_c, hconn_s, out)
+}
+
+fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server, out_ex: Option<Datagram>) {
+ let mut out = out_ex;
+ loop {
+ out = client.process(out.as_ref(), now()).dgram();
+ out = server.process(out.as_ref(), now()).dgram();
+ if out.is_none() {
+ break;
+ }
+ }
+}
+
+#[test]
+fn test_connect() {
+ let (_hconn_c, _hconn_s, _d) = connect();
+}
+
+#[test]
+fn test_fetch() {
+ let (mut hconn_c, mut hconn_s, dgram) = connect();
+
+ qtrace!("-----client");
+ let req = hconn_c
+ .fetch(
+ now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::default(),
+ )
+ .unwrap();
+ assert_eq!(req, 0);
+ hconn_c.stream_close_send(req).unwrap();
+ let out = hconn_c.process(dgram.as_ref(), now());
+ qtrace!("-----server");
+ let out = hconn_s.process(out.as_dgram_ref(), now());
+ mem::drop(hconn_c.process(out.as_dgram_ref(), now()));
+ process_server_events(&mut hconn_s);
+ let out = hconn_s.process(None, now());
+
+ qtrace!("-----client");
+ mem::drop(hconn_c.process(out.as_dgram_ref(), now()));
+ let out = hconn_s.process(None, now());
+ mem::drop(hconn_c.process(out.as_dgram_ref(), now()));
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn test_103_response() {
+ let (mut hconn_c, mut hconn_s, dgram) = connect();
+
+ let req = hconn_c
+ .fetch(
+ now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::default(),
+ )
+ .unwrap();
+ assert_eq!(req, 0);
+ hconn_c.stream_close_send(req).unwrap();
+ let out = hconn_c.process(dgram.as_ref(), now());
+
+ let out = hconn_s.process(out.as_dgram_ref(), now());
+ mem::drop(hconn_c.process(out.as_dgram_ref(), now()));
+ let mut request = receive_request(&mut hconn_s).unwrap();
+
+ let info_headers = [
+ Header::new(":status", "103"),
+ Header::new("link", "</style.css>; rel=preload; as=style"),
+ ];
+ // Send 103
+ request.send_headers(&info_headers).unwrap();
+ let out = hconn_s.process(None, now());
+
+ mem::drop(hconn_c.process(out.as_dgram_ref(), now()));
+
+ let info_headers_event = |e| {
+ matches!(e, Http3ClientEvent::HeaderReady { headers,
+ interim,
+ fin, .. } if !fin && interim && headers.as_ref() == info_headers)
+ };
+ assert!(hconn_c.events().any(info_headers_event));
+
+ set_response(&mut request);
+ let out = hconn_s.process(None, now());
+ mem::drop(hconn_c.process(out.as_dgram_ref(), now()));
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn test_data_writable_events() {
+ const STREAM_LIMIT: u64 = 5000;
+ const DATA_AMOUNT: usize = 10000;
+
+ let mut hconn_c = http3_client_with_params(Http3Parameters::default().connection_parameters(
+ ConnectionParameters::default().max_stream_data(StreamType::BiDi, false, STREAM_LIMIT),
+ ));
+ let mut hconn_s = default_http3_server();
+
+ mem::drop(connect_peers(&mut hconn_c, &mut hconn_s));
+
+ // Create a request.
+ let req = hconn_c
+ .fetch(
+ now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::default(),
+ )
+ .unwrap();
+ hconn_c.stream_close_send(req).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s, None);
+
+ let mut request = receive_request(&mut hconn_s).unwrap();
+
+ request
+ .send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("content-length", DATA_AMOUNT.to_string()),
+ ])
+ .unwrap();
+
+ // Send a lot of data
+ let buf = &[1; DATA_AMOUNT];
+ let mut sent = request.send_data(buf).unwrap();
+ assert!(sent < DATA_AMOUNT);
+
+ // Exchange packets and read the data on the client side.
+ exchange_packets(&mut hconn_c, &mut hconn_s, None);
+ let stream_id = request.stream_id();
+ let mut recv_buf = [0_u8; DATA_AMOUNT];
+ let (mut recvd, _) = hconn_c.read_data(now(), stream_id, &mut recv_buf).unwrap();
+ assert_eq!(sent, recvd);
+ exchange_packets(&mut hconn_c, &mut hconn_s, None);
+
+ let data_writable = |e| {
+ matches!(
+ e,
+ Http3ServerEvent::DataWritable {
+ stream
+ } if stream.stream_id() == stream_id
+ )
+ };
+ // Make sure we have a DataWritable event.
+ assert!(hconn_s.events().any(data_writable));
+ // Data can be sent again.
+ let s = request.send_data(&buf[sent..]).unwrap();
+ assert!(s > 0);
+ sent += s;
+
+ // Exchange packets and read the data on the client side.
+ exchange_packets(&mut hconn_c, &mut hconn_s, None);
+ let (r, _) = hconn_c
+ .read_data(now(), stream_id, &mut recv_buf[recvd..])
+ .unwrap();
+ recvd += r;
+ exchange_packets(&mut hconn_c, &mut hconn_s, None);
+ assert_eq!(sent, recvd);
+
+ // One more DataWritable event.
+ assert!(hconn_s.events().any(data_writable));
+ // Send more data.
+ let s = request.send_data(&buf[sent..]).unwrap();
+ assert!(s > 0);
+ sent += s;
+ assert_eq!(sent, DATA_AMOUNT);
+
+ exchange_packets(&mut hconn_c, &mut hconn_s, None);
+ let (r, _) = hconn_c
+ .read_data(now(), stream_id, &mut recv_buf[recvd..])
+ .unwrap();
+ recvd += r;
+
+ // Make sure all data is received by the client.
+ assert_eq!(recvd, DATA_AMOUNT);
+ assert_eq!(&recv_buf, buf);
+}
+
+fn get_token(client: &mut Http3Client) -> ResumptionToken {
+ assert_eq!(client.state(), Http3State::Connected);
+ client
+ .events()
+ .find_map(|e| {
+ if let Http3ClientEvent::ResumptionToken(token) = e {
+ Some(token)
+ } else {
+ None
+ }
+ })
+ .unwrap()
+}
+
+#[test]
+fn zerortt() {
+ let (mut hconn_c, _, dgram) = connect();
+ let token = get_token(&mut hconn_c);
+
+ // Create a new connection with a resumption token.
+ let mut hconn_c = default_http3_client();
+ hconn_c
+ .enable_resumption(now(), &token)
+ .expect("Set resumption token.");
+ let mut hconn_s = default_http3_server();
+
+ // Create a request.
+ let req = hconn_c
+ .fetch(
+ now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::default(),
+ )
+ .unwrap();
+ hconn_c.stream_close_send(req).unwrap();
+
+ let out = hconn_c.process(dgram.as_ref(), now());
+ let out = hconn_s.process(out.as_dgram_ref(), now());
+
+ let mut request_stream = None;
+ let mut zerortt_state_change = false;
+ while let Some(event) = hconn_s.next_event() {
+ match event {
+ Http3ServerEvent::Headers {
+ stream,
+ headers,
+ fin,
+ } => {
+ assert_eq!(
+ &headers,
+ &[
+ Header::new(":method", "GET"),
+ Header::new(":scheme", "https"),
+ Header::new(":authority", "something.com"),
+ Header::new(":path", "/")
+ ]
+ );
+ assert!(fin);
+
+ request_stream = Some(stream);
+ }
+ Http3ServerEvent::StateChange { state, .. } => {
+ assert_eq!(state, Http3State::ZeroRtt);
+ zerortt_state_change = true;
+ }
+ _ => {}
+ }
+ }
+ assert!(zerortt_state_change);
+ let mut request_stream = request_stream.unwrap();
+
+ // Send a response
+ set_response(&mut request_stream);
+
+ // Receive the response
+ exchange_packets(&mut hconn_c, &mut hconn_s, out.dgram());
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+/// When a client has an outstanding fetch, it will send keepalives.
+/// Test that it will successfully run until the connection times out.
+fn fetch_noresponse_will_idletimeout() {
+ let mut hconn_c = default_http3_client();
+ let mut hconn_s = default_http3_server();
+
+ let (dgram, mut now) =
+ connect_peers_with_network_propagation_delay(&mut hconn_c, &mut hconn_s, 10);
+
+ qtrace!("-----client");
+ let req = hconn_c
+ .fetch(
+ now,
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::default(),
+ )
+ .unwrap();
+ assert_eq!(req, 0);
+ hconn_c.stream_close_send(req).unwrap();
+ let _out = hconn_c.process(dgram.as_ref(), now);
+ qtrace!("-----server");
+
+ let mut done = false;
+ while !done {
+ while let Some(event) = hconn_c.next_event() {
+ if let Http3ClientEvent::StateChange(state) = event {
+ match state {
+ Http3State::Closing(error_code) | Http3State::Closed(error_code) => {
+ assert_eq!(error_code, ConnectionError::Transport(Error::IdleTimeout));
+ done = true;
+ }
+ _ => {}
+ }
+ }
+ }
+
+ if let Output::Callback(t) = hconn_c.process_output(now) {
+ now += t;
+ }
+ }
+}
diff --git a/third_party/rust/neqo-http3/tests/priority.rs b/third_party/rust/neqo-http3/tests/priority.rs
new file mode 100644
index 0000000000..cdec161058
--- /dev/null
+++ b/third_party/rust/neqo-http3/tests/priority.rs
@@ -0,0 +1,148 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::time::Instant;
+
+use neqo_common::event::Provider;
+use neqo_crypto::AuthenticationStatus;
+use neqo_http3::{
+ Header, Http3Client, Http3ClientEvent, Http3Server, Http3ServerEvent, Http3State, Priority,
+};
+use test_fixture::*;
+
+fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
+ let mut out = None;
+ loop {
+ out = client.process(out.as_ref(), now()).dgram();
+ let client_done = out.is_none();
+ out = server.process(out.as_ref(), now()).dgram();
+ if out.is_none() && client_done {
+ break;
+ }
+ }
+}
+
+// Perform only Quic transport handshake.
+fn connect_with(client: &mut Http3Client, server: &mut Http3Server) {
+ assert_eq!(client.state(), Http3State::Initializing);
+ let out = client.process(None, now());
+ assert_eq!(client.state(), Http3State::Initializing);
+
+ let out = server.process(out.as_dgram_ref(), now());
+ let out = client.process(out.as_dgram_ref(), now());
+ let out = server.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_none());
+
+ let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
+ assert!(client.events().any(authentication_needed));
+ client.authenticated(AuthenticationStatus::Ok, now());
+
+ let out = client.process(out.as_dgram_ref(), now());
+ let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected));
+ assert!(client.events().any(connected));
+
+ assert_eq!(client.state(), Http3State::Connected);
+ // Exchange H3 setttings
+ let out = server.process(out.as_dgram_ref(), now());
+ let out = client.process(out.as_dgram_ref(), now());
+ let out = server.process(out.as_dgram_ref(), now());
+ let out = client.process(out.as_dgram_ref(), now());
+ _ = server.process(out.as_dgram_ref(), now());
+}
+
+fn connect() -> (Http3Client, Http3Server) {
+ let mut client = default_http3_client();
+ let mut server = default_http3_server();
+ connect_with(&mut client, &mut server);
+ (client, server)
+}
+
+#[test]
+fn priority_update() {
+ let (mut client, mut server) = connect();
+ let stream_id = client
+ .fetch(
+ Instant::now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::new(4, true),
+ )
+ .unwrap();
+ exchange_packets(&mut client, &mut server);
+
+ // get event of the above request, skipping events of the connection setup
+ let header_event = loop {
+ let event = server.next_event().unwrap();
+ if matches!(event, Http3ServerEvent::Headers { .. }) {
+ break event;
+ }
+ };
+
+ match header_event {
+ Http3ServerEvent::Headers {
+ stream: _,
+ headers,
+ fin,
+ } => {
+ let expected_headers = &[
+ Header::new(":method", "GET"),
+ Header::new(":scheme", "https"),
+ Header::new(":authority", "something.com"),
+ Header::new(":path", "/"),
+ Header::new("priority", "u=4,i"),
+ ];
+ assert_eq!(&headers, expected_headers);
+ assert!(!fin);
+ }
+ other => panic!("unexpected server event: {:?}", other),
+ }
+
+ let update_priority = Priority::new(3, false);
+ client.priority_update(stream_id, update_priority).unwrap();
+ exchange_packets(&mut client, &mut server);
+
+ let found = server.events().any(|e| {
+ if let Http3ServerEvent::PriorityUpdate {
+ stream_id: update_id,
+ priority,
+ } = e
+ {
+ assert_eq!(update_id, stream_id);
+ assert_eq!(priority, update_priority);
+ true
+ } else {
+ false
+ }
+ });
+ assert!(found);
+}
+
+#[test]
+fn priority_update_dont_send_for_cancelled_stream() {
+ let (mut client, mut server) = connect();
+ let stream_id = client
+ .fetch(
+ Instant::now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::new(5, false),
+ )
+ .unwrap();
+ exchange_packets(&mut client, &mut server);
+
+ let update_priority = Priority::new(6, false);
+ client.priority_update(stream_id, update_priority).unwrap();
+ client.cancel_fetch(stream_id, 11).unwrap();
+ exchange_packets(&mut client, &mut server);
+
+ while let Some(event) = server.next_event() {
+ if matches!(event, Http3ServerEvent::PriorityUpdate { .. }) {
+ panic!("Priority update sent on cancelled stream");
+ }
+ }
+}
diff --git a/third_party/rust/neqo-http3/tests/send_message.rs b/third_party/rust/neqo-http3/tests/send_message.rs
new file mode 100644
index 0000000000..507c4bd552
--- /dev/null
+++ b/third_party/rust/neqo-http3/tests/send_message.rs
@@ -0,0 +1,329 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use lazy_static::lazy_static;
+use neqo_common::event::Provider;
+use neqo_crypto::AuthenticationStatus;
+use neqo_http3::{
+ Error, Header, Http3Client, Http3ClientEvent, Http3OrWebTransportStream, Http3Server,
+ Http3ServerEvent, Priority,
+};
+use test_fixture::*;
+
+const RESPONSE_DATA: &[u8] = &[0x61, 0x62, 0x63];
+
+lazy_static! {
+ static ref RESPONSE_HEADER_NO_DATA: Vec<Header> =
+ vec![Header::new(":status", "200"), Header::new("something", "3")];
+}
+
+lazy_static! {
+ static ref RESPONSE_HEADER_103: Vec<Header> =
+ vec![Header::new(":status", "103"), Header::new("link", "...")];
+}
+
+fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
+ let mut out = None;
+ loop {
+ out = client.process(out.as_ref(), now()).dgram();
+ out = server.process(out.as_ref(), now()).dgram();
+ if out.is_none() {
+ break;
+ }
+ }
+}
+
+fn receive_request(server: &mut Http3Server) -> Option<Http3OrWebTransportStream> {
+ while let Some(event) = server.next_event() {
+ if let Http3ServerEvent::Headers {
+ stream,
+ headers,
+ fin,
+ } = event
+ {
+ assert_eq!(
+ &headers,
+ &[
+ Header::new(":method", "GET"),
+ Header::new(":scheme", "https"),
+ Header::new(":authority", "something.com"),
+ Header::new(":path", "/")
+ ]
+ );
+ assert!(fin);
+ return Some(stream);
+ }
+ }
+ None
+}
+
+fn send_trailers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> {
+ request.send_headers(&[
+ Header::new("something1", "something"),
+ Header::new("something2", "3"),
+ ])
+}
+
+fn send_informational_headers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> {
+ request.send_headers(&RESPONSE_HEADER_103)
+}
+
+fn send_headers(request: &mut Http3OrWebTransportStream) -> Result<(), Error> {
+ request.send_headers(&[
+ Header::new(":status", "200"),
+ Header::new("content-length", "3"),
+ ])
+}
+
+fn process_client_events(conn: &mut Http3Client) {
+ let mut response_header_found = false;
+ let mut response_data_found = false;
+ while let Some(event) = conn.next_event() {
+ match event {
+ Http3ClientEvent::HeaderReady { headers, fin, .. } => {
+ assert!(
+ (headers.as_ref()
+ == [
+ Header::new(":status", "200"),
+ Header::new("content-length", "3"),
+ ])
+ || (headers.as_ref() == *RESPONSE_HEADER_103)
+ );
+ assert!(!fin);
+ response_header_found = true;
+ }
+ Http3ClientEvent::DataReadable { stream_id } => {
+ let mut buf = [0u8; 100];
+ let (amount, fin) = conn.read_data(now(), stream_id, &mut buf).unwrap();
+ assert!(fin);
+ assert_eq!(amount, RESPONSE_DATA.len());
+ assert_eq!(&buf[..RESPONSE_DATA.len()], RESPONSE_DATA);
+ response_data_found = true;
+ }
+ _ => {}
+ }
+ }
+ assert!(response_header_found);
+ assert!(response_data_found);
+}
+
+fn process_client_events_no_data(conn: &mut Http3Client) {
+ let mut response_header_found = false;
+ let mut fin_received = false;
+ while let Some(event) = conn.next_event() {
+ match event {
+ Http3ClientEvent::HeaderReady { headers, fin, .. } => {
+ assert_eq!(headers.as_ref(), *RESPONSE_HEADER_NO_DATA);
+ fin_received = fin;
+ response_header_found = true;
+ }
+ Http3ClientEvent::DataReadable { stream_id } => {
+ let mut buf = [0u8; 100];
+ let (amount, fin) = conn.read_data(now(), stream_id, &mut buf).unwrap();
+ assert!(fin);
+ fin_received = true;
+ assert_eq!(amount, 0);
+ }
+ _ => {}
+ }
+ }
+ assert!(response_header_found);
+ assert!(fin_received);
+}
+
+fn connect_send_and_receive_request() -> (Http3Client, Http3Server, Http3OrWebTransportStream) {
+ let mut hconn_c = default_http3_client();
+ let mut hconn_s = default_http3_server();
+
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
+ assert!(hconn_c.events().any(authentication_needed));
+ hconn_c.authenticated(AuthenticationStatus::Ok, now());
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+
+ let req = hconn_c
+ .fetch(
+ now(),
+ "GET",
+ &("https", "something.com", "/"),
+ &[],
+ Priority::default(),
+ )
+ .unwrap();
+ assert_eq!(req, 0);
+ hconn_c.stream_close_send(req).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+
+ let request = receive_request(&mut hconn_s).unwrap();
+
+ (hconn_c, hconn_s, request)
+}
+
+#[test]
+fn response_trailers1() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ send_trailers(&mut request).unwrap();
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn response_trailers2() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ send_trailers(&mut request).unwrap();
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn response_trailers3() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ send_trailers(&mut request).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn response_trailers_no_data() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ send_trailers(&mut request).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events_no_data(&mut hconn_c);
+}
+
+#[test]
+fn multiple_response_trailers() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ send_trailers(&mut request).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+
+ assert_eq!(send_trailers(&mut request), Err(Error::InvalidInput));
+
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn data_after_trailer() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ send_trailers(&mut request).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+
+ assert_eq!(request.send_data(RESPONSE_DATA), Err(Error::InvalidInput));
+
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn trailers_after_close() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ request.stream_close_send().unwrap();
+
+ assert_eq!(send_trailers(&mut request), Err(Error::InvalidStreamId));
+
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn multiple_response_headers() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap();
+
+ assert_eq!(
+ request.send_headers(&RESPONSE_HEADER_NO_DATA),
+ Err(Error::InvalidHeader)
+ );
+
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events_no_data(&mut hconn_c);
+}
+
+#[test]
+fn informational_after_response_headers() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ request.send_headers(&RESPONSE_HEADER_NO_DATA).unwrap();
+
+ assert_eq!(
+ send_informational_headers(&mut request),
+ Err(Error::InvalidHeader)
+ );
+
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events_no_data(&mut hconn_c);
+}
+
+#[test]
+fn data_after_informational() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_informational_headers(&mut request).unwrap();
+
+ assert_eq!(request.send_data(RESPONSE_DATA), Err(Error::InvalidInput));
+
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn non_trailers_headers_after_data() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+
+ assert_eq!(
+ request.send_headers(&RESPONSE_HEADER_NO_DATA),
+ Err(Error::InvalidHeader)
+ );
+
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
+
+#[test]
+fn data_before_headers() {
+ let (mut hconn_c, mut hconn_s, mut request) = connect_send_and_receive_request();
+ assert_eq!(request.send_data(RESPONSE_DATA), Err(Error::InvalidInput));
+
+ send_headers(&mut request).unwrap();
+ request.send_data(RESPONSE_DATA).unwrap();
+ request.stream_close_send().unwrap();
+ exchange_packets(&mut hconn_c, &mut hconn_s);
+ process_client_events(&mut hconn_c);
+}
diff --git a/third_party/rust/neqo-http3/tests/webtransport.rs b/third_party/rust/neqo-http3/tests/webtransport.rs
new file mode 100644
index 0000000000..4e943d86cb
--- /dev/null
+++ b/third_party/rust/neqo-http3/tests/webtransport.rs
@@ -0,0 +1,320 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::{cell::RefCell, rc::Rc};
+
+use neqo_common::{event::Provider, Header};
+use neqo_crypto::AuthenticationStatus;
+use neqo_http3::{
+ Http3Client, Http3ClientEvent, Http3OrWebTransportStream, Http3Parameters, Http3Server,
+ Http3ServerEvent, Http3State, WebTransportEvent, WebTransportRequest, WebTransportServerEvent,
+ WebTransportSessionAcceptAction,
+};
+use neqo_transport::{StreamId, StreamType};
+use test_fixture::{
+ addr, anti_replay, fixture_init, now, CountingConnectionIdGenerator, DEFAULT_ALPN_H3,
+ DEFAULT_KEYS, DEFAULT_SERVER_NAME,
+};
+
+fn connect() -> (Http3Client, Http3Server) {
+ fixture_init();
+ let mut client = Http3Client::new(
+ DEFAULT_SERVER_NAME,
+ Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
+ addr(),
+ addr(),
+ Http3Parameters::default().webtransport(true),
+ now(),
+ )
+ .expect("create a default client");
+ let mut server = Http3Server::new(
+ now(),
+ DEFAULT_KEYS,
+ DEFAULT_ALPN_H3,
+ anti_replay(),
+ Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
+ Http3Parameters::default().webtransport(true),
+ None,
+ )
+ .expect("create a server");
+ assert_eq!(client.state(), Http3State::Initializing);
+ let out = client.process(None, now());
+ assert_eq!(client.state(), Http3State::Initializing);
+
+ let out = server.process(out.as_dgram_ref(), now());
+ let out = client.process(out.as_dgram_ref(), now());
+ let out = server.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_none());
+
+ let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
+ assert!(client.events().any(authentication_needed));
+ client.authenticated(AuthenticationStatus::Ok, now());
+
+ let mut out = client.process(out.as_dgram_ref(), now()).dgram();
+ let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected));
+ assert!(client.events().any(connected));
+
+ assert_eq!(client.state(), Http3State::Connected);
+
+ // Exchange H3 setttings
+ loop {
+ out = server.process(out.as_ref(), now()).dgram();
+ let dgram_present = out.is_some();
+ out = client.process(out.as_ref(), now()).dgram();
+ if out.is_none() && !dgram_present {
+ break;
+ }
+ }
+ (client, server)
+}
+
+fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
+ let mut out = None;
+ loop {
+ out = client.process(out.as_ref(), now()).dgram();
+ out = server.process(out.as_ref(), now()).dgram();
+ if out.is_none() {
+ break;
+ }
+ }
+}
+
+fn create_wt_session(client: &mut Http3Client, server: &mut Http3Server) -> WebTransportRequest {
+ let wt_session_id = client
+ .webtransport_create_session(now(), &("https", "something.com", "/"), &[])
+ .unwrap();
+ exchange_packets(client, server);
+
+ let mut wt_server_session = None;
+ while let Some(event) = server.next_event() {
+ match event {
+ Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession {
+ mut session,
+ headers,
+ }) => {
+ assert!(
+ headers
+ .iter()
+ .any(|h| h.name() == ":method" && h.value() == "CONNECT")
+ && headers
+ .iter()
+ .any(|h| h.name() == ":protocol" && h.value() == "webtransport")
+ );
+ session
+ .response(&WebTransportSessionAcceptAction::Accept)
+ .unwrap();
+ wt_server_session = Some(session);
+ }
+ Http3ServerEvent::Data { .. } => {
+ panic!("There should not be any data events!");
+ }
+ _ => {}
+ }
+ }
+
+ exchange_packets(client, server);
+
+ let wt_session_negotiated_event = |e| {
+ matches!(
+ e,
+ Http3ClientEvent::WebTransport(WebTransportEvent::Session{
+ stream_id,
+ status,
+ headers,
+ }) if (
+ stream_id == wt_session_id &&
+ status == 200 &&
+ headers.contains(&Header::new(":status", "200"))
+ )
+ )
+ };
+ assert!(client.events().any(wt_session_negotiated_event));
+
+ let wt_server_session = wt_server_session.unwrap();
+ assert_eq!(wt_session_id, wt_server_session.stream_id());
+ wt_server_session
+}
+
+fn send_data_client(
+ client: &mut Http3Client,
+ server: &mut Http3Server,
+ wt_stream_id: StreamId,
+ data: &[u8],
+) {
+ assert_eq!(client.send_data(wt_stream_id, data).unwrap(), data.len());
+ exchange_packets(client, server);
+}
+
+fn send_data_server(
+ client: &mut Http3Client,
+ server: &mut Http3Server,
+ wt_stream: &mut Http3OrWebTransportStream,
+ data: &[u8],
+) {
+ assert_eq!(wt_stream.send_data(data).unwrap(), data.len());
+ exchange_packets(client, server);
+}
+
+fn receive_data_client(
+ client: &mut Http3Client,
+ expected_stream_id: StreamId,
+ new_stream: bool,
+ expected_data: &[u8],
+ expected_fin: bool,
+) {
+ let mut new_stream_received = false;
+ let mut data_received = false;
+ while let Some(event) = client.next_event() {
+ match event {
+ Http3ClientEvent::WebTransport(WebTransportEvent::NewStream { stream_id, .. }) => {
+ assert_eq!(stream_id, expected_stream_id);
+ new_stream_received = true;
+ }
+ Http3ClientEvent::DataReadable { stream_id } => {
+ assert_eq!(stream_id, expected_stream_id);
+ let mut buf = [0; 100];
+ let (amount, fin) = client.read_data(now(), stream_id, &mut buf).unwrap();
+ assert_eq!(fin, expected_fin);
+ assert_eq!(amount, expected_data.len());
+ assert_eq!(&buf[..amount], expected_data);
+ data_received = true;
+ }
+ _ => {}
+ }
+ }
+ assert!(data_received);
+ assert_eq!(new_stream, new_stream_received);
+}
+
+fn receive_data_server(
+ client: &mut Http3Client,
+ server: &mut Http3Server,
+ stream_id: StreamId,
+ new_stream: bool,
+ expected_data: &[u8],
+ expected_fin: bool,
+) -> Http3OrWebTransportStream {
+ exchange_packets(client, server);
+ let mut new_stream_received = false;
+ let mut data_received = false;
+ let mut wt_stream = None;
+ let mut stream_closed = false;
+ let mut recv_data = Vec::new();
+ while let Some(event) = server.next_event() {
+ match event {
+ Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(request)) => {
+ assert_eq!(stream_id, request.stream_id());
+ new_stream_received = true;
+ }
+ Http3ServerEvent::Data {
+ mut data,
+ fin,
+ stream,
+ } => {
+ recv_data.append(&mut data);
+ stream_closed = fin;
+ data_received = true;
+ wt_stream = Some(stream);
+ }
+ _ => {}
+ }
+ }
+ assert_eq!(&recv_data[..], expected_data);
+ assert!(data_received);
+ assert_eq!(new_stream, new_stream_received);
+ assert_eq!(stream_closed, expected_fin);
+ wt_stream.unwrap()
+}
+
+#[test]
+fn wt_client_stream_uni() {
+ const BUF_CLIENT: &[u8] = &[0; 10];
+
+ let (mut client, mut server) = connect();
+ let wt_session = create_wt_session(&mut client, &mut server);
+ let wt_stream = client
+ .webtransport_create_stream(wt_session.stream_id(), StreamType::UniDi)
+ .unwrap();
+ send_data_client(&mut client, &mut server, wt_stream, BUF_CLIENT);
+ exchange_packets(&mut client, &mut server);
+ receive_data_server(&mut client, &mut server, wt_stream, true, BUF_CLIENT, false);
+}
+
+#[test]
+fn wt_client_stream_bidi() {
+ const BUF_CLIENT: &[u8] = &[0; 10];
+ const BUF_SERVER: &[u8] = &[1; 20];
+
+ let (mut client, mut server) = connect();
+ let wt_session = create_wt_session(&mut client, &mut server);
+ let wt_client_stream = client
+ .webtransport_create_stream(wt_session.stream_id(), StreamType::BiDi)
+ .unwrap();
+ send_data_client(&mut client, &mut server, wt_client_stream, BUF_CLIENT);
+ let mut wt_server_stream = receive_data_server(
+ &mut client,
+ &mut server,
+ wt_client_stream,
+ true,
+ BUF_CLIENT,
+ false,
+ );
+ send_data_server(&mut client, &mut server, &mut wt_server_stream, BUF_SERVER);
+ receive_data_client(&mut client, wt_client_stream, false, BUF_SERVER, false);
+}
+
+#[test]
+fn wt_server_stream_uni() {
+ const BUF_SERVER: &[u8] = &[2; 30];
+
+ let (mut client, mut server) = connect();
+ let mut wt_session = create_wt_session(&mut client, &mut server);
+ let mut wt_server_stream = wt_session.create_stream(StreamType::UniDi).unwrap();
+ send_data_server(&mut client, &mut server, &mut wt_server_stream, BUF_SERVER);
+ receive_data_client(
+ &mut client,
+ wt_server_stream.stream_id(),
+ true,
+ BUF_SERVER,
+ false,
+ );
+}
+
+#[test]
+fn wt_server_stream_bidi() {
+ const BUF_CLIENT: &[u8] = &[0; 10];
+ const BUF_SERVER: &[u8] = &[1; 20];
+
+ let (mut client, mut server) = connect();
+ let mut wt_session = create_wt_session(&mut client, &mut server);
+ let mut wt_server_stream = wt_session.create_stream(StreamType::BiDi).unwrap();
+ send_data_server(&mut client, &mut server, &mut wt_server_stream, BUF_SERVER);
+ receive_data_client(
+ &mut client,
+ wt_server_stream.stream_id(),
+ true,
+ BUF_SERVER,
+ false,
+ );
+ send_data_client(
+ &mut client,
+ &mut server,
+ wt_server_stream.stream_id(),
+ BUF_CLIENT,
+ );
+ assert_eq!(
+ receive_data_server(
+ &mut client,
+ &mut server,
+ wt_server_stream.stream_id(),
+ false,
+ BUF_CLIENT,
+ false
+ )
+ .stream_id(),
+ wt_server_stream.stream_id()
+ );
+}