diff options
Diffstat (limited to 'third_party/rust/neqo-transport/tests')
-rw-r--r-- | third_party/rust/neqo-transport/tests/common/mod.rs | 235 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/conn_vectors.rs | 286 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/connection.rs | 208 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/network.rs | 177 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/retry.rs | 475 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/server.rs | 779 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/connection.rs | 315 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/delay.rs | 102 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/drop.rs | 75 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/mod.rs | 232 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/net.rs | 111 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/rng.rs | 81 | ||||
-rw-r--r-- | third_party/rust/neqo-transport/tests/sim/taildrop.rs | 188 |
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(¬oken_header)); + + // Encrypt. + let mut notoken_packet = Encoder::with_capacity(1200) + .encode(¬oken_header) + .as_ref() + .to_vec(); + notoken_packet.resize_with(1200, u8::default); + aead.encrypt( + pn, + ¬oken_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(¬oken_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") + } +} |