summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-transport/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/neqo-transport/tests
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/neqo-transport/tests')
-rw-r--r--third_party/rust/neqo-transport/tests/common/mod.rs235
-rw-r--r--third_party/rust/neqo-transport/tests/conn_vectors.rs286
-rw-r--r--third_party/rust/neqo-transport/tests/connection.rs208
-rw-r--r--third_party/rust/neqo-transport/tests/network.rs177
-rw-r--r--third_party/rust/neqo-transport/tests/retry.rs475
-rw-r--r--third_party/rust/neqo-transport/tests/server.rs779
-rw-r--r--third_party/rust/neqo-transport/tests/sim/connection.rs315
-rw-r--r--third_party/rust/neqo-transport/tests/sim/delay.rs102
-rw-r--r--third_party/rust/neqo-transport/tests/sim/drop.rs75
-rw-r--r--third_party/rust/neqo-transport/tests/sim/mod.rs232
-rw-r--r--third_party/rust/neqo-transport/tests/sim/net.rs111
-rw-r--r--third_party/rust/neqo-transport/tests/sim/rng.rs81
-rw-r--r--third_party/rust/neqo-transport/tests/sim/taildrop.rs188
13 files changed, 3264 insertions, 0 deletions
diff --git a/third_party/rust/neqo-transport/tests/common/mod.rs b/third_party/rust/neqo-transport/tests/common/mod.rs
new file mode 100644
index 0000000000..a43f91e3fe
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/common/mod.rs
@@ -0,0 +1,235 @@
+// 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)]
+#![allow(unused)]
+
+use std::{cell::RefCell, convert::TryFrom, mem, ops::Range, rc::Rc};
+
+use neqo_common::{event::Provider, hex_with_len, qtrace, Datagram, Decoder, Role};
+use neqo_crypto::{
+ constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
+ hkdf,
+ hp::HpKey,
+ Aead, AllowZeroRtt, AuthenticationStatus, ResumptionToken,
+};
+use neqo_transport::{
+ server::{ActiveConnectionRef, Server, ValidateAddress},
+ Connection, ConnectionEvent, ConnectionParameters, State,
+};
+use test_fixture::{self, default_client, now, CountingConnectionIdGenerator};
+
+/// Create a server. This is different than the one in the fixture, which is a single connection.
+pub fn new_server(params: ConnectionParameters) -> Server {
+ Server::new(
+ now(),
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ test_fixture::anti_replay(),
+ Box::new(AllowZeroRtt {}),
+ Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
+ params,
+ )
+ .expect("should create a server")
+}
+
+/// Create a server. This is different than the one in the fixture, which is a single connection.
+pub fn default_server() -> Server {
+ new_server(ConnectionParameters::default())
+}
+
+// Check that there is at least one connection. Returns a ref to the first confirmed connection.
+pub fn connected_server(server: &mut Server) -> ActiveConnectionRef {
+ let server_connections = server.active_connections();
+ // Find confirmed connections. There should only be one.
+ let mut confirmed = server_connections
+ .iter()
+ .filter(|c: &&ActiveConnectionRef| *c.borrow().state() == State::Confirmed);
+ let c = confirmed.next().expect("one confirmed");
+ assert!(confirmed.next().is_none(), "only one confirmed");
+ c.clone()
+}
+
+/// Connect. This returns a reference to the server connection.
+pub fn connect(client: &mut Connection, server: &mut Server) -> ActiveConnectionRef {
+ server.set_validation(ValidateAddress::Never);
+
+ assert_eq!(*client.state(), State::Init);
+ let out = client.process(None, now()); // ClientHello
+ assert!(out.as_dgram_ref().is_some());
+ let out = server.process(out.as_dgram_ref(), now()); // ServerHello...
+ assert!(out.as_dgram_ref().is_some());
+
+ // Ingest the server Certificate.
+ let out = client.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_some()); // This should just be an ACK.
+ let out = server.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_none()); // So the server should have nothing to say.
+
+ // Now mark the server as authenticated.
+ client.authenticated(AuthenticationStatus::Ok, now());
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(*client.state(), State::Connected);
+ let out = server.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_some()); // ACK + HANDSHAKE_DONE + NST
+
+ // Have the client process the HANDSHAKE_DONE.
+ let out = client.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_none());
+ assert_eq!(*client.state(), State::Confirmed);
+
+ connected_server(server)
+}
+
+// Decode the header of a client Initial packet, returning three values:
+// * the entire header short of the packet number,
+// * just the DCID,
+// * just the SCID, and
+// * the protected payload including the packet number.
+// Any token is thrown away.
+#[must_use]
+pub fn decode_initial_header(dgram: &Datagram, role: Role) -> (&[u8], &[u8], &[u8], &[u8]) {
+ let mut dec = Decoder::new(&dgram[..]);
+ let type_and_ver = dec.decode(5).unwrap().to_vec();
+ // The client sets the QUIC bit, the server might not.
+ match role {
+ Role::Client => assert_eq!(type_and_ver[0] & 0xf0, 0xc0),
+ Role::Server => assert_eq!(type_and_ver[0] & 0xb0, 0x80),
+ }
+ let dest_cid = dec.decode_vec(1).unwrap();
+ let src_cid = dec.decode_vec(1).unwrap();
+ dec.skip_vvec(); // Ignore any the token.
+
+ // Need to read of the length separately so that we can find the packet number.
+ let payload_len = usize::try_from(dec.decode_varint().unwrap()).unwrap();
+ let pn_offset = dgram.len() - dec.remaining();
+ (
+ &dgram[..pn_offset],
+ dest_cid,
+ src_cid,
+ dec.decode(payload_len).unwrap(),
+ )
+}
+
+/// Generate an AEAD and header protection object for a client Initial.
+/// Note that this works for QUIC version 1 only.
+#[must_use]
+pub fn initial_aead_and_hp(dcid: &[u8], role: Role) -> (Aead, HpKey) {
+ const INITIAL_SALT: &[u8] = &[
+ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c,
+ 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
+ ];
+ let initial_secret = hkdf::extract(
+ TLS_VERSION_1_3,
+ TLS_AES_128_GCM_SHA256,
+ Some(
+ hkdf::import_key(TLS_VERSION_1_3, INITIAL_SALT)
+ .as_ref()
+ .unwrap(),
+ ),
+ hkdf::import_key(TLS_VERSION_1_3, dcid).as_ref().unwrap(),
+ )
+ .unwrap();
+
+ let secret = hkdf::expand_label(
+ TLS_VERSION_1_3,
+ TLS_AES_128_GCM_SHA256,
+ &initial_secret,
+ &[],
+ match role {
+ Role::Client => "client in",
+ Role::Server => "server in",
+ },
+ )
+ .unwrap();
+ (
+ Aead::new(
+ false,
+ TLS_VERSION_1_3,
+ TLS_AES_128_GCM_SHA256,
+ &secret,
+ "quic ",
+ )
+ .unwrap(),
+ HpKey::extract(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic hp").unwrap(),
+ )
+}
+
+// Remove header protection, returning the unmasked header and the packet number.
+#[must_use]
+pub fn remove_header_protection(hp: &HpKey, header: &[u8], payload: &[u8]) -> (Vec<u8>, u64) {
+ // Make a copy of the header that can be modified.
+ let mut fixed_header = header.to_vec();
+ let pn_offset = header.len();
+ // Save 4 extra in case the packet number is that long.
+ fixed_header.extend_from_slice(&payload[..4]);
+
+ // Sample for masking and apply the mask.
+ let mask = hp.mask(&payload[4..20]).unwrap();
+ fixed_header[0] ^= mask[0] & 0xf;
+ let pn_len = 1 + usize::from(fixed_header[0] & 0x3);
+ for i in 0..pn_len {
+ fixed_header[pn_offset + i] ^= mask[1 + i];
+ }
+ // Trim down to size.
+ fixed_header.truncate(pn_offset + pn_len);
+ // The packet number should be 1.
+ let pn = Decoder::new(&fixed_header[pn_offset..])
+ .decode_uint(pn_len)
+ .unwrap();
+
+ (fixed_header, pn)
+}
+
+pub fn apply_header_protection(hp: &HpKey, packet: &mut [u8], pn_bytes: Range<usize>) {
+ let sample_start = pn_bytes.start + 4;
+ let sample_end = sample_start + 16;
+ let mask = hp.mask(&packet[sample_start..sample_end]).unwrap();
+ qtrace!(
+ "sample={} mask={}",
+ hex_with_len(&packet[sample_start..sample_end]),
+ hex_with_len(&mask)
+ );
+ packet[0] ^= mask[0] & 0xf;
+ for i in 0..(pn_bytes.end - pn_bytes.start) {
+ packet[pn_bytes.start + i] ^= mask[1 + i];
+ }
+}
+
+/// Scrub through client events to find a resumption token.
+pub fn find_ticket(client: &mut Connection) -> ResumptionToken {
+ client
+ .events()
+ .find_map(|e| {
+ if let ConnectionEvent::ResumptionToken(token) = e {
+ Some(token)
+ } else {
+ None
+ }
+ })
+ .unwrap()
+}
+
+/// Connect to the server and have it generate a ticket.
+pub fn generate_ticket(server: &mut Server) -> ResumptionToken {
+ let mut client = default_client();
+ let mut server_conn = connect(&mut client, server);
+
+ server_conn.borrow_mut().send_ticket(now(), &[]).unwrap();
+ let out = server.process(None, now());
+ client.process_input(out.as_dgram_ref().unwrap(), now()); // Consume ticket, ignore output.
+ let ticket = find_ticket(&mut client);
+
+ // Have the client close the connection and then let the server clean up.
+ client.close(now(), 0, "got a ticket");
+ let out = client.process_output(now());
+ mem::drop(server.process(out.as_dgram_ref(), now()));
+ // Calling active_connections clears the set of active connections.
+ assert_eq!(server.active_connections().len(), 1);
+ ticket
+}
diff --git a/third_party/rust/neqo-transport/tests/conn_vectors.rs b/third_party/rust/neqo-transport/tests/conn_vectors.rs
new file mode 100644
index 0000000000..91dbbf31cc
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/conn_vectors.rs
@@ -0,0 +1,286 @@
+// 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.
+
+// Tests with the test vectors from the spec.
+#![deny(clippy::pedantic)]
+#![cfg(not(feature = "fuzzing"))]
+
+use std::{cell::RefCell, rc::Rc};
+
+use neqo_transport::{
+ Connection, ConnectionParameters, RandomConnectionIdGenerator, State, Version,
+};
+use test_fixture::{self, datagram, now};
+
+const INITIAL_PACKET_V2: &[u8] = &[
+ 0xd7, 0x6b, 0x33, 0x43, 0xcf, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, 0x00,
+ 0x44, 0x9e, 0xa0, 0xc9, 0x5e, 0x82, 0xff, 0xe6, 0x7b, 0x6a, 0xbc, 0xdb, 0x42, 0x98, 0xb4, 0x85,
+ 0xdd, 0x04, 0xde, 0x80, 0x60, 0x71, 0xbf, 0x03, 0xdc, 0xee, 0xbf, 0xa1, 0x62, 0xe7, 0x5d, 0x6c,
+ 0x96, 0x05, 0x8b, 0xdb, 0xfb, 0x12, 0x7c, 0xdf, 0xcb, 0xf9, 0x03, 0x38, 0x8e, 0x99, 0xad, 0x04,
+ 0x9f, 0x9a, 0x3d, 0xd4, 0x42, 0x5a, 0xe4, 0xd0, 0x99, 0x2c, 0xff, 0xf1, 0x8e, 0xcf, 0x0f, 0xdb,
+ 0x5a, 0x84, 0x2d, 0x09, 0x74, 0x70, 0x52, 0xf1, 0x7a, 0xc2, 0x05, 0x3d, 0x21, 0xf5, 0x7c, 0x5d,
+ 0x25, 0x0f, 0x2c, 0x4f, 0x0e, 0x02, 0x02, 0xb7, 0x07, 0x85, 0xb7, 0x94, 0x6e, 0x99, 0x2e, 0x58,
+ 0xa5, 0x9a, 0xc5, 0x2d, 0xea, 0x67, 0x74, 0xd4, 0xf0, 0x3b, 0x55, 0x54, 0x52, 0x43, 0xcf, 0x1a,
+ 0x12, 0x83, 0x4e, 0x3f, 0x24, 0x9a, 0x78, 0xd3, 0x95, 0xe0, 0xd1, 0x8f, 0x4d, 0x76, 0x60, 0x04,
+ 0xf1, 0xa2, 0x67, 0x48, 0x02, 0xa7, 0x47, 0xea, 0xa9, 0x01, 0xc3, 0xf1, 0x0c, 0xda, 0x55, 0x00,
+ 0xcb, 0x91, 0x22, 0xfa, 0xa9, 0xf1, 0xdf, 0x66, 0xc3, 0x92, 0x07, 0x9a, 0x1b, 0x40, 0xf0, 0xde,
+ 0x1c, 0x60, 0x54, 0x19, 0x6a, 0x11, 0xcb, 0xea, 0x40, 0xaf, 0xb6, 0xef, 0x52, 0x53, 0xcd, 0x68,
+ 0x18, 0xf6, 0x62, 0x5e, 0xfc, 0xe3, 0xb6, 0xde, 0xf6, 0xba, 0x7e, 0x4b, 0x37, 0xa4, 0x0f, 0x77,
+ 0x32, 0xe0, 0x93, 0xda, 0xa7, 0xd5, 0x21, 0x90, 0x93, 0x5b, 0x8d, 0xa5, 0x89, 0x76, 0xff, 0x33,
+ 0x12, 0xae, 0x50, 0xb1, 0x87, 0xc1, 0x43, 0x3c, 0x0f, 0x02, 0x8e, 0xdc, 0xc4, 0xc2, 0x83, 0x8b,
+ 0x6a, 0x9b, 0xfc, 0x22, 0x6c, 0xa4, 0xb4, 0x53, 0x0e, 0x7a, 0x4c, 0xce, 0xe1, 0xbf, 0xa2, 0xa3,
+ 0xd3, 0x96, 0xae, 0x5a, 0x3f, 0xb5, 0x12, 0x38, 0x4b, 0x2f, 0xdd, 0x85, 0x1f, 0x78, 0x4a, 0x65,
+ 0xe0, 0x3f, 0x2c, 0x4f, 0xbe, 0x11, 0xa5, 0x3c, 0x77, 0x77, 0xc0, 0x23, 0x46, 0x22, 0x39, 0xdd,
+ 0x6f, 0x75, 0x21, 0xa3, 0xf6, 0xc7, 0xd5, 0xdd, 0x3e, 0xc9, 0xb3, 0xf2, 0x33, 0x77, 0x3d, 0x4b,
+ 0x46, 0xd2, 0x3c, 0xc3, 0x75, 0xeb, 0x19, 0x8c, 0x63, 0x30, 0x1c, 0x21, 0x80, 0x1f, 0x65, 0x20,
+ 0xbc, 0xfb, 0x79, 0x66, 0xfc, 0x49, 0xb3, 0x93, 0xf0, 0x06, 0x1d, 0x97, 0x4a, 0x27, 0x06, 0xdf,
+ 0x8c, 0x4a, 0x94, 0x49, 0xf1, 0x1d, 0x7f, 0x3d, 0x2d, 0xcb, 0xb9, 0x0c, 0x6b, 0x87, 0x70, 0x45,
+ 0x63, 0x6e, 0x7c, 0x0c, 0x0f, 0xe4, 0xeb, 0x0f, 0x69, 0x75, 0x45, 0x46, 0x0c, 0x80, 0x69, 0x10,
+ 0xd2, 0xc3, 0x55, 0xf1, 0xd2, 0x53, 0xbc, 0x9d, 0x24, 0x52, 0xaa, 0xa5, 0x49, 0xe2, 0x7a, 0x1f,
+ 0xac, 0x7c, 0xf4, 0xed, 0x77, 0xf3, 0x22, 0xe8, 0xfa, 0x89, 0x4b, 0x6a, 0x83, 0x81, 0x0a, 0x34,
+ 0xb3, 0x61, 0x90, 0x17, 0x51, 0xa6, 0xf5, 0xeb, 0x65, 0xa0, 0x32, 0x6e, 0x07, 0xde, 0x7c, 0x12,
+ 0x16, 0xcc, 0xce, 0x2d, 0x01, 0x93, 0xf9, 0x58, 0xbb, 0x38, 0x50, 0xa8, 0x33, 0xf7, 0xae, 0x43,
+ 0x2b, 0x65, 0xbc, 0x5a, 0x53, 0x97, 0x5c, 0x15, 0x5a, 0xa4, 0xbc, 0xb4, 0xf7, 0xb2, 0xc4, 0xe5,
+ 0x4d, 0xf1, 0x6e, 0xfa, 0xf6, 0xdd, 0xea, 0x94, 0xe2, 0xc5, 0x0b, 0x4c, 0xd1, 0xdf, 0xe0, 0x60,
+ 0x17, 0xe0, 0xe9, 0xd0, 0x29, 0x00, 0xcf, 0xfe, 0x19, 0x35, 0xe0, 0x49, 0x1d, 0x77, 0xff, 0xb4,
+ 0xfd, 0xf8, 0x52, 0x90, 0xfd, 0xd8, 0x93, 0xd5, 0x77, 0xb1, 0x13, 0x1a, 0x61, 0x0e, 0xf6, 0xa5,
+ 0xc3, 0x2b, 0x2e, 0xe0, 0x29, 0x36, 0x17, 0xa3, 0x7c, 0xbb, 0x08, 0xb8, 0x47, 0x74, 0x1c, 0x3b,
+ 0x80, 0x17, 0xc2, 0x5c, 0xa9, 0x05, 0x2c, 0xa1, 0x07, 0x9d, 0x8b, 0x78, 0xae, 0xbd, 0x47, 0x87,
+ 0x6d, 0x33, 0x0a, 0x30, 0xf6, 0xa8, 0xc6, 0xd6, 0x1d, 0xd1, 0xab, 0x55, 0x89, 0x32, 0x9d, 0xe7,
+ 0x14, 0xd1, 0x9d, 0x61, 0x37, 0x0f, 0x81, 0x49, 0x74, 0x8c, 0x72, 0xf1, 0x32, 0xf0, 0xfc, 0x99,
+ 0xf3, 0x4d, 0x76, 0x6c, 0x69, 0x38, 0x59, 0x70, 0x40, 0xd8, 0xf9, 0xe2, 0xbb, 0x52, 0x2f, 0xf9,
+ 0x9c, 0x63, 0xa3, 0x44, 0xd6, 0xa2, 0xae, 0x8a, 0xa8, 0xe5, 0x1b, 0x7b, 0x90, 0xa4, 0xa8, 0x06,
+ 0x10, 0x5f, 0xcb, 0xca, 0x31, 0x50, 0x6c, 0x44, 0x61, 0x51, 0xad, 0xfe, 0xce, 0xb5, 0x1b, 0x91,
+ 0xab, 0xfe, 0x43, 0x96, 0x09, 0x77, 0xc8, 0x74, 0x71, 0xcf, 0x9a, 0xd4, 0x07, 0x4d, 0x30, 0xe1,
+ 0x0d, 0x6a, 0x7f, 0x03, 0xc6, 0x3b, 0xd5, 0xd4, 0x31, 0x7f, 0x68, 0xff, 0x32, 0x5b, 0xa3, 0xbd,
+ 0x80, 0xbf, 0x4d, 0xc8, 0xb5, 0x2a, 0x0b, 0xa0, 0x31, 0x75, 0x80, 0x22, 0xeb, 0x02, 0x5c, 0xdd,
+ 0x77, 0x0b, 0x44, 0xd6, 0xd6, 0xcf, 0x06, 0x70, 0xf4, 0xe9, 0x90, 0xb2, 0x23, 0x47, 0xa7, 0xdb,
+ 0x84, 0x82, 0x65, 0xe3, 0xe5, 0xeb, 0x72, 0xdf, 0xe8, 0x29, 0x9a, 0xd7, 0x48, 0x1a, 0x40, 0x83,
+ 0x22, 0xca, 0xc5, 0x57, 0x86, 0xe5, 0x2f, 0x63, 0x3b, 0x2f, 0xb6, 0xb6, 0x14, 0xea, 0xed, 0x18,
+ 0xd7, 0x03, 0xdd, 0x84, 0x04, 0x5a, 0x27, 0x4a, 0xe8, 0xbf, 0xa7, 0x33, 0x79, 0x66, 0x13, 0x88,
+ 0xd6, 0x99, 0x1f, 0xe3, 0x9b, 0x0d, 0x93, 0xde, 0xbb, 0x41, 0x70, 0x0b, 0x41, 0xf9, 0x0a, 0x15,
+ 0xc4, 0xd5, 0x26, 0x25, 0x02, 0x35, 0xdd, 0xcd, 0x67, 0x76, 0xfc, 0x77, 0xbc, 0x97, 0xe7, 0xa4,
+ 0x17, 0xeb, 0xcb, 0x31, 0x60, 0x0d, 0x01, 0xe5, 0x7f, 0x32, 0x16, 0x2a, 0x85, 0x60, 0xca, 0xcc,
+ 0x7e, 0x27, 0xa0, 0x96, 0xd3, 0x7a, 0x1a, 0x86, 0x95, 0x2e, 0xc7, 0x1b, 0xd8, 0x9a, 0x3e, 0x9a,
+ 0x30, 0xa2, 0xa2, 0x61, 0x62, 0x98, 0x4d, 0x77, 0x40, 0xf8, 0x11, 0x93, 0xe8, 0x23, 0x8e, 0x61,
+ 0xf6, 0xb5, 0xb9, 0x84, 0xd4, 0xd3, 0xdf, 0xa0, 0x33, 0xc1, 0xbb, 0x7e, 0x4f, 0x00, 0x37, 0xfe,
+ 0xbf, 0x40, 0x6d, 0x91, 0xc0, 0xdc, 0xcf, 0x32, 0xac, 0xf4, 0x23, 0xcf, 0xa1, 0xe7, 0x07, 0x10,
+ 0x10, 0xd3, 0xf2, 0x70, 0x12, 0x1b, 0x49, 0x3c, 0xe8, 0x50, 0x54, 0xef, 0x58, 0xba, 0xda, 0x42,
+ 0x31, 0x01, 0x38, 0xfe, 0x08, 0x1a, 0xdb, 0x04, 0xe2, 0xbd, 0x90, 0x1f, 0x2f, 0x13, 0x45, 0x8b,
+ 0x3d, 0x67, 0x58, 0x15, 0x81, 0x97, 0x10, 0x7c, 0x14, 0xeb, 0xb1, 0x93, 0x23, 0x0c, 0xd1, 0x15,
+ 0x73, 0x80, 0xaa, 0x79, 0xca, 0xe1, 0x37, 0x4a, 0x7c, 0x1e, 0x5b, 0xbc, 0xb8, 0x0e, 0xe2, 0x3e,
+ 0x06, 0xeb, 0xfd, 0xe2, 0x06, 0xbf, 0xb0, 0xfc, 0xbc, 0x0e, 0xdc, 0x4e, 0xbe, 0xc3, 0x09, 0x66,
+ 0x1b, 0xdd, 0x90, 0x8d, 0x53, 0x2e, 0xb0, 0xc6, 0xad, 0xc3, 0x8b, 0x7c, 0xa7, 0x33, 0x1d, 0xce,
+ 0x8d, 0xfc, 0xe3, 0x9a, 0xb7, 0x1e, 0x7c, 0x32, 0xd3, 0x18, 0xd1, 0x36, 0xb6, 0x10, 0x06, 0x71,
+ 0xa1, 0xae, 0x6a, 0x66, 0x00, 0xe3, 0x89, 0x9f, 0x31, 0xf0, 0xee, 0xd1, 0x9e, 0x34, 0x17, 0xd1,
+ 0x34, 0xb9, 0x0c, 0x90, 0x58, 0xf8, 0x63, 0x2c, 0x79, 0x8d, 0x44, 0x90, 0xda, 0x49, 0x87, 0x30,
+ 0x7c, 0xba, 0x92, 0x2d, 0x61, 0xc3, 0x98, 0x05, 0xd0, 0x72, 0xb5, 0x89, 0xbd, 0x52, 0xfd, 0xf1,
+ 0xe8, 0x62, 0x15, 0xc2, 0xd5, 0x4e, 0x66, 0x70, 0xe0, 0x73, 0x83, 0xa2, 0x7b, 0xbf, 0xfb, 0x5a,
+ 0xdd, 0xf4, 0x7d, 0x66, 0xaa, 0x85, 0xa0, 0xc6, 0xf9, 0xf3, 0x2e, 0x59, 0xd8, 0x5a, 0x44, 0xdd,
+ 0x5d, 0x3b, 0x22, 0xdc, 0x2b, 0xe8, 0x09, 0x19, 0xb4, 0x90, 0x43, 0x7a, 0xe4, 0xf3, 0x6a, 0x0a,
+ 0xe5, 0x5e, 0xdf, 0x1d, 0x0b, 0x5c, 0xb4, 0xe9, 0xa3, 0xec, 0xab, 0xee, 0x93, 0xdf, 0xc6, 0xe3,
+ 0x8d, 0x20, 0x9d, 0x0f, 0xa6, 0x53, 0x6d, 0x27, 0xa5, 0xd6, 0xfb, 0xb1, 0x76, 0x41, 0xcd, 0xe2,
+ 0x75, 0x25, 0xd6, 0x10, 0x93, 0xf1, 0xb2, 0x80, 0x72, 0xd1, 0x11, 0xb2, 0xb4, 0xae, 0x5f, 0x89,
+ 0xd5, 0x97, 0x4e, 0xe1, 0x2e, 0x5c, 0xf7, 0xd5, 0xda, 0x4d, 0x6a, 0x31, 0x12, 0x30, 0x41, 0xf3,
+ 0x3e, 0x61, 0x40, 0x7e, 0x76, 0xcf, 0xfc, 0xdc, 0xfd, 0x7e, 0x19, 0xba, 0x58, 0xcf, 0x4b, 0x53,
+ 0x6f, 0x4c, 0x49, 0x38, 0xae, 0x79, 0x32, 0x4d, 0xc4, 0x02, 0x89, 0x4b, 0x44, 0xfa, 0xf8, 0xaf,
+ 0xba, 0xb3, 0x52, 0x82, 0xab, 0x65, 0x9d, 0x13, 0xc9, 0x3f, 0x70, 0x41, 0x2e, 0x85, 0xcb, 0x19,
+ 0x9a, 0x37, 0xdd, 0xec, 0x60, 0x05, 0x45, 0x47, 0x3c, 0xfb, 0x5a, 0x05, 0xe0, 0x8d, 0x0b, 0x20,
+ 0x99, 0x73, 0xb2, 0x17, 0x2b, 0x4d, 0x21, 0xfb, 0x69, 0x74, 0x5a, 0x26, 0x2c, 0xcd, 0xe9, 0x6b,
+ 0xa1, 0x8b, 0x2f, 0xaa, 0x74, 0x5b, 0x6f, 0xe1, 0x89, 0xcf, 0x77, 0x2a, 0x9f, 0x84, 0xcb, 0xfc,
+];
+
+const INITIAL_PACKET_V1: &[u8] = &[
+ 0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, 0x00,
+ 0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34, 0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, 0x9f, 0xb8, 0xec, 0x11,
+ 0xd2, 0x42, 0xb1, 0x23, 0xdc, 0x9b, 0xd8, 0xba, 0xb9, 0x36, 0xb4, 0x7d, 0x92, 0xec, 0x35, 0x6c,
+ 0x0b, 0xab, 0x7d, 0xf5, 0x97, 0x6d, 0x27, 0xcd, 0x44, 0x9f, 0x63, 0x30, 0x00, 0x99, 0xf3, 0x99,
+ 0x1c, 0x26, 0x0e, 0xc4, 0xc6, 0x0d, 0x17, 0xb3, 0x1f, 0x84, 0x29, 0x15, 0x7b, 0xb3, 0x5a, 0x12,
+ 0x82, 0xa6, 0x43, 0xa8, 0xd2, 0x26, 0x2c, 0xad, 0x67, 0x50, 0x0c, 0xad, 0xb8, 0xe7, 0x37, 0x8c,
+ 0x8e, 0xb7, 0x53, 0x9e, 0xc4, 0xd4, 0x90, 0x5f, 0xed, 0x1b, 0xee, 0x1f, 0xc8, 0xaa, 0xfb, 0xa1,
+ 0x7c, 0x75, 0x0e, 0x2c, 0x7a, 0xce, 0x01, 0xe6, 0x00, 0x5f, 0x80, 0xfc, 0xb7, 0xdf, 0x62, 0x12,
+ 0x30, 0xc8, 0x37, 0x11, 0xb3, 0x93, 0x43, 0xfa, 0x02, 0x8c, 0xea, 0x7f, 0x7f, 0xb5, 0xff, 0x89,
+ 0xea, 0xc2, 0x30, 0x82, 0x49, 0xa0, 0x22, 0x52, 0x15, 0x5e, 0x23, 0x47, 0xb6, 0x3d, 0x58, 0xc5,
+ 0x45, 0x7a, 0xfd, 0x84, 0xd0, 0x5d, 0xff, 0xfd, 0xb2, 0x03, 0x92, 0x84, 0x4a, 0xe8, 0x12, 0x15,
+ 0x46, 0x82, 0xe9, 0xcf, 0x01, 0x2f, 0x90, 0x21, 0xa6, 0xf0, 0xbe, 0x17, 0xdd, 0xd0, 0xc2, 0x08,
+ 0x4d, 0xce, 0x25, 0xff, 0x9b, 0x06, 0xcd, 0xe5, 0x35, 0xd0, 0xf9, 0x20, 0xa2, 0xdb, 0x1b, 0xf3,
+ 0x62, 0xc2, 0x3e, 0x59, 0x6d, 0x11, 0xa4, 0xf5, 0xa6, 0xcf, 0x39, 0x48, 0x83, 0x8a, 0x3a, 0xec,
+ 0x4e, 0x15, 0xda, 0xf8, 0x50, 0x0a, 0x6e, 0xf6, 0x9e, 0xc4, 0xe3, 0xfe, 0xb6, 0xb1, 0xd9, 0x8e,
+ 0x61, 0x0a, 0xc8, 0xb7, 0xec, 0x3f, 0xaf, 0x6a, 0xd7, 0x60, 0xb7, 0xba, 0xd1, 0xdb, 0x4b, 0xa3,
+ 0x48, 0x5e, 0x8a, 0x94, 0xdc, 0x25, 0x0a, 0xe3, 0xfd, 0xb4, 0x1e, 0xd1, 0x5f, 0xb6, 0xa8, 0xe5,
+ 0xeb, 0xa0, 0xfc, 0x3d, 0xd6, 0x0b, 0xc8, 0xe3, 0x0c, 0x5c, 0x42, 0x87, 0xe5, 0x38, 0x05, 0xdb,
+ 0x05, 0x9a, 0xe0, 0x64, 0x8d, 0xb2, 0xf6, 0x42, 0x64, 0xed, 0x5e, 0x39, 0xbe, 0x2e, 0x20, 0xd8,
+ 0x2d, 0xf5, 0x66, 0xda, 0x8d, 0xd5, 0x99, 0x8c, 0xca, 0xbd, 0xae, 0x05, 0x30, 0x60, 0xae, 0x6c,
+ 0x7b, 0x43, 0x78, 0xe8, 0x46, 0xd2, 0x9f, 0x37, 0xed, 0x7b, 0x4e, 0xa9, 0xec, 0x5d, 0x82, 0xe7,
+ 0x96, 0x1b, 0x7f, 0x25, 0xa9, 0x32, 0x38, 0x51, 0xf6, 0x81, 0xd5, 0x82, 0x36, 0x3a, 0xa5, 0xf8,
+ 0x99, 0x37, 0xf5, 0xa6, 0x72, 0x58, 0xbf, 0x63, 0xad, 0x6f, 0x1a, 0x0b, 0x1d, 0x96, 0xdb, 0xd4,
+ 0xfa, 0xdd, 0xfc, 0xef, 0xc5, 0x26, 0x6b, 0xa6, 0x61, 0x17, 0x22, 0x39, 0x5c, 0x90, 0x65, 0x56,
+ 0xbe, 0x52, 0xaf, 0xe3, 0xf5, 0x65, 0x63, 0x6a, 0xd1, 0xb1, 0x7d, 0x50, 0x8b, 0x73, 0xd8, 0x74,
+ 0x3e, 0xeb, 0x52, 0x4b, 0xe2, 0x2b, 0x3d, 0xcb, 0xc2, 0xc7, 0x46, 0x8d, 0x54, 0x11, 0x9c, 0x74,
+ 0x68, 0x44, 0x9a, 0x13, 0xd8, 0xe3, 0xb9, 0x58, 0x11, 0xa1, 0x98, 0xf3, 0x49, 0x1d, 0xe3, 0xe7,
+ 0xfe, 0x94, 0x2b, 0x33, 0x04, 0x07, 0xab, 0xf8, 0x2a, 0x4e, 0xd7, 0xc1, 0xb3, 0x11, 0x66, 0x3a,
+ 0xc6, 0x98, 0x90, 0xf4, 0x15, 0x70, 0x15, 0x85, 0x3d, 0x91, 0xe9, 0x23, 0x03, 0x7c, 0x22, 0x7a,
+ 0x33, 0xcd, 0xd5, 0xec, 0x28, 0x1c, 0xa3, 0xf7, 0x9c, 0x44, 0x54, 0x6b, 0x9d, 0x90, 0xca, 0x00,
+ 0xf0, 0x64, 0xc9, 0x9e, 0x3d, 0xd9, 0x79, 0x11, 0xd3, 0x9f, 0xe9, 0xc5, 0xd0, 0xb2, 0x3a, 0x22,
+ 0x9a, 0x23, 0x4c, 0xb3, 0x61, 0x86, 0xc4, 0x81, 0x9e, 0x8b, 0x9c, 0x59, 0x27, 0x72, 0x66, 0x32,
+ 0x29, 0x1d, 0x6a, 0x41, 0x82, 0x11, 0xcc, 0x29, 0x62, 0xe2, 0x0f, 0xe4, 0x7f, 0xeb, 0x3e, 0xdf,
+ 0x33, 0x0f, 0x2c, 0x60, 0x3a, 0x9d, 0x48, 0xc0, 0xfc, 0xb5, 0x69, 0x9d, 0xbf, 0xe5, 0x89, 0x64,
+ 0x25, 0xc5, 0xba, 0xc4, 0xae, 0xe8, 0x2e, 0x57, 0xa8, 0x5a, 0xaf, 0x4e, 0x25, 0x13, 0xe4, 0xf0,
+ 0x57, 0x96, 0xb0, 0x7b, 0xa2, 0xee, 0x47, 0xd8, 0x05, 0x06, 0xf8, 0xd2, 0xc2, 0x5e, 0x50, 0xfd,
+ 0x14, 0xde, 0x71, 0xe6, 0xc4, 0x18, 0x55, 0x93, 0x02, 0xf9, 0x39, 0xb0, 0xe1, 0xab, 0xd5, 0x76,
+ 0xf2, 0x79, 0xc4, 0xb2, 0xe0, 0xfe, 0xb8, 0x5c, 0x1f, 0x28, 0xff, 0x18, 0xf5, 0x88, 0x91, 0xff,
+ 0xef, 0x13, 0x2e, 0xef, 0x2f, 0xa0, 0x93, 0x46, 0xae, 0xe3, 0x3c, 0x28, 0xeb, 0x13, 0x0f, 0xf2,
+ 0x8f, 0x5b, 0x76, 0x69, 0x53, 0x33, 0x41, 0x13, 0x21, 0x19, 0x96, 0xd2, 0x00, 0x11, 0xa1, 0x98,
+ 0xe3, 0xfc, 0x43, 0x3f, 0x9f, 0x25, 0x41, 0x01, 0x0a, 0xe1, 0x7c, 0x1b, 0xf2, 0x02, 0x58, 0x0f,
+ 0x60, 0x47, 0x47, 0x2f, 0xb3, 0x68, 0x57, 0xfe, 0x84, 0x3b, 0x19, 0xf5, 0x98, 0x40, 0x09, 0xdd,
+ 0xc3, 0x24, 0x04, 0x4e, 0x84, 0x7a, 0x4f, 0x4a, 0x0a, 0xb3, 0x4f, 0x71, 0x95, 0x95, 0xde, 0x37,
+ 0x25, 0x2d, 0x62, 0x35, 0x36, 0x5e, 0x9b, 0x84, 0x39, 0x2b, 0x06, 0x10, 0x85, 0x34, 0x9d, 0x73,
+ 0x20, 0x3a, 0x4a, 0x13, 0xe9, 0x6f, 0x54, 0x32, 0xec, 0x0f, 0xd4, 0xa1, 0xee, 0x65, 0xac, 0xcd,
+ 0xd5, 0xe3, 0x90, 0x4d, 0xf5, 0x4c, 0x1d, 0xa5, 0x10, 0xb0, 0xff, 0x20, 0xdc, 0xc0, 0xc7, 0x7f,
+ 0xcb, 0x2c, 0x0e, 0x0e, 0xb6, 0x05, 0xcb, 0x05, 0x04, 0xdb, 0x87, 0x63, 0x2c, 0xf3, 0xd8, 0xb4,
+ 0xda, 0xe6, 0xe7, 0x05, 0x76, 0x9d, 0x1d, 0xe3, 0x54, 0x27, 0x01, 0x23, 0xcb, 0x11, 0x45, 0x0e,
+ 0xfc, 0x60, 0xac, 0x47, 0x68, 0x3d, 0x7b, 0x8d, 0x0f, 0x81, 0x13, 0x65, 0x56, 0x5f, 0xd9, 0x8c,
+ 0x4c, 0x8e, 0xb9, 0x36, 0xbc, 0xab, 0x8d, 0x06, 0x9f, 0xc3, 0x3b, 0xd8, 0x01, 0xb0, 0x3a, 0xde,
+ 0xa2, 0xe1, 0xfb, 0xc5, 0xaa, 0x46, 0x3d, 0x08, 0xca, 0x19, 0x89, 0x6d, 0x2b, 0xf5, 0x9a, 0x07,
+ 0x1b, 0x85, 0x1e, 0x6c, 0x23, 0x90, 0x52, 0x17, 0x2f, 0x29, 0x6b, 0xfb, 0x5e, 0x72, 0x40, 0x47,
+ 0x90, 0xa2, 0x18, 0x10, 0x14, 0xf3, 0xb9, 0x4a, 0x4e, 0x97, 0xd1, 0x17, 0xb4, 0x38, 0x13, 0x03,
+ 0x68, 0xcc, 0x39, 0xdb, 0xb2, 0xd1, 0x98, 0x06, 0x5a, 0xe3, 0x98, 0x65, 0x47, 0x92, 0x6c, 0xd2,
+ 0x16, 0x2f, 0x40, 0xa2, 0x9f, 0x0c, 0x3c, 0x87, 0x45, 0xc0, 0xf5, 0x0f, 0xba, 0x38, 0x52, 0xe5,
+ 0x66, 0xd4, 0x45, 0x75, 0xc2, 0x9d, 0x39, 0xa0, 0x3f, 0x0c, 0xda, 0x72, 0x19, 0x84, 0xb6, 0xf4,
+ 0x40, 0x59, 0x1f, 0x35, 0x5e, 0x12, 0xd4, 0x39, 0xff, 0x15, 0x0a, 0xab, 0x76, 0x13, 0x49, 0x9d,
+ 0xbd, 0x49, 0xad, 0xab, 0xc8, 0x67, 0x6e, 0xef, 0x02, 0x3b, 0x15, 0xb6, 0x5b, 0xfc, 0x5c, 0xa0,
+ 0x69, 0x48, 0x10, 0x9f, 0x23, 0xf3, 0x50, 0xdb, 0x82, 0x12, 0x35, 0x35, 0xeb, 0x8a, 0x74, 0x33,
+ 0xbd, 0xab, 0xcb, 0x90, 0x92, 0x71, 0xa6, 0xec, 0xbc, 0xb5, 0x8b, 0x93, 0x6a, 0x88, 0xcd, 0x4e,
+ 0x8f, 0x2e, 0x6f, 0xf5, 0x80, 0x01, 0x75, 0xf1, 0x13, 0x25, 0x3d, 0x8f, 0xa9, 0xca, 0x88, 0x85,
+ 0xc2, 0xf5, 0x52, 0xe6, 0x57, 0xdc, 0x60, 0x3f, 0x25, 0x2e, 0x1a, 0x8e, 0x30, 0x8f, 0x76, 0xf0,
+ 0xbe, 0x79, 0xe2, 0xfb, 0x8f, 0x5d, 0x5f, 0xbb, 0xe2, 0xe3, 0x0e, 0xca, 0xdd, 0x22, 0x07, 0x23,
+ 0xc8, 0xc0, 0xae, 0xa8, 0x07, 0x8c, 0xdf, 0xcb, 0x38, 0x68, 0x26, 0x3f, 0xf8, 0xf0, 0x94, 0x00,
+ 0x54, 0xda, 0x48, 0x78, 0x18, 0x93, 0xa7, 0xe4, 0x9a, 0xd5, 0xaf, 0xf4, 0xaf, 0x30, 0x0c, 0xd8,
+ 0x04, 0xa6, 0xb6, 0x27, 0x9a, 0xb3, 0xff, 0x3a, 0xfb, 0x64, 0x49, 0x1c, 0x85, 0x19, 0x4a, 0xab,
+ 0x76, 0x0d, 0x58, 0xa6, 0x06, 0x65, 0x4f, 0x9f, 0x44, 0x00, 0xe8, 0xb3, 0x85, 0x91, 0x35, 0x6f,
+ 0xbf, 0x64, 0x25, 0xac, 0xa2, 0x6d, 0xc8, 0x52, 0x44, 0x25, 0x9f, 0xf2, 0xb1, 0x9c, 0x41, 0xb9,
+ 0xf9, 0x6f, 0x3c, 0xa9, 0xec, 0x1d, 0xde, 0x43, 0x4d, 0xa7, 0xd2, 0xd3, 0x92, 0xb9, 0x05, 0xdd,
+ 0xf3, 0xd1, 0xf9, 0xaf, 0x93, 0xd1, 0xaf, 0x59, 0x50, 0xbd, 0x49, 0x3f, 0x5a, 0xa7, 0x31, 0xb4,
+ 0x05, 0x6d, 0xf3, 0x1b, 0xd2, 0x67, 0xb6, 0xb9, 0x0a, 0x07, 0x98, 0x31, 0xaa, 0xf5, 0x79, 0xbe,
+ 0x0a, 0x39, 0x01, 0x31, 0x37, 0xaa, 0xc6, 0xd4, 0x04, 0xf5, 0x18, 0xcf, 0xd4, 0x68, 0x40, 0x64,
+ 0x7e, 0x78, 0xbf, 0xe7, 0x06, 0xca, 0x4c, 0xf5, 0xe9, 0xc5, 0x45, 0x3e, 0x9f, 0x7c, 0xfd, 0x2b,
+ 0x8b, 0x4c, 0x8d, 0x16, 0x9a, 0x44, 0xe5, 0x5c, 0x88, 0xd4, 0xa9, 0xa7, 0xf9, 0x47, 0x42, 0x41,
+ 0xe2, 0x21, 0xaf, 0x44, 0x86, 0x00, 0x18, 0xab, 0x08, 0x56, 0x97, 0x2e, 0x19, 0x4c, 0xd9, 0x34,
+];
+
+const INITIAL_PACKET_29: &[u8] = &[
+ 0xcd, 0xff, 0x00, 0x00, 0x1d, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, 0x00,
+ 0x44, 0x9e, 0x9c, 0xdb, 0x99, 0x0b, 0xfb, 0x66, 0xbc, 0x6a, 0x93, 0x03, 0x2b, 0x50, 0xdd, 0x89,
+ 0x73, 0x97, 0x2d, 0x14, 0x94, 0x21, 0x87, 0x4d, 0x38, 0x49, 0xe3, 0x70, 0x8d, 0x71, 0x35, 0x4e,
+ 0xa3, 0x3b, 0xcd, 0xc3, 0x56, 0xf3, 0xea, 0x6e, 0x2a, 0x1a, 0x1b, 0xd7, 0xc3, 0xd1, 0x40, 0x03,
+ 0x8d, 0x3e, 0x78, 0x4d, 0x04, 0xc3, 0x0a, 0x2c, 0xdb, 0x40, 0xc3, 0x25, 0x23, 0xab, 0xa2, 0xda,
+ 0xfe, 0x1c, 0x1b, 0xf3, 0xd2, 0x7a, 0x6b, 0xe3, 0x8f, 0xe3, 0x8a, 0xe0, 0x33, 0xfb, 0xb0, 0x71,
+ 0x3c, 0x1c, 0x73, 0x66, 0x1b, 0xb6, 0x63, 0x97, 0x95, 0xb4, 0x2b, 0x97, 0xf7, 0x70, 0x68, 0xea,
+ 0xd5, 0x1f, 0x11, 0xfb, 0xf9, 0x48, 0x9a, 0xf2, 0x50, 0x1d, 0x09, 0x48, 0x1e, 0x6c, 0x64, 0xd4,
+ 0xb8, 0x55, 0x1c, 0xd3, 0xce, 0xa7, 0x0d, 0x83, 0x0c, 0xe2, 0xae, 0xee, 0xc7, 0x89, 0xef, 0x55,
+ 0x1a, 0x7f, 0xbe, 0x36, 0xb3, 0xf7, 0xe1, 0x54, 0x9a, 0x9f, 0x8d, 0x8e, 0x15, 0x3b, 0x3f, 0xac,
+ 0x3f, 0xb7, 0xb7, 0x81, 0x2c, 0x9e, 0xd7, 0xc2, 0x0b, 0x4b, 0xe1, 0x90, 0xeb, 0xd8, 0x99, 0x56,
+ 0x26, 0xe7, 0xf0, 0xfc, 0x88, 0x79, 0x25, 0xec, 0x6f, 0x06, 0x06, 0xc5, 0xd3, 0x6a, 0xa8, 0x1b,
+ 0xeb, 0xb7, 0xaa, 0xcd, 0xc4, 0xa3, 0x1b, 0xb5, 0xf2, 0x3d, 0x55, 0xfa, 0xef, 0x5c, 0x51, 0x90,
+ 0x57, 0x83, 0x38, 0x4f, 0x37, 0x5a, 0x43, 0x23, 0x5b, 0x5c, 0x74, 0x2c, 0x78, 0xab, 0x1b, 0xae,
+ 0x0a, 0x18, 0x8b, 0x75, 0xef, 0xbd, 0xe6, 0xb3, 0x77, 0x4e, 0xd6, 0x12, 0x82, 0xf9, 0x67, 0x0a,
+ 0x9d, 0xea, 0x19, 0xe1, 0x56, 0x61, 0x03, 0xce, 0x67, 0x5a, 0xb4, 0xe2, 0x10, 0x81, 0xfb, 0x58,
+ 0x60, 0x34, 0x0a, 0x1e, 0x88, 0xe4, 0xf1, 0x0e, 0x39, 0xea, 0xe2, 0x5c, 0xd6, 0x85, 0xb1, 0x09,
+ 0x29, 0x63, 0x6d, 0x4f, 0x02, 0xe7, 0xfa, 0xd2, 0xa5, 0xa4, 0x58, 0x24, 0x9f, 0x5c, 0x02, 0x98,
+ 0xa6, 0xd5, 0x3a, 0xcb, 0xe4, 0x1a, 0x7f, 0xc8, 0x3f, 0xa7, 0xcc, 0x01, 0x97, 0x3f, 0x7a, 0x74,
+ 0xd1, 0x23, 0x7a, 0x51, 0x97, 0x4e, 0x09, 0x76, 0x36, 0xb6, 0x20, 0x39, 0x97, 0xf9, 0x21, 0xd0,
+ 0x7b, 0xc1, 0x94, 0x0a, 0x6f, 0x2d, 0x0d, 0xe9, 0xf5, 0xa1, 0x14, 0x32, 0x94, 0x61, 0x59, 0xed,
+ 0x6c, 0xc2, 0x1d, 0xf6, 0x5c, 0x4d, 0xdd, 0x11, 0x15, 0xf8, 0x64, 0x27, 0x25, 0x9a, 0x19, 0x6c,
+ 0x71, 0x48, 0xb2, 0x5b, 0x64, 0x78, 0xb0, 0xdc, 0x77, 0x66, 0xe1, 0xc4, 0xd1, 0xb1, 0xf5, 0x15,
+ 0x9f, 0x90, 0xea, 0xbc, 0x61, 0x63, 0x62, 0x26, 0x24, 0x46, 0x42, 0xee, 0x14, 0x8b, 0x46, 0x4c,
+ 0x9e, 0x61, 0x9e, 0xe5, 0x0a, 0x5e, 0x3d, 0xdc, 0x83, 0x62, 0x27, 0xca, 0xd9, 0x38, 0x98, 0x7c,
+ 0x4e, 0xa3, 0xc1, 0xfa, 0x7c, 0x75, 0xbb, 0xf8, 0x8d, 0x89, 0xe9, 0xad, 0xa6, 0x42, 0xb2, 0xb8,
+ 0x8f, 0xe8, 0x10, 0x7b, 0x7e, 0xa3, 0x75, 0xb1, 0xb6, 0x48, 0x89, 0xa4, 0xe9, 0xe5, 0xc3, 0x8a,
+ 0x1c, 0x89, 0x6c, 0xe2, 0x75, 0xa5, 0x65, 0x8d, 0x25, 0x0e, 0x2d, 0x76, 0xe1, 0xed, 0x3a, 0x34,
+ 0xce, 0x7e, 0x3a, 0x3f, 0x38, 0x3d, 0x0c, 0x99, 0x6d, 0x0b, 0xed, 0x10, 0x6c, 0x28, 0x99, 0xca,
+ 0x6f, 0xc2, 0x63, 0xef, 0x04, 0x55, 0xe7, 0x4b, 0xb6, 0xac, 0x16, 0x40, 0xea, 0x7b, 0xfe, 0xdc,
+ 0x59, 0xf0, 0x3f, 0xee, 0x0e, 0x17, 0x25, 0xea, 0x15, 0x0f, 0xf4, 0xd6, 0x9a, 0x76, 0x60, 0xc5,
+ 0x54, 0x21, 0x19, 0xc7, 0x1d, 0xe2, 0x70, 0xae, 0x7c, 0x3e, 0xcf, 0xd1, 0xaf, 0x2c, 0x4c, 0xe5,
+ 0x51, 0x98, 0x69, 0x49, 0xcc, 0x34, 0xa6, 0x6b, 0x3e, 0x21, 0x6b, 0xfe, 0x18, 0xb3, 0x47, 0xe6,
+ 0xc0, 0x5f, 0xd0, 0x50, 0xf8, 0x59, 0x12, 0xdb, 0x30, 0x3a, 0x8f, 0x05, 0x4e, 0xc2, 0x3e, 0x38,
+ 0xf4, 0x4d, 0x1c, 0x72, 0x5a, 0xb6, 0x41, 0xae, 0x92, 0x9f, 0xec, 0xc8, 0xe3, 0xce, 0xfa, 0x56,
+ 0x19, 0xdf, 0x42, 0x31, 0xf5, 0xb4, 0xc0, 0x09, 0xfa, 0x0c, 0x0b, 0xbc, 0x60, 0xbc, 0x75, 0xf7,
+ 0x6d, 0x06, 0xef, 0x15, 0x4f, 0xc8, 0x57, 0x70, 0x77, 0xd9, 0xd6, 0xa1, 0xd2, 0xbd, 0x9b, 0xf0,
+ 0x81, 0xdc, 0x78, 0x3e, 0xce, 0x60, 0x11, 0x1b, 0xea, 0x7d, 0xa9, 0xe5, 0xa9, 0x74, 0x80, 0x69,
+ 0xd0, 0x78, 0xb2, 0xbe, 0xf4, 0x8d, 0xe0, 0x4c, 0xab, 0xe3, 0x75, 0x5b, 0x19, 0x7d, 0x52, 0xb3,
+ 0x20, 0x46, 0x94, 0x9e, 0xca, 0xa3, 0x10, 0x27, 0x4b, 0x4a, 0xac, 0x0d, 0x00, 0x8b, 0x19, 0x48,
+ 0xc1, 0x08, 0x2c, 0xdf, 0xe2, 0x08, 0x3e, 0x38, 0x6d, 0x4f, 0xd8, 0x4c, 0x0e, 0xd0, 0x66, 0x6d,
+ 0x3e, 0xe2, 0x6c, 0x45, 0x15, 0xc4, 0xfe, 0xe7, 0x34, 0x33, 0xac, 0x70, 0x3b, 0x69, 0x0a, 0x9f,
+ 0x7b, 0xf2, 0x78, 0xa7, 0x74, 0x86, 0xac, 0xe4, 0x4c, 0x48, 0x9a, 0x0c, 0x7a, 0xc8, 0xdf, 0xe4,
+ 0xd1, 0xa5, 0x8f, 0xb3, 0xa7, 0x30, 0xb9, 0x93, 0xff, 0x0f, 0x0d, 0x61, 0xb4, 0xd8, 0x95, 0x57,
+ 0x83, 0x1e, 0xb4, 0xc7, 0x52, 0xff, 0xd3, 0x9c, 0x10, 0xf6, 0xb9, 0xf4, 0x6d, 0x8d, 0xb2, 0x78,
+ 0xda, 0x62, 0x4f, 0xd8, 0x00, 0xe4, 0xaf, 0x85, 0x54, 0x8a, 0x29, 0x4c, 0x15, 0x18, 0x89, 0x3a,
+ 0x87, 0x78, 0xc4, 0xf6, 0xd6, 0xd7, 0x3c, 0x93, 0xdf, 0x20, 0x09, 0x60, 0x10, 0x4e, 0x06, 0x2b,
+ 0x38, 0x8e, 0xa9, 0x7d, 0xcf, 0x40, 0x16, 0xbc, 0xed, 0x7f, 0x62, 0xb4, 0xf0, 0x62, 0xcb, 0x6c,
+ 0x04, 0xc2, 0x06, 0x93, 0xd9, 0xa0, 0xe3, 0xb7, 0x4b, 0xa8, 0xfe, 0x74, 0xcc, 0x01, 0x23, 0x78,
+ 0x84, 0xf4, 0x0d, 0x76, 0x5a, 0xe5, 0x6a, 0x51, 0x68, 0x8d, 0x98, 0x5c, 0xf0, 0xce, 0xae, 0xf4,
+ 0x30, 0x45, 0xed, 0x8c, 0x3f, 0x0c, 0x33, 0xbc, 0xed, 0x08, 0x53, 0x7f, 0x68, 0x82, 0x61, 0x3a,
+ 0xcd, 0x3b, 0x08, 0xd6, 0x65, 0xfc, 0xe9, 0xdd, 0x8a, 0xa7, 0x31, 0x71, 0xe2, 0xd3, 0x77, 0x1a,
+ 0x61, 0xdb, 0xa2, 0x79, 0x0e, 0x49, 0x1d, 0x41, 0x3d, 0x93, 0xd9, 0x87, 0xe2, 0x74, 0x5a, 0xf2,
+ 0x94, 0x18, 0xe4, 0x28, 0xbe, 0x34, 0x94, 0x14, 0x85, 0xc9, 0x34, 0x47, 0x52, 0x0f, 0xfe, 0x23,
+ 0x1d, 0xa2, 0x30, 0x4d, 0x6a, 0x0f, 0xd5, 0xd0, 0x7d, 0x08, 0x37, 0x22, 0x02, 0x36, 0x96, 0x61,
+ 0x59, 0xbe, 0xf3, 0xcf, 0x90, 0x4d, 0x72, 0x23, 0x24, 0xdd, 0x85, 0x25, 0x13, 0xdf, 0x39, 0xae,
+ 0x03, 0x0d, 0x81, 0x73, 0x90, 0x8d, 0xa6, 0x36, 0x47, 0x86, 0xd3, 0xc1, 0xbf, 0xcb, 0x19, 0xea,
+ 0x77, 0xa6, 0x3b, 0x25, 0xf1, 0xe7, 0xfc, 0x66, 0x1d, 0xef, 0x48, 0x0c, 0x5d, 0x00, 0xd4, 0x44,
+ 0x56, 0x26, 0x9e, 0xbd, 0x84, 0xef, 0xd8, 0xe3, 0xa8, 0xb2, 0xc2, 0x57, 0xee, 0xc7, 0x60, 0x60,
+ 0x68, 0x28, 0x48, 0xcb, 0xf5, 0x19, 0x4b, 0xc9, 0x9e, 0x49, 0xee, 0x75, 0xe4, 0xd0, 0xd2, 0x54,
+ 0xba, 0xd4, 0xbf, 0xd7, 0x49, 0x70, 0xc3, 0x0e, 0x44, 0xb6, 0x55, 0x11, 0xd4, 0xad, 0x0e, 0x6e,
+ 0xc7, 0x39, 0x8e, 0x08, 0xe0, 0x13, 0x07, 0xee, 0xee, 0xa1, 0x4e, 0x46, 0xcc, 0xd8, 0x7c, 0xf3,
+ 0x6b, 0x28, 0x52, 0x21, 0x25, 0x4d, 0x8f, 0xc6, 0xa6, 0x76, 0x5c, 0x52, 0x4d, 0xed, 0x00, 0x85,
+ 0xdc, 0xa5, 0xbd, 0x68, 0x8d, 0xdf, 0x72, 0x2e, 0x2c, 0x0f, 0xaf, 0x9d, 0x0f, 0xb2, 0xce, 0x7a,
+ 0x0c, 0x3f, 0x2c, 0xee, 0x19, 0xca, 0x0f, 0xfb, 0xa4, 0x61, 0xca, 0x8d, 0xc5, 0xd2, 0xc8, 0x17,
+ 0x8b, 0x07, 0x62, 0xcf, 0x67, 0x13, 0x55, 0x58, 0x49, 0x4d, 0x2a, 0x96, 0xf1, 0xa1, 0x39, 0xf0,
+ 0xed, 0xb4, 0x2d, 0x2a, 0xf8, 0x9a, 0x9c, 0x91, 0x22, 0xb0, 0x7a, 0xcb, 0xc2, 0x9e, 0x5e, 0x72,
+ 0x2d, 0xf8, 0x61, 0x5c, 0x34, 0x37, 0x02, 0x49, 0x10, 0x98, 0x47, 0x8a, 0x38, 0x9c, 0x98, 0x72,
+ 0xa1, 0x0b, 0x0c, 0x98, 0x75, 0x12, 0x5e, 0x25, 0x7c, 0x7b, 0xfd, 0xf2, 0x7e, 0xef, 0x40, 0x60,
+ 0xbd, 0x3d, 0x00, 0xf4, 0xc1, 0x4f, 0xd3, 0xe3, 0x49, 0x6c, 0x38, 0xd3, 0xc5, 0xd1, 0xa5, 0x66,
+ 0x8c, 0x39, 0x35, 0x0e, 0xff, 0xbc, 0x2d, 0x16, 0xca, 0x17, 0xbe, 0x4c, 0xe2, 0x9f, 0x02, 0xed,
+ 0x96, 0x95, 0x04, 0xdd, 0xa2, 0xa8, 0xc6, 0xb9, 0xff, 0x91, 0x9e, 0x69, 0x3e, 0xe7, 0x9e, 0x09,
+ 0x08, 0x93, 0x16, 0xe7, 0xd1, 0xd8, 0x9e, 0xc0, 0x99, 0xdb, 0x3b, 0x2b, 0x26, 0x87, 0x25, 0xd8,
+ 0x88, 0x53, 0x6a, 0x4b, 0x8b, 0xf9, 0xae, 0xe8, 0xfb, 0x43, 0xe8, 0x2a, 0x4d, 0x91, 0x9d, 0x48,
+ 0x18, 0x02, 0x77, 0x1a, 0x44, 0x9b, 0x30, 0xf3, 0xfa, 0x22, 0x89, 0x85, 0x26, 0x07, 0xb6, 0x60,
+];
+
+fn make_server(v: Version) -> Connection {
+ test_fixture::fixture_init();
+ Connection::new_server(
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(RandomConnectionIdGenerator::new(5))),
+ ConnectionParameters::default().versions(v, vec![v]),
+ )
+ .expect("create a default server")
+}
+
+fn process_client_initial(v: Version, packet: &[u8]) {
+ let mut server = make_server(v);
+
+ let dgram = datagram(packet.to_vec());
+ assert_eq!(*server.state(), State::Init);
+ let out = server.process(Some(&dgram), now());
+ assert_eq!(*server.state(), State::Handshaking);
+ assert!(out.dgram().is_some());
+}
+
+#[test]
+fn process_client_initial_v2() {
+ process_client_initial(Version::Version2, INITIAL_PACKET_V2);
+}
+
+#[test]
+fn process_client_initial_v1() {
+ process_client_initial(Version::Version1, INITIAL_PACKET_V1);
+}
+
+#[test]
+fn process_client_initial_29() {
+ process_client_initial(Version::Draft29, INITIAL_PACKET_29);
+}
diff --git a/third_party/rust/neqo-transport/tests/connection.rs b/third_party/rust/neqo-transport/tests/connection.rs
new file mode 100644
index 0000000000..4cbf57f405
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/connection.rs
@@ -0,0 +1,208 @@
+// 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::use_self)]
+
+mod common;
+
+use std::convert::TryFrom;
+
+use common::{
+ apply_header_protection, decode_initial_header, initial_aead_and_hp, remove_header_protection,
+};
+use neqo_common::{Datagram, Decoder, Encoder, Role};
+use neqo_transport::{ConnectionError, ConnectionParameters, Error, State, Version};
+use test_fixture::{self, default_client, default_server, new_client, now, split_datagram};
+
+#[test]
+fn connect() {
+ let (_client, _server) = test_fixture::connect();
+}
+
+#[test]
+fn truncate_long_packet() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+ let out = server.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_some());
+
+ // This will truncate the Handshake packet from the server.
+ let dupe = out.as_dgram_ref().unwrap().clone();
+ // Count the padding in the packet, plus 1.
+ let tail = dupe.iter().rev().take_while(|b| **b == 0).count() + 1;
+ let truncated = Datagram::new(
+ dupe.source(),
+ dupe.destination(),
+ dupe.tos(),
+ dupe.ttl(),
+ &dupe[..(dupe.len() - tail)],
+ );
+ let hs_probe = client.process(Some(&truncated), now()).dgram();
+ assert!(hs_probe.is_some());
+
+ // Now feed in the untruncated packet.
+ let out = client.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_some()); // Throw this ACK away.
+ assert!(test_fixture::maybe_authenticate(&mut client));
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+
+ assert!(client.state().connected());
+ let out = server.process(out.as_dgram_ref(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert!(server.state().connected());
+}
+
+/// Test that reordering parts of the server Initial doesn't change things.
+#[test]
+fn reorder_server_initial() {
+ // A simple ACK frame for a single packet with packet number 0.
+ const ACK_FRAME: &[u8] = &[0x02, 0x00, 0x00, 0x00, 0x00];
+
+ let mut client = new_client(
+ ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
+ );
+ let mut server = default_server();
+
+ let client_initial = client.process_output(now());
+ let (_, client_dcid, _, _) =
+ decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client);
+ let client_dcid = client_dcid.to_owned();
+
+ let server_packet = server.process(client_initial.as_dgram_ref(), now()).dgram();
+ let (server_initial, server_hs) = split_datagram(server_packet.as_ref().unwrap());
+ let (protected_header, _, _, payload) = decode_initial_header(&server_initial, Role::Server);
+
+ // Now decrypt the packet.
+ let (aead, hp) = initial_aead_and_hp(&client_dcid, Role::Server);
+ let (header, pn) = remove_header_protection(&hp, protected_header, payload);
+ assert_eq!(pn, 0);
+ let pn_len = header.len() - protected_header.len();
+ let mut buf = vec![0; payload.len()];
+ let mut plaintext = aead
+ .decrypt(pn, &header, &payload[pn_len..], &mut buf)
+ .unwrap()
+ .to_owned();
+
+ // Now we need to find the frames. Make some really strong assumptions.
+ let mut dec = Decoder::new(&plaintext[..]);
+ assert_eq!(dec.decode(ACK_FRAME.len()), Some(ACK_FRAME));
+ assert_eq!(dec.decode_varint(), Some(0x06)); // CRYPTO
+ assert_eq!(dec.decode_varint(), Some(0x00)); // offset
+ dec.skip_vvec(); // Skip over the payload.
+ let end = dec.offset();
+
+ // Move the ACK frame after the CRYPTO frame.
+ plaintext[..end].rotate_left(ACK_FRAME.len());
+
+ // And rebuild a packet.
+ let mut packet = header.clone();
+ packet.resize(1200, 0);
+ aead.encrypt(pn, &header, &plaintext, &mut packet[header.len()..])
+ .unwrap();
+ apply_header_protection(&hp, &mut packet, protected_header.len()..header.len());
+ let reordered = Datagram::new(
+ server_initial.source(),
+ server_initial.destination(),
+ server_initial.tos(),
+ server_initial.ttl(),
+ packet,
+ );
+
+ // Now a connection can be made successfully.
+ // Though we modified the server's Initial packet, we get away with it.
+ // TLS only authenticates the content of the CRYPTO frame, which was untouched.
+ client.process_input(&reordered, now());
+ client.process_input(&server_hs.unwrap(), now());
+ assert!(test_fixture::maybe_authenticate(&mut client));
+ let finished = client.process_output(now());
+ assert_eq!(*client.state(), State::Connected);
+
+ let done = server.process(finished.as_dgram_ref(), now());
+ assert_eq!(*server.state(), State::Confirmed);
+
+ client.process_input(done.as_dgram_ref().unwrap(), now());
+ assert_eq!(*client.state(), State::Confirmed);
+}
+
+/// Overflow the crypto buffer.
+#[test]
+fn overflow_crypto() {
+ let mut client = new_client(
+ ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
+ );
+ let mut server = default_server();
+
+ let client_initial = client.process_output(now()).dgram();
+ let (_, client_dcid, _, _) =
+ decode_initial_header(client_initial.as_ref().unwrap(), Role::Client);
+ let client_dcid = client_dcid.to_owned();
+
+ let server_packet = server.process(client_initial.as_ref(), now()).dgram();
+ let (server_initial, _) = split_datagram(server_packet.as_ref().unwrap());
+
+ // Now decrypt the server packet to get AEAD and HP instances.
+ // We won't be using the packet, but making new ones.
+ let (aead, hp) = initial_aead_and_hp(&client_dcid, Role::Server);
+ let (_, server_dcid, server_scid, _) = decode_initial_header(&server_initial, Role::Server);
+
+ // Send in 100 packets, each with 1000 bytes of crypto frame data each,
+ // eventually this will overrun the buffer we keep for crypto data.
+ let mut payload = Encoder::with_capacity(1024);
+ for pn in 0..100_u64 {
+ payload.truncate(0);
+ payload
+ .encode_varint(0x06_u64) // CRYPTO frame type.
+ .encode_varint(pn * 1000 + 1) // offset
+ .encode_varint(1000_u64); // length
+ let plen = payload.len();
+ payload.pad_to(plen + 1000, 44);
+
+ let mut packet = Encoder::with_capacity(1200);
+ packet
+ .encode_byte(0xc1) // Initial with packet number length of 2.
+ .encode_uint(4, Version::Version1.wire_version())
+ .encode_vec(1, server_dcid)
+ .encode_vec(1, server_scid)
+ .encode_vvec(&[]) // token
+ .encode_varint(u64::try_from(2 + payload.len() + aead.expansion()).unwrap()); // length
+ let pn_offset = packet.len();
+ packet.encode_uint(2, pn);
+
+ let mut packet = Vec::from(packet);
+ let header = packet.clone();
+ packet.resize(header.len() + payload.len() + aead.expansion(), 0);
+ aead.encrypt(pn, &header, payload.as_ref(), &mut packet[header.len()..])
+ .unwrap();
+ apply_header_protection(&hp, &mut packet, pn_offset..(pn_offset + 2));
+ packet.resize(1200, 0); // Initial has to be 1200 bytes!
+
+ let dgram = Datagram::new(
+ server_initial.source(),
+ server_initial.destination(),
+ server_initial.tos(),
+ server_initial.ttl(),
+ packet,
+ );
+ client.process_input(&dgram, now());
+ if let State::Closing { error, .. } = client.state() {
+ assert!(
+ matches!(
+ error,
+ ConnectionError::Transport(Error::CryptoBufferExceeded),
+ ),
+ "the connection need to abort on crypto buffer"
+ );
+ assert!(pn > 64, "at least 64000 bytes of data is buffered");
+ return;
+ }
+ }
+ panic!("Was not able to overflow the crypto buffer");
+}
diff --git a/third_party/rust/neqo-transport/tests/network.rs b/third_party/rust/neqo-transport/tests/network.rs
new file mode 100644
index 0000000000..8c388457c5
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/network.rs
@@ -0,0 +1,177 @@
+// 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)]
+
+mod sim;
+
+use std::{ops::Range, time::Duration};
+
+use neqo_transport::{ConnectionError, ConnectionParameters, Error, State};
+use sim::{
+ connection::{ConnectionNode, ReachState, ReceiveData, SendData},
+ network::{Delay, Drop, TailDrop},
+ Simulator,
+};
+
+/// The amount of transfer. Much more than this takes a surprising amount of time.
+const TRANSFER_AMOUNT: usize = 1 << 20; // 1M
+const ZERO: Duration = Duration::from_millis(0);
+const DELAY: Duration = Duration::from_millis(50);
+const DELAY_RANGE: Range<Duration> = DELAY..Duration::from_millis(55);
+const JITTER: Duration = Duration::from_millis(10);
+
+const fn weeks(m: u32) -> Duration {
+ Duration::from_secs((m as u64) * 60 * 60 * 24 * 7)
+}
+
+simulate!(
+ connect_direct,
+ [
+ ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]),
+ ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]),
+ ]
+);
+
+simulate!(
+ idle_timeout,
+ [
+ ConnectionNode::default_client(boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]),
+ ConnectionNode::default_server(boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]),
+ ]
+);
+
+simulate!(
+ idle_timeout_crazy_rtt,
+ [
+ ConnectionNode::new_client(
+ ConnectionParameters::default().idle_timeout(weeks(1000)),
+ boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]
+ ),
+ Delay::new(weeks(6)..weeks(6)),
+ Drop::percentage(10),
+ ConnectionNode::new_server(
+ ConnectionParameters::default().idle_timeout(weeks(1000)),
+ boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]
+ ),
+ Delay::new(weeks(8)..weeks(8)),
+ Drop::percentage(10),
+ ],
+);
+
+simulate!(
+ transfer,
+ [
+ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ ]
+);
+
+simulate!(
+ connect_fixed_rtt,
+ [
+ ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]),
+ Delay::new(DELAY..DELAY),
+ ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]),
+ Delay::new(DELAY..DELAY),
+ ],
+);
+
+simulate!(
+ connect_taildrop_jitter,
+ [
+ ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_uplink(),
+ Delay::new(ZERO..JITTER),
+ ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_downlink(),
+ Delay::new(ZERO..JITTER),
+ ],
+);
+
+simulate!(
+ connect_taildrop,
+ [
+ ConnectionNode::default_client(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_uplink(),
+ ConnectionNode::default_server(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_downlink(),
+ ],
+);
+
+simulate!(
+ transfer_delay_drop,
+ [
+ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ Delay::new(DELAY_RANGE),
+ Drop::percentage(1),
+ ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ Delay::new(DELAY_RANGE),
+ Drop::percentage(1),
+ ],
+);
+
+simulate!(
+ transfer_taildrop,
+ [
+ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_uplink(),
+ ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_downlink(),
+ ],
+);
+
+simulate!(
+ transfer_taildrop_jitter,
+ [
+ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_uplink(),
+ Delay::new(ZERO..JITTER),
+ ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_downlink(),
+ Delay::new(ZERO..JITTER),
+ ],
+);
+
+/// This test is a nasty piece of work. Delays are anything from 0 to 50ms and 1% of
+/// packets get dropped.
+#[test]
+fn transfer_fixed_seed() {
+ let mut sim = Simulator::new(
+ "transfer_fixed_seed",
+ boxed![
+ ConnectionNode::default_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ Delay::new(ZERO..DELAY),
+ Drop::percentage(1),
+ ConnectionNode::default_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ Delay::new(ZERO..DELAY),
+ Drop::percentage(1),
+ ],
+ );
+ sim.seed_str("117f65d90ee5c1a7fb685f3af502c7730ba5d31866b758d98f5e3c2117cf9b86");
+ sim.run();
+}
diff --git a/third_party/rust/neqo-transport/tests/retry.rs b/third_party/rust/neqo-transport/tests/retry.rs
new file mode 100644
index 0000000000..93759c7df9
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/retry.rs
@@ -0,0 +1,475 @@
+// 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)]
+#![cfg(not(feature = "fuzzing"))]
+
+mod common;
+
+use std::{
+ convert::TryFrom,
+ mem,
+ net::{IpAddr, Ipv4Addr, SocketAddr},
+ time::Duration,
+};
+
+use common::{
+ apply_header_protection, connected_server, decode_initial_header, default_server,
+ generate_ticket, initial_aead_and_hp, remove_header_protection,
+};
+use neqo_common::{hex_with_len, qdebug, qtrace, Datagram, Encoder, Role};
+use neqo_crypto::AuthenticationStatus;
+use neqo_transport::{server::ValidateAddress, ConnectionError, Error, State, StreamType};
+use test_fixture::{self, assertions, datagram, default_client, now, split_datagram};
+
+#[test]
+fn retry_basic() {
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+
+ let dgram = client.process(None, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram.as_ref(), now()).dgram(); // Initial w/token
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Initial, HS
+ assert!(dgram.is_some());
+ mem::drop(client.process(dgram.as_ref(), now()).dgram()); // Ingest, drop any ACK.
+ client.authenticated(AuthenticationStatus::Ok, now());
+ let dgram = client.process(None, now()).dgram(); // Send Finished
+ assert!(dgram.is_some());
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // (done)
+ assert!(dgram.is_some()); // Note that this packet will be dropped...
+ connected_server(&mut server);
+}
+
+/// Receiving a Retry is enough to infer something about the RTT.
+/// Probably.
+#[test]
+fn implicit_rtt_retry() {
+ const RTT: Duration = Duration::from_secs(2);
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+ let mut now = now();
+
+ let dgram = client.process(None, now).dgram();
+ now += RTT / 2;
+ let dgram = server.process(dgram.as_ref(), now).dgram();
+ assertions::assert_retry(dgram.as_ref().unwrap());
+ now += RTT / 2;
+ client.process_input(&dgram.unwrap(), now);
+
+ assert_eq!(client.stats().rtt, RTT);
+}
+
+#[test]
+fn retry_expired() {
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+ let mut now = now();
+
+ let dgram = client.process(None, now).dgram(); // Initial
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram.as_ref(), now).dgram(); // Initial w/token
+ assert!(dgram.is_some());
+
+ now += Duration::from_secs(60); // Too long for Retry.
+ let dgram = server.process(dgram.as_ref(), now).dgram(); // Initial, HS
+ assert!(dgram.is_none());
+}
+
+// Attempt a retry with 0-RTT, and have 0-RTT packets sent with the second ClientHello.
+#[test]
+fn retry_0rtt() {
+ let mut server = default_server();
+ let token = generate_ticket(&mut server);
+ server.set_validation(ValidateAddress::Always);
+
+ let mut client = default_client();
+ client.enable_resumption(now(), &token).unwrap();
+
+ let client_stream = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream, &[1, 2, 3]).unwrap();
+
+ let dgram = client.process(None, now()).dgram(); // Initial w/0-RTT
+ assert!(dgram.is_some());
+ assertions::assert_coalesced_0rtt(dgram.as_ref().unwrap());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+ assertions::assert_retry(dgram.as_ref().unwrap());
+
+ // After retry, there should be a token and still coalesced 0-RTT.
+ let dgram = client.process(dgram.as_ref(), now()).dgram();
+ assert!(dgram.is_some());
+ assertions::assert_coalesced_0rtt(dgram.as_ref().unwrap());
+
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Initial, HS
+ assert!(dgram.is_some());
+ let dgram = client.process(dgram.as_ref(), now()).dgram();
+ // Note: the client doesn't need to authenticate the server here
+ // as there is no certificate; authentication is based on the ticket.
+ assert!(dgram.is_some());
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // (done)
+ assert!(dgram.is_some());
+ connected_server(&mut server);
+ assert!(client.tls_info().unwrap().resumed());
+}
+
+#[test]
+fn retry_different_ip() {
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+
+ let dgram = client.process(None.as_ref(), now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram.as_ref(), now()).dgram(); // Initial w/token
+ assert!(dgram.is_some());
+
+ // Change the source IP on the address from the client.
+ let dgram = dgram.unwrap();
+ let other_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2));
+ let other_addr = SocketAddr::new(other_v4, 443);
+ let from_other = Datagram::new(
+ other_addr,
+ dgram.destination(),
+ dgram.tos(),
+ dgram.ttl(),
+ &dgram[..],
+ );
+ let dgram = server.process(Some(&from_other), now()).dgram();
+ assert!(dgram.is_none());
+}
+
+#[test]
+fn new_token_different_ip() {
+ let mut server = default_server();
+ let token = generate_ticket(&mut server);
+ server.set_validation(ValidateAddress::NoToken);
+
+ let mut client = default_client();
+ client.enable_resumption(now(), &token).unwrap();
+
+ let dgram = client.process(None, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), true);
+
+ // Now rewrite the source address.
+ let d = dgram.unwrap();
+ let src = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), d.source().port());
+ let dgram = Some(Datagram::new(
+ src,
+ d.destination(),
+ d.tos(),
+ d.ttl(),
+ &d[..],
+ ));
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+ assertions::assert_retry(dgram.as_ref().unwrap());
+}
+
+#[test]
+fn new_token_expired() {
+ let mut server = default_server();
+ let token = generate_ticket(&mut server);
+ server.set_validation(ValidateAddress::NoToken);
+
+ let mut client = default_client();
+ client.enable_resumption(now(), &token).unwrap();
+
+ let dgram = client.process(None, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), true);
+
+ // Now move into the future.
+ // We can't go too far or we'll overflow our field. Not when checking,
+ // but when trying to generate another Retry. A month is fine.
+ let the_future = now() + Duration::from_secs(60 * 60 * 24 * 30);
+ let d = dgram.unwrap();
+ let src = SocketAddr::new(d.source().ip(), d.source().port() + 1);
+ let dgram = Some(Datagram::new(
+ src,
+ d.destination(),
+ d.tos(),
+ d.ttl(),
+ &d[..],
+ ));
+ let dgram = server.process(dgram.as_ref(), the_future).dgram(); // Retry
+ assert!(dgram.is_some());
+ assertions::assert_retry(dgram.as_ref().unwrap());
+}
+
+#[test]
+fn retry_after_initial() {
+ let mut server = default_server();
+ let mut retry_server = default_server();
+ retry_server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+
+ let cinit = client.process(None, now()).dgram(); // Initial
+ assert!(cinit.is_some());
+ let server_flight = server.process(cinit.as_ref(), now()).dgram(); // Initial
+ assert!(server_flight.is_some());
+
+ // We need to have the client just process the Initial.
+ let (server_initial, _other) = split_datagram(server_flight.as_ref().unwrap());
+ let dgram = client.process(Some(&server_initial), now()).dgram();
+ assert!(dgram.is_some());
+ assert!(*client.state() != State::Connected);
+
+ let retry = retry_server.process(cinit.as_ref(), now()).dgram(); // Retry!
+ assert!(retry.is_some());
+ assertions::assert_retry(retry.as_ref().unwrap());
+
+ // The client should ignore the retry.
+ let junk = client.process(retry.as_ref(), now()).dgram();
+ assert!(junk.is_none());
+
+ // Either way, the client should still be able to process the server flight and connect.
+ let dgram = client.process(server_flight.as_ref(), now()).dgram();
+ assert!(dgram.is_some()); // Drop this one.
+ assert!(test_fixture::maybe_authenticate(&mut client));
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // (done)
+ assert!(dgram.is_some());
+ connected_server(&mut server);
+}
+
+#[test]
+fn retry_bad_integrity() {
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+
+ let dgram = client.process(None, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ let retry = &dgram.as_ref().unwrap();
+ assertions::assert_retry(retry);
+
+ let mut tweaked = retry.to_vec();
+ tweaked[retry.len() - 1] ^= 0x45; // damage the auth tag
+ let tweaked_packet = Datagram::new(
+ retry.source(),
+ retry.destination(),
+ retry.tos(),
+ retry.ttl(),
+ tweaked,
+ );
+
+ // The client should ignore this packet.
+ let dgram = client.process(Some(&tweaked_packet), now()).dgram();
+ assert!(dgram.is_none());
+}
+
+#[test]
+fn retry_bad_token() {
+ let mut client = default_client();
+ let mut retry_server = default_server();
+ retry_server.set_validation(ValidateAddress::Always);
+ let mut server = default_server();
+
+ // Send a retry to one server, then replay it to the other.
+ let client_initial1 = client.process(None, now()).dgram();
+ assert!(client_initial1.is_some());
+ let retry = retry_server
+ .process(client_initial1.as_ref(), now())
+ .dgram();
+ assert!(retry.is_some());
+ let client_initial2 = client.process(retry.as_ref(), now()).dgram();
+ assert!(client_initial2.is_some());
+
+ let dgram = server.process(client_initial2.as_ref(), now()).dgram();
+ assert!(dgram.is_none());
+}
+
+// This is really a client test, but we need a server with Retry to test it.
+// In this test, the client sends Initial on PTO. The Retry should cause
+// all loss recovery timers to be reset, but we had a bug where the PTO timer
+// was not properly reset. This tests that the client generates a new Initial
+// in response to receiving a Retry, even after it sends the Initial on PTO.
+#[test]
+fn retry_after_pto() {
+ let mut client = default_client();
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut now = now();
+
+ let ci = client.process(None, now).dgram();
+ assert!(ci.is_some()); // sit on this for a bit.RefCell
+
+ // Let PTO fire on the client and then let it exhaust its PTO packets.
+ now += Duration::from_secs(1);
+ let pto = client.process(None, now).dgram();
+ assert!(pto.unwrap().len() >= 1200);
+ let cb = client.process(None, now).callback();
+ assert_ne!(cb, Duration::new(0, 0));
+
+ let retry = server.process(ci.as_ref(), now).dgram();
+ assertions::assert_retry(retry.as_ref().unwrap());
+
+ let ci2 = client.process(retry.as_ref(), now).dgram();
+ assert!(ci2.unwrap().len() >= 1200);
+}
+
+#[test]
+fn vn_after_retry() {
+ let mut server = default_server();
+ server.set_validation(ValidateAddress::Always);
+ let mut client = default_client();
+
+ let dgram = client.process(None, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram.as_ref(), now()).dgram(); // Initial w/token
+ assert!(dgram.is_some());
+
+ let mut encoder = Encoder::default();
+ encoder.encode_byte(0x80);
+ encoder.encode(&[0; 4]); // Zero version == VN.
+ encoder.encode_vec(1, &client.odcid().unwrap()[..]);
+ encoder.encode_vec(1, &[]);
+ encoder.encode_uint(4, 0x5a5a_6a6a_u64);
+ let vn = datagram(encoder.into());
+
+ assert_ne!(
+ client.process(Some(&vn), now()).callback(),
+ Duration::from_secs(0)
+ );
+}
+
+// This tests a simulated on-path attacker that intercepts the first
+// client Initial packet and spoofs a retry.
+// The tricky part is in rewriting the second client Initial so that
+// the server doesn't reject the Initial for having a bad token.
+// The client is the only one that can detect this, and that is because
+// the original connection ID is not in transport parameters.
+//
+// Note that this depends on having the server produce a CID that is
+// at least 8 bytes long. Otherwise, the second Initial won't have a
+// long enough connection ID.
+#[test]
+#[allow(clippy::shadow_unrelated)]
+fn mitm_retry() {
+ let mut client = default_client();
+ let mut retry_server = default_server();
+ retry_server.set_validation(ValidateAddress::Always);
+ let mut server = default_server();
+
+ // Trigger initial and a second client Initial.
+ let client_initial1 = client.process(None, now()).dgram();
+ assert!(client_initial1.is_some());
+ let retry = retry_server
+ .process(client_initial1.as_ref(), now())
+ .dgram();
+ assert!(retry.is_some());
+ let client_initial2 = client.process(retry.as_ref(), now()).dgram();
+ assert!(client_initial2.is_some());
+
+ // Now to start the epic process of decrypting the packet,
+ // rewriting the header to remove the token, and then re-encrypting.
+ let client_initial2 = client_initial2.unwrap();
+ let (protected_header, d_cid, s_cid, payload) =
+ decode_initial_header(&client_initial2, Role::Client);
+
+ // Now we have enough information to make keys.
+ let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
+ let (header, pn) = remove_header_protection(&hp, protected_header, payload);
+ let pn_len = header.len() - protected_header.len();
+
+ // Decrypt.
+ assert_eq!(pn, 1);
+ let mut plaintext_buf = vec![0; client_initial2.len()];
+ let plaintext = aead
+ .decrypt(pn, &header, &payload[pn_len..], &mut plaintext_buf)
+ .unwrap();
+
+ // Now re-encode without the token.
+ let mut enc = Encoder::with_capacity(header.len());
+ enc.encode(&header[..5])
+ .encode_vec(1, d_cid)
+ .encode_vec(1, s_cid)
+ .encode_vvec(&[])
+ .encode_varint(u64::try_from(payload.len()).unwrap());
+ let pn_offset = enc.len();
+ let notoken_header = enc.encode_uint(pn_len, pn).as_ref().to_vec();
+ qtrace!("notoken_header={}", hex_with_len(&notoken_header));
+
+ // Encrypt.
+ let mut notoken_packet = Encoder::with_capacity(1200)
+ .encode(&notoken_header)
+ .as_ref()
+ .to_vec();
+ notoken_packet.resize_with(1200, u8::default);
+ aead.encrypt(
+ pn,
+ &notoken_header,
+ plaintext,
+ &mut notoken_packet[notoken_header.len()..],
+ )
+ .unwrap();
+ // Unlike with decryption, don't truncate.
+ // All 1200 bytes are needed to reach the minimum datagram size.
+
+ apply_header_protection(&hp, &mut notoken_packet, pn_offset..(pn_offset + pn_len));
+ qtrace!("packet={}", hex_with_len(&notoken_packet));
+
+ let new_datagram = Datagram::new(
+ client_initial2.source(),
+ client_initial2.destination(),
+ client_initial2.tos(),
+ client_initial2.ttl(),
+ notoken_packet,
+ );
+ qdebug!("passing modified Initial to the main server");
+ let dgram = server.process(Some(&new_datagram), now()).dgram();
+ assert!(dgram.is_some());
+
+ let dgram = client.process(dgram.as_ref(), now()).dgram(); // Generate an ACK.
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram();
+ assert!(dgram.is_none());
+ assert!(test_fixture::maybe_authenticate(&mut client));
+ let dgram = client.process(dgram.as_ref(), now()).dgram();
+ assert!(dgram.is_some()); // Client sending CLOSE_CONNECTIONs
+ assert!(matches!(
+ *client.state(),
+ State::Closing {
+ error: ConnectionError::Transport(Error::ProtocolViolation),
+ ..
+ }
+ ));
+}
diff --git a/third_party/rust/neqo-transport/tests/server.rs b/third_party/rust/neqo-transport/tests/server.rs
new file mode 100644
index 0000000000..d6c9c2df95
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/server.rs
@@ -0,0 +1,779 @@
+// 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)]
+
+mod common;
+
+use std::{cell::RefCell, convert::TryFrom, mem, net::SocketAddr, rc::Rc, time::Duration};
+
+use common::{
+ apply_header_protection, connect, connected_server, decode_initial_header, default_server,
+ find_ticket, generate_ticket, initial_aead_and_hp, new_server, remove_header_protection,
+};
+use neqo_common::{qtrace, Datagram, Decoder, Encoder, Role};
+use neqo_crypto::{
+ generate_ech_keys, AllowZeroRtt, AuthenticationStatus, ZeroRttCheckResult, ZeroRttChecker,
+};
+use neqo_transport::{
+ server::{ActiveConnectionRef, Server, ValidateAddress},
+ Connection, ConnectionError, ConnectionParameters, Error, Output, State, StreamType, Version,
+};
+use test_fixture::{
+ self, assertions, datagram, default_client, new_client, now, split_datagram,
+ CountingConnectionIdGenerator,
+};
+
+/// Take a pair of connections in any state and complete the handshake.
+/// The `datagram` argument is a packet that was received from the server.
+/// See `connect` for what this returns.
+///
+/// # Panics
+///
+/// Only when the connection fails.
+pub fn complete_connection(
+ client: &mut Connection,
+ server: &mut Server,
+ mut datagram: Option<Datagram>,
+) -> ActiveConnectionRef {
+ let is_done = |c: &Connection| {
+ matches!(
+ c.state(),
+ State::Confirmed | State::Closing { .. } | State::Closed(..)
+ )
+ };
+ while !is_done(client) {
+ _ = test_fixture::maybe_authenticate(client);
+ let out = client.process(datagram.as_ref(), now());
+ let out = server.process(out.as_dgram_ref(), now());
+ datagram = out.dgram();
+ }
+
+ assert_eq!(*client.state(), State::Confirmed);
+ connected_server(server)
+}
+
+#[test]
+fn single_client() {
+ let mut server = default_server();
+ let mut client = default_client();
+ connect(&mut client, &mut server);
+}
+
+#[test]
+fn connect_single_version_both() {
+ fn connect_one_version(version: Version) {
+ let params = ConnectionParameters::default().versions(version, vec![version]);
+ let mut server = new_server(params.clone());
+
+ let mut client = new_client(params);
+ let server_conn = connect(&mut client, &mut server);
+ assert_eq!(client.version(), version);
+ assert_eq!(server_conn.borrow().version(), version);
+ }
+
+ for v in Version::all() {
+ println!("Connecting with {v:?}");
+ connect_one_version(v);
+ }
+}
+
+#[test]
+fn connect_single_version_client() {
+ fn connect_one_version(version: Version) {
+ let mut server = default_server();
+
+ let mut client =
+ new_client(ConnectionParameters::default().versions(version, vec![version]));
+ let server_conn = connect(&mut client, &mut server);
+ assert_eq!(client.version(), version);
+ assert_eq!(server_conn.borrow().version(), version);
+ }
+
+ for v in Version::all() {
+ println!("Connecting with {v:?}");
+ connect_one_version(v);
+ }
+}
+
+#[test]
+fn connect_single_version_server() {
+ fn connect_one_version(version: Version) {
+ let mut server =
+ new_server(ConnectionParameters::default().versions(version, vec![version]));
+
+ let mut client = default_client();
+
+ if client.version() != version {
+ // Run the version negotiation exchange if necessary.
+ let out = client.process_output(now());
+ assert!(out.as_dgram_ref().is_some());
+ let dgram = server.process(out.as_dgram_ref(), now()).dgram();
+ assertions::assert_vn(dgram.as_ref().unwrap());
+ client.process_input(&dgram.unwrap(), now());
+ }
+
+ let server_conn = connect(&mut client, &mut server);
+ assert_eq!(client.version(), version);
+ assert_eq!(server_conn.borrow().version(), version);
+ }
+
+ for v in Version::all() {
+ println!("Connecting with {v:?}");
+ connect_one_version(v);
+ }
+}
+
+#[test]
+fn duplicate_initial() {
+ let mut server = default_server();
+ let mut client = default_client();
+
+ assert_eq!(*client.state(), State::Init);
+ let initial = client.process(None, now());
+ assert!(initial.as_dgram_ref().is_some());
+
+ // The server should ignore a packets with the same remote address and
+ // destination connection ID as an existing connection attempt.
+ let server_initial = server.process(initial.as_dgram_ref(), now()).dgram();
+ assert!(server_initial.is_some());
+ let dgram = server.process(initial.as_dgram_ref(), now()).dgram();
+ assert!(dgram.is_none());
+
+ assert_eq!(server.active_connections().len(), 1);
+ complete_connection(&mut client, &mut server, server_initial);
+}
+
+#[test]
+fn duplicate_initial_new_path() {
+ let mut server = default_server();
+ let mut client = default_client();
+
+ assert_eq!(*client.state(), State::Init);
+ let initial = client.process(None, now()).dgram().unwrap();
+ let other = Datagram::new(
+ SocketAddr::new(initial.source().ip(), initial.source().port() ^ 23),
+ initial.destination(),
+ initial.tos(),
+ initial.ttl(),
+ &initial[..],
+ );
+
+ // The server should respond to both as these came from different addresses.
+ let dgram = server.process(Some(&other), now()).dgram();
+ assert!(dgram.is_some());
+
+ let server_initial = server.process(Some(&initial), now()).dgram();
+ assert!(server_initial.is_some());
+
+ assert_eq!(server.active_connections().len(), 2);
+ complete_connection(&mut client, &mut server, server_initial);
+}
+
+#[test]
+fn different_initials_same_path() {
+ let mut server = default_server();
+ let mut client1 = default_client();
+ let mut client2 = default_client();
+
+ let client_initial1 = client1.process(None, now());
+ assert!(client_initial1.as_dgram_ref().is_some());
+ let client_initial2 = client2.process(None, now());
+ assert!(client_initial2.as_dgram_ref().is_some());
+
+ // The server should respond to both as these came from different addresses.
+ let server_initial1 = server
+ .process(client_initial1.as_dgram_ref(), now())
+ .dgram();
+ assert!(server_initial1.is_some());
+
+ let server_initial2 = server
+ .process(client_initial2.as_dgram_ref(), now())
+ .dgram();
+ assert!(server_initial2.is_some());
+
+ assert_eq!(server.active_connections().len(), 2);
+ complete_connection(&mut client1, &mut server, server_initial1);
+ complete_connection(&mut client2, &mut server, server_initial2);
+}
+
+#[test]
+fn same_initial_after_connected() {
+ let mut server = default_server();
+ let mut client = default_client();
+
+ let client_initial = client.process(None, now());
+ assert!(client_initial.as_dgram_ref().is_some());
+
+ let server_initial = server.process(client_initial.as_dgram_ref(), now()).dgram();
+ assert!(server_initial.is_some());
+ complete_connection(&mut client, &mut server, server_initial);
+ // This removes the connection from the active set until something happens to it.
+ assert_eq!(server.active_connections().len(), 0);
+
+ // Now make a new connection using the exact same initial as before.
+ // The server should respond to an attempt to connect with the same Initial.
+ let dgram = server.process(client_initial.as_dgram_ref(), now()).dgram();
+ assert!(dgram.is_some());
+ // The server should make a new connection object.
+ assert_eq!(server.active_connections().len(), 1);
+}
+
+#[test]
+fn drop_non_initial() {
+ const CID: &[u8] = &[55; 8]; // not a real connection ID
+ let mut server = default_server();
+
+ // This is big enough to look like an Initial, but it uses the Retry type.
+ let mut header = neqo_common::Encoder::with_capacity(1200);
+ header
+ .encode_byte(0xfa)
+ .encode_uint(4, Version::default().wire_version())
+ .encode_vec(1, CID)
+ .encode_vec(1, CID);
+ let mut bogus_data: Vec<u8> = header.into();
+ bogus_data.resize(1200, 66);
+
+ let bogus = datagram(bogus_data);
+ assert!(server.process(Some(&bogus), now()).dgram().is_none());
+}
+
+#[test]
+fn drop_short_initial() {
+ const CID: &[u8] = &[55; 8]; // not a real connection ID
+ let mut server = default_server();
+
+ // This too small to be an Initial, but it is otherwise plausible.
+ let mut header = neqo_common::Encoder::with_capacity(1199);
+ header
+ .encode_byte(0xca)
+ .encode_uint(4, Version::default().wire_version())
+ .encode_vec(1, CID)
+ .encode_vec(1, CID);
+ let mut bogus_data: Vec<u8> = header.into();
+ bogus_data.resize(1199, 66);
+
+ let bogus = datagram(bogus_data);
+ assert!(server.process(Some(&bogus), now()).dgram().is_none());
+}
+
+/// Verify that the server can read 0-RTT properly. A more robust server would buffer
+/// 0-RTT before the handshake begins and let 0-RTT arrive for a short periiod after
+/// the handshake completes, but ours is for testing so it only allows 0-RTT while
+/// the handshake is running.
+#[test]
+fn zero_rtt() {
+ let mut server = default_server();
+ let token = generate_ticket(&mut server);
+
+ // Discharge the old connection so that we don't have to worry about it.
+ let mut now = now();
+ let t = server.process(None, now).callback();
+ now += t;
+ assert_eq!(server.process(None, now), Output::None);
+ assert_eq!(server.active_connections().len(), 1);
+
+ let start_time = now;
+ let mut client = default_client();
+ client.enable_resumption(now, &token).unwrap();
+
+ let mut client_send = || {
+ let client_stream = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream, &[1, 2, 3]).unwrap();
+ match client.process(None, now) {
+ Output::Datagram(d) => d,
+ Output::Callback(t) => {
+ // Pacing...
+ now += t;
+ client.process(None, now).dgram().unwrap()
+ }
+ Output::None => panic!(),
+ }
+ };
+
+ // Now generate a bunch of 0-RTT packets...
+ let c1 = client_send();
+ assertions::assert_coalesced_0rtt(&c1);
+ let c2 = client_send();
+ let c3 = client_send();
+ let c4 = client_send();
+
+ // 0-RTT packets that arrive before the handshake get dropped.
+ mem::drop(server.process(Some(&c2), now));
+ assert!(server.active_connections().is_empty());
+
+ // Now handshake and let another 0-RTT packet in.
+ let shs = server.process(Some(&c1), now);
+ mem::drop(server.process(Some(&c3), now));
+ // The server will have received two STREAM frames now if it processed both packets.
+ let active = server.active_connections();
+ assert_eq!(active.len(), 1);
+ assert_eq!(active[0].borrow().stats().frame_rx.stream, 2);
+
+ // Complete the handshake. As the client was pacing 0-RTT packets, extend the time
+ // a little so that the pacer doesn't prevent the Finished from being sent.
+ now += now - start_time;
+ let cfin = client.process(shs.as_dgram_ref(), now);
+ mem::drop(server.process(cfin.as_dgram_ref(), now));
+
+ // The server will drop this last 0-RTT packet.
+ mem::drop(server.process(Some(&c4), now));
+ let active = server.active_connections();
+ assert_eq!(active.len(), 1);
+ assert_eq!(active[0].borrow().stats().frame_rx.stream, 2);
+}
+
+#[test]
+fn new_token_0rtt() {
+ let mut server = default_server();
+ let token = generate_ticket(&mut server);
+ server.set_validation(ValidateAddress::NoToken);
+
+ let mut client = default_client();
+ client.enable_resumption(now(), &token).unwrap();
+
+ let client_stream = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream, &[1, 2, 3]).unwrap();
+
+ let out = client.process(None, now()); // Initial w/0-RTT
+ assert!(out.as_dgram_ref().is_some());
+ assertions::assert_initial(out.as_dgram_ref().unwrap(), true);
+ assertions::assert_coalesced_0rtt(out.as_dgram_ref().unwrap());
+ let out = server.process(out.as_dgram_ref(), now()); // Initial
+ assert!(out.as_dgram_ref().is_some());
+ assertions::assert_initial(out.as_dgram_ref().unwrap(), false);
+
+ let dgram = client.process(out.as_dgram_ref(), now());
+ // Note: the client doesn't need to authenticate the server here
+ // as there is no certificate; authentication is based on the ticket.
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram.as_dgram_ref(), now()); // (done)
+ assert!(dgram.as_dgram_ref().is_some());
+ connected_server(&mut server);
+ assert!(client.tls_info().unwrap().resumed());
+}
+
+#[test]
+fn new_token_different_port() {
+ let mut server = default_server();
+ let token = generate_ticket(&mut server);
+ server.set_validation(ValidateAddress::NoToken);
+
+ let mut client = default_client();
+ client.enable_resumption(now(), &token).unwrap();
+
+ let dgram = client.process(None, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), true);
+
+ // Now rewrite the source port, which should not change that the token is OK.
+ let d = dgram.unwrap();
+ let src = SocketAddr::new(d.source().ip(), d.source().port() + 1);
+ let dgram = Some(Datagram::new(
+ src,
+ d.destination(),
+ d.tos(),
+ d.ttl(),
+ &d[..],
+ ));
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // Retry
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), false);
+}
+
+#[test]
+fn bad_client_initial() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let dgram = client.process(None, now()).dgram().expect("a datagram");
+ let (header, d_cid, s_cid, payload) = decode_initial_header(&dgram, Role::Client);
+ let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
+ let (fixed_header, pn) = remove_header_protection(&hp, header, payload);
+ let payload = &payload[(fixed_header.len() - header.len())..];
+
+ let mut plaintext_buf = vec![0; dgram.len()];
+ let plaintext = aead
+ .decrypt(pn, &fixed_header, payload, &mut plaintext_buf)
+ .unwrap();
+
+ let mut payload_enc = Encoder::from(plaintext);
+ payload_enc.encode(&[0x08, 0x02, 0x00, 0x00]); // Add a stream frame.
+
+ // Make a new header with a 1 byte packet number length.
+ let mut header_enc = Encoder::new();
+ header_enc
+ .encode_byte(0xc0) // Initial with 1 byte packet number.
+ .encode_uint(4, Version::default().wire_version())
+ .encode_vec(1, d_cid)
+ .encode_vec(1, s_cid)
+ .encode_vvec(&[])
+ .encode_varint(u64::try_from(payload_enc.len() + aead.expansion() + 1).unwrap())
+ .encode_byte(u8::try_from(pn).unwrap());
+
+ let mut ciphertext = header_enc.as_ref().to_vec();
+ ciphertext.resize(header_enc.len() + payload_enc.len() + aead.expansion(), 0);
+ let v = aead
+ .encrypt(
+ pn,
+ header_enc.as_ref(),
+ payload_enc.as_ref(),
+ &mut ciphertext[header_enc.len()..],
+ )
+ .unwrap();
+ assert_eq!(header_enc.len() + v.len(), ciphertext.len());
+ // Pad with zero to get up to 1200.
+ ciphertext.resize(1200, 0);
+
+ apply_header_protection(
+ &hp,
+ &mut ciphertext,
+ (header_enc.len() - 1)..header_enc.len(),
+ );
+ let bad_dgram = Datagram::new(
+ dgram.source(),
+ dgram.destination(),
+ dgram.tos(),
+ dgram.ttl(),
+ ciphertext,
+ );
+
+ // The server should reject this.
+ let response = server.process(Some(&bad_dgram), now());
+ let close_dgram = response.dgram().unwrap();
+ // The resulting datagram might contain multiple packets, but each is small.
+ let (initial_close, rest) = split_datagram(&close_dgram);
+ // Allow for large connection IDs and a 32 byte CONNECTION_CLOSE.
+ assert!(initial_close.len() <= 100);
+ let (handshake_close, short_close) = split_datagram(&rest.unwrap());
+ // The Handshake packet containing the close is the same size as the Initial,
+ // plus 1 byte for the Token field in the Initial.
+ assert_eq!(initial_close.len(), handshake_close.len() + 1);
+ assert!(short_close.unwrap().len() <= 73);
+
+ // The client should accept this new and stop trying to connect.
+ // It will generate a CONNECTION_CLOSE first though.
+ let response = client.process(Some(&close_dgram), now()).dgram();
+ assert!(response.is_some());
+ // The client will now wait out its closing period.
+ let delay = client.process(None, now()).callback();
+ assert_ne!(delay, Duration::from_secs(0));
+ assert!(matches!(
+ *client.state(),
+ State::Draining { error: ConnectionError::Transport(Error::PeerError(code)), .. } if code == Error::ProtocolViolation.code()
+ ));
+
+ for server in server.active_connections() {
+ assert_eq!(
+ *server.borrow().state(),
+ State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
+ );
+ }
+
+ // After sending the CONNECTION_CLOSE, the server goes idle.
+ let res = server.process(None, now());
+ assert_eq!(res, Output::None);
+}
+
+#[test]
+fn version_negotiation_ignored() {
+ let mut server = default_server();
+ let mut client = default_client();
+
+ // Any packet will do, but let's make something that looks real.
+ let dgram = client.process(None, now()).dgram().expect("a datagram");
+ let mut input = dgram.to_vec();
+ input[1] ^= 0x12;
+ let damaged = Datagram::new(
+ dgram.source(),
+ dgram.destination(),
+ dgram.tos(),
+ dgram.ttl(),
+ input.clone(),
+ );
+ let vn = server.process(Some(&damaged), now()).dgram();
+
+ let mut dec = Decoder::from(&input[5..]); // Skip past version.
+ let d_cid = dec.decode_vec(1).expect("client DCID").to_vec();
+ let s_cid = dec.decode_vec(1).expect("client SCID").to_vec();
+
+ // We should have received a VN packet.
+ let vn = vn.expect("a vn packet");
+ let mut dec = Decoder::from(&vn[1..]); // Skip first byte.
+
+ assert_eq!(dec.decode_uint(4).expect("VN"), 0);
+ assert_eq!(dec.decode_vec(1).expect("VN DCID"), &s_cid[..]);
+ assert_eq!(dec.decode_vec(1).expect("VN SCID"), &d_cid[..]);
+ let mut found = false;
+ while dec.remaining() > 0 {
+ let v = dec.decode_uint(4).expect("supported version");
+ found |= v == u64::from(Version::default().wire_version());
+ }
+ assert!(found, "valid version not found");
+
+ // Client ignores VN packet that contain negotiated version.
+ let res = client.process(Some(&vn), now());
+ assert!(res.callback() > Duration::new(0, 120));
+ assert_eq!(client.state(), &State::WaitInitial);
+}
+
+/// Test that if the server doesn't support a version it will signal with a
+/// Version Negotiation packet and the client will use that version.
+#[test]
+fn version_negotiation() {
+ const VN_VERSION: Version = Version::Draft29;
+ assert_ne!(VN_VERSION, Version::default());
+ assert!(!Version::default().is_compatible(VN_VERSION));
+
+ let mut server =
+ new_server(ConnectionParameters::default().versions(VN_VERSION, vec![VN_VERSION]));
+ let mut client = default_client();
+
+ // `connect()` runs a fixed exchange, so manually run the Version Negotiation.
+ let dgram = client.process_output(now()).dgram();
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram.as_ref(), now()).dgram();
+ assertions::assert_vn(dgram.as_ref().unwrap());
+ client.process_input(&dgram.unwrap(), now());
+
+ let sconn = connect(&mut client, &mut server);
+ assert_eq!(client.version(), VN_VERSION);
+ assert_eq!(sconn.borrow().version(), VN_VERSION);
+}
+
+/// Test that the client can pick a version from a Version Negotiation packet,
+/// which is then subsequently upgraded to a compatible version by the server.
+#[test]
+fn version_negotiation_and_compatible() {
+ const ORIG_VERSION: Version = Version::Draft29;
+ const VN_VERSION: Version = Version::Version1;
+ const COMPAT_VERSION: Version = Version::Version2;
+ assert!(!ORIG_VERSION.is_compatible(VN_VERSION));
+ assert!(!ORIG_VERSION.is_compatible(COMPAT_VERSION));
+ assert!(VN_VERSION.is_compatible(COMPAT_VERSION));
+
+ let mut server = new_server(
+ ConnectionParameters::default().versions(VN_VERSION, vec![COMPAT_VERSION, VN_VERSION]),
+ );
+ // Note that the order of versions at the client only determines what it tries first.
+ // The server will pick between VN_VERSION and COMPAT_VERSION.
+ let mut client = new_client(
+ ConnectionParameters::default()
+ .versions(ORIG_VERSION, vec![ORIG_VERSION, VN_VERSION, COMPAT_VERSION]),
+ );
+
+ // Run the full exchange so that we can observe the versions in use.
+
+ // Version Negotiation
+ let dgram = client.process_output(now()).dgram();
+ assert!(dgram.is_some());
+ assertions::assert_version(dgram.as_ref().unwrap(), ORIG_VERSION.wire_version());
+ let dgram = server.process(dgram.as_ref(), now()).dgram();
+ assertions::assert_vn(dgram.as_ref().unwrap());
+ client.process_input(&dgram.unwrap(), now());
+
+ let dgram = client.process(None, now()).dgram(); // ClientHello
+ assertions::assert_version(dgram.as_ref().unwrap(), VN_VERSION.wire_version());
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // ServerHello...
+ assertions::assert_version(dgram.as_ref().unwrap(), COMPAT_VERSION.wire_version());
+ client.process_input(&dgram.unwrap(), now());
+
+ client.authenticated(AuthenticationStatus::Ok, now());
+ let dgram = client.process_output(now()).dgram();
+ assertions::assert_version(dgram.as_ref().unwrap(), COMPAT_VERSION.wire_version());
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram.as_ref(), now()).dgram(); // ACK + HANDSHAKE_DONE + NST
+ client.process_input(&dgram.unwrap(), now());
+ assert_eq!(*client.state(), State::Confirmed);
+
+ let sconn = connected_server(&mut server);
+ assert_eq!(client.version(), COMPAT_VERSION);
+ assert_eq!(sconn.borrow().version(), COMPAT_VERSION);
+}
+
+/// When a client resumes it remembers the version that the connection last used.
+/// A subsequent connection will use that version, but if it then receives
+/// a version negotiation packet, it should validate based on what it attempted
+/// not what it was originally configured for.
+#[test]
+fn compatible_upgrade_resumption_and_vn() {
+ // Start at v1, compatible upgrade to v2.
+ const ORIG_VERSION: Version = Version::Version1;
+ const COMPAT_VERSION: Version = Version::Version2;
+ const RESUMPTION_VERSION: Version = Version::Draft29;
+
+ let client_params = ConnectionParameters::default().versions(
+ ORIG_VERSION,
+ vec![COMPAT_VERSION, ORIG_VERSION, RESUMPTION_VERSION],
+ );
+ let mut client = new_client(client_params.clone());
+ assert_eq!(client.version(), ORIG_VERSION);
+
+ let mut server = default_server();
+ let mut server_conn = connect(&mut client, &mut server);
+ assert_eq!(client.version(), COMPAT_VERSION);
+ assert_eq!(server_conn.borrow().version(), COMPAT_VERSION);
+
+ server_conn.borrow_mut().send_ticket(now(), &[]).unwrap();
+ let dgram = server.process(None, now()).dgram();
+ client.process_input(&dgram.unwrap(), now()); // Consume ticket, ignore output.
+ let ticket = find_ticket(&mut client);
+
+ // This new server will reject the ticket, but it will also generate a VN packet.
+ let mut client = new_client(client_params);
+ let mut server = new_server(
+ ConnectionParameters::default().versions(RESUMPTION_VERSION, vec![RESUMPTION_VERSION]),
+ );
+ client.enable_resumption(now(), ticket).unwrap();
+
+ // The version negotiation exchange.
+ let dgram = client.process_output(now()).dgram();
+ assert!(dgram.is_some());
+ assertions::assert_version(dgram.as_ref().unwrap(), COMPAT_VERSION.wire_version());
+ let dgram = server.process(dgram.as_ref(), now()).dgram();
+ assertions::assert_vn(dgram.as_ref().unwrap());
+ client.process_input(&dgram.unwrap(), now());
+
+ let server_conn = connect(&mut client, &mut server);
+ assert_eq!(client.version(), RESUMPTION_VERSION);
+ assert_eq!(server_conn.borrow().version(), RESUMPTION_VERSION);
+}
+
+#[test]
+fn closed() {
+ // Let a server connection idle and it should be removed.
+ let mut server = default_server();
+ let mut client = default_client();
+ connect(&mut client, &mut server);
+
+ // The server will have sent a few things, so it will be on PTO.
+ let res = server.process(None, now());
+ assert!(res.callback() > Duration::new(0, 0));
+ // The client will be on the delayed ACK timer.
+ let res = client.process(None, now());
+ assert!(res.callback() > Duration::new(0, 0));
+
+ qtrace!("60s later");
+ let res = server.process(None, now() + Duration::from_secs(60));
+ assert_eq!(res, Output::None);
+}
+
+fn can_create_streams(c: &mut Connection, t: StreamType, n: u64) {
+ for _ in 0..n {
+ c.stream_create(t).unwrap();
+ }
+ assert_eq!(c.stream_create(t), Err(Error::StreamLimitError));
+}
+
+#[test]
+fn max_streams() {
+ const MAX_STREAMS: u64 = 40;
+ let mut server = Server::new(
+ now(),
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ test_fixture::anti_replay(),
+ Box::new(AllowZeroRtt {}),
+ Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
+ ConnectionParameters::default()
+ .max_streams(StreamType::BiDi, MAX_STREAMS)
+ .max_streams(StreamType::UniDi, MAX_STREAMS),
+ )
+ .expect("should create a server");
+
+ let mut client = default_client();
+ connect(&mut client, &mut server);
+
+ // Make sure that we can create MAX_STREAMS uni- and bidirectional streams.
+ can_create_streams(&mut client, StreamType::UniDi, MAX_STREAMS);
+ can_create_streams(&mut client, StreamType::BiDi, MAX_STREAMS);
+}
+
+#[test]
+fn max_streams_default() {
+ let mut server = Server::new(
+ now(),
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ test_fixture::anti_replay(),
+ Box::new(AllowZeroRtt {}),
+ Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
+ ConnectionParameters::default(),
+ )
+ .expect("should create a server");
+
+ let mut client = default_client();
+ connect(&mut client, &mut server);
+
+ // Make sure that we can create streams up to the local limit.
+ let local_limit_unidi = ConnectionParameters::default().get_max_streams(StreamType::UniDi);
+ can_create_streams(&mut client, StreamType::UniDi, local_limit_unidi);
+ let local_limit_bidi = ConnectionParameters::default().get_max_streams(StreamType::BiDi);
+ can_create_streams(&mut client, StreamType::BiDi, local_limit_bidi);
+}
+
+#[derive(Debug)]
+struct RejectZeroRtt {}
+impl ZeroRttChecker for RejectZeroRtt {
+ fn check(&self, _token: &[u8]) -> ZeroRttCheckResult {
+ ZeroRttCheckResult::Reject
+ }
+}
+
+#[test]
+fn max_streams_after_0rtt_rejection() {
+ const MAX_STREAMS_BIDI: u64 = 40;
+ const MAX_STREAMS_UNIDI: u64 = 30;
+ let mut server = Server::new(
+ now(),
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ test_fixture::anti_replay(),
+ Box::new(RejectZeroRtt {}),
+ Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
+ ConnectionParameters::default()
+ .max_streams(StreamType::BiDi, MAX_STREAMS_BIDI)
+ .max_streams(StreamType::UniDi, MAX_STREAMS_UNIDI),
+ )
+ .expect("should create a server");
+ let token = generate_ticket(&mut server);
+
+ let mut client = default_client();
+ client.enable_resumption(now(), &token).unwrap();
+ _ = client.stream_create(StreamType::BiDi).unwrap();
+ let dgram = client.process_output(now()).dgram();
+ let dgram = server.process(dgram.as_ref(), now()).dgram();
+ let dgram = client.process(dgram.as_ref(), now()).dgram();
+ assert!(dgram.is_some()); // We're far enough along to complete the test now.
+
+ // Make sure that we can create MAX_STREAMS uni- and bidirectional streams.
+ can_create_streams(&mut client, StreamType::UniDi, MAX_STREAMS_UNIDI);
+ can_create_streams(&mut client, StreamType::BiDi, MAX_STREAMS_BIDI);
+}
+
+#[test]
+fn ech() {
+ // Check that ECH can be used.
+ let mut server = default_server();
+ let (sk, pk) = generate_ech_keys().unwrap();
+ server.enable_ech(0x4a, "public.example", &sk, &pk).unwrap();
+
+ let mut client = default_client();
+ client.client_enable_ech(server.ech_config()).unwrap();
+ let server_instance = connect(&mut client, &mut server);
+
+ assert!(client.tls_info().unwrap().ech_accepted());
+ assert!(server_instance.borrow().tls_info().unwrap().ech_accepted());
+ assert!(client.tls_preinfo().unwrap().ech_accepted().unwrap());
+ assert!(server_instance
+ .borrow()
+ .tls_preinfo()
+ .unwrap()
+ .ech_accepted()
+ .unwrap());
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/connection.rs b/third_party/rust/neqo-transport/tests/sim/connection.rs
new file mode 100644
index 0000000000..45a5234512
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/connection.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.
+
+#![allow(clippy::module_name_repetitions)]
+
+use std::{
+ cmp::min,
+ fmt::{self, Debug},
+ time::Instant,
+};
+
+use neqo_common::{event::Provider, qdebug, qtrace, Datagram};
+use neqo_crypto::AuthenticationStatus;
+use neqo_transport::{
+ Connection, ConnectionEvent, ConnectionParameters, Output, State, StreamId, StreamType,
+};
+
+use super::{Node, Rng};
+
+/// The status of the processing of an event.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum GoalStatus {
+ /// The event didn't result in doing anything; the goal is waiting for something.
+ Waiting,
+ /// An action was taken as a result of the event.
+ Active,
+ /// The goal was accomplished.
+ Done,
+}
+
+/// A goal for the connection.
+/// Goals can be accomplished in any order.
+pub trait ConnectionGoal {
+ fn init(&mut self, _c: &mut Connection, _now: Instant) {}
+ /// Perform some processing.
+ fn process(&mut self, _c: &mut Connection, _now: Instant) -> GoalStatus {
+ GoalStatus::Waiting
+ }
+ /// Handle an event from the provided connection, returning `true` when the
+ /// goal is achieved.
+ fn handle_event(&mut self, c: &mut Connection, e: &ConnectionEvent, now: Instant)
+ -> GoalStatus;
+}
+
+pub struct ConnectionNode {
+ c: Connection,
+ goals: Vec<Box<dyn ConnectionGoal>>,
+}
+
+impl ConnectionNode {
+ pub fn new_client(
+ params: ConnectionParameters,
+ goals: impl IntoIterator<Item = Box<dyn ConnectionGoal>>,
+ ) -> Self {
+ Self {
+ c: test_fixture::new_client(params),
+ goals: goals.into_iter().collect(),
+ }
+ }
+
+ pub fn new_server(
+ params: ConnectionParameters,
+ goals: impl IntoIterator<Item = Box<dyn ConnectionGoal>>,
+ ) -> Self {
+ Self {
+ c: test_fixture::new_server(test_fixture::DEFAULT_ALPN, params),
+ goals: goals.into_iter().collect(),
+ }
+ }
+
+ pub fn default_client(goals: impl IntoIterator<Item = Box<dyn ConnectionGoal>>) -> Self {
+ Self::new_client(ConnectionParameters::default(), goals)
+ }
+
+ pub fn default_server(goals: impl IntoIterator<Item = Box<dyn ConnectionGoal>>) -> Self {
+ Self::new_server(ConnectionParameters::default(), goals)
+ }
+
+ #[allow(dead_code)]
+ pub fn clear_goals(&mut self) {
+ self.goals.clear();
+ }
+
+ #[allow(dead_code)]
+ pub fn add_goal(&mut self, goal: Box<dyn ConnectionGoal>) {
+ self.goals.push(goal);
+ }
+
+ /// Process all goals using the given closure and return whether any were active.
+ fn process_goals<F>(&mut self, mut f: F) -> bool
+ where
+ F: FnMut(&mut Box<dyn ConnectionGoal>, &mut Connection) -> GoalStatus,
+ {
+ // Waiting on drain_filter...
+ // self.goals.drain_filter(|g| f(g, &mut self.c, &e)).count();
+ let mut active = false;
+ let mut i = 0;
+ while i < self.goals.len() {
+ let status = f(&mut self.goals[i], &mut self.c);
+ if status == GoalStatus::Done {
+ self.goals.remove(i);
+ active = true;
+ } else {
+ active |= status == GoalStatus::Active;
+ i += 1;
+ }
+ }
+ active
+ }
+}
+
+impl Node for ConnectionNode {
+ fn init(&mut self, _rng: Rng, now: Instant) {
+ for g in &mut self.goals {
+ g.init(&mut self.c, now);
+ }
+ }
+
+ fn process(&mut self, mut d: Option<Datagram>, now: Instant) -> Output {
+ _ = self.process_goals(|goal, c| goal.process(c, now));
+ loop {
+ let res = self.c.process(d.take().as_ref(), now);
+
+ let mut active = false;
+ while let Some(e) = self.c.next_event() {
+ qtrace!([self.c], "received event {:?}", e);
+
+ // Perform authentication automatically.
+ if matches!(e, ConnectionEvent::AuthenticationNeeded) {
+ self.c.authenticated(AuthenticationStatus::Ok, now);
+ }
+
+ active |= self.process_goals(|goal, c| goal.handle_event(c, &e, now));
+ }
+ // Exit at this point if the connection produced a datagram.
+ // We also exit if none of the goals were active, as there is
+ // no point trying again if they did nothing.
+ if matches!(res, Output::Datagram(_)) || !active {
+ return res;
+ }
+ qdebug!([self.c], "no datagram and goal activity, looping");
+ }
+ }
+
+ fn done(&self) -> bool {
+ self.goals.is_empty()
+ }
+
+ fn print_summary(&self, test_name: &str) {
+ println!("{}: {:?}", test_name, self.c.stats());
+ }
+}
+
+impl Debug for ConnectionNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.c, f)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct ReachState {
+ target: State,
+}
+
+impl ReachState {
+ pub fn new(target: State) -> Self {
+ Self { target }
+ }
+}
+
+impl ConnectionGoal for ReachState {
+ fn handle_event(
+ &mut self,
+ _c: &mut Connection,
+ e: &ConnectionEvent,
+ _now: Instant,
+ ) -> GoalStatus {
+ if matches!(e, ConnectionEvent::StateChange(state) if *state == self.target) {
+ GoalStatus::Done
+ } else {
+ GoalStatus::Waiting
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct SendData {
+ remaining: usize,
+ stream_id: Option<StreamId>,
+}
+
+impl SendData {
+ pub fn new(amount: usize) -> Self {
+ Self {
+ remaining: amount,
+ stream_id: None,
+ }
+ }
+
+ fn make_stream(&mut self, c: &mut Connection) {
+ if self.stream_id.is_none() {
+ if let Ok(stream_id) = c.stream_create(StreamType::UniDi) {
+ qdebug!([c], "made stream {} for sending", stream_id);
+ self.stream_id = Some(stream_id);
+ }
+ }
+ }
+
+ fn send(&mut self, c: &mut Connection, stream_id: StreamId) -> GoalStatus {
+ const DATA: &[u8] = &[0; 4096];
+ let mut status = GoalStatus::Waiting;
+ loop {
+ let end = min(self.remaining, DATA.len());
+ let sent = c.stream_send(stream_id, &DATA[..end]).unwrap();
+ if sent == 0 {
+ return status;
+ }
+ self.remaining -= sent;
+ qtrace!("sent {} remaining {}", sent, self.remaining);
+ if self.remaining == 0 {
+ c.stream_close_send(stream_id).unwrap();
+ return GoalStatus::Done;
+ }
+ status = GoalStatus::Active;
+ }
+ }
+}
+
+impl ConnectionGoal for SendData {
+ fn init(&mut self, c: &mut Connection, _now: Instant) {
+ self.make_stream(c);
+ }
+
+ fn process(&mut self, c: &mut Connection, _now: Instant) -> GoalStatus {
+ self.stream_id
+ .map_or(GoalStatus::Waiting, |stream_id| self.send(c, stream_id))
+ }
+
+ fn handle_event(
+ &mut self,
+ c: &mut Connection,
+ e: &ConnectionEvent,
+ _now: Instant,
+ ) -> GoalStatus {
+ match e {
+ ConnectionEvent::SendStreamCreatable {
+ stream_type: StreamType::UniDi,
+ }
+ // TODO(mt): remove the second condition when #842 is fixed.
+ | ConnectionEvent::StateChange(_) => {
+ self.make_stream(c);
+ GoalStatus::Active
+ }
+
+ ConnectionEvent::SendStreamWritable { stream_id }
+ if Some(*stream_id) == self.stream_id =>
+ {
+ self.send(c, *stream_id)
+ }
+
+ // If we sent data in 0-RTT, then we didn't track how much we should
+ // have sent. This is trivial to fix if 0-RTT testing is ever needed.
+ ConnectionEvent::ZeroRttRejected => panic!("not supported"),
+ _ => GoalStatus::Waiting,
+ }
+ }
+}
+
+/// Receive a prescribed amount of data from any stream.
+#[derive(Debug)]
+pub struct ReceiveData {
+ remaining: usize,
+}
+
+impl ReceiveData {
+ pub fn new(amount: usize) -> Self {
+ Self { remaining: amount }
+ }
+
+ fn recv(&mut self, c: &mut Connection, stream_id: StreamId) -> GoalStatus {
+ let mut buf = vec![0; 4096];
+ let mut status = GoalStatus::Waiting;
+ loop {
+ let end = min(self.remaining, buf.len());
+ let (recvd, _) = c.stream_recv(stream_id, &mut buf[..end]).unwrap();
+ qtrace!("received {} remaining {}", recvd, self.remaining);
+ if recvd == 0 {
+ return status;
+ }
+ self.remaining -= recvd;
+ if self.remaining == 0 {
+ return GoalStatus::Done;
+ }
+ status = GoalStatus::Active;
+ }
+ }
+}
+
+impl ConnectionGoal for ReceiveData {
+ fn handle_event(
+ &mut self,
+ c: &mut Connection,
+ e: &ConnectionEvent,
+ _now: Instant,
+ ) -> GoalStatus {
+ if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
+ self.recv(c, *stream_id)
+ } else {
+ GoalStatus::Waiting
+ }
+ }
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/delay.rs b/third_party/rust/neqo-transport/tests/sim/delay.rs
new file mode 100644
index 0000000000..34cb923084
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/delay.rs
@@ -0,0 +1,102 @@
+// 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::{
+ collections::BTreeMap,
+ convert::TryFrom,
+ fmt::{self, Debug},
+ ops::Range,
+ time::{Duration, Instant},
+};
+
+use neqo_common::Datagram;
+use neqo_transport::Output;
+
+use super::{Node, Rng};
+
+/// An iterator that shares a `Random` instance and produces uniformly
+/// random `Duration`s within a specified range.
+pub struct RandomDelay {
+ start: Duration,
+ max: u64,
+ rng: Option<Rng>,
+}
+
+impl RandomDelay {
+ /// Make a new random `Duration` generator. This panics if the range provided
+ /// is inverted (i.e., `bounds.start > bounds.end`), or spans 2^64
+ /// or more nanoseconds.
+ /// A zero-length range means that random values won't be taken from the Rng
+ pub fn new(bounds: Range<Duration>) -> Self {
+ let max = u64::try_from((bounds.end - bounds.start).as_nanos()).unwrap();
+ Self {
+ start: bounds.start,
+ max,
+ rng: None,
+ }
+ }
+
+ pub fn set_rng(&mut self, rng: Rng) {
+ self.rng = Some(rng);
+ }
+
+ pub fn next(&mut self) -> Duration {
+ let mut rng = self.rng.as_ref().unwrap().borrow_mut();
+ let r = rng.random_from(0..self.max);
+ self.start + Duration::from_nanos(r)
+ }
+}
+
+pub struct Delay {
+ random: RandomDelay,
+ queue: BTreeMap<Instant, Datagram>,
+}
+
+impl Delay {
+ pub fn new(bounds: Range<Duration>) -> Self {
+ Self {
+ random: RandomDelay::new(bounds),
+ queue: BTreeMap::default(),
+ }
+ }
+
+ fn insert(&mut self, d: Datagram, now: Instant) {
+ let mut t = now + self.random.next();
+ while self.queue.contains_key(&t) {
+ // This is a little inefficient, but it avoids drops on collisions,
+ // which are super-common for a fixed delay.
+ t += Duration::from_nanos(1);
+ }
+ self.queue.insert(t, d);
+ }
+}
+
+impl Node for Delay {
+ fn init(&mut self, rng: Rng, _now: Instant) {
+ self.random.set_rng(rng);
+ }
+
+ fn process(&mut self, d: Option<Datagram>, now: Instant) -> Output {
+ if let Some(dgram) = d {
+ self.insert(dgram, now);
+ }
+ if let Some((&k, _)) = self.queue.range(..=now).next() {
+ Output::Datagram(self.queue.remove(&k).unwrap())
+ } else if let Some(&t) = self.queue.keys().next() {
+ Output::Callback(t - now)
+ } else {
+ Output::None
+ }
+ }
+}
+
+impl Debug for Delay {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("delay")
+ }
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/drop.rs b/third_party/rust/neqo-transport/tests/sim/drop.rs
new file mode 100644
index 0000000000..629fbf48d3
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/drop.rs
@@ -0,0 +1,75 @@
+// 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::{self, Debug},
+ time::Instant,
+};
+
+use neqo_common::{qtrace, Datagram};
+use neqo_transport::Output;
+
+use super::{Node, Rng};
+
+/// A random dropper.
+pub struct Drop {
+ threshold: u64,
+ max: u64,
+ rng: Option<Rng>,
+}
+
+impl Drop {
+ /// Make a new random drop generator. Each `drop` is called, this generates a
+ /// random value between 0 and `max` (exclusive). If this value is less than
+ /// `threshold` a value of `true` is returned.
+ pub fn new(threshold: u64, max: u64) -> Self {
+ Self {
+ threshold,
+ max,
+ rng: None,
+ }
+ }
+
+ /// Generate random drops with the given percentage.
+ pub fn percentage(pct: u8) -> Self {
+ // Multiply by 10 so that the random number generator works more efficiently.
+ Self::new(u64::from(pct) * 10, 1000)
+ }
+
+ pub fn drop(&mut self) -> bool {
+ let mut rng = self.rng.as_ref().unwrap().borrow_mut();
+ let r = rng.random_from(0..self.max);
+ r < self.threshold
+ }
+}
+
+impl Node for Drop {
+ fn init(&mut self, rng: Rng, _now: Instant) {
+ self.rng = Some(rng);
+ }
+
+ // Pass any datagram provided directly out, but drop some of them.
+ fn process(&mut self, d: Option<Datagram>, _now: Instant) -> Output {
+ if let Some(dgram) = d {
+ if self.drop() {
+ qtrace!("drop {}", dgram.len());
+ Output::None
+ } else {
+ Output::Datagram(dgram)
+ }
+ } else {
+ Output::None
+ }
+ }
+}
+
+impl Debug for Drop {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("drop")
+ }
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/mod.rs b/third_party/rust/neqo-transport/tests/sim/mod.rs
new file mode 100644
index 0000000000..9ab9d57a4a
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/mod.rs
@@ -0,0 +1,232 @@
+// 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.
+
+// Tests with simulated network
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+
+pub mod connection;
+mod delay;
+mod drop;
+pub mod rng;
+mod taildrop;
+
+use std::{
+ cell::RefCell,
+ cmp::min,
+ convert::TryFrom,
+ fmt::Debug,
+ rc::Rc,
+ time::{Duration, Instant},
+};
+
+use neqo_common::{qdebug, qinfo, qtrace, Datagram, Encoder};
+use neqo_transport::Output;
+use rng::Random;
+use test_fixture::{self, now};
+use NodeState::{Active, Idle, Waiting};
+
+pub mod network {
+ pub use super::{delay::Delay, drop::Drop, taildrop::TailDrop};
+}
+
+type Rng = Rc<RefCell<Random>>;
+
+/// A macro that turns a list of values into boxed versions of the same.
+#[macro_export]
+macro_rules! boxed {
+ [$($v:expr),+ $(,)?] => {
+ vec![ $( Box::new($v) as _ ),+ ]
+ };
+}
+
+/// Create a simulation test case. This takes either two or three arguments.
+/// The two argument form takes a bare name (`ident`), a comma, and an array of
+/// items that implement `Node`.
+/// The three argument form adds a setup block that can be used to construct a
+/// complex value that is then shared between all nodes. The values in the
+/// three-argument form have to be closures (or functions) that accept a reference
+/// to the value returned by the setup.
+#[macro_export]
+macro_rules! simulate {
+ ($n:ident, [ $($v:expr),+ $(,)? ] $(,)?) => {
+ simulate!($n, (), [ $(|_| $v),+ ]);
+ };
+ ($n:ident, $setup:expr, [ $( $v:expr ),+ $(,)? ] $(,)?) => {
+ #[test]
+ fn $n() {
+ let fixture = $setup;
+ let mut nodes: Vec<Box<dyn $crate::sim::Node>> = Vec::new();
+ $(
+ let f: Box<dyn FnOnce(&_) -> _> = Box::new($v);
+ nodes.push(Box::new(f(&fixture)));
+ )*
+ let mut sim = Simulator::new(stringify!($n), nodes);
+ if let Ok(seed) = std::env::var("SIMULATION_SEED") {
+ sim.seed_str(seed);
+ }
+ sim.run();
+ }
+ };
+}
+
+pub trait Node: Debug {
+ fn init(&mut self, _rng: Rng, _now: Instant) {}
+ /// Perform processing. This optionally takes a datagram and produces either
+ /// another data, a time that the simulator needs to wait, or nothing.
+ fn process(&mut self, d: Option<Datagram>, now: Instant) -> Output;
+ /// An node can report when it considers itself "done".
+ fn done(&self) -> bool {
+ true
+ }
+ fn print_summary(&self, _test_name: &str) {}
+}
+
+/// The state of a single node. Nodes will be activated if they are `Active`
+/// or if the previous node in the loop generated a datagram. Nodes that return
+/// `true` from `Node::done` will be activated as normal.
+#[derive(Debug, PartialEq)]
+enum NodeState {
+ /// The node just produced a datagram. It should be activated again as soon as possible.
+ Active,
+ /// The node is waiting.
+ Waiting(Instant),
+ /// The node became idle.
+ Idle,
+}
+
+#[derive(Debug)]
+struct NodeHolder {
+ node: Box<dyn Node>,
+ state: NodeState,
+}
+
+impl NodeHolder {
+ fn ready(&self, now: Instant) -> bool {
+ match self.state {
+ Active => true,
+ Waiting(t) => t >= now,
+ Idle => false,
+ }
+ }
+}
+
+pub struct Simulator {
+ name: String,
+ nodes: Vec<NodeHolder>,
+ rng: Rng,
+}
+
+impl Simulator {
+ pub fn new(name: impl AsRef<str>, nodes: impl IntoIterator<Item = Box<dyn Node>>) -> Self {
+ let name = String::from(name.as_ref());
+ // The first node is marked as Active, the rest are idle.
+ let mut it = nodes.into_iter();
+ let nodes = it
+ .next()
+ .map(|node| NodeHolder {
+ node,
+ state: Active,
+ })
+ .into_iter()
+ .chain(it.map(|node| NodeHolder { node, state: Idle }))
+ .collect::<Vec<_>>();
+ Self {
+ name,
+ nodes,
+ rng: Rc::default(),
+ }
+ }
+
+ pub fn seed(&mut self, seed: [u8; 32]) {
+ self.rng = Rc::new(RefCell::new(Random::new(seed)));
+ }
+
+ /// Seed from a hex string.
+ /// Though this is convenient, it panics if this isn't a 64 character hex string.
+ pub fn seed_str(&mut self, seed: impl AsRef<str>) {
+ let seed = Encoder::from_hex(seed);
+ self.seed(<[u8; 32]>::try_from(seed.as_ref()).unwrap());
+ }
+
+ fn next_time(&self, now: Instant) -> Instant {
+ let mut next = None;
+ for n in &self.nodes {
+ match n.state {
+ Idle => continue,
+ Active => return now,
+ Waiting(a) => next = Some(next.map_or(a, |b| min(a, b))),
+ }
+ }
+ next.expect("a node cannot be idle and not done")
+ }
+
+ /// Runs the simulation.
+ pub fn run(mut self) -> Duration {
+ let start = now();
+ let mut now = start;
+ let mut dgram = None;
+
+ for n in &mut self.nodes {
+ n.node.init(self.rng.clone(), now);
+ }
+ println!("{}: seed {}", self.name, self.rng.borrow().seed_str());
+
+ let real_start = Instant::now();
+ loop {
+ for n in &mut self.nodes {
+ if dgram.is_none() && !n.ready(now) {
+ qdebug!([self.name], "skipping {:?}", n.node);
+ continue;
+ }
+
+ qdebug!([self.name], "processing {:?}", n.node);
+ let res = n.node.process(dgram.take(), now);
+ n.state = match res {
+ Output::Datagram(d) => {
+ qtrace!([self.name], " => datagram {}", d.len());
+ dgram = Some(d);
+ Active
+ }
+ Output::Callback(delay) => {
+ qtrace!([self.name], " => callback {:?}", delay);
+ assert_ne!(delay, Duration::new(0, 0));
+ Waiting(now + delay)
+ }
+ Output::None => {
+ qtrace!([self.name], " => nothing");
+ assert!(n.node.done(), "nodes have to be done when they go idle");
+ Idle
+ }
+ };
+ }
+
+ if self.nodes.iter().all(|n| n.node.done()) {
+ let real_elapsed = real_start.elapsed();
+ println!("{}: real elapsed time: {:?}", self.name, real_elapsed);
+ let elapsed = now - start;
+ println!("{}: simulated elapsed time: {:?}", self.name, elapsed);
+ for n in &self.nodes {
+ n.node.print_summary(&self.name);
+ }
+ return elapsed;
+ }
+
+ if dgram.is_none() {
+ let next = self.next_time(now);
+ if next > now {
+ qinfo!(
+ [self.name],
+ "advancing time by {:?} to {:?}",
+ next - now,
+ next - start
+ );
+ now = next;
+ }
+ }
+ }
+ }
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/net.rs b/third_party/rust/neqo-transport/tests/sim/net.rs
new file mode 100644
index 0000000000..754426f895
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/net.rs
@@ -0,0 +1,111 @@
+// 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::rng::RandomDuration;
+use super::{Node, Rng};
+use neqo_common::Datagram;
+use neqo_transport::Output;
+use std::collections::BTreeMap;
+use std::fmt::{self, Debug};
+use std::iter;
+use std::ops::Range;
+use std::time::{Duration, Instant};
+
+///
+pub struct RandomDrop {
+ threshold: u64,
+ max: u64,
+ rng: Rng,
+}
+
+impl RandomDuration {
+ /// Make a new random `Duration` generator. This asserts if the range provided
+ /// is inverted (i.e., `bounds.start > bounds.end`), or spans 2^64
+ /// or more nanoseconds.
+ /// A zero-length range means that random values won't be taken from the Rng
+ pub fn new(bounds: Range<Duration>, rng: Rng) -> Self {
+ let max = u64::try_from((bounds.end - bounds.start).as_nanos()).unwrap();
+ Self {
+ start: bounds.start,
+ max,
+ rng,
+ }
+ }
+
+ fn next(&mut self) -> Duration {
+ let r = if self.max == 0 {
+ Duration::new(0, 0)
+ } else {
+ self.rng.borrow_mut().random_from(0..self.max)
+ }
+ self.start + Duration::from_nanos(r)
+ }
+}
+
+enum DelayState {
+ New(Range<Duration>),
+ Ready(RandomDuration),
+}
+
+pub struct Delay {
+ state: DelayState,
+ queue: BTreeMap<Instant, Datagram>,
+}
+
+impl Delay
+{
+ pub fn new(bounds: Range<Duration>) -> Self
+ {
+ Self {
+ State: DelayState::New(bounds),
+ queue: BTreeMap::default(),
+ }
+ }
+
+ fn insert(&mut self, d: Datagram, now: Instant) {
+ let mut t = if let State::Ready(r) = self.state {
+ now + self.source.next()
+ } else {
+ unreachable!();
+ }
+ while self.queue.contains_key(&t) {
+ // This is a little inefficient, but it avoids drops on collisions,
+ // which are super-common for a fixed delay.
+ t += Duration::from_nanos(1);
+ }
+ self.queue.insert(t, d);
+ }
+}
+
+impl Node for Delay
+{
+ fn init(&mut self, rng: Rng, now: Instant) {
+ if let DelayState::New(bounds) = self.state {
+ self.state = RandomDuration::new(bounds);
+ } else {
+ unreachable!();
+ }
+ }
+
+ fn process(&mut self, d: Option<Datagram>, now: Instant) -> Output {
+ if let Some(dgram) = d {
+ self.insert(dgram, now);
+ }
+ if let Some((&k, _)) = self.queue.range(..now).nth(0) {
+ Output::Datagram(self.queue.remove(&k).unwrap())
+ } else if let Some(&t) = self.queue.keys().nth(0) {
+ Output::Callback(t - now)
+ } else {
+ Output::None
+ }
+ }
+}
+
+impl<T> Debug for Delay<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("delay")
+ }
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/rng.rs b/third_party/rust/neqo-transport/tests/sim/rng.rs
new file mode 100644
index 0000000000..af4f70eb5f
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/rng.rs
@@ -0,0 +1,81 @@
+// 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, ops::Range};
+
+use neqo_common::Decoder;
+
+/// An implementation of a xoshiro256** pseudorandom generator.
+pub struct Random {
+ state: [u64; 4],
+}
+
+impl Random {
+ pub fn new(seed: [u8; 32]) -> Self {
+ assert!(seed.iter().any(|&x| x != 0));
+ let mut dec = Decoder::from(&seed);
+ Self {
+ state: [
+ dec.decode_uint(8).unwrap(),
+ dec.decode_uint(8).unwrap(),
+ dec.decode_uint(8).unwrap(),
+ dec.decode_uint(8).unwrap(),
+ ],
+ }
+ }
+
+ pub fn random(&mut self) -> u64 {
+ let result = (self.state[1].overflowing_mul(5).0)
+ .rotate_right(7)
+ .overflowing_mul(9)
+ .0;
+ let t = self.state[1] << 17;
+
+ self.state[2] ^= self.state[0];
+ self.state[3] ^= self.state[1];
+ self.state[1] ^= self.state[2];
+ self.state[0] ^= self.state[3];
+
+ self.state[2] ^= t;
+ self.state[3] = self.state[3].rotate_right(45);
+
+ result
+ }
+
+ /// Generate a random value from the range.
+ /// If the range is empty or inverted (`range.start > range.end`), then
+ /// this returns the value of `range.start` without generating any random values.
+ pub fn random_from(&mut self, range: Range<u64>) -> u64 {
+ let max = range.end.saturating_sub(range.start);
+ if max == 0 {
+ return range.start;
+ }
+
+ let shift = (max - 1).leading_zeros();
+ assert_ne!(max, 0);
+ loop {
+ let r = self.random() >> shift;
+ if r < max {
+ return range.start + r;
+ }
+ }
+ }
+
+ /// Get the seed necessary to continue from this point.
+ pub fn seed_str(&self) -> String {
+ format!(
+ "{:8x}{:8x}{:8x}{:8x}",
+ self.state[0], self.state[1], self.state[2], self.state[3],
+ )
+ }
+}
+
+impl Default for Random {
+ fn default() -> Self {
+ let buf = neqo_crypto::random(32);
+ Random::new(<[u8; 32]>::try_from(&buf[..]).unwrap())
+ }
+}
diff --git a/third_party/rust/neqo-transport/tests/sim/taildrop.rs b/third_party/rust/neqo-transport/tests/sim/taildrop.rs
new file mode 100644
index 0000000000..26813800c9
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/taildrop.rs
@@ -0,0 +1,188 @@
+// 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::{
+ cmp::max,
+ collections::VecDeque,
+ convert::TryFrom,
+ fmt::{self, Debug},
+ time::{Duration, Instant},
+};
+
+use neqo_common::{qtrace, Datagram};
+use neqo_transport::Output;
+
+use super::Node;
+
+/// One second in nanoseconds.
+const ONE_SECOND_NS: u128 = 1_000_000_000;
+
+/// This models a link with a tail drop router at the front of it.
+pub struct TailDrop {
+ /// An overhead associated with each entry. This accounts for
+ /// layer 2, IP, and UDP overheads.
+ overhead: usize,
+ /// The rate at which bytes egress the link, in bytes per second.
+ rate: usize,
+ /// The depth of the queue, in bytes.
+ capacity: usize,
+
+ /// A counter for how many bytes are enqueued.
+ used: usize,
+ /// A queue of unsent bytes.
+ queue: VecDeque<Datagram>,
+ /// The time that the next datagram can enter the link.
+ next_deque: Option<Instant>,
+
+ /// Any sub-ns delay from the last enqueue.
+ sub_ns_delay: u32,
+ /// The time it takes a byte to exit the other end of the link.
+ delay: Duration,
+ /// The packets that are on the link and when they can be delivered.
+ on_link: VecDeque<(Instant, Datagram)>,
+
+ /// The number of packets received.
+ received: usize,
+ /// The number of packets dropped.
+ dropped: usize,
+ /// The number of packets delivered.
+ delivered: usize,
+ /// The maximum amount of queue capacity ever used.
+ /// As packets leave the queue as soon as they start being used, this doesn't
+ /// count them.
+ maxq: usize,
+}
+
+impl TailDrop {
+ /// Make a new taildrop node with the given rate, queue capacity, and link delay.
+ pub fn new(rate: usize, capacity: usize, delay: Duration) -> Self {
+ Self {
+ overhead: 64,
+ rate,
+ capacity,
+ used: 0,
+ queue: VecDeque::new(),
+ next_deque: None,
+ sub_ns_delay: 0,
+ delay,
+ on_link: VecDeque::new(),
+ received: 0,
+ dropped: 0,
+ delivered: 0,
+ maxq: 0,
+ }
+ }
+
+ /// A tail drop queue on a 10Mbps link (approximated to 1 million bytes per second)
+ /// with a fat 32k buffer (about 30ms), and the default forward delay of 50ms.
+ pub fn dsl_uplink() -> Self {
+ TailDrop::new(1_000_000, 32_768, Duration::from_millis(50))
+ }
+
+ /// Cut downlink to one fifth of the uplink (2Mbps), and reduce the buffer to 1/4.
+ pub fn dsl_downlink() -> Self {
+ TailDrop::new(200_000, 8_192, Duration::from_millis(50))
+ }
+
+ /// How "big" is this datagram, accounting for overheads.
+ /// This approximates by using the same overhead for storing in the queue
+ /// and for sending on the wire.
+ fn size(&self, d: &Datagram) -> usize {
+ d.len() + self.overhead
+ }
+
+ /// Start sending a datagram.
+ fn send(&mut self, d: Datagram, now: Instant) {
+ // How many bytes are we "transmitting"?
+ let sz = u128::try_from(self.size(&d)).unwrap();
+
+ // Calculate how long it takes to put the packet on the link.
+ // Perform the calculation based on 2^32 seconds and save any remainder.
+ // This ensures that high rates and small packets don't result in rounding
+ // down times too badly.
+ // Duration consists of a u64 and a u32, so we have 32 high bits to spare.
+ let t = sz * (ONE_SECOND_NS << 32) / u128::try_from(self.rate).unwrap()
+ + u128::from(self.sub_ns_delay);
+ let send_ns = u64::try_from(t >> 32).unwrap();
+ assert_ne!(send_ns, 0, "sending a packet takes <1ns");
+ self.sub_ns_delay = u32::try_from(t & u128::from(u32::MAX)).unwrap();
+ let deque_time = now + Duration::from_nanos(send_ns);
+ self.next_deque = Some(deque_time);
+
+ // Now work out when the packet is fully received at the other end of
+ // the link. Setup to deliver the packet then.
+ let delivery_time = deque_time + self.delay;
+ self.on_link.push_back((delivery_time, d));
+ }
+
+ /// Enqueue for sending. Maybe. If this overflows the queue, drop it instead.
+ fn maybe_enqueue(&mut self, d: Datagram, now: Instant) {
+ self.received += 1;
+ if self.next_deque.is_none() {
+ // Nothing in the queue and nothing still sending.
+ debug_assert!(self.queue.is_empty());
+ self.send(d, now);
+ } else if self.used + self.size(&d) <= self.capacity {
+ self.used += self.size(&d);
+ self.maxq = max(self.maxq, self.used);
+ self.queue.push_back(d);
+ } else {
+ qtrace!("taildrop dropping {} bytes", d.len());
+ self.dropped += 1;
+ }
+ }
+
+ /// If the last packet that was sending has been sent, start sending
+ /// the next one.
+ fn maybe_send(&mut self, now: Instant) {
+ if self.next_deque.as_ref().map_or(false, |t| *t <= now) {
+ if let Some(d) = self.queue.pop_front() {
+ self.used -= self.size(&d);
+ self.send(d, now);
+ } else {
+ self.next_deque = None;
+ self.sub_ns_delay = 0;
+ }
+ }
+ }
+}
+
+impl Node for TailDrop {
+ fn process(&mut self, d: Option<Datagram>, now: Instant) -> Output {
+ if let Some(dgram) = d {
+ self.maybe_enqueue(dgram, now);
+ }
+
+ self.maybe_send(now);
+
+ if let Some((t, _)) = self.on_link.front() {
+ if *t <= now {
+ let (_, d) = self.on_link.pop_front().unwrap();
+ self.delivered += 1;
+ Output::Datagram(d)
+ } else {
+ Output::Callback(*t - now)
+ }
+ } else {
+ Output::None
+ }
+ }
+
+ fn print_summary(&self, test_name: &str) {
+ println!(
+ "{}: taildrop: rx {} drop {} tx {} maxq {}",
+ test_name, self.received, self.dropped, self.delivered, self.maxq,
+ );
+ }
+}
+
+impl Debug for TailDrop {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("taildrop")
+ }
+}