diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/neqo-http3/tests | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/neqo-http3/tests')
-rw-r--r-- | third_party/rust/neqo-http3/tests/httpconn.rs | 460 | ||||
-rw-r--r-- | third_party/rust/neqo-http3/tests/priority.rs | 149 | ||||
-rw-r--r-- | third_party/rust/neqo-http3/tests/send_message.rs | 329 | ||||
-rw-r--r-- | third_party/rust/neqo-http3/tests/webtransport.rs | 315 |
4 files changed, 1253 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..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..f5ba76ced8 --- /dev/null +++ b/third_party/rust/neqo-http3/tests/webtransport.rs @@ -0,0 +1,315 @@ +// 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::{ + 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 + }) if stream_id == wt_session_id && 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() + ); +} |