summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-transport
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/neqo-transport
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/neqo-transport')
-rw-r--r--third_party/rust/neqo-transport/.cargo-checksum.json1
-rw-r--r--third_party/rust/neqo-transport/Cargo.toml22
-rwxr-xr-xthird_party/rust/neqo-transport/TODO9
-rw-r--r--third_party/rust/neqo-transport/src/addr_valid.rs509
-rw-r--r--third_party/rust/neqo-transport/src/cc/classic_cc.rs955
-rw-r--r--third_party/rust/neqo-transport/src/cc/mod.rs55
-rw-r--r--third_party/rust/neqo-transport/src/cc/new_reno.rs44
-rw-r--r--third_party/rust/neqo-transport/src/cid.rs157
-rw-r--r--third_party/rust/neqo-transport/src/connection/idle.rs90
-rw-r--r--third_party/rust/neqo-transport/src/connection/mod.rs2768
-rw-r--r--third_party/rust/neqo-transport/src/connection/params.rs71
-rw-r--r--third_party/rust/neqo-transport/src/connection/saved.rs72
-rw-r--r--third_party/rust/neqo-transport/src/connection/state.rs207
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/cc.rs526
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/close.rs206
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/handshake.rs697
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/idle.rs274
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/keys.rs330
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/mod.rs305
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/recovery.rs636
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/resumption.rs182
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/stream.rs580
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/vn.rs201
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/zerortt.rs193
-rw-r--r--third_party/rust/neqo-transport/src/crypto.rs1293
-rw-r--r--third_party/rust/neqo-transport/src/dump.rs32
-rw-r--r--third_party/rust/neqo-transport/src/events.rs254
-rw-r--r--third_party/rust/neqo-transport/src/flow_mgr.rs400
-rw-r--r--third_party/rust/neqo-transport/src/frame.rs835
-rw-r--r--third_party/rust/neqo-transport/src/lib.rs195
-rw-r--r--third_party/rust/neqo-transport/src/pace.rs138
-rw-r--r--third_party/rust/neqo-transport/src/packet/mod.rs1339
-rw-r--r--third_party/rust/neqo-transport/src/packet/retry.rs63
-rw-r--r--third_party/rust/neqo-transport/src/path.rs109
-rw-r--r--third_party/rust/neqo-transport/src/qlog.rs442
-rw-r--r--third_party/rust/neqo-transport/src/recovery.rs1470
-rw-r--r--third_party/rust/neqo-transport/src/recv_stream.rs1110
-rw-r--r--third_party/rust/neqo-transport/src/send_stream.rs1746
-rw-r--r--third_party/rust/neqo-transport/src/sender.rs124
-rw-r--r--third_party/rust/neqo-transport/src/server.rs636
-rw-r--r--third_party/rust/neqo-transport/src/stats.rs195
-rw-r--r--third_party/rust/neqo-transport/src/stream_id.rs205
-rw-r--r--third_party/rust/neqo-transport/src/tparams.rs541
-rw-r--r--third_party/rust/neqo-transport/src/tracking.rs992
-rw-r--r--third_party/rust/neqo-transport/tests/conn_vectors.rs201
-rw-r--r--third_party/rust/neqo-transport/tests/connection.rs51
-rw-r--r--third_party/rust/neqo-transport/tests/network.rs168
-rw-r--r--third_party/rust/neqo-transport/tests/server.rs1011
-rw-r--r--third_party/rust/neqo-transport/tests/sim/connection.rs296
-rw-r--r--third_party/rust/neqo-transport/tests/sim/delay.rs98
-rw-r--r--third_party/rust/neqo-transport/tests/sim/drop.rs71
-rw-r--r--third_party/rust/neqo-transport/tests/sim/mod.rs232
-rw-r--r--third_party/rust/neqo-transport/tests/sim/net.rs111
-rw-r--r--third_party/rust/neqo-transport/tests/sim/rng.rs81
-rw-r--r--third_party/rust/neqo-transport/tests/sim/taildrop.rs184
55 files changed, 23713 insertions, 0 deletions
diff --git a/third_party/rust/neqo-transport/.cargo-checksum.json b/third_party/rust/neqo-transport/.cargo-checksum.json
new file mode 100644
index 0000000000..26516ac9c6
--- /dev/null
+++ b/third_party/rust/neqo-transport/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"e8102a96c0d1044fe659de2737274de30b567477c356f0d51ff7cc27b8fcca8e","TODO":"d759cb804b32fa9d96ea8d3574a3c4073da9fe6a0b02b708a0e22cce5a5b4a0f","src/addr_valid.rs":"a7e14df4f2b415f2c635268b4b4b5d75241f201f3895231cab94202538c90cb4","src/cc/classic_cc.rs":"c3b96ebc9ee909cc54cf5279bd6fbd9afa4e0b9e5af98cc3df4a03ffffdb50d4","src/cc/mod.rs":"45ac2feeb0732bac175f8d477f0f23c237194e5fc2095239b341dd4719b51e94","src/cc/new_reno.rs":"4734a85122dae88a087952ce1f9ef006aea8405a2bc1d4a0565c5bfd238e4ef4","src/cid.rs":"e231286df599b9ee7cbae8cab274908e0197baf2b2e88bea9d8f40dbeb02c57d","src/connection/idle.rs":"e3c902858737de89e2e8955796c6e60dcfbcffaf58b3a1c4473fed04c7eae55c","src/connection/mod.rs":"272a061375de07a48aa84786263bd08079a921017feb044e14971391189363ba","src/connection/params.rs":"48693a2c635c6a3afb9db1d9e07fae6e02005f0207d8edc3851678ea0698b518","src/connection/saved.rs":"b70f81635e210102208790ef308a0dea4b7e87900e6083ca6b299d4c1d5b9058","src/connection/state.rs":"0001a83538e85e51eb0789f954593c0dfe3a58e27e6bf4471a768ce713742284","src/connection/tests/cc.rs":"4180e5cdae7f084bb56e85b4e16f231527d896429f8a8bb25dfd6e85cb8d99b8","src/connection/tests/close.rs":"cd2fbaf378afcf7bad5046bd11bdcd1fc66a2a8108401e0a3dd2031df1f95091","src/connection/tests/handshake.rs":"0d2618fe3fb6b050a0b78e179596f64998ae0e4ed3da6a3b809d6fbc298e4d02","src/connection/tests/idle.rs":"7c73be2a1079d2ee66960d403076b059356736b35bba657f278348493068a6a5","src/connection/tests/keys.rs":"029e0cb2a043828059f006d0e17eb36b7d978c48d16340a80711e144a709245e","src/connection/tests/mod.rs":"944594bac7ec53fd4623cd223c84efefe717336765d38a0637a9174453fcb4d6","src/connection/tests/recovery.rs":"f6dbdeeaec18510c0f9345ee25947c0ed988b14fddf30e124dfe6d1b9a160a71","src/connection/tests/resumption.rs":"538c0ecb7eed22ea9c22882e934bf91cc87c064c6d4286e5cc7fbbaecbc650fc","src/connection/tests/stream.rs":"37e91c34851e85763684afceca9a8498101602d629e161d99e10248f3b908a81","src/connection/tests/vn.rs":"ffb1c34be57b2cae704b387a2599cedeb1a8775d5a5e48eb0d0113d767c4e6a6","src/connection/tests/zerortt.rs":"02e5f270138a99feb9479e4d0f44feb0663516b3373a9a84a690384177726263","src/crypto.rs":"ed07cd03f9bcbbcba770f42062e72b2957bf1fb098c77d23e7a03c4ddc6fe213","src/dump.rs":"d69ccb0e3b240823b886a791186afbac9f2e26d1f1f67a55dbf86f8cd3e6203e","src/events.rs":"6d1e168eb8c425018281e82ba3ff05ea0321035eb677f1d1bdd19fe32c1e62ce","src/flow_mgr.rs":"c2b6452a0cffa23ae70184ea0216b9f5c895cb3446e5f1c95dc3ba72b0dfc52f","src/frame.rs":"d2d175fb058c4157c8717595784c2ab26df8ae9b5e2ba2e99d579203314991c0","src/lib.rs":"c479560e3feb9606a82b2566bbcebbc7025f8b268298c387d0016cb9564605d8","src/pace.rs":"eb9094cfcae54162022f70f230b6a9811add0063b878100f147a9365473f6612","src/packet/mod.rs":"bcc334e023be39742303d811285d9e19a52900f21d69ec974854a05cbd12e5b9","src/packet/retry.rs":"670b2d3363121a7a98fb6efe186c3818786093502b53b7d9d104368de3376555","src/path.rs":"92b2701d1e486250f472b2cdc487b1e9e9c8753c9c40b626c2d6d1ccd6e70c38","src/qlog.rs":"b2853fdc7acfb86c8b802a39f32b0462dd82bf86f5c427ffdb523be933268532","src/recovery.rs":"34653339fc108c6e1e61fd44d253609fbc0acd090724cac39b3b3d010be9ed93","src/recv_stream.rs":"71143d3ce2670d55dbc5cf7ecd9d3b5826a3060029162f335fffd197bedf149f","src/send_stream.rs":"d0654046653f4af6033b904b5dc6c8639f5369f039a0c61c6ee8ee047faf8019","src/sender.rs":"df6ded2ad06983d167c0a7d29ec06798ccb7be681869bc1d833ea0d3351cb9a6","src/server.rs":"3d67b7a1345c6e07489ca51a384158d54bf9b17f7d7970755aa6a79be49be0af","src/stats.rs":"4a3b5560dbb6a32414b80cc82c062c9a64e5f7aba75416f0208a2477f355e6f0","src/stream_id.rs":"98f656157e0eeb7f32802cf2b3dbd68ef8d7a89578a097a88db7157d4a712202","src/tparams.rs":"07ee3df53552a281a009fb68972ac9f19a239cd13d1ab48c3b13de07a242c051","src/tracking.rs":"41dfdd867da935027c94d35156a7a61c9b80f61f556c8683cdd599e979ad1f90","tests/conn_vectors.rs":"ae0b3315ee22e78dcb028cbce15977795bb37282285deee874c0465c3676ab28","tests/connection.rs":"fe9b7069b34fea7f5154e9b1ea3bffb290a4aa9a7c97c30992c51fa7a6ad6673","tests/network.rs":"a986c22da7132ec843a44c4bcb5a7d2726132aa27a47a8ea91634cd88e1b763b","tests/server.rs":"76ac290ccf8bdd448b84bb699359d92fafb1aa91ec31b4a395cf4a6892831331","tests/sim/connection.rs":"51c582423dd4f4b2484d0ee790235ce9b4677f936a6c8ce09a6c3d3da9b3e519","tests/sim/delay.rs":"9efa722adb89e37262369e9f3c67405f0acc8c24997271811e48df9e856e5a8d","tests/sim/drop.rs":"bd89e5c71cdd1b27cd755faaedd87d5feadf2f424df721a7df41a51bcebcbb58","tests/sim/mod.rs":"9a930682cf92e7279bccdd2145f19ff17f5aa950994e7b3e25749651511c2753","tests/sim/net.rs":"597f4d37bc26c3d82eeeaa6d14dd03bc2be3930686df2b293748b43c07c497d7","tests/sim/rng.rs":"2c90b0bbaf0c952ebee232deb3594f7a86af387737b15474de3e97ee6b623d90","tests/sim/taildrop.rs":"5c505d150f0071e8cc2d540b3a817a6942fdf13df32f1fbc6822952f2e146176"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-transport/Cargo.toml b/third_party/rust/neqo-transport/Cargo.toml
new file mode 100644
index 0000000000..127d4f7cf4
--- /dev/null
+++ b/third_party/rust/neqo-transport/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "neqo-transport"
+version = "0.4.19"
+authors = ["EKR <ekr@rtfm.com>", "Andy Grover <agrover@mozilla.com>"]
+edition = "2018"
+license = "MIT/Apache-2.0"
+
+[dependencies]
+neqo-crypto = { path = "../neqo-crypto" }
+neqo-common = { path = "../neqo-common" }
+lazy_static = "1.3.0"
+log = {version = "0.4.0", default-features = false}
+smallvec = "1.0.0"
+qlog = "0.4.0"
+indexmap = "1.0"
+
+[dev-dependencies]
+test-fixture = { path = "../test-fixture" }
+
+[features]
+default = ["deny-warnings"]
+deny-warnings = []
diff --git a/third_party/rust/neqo-transport/TODO b/third_party/rust/neqo-transport/TODO
new file mode 100755
index 0000000000..151dbd1753
--- /dev/null
+++ b/third_party/rust/neqo-transport/TODO
@@ -0,0 +1,9 @@
+Use stream events in h3 // grover or dragana?
+harmonize our rust usage:
+ - use foo::* or use foo::{bar, baz} and ordering/grouping
+ - remove extern crate
+ - sort #[derive()] args
+cleanup public API
+write docs for public API
+write docs for everything else
+CI
diff --git a/third_party/rust/neqo-transport/src/addr_valid.rs b/third_party/rust/neqo-transport/src/addr_valid.rs
new file mode 100644
index 0000000000..a8fcd76ab9
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/addr_valid.rs
@@ -0,0 +1,509 @@
+// 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.
+
+// This file implements functions necessary for address validation.
+
+use neqo_common::{qinfo, qtrace, Decoder, Encoder, Role};
+use neqo_crypto::{
+ constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
+ selfencrypt::SelfEncrypt,
+};
+
+use crate::cid::ConnectionId;
+use crate::packet::PacketBuilder;
+use crate::recovery::RecoveryToken;
+use crate::stats::FrameStats;
+use crate::Res;
+
+use smallvec::SmallVec;
+use std::convert::TryFrom;
+use std::net::{IpAddr, SocketAddr};
+use std::time::{Duration, Instant};
+
+/// A prefix we add to Retry tokens to distinguish them from NEW_TOKEN tokens.
+const TOKEN_IDENTIFIER_RETRY: &[u8] = &[0x52, 0x65, 0x74, 0x72, 0x79];
+/// A prefix on NEW_TOKEN tokens, that is maximally Hamming distant from NEW_TOKEN.
+/// Together, these need to have a low probability of collision, even if there is
+/// corruption of individual bits in transit.
+const TOKEN_IDENTIFIER_NEW_TOKEN: &[u8] = &[0xad, 0x9a, 0x8b, 0x8d, 0x86];
+
+/// The maximum number of tokens we'll save from NEW_TOKEN frames.
+/// This should be the same as the value of MAX_TICKETS in neqo-crypto.
+const MAX_NEW_TOKEN: usize = 4;
+/// The number of tokens we'll track for the purposes of looking for duplicates.
+/// This is based on how many might be received over a period where could be
+/// retransmissions. It should be at least `MAX_NEW_TOKEN`.
+const MAX_SAVED_TOKENS: usize = 8;
+
+/// `ValidateAddress` determines what sort of address validation is performed.
+/// In short, this determines when a Retry packet is sent.
+#[derive(Debug, PartialEq, Eq)]
+pub enum ValidateAddress {
+ /// Require address validation never.
+ Never,
+ /// Require address validation unless a NEW_TOKEN token is provided.
+ NoToken,
+ /// Require address validation even if a NEW_TOKEN token is provided.
+ Always,
+}
+
+pub enum AddressValidationResult {
+ Pass,
+ ValidRetry(ConnectionId),
+ Validate,
+ Invalid,
+}
+
+pub struct AddressValidation {
+ /// What sort of validation is performed.
+ validation: ValidateAddress,
+ /// A self-encryption object used for protecting Retry tokens.
+ self_encrypt: SelfEncrypt,
+ /// When this object was created.
+ start_time: Instant,
+}
+
+impl AddressValidation {
+ pub fn new(now: Instant, validation: ValidateAddress) -> Res<Self> {
+ Ok(Self {
+ validation,
+ self_encrypt: SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256)?,
+ start_time: now,
+ })
+ }
+
+ fn encode_aad(peer_address: SocketAddr, retry: bool) -> Encoder {
+ // Let's be "clever" by putting the peer's address in the AAD.
+ // We don't need to encode these into the token as they should be
+ // available when we need to check the token.
+ let mut aad = Encoder::default();
+ if retry {
+ aad.encode(TOKEN_IDENTIFIER_RETRY);
+ } else {
+ aad.encode(TOKEN_IDENTIFIER_NEW_TOKEN);
+ }
+ match peer_address.ip() {
+ IpAddr::V4(a) => {
+ aad.encode_byte(4);
+ aad.encode(&a.octets());
+ }
+ IpAddr::V6(a) => {
+ aad.encode_byte(6);
+ aad.encode(&a.octets());
+ }
+ }
+ if retry {
+ aad.encode_uint(2, peer_address.port());
+ }
+ aad
+ }
+
+ pub fn generate_token(
+ &self,
+ dcid: Option<&ConnectionId>,
+ peer_address: SocketAddr,
+ now: Instant,
+ ) -> Res<Vec<u8>> {
+ const EXPIRATION_RETRY: Duration = Duration::from_secs(5);
+ const EXPIRATION_NEW_TOKEN: Duration = Duration::from_secs(60 * 60 * 24);
+
+ // TODO(mt) rotate keys on a fixed schedule.
+ let retry = dcid.is_some();
+ let mut data = Encoder::default();
+ let end = now
+ + if retry {
+ EXPIRATION_RETRY
+ } else {
+ EXPIRATION_NEW_TOKEN
+ };
+ let end_millis = u32::try_from(end.duration_since(self.start_time).as_millis())?;
+ data.encode_uint(4, end_millis);
+ if let Some(dcid) = dcid {
+ data.encode(dcid);
+ }
+
+ // Include the token identifier ("Retry"/~) in the AAD, then keep it for plaintext.
+ let mut buf = Self::encode_aad(peer_address, retry);
+ let encrypted = self.self_encrypt.seal(&buf, &data)?;
+ buf.truncate(TOKEN_IDENTIFIER_RETRY.len());
+ buf.encode(&encrypted);
+ Ok(buf.into())
+ }
+
+ /// This generates a token for use with Retry.
+ pub fn generate_retry_token(
+ &self,
+ dcid: &ConnectionId,
+ peer_address: SocketAddr,
+ now: Instant,
+ ) -> Res<Vec<u8>> {
+ self.generate_token(Some(dcid), peer_address, now)
+ }
+
+ /// This generates a token for use with NEW_TOKEN.
+ pub fn generate_new_token(&self, peer_address: SocketAddr, now: Instant) -> Res<Vec<u8>> {
+ self.generate_token(None, peer_address, now)
+ }
+
+ pub fn set_validation(&mut self, validation: ValidateAddress) {
+ qtrace!("AddressValidation {:p}: set to {:?}", self, validation);
+ self.validation = validation;
+ }
+
+ /// Decrypts `token` and returns the connection ID it contains.
+ /// Returns a tuple with a boolean indicating whether this thinks
+ /// that the token was a Retry token, and a connection ID, that is
+ /// None if the token wasn't successfully decrypted.
+ fn decrypt_token(
+ &self,
+ token: &[u8],
+ peer_address: SocketAddr,
+ retry: bool,
+ now: Instant,
+ ) -> Option<ConnectionId> {
+ let peer_addr = Self::encode_aad(peer_address, retry);
+ let data = if let Ok(d) = self.self_encrypt.open(&peer_addr, token) {
+ d
+ } else {
+ return None;
+ };
+ let mut dec = Decoder::new(&data);
+ match dec.decode_uint(4) {
+ Some(d) => {
+ let end = self.start_time + Duration::from_millis(d);
+ if end < now {
+ qtrace!("Expired token: {:?} vs. {:?}", end, now);
+ return None;
+ }
+ }
+ _ => return None,
+ }
+ Some(ConnectionId::from(dec.decode_remainder()))
+ }
+
+ /// Calculate the Hamming difference between our identifier and the target.
+ /// Less than one difference per byte indicates that it is likely not a Retry.
+ /// This generous interpretation allows for a lot of damage in transit.
+ /// Note that if this check fails, then the token will be treated like it came
+ /// from NEW_TOKEN instead. If there truly is corruption of packets that causes
+ /// validation failure, it will be a failure that we try to recover from.
+ fn is_likely_retry(token: &[u8]) -> bool {
+ let mut difference = 0;
+ for i in 0..TOKEN_IDENTIFIER_RETRY.len() {
+ difference += (token[i] ^ TOKEN_IDENTIFIER_RETRY[i]).count_ones();
+ }
+ usize::try_from(difference).unwrap() < TOKEN_IDENTIFIER_RETRY.len()
+ }
+
+ pub fn validate(
+ &self,
+ token: &[u8],
+ peer_address: SocketAddr,
+ now: Instant,
+ ) -> AddressValidationResult {
+ qtrace!(
+ "AddressValidation {:p}: validate {:?}",
+ self,
+ self.validation
+ );
+
+ if token.is_empty() {
+ if self.validation == ValidateAddress::Never {
+ qinfo!("AddressValidation: no token; accepting");
+ return AddressValidationResult::Pass;
+ } else {
+ qinfo!("AddressValidation: no token; validating");
+ return AddressValidationResult::Validate;
+ }
+ }
+ if token.len() <= TOKEN_IDENTIFIER_RETRY.len() {
+ // Treat bad tokens strictly.
+ qinfo!("AddressValidation: too short token");
+ return AddressValidationResult::Invalid;
+ }
+ let retry = Self::is_likely_retry(token);
+ let enc = &token[TOKEN_IDENTIFIER_RETRY.len()..];
+ // Note that this allows the token identifier part to be corrupted.
+ // That's OK here as we don't depend on that being authenticated.
+ if let Some(cid) = self.decrypt_token(enc, peer_address, retry, now) {
+ if retry {
+ // This is from Retry, so we should have an ODCID >= 8.
+ if cid.len() >= 8 {
+ qinfo!("AddressValidation: valid Retry token for {}", cid);
+ AddressValidationResult::ValidRetry(cid)
+ } else {
+ panic!("AddressValidation: Retry token with small CID {}", cid);
+ }
+ } else if cid.is_empty() {
+ // An empty connection ID means NEW_TOKEN.
+ if self.validation == ValidateAddress::Always {
+ qinfo!("AddressValidation: valid NEW_TOKEN token; validating again");
+ AddressValidationResult::Validate
+ } else {
+ qinfo!("AddressValidation: valid NEW_TOKEN token; accepting");
+ AddressValidationResult::Pass
+ }
+ } else {
+ panic!("AddressValidation: NEW_TOKEN token with CID {}", cid);
+ }
+ } else {
+ // From here on, we have a token that we couldn't decrypt.
+ // We've either lost the keys or we've received junk.
+ if retry {
+ // If this looked like a Retry, treat it as being bad.
+ qinfo!("AddressValidation: invalid Retry token; rejecting");
+ AddressValidationResult::Invalid
+ } else if self.validation == ValidateAddress::Never {
+ // We don't require validation, so OK.
+ qinfo!("AddressValidation: invalid NEW_TOKEN token; accepting");
+ AddressValidationResult::Pass
+ } else {
+ // This might be an invalid NEW_TOKEN token, or a valid one
+ // for which we have since lost the keys. Check again.
+ qinfo!("AddressValidation: invalid NEW_TOKEN token; validating again");
+ AddressValidationResult::Validate
+ }
+ }
+ }
+}
+
+// Note: these lint override can be removed in later versions where the lints
+// either don't trip a false positive or don't apply. rustc 1.46 is fine.
+#[allow(dead_code, clippy::large_enum_variant)]
+pub enum NewTokenState {
+ Client {
+ /// Tokens that haven't been taken yet.
+ pending: SmallVec<[Vec<u8>; MAX_NEW_TOKEN]>,
+ /// Tokens that have been taken, saved so that we can discard duplicates.
+ old: SmallVec<[Vec<u8>; MAX_SAVED_TOKENS]>,
+ },
+ Server(NewTokenSender),
+}
+
+impl NewTokenState {
+ pub fn new(role: Role) -> Self {
+ match role {
+ Role::Client => Self::Client {
+ pending: SmallVec::<[_; MAX_NEW_TOKEN]>::new(),
+ old: SmallVec::<[_; MAX_SAVED_TOKENS]>::new(),
+ },
+ Role::Server => Self::Server(NewTokenSender::default()),
+ }
+ }
+
+ /// Is there a token available?
+ pub fn has_token(&self) -> bool {
+ match self {
+ Self::Client { ref pending, .. } => !pending.is_empty(),
+ Self::Server(..) => false,
+ }
+ }
+
+ /// If this is a client, take a token if there is one.
+ /// If this is a server, panic.
+ pub fn take_token(&mut self) -> Option<&[u8]> {
+ if let Self::Client {
+ ref mut pending,
+ ref mut old,
+ } = self
+ {
+ if let Some(t) = pending.pop() {
+ if old.len() >= MAX_SAVED_TOKENS {
+ old.remove(0);
+ }
+ old.push(t);
+ Some(&old[old.len() - 1])
+ } else {
+ None
+ }
+ } else {
+ unreachable!();
+ }
+ }
+
+ /// If this is a client, save a token.
+ /// If this is a server, panic.
+ pub fn save_token(&mut self, token: Vec<u8>) {
+ if let Self::Client {
+ ref mut pending,
+ ref old,
+ } = self
+ {
+ for t in old.iter().rev().chain(pending.iter().rev()) {
+ if t == &token {
+ qinfo!("NewTokenState discarding duplicate NEW_TOKEN");
+ return;
+ }
+ }
+
+ if pending.len() >= MAX_NEW_TOKEN {
+ pending.remove(0);
+ }
+ pending.push(token);
+ } else {
+ unreachable!();
+ }
+ }
+
+ /// If this is a server, maybe send a frame.
+ /// If this is a client, do nothing.
+ pub fn write_frames(
+ &mut self,
+ builder: &mut PacketBuilder,
+ tokens: &mut Vec<RecoveryToken>,
+ stats: &mut FrameStats,
+ ) {
+ if let Self::Server(ref mut sender) = self {
+ sender.write_frames(builder, tokens, stats);
+ }
+ }
+
+ /// If this a server, buffer a NEW_TOKEN for sending.
+ /// If this is a client, panic.
+ pub fn send_new_token(&mut self, token: Vec<u8>) {
+ if let Self::Server(ref mut sender) = self {
+ sender.send_new_token(token);
+ } else {
+ unreachable!();
+ }
+ }
+
+ /// If this a server, process a lost signal for a NEW_TOKEN frame.
+ /// If this is a client, panic.
+ pub fn lost(&mut self, seqno: usize) {
+ if let Self::Server(ref mut sender) = self {
+ sender.lost(seqno);
+ } else {
+ unreachable!();
+ }
+ }
+
+ /// If this a server, process remove the acknowledged NEW_TOKEN frame.
+ /// If this is a client, panic.
+ pub fn acked(&mut self, seqno: usize) {
+ if let Self::Server(ref mut sender) = self {
+ sender.acked(seqno);
+ } else {
+ unreachable!();
+ }
+ }
+}
+
+struct NewTokenFrameStatus {
+ seqno: usize,
+ token: Vec<u8>,
+ needs_sending: bool,
+}
+
+impl NewTokenFrameStatus {
+ fn len(&self) -> usize {
+ 1 + Encoder::vvec_len(self.token.len())
+ }
+}
+
+#[derive(Default)]
+pub struct NewTokenSender {
+ /// The unacknowledged NEW_TOKEN frames we are yet to send.
+ tokens: Vec<NewTokenFrameStatus>,
+ /// A sequence number that is used to track individual tokens
+ /// by reference (so that recovery tokens can be simple).
+ next_seqno: usize,
+}
+
+impl NewTokenSender {
+ /// Add a token to be sent.
+ pub fn send_new_token(&mut self, token: Vec<u8>) {
+ self.tokens.push(NewTokenFrameStatus {
+ seqno: self.next_seqno,
+ token,
+ needs_sending: true,
+ });
+ self.next_seqno += 1;
+ }
+
+ pub fn write_frames(
+ &mut self,
+ builder: &mut PacketBuilder,
+ tokens: &mut Vec<RecoveryToken>,
+ stats: &mut FrameStats,
+ ) {
+ for t in self.tokens.iter_mut() {
+ if t.needs_sending && t.len() <= builder.remaining() {
+ t.needs_sending = false;
+
+ builder.encode_varint(crate::frame::FRAME_TYPE_NEW_TOKEN);
+ builder.encode_vvec(&t.token);
+
+ tokens.push(RecoveryToken::NewToken(t.seqno));
+ stats.new_token += 1;
+ }
+ }
+ }
+
+ pub fn lost(&mut self, seqno: usize) {
+ for t in self.tokens.iter_mut() {
+ if t.seqno == seqno {
+ t.needs_sending = true;
+ break;
+ }
+ }
+ }
+
+ pub fn acked(&mut self, seqno: usize) {
+ self.tokens.retain(|i| i.seqno != seqno);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::NewTokenState;
+ use neqo_common::Role;
+
+ const ONE: &[u8] = &[1, 2, 3];
+ const TWO: &[u8] = &[4, 5];
+
+ #[test]
+ fn duplicate_saved() {
+ let mut tokens = NewTokenState::new(Role::Client);
+ tokens.save_token(ONE.to_vec());
+ tokens.save_token(TWO.to_vec());
+ tokens.save_token(ONE.to_vec());
+ assert!(tokens.has_token());
+ assert!(tokens.take_token().is_some()); // probably TWO
+ assert!(tokens.has_token());
+ assert!(tokens.take_token().is_some()); // probably ONE
+ assert!(!tokens.has_token());
+ assert!(tokens.take_token().is_none());
+ }
+
+ #[test]
+ fn duplicate_after_take() {
+ let mut tokens = NewTokenState::new(Role::Client);
+ tokens.save_token(ONE.to_vec());
+ tokens.save_token(TWO.to_vec());
+ assert!(tokens.has_token());
+ assert!(tokens.take_token().is_some()); // probably TWO
+ tokens.save_token(ONE.to_vec());
+ assert!(tokens.has_token());
+ assert!(tokens.take_token().is_some()); // probably ONE
+ assert!(!tokens.has_token());
+ assert!(tokens.take_token().is_none());
+ }
+
+ #[test]
+ fn duplicate_after_empty() {
+ let mut tokens = NewTokenState::new(Role::Client);
+ tokens.save_token(ONE.to_vec());
+ tokens.save_token(TWO.to_vec());
+ assert!(tokens.has_token());
+ assert!(tokens.take_token().is_some()); // probably TWO
+ assert!(tokens.has_token());
+ assert!(tokens.take_token().is_some()); // probably ONE
+ tokens.save_token(ONE.to_vec());
+ assert!(!tokens.has_token());
+ assert!(tokens.take_token().is_none());
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/cc/classic_cc.rs b/third_party/rust/neqo-transport/src/cc/classic_cc.rs
new file mode 100644
index 0000000000..b41969c680
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/cc/classic_cc.rs
@@ -0,0 +1,955 @@
+// 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.
+
+// Congestion control
+#![deny(clippy::pedantic)]
+
+use std::cmp::{max, min};
+use std::fmt::{self, Debug, Display};
+use std::time::{Duration, Instant};
+
+use super::CongestionControl;
+
+use crate::cc::MAX_DATAGRAM_SIZE;
+use crate::qlog::{self, QlogMetric};
+use crate::sender::PACING_BURST_SIZE;
+use crate::tracking::SentPacket;
+use neqo_common::{const_max, const_min, qdebug, qinfo, qlog::NeqoQlog, qtrace};
+
+pub const CWND_INITIAL_PKTS: usize = 10;
+const CWND_INITIAL: usize = const_min(
+ CWND_INITIAL_PKTS * MAX_DATAGRAM_SIZE,
+ const_max(2 * MAX_DATAGRAM_SIZE, 14720),
+);
+pub const CWND_MIN: usize = MAX_DATAGRAM_SIZE * 2;
+const PERSISTENT_CONG_THRESH: u32 = 3;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum State {
+ /// In either slow start or congestion avoidance, not recovery.
+ SlowStart,
+ /// In congestion avoidance.
+ CongestionAvoidance,
+ /// In a recovery period, but no packets have been sent yet. This is a
+ /// transient state because we want to exempt the first packet sent after
+ /// entering recovery from the congestion window.
+ RecoveryStart,
+ /// In a recovery period, with the first packet sent at this time.
+ Recovery,
+ /// Start of persistent congestion, which is transient, like `RecoveryStart`.
+ PersistentCongestion,
+}
+
+impl State {
+ pub fn in_recovery(self) -> bool {
+ matches!(self, Self::RecoveryStart | Self::Recovery)
+ }
+
+ pub fn in_slow_start(self) -> bool {
+ self == Self::SlowStart
+ }
+
+ /// These states are transient, we tell qlog on entry, but not on exit.
+ pub fn transient(self) -> bool {
+ matches!(self, Self::RecoveryStart | Self::PersistentCongestion)
+ }
+
+ /// Update a transient state to the true state.
+ pub fn update(&mut self) {
+ *self = match self {
+ Self::PersistentCongestion => Self::SlowStart,
+ Self::RecoveryStart => Self::Recovery,
+ _ => unreachable!(),
+ };
+ }
+
+ pub fn to_qlog(self) -> &'static str {
+ match self {
+ Self::SlowStart | Self::PersistentCongestion => "slow_start",
+ Self::CongestionAvoidance => "congestion_avoidance",
+ Self::Recovery | Self::RecoveryStart => "recovery",
+ }
+ }
+}
+
+pub trait WindowAdjustment: Display + Debug {
+ fn on_packets_acked(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize);
+ fn on_congestion_event(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize);
+}
+
+#[derive(Debug)]
+pub struct ClassicCongestionControl<T> {
+ cc_algorithm: T,
+ state: State,
+ congestion_window: usize, // = kInitialWindow
+ bytes_in_flight: usize,
+ acked_bytes: usize,
+ ssthresh: usize,
+ recovery_start: Option<Instant>,
+
+ qlog: NeqoQlog,
+}
+
+impl<T: WindowAdjustment> Display for ClassicCongestionControl<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{} CongCtrl {}/{} ssthresh {}",
+ self.cc_algorithm, self.bytes_in_flight, self.congestion_window, self.ssthresh,
+ )?;
+ Ok(())
+ }
+}
+
+impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
+ fn set_qlog(&mut self, qlog: NeqoQlog) {
+ self.qlog = qlog;
+ }
+
+ #[must_use]
+ fn cwnd(&self) -> usize {
+ self.congestion_window
+ }
+
+ #[must_use]
+ fn bytes_in_flight(&self) -> usize {
+ self.bytes_in_flight
+ }
+
+ #[must_use]
+ fn cwnd_avail(&self) -> usize {
+ // BIF can be higher than cwnd due to PTO packets, which are sent even
+ // if avail is 0, but still count towards BIF.
+ self.congestion_window.saturating_sub(self.bytes_in_flight)
+ }
+
+ // Multi-packet version of OnPacketAckedCC
+ fn on_packets_acked(&mut self, acked_pkts: &[SentPacket]) {
+ // Check whether we are app limited before acked packets are removed
+ // from bytes_in_flight.
+ let is_app_limited = self.app_limited();
+ qtrace!(
+ [self],
+ "app limited={}, bytes_in_flight:{}, cwnd: {}, state: {:?} pacing_burst_size: {}",
+ is_app_limited,
+ self.bytes_in_flight,
+ self.congestion_window,
+ self.state,
+ MAX_DATAGRAM_SIZE * PACING_BURST_SIZE,
+ );
+
+ let mut acked_bytes = 0;
+ for pkt in acked_pkts.iter().filter(|pkt| pkt.cc_outstanding()) {
+ assert!(self.bytes_in_flight >= pkt.size);
+ self.bytes_in_flight -= pkt.size;
+
+ if !self.after_recovery_start(pkt) {
+ // Do not increase congestion window for packets sent before
+ // recovery last started.
+ continue;
+ }
+
+ if self.state.in_recovery() {
+ self.set_state(State::CongestionAvoidance);
+ qlog::metrics_updated(&mut self.qlog, &[QlogMetric::InRecovery(false)]);
+ }
+
+ acked_bytes += pkt.size;
+ }
+
+ if !is_app_limited {
+ self.acked_bytes += acked_bytes;
+ }
+
+ qtrace!([self], "ACK received, acked_bytes = {}", self.acked_bytes);
+
+ // Slow start, up to the slow start threshold.
+ if self.congestion_window < self.ssthresh {
+ let increase = min(self.ssthresh - self.congestion_window, self.acked_bytes);
+ self.congestion_window += increase;
+ self.acked_bytes -= increase;
+ qinfo!([self], "slow start += {}", increase);
+ if self.congestion_window == self.ssthresh {
+ // This doesn't look like it is necessary, but it can happen
+ // after persistent congestion.
+ self.set_state(State::CongestionAvoidance);
+ }
+ }
+ // Congestion avoidance, above the slow start threshold.
+ if self.congestion_window >= self.ssthresh {
+ let (cwnd, acked_bytes) = self
+ .cc_algorithm
+ .on_packets_acked(self.congestion_window, self.acked_bytes);
+ self.congestion_window = cwnd;
+ self.acked_bytes = acked_bytes;
+ }
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[
+ QlogMetric::CongestionWindow(self.congestion_window),
+ QlogMetric::BytesInFlight(self.bytes_in_flight),
+ ],
+ );
+ }
+
+ /// Update congestion controller state based on lost packets.
+ fn on_packets_lost(
+ &mut self,
+ first_rtt_sample_time: Option<Instant>,
+ prev_largest_acked_sent: Option<Instant>,
+ pto: Duration,
+ lost_packets: &[SentPacket],
+ ) {
+ if lost_packets.is_empty() {
+ return;
+ }
+
+ for pkt in lost_packets.iter().filter(|pkt| pkt.ack_eliciting()) {
+ assert!(self.bytes_in_flight >= pkt.size);
+ self.bytes_in_flight -= pkt.size;
+ }
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[QlogMetric::BytesInFlight(self.bytes_in_flight)],
+ );
+
+ qdebug!([self], "Pkts lost {}", lost_packets.len());
+
+ self.on_congestion_event(lost_packets.last().unwrap());
+ self.detect_persistent_congestion(
+ first_rtt_sample_time,
+ prev_largest_acked_sent,
+ pto,
+ lost_packets,
+ );
+ }
+
+ fn discard(&mut self, pkt: &SentPacket) {
+ if pkt.cc_outstanding() {
+ assert!(self.bytes_in_flight >= pkt.size);
+ self.bytes_in_flight -= pkt.size;
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[QlogMetric::BytesInFlight(self.bytes_in_flight)],
+ );
+ qtrace!([self], "Ignore pkt with size {}", pkt.size);
+ }
+ }
+
+ fn on_packet_sent(&mut self, pkt: &SentPacket) {
+ // Record the recovery time and exit any transient state.
+ if self.state.transient() {
+ self.recovery_start = Some(pkt.time_sent);
+ self.state.update();
+ }
+
+ if !pkt.ack_eliciting() {
+ return;
+ }
+
+ self.bytes_in_flight += pkt.size;
+ qdebug!(
+ [self],
+ "Pkt Sent len {}, bif {}, cwnd {}",
+ pkt.size,
+ self.bytes_in_flight,
+ self.congestion_window
+ );
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[QlogMetric::BytesInFlight(self.bytes_in_flight)],
+ );
+ }
+
+ /// Whether a packet can be sent immediately as a result of entering recovery.
+ #[must_use]
+ fn recovery_packet(&self) -> bool {
+ self.state == State::RecoveryStart
+ }
+}
+
+impl<T: WindowAdjustment> ClassicCongestionControl<T> {
+ pub fn new(cc_algorithm: T) -> Self {
+ Self {
+ cc_algorithm,
+ state: State::SlowStart,
+ congestion_window: CWND_INITIAL,
+ bytes_in_flight: 0,
+ acked_bytes: 0,
+ ssthresh: usize::MAX,
+ recovery_start: None,
+ qlog: NeqoQlog::disabled(),
+ }
+ }
+
+ #[cfg(test)]
+ #[must_use]
+ pub fn ssthresh(&self) -> usize {
+ self.ssthresh
+ }
+
+ fn set_state(&mut self, state: State) {
+ if self.state != state {
+ qdebug!([self], "state -> {:?}", state);
+ let old_state = self.state;
+ self.qlog.add_event(|| {
+ // No need to tell qlog about exit from transient states.
+ if old_state.transient() {
+ None
+ } else {
+ Some(::qlog::event::Event::congestion_state_updated(
+ Some(old_state.to_qlog().to_owned()),
+ state.to_qlog().to_owned(),
+ ))
+ }
+ });
+ self.state = state;
+ }
+ }
+
+ fn detect_persistent_congestion(
+ &mut self,
+ first_rtt_sample_time: Option<Instant>,
+ prev_largest_acked_sent: Option<Instant>,
+ pto: Duration,
+ lost_packets: &[SentPacket],
+ ) {
+ if first_rtt_sample_time.is_none() {
+ return;
+ }
+
+ let pc_period = pto * PERSISTENT_CONG_THRESH;
+
+ let mut last_pn = 1 << 62; // Impossibly large, but not enough to overflow.
+ let mut start = None;
+
+ // Look for the first lost packet after the previous largest acknowledged.
+ // Ignore packets that weren't ack-eliciting for the start of this range.
+ // Also, make sure to ignore any packets sent before we got an RTT estimate
+ // as we might not have sent PTO packets soon enough after those.
+ let cutoff = max(first_rtt_sample_time, prev_largest_acked_sent);
+ for p in lost_packets
+ .iter()
+ .skip_while(|p| Some(p.time_sent) < cutoff)
+ {
+ if p.pn != last_pn + 1 {
+ // Not a contiguous range of lost packets, start over.
+ start = None;
+ }
+ last_pn = p.pn;
+ if !p.ack_eliciting() {
+ // Not interesting, keep looking.
+ continue;
+ }
+ if let Some(t) = start {
+ if p.time_sent.duration_since(t) > pc_period {
+ qinfo!([self], "persistent congestion");
+ self.congestion_window = CWND_MIN;
+ self.acked_bytes = 0;
+ self.set_state(State::PersistentCongestion);
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[QlogMetric::CongestionWindow(self.congestion_window)],
+ );
+ return;
+ }
+ } else {
+ start = Some(p.time_sent);
+ }
+ }
+ }
+
+ #[must_use]
+ fn after_recovery_start(&mut self, packet: &SentPacket) -> bool {
+ // At the start of the first recovery period, if the state is
+ // transient, all packets will have been sent before recovery.
+ self.recovery_start
+ .map_or(!self.state.transient(), |t| packet.time_sent >= t)
+ }
+
+ /// Handle a congestion event.
+ fn on_congestion_event(&mut self, last_packet: &SentPacket) {
+ // Start a new congestion event if lost packet was sent after the start
+ // of the previous congestion recovery period.
+ if self.after_recovery_start(last_packet) {
+ let (cwnd, acked_bytes) = self
+ .cc_algorithm
+ .on_congestion_event(self.congestion_window, self.acked_bytes);
+ self.congestion_window = max(cwnd, CWND_MIN);
+ self.acked_bytes = acked_bytes;
+ self.ssthresh = self.congestion_window;
+ qinfo!(
+ [self],
+ "Cong event -> recovery; cwnd {}, ssthresh {}",
+ self.congestion_window,
+ self.ssthresh
+ );
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[
+ QlogMetric::CongestionWindow(self.congestion_window),
+ QlogMetric::SsThresh(self.ssthresh),
+ QlogMetric::InRecovery(true),
+ ],
+ );
+ self.set_state(State::RecoveryStart);
+ }
+ }
+
+ #[allow(clippy::unused_self)]
+ fn app_limited(&self) -> bool {
+ if self.bytes_in_flight >= self.congestion_window {
+ false
+ } else if self.state.in_slow_start() {
+ // Allow for potential doubling of the congestion window during slow start.
+ // That is, the application might not have been able to send enough to respond
+ // to increases to the congestion window.
+ self.bytes_in_flight < self.congestion_window / 2
+ } else {
+ // We're not limited if the in-flight data is within a single burst of the
+ // congestion window.
+ (self.bytes_in_flight + MAX_DATAGRAM_SIZE * PACING_BURST_SIZE) < self.congestion_window
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{ClassicCongestionControl, CWND_INITIAL, CWND_MIN, PERSISTENT_CONG_THRESH};
+ use crate::cc::new_reno::NewReno;
+ use crate::cc::{CongestionControl, CWND_INITIAL_PKTS, MAX_DATAGRAM_SIZE};
+ use crate::packet::{PacketNumber, PacketType};
+ use crate::tracking::SentPacket;
+ use std::convert::TryFrom;
+ use std::time::{Duration, Instant};
+ use test_fixture::now;
+
+ const PTO: Duration = Duration::from_millis(100);
+ const RTT: Duration = Duration::from_millis(98);
+ const ZERO: Duration = Duration::from_secs(0);
+ const EPSILON: Duration = Duration::from_nanos(1);
+ const GAP: Duration = Duration::from_secs(1);
+ /// The largest time between packets without causing persistent congestion.
+ const SUB_PC: Duration = Duration::from_millis(100 * PERSISTENT_CONG_THRESH as u64);
+ /// The minimum time between packets to cause persistent congestion.
+ /// Uses an odd expression because `Duration` arithmetic isn't `const`.
+ const PC: Duration = Duration::from_nanos(100_000_000 * (PERSISTENT_CONG_THRESH as u64) + 1);
+
+ fn cwnd_is_default(cc: &ClassicCongestionControl<NewReno>) {
+ assert_eq!(cc.cwnd(), CWND_INITIAL);
+ assert_eq!(cc.ssthresh(), usize::MAX);
+ }
+
+ fn cwnd_is_halved(cc: &ClassicCongestionControl<NewReno>) {
+ assert_eq!(cc.cwnd(), CWND_INITIAL / 2);
+ assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
+ }
+
+ #[test]
+ fn issue_876() {
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+ let time_now = now();
+ let time_before = time_now - Duration::from_millis(100);
+ let time_after = time_now + Duration::from_millis(150);
+
+ let sent_packets = &[
+ SentPacket::new(
+ PacketType::Short,
+ 1, // pn
+ time_before, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE - 1, // size
+ ),
+ SentPacket::new(
+ PacketType::Short,
+ 2, // pn
+ time_before, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE - 2, // size
+ ),
+ SentPacket::new(
+ PacketType::Short,
+ 3, // pn
+ time_before, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ ),
+ SentPacket::new(
+ PacketType::Short,
+ 4, // pn
+ time_before, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ ),
+ SentPacket::new(
+ PacketType::Short,
+ 5, // pn
+ time_before, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ ),
+ SentPacket::new(
+ PacketType::Short,
+ 6, // pn
+ time_before, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ ),
+ SentPacket::new(
+ PacketType::Short,
+ 7, // pn
+ time_after, // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE - 3, // size
+ ),
+ ];
+
+ // Send some more packets so that the cc is not app-limited.
+ for p in &sent_packets[..6] {
+ cc.on_packet_sent(p);
+ }
+ assert_eq!(cc.acked_bytes, 0);
+ cwnd_is_default(&cc);
+ assert_eq!(cc.bytes_in_flight(), 6 * MAX_DATAGRAM_SIZE - 3);
+
+ cc.on_packets_lost(Some(time_now), None, PTO, &sent_packets[0..1]);
+
+ // We are now in recovery
+ assert!(cc.recovery_packet());
+ assert_eq!(cc.acked_bytes, 0);
+ cwnd_is_halved(&cc);
+ assert_eq!(cc.bytes_in_flight(), 5 * MAX_DATAGRAM_SIZE - 2);
+
+ // Send a packet after recovery starts
+ cc.on_packet_sent(&sent_packets[6]);
+ assert!(!cc.recovery_packet());
+ cwnd_is_halved(&cc);
+ assert_eq!(cc.acked_bytes, 0);
+ assert_eq!(cc.bytes_in_flight(), 6 * MAX_DATAGRAM_SIZE - 5);
+
+ // and ack it. cwnd increases slightly
+ cc.on_packets_acked(&sent_packets[6..]);
+ assert_eq!(cc.acked_bytes, sent_packets[6].size);
+ cwnd_is_halved(&cc);
+ assert_eq!(cc.bytes_in_flight(), 5 * MAX_DATAGRAM_SIZE - 2);
+
+ // Packet from before is lost. Should not hurt cwnd.
+ cc.on_packets_lost(Some(time_now), None, PTO, &sent_packets[1..2]);
+ assert!(!cc.recovery_packet());
+ assert_eq!(cc.acked_bytes, sent_packets[6].size);
+ cwnd_is_halved(&cc);
+ assert_eq!(cc.bytes_in_flight(), 4 * MAX_DATAGRAM_SIZE);
+ }
+
+ fn lost(pn: PacketNumber, ack_eliciting: bool, t: Duration) -> SentPacket {
+ SentPacket::new(
+ PacketType::Short,
+ pn,
+ now() + t,
+ ack_eliciting,
+ Vec::new(),
+ 100,
+ )
+ }
+
+ fn persistent_congestion(lost_packets: &[SentPacket]) -> bool {
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+ for p in lost_packets {
+ cc.on_packet_sent(p);
+ }
+
+ cc.on_packets_lost(Some(now()), None, PTO, lost_packets);
+ if cc.cwnd() == CWND_INITIAL / 2 {
+ false
+ } else if cc.cwnd() == CWND_MIN {
+ true
+ } else {
+ panic!("unexpected cwnd");
+ }
+ }
+
+ /// A span of exactly the PC threshold only reduces the window on loss.
+ #[test]
+ fn persistent_congestion_none() {
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, SUB_PC),
+ ]));
+ }
+
+ /// A span of just more than the PC threshold causes persistent congestion.
+ #[test]
+ fn persistent_congestion_simple() {
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PC),
+ ]));
+ }
+
+ /// Both packets need to be ack-eliciting.
+ #[test]
+ fn persistent_congestion_non_ack_eliciting() {
+ assert!(!persistent_congestion(&[
+ lost(1, false, ZERO),
+ lost(2, true, PC),
+ ]));
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, false, PC),
+ ]));
+ }
+
+ /// Packets in the middle, of any type, are OK.
+ #[test]
+ fn persistent_congestion_middle() {
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, false, RTT),
+ lost(3, true, PC),
+ ]));
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, RTT),
+ lost(3, true, PC),
+ ]));
+ }
+
+ /// Leading non-ack-eliciting packets are skipped.
+ #[test]
+ fn persistent_congestion_leading_non_ack_eliciting() {
+ assert!(!persistent_congestion(&[
+ lost(1, false, ZERO),
+ lost(2, true, RTT),
+ lost(3, true, PC),
+ ]));
+ assert!(persistent_congestion(&[
+ lost(1, false, ZERO),
+ lost(2, true, RTT),
+ lost(3, true, RTT + PC),
+ ]));
+ }
+
+ /// Trailing non-ack-eliciting packets aren't relevant.
+ #[test]
+ fn persistent_congestion_trailing_non_ack_eliciting() {
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PC),
+ lost(3, false, PC + EPSILON),
+ ]));
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, SUB_PC),
+ lost(3, false, PC),
+ ]));
+ }
+
+ /// Gaps in the middle, of any type, restart the count.
+ #[test]
+ fn persistent_congestion_gap_reset() {
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(3, true, PC),
+ ]));
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, RTT),
+ lost(4, true, GAP),
+ lost(5, true, GAP + PTO * PERSISTENT_CONG_THRESH),
+ ]));
+ }
+
+ /// A span either side of a gap will cause persistent congestion.
+ #[test]
+ fn persistent_congestion_gap_or() {
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PC),
+ lost(4, true, GAP),
+ lost(5, true, GAP + PTO),
+ ]));
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PTO),
+ lost(4, true, GAP),
+ lost(5, true, GAP + PC),
+ ]));
+ }
+
+ /// A gap only restarts after an ack-eliciting packet.
+ #[test]
+ fn persistent_congestion_gap_non_ack_eliciting() {
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PTO),
+ lost(4, false, GAP),
+ lost(5, true, GAP + PC),
+ ]));
+ assert!(!persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PTO),
+ lost(4, false, GAP),
+ lost(5, true, GAP + RTT),
+ lost(6, true, GAP + RTT + SUB_PC),
+ ]));
+ assert!(persistent_congestion(&[
+ lost(1, true, ZERO),
+ lost(2, true, PTO),
+ lost(4, false, GAP),
+ lost(5, true, GAP + RTT),
+ lost(6, true, GAP + RTT + PC),
+ ]));
+ }
+
+ /// Get a time, in multiples of `PTO`, relative to `now()`.
+ fn by_pto(t: u32) -> Instant {
+ now() + (PTO * t)
+ }
+
+ /// Make packets that will be made lost.
+ /// `times` is the time of sending, in multiples of `PTO`, relative to `now()`.
+ fn make_lost(times: &[u32]) -> Vec<SentPacket> {
+ times
+ .iter()
+ .enumerate()
+ .map(|(i, &t)| {
+ SentPacket::new(
+ PacketType::Short,
+ u64::try_from(i).unwrap(),
+ by_pto(t),
+ true,
+ Vec::new(),
+ 1000,
+ )
+ })
+ .collect::<Vec<_>>()
+ }
+
+ /// Call `detect_persistent_congestion` using times relative to now and the fixed PTO time.
+ /// `last_ack` and `rtt_time` are times in multiples of `PTO`, relative to `now()`,
+ /// for the time of the largest acknowledged and the first RTT sample, respectively.
+ fn persistent_congestion_by_pto(last_ack: u32, rtt_time: u32, lost: &[SentPacket]) -> bool {
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+ assert_eq!(cc.cwnd(), CWND_INITIAL);
+
+ let last_ack = Some(by_pto(last_ack));
+ let rtt_time = Some(by_pto(rtt_time));
+
+ // Persistent congestion is never declared if the RTT time is `None`.
+ cc.detect_persistent_congestion(None, None, PTO, lost);
+ assert_eq!(cc.cwnd(), CWND_INITIAL);
+ cc.detect_persistent_congestion(None, last_ack, PTO, lost);
+ assert_eq!(cc.cwnd(), CWND_INITIAL);
+
+ cc.detect_persistent_congestion(rtt_time, last_ack, PTO, lost);
+ cc.cwnd() == CWND_MIN
+ }
+
+ /// No persistent congestion can be had if there are no lost packets.
+ #[test]
+ fn persistent_congestion_no_lost() {
+ let lost = make_lost(&[]);
+ assert!(!persistent_congestion_by_pto(0, 0, &lost));
+ }
+
+ /// No persistent congestion can be had if there is only one lost packet.
+ #[test]
+ fn persistent_congestion_one_lost() {
+ let lost = make_lost(&[1]);
+ assert!(!persistent_congestion_by_pto(0, 0, &lost));
+ }
+
+ /// Persistent congestion can't happen based on old packets.
+ #[test]
+ fn persistent_congestion_past() {
+ // Packets sent prior to either the last acknowledged or the first RTT
+ // sample are not considered. So 0 is ignored.
+ let lost = make_lost(&[0, PERSISTENT_CONG_THRESH + 1, PERSISTENT_CONG_THRESH + 2]);
+ assert!(!persistent_congestion_by_pto(1, 1, &lost));
+ assert!(!persistent_congestion_by_pto(0, 1, &lost));
+ assert!(!persistent_congestion_by_pto(1, 0, &lost));
+ }
+
+ /// Persistent congestion doesn't start unless the packet is ack-eliciting.
+ #[test]
+ fn persistent_congestion_ack_eliciting() {
+ let mut lost = make_lost(&[1, PERSISTENT_CONG_THRESH + 2]);
+ lost[0] = SentPacket::new(
+ lost[0].pt,
+ lost[0].pn,
+ lost[0].time_sent,
+ false,
+ Vec::new(),
+ lost[0].size,
+ );
+ assert!(!persistent_congestion_by_pto(0, 0, &lost));
+ }
+
+ /// Detect persistent congestion. Note that the first lost packet needs to have a time
+ /// greater than the previously acknowledged packet AND the first RTT sample. And the
+ /// difference in times needs to be greater than the persistent congestion threshold.
+ #[test]
+ fn persistent_congestion_min() {
+ let lost = make_lost(&[1, PERSISTENT_CONG_THRESH + 2]);
+ assert!(persistent_congestion_by_pto(0, 0, &lost));
+ }
+
+ /// Make sure that not having a previous largest acknowledged also results
+ /// in detecting persistent congestion. (This is not expected to happen, but
+ /// the code permits it).
+ #[test]
+ fn persistent_congestion_no_prev_ack() {
+ let lost = make_lost(&[1, PERSISTENT_CONG_THRESH + 2]);
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+ cc.detect_persistent_congestion(Some(by_pto(0)), None, PTO, &lost);
+ assert_eq!(cc.cwnd(), CWND_MIN);
+ }
+
+ /// The code asserts on ordering errors.
+ #[test]
+ #[should_panic]
+ fn persistent_congestion_unsorted() {
+ let lost = make_lost(&[PERSISTENT_CONG_THRESH + 2, 1]);
+ assert!(!persistent_congestion_by_pto(0, 0, &lost));
+ }
+
+ #[test]
+ fn app_limited_slow_start() {
+ const LESS_THAN_CWND_PKTS: usize = 4;
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+
+ for i in 0..CWND_INITIAL_PKTS {
+ let sent = SentPacket::new(
+ PacketType::Short,
+ u64::try_from(i).unwrap(), // pn
+ now(), // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ );
+ cc.on_packet_sent(&sent);
+ }
+ assert_eq!(cc.bytes_in_flight(), CWND_INITIAL);
+
+ for i in 0..LESS_THAN_CWND_PKTS {
+ let acked = SentPacket::new(
+ PacketType::Short,
+ u64::try_from(i).unwrap(), // pn
+ now(), // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ );
+ cc.on_packets_acked(&[acked]);
+
+ assert_eq!(
+ cc.bytes_in_flight(),
+ (CWND_INITIAL_PKTS - i - 1) * MAX_DATAGRAM_SIZE
+ );
+ assert_eq!(cc.cwnd(), (CWND_INITIAL_PKTS + i + 1) * MAX_DATAGRAM_SIZE);
+ }
+
+ // Now we are app limited
+ for i in 4..CWND_INITIAL_PKTS {
+ let p = [SentPacket::new(
+ PacketType::Short,
+ u64::try_from(i).unwrap(), // pn
+ now(), // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ )];
+ cc.on_packets_acked(&p);
+
+ assert_eq!(
+ cc.bytes_in_flight(),
+ (CWND_INITIAL_PKTS - i - 1) * MAX_DATAGRAM_SIZE
+ );
+ assert_eq!(cc.cwnd(), (CWND_INITIAL_PKTS + 4) * MAX_DATAGRAM_SIZE);
+ }
+ }
+
+ #[test]
+ fn app_limited_congestion_avoidance() {
+ const CWND_PKTS_CA: usize = CWND_INITIAL_PKTS / 2;
+
+ let mut cc = ClassicCongestionControl::new(NewReno::default());
+
+ // Change state to congestion avoidance by introducing loss.
+
+ let p_lost = SentPacket::new(
+ PacketType::Short,
+ 1, // pn
+ now(), // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ );
+ cc.on_packet_sent(&p_lost);
+ cwnd_is_default(&cc);
+ cc.on_packets_lost(Some(now()), None, PTO, &[p_lost]);
+ cwnd_is_halved(&cc);
+ let p_not_lost = SentPacket::new(
+ PacketType::Short,
+ 1, // pn
+ now(), // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ );
+ cc.on_packet_sent(&p_not_lost);
+ cc.on_packets_acked(&[p_not_lost]);
+ cwnd_is_halved(&cc);
+ // cc is app limited therefore cwnd in not increased.
+ assert_eq!(cc.acked_bytes, 0);
+
+ // Now we are in the congestion avoidance state.
+ let mut pkts = Vec::new();
+ for i in 0..CWND_PKTS_CA {
+ let p = SentPacket::new(
+ PacketType::Short,
+ u64::try_from(i + 3).unwrap(), // pn
+ now(), // time sent
+ true, // ack eliciting
+ Vec::new(), // tokens
+ MAX_DATAGRAM_SIZE, // size
+ );
+ cc.on_packet_sent(&p);
+ pkts.push(p);
+ }
+ assert_eq!(cc.bytes_in_flight(), CWND_INITIAL / 2);
+
+ for i in 0..CWND_PKTS_CA - 2 {
+ cc.on_packets_acked(&pkts[i..=i]);
+
+ assert_eq!(
+ cc.bytes_in_flight(),
+ (CWND_PKTS_CA - i - 1) * MAX_DATAGRAM_SIZE
+ );
+ assert_eq!(cc.cwnd(), CWND_PKTS_CA * MAX_DATAGRAM_SIZE);
+ assert_eq!(cc.acked_bytes, MAX_DATAGRAM_SIZE * (i + 1));
+ }
+
+ // Now we are app limited
+ for i in CWND_PKTS_CA - 2..CWND_PKTS_CA {
+ cc.on_packets_acked(&pkts[i..=i]);
+
+ assert_eq!(
+ cc.bytes_in_flight(),
+ (CWND_PKTS_CA - i - 1) * MAX_DATAGRAM_SIZE
+ );
+ assert_eq!(cc.cwnd(), CWND_PKTS_CA * MAX_DATAGRAM_SIZE);
+ assert_eq!(cc.acked_bytes, MAX_DATAGRAM_SIZE * 3);
+ }
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/cc/mod.rs b/third_party/rust/neqo-transport/src/cc/mod.rs
new file mode 100644
index 0000000000..996054ad08
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/cc/mod.rs
@@ -0,0 +1,55 @@
+// 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.
+
+// Congestion control
+#![deny(clippy::pedantic)]
+
+use crate::path::PATH_MTU_V6;
+use crate::tracking::SentPacket;
+use neqo_common::qlog::NeqoQlog;
+
+use std::fmt::{Debug, Display};
+use std::time::{Duration, Instant};
+
+mod classic_cc;
+mod new_reno;
+
+pub use classic_cc::ClassicCongestionControl;
+pub use classic_cc::{CWND_INITIAL_PKTS, CWND_MIN};
+pub use new_reno::NewReno;
+
+pub const MAX_DATAGRAM_SIZE: usize = PATH_MTU_V6;
+
+pub trait CongestionControl: Display + Debug {
+ fn set_qlog(&mut self, qlog: NeqoQlog);
+
+ fn cwnd(&self) -> usize;
+
+ fn bytes_in_flight(&self) -> usize;
+
+ fn cwnd_avail(&self) -> usize;
+
+ fn on_packets_acked(&mut self, acked_pkts: &[SentPacket]);
+
+ fn on_packets_lost(
+ &mut self,
+ first_rtt_sample_time: Option<Instant>,
+ prev_largest_acked_sent: Option<Instant>,
+ pto: Duration,
+ lost_packets: &[SentPacket],
+ );
+
+ fn recovery_packet(&self) -> bool;
+
+ fn discard(&mut self, pkt: &SentPacket);
+
+ fn on_packet_sent(&mut self, pkt: &SentPacket);
+}
+
+#[derive(Copy, Clone)]
+pub enum CongestionControlAlgorithm {
+ NewReno,
+}
diff --git a/third_party/rust/neqo-transport/src/cc/new_reno.rs b/third_party/rust/neqo-transport/src/cc/new_reno.rs
new file mode 100644
index 0000000000..a398887d61
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/cc/new_reno.rs
@@ -0,0 +1,44 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Congestion control
+#![deny(clippy::pedantic)]
+
+use std::fmt::{self, Display};
+
+use crate::cc::{classic_cc::WindowAdjustment, MAX_DATAGRAM_SIZE};
+use neqo_common::qinfo;
+
+#[derive(Debug)]
+pub struct NewReno {}
+
+impl Default for NewReno {
+ fn default() -> Self {
+ Self {}
+ }
+}
+
+impl Display for NewReno {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "NewReno")?;
+ Ok(())
+ }
+}
+
+impl WindowAdjustment for NewReno {
+ fn on_packets_acked(&mut self, mut curr_cwnd: usize, mut acked_bytes: usize) -> (usize, usize) {
+ if acked_bytes >= curr_cwnd {
+ acked_bytes -= curr_cwnd;
+ curr_cwnd += MAX_DATAGRAM_SIZE;
+ qinfo!([self], "congestion avoidance += {}", MAX_DATAGRAM_SIZE);
+ }
+ (curr_cwnd, acked_bytes)
+ }
+
+ fn on_congestion_event(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize) {
+ (curr_cwnd / 2, acked_bytes / 2)
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/cid.rs b/third_party/rust/neqo-transport/src/cid.rs
new file mode 100644
index 0000000000..ef2b938c28
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/cid.rs
@@ -0,0 +1,157 @@
+// 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.
+
+// Encoding and decoding packets off the wire.
+
+use neqo_common::{hex, hex_with_len, Decoder};
+use neqo_crypto::random;
+
+use std::borrow::Borrow;
+use std::cmp::max;
+use std::convert::AsRef;
+
+pub const MAX_CONNECTION_ID_LEN: usize = 20;
+
+#[derive(Clone, Default, Eq, Hash, PartialEq)]
+pub struct ConnectionId {
+ pub(crate) cid: Vec<u8>,
+}
+
+impl ConnectionId {
+ pub fn generate(len: usize) -> Self {
+ assert!(matches!(len, 0..=MAX_CONNECTION_ID_LEN));
+ Self { cid: random(len) }
+ }
+
+ // Apply a wee bit of greasing here in picking a length between 8 and 20 bytes long.
+ pub fn generate_initial() -> Self {
+ let v = random(1);
+ // Bias selection toward picking 8 (>50% of the time).
+ let len: usize = max(8, 5 + (v[0] & (v[0] >> 4))).into();
+ Self::generate(len)
+ }
+
+ pub fn as_cid_ref(&self) -> ConnectionIdRef {
+ ConnectionIdRef::from(&self.cid[..])
+ }
+}
+
+impl AsRef<[u8]> for ConnectionId {
+ fn as_ref(&self) -> &[u8] {
+ self.borrow()
+ }
+}
+
+impl Borrow<[u8]> for ConnectionId {
+ fn borrow(&self) -> &[u8] {
+ &self.cid
+ }
+}
+
+impl From<&[u8]> for ConnectionId {
+ fn from(buf: &[u8]) -> Self {
+ Self {
+ cid: Vec::from(buf),
+ }
+ }
+}
+
+impl<'a> From<&ConnectionIdRef<'a>> for ConnectionId {
+ fn from(cidref: &ConnectionIdRef<'a>) -> Self {
+ Self {
+ cid: Vec::from(cidref.cid),
+ }
+ }
+}
+
+impl std::ops::Deref for ConnectionId {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.cid
+ }
+}
+
+impl ::std::fmt::Debug for ConnectionId {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "CID {}", hex_with_len(&self.cid))
+ }
+}
+
+impl ::std::fmt::Display for ConnectionId {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{}", hex(&self.cid))
+ }
+}
+
+impl<'a> PartialEq<ConnectionIdRef<'a>> for ConnectionId {
+ fn eq(&self, other: &ConnectionIdRef<'a>) -> bool {
+ &self.cid[..] == other.cid
+ }
+}
+
+#[derive(Hash, Eq, PartialEq)]
+pub struct ConnectionIdRef<'a> {
+ cid: &'a [u8],
+}
+
+impl<'a> ::std::fmt::Debug for ConnectionIdRef<'a> {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "CID {}", hex_with_len(&self.cid))
+ }
+}
+
+impl<'a> ::std::fmt::Display for ConnectionIdRef<'a> {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{}", hex(&self.cid))
+ }
+}
+
+impl<'a> From<&'a [u8]> for ConnectionIdRef<'a> {
+ fn from(cid: &'a [u8]) -> Self {
+ Self { cid }
+ }
+}
+
+impl<'a> std::ops::Deref for ConnectionIdRef<'a> {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.cid
+ }
+}
+
+impl<'a> PartialEq<ConnectionId> for ConnectionIdRef<'a> {
+ fn eq(&self, other: &ConnectionId) -> bool {
+ self.cid == &other.cid[..]
+ }
+}
+
+pub trait ConnectionIdDecoder {
+ fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>>;
+}
+
+pub trait ConnectionIdManager: ConnectionIdDecoder {
+ fn generate_cid(&mut self) -> ConnectionId;
+ fn as_decoder(&self) -> &dyn ConnectionIdDecoder;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use test_fixture::fixture_init;
+
+ #[test]
+ fn generate_initial_cid() {
+ fixture_init();
+ for _ in 0..100 {
+ let cid = ConnectionId::generate_initial();
+ if !matches!(cid.len(), 8..=MAX_CONNECTION_ID_LEN) {
+ panic!("connection ID {:?}", cid);
+ }
+ }
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/connection/idle.rs b/third_party/rust/neqo-transport/src/connection/idle.rs
new file mode 100644
index 0000000000..9cf3be20a1
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/idle.rs
@@ -0,0 +1,90 @@
+// 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::cmp::{max, min};
+use std::time::{Duration, Instant};
+
+pub const LOCAL_IDLE_TIMEOUT: Duration = Duration::from_secs(30);
+
+#[derive(Debug, Clone)]
+/// There's a little bit of different behavior for resetting idle timeout. See
+/// -transport 10.2 ("Idle Timeout").
+enum IdleTimeoutState {
+ Init,
+ PacketReceived(Instant),
+ AckElicitingPacketSent(Instant),
+}
+
+#[derive(Debug, Clone)]
+/// There's a little bit of different behavior for resetting idle timeout. See
+/// -transport 10.2 ("Idle Timeout").
+pub struct IdleTimeout {
+ timeout: Duration,
+ state: IdleTimeoutState,
+}
+
+#[cfg(test)]
+impl IdleTimeout {
+ pub fn new(timeout: Duration) -> Self {
+ Self {
+ timeout,
+ state: IdleTimeoutState::Init,
+ }
+ }
+}
+
+impl Default for IdleTimeout {
+ fn default() -> Self {
+ Self {
+ timeout: LOCAL_IDLE_TIMEOUT,
+ state: IdleTimeoutState::Init,
+ }
+ }
+}
+
+impl IdleTimeout {
+ pub fn set_peer_timeout(&mut self, peer_timeout: Duration) {
+ self.timeout = min(self.timeout, peer_timeout);
+ }
+
+ pub fn expiry(&self, now: Instant, pto: Duration) -> Instant {
+ let start = match self.state {
+ IdleTimeoutState::Init => now,
+ IdleTimeoutState::PacketReceived(t) | IdleTimeoutState::AckElicitingPacketSent(t) => t,
+ };
+ start + max(self.timeout, pto * 3)
+ }
+
+ pub fn on_packet_sent(&mut self, now: Instant) {
+ // Only reset idle timeout if we've received a packet since the last
+ // time we reset the timeout here.
+ match self.state {
+ IdleTimeoutState::AckElicitingPacketSent(_) => {}
+ IdleTimeoutState::Init | IdleTimeoutState::PacketReceived(_) => {
+ self.state = IdleTimeoutState::AckElicitingPacketSent(now);
+ }
+ }
+ }
+
+ pub fn on_packet_received(&mut self, now: Instant) {
+ // Only update if this doesn't rewind the idle timeout.
+ // We sometimes process packets after caching them, which uses
+ // the time the packet was received. That could be in the past.
+ let update = match self.state {
+ IdleTimeoutState::Init => true,
+ IdleTimeoutState::AckElicitingPacketSent(t) | IdleTimeoutState::PacketReceived(t) => {
+ t <= now
+ }
+ };
+ if update {
+ self.state = IdleTimeoutState::PacketReceived(now);
+ }
+ }
+
+ pub fn expired(&self, now: Instant, pto: Duration) -> bool {
+ now >= self.expiry(now, pto)
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/connection/mod.rs b/third_party/rust/neqo-transport/src/connection/mod.rs
new file mode 100644
index 0000000000..ba6e628809
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/mod.rs
@@ -0,0 +1,2768 @@
+// 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.
+
+// The class implementing a QUIC connection.
+
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::fmt::{self, Debug};
+use std::mem;
+use std::net::SocketAddr;
+use std::rc::{Rc, Weak};
+use std::time::{Duration, Instant};
+
+use smallvec::SmallVec;
+
+use neqo_common::{
+ event::Provider as EventProvider, hex, hex_snip_middle, qdebug, qerror, qinfo, qlog::NeqoQlog,
+ qtrace, qwarn, Datagram, Decoder, Encoder, Role,
+};
+use neqo_crypto::agent::CertificateInfo;
+use neqo_crypto::{
+ Agent, AntiReplay, AuthenticationStatus, Cipher, Client, HandshakeState, ResumptionToken,
+ SecretAgentInfo, Server, ZeroRttChecker,
+};
+
+use crate::addr_valid::{AddressValidation, NewTokenState};
+use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdManager, ConnectionIdRef};
+use crate::crypto::{Crypto, CryptoDxState, CryptoSpace};
+use crate::dump::*;
+use crate::events::{ConnectionEvent, ConnectionEvents};
+use crate::flow_mgr::FlowMgr;
+use crate::frame::{
+ AckRange, CloseError, Frame, FrameType, StreamType, FRAME_TYPE_CONNECTION_CLOSE_APPLICATION,
+ FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT,
+};
+use crate::packet::{
+ DecryptedPacket, PacketBuilder, PacketNumber, PacketType, PublicPacket, QuicVersion,
+};
+use crate::path::Path;
+use crate::qlog;
+use crate::recovery::{LossRecovery, RecoveryToken, SendProfile, GRANULARITY};
+use crate::recv_stream::{RecvStream, RecvStreams, RECV_BUFFER_SIZE};
+use crate::send_stream::{SendStream, SendStreams};
+use crate::stats::{Stats, StatsCell};
+use crate::stream_id::{StreamId, StreamIndex, StreamIndexes};
+use crate::tparams::{
+ self, TransportParameter, TransportParameterId, TransportParameters, TransportParametersHandler,
+};
+use crate::tracking::{AckTracker, PNSpace, SentPacket};
+use crate::ConnectionParameters;
+use crate::{AppError, ConnectionError, Error, Res};
+
+mod idle;
+pub mod params;
+mod saved;
+mod state;
+
+use idle::IdleTimeout;
+pub use idle::LOCAL_IDLE_TIMEOUT;
+use saved::SavedDatagrams;
+pub use state::State;
+use state::StateSignaling;
+
+#[derive(Debug, Default)]
+struct Packet(Vec<u8>);
+
+pub const LOCAL_STREAM_LIMIT_BIDI: u64 = 16;
+pub const LOCAL_STREAM_LIMIT_UNI: u64 = 16;
+
+/// The number of Initial packets that the client will send in response
+/// to receiving an undecryptable packet during the early part of the
+/// handshake. This is a hack, but a useful one.
+const EXTRA_INITIALS: usize = 4;
+const LOCAL_MAX_DATA: u64 = 0x3FFF_FFFF_FFFF_FFFF; // 2^62-1
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum ZeroRttState {
+ Init,
+ Sending,
+ AcceptedClient,
+ AcceptedServer,
+ Rejected,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+/// Type returned from process() and `process_output()`. Users are required to
+/// call these repeatedly until `Callback` or `None` is returned.
+pub enum Output {
+ /// Connection requires no action.
+ None,
+ /// Connection requires the datagram be sent.
+ Datagram(Datagram),
+ /// Connection requires `process_input()` be called when the `Duration`
+ /// elapses.
+ Callback(Duration),
+}
+
+impl Output {
+ /// Convert into an `Option<Datagram>`.
+ #[must_use]
+ pub fn dgram(self) -> Option<Datagram> {
+ match self {
+ Self::Datagram(dg) => Some(dg),
+ _ => None,
+ }
+ }
+
+ /// Get a reference to the Datagram, if any.
+ pub fn as_dgram_ref(&self) -> Option<&Datagram> {
+ match self {
+ Self::Datagram(dg) => Some(dg),
+ _ => None,
+ }
+ }
+
+ /// Ask how long the caller should wait before calling back.
+ #[must_use]
+ pub fn callback(&self) -> Duration {
+ match self {
+ Self::Callback(t) => *t,
+ _ => Duration::new(0, 0),
+ }
+ }
+}
+
+/// Used by inner functions like Connection::output.
+enum SendOption {
+ /// Yes, please send this datagram.
+ Yes(Datagram),
+ /// Don't send. If this was blocked on the pacer (the arg is true).
+ No(bool),
+}
+
+impl Default for SendOption {
+ fn default() -> Self {
+ Self::No(false)
+ }
+}
+
+/// Used by `Connection::preprocess` to determine what to do
+/// with an packet before attempting to remove protection.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum PreprocessResult {
+ /// End processing and return successfully.
+ End,
+ /// Stop processing this datagram and move on to the next.
+ Next,
+ /// Continue and process this packet.
+ Continue,
+}
+
+/// Alias the common form for ConnectionIdManager.
+type CidMgr = Rc<RefCell<dyn ConnectionIdManager>>;
+
+/// An FixedConnectionIdManager produces random connection IDs of a fixed length.
+pub struct FixedConnectionIdManager {
+ len: usize,
+}
+impl FixedConnectionIdManager {
+ pub fn new(len: usize) -> Self {
+ Self { len }
+ }
+}
+impl ConnectionIdDecoder for FixedConnectionIdManager {
+ fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> {
+ dec.decode(self.len).map(ConnectionIdRef::from)
+ }
+}
+impl ConnectionIdManager for FixedConnectionIdManager {
+ fn generate_cid(&mut self) -> ConnectionId {
+ ConnectionId::generate(self.len)
+ }
+ fn as_decoder(&self) -> &dyn ConnectionIdDecoder {
+ self
+ }
+}
+
+/// `AddressValidationInfo` holds information relevant to either
+/// responding to address validation (`NewToken`, `Retry`) or generating
+/// tokens for address validation (`Server`).
+enum AddressValidationInfo {
+ None,
+ // We are a client and have information from `NEW_TOKEN`.
+ NewToken(Vec<u8>),
+ // We are a client and have received a `Retry` packet.
+ Retry {
+ token: Vec<u8>,
+ retry_source_cid: ConnectionId,
+ },
+ // We are a server and can generate tokens.
+ Server(Weak<RefCell<AddressValidation>>),
+}
+
+impl AddressValidationInfo {
+ pub fn token(&self) -> &[u8] {
+ match self {
+ Self::NewToken(token) | Self::Retry { token, .. } => &token,
+ _ => &[],
+ }
+ }
+
+ pub fn generate_new_token(
+ &mut self,
+ peer_address: SocketAddr,
+ now: Instant,
+ ) -> Option<Vec<u8>> {
+ match self {
+ Self::Server(ref w) => {
+ if let Some(validation) = w.upgrade() {
+ validation
+ .borrow()
+ .generate_new_token(peer_address, now)
+ .ok()
+ } else {
+ None
+ }
+ }
+ Self::None => None,
+ _ => unreachable!("called a server function on a client"),
+ }
+ }
+}
+
+/// A QUIC Connection
+///
+/// First, create a new connection using `new_client()` or `new_server()`.
+///
+/// For the life of the connection, handle activity in the following manner:
+/// 1. Perform operations using the `stream_*()` methods.
+/// 1. Call `process_input()` when a datagram is received or the timer
+/// expires. Obtain information on connection state changes by checking
+/// `events()`.
+/// 1. Having completed handling current activity, repeatedly call
+/// `process_output()` for packets to send, until it returns `Output::Callback`
+/// or `Output::None`.
+///
+/// After the connection is closed (either by calling `close()` or by the
+/// remote) continue processing until `state()` returns `Closed`.
+pub struct Connection {
+ role: Role,
+ state: State,
+ tps: Rc<RefCell<TransportParametersHandler>>,
+ /// What we are doing with 0-RTT.
+ zero_rtt_state: ZeroRttState,
+ /// This object will generate connection IDs for the connection.
+ cid_manager: CidMgr,
+ /// Network paths. Right now, this tracks at most one path, so it uses `Option`.
+ path: Option<Path>,
+ /// The connection IDs that we will accept.
+ /// This includes any we advertise in NEW_CONNECTION_ID that haven't been bound to a path yet.
+ /// During the handshake at the server, it also includes the randomized DCID pick by the client.
+ valid_cids: Vec<ConnectionId>,
+ address_validation: AddressValidationInfo,
+
+ /// The source connection ID that this endpoint uses for the handshake.
+ /// Since we need to communicate this to our peer in tparams, setting this
+ /// value is part of constructing the struct.
+ local_initial_source_cid: ConnectionId,
+ /// The source connection ID from the first packet from the other end.
+ /// This is checked against the peer's transport parameters.
+ remote_initial_source_cid: Option<ConnectionId>,
+ /// The destination connection ID from the first packet from the client.
+ /// This is checked by the client against the server's transport parameters.
+ original_destination_cid: Option<ConnectionId>,
+
+ /// We sometimes save a datagram against the possibility that keys will later
+ /// become available. This avoids reporting packets as dropped during the handshake
+ /// when they are either just reordered or we haven't been able to install keys yet.
+ /// In particular, this occurs when asynchronous certificate validation happens.
+ saved_datagrams: SavedDatagrams,
+
+ pub(crate) crypto: Crypto,
+ pub(crate) acks: AckTracker,
+ idle_timeout: IdleTimeout,
+ pub(crate) indexes: StreamIndexes,
+ connection_ids: HashMap<u64, (ConnectionId, [u8; 16])>, // (sequence number, (connection id, reset token))
+ pub(crate) send_streams: SendStreams,
+ pub(crate) recv_streams: RecvStreams,
+ pub(crate) flow_mgr: Rc<RefCell<FlowMgr>>,
+ state_signaling: StateSignaling,
+ loss_recovery: LossRecovery,
+ events: ConnectionEvents,
+ new_token: NewTokenState,
+ stats: StatsCell,
+ qlog: NeqoQlog,
+ /// A session ticket was received without NEW_TOKEN,
+ /// this is when that turns into an event without NEW_TOKEN.
+ release_resumption_token_timer: Option<Instant>,
+ quic_version: QuicVersion,
+}
+
+impl Debug for Connection {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{:?} Connection: {:?} {:?}",
+ self.role, self.state, self.path
+ )
+ }
+}
+
+impl Connection {
+ /// Create a new QUIC connection with Client role.
+ pub fn new_client(
+ server_name: &str,
+ protocols: &[impl AsRef<str>],
+ cid_manager: CidMgr,
+ local_addr: SocketAddr,
+ remote_addr: SocketAddr,
+ conn_params: &ConnectionParameters,
+ ) -> Res<Self> {
+ let dcid = ConnectionId::generate_initial();
+ let mut c = Self::new(
+ Role::Client,
+ Client::new(server_name)?.into(),
+ cid_manager,
+ protocols,
+ None,
+ conn_params,
+ )?;
+ c.crypto
+ .states
+ .init(conn_params.get_quic_version(), Role::Client, &dcid);
+ c.original_destination_cid = Some(dcid);
+ c.initialize_path(local_addr, remote_addr);
+ Ok(c)
+ }
+
+ /// Create a new QUIC connection with Server role.
+ pub fn new_server(
+ certs: &[impl AsRef<str>],
+ protocols: &[impl AsRef<str>],
+ cid_manager: CidMgr,
+ conn_params: &ConnectionParameters,
+ ) -> Res<Self> {
+ Self::new(
+ Role::Server,
+ Server::new(certs)?.into(),
+ cid_manager,
+ protocols,
+ None,
+ conn_params,
+ )
+ }
+
+ pub fn server_enable_0rtt(
+ &mut self,
+ anti_replay: &AntiReplay,
+ zero_rtt_checker: impl ZeroRttChecker + 'static,
+ ) -> Res<()> {
+ self.crypto
+ .server_enable_0rtt(self.tps.clone(), anti_replay, zero_rtt_checker)
+ }
+
+ fn set_tp_defaults(tps: &mut TransportParameters) {
+ tps.set_integer(
+ tparams::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+ u64::try_from(RECV_BUFFER_SIZE).unwrap(),
+ );
+ tps.set_integer(
+ tparams::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+ u64::try_from(RECV_BUFFER_SIZE).unwrap(),
+ );
+ tps.set_integer(
+ tparams::INITIAL_MAX_STREAM_DATA_UNI,
+ u64::try_from(RECV_BUFFER_SIZE).unwrap(),
+ );
+ tps.set_integer(tparams::INITIAL_MAX_STREAMS_BIDI, LOCAL_STREAM_LIMIT_BIDI);
+ tps.set_integer(tparams::INITIAL_MAX_STREAMS_UNI, LOCAL_STREAM_LIMIT_UNI);
+ tps.set_integer(tparams::INITIAL_MAX_DATA, LOCAL_MAX_DATA);
+ tps.set_integer(
+ tparams::IDLE_TIMEOUT,
+ u64::try_from(LOCAL_IDLE_TIMEOUT.as_millis()).unwrap(),
+ );
+ tps.set_empty(tparams::DISABLE_MIGRATION);
+ tps.set_empty(tparams::GREASE_QUIC_BIT);
+ }
+
+ fn new(
+ role: Role,
+ agent: Agent,
+ cid_manager: CidMgr,
+ protocols: &[impl AsRef<str>],
+ path: Option<Path>,
+ conn_params: &ConnectionParameters,
+ ) -> Res<Self> {
+ let tphandler = Rc::new(RefCell::new(TransportParametersHandler::default()));
+ Self::set_tp_defaults(&mut tphandler.borrow_mut().local);
+ tphandler.borrow_mut().local.set_integer(
+ tparams::INITIAL_MAX_STREAMS_BIDI,
+ conn_params.get_max_streams(StreamType::BiDi),
+ );
+ tphandler.borrow_mut().local.set_integer(
+ tparams::INITIAL_MAX_STREAMS_UNI,
+ conn_params.get_max_streams(StreamType::UniDi),
+ );
+ let local_initial_source_cid = cid_manager.borrow_mut().generate_cid();
+ tphandler.borrow_mut().local.set_bytes(
+ tparams::INITIAL_SOURCE_CONNECTION_ID,
+ local_initial_source_cid.to_vec(),
+ );
+
+ let crypto = Crypto::new(agent, protocols, tphandler.clone())?;
+
+ let stats = StatsCell::default();
+ let c = Self {
+ role,
+ state: State::Init,
+ cid_manager,
+ path,
+ valid_cids: Vec::new(),
+ tps: tphandler,
+ zero_rtt_state: ZeroRttState::Init,
+ address_validation: AddressValidationInfo::None,
+ local_initial_source_cid,
+ remote_initial_source_cid: None,
+ original_destination_cid: None,
+ saved_datagrams: SavedDatagrams::default(),
+ crypto,
+ acks: AckTracker::default(),
+ idle_timeout: IdleTimeout::default(),
+ indexes: StreamIndexes::new(),
+ connection_ids: HashMap::new(),
+ send_streams: SendStreams::default(),
+ recv_streams: RecvStreams::default(),
+ flow_mgr: Rc::new(RefCell::new(FlowMgr::default())),
+ state_signaling: StateSignaling::Idle,
+ loss_recovery: LossRecovery::new(conn_params.get_cc_algorithm(), stats.clone()),
+ events: ConnectionEvents::default(),
+ new_token: NewTokenState::new(role),
+ stats,
+ qlog: NeqoQlog::disabled(),
+ release_resumption_token_timer: None,
+ quic_version: conn_params.get_quic_version(),
+ };
+ c.stats.borrow_mut().init(format!("{}", c));
+ Ok(c)
+ }
+
+ /// Get the local path.
+ pub fn path(&self) -> Option<&Path> {
+ self.path.as_ref()
+ }
+
+ /// Set or clear the qlog for this connection.
+ pub fn set_qlog(&mut self, qlog: NeqoQlog) {
+ self.loss_recovery.set_qlog(qlog.clone());
+ self.qlog = qlog;
+ }
+
+ /// Get the qlog (if any) for this connection.
+ pub fn qlog_mut(&mut self) -> &mut NeqoQlog {
+ &mut self.qlog
+ }
+
+ /// Get the original destination connection id for this connection. This
+ /// will always be present for Role::Client but not if Role::Server is in
+ /// State::Init.
+ pub fn odcid(&self) -> Option<&ConnectionId> {
+ self.original_destination_cid.as_ref()
+ }
+
+ /// Set a local transport parameter, possibly overriding a default value.
+ pub fn set_local_tparam(&self, tp: TransportParameterId, value: TransportParameter) -> Res<()> {
+ if *self.state() == State::Init {
+ self.tps.borrow_mut().local.set(tp, value);
+ Ok(())
+ } else {
+ qerror!("Current state: {:?}", self.state());
+ qerror!("Cannot set local tparam when not in an initial connection state.");
+ Err(Error::ConnectionState)
+ }
+ }
+
+ /// `odcid` is their original choice for our CID, which we get from the Retry token.
+ /// `remote_cid` is the value from the Source Connection ID field of
+ /// an incoming packet: what the peer wants us to use now.
+ /// `retry_cid` is what we asked them to use when we sent the Retry.
+ pub(crate) fn set_retry_cids(
+ &mut self,
+ odcid: ConnectionId,
+ remote_cid: ConnectionId,
+ retry_cid: ConnectionId,
+ ) {
+ debug_assert_eq!(self.role, Role::Server);
+ qtrace!(
+ [self],
+ "Retry CIDs: odcid={} remote={} retry={}",
+ odcid,
+ remote_cid,
+ retry_cid
+ );
+ // We advertise "our" choices in transport parameters.
+ let local_tps = &mut self.tps.borrow_mut().local;
+ local_tps.set_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID, odcid.to_vec());
+ local_tps.set_bytes(tparams::RETRY_SOURCE_CONNECTION_ID, retry_cid.to_vec());
+
+ // ...and save their choices for later validation.
+ self.remote_initial_source_cid = Some(remote_cid);
+ }
+
+ fn retry_sent(&self) -> bool {
+ self.tps
+ .borrow()
+ .local
+ .get_bytes(tparams::RETRY_SOURCE_CONNECTION_ID)
+ .is_some()
+ }
+
+ /// Set ALPN preferences. Strings that appear earlier in the list are given
+ /// higher preference.
+ pub fn set_alpn(&mut self, protocols: &[impl AsRef<str>]) -> Res<()> {
+ self.crypto.tls.set_alpn(protocols)?;
+ Ok(())
+ }
+
+ /// Enable a set of ciphers.
+ pub fn set_ciphers(&mut self, ciphers: &[Cipher]) -> Res<()> {
+ if self.state != State::Init {
+ qerror!([self], "Cannot enable ciphers in state {:?}", self.state);
+ return Err(Error::ConnectionState);
+ }
+ self.crypto.tls.set_ciphers(ciphers)?;
+ Ok(())
+ }
+
+ fn make_resumption_token(&mut self) -> ResumptionToken {
+ debug_assert_eq!(self.role, Role::Client);
+ debug_assert!(self.crypto.has_resumption_token());
+ self.crypto
+ .create_resumption_token(
+ self.new_token.take_token(),
+ self.tps
+ .borrow()
+ .remote
+ .as_ref()
+ .expect("should have transport parameters"),
+ u64::try_from(self.loss_recovery.rtt().as_millis()).unwrap_or(0),
+ )
+ .unwrap()
+ }
+
+ fn create_resumption_token(&mut self, now: Instant) {
+ if self.role == Role::Server || self.state < State::Connected {
+ return;
+ }
+
+ qtrace!(
+ [self],
+ "Maybe create resumption token: {} {}",
+ self.crypto.has_resumption_token(),
+ self.new_token.has_token()
+ );
+
+ while self.crypto.has_resumption_token() && self.new_token.has_token() {
+ let token = self.make_resumption_token();
+ self.events.client_resumption_token(token);
+ }
+
+ // If we have a resumption ticket check or set a timer.
+ if self.crypto.has_resumption_token() {
+ let arm = if let Some(expiration_time) = self.release_resumption_token_timer {
+ if expiration_time <= now {
+ let token = self.make_resumption_token();
+ self.events.client_resumption_token(token);
+ self.release_resumption_token_timer = None;
+
+ // This means that we release one session ticket every 3 PTOs
+ // if no NEW_TOKEN frame is received.
+ self.crypto.has_resumption_token()
+ } else {
+ false
+ }
+ } else {
+ true
+ };
+
+ if arm {
+ self.release_resumption_token_timer =
+ Some(now + 3 * self.loss_recovery.pto_raw(PNSpace::ApplicationData));
+ }
+ }
+ }
+
+ /// Get a resumption token. The correct way to obtain a resumption token is
+ /// waiting for the `ConnectionEvent::ResumptionToken` event. However, some
+ /// servers don't send `NEW_TOKEN` frames and so that event might be slow in
+ /// arriving. This is especially a problem for short-lived connections, where
+ /// the connection is closed before any events are released. This retrieves
+ /// the token, without waiting for the `NEW_TOKEN` frame to arrive.
+ ///
+ /// # Panics
+ /// If this is called on a server.
+ pub fn take_resumption_token(&mut self, now: Instant) -> Option<ResumptionToken> {
+ assert_eq!(self.role, Role::Client);
+
+ if self.crypto.has_resumption_token() {
+ let token = self.make_resumption_token();
+ if self.crypto.has_resumption_token() {
+ self.release_resumption_token_timer =
+ Some(now + 3 * self.loss_recovery.pto_raw(PNSpace::ApplicationData));
+ }
+ Some(token)
+ } else {
+ None
+ }
+ }
+
+ /// Enable resumption, using a token previously provided.
+ /// This can only be called once and only on the client.
+ /// After calling the function, it should be possible to attempt 0-RTT
+ /// if the token supports that.
+ pub fn enable_resumption(&mut self, now: Instant, token: impl AsRef<[u8]>) -> Res<()> {
+ if self.state != State::Init {
+ qerror!([self], "set token in state {:?}", self.state);
+ return Err(Error::ConnectionState);
+ }
+ if self.role == Role::Server {
+ return Err(Error::ConnectionState);
+ }
+
+ qinfo!(
+ [self],
+ "resumption token {}",
+ hex_snip_middle(token.as_ref())
+ );
+ let mut dec = Decoder::from(token.as_ref());
+
+ let smoothed_rtt =
+ Duration::from_millis(dec.decode_varint().ok_or(Error::InvalidResumptionToken)?);
+ qtrace!([self], " RTT {:?}", smoothed_rtt);
+
+ let tp_slice = dec.decode_vvec().ok_or(Error::InvalidResumptionToken)?;
+ qtrace!([self], " transport parameters {}", hex(&tp_slice));
+ let mut dec_tp = Decoder::from(tp_slice);
+ let tp =
+ TransportParameters::decode(&mut dec_tp).map_err(|_| Error::InvalidResumptionToken)?;
+
+ let init_token = dec.decode_vvec().ok_or(Error::InvalidResumptionToken)?;
+ qtrace!([self], " Initial token {}", hex(&init_token));
+
+ let tok = dec.decode_remainder();
+ qtrace!([self], " TLS token {}", hex(&tok));
+ match self.crypto.tls {
+ Agent::Client(ref mut c) => {
+ let res = c.enable_resumption(&tok);
+ if let Err(e) = res {
+ self.absorb_error::<Error>(now, Err(Error::from(e)));
+ return Ok(());
+ }
+ }
+ Agent::Server(_) => return Err(Error::WrongRole),
+ }
+
+ self.tps.borrow_mut().remote_0rtt = Some(tp);
+ if !init_token.is_empty() {
+ self.address_validation = AddressValidationInfo::NewToken(init_token.to_vec());
+ }
+ if smoothed_rtt > GRANULARITY {
+ self.loss_recovery.set_initial_rtt(smoothed_rtt);
+ }
+ self.set_initial_limits();
+ // Start up TLS, which has the effect of setting up all the necessary
+ // state for 0-RTT. This only stages the CRYPTO frames.
+ let res = self.client_start(now);
+ self.absorb_error(now, res);
+ Ok(())
+ }
+
+ pub(crate) fn set_validation(&mut self, validation: Rc<RefCell<AddressValidation>>) {
+ qtrace!([self], "Enabling NEW_TOKEN");
+ assert_eq!(self.role, Role::Server);
+ self.address_validation = AddressValidationInfo::Server(Rc::downgrade(&validation));
+ }
+
+ /// Send a TLS session ticket AND a NEW_TOKEN frame (if possible).
+ pub fn send_ticket(&mut self, now: Instant, extra: &[u8]) -> Res<()> {
+ if self.role == Role::Client {
+ return Err(Error::WrongRole);
+ }
+
+ let tps = &self.tps;
+ if let Agent::Server(ref mut s) = self.crypto.tls {
+ let mut enc = Encoder::default();
+ enc.encode_vvec_with(|mut enc_inner| {
+ tps.borrow().local.encode(&mut enc_inner);
+ });
+ enc.encode(extra);
+ let records = s.send_ticket(now, &enc)?;
+ qinfo!([self], "send session ticket {}", hex(&enc));
+ self.crypto.buffer_records(records)?;
+ } else {
+ unreachable!();
+ }
+
+ // If we are able, also send a NEW_TOKEN frame.
+ // This should be recording all remote addresses that are valid,
+ // but there are just 0 or 1 in the current implementation.
+ if let Some(p) = self.path.as_ref() {
+ if let Some(token) = self
+ .address_validation
+ .generate_new_token(p.remote_address(), now)
+ {
+ self.new_token.send_new_token(token);
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn tls_info(&self) -> Option<&SecretAgentInfo> {
+ self.crypto.tls.info()
+ }
+
+ /// Get the peer's certificate chain and other info.
+ pub fn peer_certificate(&self) -> Option<CertificateInfo> {
+ self.crypto.tls.peer_certificate()
+ }
+
+ /// Call by application when the peer cert has been verified.
+ ///
+ /// This panics if there is no active peer. It's OK to call this
+ /// when authentication isn't needed, that will likely only cause
+ /// the connection to fail. However, if no packets have been
+ /// exchanged, it's not OK.
+ pub fn authenticated(&mut self, status: AuthenticationStatus, now: Instant) {
+ qinfo!([self], "Authenticated {:?}", status);
+ self.crypto.tls.authenticated(status);
+ let res = self.handshake(now, PNSpace::Handshake, None);
+ self.absorb_error(now, res);
+ self.process_saved(now);
+ }
+
+ /// Get the role of the connection.
+ pub fn role(&self) -> Role {
+ self.role
+ }
+
+ /// Get the state of the connection.
+ pub fn state(&self) -> &State {
+ &self.state
+ }
+
+ /// Get the 0-RTT state of the connection.
+ pub fn zero_rtt_state(&self) -> &ZeroRttState {
+ &self.zero_rtt_state
+ }
+
+ /// Get a snapshot of collected statistics.
+ pub fn stats(&self) -> Stats {
+ self.stats.borrow().clone()
+ }
+
+ // This function wraps a call to another function and sets the connection state
+ // properly if that call fails.
+ fn capture_error<T>(&mut self, now: Instant, frame_type: FrameType, res: Res<T>) -> Res<T> {
+ if let Err(v) = &res {
+ #[cfg(debug_assertions)]
+ let msg = format!("{:?}", v);
+ #[cfg(not(debug_assertions))]
+ let msg = "";
+ let error = ConnectionError::Transport(v.clone());
+ match &self.state {
+ State::Closing { error: err, .. }
+ | State::Draining { error: err, .. }
+ | State::Closed(err) => {
+ qwarn!([self], "Closing again after error {:?}", err);
+ }
+ State::Init => {
+ // We have not even sent anything just close the connection without sending any error.
+ // This may happen when client_start fails.
+ self.set_state(State::Closed(error));
+ }
+ State::WaitInitial => {
+ // We don't have any state yet, so don't bother with
+ // the closing state, just send one CONNECTION_CLOSE.
+ self.state_signaling.close(error.clone(), frame_type, msg);
+ self.set_state(State::Closed(error));
+ }
+ _ => {
+ self.state_signaling.close(error.clone(), frame_type, msg);
+ if matches!(v, Error::KeysExhausted) {
+ self.set_state(State::Closed(error));
+ } else {
+ self.set_state(State::Closing {
+ error,
+ timeout: self.get_closing_period_time(now),
+ });
+ }
+ }
+ }
+ }
+ res
+ }
+
+ /// For use with process_input(). Errors there can be ignored, but this
+ /// needs to ensure that the state is updated.
+ fn absorb_error<T>(&mut self, now: Instant, res: Res<T>) -> Option<T> {
+ self.capture_error(now, 0, res).ok()
+ }
+
+ fn process_timer(&mut self, now: Instant) {
+ if let State::Closing { error, timeout } | State::Draining { error, timeout } = &self.state
+ {
+ if *timeout <= now {
+ // Close timeout expired, move to Closed
+ let st = State::Closed(error.clone());
+ self.set_state(st);
+ qinfo!("Closing timer expired");
+ return;
+ }
+ }
+ if let State::Closed(_) = self.state {
+ qdebug!("Timer fired while closed");
+ return;
+ }
+
+ let pto = self.loss_recovery.pto_raw(PNSpace::ApplicationData);
+ if self.idle_timeout.expired(now, pto) {
+ qinfo!([self], "idle timeout expired");
+ self.set_state(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout,
+ )));
+ return;
+ }
+
+ self.cleanup_streams();
+
+ let res = self.crypto.states.check_key_update(now);
+ self.absorb_error(now, res);
+
+ let lost = self.loss_recovery.timeout(now);
+ self.handle_lost_packets(&lost);
+ qlog::packets_lost(&mut self.qlog, &lost);
+
+ if self.release_resumption_token_timer.is_some() {
+ self.create_resumption_token(now);
+ }
+ }
+
+ /// Process new input datagrams on the connection.
+ pub fn process_input(&mut self, d: Datagram, now: Instant) {
+ let res = self.input(d, now);
+ self.absorb_error(now, res);
+ self.process_saved(now);
+ self.cleanup_streams();
+ }
+
+ /// Get the time that we next need to be called back, relative to `now`.
+ fn next_delay(&mut self, now: Instant, paced: bool) -> Duration {
+ qtrace!([self], "Get callback delay {:?}", now);
+
+ // Only one timer matters when closing...
+ if let State::Closing { timeout, .. } | State::Draining { timeout, .. } = self.state {
+ return timeout.duration_since(now);
+ }
+
+ let mut delays = SmallVec::<[_; 6]>::new();
+ if let Some(ack_time) = self.acks.ack_time(now) {
+ qtrace!([self], "Delayed ACK timer {:?}", ack_time);
+ delays.push(ack_time);
+ }
+
+ let pto = self.loss_recovery.pto_raw(PNSpace::ApplicationData);
+ let idle_time = self.idle_timeout.expiry(now, pto);
+ qtrace!([self], "Idle timer {:?}", idle_time);
+ delays.push(idle_time);
+
+ if let Some(lr_time) = self.loss_recovery.next_timeout() {
+ qtrace!([self], "Loss recovery timer {:?}", lr_time);
+ delays.push(lr_time);
+ }
+
+ if let Some(key_update_time) = self.crypto.states.update_time() {
+ qtrace!([self], "Key update timer {:?}", key_update_time);
+ delays.push(key_update_time);
+ }
+
+ if paced {
+ if let Some(pace_time) = self.loss_recovery.next_paced() {
+ qtrace!([self], "Pacing timer {:?}", pace_time);
+ delays.push(pace_time);
+ }
+ }
+
+ // `release_resumption_token_timer` is not considered here, because
+ // it is not important enough to force the application to set a
+ // timeout for it It is expected thatt other activities will
+ // drive it.
+
+ let earliest = delays.into_iter().min().unwrap();
+ // TODO(agrover, mt) - need to analyze and fix #47
+ // rather than just clamping to zero here.
+ qdebug!(
+ [self],
+ "delay duration {:?}",
+ max(now, earliest).duration_since(now)
+ );
+ debug_assert!(earliest > now);
+ max(now, earliest).duration_since(now)
+ }
+
+ /// Get output packets, as a result of receiving packets, or actions taken
+ /// by the application.
+ /// Returns datagrams to send, and how long to wait before calling again
+ /// even if no incoming packets.
+ #[must_use = "Output of the process_output function must be handled"]
+ pub fn process_output(&mut self, now: Instant) -> Output {
+ qtrace!([self], "process_output {:?} {:?}", self.state, now);
+
+ if self.state == State::Init {
+ if self.role == Role::Client {
+ let res = self.client_start(now);
+ self.absorb_error(now, res);
+ }
+ } else {
+ self.process_timer(now);
+ }
+
+ match self.output(now) {
+ SendOption::Yes(dgram) => Output::Datagram(dgram),
+ SendOption::No(paced) => match self.state {
+ State::Init | State::Closed(_) => Output::None,
+ State::Closing { timeout, .. } | State::Draining { timeout, .. } => {
+ Output::Callback(timeout.duration_since(now))
+ }
+ _ => Output::Callback(self.next_delay(now, paced)),
+ },
+ }
+ }
+
+ /// Process input and generate output.
+ #[must_use = "Output of the process function must be handled"]
+ pub fn process(&mut self, dgram: Option<Datagram>, now: Instant) -> Output {
+ if let Some(d) = dgram {
+ let res = self.input(d, now);
+ self.absorb_error(now, res);
+ self.process_saved(now);
+ }
+ self.process_output(now)
+ }
+
+ fn is_valid_cid(&self, cid: &ConnectionIdRef) -> bool {
+ self.valid_cids.iter().any(|c| c == cid) || self.path.iter().any(|p| p.valid_local_cid(cid))
+ }
+
+ fn handle_retry(&mut self, packet: &PublicPacket) -> Res<()> {
+ qinfo!([self], "received Retry");
+ if matches!(self.address_validation, AddressValidationInfo::Retry { .. }) {
+ self.stats.borrow_mut().pkt_dropped("Extra Retry");
+ return Ok(());
+ }
+ if packet.token().is_empty() {
+ self.stats.borrow_mut().pkt_dropped("Retry without a token");
+ return Ok(());
+ }
+ if !packet.is_valid_retry(&self.original_destination_cid.as_ref().unwrap()) {
+ self.stats
+ .borrow_mut()
+ .pkt_dropped("Retry with bad integrity tag");
+ return Ok(());
+ }
+ if let Some(p) = &mut self.path {
+ // At this point, we shouldn't have a remote connection ID for the path.
+ p.set_remote_cid(packet.scid());
+ } else {
+ qinfo!([self], "No path, but we received a Retry");
+ return Err(Error::InternalError);
+ };
+
+ let retry_scid = ConnectionId::from(packet.scid());
+ qinfo!(
+ [self],
+ "Valid Retry received, token={} scid={}",
+ hex(packet.token()),
+ retry_scid
+ );
+
+ let lost_packets = self.loss_recovery.retry();
+ self.handle_lost_packets(&lost_packets);
+
+ self.crypto
+ .states
+ .init(self.quic_version, self.role, &retry_scid);
+ self.address_validation = AddressValidationInfo::Retry {
+ token: packet.token().to_vec(),
+ retry_source_cid: retry_scid,
+ };
+ Ok(())
+ }
+
+ fn discard_keys(&mut self, space: PNSpace, now: Instant) {
+ if self.crypto.discard(space) {
+ qinfo!([self], "Drop packet number space {}", space);
+ self.loss_recovery.discard(space, now);
+ self.acks.drop_space(space);
+ }
+ }
+
+ fn token_equal(a: &[u8; 16], b: &[u8; 16]) -> bool {
+ // rustc might decide to optimize this and make this non-constant-time
+ // with respect to `t`, but it doesn't appear to currently.
+ let mut c = 0;
+ for (&a, &b) in a.iter().zip(b) {
+ c |= a ^ b;
+ }
+ c == 0
+ }
+
+ fn is_stateless_reset(&self, d: &Datagram) -> bool {
+ if d.len() < 16 {
+ return false;
+ }
+ let token = <&[u8; 16]>::try_from(&d[d.len() - 16..]).unwrap();
+ // TODO(mt) only check the path that matches the datagram.
+ self.path
+ .as_ref()
+ .map(|p| p.reset_token())
+ .flatten()
+ .map_or(false, |t| Self::token_equal(t, token))
+ }
+
+ fn check_stateless_reset<'a, 'b>(
+ &'a mut self,
+ d: &'b Datagram,
+ first: bool,
+ now: Instant,
+ ) -> Res<()> {
+ if first && self.is_stateless_reset(d) {
+ // Failing to process a packet in a datagram might
+ // indicate that there is a stateless reset present.
+ qdebug!([self], "Stateless reset: {}", hex(&d[d.len() - 16..]));
+ self.state_signaling.reset();
+ self.set_state(State::Draining {
+ error: ConnectionError::Transport(Error::StatelessReset),
+ timeout: self.get_closing_period_time(now),
+ });
+ Err(Error::StatelessReset)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Process any saved datagrams that might be available for processing.
+ fn process_saved(&mut self, now: Instant) {
+ while let Some(cspace) = self.saved_datagrams.available() {
+ qdebug!([self], "process saved for space {:?}", cspace);
+ debug_assert!(self.crypto.states.rx_hp(cspace).is_some());
+ for saved in self.saved_datagrams.take_saved() {
+ qtrace!([self], "input saved @{:?}: {:?}", saved.t, saved.d);
+ let res = self.input(saved.d, saved.t);
+ self.absorb_error(now, res);
+ }
+ }
+ }
+
+ /// In case a datagram arrives that we can only partially process, save any
+ /// part that we don't have keys for.
+ fn save_datagram(&mut self, cspace: CryptoSpace, d: Datagram, remaining: usize, now: Instant) {
+ let d = if remaining < d.len() {
+ Datagram::new(d.source(), d.destination(), &d[d.len() - remaining..])
+ } else {
+ d
+ };
+ self.saved_datagrams.save(cspace, d, now);
+ self.stats.borrow_mut().saved_datagrams += 1;
+ }
+
+ /// Perform any processing that we might have to do on packets prior to
+ /// attempting to remove protection.
+ fn preprocess(
+ &mut self,
+ packet: &PublicPacket,
+ dcid: Option<&ConnectionId>,
+ now: Instant,
+ ) -> Res<PreprocessResult> {
+ if dcid.map_or(false, |d| d != packet.dcid()) {
+ self.stats
+ .borrow_mut()
+ .pkt_dropped("Coalesced packet has different DCID");
+ return Ok(PreprocessResult::Next);
+ }
+
+ match (packet.packet_type(), &self.state, &self.role) {
+ (PacketType::Initial, State::Init, Role::Server) => {
+ if !packet.is_valid_initial() {
+ self.stats.borrow_mut().pkt_dropped("Invalid Initial");
+ return Ok(PreprocessResult::Next);
+ }
+ qinfo!(
+ [self],
+ "Received valid Initial packet with scid {:?} dcid {:?}",
+ packet.scid(),
+ packet.dcid()
+ );
+ self.set_state(State::WaitInitial);
+ self.loss_recovery.start_pacer(now);
+ self.crypto
+ .states
+ .init(self.quic_version, self.role, &packet.dcid());
+
+ // We need to make sure that we set this transport parameter.
+ // This has to happen prior to processing the packet so that
+ // the TLS handshake has all it needs.
+ if !self.retry_sent() {
+ self.tps.borrow_mut().local.set_bytes(
+ tparams::ORIGINAL_DESTINATION_CONNECTION_ID,
+ packet.dcid().to_vec(),
+ )
+ }
+ }
+ (PacketType::VersionNegotiation, State::WaitInitial, Role::Client) => {
+ match packet.supported_versions() {
+ Ok(versions) => {
+ if versions.is_empty()
+ || versions.contains(&self.quic_version.as_u32())
+ || packet.dcid() != self.odcid().unwrap()
+ || matches!(self.address_validation, AddressValidationInfo::Retry { .. })
+ {
+ // Ignore VersionNegotiation packets that contain the current version.
+ // Or don't have the right connection ID.
+ // Or are received after a Retry.
+ self.stats.borrow_mut().pkt_dropped("Invalid VN");
+ return Ok(PreprocessResult::End);
+ }
+
+ self.set_state(State::Closed(ConnectionError::Transport(
+ Error::VersionNegotiation,
+ )));
+ return Err(Error::VersionNegotiation);
+ }
+ Err(_) => {
+ self.stats.borrow_mut().pkt_dropped("Invalid VN");
+ return Ok(PreprocessResult::End);
+ }
+ }
+ }
+ (PacketType::Retry, State::WaitInitial, Role::Client) => {
+ self.handle_retry(packet)?;
+ return Ok(PreprocessResult::Next);
+ }
+ (PacketType::Handshake, State::WaitInitial, Role::Client)
+ | (PacketType::Short, State::WaitInitial, Role::Client) => {
+ // This packet can't be processed now, but it could be a sign
+ // that Initial packets were lost.
+ // Resend Initial CRYPTO frames immediately a few times just
+ // in case. As we don't have an RTT estimate yet, this helps
+ // when there is a short RTT and losses.
+ if dcid.is_none()
+ && self.is_valid_cid(packet.dcid())
+ && self.stats.borrow().saved_datagrams <= EXTRA_INITIALS
+ {
+ self.crypto.resend_unacked(PNSpace::Initial);
+ }
+ }
+ (PacketType::VersionNegotiation, ..)
+ | (PacketType::Retry, ..)
+ | (PacketType::OtherVersion, ..) => {
+ self.stats
+ .borrow_mut()
+ .pkt_dropped(format!("{:?}", packet.packet_type()));
+ return Ok(PreprocessResult::Next);
+ }
+ _ => {}
+ }
+
+ let res = match self.state {
+ State::Init => {
+ self.stats
+ .borrow_mut()
+ .pkt_dropped("Received while in Init state");
+ PreprocessResult::Next
+ }
+ State::WaitInitial => PreprocessResult::Continue,
+ State::Handshaking | State::Connected | State::Confirmed => {
+ if !self.is_valid_cid(packet.dcid()) {
+ self.stats
+ .borrow_mut()
+ .pkt_dropped(format!("Invalid DCID {:?}", packet.dcid()));
+ PreprocessResult::Next
+ } else {
+ if self.role == Role::Server && packet.packet_type() == PacketType::Handshake {
+ // Server has received a Handshake packet -> discard Initial keys and states
+ self.discard_keys(PNSpace::Initial, now);
+ }
+ PreprocessResult::Continue
+ }
+ }
+ State::Closing { .. } => {
+ // Don't bother processing the packet. Instead ask to get a
+ // new close frame.
+ self.state_signaling.send_close();
+ PreprocessResult::Next
+ }
+ State::Draining { .. } | State::Closed(..) => {
+ // Do nothing.
+ self.stats
+ .borrow_mut()
+ .pkt_dropped(format!("State {:?}", self.state));
+ PreprocessResult::Next
+ }
+ };
+ Ok(res)
+ }
+
+ /// Take a datagram as input. This reports an error if the packet was bad.
+ fn input(&mut self, d: Datagram, now: Instant) -> Res<()> {
+ let mut slc = &d[..];
+ let mut dcid = None;
+
+ qtrace!([self], "input {}", hex(&**d));
+
+ // Handle each packet in the datagram.
+ while !slc.is_empty() {
+ self.stats.borrow_mut().packets_rx += 1;
+ let (packet, remainder) =
+ match PublicPacket::decode(slc, self.cid_manager.borrow().as_decoder()) {
+ Ok((packet, remainder)) => (packet, remainder),
+ Err(e) => {
+ qinfo!([self], "Garbage packet: {}", e);
+ qtrace!([self], "Garbage packet contents: {}", hex(slc));
+ self.stats.borrow_mut().pkt_dropped("Garbage packet");
+ break;
+ }
+ };
+ match self.preprocess(&packet, dcid.as_ref(), now)? {
+ PreprocessResult::Continue => (),
+ PreprocessResult::Next => break,
+ PreprocessResult::End => return Ok(()),
+ }
+
+ qtrace!([self], "Received unverified packet {:?}", packet);
+
+ let pto = self.loss_recovery.pto_raw(PNSpace::ApplicationData);
+ match packet.decrypt(&mut self.crypto.states, now + pto) {
+ Ok(payload) => {
+ // TODO(ekr@rtfm.com): Have the server blow away the initial
+ // crypto state if this fails? Otherwise, we will get a panic
+ // on the assert for doesn't exist.
+ // OK, we have a valid packet.
+ self.idle_timeout.on_packet_received(now);
+ dump_packet(
+ self,
+ "-> RX",
+ payload.packet_type(),
+ payload.pn(),
+ &payload[..],
+ );
+ qlog::packet_received(&mut self.qlog, &packet, &payload);
+ let res = self.process_packet(&payload, now);
+ if res.is_err() && self.path.is_none() {
+ // We need to make a path for sending an error message.
+ // But this connection is going to be closed.
+ self.remote_initial_source_cid = Some(ConnectionId::from(packet.scid()));
+ self.initialize_path(d.destination(), d.source());
+ }
+ res?;
+ if self.state == State::WaitInitial {
+ self.start_handshake(&packet, &d)?;
+ }
+ self.process_migrations(&d)?;
+ }
+ Err(e) => {
+ match e {
+ Error::KeysPending(cspace) => {
+ // This packet can't be decrypted because we don't have the keys yet.
+ // Don't check this packet for a stateless reset, just return.
+ let remaining = slc.len();
+ self.save_datagram(cspace, d, remaining, now);
+ return Ok(());
+ }
+ Error::KeysExhausted => {
+ // Exhausting read keys is fatal.
+ return Err(e);
+ }
+ _ => (),
+ }
+ // Decryption failure, or not having keys is not fatal.
+ // If the state isn't available, or we can't decrypt the packet, drop
+ // the rest of the datagram on the floor, but don't generate an error.
+ self.check_stateless_reset(&d, dcid.is_none(), now)?;
+ self.stats.borrow_mut().pkt_dropped("Decryption failure");
+ qlog::packet_dropped(&mut self.qlog, &packet);
+ }
+ }
+ slc = remainder;
+ dcid = Some(ConnectionId::from(packet.dcid()));
+ }
+ self.check_stateless_reset(&d, dcid.is_none(), now)?;
+ Ok(())
+ }
+
+ fn process_packet(&mut self, packet: &DecryptedPacket, now: Instant) -> Res<()> {
+ // TODO(ekr@rtfm.com): Have the server blow away the initial
+ // crypto state if this fails? Otherwise, we will get a panic
+ // on the assert for doesn't exist.
+ // OK, we have a valid packet.
+
+ let space = PNSpace::from(packet.packet_type());
+ if self.acks.get_mut(space).unwrap().is_duplicate(packet.pn()) {
+ qdebug!([self], "Duplicate packet from {} pn={}", space, packet.pn());
+ self.stats.borrow_mut().dups_rx += 1;
+ return Ok(());
+ }
+
+ let mut ack_eliciting = false;
+ let mut d = Decoder::from(&packet[..]);
+ let mut consecutive_padding = 0;
+ while d.remaining() > 0 {
+ let mut f = Frame::decode(&mut d)?;
+
+ // Skip padding
+ while f == Frame::Padding && d.remaining() > 0 {
+ consecutive_padding += 1;
+ f = Frame::decode(&mut d)?;
+ }
+ if consecutive_padding > 0 {
+ qdebug!(
+ [self],
+ "PADDING frame repeated {} times",
+ consecutive_padding
+ );
+ consecutive_padding = 0;
+ }
+
+ ack_eliciting |= f.ack_eliciting();
+ let t = f.get_type();
+ let res = self.input_frame(packet.packet_type(), f, now);
+ self.capture_error(now, t, res)?;
+ }
+ self.acks
+ .get_mut(space)
+ .unwrap()
+ .set_received(now, packet.pn(), ack_eliciting);
+
+ Ok(())
+ }
+
+ fn initialize_path(&mut self, local_addr: SocketAddr, remote_addr: SocketAddr) {
+ debug_assert!(self.path.is_none());
+ self.path = Some(Path::new(
+ local_addr,
+ remote_addr,
+ self.local_initial_source_cid.clone(),
+ // Ideally we know what the peer wants us to use for the remote CID.
+ // But we will use our own guess if necessary.
+ self.remote_initial_source_cid
+ .as_ref()
+ .or_else(|| self.original_destination_cid.as_ref())
+ .unwrap()
+ .clone(),
+ ));
+ }
+
+ fn start_handshake(&mut self, packet: &PublicPacket, d: &Datagram) -> Res<()> {
+ qtrace!([self], "starting handshake");
+ debug_assert_eq!(packet.packet_type(), PacketType::Initial);
+ self.remote_initial_source_cid = Some(ConnectionId::from(packet.scid()));
+
+ if self.role == Role::Server {
+ // A server needs to accept the client's selected CID during the handshake.
+ self.valid_cids.push(ConnectionId::from(packet.dcid()));
+ self.original_destination_cid = Some(ConnectionId::from(packet.dcid()));
+ // Install a path.
+ self.initialize_path(d.destination(), d.source());
+
+ self.zero_rtt_state = match self.crypto.enable_0rtt(self.role) {
+ Ok(true) => {
+ qdebug!([self], "Accepted 0-RTT");
+ ZeroRttState::AcceptedServer
+ }
+ _ => ZeroRttState::Rejected,
+ };
+ } else {
+ qdebug!([self], "Changing to use Server CID={}", packet.scid());
+ let p = self
+ .path
+ .iter_mut()
+ .find(|p| p.received_on(&d))
+ .expect("should have a path for sending Initial");
+ p.set_remote_cid(packet.scid());
+ }
+
+ self.set_state(State::Handshaking);
+ Ok(())
+ }
+
+ fn process_migrations(&self, d: &Datagram) -> Res<()> {
+ if self.path.iter().any(|p| p.received_on(&d)) {
+ Ok(())
+ } else {
+ // Right now, we don't support any form of migration.
+ // So generate an error if a packet is received on a new path.
+ Err(Error::InvalidMigration)
+ }
+ }
+
+ fn output(&mut self, now: Instant) -> SendOption {
+ qtrace!([self], "output {:?}", now);
+ if let Some(mut path) = self.path.take() {
+ let res = match &self.state {
+ State::Init
+ | State::WaitInitial
+ | State::Handshaking
+ | State::Connected
+ | State::Confirmed => self.output_path(&mut path, now),
+ State::Closing { .. } | State::Draining { .. } | State::Closed(_) => {
+ if let Some(frame) = self.state_signaling.close_frame() {
+ self.output_close(&path, &frame)
+ } else {
+ Ok(SendOption::default())
+ }
+ }
+ };
+ let out = self.absorb_error(now, res).unwrap_or_default();
+ self.path = Some(path);
+ out
+ } else {
+ SendOption::default()
+ }
+ }
+
+ fn build_packet_header(
+ path: &Path,
+ cspace: CryptoSpace,
+ encoder: Encoder,
+ tx: &CryptoDxState,
+ address_validation: &AddressValidationInfo,
+ quic_version: QuicVersion,
+ grease_quic_bit: bool,
+ ) -> (PacketType, PacketBuilder) {
+ let pt = PacketType::from(cspace);
+ let mut builder = if pt == PacketType::Short {
+ qdebug!("Building Short dcid {}", path.remote_cid());
+ PacketBuilder::short(encoder, tx.key_phase(), path.remote_cid())
+ } else {
+ qdebug!(
+ "Building {:?} dcid {} scid {}",
+ pt,
+ path.remote_cid(),
+ path.local_cid(),
+ );
+
+ PacketBuilder::long(
+ encoder,
+ pt,
+ quic_version,
+ path.remote_cid(),
+ path.local_cid(),
+ )
+ };
+ builder.scramble(grease_quic_bit);
+ if pt == PacketType::Initial {
+ builder.initial_token(address_validation.token());
+ }
+
+ (pt, builder)
+ }
+
+ fn add_packet_number(
+ builder: &mut PacketBuilder,
+ tx: &CryptoDxState,
+ largest_acknowledged: Option<PacketNumber>,
+ ) -> PacketNumber {
+ // Get the packet number and work out how long it is.
+ let pn = tx.next_pn();
+ let unacked_range = if let Some(la) = largest_acknowledged {
+ // Double the range from this to the last acknowledged in this space.
+ (pn - la) << 1
+ } else {
+ pn + 1
+ };
+ // Count how many bytes in this range are non-zero.
+ let pn_len = mem::size_of::<PacketNumber>()
+ - usize::try_from(unacked_range.leading_zeros() / 8).unwrap();
+ // pn_len can't be zero (unacked_range is > 0)
+ // TODO(mt) also use `4*path CWND/path MTU` to set a minimum length.
+ builder.pn(pn, pn_len);
+ pn
+ }
+
+ fn can_grease_quic_bit(&self) -> bool {
+ let tph = self.tps.borrow();
+ if let Some(r) = &tph.remote {
+ r.get_empty(tparams::GREASE_QUIC_BIT)
+ } else if let Some(r) = &tph.remote_0rtt {
+ r.get_empty(tparams::GREASE_QUIC_BIT)
+ } else {
+ false
+ }
+ }
+
+ fn output_close(&mut self, path: &Path, frame: &Frame) -> Res<SendOption> {
+ let mut encoder = Encoder::with_capacity(path.mtu());
+ let grease_quic_bit = self.can_grease_quic_bit();
+ for space in PNSpace::iter() {
+ let (cspace, tx) = if let Some(crypto) = self.crypto.states.select_tx(*space) {
+ crypto
+ } else {
+ continue;
+ };
+
+ let (_, mut builder) = Self::build_packet_header(
+ path,
+ cspace,
+ encoder,
+ tx,
+ &AddressValidationInfo::None,
+ self.quic_version,
+ grease_quic_bit,
+ );
+ let _ = Self::add_packet_number(
+ &mut builder,
+ tx,
+ self.loss_recovery.largest_acknowledged_pn(*space),
+ );
+
+ // ConnectionError::Application is only allowed at 1RTT.
+ let sanitized = if *space == PNSpace::ApplicationData {
+ &frame
+ } else {
+ frame.sanitize_close()
+ };
+ if let Frame::ConnectionClose {
+ error_code,
+ frame_type,
+ reason_phrase,
+ } = sanitized
+ {
+ builder.encode_varint(sanitized.get_type());
+ builder.encode_varint(error_code.code());
+ if let CloseError::Transport(_) = error_code {
+ builder.encode_varint(*frame_type);
+ }
+ let reason_len = min(min(reason_phrase.len(), 256), builder.remaining() - 2);
+ builder.encode_vvec(&reason_phrase[..reason_len]);
+ } else {
+ unreachable!();
+ }
+
+ encoder = builder.build(tx)?;
+ }
+
+ Ok(SendOption::Yes(path.datagram(encoder)))
+ }
+
+ /// Write frames to the provided builder. Returns a list of tokens used for
+ /// tracking loss or acknowledgment, whether any frame was ACK eliciting, and
+ /// whether the packet was padded.
+ fn write_frames(
+ &mut self,
+ space: PNSpace,
+ profile: &SendProfile,
+ builder: &mut PacketBuilder,
+ mut pad: bool,
+ now: Instant,
+ ) -> (Vec<RecoveryToken>, bool, bool) {
+ let mut tokens = Vec::new();
+ let stats = &mut self.stats.borrow_mut().frame_tx;
+
+ let ack_token = self.acks.write_frame(space, now, builder, stats);
+
+ if profile.ack_only(space) {
+ // If we are CC limited we can only send acks!
+ if let Some(t) = ack_token {
+ tokens.push(t);
+ }
+ return (tokens, false, false);
+ }
+
+ if space == PNSpace::ApplicationData && self.role == Role::Server {
+ if let Some(t) = self.state_signaling.write_done(builder) {
+ tokens.push(t);
+ stats.handshake_done += 1;
+ }
+ }
+
+ if let Some(t) = self.crypto.streams.write_frame(space, builder) {
+ tokens.push(t);
+ stats.crypto += 1;
+ }
+
+ if space == PNSpace::ApplicationData {
+ self.flow_mgr
+ .borrow_mut()
+ .write_frames(builder, &mut tokens, stats);
+
+ self.send_streams.write_frames(builder, &mut tokens, stats);
+ self.new_token.write_frames(builder, &mut tokens, stats);
+ }
+
+ // Anything - other than ACK - that registered a token wants an acknowledgment.
+ let ack_eliciting = !tokens.is_empty()
+ || if profile.should_probe(space) {
+ // Nothing ack-eliciting and we need to probe; send PING.
+ debug_assert_ne!(builder.remaining(), 0);
+ builder.encode_varint(crate::frame::FRAME_TYPE_PING);
+ stats.ping += 1;
+ stats.all += 1;
+ true
+ } else {
+ false
+ };
+
+ // Add padding. Only pad 1-RTT packets so that we don't prevent coalescing.
+ // And avoid padding packets that otherwise only contain ACK because adding PADDING
+ // causes those packets to consume congestion window, which is not tracked (yet).
+ pad &= ack_eliciting && space == PNSpace::ApplicationData;
+ if pad {
+ builder.pad();
+ stats.padding += 1;
+ stats.all += 1;
+ }
+
+ if let Some(t) = ack_token {
+ tokens.push(t);
+ }
+ stats.all += tokens.len();
+ (tokens, ack_eliciting, pad)
+ }
+
+ /// Build a datagram, possibly from multiple packets (for different PN
+ /// spaces) and each containing 1+ frames.
+ fn output_path(&mut self, path: &mut Path, now: Instant) -> Res<SendOption> {
+ let mut initial_sent = None;
+ let mut needs_padding = false;
+ let grease_quic_bit = self.can_grease_quic_bit();
+
+ // Determine how we are sending packets (PTO, etc..).
+ let profile = self.loss_recovery.send_profile(now, path.mtu());
+ qdebug!([self], "output_path send_profile {:?}", profile);
+
+ // Frames for different epochs must go in different packets, but then these
+ // packets can go in a single datagram
+ let mut encoder = Encoder::with_capacity(profile.limit());
+ for space in PNSpace::iter() {
+ // Ensure we have tx crypto state for this epoch, or skip it.
+ let (cspace, tx) = if let Some(crypto) = self.crypto.states.select_tx(*space) {
+ crypto
+ } else {
+ continue;
+ };
+
+ let header_start = encoder.len();
+ let (pt, mut builder) = Self::build_packet_header(
+ path,
+ cspace,
+ encoder,
+ tx,
+ &self.address_validation,
+ self.quic_version,
+ grease_quic_bit,
+ );
+ let pn = Self::add_packet_number(
+ &mut builder,
+ tx,
+ self.loss_recovery.largest_acknowledged_pn(*space),
+ );
+ let payload_start = builder.len();
+
+ // Work out if we have space left.
+ let aead_expansion = tx.expansion();
+ if builder.len() + aead_expansion > profile.limit() {
+ // No space for a packet of this type.
+ encoder = builder.abort();
+ continue;
+ }
+
+ // Add frames to the packet.
+ let limit = profile.limit() - aead_expansion;
+ builder.set_limit(limit);
+ let (tokens, ack_eliciting, padded) =
+ self.write_frames(*space, &profile, &mut builder, needs_padding, now);
+ if builder.packet_empty() {
+ // Nothing to include in this packet.
+ encoder = builder.abort();
+ continue;
+ }
+
+ dump_packet(self, "TX ->", pt, pn, &builder[payload_start..]);
+ qlog::packet_sent(
+ &mut self.qlog,
+ pt,
+ pn,
+ builder.len() - header_start + aead_expansion,
+ &builder[payload_start..],
+ );
+
+ self.stats.borrow_mut().packets_tx += 1;
+ encoder = builder.build(self.crypto.states.tx(cspace).unwrap())?;
+ debug_assert!(encoder.len() <= path.mtu());
+ self.crypto.states.auto_update()?;
+
+ if ack_eliciting {
+ self.idle_timeout.on_packet_sent(now);
+ }
+ let sent = SentPacket::new(
+ pt,
+ pn,
+ now,
+ ack_eliciting,
+ tokens,
+ encoder.len() - header_start,
+ );
+ if padded {
+ needs_padding = false;
+ self.loss_recovery.on_packet_sent(sent);
+ } else if pt == PacketType::Initial && (self.role == Role::Client || ack_eliciting) {
+ // Packets containing Initial packets might need padding, and we want to
+ // track that padding along with the Initial packet. So defer tracking.
+ initial_sent = Some(sent);
+ needs_padding = true;
+ } else {
+ if pt == PacketType::Handshake && self.role == Role::Client {
+ needs_padding = false;
+ }
+ self.loss_recovery.on_packet_sent(sent);
+ }
+
+ if *space == PNSpace::Handshake {
+ if self.role == Role::Client {
+ // Client can send Handshake packets -> discard Initial keys and states
+ self.discard_keys(PNSpace::Initial, now);
+ } else if self.state == State::Confirmed {
+ // We could discard handshake keys in set_state, but wait until after sending an ACK.
+ self.discard_keys(PNSpace::Handshake, now);
+ }
+ }
+ }
+
+ if encoder.is_empty() {
+ Ok(SendOption::No(profile.paced()))
+ } else {
+ // Perform additional padding for Initial packets as necessary.
+ let mut packets: Vec<u8> = encoder.into();
+ if let Some(mut initial) = initial_sent.take() {
+ if needs_padding {
+ qdebug!([self], "pad Initial to path MTU {}", path.mtu());
+ initial.size += path.mtu() - packets.len();
+ packets.resize(path.mtu(), 0);
+ }
+ self.loss_recovery.on_packet_sent(initial);
+ }
+ Ok(SendOption::Yes(path.datagram(packets)))
+ }
+ }
+
+ pub fn initiate_key_update(&mut self) -> Res<()> {
+ if self.state == State::Confirmed {
+ let la = self
+ .loss_recovery
+ .largest_acknowledged_pn(PNSpace::ApplicationData);
+ qinfo!([self], "Initiating key update");
+ self.crypto.states.initiate_key_update(la)
+ } else {
+ Err(Error::KeyUpdateBlocked)
+ }
+ }
+
+ #[cfg(test)]
+ pub fn get_epochs(&self) -> (Option<usize>, Option<usize>) {
+ self.crypto.states.get_epochs()
+ }
+
+ fn client_start(&mut self, now: Instant) -> Res<()> {
+ qinfo!([self], "client_start");
+ debug_assert_eq!(self.role, Role::Client);
+ qlog::client_connection_started(&mut self.qlog, self.path.as_ref().unwrap());
+ self.loss_recovery.start_pacer(now);
+
+ self.handshake(now, PNSpace::Initial, None)?;
+ self.set_state(State::WaitInitial);
+ self.zero_rtt_state = if self.crypto.enable_0rtt(self.role)? {
+ qdebug!([self], "Enabled 0-RTT");
+ ZeroRttState::Sending
+ } else {
+ ZeroRttState::Init
+ };
+ Ok(())
+ }
+
+ fn get_closing_period_time(&self, now: Instant) -> Instant {
+ // Spec says close time should be at least PTO times 3.
+ now + (self.loss_recovery.pto_raw(PNSpace::ApplicationData) * 3)
+ }
+
+ /// Close the connection.
+ pub fn close(&mut self, now: Instant, app_error: AppError, msg: impl AsRef<str>) {
+ let error = ConnectionError::Application(app_error);
+ let timeout = self.get_closing_period_time(now);
+ self.state_signaling.close(error.clone(), 0, msg);
+ self.set_state(State::Closing { error, timeout });
+ }
+
+ fn set_initial_limits(&mut self) {
+ let tps = self.tps.borrow();
+ let remote = tps.remote();
+ self.indexes.remote_max_stream_bidi =
+ StreamIndex::new(remote.get_integer(tparams::INITIAL_MAX_STREAMS_BIDI));
+ self.indexes.remote_max_stream_uni =
+ StreamIndex::new(remote.get_integer(tparams::INITIAL_MAX_STREAMS_UNI));
+ self.flow_mgr
+ .borrow_mut()
+ .conn_increase_max_credit(remote.get_integer(tparams::INITIAL_MAX_DATA));
+
+ let peer_timeout = remote.get_integer(tparams::IDLE_TIMEOUT);
+ if peer_timeout > 0 {
+ self.idle_timeout
+ .set_peer_timeout(Duration::from_millis(peer_timeout));
+ }
+ }
+
+ /// Process the final set of transport parameters.
+ fn process_tps(&mut self) -> Res<()> {
+ self.validate_cids()?;
+ {
+ let tps = self.tps.borrow();
+ if let Some(token) = tps
+ .remote
+ .as_ref()
+ .unwrap()
+ .get_bytes(tparams::STATELESS_RESET_TOKEN)
+ {
+ let reset_token = <[u8; 16]>::try_from(token).unwrap().to_owned();
+ self.path.as_mut().unwrap().set_reset_token(reset_token);
+ }
+ let mad = Duration::from_millis(
+ tps.remote
+ .as_ref()
+ .unwrap()
+ .get_integer(tparams::MAX_ACK_DELAY),
+ );
+ self.loss_recovery.set_peer_max_ack_delay(mad);
+ }
+ self.set_initial_limits();
+ qlog::connection_tparams_set(&mut self.qlog, &*self.tps.borrow());
+ Ok(())
+ }
+
+ fn validate_cids(&mut self) -> Res<()> {
+ match self.quic_version {
+ QuicVersion::Draft27 => self.validate_cids_draft_27(),
+ _ => self.validate_cids_draft_28_plus(),
+ }
+ }
+
+ fn validate_cids_draft_27(&mut self) -> Res<()> {
+ if let AddressValidationInfo::Retry { token, .. } = &self.address_validation {
+ debug_assert!(!token.is_empty());
+ let tph = self.tps.borrow();
+ let tp = tph
+ .remote
+ .as_ref()
+ .unwrap()
+ .get_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID);
+ if self
+ .original_destination_cid
+ .as_ref()
+ .map(ConnectionId::as_cid_ref)
+ != tp.map(ConnectionIdRef::from)
+ {
+ return Err(Error::InvalidRetry);
+ }
+ }
+ Ok(())
+ }
+
+ fn validate_cids_draft_28_plus(&mut self) -> Res<()> {
+ let tph = self.tps.borrow();
+ let remote_tps = tph.remote.as_ref().unwrap();
+
+ let tp = remote_tps.get_bytes(tparams::INITIAL_SOURCE_CONNECTION_ID);
+ if self
+ .remote_initial_source_cid
+ .as_ref()
+ .map(ConnectionId::as_cid_ref)
+ != tp.map(ConnectionIdRef::from)
+ {
+ qwarn!(
+ [self],
+ "ISCID test failed: self cid {:?} != tp cid {:?}",
+ self.remote_initial_source_cid,
+ tp.map(hex),
+ );
+ return Err(Error::ProtocolViolation);
+ }
+
+ if self.role == Role::Client {
+ let tp = remote_tps.get_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID);
+ if self
+ .original_destination_cid
+ .as_ref()
+ .map(ConnectionId::as_cid_ref)
+ != tp.map(ConnectionIdRef::from)
+ {
+ qwarn!(
+ [self],
+ "ODCID test failed: self cid {:?} != tp cid {:?}",
+ self.original_destination_cid,
+ tp.map(hex),
+ );
+ return Err(Error::ProtocolViolation);
+ }
+
+ let tp = remote_tps.get_bytes(tparams::RETRY_SOURCE_CONNECTION_ID);
+ let expected = if let AddressValidationInfo::Retry {
+ retry_source_cid, ..
+ } = &self.address_validation
+ {
+ Some(retry_source_cid.as_cid_ref())
+ } else {
+ None
+ };
+ if expected != tp.map(ConnectionIdRef::from) {
+ qwarn!(
+ [self],
+ "RSCID test failed. self cid {:?} != tp cid {:?}",
+ expected,
+ tp.map(hex),
+ );
+ return Err(Error::ProtocolViolation);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn handshake(&mut self, now: Instant, space: PNSpace, data: Option<&[u8]>) -> Res<()> {
+ qtrace!([self], "Handshake space={} data={:0x?}", space, data);
+
+ let try_update = data.is_some();
+ match self.crypto.handshake(now, space, data)? {
+ HandshakeState::Authenticated(_) | HandshakeState::InProgress => (),
+ HandshakeState::AuthenticationPending => self.events.authentication_needed(),
+ HandshakeState::Complete(_) => {
+ if !self.state.connected() {
+ self.set_connected(now)?;
+ }
+ }
+ _ => {
+ unreachable!("Crypto state should not be new or failed after successful handshake")
+ }
+ }
+
+ // There is a chance that this could be called less often, but getting the
+ // conditions right is a little tricky, so call it on every CRYPTO frame.
+ if try_update {
+ // We have transport parameters, it's go time.
+ if self.tps.borrow().remote.is_some() {
+ self.set_initial_limits();
+ }
+ if self.crypto.install_keys(self.role)? {
+ self.saved_datagrams.make_available(CryptoSpace::Handshake);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn handle_max_data(&mut self, maximum_data: u64) {
+ let conn_was_blocked = self.flow_mgr.borrow().conn_credit_avail() == 0;
+ let conn_credit_increased = self
+ .flow_mgr
+ .borrow_mut()
+ .conn_increase_max_credit(maximum_data);
+
+ if conn_was_blocked && conn_credit_increased {
+ for (id, ss) in &mut self.send_streams {
+ if ss.avail() > 0 {
+ // These may not actually all be writable if one
+ // uses up all the conn credit. Not our fault.
+ self.events.send_stream_writable(*id)
+ }
+ }
+ }
+ }
+
+ fn input_frame(&mut self, ptype: PacketType, frame: Frame, now: Instant) -> Res<()> {
+ if !frame.is_allowed(ptype) {
+ qinfo!("frame not allowed: {:?} {:?}", frame, ptype);
+ return Err(Error::ProtocolViolation);
+ }
+ self.stats.borrow_mut().frame_rx.all += 1;
+ let space = PNSpace::from(ptype);
+ match frame {
+ Frame::Padding => {
+ // Note: This counts contiguous padding as a single frame.
+ self.stats.borrow_mut().frame_rx.padding += 1;
+ }
+ Frame::Ping => {
+ // If we get a PING and there are outstanding CRYPTO frames,
+ // prepare to resend them.
+ self.stats.borrow_mut().frame_rx.ping += 1;
+ self.crypto.resend_unacked(space);
+ }
+ Frame::Ack {
+ largest_acknowledged,
+ ack_delay,
+ first_ack_range,
+ ack_ranges,
+ } => {
+ self.handle_ack(
+ space,
+ largest_acknowledged,
+ ack_delay,
+ first_ack_range,
+ ack_ranges,
+ now,
+ )?;
+ }
+ Frame::ResetStream {
+ stream_id,
+ application_error_code,
+ ..
+ } => {
+ // TODO(agrover@mozilla.com): use final_size for connection MaxData calc
+ self.stats.borrow_mut().frame_rx.reset_stream += 1;
+ if let (_, Some(rs)) = self.obtain_stream(stream_id)? {
+ rs.reset(application_error_code);
+ }
+ }
+ Frame::StopSending {
+ stream_id,
+ application_error_code,
+ } => {
+ self.stats.borrow_mut().frame_rx.stop_sending += 1;
+ self.events
+ .send_stream_stop_sending(stream_id, application_error_code);
+ if let (Some(ss), _) = self.obtain_stream(stream_id)? {
+ ss.reset(application_error_code);
+ }
+ }
+ Frame::Crypto { offset, data } => {
+ qtrace!(
+ [self],
+ "Crypto frame on space={} offset={}, data={:0x?}",
+ space,
+ offset,
+ &data
+ );
+ self.stats.borrow_mut().frame_rx.crypto += 1;
+ self.crypto.streams.inbound_frame(space, offset, data);
+ if self.crypto.streams.data_ready(space) {
+ let mut buf = Vec::new();
+ let read = self.crypto.streams.read_to_end(space, &mut buf);
+ qdebug!("Read {} bytes", read);
+ self.handshake(now, space, Some(&buf))?;
+ self.create_resumption_token(now);
+ } else {
+ // If we get a useless CRYPTO frame send outstanding CRYPTO frames again.
+ self.crypto.resend_unacked(space);
+ }
+ }
+ Frame::NewToken { token } => {
+ self.stats.borrow_mut().frame_rx.new_token += 1;
+ self.new_token.save_token(token.to_vec());
+ self.create_resumption_token(now);
+ }
+ Frame::Stream {
+ fin,
+ stream_id,
+ offset,
+ data,
+ ..
+ } => {
+ self.stats.borrow_mut().frame_rx.stream += 1;
+ if let (_, Some(rs)) = self.obtain_stream(stream_id)? {
+ rs.inbound_stream_frame(fin, offset, data)?;
+ }
+ }
+ Frame::MaxData { maximum_data } => {
+ self.stats.borrow_mut().frame_rx.max_data += 1;
+ self.handle_max_data(maximum_data);
+ }
+ Frame::MaxStreamData {
+ stream_id,
+ maximum_stream_data,
+ } => {
+ self.stats.borrow_mut().frame_rx.max_stream_data += 1;
+ if let (Some(ss), _) = self.obtain_stream(stream_id)? {
+ ss.set_max_stream_data(maximum_stream_data);
+ }
+ }
+ Frame::MaxStreams {
+ stream_type,
+ maximum_streams,
+ } => {
+ self.stats.borrow_mut().frame_rx.max_streams += 1;
+ let remote_max = match stream_type {
+ StreamType::BiDi => &mut self.indexes.remote_max_stream_bidi,
+ StreamType::UniDi => &mut self.indexes.remote_max_stream_uni,
+ };
+
+ if maximum_streams > *remote_max {
+ *remote_max = maximum_streams;
+ self.events.send_stream_creatable(stream_type);
+ }
+ }
+ Frame::DataBlocked { data_limit } => {
+ // Should never happen since we set data limit to max
+ qwarn!(
+ [self],
+ "Received DataBlocked with data limit {}",
+ data_limit
+ );
+ self.stats.borrow_mut().frame_rx.data_blocked += 1;
+ // But if it does, open it up all the way
+ self.flow_mgr.borrow_mut().max_data(LOCAL_MAX_DATA);
+ }
+ Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit,
+ } => {
+ self.stats.borrow_mut().frame_rx.stream_data_blocked += 1;
+ // Terminate connection with STREAM_STATE_ERROR if send-only
+ // stream (-transport 19.13)
+ if stream_id.is_send_only(self.role()) {
+ return Err(Error::StreamStateError);
+ }
+
+ if let (_, Some(rs)) = self.obtain_stream(stream_id)? {
+ if let Some(msd) = rs.max_stream_data() {
+ qinfo!(
+ [self],
+ "Got StreamDataBlocked(id {} MSD {}); curr MSD {}",
+ stream_id.as_u64(),
+ stream_data_limit,
+ msd
+ );
+ if stream_data_limit != msd {
+ self.flow_mgr.borrow_mut().max_stream_data(stream_id, msd)
+ }
+ }
+ }
+ }
+ Frame::StreamsBlocked { stream_type, .. } => {
+ self.stats.borrow_mut().frame_rx.streams_blocked += 1;
+ let local_max = match stream_type {
+ StreamType::BiDi => &mut self.indexes.local_max_stream_bidi,
+ StreamType::UniDi => &mut self.indexes.local_max_stream_uni,
+ };
+
+ self.flow_mgr
+ .borrow_mut()
+ .max_streams(*local_max, stream_type)
+ }
+ Frame::NewConnectionId {
+ sequence_number,
+ connection_id,
+ stateless_reset_token,
+ ..
+ } => {
+ self.stats.borrow_mut().frame_rx.new_connection_id += 1;
+ let cid = ConnectionId::from(connection_id);
+ let srt = stateless_reset_token.to_owned();
+ self.connection_ids.insert(sequence_number, (cid, srt));
+ }
+ Frame::RetireConnectionId { sequence_number } => {
+ self.stats.borrow_mut().frame_rx.retire_connection_id += 1;
+ self.connection_ids.remove(&sequence_number);
+ }
+ Frame::PathChallenge { data } => {
+ self.stats.borrow_mut().frame_rx.path_challenge += 1;
+ self.flow_mgr.borrow_mut().path_response(data);
+ }
+ Frame::PathResponse { .. } => {
+ // Should never see this, we don't support migration atm and
+ // do not send path challenges
+ qwarn!([self], "Received Path Response");
+ self.stats.borrow_mut().frame_rx.path_response += 1;
+ }
+ Frame::ConnectionClose {
+ error_code,
+ frame_type,
+ reason_phrase,
+ } => {
+ self.stats.borrow_mut().frame_rx.connection_close += 1;
+ let reason_phrase = String::from_utf8_lossy(&reason_phrase);
+ qinfo!(
+ [self],
+ "ConnectionClose received. Error code: {:?} frame type {:x} reason {}",
+ error_code,
+ frame_type,
+ reason_phrase
+ );
+ let (detail, frame_type) = if let CloseError::Application(_) = error_code {
+ // Use a transport error here because we want to send
+ // NO_ERROR in this case.
+ (
+ Error::PeerApplicationError(error_code.code()),
+ FRAME_TYPE_CONNECTION_CLOSE_APPLICATION,
+ )
+ } else {
+ (
+ Error::PeerError(error_code.code()),
+ FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT,
+ )
+ };
+ let error = ConnectionError::Transport(detail);
+ self.state_signaling.drain(error.clone(), frame_type, "");
+ self.set_state(State::Draining {
+ error,
+ timeout: self.get_closing_period_time(now),
+ });
+ }
+ Frame::HandshakeDone => {
+ self.stats.borrow_mut().frame_rx.handshake_done += 1;
+ if self.role == Role::Server || !self.state.connected() {
+ return Err(Error::ProtocolViolation);
+ }
+ self.set_state(State::Confirmed);
+ self.discard_keys(PNSpace::Handshake, now);
+ }
+ };
+
+ Ok(())
+ }
+
+ /// Given a set of `SentPacket` instances, ensure that the source of the packet
+ /// is told that they are lost. This gives the frame generation code a chance
+ /// to retransmit the frame as needed.
+ fn handle_lost_packets(&mut self, lost_packets: &[SentPacket]) {
+ for lost in lost_packets {
+ for token in &lost.tokens {
+ qdebug!([self], "Lost: {:?}", token);
+ match token {
+ RecoveryToken::Ack(_) => {}
+ RecoveryToken::Stream(st) => self.send_streams.lost(&st),
+ RecoveryToken::Crypto(ct) => self.crypto.lost(&ct),
+ RecoveryToken::Flow(ft) => self.flow_mgr.borrow_mut().lost(
+ &ft,
+ &mut self.send_streams,
+ &mut self.recv_streams,
+ &mut self.indexes,
+ ),
+ RecoveryToken::HandshakeDone => self.state_signaling.handshake_done(),
+ RecoveryToken::NewToken(seqno) => self.new_token.lost(*seqno),
+ }
+ }
+ }
+ }
+
+ fn decode_ack_delay(&self, v: u64) -> Duration {
+ // If we have remote transport parameters, use them.
+ // Otherwise, ack delay should be zero (because it's the handshake).
+ if let Some(r) = self.tps.borrow().remote.as_ref() {
+ let exponent = u32::try_from(r.get_integer(tparams::ACK_DELAY_EXPONENT)).unwrap();
+ Duration::from_micros(v.checked_shl(exponent).unwrap_or(u64::MAX))
+ } else {
+ Duration::new(0, 0)
+ }
+ }
+
+ fn handle_ack(
+ &mut self,
+ space: PNSpace,
+ largest_acknowledged: u64,
+ ack_delay: u64,
+ first_ack_range: u64,
+ ack_ranges: Vec<AckRange>,
+ now: Instant,
+ ) -> Res<()> {
+ qinfo!(
+ [self],
+ "Rx ACK space={}, largest_acked={}, first_ack_range={}, ranges={:?}",
+ space,
+ largest_acknowledged,
+ first_ack_range,
+ ack_ranges
+ );
+
+ let acked_ranges =
+ Frame::decode_ack_frame(largest_acknowledged, first_ack_range, &ack_ranges)?;
+ let (acked_packets, lost_packets) = self.loss_recovery.on_ack_received(
+ space,
+ largest_acknowledged,
+ acked_ranges,
+ self.decode_ack_delay(ack_delay),
+ now,
+ );
+ for acked in acked_packets {
+ for token in &acked.tokens {
+ match token {
+ RecoveryToken::Ack(at) => self.acks.acked(at),
+ RecoveryToken::Stream(st) => self.send_streams.acked(st),
+ RecoveryToken::Crypto(ct) => self.crypto.acked(ct),
+ RecoveryToken::Flow(ft) => {
+ self.flow_mgr.borrow_mut().acked(ft, &mut self.send_streams)
+ }
+ RecoveryToken::HandshakeDone => (),
+ RecoveryToken::NewToken(seqno) => self.new_token.acked(*seqno),
+ }
+ }
+ }
+ self.handle_lost_packets(&lost_packets);
+ qlog::packets_lost(&mut self.qlog, &lost_packets);
+ let stats = &mut self.stats.borrow_mut().frame_rx;
+ stats.ack += 1;
+ stats.largest_acknowledged = max(stats.largest_acknowledged, largest_acknowledged);
+ Ok(())
+ }
+
+ /// When the server rejects 0-RTT we need to drop a bunch of stuff.
+ fn client_0rtt_rejected(&mut self) {
+ if !matches!(self.zero_rtt_state, ZeroRttState::Sending) {
+ return;
+ }
+ qdebug!([self], "0-RTT rejected");
+
+ // Tell 0-RTT packets that they were "lost".
+ let dropped = self.loss_recovery.drop_0rtt();
+ self.handle_lost_packets(&dropped);
+
+ self.send_streams.clear();
+ self.recv_streams.clear();
+ self.indexes = StreamIndexes::new();
+ self.crypto.states.discard_0rtt_keys();
+ self.events.client_0rtt_rejected();
+ }
+
+ fn set_connected(&mut self, now: Instant) -> Res<()> {
+ qinfo!([self], "TLS connection complete");
+ if self.crypto.tls.info().map(SecretAgentInfo::alpn).is_none() {
+ qwarn!([self], "No ALPN. Closing connection.");
+ // 120 = no_application_protocol
+ return Err(Error::CryptoAlert(120));
+ }
+ if self.role == Role::Server {
+ // Remove the randomized client CID from the list of acceptable CIDs.
+ debug_assert_eq!(1, self.valid_cids.len());
+ self.valid_cids.clear();
+ // Generate a qlog event that the server connection started.
+ qlog::server_connection_started(&mut self.qlog, self.path.as_ref().unwrap());
+ } else {
+ self.zero_rtt_state = if self.crypto.tls.info().unwrap().early_data_accepted() {
+ ZeroRttState::AcceptedClient
+ } else {
+ self.client_0rtt_rejected();
+ ZeroRttState::Rejected
+ };
+ }
+
+ // Setting application keys has to occur after 0-RTT rejection.
+ let pto = self.loss_recovery.pto_raw(PNSpace::ApplicationData);
+ self.crypto.install_application_keys(now + pto)?;
+ self.process_tps()?;
+ self.set_state(State::Connected);
+ self.create_resumption_token(now);
+ self.saved_datagrams
+ .make_available(CryptoSpace::ApplicationData);
+ self.stats.borrow_mut().resumed = self.crypto.tls.info().unwrap().resumed();
+ if self.role == Role::Server {
+ self.state_signaling.handshake_done();
+ self.set_state(State::Confirmed);
+ }
+ qinfo!([self], "Connection established");
+ Ok(())
+ }
+
+ fn set_state(&mut self, state: State) {
+ if state > self.state {
+ qinfo!([self], "State change from {:?} -> {:?}", self.state, state);
+ self.state = state.clone();
+ if self.state.closed() {
+ self.send_streams.clear();
+ self.recv_streams.clear();
+ }
+ self.events.connection_state_change(state);
+ qlog::connection_state_updated(&mut self.qlog, &self.state)
+ } else if mem::discriminant(&state) != mem::discriminant(&self.state) {
+ // Only tolerate a regression in state if the new state is closing
+ // and the connection is already closed.
+ debug_assert!(matches!(state, State::Closing { .. } | State::Draining { .. }));
+ debug_assert!(self.state.closed());
+ }
+ }
+
+ fn cleanup_streams(&mut self) {
+ self.send_streams.clear_terminal();
+ let recv_to_remove = self
+ .recv_streams
+ .iter()
+ .filter_map(|(id, stream)| {
+ // Remove all streams for which the receiving is done (or aborted).
+ // But only if they are unidirectional, or we have finished sending.
+ if stream.is_terminal() && (id.is_uni() || !self.send_streams.exists(*id)) {
+ Some(*id)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let mut removed_bidi = 0;
+ let mut removed_uni = 0;
+ for id in &recv_to_remove {
+ self.recv_streams.remove(&id);
+ if id.is_remote_initiated(self.role()) {
+ if id.is_bidi() {
+ removed_bidi += 1;
+ } else {
+ removed_uni += 1;
+ }
+ }
+ }
+
+ // Send max_streams updates if we removed remote-initiated recv streams.
+ if removed_bidi > 0 {
+ self.indexes.local_max_stream_bidi += removed_bidi;
+ self.flow_mgr
+ .borrow_mut()
+ .max_streams(self.indexes.local_max_stream_bidi, StreamType::BiDi)
+ }
+ if removed_uni > 0 {
+ self.indexes.local_max_stream_uni += removed_uni;
+ self.flow_mgr
+ .borrow_mut()
+ .max_streams(self.indexes.local_max_stream_uni, StreamType::UniDi)
+ }
+ }
+
+ /// Get or make a stream, and implicitly open additional streams as
+ /// indicated by its stream id.
+ fn obtain_stream(
+ &mut self,
+ stream_id: StreamId,
+ ) -> Res<(Option<&mut SendStream>, Option<&mut RecvStream>)> {
+ if !self.state.connected()
+ && !matches!(
+ (&self.state, &self.zero_rtt_state),
+ (State::Handshaking, ZeroRttState::AcceptedServer)
+ )
+ {
+ return Err(Error::ConnectionState);
+ }
+
+ // May require creating new stream(s)
+ if stream_id.is_remote_initiated(self.role()) {
+ let next_stream_idx = if stream_id.is_bidi() {
+ &mut self.indexes.local_next_stream_bidi
+ } else {
+ &mut self.indexes.local_next_stream_uni
+ };
+ let stream_idx: StreamIndex = stream_id.into();
+
+ if stream_idx >= *next_stream_idx {
+ let recv_initial_max_stream_data = if stream_id.is_bidi() {
+ if stream_idx > self.indexes.local_max_stream_bidi {
+ qwarn!(
+ [self],
+ "remote bidi stream create blocked, next={:?} max={:?}",
+ stream_idx,
+ self.indexes.local_max_stream_bidi
+ );
+ return Err(Error::StreamLimitError);
+ }
+ // From the local perspective, this is a remote- originated BiDi stream. From
+ // the remote perspective, this is a local-originated BiDi stream. Therefore,
+ // look at the local transport parameters for the
+ // INITIAL_MAX_STREAM_DATA_BIDI_REMOTE value to decide how much this endpoint
+ // will allow its peer to send.
+ self.tps
+ .borrow()
+ .local
+ .get_integer(tparams::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE)
+ } else {
+ if stream_idx > self.indexes.local_max_stream_uni {
+ qwarn!(
+ [self],
+ "remote uni stream create blocked, next={:?} max={:?}",
+ stream_idx,
+ self.indexes.local_max_stream_uni
+ );
+ return Err(Error::StreamLimitError);
+ }
+ self.tps
+ .borrow()
+ .local
+ .get_integer(tparams::INITIAL_MAX_STREAM_DATA_UNI)
+ };
+
+ loop {
+ let next_stream_id =
+ next_stream_idx.to_stream_id(stream_id.stream_type(), stream_id.role());
+ self.events.new_stream(next_stream_id);
+
+ self.recv_streams.insert(
+ next_stream_id,
+ RecvStream::new(
+ next_stream_id,
+ recv_initial_max_stream_data,
+ self.flow_mgr.clone(),
+ self.events.clone(),
+ ),
+ );
+
+ if next_stream_id.is_bidi() {
+ // From the local perspective, this is a remote- originated BiDi stream.
+ // From the remote perspective, this is a local-originated BiDi stream.
+ // Therefore, look at the remote's transport parameters for the
+ // INITIAL_MAX_STREAM_DATA_BIDI_LOCAL value to decide how much this endpoint
+ // is allowed to send its peer.
+ let send_initial_max_stream_data = self
+ .tps
+ .borrow()
+ .remote()
+ .get_integer(tparams::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
+ self.send_streams.insert(
+ next_stream_id,
+ SendStream::new(
+ next_stream_id,
+ send_initial_max_stream_data,
+ self.flow_mgr.clone(),
+ self.events.clone(),
+ ),
+ );
+ }
+
+ *next_stream_idx += 1;
+ if *next_stream_idx > stream_idx {
+ break;
+ }
+ }
+ }
+ }
+
+ Ok((
+ self.send_streams.get_mut(stream_id).ok(),
+ self.recv_streams.get_mut(&stream_id),
+ ))
+ }
+
+ /// Create a stream.
+ /// Returns new stream id
+ /// # Errors
+ /// `ConnectionState` if the connecton stat does not allow to create streams.
+ /// `StreamLimitError` if we are limiied by server's stream concurence.
+ pub fn stream_create(&mut self, st: StreamType) -> Res<u64> {
+ // Can't make streams while closing, otherwise rely on the stream limits.
+ match self.state {
+ State::Closing { .. } | State::Draining { .. } | State::Closed { .. } => {
+ return Err(Error::ConnectionState);
+ }
+ State::WaitInitial | State::Handshaking => {
+ if self.role == Role::Client && self.zero_rtt_state != ZeroRttState::Sending {
+ return Err(Error::ConnectionState);
+ }
+ }
+ // In all other states, trust that the stream limits are correct.
+ _ => (),
+ }
+
+ Ok(match st {
+ StreamType::UniDi => {
+ if self.indexes.remote_next_stream_uni >= self.indexes.remote_max_stream_uni {
+ self.flow_mgr
+ .borrow_mut()
+ .streams_blocked(self.indexes.remote_max_stream_uni, StreamType::UniDi);
+ qwarn!(
+ [self],
+ "local uni stream create blocked, next={:?} max={:?}",
+ self.indexes.remote_next_stream_uni,
+ self.indexes.remote_max_stream_uni
+ );
+ return Err(Error::StreamLimitError);
+ }
+ let new_id = self
+ .indexes
+ .remote_next_stream_uni
+ .to_stream_id(StreamType::UniDi, self.role);
+ self.indexes.remote_next_stream_uni += 1;
+ let initial_max_stream_data = self
+ .tps
+ .borrow()
+ .remote()
+ .get_integer(tparams::INITIAL_MAX_STREAM_DATA_UNI);
+
+ self.send_streams.insert(
+ new_id,
+ SendStream::new(
+ new_id,
+ initial_max_stream_data,
+ self.flow_mgr.clone(),
+ self.events.clone(),
+ ),
+ );
+ new_id.as_u64()
+ }
+ StreamType::BiDi => {
+ if self.indexes.remote_next_stream_bidi >= self.indexes.remote_max_stream_bidi {
+ self.flow_mgr
+ .borrow_mut()
+ .streams_blocked(self.indexes.remote_max_stream_bidi, StreamType::BiDi);
+ qwarn!(
+ [self],
+ "local bidi stream create blocked, next={:?} max={:?}",
+ self.indexes.remote_next_stream_bidi,
+ self.indexes.remote_max_stream_bidi
+ );
+ return Err(Error::StreamLimitError);
+ }
+ let new_id = self
+ .indexes
+ .remote_next_stream_bidi
+ .to_stream_id(StreamType::BiDi, self.role);
+ self.indexes.remote_next_stream_bidi += 1;
+ // From the local perspective, this is a local- originated BiDi stream. From the
+ // remote perspective, this is a remote-originated BiDi stream. Therefore, look at
+ // the remote transport parameters for the INITIAL_MAX_STREAM_DATA_BIDI_REMOTE value
+ // to decide how much this endpoint is allowed to send its peer.
+ let send_initial_max_stream_data = self
+ .tps
+ .borrow()
+ .remote()
+ .get_integer(tparams::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
+
+ self.send_streams.insert(
+ new_id,
+ SendStream::new(
+ new_id,
+ send_initial_max_stream_data,
+ self.flow_mgr.clone(),
+ self.events.clone(),
+ ),
+ );
+ // From the local perspective, this is a local- originated BiDi stream. From the
+ // remote perspective, this is a remote-originated BiDi stream. Therefore, look at
+ // the local transport parameters for the INITIAL_MAX_STREAM_DATA_BIDI_LOCAL value
+ // to decide how much this endpoint will allow its peer to send.
+ let recv_initial_max_stream_data = self
+ .tps
+ .borrow()
+ .local
+ .get_integer(tparams::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
+
+ self.recv_streams.insert(
+ new_id,
+ RecvStream::new(
+ new_id,
+ recv_initial_max_stream_data,
+ self.flow_mgr.clone(),
+ self.events.clone(),
+ ),
+ );
+ new_id.as_u64()
+ }
+ })
+ }
+
+ /// Send data on a stream.
+ /// Returns how many bytes were successfully sent. Could be less
+ /// than total, based on receiver credit space available, etc.
+ /// # Errors
+ /// `InvalidStreamId` the stream does not exist,
+ /// `InvalidInput` if length of `data` is zero,
+ /// `FinalSizeError` if the stream has already been closed.
+ pub fn stream_send(&mut self, stream_id: u64, data: &[u8]) -> Res<usize> {
+ self.send_streams.get_mut(stream_id.into())?.send(data)
+ }
+
+ /// Send all data or nothing on a stream. May cause DATA_BLOCKED or
+ /// STREAM_DATA_BLOCKED frames to be sent.
+ /// Returns true if data was successfully sent, otherwise false.
+ /// # Errors
+ /// `InvalidStreamId` the stream does not exist,
+ /// `InvalidInput` if length of `data` is zero,
+ /// `FinalSizeError` if the stream has already been closed.
+ pub fn stream_send_atomic(&mut self, stream_id: u64, data: &[u8]) -> Res<bool> {
+ let val = self
+ .send_streams
+ .get_mut(stream_id.into())?
+ .send_atomic(data);
+ if let Ok(val) = val {
+ debug_assert!(
+ val == 0 || val == data.len(),
+ "Unexpected value {} when trying to send {} bytes atomically",
+ val,
+ data.len()
+ );
+ }
+ val.map(|v| v == data.len())
+ }
+
+ /// Bytes that stream_send() is guaranteed to accept for sending.
+ /// i.e. that will not be blocked by flow credits or send buffer max
+ /// capacity.
+ pub fn stream_avail_send_space(&self, stream_id: u64) -> Res<usize> {
+ Ok(self.send_streams.get(stream_id.into())?.avail())
+ }
+
+ /// Close the stream. Enqueued data will be sent.
+ pub fn stream_close_send(&mut self, stream_id: u64) -> Res<()> {
+ self.send_streams.get_mut(stream_id.into())?.close();
+ Ok(())
+ }
+
+ /// Abandon transmission of in-flight and future stream data.
+ pub fn stream_reset_send(&mut self, stream_id: u64, err: AppError) -> Res<()> {
+ self.send_streams.get_mut(stream_id.into())?.reset(err);
+ Ok(())
+ }
+
+ /// Read buffered data from stream. bool says whether read bytes includes
+ /// the final data on stream.
+ /// # Errors
+ /// `InvalidStreamId` if the stream does not exist.
+ /// `NoMoreData` if data and fin bit were previously read by the application.
+ pub fn stream_recv(&mut self, stream_id: u64, data: &mut [u8]) -> Res<(usize, bool)> {
+ let stream = self
+ .recv_streams
+ .get_mut(&stream_id.into())
+ .ok_or(Error::InvalidStreamId)?;
+
+ let rb = stream.read(data)?;
+ Ok((rb.0 as usize, rb.1))
+ }
+
+ /// Application is no longer interested in this stream.
+ pub fn stream_stop_sending(&mut self, stream_id: u64, err: AppError) -> Res<()> {
+ let stream = self
+ .recv_streams
+ .get_mut(&stream_id.into())
+ .ok_or(Error::InvalidStreamId)?;
+
+ stream.stop_sending(err);
+ Ok(())
+ }
+
+ #[cfg(test)]
+ pub fn get_pto(&self) -> Duration {
+ self.loss_recovery.pto_raw(PNSpace::ApplicationData)
+ }
+}
+
+impl EventProvider for Connection {
+ type Event = ConnectionEvent;
+
+ /// Return true if there are outstanding events.
+ fn has_events(&self) -> bool {
+ self.events.has_events()
+ }
+
+ /// Get events that indicate state changes on the connection. This method
+ /// correctly handles cases where handling one event can obsolete
+ /// previously-queued events, or cause new events to be generated.
+ fn next_event(&mut self) -> Option<Self::Event> {
+ self.events.next_event()
+ }
+}
+
+impl ::std::fmt::Display for Connection {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{:?} ", self.role)?;
+ if let Some(cid) = self.odcid() {
+ std::fmt::Display::fmt(&cid, f)
+ } else {
+ write!(f, "...")
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/neqo-transport/src/connection/params.rs b/third_party/rust/neqo-transport/src/connection/params.rs
new file mode 100644
index 0000000000..c4404b54d9
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/params.rs
@@ -0,0 +1,71 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::frame::StreamType;
+use crate::{
+ CongestionControlAlgorithm, QuicVersion, LOCAL_STREAM_LIMIT_BIDI, LOCAL_STREAM_LIMIT_UNI,
+};
+
+/// ConnectionParameters use for setting intitial value for QUIC parameters.
+/// This collect like initial limits, protocol version and congestion control.
+#[derive(Clone)]
+pub struct ConnectionParameters {
+ quic_version: QuicVersion,
+ cc_algorithm: CongestionControlAlgorithm,
+ max_streams_bidi: u64,
+ max_streams_uni: u64,
+}
+
+impl Default for ConnectionParameters {
+ fn default() -> Self {
+ Self {
+ quic_version: QuicVersion::default(),
+ cc_algorithm: CongestionControlAlgorithm::NewReno,
+ max_streams_bidi: LOCAL_STREAM_LIMIT_BIDI,
+ max_streams_uni: LOCAL_STREAM_LIMIT_UNI,
+ }
+ }
+}
+
+impl ConnectionParameters {
+ pub fn get_quic_version(&self) -> QuicVersion {
+ self.quic_version
+ }
+
+ pub fn quic_version(mut self, v: QuicVersion) -> Self {
+ self.quic_version = v;
+ self
+ }
+
+ pub fn get_cc_algorithm(&self) -> CongestionControlAlgorithm {
+ self.cc_algorithm
+ }
+
+ pub fn cc_algorithm(mut self, v: CongestionControlAlgorithm) -> Self {
+ self.cc_algorithm = v;
+ self
+ }
+
+ pub fn get_max_streams(&self, stream_type: StreamType) -> u64 {
+ match stream_type {
+ StreamType::BiDi => self.max_streams_bidi,
+ StreamType::UniDi => self.max_streams_uni,
+ }
+ }
+
+ pub fn max_streams(mut self, stream_type: StreamType, v: u64) -> Self {
+ assert!(v <= (1 << 60), "max_streams's parameter too big");
+ match stream_type {
+ StreamType::BiDi => {
+ self.max_streams_bidi = v;
+ }
+ StreamType::UniDi => {
+ self.max_streams_uni = v;
+ }
+ }
+ self
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/connection/saved.rs b/third_party/rust/neqo-transport/src/connection/saved.rs
new file mode 100644
index 0000000000..b2da0c644a
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/saved.rs
@@ -0,0 +1,72 @@
+// 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::mem;
+use std::time::Instant;
+
+use crate::crypto::CryptoSpace;
+use neqo_common::{qdebug, qinfo, Datagram};
+
+/// The number of datagrams that are saved during the handshake when
+/// keys to decrypt them are not yet available.
+///
+/// This value exceeds what should be possible to send during the handshake.
+/// Neither endpoint should have enough congestion window to send this
+/// much before the handshake completes.
+const MAX_SAVED_DATAGRAMS: usize = 32;
+
+pub struct SavedDatagram {
+ /// The datagram.
+ pub d: Datagram,
+ /// The time that the datagram was received.
+ pub t: Instant,
+}
+
+#[derive(Default)]
+pub struct SavedDatagrams {
+ handshake: Vec<SavedDatagram>,
+ application_data: Vec<SavedDatagram>,
+ available: Option<CryptoSpace>,
+}
+
+impl SavedDatagrams {
+ fn store(&mut self, cspace: CryptoSpace) -> &mut Vec<SavedDatagram> {
+ match cspace {
+ CryptoSpace::Handshake => &mut self.handshake,
+ CryptoSpace::ApplicationData => &mut self.application_data,
+ _ => panic!("unexpected space"),
+ }
+ }
+
+ pub fn save(&mut self, cspace: CryptoSpace, d: Datagram, t: Instant) {
+ let store = self.store(cspace);
+
+ if store.len() < MAX_SAVED_DATAGRAMS {
+ qdebug!("saving datagram of {} bytes", d.len());
+ store.push(SavedDatagram { d, t });
+ } else {
+ qinfo!("not saving datagram of {} bytes", d.len());
+ }
+ }
+
+ pub fn make_available(&mut self, cspace: CryptoSpace) {
+ debug_assert_ne!(cspace, CryptoSpace::ZeroRtt);
+ debug_assert_ne!(cspace, CryptoSpace::Initial);
+ if !self.store(cspace).is_empty() {
+ self.available = Some(cspace);
+ }
+ }
+
+ pub fn available(&self) -> Option<CryptoSpace> {
+ self.available
+ }
+
+ pub fn take_saved(&mut self) -> Vec<SavedDatagram> {
+ self.available
+ .take()
+ .map_or_else(Vec::new, |cspace| mem::take(self.store(cspace)))
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/connection/state.rs b/third_party/rust/neqo-transport/src/connection/state.rs
new file mode 100644
index 0000000000..1b38c1650c
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/state.rs
@@ -0,0 +1,207 @@
+// 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::cmp::Ordering;
+use std::mem;
+use std::time::Instant;
+
+use crate::frame::{Frame, FrameType};
+use crate::packet::PacketBuilder;
+use crate::recovery::RecoveryToken;
+use crate::{CloseError, ConnectionError};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+/// The state of the Connection.
+pub enum State {
+ Init,
+ WaitInitial,
+ Handshaking,
+ Connected,
+ Confirmed,
+ Closing {
+ error: ConnectionError,
+ timeout: Instant,
+ },
+ Draining {
+ error: ConnectionError,
+ timeout: Instant,
+ },
+ Closed(ConnectionError),
+}
+
+impl State {
+ #[must_use]
+ pub fn connected(&self) -> bool {
+ matches!(self, Self::Connected | Self::Confirmed)
+ }
+
+ #[must_use]
+ pub fn closed(&self) -> bool {
+ matches!(self, Self::Closing { .. } | Self::Draining { .. } | Self::Closed(_))
+ }
+}
+
+// Implement `PartialOrd` so that we can enforce monotonic state progression.
+impl PartialOrd for State {
+ #[allow(clippy::match_same_arms)] // Lint bug: rust-lang/rust-clippy#860
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ if mem::discriminant(self) == mem::discriminant(other) {
+ return Some(Ordering::Equal);
+ }
+ Some(match (self, other) {
+ (Self::Init, _) => Ordering::Less,
+ (_, Self::Init) => Ordering::Greater,
+ (Self::WaitInitial, _) => Ordering::Less,
+ (_, Self::WaitInitial) => Ordering::Greater,
+ (Self::Handshaking, _) => Ordering::Less,
+ (_, Self::Handshaking) => Ordering::Greater,
+ (Self::Connected, _) => Ordering::Less,
+ (_, Self::Connected) => Ordering::Greater,
+ (Self::Confirmed, _) => Ordering::Less,
+ (_, Self::Confirmed) => Ordering::Greater,
+ (Self::Closing { .. }, _) => Ordering::Less,
+ (_, Self::Closing { .. }) => Ordering::Greater,
+ (Self::Draining { .. }, _) => Ordering::Less,
+ (_, Self::Draining { .. }) => Ordering::Greater,
+ (Self::Closed(_), _) => unreachable!(),
+ })
+ }
+}
+
+impl Ord for State {
+ fn cmp(&self, other: &Self) -> Ordering {
+ if mem::discriminant(self) == mem::discriminant(other) {
+ return Ordering::Equal;
+ }
+ match (self, other) {
+ (Self::Init, _) => Ordering::Less,
+ (_, Self::Init) => Ordering::Greater,
+ (Self::WaitInitial, _) => Ordering::Less,
+ (_, Self::WaitInitial) => Ordering::Greater,
+ (Self::Handshaking, _) => Ordering::Less,
+ (_, Self::Handshaking) => Ordering::Greater,
+ (Self::Connected, _) => Ordering::Less,
+ (_, Self::Connected) => Ordering::Greater,
+ (Self::Confirmed, _) => Ordering::Less,
+ (_, Self::Confirmed) => Ordering::Greater,
+ (Self::Closing { .. }, _) => Ordering::Less,
+ (_, Self::Closing { .. }) => Ordering::Greater,
+ (Self::Draining { .. }, _) => Ordering::Less,
+ (_, Self::Draining { .. }) => Ordering::Greater,
+ (Self::Closed(_), _) => unreachable!(),
+ }
+ }
+}
+
+type ClosingFrame = Frame<'static>;
+
+/// `StateSignaling` manages whether we need to send HANDSHAKE_DONE and CONNECTION_CLOSE.
+/// Valid state transitions are:
+/// * Idle -> HandshakeDone: at the server when the handshake completes
+/// * HandshakeDone -> Idle: when a HANDSHAKE_DONE frame is sent
+/// * Idle/HandshakeDone -> Closing/Draining: when closing or draining
+/// * Closing/Draining -> CloseSent: after sending CONNECTION_CLOSE
+/// * CloseSent -> Closing: any time a new CONNECTION_CLOSE is needed
+/// * -> Reset: from any state in case of a stateless reset
+#[derive(Debug, Clone, PartialEq)]
+pub enum StateSignaling {
+ Idle,
+ HandshakeDone,
+ /// These states save the frame that needs to be sent.
+ Closing(ClosingFrame),
+ Draining(ClosingFrame),
+ /// This state saves the frame that might need to be sent again.
+ /// If it is `None`, then we are draining and don't send.
+ CloseSent(Option<ClosingFrame>),
+ Reset,
+}
+
+impl StateSignaling {
+ pub fn handshake_done(&mut self) {
+ if *self != Self::Idle {
+ debug_assert!(false, "StateSignaling must be in Idle state.");
+ return;
+ }
+ *self = Self::HandshakeDone
+ }
+
+ pub fn write_done(&mut self, builder: &mut PacketBuilder) -> Option<RecoveryToken> {
+ if *self == Self::HandshakeDone && builder.remaining() >= 1 {
+ *self = Self::Idle;
+ builder.encode_varint(Frame::HandshakeDone.get_type());
+ Some(RecoveryToken::HandshakeDone)
+ } else {
+ None
+ }
+ }
+
+ fn make_close_frame(
+ error: ConnectionError,
+ frame_type: FrameType,
+ message: impl AsRef<str>,
+ ) -> ClosingFrame {
+ let reason_phrase = message.as_ref().as_bytes().to_owned();
+ Frame::ConnectionClose {
+ error_code: CloseError::from(error),
+ frame_type,
+ reason_phrase,
+ }
+ }
+
+ pub fn close(
+ &mut self,
+ error: ConnectionError,
+ frame_type: FrameType,
+ message: impl AsRef<str>,
+ ) {
+ if *self != Self::Reset {
+ *self = Self::Closing(Self::make_close_frame(error, frame_type, message));
+ }
+ }
+
+ pub fn drain(
+ &mut self,
+ error: ConnectionError,
+ frame_type: FrameType,
+ message: impl AsRef<str>,
+ ) {
+ if *self != Self::Reset {
+ *self = Self::Draining(Self::make_close_frame(error, frame_type, message));
+ }
+ }
+
+ /// If a close is pending, take a frame.
+ pub fn close_frame(&mut self) -> Option<ClosingFrame> {
+ match self {
+ Self::Closing(frame) => {
+ // When we are closing, we might need to send the close frame again.
+ let frame = mem::replace(frame, Frame::Padding);
+ *self = Self::CloseSent(Some(frame.clone()));
+ Some(frame)
+ }
+ Self::Draining(frame) => {
+ // When we are draining, just send once.
+ let frame = mem::replace(frame, Frame::Padding);
+ *self = Self::CloseSent(None);
+ Some(frame)
+ }
+ _ => None,
+ }
+ }
+
+ /// If a close can be sent again, prepare to send it again.
+ pub fn send_close(&mut self) {
+ if let Self::CloseSent(Some(frame)) = self {
+ let frame = mem::replace(frame, Frame::Padding);
+ *self = Self::Closing(frame);
+ }
+ }
+
+ /// We just got a stateless reset. Terminate.
+ pub fn reset(&mut self) {
+ *self = Self::Reset;
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/cc.rs b/third_party/rust/neqo-transport/src/connection/tests/cc.rs
new file mode 100644
index 0000000000..f69234a357
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/cc.rs
@@ -0,0 +1,526 @@
+// 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::super::{Connection, Output};
+use super::{
+ assert_full_cwnd, connect_force_idle, connect_rtt_idle, cwnd_packets, default_client,
+ default_server, fill_cwnd, send_something, AT_LEAST_PTO, DEFAULT_RTT, POST_HANDSHAKE_CWND,
+};
+use crate::cc::{CWND_MIN, MAX_DATAGRAM_SIZE};
+use crate::frame::StreamType;
+use crate::packet::PacketNumber;
+use crate::recovery::{ACK_ONLY_SIZE_LIMIT, PACKET_THRESHOLD};
+use crate::sender::PACING_BURST_SIZE;
+use crate::stats::MAX_PTO_COUNTS;
+use crate::tparams::{self, TransportParameter};
+use crate::tracking::MAX_UNACKED_PKTS;
+
+use neqo_common::{qdebug, qinfo, qtrace, Datagram};
+use std::convert::TryFrom;
+use std::time::{Duration, Instant};
+use test_fixture::{self, now};
+
+fn induce_persistent_congestion(
+ client: &mut Connection,
+ server: &mut Connection,
+ mut now: Instant,
+) -> Instant {
+ // Note: wait some arbitrary time that should be longer than pto
+ // timer. This is rather brittle.
+ now += AT_LEAST_PTO;
+
+ let mut pto_counts = [0; MAX_PTO_COUNTS];
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ qtrace!([client], "first PTO");
+ let (c_tx_dgrams, next_now) = fill_cwnd(client, 0, now);
+ now = next_now;
+ assert_eq!(c_tx_dgrams.len(), 2); // Two PTO packets
+
+ pto_counts[0] = 1;
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ qtrace!([client], "second PTO");
+ now += AT_LEAST_PTO * 2;
+ let (c_tx_dgrams, next_now) = fill_cwnd(client, 0, now);
+ now = next_now;
+ assert_eq!(c_tx_dgrams.len(), 2); // Two PTO packets
+
+ pto_counts[0] = 0;
+ pto_counts[1] = 1;
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ qtrace!([client], "third PTO");
+ now += AT_LEAST_PTO * 4;
+ let (c_tx_dgrams, next_now) = fill_cwnd(client, 0, now);
+ now = next_now;
+ assert_eq!(c_tx_dgrams.len(), 2); // Two PTO packets
+
+ pto_counts[1] = 0;
+ pto_counts[2] = 1;
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ // Generate ACK
+ let s_tx_dgram = ack_bytes(server, 0, c_tx_dgrams, now);
+
+ // An ACK for the third PTO causes persistent congestion.
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+
+ assert_eq!(client.loss_recovery.cwnd(), CWND_MIN);
+ now
+}
+
+// Receive multiple packets and generate an ack-only packet.
+fn ack_bytes<D>(dest: &mut Connection, stream: u64, in_dgrams: D, now: Instant) -> Vec<Datagram>
+where
+ D: IntoIterator<Item = Datagram>,
+ D::IntoIter: ExactSizeIterator,
+{
+ let mut srv_buf = [0; 4_096];
+
+ let in_dgrams = in_dgrams.into_iter();
+ qdebug!([dest], "ack_bytes {} datagrams", in_dgrams.len());
+ for dgram in in_dgrams {
+ dest.process_input(dgram, now);
+ }
+
+ loop {
+ let (bytes_read, _fin) = dest.stream_recv(stream, &mut srv_buf).unwrap();
+ qtrace!([dest], "ack_bytes read {} bytes", bytes_read);
+ if bytes_read == 0 {
+ break;
+ }
+ }
+
+ let mut tx_dgrams = Vec::new();
+ while let Output::Datagram(dg) = dest.process_output(now) {
+ tx_dgrams.push(dg);
+ }
+
+ assert!((tx_dgrams.len() == 1) || (tx_dgrams.len() == 2));
+ tx_dgrams
+}
+
+#[test]
+/// Verify initial CWND is honored.
+fn cc_slow_start() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ server
+ .set_local_tparam(
+ tparams::INITIAL_MAX_DATA,
+ TransportParameter::Integer(65536),
+ )
+ .unwrap();
+ let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
+
+ // Try to send a lot of data
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ let (c_tx_dgrams, _) = fill_cwnd(&mut client, 2, now);
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+ assert!(client.loss_recovery.cwnd_avail() < ACK_ONLY_SIZE_LIMIT);
+}
+
+#[test]
+/// Verify that CC moves to cong avoidance when a packet is marked lost.
+fn cc_slow_start_to_cong_avoidance_recovery_period() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
+
+ // Create stream 0
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets
+ let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now);
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+ // Predict the packet number of the last packet sent.
+ // We have already sent one packet in `connect_force_idle` (an ACK),
+ // so this will be equal to the number of packets in this flight.
+ let flight1_largest = PacketNumber::try_from(c_tx_dgrams.len()).unwrap();
+
+ // Server: Receive and generate ack
+ now += DEFAULT_RTT / 2;
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+ assert_eq!(
+ server.stats().frame_tx.largest_acknowledged,
+ flight1_largest
+ );
+
+ // Client: Process ack
+ now += DEFAULT_RTT / 2;
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+ assert_eq!(
+ client.stats().frame_rx.largest_acknowledged,
+ flight1_largest
+ );
+
+ // Client: send more
+ let (mut c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now);
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND * 2);
+ let flight2_largest = flight1_largest + u64::try_from(c_tx_dgrams.len()).unwrap();
+
+ // Server: Receive and generate ack again, but drop first packet
+ now += DEFAULT_RTT / 2;
+ c_tx_dgrams.remove(0);
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+ assert_eq!(
+ server.stats().frame_tx.largest_acknowledged,
+ flight2_largest
+ );
+
+ // Client: Process ack
+ now += DEFAULT_RTT / 2;
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+ assert_eq!(
+ client.stats().frame_rx.largest_acknowledged,
+ flight2_largest
+ );
+}
+
+#[test]
+/// Verify that CC stays in recovery period when packet sent before start of
+/// recovery period is acked.
+fn cc_cong_avoidance_recovery_period_unchanged() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // Create stream 0
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets
+ let (mut c_tx_dgrams, now) = fill_cwnd(&mut client, 0, now());
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+
+ // Drop 0th packet. When acked, this should put client into CARP.
+ c_tx_dgrams.remove(0);
+
+ let c_tx_dgrams2 = c_tx_dgrams.split_off(5);
+
+ // Server: Receive and generate ack
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+
+ let cwnd1 = client.loss_recovery.cwnd();
+
+ // Generate ACK for more received packets
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams2, now);
+
+ // ACK more packets but they were sent before end of recovery period
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+
+ // cwnd should not have changed since ACKed packets were sent before
+ // recovery period expired
+ let cwnd2 = client.loss_recovery.cwnd();
+ assert_eq!(cwnd1, cwnd2);
+}
+
+#[test]
+/// Ensure that a single packet is sent after entering recovery, even
+/// when that exceeds the available congestion window.
+fn single_packet_on_recovery() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // Drop a few packets, up to the reordering threshold.
+ for _ in 0..PACKET_THRESHOLD {
+ let _dropped = send_something(&mut client, now());
+ }
+ let delivered = send_something(&mut client, now());
+
+ // Now fill the congestion window.
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+ let _ = fill_cwnd(&mut client, 0, now());
+ assert!(client.loss_recovery.cwnd_avail() < ACK_ONLY_SIZE_LIMIT);
+
+ // Acknowledge just one packet and cause one packet to be declared lost.
+ // The length is the amount of credit the client should have.
+ let ack = server.process(Some(delivered), now()).dgram();
+ assert!(ack.is_some());
+
+ // The client should see the loss and enter recovery.
+ // As there are many outstanding packets, there should be no available cwnd.
+ client.process_input(ack.unwrap(), now());
+ assert_eq!(client.loss_recovery.cwnd_avail(), 0);
+
+ // The client should send one packet, ignoring the cwnd.
+ let dgram = client.process_output(now()).dgram();
+ assert!(dgram.is_some());
+}
+
+#[test]
+/// Verify that CC moves out of recovery period when packet sent after start
+/// of recovery period is acked.
+fn cc_cong_avoidance_recovery_period_to_cong_avoidance() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
+
+ // Create stream 0
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets
+ let (mut c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now);
+
+ // Drop 0th packet. When acked, this should put client into CARP.
+ c_tx_dgrams.remove(0);
+
+ // Server: Receive and generate ack
+ now += DEFAULT_RTT / 2;
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+
+ // Client: Process ack
+ now += DEFAULT_RTT / 2;
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+
+ // Should be in CARP now.
+ now += DEFAULT_RTT / 2;
+ qinfo!(
+ "moving to congestion avoidance {}",
+ client.loss_recovery.cwnd()
+ );
+
+ // Now make sure that we increase congestion window according to the
+ // accurate byte counting version of congestion avoidance.
+ // Check over several increases to be sure.
+ let mut expected_cwnd = client.loss_recovery.cwnd();
+ // Fill cwnd.
+ let (mut c_tx_dgrams, next_now) = fill_cwnd(&mut client, 0, now);
+ now = next_now;
+ for i in 0..5 {
+ qinfo!("iteration {}", i);
+
+ let c_tx_size: usize = c_tx_dgrams.iter().map(|d| d.len()).sum();
+ qinfo!(
+ "client sending {} bytes into cwnd of {}",
+ c_tx_size,
+ client.loss_recovery.cwnd()
+ );
+ assert_eq!(c_tx_size, expected_cwnd);
+
+ // As acks arrive we will continue filling cwnd and save all packets
+ // from this cycle will be stored in next_c_tx_dgrams.
+ let mut next_c_tx_dgrams: Vec<Datagram> = Vec::new();
+
+ // Until we process all the packets, the congestion window remains the same.
+ // Note that we need the client to process ACK frames in stages, so split the
+ // datagrams into two, ensuring that we allow for an ACK for each batch.
+ let most = c_tx_dgrams.len() - MAX_UNACKED_PKTS - 1;
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams.drain(..most), now);
+ for dgram in s_tx_dgram {
+ assert_eq!(client.loss_recovery.cwnd(), expected_cwnd);
+ client.process_input(dgram, now);
+ // make sure to fill cwnd again.
+ let (mut new_pkts, next_now) = fill_cwnd(&mut client, 0, now);
+ now = next_now;
+ next_c_tx_dgrams.append(&mut new_pkts);
+ }
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+ for dgram in s_tx_dgram {
+ assert_eq!(client.loss_recovery.cwnd(), expected_cwnd);
+ client.process_input(dgram, now);
+ // make sure to fill cwnd again.
+ let (mut new_pkts, next_now) = fill_cwnd(&mut client, 0, now);
+ now = next_now;
+ next_c_tx_dgrams.append(&mut new_pkts);
+ }
+ expected_cwnd += MAX_DATAGRAM_SIZE;
+ assert_eq!(client.loss_recovery.cwnd(), expected_cwnd);
+ c_tx_dgrams = next_c_tx_dgrams;
+ }
+}
+
+#[test]
+/// Verify transition to persistent congestion state if conditions are met.
+fn cc_slow_start_to_persistent_congestion_no_acks() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
+
+ // Create stream 0
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets
+ let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now);
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+
+ // Server: Receive and generate ack
+ now += DEFAULT_RTT / 2;
+ let _ = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+
+ // ACK lost.
+ induce_persistent_congestion(&mut client, &mut server, now);
+}
+
+#[test]
+/// Verify transition to persistent congestion state if conditions are met.
+fn cc_slow_start_to_persistent_congestion_some_acks() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // Create stream 0
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets
+ let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now());
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+
+ // Server: Receive and generate ack
+ now += Duration::from_millis(100);
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+
+ now += Duration::from_millis(100);
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+
+ // send bytes that will be lost
+ let (_, next_now) = fill_cwnd(&mut client, 0, now);
+ now = next_now + Duration::from_millis(100);
+
+ induce_persistent_congestion(&mut client, &mut server, now);
+}
+
+#[test]
+/// Verify persistent congestion moves to slow start after recovery period
+/// ends.
+fn cc_persistent_congestion_to_slow_start() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // Create stream 0
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets
+ let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now());
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+
+ // Server: Receive and generate ack
+ now += Duration::from_millis(10);
+ let _ = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+
+ // ACK lost.
+
+ now = induce_persistent_congestion(&mut client, &mut server, now);
+
+ // New part of test starts here
+
+ now += Duration::from_millis(10);
+
+ // Send packets from after start of CARP
+ let (c_tx_dgrams, next_now) = fill_cwnd(&mut client, 0, now);
+ assert_eq!(c_tx_dgrams.len(), 2);
+
+ // Server: Receive and generate ack
+ now = next_now + Duration::from_millis(100);
+ let s_tx_dgram = ack_bytes(&mut server, 0, c_tx_dgrams, now);
+
+ // No longer in CARP. (pkts acked from after start of CARP)
+ // Should be in slow start now.
+ for dgram in s_tx_dgram {
+ client.process_input(dgram, now);
+ }
+
+ // ACKing 2 packets should let client send 4.
+ let (c_tx_dgrams, _) = fill_cwnd(&mut client, 0, now);
+ assert_eq!(c_tx_dgrams.len(), 4);
+}
+
+#[test]
+fn ack_are_not_cc() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // Create a stream
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+
+ // Buffer up lot of data and generate packets, so that cc window is filled.
+ let (c_tx_dgrams, now) = fill_cwnd(&mut client, 0, now());
+ assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
+
+ // The server hasn't received any of these packets yet, the server
+ // won't ACK, but if it sends an ack-eliciting packet instead.
+ qdebug!([server], "Sending ack-eliciting");
+ assert_eq!(server.stream_create(StreamType::BiDi).unwrap(), 1);
+ server.stream_send(1, b"dropped").unwrap();
+ let dropped_packet = server.process(None, now).dgram();
+ assert!(dropped_packet.is_some()); // Now drop this one.
+
+ // Now the server sends a packet that will force an ACK,
+ // because the client will detect a gap.
+ server.stream_send(1, b"sent").unwrap();
+ let ack_eliciting_packet = server.process(None, now).dgram();
+ assert!(ack_eliciting_packet.is_some());
+
+ // The client can ack the server packet even if cc windows is full.
+ qdebug!([client], "Process ack-eliciting");
+ let ack_pkt = client.process(ack_eliciting_packet, now).dgram();
+ assert!(ack_pkt.is_some());
+ qdebug!([server], "Handle ACK");
+ let prev_ack_count = server.stats().frame_rx.ack;
+ server.process_input(ack_pkt.unwrap(), now);
+ assert_eq!(server.stats().frame_rx.ack, prev_ack_count + 1);
+}
+
+#[test]
+fn pace() {
+ const RTT: Duration = Duration::from_millis(1000);
+ const DATA: &[u8] = &[0xcc; 4_096];
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = connect_rtt_idle(&mut client, &mut server, RTT);
+
+ // Now fill up the pipe and watch it trickle out.
+ let stream = client.stream_create(StreamType::BiDi).unwrap();
+ loop {
+ let written = client.stream_send(stream, DATA).unwrap();
+ if written < DATA.len() {
+ break;
+ }
+ }
+ let mut count = 0;
+ // We should get a burst at first.
+ for _ in 0..PACING_BURST_SIZE {
+ let dgram = client.process_output(now).dgram();
+ assert!(dgram.is_some());
+ count += 1;
+ }
+ let gap = client.process_output(now).callback();
+ assert_ne!(gap, Duration::new(0, 0));
+ // The last one will not be paced.
+ for _ in PACING_BURST_SIZE..cwnd_packets(POST_HANDSHAKE_CWND) - 1 {
+ assert_eq!(client.process_output(now).callback(), gap);
+ now += gap;
+ let dgram = client.process_output(now).dgram();
+ assert!(dgram.is_some());
+ count += 1;
+ }
+ let dgram = client.process_output(now).dgram();
+ assert!(dgram.is_some());
+ count += 1;
+ assert_eq!(count, cwnd_packets(POST_HANDSHAKE_CWND));
+ let fin = client.process_output(now).callback();
+ assert_ne!(fin, Duration::new(0, 0));
+ assert_ne!(fin, gap);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/close.rs b/third_party/rust/neqo-transport/src/connection/tests/close.rs
new file mode 100644
index 0000000000..715073523b
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/close.rs
@@ -0,0 +1,206 @@
+// 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::super::{Connection, Output, State};
+use super::{connect, connect_force_idle, default_client, default_server, send_something};
+use crate::tparams::{self, TransportParameter};
+use crate::{AppError, ConnectionError, Error, ERROR_APPLICATION_CLOSE};
+
+use neqo_common::Datagram;
+use std::time::Duration;
+use test_fixture::{self, loopback, now};
+
+fn assert_draining(c: &Connection, expected: &Error) {
+ assert!(c.state().closed());
+ if let State::Draining {
+ error: ConnectionError::Transport(error),
+ ..
+ } = c.state()
+ {
+ assert_eq!(error, expected);
+ } else {
+ panic!();
+ }
+}
+
+#[test]
+fn connection_close() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let now = now();
+
+ client.close(now, 42, "");
+
+ let out = client.process(None, now);
+
+ server.process_input(out.dgram().unwrap(), now);
+ assert_draining(&server, &Error::PeerApplicationError(42));
+}
+
+#[test]
+fn connection_close_with_long_reason_string() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let now = now();
+ // Create a long string and use it as the close reason.
+ let long_reason = String::from_utf8([0x61; 2048].to_vec()).unwrap();
+ client.close(now, 42, long_reason);
+
+ let out = client.process(None, now);
+
+ server.process_input(out.dgram().unwrap(), now);
+ assert_draining(&server, &Error::PeerApplicationError(42));
+}
+
+// During the handshake, an application close should be sanitized.
+#[test]
+fn early_application_close() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ // One flight each.
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_some());
+
+ server.close(now(), 77, String::from(""));
+ assert!(server.state().closed());
+ let dgram = server.process(None, now()).dgram();
+ assert!(dgram.is_some());
+
+ client.process_input(dgram.unwrap(), now());
+ assert_draining(&client, &Error::PeerError(ERROR_APPLICATION_CLOSE));
+}
+
+#[test]
+fn bad_tls_version() {
+ let mut client = default_client();
+ // Do a bad, bad thing.
+ client
+ .crypto
+ .tls
+ .set_option(neqo_crypto::Opt::Tls13CompatMode, true)
+ .unwrap();
+ let mut server = default_server();
+
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram();
+ assert_eq!(
+ *server.state(),
+ State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
+ );
+ assert!(dgram.is_some());
+ client.process_input(dgram.unwrap(), now());
+ assert_draining(&client, &Error::PeerError(Error::ProtocolViolation.code()));
+}
+
+/// Test the interaction between the loss recovery timer
+/// and the closing timer.
+#[test]
+fn closing_timers_interation() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let mut now = now();
+
+ // We're going to induce time-based loss recovery so that timer is set.
+ let _p1 = send_something(&mut client, now);
+ let p2 = send_something(&mut client, now);
+ let ack = server.process(Some(p2), now).dgram();
+ assert!(ack.is_some()); // This is an ACK.
+
+ // After processing the ACK, we should be on the loss recovery timer.
+ let cb = client.process(ack, now).callback();
+ assert_ne!(cb, Duration::from_secs(0));
+ now += cb;
+
+ // Rather than let the timer pop, close the connection.
+ client.close(now, 0, "");
+ let client_close = client.process(None, now).dgram();
+ assert!(client_close.is_some());
+ // This should now report the end of the closing period, not a
+ // zero-duration wait driven by the (now defunct) loss recovery timer.
+ let client_close_timer = client.process(None, now).callback();
+ assert_ne!(client_close_timer, Duration::from_secs(0));
+}
+
+#[test]
+fn closing_and_draining() {
+ const APP_ERROR: AppError = 7;
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // Save a packet from the client for later.
+ let p1 = send_something(&mut client, now());
+
+ // Close the connection.
+ client.close(now(), APP_ERROR, "");
+ let client_close = client.process(None, now()).dgram();
+ assert!(client_close.is_some());
+ let client_close_timer = client.process(None, now()).callback();
+ assert_ne!(client_close_timer, Duration::from_secs(0));
+
+ // The client will spit out the same packet in response to anything it receives.
+ let p3 = send_something(&mut server, now());
+ let client_close2 = client.process(Some(p3), now()).dgram();
+ assert_eq!(
+ client_close.as_ref().unwrap().len(),
+ client_close2.as_ref().unwrap().len()
+ );
+
+ // After this time, the client should transition to closed.
+ let end = client.process(None, now() + client_close_timer);
+ assert_eq!(end, Output::None);
+ assert_eq!(
+ *client.state(),
+ State::Closed(ConnectionError::Application(APP_ERROR))
+ );
+
+ // When the server receives the close, it too should generate CONNECTION_CLOSE.
+ let server_close = server.process(client_close, now()).dgram();
+ assert!(server.state().closed());
+ assert!(server_close.is_some());
+ // .. but it ignores any further close packets.
+ let server_close_timer = server.process(client_close2, now()).callback();
+ assert_ne!(server_close_timer, Duration::from_secs(0));
+ // Even a legitimate packet without a close in it.
+ let server_close_timer2 = server.process(Some(p1), now()).callback();
+ assert_eq!(server_close_timer, server_close_timer2);
+
+ let end = server.process(None, now() + server_close_timer);
+ assert_eq!(end, Output::None);
+ assert_eq!(
+ *server.state(),
+ State::Closed(ConnectionError::Transport(Error::PeerApplicationError(
+ APP_ERROR
+ )))
+ );
+}
+
+/// Test that a client can handle a stateless reset correctly.
+#[test]
+fn stateless_reset_client() {
+ let mut client = default_client();
+ let mut server = default_server();
+ server
+ .set_local_tparam(
+ tparams::STATELESS_RESET_TOKEN,
+ TransportParameter::Bytes(vec![77; 16]),
+ )
+ .unwrap();
+ connect_force_idle(&mut client, &mut server);
+
+ client.process_input(Datagram::new(loopback(), loopback(), vec![77; 21]), now());
+ assert_draining(&client, &Error::StatelessReset);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
new file mode 100644
index 0000000000..c9769b3c3c
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
@@ -0,0 +1,697 @@
+// 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::super::{Connection, FixedConnectionIdManager, Output, State, LOCAL_IDLE_TIMEOUT};
+use super::{
+ assert_error, connect_force_idle, connect_with_rtt, default_client, default_server, get_tokens,
+ handshake, maybe_authenticate, send_something, AT_LEAST_PTO, DEFAULT_RTT, DEFAULT_STREAM_DATA,
+};
+use crate::connection::AddressValidation;
+use crate::events::ConnectionEvent;
+use crate::frame::StreamType;
+use crate::path::PATH_MTU_V6;
+use crate::server::ValidateAddress;
+use crate::{ConnectionError, ConnectionParameters, Error};
+
+use neqo_common::{event::Provider, qdebug, Datagram};
+use neqo_crypto::{constants::TLS_CHACHA20_POLY1305_SHA256, AuthenticationStatus};
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::time::Duration;
+use test_fixture::{self, assertions, fixture_init, loopback, now, split_datagram};
+
+#[test]
+fn full_handshake() {
+ qdebug!("---- client: generate CH");
+ let mut client = default_client();
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(out.as_dgram_ref().unwrap().len(), PATH_MTU_V6);
+
+ qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN");
+ let mut server = default_server();
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(out.as_dgram_ref().unwrap().len(), PATH_MTU_V6);
+
+ qdebug!("---- client: cert verification");
+ let out = client.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_none());
+
+ assert!(maybe_authenticate(&mut client));
+
+ qdebug!("---- client: SH..FIN -> FIN");
+ let out = client.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(*client.state(), State::Connected);
+
+ qdebug!("---- server: FIN -> ACKS");
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(*server.state(), State::Confirmed);
+
+ qdebug!("---- client: ACKS -> 0");
+ let out = client.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_none());
+ assert_eq!(*client.state(), State::Confirmed);
+}
+
+#[test]
+fn handshake_failed_authentication() {
+ qdebug!("---- client: generate CH");
+ let mut client = default_client();
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+
+ qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN");
+ let mut server = default_server();
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+
+ qdebug!("---- client: cert verification");
+ let out = client.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_none());
+
+ let authentication_needed = |e| matches!(e, ConnectionEvent::AuthenticationNeeded);
+ assert!(client.events().any(authentication_needed));
+ qdebug!("---- client: Alert(certificate_revoked)");
+ client.authenticated(AuthenticationStatus::CertRevoked, now());
+
+ qdebug!("---- client: -> Alert(certificate_revoked)");
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+
+ qdebug!("---- server: Alert(certificate_revoked)");
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_error(&client, &ConnectionError::Transport(Error::CryptoAlert(44)));
+ assert_error(&server, &ConnectionError::Transport(Error::PeerError(300)));
+}
+
+#[test]
+fn no_alpn() {
+ fixture_init();
+ let mut client = Connection::new_client(
+ "example.com",
+ &["bad-alpn"],
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(9))),
+ loopback(),
+ loopback(),
+ &ConnectionParameters::default(),
+ )
+ .unwrap();
+ let mut server = default_server();
+
+ handshake(&mut client, &mut server, now(), Duration::new(0, 0));
+ // TODO (mt): errors are immediate, which means that we never send CONNECTION_CLOSE
+ // and the client never sees the server's rejection of its handshake.
+ //assert_error(&client, ConnectionError::Transport(Error::CryptoAlert(120)));
+ assert_error(
+ &server,
+ &ConnectionError::Transport(Error::CryptoAlert(120)),
+ );
+}
+
+#[test]
+fn dup_server_flight1() {
+ qdebug!("---- client: generate CH");
+ let mut client = default_client();
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(out.as_dgram_ref().unwrap().len(), PATH_MTU_V6);
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+
+ qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN");
+ let mut server = default_server();
+ let out_to_rep = server.process(out.dgram(), now());
+ assert!(out_to_rep.as_dgram_ref().is_some());
+ qdebug!("Output={:0x?}", out_to_rep.as_dgram_ref());
+
+ qdebug!("---- client: cert verification");
+ let out = client.process(Some(out_to_rep.as_dgram_ref().unwrap().clone()), now());
+ assert!(out.as_dgram_ref().is_some());
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_none());
+
+ assert!(maybe_authenticate(&mut client));
+
+ qdebug!("---- client: SH..FIN -> FIN");
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+
+ assert_eq!(3, client.stats().packets_rx);
+ assert_eq!(0, client.stats().dups_rx);
+ assert_eq!(1, client.stats().dropped_rx);
+
+ qdebug!("---- Dup, ignored");
+ let out = client.process(out_to_rep.dgram(), now());
+ assert!(out.as_dgram_ref().is_none());
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+
+ // Four packets total received, 1 of them is a dup and one has been dropped because Initial keys
+ // are dropped. Add 2 counts of the padding that the server adds to Initial packets.
+ assert_eq!(6, client.stats().packets_rx);
+ assert_eq!(1, client.stats().dups_rx);
+ assert_eq!(3, client.stats().dropped_rx);
+}
+
+// Test that we split crypto data if they cannot fit into one packet.
+// To test this we will use a long server certificate.
+#[test]
+fn crypto_frame_split() {
+ let mut client = default_client();
+
+ let mut server = Connection::new_server(
+ test_fixture::LONG_CERT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(6))),
+ &ConnectionParameters::default(),
+ )
+ .expect("create a server");
+
+ let client1 = client.process(None, now());
+ assert!(client1.as_dgram_ref().is_some());
+
+ // The entire server flight doesn't fit in a single packet because the
+ // certificate is large, therefore the server will produce 2 packets.
+ let server1 = server.process(client1.dgram(), now());
+ assert!(server1.as_dgram_ref().is_some());
+ let server2 = server.process(None, now());
+ assert!(server2.as_dgram_ref().is_some());
+
+ let client2 = client.process(server1.dgram(), now());
+ // This is an ack.
+ assert!(client2.as_dgram_ref().is_some());
+ // The client might have the certificate now, so we can't guarantee that
+ // this will work.
+ let auth1 = maybe_authenticate(&mut client);
+ assert_eq!(*client.state(), State::Handshaking);
+
+ // let server process the ack for the first packet.
+ let server3 = server.process(client2.dgram(), now());
+ assert!(server3.as_dgram_ref().is_none());
+
+ // Consume the second packet from the server.
+ let client3 = client.process(server2.dgram(), now());
+
+ // Check authentication.
+ let auth2 = maybe_authenticate(&mut client);
+ assert!(auth1 ^ auth2);
+ // Now client has all data to finish handshake.
+ assert_eq!(*client.state(), State::Connected);
+
+ let client4 = client.process(server3.dgram(), now());
+ // One of these will contain data depending on whether Authentication was completed
+ // after the first or second server packet.
+ assert!(client3.as_dgram_ref().is_some() ^ client4.as_dgram_ref().is_some());
+
+ let _ = server.process(client3.dgram(), now());
+ let _ = server.process(client4.dgram(), now());
+
+ assert_eq!(*client.state(), State::Connected);
+ assert_eq!(*server.state(), State::Confirmed);
+}
+
+/// Run a single ChaCha20-Poly1305 test and get a PTO.
+#[test]
+fn chacha20poly1305() {
+ let mut server = default_server();
+ let mut client = Connection::new_client(
+ test_fixture::DEFAULT_SERVER_NAME,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(0))),
+ loopback(),
+ loopback(),
+ &ConnectionParameters::default(),
+ )
+ .expect("create a default client");
+ client.set_ciphers(&[TLS_CHACHA20_POLY1305_SHA256]).unwrap();
+ connect_force_idle(&mut client, &mut server);
+}
+
+/// Test that a server can send 0.5 RTT application data.
+#[test]
+fn send_05rtt() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let c1 = client.process(None, now()).dgram();
+ assert!(c1.is_some());
+ let s1 = server.process(c1, now()).dgram().unwrap();
+ assert_eq!(s1.len(), PATH_MTU_V6);
+
+ // The server should accept writes at this point.
+ let s2 = send_something(&mut server, now());
+
+ // Complete the handshake at the client.
+ client.process_input(s1, now());
+ maybe_authenticate(&mut client);
+ assert_eq!(*client.state(), State::Connected);
+
+ // The client should receive the 0.5-RTT data now.
+ client.process_input(s2, now());
+ let mut buf = vec![0; DEFAULT_STREAM_DATA.len() + 1];
+ let stream_id = client
+ .events()
+ .find_map(|e| {
+ if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
+ Some(stream_id)
+ } else {
+ None
+ }
+ })
+ .unwrap();
+ let (l, ended) = client.stream_recv(stream_id, &mut buf).unwrap();
+ assert_eq!(&buf[..l], DEFAULT_STREAM_DATA);
+ assert!(ended);
+}
+
+/// Test that a client buffers 0.5-RTT data when it arrives early.
+#[test]
+fn reorder_05rtt() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let c1 = client.process(None, now()).dgram();
+ assert!(c1.is_some());
+ let s1 = server.process(c1, now()).dgram().unwrap();
+
+ // The server should accept writes at this point.
+ let s2 = send_something(&mut server, now());
+
+ // We can't use the standard facility to complete the handshake, so
+ // drive it as aggressively as possible.
+ client.process_input(s2, now());
+ assert_eq!(client.stats().saved_datagrams, 1);
+
+ // After processing the first packet, the client should go back and
+ // process the 0.5-RTT packet data, which should make data available.
+ client.process_input(s1, now());
+ // We can't use `maybe_authenticate` here as that consumes events.
+ client.authenticated(AuthenticationStatus::Ok, now());
+ assert_eq!(*client.state(), State::Connected);
+
+ let mut buf = vec![0; DEFAULT_STREAM_DATA.len() + 1];
+ let stream_id = client
+ .events()
+ .find_map(|e| {
+ if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
+ Some(stream_id)
+ } else {
+ None
+ }
+ })
+ .unwrap();
+ let (l, ended) = client.stream_recv(stream_id, &mut buf).unwrap();
+ assert_eq!(&buf[..l], DEFAULT_STREAM_DATA);
+ assert!(ended);
+}
+
+#[test]
+fn reorder_05rtt_with_0rtt() {
+ const RTT: Duration = Duration::from_millis(100);
+
+ let mut client = default_client();
+ let mut server = default_server();
+ let validation = AddressValidation::new(now(), ValidateAddress::NoToken).unwrap();
+ let validation = Rc::new(RefCell::new(validation));
+ server.set_validation(Rc::clone(&validation));
+ let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT);
+
+ // Include RTT in sending the ticket or the ticket age reported by the
+ // client is wrong, which causes the server to reject 0-RTT.
+ now += RTT / 2;
+ server.send_ticket(now, &[]).unwrap();
+ let ticket = server.process_output(now).dgram().unwrap();
+ now += RTT / 2;
+ client.process_input(ticket, now);
+
+ let token = get_tokens(&mut client).pop().unwrap();
+ let mut client = default_client();
+ client.enable_resumption(now, token).unwrap();
+ let mut server = default_server();
+
+ // Send ClientHello and some 0-RTT.
+ let c1 = send_something(&mut client, now);
+ assertions::assert_coalesced_0rtt(&c1[..]);
+ // Drop the 0-RTT from the coalesced datagram, so that the server
+ // acknowledges the next 0-RTT packet.
+ let (c1, _) = split_datagram(&c1);
+ let c2 = send_something(&mut client, now);
+
+ // Handle the first packet and send 0.5-RTT in response. Drop the response.
+ now += RTT / 2;
+ let _ = server.process(Some(c1), now).dgram().unwrap();
+ // The gap in 0-RTT will result in this 0.5 RTT containing an ACK.
+ server.process_input(c2, now);
+ let s2 = send_something(&mut server, now);
+
+ // Save the 0.5 RTT.
+ now += RTT / 2;
+ client.process_input(s2, now);
+ assert_eq!(client.stats().saved_datagrams, 1);
+
+ // Now PTO at the client and cause the server to re-send handshake packets.
+ now += AT_LEAST_PTO;
+ let c3 = client.process(None, now).dgram();
+
+ now += RTT / 2;
+ let s3 = server.process(c3, now).dgram().unwrap();
+ assertions::assert_no_1rtt(&s3[..]);
+
+ // The client should be able to process the 0.5 RTT now.
+ // This should contain an ACK, so we are processing an ACK from the past.
+ now += RTT / 2;
+ client.process_input(s3, now);
+ maybe_authenticate(&mut client);
+ let c4 = client.process(None, now).dgram();
+ assert_eq!(*client.state(), State::Connected);
+ assert_eq!(client.loss_recovery.rtt(), RTT);
+
+ now += RTT / 2;
+ server.process_input(c4.unwrap(), now);
+ assert_eq!(*server.state(), State::Confirmed);
+ assert_eq!(server.loss_recovery.rtt(), RTT);
+}
+
+/// Test that a server that coalesces 0.5 RTT with handshake packets
+/// doesn't cause the client to drop application data.
+#[test]
+fn coalesce_05rtt() {
+ const RTT: Duration = Duration::from_millis(100);
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = now();
+
+ // The first exchange doesn't offer a chance for the server to send.
+ // So drop the server flight and wait for the PTO.
+ let c1 = client.process(None, now).dgram();
+ assert!(c1.is_some());
+ now += RTT / 2;
+ let s1 = server.process(c1, now).dgram();
+ assert!(s1.is_some());
+
+ // Drop the server flight. Then send some data.
+ let stream_id = server.stream_create(StreamType::UniDi).unwrap();
+ assert!(server.stream_send(stream_id, DEFAULT_STREAM_DATA).is_ok());
+ assert!(server.stream_close_send(stream_id).is_ok());
+
+ // Now after a PTO the client can send another packet.
+ // The server should then send its entire flight again,
+ // including the application data, which it sends in a 1-RTT packet.
+ now += AT_LEAST_PTO;
+ let c2 = client.process(None, now).dgram();
+ assert!(c2.is_some());
+ now += RTT / 2;
+ let s2 = server.process(c2, now).dgram();
+ // Even though there is a 1-RTT packet at the end of the datagram, the
+ // flight should be padded to full size.
+ assert_eq!(s2.as_ref().unwrap().len(), PATH_MTU_V6);
+
+ // The client should process the datagram. It can't process the 1-RTT
+ // packet until authentication completes though. So it saves it.
+ now += RTT / 2;
+ assert_eq!(client.stats().dropped_rx, 0);
+ let _ = client.process(s2, now).dgram();
+ // This packet will contain an ACK, but we can ignore it.
+ assert_eq!(client.stats().dropped_rx, 0);
+ assert_eq!(client.stats().packets_rx, 3);
+ assert_eq!(client.stats().saved_datagrams, 1);
+
+ // After (successful) authentication, the packet is processed.
+ maybe_authenticate(&mut client);
+ let c3 = client.process(None, now).dgram();
+ assert!(c3.is_some());
+ assert_eq!(client.stats().dropped_rx, 0); // No Initial padding.
+ assert_eq!(client.stats().packets_rx, 4);
+ assert_eq!(client.stats().saved_datagrams, 1);
+ assert_eq!(client.stats().frame_rx.padding, 1); // Padding uses frames.
+
+ // Allow the handshake to complete.
+ now += RTT / 2;
+ let s3 = server.process(c3, now).dgram();
+ assert!(s3.is_some());
+ assert_eq!(*server.state(), State::Confirmed);
+ now += RTT / 2;
+ let _ = client.process(s3, now).dgram();
+ assert_eq!(*client.state(), State::Confirmed);
+
+ assert_eq!(client.stats().dropped_rx, 0); // No dropped packets.
+}
+
+#[test]
+fn reorder_handshake() {
+ const RTT: Duration = Duration::from_millis(100);
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = now();
+
+ let c1 = client.process(None, now).dgram();
+ assert!(c1.is_some());
+
+ now += RTT / 2;
+ let s1 = server.process(c1, now).dgram();
+ assert!(s1.is_some());
+
+ // Drop the Initial packet from this.
+ let (_, s_hs) = split_datagram(&s1.unwrap());
+ assert!(s_hs.is_some());
+
+ // Pass just the handshake packet in and the client can't handle it yet.
+ // It can only send another Initial packet.
+ now += RTT / 2;
+ let dgram = client.process(s_hs, now).dgram();
+ assertions::assert_initial(&dgram.as_ref().unwrap(), false);
+ assert_eq!(client.stats().saved_datagrams, 1);
+ assert_eq!(client.stats().packets_rx, 1);
+
+ // Get the server to try again.
+ // Though we currently allow the server to arm its PTO timer, use
+ // a second client Initial packet to cause it to send again.
+ now += AT_LEAST_PTO;
+ let c2 = client.process(None, now).dgram();
+ now += RTT / 2;
+ let s2 = server.process(c2, now).dgram();
+ assert!(s2.is_some());
+
+ let (s_init, s_hs) = split_datagram(&s2.unwrap());
+ assert!(s_hs.is_some());
+
+ // Processing the Handshake packet first should save it.
+ now += RTT / 2;
+ client.process_input(s_hs.unwrap(), now);
+ assert_eq!(client.stats().saved_datagrams, 2);
+ assert_eq!(client.stats().packets_rx, 2);
+
+ client.process_input(s_init, now);
+ // Each saved packet should now be "received" again.
+ assert_eq!(client.stats().packets_rx, 7);
+ maybe_authenticate(&mut client);
+ let c3 = client.process(None, now).dgram();
+ assert!(c3.is_some());
+
+ // Note that though packets were saved and processed very late,
+ // they don't cause the RTT to change.
+ now += RTT / 2;
+ let s3 = server.process(c3, now).dgram();
+ assert_eq!(*server.state(), State::Confirmed);
+ assert_eq!(server.loss_recovery.rtt(), RTT);
+
+ now += RTT / 2;
+ client.process_input(s3.unwrap(), now);
+ assert_eq!(*client.state(), State::Confirmed);
+ assert_eq!(client.loss_recovery.rtt(), RTT);
+}
+
+#[test]
+fn reorder_1rtt() {
+ const RTT: Duration = Duration::from_millis(100);
+ const PACKETS: usize = 6; // Many, but not enough to overflow cwnd.
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = now();
+
+ let c1 = client.process(None, now).dgram();
+ assert!(c1.is_some());
+
+ now += RTT / 2;
+ let s1 = server.process(c1, now).dgram();
+ assert!(s1.is_some());
+
+ now += RTT / 2;
+ client.process_input(s1.unwrap(), now);
+ maybe_authenticate(&mut client);
+ let c2 = client.process(None, now).dgram();
+ assert!(c2.is_some());
+
+ // Now get a bunch of packets from the client.
+ // Give them to the server before giving it `c2`.
+ for _ in 0..PACKETS {
+ let d = send_something(&mut client, now);
+ server.process_input(d, now + RTT / 2);
+ }
+ // The server has now received those packets, and saved them.
+ // The two extra received are Initial + the junk we use for padding.
+ assert_eq!(server.stats().packets_rx, PACKETS + 2);
+ assert_eq!(server.stats().saved_datagrams, PACKETS);
+ assert_eq!(server.stats().dropped_rx, 1);
+
+ now += RTT / 2;
+ let s2 = server.process(c2, now).dgram();
+ // The server has now received those packets, and saved them.
+ // The two additional are an Initial ACK and Handshake.
+ assert_eq!(server.stats().packets_rx, PACKETS * 2 + 4);
+ assert_eq!(server.stats().saved_datagrams, PACKETS);
+ assert_eq!(server.stats().dropped_rx, 1);
+ assert_eq!(*server.state(), State::Confirmed);
+ assert_eq!(server.loss_recovery.rtt(), RTT);
+
+ now += RTT / 2;
+ client.process_input(s2.unwrap(), now);
+ assert_eq!(client.loss_recovery.rtt(), RTT);
+
+ // All the stream data that was sent should now be available.
+ let streams = server
+ .events()
+ .filter_map(|e| {
+ if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
+ Some(stream_id)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+ assert_eq!(streams.len(), PACKETS);
+ for stream_id in streams {
+ let mut buf = vec![0; DEFAULT_STREAM_DATA.len() + 1];
+ let (recvd, fin) = server.stream_recv(stream_id, &mut buf).unwrap();
+ assert_eq!(recvd, DEFAULT_STREAM_DATA.len());
+ assert!(fin);
+ }
+}
+
+#[test]
+fn corrupted_initial() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let d = client.process(None, now()).dgram().unwrap();
+ let mut corrupted = Vec::from(&d[..]);
+ // Find the last non-zero value and corrupt that.
+ let (idx, _) = corrupted
+ .iter()
+ .enumerate()
+ .rev()
+ .find(|(_, &v)| v != 0)
+ .unwrap();
+ corrupted[idx] ^= 0x76;
+ let dgram = Datagram::new(d.source(), d.destination(), corrupted);
+ server.process_input(dgram, now());
+ // The server should have received two packets,
+ // the first should be dropped, the second saved.
+ assert_eq!(server.stats().packets_rx, 2);
+ assert_eq!(server.stats().dropped_rx, 2);
+ assert_eq!(server.stats().saved_datagrams, 0);
+}
+
+#[test]
+// Absent path PTU discovery, max v6 packet size should be PATH_MTU_V6.
+fn verify_pkt_honors_mtu() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ // Try to send a large stream and verify first packet is correctly sized
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_send(2, &[0xbb; 2000]).unwrap(), 2000);
+ let pkt0 = client.process(None, now);
+ assert!(matches!(pkt0, Output::Datagram(_)));
+ assert_eq!(pkt0.as_dgram_ref().unwrap().len(), PATH_MTU_V6);
+}
+
+#[test]
+fn extra_initial_hs() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = now();
+
+ let c_init = client.process(None, now).dgram();
+ assert!(c_init.is_some());
+ now += DEFAULT_RTT / 2;
+ let s_init = server.process(c_init, now).dgram();
+ assert!(s_init.is_some());
+ now += DEFAULT_RTT / 2;
+
+ // Drop the Initial packet, keep only the Handshake.
+ let (_, undecryptable) = split_datagram(&s_init.unwrap());
+ assert!(undecryptable.is_some());
+
+ // Feed the same undecryptable packet into the client a few times.
+ // Do that EXTRA_INITIALS times and each time the client will emit
+ // another Initial packet.
+ for _ in 0..=super::super::EXTRA_INITIALS {
+ let c_init = client.process(undecryptable.clone(), now).dgram();
+ assertions::assert_initial(&c_init.as_ref().unwrap(), false);
+ now += DEFAULT_RTT / 10;
+ }
+
+ // After EXTRA_INITIALS, the client stops sending Initial packets.
+ let nothing = client.process(undecryptable, now).dgram();
+ assert!(nothing.is_none());
+
+ // Until PTO, where another Initial can be used to complete the handshake.
+ now += AT_LEAST_PTO;
+ let c_init = client.process(None, now).dgram();
+ assertions::assert_initial(c_init.as_ref().unwrap(), false);
+ now += DEFAULT_RTT / 2;
+ let s_init = server.process(c_init, now).dgram();
+ now += DEFAULT_RTT / 2;
+ client.process_input(s_init.unwrap(), now);
+ maybe_authenticate(&mut client);
+ let c_fin = client.process_output(now).dgram();
+ assert_eq!(*client.state(), State::Connected);
+ now += DEFAULT_RTT / 2;
+ server.process_input(c_fin.unwrap(), now);
+ assert_eq!(*server.state(), State::Confirmed);
+}
+
+#[test]
+fn extra_initial_invalid_cid() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = now();
+
+ let c_init = client.process(None, now).dgram();
+ assert!(c_init.is_some());
+ now += DEFAULT_RTT / 2;
+ let s_init = server.process(c_init, now).dgram();
+ assert!(s_init.is_some());
+ now += DEFAULT_RTT / 2;
+
+ // If the client receives a packet that contains the wrong connection
+ // ID, it won't send another Initial.
+ let (_, hs) = split_datagram(&s_init.unwrap());
+ let hs = hs.unwrap();
+ let mut copy = hs.to_vec();
+ assert_ne!(copy[5], 0); // The DCID should be non-zero length.
+ copy[6] ^= 0xc4;
+ let dgram_copy = Datagram::new(hs.destination(), hs.source(), copy);
+ let nothing = client.process(Some(dgram_copy), now).dgram();
+ assert!(nothing.is_none());
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/idle.rs b/third_party/rust/neqo-transport/src/connection/tests/idle.rs
new file mode 100644
index 0000000000..962645eff3
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/idle.rs
@@ -0,0 +1,274 @@
+// 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::super::{IdleTimeout, Output, State, LOCAL_IDLE_TIMEOUT};
+use super::{
+ connect, connect_force_idle, connect_with_rtt, default_client, default_server,
+ maybe_authenticate, send_something, AT_LEAST_PTO,
+};
+use crate::frame::StreamType;
+use crate::packet::PacketBuilder;
+use crate::tparams::{self, TransportParameter};
+use crate::tracking::PNSpace;
+
+use neqo_common::Encoder;
+use std::time::Duration;
+use test_fixture::{self, now, split_datagram};
+
+#[test]
+fn idle_timeout() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ // Still connected after 29 seconds. Idle timer not reset
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT - Duration::from_secs(1));
+ assert!(matches!(client.state(), State::Confirmed));
+
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT);
+
+ // Not connected after LOCAL_IDLE_TIMEOUT seconds.
+ assert!(matches!(client.state(), State::Closed(_)));
+}
+
+#[test]
+fn asymmetric_idle_timeout() {
+ const LOWER_TIMEOUT_MS: u64 = 1000;
+ const LOWER_TIMEOUT: Duration = Duration::from_millis(LOWER_TIMEOUT_MS);
+ // Sanity check the constant.
+ assert!(LOWER_TIMEOUT < LOCAL_IDLE_TIMEOUT);
+
+ let mut client = default_client();
+ let mut server = default_server();
+
+ // Overwrite the default at the server.
+ server
+ .tps
+ .borrow_mut()
+ .local
+ .set_integer(tparams::IDLE_TIMEOUT, LOWER_TIMEOUT_MS);
+ server.idle_timeout = IdleTimeout::new(LOWER_TIMEOUT);
+
+ // Now connect and force idleness manually.
+ connect(&mut client, &mut server);
+ let p1 = send_something(&mut server, now());
+ let p2 = send_something(&mut server, now());
+ client.process_input(p2, now());
+ let ack = client.process(Some(p1), now()).dgram();
+ assert!(ack.is_some());
+ // Now the server has its ACK and both should be idle.
+ assert_eq!(server.process(ack, now()), Output::Callback(LOWER_TIMEOUT));
+ assert_eq!(client.process(None, now()), Output::Callback(LOWER_TIMEOUT));
+}
+
+#[test]
+fn tiny_idle_timeout() {
+ const RTT: Duration = Duration::from_millis(500);
+ const LOWER_TIMEOUT_MS: u64 = 100;
+ const LOWER_TIMEOUT: Duration = Duration::from_millis(LOWER_TIMEOUT_MS);
+ // We won't respect a value that is lower than 3*PTO, sanity check.
+ assert!(LOWER_TIMEOUT < 3 * RTT);
+
+ let mut client = default_client();
+ let mut server = default_server();
+
+ // Overwrite the default at the server.
+ server
+ .set_local_tparam(
+ tparams::IDLE_TIMEOUT,
+ TransportParameter::Integer(LOWER_TIMEOUT_MS),
+ )
+ .unwrap();
+ server.idle_timeout = IdleTimeout::new(LOWER_TIMEOUT);
+
+ // Now connect with an RTT and force idleness manually.
+ let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT);
+ let p1 = send_something(&mut server, now);
+ let p2 = send_something(&mut server, now);
+ now += RTT / 2;
+ client.process_input(p2, now);
+ let ack = client.process(Some(p1), now).dgram();
+ assert!(ack.is_some());
+
+ // The client should be idle now, but with a different timer.
+ if let Output::Callback(t) = client.process(None, now) {
+ assert!(t > LOWER_TIMEOUT);
+ } else {
+ panic!("Client not idle");
+ }
+
+ // The server should go idle after the ACK, but again with a larger timeout.
+ now += RTT / 2;
+ if let Output::Callback(t) = client.process(ack, now) {
+ assert!(t > LOWER_TIMEOUT);
+ } else {
+ panic!("Client not idle");
+ }
+}
+
+#[test]
+fn idle_send_packet1() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_send(2, b"hello").unwrap(), 5);
+
+ let out = client.process(None, now + Duration::from_secs(10));
+ let out = server.process(out.dgram(), now + Duration::from_secs(10));
+
+ // Still connected after 39 seconds because idle timer reset by outgoing
+ // packet
+ let _ = client.process(
+ out.dgram(),
+ now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(9),
+ );
+ assert!(matches!(client.state(), State::Confirmed));
+
+ // Not connected after 40 seconds.
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(10));
+
+ assert!(matches!(client.state(), State::Closed(_)));
+}
+
+#[test]
+fn idle_send_packet2() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_send(2, b"hello").unwrap(), 5);
+
+ let _out = client.process(None, now + Duration::from_secs(10));
+
+ assert_eq!(client.stream_send(2, b"there").unwrap(), 5);
+ let _out = client.process(None, now + Duration::from_secs(20));
+
+ // Still connected after 39 seconds.
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(9));
+ assert!(matches!(client.state(), State::Confirmed));
+
+ // Not connected after 40 seconds because timer not reset by second
+ // outgoing packet
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(10));
+ assert!(matches!(client.state(), State::Closed(_)));
+}
+
+#[test]
+fn idle_recv_packet() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+ assert_eq!(client.stream_send(0, b"hello").unwrap(), 5);
+
+ // Respond with another packet
+ let out = client.process(None, now + Duration::from_secs(10));
+ server.process_input(out.dgram().unwrap(), now + Duration::from_secs(10));
+ assert_eq!(server.stream_send(0, b"world").unwrap(), 5);
+ let out = server.process_output(now + Duration::from_secs(10));
+ assert_ne!(out.as_dgram_ref(), None);
+
+ let _ = client.process(out.dgram(), now + Duration::from_secs(20));
+ assert!(matches!(client.state(), State::Confirmed));
+
+ // Still connected after 49 seconds because idle timer reset by received
+ // packet
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(19));
+ assert!(matches!(client.state(), State::Confirmed));
+
+ // Not connected after 50 seconds.
+ let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(20));
+
+ assert!(matches!(client.state(), State::Closed(_)));
+}
+
+/// Caching packets should not cause the connection to become idle.
+/// This requires a few tricks to keep the connection from going
+/// idle while preventing any progress on the handshake.
+#[test]
+fn idle_caching() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let start = now();
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+
+ // Perform the first round trip, but drop the Initial from the server.
+ // The client then caches the Handshake packet.
+ let dgram = client.process_output(start).dgram();
+ let dgram = server.process(dgram, start).dgram();
+ let (_, handshake) = split_datagram(&dgram.unwrap());
+ client.process_input(handshake.unwrap(), start);
+
+ // Perform an exchange and keep the connection alive.
+ // Only allow a packet containing a PING to pass.
+ let middle = start + AT_LEAST_PTO;
+ let _ = client.process_output(middle);
+ let dgram = client.process_output(middle).dgram();
+
+ // Get the server to send its first probe and throw that away.
+ let _ = server.process_output(middle).dgram();
+ // Now let the server process the client PING. This causes the server
+ // to send CRYPTO frames again, so manually extract and discard those.
+ let ping_before_s = server.stats().frame_rx.ping;
+ server.process_input(dgram.unwrap(), middle);
+ assert_eq!(server.stats().frame_rx.ping, ping_before_s + 1);
+ let crypto = server
+ .crypto
+ .streams
+ .write_frame(PNSpace::Initial, &mut builder);
+ assert!(crypto.is_some());
+ let crypto = server
+ .crypto
+ .streams
+ .write_frame(PNSpace::Initial, &mut builder);
+ assert!(crypto.is_none());
+ let dgram = server.process_output(middle).dgram();
+
+ // Now only allow the Initial packet from the server through;
+ // it shouldn't contain a CRYPTO frame.
+ let (initial, _) = split_datagram(&dgram.unwrap());
+ let ping_before_c = client.stats().frame_rx.ping;
+ let ack_before = client.stats().frame_rx.ack;
+ client.process_input(initial, middle);
+ assert_eq!(client.stats().frame_rx.ping, ping_before_c + 1);
+ assert_eq!(client.stats().frame_rx.ack, ack_before + 1);
+
+ let end = start + LOCAL_IDLE_TIMEOUT + (AT_LEAST_PTO / 2);
+ // Now let the server Initial through, with the CRYPTO frame.
+ let dgram = server.process_output(end).dgram();
+ let (initial, _) = split_datagram(&dgram.unwrap());
+ let _ = client.process(Some(initial), end);
+ maybe_authenticate(&mut client);
+ let dgram = client.process_output(end).dgram();
+ let dgram = server.process(dgram, end).dgram();
+ client.process_input(dgram.unwrap(), end);
+ assert_eq!(*client.state(), State::Confirmed);
+ assert_eq!(*server.state(), State::Confirmed);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/keys.rs b/third_party/rust/neqo-transport/src/connection/tests/keys.rs
new file mode 100644
index 0000000000..cc572e85ca
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/keys.rs
@@ -0,0 +1,330 @@
+// 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::super::super::{ConnectionError, ERROR_AEAD_LIMIT_REACHED};
+use super::super::{Connection, Error, Output, State, StreamType, LOCAL_IDLE_TIMEOUT};
+use super::{
+ connect, connect_force_idle, default_client, default_server, maybe_authenticate,
+ send_and_receive, send_something, AT_LEAST_PTO,
+};
+use crate::crypto::{OVERWRITE_INVOCATIONS, UPDATE_WRITE_KEYS_AT};
+use crate::packet::PacketNumber;
+use crate::path::PATH_MTU_V6;
+
+use neqo_common::{qdebug, Datagram};
+use test_fixture::{self, now};
+
+fn check_discarded(peer: &mut Connection, pkt: Datagram, dropped: usize, dups: usize) {
+ // Make sure to flush any saved datagrams before doing this.
+ let _ = peer.process_output(now());
+
+ let before = peer.stats();
+ let out = peer.process(Some(pkt), now());
+ assert!(out.as_dgram_ref().is_none());
+ let after = peer.stats();
+ assert_eq!(dropped, after.dropped_rx - before.dropped_rx);
+ assert_eq!(dups, after.dups_rx - before.dups_rx);
+}
+
+fn assert_update_blocked(c: &mut Connection) {
+ assert_eq!(
+ c.initiate_key_update().unwrap_err(),
+ Error::KeyUpdateBlocked
+ )
+}
+
+fn overwrite_invocations(n: PacketNumber) {
+ OVERWRITE_INVOCATIONS.with(|v| {
+ *v.borrow_mut() = Some(n);
+ });
+}
+
+#[test]
+fn discarded_initial_keys() {
+ qdebug!("---- client: generate CH");
+ let mut client = default_client();
+ let init_pkt_c = client.process(None, now()).dgram();
+ assert!(init_pkt_c.is_some());
+ assert_eq!(init_pkt_c.as_ref().unwrap().len(), PATH_MTU_V6);
+
+ qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN");
+ let mut server = default_server();
+ let init_pkt_s = server.process(init_pkt_c.clone(), now()).dgram();
+ assert!(init_pkt_s.is_some());
+
+ qdebug!("---- client: cert verification");
+ let out = client.process(init_pkt_s.clone(), now()).dgram();
+ assert!(out.is_some());
+
+ // The client has received handshake packet. It will remove the Initial keys.
+ // We will check this by processing init_pkt_s a second time.
+ // The initial packet should be dropped. The packet contains a Handshake packet as well, which
+ // will be marked as dup. And it will contain padding, which will be "dropped".
+ check_discarded(&mut client, init_pkt_s.unwrap(), 2, 1);
+
+ assert!(maybe_authenticate(&mut client));
+
+ // The server has not removed the Initial keys yet, because it has not yet received a Handshake
+ // packet from the client.
+ // We will check this by processing init_pkt_c a second time.
+ // The dropped packet is padding. The Initial packet has been mark dup.
+ check_discarded(&mut server, init_pkt_c.clone().unwrap(), 1, 1);
+
+ qdebug!("---- client: SH..FIN -> FIN");
+ let out = client.process(None, now()).dgram();
+ assert!(out.is_some());
+
+ // The server will process the first Handshake packet.
+ // After this the Initial keys will be dropped.
+ let out = server.process(out, now()).dgram();
+ assert!(out.is_some());
+
+ // Check that the Initial keys are dropped at the server
+ // We will check this by processing init_pkt_c a third time.
+ // The Initial packet has been dropped and padding that follows it.
+ // There is no dups, everything has been dropped.
+ check_discarded(&mut server, init_pkt_c.unwrap(), 1, 0);
+}
+
+#[test]
+fn key_update_client() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+ let mut now = now();
+
+ assert_eq!(client.get_epochs(), (Some(3), Some(3))); // (write, read)
+ assert_eq!(server.get_epochs(), (Some(3), Some(3)));
+
+ assert!(client.initiate_key_update().is_ok());
+ assert_update_blocked(&mut client);
+
+ // Initiating an update should only increase the write epoch.
+ assert_eq!(
+ Output::Callback(LOCAL_IDLE_TIMEOUT),
+ client.process(None, now)
+ );
+ assert_eq!(client.get_epochs(), (Some(4), Some(3)));
+
+ // Send something to propagate the update.
+ assert!(send_and_receive(&mut client, &mut server, now).is_none());
+
+ // The server should now be waiting to discharge read keys.
+ assert_eq!(server.get_epochs(), (Some(4), Some(3)));
+ let res = server.process(None, now);
+ if let Output::Callback(t) = res {
+ assert!(t < LOCAL_IDLE_TIMEOUT);
+ } else {
+ panic!("server should now be waiting to clear keys");
+ }
+
+ // Without having had time to purge old keys, more updates are blocked.
+ // The spec would permits it at this point, but we are more conservative.
+ assert_update_blocked(&mut client);
+ // The server can't update until it receives an ACK for a packet.
+ assert_update_blocked(&mut server);
+
+ // Waiting now for at least a PTO should cause the server to drop old keys.
+ // But at this point the client hasn't received a key update from the server.
+ // It will be stuck with old keys.
+ now += AT_LEAST_PTO;
+ let dgram = client.process(None, now).dgram();
+ assert!(dgram.is_some()); // Drop this packet.
+ assert_eq!(client.get_epochs(), (Some(4), Some(3)));
+ let _ = server.process(None, now);
+ assert_eq!(server.get_epochs(), (Some(4), Some(4)));
+
+ // Even though the server has updated, it hasn't received an ACK yet.
+ assert_update_blocked(&mut server);
+
+ // Now get an ACK from the server.
+ // The previous PTO packet (see above) was dropped, so we should get an ACK here.
+ let dgram = send_and_receive(&mut client, &mut server, now);
+ assert!(dgram.is_some());
+ let res = client.process(dgram, now);
+ // This is the first packet that the client has received from the server
+ // with new keys, so its read timer just started.
+ if let Output::Callback(t) = res {
+ assert!(t < LOCAL_IDLE_TIMEOUT);
+ } else {
+ panic!("client should now be waiting to clear keys");
+ }
+
+ assert_update_blocked(&mut client);
+ assert_eq!(client.get_epochs(), (Some(4), Some(3)));
+ // The server can't update until it gets something from the client.
+ assert_update_blocked(&mut server);
+
+ now += AT_LEAST_PTO;
+ let _ = client.process(None, now);
+ assert_eq!(client.get_epochs(), (Some(4), Some(4)));
+}
+
+#[test]
+fn key_update_consecutive() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+ let now = now();
+
+ assert!(server.initiate_key_update().is_ok());
+ assert_eq!(server.get_epochs(), (Some(4), Some(3)));
+
+ // Server sends something.
+ // Send twice and drop the first to induce an ACK from the client.
+ let _ = send_something(&mut server, now); // Drop this.
+
+ // Another packet from the server will cause the client to ACK and update keys.
+ let dgram = send_and_receive(&mut server, &mut client, now);
+ assert!(dgram.is_some());
+ assert_eq!(client.get_epochs(), (Some(4), Some(3)));
+
+ // Have the server process the ACK.
+ if let Output::Callback(_) = server.process(dgram, now) {
+ assert_eq!(server.get_epochs(), (Some(4), Some(3)));
+ // Now move the server temporarily into the future so that it
+ // rotates the keys. The client stays in the present.
+ let _ = server.process(None, now + AT_LEAST_PTO);
+ assert_eq!(server.get_epochs(), (Some(4), Some(4)));
+ } else {
+ panic!("server should have a timer set");
+ }
+
+ // Now update keys on the server again.
+ assert!(server.initiate_key_update().is_ok());
+ assert_eq!(server.get_epochs(), (Some(5), Some(4)));
+
+ let dgram = send_something(&mut server, now + AT_LEAST_PTO);
+
+ // However, as the server didn't wait long enough to update again, the
+ // client hasn't rotated its keys, so the packet gets dropped.
+ check_discarded(&mut client, dgram, 1, 0);
+}
+
+// Key updates can't be initiated too early.
+#[test]
+fn key_update_before_confirmed() {
+ let mut client = default_client();
+ assert_update_blocked(&mut client);
+ let mut server = default_server();
+ assert_update_blocked(&mut server);
+
+ // Client Initial
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+ assert_update_blocked(&mut client);
+
+ // Server Initial + Handshake
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_some());
+ assert_update_blocked(&mut server);
+
+ // Client Handshake
+ client.process_input(dgram.unwrap(), now());
+ assert_update_blocked(&mut client);
+
+ assert!(maybe_authenticate(&mut client));
+ assert_update_blocked(&mut client);
+
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+ assert_update_blocked(&mut client);
+
+ // Server HANDSHAKE_DONE
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_some());
+ assert!(server.initiate_key_update().is_ok());
+
+ // Client receives HANDSHAKE_DONE
+ let dgram = client.process(dgram, now()).dgram();
+ assert!(dgram.is_none());
+ assert!(client.initiate_key_update().is_ok());
+}
+
+#[test]
+fn exhaust_write_keys() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ overwrite_invocations(0);
+ let stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ assert!(client.stream_send(stream_id, b"explode!").is_ok());
+ let dgram = client.process_output(now()).dgram();
+ assert!(dgram.is_none());
+ assert!(matches!(
+ client.state(),
+ State::Closed(ConnectionError::Transport(Error::KeysExhausted))
+ ));
+}
+
+#[test]
+fn exhaust_read_keys() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let dgram = send_something(&mut client, now());
+
+ overwrite_invocations(0);
+ let dgram = server.process(Some(dgram), now()).dgram();
+ assert!(matches!(
+ server.state(),
+ State::Closed(ConnectionError::Transport(Error::KeysExhausted))
+ ));
+
+ client.process_input(dgram.unwrap(), now());
+ assert!(matches!(client.state(), State::Draining {
+ error: ConnectionError::Transport(Error::PeerError(ERROR_AEAD_LIMIT_REACHED)), ..
+ }));
+}
+
+#[test]
+fn automatic_update_write_keys() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ overwrite_invocations(UPDATE_WRITE_KEYS_AT);
+ let _ = send_something(&mut client, now());
+ assert_eq!(client.get_epochs(), (Some(4), Some(3)));
+}
+
+#[test]
+fn automatic_update_write_keys_later() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ overwrite_invocations(UPDATE_WRITE_KEYS_AT + 2);
+ // No update after the first.
+ let _ = send_something(&mut client, now());
+ assert_eq!(client.get_epochs(), (Some(3), Some(3)));
+ // The second will update though.
+ let _ = send_something(&mut client, now());
+ assert_eq!(client.get_epochs(), (Some(4), Some(3)));
+}
+
+#[test]
+fn automatic_update_write_keys_blocked() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ // An outstanding key update will block the automatic update.
+ client.initiate_key_update().unwrap();
+
+ overwrite_invocations(UPDATE_WRITE_KEYS_AT);
+ let stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ assert!(client.stream_send(stream_id, b"explode!").is_ok());
+ let dgram = client.process_output(now()).dgram();
+ // Not being able to update is fatal.
+ assert!(dgram.is_none());
+ assert!(matches!(
+ client.state(),
+ State::Closed(ConnectionError::Transport(Error::KeysExhausted))
+ ));
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/mod.rs b/third_party/rust/neqo-transport/src/connection/tests/mod.rs
new file mode 100644
index 0000000000..20add0faad
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/mod.rs
@@ -0,0 +1,305 @@
+// 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.
+
+#![deny(clippy::pedantic)]
+
+use super::{
+ Connection, ConnectionError, FixedConnectionIdManager, Output, State, LOCAL_IDLE_TIMEOUT,
+};
+use crate::addr_valid::{AddressValidation, ValidateAddress};
+use crate::cc::CWND_INITIAL_PKTS;
+use crate::events::ConnectionEvent;
+use crate::frame::StreamType;
+use crate::path::PATH_MTU_V6;
+use crate::recovery::ACK_ONLY_SIZE_LIMIT;
+use crate::ConnectionParameters;
+
+use std::cell::RefCell;
+use std::mem;
+use std::rc::Rc;
+use std::time::{Duration, Instant};
+
+use neqo_common::{event::Provider, qdebug, qtrace, Datagram};
+use neqo_crypto::{AllowZeroRtt, AuthenticationStatus, ResumptionToken};
+use test_fixture::{self, fixture_init, loopback, now};
+
+// All the tests.
+mod cc;
+mod close;
+mod handshake;
+mod idle;
+mod keys;
+mod recovery;
+mod resumption;
+mod stream;
+mod vn;
+mod zerortt;
+
+const DEFAULT_RTT: Duration = Duration::from_millis(100);
+const AT_LEAST_PTO: Duration = Duration::from_secs(1);
+const DEFAULT_STREAM_DATA: &[u8] = b"message";
+
+// This is fabulous: because test_fixture uses the public API for Connection,
+// it gets a different type to the ones that are referenced via super::super::*.
+// Thus, this code can't use default_client() and default_server() from
+// test_fixture because they produce different types.
+//
+// These are a direct copy of those functions.
+pub fn default_client() -> Connection {
+ fixture_init();
+ Connection::new_client(
+ test_fixture::DEFAULT_SERVER_NAME,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(3))),
+ loopback(),
+ loopback(),
+ &ConnectionParameters::default(),
+ )
+ .expect("create a default client")
+}
+pub fn default_server() -> Connection {
+ fixture_init();
+
+ let mut c = Connection::new_server(
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(5))),
+ &ConnectionParameters::default(),
+ )
+ .expect("create a default server");
+ c.server_enable_0rtt(&test_fixture::anti_replay(), AllowZeroRtt {})
+ .expect("enable 0-RTT");
+ c
+}
+
+/// If state is `AuthenticationNeeded` call `authenticated()`. This function will
+/// consume all outstanding events on the connection.
+pub fn maybe_authenticate(conn: &mut Connection) -> bool {
+ let authentication_needed = |e| matches!(e, ConnectionEvent::AuthenticationNeeded);
+ if conn.events().any(authentication_needed) {
+ conn.authenticated(AuthenticationStatus::Ok, now());
+ return true;
+ }
+ false
+}
+
+/// Drive the handshake between the client and server.
+fn handshake(
+ client: &mut Connection,
+ server: &mut Connection,
+ now: Instant,
+ rtt: Duration,
+) -> Instant {
+ let mut a = client;
+ let mut b = server;
+ let mut now = now;
+
+ let mut input = None;
+ let is_done = |c: &mut Connection| matches!(c.state(), State::Confirmed | State::Closing { .. } | State::Closed(..));
+
+ while !is_done(a) {
+ let _ = maybe_authenticate(a);
+ let had_input = input.is_some();
+ let output = a.process(input, now).dgram();
+ assert!(had_input || output.is_some());
+ input = output;
+ qtrace!("t += {:?}", rtt / 2);
+ now += rtt / 2;
+ mem::swap(&mut a, &mut b);
+ }
+ let _ = a.process(input, now);
+ now
+}
+
+fn connect_with_rtt(
+ client: &mut Connection,
+ server: &mut Connection,
+ now: Instant,
+ rtt: Duration,
+) -> Instant {
+ let now = handshake(client, server, now, rtt);
+ assert_eq!(*client.state(), State::Confirmed);
+ assert_eq!(*client.state(), State::Confirmed);
+
+ assert_eq!(client.loss_recovery.rtt(), rtt);
+ assert_eq!(server.loss_recovery.rtt(), rtt);
+ now
+}
+
+fn connect(client: &mut Connection, server: &mut Connection) {
+ connect_with_rtt(client, server, now(), Duration::new(0, 0));
+}
+
+fn assert_error(c: &Connection, err: &ConnectionError) {
+ match c.state() {
+ State::Closing { error, .. } | State::Draining { error, .. } | State::Closed(error) => {
+ assert_eq!(*error, *err);
+ }
+ _ => panic!("bad state {:?}", c.state()),
+ }
+}
+
+fn exchange_ticket(
+ client: &mut Connection,
+ server: &mut Connection,
+ now: Instant,
+) -> ResumptionToken {
+ let validation = AddressValidation::new(now, ValidateAddress::NoToken).unwrap();
+ let validation = Rc::new(RefCell::new(validation));
+ server.set_validation(Rc::clone(&validation));
+ server.send_ticket(now, &[]).expect("can send ticket");
+ let ticket = server.process_output(now).dgram();
+ assert!(ticket.is_some());
+ client.process_input(ticket.unwrap(), now);
+ assert_eq!(*client.state(), State::Confirmed);
+ get_tokens(client).pop().expect("should have token")
+}
+
+/// Connect with an RTT and then force both peers to be idle.
+/// Getting the client and server to reach an idle state is surprisingly hard.
+/// The server sends `HANDSHAKE_DONE` at the end of the handshake, and the client
+/// doesn't immediately acknowledge it. Reordering packets does the trick.
+fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Duration) -> Instant {
+ let mut now = connect_with_rtt(client, server, now(), rtt);
+ let p1 = send_something(server, now);
+ let p2 = send_something(server, now);
+ now += rtt / 2;
+ // Delivering p2 first at the client causes it to want to ACK.
+ client.process_input(p2, now);
+ // Delivering p1 should not have the client change its mind about the ACK.
+ let ack = client.process(Some(p1), now).dgram();
+ assert!(ack.is_some());
+ assert_eq!(
+ server.process(ack, now),
+ Output::Callback(LOCAL_IDLE_TIMEOUT)
+ );
+ assert_eq!(
+ client.process_output(now),
+ Output::Callback(LOCAL_IDLE_TIMEOUT)
+ );
+ now
+}
+
+fn connect_force_idle(client: &mut Connection, server: &mut Connection) {
+ connect_rtt_idle(client, server, Duration::new(0, 0));
+}
+
+/// This fills the congestion window from a single source.
+/// As the pacer will interfere with this, this moves time forward
+/// as `Output::Callback` is received. Because it is hard to tell
+/// from the return value whether a timeout is an ACK delay, PTO, or
+/// pacing, this looks at the congestion window to tell when to stop.
+/// Returns a list of datagrams and the new time.
+fn fill_cwnd(src: &mut Connection, stream: u64, mut now: Instant) -> (Vec<Datagram>, Instant) {
+ const BLOCK_SIZE: usize = 4_096;
+ let mut total_dgrams = Vec::new();
+
+ qtrace!(
+ "fill_cwnd starting cwnd: {}",
+ src.loss_recovery.cwnd_avail()
+ );
+
+ loop {
+ let bytes_sent = src.stream_send(stream, &[0x42; BLOCK_SIZE]).unwrap();
+ qtrace!("fill_cwnd wrote {} bytes", bytes_sent);
+ if bytes_sent < BLOCK_SIZE {
+ break;
+ }
+ }
+
+ loop {
+ let pkt = src.process_output(now);
+ qtrace!(
+ "fill_cwnd cwnd remaining={}, output: {:?}",
+ src.loss_recovery.cwnd_avail(),
+ pkt
+ );
+ match pkt {
+ Output::Datagram(dgram) => {
+ total_dgrams.push(dgram);
+ }
+ Output::Callback(t) => {
+ if src.loss_recovery.cwnd_avail() < ACK_ONLY_SIZE_LIMIT {
+ break;
+ }
+ now += t;
+ }
+ Output::None => panic!(),
+ }
+ }
+
+ (total_dgrams, now)
+}
+
+/// This magic number is the size of the client's CWND after the handshake completes.
+/// This is the same as the initial congestion window, because during the handshake
+/// the cc is app limited and cwnd is not increased.
+///
+/// As we change how we build packets, or even as NSS changes,
+/// this number might be different. The tests that depend on this
+/// value could fail as a result of variations, so it's OK to just
+/// change this value, but it is good to first understand where the
+/// change came from.
+const POST_HANDSHAKE_CWND: usize = PATH_MTU_V6 * CWND_INITIAL_PKTS;
+
+/// Determine the number of packets required to fill the CWND.
+const fn cwnd_packets(data: usize) -> usize {
+ (data + ACK_ONLY_SIZE_LIMIT - 1) / PATH_MTU_V6
+}
+
+/// Determine the size of the last packet.
+/// The minimal size of a packet is `ACK_ONLY_SIZE_LIMIT`.
+fn last_packet(cwnd: usize) -> usize {
+ if (cwnd % PATH_MTU_V6) > ACK_ONLY_SIZE_LIMIT {
+ cwnd % PATH_MTU_V6
+ } else {
+ PATH_MTU_V6
+ }
+}
+
+/// Assert that the set of packets fill the CWND.
+fn assert_full_cwnd(packets: &[Datagram], cwnd: usize) {
+ assert_eq!(packets.len(), cwnd_packets(cwnd));
+ let (last, rest) = packets.split_last().unwrap();
+ assert!(rest.iter().all(|d| d.len() == PATH_MTU_V6));
+ assert_eq!(last.len(), last_packet(cwnd));
+}
+
+/// Send something on a stream from `sender` to `receiver`.
+/// Return the resulting datagram.
+#[must_use]
+fn send_something(sender: &mut Connection, now: Instant) -> Datagram {
+ let stream_id = sender.stream_create(StreamType::UniDi).unwrap();
+ assert!(sender.stream_send(stream_id, DEFAULT_STREAM_DATA).is_ok());
+ assert!(sender.stream_close_send(stream_id).is_ok());
+ qdebug!([sender], "send_something on {}", stream_id);
+ let dgram = sender.process(None, now).dgram();
+ dgram.expect("should have something to send")
+}
+
+/// Send something on a stream from `sender` to `receiver`.
+/// Return any ACK that might result.
+fn send_and_receive(
+ sender: &mut Connection,
+ receiver: &mut Connection,
+ now: Instant,
+) -> Option<Datagram> {
+ let dgram = send_something(sender, now);
+ receiver.process(Some(dgram), now).dgram()
+}
+
+fn get_tokens(client: &mut Connection) -> Vec<ResumptionToken> {
+ client
+ .events()
+ .filter_map(|e| {
+ if let ConnectionEvent::ResumptionToken(token) = e {
+ Some(token)
+ } else {
+ None
+ }
+ })
+ .collect()
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/recovery.rs b/third_party/rust/neqo-transport/src/connection/tests/recovery.rs
new file mode 100644
index 0000000000..ba7069ccb2
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/recovery.rs
@@ -0,0 +1,636 @@
+// 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::super::{Output, State, LOCAL_IDLE_TIMEOUT};
+use super::{
+ assert_full_cwnd, connect, connect_force_idle, connect_with_rtt, default_client,
+ default_server, fill_cwnd, maybe_authenticate, send_and_receive, send_something, AT_LEAST_PTO,
+ POST_HANDSHAKE_CWND,
+};
+use crate::frame::StreamType;
+use crate::path::PATH_MTU_V6;
+use crate::recovery::PTO_PACKET_COUNT;
+use crate::stats::MAX_PTO_COUNTS;
+use crate::tparams::TransportParameter;
+use crate::tracking::ACK_DELAY;
+
+use neqo_common::qdebug;
+use neqo_crypto::AuthenticationStatus;
+use std::time::Duration;
+use test_fixture::{self, now, split_datagram};
+
+#[test]
+fn pto_works_basic() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let mut now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ // Send data on two streams
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_send(2, b"hello").unwrap(), 5);
+ assert_eq!(client.stream_send(2, b" world").unwrap(), 6);
+
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 6);
+ assert_eq!(client.stream_send(6, b"there!").unwrap(), 6);
+
+ // Send a packet after some time.
+ now += Duration::from_secs(10);
+ let out = client.process(None, now);
+ assert!(out.dgram().is_some());
+
+ // Nothing to do, should return callback
+ let out = client.process(None, now);
+ assert!(matches!(out, Output::Callback(_)));
+
+ // One second later, it should want to send PTO packet
+ now += AT_LEAST_PTO;
+ let out = client.process(None, now);
+
+ let stream_before = server.stats().frame_rx.stream;
+ server.process_input(out.dgram().unwrap(), now);
+ assert_eq!(server.stats().frame_rx.stream, stream_before + 2);
+}
+
+#[test]
+fn pto_works_full_cwnd() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let res = client.process(None, now());
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ // Send lots of data.
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ let (dgrams, now) = fill_cwnd(&mut client, 2, now());
+ assert_full_cwnd(&dgrams, POST_HANDSHAKE_CWND);
+
+ neqo_common::qwarn!("waiting over");
+ // Fill the CWND after waiting for a PTO.
+ let (dgrams, now) = fill_cwnd(&mut client, 2, now + AT_LEAST_PTO);
+ // Two packets in the PTO.
+ // The first should be full sized; the second might be small.
+ assert_eq!(dgrams.len(), 2);
+ assert_eq!(dgrams[0].len(), PATH_MTU_V6);
+
+ // Both datagrams contain a STREAM frame.
+ for d in dgrams {
+ let stream_before = server.stats().frame_rx.stream;
+ server.process_input(d, now);
+ assert_eq!(server.stats().frame_rx.stream, stream_before + 1);
+ }
+}
+
+#[test]
+#[allow(clippy::cognitive_complexity)]
+fn pto_works_ping() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let now = now();
+
+ let res = client.process(None, now);
+ assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
+
+ // Send "zero" pkt
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_send(2, b"zero").unwrap(), 4);
+ let pkt0 = client.process(None, now + Duration::from_secs(10));
+ assert!(matches!(pkt0, Output::Datagram(_)));
+
+ // Send "one" pkt
+ assert_eq!(client.stream_send(2, b"one").unwrap(), 3);
+ let pkt1 = client.process(None, now + Duration::from_secs(10));
+
+ // Send "two" pkt
+ assert_eq!(client.stream_send(2, b"two").unwrap(), 3);
+ let pkt2 = client.process(None, now + Duration::from_secs(10));
+
+ // Send "three" pkt
+ assert_eq!(client.stream_send(2, b"three").unwrap(), 5);
+ let pkt3 = client.process(None, now + Duration::from_secs(10));
+
+ // Nothing to do, should return callback
+ let out = client.process(None, now + Duration::from_secs(10));
+ // Check callback delay is what we expect
+ assert!(matches!(out, Output::Callback(x) if x == Duration::from_millis(45)));
+
+ // Process these by server, skipping pkt0
+ let srv0_pkt1 = server.process(pkt1.dgram(), now + Duration::from_secs(10));
+ // ooo, ack client pkt 1
+ assert!(matches!(srv0_pkt1, Output::Datagram(_)));
+
+ // process pkt2 (no ack yet)
+ let srv2 = server.process(
+ pkt2.dgram(),
+ now + Duration::from_secs(10) + Duration::from_millis(20),
+ );
+ assert!(matches!(srv2, Output::Callback(_)));
+
+ // process pkt3 (acked)
+ let srv2 = server.process(
+ pkt3.dgram(),
+ now + Duration::from_secs(10) + Duration::from_millis(20),
+ );
+ // ack client pkt 2 & 3
+ assert!(matches!(srv2, Output::Datagram(_)));
+
+ // client processes ack
+ let pkt4 = client.process(
+ srv2.dgram(),
+ now + Duration::from_secs(10) + Duration::from_millis(40),
+ );
+ // client resends data from pkt0
+ assert!(matches!(pkt4, Output::Datagram(_)));
+
+ // server sees ooo pkt0 and generates ack
+ let srv_pkt2 = server.process(
+ pkt0.dgram(),
+ now + Duration::from_secs(10) + Duration::from_millis(40),
+ );
+ assert!(matches!(srv_pkt2, Output::Datagram(_)));
+
+ // Orig data is acked
+ let pkt5 = client.process(
+ srv_pkt2.dgram(),
+ now + Duration::from_secs(10) + Duration::from_millis(40),
+ );
+ assert!(matches!(pkt5, Output::Callback(_)));
+
+ // PTO expires. No unacked data. Only send PING.
+ let pkt6 = client.process(
+ None,
+ now + Duration::from_secs(10) + Duration::from_millis(110),
+ );
+
+ let ping_before = server.stats().frame_rx.ping;
+ server.process_input(
+ pkt6.dgram().unwrap(),
+ now + Duration::from_secs(10) + Duration::from_millis(110),
+ );
+ assert_eq!(server.stats().frame_rx.ping, ping_before + 1);
+}
+
+#[test]
+fn pto_initial() {
+ const INITIAL_PTO: Duration = Duration::from_millis(300);
+ let mut now = now();
+
+ qdebug!("---- client: generate CH");
+ let mut client = default_client();
+ let pkt1 = client.process(None, now).dgram();
+ assert!(pkt1.is_some());
+ assert_eq!(pkt1.clone().unwrap().len(), PATH_MTU_V6);
+
+ let delay = client.process(None, now).callback();
+ assert_eq!(delay, INITIAL_PTO);
+
+ // Resend initial after PTO.
+ now += delay;
+ let pkt2 = client.process(None, now).dgram();
+ assert!(pkt2.is_some());
+ assert_eq!(pkt2.unwrap().len(), PATH_MTU_V6);
+
+ let pkt3 = client.process(None, now).dgram();
+ assert!(pkt3.is_some());
+ assert_eq!(pkt3.unwrap().len(), PATH_MTU_V6);
+
+ let delay = client.process(None, now).callback();
+ // PTO has doubled.
+ assert_eq!(delay, INITIAL_PTO * 2);
+
+ // Server process the first initial pkt.
+ let mut server = default_server();
+ let out = server.process(pkt1, now).dgram();
+ assert!(out.is_some());
+
+ // Client receives ack for the first initial packet as well a Handshake packet.
+ // After the handshake packet the initial keys and the crypto stream for the initial
+ // packet number space will be discarded.
+ // Here only an ack for the Handshake packet will be sent.
+ let out = client.process(out, now).dgram();
+ assert!(out.is_some());
+
+ // We do not have PTO for the resent initial packet any more, but
+ // the Handshake PTO timer should be armed. As the RTT is apparently
+ // the same as the initial PTO value, and there is only one sample,
+ // the PTO will be 3x the INITIAL PTO.
+ let delay = client.process(None, now).callback();
+ assert_eq!(delay, INITIAL_PTO * 3);
+}
+
+/// A complete handshake that involves a PTO in the Handshake space.
+#[test]
+fn pto_handshake_complete() {
+ let mut now = now();
+ // start handshake
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let pkt = client.process(None, now).dgram();
+ let cb = client.process(None, now).callback();
+ assert_eq!(cb, Duration::from_millis(300));
+
+ now += Duration::from_millis(10);
+ let pkt = server.process(pkt, now).dgram();
+
+ now += Duration::from_millis(10);
+ let pkt = client.process(pkt, now).dgram();
+
+ let cb = client.process(None, now).callback();
+ // The client now has a single RTT estimate (20ms), so
+ // the handshake PTO is set based on that.
+ assert_eq!(cb, Duration::from_millis(60));
+
+ now += Duration::from_millis(10);
+ let pkt = server.process(pkt, now).dgram();
+ assert!(pkt.is_none());
+
+ now += Duration::from_millis(10);
+ client.authenticated(AuthenticationStatus::Ok, now);
+
+ qdebug!("---- client: SH..FIN -> FIN");
+ let pkt1 = client.process(None, now).dgram();
+ assert!(pkt1.is_some());
+
+ let cb = client.process(None, now).callback();
+ assert_eq!(cb, Duration::from_millis(60));
+
+ let mut pto_counts = [0; MAX_PTO_COUNTS];
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ // Wait for PTO to expire and resend a handshake packet
+ now += Duration::from_millis(60);
+ let pkt2 = client.process(None, now).dgram();
+ assert!(pkt2.is_some());
+
+ pto_counts[0] = 1;
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ // Get a second PTO packet.
+ let pkt3 = client.process(None, now).dgram();
+ assert!(pkt3.is_some());
+
+ // PTO has been doubled.
+ let cb = client.process(None, now).callback();
+ assert_eq!(cb, Duration::from_millis(120));
+
+ // We still have only a single PTO
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ now += Duration::from_millis(10);
+ // Server receives the first packet.
+ // The output will be a Handshake packet with an ack and a app pn space packet with
+ // HANDSHAKE_DONE.
+ let pkt = server.process(pkt1, now).dgram();
+ assert!(pkt.is_some());
+
+ // Check that the PTO packets (pkt2, pkt3) are Handshake packets.
+ // The server discarded the Handshake keys already, therefore they are dropped.
+ let dropped_before1 = server.stats().dropped_rx;
+ let frames_before = server.stats().frame_rx.all;
+ server.process_input(pkt2.unwrap(), now);
+ assert_eq!(1, server.stats().dropped_rx - dropped_before1);
+ assert_eq!(server.stats().frame_rx.all, frames_before);
+
+ let dropped_before2 = server.stats().dropped_rx;
+ server.process_input(pkt3.unwrap(), now);
+ assert_eq!(1, server.stats().dropped_rx - dropped_before2);
+ assert_eq!(server.stats().frame_rx.all, frames_before);
+
+ now += Duration::from_millis(10);
+ // Client receive ack for the first packet
+ let cb = client.process(pkt, now).callback();
+ // Ack delay timer for the packet carrying HANDSHAKE_DONE.
+ assert_eq!(cb, ACK_DELAY);
+
+ // Let the ack timer expire.
+ now += cb;
+ let out = client.process(None, now).dgram();
+ assert!(out.is_some());
+ let cb = client.process(None, now).callback();
+ // The handshake keys are discarded, but now we're back to the idle timeout.
+ // We don't send another PING because the handshake space is done and there
+ // is nothing to probe for.
+
+ pto_counts[0] = 1;
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+ assert_eq!(cb, LOCAL_IDLE_TIMEOUT - ACK_DELAY);
+}
+
+/// Test that PTO in the Handshake space contains the right frames.
+#[test]
+fn pto_handshake_frames() {
+ let mut now = now();
+ qdebug!("---- client: generate CH");
+ let mut client = default_client();
+ let pkt = client.process(None, now);
+
+ now += Duration::from_millis(10);
+ qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN");
+ let mut server = default_server();
+ let pkt = server.process(pkt.dgram(), now);
+
+ now += Duration::from_millis(10);
+ qdebug!("---- client: cert verification");
+ let pkt = client.process(pkt.dgram(), now);
+
+ now += Duration::from_millis(10);
+ let _ = server.process(pkt.dgram(), now);
+
+ now += Duration::from_millis(10);
+ client.authenticated(AuthenticationStatus::Ok, now);
+
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_send(2, b"zero").unwrap(), 4);
+ qdebug!("---- client: SH..FIN -> FIN and 1RTT packet");
+ let pkt1 = client.process(None, now).dgram();
+ assert!(pkt1.is_some());
+
+ // Get PTO timer.
+ let out = client.process(None, now);
+ assert_eq!(out, Output::Callback(Duration::from_millis(60)));
+
+ // Wait for PTO to expire and resend a handshake packet.
+ now += Duration::from_millis(60);
+ let pkt2 = client.process(None, now).dgram();
+ assert!(pkt2.is_some());
+
+ now += Duration::from_millis(10);
+ let crypto_before = server.stats().frame_rx.crypto;
+ server.process_input(pkt2.unwrap(), now);
+ assert_eq!(server.stats().frame_rx.crypto, crypto_before + 1)
+}
+
+/// In the case that the Handshake takes too many packets, the server might
+/// be stalled on the anti-amplification limit. If a Handshake ACK from the
+/// client is lost, the client has to keep the PTO timer armed or the server
+/// might be unable to send anything, causing a deadlock.
+#[test]
+fn handshake_ack_pto() {
+ const RTT: Duration = Duration::from_millis(10);
+ let mut now = now();
+ let mut client = default_client();
+ let mut server = default_server();
+ // This is a greasing transport parameter, and large enough that the
+ // server needs to send two Handshake packets.
+ let big = TransportParameter::Bytes(vec![0; PATH_MTU_V6]);
+ server.set_local_tparam(0xce16, big).unwrap();
+
+ let c1 = client.process(None, now).dgram();
+
+ now += RTT / 2;
+ let s1 = server.process(c1, now).dgram();
+ assert!(s1.is_some());
+ let s2 = server.process(None, now).dgram();
+ assert!(s1.is_some());
+
+ // Now let the client have the Initial, but drop the first coalesced Handshake packet.
+ now += RTT / 2;
+ let (initial, _) = split_datagram(&s1.unwrap());
+ client.process_input(initial, now);
+ let c2 = client.process(s2, now).dgram();
+ assert!(c2.is_some()); // This is an ACK. Drop it.
+ let delay = client.process(None, now).callback();
+ assert_eq!(delay, RTT * 3);
+
+ let mut pto_counts = [0; MAX_PTO_COUNTS];
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ // Wait for the PTO and ensure that the client generates a packet.
+ now += delay;
+ let c3 = client.process(None, now).dgram();
+ assert!(c3.is_some());
+
+ now += RTT / 2;
+ let ping_before = server.stats().frame_rx.ping;
+ server.process_input(c3.unwrap(), now);
+ assert_eq!(server.stats().frame_rx.ping, ping_before + 1);
+
+ pto_counts[0] = 1;
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+
+ // Now complete the handshake as cheaply as possible.
+ let dgram = server.process(None, now).dgram();
+ client.process_input(dgram.unwrap(), now);
+ maybe_authenticate(&mut client);
+ let dgram = client.process(None, now).dgram();
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram, now).dgram();
+ assert_eq!(*server.state(), State::Confirmed);
+ client.process_input(dgram.unwrap(), now);
+ assert_eq!(*client.state(), State::Confirmed);
+
+ assert_eq!(client.stats.borrow().pto_counts, pto_counts);
+}
+
+#[test]
+fn loss_recovery_crash() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+ let now = now();
+
+ // The server sends something, but we will drop this.
+ let _ = send_something(&mut server, now);
+
+ // Then send something again, but let it through.
+ let ack = send_and_receive(&mut server, &mut client, now);
+ assert!(ack.is_some());
+
+ // Have the server process the ACK.
+ let cb = server.process(ack, now).callback();
+ assert!(cb > Duration::from_secs(0));
+
+ // Now we leap into the future. The server should regard the first
+ // packet as lost based on time alone.
+ let dgram = server.process(None, now + AT_LEAST_PTO).dgram();
+ assert!(dgram.is_some());
+
+ // This crashes.
+ let _ = send_something(&mut server, now + AT_LEAST_PTO);
+}
+
+// If we receive packets after the PTO timer has fired, we won't clear
+// the PTO state, but we might need to acknowledge those packets.
+// This shouldn't happen, but we found that some implementations do this.
+#[test]
+fn ack_after_pto() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect_force_idle(&mut client, &mut server);
+
+ let mut now = now();
+
+ // The client sends and is forced into a PTO.
+ let _ = send_something(&mut client, now);
+
+ // Jump forward to the PTO and drain the PTO packets.
+ now += AT_LEAST_PTO;
+ for _ in 0..PTO_PACKET_COUNT {
+ let dgram = client.process(None, now).dgram();
+ assert!(dgram.is_some());
+ }
+ assert!(client.process(None, now).dgram().is_none());
+
+ // The server now needs to send something that will cause the
+ // client to want to acknowledge it. A little out of order
+ // delivery is just the thing.
+ // Note: The server can't ACK anything here, but none of what
+ // the client has sent so far has been transferred.
+ let _ = send_something(&mut server, now);
+ let dgram = send_something(&mut server, now);
+
+ // The client is now after a PTO, but if it receives something
+ // that demands acknowledgment, it will send just the ACK.
+ let ack = client.process(Some(dgram), now).dgram();
+ assert!(ack.is_some());
+
+ // Make sure that the packet only contained an ACK frame.
+ let all_frames_before = server.stats().frame_rx.all;
+ let ack_before = server.stats().frame_rx.ack;
+ server.process_input(ack.unwrap(), now);
+ assert_eq!(server.stats().frame_rx.all, all_frames_before + 1);
+ assert_eq!(server.stats().frame_rx.ack, ack_before + 1);
+}
+
+/// When we declare a packet as lost, we keep it around for a while for another loss period.
+/// Those packets should not affect how we report the loss recovery timer.
+/// As the loss recovery timer based on RTT we use that to drive the state.
+#[test]
+fn lost_but_kept_and_lr_timer() {
+ const RTT: Duration = Duration::from_secs(1);
+ let mut client = default_client();
+ let mut server = default_server();
+ let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT);
+
+ // Two packets (p1, p2) are sent at around t=0. The first is lost.
+ let _p1 = send_something(&mut client, now);
+ let p2 = send_something(&mut client, now);
+
+ // At t=RTT/2 the server receives the packet and ACKs it.
+ now += RTT / 2;
+ let ack = server.process(Some(p2), now).dgram();
+ assert!(ack.is_some());
+ // The client also sends another two packets (p3, p4), again losing the first.
+ let _p3 = send_something(&mut client, now);
+ let p4 = send_something(&mut client, now);
+
+ // At t=RTT the client receives the ACK and goes into timed loss recovery.
+ // The client doesn't call p1 lost at this stage, but it will soon.
+ now += RTT / 2;
+ let res = client.process(ack, now);
+ // The client should be on a loss recovery timer as p1 is missing.
+ let lr_timer = res.callback();
+ // Loss recovery timer should be RTT/8, but only check for 0 or >=RTT/2.
+ assert_ne!(lr_timer, Duration::from_secs(0));
+ assert!(lr_timer < (RTT / 2));
+ // The server also receives and acknowledges p4, again sending an ACK.
+ let ack = server.process(Some(p4), now).dgram();
+ assert!(ack.is_some());
+
+ // At t=RTT*3/2 the client should declare p1 to be lost.
+ now += RTT / 2;
+ // So the client will send the data from p1 again.
+ let res = client.process(None, now);
+ assert!(res.dgram().is_some());
+ // When the client processes the ACK, it should engage the
+ // loss recovery timer for p3, not p1 (even though it still tracks p1).
+ let res = client.process(ack, now);
+ let lr_timer2 = res.callback();
+ assert_eq!(lr_timer, lr_timer2);
+}
+
+/// We should not be setting the loss recovery timer based on packets
+/// that are sent prior to the largest acknowledged.
+/// Testing this requires that we construct a case where one packet
+/// number space causes the loss recovery timer to be engaged. At the same time,
+/// there is a packet in another space that hasn't been acknowledged AND
+/// that packet number space has not received acknowledgments for later packets.
+#[test]
+fn loss_time_past_largest_acked() {
+ const RTT: Duration = Duration::from_secs(10);
+ const INCR: Duration = Duration::from_millis(1);
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let mut now = now();
+
+ // Start the handshake.
+ let c_in = client.process(None, now).dgram();
+ now += RTT / 2;
+ let s_hs1 = server.process(c_in, now).dgram();
+
+ // Get some spare server handshake packets for the client to ACK.
+ // This involves a time machine, so be a little cautious.
+ // This test uses an RTT of 10s, but our server starts
+ // with a much lower RTT estimate, so the PTO at this point should
+ // be much smaller than an RTT and so the server shouldn't see
+ // time go backwards.
+ let s_pto = server.process(None, now).callback();
+ assert_ne!(s_pto, Duration::from_secs(0));
+ assert!(s_pto < RTT);
+ let s_hs2 = server.process(None, now + s_pto).dgram();
+ assert!(s_hs2.is_some());
+ let s_hs3 = server.process(None, now + s_pto).dgram();
+ assert!(s_hs3.is_some());
+
+ // Get some Handshake packets from the client.
+ // We need one to be left unacknowledged before one that is acknowledged.
+ // So that the client engages the loss recovery timer.
+ // This is complicated by the fact that it is hard to cause the client
+ // to generate an ack-eliciting packet. For that, we use the Finished message.
+ // Reordering delivery ensures that the later packet is also acknowledged.
+ now += RTT / 2;
+ let c_hs1 = client.process(s_hs1, now).dgram();
+ assert!(c_hs1.is_some()); // This comes first, so it's useless.
+ maybe_authenticate(&mut client);
+ let c_hs2 = client.process(None, now).dgram();
+ assert!(c_hs2.is_some()); // This one will elicit an ACK.
+
+ // The we need the outstanding packet to be sent after the
+ // application data packet, so space these out a tiny bit.
+ let _p1 = send_something(&mut client, now + INCR);
+ let c_hs3 = client.process(s_hs2, now + (INCR * 2)).dgram();
+ assert!(c_hs3.is_some()); // This will be left outstanding.
+ let c_hs4 = client.process(s_hs3, now + (INCR * 3)).dgram();
+ assert!(c_hs4.is_some()); // This will be acknowledged.
+
+ // Process c_hs2 and c_hs4, but skip c_hs3.
+ // Then get an ACK for the client.
+ now += RTT / 2;
+ // Deliver c_hs4 first, but don't generate a packet.
+ server.process_input(c_hs4.unwrap(), now);
+ let s_ack = server.process(c_hs2, now).dgram();
+ assert!(s_ack.is_some());
+ // This includes an ACK, but it also includes HANDSHAKE_DONE,
+ // which we need to remove because that will cause the Handshake loss
+ // recovery state to be dropped.
+ let (s_hs_ack, _s_ap_ack) = split_datagram(&s_ack.unwrap());
+
+ // Now the client should start its loss recovery timer based on the ACK.
+ now += RTT / 2;
+ let c_ack = client.process(Some(s_hs_ack), now).dgram();
+ assert!(c_ack.is_none());
+ // The client should now have the loss recovery timer active.
+ let lr_time = client.process(None, now).callback();
+ assert_ne!(lr_time, Duration::from_secs(0));
+ assert!(lr_time < (RTT / 2));
+
+ // Skipping forward by the loss recovery timer should cause the client to
+ // mark packets as lost and retransmit, after which we should be on the PTO
+ // timer.
+ now += lr_time;
+ let delay = client.process(None, now).callback();
+ assert_ne!(delay, Duration::from_secs(0));
+ assert!(delay > lr_time);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/resumption.rs b/third_party/rust/neqo-transport/src/connection/tests/resumption.rs
new file mode 100644
index 0000000000..19421dded8
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/resumption.rs
@@ -0,0 +1,182 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::{
+ connect, connect_with_rtt, default_client, default_server, exchange_ticket, get_tokens,
+ send_something, AT_LEAST_PTO,
+};
+use crate::addr_valid::{AddressValidation, ValidateAddress};
+
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::time::Duration;
+use test_fixture::{self, assertions, now};
+
+#[test]
+fn resume() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let token = exchange_ticket(&mut client, &mut server, now());
+ let mut client = default_client();
+ client
+ .enable_resumption(now(), token)
+ .expect("should set token");
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+ assert!(client.tls_info().unwrap().resumed());
+ assert!(server.tls_info().unwrap().resumed());
+}
+
+#[test]
+fn remember_smoothed_rtt() {
+ const RTT1: Duration = Duration::from_millis(130);
+ const RTT2: Duration = Duration::from_millis(70);
+
+ let mut client = default_client();
+ let mut server = default_server();
+
+ let now = connect_with_rtt(&mut client, &mut server, now(), RTT1);
+ assert_eq!(client.loss_recovery.rtt(), RTT1);
+
+ let token = exchange_ticket(&mut client, &mut server, now);
+ let mut client = default_client();
+ let mut server = default_server();
+ client.enable_resumption(now, token).unwrap();
+ assert_eq!(
+ client.loss_recovery.rtt(),
+ RTT1,
+ "client should remember previous RTT"
+ );
+
+ connect_with_rtt(&mut client, &mut server, now, RTT2);
+ assert_eq!(
+ client.loss_recovery.rtt(),
+ RTT2,
+ "previous RTT should be completely erased"
+ );
+}
+
+/// Check that a resumed connection uses a token on Initial packets.
+#[test]
+fn address_validation_token_resume() {
+ const RTT: Duration = Duration::from_millis(10);
+
+ let mut client = default_client();
+ let mut server = default_server();
+ let validation = AddressValidation::new(now(), ValidateAddress::Always).unwrap();
+ let validation = Rc::new(RefCell::new(validation));
+ server.set_validation(Rc::clone(&validation));
+ let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT);
+
+ let token = exchange_ticket(&mut client, &mut server, now);
+ let mut client = default_client();
+ client.enable_resumption(now, token).unwrap();
+ let mut server = default_server();
+
+ // Grab an Initial packet from the client.
+ let dgram = client.process(None, now).dgram();
+ assertions::assert_initial(dgram.as_ref().unwrap(), true);
+
+ // Now try to complete the handshake after giving time for a client PTO.
+ now += AT_LEAST_PTO;
+ connect_with_rtt(&mut client, &mut server, now, RTT);
+ assert!(client.crypto.tls.info().unwrap().resumed());
+ assert!(server.crypto.tls.info().unwrap().resumed());
+}
+
+fn can_resume(token: impl AsRef<[u8]>, initial_has_token: bool) {
+ let mut client = default_client();
+ client.enable_resumption(now(), token).unwrap();
+ let initial = client.process_output(now()).dgram();
+ assertions::assert_initial(initial.as_ref().unwrap(), initial_has_token);
+}
+
+#[test]
+fn two_tickets_on_timer() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // Send two tickets and then bundle those into a packet.
+ server.send_ticket(now(), &[]).expect("send ticket1");
+ server.send_ticket(now(), &[]).expect("send ticket2");
+ let pkt = send_something(&mut server, now());
+
+ // process() will return an ack first
+ assert!(client.process(Some(pkt), now()).dgram().is_some());
+ // We do not have a ResumptionToken event yet, because NEW_TOKEN was not sent.
+ assert_eq!(get_tokens(&mut client).len(), 0);
+
+ // We need to wait for release_resumption_token_timer to expire. The timer will be
+ // set to 3 * PTO
+ let mut now = now() + 3 * client.get_pto();
+ let _ = client.process(None, now);
+ let mut recv_tokens = get_tokens(&mut client);
+ assert_eq!(recv_tokens.len(), 1);
+ let token1 = recv_tokens.pop().unwrap();
+ // Wai for anottheer 3 * PTO to get the nex okeen.
+ now += 3 * client.get_pto();
+ let _ = client.process(None, now);
+ let mut recv_tokens = get_tokens(&mut client);
+ assert_eq!(recv_tokens.len(), 1);
+ let token2 = recv_tokens.pop().unwrap();
+ // Wait for 3 * PTO, but now there are no more tokens.
+ now += 3 * client.get_pto();
+ let _ = client.process(None, now);
+ assert_eq!(get_tokens(&mut client).len(), 0);
+ assert_ne!(token1.as_ref(), token2.as_ref());
+
+ can_resume(&token1, false);
+ can_resume(&token2, false);
+}
+
+#[test]
+fn two_tickets_with_new_token() {
+ let mut client = default_client();
+ let mut server = default_server();
+ let validation = AddressValidation::new(now(), ValidateAddress::Always).unwrap();
+ let validation = Rc::new(RefCell::new(validation));
+ server.set_validation(Rc::clone(&validation));
+ connect(&mut client, &mut server);
+
+ // Send two tickets with tokens and then bundle those into a packet.
+ server.send_ticket(now(), &[]).expect("send ticket1");
+ server.send_ticket(now(), &[]).expect("send ticket2");
+ let pkt = send_something(&mut server, now());
+
+ client.process_input(pkt, now());
+ let mut all_tokens = get_tokens(&mut client);
+ assert_eq!(all_tokens.len(), 2);
+ let token1 = all_tokens.pop().unwrap();
+ let token2 = all_tokens.pop().unwrap();
+ assert_ne!(token1.as_ref(), token2.as_ref());
+
+ can_resume(&token1, true);
+ can_resume(&token2, true);
+}
+
+/// By disabling address validation, the server won't send `NEW_TOKEN`, but
+/// we can take the session ticket still.
+#[test]
+fn take_token() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ server.send_ticket(now(), &[]).unwrap();
+ let dgram = server.process(None, now()).dgram();
+ client.process_input(dgram.unwrap(), now());
+
+ // There should be no ResumptionToken event here.
+ let tokens = get_tokens(&mut client);
+ assert_eq!(tokens.len(), 0);
+
+ // But we should be able to get the token directly, and use it.
+ let token = client.take_resumption_token(now()).unwrap();
+ can_resume(&token, false);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/stream.rs b/third_party/rust/neqo-transport/src/connection/tests/stream.rs
new file mode 100644
index 0000000000..1c9eebaa17
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/stream.rs
@@ -0,0 +1,580 @@
+// 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::super::State;
+use super::{
+ connect, default_client, default_server, maybe_authenticate, send_something,
+ DEFAULT_STREAM_DATA,
+};
+use crate::events::ConnectionEvent;
+use crate::frame::StreamType;
+use crate::recv_stream::RECV_BUFFER_SIZE;
+use crate::send_stream::SEND_BUFFER_SIZE;
+use crate::tparams::{self, TransportParameter};
+use crate::tracking::MAX_UNACKED_PKTS;
+use crate::{Error, StreamId};
+
+use neqo_common::{event::Provider, qdebug};
+use std::convert::TryFrom;
+use test_fixture::now;
+
+#[test]
+fn stream_create() {
+ let mut client = default_client();
+
+ let out = client.process(None, now());
+ let mut server = default_server();
+ let out = server.process(out.dgram(), now());
+
+ let out = client.process(out.dgram(), now());
+ let _ = server.process(out.dgram(), now());
+ assert!(maybe_authenticate(&mut client));
+ let out = client.process(None, now());
+
+ // client now in State::Connected
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
+ assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 6);
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
+ assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 4);
+
+ let _ = server.process(out.dgram(), now());
+ // server now in State::Connected
+ assert_eq!(server.stream_create(StreamType::UniDi).unwrap(), 3);
+ assert_eq!(server.stream_create(StreamType::UniDi).unwrap(), 7);
+ assert_eq!(server.stream_create(StreamType::BiDi).unwrap(), 1);
+ assert_eq!(server.stream_create(StreamType::BiDi).unwrap(), 5);
+}
+
+#[test]
+#[allow(clippy::cognitive_complexity)]
+// tests stream send/recv after connection is established.
+fn transfer() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ qdebug!("---- client");
+ let out = client.process(None, now());
+ assert!(out.as_dgram_ref().is_some());
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+ // -->> Initial[0]: CRYPTO[CH]
+
+ qdebug!("---- server");
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+ // <<-- Initial[0]: CRYPTO[SH] ACK[0]
+ // <<-- Handshake[0]: CRYPTO[EE, CERT, CV, FIN]
+
+ qdebug!("---- client");
+ let out = client.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+ // -->> Initial[1]: ACK[0]
+
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_none());
+
+ assert!(maybe_authenticate(&mut client));
+
+ qdebug!("---- client");
+ let out = client.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(*client.state(), State::Connected);
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+ // -->> Handshake[0]: CRYPTO[FIN], ACK[0]
+
+ qdebug!("---- server");
+ let out = server.process(out.dgram(), now());
+ assert!(out.as_dgram_ref().is_some());
+ assert_eq!(*server.state(), State::Confirmed);
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+ // ACK and HANDSHAKE_DONE
+ // -->> nothing
+
+ qdebug!("---- client");
+ // Send
+ let client_stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream_id, &[6; 100]).unwrap();
+ client.stream_send(client_stream_id, &[7; 40]).unwrap();
+ client.stream_send(client_stream_id, &[8; 4000]).unwrap();
+
+ // Send to another stream but some data after fin has been set
+ let client_stream_id2 = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream_id2, &[6; 60]).unwrap();
+ client.stream_close_send(client_stream_id2).unwrap();
+ client.stream_send(client_stream_id2, &[7; 50]).unwrap_err();
+ // Sending this much takes a few datagrams.
+ let mut datagrams = vec![];
+ let mut out = client.process(out.dgram(), now());
+ while let Some(d) = out.dgram() {
+ datagrams.push(d);
+ out = client.process(None, now());
+ }
+ assert_eq!(datagrams.len(), 4);
+ assert_eq!(*client.state(), State::Confirmed);
+
+ qdebug!("---- server");
+ for (d_num, d) in datagrams.into_iter().enumerate() {
+ let out = server.process(Some(d), now());
+ assert_eq!(
+ out.as_dgram_ref().is_some(),
+ (d_num + 1) % (MAX_UNACKED_PKTS + 1) == 0
+ );
+ qdebug!("Output={:0x?}", out.as_dgram_ref());
+ }
+ assert_eq!(*server.state(), State::Confirmed);
+
+ let mut buf = vec![0; 4000];
+
+ let mut stream_ids = server.events().filter_map(|evt| match evt {
+ ConnectionEvent::NewStream { stream_id, .. } => Some(stream_id),
+ _ => None,
+ });
+ let first_stream = stream_ids.next().expect("should have a new stream event");
+ let second_stream = stream_ids
+ .next()
+ .expect("should have a second new stream event");
+ assert!(stream_ids.next().is_none());
+ let (received1, fin1) = server.stream_recv(first_stream.as_u64(), &mut buf).unwrap();
+ assert_eq!(received1, 4000);
+ assert_eq!(fin1, false);
+ let (received2, fin2) = server.stream_recv(first_stream.as_u64(), &mut buf).unwrap();
+ assert_eq!(received2, 140);
+ assert_eq!(fin2, false);
+
+ let (received3, fin3) = server
+ .stream_recv(second_stream.as_u64(), &mut buf)
+ .unwrap();
+ assert_eq!(received3, 60);
+ assert_eq!(fin3, true);
+}
+
+#[test]
+// Send fin even if a peer closes a reomte bidi send stream before sending any data.
+fn report_fin_when_stream_closed_wo_data() {
+ // Note that the two servers in this test will get different anti-replay filters.
+ // That's OK because we aren't testing anti-replay.
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // create a stream
+ let stream_id = client.stream_create(StreamType::BiDi).unwrap();
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out = client.process(None, now());
+ let _ = server.process(out.dgram(), now());
+
+ assert_eq!(Ok(()), server.stream_close_send(stream_id));
+ let out = server.process(None, now());
+ let _ = client.process(out.dgram(), now());
+ let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable {..});
+ assert!(client.events().any(stream_readable));
+}
+
+#[test]
+fn max_data() {
+ const SMALL_MAX_DATA: usize = 16383;
+
+ let mut client = default_client();
+ let mut server = default_server();
+
+ server
+ .set_local_tparam(
+ tparams::INITIAL_MAX_DATA,
+ TransportParameter::Integer(u64::try_from(SMALL_MAX_DATA).unwrap()),
+ )
+ .unwrap();
+
+ connect(&mut client, &mut server);
+
+ let stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ assert_eq!(client.events().count(), 2); // SendStreamWritable, StateChange(connected)
+ assert_eq!(stream_id, 2);
+ assert_eq!(
+ client.stream_avail_send_space(stream_id).unwrap(),
+ SMALL_MAX_DATA
+ );
+ assert_eq!(
+ client
+ .stream_send(stream_id, &vec![b'a'; RECV_BUFFER_SIZE].into_boxed_slice())
+ .unwrap(),
+ SMALL_MAX_DATA
+ );
+ assert_eq!(client.events().count(), 0);
+
+ assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 0);
+ client
+ .send_streams
+ .get_mut(stream_id.into())
+ .unwrap()
+ .mark_as_sent(0, 4096, false);
+ assert_eq!(client.events().count(), 0);
+ client
+ .send_streams
+ .get_mut(stream_id.into())
+ .unwrap()
+ .mark_as_acked(0, 4096, false);
+ assert_eq!(client.events().count(), 0);
+
+ assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 0);
+ // no event because still limited by conn max data
+ assert_eq!(client.events().count(), 0);
+
+ // Increase max data. Avail space now limited by stream credit
+ client.handle_max_data(100_000_000);
+ assert_eq!(
+ client.stream_avail_send_space(stream_id).unwrap(),
+ SEND_BUFFER_SIZE - SMALL_MAX_DATA
+ );
+
+ // Increase max stream data. Avail space now limited by tx buffer
+ client
+ .send_streams
+ .get_mut(stream_id.into())
+ .unwrap()
+ .set_max_stream_data(100_000_000);
+ assert_eq!(
+ client.stream_avail_send_space(stream_id).unwrap(),
+ SEND_BUFFER_SIZE - SMALL_MAX_DATA + 4096
+ );
+
+ let evts = client.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 1);
+ assert!(matches!(evts[0], ConnectionEvent::SendStreamWritable{..}));
+}
+
+#[test]
+// If we send a stop_sending to the peer, we should not accept more data from the peer.
+fn do_not_accept_data_after_stop_sending() {
+ // Note that the two servers in this test will get different anti-replay filters.
+ // That's OK because we aren't testing anti-replay.
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // create a stream
+ let stream_id = client.stream_create(StreamType::BiDi).unwrap();
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out = client.process(None, now());
+ let _ = server.process(out.dgram(), now());
+
+ let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable {..});
+ assert!(server.events().any(stream_readable));
+
+ // Send one more packet from client. The packet should arrive after the server
+ // has already requested stop_sending.
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out_second_data_frame = client.process(None, now());
+ // Call stop sending.
+ assert_eq!(
+ Ok(()),
+ server.stream_stop_sending(stream_id, Error::NoError.code())
+ );
+
+ // Receive the second data frame. The frame should be ignored and
+ // DataReadable events shouldn't be posted.
+ let out = server.process(out_second_data_frame.dgram(), now());
+ assert!(!server.events().any(stream_readable));
+
+ let _ = client.process(out.dgram(), now());
+ assert_eq!(
+ Err(Error::FinalSizeError),
+ client.stream_send(stream_id, &[0x00])
+ );
+}
+
+#[test]
+// Server sends stop_sending, the client simultaneous sends reset.
+fn simultaneous_stop_sending_and_reset() {
+ // Note that the two servers in this test will get different anti-replay filters.
+ // That's OK because we aren't testing anti-replay.
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // create a stream
+ let stream_id = client.stream_create(StreamType::BiDi).unwrap();
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out = client.process(None, now());
+ let _ = server.process(out.dgram(), now());
+
+ let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable {..});
+ assert!(server.events().any(stream_readable));
+
+ // The client resets the stream. The packet with reset should arrive after the server
+ // has already requested stop_sending.
+ client
+ .stream_reset_send(stream_id, Error::NoError.code())
+ .unwrap();
+ let out_reset_frame = client.process(None, now());
+ // Call stop sending.
+ assert_eq!(
+ Ok(()),
+ server.stream_stop_sending(stream_id, Error::NoError.code())
+ );
+
+ // Receive the second data frame. The frame should be ignored and
+ // DataReadable events shouldn't be posted.
+ let out = server.process(out_reset_frame.dgram(), now());
+ assert!(!server.events().any(stream_readable));
+
+ // The client gets the STOP_SENDING frame.
+ let _ = client.process(out.dgram(), now());
+ assert_eq!(
+ Err(Error::InvalidStreamId),
+ client.stream_send(stream_id, &[0x00])
+ );
+}
+
+#[test]
+fn client_fin_reorder() {
+ let mut client = default_client();
+ let mut server = default_server();
+
+ // Send ClientHello.
+ let client_hs = client.process(None, now());
+ assert!(client_hs.as_dgram_ref().is_some());
+
+ let server_hs = server.process(client_hs.dgram(), now());
+ assert!(server_hs.as_dgram_ref().is_some()); // ServerHello, etc...
+
+ let client_ack = client.process(server_hs.dgram(), now());
+ assert!(client_ack.as_dgram_ref().is_some());
+
+ let server_out = server.process(client_ack.dgram(), now());
+ assert!(server_out.as_dgram_ref().is_none());
+
+ assert!(maybe_authenticate(&mut client));
+ assert_eq!(*client.state(), State::Connected);
+
+ let client_fin = client.process(None, now());
+ assert!(client_fin.as_dgram_ref().is_some());
+
+ let client_stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream_id, &[1, 2, 3]).unwrap();
+ let client_stream_data = client.process(None, now());
+ assert!(client_stream_data.as_dgram_ref().is_some());
+
+ // Now stream data gets before client_fin
+ let server_out = server.process(client_stream_data.dgram(), now());
+ assert!(server_out.as_dgram_ref().is_none()); // the packet will be discarded
+
+ assert_eq!(*server.state(), State::Handshaking);
+ let server_out = server.process(client_fin.dgram(), now());
+ assert!(server_out.as_dgram_ref().is_some());
+}
+
+#[test]
+fn after_fin_is_read_conn_events_for_stream_should_be_removed() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let id = server.stream_create(StreamType::BiDi).unwrap();
+ server.stream_send(id, &[6; 10]).unwrap();
+ server.stream_close_send(id).unwrap();
+ let out = server.process(None, now()).dgram();
+ assert!(out.is_some());
+
+ let _ = client.process(out, now());
+
+ // read from the stream before checking connection events.
+ let mut buf = vec![0; 4000];
+ let (_, fin) = client.stream_recv(id, &mut buf).unwrap();
+ assert_eq!(fin, true);
+
+ // Make sure we do not have RecvStreamReadable events for the stream when fin has been read.
+ let readable_stream_evt =
+ |e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id } if stream_id == id);
+ assert!(!client.events().any(readable_stream_evt));
+}
+
+#[test]
+fn after_stream_stop_sending_is_called_conn_events_for_stream_should_be_removed() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let id = server.stream_create(StreamType::BiDi).unwrap();
+ server.stream_send(id, &[6; 10]).unwrap();
+ server.stream_close_send(id).unwrap();
+ let out = server.process(None, now()).dgram();
+ assert!(out.is_some());
+
+ let _ = client.process(out, now());
+
+ // send stop seending.
+ client
+ .stream_stop_sending(id, Error::NoError.code())
+ .unwrap();
+
+ // Make sure we do not have RecvStreamReadable events for the stream after stream_stop_sending
+ // has been called.
+ let readable_stream_evt =
+ |e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id } if stream_id == id);
+ assert!(!client.events().any(readable_stream_evt));
+}
+
+#[test]
+fn stream_data_blocked_generates_max_stream_data() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let now = now();
+
+ // Send some data and include STREAM_DATA_BLOCKED with any value.
+ let stream_id = server.stream_create(StreamType::UniDi).unwrap();
+ let _ = server.stream_send(stream_id, DEFAULT_STREAM_DATA).unwrap();
+ server.flow_mgr.borrow_mut().stream_data_blocked(
+ StreamId::from(stream_id),
+ u64::try_from(DEFAULT_STREAM_DATA.len()).unwrap(),
+ );
+
+ let dgram = server.process(None, now).dgram();
+ assert!(dgram.is_some());
+
+ let sdb_before = client.stats().frame_rx.stream_data_blocked;
+ client.process_input(dgram.unwrap(), now);
+ assert_eq!(client.stats().frame_rx.stream_data_blocked, sdb_before + 1);
+
+ // Consume the data.
+ let mut buf = [0; 10];
+ let (count, end) = client.stream_recv(stream_id, &mut buf[..]).unwrap();
+ assert_eq!(count, DEFAULT_STREAM_DATA.len());
+ assert!(!end);
+
+ let dgram = client.process_output(now).dgram();
+
+ // Client should have sent a MAX_STREAM_DATA frame with just a small increase
+ // on the default window size.
+ let msd_before = server.stats().frame_rx.max_stream_data;
+ server.process_input(dgram.unwrap(), now);
+ assert_eq!(server.stats().frame_rx.max_stream_data, msd_before + 1);
+
+ // Test that more space is available, but that it is small.
+ let mut written = 0;
+ loop {
+ const LARGE_BUFFER: &[u8] = &[0; 1024];
+ let amount = server.stream_send(stream_id, LARGE_BUFFER).unwrap();
+ if amount == 0 {
+ break;
+ }
+ written += amount;
+ }
+ assert_eq!(written, RECV_BUFFER_SIZE - DEFAULT_STREAM_DATA.len());
+}
+
+/// See <https://github.com/mozilla/neqo/issues/871>
+#[test]
+fn max_streams_after_bidi_closed() {
+ const REQUEST: &[u8] = b"ping";
+ const RESPONSE: &[u8] = b"pong";
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let stream_id = client.stream_create(StreamType::BiDi).unwrap();
+ while client.stream_create(StreamType::BiDi).is_ok() {
+ // Exhaust the stream limit.
+ }
+ // Write on the one stream and send that out.
+ let _ = client.stream_send(stream_id, REQUEST).unwrap();
+ client.stream_close_send(stream_id).unwrap();
+ let dgram = client.process(None, now()).dgram();
+
+ // Now handle the stream and send an incomplete response.
+ server.process_input(dgram.unwrap(), now());
+ server.stream_send(stream_id, RESPONSE).unwrap();
+ let dgram = server.process_output(now()).dgram();
+
+ // The server shouldn't have released more stream credit.
+ client.process_input(dgram.unwrap(), now());
+ let e = client.stream_create(StreamType::BiDi).unwrap_err();
+ assert!(matches!(e, Error::StreamLimitError));
+
+ // Closing the stream isn't enough.
+ server.stream_close_send(stream_id).unwrap();
+ let dgram = server.process_output(now()).dgram();
+ client.process_input(dgram.unwrap(), now());
+ assert!(client.stream_create(StreamType::BiDi).is_err());
+
+ // The server needs to see an acknowledgment from the client for its
+ // response AND the server has to read all of the request.
+ // and the server needs to read all the data. Read first.
+ let mut buf = [0; REQUEST.len()];
+ let (count, fin) = server.stream_recv(stream_id, &mut buf).unwrap();
+ assert_eq!(&buf[..count], REQUEST);
+ assert!(fin);
+
+ // We need an ACK from the client now, but that isn't guaranteed,
+ // so give the client one more packet just in case.
+ let dgram = send_something(&mut server, now());
+ client.process_input(dgram, now());
+
+ // Now get the client to send the ACK and have the server handle that.
+ let dgram = send_something(&mut client, now());
+ let dgram = server.process(Some(dgram), now()).dgram();
+ client.process_input(dgram.unwrap(), now());
+ assert!(client.stream_create(StreamType::BiDi).is_ok());
+ assert!(client.stream_create(StreamType::BiDi).is_err());
+}
+
+#[test]
+fn no_dupdata_readable_events() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // create a stream
+ let stream_id = client.stream_create(StreamType::BiDi).unwrap();
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out = client.process(None, now());
+ let _ = server.process(out.dgram(), now());
+
+ // We have a data_readable event.
+ let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable {..});
+ assert!(server.events().any(stream_readable));
+
+ // Send one more data frame from client. The previous stream data has not been read yet,
+ // therefore there should not be a new DataReadable event.
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out_second_data_frame = client.process(None, now());
+ let _ = server.process(out_second_data_frame.dgram(), now());
+ assert!(!server.events().any(stream_readable));
+
+ // One more frame with a fin will not produce a new DataReadable event, because the
+ // previous stream data has not been read yet.
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ client.stream_close_send(stream_id).unwrap();
+ let out_third_data_frame = client.process(None, now());
+ let _ = server.process(out_third_data_frame.dgram(), now());
+ assert!(!server.events().any(stream_readable));
+}
+
+#[test]
+fn no_dupdata_readable_events_empty_last_frame() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ // create a stream
+ let stream_id = client.stream_create(StreamType::BiDi).unwrap();
+ client.stream_send(stream_id, &[0x00]).unwrap();
+ let out = client.process(None, now());
+ let _ = server.process(out.dgram(), now());
+
+ // We have a data_readable event.
+ let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable {..});
+ assert!(server.events().any(stream_readable));
+
+ // An empty frame with a fin will not produce a new DataReadable event, because
+ // the previous stream data has not been read yet.
+ client.stream_close_send(stream_id).unwrap();
+ let out_second_data_frame = client.process(None, now());
+ let _ = server.process(out_second_data_frame.dgram(), now());
+ assert!(!server.events().any(stream_readable));
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/vn.rs b/third_party/rust/neqo-transport/src/connection/tests/vn.rs
new file mode 100644
index 0000000000..f7d37c7864
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/vn.rs
@@ -0,0 +1,201 @@
+// 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::super::{ConnectionError, Output, State};
+use super::{default_client, default_server};
+use crate::packet::PACKET_BIT_LONG;
+use crate::{Error, QuicVersion};
+
+use neqo_common::{Datagram, Decoder, Encoder};
+use std::time::Duration;
+use test_fixture::{self, loopback, now};
+
+// The expected PTO duration after the first Initial is sent.
+const INITIAL_PTO: Duration = Duration::from_millis(300);
+
+#[test]
+fn unknown_version() {
+ let mut client = default_client();
+ // Start the handshake.
+ let _ = client.process(None, now()).dgram();
+
+ let mut unknown_version_packet = vec![0x80, 0x1a, 0x1a, 0x1a, 0x1a];
+ unknown_version_packet.resize(1200, 0x0);
+ let _ = client.process(
+ Some(Datagram::new(
+ loopback(),
+ loopback(),
+ unknown_version_packet,
+ )),
+ now(),
+ );
+ assert_eq!(1, client.stats().dropped_rx);
+}
+
+#[test]
+fn server_receive_unknown_first_packet() {
+ let mut server = default_server();
+
+ let mut unknown_version_packet = vec![0x80, 0x1a, 0x1a, 0x1a, 0x1a];
+ unknown_version_packet.resize(1200, 0x0);
+
+ assert_eq!(
+ server.process(
+ Some(Datagram::new(
+ loopback(),
+ loopback(),
+ unknown_version_packet,
+ )),
+ now(),
+ ),
+ Output::None
+ );
+
+ assert_eq!(1, server.stats().dropped_rx);
+}
+
+fn create_vn(initial_pkt: &[u8], versions: &[u32]) -> Vec<u8> {
+ let mut dec = Decoder::from(&initial_pkt[5..]); // Skip past version.
+ let dst_cid = dec.decode_vec(1).expect("client DCID");
+ let src_cid = dec.decode_vec(1).expect("client SCID");
+
+ let mut encoder = Encoder::default();
+ encoder.encode_byte(PACKET_BIT_LONG);
+ encoder.encode(&[0; 4]); // Zero version == VN.
+ encoder.encode_vec(1, dst_cid);
+ encoder.encode_vec(1, src_cid);
+
+ for v in versions {
+ encoder.encode_uint(4, *v);
+ }
+ encoder.into()
+}
+
+#[test]
+fn version_negotiation_current_version() {
+ let mut client = default_client();
+ // Start the handshake.
+ let initial_pkt = client
+ .process(None, now())
+ .dgram()
+ .expect("a datagram")
+ .to_vec();
+
+ let vn = create_vn(
+ &initial_pkt,
+ &[0x1a1a_1a1a, QuicVersion::default().as_u32()],
+ );
+
+ let dgram = Datagram::new(loopback(), loopback(), vn);
+ let delay = client.process(Some(dgram), now()).callback();
+ assert_eq!(delay, INITIAL_PTO);
+ assert_eq!(*client.state(), State::WaitInitial);
+ assert_eq!(1, client.stats().dropped_rx);
+}
+
+#[test]
+fn version_negotiation_only_reserved() {
+ let mut client = default_client();
+ // Start the handshake.
+ let initial_pkt = client
+ .process(None, now())
+ .dgram()
+ .expect("a datagram")
+ .to_vec();
+
+ let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a]);
+
+ let dgram = Datagram::new(loopback(), loopback(), vn);
+ assert_eq!(client.process(Some(dgram), now()), Output::None);
+ match client.state() {
+ State::Closed(err) => {
+ assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation))
+ }
+ _ => panic!("Invalid client state"),
+ }
+}
+
+#[test]
+fn version_negotiation_corrupted() {
+ let mut client = default_client();
+ // Start the handshake.
+ let initial_pkt = client
+ .process(None, now())
+ .dgram()
+ .expect("a datagram")
+ .to_vec();
+
+ let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a]);
+
+ let dgram = Datagram::new(loopback(), loopback(), &vn[..vn.len() - 1]);
+ let delay = client.process(Some(dgram), now()).callback();
+ assert_eq!(delay, INITIAL_PTO);
+ assert_eq!(*client.state(), State::WaitInitial);
+ assert_eq!(1, client.stats().dropped_rx);
+}
+
+#[test]
+fn version_negotiation_empty() {
+ let mut client = default_client();
+ // Start the handshake.
+ let initial_pkt = client
+ .process(None, now())
+ .dgram()
+ .expect("a datagram")
+ .to_vec();
+
+ let vn = create_vn(&initial_pkt, &[]);
+
+ let dgram = Datagram::new(loopback(), loopback(), vn);
+ let delay = client.process(Some(dgram), now()).callback();
+ assert_eq!(delay, INITIAL_PTO);
+ assert_eq!(*client.state(), State::WaitInitial);
+ assert_eq!(1, client.stats().dropped_rx);
+}
+
+#[test]
+fn version_negotiation_not_supported() {
+ let mut client = default_client();
+ // Start the handshake.
+ let initial_pkt = client
+ .process(None, now())
+ .dgram()
+ .expect("a datagram")
+ .to_vec();
+
+ let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
+
+ assert_eq!(
+ client.process(Some(Datagram::new(loopback(), loopback(), vn)), now(),),
+ Output::None
+ );
+ match client.state() {
+ State::Closed(err) => {
+ assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation))
+ }
+ _ => panic!("Invalid client state"),
+ }
+}
+
+#[test]
+fn version_negotiation_bad_cid() {
+ let mut client = default_client();
+ // Start the handshake.
+ let initial_pkt = client
+ .process(None, now())
+ .dgram()
+ .expect("a datagram")
+ .to_vec();
+
+ let mut vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
+ vn[6] ^= 0xc4;
+
+ let dgram = Datagram::new(loopback(), loopback(), vn);
+ let delay = client.process(Some(dgram), now()).callback();
+ assert_eq!(delay, INITIAL_PTO);
+ assert_eq!(*client.state(), State::WaitInitial);
+ assert_eq!(1, client.stats().dropped_rx);
+}
diff --git a/third_party/rust/neqo-transport/src/connection/tests/zerortt.rs b/third_party/rust/neqo-transport/src/connection/tests/zerortt.rs
new file mode 100644
index 0000000000..4ccffe4203
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/connection/tests/zerortt.rs
@@ -0,0 +1,193 @@
+// 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::super::{Connection, FixedConnectionIdManager};
+use super::{connect, default_client, default_server, exchange_ticket};
+use crate::events::ConnectionEvent;
+use crate::frame::StreamType;
+use crate::{ConnectionParameters, Error};
+
+use neqo_common::event::Provider;
+use neqo_crypto::{AllowZeroRtt, AntiReplay};
+use std::cell::RefCell;
+use std::rc::Rc;
+use test_fixture::{self, assertions, now};
+
+#[test]
+fn zero_rtt_negotiate() {
+ // Note that the two servers in this test will get different anti-replay filters.
+ // That's OK because we aren't testing anti-replay.
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let token = exchange_ticket(&mut client, &mut server, now());
+ let mut client = default_client();
+ client
+ .enable_resumption(now(), token)
+ .expect("should set token");
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+ assert!(client.tls_info().unwrap().early_data_accepted());
+ assert!(server.tls_info().unwrap().early_data_accepted());
+}
+
+#[test]
+fn zero_rtt_send_recv() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let token = exchange_ticket(&mut client, &mut server, now());
+ let mut client = default_client();
+ client
+ .enable_resumption(now(), token)
+ .expect("should set token");
+ let mut server = default_server();
+
+ // Send ClientHello.
+ let client_hs = client.process(None, now());
+ assert!(client_hs.as_dgram_ref().is_some());
+
+ // Now send a 0-RTT packet.
+ let client_stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream_id, &[1, 2, 3]).unwrap();
+ let client_0rtt = client.process(None, now());
+ assert!(client_0rtt.as_dgram_ref().is_some());
+ // 0-RTT packets on their own shouldn't be padded to 1200.
+ assert!(client_0rtt.as_dgram_ref().unwrap().len() < 1200);
+
+ let server_hs = server.process(client_hs.dgram(), now());
+ assert!(server_hs.as_dgram_ref().is_some()); // ServerHello, etc...
+ let server_process_0rtt = server.process(client_0rtt.dgram(), now());
+ assert!(server_process_0rtt.as_dgram_ref().is_none());
+
+ let server_stream_id = server
+ .events()
+ .find_map(|evt| match evt {
+ ConnectionEvent::NewStream { stream_id, .. } => Some(stream_id),
+ _ => None,
+ })
+ .expect("should have received a new stream event");
+ assert_eq!(client_stream_id, server_stream_id.as_u64());
+}
+
+#[test]
+fn zero_rtt_send_coalesce() {
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let token = exchange_ticket(&mut client, &mut server, now());
+ let mut client = default_client();
+ client
+ .enable_resumption(now(), token)
+ .expect("should set token");
+ let mut server = default_server();
+
+ // Write 0-RTT before generating any packets.
+ // This should result in a datagram that coalesces Initial and 0-RTT.
+ let client_stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(client_stream_id, &[1, 2, 3]).unwrap();
+ let client_0rtt = client.process(None, now());
+ assert!(client_0rtt.as_dgram_ref().is_some());
+
+ assertions::assert_coalesced_0rtt(&client_0rtt.as_dgram_ref().unwrap()[..]);
+
+ let server_hs = server.process(client_0rtt.dgram(), now());
+ assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc...
+
+ let server_stream_id = server
+ .events()
+ .find_map(|evt| match evt {
+ ConnectionEvent::NewStream { stream_id } => Some(stream_id),
+ _ => None,
+ })
+ .expect("should have received a new stream event");
+ assert_eq!(client_stream_id, server_stream_id.as_u64());
+}
+
+#[test]
+fn zero_rtt_before_resumption_token() {
+ let mut client = default_client();
+ assert!(client.stream_create(StreamType::BiDi).is_err());
+}
+
+#[test]
+fn zero_rtt_send_reject() {
+ const MESSAGE: &[u8] = &[1, 2, 3];
+
+ let mut client = default_client();
+ let mut server = default_server();
+ connect(&mut client, &mut server);
+
+ let token = exchange_ticket(&mut client, &mut server, now());
+ let mut client = default_client();
+ client
+ .enable_resumption(now(), token)
+ .expect("should set token");
+ let mut server = Connection::new_server(
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(10))),
+ &ConnectionParameters::default(),
+ )
+ .unwrap();
+ // Using a freshly initialized anti-replay context
+ // should result in the server rejecting 0-RTT.
+ let ar =
+ AntiReplay::new(now(), test_fixture::ANTI_REPLAY_WINDOW, 1, 3).expect("setup anti-replay");
+ server
+ .server_enable_0rtt(&ar, AllowZeroRtt {})
+ .expect("enable 0-RTT");
+
+ // Send ClientHello.
+ let client_hs = client.process(None, now());
+ assert!(client_hs.as_dgram_ref().is_some());
+
+ // Write some data on the client.
+ let stream_id = client.stream_create(StreamType::UniDi).unwrap();
+ client.stream_send(stream_id, MESSAGE).unwrap();
+ let client_0rtt = client.process(None, now());
+ assert!(client_0rtt.as_dgram_ref().is_some());
+
+ let server_hs = server.process(client_hs.dgram(), now());
+ assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc...
+ let server_ignored = server.process(client_0rtt.dgram(), now());
+ assert!(server_ignored.as_dgram_ref().is_none());
+
+ // The server shouldn't receive that 0-RTT data.
+ let recvd_stream_evt = |e| matches!(e, ConnectionEvent::NewStream { .. });
+ assert!(!server.events().any(recvd_stream_evt));
+
+ // Client should get a rejection.
+ let client_fin = client.process(server_hs.dgram(), now());
+ let recvd_0rtt_reject = |e| e == ConnectionEvent::ZeroRttRejected;
+ assert!(client.events().any(recvd_0rtt_reject));
+
+ // Server consume client_fin
+ let server_ack = server.process(client_fin.dgram(), now());
+ assert!(server_ack.as_dgram_ref().is_some());
+ let client_out = client.process(server_ack.dgram(), now());
+ assert!(client_out.as_dgram_ref().is_none());
+
+ // ...and the client stream should be gone.
+ let res = client.stream_send(stream_id, MESSAGE);
+ assert!(res.is_err());
+ assert_eq!(res.unwrap_err(), Error::InvalidStreamId);
+
+ // Open a new stream and send data. StreamId should start with 0.
+ let stream_id_after_reject = client.stream_create(StreamType::UniDi).unwrap();
+ assert_eq!(stream_id, stream_id_after_reject);
+ client.stream_send(stream_id_after_reject, MESSAGE).unwrap();
+ let client_after_reject = client.process(None, now());
+ assert!(client_after_reject.as_dgram_ref().is_some());
+
+ // The server should receive new stream
+ let server_out = server.process(client_after_reject.dgram(), now());
+ assert!(server_out.as_dgram_ref().is_none()); // suppress the ack
+ assert!(server.events().any(recvd_stream_evt));
+}
diff --git a/third_party/rust/neqo-transport/src/crypto.rs b/third_party/rust/neqo-transport/src/crypto.rs
new file mode 100644
index 0000000000..bed669f003
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/crypto.rs
@@ -0,0 +1,1293 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::convert::TryFrom;
+use std::mem;
+use std::ops::{Index, IndexMut, Range};
+use std::rc::Rc;
+use std::time::Instant;
+
+use neqo_common::{hex, hex_snip_middle, qdebug, qinfo, qtrace, Encoder, Role};
+use neqo_crypto::{
+ aead::Aead, hkdf, hp::HpKey, Agent, AntiReplay, Cipher, Epoch, HandshakeState, Record,
+ RecordList, ResumptionToken, SymKey, ZeroRttChecker, TLS_AES_128_GCM_SHA256,
+ TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_CT_HANDSHAKE,
+ TLS_EPOCH_APPLICATION_DATA, TLS_EPOCH_HANDSHAKE, TLS_EPOCH_INITIAL, TLS_EPOCH_ZERO_RTT,
+ TLS_VERSION_1_3,
+};
+
+use crate::packet::{PacketBuilder, PacketNumber, QuicVersion};
+use crate::recovery::RecoveryToken;
+use crate::recv_stream::RxStreamOrderer;
+use crate::send_stream::TxBuffer;
+use crate::tparams::{TpZeroRttChecker, TransportParameters, TransportParametersHandler};
+use crate::tracking::PNSpace;
+use crate::{Error, Res};
+
+const MAX_AUTH_TAG: usize = 32;
+/// The number of invocations remaining on a write cipher before we try
+/// to update keys. This has to be much smaller than the number returned
+/// by `CryptoDxState::limit` or updates will happen too often. As we don't
+/// need to ask permission to update, this can be quite small.
+pub(crate) const UPDATE_WRITE_KEYS_AT: PacketNumber = 100;
+
+// This is a testing kludge that allows for overwriting the number of
+// invocations of the next cipher to operate. With this, it is possible
+// to test what happens when the number of invocations reaches 0, or
+// when it hits `UPDATE_WRITE_KEYS_AT` and an automatic update should occur.
+// This is a little crude, but it saves a lot of plumbing.
+#[cfg(test)]
+thread_local!(pub(crate) static OVERWRITE_INVOCATIONS: RefCell<Option<PacketNumber>> = RefCell::default());
+
+#[derive(Debug)]
+pub struct Crypto {
+ pub(crate) tls: Agent,
+ pub(crate) streams: CryptoStreams,
+ pub(crate) states: CryptoStates,
+}
+
+type TpHandler = Rc<RefCell<TransportParametersHandler>>;
+
+impl Crypto {
+ pub fn new(mut agent: Agent, protocols: &[impl AsRef<str>], tphandler: TpHandler) -> Res<Self> {
+ agent.set_version_range(TLS_VERSION_1_3, TLS_VERSION_1_3)?;
+ agent.set_ciphers(&[
+ TLS_AES_128_GCM_SHA256,
+ TLS_AES_256_GCM_SHA384,
+ TLS_CHACHA20_POLY1305_SHA256,
+ ])?;
+ agent.set_alpn(protocols)?;
+ agent.disable_end_of_early_data()?;
+ // Always enable 0-RTT on the client, but the server needs
+ // more configuration passed to server_enable_0rtt.
+ if let Agent::Client(c) = &mut agent {
+ c.enable_0rtt()?;
+ }
+ agent.extension_handler(0xffa5, tphandler)?;
+ Ok(Self {
+ tls: agent,
+ streams: Default::default(),
+ states: Default::default(),
+ })
+ }
+
+ pub fn server_enable_0rtt(
+ &mut self,
+ tphandler: TpHandler,
+ anti_replay: &AntiReplay,
+ zero_rtt_checker: impl ZeroRttChecker + 'static,
+ ) -> Res<()> {
+ if let Agent::Server(s) = &mut self.tls {
+ Ok(s.enable_0rtt(
+ anti_replay,
+ 0xffff_ffff,
+ TpZeroRttChecker::wrap(tphandler, zero_rtt_checker),
+ )?)
+ } else {
+ panic!("not a server");
+ }
+ }
+
+ pub fn handshake(
+ &mut self,
+ now: Instant,
+ space: PNSpace,
+ data: Option<&[u8]>,
+ ) -> Res<&HandshakeState> {
+ let input = data.map(|d| {
+ qtrace!("Handshake record received {:0x?} ", d);
+ let epoch = match space {
+ PNSpace::Initial => TLS_EPOCH_INITIAL,
+ PNSpace::Handshake => TLS_EPOCH_HANDSHAKE,
+ // Our epoch progresses forward, but the TLS epoch is fixed to 3.
+ PNSpace::ApplicationData => TLS_EPOCH_APPLICATION_DATA,
+ };
+ Record {
+ ct: TLS_CT_HANDSHAKE,
+ epoch,
+ data: d.to_vec(),
+ }
+ });
+
+ match self.tls.handshake_raw(now, input) {
+ Ok(output) => {
+ self.buffer_records(output)?;
+ Ok(self.tls.state())
+ }
+ Err(e) => {
+ qinfo!("Handshake failed");
+ Err(match self.tls.alert() {
+ Some(a) => Error::CryptoAlert(*a),
+ _ => Error::CryptoError(e),
+ })
+ }
+ }
+ }
+
+ /// Enable 0-RTT and return `true` if it is enabled successfully.
+ pub fn enable_0rtt(&mut self, role: Role) -> Res<bool> {
+ let info = self.tls.preinfo()?;
+ // `info.early_data()` returns false for a server,
+ // so use `early_data_cipher()` to tell if 0-RTT is enabled.
+ let cipher = info.early_data_cipher();
+ if cipher.is_none() {
+ return Ok(false);
+ }
+ let (dir, secret) = match role {
+ Role::Client => (
+ CryptoDxDirection::Write,
+ self.tls.write_secret(TLS_EPOCH_ZERO_RTT),
+ ),
+ Role::Server => (
+ CryptoDxDirection::Read,
+ self.tls.read_secret(TLS_EPOCH_ZERO_RTT),
+ ),
+ };
+ let secret = secret.ok_or(Error::InternalError)?;
+ self.states.set_0rtt_keys(dir, &secret, cipher.unwrap());
+ Ok(true)
+ }
+
+ /// Returns true if new handshake keys were installed.
+ pub fn install_keys(&mut self, role: Role) -> Res<bool> {
+ if !self.tls.state().is_final() {
+ let installed_hs = self.install_handshake_keys()?;
+ if role == Role::Server {
+ self.maybe_install_application_write_key()?;
+ }
+ Ok(installed_hs)
+ } else {
+ Ok(false)
+ }
+ }
+
+ fn install_handshake_keys(&mut self) -> Res<bool> {
+ qtrace!([self], "Attempt to install handshake keys");
+ let write_secret = if let Some(secret) = self.tls.write_secret(TLS_EPOCH_HANDSHAKE) {
+ secret
+ } else {
+ // No keys is fine.
+ return Ok(false);
+ };
+ let read_secret = self
+ .tls
+ .read_secret(TLS_EPOCH_HANDSHAKE)
+ .ok_or(Error::InternalError)?;
+ let cipher = match self.tls.info() {
+ None => self.tls.preinfo()?.cipher_suite(),
+ Some(info) => Some(info.cipher_suite()),
+ }
+ .ok_or(Error::InternalError)?;
+ self.states
+ .set_handshake_keys(&write_secret, &read_secret, cipher);
+ qdebug!([self], "Handshake keys installed");
+ Ok(true)
+ }
+
+ fn maybe_install_application_write_key(&mut self) -> Res<()> {
+ qtrace!([self], "Attempt to install application write key");
+ if let Some(secret) = self.tls.write_secret(TLS_EPOCH_APPLICATION_DATA) {
+ self.states.set_application_write_key(secret)?;
+ qdebug!([self], "Application write key installed");
+ }
+ Ok(())
+ }
+
+ pub fn install_application_keys(&mut self, expire_0rtt: Instant) -> Res<()> {
+ self.maybe_install_application_write_key()?;
+ // The write key might have been installed earlier, but it should
+ // always be installed now.
+ debug_assert!(self.states.app_write.is_some());
+ let read_secret = self
+ .tls
+ .read_secret(TLS_EPOCH_APPLICATION_DATA)
+ .ok_or(Error::InternalError)?;
+ self.states
+ .set_application_read_key(read_secret, expire_0rtt)?;
+ qdebug!([self], "application read keys installed");
+ Ok(())
+ }
+
+ /// Buffer crypto records for sending.
+ pub fn buffer_records(&mut self, records: RecordList) -> Res<()> {
+ for r in records {
+ if r.ct != TLS_CT_HANDSHAKE {
+ return Err(Error::ProtocolViolation);
+ }
+ qtrace!([self], "Adding CRYPTO data {:?}", r);
+ self.streams.send(PNSpace::from(r.epoch), &r.data);
+ }
+ Ok(())
+ }
+
+ pub fn acked(&mut self, token: &CryptoRecoveryToken) {
+ qinfo!(
+ "Acked crypto frame space={} offset={} length={}",
+ token.space,
+ token.offset,
+ token.length
+ );
+ self.streams.acked(token);
+ }
+
+ pub fn lost(&mut self, token: &CryptoRecoveryToken) {
+ qinfo!(
+ "Lost crypto frame space={} offset={} length={}",
+ token.space,
+ token.offset,
+ token.length
+ );
+ self.streams.lost(token);
+ }
+
+ /// Mark any outstanding frames in the indicated space as "lost" so
+ /// that they can be sent again.
+ pub fn resend_unacked(&mut self, space: PNSpace) {
+ self.streams.resend_unacked(space);
+ }
+
+ /// Discard state for a packet number space and return true
+ /// if something was discarded.
+ pub fn discard(&mut self, space: PNSpace) -> bool {
+ self.streams.discard(space);
+ self.states.discard(space)
+ }
+
+ pub fn create_resumption_token(
+ &mut self,
+ new_token: Option<&[u8]>,
+ tps: &TransportParameters,
+ rtt: u64,
+ ) -> Option<ResumptionToken> {
+ if let Agent::Client(ref mut c) = self.tls {
+ if let Some(ref t) = c.resumption_token() {
+ qtrace!("TLS token {}", hex(t.as_ref()));
+ let mut enc = Encoder::default();
+ enc.encode_varint(rtt);
+ enc.encode_vvec_with(|enc_inner| {
+ tps.encode(enc_inner);
+ });
+ enc.encode_vvec(new_token.unwrap_or(&[]));
+ enc.encode(t.as_ref());
+ qinfo!("resumption token {}", hex_snip_middle(&enc[..]));
+ Some(ResumptionToken::new(enc.into(), t.expiration_time()))
+ } else {
+ None
+ }
+ } else {
+ unreachable!("It is a server.");
+ }
+ }
+
+ pub fn has_resumption_token(&self) -> bool {
+ if let Agent::Client(c) = &self.tls {
+ c.has_resumption_token()
+ } else {
+ unreachable!("It is a server.");
+ }
+ }
+}
+
+impl ::std::fmt::Display for Crypto {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Crypto")
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum CryptoDxDirection {
+ Read,
+ Write,
+}
+
+#[derive(Debug)]
+pub struct CryptoDxState {
+ direction: CryptoDxDirection,
+ /// The epoch of this crypto state. This initially tracks TLS epochs
+ /// via DTLS: 0 = initial, 1 = 0-RTT, 2 = handshake, 3 = application.
+ /// But we don't need to keep that, and QUIC isn't limited in how
+ /// many times keys can be updated, so we don't use `u16` for this.
+ epoch: usize,
+ aead: Aead,
+ hpkey: HpKey,
+ /// This tracks the range of packet numbers that have been seen. This allows
+ /// for verifying that packet numbers before a key update are strictly lower
+ /// than packet numbers after a key update.
+ used_pn: Range<PacketNumber>,
+ /// This is the minimum packet number that is allowed.
+ min_pn: PacketNumber,
+ /// The total number of operations that are remaining before the keys
+ /// become exhausted and can't be used any more.
+ invocations: PacketNumber,
+}
+
+impl CryptoDxState {
+ #[allow(clippy::unknown_clippy_lints)] // Until we require rust 1.45.
+ #[allow(clippy::reversed_empty_ranges)] // To initialize an empty range.
+ pub fn new(
+ direction: CryptoDxDirection,
+ epoch: Epoch,
+ secret: &SymKey,
+ cipher: Cipher,
+ ) -> Self {
+ qinfo!(
+ "Making {:?} {} CryptoDxState, cipher={}",
+ direction,
+ epoch,
+ cipher
+ );
+ Self {
+ direction,
+ epoch: usize::from(epoch),
+ aead: Aead::new(TLS_VERSION_1_3, cipher, secret, "quic ").unwrap(),
+ hpkey: HpKey::extract(TLS_VERSION_1_3, cipher, secret, "quic hp").unwrap(),
+ used_pn: 0..0,
+ min_pn: 0,
+ invocations: Self::limit(direction, cipher),
+ }
+ }
+
+ pub fn new_initial(
+ quic_version: QuicVersion,
+ direction: CryptoDxDirection,
+ label: &str,
+ dcid: &[u8],
+ ) -> Self {
+ qtrace!("new_initial for {:?}", quic_version);
+ const INITIAL_SALT_27: &[u8] = &[
+ 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4,
+ 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
+ ];
+ const INITIAL_SALT_29_32: &[u8] = &[
+ 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
+ 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
+ ];
+ let salt = match quic_version {
+ QuicVersion::Draft27 | QuicVersion::Draft28 => INITIAL_SALT_27,
+ QuicVersion::Draft29
+ | QuicVersion::Draft30
+ | QuicVersion::Draft31
+ | QuicVersion::Draft32 => INITIAL_SALT_29_32,
+ };
+ let cipher = TLS_AES_128_GCM_SHA256;
+ let initial_secret = hkdf::extract(
+ TLS_VERSION_1_3,
+ cipher,
+ Some(
+ hkdf::import_key(TLS_VERSION_1_3, cipher, salt)
+ .as_ref()
+ .unwrap(),
+ ),
+ hkdf::import_key(TLS_VERSION_1_3, cipher, dcid)
+ .as_ref()
+ .unwrap(),
+ )
+ .unwrap();
+
+ let secret =
+ hkdf::expand_label(TLS_VERSION_1_3, cipher, &initial_secret, &[], label).unwrap();
+
+ Self::new(direction, TLS_EPOCH_INITIAL, &secret, cipher)
+ }
+
+ /// Determine the confidentiality and integrity limits for the cipher.
+ fn limit(direction: CryptoDxDirection, cipher: Cipher) -> PacketNumber {
+ match direction {
+ // This uses the smaller limits for 2^16 byte packets
+ // as we don't control incoming packet size.
+ CryptoDxDirection::Read => match cipher {
+ TLS_AES_128_GCM_SHA256 => 1 << 52,
+ TLS_AES_256_GCM_SHA384 => PacketNumber::MAX,
+ TLS_CHACHA20_POLY1305_SHA256 => 1 << 36,
+ _ => unreachable!(),
+ },
+ // This uses the larger limits for 2^11 byte packets.
+ CryptoDxDirection::Write => match cipher {
+ TLS_AES_128_GCM_SHA256 | TLS_AES_256_GCM_SHA384 => 1 << 28,
+ TLS_CHACHA20_POLY1305_SHA256 => PacketNumber::MAX,
+ _ => unreachable!(),
+ },
+ }
+ }
+
+ fn invoked(&mut self) -> Res<()> {
+ #[cfg(test)]
+ OVERWRITE_INVOCATIONS.with(|v| {
+ if let Some(i) = v.borrow_mut().take() {
+ neqo_common::qwarn!("Setting {:?} invocations to {}", self.direction, i);
+ self.invocations = i;
+ }
+ });
+ self.invocations = self
+ .invocations
+ .checked_sub(1)
+ .ok_or(Error::KeysExhausted)?;
+ Ok(())
+ }
+
+ /// Determine whether we should initiate a key update.
+ pub fn should_update(&self) -> bool {
+ // There is no point in updating read keys as the limit is global.
+ debug_assert_eq!(self.direction, CryptoDxDirection::Write);
+ self.invocations <= UPDATE_WRITE_KEYS_AT
+ }
+
+ pub fn next(&self, next_secret: &SymKey, cipher: Cipher) -> Self {
+ let pn = self.next_pn();
+ // We count invocations of each write key just for that key, but all
+ // attempts to invocations to read count toward a single limit.
+ // This doesn't count use of Handshake keys.
+ let invocations = if self.direction == CryptoDxDirection::Read {
+ self.invocations
+ } else {
+ Self::limit(CryptoDxDirection::Write, cipher)
+ };
+ Self {
+ direction: self.direction,
+ epoch: self.epoch + 1,
+ aead: Aead::new(TLS_VERSION_1_3, cipher, next_secret, "quic ").unwrap(),
+ hpkey: self.hpkey.clone(),
+ used_pn: pn..pn,
+ min_pn: pn,
+ invocations,
+ }
+ }
+
+ #[must_use]
+ pub fn key_phase(&self) -> bool {
+ // Epoch 3 => 0, 4 => 1, 5 => 0, 6 => 1, ...
+ self.epoch & 1 != 1
+ }
+
+ /// This is a continuation of a previous, so adjust the range accordingly.
+ /// Fail if the two ranges overlap. Do nothing if the directions don't match.
+ pub fn continuation(&mut self, prev: &Self) -> Res<()> {
+ debug_assert_eq!(self.direction, prev.direction);
+ let next = prev.next_pn();
+ self.min_pn = next;
+ // TODO(mt) use Range::is_empty() when available
+ if self.used_pn.start == self.used_pn.end {
+ self.used_pn = next..next;
+ Ok(())
+ } else if prev.used_pn.end > self.used_pn.start {
+ qdebug!(
+ [self],
+ "Found packet with too new packet number {} > {}, compared to {}",
+ self.used_pn.start,
+ prev.used_pn.end,
+ prev,
+ );
+ Err(Error::PacketNumberOverlap)
+ } else {
+ self.used_pn.start = next;
+ Ok(())
+ }
+ }
+
+ /// Mark a packet number as used. If this is too low, reject it.
+ /// Note that this won't catch a value that is too high if packets protected with
+ /// old keys are received after a key update. That needs to be caught elsewhere.
+ pub fn used(&mut self, pn: PacketNumber) -> Res<()> {
+ if pn < self.min_pn {
+ qdebug!(
+ [self],
+ "Found packet with too old packet number: {} < {}",
+ pn,
+ self.min_pn
+ );
+ return Err(Error::PacketNumberOverlap);
+ }
+ if self.used_pn.start == self.used_pn.end {
+ self.used_pn.start = pn;
+ }
+ self.used_pn.end = max(pn + 1, self.used_pn.end);
+ Ok(())
+ }
+
+ #[must_use]
+ pub fn needs_update(&self) -> bool {
+ // Only initiate a key update if we have processed exactly one packet
+ // and we are in an epoch greater than 3.
+ self.used_pn.start + 1 == self.used_pn.end
+ && self.epoch > usize::from(TLS_EPOCH_APPLICATION_DATA)
+ }
+
+ #[must_use]
+ pub fn can_update(&self, largest_acknowledged: Option<PacketNumber>) -> bool {
+ if let Some(la) = largest_acknowledged {
+ self.used_pn.contains(&la)
+ } else {
+ // If we haven't received any acknowledgments, it's OK to update
+ // the first application data epoch.
+ self.epoch == usize::from(TLS_EPOCH_APPLICATION_DATA)
+ }
+ }
+
+ pub fn compute_mask(&self, sample: &[u8]) -> Res<Vec<u8>> {
+ let mask = self.hpkey.mask(sample)?;
+ qtrace!([self], "HP sample={} mask={}", hex(sample), hex(&mask));
+ Ok(mask)
+ }
+
+ #[must_use]
+ pub fn next_pn(&self) -> PacketNumber {
+ self.used_pn.end
+ }
+
+ pub fn encrypt(&mut self, pn: PacketNumber, hdr: &[u8], body: &[u8]) -> Res<Vec<u8>> {
+ debug_assert_eq!(self.direction, CryptoDxDirection::Write);
+ qtrace!(
+ [self],
+ "encrypt pn={} hdr={} body={}",
+ pn,
+ hex(hdr),
+ hex(body)
+ );
+ // The numbers in `Self::limit` assume a maximum packet size of 2^11.
+ assert!(body.len() <= 2048);
+ self.invoked()?;
+
+ let size = body.len() + MAX_AUTH_TAG;
+ let mut out = vec![0; size];
+ let res = self.aead.encrypt(pn, hdr, body, &mut out)?;
+
+ qtrace!([self], "encrypt ct={}", hex(res));
+ debug_assert_eq!(pn, self.next_pn());
+ self.used(pn)?;
+ Ok(res.to_vec())
+ }
+
+ #[must_use]
+ pub fn expansion(&self) -> usize {
+ self.aead.expansion()
+ }
+
+ pub fn decrypt(&mut self, pn: PacketNumber, hdr: &[u8], body: &[u8]) -> Res<Vec<u8>> {
+ debug_assert_eq!(self.direction, CryptoDxDirection::Read);
+ qtrace!(
+ [self],
+ "decrypt pn={} hdr={} body={}",
+ pn,
+ hex(hdr),
+ hex(body)
+ );
+ self.invoked()?;
+ let mut out = vec![0; body.len()];
+ let res = self.aead.decrypt(pn, hdr, body, &mut out)?;
+ self.used(pn)?;
+ Ok(res.to_vec())
+ }
+
+ #[cfg(test)]
+ pub(crate) fn test_default() -> Self {
+ // This matches the value in packet.rs
+ const CLIENT_CID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
+ Self::new_initial(
+ QuicVersion::default(),
+ CryptoDxDirection::Write,
+ "server in",
+ CLIENT_CID,
+ )
+ }
+
+ /// Get the amount of extra padding packets protected with this profile need.
+ /// This is the difference between the size of the header protection sample
+ /// and the AEAD expansion.
+ pub fn extra_padding(&self) -> usize {
+ self.hpkey
+ .sample_size()
+ .saturating_sub(self.aead.expansion())
+ }
+}
+
+impl std::fmt::Display for CryptoDxState {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "epoch {} {:?}", self.epoch, self.direction)
+ }
+}
+
+#[derive(Debug)]
+pub struct CryptoState {
+ tx: CryptoDxState,
+ rx: CryptoDxState,
+}
+
+impl Index<CryptoDxDirection> for CryptoState {
+ type Output = CryptoDxState;
+
+ fn index(&self, dir: CryptoDxDirection) -> &Self::Output {
+ match dir {
+ CryptoDxDirection::Read => &self.rx,
+ CryptoDxDirection::Write => &self.tx,
+ }
+ }
+}
+
+impl IndexMut<CryptoDxDirection> for CryptoState {
+ fn index_mut(&mut self, dir: CryptoDxDirection) -> &mut Self::Output {
+ match dir {
+ CryptoDxDirection::Read => &mut self.rx,
+ CryptoDxDirection::Write => &mut self.tx,
+ }
+ }
+}
+
+/// `CryptoDxAppData` wraps the state necessary for one direction of application data keys.
+/// This includes the secret needed to generate the next set of keys.
+#[derive(Debug)]
+pub(crate) struct CryptoDxAppData {
+ dx: CryptoDxState,
+ cipher: Cipher,
+ // Not the secret used to create `self.dx`, but the one needed for the next iteration.
+ next_secret: SymKey,
+}
+
+impl CryptoDxAppData {
+ pub fn new(dir: CryptoDxDirection, secret: SymKey, cipher: Cipher) -> Res<Self> {
+ Ok(Self {
+ dx: CryptoDxState::new(dir, TLS_EPOCH_APPLICATION_DATA, &secret, cipher),
+ cipher,
+ next_secret: Self::update_secret(cipher, &secret)?,
+ })
+ }
+
+ fn update_secret(cipher: Cipher, secret: &SymKey) -> Res<SymKey> {
+ let next = hkdf::expand_label(TLS_VERSION_1_3, cipher, secret, &[], "quic ku")?;
+ Ok(next)
+ }
+
+ pub fn next(&self) -> Res<Self> {
+ if self.dx.epoch == usize::max_value() {
+ // Guard against too many key updates.
+ return Err(Error::KeysExhausted);
+ }
+ let next_secret = Self::update_secret(self.cipher, &self.next_secret)?;
+ Ok(Self {
+ dx: self.dx.next(&self.next_secret, self.cipher),
+ cipher: self.cipher,
+ next_secret,
+ })
+ }
+
+ pub fn epoch(&self) -> usize {
+ self.dx.epoch
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub enum CryptoSpace {
+ Initial,
+ ZeroRtt,
+ Handshake,
+ ApplicationData,
+}
+
+#[derive(Debug, Default)]
+pub struct CryptoStates {
+ initial: Option<CryptoState>,
+ handshake: Option<CryptoState>,
+ zero_rtt: Option<CryptoDxState>, // One direction only!
+ cipher: Cipher,
+ app_write: Option<CryptoDxAppData>,
+ app_read: Option<CryptoDxAppData>,
+ app_read_next: Option<CryptoDxAppData>,
+ // If this is set, then we have noticed a genuine update.
+ // Once this time passes, we should switch in new keys.
+ read_update_time: Option<Instant>,
+}
+
+impl CryptoStates {
+ /// Select a `CryptoDxState` and `CryptoSpace` for the given `PNSpace`.
+ /// This selects 0-RTT keys for `PNSpace::ApplicationData` if 1-RTT keys are
+ /// not yet available.
+ pub fn select_tx(&mut self, space: PNSpace) -> Option<(CryptoSpace, &mut CryptoDxState)> {
+ match space {
+ PNSpace::Initial => self
+ .tx(CryptoSpace::Initial)
+ .map(|dx| (CryptoSpace::Initial, dx)),
+ PNSpace::Handshake => self
+ .tx(CryptoSpace::Handshake)
+ .map(|dx| (CryptoSpace::Handshake, dx)),
+ PNSpace::ApplicationData => {
+ if let Some(app) = self.app_write.as_mut() {
+ Some((CryptoSpace::ApplicationData, &mut app.dx))
+ } else {
+ self.zero_rtt.as_mut().map(|dx| (CryptoSpace::ZeroRtt, dx))
+ }
+ }
+ }
+ }
+
+ pub fn tx<'a>(&'a mut self, cspace: CryptoSpace) -> Option<&'a mut CryptoDxState> {
+ let tx = |k: Option<&'a mut CryptoState>| k.map(|dx| &mut dx.tx);
+ match cspace {
+ CryptoSpace::Initial => tx(self.initial.as_mut()),
+ CryptoSpace::ZeroRtt => self
+ .zero_rtt
+ .as_mut()
+ .filter(|z| z.direction == CryptoDxDirection::Write),
+ CryptoSpace::Handshake => tx(self.handshake.as_mut()),
+ CryptoSpace::ApplicationData => self.app_write.as_mut().map(|app| &mut app.dx),
+ }
+ }
+
+ pub fn rx_hp(&mut self, cspace: CryptoSpace) -> Option<&mut CryptoDxState> {
+ if let CryptoSpace::ApplicationData = cspace {
+ self.app_read.as_mut().map(|ar| &mut ar.dx)
+ } else {
+ self.rx(cspace, false)
+ }
+ }
+
+ pub fn rx<'a>(
+ &'a mut self,
+ cspace: CryptoSpace,
+ key_phase: bool,
+ ) -> Option<&'a mut CryptoDxState> {
+ let rx = |x: Option<&'a mut CryptoState>| x.map(|dx| &mut dx.rx);
+ match cspace {
+ CryptoSpace::Initial => rx(self.initial.as_mut()),
+ CryptoSpace::ZeroRtt => self
+ .zero_rtt
+ .as_mut()
+ .filter(|z| z.direction == CryptoDxDirection::Read),
+ CryptoSpace::Handshake => rx(self.handshake.as_mut()),
+ CryptoSpace::ApplicationData => {
+ let f = |a: Option<&'a mut CryptoDxAppData>| {
+ a.filter(|ar| ar.dx.key_phase() == key_phase)
+ };
+ // XOR to reduce the leakage about which key is chosen.
+ f(self.app_read.as_mut())
+ .xor(f(self.app_read_next.as_mut()))
+ .map(|ar| &mut ar.dx)
+ }
+ }
+ }
+
+ /// Whether keys for processing packets in the indicated space are pending.
+ /// This allows the caller to determine whether to save a packet for later
+ /// when keys are not available.
+ /// NOTE: 0-RTT keys are not considered here. The expectation is that a
+ /// server will have to save 0-RTT packets in a different place. Though it
+ /// is possible to attribute 0-RTT packets to an existing connection if there
+ /// is a multi-packet Initial, that is an unusual circumstance, so we
+ /// don't do caching for that in those places that call this function.
+ pub fn rx_pending(&self, space: CryptoSpace) -> bool {
+ match space {
+ CryptoSpace::Initial | CryptoSpace::ZeroRtt => false,
+ CryptoSpace::Handshake => self.handshake.is_none() && self.initial.is_some(),
+ CryptoSpace::ApplicationData => self.app_read.is_none(),
+ }
+ }
+
+ /// Create the initial crypto state.
+ pub fn init(&mut self, quic_version: QuicVersion, role: Role, dcid: &[u8]) {
+ const CLIENT_INITIAL_LABEL: &str = "client in";
+ const SERVER_INITIAL_LABEL: &str = "server in";
+
+ qinfo!(
+ [self],
+ "Creating initial cipher state role={:?} dcid={}",
+ role,
+ hex(dcid)
+ );
+
+ let (write, read) = match role {
+ Role::Client => (CLIENT_INITIAL_LABEL, SERVER_INITIAL_LABEL),
+ Role::Server => (SERVER_INITIAL_LABEL, CLIENT_INITIAL_LABEL),
+ };
+
+ let mut initial = CryptoState {
+ tx: CryptoDxState::new_initial(quic_version, CryptoDxDirection::Write, write, dcid),
+ rx: CryptoDxState::new_initial(quic_version, CryptoDxDirection::Read, read, dcid),
+ };
+ if let Some(prev) = &self.initial {
+ qinfo!(
+ [self],
+ "Continue packet numbers for initial after retry (write is {:?})",
+ prev.rx.used_pn,
+ );
+ initial.tx.continuation(&prev.tx).unwrap();
+ }
+ self.initial = Some(initial);
+ }
+
+ pub fn set_0rtt_keys(&mut self, dir: CryptoDxDirection, secret: &SymKey, cipher: Cipher) {
+ qtrace!([self], "install 0-RTT keys");
+ self.zero_rtt = Some(CryptoDxState::new(dir, TLS_EPOCH_ZERO_RTT, secret, cipher));
+ }
+
+ /// Discard keys and return true if that happened.
+ pub fn discard(&mut self, space: PNSpace) -> bool {
+ match space {
+ PNSpace::Initial => self.initial.take().is_some(),
+ PNSpace::Handshake => self.handshake.take().is_some(),
+ PNSpace::ApplicationData => panic!("Can't drop application data keys"),
+ }
+ }
+
+ pub fn discard_0rtt_keys(&mut self) {
+ qtrace!([self], "discard 0-RTT keys");
+ assert!(
+ self.app_read.is_none(),
+ "Can't discard 0-RTT after setting application keys"
+ );
+ self.zero_rtt = None;
+ }
+
+ pub fn set_handshake_keys(
+ &mut self,
+ write_secret: &SymKey,
+ read_secret: &SymKey,
+ cipher: Cipher,
+ ) {
+ self.cipher = cipher;
+ self.handshake = Some(CryptoState {
+ tx: CryptoDxState::new(
+ CryptoDxDirection::Write,
+ TLS_EPOCH_HANDSHAKE,
+ write_secret,
+ cipher,
+ ),
+ rx: CryptoDxState::new(
+ CryptoDxDirection::Read,
+ TLS_EPOCH_HANDSHAKE,
+ read_secret,
+ cipher,
+ ),
+ });
+ }
+
+ pub fn set_application_write_key(&mut self, secret: SymKey) -> Res<()> {
+ debug_assert!(self.app_write.is_none());
+ debug_assert_ne!(self.cipher, 0);
+ let mut app = CryptoDxAppData::new(CryptoDxDirection::Write, secret, self.cipher)?;
+ if let Some(z) = &self.zero_rtt {
+ if z.direction == CryptoDxDirection::Write {
+ app.dx.continuation(z)?;
+ }
+ }
+ self.zero_rtt = None;
+ self.app_write = Some(app);
+ Ok(())
+ }
+
+ pub fn set_application_read_key(&mut self, secret: SymKey, expire_0rtt: Instant) -> Res<()> {
+ debug_assert!(self.app_write.is_some(), "should have write keys installed");
+ debug_assert!(self.app_read.is_none());
+ let mut app = CryptoDxAppData::new(CryptoDxDirection::Read, secret, self.cipher)?;
+ if let Some(z) = &self.zero_rtt {
+ if z.direction == CryptoDxDirection::Read {
+ app.dx.continuation(z)?;
+ }
+ self.read_update_time = Some(expire_0rtt);
+ }
+ self.app_read_next = Some(app.next()?);
+ self.app_read = Some(app);
+ Ok(())
+ }
+
+ /// Update the write keys.
+ pub fn initiate_key_update(&mut self, largest_acknowledged: Option<PacketNumber>) -> Res<()> {
+ // Only update if we are able to. We can only do this if we have
+ // received an acknowledgement for a packet in the current phase.
+ // Also, skip this if we are waiting for read keys on the existing
+ // key update to be rolled over.
+ let write = &self.app_write.as_ref().unwrap().dx;
+ if write.can_update(largest_acknowledged) && self.read_update_time.is_none() {
+ // This call additionally checks that we don't advance to the next
+ // epoch while a key update is in progress.
+ if self.maybe_update_write()? {
+ Ok(())
+ } else {
+ qdebug!([self], "Write keys already updated");
+ Err(Error::KeyUpdateBlocked)
+ }
+ } else {
+ qdebug!([self], "Waiting for ACK or blocked on read key timer");
+ Err(Error::KeyUpdateBlocked)
+ }
+ }
+
+ /// Try to update, and return true if it happened.
+ fn maybe_update_write(&mut self) -> Res<bool> {
+ // Update write keys. But only do so if the write keys are not already
+ // ahead of the read keys. If we initiated the key update, the write keys
+ // will already be ahead.
+ debug_assert!(self.read_update_time.is_none());
+ let write = &self.app_write.as_ref().unwrap();
+ let read = &self.app_read.as_ref().unwrap();
+ if write.epoch() == read.epoch() {
+ qdebug!([self], "Update write keys to epoch={}", write.epoch() + 1);
+ self.app_write = Some(write.next()?);
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// Check whether write keys are close to running out of invocations.
+ /// If that is close, update them if possible. Failing to update at
+ /// this stage is cause for a fatal error.
+ pub fn auto_update(&mut self) -> Res<()> {
+ if let Some(app_write) = self.app_write.as_ref() {
+ if app_write.dx.should_update() {
+ qinfo!([self], "Initiating automatic key update");
+ if !self.maybe_update_write()? {
+ return Err(Error::KeysExhausted);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn has_0rtt_read(&self) -> bool {
+ self.zero_rtt
+ .as_ref()
+ .filter(|z| z.direction == CryptoDxDirection::Read)
+ .is_some()
+ }
+
+ /// Prepare to update read keys. This doesn't happen immediately as
+ /// we want to ensure that we can continue to receive any delayed
+ /// packets that use the old keys. So we just set a timer.
+ pub fn key_update_received(&mut self, expiration: Instant) -> Res<()> {
+ qtrace!([self], "Key update received");
+ // If we received a key update, then we assume that the peer has
+ // acknowledged a packet we sent in this epoch. It's OK to do that
+ // because they aren't allowed to update without first having received
+ // something from us. If the ACK isn't in the packet that triggered this
+ // key update, it must be in some other packet they have sent.
+ let _ = self.maybe_update_write()?;
+
+ // We shouldn't have 0-RTT keys at this point, but if we do, dump them.
+ debug_assert_eq!(self.read_update_time.is_some(), self.has_0rtt_read());
+ if self.has_0rtt_read() {
+ self.zero_rtt = None;
+ }
+ self.read_update_time = Some(expiration);
+ Ok(())
+ }
+
+ #[must_use]
+ pub fn update_time(&self) -> Option<Instant> {
+ self.read_update_time
+ }
+
+ /// Check if time has passed for updating key update parameters.
+ /// If it has, then swap keys over and allow more key updates to be initiated.
+ /// This is also used to discard 0-RTT read keys at the server in the same way.
+ pub fn check_key_update(&mut self, now: Instant) -> Res<()> {
+ if let Some(expiry) = self.read_update_time {
+ // If enough time has passed, then install new keys and clear the timer.
+ if now >= expiry {
+ if self.has_0rtt_read() {
+ qtrace!([self], "Discarding 0-RTT keys");
+ self.zero_rtt = None;
+ } else {
+ qtrace!([self], "Rotating read keys");
+ mem::swap(&mut self.app_read, &mut self.app_read_next);
+ self.app_read_next = Some(self.app_read.as_ref().unwrap().next()?);
+ }
+ self.read_update_time = None;
+ }
+ }
+ Ok(())
+ }
+
+ /// Get the current/highest epoch. This returns (write, read) epochs.
+ #[cfg(test)]
+ pub fn get_epochs(&self) -> (Option<usize>, Option<usize>) {
+ let to_epoch = |app: &Option<CryptoDxAppData>| app.as_ref().map(|a| a.dx.epoch);
+ (to_epoch(&self.app_write), to_epoch(&self.app_read))
+ }
+
+ /// While we are awaiting the completion of a key update, we might receive
+ /// valid packets that are protected with old keys. We need to ensure that
+ /// these don't carry packet numbers higher than those in packets protected
+ /// with the newer keys. To ensure that, this is called after every decryption.
+ pub fn check_pn_overlap(&mut self) -> Res<()> {
+ // We only need to do the check while we are waiting for read keys to be updated.
+ if self.read_update_time.is_some() {
+ qtrace!([self], "Checking for PN overlap");
+ let next_dx = &mut self.app_read_next.as_mut().unwrap().dx;
+ next_dx.continuation(&self.app_read.as_ref().unwrap().dx)?;
+ }
+ Ok(())
+ }
+
+ /// Make some state for removing protection in tests.
+ #[cfg(test)]
+ pub(crate) fn test_default() -> Self {
+ let read = |epoch| {
+ let mut dx = CryptoDxState::test_default();
+ dx.direction = CryptoDxDirection::Read;
+ dx.epoch = epoch;
+ dx
+ };
+ let app_read = |epoch| CryptoDxAppData {
+ dx: read(epoch),
+ cipher: TLS_AES_128_GCM_SHA256,
+ next_secret: hkdf::import_key(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &[0xaa; 32])
+ .unwrap(),
+ };
+ Self {
+ initial: Some(CryptoState {
+ tx: CryptoDxState::test_default(),
+ rx: read(0),
+ }),
+ handshake: None,
+ zero_rtt: None,
+ cipher: TLS_AES_128_GCM_SHA256,
+ // This isn't used, but the epoch is read to check for a key update.
+ app_write: Some(app_read(3)),
+ app_read: Some(app_read(3)),
+ app_read_next: Some(app_read(4)),
+ read_update_time: None,
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn test_chacha() -> Self {
+ const SECRET: &[u8] = &[
+ 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, 0x27, 0x48, 0xad,
+ 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, 0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3,
+ 0x0f, 0x21, 0x63, 0x2b,
+ ];
+ let secret =
+ hkdf::import_key(TLS_VERSION_1_3, TLS_CHACHA20_POLY1305_SHA256, SECRET).unwrap();
+ let app_read = |epoch| CryptoDxAppData {
+ dx: CryptoDxState {
+ direction: CryptoDxDirection::Read,
+ epoch,
+ aead: Aead::new(
+ TLS_VERSION_1_3,
+ TLS_CHACHA20_POLY1305_SHA256,
+ &secret,
+ "quic ",
+ )
+ .unwrap(),
+ hpkey: HpKey::extract(
+ TLS_VERSION_1_3,
+ TLS_CHACHA20_POLY1305_SHA256,
+ &secret,
+ "quic hp",
+ )
+ .unwrap(),
+ used_pn: 0..645_971_972,
+ min_pn: 0,
+ invocations: 10,
+ },
+ cipher: TLS_CHACHA20_POLY1305_SHA256,
+ next_secret: secret.clone(),
+ };
+ Self {
+ initial: None,
+ handshake: None,
+ zero_rtt: None,
+ cipher: TLS_CHACHA20_POLY1305_SHA256,
+ app_write: Some(app_read(3)),
+ app_read: Some(app_read(3)),
+ app_read_next: Some(app_read(4)),
+ read_update_time: None,
+ }
+ }
+}
+
+impl std::fmt::Display for CryptoStates {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "CryptoStates")
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct CryptoStream {
+ tx: TxBuffer,
+ rx: RxStreamOrderer,
+}
+
+#[derive(Debug)]
+#[allow(dead_code)] // Suppress false positive: https://github.com/rust-lang/rust/issues/68408
+pub enum CryptoStreams {
+ Initial {
+ initial: CryptoStream,
+ handshake: CryptoStream,
+ application: CryptoStream,
+ },
+ Handshake {
+ handshake: CryptoStream,
+ application: CryptoStream,
+ },
+ ApplicationData {
+ application: CryptoStream,
+ },
+}
+
+impl CryptoStreams {
+ pub fn discard(&mut self, space: PNSpace) {
+ match space {
+ PNSpace::Initial => {
+ if let Self::Initial {
+ handshake,
+ application,
+ ..
+ } = self
+ {
+ *self = Self::Handshake {
+ handshake: mem::take(handshake),
+ application: mem::take(application),
+ };
+ }
+ }
+ PNSpace::Handshake => {
+ if let Self::Handshake { application, .. } = self {
+ *self = Self::ApplicationData {
+ application: mem::take(application),
+ };
+ } else if matches!(self, Self::Initial {..}) {
+ panic!("Discarding handshake before initial discarded");
+ }
+ }
+ PNSpace::ApplicationData => panic!("Discarding application data crypto streams"),
+ }
+ }
+
+ pub fn send(&mut self, space: PNSpace, data: &[u8]) {
+ self.get_mut(space).unwrap().tx.send(data);
+ }
+
+ pub fn inbound_frame(&mut self, space: PNSpace, offset: u64, data: &[u8]) {
+ self.get_mut(space).unwrap().rx.inbound_frame(offset, data);
+ }
+
+ pub fn data_ready(&self, space: PNSpace) -> bool {
+ self.get(space).map_or(false, |cs| cs.rx.data_ready())
+ }
+
+ pub fn read_to_end(&mut self, space: PNSpace, buf: &mut Vec<u8>) -> usize {
+ self.get_mut(space).unwrap().rx.read_to_end(buf)
+ }
+
+ pub fn acked(&mut self, token: &CryptoRecoveryToken) {
+ self.get_mut(token.space)
+ .unwrap()
+ .tx
+ .mark_as_acked(token.offset, token.length);
+ }
+
+ pub fn lost(&mut self, token: &CryptoRecoveryToken) {
+ // See BZ 1624800, ignore lost packets in spaces we've dropped keys
+ if let Some(cs) = self.get_mut(token.space) {
+ cs.tx.mark_as_lost(token.offset, token.length);
+ }
+ }
+
+ /// Resend any Initial or Handshake CRYPTO frames that might be outstanding.
+ /// This can help speed up handshake times.
+ pub fn resend_unacked(&mut self, space: PNSpace) {
+ if space != PNSpace::ApplicationData {
+ if let Some(cs) = self.get_mut(space) {
+ cs.tx.unmark_sent();
+ }
+ }
+ }
+
+ fn get(&self, space: PNSpace) -> Option<&CryptoStream> {
+ let (initial, hs, app) = match self {
+ Self::Initial {
+ initial,
+ handshake,
+ application,
+ } => (Some(initial), Some(handshake), Some(application)),
+ Self::Handshake {
+ handshake,
+ application,
+ } => (None, Some(handshake), Some(application)),
+ Self::ApplicationData { application } => (None, None, Some(application)),
+ };
+ match space {
+ PNSpace::Initial => initial,
+ PNSpace::Handshake => hs,
+ PNSpace::ApplicationData => app,
+ }
+ }
+
+ fn get_mut(&mut self, space: PNSpace) -> Option<&mut CryptoStream> {
+ let (initial, hs, app) = match self {
+ Self::Initial {
+ initial,
+ handshake,
+ application,
+ } => (Some(initial), Some(handshake), Some(application)),
+ Self::Handshake {
+ handshake,
+ application,
+ } => (None, Some(handshake), Some(application)),
+ Self::ApplicationData { application } => (None, None, Some(application)),
+ };
+ match space {
+ PNSpace::Initial => initial,
+ PNSpace::Handshake => hs,
+ PNSpace::ApplicationData => app,
+ }
+ }
+
+ pub fn write_frame(
+ &mut self,
+ space: PNSpace,
+ builder: &mut PacketBuilder,
+ ) -> Option<RecoveryToken> {
+ let cs = self.get_mut(space).unwrap();
+ if let Some((offset, data)) = cs.tx.next_bytes() {
+ let mut header_len = 1 + Encoder::varint_len(offset) + 1;
+
+ // Don't bother if there isn't room for the header and some data.
+ if builder.remaining() < header_len + 1 {
+ return None;
+ }
+ // Calculate length of data based on the minimum of:
+ // - available data
+ // - remaining space, less the header, which counts only one byte
+ // for the length at first to avoid underestimating length
+ let length = min(data.len(), builder.remaining() - header_len);
+ header_len += Encoder::varint_len(u64::try_from(length).unwrap()) - 1;
+ let length = min(data.len(), builder.remaining() - header_len);
+
+ builder.encode_varint(crate::frame::FRAME_TYPE_CRYPTO);
+ builder.encode_varint(offset);
+ builder.encode_vvec(&data[..length]);
+ cs.tx.mark_as_sent(offset, length);
+
+ qdebug!("CRYPTO for {} offset={}, len={}", space, offset, length);
+ Some(RecoveryToken::Crypto(CryptoRecoveryToken {
+ space,
+ offset,
+ length,
+ }))
+ } else {
+ None
+ }
+ }
+}
+
+impl Default for CryptoStreams {
+ fn default() -> Self {
+ Self::Initial {
+ initial: CryptoStream::default(),
+ handshake: CryptoStream::default(),
+ application: CryptoStream::default(),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct CryptoRecoveryToken {
+ space: PNSpace,
+ offset: u64,
+ length: usize,
+}
diff --git a/third_party/rust/neqo-transport/src/dump.rs b/third_party/rust/neqo-transport/src/dump.rs
new file mode 100644
index 0000000000..e8f5b32ae9
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/dump.rs
@@ -0,0 +1,32 @@
+// 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.
+
+// Enable just this file for logging to just see packets.
+// e.g. "RUST_LOG=neqo_transport::dump neqo-client ..."
+
+use crate::connection::Connection;
+use crate::frame::Frame;
+use crate::packet::{PacketNumber, PacketType};
+use neqo_common::{qdebug, Decoder};
+
+#[allow(clippy::module_name_repetitions)]
+pub fn dump_packet(conn: &Connection, dir: &str, pt: PacketType, pn: PacketNumber, payload: &[u8]) {
+ let mut s = String::from("");
+ let mut d = Decoder::from(payload);
+ while d.remaining() > 0 {
+ let f = match Frame::decode(&mut d) {
+ Ok(f) => f,
+ Err(_) => {
+ s.push_str(" [broken]...");
+ break;
+ }
+ };
+ if let Some(x) = f.dump() {
+ s.push_str(&format!("\n {} {}", dir, &x));
+ }
+ }
+ qdebug!([conn], "pn={} type={:?}{}", pn, pt, s);
+}
diff --git a/third_party/rust/neqo-transport/src/events.rs b/third_party/rust/neqo-transport/src/events.rs
new file mode 100644
index 0000000000..fc998cf32c
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/events.rs
@@ -0,0 +1,254 @@
+// 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.
+
+// Collecting a list of events relevant to whoever is using the Connection.
+
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::rc::Rc;
+
+use crate::connection::State;
+use crate::frame::StreamType;
+use crate::stream_id::StreamId;
+use crate::AppError;
+use neqo_common::event::Provider as EventProvider;
+use neqo_crypto::ResumptionToken;
+
+#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub enum ConnectionEvent {
+ /// Cert authentication needed
+ AuthenticationNeeded,
+ /// A new uni (read) or bidi stream has been opened by the peer.
+ NewStream {
+ stream_id: StreamId,
+ },
+ /// Space available in the buffer for an application write to succeed.
+ SendStreamWritable {
+ stream_id: StreamId,
+ },
+ /// New bytes available for reading.
+ RecvStreamReadable {
+ stream_id: u64,
+ },
+ /// Peer reset the stream.
+ RecvStreamReset {
+ stream_id: u64,
+ app_error: AppError,
+ },
+ /// Peer has sent STOP_SENDING
+ SendStreamStopSending {
+ stream_id: u64,
+ app_error: AppError,
+ },
+ /// Peer has acked everything sent on the stream.
+ SendStreamComplete {
+ stream_id: u64,
+ },
+ /// Peer increased MAX_STREAMS
+ SendStreamCreatable {
+ stream_type: StreamType,
+ },
+ /// Connection state change.
+ StateChange(State),
+ /// The server rejected 0-RTT.
+ /// This event invalidates all state in streams that has been created.
+ /// Any data written to streams needs to be written again.
+ ZeroRttRejected,
+ ResumptionToken(ResumptionToken),
+}
+
+#[derive(Debug, Default, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub struct ConnectionEvents {
+ events: Rc<RefCell<VecDeque<ConnectionEvent>>>,
+}
+
+impl ConnectionEvents {
+ pub fn authentication_needed(&self) {
+ self.insert(ConnectionEvent::AuthenticationNeeded);
+ }
+
+ pub fn new_stream(&self, stream_id: StreamId) {
+ self.insert(ConnectionEvent::NewStream { stream_id });
+ }
+
+ pub fn recv_stream_readable(&self, stream_id: StreamId) {
+ self.insert(ConnectionEvent::RecvStreamReadable {
+ stream_id: stream_id.as_u64(),
+ });
+ }
+
+ pub fn recv_stream_reset(&self, stream_id: StreamId, app_error: AppError) {
+ // If reset, no longer readable.
+ self.remove(|evt| matches!(evt, ConnectionEvent::RecvStreamReadable { stream_id: x } if *x == stream_id.as_u64()));
+
+ self.insert(ConnectionEvent::RecvStreamReset {
+ stream_id: stream_id.as_u64(),
+ app_error,
+ });
+ }
+
+ pub fn send_stream_writable(&self, stream_id: StreamId) {
+ self.insert(ConnectionEvent::SendStreamWritable { stream_id });
+ }
+
+ pub fn send_stream_stop_sending(&self, stream_id: StreamId, app_error: AppError) {
+ // If stopped, no longer writable.
+ self.remove(|evt| matches!(evt, ConnectionEvent::SendStreamWritable { stream_id: x } if *x == stream_id.as_u64()));
+
+ self.insert(ConnectionEvent::SendStreamStopSending {
+ stream_id: stream_id.as_u64(),
+ app_error,
+ });
+ }
+
+ pub fn send_stream_complete(&self, stream_id: StreamId) {
+ self.remove(|evt| matches!(evt, ConnectionEvent::SendStreamWritable { stream_id: x } if *x == stream_id));
+
+ self.remove(|evt| matches!(evt, ConnectionEvent::SendStreamStopSending { stream_id: x, .. } if *x == stream_id.as_u64()));
+
+ self.insert(ConnectionEvent::SendStreamComplete {
+ stream_id: stream_id.as_u64(),
+ });
+ }
+
+ pub fn send_stream_creatable(&self, stream_type: StreamType) {
+ self.insert(ConnectionEvent::SendStreamCreatable { stream_type });
+ }
+
+ pub fn connection_state_change(&self, state: State) {
+ // If closing, existing events no longer relevant.
+ match state {
+ State::Closing { .. } | State::Closed(_) => self.events.borrow_mut().clear(),
+ _ => (),
+ }
+ self.insert(ConnectionEvent::StateChange(state));
+ }
+
+ pub fn client_resumption_token(&self, token: ResumptionToken) {
+ self.insert(ConnectionEvent::ResumptionToken(token));
+ }
+
+ pub fn client_0rtt_rejected(&self) {
+ // If 0rtt rejected, must start over and existing events are no longer
+ // relevant.
+ self.events.borrow_mut().clear();
+ self.insert(ConnectionEvent::ZeroRttRejected);
+ }
+
+ pub fn recv_stream_complete(&self, stream_id: StreamId) {
+ // If stopped, no longer readable.
+ self.remove(|evt| matches!(evt, ConnectionEvent::RecvStreamReadable { stream_id: x } if *x == stream_id.as_u64()));
+ }
+
+ fn insert(&self, event: ConnectionEvent) {
+ let mut q = self.events.borrow_mut();
+
+ // Special-case two enums that are not strictly PartialEq equal but that
+ // we wish to avoid inserting duplicates.
+ let already_present = match &event {
+ ConnectionEvent::SendStreamStopSending { stream_id, .. } => q.iter().any(|evt| {
+ matches!(evt, ConnectionEvent::SendStreamStopSending { stream_id: x, .. }
+ if *x == *stream_id)
+ }),
+ ConnectionEvent::RecvStreamReset { stream_id, .. } => q.iter().any(|evt| {
+ matches!(evt, ConnectionEvent::RecvStreamReset { stream_id: x, .. }
+ if *x == *stream_id)
+ }),
+ _ => q.contains(&event),
+ };
+ if !already_present {
+ q.push_back(event);
+ }
+ }
+
+ fn remove<F>(&self, f: F)
+ where
+ F: Fn(&ConnectionEvent) -> bool,
+ {
+ self.events.borrow_mut().retain(|evt| !f(evt))
+ }
+}
+
+impl EventProvider for ConnectionEvents {
+ type Event = ConnectionEvent;
+
+ fn has_events(&self) -> bool {
+ !self.events.borrow().is_empty()
+ }
+
+ fn next_event(&mut self) -> Option<Self::Event> {
+ self.events.borrow_mut().pop_front()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{ConnectionError, Error};
+
+ #[test]
+ fn event_culling() {
+ let mut evts = ConnectionEvents::default();
+
+ evts.client_0rtt_rejected();
+ evts.client_0rtt_rejected();
+ assert_eq!(evts.events().count(), 1);
+ assert_eq!(evts.events().count(), 0);
+
+ evts.new_stream(4.into());
+ evts.new_stream(4.into());
+ assert_eq!(evts.events().count(), 1);
+
+ evts.recv_stream_readable(6.into());
+ evts.recv_stream_reset(6.into(), 66);
+ evts.recv_stream_reset(6.into(), 65);
+ assert_eq!(evts.events().count(), 1);
+
+ evts.send_stream_writable(8.into());
+ evts.send_stream_writable(8.into());
+ evts.send_stream_stop_sending(8.into(), 55);
+ evts.send_stream_stop_sending(8.into(), 56);
+ let events = evts.events().collect::<Vec<_>>();
+ assert_eq!(events.len(), 1);
+ assert_eq!(
+ events[0],
+ ConnectionEvent::SendStreamStopSending {
+ stream_id: 8,
+ app_error: 55
+ }
+ );
+
+ evts.send_stream_writable(8.into());
+ evts.send_stream_writable(8.into());
+ evts.send_stream_stop_sending(8.into(), 55);
+ evts.send_stream_stop_sending(8.into(), 56);
+ evts.send_stream_complete(8.into());
+ assert_eq!(evts.events().count(), 1);
+
+ evts.send_stream_writable(8.into());
+ evts.send_stream_writable(9.into());
+ evts.send_stream_stop_sending(10.into(), 55);
+ evts.send_stream_stop_sending(11.into(), 56);
+ evts.send_stream_complete(12.into());
+ assert_eq!(evts.events().count(), 5);
+
+ evts.send_stream_writable(8.into());
+ evts.send_stream_writable(9.into());
+ evts.send_stream_stop_sending(10.into(), 55);
+ evts.send_stream_stop_sending(11.into(), 56);
+ evts.send_stream_complete(12.into());
+ evts.client_0rtt_rejected();
+ assert_eq!(evts.events().count(), 1);
+
+ evts.send_stream_writable(9.into());
+ evts.send_stream_stop_sending(10.into(), 55);
+ evts.connection_state_change(State::Closed(ConnectionError::Transport(
+ Error::StreamStateError,
+ )));
+ assert_eq!(evts.events().count(), 1);
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/flow_mgr.rs b/third_party/rust/neqo-transport/src/flow_mgr.rs
new file mode 100644
index 0000000000..0ae7fd3c00
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/flow_mgr.rs
@@ -0,0 +1,400 @@
+// 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.
+
+// Tracks possibly-redundant flow control signals from other code and converts
+// into flow control frames needing to be sent to the remote.
+
+use std::collections::HashMap;
+use std::mem;
+
+use neqo_common::{qinfo, qwarn, Encoder};
+use smallvec::{smallvec, SmallVec};
+
+use crate::frame::{Frame, StreamType};
+use crate::packet::PacketBuilder;
+use crate::recovery::RecoveryToken;
+use crate::recv_stream::RecvStreams;
+use crate::send_stream::SendStreams;
+use crate::stats::FrameStats;
+use crate::stream_id::{StreamId, StreamIndex, StreamIndexes};
+use crate::AppError;
+
+type FlowFrame = Frame<'static>;
+pub type FlowControlRecoveryToken = FlowFrame;
+
+#[derive(Debug, Default)]
+pub struct FlowMgr {
+ // Discriminant as key ensures only 1 of every frame type will be queued.
+ from_conn: HashMap<mem::Discriminant<FlowFrame>, FlowFrame>,
+
+ // (id, discriminant) as key ensures only 1 of every frame type per stream
+ // will be queued.
+ from_streams: HashMap<(StreamId, mem::Discriminant<FlowFrame>), FlowFrame>,
+
+ // (stream_type, discriminant) as key ensures only 1 of every frame type
+ // per stream type will be queued.
+ from_stream_types: HashMap<(StreamType, mem::Discriminant<FlowFrame>), FlowFrame>,
+
+ used_data: u64,
+ max_data: u64,
+}
+
+impl FlowMgr {
+ pub fn conn_credit_avail(&self) -> u64 {
+ self.max_data - self.used_data
+ }
+
+ pub fn conn_increase_credit_used(&mut self, amount: u64) {
+ self.used_data += amount;
+ assert!(self.used_data <= self.max_data)
+ }
+
+ // Dummy DataBlocked frame for discriminant use below
+
+ /// Returns whether max credit was actually increased.
+ pub fn conn_increase_max_credit(&mut self, new: u64) -> bool {
+ const DB_FRAME: FlowFrame = Frame::DataBlocked { data_limit: 0 };
+
+ if new > self.max_data {
+ self.max_data = new;
+ self.from_conn.remove(&mem::discriminant(&DB_FRAME));
+
+ true
+ } else {
+ false
+ }
+ }
+
+ // -- frames scoped on connection --
+
+ pub fn data_blocked(&mut self) {
+ let frame = Frame::DataBlocked {
+ data_limit: self.max_data,
+ };
+ self.from_conn.insert(mem::discriminant(&frame), frame);
+ }
+
+ pub fn path_response(&mut self, data: [u8; 8]) {
+ let frame = Frame::PathResponse { data };
+ self.from_conn.insert(mem::discriminant(&frame), frame);
+ }
+
+ pub fn max_data(&mut self, maximum_data: u64) {
+ let frame = Frame::MaxData { maximum_data };
+ self.from_conn.insert(mem::discriminant(&frame), frame);
+ }
+
+ // -- frames scoped on stream --
+
+ /// Indicate to receiving remote the stream is reset
+ pub fn stream_reset(
+ &mut self,
+ stream_id: StreamId,
+ application_error_code: AppError,
+ final_size: u64,
+ ) {
+ let frame = Frame::ResetStream {
+ stream_id,
+ application_error_code,
+ final_size,
+ };
+ self.from_streams
+ .insert((stream_id, mem::discriminant(&frame)), frame);
+ }
+
+ /// Indicate to sending remote we are no longer interested in the stream
+ pub fn stop_sending(&mut self, stream_id: StreamId, application_error_code: AppError) {
+ let frame = Frame::StopSending {
+ stream_id,
+ application_error_code,
+ };
+ self.from_streams
+ .insert((stream_id, mem::discriminant(&frame)), frame);
+ }
+
+ /// Update sending remote with more credits
+ pub fn max_stream_data(&mut self, stream_id: StreamId, maximum_stream_data: u64) {
+ let frame = Frame::MaxStreamData {
+ stream_id,
+ maximum_stream_data,
+ };
+ self.from_streams
+ .insert((stream_id, mem::discriminant(&frame)), frame);
+ }
+
+ /// Don't send stream data updates if no more data is coming
+ pub fn clear_max_stream_data(&mut self, stream_id: StreamId) {
+ let frame = Frame::MaxStreamData {
+ stream_id,
+ maximum_stream_data: 0,
+ };
+ self.from_streams
+ .remove(&(stream_id, mem::discriminant(&frame)));
+ }
+
+ /// Indicate to receiving remote we need more credits
+ pub fn stream_data_blocked(&mut self, stream_id: StreamId, stream_data_limit: u64) {
+ let frame = Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit,
+ };
+ self.from_streams
+ .insert((stream_id, mem::discriminant(&frame)), frame);
+ }
+
+ // -- frames scoped on stream type --
+
+ pub fn max_streams(&mut self, stream_limit: StreamIndex, stream_type: StreamType) {
+ let frame = Frame::MaxStreams {
+ stream_type,
+ maximum_streams: stream_limit,
+ };
+ self.from_stream_types
+ .insert((stream_type, mem::discriminant(&frame)), frame);
+ }
+
+ pub fn streams_blocked(&mut self, stream_limit: StreamIndex, stream_type: StreamType) {
+ let frame = Frame::StreamsBlocked {
+ stream_type,
+ stream_limit,
+ };
+ self.from_stream_types
+ .insert((stream_type, mem::discriminant(&frame)), frame);
+ }
+
+ pub fn peek(&self) -> Option<&Frame> {
+ if let Some(key) = self.from_conn.keys().next() {
+ self.from_conn.get(key)
+ } else if let Some(key) = self.from_streams.keys().next() {
+ self.from_streams.get(key)
+ } else if let Some(key) = self.from_stream_types.keys().next() {
+ self.from_stream_types.get(key)
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn acked(
+ &mut self,
+ token: &FlowControlRecoveryToken,
+ send_streams: &mut SendStreams,
+ ) {
+ const RESET_STREAM: &Frame = &Frame::ResetStream {
+ stream_id: StreamId::new(0),
+ application_error_code: 0,
+ final_size: 0,
+ };
+
+ if let Frame::ResetStream { stream_id, .. } = token {
+ qinfo!("Reset received stream={}", stream_id.as_u64());
+
+ if self
+ .from_streams
+ .remove(&(*stream_id, mem::discriminant(RESET_STREAM)))
+ .is_some()
+ {
+ qinfo!("Removed RESET_STREAM frame for {}", stream_id.as_u64());
+ }
+
+ send_streams.reset_acked(*stream_id);
+ }
+ }
+
+ pub(crate) fn lost(
+ &mut self,
+ token: &FlowControlRecoveryToken,
+ send_streams: &mut SendStreams,
+ recv_streams: &mut RecvStreams,
+ indexes: &mut StreamIndexes,
+ ) {
+ match *token {
+ // Always resend ResetStream if lost
+ Frame::ResetStream {
+ stream_id,
+ application_error_code,
+ final_size,
+ } => {
+ qinfo!(
+ "Reset lost stream={} err={} final_size={}",
+ stream_id.as_u64(),
+ application_error_code,
+ final_size
+ );
+ if send_streams.get(stream_id).is_ok() {
+ self.stream_reset(stream_id, application_error_code, final_size);
+ }
+ }
+ // Resend MaxStreams if lost (with updated value)
+ Frame::MaxStreams { stream_type, .. } => {
+ let local_max = match stream_type {
+ StreamType::BiDi => &mut indexes.local_max_stream_bidi,
+ StreamType::UniDi => &mut indexes.local_max_stream_uni,
+ };
+
+ self.max_streams(*local_max, stream_type)
+ }
+ // Only resend "*Blocked" frames if still blocked
+ Frame::DataBlocked { .. } => {
+ if self.conn_credit_avail() == 0 {
+ self.data_blocked()
+ }
+ }
+ Frame::StreamDataBlocked { stream_id, .. } => {
+ if let Ok(ss) = send_streams.get(stream_id) {
+ if ss.credit_avail() == 0 {
+ self.stream_data_blocked(stream_id, ss.max_stream_data())
+ }
+ }
+ }
+ Frame::StreamsBlocked { stream_type, .. } => match stream_type {
+ StreamType::UniDi => {
+ if indexes.remote_next_stream_uni >= indexes.remote_max_stream_uni {
+ self.streams_blocked(indexes.remote_max_stream_uni, StreamType::UniDi);
+ }
+ }
+ StreamType::BiDi => {
+ if indexes.remote_next_stream_bidi >= indexes.remote_max_stream_bidi {
+ self.streams_blocked(indexes.remote_max_stream_bidi, StreamType::BiDi);
+ }
+ }
+ },
+ // Resend StopSending
+ Frame::StopSending {
+ stream_id,
+ application_error_code,
+ } => self.stop_sending(stream_id, application_error_code),
+ Frame::MaxStreamData { stream_id, .. } => {
+ if let Some(rs) = recv_streams.get_mut(&stream_id) {
+ if let Some(msd) = rs.max_stream_data() {
+ self.max_stream_data(stream_id, msd)
+ }
+ }
+ }
+ Frame::PathResponse { .. } => qinfo!("Path Response lost, not re-sent"),
+ _ => qwarn!("Unexpected Flow frame {:?} lost, not re-sent", token),
+ }
+ }
+
+ pub(crate) fn write_frames(
+ &mut self,
+ builder: &mut PacketBuilder,
+ tokens: &mut Vec<RecoveryToken>,
+ stats: &mut FrameStats,
+ ) {
+ while let Some(frame) = self.peek() {
+ // All these frames are bags of varints, so we can just extract the
+ // varints and use common code for writing.
+ let values: SmallVec<[_; 3]> = match frame {
+ Frame::ResetStream {
+ stream_id,
+ application_error_code,
+ final_size,
+ } => {
+ stats.reset_stream += 1;
+ smallvec![stream_id.as_u64(), *application_error_code, *final_size]
+ }
+ Frame::StopSending {
+ stream_id,
+ application_error_code,
+ } => {
+ stats.stop_sending += 1;
+ smallvec![stream_id.as_u64(), *application_error_code]
+ }
+
+ Frame::MaxStreams {
+ maximum_streams, ..
+ } => {
+ stats.max_streams += 1;
+ smallvec![maximum_streams.as_u64()]
+ }
+ Frame::StreamsBlocked { stream_limit, .. } => {
+ stats.streams_blocked += 1;
+ smallvec![stream_limit.as_u64()]
+ }
+
+ Frame::MaxData { maximum_data } => {
+ stats.max_data += 1;
+ smallvec![*maximum_data]
+ }
+ Frame::DataBlocked { data_limit } => {
+ stats.data_blocked += 1;
+ smallvec![*data_limit]
+ }
+
+ Frame::MaxStreamData {
+ stream_id,
+ maximum_stream_data,
+ } => {
+ stats.max_stream_data += 1;
+ smallvec![stream_id.as_u64(), *maximum_stream_data]
+ }
+ Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit,
+ } => {
+ stats.stream_data_blocked += 1;
+ smallvec![stream_id.as_u64(), *stream_data_limit]
+ }
+
+ // A special case, just write it out and move on..
+ Frame::PathResponse { data } => {
+ stats.path_response += 1;
+ if builder.remaining() >= Encoder::varint_len(frame.get_type()) + data.len() {
+ builder.encode_varint(frame.get_type());
+ builder.encode(data);
+ tokens.push(RecoveryToken::Flow(self.next().unwrap()));
+ continue;
+ } else {
+ return;
+ }
+ }
+
+ _ => unreachable!("{:?}", frame),
+ };
+ debug_assert!(!values.spilled());
+
+ if builder.remaining()
+ >= Encoder::varint_len(frame.get_type())
+ + values
+ .iter()
+ .map(|&v| Encoder::varint_len(v))
+ .sum::<usize>()
+ {
+ builder.encode_varint(frame.get_type());
+ for v in values {
+ builder.encode_varint(v);
+ }
+ tokens.push(RecoveryToken::Flow(self.next().unwrap()));
+ } else {
+ return;
+ }
+ }
+ }
+}
+
+impl Iterator for FlowMgr {
+ type Item = FlowFrame;
+
+ /// Used by generator to get a flow control frame.
+ fn next(&mut self) -> Option<Self::Item> {
+ let first_key = self.from_conn.keys().next();
+ if let Some(&first_key) = first_key {
+ return self.from_conn.remove(&first_key);
+ }
+
+ let first_key = self.from_streams.keys().next();
+ if let Some(&first_key) = first_key {
+ return self.from_streams.remove(&first_key);
+ }
+
+ let first_key = self.from_stream_types.keys().next();
+ if let Some(&first_key) = first_key {
+ return self.from_stream_types.remove(&first_key);
+ }
+
+ None
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/frame.rs b/third_party/rust/neqo-transport/src/frame.rs
new file mode 100644
index 0000000000..5f3e7c9b78
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/frame.rs
@@ -0,0 +1,835 @@
+// 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.
+
+// Directly relating to QUIC frames.
+
+use neqo_common::{qtrace, Decoder};
+
+use crate::cid::MAX_CONNECTION_ID_LEN;
+use crate::packet::PacketType;
+use crate::stream_id::{StreamId, StreamIndex};
+use crate::{AppError, ConnectionError, Error, Res, TransportError, ERROR_APPLICATION_CLOSE};
+
+use std::convert::TryFrom;
+use std::ops::RangeInclusive;
+
+#[allow(clippy::module_name_repetitions)]
+pub type FrameType = u64;
+
+const FRAME_TYPE_PADDING: FrameType = 0x0;
+pub const FRAME_TYPE_PING: FrameType = 0x1;
+pub const FRAME_TYPE_ACK: FrameType = 0x2;
+const FRAME_TYPE_ACK_ECN: FrameType = 0x3;
+const FRAME_TYPE_RST_STREAM: FrameType = 0x4;
+const FRAME_TYPE_STOP_SENDING: FrameType = 0x5;
+pub const FRAME_TYPE_CRYPTO: FrameType = 0x6;
+pub const FRAME_TYPE_NEW_TOKEN: FrameType = 0x7;
+const FRAME_TYPE_STREAM: FrameType = 0x8;
+const FRAME_TYPE_STREAM_MAX: FrameType = 0xf;
+const FRAME_TYPE_MAX_DATA: FrameType = 0x10;
+const FRAME_TYPE_MAX_STREAM_DATA: FrameType = 0x11;
+const FRAME_TYPE_MAX_STREAMS_BIDI: FrameType = 0x12;
+const FRAME_TYPE_MAX_STREAMS_UNIDI: FrameType = 0x13;
+const FRAME_TYPE_DATA_BLOCKED: FrameType = 0x14;
+const FRAME_TYPE_STREAM_DATA_BLOCKED: FrameType = 0x15;
+const FRAME_TYPE_STREAMS_BLOCKED_BIDI: FrameType = 0x16;
+const FRAME_TYPE_STREAMS_BLOCKED_UNIDI: FrameType = 0x17;
+const FRAME_TYPE_NEW_CONNECTION_ID: FrameType = 0x18;
+const FRAME_TYPE_RETIRE_CONNECTION_ID: FrameType = 0x19;
+const FRAME_TYPE_PATH_CHALLENGE: FrameType = 0x1a;
+const FRAME_TYPE_PATH_RESPONSE: FrameType = 0x1b;
+pub const FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT: FrameType = 0x1c;
+pub const FRAME_TYPE_CONNECTION_CLOSE_APPLICATION: FrameType = 0x1d;
+const FRAME_TYPE_HANDSHAKE_DONE: FrameType = 0x1e;
+
+const STREAM_FRAME_BIT_FIN: u64 = 0x01;
+const STREAM_FRAME_BIT_LEN: u64 = 0x02;
+const STREAM_FRAME_BIT_OFF: u64 = 0x04;
+
+/// `FRAME_APPLICATION_CLOSE` is the default CONNECTION_CLOSE frame that
+/// is sent when an application error code needs to be sent in an
+/// Initial or Handshake packet.
+const FRAME_APPLICATION_CLOSE: &Frame = &Frame::ConnectionClose {
+ error_code: CloseError::Transport(ERROR_APPLICATION_CLOSE),
+ frame_type: 0,
+ reason_phrase: Vec::new(),
+};
+
+#[derive(PartialEq, Debug, Copy, Clone, PartialOrd, Eq, Ord, Hash)]
+/// Bi-Directional or Uni-Directional.
+pub enum StreamType {
+ BiDi,
+ UniDi,
+}
+
+impl StreamType {
+ fn frame_type_bit(self) -> u64 {
+ match self {
+ Self::BiDi => 0,
+ Self::UniDi => 1,
+ }
+ }
+ fn from_type_bit(bit: u64) -> Self {
+ if (bit & 0x01) == 0 {
+ Self::BiDi
+ } else {
+ Self::UniDi
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Clone, Copy)]
+pub enum CloseError {
+ Transport(TransportError),
+ Application(AppError),
+}
+
+impl CloseError {
+ fn frame_type_bit(self) -> u64 {
+ match self {
+ Self::Transport(_) => 0,
+ Self::Application(_) => 1,
+ }
+ }
+
+ fn from_type_bit(bit: u64, code: u64) -> Self {
+ if (bit & 0x01) == 0 {
+ Self::Transport(code)
+ } else {
+ Self::Application(code)
+ }
+ }
+
+ pub fn code(&self) -> u64 {
+ match self {
+ Self::Transport(c) | Self::Application(c) => *c,
+ }
+ }
+}
+
+impl From<ConnectionError> for CloseError {
+ fn from(err: ConnectionError) -> Self {
+ match err {
+ ConnectionError::Transport(c) => Self::Transport(c.code()),
+ ConnectionError::Application(c) => Self::Application(c),
+ }
+ }
+}
+
+#[derive(PartialEq, Debug, Default, Clone)]
+pub struct AckRange {
+ pub(crate) gap: u64,
+ pub(crate) range: u64,
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub enum Frame<'a> {
+ Padding,
+ Ping,
+ Ack {
+ largest_acknowledged: u64,
+ ack_delay: u64,
+ first_ack_range: u64,
+ ack_ranges: Vec<AckRange>,
+ },
+ ResetStream {
+ stream_id: StreamId,
+ application_error_code: AppError,
+ final_size: u64,
+ },
+ StopSending {
+ stream_id: StreamId,
+ application_error_code: AppError,
+ },
+ Crypto {
+ offset: u64,
+ data: &'a [u8],
+ },
+ NewToken {
+ token: &'a [u8],
+ },
+ Stream {
+ stream_id: StreamId,
+ offset: u64,
+ data: &'a [u8],
+ fin: bool,
+ fill: bool,
+ },
+ MaxData {
+ maximum_data: u64,
+ },
+ MaxStreamData {
+ stream_id: StreamId,
+ maximum_stream_data: u64,
+ },
+ MaxStreams {
+ stream_type: StreamType,
+ maximum_streams: StreamIndex,
+ },
+ DataBlocked {
+ data_limit: u64,
+ },
+ StreamDataBlocked {
+ stream_id: StreamId,
+ stream_data_limit: u64,
+ },
+ StreamsBlocked {
+ stream_type: StreamType,
+ stream_limit: StreamIndex,
+ },
+ NewConnectionId {
+ sequence_number: u64,
+ retire_prior: u64,
+ connection_id: &'a [u8],
+ stateless_reset_token: &'a [u8; 16],
+ },
+ RetireConnectionId {
+ sequence_number: u64,
+ },
+ PathChallenge {
+ data: [u8; 8],
+ },
+ PathResponse {
+ data: [u8; 8],
+ },
+ ConnectionClose {
+ error_code: CloseError,
+ frame_type: u64,
+ // Not a reference as we use this to hold the value.
+ // This is not used in optimized builds anyway.
+ reason_phrase: Vec<u8>,
+ },
+ HandshakeDone,
+}
+
+impl<'a> Frame<'a> {
+ pub fn get_type(&self) -> FrameType {
+ match self {
+ Self::Padding => FRAME_TYPE_PADDING,
+ Self::Ping => FRAME_TYPE_PING,
+ Self::Ack { .. } => FRAME_TYPE_ACK, // We don't do ACK ECN.
+ Self::ResetStream { .. } => FRAME_TYPE_RST_STREAM,
+ Self::StopSending { .. } => FRAME_TYPE_STOP_SENDING,
+ Self::Crypto { .. } => FRAME_TYPE_CRYPTO,
+ Self::NewToken { .. } => FRAME_TYPE_NEW_TOKEN,
+ Self::Stream {
+ fin, offset, fill, ..
+ } => Self::stream_type(*fin, *offset > 0, *fill),
+ Self::MaxData { .. } => FRAME_TYPE_MAX_DATA,
+ Self::MaxStreamData { .. } => FRAME_TYPE_MAX_STREAM_DATA,
+ Self::MaxStreams { stream_type, .. } => {
+ FRAME_TYPE_MAX_STREAMS_BIDI + stream_type.frame_type_bit()
+ }
+ Self::DataBlocked { .. } => FRAME_TYPE_DATA_BLOCKED,
+ Self::StreamDataBlocked { .. } => FRAME_TYPE_STREAM_DATA_BLOCKED,
+ Self::StreamsBlocked { stream_type, .. } => {
+ FRAME_TYPE_STREAMS_BLOCKED_BIDI + stream_type.frame_type_bit()
+ }
+ Self::NewConnectionId { .. } => FRAME_TYPE_NEW_CONNECTION_ID,
+ Self::RetireConnectionId { .. } => FRAME_TYPE_RETIRE_CONNECTION_ID,
+ Self::PathChallenge { .. } => FRAME_TYPE_PATH_CHALLENGE,
+ Self::PathResponse { .. } => FRAME_TYPE_PATH_RESPONSE,
+ Self::ConnectionClose { error_code, .. } => {
+ FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT + error_code.frame_type_bit()
+ }
+ Self::HandshakeDone => FRAME_TYPE_HANDSHAKE_DONE,
+ }
+ }
+
+ pub fn stream_type(fin: bool, nonzero_offset: bool, fill: bool) -> u64 {
+ let mut t = FRAME_TYPE_STREAM;
+ if fin {
+ t |= STREAM_FRAME_BIT_FIN;
+ }
+ if nonzero_offset {
+ t |= STREAM_FRAME_BIT_OFF;
+ }
+ if !fill {
+ t |= STREAM_FRAME_BIT_LEN;
+ }
+ t
+ }
+
+ /// Convert a CONNECTION_CLOSE into a nicer CONNECTION_CLOSE.
+ pub fn sanitize_close(&self) -> &Self {
+ if let Self::ConnectionClose { error_code, .. } = &self {
+ if let CloseError::Application(_) = error_code {
+ FRAME_APPLICATION_CLOSE
+ } else {
+ self
+ }
+ } else {
+ panic!("Attempted to sanitize a non-close frame");
+ }
+ }
+
+ pub fn ack_eliciting(&self) -> bool {
+ !matches!(self, Self::Ack { .. } | Self::Padding | Self::ConnectionClose { .. })
+ }
+
+ /// Converts AckRanges as encoded in a ACK frame (see -transport
+ /// 19.3.1) into ranges of acked packets (end, start), inclusive of
+ /// start and end values.
+ pub fn decode_ack_frame(
+ largest_acked: u64,
+ first_ack_range: u64,
+ ack_ranges: &[AckRange],
+ ) -> Res<Vec<RangeInclusive<u64>>> {
+ let mut acked_ranges = Vec::with_capacity(ack_ranges.len() + 1);
+
+ if largest_acked < first_ack_range {
+ return Err(Error::FrameEncodingError);
+ }
+ acked_ranges.push((largest_acked - first_ack_range)..=largest_acked);
+ if !ack_ranges.is_empty() && largest_acked < first_ack_range + 1 {
+ return Err(Error::FrameEncodingError);
+ }
+ let mut cur = if ack_ranges.is_empty() {
+ 0
+ } else {
+ largest_acked - first_ack_range - 1
+ };
+ for r in ack_ranges {
+ if cur < r.gap + 1 {
+ return Err(Error::FrameEncodingError);
+ }
+ cur = cur - r.gap - 1;
+
+ if cur < r.range {
+ return Err(Error::FrameEncodingError);
+ }
+ acked_ranges.push((cur - r.range)..=cur);
+
+ if cur > r.range + 1 {
+ cur -= r.range + 1;
+ } else {
+ cur -= r.range;
+ }
+ }
+
+ Ok(acked_ranges)
+ }
+
+ pub fn dump(&self) -> Option<String> {
+ match self {
+ Self::Crypto { offset, data } => Some(format!(
+ "Crypto {{ offset: {}, len: {} }}",
+ offset,
+ data.len()
+ )),
+ Self::Stream {
+ stream_id,
+ offset,
+ fill,
+ data,
+ fin,
+ } => Some(format!(
+ "Stream {{ stream_id: {}, offset: {}, len: {}{}, fin: {} }}",
+ stream_id.as_u64(),
+ offset,
+ if *fill { ">>" } else { "" },
+ data.len(),
+ fin,
+ )),
+ Self::Padding => None,
+ _ => Some(format!("{:?}", self)),
+ }
+ }
+
+ pub fn is_allowed(&self, pt: PacketType) -> bool {
+ match self {
+ Self::Padding | Self::Ping => true,
+ Self::Crypto { .. }
+ | Self::Ack { .. }
+ | Self::ConnectionClose {
+ error_code: CloseError::Transport(_),
+ ..
+ } => pt != PacketType::ZeroRtt,
+ Self::NewToken { .. } | Self::ConnectionClose { .. } => pt == PacketType::Short,
+ _ => pt == PacketType::ZeroRtt || pt == PacketType::Short,
+ }
+ }
+
+ pub fn decode(dec: &mut Decoder<'a>) -> Res<Self> {
+ fn d<T>(v: Option<T>) -> Res<T> {
+ v.ok_or(Error::NoMoreData)
+ }
+ fn dv(dec: &mut Decoder) -> Res<u64> {
+ d(dec.decode_varint())
+ }
+
+ // TODO(ekr@rtfm.com): check for minimal encoding
+ let t = d(dec.decode_varint())?;
+ match t {
+ FRAME_TYPE_PADDING => Ok(Self::Padding),
+ FRAME_TYPE_PING => Ok(Self::Ping),
+ FRAME_TYPE_RST_STREAM => Ok(Self::ResetStream {
+ stream_id: StreamId::from(dv(dec)?),
+ application_error_code: d(dec.decode_varint())?,
+ final_size: match dec.decode_varint() {
+ Some(v) => v,
+ _ => return Err(Error::NoMoreData),
+ },
+ }),
+ FRAME_TYPE_ACK | FRAME_TYPE_ACK_ECN => {
+ let la = dv(dec)?;
+ let ad = dv(dec)?;
+ let nr = dv(dec)?;
+ let fa = dv(dec)?;
+ let mut arr: Vec<AckRange> = Vec::with_capacity(nr as usize);
+ for _ in 0..nr {
+ let ar = AckRange {
+ gap: dv(dec)?,
+ range: dv(dec)?,
+ };
+ arr.push(ar);
+ }
+
+ // Now check for the values for ACK_ECN.
+ if t == FRAME_TYPE_ACK_ECN {
+ dv(dec)?;
+ dv(dec)?;
+ dv(dec)?;
+ }
+
+ Ok(Self::Ack {
+ largest_acknowledged: la,
+ ack_delay: ad,
+ first_ack_range: fa,
+ ack_ranges: arr,
+ })
+ }
+ FRAME_TYPE_STOP_SENDING => Ok(Self::StopSending {
+ stream_id: StreamId::from(dv(dec)?),
+ application_error_code: d(dec.decode_varint())?,
+ }),
+ FRAME_TYPE_CRYPTO => {
+ let offset = dv(dec)?;
+ let data = d(dec.decode_vvec())?;
+ if offset + u64::try_from(data.len()).unwrap() > ((1 << 62) - 1) {
+ return Err(Error::FrameEncodingError);
+ }
+ Ok(Self::Crypto { offset, data })
+ }
+ FRAME_TYPE_NEW_TOKEN => {
+ let token = d(dec.decode_vvec())?;
+ if token.is_empty() {
+ return Err(Error::FrameEncodingError);
+ }
+ Ok(Self::NewToken { token })
+ }
+ FRAME_TYPE_STREAM..=FRAME_TYPE_STREAM_MAX => {
+ let s = dv(dec)?;
+ let o = if t & STREAM_FRAME_BIT_OFF == 0 {
+ 0
+ } else {
+ dv(dec)?
+ };
+ let fill = (t & STREAM_FRAME_BIT_LEN) == 0;
+ let data = if fill {
+ qtrace!("STREAM frame, extends to the end of the packet");
+ dec.decode_remainder()
+ } else {
+ qtrace!("STREAM frame, with length");
+ d(dec.decode_vvec())?
+ };
+ if o + u64::try_from(data.len()).unwrap() > ((1 << 62) - 1) {
+ return Err(Error::FrameEncodingError);
+ }
+ Ok(Self::Stream {
+ fin: (t & STREAM_FRAME_BIT_FIN) != 0,
+ stream_id: StreamId::from(s),
+ offset: o,
+ data,
+ fill,
+ })
+ }
+ FRAME_TYPE_MAX_DATA => Ok(Self::MaxData {
+ maximum_data: dv(dec)?,
+ }),
+ FRAME_TYPE_MAX_STREAM_DATA => Ok(Self::MaxStreamData {
+ stream_id: StreamId::from(dv(dec)?),
+ maximum_stream_data: dv(dec)?,
+ }),
+ FRAME_TYPE_MAX_STREAMS_BIDI | FRAME_TYPE_MAX_STREAMS_UNIDI => {
+ let m = dv(dec)?;
+ if m > (1 << 60) {
+ return Err(Error::StreamLimitError);
+ }
+ Ok(Self::MaxStreams {
+ stream_type: StreamType::from_type_bit(t),
+ maximum_streams: StreamIndex::new(m),
+ })
+ }
+ FRAME_TYPE_DATA_BLOCKED => Ok(Self::DataBlocked {
+ data_limit: dv(dec)?,
+ }),
+ FRAME_TYPE_STREAM_DATA_BLOCKED => Ok(Self::StreamDataBlocked {
+ stream_id: dv(dec)?.into(),
+ stream_data_limit: dv(dec)?,
+ }),
+ FRAME_TYPE_STREAMS_BLOCKED_BIDI | FRAME_TYPE_STREAMS_BLOCKED_UNIDI => {
+ Ok(Self::StreamsBlocked {
+ stream_type: StreamType::from_type_bit(t),
+ stream_limit: StreamIndex::new(dv(dec)?),
+ })
+ }
+ FRAME_TYPE_NEW_CONNECTION_ID => {
+ let sequence_number = dv(dec)?;
+ let retire_prior = dv(dec)?;
+ let connection_id = d(dec.decode_vec(1))?;
+ if connection_id.len() > MAX_CONNECTION_ID_LEN {
+ return Err(Error::DecodingFrame);
+ }
+ let srt = d(dec.decode(16))?;
+ let stateless_reset_token = <&[_; 16]>::try_from(srt).unwrap();
+
+ Ok(Self::NewConnectionId {
+ sequence_number,
+ retire_prior,
+ connection_id,
+ stateless_reset_token,
+ })
+ }
+ FRAME_TYPE_RETIRE_CONNECTION_ID => Ok(Self::RetireConnectionId {
+ sequence_number: dv(dec)?,
+ }),
+ FRAME_TYPE_PATH_CHALLENGE => {
+ let data = d(dec.decode(8))?;
+ let mut datav: [u8; 8] = [0; 8];
+ datav.copy_from_slice(&data);
+ Ok(Self::PathChallenge { data: datav })
+ }
+ FRAME_TYPE_PATH_RESPONSE => {
+ let data = d(dec.decode(8))?;
+ let mut datav: [u8; 8] = [0; 8];
+ datav.copy_from_slice(&data);
+ Ok(Self::PathResponse { data: datav })
+ }
+ FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT | FRAME_TYPE_CONNECTION_CLOSE_APPLICATION => {
+ let error_code = CloseError::from_type_bit(t, d(dec.decode_varint())?);
+ let frame_type = if t == FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT {
+ dv(dec)?
+ } else {
+ 0
+ };
+ // We can tolerate this copy for now.
+ let reason_phrase = d(dec.decode_vvec())?.to_vec();
+ Ok(Self::ConnectionClose {
+ error_code,
+ frame_type,
+ reason_phrase,
+ })
+ }
+ FRAME_TYPE_HANDSHAKE_DONE => Ok(Self::HandshakeDone),
+ _ => Err(Error::UnknownFrameType),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use neqo_common::{Decoder, Encoder};
+
+ fn just_dec(f: &Frame, s: &str) {
+ let encoded = Encoder::from_hex(s);
+ let decoded = Frame::decode(&mut encoded.as_decoder()).unwrap();
+ assert_eq!(*f, decoded);
+ }
+
+ #[test]
+ fn padding() {
+ let f = Frame::Padding;
+ just_dec(&f, "00");
+ }
+
+ #[test]
+ fn ping() {
+ let f = Frame::Ping;
+ just_dec(&f, "01");
+ }
+
+ #[test]
+ fn ack() {
+ let ar = vec![AckRange { gap: 1, range: 2 }, AckRange { gap: 3, range: 4 }];
+
+ let f = Frame::Ack {
+ largest_acknowledged: 0x1234,
+ ack_delay: 0x1235,
+ first_ack_range: 0x1236,
+ ack_ranges: ar,
+ };
+
+ just_dec(&f, "025234523502523601020304");
+
+ // Try to parse ACK_ECN without ECN values
+ let enc = Encoder::from_hex("035234523502523601020304");
+ let mut dec = enc.as_decoder();
+ assert_eq!(Frame::decode(&mut dec).unwrap_err(), Error::NoMoreData);
+
+ // Try to parse ACK_ECN without ECN values
+ let enc = Encoder::from_hex("035234523502523601020304010203");
+ let mut dec = enc.as_decoder();
+ assert_eq!(Frame::decode(&mut dec).unwrap(), f);
+ }
+
+ #[test]
+ fn reset_stream() {
+ let f = Frame::ResetStream {
+ stream_id: StreamId::from(0x1234),
+ application_error_code: 0x77,
+ final_size: 0x3456,
+ };
+
+ just_dec(&f, "04523440777456");
+ }
+
+ #[test]
+ fn stop_sending() {
+ let f = Frame::StopSending {
+ stream_id: StreamId::from(63),
+ application_error_code: 0x77,
+ };
+
+ just_dec(&f, "053F4077")
+ }
+
+ #[test]
+ fn crypto() {
+ let f = Frame::Crypto {
+ offset: 1,
+ data: &[1, 2, 3],
+ };
+
+ just_dec(&f, "060103010203");
+ }
+
+ #[test]
+ fn new_token() {
+ let f = Frame::NewToken {
+ token: &[0x12, 0x34, 0x56],
+ };
+
+ just_dec(&f, "0703123456");
+ }
+
+ #[test]
+ fn empty_new_token() {
+ let mut dec = Decoder::from(&[0x07, 0x00][..]);
+ assert_eq!(
+ Frame::decode(&mut dec).unwrap_err(),
+ Error::FrameEncodingError
+ );
+ }
+
+ #[test]
+ fn stream() {
+ // First, just set the length bit.
+ let f = Frame::Stream {
+ fin: false,
+ stream_id: StreamId::from(5),
+ offset: 0,
+ data: &[1, 2, 3],
+ fill: false,
+ };
+
+ just_dec(&f, "0a0503010203");
+
+ // Now with offset != 0 and FIN
+ let f = Frame::Stream {
+ fin: true,
+ stream_id: StreamId::from(5),
+ offset: 1,
+ data: &[1, 2, 3],
+ fill: false,
+ };
+ just_dec(&f, "0f050103010203");
+
+ // Now to fill the packet.
+ let f = Frame::Stream {
+ fin: true,
+ stream_id: StreamId::from(5),
+ offset: 0,
+ data: &[1, 2, 3],
+ fill: true,
+ };
+ just_dec(&f, "0905010203");
+ }
+
+ #[test]
+ fn max_data() {
+ let f = Frame::MaxData {
+ maximum_data: 0x1234,
+ };
+
+ just_dec(&f, "105234");
+ }
+
+ #[test]
+ fn max_stream_data() {
+ let f = Frame::MaxStreamData {
+ stream_id: StreamId::from(5),
+ maximum_stream_data: 0x1234,
+ };
+
+ just_dec(&f, "11055234");
+ }
+
+ #[test]
+ fn max_streams() {
+ let mut f = Frame::MaxStreams {
+ stream_type: StreamType::BiDi,
+ maximum_streams: StreamIndex::new(0x1234),
+ };
+
+ just_dec(&f, "125234");
+
+ f = Frame::MaxStreams {
+ stream_type: StreamType::UniDi,
+ maximum_streams: StreamIndex::new(0x1234),
+ };
+
+ just_dec(&f, "135234");
+ }
+
+ #[test]
+ fn data_blocked() {
+ let f = Frame::DataBlocked { data_limit: 0x1234 };
+
+ just_dec(&f, "145234");
+ }
+
+ #[test]
+ fn stream_data_blocked() {
+ let f = Frame::StreamDataBlocked {
+ stream_id: StreamId::from(5),
+ stream_data_limit: 0x1234,
+ };
+
+ just_dec(&f, "15055234");
+ }
+
+ #[test]
+ fn streams_blocked() {
+ let mut f = Frame::StreamsBlocked {
+ stream_type: StreamType::BiDi,
+ stream_limit: StreamIndex::new(0x1234),
+ };
+
+ just_dec(&f, "165234");
+
+ f = Frame::StreamsBlocked {
+ stream_type: StreamType::UniDi,
+ stream_limit: StreamIndex::new(0x1234),
+ };
+
+ just_dec(&f, "175234");
+ }
+
+ #[test]
+ fn new_connection_id() {
+ let f = Frame::NewConnectionId {
+ sequence_number: 0x1234,
+ retire_prior: 0,
+ connection_id: &[0x01, 0x02],
+ stateless_reset_token: &[9; 16],
+ };
+
+ just_dec(&f, "1852340002010209090909090909090909090909090909");
+ }
+
+ #[test]
+ fn too_large_new_connection_id() {
+ let mut enc = Encoder::from_hex("18523400"); // up to the CID
+ enc.encode_vvec(&[0x0c; MAX_CONNECTION_ID_LEN + 10]);
+ enc.encode(&[0x11; 16][..]);
+ assert_eq!(
+ Frame::decode(&mut enc.as_decoder()).unwrap_err(),
+ Error::DecodingFrame
+ );
+ }
+
+ #[test]
+ fn retire_connection_id() {
+ let f = Frame::RetireConnectionId {
+ sequence_number: 0x1234,
+ };
+
+ just_dec(&f, "195234");
+ }
+
+ #[test]
+ fn path_challenge() {
+ let f = Frame::PathChallenge { data: [9; 8] };
+
+ just_dec(&f, "1a0909090909090909");
+ }
+
+ #[test]
+ fn path_response() {
+ let f = Frame::PathResponse { data: [9; 8] };
+
+ just_dec(&f, "1b0909090909090909");
+ }
+
+ #[test]
+ fn connection_close_transport() {
+ let f = Frame::ConnectionClose {
+ error_code: CloseError::Transport(0x5678),
+ frame_type: 0x1234,
+ reason_phrase: vec![0x01, 0x02, 0x03],
+ };
+
+ just_dec(&f, "1c80005678523403010203");
+ }
+
+ #[test]
+ fn connection_close_application() {
+ let f = Frame::ConnectionClose {
+ error_code: CloseError::Application(0x5678),
+ frame_type: 0,
+ reason_phrase: vec![0x01, 0x02, 0x03],
+ };
+
+ just_dec(&f, "1d8000567803010203");
+ }
+
+ #[test]
+ fn test_compare() {
+ let f1 = Frame::Padding;
+ let f2 = Frame::Padding;
+ let f3 = Frame::Crypto {
+ offset: 0,
+ data: &[1, 2, 3],
+ };
+ let f4 = Frame::Crypto {
+ offset: 0,
+ data: &[1, 2, 3],
+ };
+ let f5 = Frame::Crypto {
+ offset: 1,
+ data: &[1, 2, 3],
+ };
+ let f6 = Frame::Crypto {
+ offset: 0,
+ data: &[1, 2, 4],
+ };
+
+ assert_eq!(f1, f2);
+ assert_ne!(f1, f3);
+ assert_eq!(f3, f4);
+ assert_ne!(f3, f5);
+ assert_ne!(f3, f6);
+ }
+
+ #[test]
+ fn decode_ack_frame() {
+ let res = Frame::decode_ack_frame(7, 2, &[AckRange { gap: 0, range: 3 }]);
+ assert!(res.is_ok());
+ assert_eq!(res.unwrap(), vec![5..=7, 0..=3]);
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/lib.rs b/third_party/rust/neqo-transport/src/lib.rs
new file mode 100644
index 0000000000..30c9e783af
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/lib.rs
@@ -0,0 +1,195 @@
+// 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)]
+
+use neqo_common::qinfo;
+
+mod addr_valid;
+mod cc;
+mod cid;
+mod connection;
+mod crypto;
+mod dump;
+mod events;
+mod flow_mgr;
+mod frame;
+mod pace;
+mod packet;
+mod path;
+mod qlog;
+mod recovery;
+mod recv_stream;
+mod send_stream;
+mod sender;
+pub mod server;
+mod stats;
+mod stream_id;
+pub mod tparams;
+mod tracking;
+
+pub use self::cc::CongestionControlAlgorithm;
+pub use self::cid::{ConnectionId, ConnectionIdManager};
+pub use self::connection::{
+ params::ConnectionParameters, Connection, FixedConnectionIdManager, Output, State,
+ ZeroRttState, LOCAL_STREAM_LIMIT_BIDI, LOCAL_STREAM_LIMIT_UNI,
+};
+pub use self::events::{ConnectionEvent, ConnectionEvents};
+pub use self::frame::{CloseError, StreamType};
+pub use self::packet::QuicVersion;
+pub use self::sender::PacketSender;
+pub use self::stats::Stats;
+pub use self::stream_id::StreamId;
+
+pub use self::recv_stream::RECV_BUFFER_SIZE;
+pub use self::send_stream::SEND_BUFFER_SIZE;
+
+pub type TransportError = u64;
+const ERROR_APPLICATION_CLOSE: TransportError = 12;
+const ERROR_AEAD_LIMIT_REACHED: TransportError = 15;
+
+#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
+#[allow(clippy::pub_enum_variant_names)]
+pub enum Error {
+ NoError,
+ InternalError,
+ ConnectionRefused,
+ FlowControlError,
+ StreamLimitError,
+ StreamStateError,
+ FinalSizeError,
+ FrameEncodingError,
+ TransportParameterError,
+ ProtocolViolation,
+ InvalidToken,
+ ApplicationError,
+ CryptoError(neqo_crypto::Error),
+ QlogError,
+ CryptoAlert(u8),
+
+ // All internal errors from here.
+ AckedUnsentPacket,
+ ConnectionState,
+ DecodingFrame,
+ DecryptError,
+ HandshakeFailed,
+ IdleTimeout,
+ IntegerOverflow,
+ InvalidInput,
+ InvalidMigration,
+ InvalidPacket,
+ InvalidResumptionToken,
+ InvalidRetry,
+ InvalidStreamId,
+ KeysDiscarded,
+ /// Packet protection keys are exhausted.
+ /// Also used when too many key updates have happened.
+ KeysExhausted,
+ /// Packet protection keys aren't available yet for the identified space.
+ KeysPending(crypto::CryptoSpace),
+ /// An attempt to update keys can be blocked if
+ /// a packet sent with the current keys hasn't been acknowledged.
+ KeyUpdateBlocked,
+ NoMoreData,
+ NotConnected,
+ PacketNumberOverlap,
+ PeerApplicationError(AppError),
+ PeerError(TransportError),
+ StatelessReset,
+ TooMuchData,
+ UnexpectedMessage,
+ UnknownFrameType,
+ VersionNegotiation,
+ WrongRole,
+}
+
+impl Error {
+ pub fn code(&self) -> TransportError {
+ match self {
+ Self::NoError
+ | Self::IdleTimeout
+ | Self::PeerError(_)
+ | Self::PeerApplicationError(_) => 0,
+ Self::ConnectionRefused => 2,
+ Self::FlowControlError => 3,
+ Self::StreamLimitError => 4,
+ Self::StreamStateError => 5,
+ Self::FinalSizeError => 6,
+ Self::FrameEncodingError => 7,
+ Self::TransportParameterError => 8,
+ Self::ProtocolViolation => 10,
+ Self::InvalidToken => 11,
+ Self::KeysExhausted => ERROR_AEAD_LIMIT_REACHED,
+ Self::ApplicationError => ERROR_APPLICATION_CLOSE,
+ Self::CryptoAlert(a) => 0x100 + u64::from(*a),
+ // All the rest are internal errors.
+ _ => 1,
+ }
+ }
+}
+
+impl From<neqo_crypto::Error> for Error {
+ fn from(err: neqo_crypto::Error) -> Self {
+ qinfo!("Crypto operation failed {:?}", err);
+ Self::CryptoError(err)
+ }
+}
+
+impl From<::qlog::Error> for Error {
+ fn from(_err: ::qlog::Error) -> Self {
+ Self::QlogError
+ }
+}
+
+impl From<std::num::TryFromIntError> for Error {
+ fn from(_: std::num::TryFromIntError) -> Self {
+ Self::IntegerOverflow
+ }
+}
+
+impl ::std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
+ match self {
+ Self::CryptoError(e) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+impl ::std::fmt::Display for Error {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Transport error: {:?}", self)
+ }
+}
+
+pub type AppError = u64;
+
+#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
+pub enum ConnectionError {
+ Transport(Error),
+ Application(AppError),
+}
+
+impl ConnectionError {
+ pub fn app_code(&self) -> Option<AppError> {
+ match self {
+ Self::Application(e) => Some(*e),
+ _ => None,
+ }
+ }
+}
+
+impl From<CloseError> for ConnectionError {
+ fn from(err: CloseError) -> Self {
+ match err {
+ CloseError::Transport(c) => Self::Transport(Error::PeerError(c)),
+ CloseError::Application(c) => Self::Application(c),
+ }
+ }
+}
+
+pub type Res<T> = std::result::Result<T, Error>;
diff --git a/third_party/rust/neqo-transport/src/pace.rs b/third_party/rust/neqo-transport/src/pace.rs
new file mode 100644
index 0000000000..624fb8622e
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/pace.rs
@@ -0,0 +1,138 @@
+// 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.
+
+// Pacer
+#![deny(clippy::pedantic)]
+
+use neqo_common::qtrace;
+
+use std::cmp::min;
+use std::convert::TryFrom;
+use std::fmt::{Debug, Display};
+use std::time::{Duration, Instant};
+
+/// This value determines how much faster the pacer operates than the
+/// congestion window.
+///
+/// A value of 1 would cause all packets to be spaced over the entire RTT,
+/// which is a little slow and might act as an additional restriction in
+/// the case the congestion controller increases the congestion window.
+/// This value spaces packets over half the congestion window, which matches
+/// our current congestion controller, which double the window every RTT.
+const PACER_SPEEDUP: usize = 2;
+
+/// A pacer that uses a leaky bucket.
+pub struct Pacer {
+ /// The last update time.
+ t: Instant,
+ /// The maximum capacity, or burst size, in bytes.
+ m: usize,
+ /// The current capacity, in bytes.
+ c: usize,
+ /// The packet size or minimum capacity for sending, in bytes.
+ p: usize,
+}
+
+impl Pacer {
+ /// Create a new `Pacer`. This takes the current time and the
+ /// initial congestion window.
+ ///
+ /// The value of `m` is the maximum capacity. `m` primes the pacer
+ /// with credit and determines the burst size. `m` must not exceed
+ /// the initial congestion window, but it should probably be lower.
+ ///
+ /// The value of `p` is the packet size, which determines the minimum
+ /// credit needed before a packet is sent. This should be a substantial
+ /// fraction of the maximum packet size, if not the packet size.
+ pub fn new(now: Instant, m: usize, p: usize) -> Self {
+ assert!(m >= p, "maximum capacity has to be at least one packet");
+ Self { t: now, m, c: m, p }
+ }
+
+ /// Determine when the next packet will be available based on the provided RTT
+ /// and congestion window. This doesn't update state.
+ /// This returns a time, which could be in the past (this object doesn't know what
+ /// the current time is).
+ pub fn next(&self, rtt: Duration, cwnd: usize) -> Instant {
+ if self.c >= self.p {
+ qtrace!([self], "next {}/{:?} no wait = {:?}", cwnd, rtt, self.t);
+ self.t
+ } else {
+ // This is the inverse of the function in `spend`:
+ // self.t + rtt * (self.p - self.c) / (PACER_SPEEDUP * cwnd)
+ let r = rtt.as_nanos();
+ let d = r.saturating_mul(u128::try_from(self.p - self.c).unwrap());
+ let add = d / u128::try_from(cwnd * PACER_SPEEDUP).unwrap();
+ let w = u64::try_from(add).map(Duration::from_nanos).unwrap_or(rtt);
+ let nxt = self.t + w;
+ qtrace!([self], "next {}/{:?} wait {:?} = {:?}", cwnd, rtt, w, nxt);
+ nxt
+ }
+ }
+
+ /// Spend credit. This cannot fail; users of this API are expected to call
+ /// next() to determine when to spend. This takes the current time (`now`),
+ /// an estimate of the round trip time (`rtt`), the estimated congestion
+ /// window (`cwnd`), and the number of bytes that were sent (`count`).
+ pub fn spend(&mut self, now: Instant, rtt: Duration, cwnd: usize, count: usize) {
+ qtrace!([self], "spend {} over {}, {:?}", count, cwnd, rtt);
+ // Increase the capacity by:
+ // `(now - self.t) * PACER_SPEEDUP * cwnd / rtt`
+ // That is, the elapsed fraction of the RTT times rate that data is added.
+ let incr = now
+ .saturating_duration_since(self.t)
+ .as_nanos()
+ .saturating_mul(u128::try_from(cwnd * PACER_SPEEDUP).unwrap())
+ .checked_div(rtt.as_nanos())
+ .and_then(|i| usize::try_from(i).ok())
+ .unwrap_or(self.m);
+
+ // Add the capacity up to a limit of `self.m`, then subtract `count`.
+ self.c = min(self.m, (self.c + incr).saturating_sub(count));
+ self.t = now;
+ }
+}
+
+impl Display for Pacer {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Pacer {}/{}", self.c, self.p)
+ }
+}
+
+impl Debug for Pacer {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Pacer@{:?} {}/{}..{}", self.t, self.c, self.p, self.m)
+ }
+}
+
+#[cfg(tests)]
+mod tests {
+ use super::Pacer;
+ use test_fixture::now;
+
+ const RTT: Duration = Duration::from_millis(1000);
+ const PACKET: usize = 1000;
+ const CWND: usize = PACKET * 10;
+
+ #[test]
+ fn even() {
+ let mut n = now();
+ let p = Pacer::new(n, PACKET, PACKET);
+ assert_eq!(p.next(RTT, CWND), None);
+ p.spend(n, RTT, CWND, PACKET);
+ assert_eq!(p.next(RTT, CWND), Some(n + (RTT / 10)));
+ }
+
+ #[test]
+ fn backwards_in_time() {
+ let mut n = now();
+ let p = Pacer::new(n + RTT, PACKET, PACKET);
+ assert_eq!(p.next(RTT, CWND), None);
+ // Now spend some credit in the past using a time machine.
+ p.spend(n, RTT, CWND, PACKET);
+ assert_eq!(p.next(RTT, CWND), Some(n + (RTT / 10)));
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/packet/mod.rs b/third_party/rust/neqo-transport/src/packet/mod.rs
new file mode 100644
index 0000000000..9ab4d811a1
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/packet/mod.rs
@@ -0,0 +1,1339 @@
+// 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.
+
+// Encoding and decoding packets off the wire.
+use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef, MAX_CONNECTION_ID_LEN};
+use crate::crypto::{CryptoDxState, CryptoSpace, CryptoStates};
+use crate::{Error, Res};
+
+use neqo_common::{hex, hex_with_len, qtrace, qwarn, Decoder, Encoder};
+use neqo_crypto::random;
+
+use std::cmp::min;
+use std::convert::TryFrom;
+use std::fmt;
+use std::iter::ExactSizeIterator;
+use std::ops::{Deref, DerefMut, Range};
+use std::time::Instant;
+
+const PACKET_TYPE_INITIAL: u8 = 0x0;
+const PACKET_TYPE_0RTT: u8 = 0x01;
+const PACKET_TYPE_HANDSHAKE: u8 = 0x2;
+const PACKET_TYPE_RETRY: u8 = 0x03;
+
+pub const PACKET_BIT_LONG: u8 = 0x80;
+const PACKET_BIT_SHORT: u8 = 0x00;
+const PACKET_BIT_FIXED_QUIC: u8 = 0x40;
+const PACKET_BIT_SPIN: u8 = 0x20;
+const PACKET_BIT_KEY_PHASE: u8 = 0x04;
+
+const PACKET_HP_MASK_LONG: u8 = 0x0f;
+const PACKET_HP_MASK_SHORT: u8 = 0x1f;
+
+const SAMPLE_SIZE: usize = 16;
+const SAMPLE_OFFSET: usize = 4;
+const MAX_PACKET_NUMBER_LEN: usize = 4;
+
+mod retry;
+
+pub type PacketNumber = u64;
+type Version = u32;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PacketType {
+ VersionNegotiation,
+ Initial,
+ Handshake,
+ ZeroRtt,
+ Retry,
+ Short,
+ OtherVersion,
+}
+
+impl PacketType {
+ #[must_use]
+ fn code(self) -> u8 {
+ match self {
+ Self::Initial => PACKET_TYPE_INITIAL,
+ Self::ZeroRtt => PACKET_TYPE_0RTT,
+ Self::Handshake => PACKET_TYPE_HANDSHAKE,
+ Self::Retry => PACKET_TYPE_RETRY,
+ _ => panic!("shouldn't be here"),
+ }
+ }
+}
+
+impl Into<CryptoSpace> for PacketType {
+ fn into(self) -> CryptoSpace {
+ match self {
+ Self::Initial => CryptoSpace::Initial,
+ Self::ZeroRtt => CryptoSpace::ZeroRtt,
+ Self::Handshake => CryptoSpace::Handshake,
+ Self::Short => CryptoSpace::ApplicationData,
+ _ => panic!("shouldn't be here"),
+ }
+ }
+}
+
+impl From<CryptoSpace> for PacketType {
+ fn from(cs: CryptoSpace) -> Self {
+ match cs {
+ CryptoSpace::Initial => Self::Initial,
+ CryptoSpace::ZeroRtt => Self::ZeroRtt,
+ CryptoSpace::Handshake => Self::Handshake,
+ CryptoSpace::ApplicationData => Self::Short,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum QuicVersion {
+ Draft27,
+ Draft28,
+ Draft29,
+ Draft30,
+ Draft31,
+ Draft32,
+}
+
+impl QuicVersion {
+ pub fn as_u32(self) -> Version {
+ match self {
+ Self::Draft27 => 0xff00_0000 + 27,
+ Self::Draft28 => 0xff00_0000 + 28,
+ Self::Draft29 => 0xff00_0000 + 29,
+ Self::Draft30 => 0xff00_0000 + 30,
+ Self::Draft31 => 0xff00_0000 + 31,
+ Self::Draft32 => 0xff00_0000 + 32,
+ }
+ }
+}
+
+impl Default for QuicVersion {
+ fn default() -> Self {
+ Self::Draft29
+ }
+}
+
+impl TryFrom<Version> for QuicVersion {
+ type Error = Error;
+
+ fn try_from(ver: Version) -> Res<Self> {
+ if ver == 0xff00_0000 + 27 {
+ Ok(Self::Draft27)
+ } else if ver == 0xff00_0000 + 28 {
+ Ok(Self::Draft28)
+ } else if ver == 0xff00_0000 + 29 {
+ Ok(Self::Draft29)
+ } else if ver == 0xff00_0000 + 30 {
+ Ok(Self::Draft30)
+ } else if ver == 0xff00_0000 + 31 {
+ Ok(Self::Draft31)
+ } else if ver == 0xff00_0000 + 32 {
+ Ok(Self::Draft32)
+ } else {
+ Err(Error::VersionNegotiation)
+ }
+ }
+}
+
+struct PacketBuilderOffsets {
+ /// The bits of the first octet that need masking.
+ first_byte_mask: u8,
+ /// The offset of the length field.
+ len: usize,
+ /// The location of the packet number field.
+ pn: Range<usize>,
+}
+
+/// A packet builder that can be used to produce short packets and long packets.
+/// This does not produce Retry or Version Negotiation.
+pub struct PacketBuilder {
+ encoder: Encoder,
+ pn: PacketNumber,
+ header: Range<usize>,
+ offsets: PacketBuilderOffsets,
+ limit: usize,
+}
+
+impl PacketBuilder {
+ fn infer_limit(encoder: &Encoder) -> usize {
+ if encoder.capacity() > 64 {
+ encoder.capacity()
+ } else {
+ 2048
+ }
+ }
+
+ /// Start building a short header packet.
+ #[allow(clippy::unknown_clippy_lints)] // Until we require rust 1.45.
+ #[allow(clippy::reversed_empty_ranges)]
+ pub fn short(mut encoder: Encoder, key_phase: bool, dcid: impl AsRef<[u8]>) -> Self {
+ let header_start = encoder.len();
+ encoder.encode_byte(PACKET_BIT_SHORT | PACKET_BIT_FIXED_QUIC | (u8::from(key_phase) << 2));
+ encoder.encode(dcid.as_ref());
+ let limit = Self::infer_limit(&encoder);
+ Self {
+ encoder,
+ pn: u64::max_value(),
+ header: header_start..header_start,
+ offsets: PacketBuilderOffsets {
+ first_byte_mask: PACKET_HP_MASK_SHORT,
+ pn: 0..0,
+ len: 0,
+ },
+ limit,
+ }
+ }
+
+ /// Start building a long header packet.
+ /// For an Initial packet you will need to call initial_token(),
+ /// even if the token is empty.
+ #[allow(clippy::unknown_clippy_lints)] // Until we require rust 1.45.
+ #[allow(clippy::reversed_empty_ranges)] // For initializing an empty range.
+ pub fn long(
+ mut encoder: Encoder,
+ pt: PacketType,
+ quic_version: QuicVersion,
+ dcid: impl AsRef<[u8]>,
+ scid: impl AsRef<[u8]>,
+ ) -> Self {
+ let header_start = encoder.len();
+ encoder.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC | pt.code() << 4);
+ encoder.encode_uint(4, quic_version.as_u32());
+ encoder.encode_vec(1, dcid.as_ref());
+ encoder.encode_vec(1, scid.as_ref());
+ let limit = Self::infer_limit(&encoder);
+ Self {
+ encoder,
+ pn: u64::max_value(),
+ header: header_start..header_start,
+ offsets: PacketBuilderOffsets {
+ first_byte_mask: PACKET_HP_MASK_LONG,
+ pn: 0..0,
+ len: 0,
+ },
+ limit,
+ }
+ }
+
+ fn is_long(&self) -> bool {
+ self[self.header.start] & 0x80 == PACKET_BIT_LONG
+ }
+
+ /// This stores a value that can be used as a limit. This does not cause
+ /// this limit to be enforced until encryption occurs. Prior to that, it
+ /// is only used voluntarily by users of the builder, through `remaining()`.
+ pub fn set_limit(&mut self, limit: usize) {
+ self.limit = limit;
+ }
+
+ /// How many bytes remain against the size limit for the builder.
+ #[must_use]
+ pub fn remaining(&self) -> usize {
+ self.limit - self.encoder.len()
+ }
+
+ /// Pad with "PADDING" frames.
+ pub fn pad(&mut self) {
+ self.encoder.pad_to(self.limit, 0);
+ }
+
+ /// Add unpredictable values for unprotected parts of the packet.
+ pub fn scramble(&mut self, quic_bit: bool) {
+ let mask = if quic_bit { PACKET_BIT_FIXED_QUIC } else { 0 }
+ | if self.is_long() { 0 } else { PACKET_BIT_SPIN };
+ let first = self.header.start;
+ self[first] ^= random(1)[0] & mask;
+ }
+
+ /// For an Initial packet, encode the token.
+ /// If you fail to do this, then you will not get a valid packet.
+ pub fn initial_token(&mut self, token: &[u8]) {
+ debug_assert_eq!(
+ self.encoder[self.header.start] & 0xb0,
+ PACKET_BIT_LONG | PACKET_TYPE_INITIAL << 4
+ );
+ self.encoder.encode_vvec(token);
+ }
+
+ /// Add a packet number of the given size.
+ /// For a long header packet, this also inserts a dummy length.
+ /// The length is filled in after calling `build`.
+ pub fn pn(&mut self, pn: PacketNumber, pn_len: usize) {
+ // Reserve space for a length in long headers.
+ if self.is_long() {
+ self.offsets.len = self.encoder.len();
+ self.encoder.encode(&[0; 2]);
+ }
+
+ // This allows the input to be >4, which is absurd, but we can eat that.
+ let pn_len = min(MAX_PACKET_NUMBER_LEN, pn_len);
+ debug_assert_ne!(pn_len, 0);
+ // Encode the packet number and save its offset.
+ let pn_offset = self.encoder.len();
+ self.encoder.encode_uint(pn_len, pn);
+ self.offsets.pn = pn_offset..self.encoder.len();
+
+ // Now encode the packet number length and save the header length.
+ self.encoder[self.header.start] |= u8::try_from(pn_len - 1).unwrap();
+ self.header.end = self.encoder.len();
+ self.pn = pn;
+ }
+
+ fn write_len(&mut self, expansion: usize) {
+ let len = self.encoder.len() - (self.offsets.len + 2) + expansion;
+ self.encoder[self.offsets.len] = 0x40 | ((len >> 8) & 0x3f) as u8;
+ self.encoder[self.offsets.len + 1] = (len & 0xff) as u8;
+ }
+
+ fn pad_for_crypto(&mut self, crypto: &mut CryptoDxState) {
+ // Make sure that there is enough data in the packet.
+ // The length of the packet number plus the payload length needs to
+ // be at least 4 (MAX_PACKET_NUMBER_LEN) plus any amount by which
+ // the header protection sample exceeds the AEAD expansion.
+ let crypto_pad = crypto.extra_padding();
+ self.encoder.pad_to(
+ self.offsets.pn.start + MAX_PACKET_NUMBER_LEN + crypto_pad,
+ 0,
+ );
+ }
+
+ /// Build the packet and return the encoder.
+ pub fn build(mut self, crypto: &mut CryptoDxState) -> Res<Encoder> {
+ if self.len() > self.limit {
+ qwarn!("Packet contents are more than the limit");
+ debug_assert!(false);
+ return Err(Error::InternalError);
+ }
+
+ self.pad_for_crypto(crypto);
+ if self.offsets.len > 0 {
+ self.write_len(crypto.expansion());
+ }
+
+ let hdr = &self.encoder[self.header.clone()];
+ let body = &self.encoder[self.header.end..];
+ qtrace!(
+ "Packet build pn={} hdr={} body={}",
+ self.pn,
+ hex(hdr),
+ hex(body)
+ );
+ let ciphertext = crypto.encrypt(self.pn, hdr, body)?;
+
+ // Calculate the mask.
+ let offset = SAMPLE_OFFSET - self.offsets.pn.len();
+ assert!(offset + SAMPLE_SIZE <= ciphertext.len());
+ let sample = &ciphertext[offset..offset + SAMPLE_SIZE];
+ let mask = crypto.compute_mask(sample)?;
+
+ // Apply the mask.
+ self.encoder[self.header.start] ^= mask[0] & self.offsets.first_byte_mask;
+ for (i, j) in (1..=self.offsets.pn.len()).zip(self.offsets.pn) {
+ self.encoder[j] ^= mask[i];
+ }
+
+ // Finally, cut off the plaintext and add back the ciphertext.
+ self.encoder.truncate(self.header.end);
+ self.encoder.encode(&ciphertext);
+ qtrace!("Packet built {}", hex(&self.encoder));
+ Ok(self.encoder)
+ }
+
+ /// Abort writing of this packet and return the encoder.
+ #[must_use]
+ pub fn abort(mut self) -> Encoder {
+ self.encoder.truncate(self.header.start);
+ self.encoder
+ }
+
+ /// Work out if nothing was added after the header.
+ #[must_use]
+ pub fn packet_empty(&self) -> bool {
+ self.encoder.len() == self.header.end
+ }
+
+ /// Make a retry packet.
+ /// As this is a simple packet, this is just an associated function.
+ /// As Retry is odd (it has to be constructed with leading bytes),
+ /// this returns a Vec<u8> rather than building on an encoder.
+ pub fn retry(
+ quic_version: QuicVersion,
+ dcid: &[u8],
+ scid: &[u8],
+ token: &[u8],
+ odcid: &[u8],
+ ) -> Res<Vec<u8>> {
+ let mut encoder = Encoder::default();
+ encoder.encode_vec(1, odcid);
+ let start = encoder.len();
+ encoder.encode_byte(
+ PACKET_BIT_LONG
+ | PACKET_BIT_FIXED_QUIC
+ | (PACKET_TYPE_RETRY << 4)
+ | (random(1)[0] & 0xf),
+ );
+ encoder.encode_uint(4, quic_version.as_u32());
+ encoder.encode_vec(1, dcid);
+ encoder.encode_vec(1, scid);
+ debug_assert_ne!(token.len(), 0);
+ encoder.encode(token);
+ let tag = retry::use_aead(quic_version, |aead| {
+ let mut buf = vec![0; aead.expansion()];
+ Ok(aead.encrypt(0, &encoder, &[], &mut buf)?.to_vec())
+ })?;
+ encoder.encode(&tag);
+ let mut complete: Vec<u8> = encoder.into();
+ Ok(complete.split_off(start))
+ }
+
+ /// Make a Version Negotiation packet.
+ pub fn version_negotiation(dcid: &[u8], scid: &[u8]) -> Vec<u8> {
+ let mut encoder = Encoder::default();
+ let mut grease = random(5);
+ // This will not include the "QUIC bit" sometimes. Intentionally.
+ encoder.encode_byte(PACKET_BIT_LONG | (grease[4] & 0x7f));
+ encoder.encode(&[0; 4]); // Zero version == VN.
+ encoder.encode_vec(1, dcid);
+ encoder.encode_vec(1, scid);
+ encoder.encode_uint(4, QuicVersion::Draft27.as_u32());
+ encoder.encode_uint(4, QuicVersion::Draft28.as_u32());
+ encoder.encode_uint(4, QuicVersion::Draft29.as_u32());
+ encoder.encode_uint(4, QuicVersion::Draft30.as_u32());
+ encoder.encode_uint(4, QuicVersion::Draft31.as_u32());
+ encoder.encode_uint(4, QuicVersion::Draft32.as_u32());
+ // Add a greased version, using the randomness already generated.
+ for g in &mut grease[..4] {
+ *g = *g & 0xf0 | 0x0a;
+ }
+ encoder.encode(&grease[0..4]);
+ encoder.into()
+ }
+}
+
+impl Deref for PacketBuilder {
+ type Target = Encoder;
+
+ fn deref(&self) -> &Self::Target {
+ &self.encoder
+ }
+}
+
+impl DerefMut for PacketBuilder {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.encoder
+ }
+}
+
+impl Into<Encoder> for PacketBuilder {
+ fn into(self) -> Encoder {
+ self.encoder
+ }
+}
+
+/// PublicPacket holds information from packets that is public only. This allows for
+/// processing of packets prior to decryption.
+pub struct PublicPacket<'a> {
+ /// The packet type.
+ packet_type: PacketType,
+ /// The recovered destination connection ID.
+ dcid: ConnectionIdRef<'a>,
+ /// The source connection ID, if this is a long header packet.
+ scid: Option<ConnectionIdRef<'a>>,
+ /// Any token that is included in the packet (Retry always has a token; Initial sometimes does).
+ /// This is empty when there is no token.
+ token: &'a [u8],
+ /// The size of the header, not including the packet number.
+ header_len: usize,
+ /// Protocol version, if present in header.
+ quic_version: Option<QuicVersion>,
+ /// A reference to the entire packet, including the header.
+ data: &'a [u8],
+}
+
+impl<'a> PublicPacket<'a> {
+ fn opt<T>(v: Option<T>) -> Res<T> {
+ if let Some(v) = v {
+ Ok(v)
+ } else {
+ Err(Error::NoMoreData)
+ }
+ }
+
+ /// Decode the type-specific portions of a long header.
+ /// This includes reading the length and the remainder of the packet.
+ /// Returns a tuple of any token and the length of the header.
+ fn decode_long(
+ decoder: &mut Decoder<'a>,
+ packet_type: PacketType,
+ quic_version: QuicVersion,
+ ) -> Res<(&'a [u8], usize)> {
+ if packet_type == PacketType::Retry {
+ let header_len = decoder.offset();
+ let expansion = retry::expansion(quic_version);
+ let token = Self::opt(decoder.decode(decoder.remaining() - expansion))?;
+ if token.is_empty() {
+ return Err(Error::InvalidPacket);
+ }
+ Self::opt(decoder.decode(expansion))?;
+ return Ok((token, header_len));
+ }
+ let token = if packet_type == PacketType::Initial {
+ Self::opt(decoder.decode_vvec())?
+ } else {
+ &[]
+ };
+ let len = Self::opt(decoder.decode_varint())?;
+ let header_len = decoder.offset();
+ let _body = Self::opt(decoder.decode(usize::try_from(len)?))?;
+ Ok((token, header_len))
+ }
+
+ /// Decode the common parts of a packet. This provides minimal parsing and validation.
+ /// Returns a tuple of a `PublicPacket` and a slice with any remainder from the datagram.
+ pub fn decode(data: &'a [u8], dcid_decoder: &dyn ConnectionIdDecoder) -> Res<(Self, &'a [u8])> {
+ let mut decoder = Decoder::new(data);
+ let first = Self::opt(decoder.decode_byte())?;
+
+ if first & 0x80 == PACKET_BIT_SHORT {
+ let dcid = Self::opt(dcid_decoder.decode_cid(&mut decoder))?;
+ if decoder.remaining() < SAMPLE_OFFSET + SAMPLE_SIZE {
+ return Err(Error::InvalidPacket);
+ }
+ let header_len = decoder.offset();
+ return Ok((
+ Self {
+ packet_type: PacketType::Short,
+ dcid,
+ scid: None,
+ token: &[],
+ header_len,
+ quic_version: None,
+ data,
+ },
+ &[],
+ ));
+ }
+
+ // Generic long header.
+ let version = Version::try_from(Self::opt(decoder.decode_uint(4))?).unwrap();
+ let dcid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?);
+ let scid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?);
+
+ // Version negotiation.
+ if version == 0 {
+ return Ok((
+ Self {
+ packet_type: PacketType::VersionNegotiation,
+ dcid,
+ scid: Some(scid),
+ token: &[],
+ header_len: decoder.offset(),
+ quic_version: None,
+ data,
+ },
+ &[],
+ ));
+ }
+
+ // Check that this is a long header from a supported version.
+ let quic_version = if let Ok(v) = QuicVersion::try_from(version) {
+ v
+ } else {
+ return Ok((
+ Self {
+ packet_type: PacketType::OtherVersion,
+ dcid,
+ scid: Some(scid),
+ token: &[],
+ header_len: decoder.offset(),
+ quic_version: None,
+ data,
+ },
+ &[],
+ ));
+ };
+
+ if dcid.len() > MAX_CONNECTION_ID_LEN || scid.len() > MAX_CONNECTION_ID_LEN {
+ return Err(Error::InvalidPacket);
+ }
+ let packet_type = match (first >> 4) & 3 {
+ PACKET_TYPE_INITIAL => PacketType::Initial,
+ PACKET_TYPE_0RTT => PacketType::ZeroRtt,
+ PACKET_TYPE_HANDSHAKE => PacketType::Handshake,
+ PACKET_TYPE_RETRY => PacketType::Retry,
+ _ => unreachable!(),
+ };
+
+ // The type-specific code includes a token. This consumes the remainder of the packet.
+ let (token, header_len) = Self::decode_long(&mut decoder, packet_type, quic_version)?;
+ let end = data.len() - decoder.remaining();
+ let (data, remainder) = data.split_at(end);
+ Ok((
+ Self {
+ packet_type,
+ dcid,
+ scid: Some(scid),
+ token,
+ header_len,
+ quic_version: Some(quic_version),
+ data,
+ },
+ remainder,
+ ))
+ }
+
+ /// Validate the given packet as though it were a retry.
+ pub fn is_valid_retry(&self, odcid: &ConnectionId) -> bool {
+ if self.packet_type != PacketType::Retry {
+ return false;
+ }
+ let version = self.quic_version.unwrap();
+ let expansion = retry::expansion(version);
+ if self.data.len() <= expansion {
+ return false;
+ }
+ let (header, tag) = self.data.split_at(self.data.len() - expansion);
+ let mut encoder = Encoder::with_capacity(self.data.len());
+ encoder.encode_vec(1, odcid);
+ encoder.encode(header);
+ retry::use_aead(version, |aead| {
+ let mut buf = vec![0; expansion];
+ Ok(aead.decrypt(0, &encoder, tag, &mut buf)?.is_empty())
+ })
+ .unwrap_or(false)
+ }
+
+ pub fn is_valid_initial(&self) -> bool {
+ // Packet has to be an initial, with a DCID of 8 bytes, or a token.
+ // Note: the Server class validates the token and checks the length.
+ self.packet_type == PacketType::Initial
+ && (self.dcid().len() >= 8 || !self.token.is_empty())
+ }
+
+ pub fn packet_type(&self) -> PacketType {
+ self.packet_type
+ }
+
+ pub fn dcid(&self) -> &ConnectionIdRef<'a> {
+ &self.dcid
+ }
+
+ pub fn scid(&self) -> &ConnectionIdRef<'a> {
+ self.scid
+ .as_ref()
+ .expect("should only be called for long header packets")
+ }
+
+ pub fn token(&self) -> &'a [u8] {
+ self.token
+ }
+
+ pub fn version(&self) -> Option<QuicVersion> {
+ self.quic_version
+ }
+
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ fn decode_pn(expected: PacketNumber, pn: u64, w: usize) -> PacketNumber {
+ let window = 1_u64 << (w * 8);
+ let candidate = (expected & !(window - 1)) | pn;
+ if candidate + (window / 2) <= expected {
+ candidate + window
+ } else if candidate > expected + (window / 2) {
+ match candidate.checked_sub(window) {
+ Some(pn_sub) => pn_sub,
+ None => candidate,
+ }
+ } else {
+ candidate
+ }
+ }
+
+ /// Decrypt the header of the packet.
+ fn decrypt_header(
+ &self,
+ crypto: &mut CryptoDxState,
+ ) -> Res<(bool, PacketNumber, Vec<u8>, &'a [u8])> {
+ assert_ne!(self.packet_type, PacketType::Retry);
+ assert_ne!(self.packet_type, PacketType::VersionNegotiation);
+
+ qtrace!(
+ "unmask hdr={}",
+ hex(&self.data[..self.header_len + SAMPLE_OFFSET])
+ );
+
+ let sample_offset = self.header_len + SAMPLE_OFFSET;
+ let mask = if let Some(sample) = self.data.get(sample_offset..(sample_offset + SAMPLE_SIZE))
+ {
+ crypto.compute_mask(sample)
+ } else {
+ Err(Error::NoMoreData)
+ }?;
+
+ // Un-mask the leading byte.
+ let bits = if self.packet_type == PacketType::Short {
+ PACKET_HP_MASK_SHORT
+ } else {
+ PACKET_HP_MASK_LONG
+ };
+ let first_byte = self.data[0] ^ (mask[0] & bits);
+
+ // Make a copy of the header to work on.
+ let mut hdrbytes = self.data[..self.header_len + 4].to_vec();
+ hdrbytes[0] = first_byte;
+
+ // Unmask the PN.
+ let mut pn_encoded: u64 = 0;
+ for i in 0..MAX_PACKET_NUMBER_LEN {
+ hdrbytes[self.header_len + i] ^= mask[1 + i];
+ pn_encoded <<= 8;
+ pn_encoded += u64::from(hdrbytes[self.header_len + i]);
+ }
+
+ // Now decode the packet number length and apply it, hopefully in constant time.
+ let pn_len = usize::from((first_byte & 0x3) + 1);
+ hdrbytes.truncate(self.header_len + pn_len);
+ pn_encoded >>= 8 * (MAX_PACKET_NUMBER_LEN - pn_len);
+
+ qtrace!("unmasked hdr={}", hex(&hdrbytes));
+
+ let key_phase = self.packet_type == PacketType::Short
+ && (first_byte & PACKET_BIT_KEY_PHASE) == PACKET_BIT_KEY_PHASE;
+ let pn = Self::decode_pn(crypto.next_pn(), pn_encoded, pn_len);
+ Ok((
+ key_phase,
+ pn,
+ hdrbytes,
+ &self.data[self.header_len + pn_len..],
+ ))
+ }
+
+ pub fn decrypt(&self, crypto: &mut CryptoStates, release_at: Instant) -> Res<DecryptedPacket> {
+ let cspace: CryptoSpace = self.packet_type.into();
+ // This has to work in two stages because we need to remove header protection
+ // before picking the keys to use.
+ if let Some(rx) = crypto.rx_hp(cspace) {
+ // Note that this will dump early, which creates a side-channel.
+ // This is OK in this case because we the only reason this can
+ // fail is if the cryptographic module is bad or the packet is
+ // too small (which is public information).
+ let (key_phase, pn, header, body) = self.decrypt_header(rx)?;
+ qtrace!([rx], "decoded header: {:?}", header);
+ let rx = crypto.rx(cspace, key_phase).unwrap();
+ let d = rx.decrypt(pn, &header, body)?;
+ // If this is the first packet ever successfully decrypted
+ // using `rx`, make sure to initiate a key update.
+ if rx.needs_update() {
+ crypto.key_update_received(release_at)?;
+ }
+ crypto.check_pn_overlap()?;
+ Ok(DecryptedPacket {
+ pt: self.packet_type,
+ pn,
+ data: d,
+ })
+ } else if crypto.rx_pending(cspace) {
+ Err(Error::KeysPending(cspace))
+ } else {
+ qtrace!("keys for {:?} already discarded", cspace);
+ Err(Error::KeysDiscarded)
+ }
+ }
+
+ pub fn supported_versions(&self) -> Res<Vec<Version>> {
+ assert_eq!(self.packet_type, PacketType::VersionNegotiation);
+ let mut decoder = Decoder::new(&self.data[self.header_len..]);
+ let mut res = Vec::new();
+ while decoder.remaining() > 0 {
+ let version = Version::try_from(Self::opt(decoder.decode_uint(4))?)?;
+ res.push(version);
+ }
+ Ok(res)
+ }
+}
+
+impl fmt::Debug for PublicPacket<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{:?}: {} {}",
+ self.packet_type(),
+ hex_with_len(&self.data[..self.header_len]),
+ hex_with_len(&self.data[self.header_len..])
+ )
+ }
+}
+
+pub struct DecryptedPacket {
+ pt: PacketType,
+ pn: PacketNumber,
+ data: Vec<u8>,
+}
+
+impl DecryptedPacket {
+ pub fn packet_type(&self) -> PacketType {
+ self.pt
+ }
+
+ pub fn pn(&self) -> PacketNumber {
+ self.pn
+ }
+}
+
+impl Deref for DecryptedPacket {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.data[..]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::crypto::{CryptoDxState, CryptoStates};
+ use crate::{FixedConnectionIdManager, QuicVersion};
+ use neqo_common::Encoder;
+ use test_fixture::{fixture_init, now};
+
+ const CLIENT_CID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
+ const SERVER_CID: &[u8] = &[0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5];
+
+ /// This is a connection ID manager, which is only used for decoding short header packets.
+ fn cid_mgr() -> FixedConnectionIdManager {
+ FixedConnectionIdManager::new(SERVER_CID.len())
+ }
+
+ const SAMPLE_INITIAL_PAYLOAD: &[u8] = &[
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03, 0x03,
+ 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, 0x25, 0xdd,
+ 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, 0x0b, 0x9a, 0x04,
+ 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00,
+ 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0, 0x8a, 0x60, 0x99, 0x3c, 0x14,
+ 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83, 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b,
+ 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04,
+ ];
+ const SAMPLE_INITIAL: &[u8] = &[
+ 0xc7, 0xff, 0x00, 0x00, 0x1d, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x00, 0x40, 0x75, 0xfb, 0x12, 0xff, 0x07, 0x82, 0x3a, 0x5d, 0x24, 0x53, 0x4d, 0x90, 0x6c,
+ 0xe4, 0xc7, 0x67, 0x82, 0xa2, 0x16, 0x7e, 0x34, 0x79, 0xc0, 0xf7, 0xf6, 0x39, 0x5d, 0xc2,
+ 0xc9, 0x16, 0x76, 0x30, 0x2f, 0xe6, 0xd7, 0x0b, 0xb7, 0xcb, 0xeb, 0x11, 0x7b, 0x4d, 0xdb,
+ 0x7d, 0x17, 0x34, 0x98, 0x44, 0xfd, 0x61, 0xda, 0xe2, 0x00, 0xb8, 0x33, 0x8e, 0x1b, 0x93,
+ 0x29, 0x76, 0xb6, 0x1d, 0x91, 0xe6, 0x4a, 0x02, 0xe9, 0xe0, 0xee, 0x72, 0xe3, 0xa6, 0xf6,
+ 0x3a, 0xba, 0x4c, 0xee, 0xee, 0xc5, 0xbe, 0x2f, 0x24, 0xf2, 0xd8, 0x60, 0x27, 0x57, 0x29,
+ 0x43, 0x53, 0x38, 0x46, 0xca, 0xa1, 0x3e, 0x6f, 0x16, 0x3f, 0xb2, 0x57, 0x47, 0x3d, 0xcc,
+ 0xa2, 0x53, 0x96, 0xe8, 0x87, 0x24, 0xf1, 0xe5, 0xd9, 0x64, 0xde, 0xde, 0xe9, 0xb6, 0x33,
+ ];
+
+ #[test]
+ fn sample_server_initial() {
+ fixture_init();
+ let mut prot = CryptoDxState::test_default();
+
+ // The spec uses PN=1, but our crypto refuses to skip packet numbers.
+ // So burn an encryption:
+ let burn = prot.encrypt(0, &[], &[]).expect("burn OK");
+ assert_eq!(burn.len(), prot.expansion());
+
+ let mut builder = PacketBuilder::long(
+ Encoder::new(),
+ PacketType::Initial,
+ QuicVersion::default(),
+ &ConnectionId::from(&[][..]),
+ &ConnectionId::from(SERVER_CID),
+ );
+ builder.initial_token(&[]);
+ builder.pn(1, 2);
+ builder.encode(&SAMPLE_INITIAL_PAYLOAD);
+ let packet = builder.build(&mut prot).expect("build");
+ assert_eq!(&packet[..], SAMPLE_INITIAL);
+ }
+
+ #[test]
+ fn decrypt_initial() {
+ const EXTRA: &[u8] = &[0xce; 33];
+
+ fixture_init();
+ let mut padded = SAMPLE_INITIAL.to_vec();
+ padded.extend_from_slice(EXTRA);
+ let (packet, remainder) = PublicPacket::decode(&padded, &cid_mgr()).unwrap();
+ assert_eq!(packet.packet_type(), PacketType::Initial);
+ assert_eq!(&packet.dcid()[..], &[] as &[u8]);
+ assert_eq!(&packet.scid()[..], SERVER_CID);
+ assert!(packet.token().is_empty());
+ assert_eq!(remainder, EXTRA);
+
+ let decrypted = packet
+ .decrypt(&mut CryptoStates::test_default(), now())
+ .unwrap();
+ assert_eq!(decrypted.pn(), 1);
+ }
+
+ #[test]
+ fn disallow_long_dcid() {
+ let mut enc = Encoder::new();
+ enc.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC);
+ enc.encode_uint(4, QuicVersion::default().as_u32());
+ enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 1]);
+ enc.encode_vec(1, &[]);
+ enc.encode(&[0xff; 40]); // junk
+
+ assert!(PublicPacket::decode(&enc, &cid_mgr()).is_err());
+ }
+
+ #[test]
+ fn disallow_long_scid() {
+ let mut enc = Encoder::new();
+ enc.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC);
+ enc.encode_uint(4, QuicVersion::default().as_u32());
+ enc.encode_vec(1, &[]);
+ enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 2]);
+ enc.encode(&[0xff; 40]); // junk
+
+ assert!(PublicPacket::decode(&enc, &cid_mgr()).is_err());
+ }
+
+ const SAMPLE_SHORT: &[u8] = &[
+ 0x55, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x99, 0x9c, 0xbd, 0x77, 0xf5, 0xd7,
+ 0x0a, 0x28, 0xe8, 0xfb, 0xc3, 0xed, 0xf5, 0x71, 0xb1, 0x04, 0x32, 0x2a, 0xae, 0xae,
+ ];
+ const SAMPLE_SHORT_PAYLOAD: &[u8] = &[0; 3];
+
+ #[test]
+ fn build_short() {
+ fixture_init();
+ let mut builder =
+ PacketBuilder::short(Encoder::new(), true, &ConnectionId::from(SERVER_CID));
+ builder.pn(0, 1);
+ builder.encode(SAMPLE_SHORT_PAYLOAD); // Enough payload for sampling.
+ let packet = builder
+ .build(&mut CryptoDxState::test_default())
+ .expect("build");
+ assert_eq!(&packet[..], SAMPLE_SHORT);
+ }
+
+ #[test]
+ fn scramble_short() {
+ fixture_init();
+ let mut firsts = Vec::new();
+ for _ in 0..64 {
+ let mut builder =
+ PacketBuilder::short(Encoder::new(), true, &ConnectionId::from(SERVER_CID));
+ builder.scramble(true);
+ builder.pn(0, 1);
+ firsts.push(builder[0]);
+ }
+ let is_set = |bit| move |v| v & bit == bit;
+ // There should be at least one value with the QUIC bit set:
+ assert!(firsts.iter().any(is_set(PACKET_BIT_FIXED_QUIC)));
+ // ... but not all:
+ assert!(!firsts.iter().all(is_set(PACKET_BIT_FIXED_QUIC)));
+ // There should be at least one value with the spin bit set:
+ assert!(firsts.iter().any(is_set(PACKET_BIT_SPIN)));
+ // ... but not all:
+ assert!(!firsts.iter().all(is_set(PACKET_BIT_SPIN)));
+ }
+
+ #[test]
+ fn decode_short() {
+ fixture_init();
+ let (packet, remainder) = PublicPacket::decode(SAMPLE_SHORT, &cid_mgr()).unwrap();
+ assert_eq!(packet.packet_type(), PacketType::Short);
+ assert!(remainder.is_empty());
+ let decrypted = packet
+ .decrypt(&mut CryptoStates::test_default(), now())
+ .unwrap();
+ assert_eq!(&decrypted[..], SAMPLE_SHORT_PAYLOAD);
+ }
+
+ /// By telling the decoder that the connection ID is shorter than it really is, we get a decryption error.
+ #[test]
+ fn decode_short_bad_cid() {
+ fixture_init();
+ let (packet, remainder) = PublicPacket::decode(
+ SAMPLE_SHORT,
+ &FixedConnectionIdManager::new(SERVER_CID.len() - 1),
+ )
+ .unwrap();
+ assert_eq!(packet.packet_type(), PacketType::Short);
+ assert!(remainder.is_empty());
+ assert!(packet
+ .decrypt(&mut CryptoStates::test_default(), now())
+ .is_err());
+ }
+
+ /// Saying that the connection ID is longer causes the initial decode to fail.
+ #[test]
+ fn decode_short_long_cid() {
+ assert!(PublicPacket::decode(
+ SAMPLE_SHORT,
+ &FixedConnectionIdManager::new(SERVER_CID.len() + 1)
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn build_two() {
+ fixture_init();
+ let mut prot = CryptoDxState::test_default();
+ let mut builder = PacketBuilder::long(
+ Encoder::new(),
+ PacketType::Handshake,
+ QuicVersion::default(),
+ &ConnectionId::from(SERVER_CID),
+ &ConnectionId::from(CLIENT_CID),
+ );
+ builder.pn(0, 1);
+ builder.encode(&[0; 3]);
+ let encoder = builder.build(&mut prot).expect("build");
+ assert_eq!(encoder.len(), 45);
+ let first = encoder.clone();
+
+ let mut builder = PacketBuilder::short(encoder, false, &ConnectionId::from(SERVER_CID));
+ builder.pn(1, 3);
+ builder.encode(&[0]); // Minimal size (packet number is big enough).
+ let encoder = builder.build(&mut prot).expect("build");
+ assert_eq!(
+ &first[..],
+ &encoder[..first.len()],
+ "the first packet should be a prefix"
+ );
+ assert_eq!(encoder.len(), 45 + 29);
+ }
+
+ #[test]
+ fn build_long() {
+ const EXPECTED: &[u8] = &[
+ 0xe5, 0xff, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x40, 0x14, 0xa8, 0x9d, 0xbf, 0x74, 0x70,
+ 0x32, 0xda, 0xba, 0xfb, 0x87, 0x61, 0xb8, 0x31, 0x90, 0xf3, 0x25, 0x52, 0x0b, 0xbe,
+ 0xdb,
+ ];
+
+ fixture_init();
+ let mut builder = PacketBuilder::long(
+ Encoder::new(),
+ PacketType::Handshake,
+ QuicVersion::default(),
+ &ConnectionId::from(&[][..]),
+ &ConnectionId::from(&[][..]),
+ );
+ builder.pn(0, 1);
+ builder.encode(&[1, 2, 3]);
+ let packet = builder.build(&mut CryptoDxState::test_default()).unwrap();
+ assert_eq!(&packet[..], EXPECTED);
+ }
+
+ #[test]
+ fn scramble_long() {
+ fixture_init();
+ let mut found_unset = false;
+ let mut found_set = false;
+ for _ in 1..64 {
+ let mut builder = PacketBuilder::long(
+ Encoder::new(),
+ PacketType::Handshake,
+ QuicVersion::default(),
+ &ConnectionId::from(&[][..]),
+ &ConnectionId::from(&[][..]),
+ );
+ builder.pn(0, 1);
+ builder.scramble(true);
+ if (builder[0] & PACKET_BIT_FIXED_QUIC) == 0 {
+ found_unset = true;
+ } else {
+ found_set = true;
+ }
+ }
+ assert!(found_unset);
+ assert!(found_set);
+ }
+
+ #[test]
+ fn build_abort() {
+ let mut builder = PacketBuilder::long(
+ Encoder::new(),
+ PacketType::Initial,
+ QuicVersion::default(),
+ &ConnectionId::from(&[][..]),
+ &ConnectionId::from(SERVER_CID),
+ );
+ builder.initial_token(&[]);
+ builder.pn(1, 2);
+ let encoder = builder.abort();
+ assert!(encoder.is_empty());
+ }
+
+ const SAMPLE_RETRY_27: &[u8] = &[
+ 0xff, 0xff, 0x00, 0x00, 0x1b, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xa5, 0x23, 0xcb, 0x5b, 0xa5, 0x24, 0x69, 0x5f, 0x65, 0x69,
+ 0xf2, 0x93, 0xa1, 0x35, 0x9d, 0x8e,
+ ];
+
+ const SAMPLE_RETRY_28: &[u8] = &[
+ 0xff, 0xff, 0x00, 0x00, 0x1c, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xf7, 0x1a, 0x5f, 0x12, 0xaf, 0xe3, 0xec, 0xf8, 0x00, 0x1a,
+ 0x92, 0x0e, 0x6f, 0xdf, 0x1d, 0x63,
+ ];
+
+ const SAMPLE_RETRY_29: &[u8] = &[
+ 0xff, 0xff, 0x00, 0x00, 0x1d, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xd1, 0x69, 0x26, 0xd8, 0x1f, 0x6f, 0x9c, 0xa2, 0x95, 0x3a,
+ 0x8a, 0xa4, 0x57, 0x5e, 0x1e, 0x49,
+ ];
+
+ const SAMPLE_RETRY_30: &[u8] = &[
+ 0xff, 0xff, 0x00, 0x00, 0x1e, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2d, 0x3e, 0x04, 0x5d, 0x6d, 0x39, 0x20, 0x67, 0x89, 0x94,
+ 0x37, 0x10, 0x8c, 0xe0, 0x0a, 0x61,
+ ];
+
+ const SAMPLE_RETRY_31: &[u8] = &[
+ 0xff, 0xff, 0x00, 0x00, 0x1f, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xc7, 0x0c, 0xe5, 0xde, 0x43, 0x0b, 0x4b, 0xdb, 0x7d, 0xf1,
+ 0xa3, 0x83, 0x3a, 0x75, 0xf9, 0x86,
+ ];
+
+ const SAMPLE_RETRY_32: &[u8] = &[
+ 0xff, 0xff, 0x00, 0x00, 0x20, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x59, 0x75, 0x65, 0x19, 0xdd, 0x6c, 0xc8, 0x5b, 0xd9, 0x0e,
+ 0x33, 0xa9, 0x34, 0xd2, 0xff, 0x85,
+ ];
+
+ const RETRY_TOKEN: &[u8] = b"token";
+
+ fn build_retry_single(quic_version: QuicVersion, sample_retry: &[u8]) {
+ fixture_init();
+ let retry =
+ PacketBuilder::retry(quic_version, &[], SERVER_CID, RETRY_TOKEN, CLIENT_CID).unwrap();
+
+ let (packet, remainder) = PublicPacket::decode(&retry, &cid_mgr()).unwrap();
+ assert!(packet.is_valid_retry(&ConnectionId::from(CLIENT_CID)));
+ assert!(remainder.is_empty());
+
+ // The builder adds randomness, which makes expectations hard.
+ // So only do a full check when that randomness matches up.
+ if retry[0] == sample_retry[0] {
+ assert_eq!(&retry, &sample_retry);
+ } else {
+ // Otherwise, just check that the header is OK.
+ assert_eq!(retry[0] & 0xf0, 0xf0);
+ let header_range = 1..retry.len() - 16;
+ assert_eq!(&retry[header_range.clone()], &sample_retry[header_range]);
+ }
+ }
+
+ #[test]
+ fn build_retry_27() {
+ build_retry_single(QuicVersion::Draft27, SAMPLE_RETRY_27);
+ }
+
+ #[test]
+ fn build_retry_28() {
+ build_retry_single(QuicVersion::Draft28, SAMPLE_RETRY_28);
+ }
+
+ #[test]
+ fn build_retry_29() {
+ build_retry_single(QuicVersion::Draft29, SAMPLE_RETRY_29);
+ }
+
+ #[test]
+ fn build_retry_30() {
+ build_retry_single(QuicVersion::Draft30, SAMPLE_RETRY_30);
+ }
+
+ #[test]
+ fn build_retry_31() {
+ build_retry_single(QuicVersion::Draft31, SAMPLE_RETRY_31);
+ }
+
+ #[test]
+ fn build_retry_32() {
+ build_retry_single(QuicVersion::Draft32, SAMPLE_RETRY_32);
+ }
+
+ #[test]
+ fn build_retry_multiple() {
+ // Run the build_retry test a few times.
+ // Odds are approximately 1 in 8 that the full comparison doesn't happen
+ // for a given version.
+ for _ in 0..32 {
+ build_retry_27();
+ build_retry_28();
+ build_retry_29();
+ build_retry_30();
+ }
+ }
+
+ fn decode_retry(quic_version: QuicVersion, sample_retry: &[u8]) {
+ fixture_init();
+ let (packet, remainder) =
+ PublicPacket::decode(sample_retry, &FixedConnectionIdManager::new(5)).unwrap();
+ assert!(packet.is_valid_retry(&ConnectionId::from(CLIENT_CID)));
+ assert_eq!(Some(quic_version), packet.quic_version);
+ assert!(packet.dcid().is_empty());
+ assert_eq!(&packet.scid()[..], SERVER_CID);
+ assert_eq!(packet.token(), RETRY_TOKEN);
+ assert!(remainder.is_empty());
+ }
+
+ #[test]
+ fn decode_retry_27() {
+ decode_retry(QuicVersion::Draft27, SAMPLE_RETRY_27);
+ }
+
+ #[test]
+ fn decode_retry_28() {
+ decode_retry(QuicVersion::Draft28, SAMPLE_RETRY_28);
+ }
+
+ #[test]
+ fn decode_retry_29() {
+ decode_retry(QuicVersion::Draft29, SAMPLE_RETRY_29);
+ }
+
+ #[test]
+ fn decode_retry_30() {
+ decode_retry(QuicVersion::Draft30, SAMPLE_RETRY_30);
+ }
+
+ #[test]
+ fn decode_retry_31() {
+ decode_retry(QuicVersion::Draft31, SAMPLE_RETRY_31);
+ }
+
+ #[test]
+ fn decode_retry_32() {
+ decode_retry(QuicVersion::Draft32, SAMPLE_RETRY_32);
+ }
+
+ /// Check some packets that are clearly not valid Retry packets.
+ #[test]
+ fn invalid_retry() {
+ fixture_init();
+ let cid_mgr = FixedConnectionIdManager::new(5);
+ let odcid = ConnectionId::from(CLIENT_CID);
+
+ assert!(PublicPacket::decode(&[], &cid_mgr).is_err());
+
+ let (packet, remainder) = PublicPacket::decode(SAMPLE_RETRY_28, &cid_mgr).unwrap();
+ assert!(remainder.is_empty());
+ assert!(packet.is_valid_retry(&odcid));
+
+ let mut damaged_retry = SAMPLE_RETRY_28.to_vec();
+ let last = damaged_retry.len() - 1;
+ damaged_retry[last] ^= 66;
+ let (packet, remainder) = PublicPacket::decode(&damaged_retry, &cid_mgr).unwrap();
+ assert!(remainder.is_empty());
+ assert!(!packet.is_valid_retry(&odcid));
+
+ damaged_retry.truncate(last);
+ let (packet, remainder) = PublicPacket::decode(&damaged_retry, &cid_mgr).unwrap();
+ assert!(remainder.is_empty());
+ assert!(!packet.is_valid_retry(&odcid));
+
+ // An invalid token should be rejected sooner.
+ damaged_retry.truncate(last - 4);
+ assert!(PublicPacket::decode(&damaged_retry, &cid_mgr).is_err());
+
+ damaged_retry.truncate(last - 1);
+ assert!(PublicPacket::decode(&damaged_retry, &cid_mgr).is_err());
+ }
+
+ const SAMPLE_VN: &[u8] = &[
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x08,
+ 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0xff, 0x00, 0x00, 0x1b, 0xff, 0x00, 0x00,
+ 0x1c, 0xff, 0x00, 0x00, 0x1d, 0xff, 0x00, 0x00, 0x1e, 0xff, 0x00, 0x00, 0x1f, 0xff, 0x00,
+ 0x00, 0x20, 0x0a, 0x0a, 0x0a, 0x0a,
+ ];
+
+ #[test]
+ fn build_vn() {
+ fixture_init();
+ let mut vn = PacketBuilder::version_negotiation(SERVER_CID, CLIENT_CID);
+ // Erase randomness from greasing...
+ assert_eq!(vn.len(), SAMPLE_VN.len());
+ vn[0] &= 0x80;
+ for v in vn.iter_mut().skip(SAMPLE_VN.len() - 4) {
+ *v &= 0x0f;
+ }
+ assert_eq!(&vn, &SAMPLE_VN);
+ }
+
+ #[test]
+ fn parse_vn() {
+ let (packet, remainder) =
+ PublicPacket::decode(SAMPLE_VN, &FixedConnectionIdManager::new(5)).unwrap();
+ assert!(remainder.is_empty());
+ assert_eq!(&packet.dcid[..], SERVER_CID);
+ assert!(packet.scid.is_some());
+ assert_eq!(&packet.scid.unwrap()[..], CLIENT_CID);
+ }
+
+ /// A Version Negotiation packet can have a long connection ID.
+ #[test]
+ fn parse_vn_big_cid() {
+ const BIG_DCID: &[u8] = &[0x44; MAX_CONNECTION_ID_LEN + 1];
+ const BIG_SCID: &[u8] = &[0xee; 255];
+
+ let mut enc = Encoder::from(&[0xff, 0x00, 0x00, 0x00, 0x00][..]);
+ enc.encode_vec(1, BIG_DCID);
+ enc.encode_vec(1, BIG_SCID);
+ enc.encode_uint(4, 0x1a2a_3a4a_u64);
+ enc.encode_uint(4, QuicVersion::default().as_u32());
+ enc.encode_uint(4, 0x5a6a_7a8a_u64);
+
+ let (packet, remainder) =
+ PublicPacket::decode(&enc, &FixedConnectionIdManager::new(5)).unwrap();
+ assert!(remainder.is_empty());
+ assert_eq!(&packet.dcid[..], BIG_DCID);
+ assert!(packet.scid.is_some());
+ assert_eq!(&packet.scid.unwrap()[..], BIG_SCID);
+ }
+
+ #[test]
+ fn decode_pn() {
+ // When the expected value is low, the value doesn't go negative.
+ assert_eq!(PublicPacket::decode_pn(0, 0, 1), 0);
+ assert_eq!(PublicPacket::decode_pn(0, 0xff, 1), 0xff);
+ assert_eq!(PublicPacket::decode_pn(10, 0, 1), 0);
+ assert_eq!(PublicPacket::decode_pn(0x7f, 0, 1), 0);
+ assert_eq!(PublicPacket::decode_pn(0x80, 0, 1), 0x100);
+ assert_eq!(PublicPacket::decode_pn(0x80, 2, 1), 2);
+ assert_eq!(PublicPacket::decode_pn(0x80, 0xff, 1), 0xff);
+ assert_eq!(PublicPacket::decode_pn(0x7ff, 0xfe, 1), 0x7fe);
+
+ // This is invalid by spec, as we are expected to check for overflow around 2^62-1,
+ // but we don't need to worry about overflow
+ // and hitting this is basically impossible in practice.
+ assert_eq!(
+ PublicPacket::decode_pn(0x3fff_ffff_ffff_ffff, 2, 4),
+ 0x4000_0000_0000_0002
+ );
+ }
+
+ #[test]
+ fn chacha20_sample() {
+ const PACKET: &[u8] = &[
+ 0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57,
+ 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb,
+ ];
+ fixture_init();
+ let (packet, slice) =
+ PublicPacket::decode(PACKET, &FixedConnectionIdManager::new(0)).unwrap();
+ assert!(slice.is_empty());
+ let decrypted = packet
+ .decrypt(&mut CryptoStates::test_chacha(), now())
+ .unwrap();
+ assert_eq!(decrypted.packet_type(), PacketType::Short);
+ assert_eq!(decrypted.pn(), 654_360_564);
+ assert_eq!(&decrypted[..], &[0x01]);
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/packet/retry.rs b/third_party/rust/neqo-transport/src/packet/retry.rs
new file mode 100644
index 0000000000..596714aa6d
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/packet/retry.rs
@@ -0,0 +1,63 @@
+// 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.
+
+#![deny(clippy::pedantic)]
+
+use crate::packet::QuicVersion;
+use crate::{Error, Res};
+
+use neqo_common::qerror;
+use neqo_crypto::{aead::Aead, hkdf, TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3};
+
+use std::cell::RefCell;
+
+const RETRY_SECRET_27: &[u8] = &[
+ 0x65, 0x6e, 0x61, 0xe3, 0x36, 0xae, 0x94, 0x17, 0xf7, 0xf0, 0xed, 0xd8, 0xd7, 0x8d, 0x46, 0x1e,
+ 0x2a, 0xa7, 0x08, 0x4a, 0xba, 0x7a, 0x14, 0xc1, 0xe9, 0xf7, 0x26, 0xd5, 0x57, 0x09, 0x16, 0x9a,
+];
+const RETRY_SECRET_29: &[u8] = &[
+ 0x8b, 0x0d, 0x37, 0xeb, 0x85, 0x35, 0x02, 0x2e, 0xbc, 0x8d, 0x76, 0xa2, 0x07, 0xd8, 0x0d, 0xf2,
+ 0x26, 0x46, 0xec, 0x06, 0xdc, 0x80, 0x96, 0x42, 0xc3, 0x0a, 0x8b, 0xaa, 0x2b, 0xaa, 0xff, 0x4c,
+];
+
+/// The AEAD used for Retry is fixed, so use thread local storage.
+fn make_aead(secret: &[u8]) -> Aead {
+ #[cfg(debug_assertions)]
+ ::neqo_crypto::assert_initialized();
+
+ let secret = hkdf::import_key(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, secret).unwrap();
+ Aead::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic ").unwrap()
+}
+thread_local!(static RETRY_AEAD_27: RefCell<Aead> = RefCell::new(make_aead(RETRY_SECRET_27)));
+thread_local!(static RETRY_AEAD_29: RefCell<Aead> = RefCell::new(make_aead(RETRY_SECRET_29)));
+
+/// Run a function with the appropriate Retry AEAD.
+pub fn use_aead<F, T>(quic_version: QuicVersion, f: F) -> Res<T>
+where
+ F: FnOnce(&Aead) -> Res<T>,
+{
+ match quic_version {
+ QuicVersion::Draft27 | QuicVersion::Draft28 => &RETRY_AEAD_27,
+ QuicVersion::Draft29
+ | QuicVersion::Draft30
+ | QuicVersion::Draft31
+ | QuicVersion::Draft32 => &RETRY_AEAD_29,
+ }
+ .try_with(|aead| f(&aead.borrow()))
+ .map_err(|e| {
+ qerror!("Unable to access Retry AEAD: {:?}", e);
+ Error::InternalError
+ })?
+}
+
+/// Determine how large the expansion is for a given key.
+pub fn expansion(quic_version: QuicVersion) -> usize {
+ if let Ok(ex) = use_aead(quic_version, |aead| Ok(aead.expansion())) {
+ ex
+ } else {
+ panic!("Unable to access Retry AEAD")
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/path.rs b/third_party/rust/neqo-transport/src/path.rs
new file mode 100644
index 0000000000..a4e6b2f361
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/path.rs
@@ -0,0 +1,109 @@
+// 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::net::SocketAddr;
+
+use crate::cid::{ConnectionId, ConnectionIdRef};
+
+use neqo_common::Datagram;
+
+/// This is the MTU that we assume when using IPv6.
+/// We use this size for Initial packets, so we don't need to worry about probing for support.
+/// If the path doesn't support this MTU, we will assume that it doesn't support QUIC.
+///
+/// This is a multiple of 16 greater than the largest possible short header (1 + 20 + 4).
+pub const PATH_MTU_V6: usize = 1337;
+/// The path MTU for IPv4 can be 20 bytes larger than for v6.
+pub const PATH_MTU_V4: usize = PATH_MTU_V6 + 20;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Path {
+ local: SocketAddr,
+ remote: SocketAddr,
+ local_cids: Vec<ConnectionId>,
+ remote_cid: ConnectionId,
+ reset_token: Option<[u8; 16]>,
+}
+
+impl Path {
+ /// Create a path from addresses and connection IDs.
+ pub fn new(
+ local: SocketAddr,
+ remote: SocketAddr,
+ local_cid: ConnectionId,
+ remote_cid: ConnectionId,
+ ) -> Self {
+ Self {
+ local,
+ remote,
+ local_cids: vec![local_cid],
+ remote_cid,
+ reset_token: None,
+ }
+ }
+
+ pub fn received_on(&self, d: &Datagram) -> bool {
+ self.local == d.destination() && self.remote == d.source()
+ }
+
+ pub fn mtu(&self) -> usize {
+ if self.local.is_ipv4() {
+ PATH_MTU_V4
+ } else {
+ PATH_MTU_V6 // IPv6
+ }
+ }
+
+ /// Add a connection ID to the local set.
+ pub fn add_local_cid(&mut self, cid: ConnectionId) {
+ self.local_cids.push(cid);
+ }
+
+ /// Determine if the given connection ID is valid.
+ pub fn valid_local_cid(&self, cid: &ConnectionIdRef) -> bool {
+ self.local_cids.iter().any(|c| c == cid)
+ }
+
+ /// Get the first local connection ID.
+ pub fn local_cid(&self) -> &ConnectionId {
+ self.local_cids.first().as_ref().unwrap()
+ }
+
+ /// Set the remote connection ID based on the peer's choice.
+ pub fn set_remote_cid(&mut self, cid: &ConnectionIdRef) {
+ self.remote_cid = ConnectionId::from(cid);
+ }
+
+ /// Access the remote connection ID.
+ pub fn remote_cid(&self) -> &ConnectionId {
+ &self.remote_cid
+ }
+
+ /// Set the stateless reset token for the connection ID that is currently in use.
+ pub fn set_reset_token(&mut self, token: [u8; 16]) {
+ self.reset_token = Some(token);
+ }
+
+ /// Access the reset token.
+ pub fn reset_token(&self) -> Option<&[u8; 16]> {
+ self.reset_token.as_ref()
+ }
+
+ /// Make a datagram.
+ pub fn datagram<V: Into<Vec<u8>>>(&self, payload: V) -> Datagram {
+ Datagram::new(self.local, self.remote, payload)
+ }
+
+ /// Get local address as `SocketAddr`
+ pub fn local_address(&self) -> SocketAddr {
+ self.local
+ }
+
+ /// Get remote address as `SocketAddr`
+ pub fn remote_address(&self) -> SocketAddr {
+ self.remote
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/qlog.rs b/third_party/rust/neqo-transport/src/qlog.rs
new file mode 100644
index 0000000000..b4cc1adf67
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/qlog.rs
@@ -0,0 +1,442 @@
+// 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.
+
+// Functions that handle capturing QLOG traces.
+
+use std::convert::TryFrom;
+use std::ops::RangeInclusive;
+use std::string::String;
+use std::time::Duration;
+
+use qlog::{self, event::Event, PacketHeader, QuicFrame};
+
+use neqo_common::{hex, qinfo, qlog::NeqoQlog, Decoder};
+
+use crate::connection::State;
+use crate::frame::{self, Frame};
+use crate::packet::{DecryptedPacket, PacketNumber, PacketType, PublicPacket};
+use crate::path::Path;
+use crate::tparams::{self, TransportParametersHandler};
+use crate::tracking::SentPacket;
+use crate::QuicVersion;
+
+pub fn connection_tparams_set(qlog: &mut NeqoQlog, tph: &TransportParametersHandler) {
+ qlog.add_event(|| {
+ let remote = tph.remote();
+ Some(Event::transport_parameters_set(
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ if let Some(ocid) = remote.get_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID) {
+ // Cannot use packet::ConnectionId's Display trait implementation
+ // because it does not include the 0x prefix.
+ Some(hex(ocid))
+ } else {
+ None
+ },
+ if let Some(srt) = remote.get_bytes(tparams::STATELESS_RESET_TOKEN) {
+ Some(hex(srt))
+ } else {
+ None
+ },
+ if remote.get_empty(tparams::DISABLE_MIGRATION) {
+ Some(true)
+ } else {
+ None
+ },
+ Some(remote.get_integer(tparams::IDLE_TIMEOUT)),
+ Some(remote.get_integer(tparams::MAX_UDP_PAYLOAD_SIZE)),
+ Some(remote.get_integer(tparams::ACK_DELAY_EXPONENT)),
+ Some(remote.get_integer(tparams::MAX_ACK_DELAY)),
+ // TODO(hawkinsw@obs.cr): We do not yet handle ACTIVE_CONNECTION_ID_LIMIT in tparams yet.
+ None,
+ Some(format!("{}", remote.get_integer(tparams::INITIAL_MAX_DATA))),
+ Some(format!(
+ "{}",
+ remote.get_integer(tparams::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL)
+ )),
+ Some(format!(
+ "{}",
+ remote.get_integer(tparams::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE)
+ )),
+ Some(format!(
+ "{}",
+ remote.get_integer(tparams::INITIAL_MAX_STREAM_DATA_UNI)
+ )),
+ Some(format!(
+ "{}",
+ remote.get_integer(tparams::INITIAL_MAX_STREAMS_BIDI)
+ )),
+ Some(format!(
+ "{}",
+ remote.get_integer(tparams::INITIAL_MAX_STREAMS_UNI)
+ )),
+ // TODO(hawkinsw@obs.cr): We do not yet handle PREFERRED_ADDRESS in tparams yet.
+ None,
+ ))
+ })
+}
+
+pub fn server_connection_started(qlog: &mut NeqoQlog, path: &Path) {
+ connection_started(qlog, path)
+}
+
+pub fn client_connection_started(qlog: &mut NeqoQlog, path: &Path) {
+ connection_started(qlog, path)
+}
+
+fn connection_started(qlog: &mut NeqoQlog, path: &Path) {
+ qlog.add_event(|| {
+ Some(Event::connection_started(
+ if path.local_address().ip().is_ipv4() {
+ "ipv4".into()
+ } else {
+ "ipv6".into()
+ },
+ format!("{}", path.local_address().ip()),
+ format!("{}", path.remote_address().ip()),
+ Some("QUIC".into()),
+ path.local_address().port().into(),
+ path.remote_address().port().into(),
+ Some(format!("{:x}", QuicVersion::default().as_u32())),
+ Some(format!("{}", path.local_cid())),
+ Some(format!("{}", path.remote_cid())),
+ ))
+ })
+}
+
+pub fn connection_state_updated(qlog: &mut NeqoQlog, new: &State) {
+ qlog.add_event(|| {
+ Some(Event::connection_state_updated_min(match new {
+ State::Init => qlog::ConnectionState::Attempted,
+ State::WaitInitial => qlog::ConnectionState::Attempted,
+ State::Handshaking => qlog::ConnectionState::Handshake,
+ State::Connected => qlog::ConnectionState::Active,
+ State::Confirmed => qlog::ConnectionState::Active,
+ State::Closing { .. } => qlog::ConnectionState::Draining,
+ State::Draining { .. } => qlog::ConnectionState::Draining,
+ State::Closed { .. } => qlog::ConnectionState::Closed,
+ }))
+ })
+}
+
+pub fn packet_sent(
+ qlog: &mut NeqoQlog,
+ pt: PacketType,
+ pn: PacketNumber,
+ plen: usize,
+ body: &[u8],
+) {
+ qlog.add_event_with_stream(|stream| {
+ let mut d = Decoder::from(body);
+
+ stream.add_event(Event::packet_sent_min(
+ to_qlog_pkt_type(pt),
+ PacketHeader::new(
+ pn,
+ Some(u64::try_from(plen).unwrap()),
+ None,
+ None,
+ None,
+ None,
+ ),
+ Some(Vec::new()),
+ ))?;
+
+ while d.remaining() > 0 {
+ match Frame::decode(&mut d) {
+ Ok(f) => {
+ stream.add_frame(frame_to_qlogframe(&f), false)?;
+ }
+ Err(_) => {
+ qinfo!("qlog: invalid frame");
+ break;
+ }
+ }
+ }
+
+ stream.finish_frames()
+ })
+}
+
+pub fn packet_dropped(qlog: &mut NeqoQlog, payload: &PublicPacket) {
+ qlog.add_event(|| {
+ Some(Event::packet_dropped(
+ Some(to_qlog_pkt_type(payload.packet_type())),
+ Some(u64::try_from(payload.len()).unwrap()),
+ None,
+ ))
+ })
+}
+
+pub fn packets_lost(qlog: &mut NeqoQlog, pkts: &[SentPacket]) {
+ qlog.add_event_with_stream(|stream| {
+ for pkt in pkts {
+ stream.add_event(Event::packet_lost_min(
+ to_qlog_pkt_type(pkt.pt),
+ pkt.pn.to_string(),
+ Vec::new(),
+ ))?;
+
+ stream.finish_frames()?;
+ }
+ Ok(())
+ })
+}
+
+pub fn packet_received(
+ qlog: &mut NeqoQlog,
+ public_packet: &PublicPacket,
+ payload: &DecryptedPacket,
+) {
+ qlog.add_event_with_stream(|stream| {
+ let mut d = Decoder::from(&payload[..]);
+
+ stream.add_event(Event::packet_received(
+ to_qlog_pkt_type(payload.packet_type()),
+ PacketHeader::new(
+ payload.pn(),
+ Some(u64::try_from(public_packet.len()).unwrap()),
+ None,
+ None,
+ None,
+ None,
+ ),
+ Some(Vec::new()),
+ None,
+ None,
+ None,
+ ))?;
+
+ while d.remaining() > 0 {
+ match Frame::decode(&mut d) {
+ Ok(f) => stream.add_frame(frame_to_qlogframe(&f), false)?,
+ Err(_) => {
+ qinfo!("qlog: invalid frame");
+ break;
+ }
+ }
+ }
+
+ stream.finish_frames()
+ })
+}
+
+#[allow(dead_code)]
+pub enum QlogMetric {
+ MinRtt(Duration),
+ SmoothedRtt(Duration),
+ LatestRtt(Duration),
+ RttVariance(u64),
+ MaxAckDelay(u64),
+ PtoCount(usize),
+ CongestionWindow(usize),
+ BytesInFlight(usize),
+ SsThresh(usize),
+ PacketsInFlight(u64),
+ InRecovery(bool),
+ PacingRate(u64),
+}
+
+pub fn metrics_updated(qlog: &mut NeqoQlog, updated_metrics: &[QlogMetric]) {
+ debug_assert!(!updated_metrics.is_empty());
+
+ qlog.add_event(|| {
+ let mut min_rtt: Option<u64> = None;
+ let mut smoothed_rtt: Option<u64> = None;
+ let mut latest_rtt: Option<u64> = None;
+ let mut rtt_variance: Option<u64> = None;
+ let mut max_ack_delay: Option<u64> = None;
+ let mut pto_count: Option<u64> = None;
+ let mut congestion_window: Option<u64> = None;
+ let mut bytes_in_flight: Option<u64> = None;
+ let mut ssthresh: Option<u64> = None;
+ let mut packets_in_flight: Option<u64> = None;
+ let mut in_recovery: Option<bool> = None;
+ let mut pacing_rate: Option<u64> = None;
+
+ for metric in updated_metrics {
+ match metric {
+ QlogMetric::MinRtt(v) => min_rtt = Some(u64::try_from(v.as_millis()).unwrap()),
+ QlogMetric::SmoothedRtt(v) => {
+ smoothed_rtt = Some(u64::try_from(v.as_millis()).unwrap())
+ }
+ QlogMetric::LatestRtt(v) => {
+ latest_rtt = Some(u64::try_from(v.as_millis()).unwrap())
+ }
+ QlogMetric::RttVariance(v) => rtt_variance = Some(*v),
+ QlogMetric::MaxAckDelay(v) => max_ack_delay = Some(*v),
+ QlogMetric::PtoCount(v) => pto_count = Some(u64::try_from(*v).unwrap()),
+ QlogMetric::CongestionWindow(v) => {
+ congestion_window = Some(u64::try_from(*v).unwrap())
+ }
+ QlogMetric::BytesInFlight(v) => bytes_in_flight = Some(u64::try_from(*v).unwrap()),
+ QlogMetric::SsThresh(v) => ssthresh = Some(u64::try_from(*v).unwrap()),
+ QlogMetric::PacketsInFlight(v) => packets_in_flight = Some(*v),
+ QlogMetric::InRecovery(v) => in_recovery = Some(*v),
+ QlogMetric::PacingRate(v) => pacing_rate = Some(*v),
+ }
+ }
+
+ Some(Event::metrics_updated(
+ min_rtt,
+ smoothed_rtt,
+ latest_rtt,
+ rtt_variance,
+ max_ack_delay,
+ pto_count,
+ congestion_window,
+ bytes_in_flight,
+ ssthresh,
+ packets_in_flight,
+ in_recovery,
+ pacing_rate,
+ ))
+ })
+}
+
+// Helper functions
+
+fn frame_to_qlogframe(frame: &Frame) -> QuicFrame {
+ match frame {
+ Frame::Padding => QuicFrame::padding(),
+ Frame::Ping => QuicFrame::ping(),
+ Frame::Ack {
+ largest_acknowledged,
+ ack_delay,
+ first_ack_range,
+ ack_ranges,
+ } => {
+ let ranges =
+ Frame::decode_ack_frame(*largest_acknowledged, *first_ack_range, ack_ranges).ok();
+
+ QuicFrame::ack(
+ Some(ack_delay.to_string()),
+ ranges.map(|all| {
+ all.into_iter()
+ .map(RangeInclusive::into_inner)
+ .collect::<Vec<_>>()
+ }),
+ None,
+ None,
+ None,
+ )
+ }
+ Frame::ResetStream {
+ stream_id,
+ application_error_code,
+ final_size,
+ } => QuicFrame::reset_stream(
+ stream_id.as_u64().to_string(),
+ *application_error_code,
+ final_size.to_string(),
+ ),
+ Frame::StopSending {
+ stream_id,
+ application_error_code,
+ } => QuicFrame::stop_sending(stream_id.as_u64().to_string(), *application_error_code),
+ Frame::Crypto { offset, data } => {
+ QuicFrame::crypto(offset.to_string(), data.len().to_string())
+ }
+ Frame::NewToken { token } => QuicFrame::new_token(token.len().to_string(), hex(&token)),
+ Frame::Stream {
+ fin,
+ stream_id,
+ offset,
+ data,
+ ..
+ } => QuicFrame::stream(
+ stream_id.as_u64().to_string(),
+ offset.to_string(),
+ data.len().to_string(),
+ *fin,
+ None,
+ ),
+ Frame::MaxData { maximum_data } => QuicFrame::max_data(maximum_data.to_string()),
+ Frame::MaxStreamData {
+ stream_id,
+ maximum_stream_data,
+ } => QuicFrame::max_stream_data(
+ stream_id.as_u64().to_string(),
+ maximum_stream_data.to_string(),
+ ),
+ Frame::MaxStreams {
+ stream_type,
+ maximum_streams,
+ } => QuicFrame::max_streams(
+ match stream_type {
+ frame::StreamType::BiDi => qlog::StreamType::Bidirectional,
+ frame::StreamType::UniDi => qlog::StreamType::Unidirectional,
+ },
+ maximum_streams.as_u64().to_string(),
+ ),
+ Frame::DataBlocked { data_limit } => QuicFrame::data_blocked(data_limit.to_string()),
+ Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit,
+ } => QuicFrame::stream_data_blocked(
+ stream_id.as_u64().to_string(),
+ stream_data_limit.to_string(),
+ ),
+ Frame::StreamsBlocked {
+ stream_type,
+ stream_limit,
+ } => QuicFrame::streams_blocked(
+ match stream_type {
+ frame::StreamType::BiDi => qlog::StreamType::Bidirectional,
+ frame::StreamType::UniDi => qlog::StreamType::Unidirectional,
+ },
+ stream_limit.as_u64().to_string(),
+ ),
+ Frame::NewConnectionId {
+ sequence_number,
+ retire_prior,
+ connection_id,
+ stateless_reset_token,
+ } => QuicFrame::new_connection_id(
+ sequence_number.to_string(),
+ retire_prior.to_string(),
+ connection_id.len() as u64,
+ hex(&connection_id),
+ hex(stateless_reset_token),
+ ),
+ Frame::RetireConnectionId { sequence_number } => {
+ QuicFrame::retire_connection_id(sequence_number.to_string())
+ }
+ Frame::PathChallenge { data } => QuicFrame::path_challenge(Some(hex(data))),
+ Frame::PathResponse { data } => QuicFrame::path_response(Some(hex(data))),
+ Frame::ConnectionClose {
+ error_code,
+ frame_type,
+ reason_phrase,
+ } => QuicFrame::connection_close(
+ match error_code {
+ frame::CloseError::Transport(_) => qlog::ErrorSpace::TransportError,
+ frame::CloseError::Application(_) => qlog::ErrorSpace::ApplicationError,
+ },
+ error_code.code(),
+ 0,
+ String::from_utf8_lossy(&reason_phrase).to_string(),
+ Some(frame_type.to_string()),
+ ),
+ Frame::HandshakeDone => QuicFrame::handshake_done(),
+ }
+}
+
+fn to_qlog_pkt_type(ptype: PacketType) -> qlog::PacketType {
+ match ptype {
+ PacketType::Initial => qlog::PacketType::Initial,
+ PacketType::Handshake => qlog::PacketType::Handshake,
+ PacketType::ZeroRtt => qlog::PacketType::ZeroRtt,
+ PacketType::Short => qlog::PacketType::OneRtt,
+ PacketType::Retry => qlog::PacketType::Retry,
+ PacketType::VersionNegotiation => qlog::PacketType::VersionNegotiation,
+ PacketType::OtherVersion => qlog::PacketType::Unknown,
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/recovery.rs b/third_party/rust/neqo-transport/src/recovery.rs
new file mode 100644
index 0000000000..bac07dc988
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/recovery.rs
@@ -0,0 +1,1470 @@
+// 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.
+
+// Tracking of sent packets and detecting their loss.
+
+#![deny(clippy::pedantic)]
+
+use std::cmp::{max, min};
+use std::collections::BTreeMap;
+use std::mem;
+use std::ops::RangeInclusive;
+use std::time::{Duration, Instant};
+
+use smallvec::{smallvec, SmallVec};
+
+use neqo_common::{qdebug, qinfo, qlog::NeqoQlog, qtrace};
+
+use crate::cc::CongestionControlAlgorithm;
+use crate::connection::LOCAL_IDLE_TIMEOUT;
+use crate::crypto::CryptoRecoveryToken;
+use crate::flow_mgr::FlowControlRecoveryToken;
+use crate::packet::PacketNumber;
+use crate::qlog::{self, QlogMetric};
+use crate::send_stream::StreamRecoveryToken;
+use crate::stats::{Stats, StatsCell};
+use crate::tracking::{AckToken, PNSpace, PNSpaceSet, SentPacket};
+use crate::PacketSender;
+
+pub const GRANULARITY: Duration = Duration::from_millis(20);
+/// The default value for the maximum time a peer can delay acknowledgment
+/// of an ack-eliciting packet.
+pub const MAX_ACK_DELAY: Duration = Duration::from_millis(25);
+// Defined in -recovery 6.2 as 333ms but using lower value.
+const INITIAL_RTT: Duration = Duration::from_millis(100);
+pub(crate) const PACKET_THRESHOLD: u64 = 3;
+/// `ACK_ONLY_SIZE_LIMIT` is the minimum size of the congestion window.
+/// If the congestion window is this small, we will only send ACK frames.
+pub(crate) const ACK_ONLY_SIZE_LIMIT: usize = 256;
+/// The number of packets we send on a PTO.
+/// And the number to declare lost when the PTO timer is hit.
+pub const PTO_PACKET_COUNT: usize = 2;
+
+#[derive(Debug, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub enum RecoveryToken {
+ Ack(AckToken),
+ Stream(StreamRecoveryToken),
+ Crypto(CryptoRecoveryToken),
+ Flow(FlowControlRecoveryToken),
+ HandshakeDone,
+ NewToken(usize),
+}
+
+#[derive(Debug)]
+struct RttVals {
+ first_sample_time: Option<Instant>,
+ latest_rtt: Duration,
+ smoothed_rtt: Duration,
+ rttvar: Duration,
+ min_rtt: Duration,
+ max_ack_delay: Duration,
+}
+
+impl RttVals {
+ pub fn set_initial_rtt(&mut self, rtt: Duration) {
+ // Only allow this when there are no samples.
+ debug_assert!(self.first_sample_time.is_none());
+ self.latest_rtt = rtt;
+ self.min_rtt = rtt;
+ self.smoothed_rtt = rtt;
+ self.rttvar = rtt / 2;
+ }
+
+ pub fn set_peer_max_ack_delay(&mut self, mad: Duration) {
+ self.max_ack_delay = mad;
+ }
+
+ fn update_rtt(
+ &mut self,
+ mut qlog: &mut NeqoQlog,
+ mut rtt_sample: Duration,
+ ack_delay: Duration,
+ now: Instant,
+ ) {
+ // min_rtt ignores ack delay.
+ self.min_rtt = min(self.min_rtt, rtt_sample);
+ // Note: the caller adjusts `ack_delay` based on `max_ack_delay`.
+ // Adjust for ack delay unless it goes below `min_rtt`.
+ if rtt_sample - self.min_rtt >= ack_delay {
+ rtt_sample -= ack_delay;
+ }
+
+ if self.first_sample_time.is_none() {
+ self.set_initial_rtt(rtt_sample);
+ self.first_sample_time = Some(now);
+ } else {
+ // Calculate EWMA RTT (based on {{?RFC6298}}).
+ let rttvar_sample = if self.smoothed_rtt > rtt_sample {
+ self.smoothed_rtt - rtt_sample
+ } else {
+ rtt_sample - self.smoothed_rtt
+ };
+
+ self.latest_rtt = rtt_sample;
+ self.rttvar = (self.rttvar * 3 + rttvar_sample) / 4;
+ self.smoothed_rtt = (self.smoothed_rtt * 7 + rtt_sample) / 8;
+ }
+ qtrace!(
+ "RTT latest={:?} -> estimate={:?}~{:?}",
+ self.latest_rtt,
+ self.smoothed_rtt,
+ self.rttvar
+ );
+ qlog::metrics_updated(
+ &mut qlog,
+ &[
+ QlogMetric::LatestRtt(self.latest_rtt),
+ QlogMetric::MinRtt(self.min_rtt),
+ QlogMetric::SmoothedRtt(self.smoothed_rtt),
+ ],
+ );
+ }
+
+ pub fn rtt(&self) -> Duration {
+ self.smoothed_rtt
+ }
+
+ fn pto(&self, pn_space: PNSpace) -> Duration {
+ self.rtt()
+ + max(4 * self.rttvar, GRANULARITY)
+ + if pn_space == PNSpace::ApplicationData {
+ self.max_ack_delay
+ } else {
+ Duration::from_millis(0)
+ }
+ }
+
+ fn first_sample_time(&self) -> Option<Instant> {
+ self.first_sample_time
+ }
+}
+
+impl Default for RttVals {
+ fn default() -> Self {
+ Self {
+ first_sample_time: None,
+ latest_rtt: INITIAL_RTT,
+ smoothed_rtt: INITIAL_RTT,
+ rttvar: INITIAL_RTT / 2,
+ min_rtt: INITIAL_RTT,
+ max_ack_delay: MAX_ACK_DELAY,
+ }
+ }
+}
+
+/// `SendProfile` tells a sender how to send packets.
+#[derive(Debug)]
+pub struct SendProfile {
+ limit: usize,
+ pto: Option<PNSpace>,
+ probe: PNSpaceSet,
+ paced: bool,
+}
+
+impl SendProfile {
+ pub fn new_limited(limit: usize) -> Self {
+ // When the limit is too low, we only send ACK frames.
+ // Set the limit to `ACK_ONLY_SIZE_LIMIT - 1` to ensure that
+ // ACK-only packets are still limited in size.
+ Self {
+ limit: max(ACK_ONLY_SIZE_LIMIT - 1, limit),
+ pto: None,
+ probe: PNSpaceSet::default(),
+ paced: false,
+ }
+ }
+
+ pub fn new_paced() -> Self {
+ // When pacing, we still allow ACK frames to be sent.
+ Self {
+ limit: ACK_ONLY_SIZE_LIMIT - 1,
+ pto: None,
+ probe: PNSpaceSet::default(),
+ paced: true,
+ }
+ }
+
+ pub fn new_pto(pn_space: PNSpace, mtu: usize, probe: PNSpaceSet) -> Self {
+ debug_assert!(mtu > ACK_ONLY_SIZE_LIMIT);
+ debug_assert!(probe[pn_space]);
+ Self {
+ limit: mtu,
+ pto: Some(pn_space),
+ probe,
+ paced: false,
+ }
+ }
+
+ /// Whether probing this space is helpful. This isn't necessarily the space
+ /// that caused the timer to pop, but it is helpful to send a PING in a space
+ /// that has the PTO timer armed.
+ pub fn should_probe(&self, space: PNSpace) -> bool {
+ self.probe[space]
+ }
+
+ /// Determine whether an ACK-only packet should be sent for the given packet
+ /// number space.
+ /// Send only ACKs either: when the space available is too small, or when a PTO
+ /// exists for a later packet number space (which should get the most space).
+ pub fn ack_only(&self, space: PNSpace) -> bool {
+ self.limit < ACK_ONLY_SIZE_LIMIT || self.pto.map_or(false, |sp| space < sp)
+ }
+
+ pub fn paced(&self) -> bool {
+ self.paced
+ }
+
+ pub fn limit(&self) -> usize {
+ self.limit
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct LossRecoverySpace {
+ space: PNSpace,
+ largest_acked: Option<PacketNumber>,
+ largest_acked_sent_time: Option<Instant>,
+ /// The time used to calculate the PTO timer for this space.
+ /// This is the time that the last ACK-eliciting packet in this space
+ /// was sent. This might be the time that a probe was sent.
+ pto_base_time: Option<Instant>,
+ /// The number of outstanding packets in this space that are in flight.
+ /// This might be less than the number of ACK-eliciting packets,
+ /// because PTO packets don't count.
+ in_flight_outstanding: u64,
+ sent_packets: BTreeMap<u64, SentPacket>,
+ /// The time that the first out-of-order packet was sent.
+ /// This is `None` if there were no out-of-order packets detected.
+ /// When set to `Some(T)`, time-based loss detection should be enabled.
+ first_ooo_time: Option<Instant>,
+}
+
+impl LossRecoverySpace {
+ pub fn new(space: PNSpace) -> Self {
+ Self {
+ space,
+ largest_acked: None,
+ largest_acked_sent_time: None,
+ pto_base_time: None,
+ in_flight_outstanding: 0,
+ sent_packets: BTreeMap::default(),
+ first_ooo_time: None,
+ }
+ }
+
+ #[must_use]
+ pub fn space(&self) -> PNSpace {
+ self.space
+ }
+
+ /// Find the time we sent the first packet that is lower than the
+ /// largest acknowledged and that isn't yet declared lost.
+ /// Use the value we prepared earlier in `detect_lost_packets`.
+ #[must_use]
+ pub fn loss_recovery_timer_start(&self) -> Option<Instant> {
+ self.first_ooo_time
+ }
+
+ pub fn in_flight_outstanding(&self) -> bool {
+ self.in_flight_outstanding > 0
+ }
+
+ pub fn pto_packets(&mut self, count: usize) -> impl Iterator<Item = &SentPacket> {
+ self.sent_packets
+ .iter_mut()
+ .filter_map(|(pn, sent)| {
+ if sent.pto() {
+ qtrace!("PTO: marking packet {} lost ", pn);
+ Some(&*sent)
+ } else {
+ None
+ }
+ })
+ .take(count)
+ }
+
+ pub fn pto_base_time(&self) -> Option<Instant> {
+ if self.in_flight_outstanding() {
+ debug_assert!(self.pto_base_time.is_some());
+ self.pto_base_time
+ } else if self.space == PNSpace::ApplicationData {
+ None
+ } else {
+ // Nasty special case to prevent handshake deadlocks.
+ // A client needs to keep the PTO timer armed to prevent a stall
+ // of the handshake. Technically, this has to stop once we receive
+ // an ACK of Handshake or 1-RTT, or when we receive HANDSHAKE_DONE,
+ // but a few extra probes won't hurt.
+ // A server shouldn't arm its PTO timer this way. The server sends
+ // ack-eliciting, in-flight packets immediately so this only
+ // happens when the server has nothing outstanding. If we had
+ // client authentication, this might cause some extra probes,
+ // but they would be harmless anyway.
+ self.pto_base_time
+ }
+ }
+
+ pub fn on_packet_sent(&mut self, sent_packet: SentPacket) {
+ if sent_packet.ack_eliciting() {
+ self.pto_base_time = Some(sent_packet.time_sent);
+ self.in_flight_outstanding += 1;
+ } else if self.space != PNSpace::ApplicationData && self.pto_base_time.is_none() {
+ // For Initial and Handshake spaces, make sure that we have a PTO baseline
+ // always. See `LossRecoverySpace::pto_base_time()` for details.
+ self.pto_base_time = Some(sent_packet.time_sent);
+ }
+ self.sent_packets.insert(sent_packet.pn, sent_packet);
+ }
+
+ fn remove_packet(&mut self, p: &SentPacket) {
+ if p.ack_eliciting() {
+ debug_assert!(self.in_flight_outstanding > 0);
+ self.in_flight_outstanding -= 1;
+ if self.in_flight_outstanding == 0 {
+ qtrace!("remove_packet outstanding == 0 for space {}", self.space);
+
+ // See above comments; keep PTO armed for Initial/Handshake even
+ // if no outstanding packets.
+ if self.space == PNSpace::ApplicationData {
+ self.pto_base_time = None;
+ }
+ }
+ }
+ }
+
+ /// Remove all acknowledged packets.
+ /// Returns all the acknowledged packets, with the largest packet number first.
+ /// ...and a boolean indicating if any of those packets were ack-eliciting.
+ /// This operates more efficiently because it assumes that the input is sorted
+ /// in the order that an ACK frame is (from the top).
+ fn remove_acked<R>(&mut self, acked_ranges: R, stats: &mut Stats) -> (Vec<SentPacket>, bool)
+ where
+ R: IntoIterator<Item = RangeInclusive<u64>>,
+ R::IntoIter: ExactSizeIterator,
+ {
+ let acked_ranges = acked_ranges.into_iter();
+ let mut keep = Vec::with_capacity(acked_ranges.len());
+
+ let mut acked = Vec::new();
+ let mut eliciting = false;
+ for range in acked_ranges {
+ let first_keep = *range.end() + 1;
+ if let Some((&first, _)) = self.sent_packets.range(range).next() {
+ let mut tail = self.sent_packets.split_off(&first);
+ if let Some((&next, _)) = tail.range(first_keep..).next() {
+ keep.push(tail.split_off(&next));
+ }
+ for (_, p) in tail.into_iter().rev() {
+ self.remove_packet(&p);
+ eliciting |= p.ack_eliciting();
+ if p.lost() {
+ stats.late_ack += 1;
+ }
+ if p.pto_fired() {
+ stats.pto_ack += 1;
+ }
+ acked.push(p);
+ }
+ }
+ }
+
+ for mut k in keep.into_iter().rev() {
+ self.sent_packets.append(&mut k);
+ }
+
+ (acked, eliciting)
+ }
+
+ /// Remove all tracked packets from the space.
+ /// This is called by a client when 0-RTT packets are dropped, when a Retry is received
+ /// and when keys are dropped.
+ fn remove_ignored(&mut self) -> impl Iterator<Item = SentPacket> {
+ self.in_flight_outstanding = 0;
+ mem::take(&mut self.sent_packets)
+ .into_iter()
+ .map(|(_, v)| v)
+ }
+
+ /// Remove old packets that we've been tracking in case they get acknowledged.
+ /// We try to keep these around until a probe is sent for them, so it is
+ /// important that `cd` is set to at least the current PTO time; otherwise we
+ /// might remove all in-flight packets and stop sending probes.
+ #[allow(clippy::option_if_let_else, clippy::unknown_clippy_lints)] // Hard enough to read as-is.
+ fn remove_old_lost(&mut self, now: Instant, cd: Duration) {
+ let mut it = self.sent_packets.iter();
+ // If the first item is not expired, do nothing.
+ if it.next().map_or(false, |(_, p)| p.expired(now, cd)) {
+ // Find the index of the first unexpired packet.
+ let to_remove = if let Some(first_keep) =
+ it.find_map(|(i, p)| if p.expired(now, cd) { None } else { Some(*i) })
+ {
+ // Some packets haven't expired, so keep those.
+ let keep = self.sent_packets.split_off(&first_keep);
+ mem::replace(&mut self.sent_packets, keep)
+ } else {
+ // All packets are expired.
+ mem::take(&mut self.sent_packets)
+ };
+ for (_, p) in to_remove {
+ self.remove_packet(&p);
+ }
+ }
+ }
+
+ /// Detect lost packets.
+ /// `loss_delay` is the time we will wait before declaring something lost.
+ /// `cleanup_delay` is the time we will wait before cleaning up a lost packet.
+ pub fn detect_lost_packets(
+ &mut self,
+ now: Instant,
+ loss_delay: Duration,
+ cleanup_delay: Duration,
+ lost_packets: &mut Vec<SentPacket>,
+ ) {
+ // Housekeeping.
+ self.remove_old_lost(now, cleanup_delay);
+
+ // Packets sent before this time are deemed lost.
+ let lost_deadline = now - loss_delay;
+ qtrace!(
+ "detect lost {}: now={:?} delay={:?} deadline={:?}",
+ self.space,
+ now,
+ loss_delay,
+ lost_deadline
+ );
+ self.first_ooo_time = None;
+
+ let largest_acked = self.largest_acked;
+
+ // Lost for retrans/CC purposes
+ let mut lost_pns = SmallVec::<[_; 8]>::new();
+
+ for (pn, packet) in self
+ .sent_packets
+ .iter_mut()
+ // BTreeMap iterates in order of ascending PN
+ .take_while(|(&k, _)| Some(k) < largest_acked)
+ {
+ if packet.time_sent <= lost_deadline {
+ qtrace!(
+ "lost={}, time sent {:?} is before lost_deadline {:?}",
+ pn,
+ packet.time_sent,
+ lost_deadline
+ );
+ } else if largest_acked >= Some(*pn + PACKET_THRESHOLD) {
+ qtrace!(
+ "lost={}, is >= {} from largest acked {:?}",
+ pn,
+ PACKET_THRESHOLD,
+ largest_acked
+ );
+ } else {
+ self.first_ooo_time = Some(packet.time_sent);
+ // No more packets can be declared lost after this one.
+ break;
+ };
+
+ if packet.declare_lost(now) {
+ lost_pns.push(*pn);
+ }
+ }
+
+ lost_packets.extend(lost_pns.iter().map(|pn| self.sent_packets[pn].clone()));
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct LossRecoverySpaces {
+ /// When we have all of the loss recovery spaces, this will use a separate
+ /// allocation, but this is reduced once the handshake is done.
+ spaces: SmallVec<[LossRecoverySpace; 1]>,
+}
+
+impl LossRecoverySpaces {
+ fn idx(space: PNSpace) -> usize {
+ match space {
+ PNSpace::ApplicationData => 0,
+ PNSpace::Handshake => 1,
+ PNSpace::Initial => 2,
+ }
+ }
+
+ /// Drop a packet number space and return all the packets that were
+ /// outstanding, so that those can be marked as lost.
+ /// # Panics
+ /// If the space has already been removed.
+ pub fn drop_space(&mut self, space: PNSpace) -> Vec<SentPacket> {
+ let sp = match space {
+ PNSpace::Initial => self.spaces.pop(),
+ PNSpace::Handshake => {
+ let sp = self.spaces.pop();
+ self.spaces.shrink_to_fit();
+ sp
+ }
+ PNSpace::ApplicationData => panic!("discarding application space"),
+ };
+ let mut sp = sp.unwrap();
+ assert_eq!(sp.space(), space, "dropping spaces out of order");
+ sp.remove_ignored().collect()
+ }
+
+ pub fn get(&self, space: PNSpace) -> Option<&LossRecoverySpace> {
+ self.spaces.get(Self::idx(space))
+ }
+
+ pub fn get_mut(&mut self, space: PNSpace) -> Option<&mut LossRecoverySpace> {
+ self.spaces.get_mut(Self::idx(space))
+ }
+
+ fn iter(&self) -> impl Iterator<Item = &LossRecoverySpace> {
+ self.spaces.iter()
+ }
+
+ fn iter_mut(&mut self) -> impl Iterator<Item = &mut LossRecoverySpace> {
+ self.spaces.iter_mut()
+ }
+}
+
+impl Default for LossRecoverySpaces {
+ fn default() -> Self {
+ Self {
+ spaces: smallvec![
+ LossRecoverySpace::new(PNSpace::ApplicationData),
+ LossRecoverySpace::new(PNSpace::Handshake),
+ LossRecoverySpace::new(PNSpace::Initial),
+ ],
+ }
+ }
+}
+
+#[derive(Debug)]
+struct PtoState {
+ /// The packet number space that caused the PTO to fire.
+ space: PNSpace,
+ /// The number of probes that we have sent.
+ count: usize,
+ packets: usize,
+ /// The complete set of packet number spaces that can have probes sent.
+ probe: PNSpaceSet,
+}
+
+impl PtoState {
+ pub fn new(space: PNSpace, probe: PNSpaceSet) -> Self {
+ debug_assert!(probe[space]);
+ Self {
+ space,
+ count: 1,
+ packets: PTO_PACKET_COUNT,
+ probe,
+ }
+ }
+
+ pub fn pto(&mut self, space: PNSpace, probe: PNSpaceSet) {
+ debug_assert!(probe[space]);
+ self.space = space;
+ self.count += 1;
+ self.packets = PTO_PACKET_COUNT;
+ self.probe = probe;
+ }
+
+ pub fn count(&self) -> usize {
+ self.count
+ }
+
+ pub fn count_pto(&self, stats: &mut Stats) {
+ stats.add_pto_count(self.count);
+ }
+
+ /// Generate a sending profile, indicating what space it should be from.
+ /// This takes a packet from the supply or returns an ack-only profile if it can't.
+ pub fn send_profile(&mut self, mtu: usize) -> SendProfile {
+ if self.packets > 0 {
+ self.packets -= 1;
+ SendProfile::new_pto(self.space, mtu, self.probe)
+ } else {
+ SendProfile::new_limited(0)
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct LossRecovery {
+ /// When the handshake was confirmed, if it has been.
+ confirmed_time: Option<Instant>,
+ pto_state: Option<PtoState>,
+ rtt_vals: RttVals,
+ packet_sender: PacketSender,
+ spaces: LossRecoverySpaces,
+ qlog: NeqoQlog,
+ stats: StatsCell,
+}
+
+impl LossRecovery {
+ pub fn new(alg: CongestionControlAlgorithm, stats: StatsCell) -> Self {
+ Self {
+ confirmed_time: None,
+ pto_state: None,
+ rtt_vals: RttVals::default(),
+ packet_sender: PacketSender::new(alg),
+ spaces: LossRecoverySpaces::default(),
+ qlog: NeqoQlog::default(),
+ stats,
+ }
+ }
+
+ #[cfg(test)]
+ pub fn cwnd(&self) -> usize {
+ self.packet_sender.cwnd()
+ }
+
+ pub fn rtt(&self) -> Duration {
+ self.rtt_vals.rtt()
+ }
+
+ pub fn set_initial_rtt(&mut self, rtt: Duration) {
+ self.rtt_vals.set_initial_rtt(rtt)
+ }
+
+ pub fn set_peer_max_ack_delay(&mut self, mad: Duration) {
+ self.rtt_vals.set_peer_max_ack_delay(mad);
+ }
+
+ pub fn cwnd_avail(&self) -> usize {
+ self.packet_sender.cwnd_avail()
+ }
+
+ pub fn largest_acknowledged_pn(&self, pn_space: PNSpace) -> Option<PacketNumber> {
+ self.spaces.get(pn_space).and_then(|sp| sp.largest_acked)
+ }
+
+ pub fn set_qlog(&mut self, qlog: NeqoQlog) {
+ self.packet_sender.set_qlog(qlog.clone());
+ self.qlog = qlog;
+ }
+
+ pub fn drop_0rtt(&mut self) -> Vec<SentPacket> {
+ // The largest acknowledged or loss_time should still be unset.
+ // The client should not have received any ACK frames when it drops 0-RTT.
+ assert!(self
+ .spaces
+ .get(PNSpace::ApplicationData)
+ .unwrap()
+ .largest_acked
+ .is_none());
+ self.spaces
+ .get_mut(PNSpace::ApplicationData)
+ .unwrap()
+ .remove_ignored()
+ .inspect(|p| self.packet_sender.discard(&p))
+ .collect()
+ }
+
+ pub fn on_packet_sent(&mut self, sent_packet: SentPacket) {
+ let pn_space = PNSpace::from(sent_packet.pt);
+ qdebug!([self], "packet {}-{} sent", pn_space, sent_packet.pn);
+ let rtt = self.rtt();
+ if let Some(space) = self.spaces.get_mut(pn_space) {
+ self.packet_sender.on_packet_sent(&sent_packet, rtt);
+ space.on_packet_sent(sent_packet);
+ } else {
+ qinfo!(
+ [self],
+ "ignoring {}-{} from dropped space",
+ pn_space,
+ sent_packet.pn
+ );
+ }
+ }
+
+ /// Record an RTT sample.
+ fn rtt_sample(&mut self, send_time: Instant, now: Instant, ack_delay: Duration) {
+ // Limit ack delay by max_ack_delay if confirmed.
+ let delay = self.confirmed_time.map_or(ack_delay, |confirmed| {
+ if confirmed < send_time {
+ ack_delay
+ } else {
+ min(ack_delay, self.rtt_vals.max_ack_delay)
+ }
+ });
+
+ let sample = now - send_time;
+ self.rtt_vals.update_rtt(&mut self.qlog, sample, delay, now);
+ }
+
+ /// Returns (acked packets, lost packets)
+ pub fn on_ack_received(
+ &mut self,
+ pn_space: PNSpace,
+ largest_acked: u64,
+ acked_ranges: Vec<RangeInclusive<u64>>,
+ ack_delay: Duration,
+ now: Instant,
+ ) -> (Vec<SentPacket>, Vec<SentPacket>) {
+ qdebug!(
+ [self],
+ "ACK for {} - largest_acked={}.",
+ pn_space,
+ largest_acked
+ );
+
+ let space = self
+ .spaces
+ .get_mut(pn_space)
+ .expect("ACK on discarded space");
+ let (acked_packets, any_ack_eliciting) =
+ space.remove_acked(acked_ranges, &mut *self.stats.borrow_mut());
+ if acked_packets.is_empty() {
+ // No new information.
+ return (Vec::new(), Vec::new());
+ }
+
+ // Track largest PN acked per space
+ let prev_largest_acked = space.largest_acked_sent_time;
+ if Some(largest_acked) > space.largest_acked {
+ space.largest_acked = Some(largest_acked);
+
+ // If the largest acknowledged is newly acked and any newly acked
+ // packet was ack-eliciting, update the RTT. (-recovery 5.1)
+ let largest_acked_pkt = acked_packets.first().expect("must be there");
+ space.largest_acked_sent_time = Some(largest_acked_pkt.time_sent);
+ if any_ack_eliciting {
+ self.rtt_sample(largest_acked_pkt.time_sent, now, ack_delay);
+ }
+ }
+
+ // Perform loss detection.
+ // PTO is used to remove lost packets from in-flight accounting.
+ // We need to ensure that we have sent any PTO probes before they are removed
+ // as we rely on the count of in-flight packets to determine whether to send
+ // another probe. Removing them too soon would result in not sending on PTO.
+ let loss_delay = self.loss_delay();
+ let cleanup = self.pto_period(pn_space);
+ let mut lost = Vec::new();
+ self.spaces
+ .get_mut(pn_space)
+ .unwrap()
+ .detect_lost_packets(now, loss_delay, cleanup, &mut lost);
+ self.stats.borrow_mut().lost += lost.len();
+
+ // Tell the congestion controller about any lost packets.
+ // The PTO for congestion control is the raw number, without exponential
+ // backoff, so that we can determine persistent congestion.
+ let pto_raw = self.pto_raw(pn_space);
+ let first_rtt_sample = self.rtt_vals.first_sample_time();
+ self.packet_sender
+ .on_packets_lost(first_rtt_sample, prev_largest_acked, pto_raw, &lost);
+
+ // This must happen after on_packets_lost. If in recovery, this could
+ // take us out, and then lost packets will start a new recovery period
+ // when it shouldn't.
+ self.packet_sender.on_packets_acked(&acked_packets);
+
+ self.pto_state = None;
+
+ (acked_packets, lost)
+ }
+
+ fn loss_delay(&self) -> Duration {
+ // kTimeThreshold = 9/8
+ // loss_delay = kTimeThreshold * max(latest_rtt, smoothed_rtt)
+ // loss_delay = max(loss_delay, kGranularity)
+ let rtt = max(self.rtt_vals.latest_rtt, self.rtt_vals.smoothed_rtt);
+ max(rtt * 9 / 8, GRANULARITY)
+ }
+
+ /// When receiving a retry, get all the sent packets so that they can be flushed.
+ /// We also need to pretend that they never happened for the purposes of congestion control.
+ pub fn retry(&mut self) -> Vec<SentPacket> {
+ self.pto_state = None;
+ let packet_sender = &mut self.packet_sender;
+ self.spaces
+ .iter_mut()
+ .flat_map(LossRecoverySpace::remove_ignored)
+ .inspect(|p| packet_sender.discard(&p))
+ .collect()
+ }
+
+ fn confirmed(&mut self, now: Instant) {
+ debug_assert!(self.confirmed_time.is_none());
+ self.confirmed_time = Some(now);
+ // Up until now, the ApplicationData space has been ignored for PTO.
+ // So maybe fire a PTO.
+ if let Some(pto) = self.pto_time(PNSpace::ApplicationData) {
+ if pto < now {
+ let probes = PNSpaceSet::from(&[PNSpace::ApplicationData]);
+ self.fire_pto(PNSpace::ApplicationData, probes);
+ }
+ }
+ }
+
+ /// Discard state for a given packet number space.
+ pub fn discard(&mut self, space: PNSpace, now: Instant) {
+ qdebug!([self], "Reset loss recovery state for {}", space);
+ for p in self.spaces.drop_space(space) {
+ self.packet_sender.discard(&p);
+ }
+
+ // We just made progress, so discard PTO count.
+ // The spec says that clients should not do this until confirming that
+ // the server has completed address validation, but ignore that.
+ self.pto_state = None;
+
+ if space == PNSpace::Handshake {
+ self.confirmed(now);
+ }
+ }
+
+ /// Calculate when the next timeout is likely to be. This is the earlier of the loss timer
+ /// and the PTO timer; either or both might be disabled, so this can return `None`.
+ pub fn next_timeout(&mut self) -> Option<Instant> {
+ let loss_time = self.earliest_loss_time();
+ let pto_time = self.earliest_pto();
+ qtrace!(
+ [self],
+ "next_timeout loss={:?} pto={:?}",
+ loss_time,
+ pto_time
+ );
+ match (loss_time, pto_time) {
+ (Some(loss_time), Some(pto_time)) => Some(min(loss_time, pto_time)),
+ (Some(loss_time), None) => Some(loss_time),
+ (None, Some(pto_time)) => Some(pto_time),
+ _ => None,
+ }
+ }
+
+ /// Find when the earliest sent packet should be considered lost.
+ fn earliest_loss_time(&self) -> Option<Instant> {
+ self.spaces
+ .iter()
+ .filter_map(LossRecoverySpace::loss_recovery_timer_start)
+ .min()
+ .map(|val| val + self.loss_delay())
+ }
+
+ // The borrow checker is a harsh mistress.
+ // It's important that calls to `RttVals::pto()` are routed through a central point
+ // because that ensures consistency, but we often have a mutable borrow on other
+ // pieces of `self` that prevents that.
+ // An associated function avoids another borrow on `&self`.
+ fn pto_raw_inner(rtt_vals: &RttVals, space: PNSpace) -> Duration {
+ rtt_vals.pto(space)
+ }
+
+ // Borrow checker hack, see above.
+ fn pto_period_inner(
+ rtt_vals: &RttVals,
+ pto_state: &Option<PtoState>,
+ pn_space: PNSpace,
+ ) -> Duration {
+ Self::pto_raw_inner(rtt_vals, pn_space)
+ .checked_mul(1 << pto_state.as_ref().map_or(0, |p| p.count))
+ .unwrap_or(LOCAL_IDLE_TIMEOUT * 2)
+ }
+
+ /// Get the Base PTO value, which is derived only from the `RTT` and `RTTvar` values.
+ /// This is for those cases where you need a value for the time you might sensibly
+ /// wait for a packet to propagate. Using `3*pto_raw(..)` is common.
+ pub fn pto_raw(&self, space: PNSpace) -> Duration {
+ Self::pto_raw_inner(&self.rtt_vals, space)
+ }
+
+ /// Get the current PTO period for the given packet number space.
+ /// Unlike `pto_raw`, this includes calculation for the exponential backoff.
+ fn pto_period(&self, pn_space: PNSpace) -> Duration {
+ Self::pto_period_inner(&self.rtt_vals, &self.pto_state, pn_space)
+ }
+
+ // Calculate PTO time for the given space.
+ fn pto_time(&self, pn_space: PNSpace) -> Option<Instant> {
+ if self.confirmed_time.is_none() && pn_space == PNSpace::ApplicationData {
+ None
+ } else {
+ self.spaces
+ .get(pn_space)
+ .and_then(|space| space.pto_base_time().map(|t| t + self.pto_period(pn_space)))
+ }
+ }
+
+ /// Find the earliest PTO time for all active packet number spaces.
+ /// Ignore Application if either Initial or Handshake have an active PTO.
+ fn earliest_pto(&self) -> Option<Instant> {
+ if self.confirmed_time.is_some() {
+ self.pto_time(PNSpace::ApplicationData)
+ } else {
+ self.pto_time(PNSpace::Initial)
+ .iter()
+ .chain(self.pto_time(PNSpace::Handshake).iter())
+ .min()
+ .cloned()
+ }
+ }
+
+ fn fire_pto(&mut self, pn_space: PNSpace, allow_probes: PNSpaceSet) {
+ if let Some(st) = &mut self.pto_state {
+ st.pto(pn_space, allow_probes);
+ } else {
+ self.pto_state = Some(PtoState::new(pn_space, allow_probes));
+ }
+
+ self.pto_state
+ .as_mut()
+ .unwrap()
+ .count_pto(&mut *self.stats.borrow_mut());
+
+ qlog::metrics_updated(
+ &mut self.qlog,
+ &[QlogMetric::PtoCount(
+ self.pto_state.as_ref().unwrap().count(),
+ )],
+ );
+ }
+
+ /// This checks whether the PTO timer has fired and fires it if needed.
+ /// When it has, mark a few packets as "lost" for the purposes of having frames
+ /// regenerated in subsequent packets. The packets aren't truly lost, so
+ /// we have to clone the `SentPacket` instance.
+ fn maybe_fire_pto(&mut self, now: Instant, lost: &mut Vec<SentPacket>) {
+ let mut pto_space = None;
+ // The spaces in which we will allow probing.
+ let mut allow_probes = PNSpaceSet::default();
+ for pn_space in PNSpace::iter() {
+ if let Some(t) = self.pto_time(*pn_space) {
+ allow_probes[*pn_space] = true;
+ if t <= now {
+ qdebug!([self], "PTO timer fired for {}", pn_space);
+ let space = self.spaces.get_mut(*pn_space).unwrap();
+ lost.extend(space.pto_packets(PTO_PACKET_COUNT).cloned());
+
+ pto_space = pto_space.or(Some(*pn_space));
+ }
+ }
+ }
+
+ // This has to happen outside the loop. Increasing the PTO count here causes the
+ // pto_time to increase which might cause PTO for later packet number spaces to not fire.
+ if let Some(pn_space) = pto_space {
+ qtrace!([self], "PTO {}, probing {:?}", pn_space, allow_probes);
+ self.fire_pto(pn_space, allow_probes);
+ }
+ }
+
+ pub fn timeout(&mut self, now: Instant) -> Vec<SentPacket> {
+ qtrace!([self], "timeout {:?}", now);
+
+ let loss_delay = self.loss_delay();
+ let first_rtt_sample = self.rtt_vals.first_sample_time();
+
+ let mut lost_packets = Vec::new();
+ for space in self.spaces.iter_mut() {
+ let first = lost_packets.len(); // The first packet lost in this space.
+ let pto = Self::pto_period_inner(&self.rtt_vals, &self.pto_state, space.space());
+ space.detect_lost_packets(now, loss_delay, pto, &mut lost_packets);
+ self.packet_sender.on_packets_lost(
+ first_rtt_sample,
+ space.largest_acked_sent_time,
+ Self::pto_raw_inner(&self.rtt_vals, space.space()),
+ &lost_packets[first..],
+ );
+ }
+ self.stats.borrow_mut().lost += lost_packets.len();
+
+ self.maybe_fire_pto(now, &mut lost_packets);
+ lost_packets
+ }
+
+ /// Start the packet pacer.
+ pub fn start_pacer(&mut self, now: Instant) {
+ self.packet_sender.start_pacer(now);
+ }
+
+ /// Get the next time that a paced packet might be sent.
+ pub fn next_paced(&self) -> Option<Instant> {
+ self.packet_sender.next_paced(self.rtt())
+ }
+
+ /// Check how packets should be sent, based on whether there is a PTO,
+ /// what the current congestion window is, and what the pacer says.
+ #[allow(clippy::option_if_let_else, clippy::unknown_clippy_lints)]
+ pub fn send_profile(&mut self, now: Instant, mtu: usize) -> SendProfile {
+ qdebug!([self], "get send profile {:?}", now);
+ if let Some(pto) = self.pto_state.as_mut() {
+ pto.send_profile(mtu)
+ } else {
+ let cwnd = self.cwnd_avail();
+ if cwnd > mtu {
+ // More than an MTU available; we might need to pace.
+ if self.next_paced().map_or(false, |t| t > now) {
+ SendProfile::new_paced()
+ } else {
+ SendProfile::new_limited(mtu)
+ }
+ } else if self.packet_sender.recovery_packet() {
+ // After entering recovery, allow a packet to be sent immediately.
+ // This uses the PTO machinery, probing in all spaces. This will
+ // result in a PING being sent in every active space.
+ SendProfile::new_pto(PNSpace::Initial, mtu, PNSpaceSet::all())
+ } else {
+ SendProfile::new_limited(cwnd)
+ }
+ }
+ }
+}
+
+impl ::std::fmt::Display for LossRecovery {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "LossRecovery")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ CongestionControlAlgorithm, LossRecovery, LossRecoverySpace, PNSpace, SentPacket,
+ INITIAL_RTT, MAX_ACK_DELAY,
+ };
+ use crate::packet::PacketType;
+ use crate::stats::{Stats, StatsCell};
+ use std::convert::TryInto;
+ use std::time::{Duration, Instant};
+ use test_fixture::now;
+
+ const ON_SENT_SIZE: usize = 100;
+
+ fn assert_rtts(
+ lr: &LossRecovery,
+ latest_rtt: Duration,
+ smoothed_rtt: Duration,
+ rttvar: Duration,
+ min_rtt: Duration,
+ ) {
+ println!(
+ "rtts: {:?} {:?} {:?} {:?}",
+ lr.rtt_vals.latest_rtt,
+ lr.rtt_vals.smoothed_rtt,
+ lr.rtt_vals.rttvar,
+ lr.rtt_vals.min_rtt,
+ );
+ assert_eq!(lr.rtt_vals.latest_rtt, latest_rtt, "latest RTT");
+ assert_eq!(lr.rtt_vals.smoothed_rtt, smoothed_rtt, "smoothed RTT");
+ assert_eq!(lr.rtt_vals.rttvar, rttvar, "RTT variance");
+ assert_eq!(lr.rtt_vals.min_rtt, min_rtt, "min RTT");
+ }
+
+ fn assert_sent_times(
+ lr: &LossRecovery,
+ initial: Option<Instant>,
+ handshake: Option<Instant>,
+ app_data: Option<Instant>,
+ ) {
+ let est = |sp| {
+ lr.spaces
+ .get(sp)
+ .and_then(LossRecoverySpace::loss_recovery_timer_start)
+ };
+ println!(
+ "loss times: {:?} {:?} {:?}",
+ est(PNSpace::Initial),
+ est(PNSpace::Handshake),
+ est(PNSpace::ApplicationData),
+ );
+ assert_eq!(est(PNSpace::Initial), initial, "Initial earliest sent time");
+ assert_eq!(
+ est(PNSpace::Handshake),
+ handshake,
+ "Handshake earliest sent time"
+ );
+ assert_eq!(
+ est(PNSpace::ApplicationData),
+ app_data,
+ "AppData earliest sent time"
+ );
+ }
+
+ fn assert_no_sent_times(lr: &LossRecovery) {
+ assert_sent_times(lr, None, None, None);
+ }
+
+ // Time in milliseconds.
+ macro_rules! ms {
+ ($t:expr) => {
+ Duration::from_millis($t)
+ };
+ }
+
+ // In most of the tests below, packets are sent at a fixed cadence, with PACING between each.
+ const PACING: Duration = ms!(7);
+ fn pn_time(pn: u64) -> Instant {
+ now() + (PACING * pn.try_into().unwrap())
+ }
+
+ fn pace(lr: &mut LossRecovery, count: u64) {
+ for pn in 0..count {
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Short,
+ pn,
+ pn_time(pn),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ }
+ }
+
+ const ACK_DELAY: Duration = ms!(24);
+ /// Acknowledge PN with the identified delay.
+ fn ack(lr: &mut LossRecovery, pn: u64, delay: Duration) {
+ lr.on_ack_received(
+ PNSpace::ApplicationData,
+ pn,
+ vec![pn..=pn],
+ ACK_DELAY,
+ pn_time(pn) + delay,
+ );
+ }
+
+ fn add_sent(lrs: &mut LossRecoverySpace, packet_numbers: &[u64]) {
+ for &pn in packet_numbers {
+ lrs.on_packet_sent(SentPacket::new(
+ PacketType::Short,
+ pn,
+ pn_time(pn),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ }
+ }
+
+ fn match_acked(acked: &[SentPacket], expected: &[u64]) {
+ assert!(acked.iter().map(|p| &p.pn).eq(expected));
+ }
+
+ #[test]
+ fn remove_acked() {
+ let mut lrs = LossRecoverySpace::new(PNSpace::ApplicationData);
+ let mut stats = Stats::default();
+ add_sent(&mut lrs, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ let (acked, _) = lrs.remove_acked(vec![], &mut stats);
+ assert!(acked.is_empty());
+ let (acked, _) = lrs.remove_acked(vec![7..=8, 2..=4], &mut stats);
+ match_acked(&acked, &[8, 7, 4, 3, 2]);
+ let (acked, _) = lrs.remove_acked(vec![8..=11], &mut stats);
+ match_acked(&acked, &[10, 9]);
+ let (acked, _) = lrs.remove_acked(vec![0..=2], &mut stats);
+ match_acked(&acked, &[1]);
+ let (acked, _) = lrs.remove_acked(vec![5..=6], &mut stats);
+ match_acked(&acked, &[6, 5]);
+ }
+
+ #[test]
+ fn initial_rtt() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.start_pacer(now());
+ pace(&mut lr, 1);
+ let rtt = ms!(100);
+ ack(&mut lr, 0, rtt);
+ assert_rtts(&lr, rtt, rtt, rtt / 2, rtt);
+ assert_no_sent_times(&lr);
+ }
+
+ /// An initial RTT for using with `setup_lr`.
+ const TEST_RTT: Duration = ms!(80);
+ const TEST_RTTVAR: Duration = ms!(40);
+
+ /// Send `n` packets (using PACING), then acknowledge the first.
+ fn setup_lr(n: u64) -> LossRecovery {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.start_pacer(now());
+ pace(&mut lr, n);
+ ack(&mut lr, 0, TEST_RTT);
+ assert_rtts(&lr, TEST_RTT, TEST_RTT, TEST_RTTVAR, TEST_RTT);
+ assert_no_sent_times(&lr);
+ lr
+ }
+
+ // The ack delay is removed from any RTT estimate.
+ #[test]
+ fn ack_delay_adjusted() {
+ let mut lr = setup_lr(2);
+ ack(&mut lr, 1, TEST_RTT + ACK_DELAY);
+ // RTT stays the same, but the RTTVAR is adjusted downwards.
+ assert_rtts(&lr, TEST_RTT, TEST_RTT, TEST_RTTVAR * 3 / 4, TEST_RTT);
+ assert_no_sent_times(&lr);
+ }
+
+ // The ack delay is ignored when it would cause a sample to be less than min_rtt.
+ #[test]
+ fn ack_delay_ignored() {
+ let mut lr = setup_lr(2);
+ let extra = ms!(8);
+ assert!(extra < ACK_DELAY);
+ ack(&mut lr, 1, TEST_RTT + extra);
+ let expected_rtt = TEST_RTT + (extra / 8);
+ let expected_rttvar = (TEST_RTTVAR * 3 + extra) / 4;
+ assert_rtts(
+ &lr,
+ TEST_RTT + extra,
+ expected_rtt,
+ expected_rttvar,
+ TEST_RTT,
+ );
+ assert_no_sent_times(&lr);
+ }
+
+ // A lower observed RTT is used as min_rtt (and ack delay is ignored).
+ #[test]
+ fn reduce_min_rtt() {
+ let mut lr = setup_lr(2);
+ let delta = ms!(4);
+ let reduced_rtt = TEST_RTT - delta;
+ ack(&mut lr, 1, reduced_rtt);
+ let expected_rtt = TEST_RTT - (delta / 8);
+ let expected_rttvar = (TEST_RTTVAR * 3 + delta) / 4;
+ assert_rtts(&lr, reduced_rtt, expected_rtt, expected_rttvar, reduced_rtt);
+ assert_no_sent_times(&lr);
+ }
+
+ // Acknowledging something again has no effect.
+ #[test]
+ fn no_new_acks() {
+ let mut lr = setup_lr(1);
+ let check = |lr: &LossRecovery| {
+ assert_rtts(&lr, TEST_RTT, TEST_RTT, TEST_RTTVAR, TEST_RTT);
+ assert_no_sent_times(&lr);
+ };
+ check(&lr);
+
+ ack(&mut lr, 0, ms!(1339)); // much delayed ACK
+ check(&lr);
+
+ ack(&mut lr, 0, ms!(3)); // time travel!
+ check(&lr);
+ }
+
+ // Test time loss detection as part of handling a regular ACK.
+ #[test]
+ fn time_loss_detection_gap() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.start_pacer(now());
+ // Create a single packet gap, and have pn 0 time out.
+ // This can't use the default pacing, which is too tight.
+ // So send two packets with 1/4 RTT between them. Acknowledge pn 1 after 1 RTT.
+ // pn 0 should then be marked lost because it is then outstanding for 5RTT/4
+ // the loss time for packets is 9RTT/8.
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Short,
+ 0,
+ pn_time(0),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Short,
+ 1,
+ pn_time(0) + TEST_RTT / 4,
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ let (_, lost) = lr.on_ack_received(
+ PNSpace::ApplicationData,
+ 1,
+ vec![1..=1],
+ ACK_DELAY,
+ pn_time(0) + (TEST_RTT * 5 / 4),
+ );
+ assert_eq!(lost.len(), 1);
+ assert_no_sent_times(&lr);
+ }
+
+ // Test time loss detection as part of an explicit timeout.
+ #[test]
+ fn time_loss_detection_timeout() {
+ let mut lr = setup_lr(3);
+
+ // We want to declare PN 2 as acknowledged before we declare PN 1 as lost.
+ // For this to work, we need PACING above to be less than 1/8 of an RTT.
+ let pn1_sent_time = pn_time(1);
+ let pn1_loss_time = pn1_sent_time + (TEST_RTT * 9 / 8);
+ let pn2_ack_time = pn_time(2) + TEST_RTT;
+ assert!(pn1_loss_time > pn2_ack_time);
+
+ let (_, lost) = lr.on_ack_received(
+ PNSpace::ApplicationData,
+ 2,
+ vec![2..=2],
+ ACK_DELAY,
+ pn2_ack_time,
+ );
+ assert!(lost.is_empty());
+ // Run the timeout function here to force time-based loss recovery to be enabled.
+ let lost = lr.timeout(pn2_ack_time);
+ assert!(lost.is_empty());
+ assert_sent_times(&lr, None, None, Some(pn1_sent_time));
+
+ // After time elapses, pn 1 is marked lost.
+ let callback_time = lr.next_timeout();
+ assert_eq!(callback_time, Some(pn1_loss_time));
+ let packets = lr.timeout(pn1_loss_time);
+ assert_eq!(packets.len(), 1);
+ // Checking for expiration with zero delay lets us check the loss time.
+ assert!(packets[0].expired(pn1_loss_time, Duration::new(0, 0)));
+ assert_no_sent_times(&lr);
+ }
+
+ #[test]
+ fn big_gap_loss() {
+ let mut lr = setup_lr(5); // This sends packets 0-4 and acknowledges pn 0.
+ // Acknowledge just 2-4, which will cause pn 1 to be marked as lost.
+ assert_eq!(super::PACKET_THRESHOLD, 3);
+ let (_, lost) = lr.on_ack_received(
+ PNSpace::ApplicationData,
+ 4,
+ vec![2..=4],
+ ACK_DELAY,
+ pn_time(4),
+ );
+ assert_eq!(lost.len(), 1);
+ }
+
+ #[test]
+ #[should_panic(expected = "discarding application space")]
+ fn drop_app() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.discard(PNSpace::ApplicationData, now());
+ }
+
+ #[test]
+ #[should_panic(expected = "dropping spaces out of order")]
+ fn drop_out_of_order() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.discard(PNSpace::Handshake, now());
+ }
+
+ #[test]
+ #[should_panic(expected = "ACK on discarded space")]
+ fn ack_after_drop() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.start_pacer(now());
+ lr.discard(PNSpace::Initial, now());
+ lr.on_ack_received(
+ PNSpace::Initial,
+ 0,
+ vec![],
+ Duration::from_millis(0),
+ pn_time(0),
+ );
+ }
+
+ #[test]
+ fn drop_spaces() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.start_pacer(now());
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Initial,
+ 0,
+ pn_time(0),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Handshake,
+ 0,
+ pn_time(1),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Short,
+ 0,
+ pn_time(2),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+
+ // Now put all spaces on the LR timer so we can see them.
+ for sp in &[
+ PacketType::Initial,
+ PacketType::Handshake,
+ PacketType::Short,
+ ] {
+ let sent_pkt = SentPacket::new(*sp, 1, pn_time(3), true, Vec::new(), ON_SENT_SIZE);
+ let pn_space = PNSpace::from(sent_pkt.pt);
+ lr.on_packet_sent(sent_pkt);
+ lr.on_ack_received(pn_space, 1, vec![1..=1], Duration::from_secs(0), pn_time(3));
+ let mut lost = Vec::new();
+ lr.spaces.get_mut(pn_space).unwrap().detect_lost_packets(
+ pn_time(3),
+ TEST_RTT,
+ TEST_RTT * 3, // unused
+ &mut lost,
+ );
+ assert!(lost.is_empty());
+ }
+
+ lr.discard(PNSpace::Initial, pn_time(3));
+ assert_sent_times(&lr, None, Some(pn_time(1)), Some(pn_time(2)));
+
+ lr.discard(PNSpace::Handshake, pn_time(3));
+ assert_sent_times(&lr, None, None, Some(pn_time(2)));
+
+ // There are cases where we send a packet that is not subsequently tracked.
+ // So check that this works.
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Initial,
+ 0,
+ pn_time(3),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ assert_sent_times(&lr, None, None, Some(pn_time(2)));
+ }
+
+ #[test]
+ fn rearm_pto_after_confirmed() {
+ let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
+ lr.start_pacer(now());
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Handshake,
+ 0,
+ now(),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+ lr.on_packet_sent(SentPacket::new(
+ PacketType::Short,
+ 0,
+ now(),
+ true,
+ Vec::new(),
+ ON_SENT_SIZE,
+ ));
+
+ assert_eq!(lr.pto_time(PNSpace::ApplicationData), None);
+ lr.discard(PNSpace::Initial, pn_time(1));
+ assert_eq!(lr.pto_time(PNSpace::ApplicationData), None);
+
+ // Expiring state after the PTO on the ApplicationData space has
+ // expired should result in setting a PTO state.
+ let expected_pto = pn_time(2) + (INITIAL_RTT * 3) + MAX_ACK_DELAY;
+ lr.discard(PNSpace::Handshake, expected_pto);
+ let profile = lr.send_profile(expected_pto, 10000);
+ assert!(profile.pto.is_some());
+ assert!(!profile.should_probe(PNSpace::Initial));
+ assert!(!profile.should_probe(PNSpace::Handshake));
+ assert!(profile.should_probe(PNSpace::ApplicationData));
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/recv_stream.rs b/third_party/rust/neqo-transport/src/recv_stream.rs
new file mode 100644
index 0000000000..e82e08d2ec
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/recv_stream.rs
@@ -0,0 +1,1110 @@
+// 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.
+
+// Building a stream of ordered bytes to give the application from a series of
+// incoming STREAM frames.
+
+use std::cell::RefCell;
+use std::cmp::max;
+use std::collections::BTreeMap;
+use std::convert::TryFrom;
+use std::mem;
+use std::ops::Bound::{Included, Unbounded};
+use std::rc::Rc;
+
+use smallvec::SmallVec;
+
+use crate::events::ConnectionEvents;
+use crate::flow_mgr::FlowMgr;
+use crate::stream_id::StreamId;
+use crate::{AppError, Error, Res};
+use neqo_common::qtrace;
+
+const RX_STREAM_DATA_WINDOW: u64 = 0x10_0000; // 1MiB
+
+// Export as usize for consistency with SEND_BUFFER_SIZE
+pub const RECV_BUFFER_SIZE: usize = RX_STREAM_DATA_WINDOW as usize;
+
+pub(crate) type RecvStreams = BTreeMap<StreamId, RecvStream>;
+
+/// Holds data not yet read by application. Orders and dedupes data ranges
+/// from incoming STREAM frames.
+#[derive(Debug, Default)]
+pub struct RxStreamOrderer {
+ data_ranges: BTreeMap<u64, Vec<u8>>, // (start_offset, data)
+ retired: u64, // Number of bytes the application has read
+}
+
+impl RxStreamOrderer {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Process an incoming stream frame off the wire. This may result in data
+ /// being available to upper layers if frame is not out of order (ooo) or
+ /// if the frame fills a gap.
+ pub fn inbound_frame(&mut self, mut new_start: u64, mut new_data: &[u8]) {
+ qtrace!("Inbound data offset={} len={}", new_start, new_data.len());
+
+ // Get entry before where new entry would go, so we can see if we already
+ // have the new bytes.
+ // Avoid copies and duplicated data.
+ let new_end = new_start + u64::try_from(new_data.len()).unwrap();
+
+ if new_end <= self.retired {
+ // Range already read by application, this frame is very late and unneeded.
+ return;
+ }
+
+ if new_start < self.retired {
+ new_data = &new_data[usize::try_from(self.retired - new_start).unwrap()..];
+ new_start = self.retired;
+ }
+
+ if new_data.is_empty() {
+ // No data to insert
+ return;
+ }
+
+ let extend = if let Some((&prev_start, prev_vec)) = self
+ .data_ranges
+ .range_mut((Unbounded, Included(new_start)))
+ .next_back()
+ {
+ let prev_end = prev_start + u64::try_from(prev_vec.len()).unwrap();
+ if new_end > prev_end {
+ // PPPPPP -> PPPPPP
+ // NNNNNN NN
+ // NNNNNNNN NN
+ // Add a range containing only new data
+ // (In-order frames will take this path, with no overlap)
+ let overlap = prev_end.saturating_sub(new_start);
+ qtrace!(
+ "New frame {}-{} received, overlap: {}",
+ new_start,
+ new_end,
+ overlap
+ );
+ new_start += overlap;
+ new_data = &new_data[usize::try_from(overlap).unwrap()..];
+ // If it is small enough, extend the previous buffer.
+ // This can't always extend, because otherwise the buffer could end up
+ // growing indefinitely without being released.
+ prev_vec.len() < 4096 && prev_end == new_start
+ } else {
+ // PPPPPP -> PPPPPP
+ // NNNN
+ // NNNN
+ // Do nothing
+ qtrace!(
+ "Dropping frame with already-received range {}-{}",
+ new_start,
+ new_end
+ );
+ return;
+ }
+ } else {
+ qtrace!("New frame {}-{} received", new_start, new_end);
+ false
+ };
+
+ // Now handle possible overlap with next entries
+ let mut to_remove = SmallVec::<[_; 8]>::new();
+ let mut to_add = new_data;
+
+ for (&next_start, next_data) in self.data_ranges.range_mut(new_start..) {
+ let next_end = next_start + u64::try_from(next_data.len()).unwrap();
+ let overlap = new_end.saturating_sub(next_start);
+ if overlap == 0 {
+ break;
+ } else if next_end >= new_end {
+ qtrace!(
+ "New frame {}-{} overlaps with next frame by {}, truncating",
+ new_start,
+ new_end,
+ overlap
+ );
+ let truncate_to = new_data.len() - usize::try_from(overlap).unwrap();
+ to_add = &new_data[..truncate_to];
+ break;
+ } else {
+ qtrace!(
+ "New frame {}-{} spans entire next frame {}-{}, replacing",
+ new_start,
+ new_end,
+ next_start,
+ next_end
+ );
+ to_remove.push(next_start);
+ }
+ }
+
+ for start in to_remove {
+ self.data_ranges.remove(&start);
+ }
+
+ if !to_add.is_empty() {
+ if extend {
+ let (_, buf) = self
+ .data_ranges
+ .range_mut((Unbounded, Included(new_start)))
+ .next_back()
+ .unwrap();
+ buf.extend_from_slice(to_add);
+ } else {
+ self.data_ranges.insert(new_start, to_add.to_vec());
+ }
+ }
+ }
+
+ /// Are any bytes readable?
+ pub fn data_ready(&self) -> bool {
+ self.data_ranges
+ .keys()
+ .next()
+ .map_or(false, |&start| start <= self.retired)
+ }
+
+ /// How many bytes are readable?
+ fn bytes_ready(&self) -> usize {
+ let mut prev_end = self.retired;
+ self.data_ranges
+ .iter()
+ .map(|(start_offset, data)| {
+ // All ranges don't overlap but we could have partially
+ // retired some of the first entry's data.
+ let data_len = data.len() as u64 - self.retired.saturating_sub(*start_offset);
+ (start_offset, data_len)
+ })
+ .take_while(|(start_offset, data_len)| {
+ if **start_offset <= prev_end {
+ prev_end += data_len;
+ true
+ } else {
+ false
+ }
+ })
+ .map(|(_, data_len)| data_len as usize)
+ .sum()
+ }
+
+ /// Bytes read by the application.
+ fn retired(&self) -> u64 {
+ self.retired
+ }
+
+ /// Data bytes buffered. Could be more than bytes_readable if there are
+ /// ranges missing.
+ fn buffered(&self) -> u64 {
+ self.data_ranges
+ .iter()
+ .map(|(&start, data)| data.len() as u64 - (self.retired.saturating_sub(start)))
+ .sum()
+ }
+
+ /// Copy received data (if any) into the buffer. Returns bytes copied.
+ fn read(&mut self, buf: &mut [u8]) -> usize {
+ qtrace!("Reading {} bytes, {} available", buf.len(), self.buffered());
+ let mut copied = 0;
+
+ for (&range_start, range_data) in &mut self.data_ranges {
+ let mut keep = false;
+ if self.retired >= range_start {
+ // Frame data has new contiguous bytes.
+ let copy_offset =
+ usize::try_from(max(range_start, self.retired) - range_start).unwrap();
+ assert!(range_data.len() >= copy_offset);
+ let available = range_data.len() - copy_offset;
+ let space = buf.len() - copied;
+ let copy_bytes = if available > space {
+ keep = true;
+ space
+ } else {
+ available
+ };
+
+ if copy_bytes > 0 {
+ let copy_slc = &range_data[copy_offset..copy_offset + copy_bytes];
+ buf[copied..copied + copy_bytes].copy_from_slice(copy_slc);
+ copied += copy_bytes;
+ self.retired += u64::try_from(copy_bytes).unwrap();
+ }
+ } else {
+ // The data in the buffer isn't contiguous.
+ keep = true;
+ }
+ if keep {
+ let mut keep = self.data_ranges.split_off(&range_start);
+ mem::swap(&mut self.data_ranges, &mut keep);
+ return copied;
+ }
+ }
+
+ self.data_ranges.clear();
+ copied
+ }
+
+ /// Extend the given Vector with any available data.
+ pub fn read_to_end(&mut self, buf: &mut Vec<u8>) -> usize {
+ let orig_len = buf.len();
+ buf.resize(orig_len + self.bytes_ready(), 0);
+ self.read(&mut buf[orig_len..])
+ }
+
+ fn highest_seen_offset(&self) -> u64 {
+ let maybe_ooo_last = self
+ .data_ranges
+ .iter()
+ .next_back()
+ .map(|(start, data)| *start + data.len() as u64);
+ maybe_ooo_last.unwrap_or(self.retired)
+ }
+}
+
+/// QUIC receiving states, based on -transport 3.2.
+#[derive(Debug)]
+#[allow(dead_code)]
+// Because a dead_code warning is easier than clippy::unused_self, see https://github.com/rust-lang/rust/issues/68408
+enum RecvStreamState {
+ Recv {
+ recv_buf: RxStreamOrderer,
+ max_bytes: u64, // Maximum size of recv_buf
+ max_stream_data: u64,
+ },
+ SizeKnown {
+ recv_buf: RxStreamOrderer,
+ final_size: u64,
+ },
+ DataRecvd {
+ recv_buf: RxStreamOrderer,
+ },
+ DataRead,
+ ResetRecvd,
+ // Defined by spec but we don't use it: ResetRead
+}
+
+impl RecvStreamState {
+ fn new(max_bytes: u64) -> Self {
+ Self::Recv {
+ recv_buf: RxStreamOrderer::new(),
+ max_bytes,
+ max_stream_data: max_bytes,
+ }
+ }
+
+ fn name(&self) -> &str {
+ match self {
+ Self::Recv { .. } => "Recv",
+ Self::SizeKnown { .. } => "SizeKnown",
+ Self::DataRecvd { .. } => "DataRecvd",
+ Self::DataRead => "DataRead",
+ Self::ResetRecvd => "ResetRecvd",
+ }
+ }
+
+ fn recv_buf(&self) -> Option<&RxStreamOrderer> {
+ match self {
+ Self::Recv { recv_buf, .. }
+ | Self::SizeKnown { recv_buf, .. }
+ | Self::DataRecvd { recv_buf } => Some(recv_buf),
+ Self::DataRead | Self::ResetRecvd => None,
+ }
+ }
+
+ fn final_size(&self) -> Option<u64> {
+ match self {
+ Self::SizeKnown { final_size, .. } => Some(*final_size),
+ _ => None,
+ }
+ }
+
+ fn max_stream_data(&self) -> Option<u64> {
+ match self {
+ Self::Recv {
+ max_stream_data, ..
+ } => Some(*max_stream_data),
+ _ => None,
+ }
+ }
+}
+
+/// Implement a QUIC receive stream.
+#[derive(Debug)]
+pub struct RecvStream {
+ stream_id: StreamId,
+ state: RecvStreamState,
+ flow_mgr: Rc<RefCell<FlowMgr>>,
+ conn_events: ConnectionEvents,
+}
+
+impl RecvStream {
+ pub fn new(
+ stream_id: StreamId,
+ max_stream_data: u64,
+ flow_mgr: Rc<RefCell<FlowMgr>>,
+ conn_events: ConnectionEvents,
+ ) -> Self {
+ Self {
+ stream_id,
+ state: RecvStreamState::new(max_stream_data),
+ flow_mgr,
+ conn_events,
+ }
+ }
+
+ fn set_state(&mut self, new_state: RecvStreamState) {
+ debug_assert_ne!(
+ mem::discriminant(&self.state),
+ mem::discriminant(&new_state)
+ );
+ qtrace!(
+ "RecvStream {} state {} -> {}",
+ self.stream_id.as_u64(),
+ self.state.name(),
+ new_state.name()
+ );
+
+ if let RecvStreamState::Recv { .. } = &self.state {
+ self.flow_mgr
+ .borrow_mut()
+ .clear_max_stream_data(self.stream_id)
+ }
+
+ if let RecvStreamState::DataRead = new_state {
+ self.conn_events.recv_stream_complete(self.stream_id);
+ }
+
+ self.state = new_state;
+ }
+
+ pub fn inbound_stream_frame(&mut self, fin: bool, offset: u64, data: &[u8]) -> Res<()> {
+ // We should post a DataReadable event only once when we change from no-data-ready to
+ // data-ready. Therefore remember the state before processing a new frame.
+ let already_data_ready = self.data_ready();
+ let new_end = offset + u64::try_from(data.len()).unwrap();
+
+ // Send final size errors even if stream is closed
+ if let Some(final_size) = self.state.final_size() {
+ if new_end > final_size || (fin && new_end != final_size) {
+ return Err(Error::FinalSizeError);
+ }
+ }
+
+ match &mut self.state {
+ RecvStreamState::Recv {
+ recv_buf,
+ max_stream_data,
+ ..
+ } => {
+ if new_end > *max_stream_data {
+ qtrace!("Stream RX window {} exceeded: {}", max_stream_data, new_end);
+ return Err(Error::FlowControlError);
+ }
+
+ if fin {
+ let final_size = offset + data.len() as u64;
+ if final_size < recv_buf.highest_seen_offset() {
+ return Err(Error::FinalSizeError);
+ }
+ recv_buf.inbound_frame(offset, data);
+
+ let buf = mem::replace(recv_buf, RxStreamOrderer::new());
+ if final_size == buf.retired() + buf.bytes_ready() as u64 {
+ self.set_state(RecvStreamState::DataRecvd { recv_buf: buf });
+ } else {
+ self.set_state(RecvStreamState::SizeKnown {
+ recv_buf: buf,
+ final_size,
+ });
+ }
+ } else {
+ recv_buf.inbound_frame(offset, data);
+ }
+ }
+ RecvStreamState::SizeKnown {
+ recv_buf,
+ final_size,
+ } => {
+ recv_buf.inbound_frame(offset, data);
+ if *final_size == recv_buf.retired() + recv_buf.bytes_ready() as u64 {
+ let buf = mem::replace(recv_buf, RxStreamOrderer::new());
+ self.set_state(RecvStreamState::DataRecvd { recv_buf: buf });
+ }
+ }
+ RecvStreamState::DataRecvd { .. }
+ | RecvStreamState::DataRead
+ | RecvStreamState::ResetRecvd => {
+ qtrace!("data received when we are in state {}", self.state.name())
+ }
+ }
+
+ if !already_data_ready && (self.data_ready() || self.needs_to_inform_app_about_fin()) {
+ self.conn_events.recv_stream_readable(self.stream_id)
+ }
+
+ Ok(())
+ }
+
+ pub fn reset(&mut self, application_error_code: AppError) {
+ match self.state {
+ RecvStreamState::Recv { .. } | RecvStreamState::SizeKnown { .. } => {
+ self.conn_events
+ .recv_stream_reset(self.stream_id, application_error_code);
+ self.set_state(RecvStreamState::ResetRecvd);
+ }
+ _ => {
+ // Ignore reset if in DataRecvd, DataRead, or ResetRecvd
+ }
+ }
+ }
+
+ /// If we should tell the sender they have more credit, return an offset
+ pub fn maybe_send_flowc_update(&mut self) {
+ // Only ever needed if actively receiving and not in SizeKnown state
+ if let RecvStreamState::Recv {
+ max_bytes,
+ max_stream_data,
+ recv_buf,
+ } = &mut self.state
+ {
+ // Algo: send an update if app has consumed more than half
+ // the data in the current window
+ // TODO(agrover@mozilla.com): This algo is not great but
+ // should prevent Silly Window Syndrome. Spec refers to using
+ // highest seen offset somehow? RTT maybe?
+ let maybe_new_max = recv_buf.retired() + *max_bytes;
+ if maybe_new_max > (*max_bytes / 2) + *max_stream_data {
+ *max_stream_data = maybe_new_max;
+ self.flow_mgr
+ .borrow_mut()
+ .max_stream_data(self.stream_id, maybe_new_max)
+ }
+ }
+ }
+
+ pub fn max_stream_data(&self) -> Option<u64> {
+ self.state.max_stream_data()
+ }
+
+ pub fn is_terminal(&self) -> bool {
+ matches!(
+ self.state,
+ RecvStreamState::ResetRecvd | RecvStreamState::DataRead
+ )
+ }
+
+ // App got all data but did not get the fin signal.
+ fn needs_to_inform_app_about_fin(&self) -> bool {
+ matches!(self.state, RecvStreamState::DataRecvd { .. })
+ }
+
+ fn data_ready(&self) -> bool {
+ self.state
+ .recv_buf()
+ .map_or(false, RxStreamOrderer::data_ready)
+ }
+
+ /// # Errors
+ /// `NoMoreData` if data and fin bit were previously read by the application.
+ pub fn read(&mut self, buf: &mut [u8]) -> Res<(usize, bool)> {
+ let res = match &mut self.state {
+ RecvStreamState::Recv { recv_buf, .. }
+ | RecvStreamState::SizeKnown { recv_buf, .. } => Ok((recv_buf.read(buf), false)),
+ RecvStreamState::DataRecvd { recv_buf } => {
+ let bytes_read = recv_buf.read(buf);
+ let fin_read = recv_buf.buffered() == 0;
+ if fin_read {
+ self.set_state(RecvStreamState::DataRead);
+ }
+ Ok((bytes_read, fin_read))
+ }
+ RecvStreamState::DataRead | RecvStreamState::ResetRecvd => Err(Error::NoMoreData),
+ };
+ self.maybe_send_flowc_update();
+ res
+ }
+
+ pub fn stop_sending(&mut self, err: AppError) {
+ qtrace!("stop_sending called when in state {}", self.state.name());
+ match &self.state {
+ RecvStreamState::Recv { .. } | RecvStreamState::SizeKnown { .. } => {
+ self.set_state(RecvStreamState::ResetRecvd);
+ self.flow_mgr.borrow_mut().stop_sending(self.stream_id, err)
+ }
+ RecvStreamState::DataRecvd { .. } => self.set_state(RecvStreamState::DataRead),
+ RecvStreamState::DataRead | RecvStreamState::ResetRecvd => {
+ // Already in terminal state
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::frame::Frame;
+ use std::ops::Range;
+
+ fn recv_ranges(ranges: &[Range<u64>], available: usize) {
+ const ZEROES: &[u8] = &[0; 100];
+ qtrace!("recv_ranges {:?}", ranges);
+
+ let mut s = RxStreamOrderer::default();
+ for r in ranges {
+ let data = &ZEROES[..usize::try_from(r.end - r.start).unwrap()];
+ s.inbound_frame(r.start, data);
+ }
+
+ let mut buf = [0xff; 100];
+ let mut total_recvd = 0;
+ loop {
+ let recvd = s.read(&mut buf[..]);
+ qtrace!("recv_ranges read {}", recvd);
+ total_recvd += recvd;
+ if recvd == 0 {
+ assert_eq!(total_recvd, available);
+ break;
+ }
+ }
+ }
+
+ #[test]
+ fn recv_noncontiguous() {
+ // Non-contiguous with the start, no data available.
+ recv_ranges(&[10..20], 0);
+ }
+
+ /// Overlaps with the start of a 10..20 range of bytes.
+ #[test]
+ fn recv_overlap_start() {
+ // Overlap the start, with a larger new value.
+ // More overlap than not.
+ recv_ranges(&[10..20, 4..18, 0..4], 20);
+ // Overlap the start, with a larger new value.
+ // Less overlap than not.
+ recv_ranges(&[10..20, 2..15, 0..2], 20);
+ // Overlap the start, with a smaller new value.
+ // More overlap than not.
+ recv_ranges(&[10..20, 8..14, 0..8], 20);
+ // Overlap the start, with a smaller new value.
+ // Less overlap than not.
+ recv_ranges(&[10..20, 6..13, 0..6], 20);
+
+ // Again with some of the first range split in two.
+ recv_ranges(&[10..11, 11..20, 4..18, 0..4], 20);
+ recv_ranges(&[10..11, 11..20, 2..15, 0..2], 20);
+ recv_ranges(&[10..11, 11..20, 8..14, 0..8], 20);
+ recv_ranges(&[10..11, 11..20, 6..13, 0..6], 20);
+
+ // Again with a gap in the first range.
+ recv_ranges(&[10..11, 12..20, 4..18, 0..4], 20);
+ recv_ranges(&[10..11, 12..20, 2..15, 0..2], 20);
+ recv_ranges(&[10..11, 12..20, 8..14, 0..8], 20);
+ recv_ranges(&[10..11, 12..20, 6..13, 0..6], 20);
+ }
+
+ /// Overlaps with the end of a 10..20 range of bytes.
+ #[test]
+ fn recv_overlap_end() {
+ // Overlap the end, with a larger new value.
+ // More overlap than not.
+ recv_ranges(&[10..20, 12..25, 0..10], 25);
+ // Overlap the end, with a larger new value.
+ // Less overlap than not.
+ recv_ranges(&[10..20, 17..33, 0..10], 33);
+ // Overlap the end, with a smaller new value.
+ // More overlap than not.
+ recv_ranges(&[10..20, 15..21, 0..10], 21);
+ // Overlap the end, with a smaller new value.
+ // Less overlap than not.
+ recv_ranges(&[10..20, 17..25, 0..10], 25);
+
+ // Again with some of the first range split in two.
+ recv_ranges(&[10..19, 19..20, 12..25, 0..10], 25);
+ recv_ranges(&[10..19, 19..20, 17..33, 0..10], 33);
+ recv_ranges(&[10..19, 19..20, 15..21, 0..10], 21);
+ recv_ranges(&[10..19, 19..20, 17..25, 0..10], 25);
+
+ // Again with a gap in the first range.
+ recv_ranges(&[10..18, 19..20, 12..25, 0..10], 25);
+ recv_ranges(&[10..18, 19..20, 17..33, 0..10], 33);
+ recv_ranges(&[10..18, 19..20, 15..21, 0..10], 21);
+ recv_ranges(&[10..18, 19..20, 17..25, 0..10], 25);
+ }
+
+ /// Complete overlaps with the start of a 10..20 range of bytes.
+ #[test]
+ fn recv_overlap_complete() {
+ // Complete overlap, more at the end.
+ recv_ranges(&[10..20, 9..23, 0..9], 23);
+ // Complete overlap, more at the start.
+ recv_ranges(&[10..20, 3..23, 0..3], 23);
+ // Complete overlap, to end.
+ recv_ranges(&[10..20, 5..20, 0..5], 20);
+ // Complete overlap, from start.
+ recv_ranges(&[10..20, 10..27, 0..10], 27);
+ // Complete overlap, from 0 and more.
+ recv_ranges(&[10..20, 0..23], 23);
+
+ // Again with the first range split in two.
+ recv_ranges(&[10..14, 14..20, 9..23, 0..9], 23);
+ recv_ranges(&[10..14, 14..20, 3..23, 0..3], 23);
+ recv_ranges(&[10..14, 14..20, 5..20, 0..5], 20);
+ recv_ranges(&[10..14, 14..20, 10..27, 0..10], 27);
+ recv_ranges(&[10..14, 14..20, 0..23], 23);
+
+ // Again with the a gap in the first range.
+ recv_ranges(&[10..13, 14..20, 9..23, 0..9], 23);
+ recv_ranges(&[10..13, 14..20, 3..23, 0..3], 23);
+ recv_ranges(&[10..13, 14..20, 5..20, 0..5], 20);
+ recv_ranges(&[10..13, 14..20, 10..27, 0..10], 27);
+ recv_ranges(&[10..13, 14..20, 0..23], 23);
+ }
+
+ /// An overlap with no new bytes.
+ #[test]
+ fn recv_overlap_duplicate() {
+ recv_ranges(&[10..20, 11..12, 0..10], 20);
+ recv_ranges(&[10..20, 10..15, 0..10], 20);
+ recv_ranges(&[10..20, 14..20, 0..10], 20);
+ // Now with the first range split.
+ recv_ranges(&[10..14, 14..20, 10..15, 0..10], 20);
+ recv_ranges(&[10..15, 16..20, 21..25, 10..25, 0..10], 25);
+ }
+
+ /// Reading exactly one chunk works, when the next chunk starts immediately.
+ #[test]
+ fn stop_reading_at_chunk() {
+ const CHUNK_SIZE: usize = 10;
+ const EXTRA_SIZE: usize = 3;
+ let mut s = RxStreamOrderer::new();
+
+ // Add three chunks.
+ s.inbound_frame(0, &[0; CHUNK_SIZE]);
+ let offset = u64::try_from(CHUNK_SIZE).unwrap();
+ s.inbound_frame(offset, &[0; EXTRA_SIZE]);
+ let offset = u64::try_from(CHUNK_SIZE + EXTRA_SIZE).unwrap();
+ s.inbound_frame(offset, &[0; EXTRA_SIZE]);
+
+ // Read, providing only enough space for the first.
+ let mut buf = vec![0; 100];
+ let count = s.read(&mut buf[..CHUNK_SIZE]);
+ assert_eq!(count, CHUNK_SIZE);
+ let count = s.read(&mut buf[..]);
+ assert_eq!(count, EXTRA_SIZE * 2);
+ }
+
+ #[test]
+ fn recv_overlap_while_reading() {
+ let mut s = RxStreamOrderer::new();
+
+ // Add a chunk
+ s.inbound_frame(0, &[0; 150]);
+ assert_eq!(s.data_ranges.get(&0).unwrap().len(), 150);
+ // Read, providing only enough space for the first 100.
+ let mut buf = [0; 100];
+ let count = s.read(&mut buf[..]);
+ assert_eq!(count, 100);
+ assert_eq!(s.retired, 100);
+
+ // Add a second frame that overlaps.
+ // This shouldn't truncate the first frame, as we're already
+ // Reading from it.
+ s.inbound_frame(120, &[0; 60]);
+ assert_eq!(s.data_ranges.get(&0).unwrap().len(), 180);
+ // Read second part of first frame and all of the second frame
+ let count = s.read(&mut buf[..]);
+ assert_eq!(count, 80);
+ }
+
+ /// Reading exactly one chunk works, when there is a gap.
+ #[test]
+ fn stop_reading_at_gap() {
+ const CHUNK_SIZE: usize = 10;
+ const EXTRA_SIZE: usize = 3;
+ let mut s = RxStreamOrderer::new();
+
+ // Add three chunks.
+ s.inbound_frame(0, &[0; CHUNK_SIZE]);
+ let offset = u64::try_from(CHUNK_SIZE + EXTRA_SIZE).unwrap();
+ s.inbound_frame(offset, &[0; EXTRA_SIZE]);
+
+ // Read, providing only enough space for the first chunk.
+ let mut buf = [0; 100];
+ let count = s.read(&mut buf[..CHUNK_SIZE]);
+ assert_eq!(count, CHUNK_SIZE);
+
+ // Now fill the gap and ensure that everything can be read.
+ let offset = u64::try_from(CHUNK_SIZE).unwrap();
+ s.inbound_frame(offset, &[0; EXTRA_SIZE]);
+ let count = s.read(&mut buf[..]);
+ assert_eq!(count, EXTRA_SIZE * 2);
+ }
+
+ /// Reading exactly one chunk works, when there is a gap.
+ #[test]
+ fn stop_reading_in_chunk() {
+ const CHUNK_SIZE: usize = 10;
+ const EXTRA_SIZE: usize = 3;
+ let mut s = RxStreamOrderer::new();
+
+ // Add two chunks.
+ s.inbound_frame(0, &[0; CHUNK_SIZE]);
+ let offset = u64::try_from(CHUNK_SIZE).unwrap();
+ s.inbound_frame(offset, &[0; EXTRA_SIZE]);
+
+ // Read, providing only enough space for some of the first chunk.
+ let mut buf = [0; 100];
+ let count = s.read(&mut buf[..CHUNK_SIZE - EXTRA_SIZE]);
+ assert_eq!(count, CHUNK_SIZE - EXTRA_SIZE);
+
+ let count = s.read(&mut buf[..]);
+ assert_eq!(count, EXTRA_SIZE * 2);
+ }
+
+ /// Read one byte at a time.
+ #[test]
+ fn read_byte_at_a_time() {
+ const CHUNK_SIZE: usize = 10;
+ const EXTRA_SIZE: usize = 3;
+ let mut s = RxStreamOrderer::new();
+
+ // Add two chunks.
+ s.inbound_frame(0, &[0; CHUNK_SIZE]);
+ let offset = u64::try_from(CHUNK_SIZE).unwrap();
+ s.inbound_frame(offset, &[0; EXTRA_SIZE]);
+
+ let mut buf = [0; 1];
+ for _ in 0..CHUNK_SIZE + EXTRA_SIZE {
+ let count = s.read(&mut buf[..]);
+ assert_eq!(count, 1);
+ }
+ assert_eq!(0, s.read(&mut buf[..]));
+ }
+
+ #[test]
+ fn stream_rx() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ let conn_events = ConnectionEvents::default();
+
+ let mut s = RecvStream::new(StreamId::from(567), 1024, Rc::clone(&flow_mgr), conn_events);
+
+ // test receiving a contig frame and reading it works
+ s.inbound_stream_frame(false, 0, &[1; 10]).unwrap();
+ assert_eq!(s.data_ready(), true);
+ let mut buf = vec![0u8; 100];
+ assert_eq!(s.read(&mut buf).unwrap(), (10, false));
+ assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
+ assert_eq!(s.state.recv_buf().unwrap().buffered(), 0);
+
+ // test receiving a noncontig frame
+ s.inbound_stream_frame(false, 12, &[2; 12]).unwrap();
+ assert_eq!(s.data_ready(), false);
+ assert_eq!(s.read(&mut buf).unwrap(), (0, false));
+ assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
+ assert_eq!(s.state.recv_buf().unwrap().buffered(), 12);
+
+ // another frame that overlaps the first
+ s.inbound_stream_frame(false, 14, &[3; 8]).unwrap();
+ assert_eq!(s.data_ready(), false);
+ assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
+ assert_eq!(s.state.recv_buf().unwrap().buffered(), 12);
+
+ // fill in the gap, but with a FIN
+ s.inbound_stream_frame(true, 10, &[4; 6]).unwrap_err();
+ assert_eq!(s.data_ready(), false);
+ assert_eq!(s.read(&mut buf).unwrap(), (0, false));
+ assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
+ assert_eq!(s.state.recv_buf().unwrap().buffered(), 12);
+
+ // fill in the gap
+ s.inbound_stream_frame(false, 10, &[5; 10]).unwrap();
+ assert_eq!(s.data_ready(), true);
+ assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
+ assert_eq!(s.state.recv_buf().unwrap().buffered(), 14);
+
+ // a legit FIN
+ s.inbound_stream_frame(true, 24, &[6; 18]).unwrap();
+ assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
+ assert_eq!(s.state.recv_buf().unwrap().buffered(), 32);
+ assert_eq!(s.data_ready(), true);
+ assert_eq!(s.read(&mut buf).unwrap(), (32, true));
+
+ // Stream now no longer readable (is in DataRead state)
+ s.read(&mut buf).unwrap_err();
+ }
+
+ fn check_chunks(s: &mut RxStreamOrderer, expected: &[(u64, usize)]) {
+ assert_eq!(s.data_ranges.len(), expected.len());
+ for ((start, buf), (expected_start, expected_len)) in s.data_ranges.iter().zip(expected) {
+ assert_eq!((*start, buf.len()), (*expected_start, *expected_len));
+ }
+ }
+
+ // Test deduplication when the new data is at the end.
+ #[test]
+ fn stream_rx_dedupe_tail() {
+ let mut s = RxStreamOrderer::new();
+
+ s.inbound_frame(0, &[1; 6]);
+ check_chunks(&mut s, &[(0, 6)]);
+
+ // New data that overlaps entirely (starting from the head), is ignored.
+ s.inbound_frame(0, &[2; 3]);
+ check_chunks(&mut s, &[(0, 6)]);
+
+ // New data that overlaps at the tail has any new data appended.
+ s.inbound_frame(2, &[3; 6]);
+ check_chunks(&mut s, &[(0, 8)]);
+
+ // New data that overlaps entirely (up to the tail), is ignored.
+ s.inbound_frame(4, &[4; 4]);
+ check_chunks(&mut s, &[(0, 8)]);
+
+ // New data that overlaps, starting from the beginning is appended too.
+ s.inbound_frame(0, &[5; 10]);
+ check_chunks(&mut s, &[(0, 10)]);
+
+ // New data that is entirely subsumed is ignored.
+ s.inbound_frame(2, &[6; 2]);
+ check_chunks(&mut s, &[(0, 10)]);
+
+ let mut buf = [0; 16];
+ assert_eq!(s.read(&mut buf[..]), 10);
+ assert_eq!(buf[..10], [1, 1, 1, 1, 1, 1, 3, 3, 5, 5]);
+ }
+
+ /// When chunks are added before existing data, they aren't merged.
+ #[test]
+ fn stream_rx_dedupe_head() {
+ let mut s = RxStreamOrderer::new();
+
+ s.inbound_frame(1, &[6; 6]);
+ check_chunks(&mut s, &[(1, 6)]);
+
+ // Insertion before an existing chunk causes truncation of the new chunk.
+ s.inbound_frame(0, &[7; 6]);
+ check_chunks(&mut s, &[(0, 1), (1, 6)]);
+
+ // Perfect overlap with existing slices has no effect.
+ s.inbound_frame(0, &[8; 7]);
+ check_chunks(&mut s, &[(0, 1), (1, 6)]);
+
+ let mut buf = [0; 16];
+ assert_eq!(s.read(&mut buf[..]), 7);
+ assert_eq!(buf[..7], [7, 6, 6, 6, 6, 6, 6]);
+ }
+
+ #[test]
+ fn stream_rx_dedupe_new_tail() {
+ let mut s = RxStreamOrderer::new();
+
+ s.inbound_frame(1, &[6; 6]);
+ check_chunks(&mut s, &[(1, 6)]);
+
+ // Insertion before an existing chunk causes truncation of the new chunk.
+ s.inbound_frame(0, &[7; 6]);
+ check_chunks(&mut s, &[(0, 1), (1, 6)]);
+
+ // New data at the end causes the tail to be added to the first chunk,
+ // replacing later chunks entirely.
+ s.inbound_frame(0, &[9; 8]);
+ check_chunks(&mut s, &[(0, 8)]);
+
+ let mut buf = [0; 16];
+ assert_eq!(s.read(&mut buf[..]), 8);
+ assert_eq!(buf[..8], [7, 9, 9, 9, 9, 9, 9, 9]);
+ }
+
+ #[test]
+ fn stream_rx_dedupe_replace() {
+ let mut s = RxStreamOrderer::new();
+
+ s.inbound_frame(2, &[6; 6]);
+ check_chunks(&mut s, &[(2, 6)]);
+
+ // Insertion before an existing chunk causes truncation of the new chunk.
+ s.inbound_frame(1, &[7; 6]);
+ check_chunks(&mut s, &[(1, 1), (2, 6)]);
+
+ // New data at the start and end replaces all the slices.
+ s.inbound_frame(0, &[9; 10]);
+ check_chunks(&mut s, &[(0, 10)]);
+
+ let mut buf = [0; 16];
+ assert_eq!(s.read(&mut buf[..]), 10);
+ assert_eq!(buf[..10], [9; 10]);
+ }
+
+ #[test]
+ fn trim_retired() {
+ let mut s = RxStreamOrderer::new();
+
+ let mut buf = [0; 18];
+ s.inbound_frame(0, &[1; 10]);
+
+ // Partially read slices are retained.
+ assert_eq!(s.read(&mut buf[..6]), 6);
+ check_chunks(&mut s, &[(0, 10)]);
+
+ // Partially read slices are kept and so are added to.
+ s.inbound_frame(3, &buf[..10]);
+ check_chunks(&mut s, &[(0, 13)]);
+
+ // Wholly read pieces are dropped.
+ assert_eq!(s.read(&mut buf[..]), 7);
+ assert!(s.data_ranges.is_empty());
+
+ // New data that overlaps with retired data is trimmed.
+ s.inbound_frame(0, &buf[..]);
+ check_chunks(&mut s, &[(13, 5)]);
+ }
+
+ #[test]
+ fn stream_flowc_update() {
+ let flow_mgr = Rc::default();
+ let conn_events = ConnectionEvents::default();
+
+ let frame1 = vec![0; RECV_BUFFER_SIZE];
+
+ let mut s = RecvStream::new(
+ StreamId::from(4),
+ RX_STREAM_DATA_WINDOW,
+ Rc::clone(&flow_mgr),
+ conn_events,
+ );
+
+ let mut buf = vec![0u8; RECV_BUFFER_SIZE + 100]; // Make it overlarge
+
+ s.maybe_send_flowc_update();
+ assert_eq!(s.flow_mgr.borrow().peek(), None);
+ s.inbound_stream_frame(false, 0, &frame1).unwrap();
+ s.maybe_send_flowc_update();
+ assert_eq!(s.flow_mgr.borrow().peek(), None);
+ assert_eq!(s.read(&mut buf).unwrap(), (RECV_BUFFER_SIZE, false));
+ assert_eq!(s.data_ready(), false);
+ s.maybe_send_flowc_update();
+
+ // flow msg generated!
+ assert!(s.flow_mgr.borrow().peek().is_some());
+
+ // consume it
+ s.flow_mgr.borrow_mut().next().unwrap();
+
+ // it should be gone
+ s.maybe_send_flowc_update();
+ assert_eq!(s.flow_mgr.borrow().peek(), None);
+ }
+
+ #[test]
+ fn stream_max_stream_data() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ let conn_events = ConnectionEvents::default();
+
+ let frame1 = vec![0; RECV_BUFFER_SIZE];
+ let mut s = RecvStream::new(
+ StreamId::from(67),
+ RX_STREAM_DATA_WINDOW,
+ Rc::clone(&flow_mgr),
+ conn_events,
+ );
+
+ s.maybe_send_flowc_update();
+ assert_eq!(s.flow_mgr.borrow().peek(), None);
+ s.inbound_stream_frame(false, 0, &frame1).unwrap();
+ s.inbound_stream_frame(false, RX_STREAM_DATA_WINDOW, &[1; 1])
+ .unwrap_err();
+ }
+
+ #[test]
+ fn stream_orderer_bytes_ready() {
+ let mut rx_ord = RxStreamOrderer::new();
+
+ rx_ord.inbound_frame(0, &[1; 6]);
+ assert_eq!(rx_ord.bytes_ready(), 6);
+ assert_eq!(rx_ord.buffered(), 6);
+ assert_eq!(rx_ord.retired(), 0);
+
+ // read some so there's an offset into the first frame
+ let mut buf = [0u8; 10];
+ rx_ord.read(&mut buf[..2]);
+ assert_eq!(rx_ord.bytes_ready(), 4);
+ assert_eq!(rx_ord.buffered(), 4);
+ assert_eq!(rx_ord.retired(), 2);
+
+ // an overlapping frame
+ rx_ord.inbound_frame(5, &[2; 6]);
+ assert_eq!(rx_ord.bytes_ready(), 9);
+ assert_eq!(rx_ord.buffered(), 9);
+ assert_eq!(rx_ord.retired(), 2);
+
+ // a noncontig frame
+ rx_ord.inbound_frame(20, &[3; 6]);
+ assert_eq!(rx_ord.bytes_ready(), 9);
+ assert_eq!(rx_ord.buffered(), 15);
+ assert_eq!(rx_ord.retired(), 2);
+
+ // an old frame
+ rx_ord.inbound_frame(0, &[4; 2]);
+ assert_eq!(rx_ord.bytes_ready(), 9);
+ assert_eq!(rx_ord.buffered(), 15);
+ assert_eq!(rx_ord.retired(), 2);
+ }
+
+ #[test]
+ fn no_stream_flowc_event_after_exiting_recv() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ let conn_events = ConnectionEvents::default();
+
+ let frame1 = vec![0; RECV_BUFFER_SIZE];
+ let stream_id = StreamId::from(67);
+ let mut s = RecvStream::new(
+ stream_id,
+ RX_STREAM_DATA_WINDOW,
+ Rc::clone(&flow_mgr),
+ conn_events,
+ );
+
+ s.inbound_stream_frame(false, 0, &frame1).unwrap();
+ flow_mgr.borrow_mut().max_stream_data(stream_id, 100);
+ assert!(matches!(s.flow_mgr.borrow().peek().unwrap(), Frame::MaxStreamData{..}));
+ s.inbound_stream_frame(true, RX_STREAM_DATA_WINDOW, &[])
+ .unwrap();
+ assert!(matches!(s.flow_mgr.borrow().peek(), None));
+ }
+
+ #[test]
+ fn resend_flowc_if_lost() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ let conn_events = ConnectionEvents::default();
+
+ let frame1 = &[0; RECV_BUFFER_SIZE];
+ let stream_id = StreamId::from(67);
+ let mut s = RecvStream::new(
+ stream_id,
+ RX_STREAM_DATA_WINDOW,
+ Rc::clone(&flow_mgr),
+ conn_events,
+ );
+
+ // A flow control update is queued
+ s.inbound_stream_frame(false, 0, frame1).unwrap();
+ flow_mgr.borrow_mut().max_stream_data(stream_id, 100);
+ // Generates frame
+ assert!(matches!(
+ s.flow_mgr.borrow_mut().next().unwrap(),
+ Frame::MaxStreamData { .. }
+ ));
+ // Nothing else queued
+ assert!(matches!(s.flow_mgr.borrow().peek(), None));
+ // Asking for another one won't get you one
+ s.maybe_send_flowc_update();
+ assert!(matches!(s.flow_mgr.borrow().peek(), None));
+ // But if lost, another frame is generated
+ flow_mgr.borrow_mut().max_stream_data(stream_id, 100);
+ assert!(matches!(s.flow_mgr.borrow_mut().next().unwrap(), Frame::MaxStreamData{..}));
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/send_stream.rs b/third_party/rust/neqo-transport/src/send_stream.rs
new file mode 100644
index 0000000000..b6b9eea5f5
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/send_stream.rs
@@ -0,0 +1,1746 @@
+// 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.
+
+// Buffering data to send until it is acked.
+
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::collections::{BTreeMap, VecDeque};
+use std::convert::{TryFrom, TryInto};
+use std::mem;
+use std::rc::Rc;
+
+use indexmap::IndexMap;
+use smallvec::SmallVec;
+
+use neqo_common::{qdebug, qerror, qinfo, qtrace, Encoder};
+
+use crate::events::ConnectionEvents;
+use crate::flow_mgr::FlowMgr;
+use crate::frame::Frame;
+use crate::packet::PacketBuilder;
+use crate::recovery::RecoveryToken;
+use crate::stats::FrameStats;
+use crate::stream_id::StreamId;
+use crate::{AppError, Error, Res};
+
+pub const SEND_BUFFER_SIZE: usize = 0x10_0000; // 1 MiB
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+enum RangeState {
+ Sent,
+ Acked,
+}
+
+/// Track ranges in the stream as sent or acked. Acked implies sent. Not in a
+/// range implies needing-to-be-sent, either initially or as a retransmission.
+#[derive(Debug, Default, PartialEq)]
+struct RangeTracker {
+ // offset, (len, RangeState). Use u64 for len because ranges can exceed 32bits.
+ used: BTreeMap<u64, (u64, RangeState)>,
+}
+
+impl RangeTracker {
+ fn highest_offset(&self) -> u64 {
+ self.used
+ .range(..)
+ .next_back()
+ .map_or(0, |(k, (v, _))| *k + *v)
+ }
+
+ fn acked_from_zero(&self) -> u64 {
+ self.used
+ .get(&0)
+ .filter(|(_, state)| *state == RangeState::Acked)
+ .map_or(0, |(v, _)| *v)
+ }
+
+ /// Find the first unmarked range. If all are contiguous, this will return
+ /// (highest_offset(), None).
+ fn first_unmarked_range(&self) -> (u64, Option<u64>) {
+ let mut prev_end = 0;
+
+ for (cur_off, (cur_len, _)) in &self.used {
+ if prev_end == *cur_off {
+ prev_end = cur_off + cur_len;
+ } else {
+ return (prev_end, Some(cur_off - prev_end));
+ }
+ }
+ (prev_end, None)
+ }
+
+ /// Turn one range into a list of subranges that align with existing
+ /// ranges.
+ /// Check impermissible overlaps in subregions: Sent cannot overwrite Acked.
+ //
+ // e.g. given N is new and ABC are existing:
+ // NNNNNNNNNNNNNNNN
+ // AAAAA BBBCCCCC ...then we want 5 chunks:
+ // 1122222333444555
+ //
+ // but also if we have this:
+ // NNNNNNNNNNNNNNNN
+ // AAAAAAAAAA BBBB ...then break existing A and B ranges up:
+ //
+ // 1111111122222233
+ // aaAAAAAAAA BBbb
+ //
+ // Doing all this work up front should make handling each chunk much
+ // easier.
+ fn chunk_range_on_edges(
+ &mut self,
+ new_off: u64,
+ new_len: u64,
+ new_state: RangeState,
+ ) -> Vec<(u64, u64, RangeState)> {
+ let mut tmp_off = new_off;
+ let mut tmp_len = new_len;
+ let mut v = Vec::new();
+
+ // cut previous overlapping range if needed
+ let prev = self.used.range_mut(..tmp_off).next_back();
+ if let Some((prev_off, (prev_len, prev_state))) = prev {
+ let prev_state = *prev_state;
+ let overlap = (*prev_off + *prev_len).saturating_sub(new_off);
+ *prev_len -= overlap;
+ if overlap > 0 {
+ self.used.insert(new_off, (overlap, prev_state));
+ }
+ }
+
+ let mut last_existing_remaining = None;
+ for (off, (len, state)) in self.used.range(tmp_off..tmp_off + tmp_len) {
+ // Create chunk for "overhang" before an existing range
+ if tmp_off < *off {
+ let sub_len = off - tmp_off;
+ v.push((tmp_off, sub_len, new_state));
+ tmp_off += sub_len;
+ tmp_len -= sub_len;
+ }
+
+ // Create chunk to match existing range
+ let sub_len = min(*len, tmp_len);
+ let remaining_len = len - sub_len;
+ if new_state == RangeState::Sent && *state == RangeState::Acked {
+ qinfo!(
+ "Attempted to downgrade overlapping range Acked range {}-{} with Sent {}-{}",
+ off,
+ len,
+ new_off,
+ new_len
+ );
+ } else {
+ v.push((tmp_off, sub_len, new_state));
+ }
+ tmp_off += sub_len;
+ tmp_len -= sub_len;
+
+ if remaining_len > 0 {
+ last_existing_remaining = Some((*off, sub_len, remaining_len, *state));
+ }
+ }
+
+ // Maybe break last existing range in two so that a final chunk will
+ // have the same length as an existing range entry
+ if let Some((off, sub_len, remaining_len, state)) = last_existing_remaining {
+ *self.used.get_mut(&off).expect("must be there") = (sub_len, state);
+ self.used.insert(off + sub_len, (remaining_len, state));
+ }
+
+ // Create final chunk if anything remains of the new range
+ if tmp_len > 0 {
+ v.push((tmp_off, tmp_len, new_state))
+ }
+
+ v
+ }
+
+ /// Merge contiguous Acked ranges into the first entry (0). This range may
+ /// be dropped from the send buffer.
+ fn coalesce_acked_from_zero(&mut self) {
+ let acked_range_from_zero = self
+ .used
+ .get_mut(&0)
+ .filter(|(_, state)| *state == RangeState::Acked)
+ .map(|(len, _)| *len);
+
+ if let Some(len_from_zero) = acked_range_from_zero {
+ let mut to_remove = SmallVec::<[_; 8]>::new();
+
+ let mut new_len_from_zero = len_from_zero;
+
+ // See if there's another Acked range entry contiguous to this one
+ while let Some((next_len, _)) = self
+ .used
+ .get(&new_len_from_zero)
+ .filter(|(_, state)| *state == RangeState::Acked)
+ {
+ to_remove.push(new_len_from_zero);
+ new_len_from_zero += *next_len;
+ }
+
+ if len_from_zero != new_len_from_zero {
+ self.used.get_mut(&0).expect("must be there").0 = new_len_from_zero;
+ }
+
+ for val in to_remove {
+ self.used.remove(&val);
+ }
+ }
+ }
+
+ fn mark_range(&mut self, off: u64, len: usize, state: RangeState) {
+ if len == 0 {
+ qinfo!("mark 0-length range at {}", off);
+ return;
+ }
+
+ let subranges = self.chunk_range_on_edges(off, len as u64, state);
+
+ for (sub_off, sub_len, sub_state) in subranges {
+ self.used.insert(sub_off, (sub_len, sub_state));
+ }
+
+ self.coalesce_acked_from_zero()
+ }
+
+ fn unmark_range(&mut self, off: u64, len: usize) {
+ if len == 0 {
+ qdebug!("unmark 0-length range at {}", off);
+ return;
+ }
+
+ let len = u64::try_from(len).unwrap();
+ let end_off = off + len;
+
+ let mut to_remove = SmallVec::<[_; 8]>::new();
+ let mut to_add = None;
+
+ // Walk backwards through possibly affected existing ranges
+ for (cur_off, (cur_len, cur_state)) in self.used.range_mut(..off + len).rev() {
+ // Maybe fixup range preceding the removed range
+ if *cur_off < off {
+ // Check for overlap
+ if *cur_off + *cur_len > off {
+ if *cur_state == RangeState::Acked {
+ qdebug!(
+ "Attempted to unmark Acked range {}-{} with unmark_range {}-{}",
+ cur_off,
+ cur_len,
+ off,
+ off + len
+ );
+ } else {
+ *cur_len = off - cur_off;
+ }
+ }
+ break;
+ }
+
+ if *cur_state == RangeState::Acked {
+ qdebug!(
+ "Attempted to unmark Acked range {}-{} with unmark_range {}-{}",
+ cur_off,
+ cur_len,
+ off,
+ off + len
+ );
+ continue;
+ }
+
+ // Add a new range for old subrange extending beyond
+ // to-be-unmarked range
+ let cur_end_off = cur_off + *cur_len;
+ if cur_end_off > end_off {
+ let new_cur_off = off + len;
+ let new_cur_len = cur_end_off - end_off;
+ assert_eq!(to_add, None);
+ to_add = Some((new_cur_off, new_cur_len, *cur_state));
+ }
+
+ to_remove.push(*cur_off);
+ }
+
+ for remove_off in to_remove {
+ self.used.remove(&remove_off);
+ }
+
+ if let Some((new_cur_off, new_cur_len, cur_state)) = to_add {
+ self.used.insert(new_cur_off, (new_cur_len, cur_state));
+ }
+ }
+
+ /// Unmark all sent ranges.
+ pub fn unmark_sent(&mut self) {
+ self.unmark_range(0, usize::try_from(self.highest_offset()).unwrap());
+ }
+}
+
+/// Buffer to contain queued bytes and track their state.
+#[derive(Debug, Default, PartialEq)]
+pub struct TxBuffer {
+ retired: u64, // contig acked bytes, no longer in buffer
+ send_buf: VecDeque<u8>, // buffer of not-acked bytes
+ ranges: RangeTracker, // ranges in buffer that have been sent or acked
+}
+
+impl TxBuffer {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Attempt to add some or all of the passed-in buffer to the TxBuffer.
+ pub fn send(&mut self, buf: &[u8]) -> usize {
+ let can_buffer = min(SEND_BUFFER_SIZE - self.buffered(), buf.len());
+ if can_buffer > 0 {
+ self.send_buf.extend(&buf[..can_buffer]);
+ assert!(self.send_buf.len() <= SEND_BUFFER_SIZE);
+ }
+ can_buffer
+ }
+
+ pub fn next_bytes(&self) -> Option<(u64, &[u8])> {
+ let (start, maybe_len) = self.ranges.first_unmarked_range();
+
+ if start == self.retired + u64::try_from(self.buffered()).unwrap() {
+ return None;
+ }
+
+ // Convert from ranges-relative-to-zero to
+ // ranges-relative-to-buffer-start
+ let buff_off = usize::try_from(start - self.retired).unwrap();
+
+ // Deque returns two slices. Create a subslice from whichever
+ // one contains the first unmarked data.
+ let slc = if buff_off < self.send_buf.as_slices().0.len() {
+ &self.send_buf.as_slices().0[buff_off..]
+ } else {
+ &self.send_buf.as_slices().1[buff_off - self.send_buf.as_slices().0.len()..]
+ };
+
+ let len = if let Some(range_len) = maybe_len {
+ // Truncate if range crosses deque slices
+ min(usize::try_from(range_len).unwrap(), slc.len())
+ } else {
+ slc.len()
+ };
+
+ debug_assert!(len > 0);
+ debug_assert!(len <= slc.len());
+
+ Some((start, &slc[..len]))
+ }
+
+ pub fn mark_as_sent(&mut self, offset: u64, len: usize) {
+ self.ranges.mark_range(offset, len, RangeState::Sent)
+ }
+
+ pub fn mark_as_acked(&mut self, offset: u64, len: usize) {
+ self.ranges.mark_range(offset, len, RangeState::Acked);
+
+ // We can drop contig acked range from the buffer
+ let new_retirable = self.ranges.acked_from_zero() - self.retired;
+ debug_assert!(new_retirable <= self.buffered() as u64);
+ let keep_len =
+ self.buffered() - usize::try_from(new_retirable).expect("should fit in usize");
+
+ // Truncate front
+ self.send_buf.rotate_left(self.buffered() - keep_len);
+ self.send_buf.truncate(keep_len);
+
+ self.retired += new_retirable;
+ }
+
+ pub fn mark_as_lost(&mut self, offset: u64, len: usize) {
+ self.ranges.unmark_range(offset, len)
+ }
+
+ /// Forget about anything that was marked as sent.
+ pub fn unmark_sent(&mut self) {
+ self.ranges.unmark_sent();
+ }
+
+ fn data_limit(&self) -> u64 {
+ self.buffered() as u64 + self.retired
+ }
+
+ fn buffered(&self) -> usize {
+ self.send_buf.len()
+ }
+
+ fn avail(&self) -> usize {
+ SEND_BUFFER_SIZE - self.buffered()
+ }
+
+ pub fn highest_sent(&self) -> u64 {
+ self.ranges.highest_offset()
+ }
+}
+
+/// QUIC sending stream states, based on -transport 3.1.
+#[derive(Debug, PartialEq)]
+enum SendStreamState {
+ Ready,
+ Send {
+ send_buf: TxBuffer,
+ },
+ DataSent {
+ send_buf: TxBuffer,
+ final_size: u64,
+ fin_sent: bool,
+ fin_acked: bool,
+ },
+ DataRecvd {
+ final_size: u64,
+ },
+ ResetSent,
+ ResetRecvd,
+}
+
+impl SendStreamState {
+ fn tx_buf(&self) -> Option<&TxBuffer> {
+ match self {
+ Self::Send { send_buf } | Self::DataSent { send_buf, .. } => Some(send_buf),
+ Self::Ready | Self::DataRecvd { .. } | Self::ResetSent | Self::ResetRecvd => None,
+ }
+ }
+
+ fn tx_buf_mut(&mut self) -> Option<&mut TxBuffer> {
+ match self {
+ Self::Send { send_buf } | Self::DataSent { send_buf, .. } => Some(send_buf),
+ Self::Ready | Self::DataRecvd { .. } | Self::ResetSent | Self::ResetRecvd => None,
+ }
+ }
+
+ fn tx_avail(&self) -> u64 {
+ match self {
+ // In Ready, TxBuffer not yet allocated but size is known
+ Self::Ready => SEND_BUFFER_SIZE.try_into().unwrap(),
+ Self::Send { send_buf } | Self::DataSent { send_buf, .. } => {
+ send_buf.avail().try_into().unwrap()
+ }
+ Self::DataRecvd { .. } | Self::ResetSent | Self::ResetRecvd => 0,
+ }
+ }
+
+ fn final_size(&self) -> Option<u64> {
+ match self {
+ Self::DataSent { final_size, .. } | Self::DataRecvd { final_size } => Some(*final_size),
+ Self::Ready | Self::Send { .. } | Self::ResetSent | Self::ResetRecvd => None,
+ }
+ }
+
+ fn name(&self) -> &str {
+ match self {
+ Self::Ready => "Ready",
+ Self::Send { .. } => "Send",
+ Self::DataSent { .. } => "DataSent",
+ Self::DataRecvd { .. } => "DataRecvd",
+ Self::ResetSent => "ResetSent",
+ Self::ResetRecvd => "ResetRecvd",
+ }
+ }
+
+ fn transition(&mut self, new_state: Self) {
+ qtrace!("SendStream state {} -> {}", self.name(), new_state.name());
+ *self = new_state;
+ }
+}
+
+/// Implement a QUIC send stream.
+#[derive(Debug)]
+pub struct SendStream {
+ stream_id: StreamId,
+ max_stream_data: u64,
+ state: SendStreamState,
+ flow_mgr: Rc<RefCell<FlowMgr>>,
+ conn_events: ConnectionEvents,
+}
+
+impl SendStream {
+ pub fn new(
+ stream_id: StreamId,
+ max_stream_data: u64,
+ flow_mgr: Rc<RefCell<FlowMgr>>,
+ conn_events: ConnectionEvents,
+ ) -> Self {
+ let ss = Self {
+ stream_id,
+ max_stream_data,
+ state: SendStreamState::Ready,
+ flow_mgr,
+ conn_events,
+ };
+ if ss.avail() > 0 {
+ ss.conn_events.send_stream_writable(stream_id);
+ }
+ ss
+ }
+
+ /// Return the next range to be sent, if any.
+ pub fn next_bytes(&mut self) -> Option<(u64, &[u8])> {
+ match self.state {
+ SendStreamState::Send { ref send_buf } => send_buf.next_bytes(),
+ SendStreamState::DataSent {
+ ref send_buf,
+ fin_sent,
+ final_size,
+ ..
+ } => {
+ let bytes = send_buf.next_bytes();
+ if bytes.is_some() {
+ // Must be a resend
+ bytes
+ } else if fin_sent {
+ None
+ } else {
+ // Send empty stream frame with fin set
+ Some((final_size, &[]))
+ }
+ }
+ SendStreamState::Ready
+ | SendStreamState::DataRecvd { .. }
+ | SendStreamState::ResetSent
+ | SendStreamState::ResetRecvd => None,
+ }
+ }
+
+ /// Calculate how many bytes (length) can fit into available space and whether
+ /// the remainder of the space can be filled (or if a length field is needed).
+ fn length_and_fill(data_len: usize, space: usize) -> (usize, bool) {
+ if data_len >= space {
+ // Either more data than space allows, or an exact fit.
+ qtrace!("SendStream::length_and_fill fill {}", space);
+ return (space, true);
+ }
+
+ // Estimate size of the length field based on the available space,
+ // less 1, which is the worst case.
+ let length = min(space.saturating_sub(1), data_len);
+ let length_len = Encoder::varint_len(u64::try_from(length).unwrap());
+ if length_len > space {
+ qtrace!(
+ "SendStream::length_and_fill no room for length of {} in {}",
+ length,
+ space
+ );
+ return (0, false);
+ }
+
+ let length = min(data_len, space - length_len);
+ qtrace!("SendStream::length_and_fill {} in {}", length, space);
+ (length, false)
+ }
+
+ pub fn write_frame(&mut self, builder: &mut PacketBuilder) -> Option<RecoveryToken> {
+ let id = self.stream_id;
+ let final_size = self.final_size();
+ if let Some((offset, data)) = self.next_bytes() {
+ let overhead = 1 // Frame type
+ + Encoder::varint_len(id.as_u64())
+ + if offset > 0 {
+ Encoder::varint_len(offset)
+ } else {
+ 0
+ };
+ if overhead > builder.remaining() {
+ qtrace!("SendStream::write_frame no space for header");
+ return None;
+ }
+
+ let (length, fill) = Self::length_and_fill(data.len(), builder.remaining() - overhead);
+ let fin = final_size.map_or(false, |fs| fs == offset + u64::try_from(length).unwrap());
+ if length == 0 && !fin {
+ qtrace!("SendStream::write_frame no data, no fin");
+ return None;
+ }
+
+ // Write the stream out.
+ builder.encode_varint(Frame::stream_type(fin, offset > 0, fill));
+ builder.encode_varint(id.as_u64());
+ if offset > 0 {
+ builder.encode_varint(offset);
+ }
+ if fill {
+ builder.encode(&data[..length]);
+ } else {
+ builder.encode_vvec(&data[..length]);
+ }
+
+ self.mark_as_sent(offset, length, fin);
+ Some(RecoveryToken::Stream(StreamRecoveryToken {
+ id,
+ offset,
+ length,
+ fin,
+ }))
+ } else {
+ None
+ }
+ }
+
+ pub fn mark_as_sent(&mut self, offset: u64, len: usize, fin: bool) {
+ if let Some(buf) = self.state.tx_buf_mut() {
+ buf.mark_as_sent(offset, len);
+ self.send_blocked_if_space_needed(0);
+ };
+
+ if fin {
+ if let SendStreamState::DataSent { fin_sent, .. } = &mut self.state {
+ *fin_sent = true;
+ }
+ }
+ }
+
+ pub fn mark_as_acked(&mut self, offset: u64, len: usize, fin: bool) {
+ match self.state {
+ SendStreamState::Send { ref mut send_buf } => {
+ send_buf.mark_as_acked(offset, len);
+ if self.avail() > 0 {
+ self.conn_events.send_stream_writable(self.stream_id)
+ }
+ }
+ SendStreamState::DataSent {
+ ref mut send_buf,
+ final_size,
+ ref mut fin_acked,
+ ..
+ } => {
+ send_buf.mark_as_acked(offset, len);
+ if fin {
+ *fin_acked = true;
+ }
+ if *fin_acked && send_buf.buffered() == 0 {
+ self.conn_events.send_stream_complete(self.stream_id);
+ self.state
+ .transition(SendStreamState::DataRecvd { final_size });
+ }
+ }
+ _ => qtrace!("mark_as_acked called from state {}", self.state.name()),
+ }
+ }
+
+ pub fn mark_as_lost(&mut self, offset: u64, len: usize, fin: bool) {
+ if let Some(buf) = self.state.tx_buf_mut() {
+ buf.mark_as_lost(offset, len);
+ }
+
+ if fin {
+ if let SendStreamState::DataSent {
+ fin_sent,
+ fin_acked,
+ ..
+ } = &mut self.state
+ {
+ *fin_sent = *fin_acked;
+ }
+ }
+ }
+
+ pub fn final_size(&self) -> Option<u64> {
+ self.state.final_size()
+ }
+
+ /// Stream credit available
+ pub fn credit_avail(&self) -> u64 {
+ if self.state == SendStreamState::Ready {
+ self.max_stream_data
+ } else {
+ self.state
+ .tx_buf()
+ .map_or(0, |tx| self.max_stream_data - tx.data_limit())
+ }
+ }
+
+ /// Bytes sendable on stream. Constrained by stream credit available,
+ /// connection credit available, and space in the tx buffer.
+ pub fn avail(&self) -> usize {
+ min(
+ min(self.state.tx_avail(), self.credit_avail()),
+ self.flow_mgr.borrow().conn_credit_avail(),
+ )
+ .try_into()
+ .unwrap()
+ }
+
+ pub fn max_stream_data(&self) -> u64 {
+ self.max_stream_data
+ }
+
+ pub fn set_max_stream_data(&mut self, value: u64) {
+ let stream_was_blocked = self.avail() == 0;
+ self.max_stream_data = max(self.max_stream_data, value);
+ if stream_was_blocked && self.avail() > 0 {
+ self.conn_events.send_stream_writable(self.stream_id)
+ }
+ }
+
+ pub fn reset_acked(&mut self) {
+ match self.state {
+ SendStreamState::Ready
+ | SendStreamState::Send { .. }
+ | SendStreamState::DataSent { .. }
+ | SendStreamState::DataRecvd { .. } => {
+ qtrace!("Reset acked while in {} state?", self.state.name())
+ }
+ SendStreamState::ResetSent => self.state.transition(SendStreamState::ResetRecvd),
+ SendStreamState::ResetRecvd => qtrace!("already in ResetRecvd state"),
+ };
+ }
+
+ pub fn is_terminal(&self) -> bool {
+ matches!(self.state, SendStreamState::DataRecvd { .. } | SendStreamState::ResetRecvd)
+ }
+
+ pub fn send(&mut self, buf: &[u8]) -> Res<usize> {
+ self.send_internal(buf, false)
+ }
+
+ pub fn send_atomic(&mut self, buf: &[u8]) -> Res<usize> {
+ self.send_internal(buf, true)
+ }
+
+ fn send_blocked_if_space_needed(&mut self, needed_space: u64) {
+ if self.credit_avail() <= needed_space {
+ self.flow_mgr
+ .borrow_mut()
+ .stream_data_blocked(self.stream_id, self.max_stream_data);
+ }
+
+ if self.flow_mgr.borrow().conn_credit_avail() <= needed_space {
+ self.flow_mgr.borrow_mut().data_blocked();
+ }
+ }
+
+ fn send_internal(&mut self, buf: &[u8], atomic: bool) -> Res<usize> {
+ if buf.is_empty() {
+ qerror!("zero-length send on stream {}", self.stream_id.as_u64());
+ return Err(Error::InvalidInput);
+ }
+
+ if let SendStreamState::Ready = self.state {
+ self.state.transition(SendStreamState::Send {
+ send_buf: TxBuffer::new(),
+ });
+ }
+
+ if !matches!(self.state, SendStreamState::Send{..}) {
+ return Err(Error::FinalSizeError);
+ }
+
+ let buf = if buf.is_empty() || (self.avail() == 0) {
+ return Ok(0);
+ } else if self.avail() < buf.len() {
+ if atomic {
+ self.send_blocked_if_space_needed(buf.len() as u64);
+ return Ok(0);
+ } else {
+ &buf[..self.avail()]
+ }
+ } else {
+ buf
+ };
+
+ let sent = match &mut self.state {
+ SendStreamState::Ready => unreachable!(),
+ SendStreamState::Send { send_buf } => send_buf.send(buf),
+ _ => return Err(Error::FinalSizeError),
+ };
+
+ self.flow_mgr
+ .borrow_mut()
+ .conn_increase_credit_used(sent as u64);
+
+ Ok(sent)
+ }
+
+ pub fn close(&mut self) {
+ match &mut self.state {
+ SendStreamState::Ready => {
+ self.state.transition(SendStreamState::DataSent {
+ send_buf: TxBuffer::new(),
+ final_size: 0,
+ fin_sent: false,
+ fin_acked: false,
+ });
+ }
+ SendStreamState::Send { send_buf } => {
+ let final_size = send_buf.retired + send_buf.buffered() as u64;
+ let owned_buf = mem::replace(send_buf, TxBuffer::new());
+ self.state.transition(SendStreamState::DataSent {
+ send_buf: owned_buf,
+ final_size,
+ fin_sent: false,
+ fin_acked: false,
+ });
+ }
+ SendStreamState::DataSent { .. } => qtrace!("already in DataSent state"),
+ SendStreamState::DataRecvd { .. } => qtrace!("already in DataRecvd state"),
+ SendStreamState::ResetSent => qtrace!("already in ResetSent state"),
+ SendStreamState::ResetRecvd => qtrace!("already in ResetRecvd state"),
+ }
+ }
+
+ pub fn reset(&mut self, err: AppError) {
+ match &self.state {
+ SendStreamState::Ready => {
+ self.flow_mgr
+ .borrow_mut()
+ .stream_reset(self.stream_id, err, 0);
+
+ self.state.transition(SendStreamState::ResetSent);
+ }
+ SendStreamState::Send { send_buf } => {
+ self.flow_mgr.borrow_mut().stream_reset(
+ self.stream_id,
+ err,
+ send_buf.highest_sent(),
+ );
+
+ self.state.transition(SendStreamState::ResetSent);
+ }
+ SendStreamState::DataSent { final_size, .. } => {
+ self.flow_mgr
+ .borrow_mut()
+ .stream_reset(self.stream_id, err, *final_size);
+
+ self.state.transition(SendStreamState::ResetSent);
+ }
+ SendStreamState::DataRecvd { .. } => qtrace!("already in DataRecvd state"),
+ SendStreamState::ResetSent => qtrace!("already in ResetSent state"),
+ SendStreamState::ResetRecvd => qtrace!("already in ResetRecvd state"),
+ };
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct SendStreams(IndexMap<StreamId, SendStream>);
+
+impl SendStreams {
+ pub fn get(&self, id: StreamId) -> Res<&SendStream> {
+ self.0.get(&id).ok_or(Error::InvalidStreamId)
+ }
+
+ pub fn get_mut(&mut self, id: StreamId) -> Res<&mut SendStream> {
+ self.0.get_mut(&id).ok_or(Error::InvalidStreamId)
+ }
+
+ pub fn exists(&self, id: StreamId) -> bool {
+ self.0.contains_key(&id)
+ }
+
+ pub fn insert(&mut self, id: StreamId, stream: SendStream) {
+ self.0.insert(id, stream);
+ }
+
+ pub fn acked(&mut self, token: &StreamRecoveryToken) {
+ if let Some(ss) = self.0.get_mut(&token.id) {
+ ss.mark_as_acked(token.offset, token.length, token.fin);
+ }
+ }
+
+ pub fn reset_acked(&mut self, id: StreamId) {
+ if let Some(ss) = self.0.get_mut(&id) {
+ ss.reset_acked()
+ }
+ }
+
+ pub fn lost(&mut self, token: &StreamRecoveryToken) {
+ if let Some(ss) = self.0.get_mut(&token.id) {
+ ss.mark_as_lost(token.offset, token.length, token.fin);
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.0.clear()
+ }
+
+ pub fn clear_terminal(&mut self) {
+ self.0.retain(|_, stream| !stream.is_terminal())
+ }
+
+ pub(crate) fn write_frames(
+ &mut self,
+ builder: &mut PacketBuilder,
+ tokens: &mut Vec<RecoveryToken>,
+ stats: &mut FrameStats,
+ ) {
+ for (_, stream) in self {
+ if let Some(t) = stream.write_frame(builder) {
+ tokens.push(t);
+ stats.stream += 1;
+ }
+ }
+ }
+}
+
+impl<'a> IntoIterator for &'a mut SendStreams {
+ type Item = (&'a StreamId, &'a mut SendStream);
+ type IntoIter = indexmap::map::IterMut<'a, StreamId, SendStream>;
+
+ fn into_iter(self) -> indexmap::map::IterMut<'a, StreamId, SendStream> {
+ self.0.iter_mut()
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct StreamRecoveryToken {
+ pub(crate) id: StreamId,
+ offset: u64,
+ length: usize,
+ fin: bool,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::events::ConnectionEvent;
+ use neqo_common::{event::Provider, hex_with_len, qtrace};
+
+ #[test]
+ fn test_mark_range() {
+ let mut rt = RangeTracker::default();
+
+ // ranges can go from nothing->Sent if queued for retrans and then
+ // acks arrive
+ rt.mark_range(5, 5, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 10);
+ assert_eq!(rt.acked_from_zero(), 0);
+ rt.mark_range(10, 4, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 14);
+ assert_eq!(rt.acked_from_zero(), 0);
+
+ rt.mark_range(0, 5, RangeState::Sent);
+ assert_eq!(rt.highest_offset(), 14);
+ assert_eq!(rt.acked_from_zero(), 0);
+ rt.mark_range(0, 5, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 14);
+ assert_eq!(rt.acked_from_zero(), 14);
+
+ rt.mark_range(12, 20, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 32);
+ assert_eq!(rt.acked_from_zero(), 32);
+
+ // ack the lot
+ rt.mark_range(0, 400, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 400);
+ assert_eq!(rt.acked_from_zero(), 400);
+
+ // acked trumps sent
+ rt.mark_range(0, 200, RangeState::Sent);
+ assert_eq!(rt.highest_offset(), 400);
+ assert_eq!(rt.acked_from_zero(), 400);
+ }
+
+ #[test]
+ fn unmark_sent_start() {
+ let mut rt = RangeTracker::default();
+
+ rt.mark_range(0, 5, RangeState::Sent);
+ assert_eq!(rt.highest_offset(), 5);
+ assert_eq!(rt.acked_from_zero(), 0);
+
+ rt.unmark_sent();
+ assert_eq!(rt.highest_offset(), 0);
+ assert_eq!(rt.acked_from_zero(), 0);
+ assert_eq!(rt.first_unmarked_range(), (0, None));
+ }
+
+ #[test]
+ fn unmark_sent_middle() {
+ let mut rt = RangeTracker::default();
+
+ rt.mark_range(0, 5, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 5);
+ assert_eq!(rt.acked_from_zero(), 5);
+ rt.mark_range(5, 5, RangeState::Sent);
+ assert_eq!(rt.highest_offset(), 10);
+ assert_eq!(rt.acked_from_zero(), 5);
+ rt.mark_range(10, 5, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 15);
+ assert_eq!(rt.acked_from_zero(), 5);
+ assert_eq!(rt.first_unmarked_range(), (15, None));
+
+ rt.unmark_sent();
+ assert_eq!(rt.highest_offset(), 15);
+ assert_eq!(rt.acked_from_zero(), 5);
+ assert_eq!(rt.first_unmarked_range(), (5, Some(5)));
+ }
+
+ #[test]
+ fn unmark_sent_end() {
+ let mut rt = RangeTracker::default();
+
+ rt.mark_range(0, 5, RangeState::Acked);
+ assert_eq!(rt.highest_offset(), 5);
+ assert_eq!(rt.acked_from_zero(), 5);
+ rt.mark_range(5, 5, RangeState::Sent);
+ assert_eq!(rt.highest_offset(), 10);
+ assert_eq!(rt.acked_from_zero(), 5);
+ assert_eq!(rt.first_unmarked_range(), (10, None));
+
+ rt.unmark_sent();
+ assert_eq!(rt.highest_offset(), 5);
+ assert_eq!(rt.acked_from_zero(), 5);
+ assert_eq!(rt.first_unmarked_range(), (5, None));
+ }
+
+ #[test]
+ fn truncate_front() {
+ let mut v = VecDeque::new();
+ v.push_back(5);
+ v.push_back(6);
+ v.push_back(7);
+ v.push_front(4usize);
+
+ v.rotate_left(1);
+ v.truncate(3);
+ assert_eq!(*v.front().unwrap(), 5);
+ assert_eq!(*v.back().unwrap(), 7);
+ }
+
+ #[test]
+ fn test_unmark_range() {
+ let mut rt = RangeTracker::default();
+
+ rt.mark_range(5, 5, RangeState::Acked);
+ rt.mark_range(10, 5, RangeState::Sent);
+
+ // Should unmark sent but not acked range
+ rt.unmark_range(7, 6);
+
+ let res = rt.first_unmarked_range();
+ assert_eq!(res, (0, Some(5)));
+ assert_eq!(
+ rt.used.iter().next().unwrap(),
+ (&5, &(5, RangeState::Acked))
+ );
+ assert_eq!(
+ rt.used.iter().nth(1).unwrap(),
+ (&13, &(2, RangeState::Sent))
+ );
+ assert!(rt.used.iter().nth(2).is_none());
+ rt.mark_range(0, 5, RangeState::Sent);
+
+ let res = rt.first_unmarked_range();
+ assert_eq!(res, (10, Some(3)));
+ rt.mark_range(10, 3, RangeState::Sent);
+
+ let res = rt.first_unmarked_range();
+ assert_eq!(res, (15, None));
+ }
+
+ #[test]
+ #[allow(clippy::cognitive_complexity)]
+ fn tx_buffer_next_bytes_1() {
+ let mut txb = TxBuffer::new();
+
+ assert_eq!(txb.avail(), SEND_BUFFER_SIZE);
+
+ // Fill the buffer
+ assert_eq!(txb.send(&[1; SEND_BUFFER_SIZE * 2]), SEND_BUFFER_SIZE);
+ assert!(matches!(txb.next_bytes(),
+ Some((0, x)) if x.len()==SEND_BUFFER_SIZE
+ && x.iter().all(|ch| *ch == 1)));
+
+ // Mark almost all as sent. Get what's left
+ let one_byte_from_end = SEND_BUFFER_SIZE as u64 - 1;
+ txb.mark_as_sent(0, one_byte_from_end as usize);
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 1
+ && start == one_byte_from_end
+ && x.iter().all(|ch| *ch == 1)));
+
+ // Mark all as sent. Get nothing
+ txb.mark_as_sent(0, SEND_BUFFER_SIZE);
+ assert!(matches!(txb.next_bytes(), None));
+
+ // Mark as lost. Get it again
+ txb.mark_as_lost(one_byte_from_end, 1);
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 1
+ && start == one_byte_from_end
+ && x.iter().all(|ch| *ch == 1)));
+
+ // Mark a larger range lost, including beyond what's in the buffer even.
+ // Get a little more
+ let five_bytes_from_end = SEND_BUFFER_SIZE as u64 - 5;
+ txb.mark_as_lost(five_bytes_from_end, 100);
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 5
+ && start == five_bytes_from_end
+ && x.iter().all(|ch| *ch == 1)));
+
+ // Contig acked range at start means it can be removed from buffer
+ // Impl of vecdeque should now result in a split buffer when more data
+ // is sent
+ txb.mark_as_acked(0, five_bytes_from_end as usize);
+ assert_eq!(txb.send(&[2; 30]), 30);
+ // Just get 5 even though there is more
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 5
+ && start == five_bytes_from_end
+ && x.iter().all(|ch| *ch == 1)));
+ assert_eq!(txb.retired, five_bytes_from_end);
+ assert_eq!(txb.buffered(), 35);
+
+ // Marking that bit as sent should let the last contig bit be returned
+ // when called again
+ txb.mark_as_sent(five_bytes_from_end, 5);
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 30
+ && start == SEND_BUFFER_SIZE as u64
+ && x.iter().all(|ch| *ch == 2)));
+ }
+
+ #[test]
+ fn tx_buffer_next_bytes_2() {
+ let mut txb = TxBuffer::new();
+
+ assert_eq!(txb.avail(), SEND_BUFFER_SIZE);
+
+ // Fill the buffer
+ assert_eq!(txb.send(&[1; SEND_BUFFER_SIZE * 2]), SEND_BUFFER_SIZE);
+ assert!(matches!(txb.next_bytes(),
+ Some((0, x)) if x.len()==SEND_BUFFER_SIZE
+ && x.iter().all(|ch| *ch == 1)));
+
+ // As above
+ let forty_bytes_from_end = SEND_BUFFER_SIZE as u64 - 40;
+
+ txb.mark_as_acked(0, forty_bytes_from_end as usize);
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 40
+ && start == forty_bytes_from_end
+ ));
+
+ // Valid new data placed in split locations
+ assert_eq!(txb.send(&[2; 100]), 100);
+
+ // Mark a little more as sent
+ txb.mark_as_sent(forty_bytes_from_end, 10);
+ let thirty_bytes_from_end = forty_bytes_from_end + 10;
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 30
+ && start == thirty_bytes_from_end
+ && x.iter().all(|ch| *ch == 1)));
+
+ // Mark a range 'A' in second slice as sent. Should still return the same
+ let range_a_start = SEND_BUFFER_SIZE as u64 + 30;
+ let range_a_end = range_a_start + 10;
+ txb.mark_as_sent(range_a_start, 10);
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 30
+ && start == thirty_bytes_from_end
+ && x.iter().all(|ch| *ch == 1)));
+
+ // Ack entire first slice and into second slice
+ let ten_bytes_past_end = SEND_BUFFER_SIZE as u64 + 10;
+ txb.mark_as_acked(0, ten_bytes_past_end as usize);
+
+ // Get up to marked range A
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 20
+ && start == ten_bytes_past_end
+ && x.iter().all(|ch| *ch == 2)));
+
+ txb.mark_as_sent(ten_bytes_past_end, 20);
+
+ // Get bit after earlier marked range A
+ assert!(matches!(txb.next_bytes(),
+ Some((start, x)) if x.len() == 60
+ && start == range_a_end
+ && x.iter().all(|ch| *ch == 2)));
+
+ // No more bytes.
+ txb.mark_as_sent(range_a_end, 60);
+ assert!(matches!(txb.next_bytes(), None));
+ }
+
+ #[test]
+ fn test_stream_tx() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(4096);
+ let conn_events = ConnectionEvents::default();
+
+ let mut s = SendStream::new(4.into(), 1024, Rc::clone(&flow_mgr), conn_events);
+
+ let res = s.send(&[4; 100]).unwrap();
+ assert_eq!(res, 100);
+ s.mark_as_sent(0, 50, false);
+ assert_eq!(s.state.tx_buf().unwrap().data_limit(), 100);
+
+ // Should hit stream flow control limit before filling up send buffer
+ let res = s.send(&[4; SEND_BUFFER_SIZE]).unwrap();
+ assert_eq!(res, 1024 - 100);
+
+ // should do nothing, max stream data already 1024
+ s.set_max_stream_data(1024);
+ let res = s.send(&[4; SEND_BUFFER_SIZE]).unwrap();
+ assert_eq!(res, 0);
+
+ // should now hit the conn flow control (4096)
+ s.set_max_stream_data(1_048_576);
+ let res = s.send(&[4; SEND_BUFFER_SIZE]).unwrap();
+ assert_eq!(res, 3072);
+
+ // should now hit the tx buffer size
+ flow_mgr
+ .borrow_mut()
+ .conn_increase_max_credit(SEND_BUFFER_SIZE as u64);
+ let res = s.send(&[4; SEND_BUFFER_SIZE + 100]).unwrap();
+ assert_eq!(res, SEND_BUFFER_SIZE - 4096);
+
+ // TODO(agrover@mozilla.com): test ooo acks somehow
+ s.mark_as_acked(0, 40, false);
+ }
+
+ #[test]
+ fn test_tx_buffer_acks() {
+ let mut tx = TxBuffer::new();
+ assert_eq!(tx.send(&[4; 100]), 100);
+ let res = tx.next_bytes().unwrap();
+ assert_eq!(res.0, 0);
+ assert_eq!(res.1.len(), 100);
+ tx.mark_as_sent(0, 100);
+ let res = tx.next_bytes();
+ assert_eq!(res, None);
+
+ tx.mark_as_acked(0, 100);
+ let res = tx.next_bytes();
+ assert_eq!(res, None);
+ }
+
+ #[test]
+ fn send_stream_writable_event_gen() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(2);
+ let mut conn_events = ConnectionEvents::default();
+
+ let mut s = SendStream::new(4.into(), 0, Rc::clone(&flow_mgr), conn_events.clone());
+
+ // Stream is initially blocked (conn:2, stream:0)
+ // and will not accept data.
+ assert_eq!(s.send(b"hi").unwrap(), 0);
+
+ // increasing to (conn:2, stream:2) will allow 2 bytes, and also
+ // generate a SendStreamWritable event.
+ s.set_max_stream_data(2);
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 1);
+ assert!(matches!(evts[0], ConnectionEvent::SendStreamWritable{..}));
+ assert_eq!(s.send(b"hello").unwrap(), 2);
+
+ // increasing to (conn:2, stream:4) will not generate an event or allow
+ // sending anything.
+ s.set_max_stream_data(4);
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 0);
+ assert_eq!(s.send(b"hello").unwrap(), 0);
+
+ // Increasing conn max (conn:4, stream:4) will unblock but not emit
+ // event b/c that happens in Connection::emit_frame() (tested in
+ // connection.rs)
+ assert_eq!(flow_mgr.borrow_mut().conn_increase_max_credit(4), true);
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 0);
+ assert_eq!(s.avail(), 2);
+ assert_eq!(s.send(b"hello").unwrap(), 2);
+
+ // No event because still blocked by conn
+ s.set_max_stream_data(1_000_000_000);
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 0);
+
+ // No event because happens in emit_frame()
+ flow_mgr
+ .borrow_mut()
+ .conn_increase_max_credit(1_000_000_000);
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 0);
+
+ // Unblocking both by a large amount will cause avail() to be limited by
+ // tx buffer size.
+ assert_eq!(s.avail(), SEND_BUFFER_SIZE - 4);
+
+ assert_eq!(
+ s.send(&[b'a'; SEND_BUFFER_SIZE]).unwrap(),
+ SEND_BUFFER_SIZE - 4
+ );
+
+ // No event because still blocked by tx buffer full
+ s.set_max_stream_data(2_000_000_000);
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 0);
+ assert_eq!(s.send(b"hello").unwrap(), 0);
+ }
+
+ #[test]
+ fn send_stream_writable_event_new_stream() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(2);
+ let mut conn_events = ConnectionEvents::default();
+
+ let _s = SendStream::new(4.into(), 100, flow_mgr, conn_events.clone());
+
+ // Creating a new stream with conn and stream credits should result in
+ // an event.
+ let evts = conn_events.events().collect::<Vec<_>>();
+ assert_eq!(evts.len(), 1);
+ assert!(matches!(evts[0], ConnectionEvent::SendStreamWritable{..}));
+ }
+
+ #[test]
+ // Verify lost frames handle fin properly
+ fn send_stream_get_frame_data() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(100);
+ let conn_events = ConnectionEvents::default();
+
+ let mut s = SendStream::new(0.into(), 100, flow_mgr, conn_events);
+ s.send(&[0; 10]).unwrap();
+ s.close();
+
+ let mut ss = SendStreams::default();
+ ss.insert(StreamId::from(0), s);
+
+ let mut tokens = Vec::new();
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+
+ // Write a small frame: no fin.
+ let written = builder.len();
+ builder.set_limit(written + 6);
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ assert_eq!(builder.len(), written + 6);
+ assert_eq!(tokens.len(), 1);
+ let f1_token = tokens.remove(0);
+ assert!(matches!(&f1_token, RecoveryToken::Stream(x) if !x.fin));
+
+ // Write the rest: fin.
+ let written = builder.len();
+ builder.set_limit(written + 200);
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ assert_eq!(builder.len(), written + 10);
+ assert_eq!(tokens.len(), 1);
+ let f2_token = tokens.remove(0);
+ assert!(matches!(&f2_token, RecoveryToken::Stream(x) if x.fin));
+
+ // Should be no more data to frame.
+ let written = builder.len();
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ assert_eq!(builder.len(), written);
+ assert!(tokens.is_empty());
+
+ // Mark frame 1 as lost
+ if let RecoveryToken::Stream(rt) = f1_token {
+ ss.lost(&rt);
+ } else {
+ panic!();
+ }
+
+ // Next frame should not set fin even though stream has fin but frame
+ // does not include end of stream
+ let written = builder.len();
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ assert_eq!(builder.len(), written + 7); // Needs a length this time.
+ assert_eq!(tokens.len(), 1);
+ let f4_token = tokens.remove(0);
+ assert!(matches!(&f4_token, RecoveryToken::Stream(x) if !x.fin));
+
+ // Mark frame 2 as lost
+ if let RecoveryToken::Stream(rt) = f2_token {
+ ss.lost(&rt);
+ } else {
+ panic!();
+ }
+
+ // Next frame should set fin because it includes end of stream
+ let written = builder.len();
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ assert_eq!(builder.len(), written + 10);
+ assert_eq!(tokens.len(), 1);
+ let f5_token = tokens.remove(0);
+ assert!(matches!(&f5_token, RecoveryToken::Stream(x) if x.fin));
+ }
+
+ #[test]
+ #[allow(clippy::cognitive_complexity)]
+ // Verify lost frames handle fin properly with zero length fin
+ fn send_stream_get_frame_zerolength_fin() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(100);
+ let conn_events = ConnectionEvents::default();
+
+ let mut s = SendStream::new(0.into(), 100, flow_mgr, conn_events);
+ s.send(&[0; 10]).unwrap();
+
+ let mut ss = SendStreams::default();
+ ss.insert(StreamId::from(0), s);
+
+ let mut tokens = Vec::new();
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ let f1_token = tokens.remove(0);
+ assert!(matches!(&f1_token, RecoveryToken::Stream(x) if x.offset == 0));
+ assert!(matches!(&f1_token, RecoveryToken::Stream(x) if x.length == 10));
+ assert!(matches!(&f1_token, RecoveryToken::Stream(x) if !x.fin));
+
+ // Should be no more data to frame
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ assert!(tokens.is_empty());
+
+ ss.get_mut(StreamId::from(0)).unwrap().close();
+
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ let f2_token = tokens.remove(0);
+ assert!(matches!(&f2_token, RecoveryToken::Stream(x) if x.offset == 10));
+ assert!(matches!(&f2_token, RecoveryToken::Stream(x) if x.length == 0));
+ assert!(matches!(&f2_token, RecoveryToken::Stream(x) if x.fin));
+
+ // Mark frame 2 as lost
+ if let RecoveryToken::Stream(rt) = f2_token {
+ ss.lost(&rt);
+ } else {
+ panic!();
+ }
+
+ // Next frame should set fin
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ let f3_token = tokens.remove(0);
+ assert!(matches!(&f3_token, RecoveryToken::Stream(x) if x.offset == 10));
+ assert!(matches!(&f3_token, RecoveryToken::Stream(x) if x.length == 0));
+ assert!(matches!(&f3_token, RecoveryToken::Stream(x) if x.fin));
+
+ // Mark frame 1 as lost
+ if let RecoveryToken::Stream(rt) = f1_token {
+ ss.lost(&rt);
+ } else {
+ panic!();
+ }
+
+ // Next frame should set fin and include all data
+ ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
+ let f4_token = tokens.remove(0);
+ assert!(matches!(&f4_token, RecoveryToken::Stream(x) if x.offset == 0));
+ assert!(matches!(&f4_token, RecoveryToken::Stream(x) if x.length == 10));
+ assert!(matches!(&f4_token, RecoveryToken::Stream(x) if x.fin));
+ }
+
+ #[test]
+ fn send_atomic() {
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(5);
+ let conn_events = ConnectionEvents::default();
+
+ let stream_id = StreamId::from(4);
+ let mut s = SendStream::new(stream_id, 0, Rc::clone(&flow_mgr), conn_events);
+ s.set_max_stream_data(2);
+
+ // Stream is initially blocked (conn:5, stream:2)
+ // and will not accept atomic write of 3 bytes.
+ assert_eq!(s.send_atomic(b"abc").unwrap(), 0);
+
+ // assert that STREAM_DATA_BLOCKED is sent.
+ assert_eq!(
+ flow_mgr.borrow_mut().next().unwrap(),
+ Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit: 0x2
+ }
+ );
+
+ // assert non-atomic write works
+ assert_eq!(s.send(b"abc").unwrap(), 2);
+ assert_eq!(s.next_bytes(), Some((0, &b"ab"[..])));
+ // STREAM_DATA_BLOCKED is not sent yet.
+ assert!(flow_mgr.borrow_mut().next().is_none());
+
+ // STREAM_DATA_BLOCKED is queued once bytes using all credit are sent.
+ s.mark_as_sent(0, 2, false);
+ assert_eq!(
+ flow_mgr.borrow_mut().next().unwrap(),
+ Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit: 0x2
+ }
+ );
+
+ // increasing to (conn:5, stream:10)
+ s.set_max_stream_data(10);
+ // will not accept atomic write of 4 bytes.
+ assert_eq!(s.send_atomic(b"abcd").unwrap(), 0);
+
+ // assert that STREAM_DATA_BLOCKED is sent.
+ assert_eq!(
+ flow_mgr.borrow_mut().next().unwrap(),
+ Frame::DataBlocked { data_limit: 0x5 }
+ );
+
+ // assert non-atomic write works
+ assert_eq!(s.send(b"abcd").unwrap(), 3);
+ assert_eq!(s.next_bytes(), Some((2, &b"abc"[..])));
+ // DATA_BLOCKED is not sent yet.
+ assert!(flow_mgr.borrow_mut().next().is_none());
+
+ // DATA_BLOCKED is queued once bytes using all credit are sent.
+ s.mark_as_sent(2, 3, false);
+ assert_eq!(
+ flow_mgr.borrow_mut().next().unwrap(),
+ Frame::DataBlocked { data_limit: 0x5 }
+ );
+
+ // increasing to (conn:15, stream:15)
+ s.set_max_stream_data(15);
+ flow_mgr.borrow_mut().conn_increase_max_credit(15);
+
+ // assert that atomic writing 10 byte works
+ assert_eq!(s.send_atomic(b"abcdefghij").unwrap(), 10);
+ }
+
+ #[test]
+ fn ack_fin_first() {
+ const MESSAGE: &[u8] = b"hello";
+ let len_u64 = u64::try_from(MESSAGE.len()).unwrap();
+
+ let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));
+ flow_mgr.borrow_mut().conn_increase_max_credit(len_u64);
+ let conn_events = ConnectionEvents::default();
+
+ let mut s = SendStream::new(StreamId::new(100), 0, Rc::clone(&flow_mgr), conn_events);
+ s.set_max_stream_data(len_u64);
+
+ // Send all the data, then the fin.
+ let _ = s.send(MESSAGE).unwrap();
+ s.mark_as_sent(0, MESSAGE.len(), false);
+ s.close();
+ s.mark_as_sent(len_u64, 0, true);
+
+ // Ack the fin, then the data.
+ s.mark_as_acked(len_u64, 0, true);
+ s.mark_as_acked(0, MESSAGE.len(), false);
+ assert!(s.is_terminal());
+ }
+
+ #[test]
+ fn ack_then_lose_fin() {
+ const MESSAGE: &[u8] = b"hello";
+ let len_u64 = u64::try_from(MESSAGE.len()).unwrap();
+
+ let mut flow_mgr = FlowMgr::default();
+ flow_mgr.conn_increase_max_credit(len_u64);
+ let conn_events = ConnectionEvents::default();
+
+ let id = StreamId::new(100);
+ let mut s = SendStream::new(id, 0, Rc::new(RefCell::new(flow_mgr)), conn_events);
+ s.set_max_stream_data(len_u64);
+
+ // Send all the data, then the fin.
+ let _ = s.send(MESSAGE).unwrap();
+ s.mark_as_sent(0, MESSAGE.len(), false);
+ s.close();
+ s.mark_as_sent(len_u64, 0, true);
+
+ // Ack the fin, then mark it lost.
+ s.mark_as_acked(len_u64, 0, true);
+ s.mark_as_lost(len_u64, 0, true);
+
+ // No frame should be sent here.
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ assert!(s.write_frame(&mut builder).is_none());
+ }
+
+ /// Create a `SendStream` and force it into a state where it believes that
+ /// `offset` bytes have already been sent and acknowledged.
+ fn stream_with_sent(stream: u64, offset: usize) -> SendStream {
+ const MAX_VARINT: u64 = (1 << 62) - 1;
+
+ let mut flow_mgr = FlowMgr::default();
+ flow_mgr.conn_increase_max_credit(MAX_VARINT);
+
+ let mut s = SendStream::new(
+ StreamId::from(stream),
+ MAX_VARINT,
+ Rc::new(RefCell::new(flow_mgr)),
+ ConnectionEvents::default(),
+ );
+
+ let mut send_buf = TxBuffer::new();
+ send_buf.retired = u64::try_from(offset).unwrap();
+ send_buf.ranges.mark_range(0, offset, RangeState::Acked);
+ s.state = SendStreamState::Send { send_buf };
+ s
+ }
+
+ fn frame_sent_sid(stream: u64, offset: usize, len: usize, fin: bool, space: usize) -> bool {
+ const BUF: &[u8] = &[0x42; 128];
+ let mut s = stream_with_sent(stream, offset);
+
+ // Now write out the proscribed data and maybe close.
+ if len > 0 {
+ s.send(&BUF[..len]).unwrap();
+ }
+ if fin {
+ s.close();
+ }
+
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ let header_len = builder.len();
+ builder.set_limit(header_len + space);
+ let token = s.write_frame(&mut builder);
+ qtrace!("STREAM frame: {}", hex_with_len(&builder[header_len..]));
+ token.is_some()
+ }
+
+ fn frame_sent(offset: usize, len: usize, fin: bool, space: usize) -> bool {
+ frame_sent_sid(0, offset, len, fin, space)
+ }
+
+ #[test]
+ fn stream_frame_empty() {
+ // Stream frames with empty data and no fin never work.
+ assert!(!frame_sent(10, 0, false, 2));
+ assert!(!frame_sent(10, 0, false, 3));
+ assert!(!frame_sent(10, 0, false, 4));
+ assert!(!frame_sent(10, 0, false, 5));
+ assert!(!frame_sent(10, 0, false, 100));
+
+ // Empty data with fin is only a problem if there is no space.
+ assert!(!frame_sent(0, 0, true, 1));
+ assert!(frame_sent(0, 0, true, 2));
+ assert!(!frame_sent(10, 0, true, 2));
+ assert!(frame_sent(10, 0, true, 3));
+ assert!(frame_sent(10, 0, true, 4));
+ assert!(frame_sent(10, 0, true, 5));
+ assert!(frame_sent(10, 0, true, 100));
+ }
+
+ #[test]
+ fn stream_frame_minimum() {
+ // Add minimum data
+ assert!(!frame_sent(10, 1, false, 3));
+ assert!(!frame_sent(10, 1, true, 3));
+ assert!(frame_sent(10, 1, false, 4));
+ assert!(frame_sent(10, 1, true, 4));
+ assert!(frame_sent(10, 1, false, 5));
+ assert!(frame_sent(10, 1, true, 5));
+ assert!(frame_sent(10, 1, false, 100));
+ assert!(frame_sent(10, 1, true, 100));
+ }
+
+ #[test]
+ fn stream_frame_more() {
+ // Try more data
+ assert!(!frame_sent(10, 100, false, 3));
+ assert!(!frame_sent(10, 100, true, 3));
+ assert!(frame_sent(10, 100, false, 4));
+ assert!(frame_sent(10, 100, true, 4));
+ assert!(frame_sent(10, 100, false, 5));
+ assert!(frame_sent(10, 100, true, 5));
+ assert!(frame_sent(10, 100, false, 100));
+ assert!(frame_sent(10, 100, true, 100));
+
+ assert!(frame_sent(10, 100, false, 1000));
+ assert!(frame_sent(10, 100, true, 1000));
+ }
+
+ #[test]
+ fn stream_frame_big_id() {
+ // A value that encodes to the largest varint.
+ const BIG: u64 = 1 << 30;
+ const BIGSZ: usize = 1 << 30;
+
+ assert!(!frame_sent_sid(BIG, BIGSZ, 0, false, 16));
+ assert!(!frame_sent_sid(BIG, BIGSZ, 0, true, 16));
+ assert!(!frame_sent_sid(BIG, BIGSZ, 0, false, 17));
+ assert!(frame_sent_sid(BIG, BIGSZ, 0, true, 17));
+ assert!(!frame_sent_sid(BIG, BIGSZ, 0, false, 18));
+ assert!(frame_sent_sid(BIG, BIGSZ, 0, true, 18));
+
+ assert!(!frame_sent_sid(BIG, BIGSZ, 1, false, 17));
+ assert!(!frame_sent_sid(BIG, BIGSZ, 1, true, 17));
+ assert!(frame_sent_sid(BIG, BIGSZ, 1, false, 18));
+ assert!(frame_sent_sid(BIG, BIGSZ, 1, true, 18));
+ assert!(frame_sent_sid(BIG, BIGSZ, 1, false, 19));
+ assert!(frame_sent_sid(BIG, BIGSZ, 1, true, 19));
+ assert!(frame_sent_sid(BIG, BIGSZ, 1, false, 100));
+ assert!(frame_sent_sid(BIG, BIGSZ, 1, true, 100));
+ }
+
+ #[test]
+ fn stream_frame_16384() {
+ const DATA16384: &[u8] = &[0x43; 16384];
+
+ // 16383/16384 is an odd boundary in STREAM frame construction.
+ // That is the boundary where a length goes from 2 bytes to 4 bytes.
+ // If the data fits in the available space, then it is simple:
+ let mut s = stream_with_sent(0, 0);
+ s.send(DATA16384).unwrap();
+ s.close();
+
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ let header_len = builder.len();
+ builder.set_limit(header_len + DATA16384.len() + 2);
+ let token = s.write_frame(&mut builder);
+ assert!(token.is_some());
+ // Expect STREAM + FIN only.
+ assert_eq!(&builder[header_len..header_len + 2], &[0b1001, 0]);
+ assert_eq!(&builder[header_len + 2..], DATA16384);
+
+ s.mark_as_lost(0, DATA16384.len(), true);
+
+ // However, if there is one extra byte of space, we will try to add a length.
+ // That length will then make the frame to be too large and the data will be
+ // truncated. The frame could carry one more byte of data, but it's a corner
+ // case we don't want to address as it should be rare (if not impossible).
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ let header_len = builder.len();
+ builder.set_limit(header_len + DATA16384.len() + 3);
+ let token = s.write_frame(&mut builder);
+ assert!(token.is_some());
+ // Expect STREAM + LEN + FIN.
+ assert_eq!(
+ &builder[header_len..header_len + 4],
+ &[0b1010, 0, 0x7f, 0xfd]
+ );
+ assert_eq!(
+ &builder[header_len + 4..],
+ &DATA16384[..DATA16384.len() - 3]
+ );
+ }
+
+ #[test]
+ fn stream_frame_64() {
+ const DATA64: &[u8] = &[0x43; 64];
+
+ // Unlike 16383/16384, the boundary at 63/64 is easy because the difference
+ // is just one byte. We lose just the last byte when there is more space.
+ let mut s = stream_with_sent(0, 0);
+ s.send(DATA64).unwrap();
+ s.close();
+
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ let header_len = builder.len();
+ builder.set_limit(header_len + 66);
+ let token = s.write_frame(&mut builder);
+ assert!(token.is_some());
+ // Expect STREAM + FIN only.
+ assert_eq!(&builder[header_len..header_len + 2], &[0b1001, 0]);
+ assert_eq!(&builder[header_len + 2..], DATA64);
+
+ s.mark_as_lost(0, DATA64.len(), true);
+
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ let header_len = builder.len();
+ builder.set_limit(header_len + 67);
+ let token = s.write_frame(&mut builder);
+ assert!(token.is_some());
+ // Expect STREAM + LEN, not FIN.
+ assert_eq!(&builder[header_len..header_len + 3], &[0b1010, 0, 63]);
+ assert_eq!(&builder[header_len + 3..], &DATA64[..63]);
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/sender.rs b/third_party/rust/neqo-transport/src/sender.rs
new file mode 100644
index 0000000000..15675f4b2b
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/sender.rs
@@ -0,0 +1,124 @@
+// 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.
+
+// Congestion control
+#![deny(clippy::pedantic)]
+#![allow(clippy::module_name_repetitions)]
+
+use crate::cc::{
+ ClassicCongestionControl, CongestionControl, CongestionControlAlgorithm, NewReno,
+ MAX_DATAGRAM_SIZE,
+};
+use crate::pace::Pacer;
+use crate::tracking::SentPacket;
+use neqo_common::qlog::NeqoQlog;
+
+use std::fmt::{self, Debug, Display};
+use std::time::{Duration, Instant};
+
+/// The number of packets we allow to burst from the pacer.
+pub const PACING_BURST_SIZE: usize = 2;
+
+#[derive(Debug)]
+pub struct PacketSender {
+ cc: Box<dyn CongestionControl>,
+ pacer: Option<Pacer>,
+}
+
+impl Display for PacketSender {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.cc)?;
+ if let Some(p) = &self.pacer {
+ write!(f, " {}", p)?;
+ }
+ Ok(())
+ }
+}
+
+impl PacketSender {
+ #[must_use]
+ pub fn new(alg: CongestionControlAlgorithm) -> Self {
+ Self {
+ cc: match alg {
+ CongestionControlAlgorithm::NewReno => {
+ Box::new(ClassicCongestionControl::new(NewReno::default()))
+ }
+ },
+ pacer: None,
+ }
+ }
+
+ pub fn set_qlog(&mut self, qlog: NeqoQlog) {
+ self.cc.set_qlog(qlog);
+ }
+
+ #[cfg(test)]
+ #[must_use]
+ pub fn cwnd(&self) -> usize {
+ self.cc.cwnd()
+ }
+
+ #[must_use]
+ pub fn cwnd_avail(&self) -> usize {
+ self.cc.cwnd_avail()
+ }
+
+ // Multi-packet version of OnPacketAckedCC
+ pub fn on_packets_acked(&mut self, acked_pkts: &[SentPacket]) {
+ self.cc.on_packets_acked(acked_pkts);
+ }
+
+ pub fn on_packets_lost(
+ &mut self,
+ first_rtt_sample_time: Option<Instant>,
+ prev_largest_acked_sent: Option<Instant>,
+ pto: Duration,
+ lost_packets: &[SentPacket],
+ ) {
+ self.cc.on_packets_lost(
+ first_rtt_sample_time,
+ prev_largest_acked_sent,
+ pto,
+ lost_packets,
+ );
+ }
+
+ pub fn discard(&mut self, pkt: &SentPacket) {
+ self.cc.discard(pkt);
+ }
+
+ pub fn on_packet_sent(&mut self, pkt: &SentPacket, rtt: Duration) {
+ self.pacer
+ .as_mut()
+ .unwrap()
+ .spend(pkt.time_sent, rtt, self.cc.cwnd(), pkt.size);
+ self.cc.on_packet_sent(pkt);
+ }
+
+ pub fn start_pacer(&mut self, now: Instant) {
+ // Start the pacer with a small burst size.
+ self.pacer = Some(Pacer::new(
+ now,
+ MAX_DATAGRAM_SIZE * PACING_BURST_SIZE,
+ MAX_DATAGRAM_SIZE,
+ ));
+ }
+
+ #[must_use]
+ pub fn next_paced(&self, rtt: Duration) -> Option<Instant> {
+ // Only pace if there are bytes in flight.
+ if self.cc.bytes_in_flight() > 0 {
+ Some(self.pacer.as_ref().unwrap().next(rtt, self.cc.cwnd()))
+ } else {
+ None
+ }
+ }
+
+ #[must_use]
+ pub fn recovery_packet(&self) -> bool {
+ self.cc.recovery_packet()
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/server.rs b/third_party/rust/neqo-transport/src/server.rs
new file mode 100644
index 0000000000..ef161a9fe4
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/server.rs
@@ -0,0 +1,636 @@
+// 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.
+
+// This file implements a server that can handle multiple connections.
+
+use neqo_common::{
+ self as common, event::Provider, hex, qdebug, qerror, qinfo, qlog::NeqoQlog, qtrace, qwarn,
+ timer::Timer, Datagram, Decoder, Role,
+};
+use neqo_crypto::{AntiReplay, Cipher, ZeroRttCheckResult, ZeroRttChecker};
+
+pub use crate::addr_valid::ValidateAddress;
+use crate::addr_valid::{AddressValidation, AddressValidationResult};
+use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdManager, ConnectionIdRef};
+use crate::connection::{Connection, Output, State};
+use crate::packet::{PacketBuilder, PacketType, PublicPacket};
+use crate::{ConnectionParameters, QuicVersion, Res};
+
+use std::cell::RefCell;
+use std::collections::{HashMap, HashSet, VecDeque};
+use std::fs::OpenOptions;
+use std::mem;
+use std::net::SocketAddr;
+use std::ops::{Deref, DerefMut};
+use std::path::PathBuf;
+use std::rc::{Rc, Weak};
+use std::time::{Duration, Instant};
+
+pub enum InitialResult {
+ Accept,
+ Drop,
+ Retry(Vec<u8>),
+}
+
+/// MIN_INITIAL_PACKET_SIZE is the smallest packet that can be used to establish
+/// a new connection across all QUIC versions this server supports.
+const MIN_INITIAL_PACKET_SIZE: usize = 1200;
+const TIMER_GRANULARITY: Duration = Duration::from_millis(10);
+const TIMER_CAPACITY: usize = 16384;
+
+type StateRef = Rc<RefCell<ServerConnectionState>>;
+type CidMgr = Rc<RefCell<dyn ConnectionIdManager>>;
+type ConnectionTableRef = Rc<RefCell<HashMap<ConnectionId, StateRef>>>;
+
+#[derive(Debug)]
+pub struct ServerConnectionState {
+ c: Connection,
+ active_attempt: Option<AttemptKey>,
+ last_timer: Instant,
+}
+
+impl Deref for ServerConnectionState {
+ type Target = Connection;
+ fn deref(&self) -> &Self::Target {
+ &self.c
+ }
+}
+
+impl DerefMut for ServerConnectionState {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.c
+ }
+}
+
+/// A `AttemptKey` is used to disambiguate connection attempts.
+/// Multiple connection attempts with the same key won't produce multiple connections.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+struct AttemptKey {
+ // Using the remote address is sufficient for disambiguation,
+ // until we support multiple local socket addresses.
+ remote_address: SocketAddr,
+ odcid: ConnectionId,
+}
+
+/// A `ServerZeroRttChecker` is a simple wrapper around a single checker.
+/// It uses `RefCell` so that the wrapped checker can be shared between
+/// multiple connections created by the server.
+#[derive(Clone, Debug)]
+struct ServerZeroRttChecker {
+ checker: Rc<RefCell<Box<dyn ZeroRttChecker>>>,
+}
+
+impl ServerZeroRttChecker {
+ pub fn new(checker: Box<dyn ZeroRttChecker>) -> Self {
+ Self {
+ checker: Rc::new(RefCell::new(checker)),
+ }
+ }
+}
+
+impl ZeroRttChecker for ServerZeroRttChecker {
+ fn check(&self, token: &[u8]) -> ZeroRttCheckResult {
+ self.checker.borrow().check(token)
+ }
+}
+
+/// `InitialDetails` holds important information for processing `Initial` packets.
+struct InitialDetails {
+ src_cid: ConnectionId,
+ dst_cid: ConnectionId,
+ token: Vec<u8>,
+ quic_version: QuicVersion,
+}
+
+impl InitialDetails {
+ fn new(packet: &PublicPacket) -> Self {
+ Self {
+ src_cid: ConnectionId::from(packet.scid()),
+ dst_cid: ConnectionId::from(packet.dcid()),
+ token: packet.token().to_vec(),
+ quic_version: packet.version().unwrap(),
+ }
+ }
+}
+
+pub struct Server {
+ /// The names of certificates.
+ certs: Vec<String>,
+ /// The ALPN values that the server supports.
+ protocols: Vec<String>,
+ /// The cipher suites that the server supports.
+ ciphers: Vec<Cipher>,
+ /// Anti-replay configuration for 0-RTT.
+ anti_replay: AntiReplay,
+ /// A function for determining if 0-RTT can be accepted.
+ zero_rtt_checker: ServerZeroRttChecker,
+ /// A connection ID manager.
+ cid_manager: CidMgr,
+ /// Connection parameters.
+ conn_params: ConnectionParameters,
+ /// Active connection attempts, keyed by `AttemptKey`. Initial packets with
+ /// the same key are routed to the connection that was first accepted.
+ /// This is cleared out when the connection is closed or established.
+ active_attempts: HashMap<AttemptKey, StateRef>,
+ /// All connections, keyed by ConnectionId.
+ connections: ConnectionTableRef,
+ /// The connections that have new events.
+ active: HashSet<ActiveConnectionRef>,
+ /// The set of connections that need immediate processing.
+ waiting: VecDeque<StateRef>,
+ /// Outstanding timers for connections.
+ timers: Timer<StateRef>,
+ /// Address validation logic, which determines whether we send a Retry.
+ address_validation: Rc<RefCell<AddressValidation>>,
+ /// Directory to create qlog traces in
+ qlog_dir: Option<PathBuf>,
+}
+
+impl Server {
+ /// Construct a new server.
+ /// * `now` is the time that the server is instantiated.
+ /// * `certs` is a list of the certificates that should be configured.
+ /// * `protocols` is the preference list of ALPN values.
+ /// * `anti_replay` is an anti-replay context.
+ /// * `zero_rtt_checker` determines whether 0-RTT should be accepted. This
+ /// will be passed the value of the `extra` argument that was passed to
+ /// `Connection::send_ticket` to see if it is OK.
+ /// * `cid_manager` is responsible for generating connection IDs and parsing them;
+ /// connection IDs produced by the manager cannot be zero-length.
+ pub fn new(
+ now: Instant,
+ certs: &[impl AsRef<str>],
+ protocols: &[impl AsRef<str>],
+ anti_replay: AntiReplay,
+ zero_rtt_checker: Box<dyn ZeroRttChecker>,
+ cid_manager: CidMgr,
+ conn_params: ConnectionParameters,
+ ) -> Res<Self> {
+ let validation = AddressValidation::new(now, ValidateAddress::Never)?;
+ Ok(Self {
+ certs: certs.iter().map(|x| String::from(x.as_ref())).collect(),
+ protocols: protocols.iter().map(|x| String::from(x.as_ref())).collect(),
+ ciphers: Vec::new(),
+ anti_replay,
+ zero_rtt_checker: ServerZeroRttChecker::new(zero_rtt_checker),
+ cid_manager,
+ conn_params,
+ active_attempts: HashMap::default(),
+ connections: Rc::default(),
+ active: HashSet::default(),
+ waiting: VecDeque::default(),
+ timers: Timer::new(now, TIMER_GRANULARITY, TIMER_CAPACITY),
+ address_validation: Rc::new(RefCell::new(validation)),
+ qlog_dir: None,
+ })
+ }
+
+ /// Set or clear directory to create logs of connection events in QLOG format.
+ pub fn set_qlog_dir(&mut self, dir: Option<PathBuf>) {
+ self.qlog_dir = dir;
+ }
+
+ /// Set the policy for address validation.
+ pub fn set_validation(&mut self, v: ValidateAddress) {
+ self.address_validation.borrow_mut().set_validation(v);
+ }
+
+ /// Set the cipher suites that should be used. Set an empty value to use
+ /// default values.
+ pub fn set_ciphers(&mut self, ciphers: impl AsRef<[Cipher]>) {
+ self.ciphers = Vec::from(ciphers.as_ref());
+ }
+
+ fn remove_timer(&mut self, c: &StateRef) {
+ let last = c.borrow().last_timer;
+ self.timers.remove(last, |t| Rc::ptr_eq(t, c));
+ }
+
+ fn process_connection(
+ &mut self,
+ c: StateRef,
+ dgram: Option<Datagram>,
+ now: Instant,
+ ) -> Option<Datagram> {
+ qtrace!([self], "Process connection {:?}", c);
+ let out = c.borrow_mut().process(dgram, now);
+ match out {
+ Output::Datagram(_) => {
+ qtrace!([self], "Sending packet, added to waiting connections");
+ self.waiting.push_back(Rc::clone(&c));
+ }
+ Output::Callback(delay) => {
+ let next = now + delay;
+ if next != c.borrow().last_timer {
+ qtrace!([self], "Change timer to {:?}", next);
+ self.remove_timer(&c);
+ c.borrow_mut().last_timer = next;
+ self.timers.add(next, Rc::clone(&c));
+ }
+ }
+ _ => {
+ self.remove_timer(&c);
+ }
+ }
+ if c.borrow().has_events() {
+ qtrace!([self], "Connection active: {:?}", c);
+ self.active.insert(ActiveConnectionRef { c: Rc::clone(&c) });
+ }
+
+ if *c.borrow().state() > State::Handshaking {
+ // Remove any active connection attempt now that this is no longer handshaking.
+ if let Some(k) = c.borrow_mut().active_attempt.take() {
+ self.active_attempts.remove(&k);
+ }
+ }
+
+ if matches!(c.borrow().state(), State::Closed(_)) {
+ c.borrow_mut().set_qlog(NeqoQlog::disabled());
+ self.connections
+ .borrow_mut()
+ .retain(|_, v| !Rc::ptr_eq(v, &c));
+ }
+ out.dgram()
+ }
+
+ fn connection(&self, cid: &ConnectionIdRef) -> Option<StateRef> {
+ if let Some(c) = self.connections.borrow().get(&cid[..]) {
+ Some(Rc::clone(&c))
+ } else {
+ None
+ }
+ }
+
+ fn handle_initial(
+ &mut self,
+ initial: InitialDetails,
+ dgram: Datagram,
+ now: Instant,
+ ) -> Option<Datagram> {
+ qdebug!([self], "Handle initial");
+ let res = self
+ .address_validation
+ .borrow()
+ .validate(&initial.token, dgram.source(), now);
+ match res {
+ AddressValidationResult::Invalid => None,
+ AddressValidationResult::Pass => self.connection_attempt(initial, dgram, None, now),
+ AddressValidationResult::ValidRetry(orig_dcid) => {
+ self.connection_attempt(initial, dgram, Some(orig_dcid), now)
+ }
+ AddressValidationResult::Validate => {
+ qinfo!([self], "Send retry for {:?}", initial.dst_cid);
+
+ let res = self.address_validation.borrow().generate_retry_token(
+ &initial.dst_cid,
+ dgram.source(),
+ now,
+ );
+ let token = if let Ok(t) = res {
+ t
+ } else {
+ qerror!([self], "unable to generate token, dropping packet");
+ return None;
+ };
+ let new_dcid = self.cid_manager.borrow_mut().generate_cid();
+ let packet = PacketBuilder::retry(
+ initial.quic_version,
+ &initial.src_cid,
+ &new_dcid,
+ &token,
+ &initial.dst_cid,
+ );
+ if let Ok(p) = packet {
+ let retry = Datagram::new(dgram.destination(), dgram.source(), p);
+ Some(retry)
+ } else {
+ qerror!([self], "unable to encode retry, dropping packet");
+ None
+ }
+ }
+ }
+ }
+
+ fn connection_attempt(
+ &mut self,
+ initial: InitialDetails,
+ dgram: Datagram,
+ orig_dcid: Option<ConnectionId>,
+ now: Instant,
+ ) -> Option<Datagram> {
+ let attempt_key = AttemptKey {
+ remote_address: dgram.source(),
+ odcid: orig_dcid.as_ref().unwrap_or(&initial.dst_cid).clone(),
+ };
+ if let Some(c) = self.active_attempts.get(&attempt_key) {
+ qdebug!(
+ [self],
+ "Handle Initial for existing connection attempt {:?}",
+ attempt_key
+ );
+ let c = Rc::clone(c);
+ self.process_connection(c, Some(dgram), now)
+ } else {
+ self.accept_connection(attempt_key, initial, dgram, orig_dcid, now)
+ }
+ }
+
+ fn create_qlog_trace(&self, attempt_key: &AttemptKey) -> NeqoQlog {
+ if let Some(qlog_dir) = &self.qlog_dir {
+ let mut qlog_path = qlog_dir.to_path_buf();
+
+ qlog_path.push(format!("{}.qlog", attempt_key.odcid));
+
+ // The original DCID is chosen by the client. Using create_new()
+ // prevents attackers from overwriting existing logs.
+ match OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&qlog_path)
+ {
+ Ok(f) => {
+ qinfo!("Qlog output to {}", qlog_path.display());
+
+ let streamer = ::qlog::QlogStreamer::new(
+ qlog::QLOG_VERSION.to_string(),
+ Some("Neqo server qlog".to_string()),
+ Some("Neqo server qlog".to_string()),
+ None,
+ std::time::Instant::now(),
+ common::qlog::new_trace(Role::Server),
+ Box::new(f),
+ );
+ let n_qlog = NeqoQlog::enabled(streamer, qlog_path);
+ match n_qlog {
+ Ok(nql) => nql,
+ Err(e) => {
+ // Keep going but w/o qlogging
+ qerror!("NeqoQlog error: {}", e);
+ NeqoQlog::disabled()
+ }
+ }
+ }
+ Err(e) => {
+ qerror!(
+ "Could not open file {} for qlog output: {}",
+ qlog_path.display(),
+ e
+ );
+ NeqoQlog::disabled()
+ }
+ }
+ } else {
+ NeqoQlog::disabled()
+ }
+ }
+
+ fn accept_connection(
+ &mut self,
+ attempt_key: AttemptKey,
+ initial: InitialDetails,
+ dgram: Datagram,
+ orig_dcid: Option<ConnectionId>,
+ now: Instant,
+ ) -> Option<Datagram> {
+ qinfo!([self], "Accept connection {:?}", attempt_key);
+ // The internal connection ID manager that we use is not used directly.
+ // Instead, wrap it so that we can save connection IDs.
+
+ let cid_mgr = Rc::new(RefCell::new(ServerConnectionIdManager {
+ c: Weak::new(),
+ cid_manager: Rc::clone(&self.cid_manager),
+ connections: Rc::clone(&self.connections),
+ saved_cids: Vec::new(),
+ }));
+
+ let sconn = Connection::new_server(
+ &self.certs,
+ &self.protocols,
+ Rc::clone(&cid_mgr) as _,
+ &self.conn_params.clone().quic_version(initial.quic_version),
+ );
+
+ if let Ok(mut c) = sconn {
+ let zcheck = self.zero_rtt_checker.clone();
+ if c.server_enable_0rtt(&self.anti_replay, zcheck).is_err() {
+ qwarn!([self], "Unable to enable 0-RTT");
+ }
+ if let Some(odcid) = orig_dcid {
+ // There was a retry, so set the connection IDs for.
+ c.set_retry_cids(odcid, initial.src_cid, initial.dst_cid);
+ }
+ c.set_validation(Rc::clone(&self.address_validation));
+ c.set_qlog(self.create_qlog_trace(&attempt_key));
+ let c = Rc::new(RefCell::new(ServerConnectionState {
+ c,
+ last_timer: now,
+ active_attempt: Some(attempt_key.clone()),
+ }));
+ cid_mgr.borrow_mut().set_connection(Rc::clone(&c));
+ let previous_attempt = self.active_attempts.insert(attempt_key, Rc::clone(&c));
+ debug_assert!(previous_attempt.is_none());
+ self.process_connection(c, Some(dgram), now)
+ } else {
+ qwarn!([self], "Unable to create connection");
+ None
+ }
+ }
+
+ fn process_input(&mut self, dgram: Datagram, now: Instant) -> Option<Datagram> {
+ qtrace!("Process datagram: {}", hex(&dgram[..]));
+
+ // This is only looking at the first packet header in the datagram.
+ // All packets in the datagram are routed to the same connection.
+ let res = PublicPacket::decode(&dgram[..], self.cid_manager.borrow().as_decoder());
+ let (packet, _remainder) = match res {
+ Ok(res) => res,
+ _ => {
+ qtrace!([self], "Discarding {:?}", dgram);
+ return None;
+ }
+ };
+
+ // Finding an existing connection. Should be the most common case.
+ if let Some(c) = self.connection(packet.dcid()) {
+ return self.process_connection(c, Some(dgram), now);
+ }
+
+ if packet.packet_type() == PacketType::Short {
+ // TODO send a stateless reset here.
+ qtrace!([self], "Short header packet for an unknown connection");
+ return None;
+ }
+
+ if dgram.len() < MIN_INITIAL_PACKET_SIZE {
+ qtrace!([self], "Bogus packet: too short");
+ return None;
+ }
+ match packet.packet_type() {
+ PacketType::Initial => {
+ // Copy values from `packet` because they are currently still borrowing from `dgram`.
+ let initial = InitialDetails::new(&packet);
+ self.handle_initial(initial, dgram, now)
+ }
+ PacketType::OtherVersion => {
+ let vn = PacketBuilder::version_negotiation(packet.scid(), packet.dcid());
+ Some(Datagram::new(dgram.destination(), dgram.source(), vn))
+ }
+ _ => {
+ qtrace!([self], "Not an initial packet");
+ None
+ }
+ }
+ }
+
+ /// Iterate through the pending connections looking for any that might want
+ /// to send a datagram. Stop at the first one that does.
+ fn process_next_output(&mut self, now: Instant) -> Option<Datagram> {
+ qtrace!([self], "No packet to send, look at waiting connections");
+ while let Some(c) = self.waiting.pop_front() {
+ if let Some(d) = self.process_connection(c, None, now) {
+ return Some(d);
+ }
+ }
+ qtrace!([self], "No packet to send still, run timers");
+ while let Some(c) = self.timers.take_next(now) {
+ if let Some(d) = self.process_connection(c, None, now) {
+ return Some(d);
+ }
+ }
+ None
+ }
+
+ fn next_time(&mut self, now: Instant) -> Option<Duration> {
+ if self.waiting.is_empty() {
+ self.timers.next_time().map(|x| x - now)
+ } else {
+ Some(Duration::new(0, 0))
+ }
+ }
+
+ pub fn process(&mut self, dgram: Option<Datagram>, now: Instant) -> Output {
+ let out = if let Some(d) = dgram {
+ self.process_input(d, now)
+ } else {
+ None
+ };
+ let out = out.or_else(|| self.process_next_output(now));
+ match out {
+ Some(d) => {
+ qtrace!([self], "Send packet: {:?}", d);
+ Output::Datagram(d)
+ }
+ _ => match self.next_time(now) {
+ Some(delay) => {
+ qtrace!([self], "Wait: {:?}", delay);
+ Output::Callback(delay)
+ }
+ _ => {
+ qtrace!([self], "Go dormant");
+ Output::None
+ }
+ },
+ }
+ }
+
+ /// This lists the connections that have received new events
+ /// as a result of calling `process()`.
+ pub fn active_connections(&mut self) -> Vec<ActiveConnectionRef> {
+ mem::take(&mut self.active).into_iter().collect()
+ }
+
+ pub fn add_to_waiting(&mut self, c: ActiveConnectionRef) {
+ self.waiting.push_back(c.connection());
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct ActiveConnectionRef {
+ c: StateRef,
+}
+
+impl ActiveConnectionRef {
+ pub fn borrow<'a>(&'a self) -> impl Deref<Target = Connection> + 'a {
+ std::cell::Ref::map(self.c.borrow(), |c| &c.c)
+ }
+
+ pub fn borrow_mut<'a>(&'a mut self) -> impl DerefMut<Target = Connection> + 'a {
+ std::cell::RefMut::map(self.c.borrow_mut(), |c| &mut c.c)
+ }
+
+ pub fn connection(&self) -> StateRef {
+ Rc::clone(&self.c)
+ }
+}
+
+impl std::hash::Hash for ActiveConnectionRef {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ let ptr: *const _ = self.c.as_ref();
+ ptr.hash(state)
+ }
+}
+
+impl PartialEq for ActiveConnectionRef {
+ fn eq(&self, other: &Self) -> bool {
+ Rc::ptr_eq(&self.c, &other.c)
+ }
+}
+
+impl Eq for ActiveConnectionRef {}
+
+struct ServerConnectionIdManager {
+ c: Weak<RefCell<ServerConnectionState>>,
+ connections: ConnectionTableRef,
+ cid_manager: CidMgr,
+ saved_cids: Vec<ConnectionId>,
+}
+
+impl ServerConnectionIdManager {
+ pub fn set_connection(&mut self, c: StateRef) {
+ let saved = std::mem::replace(&mut self.saved_cids, Vec::with_capacity(0));
+ for cid in saved {
+ qtrace!("ServerConnectionIdManager inserting saved cid {}", cid);
+ self.insert_cid(cid, Rc::clone(&c));
+ }
+ self.c = Rc::downgrade(&c);
+ }
+
+ fn insert_cid(&mut self, cid: ConnectionId, rc: StateRef) {
+ debug_assert!(!cid.is_empty());
+ self.connections.borrow_mut().insert(cid, rc);
+ }
+}
+
+impl ConnectionIdDecoder for ServerConnectionIdManager {
+ fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> {
+ self.cid_manager.borrow_mut().decode_cid(dec)
+ }
+}
+
+impl ConnectionIdManager for ServerConnectionIdManager {
+ fn generate_cid(&mut self) -> ConnectionId {
+ let cid = self.cid_manager.borrow_mut().generate_cid();
+ if let Some(rc) = self.c.upgrade() {
+ self.insert_cid(cid.clone(), rc);
+ } else {
+ // This function can be called before the connection is set.
+ // So save any connection IDs until that hookup happens.
+ qtrace!("ServerConnectionIdManager saving cid {}", cid);
+ self.saved_cids.push(cid.clone());
+ }
+ cid
+ }
+
+ fn as_decoder(&self) -> &dyn ConnectionIdDecoder {
+ self
+ }
+}
+
+impl ::std::fmt::Display for Server {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Server")
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/stats.rs b/third_party/rust/neqo-transport/src/stats.rs
new file mode 100644
index 0000000000..9d01dfe211
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/stats.rs
@@ -0,0 +1,195 @@
+// 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.
+
+// Tracking of some useful statistics.
+#![deny(clippy::pedantic)]
+
+use crate::packet::PacketNumber;
+use neqo_common::qinfo;
+use std::cell::RefCell;
+use std::fmt::{self, Debug};
+use std::ops::Deref;
+use std::rc::Rc;
+
+pub(crate) const MAX_PTO_COUNTS: usize = 16;
+
+#[derive(Default, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub struct FrameStats {
+ pub all: usize,
+ pub ack: usize,
+ pub largest_acknowledged: PacketNumber,
+
+ pub crypto: usize,
+ pub stream: usize,
+ pub reset_stream: usize,
+ pub stop_sending: usize,
+
+ pub ping: usize,
+ pub padding: usize,
+
+ pub max_streams: usize,
+ pub streams_blocked: usize,
+ pub max_data: usize,
+ pub data_blocked: usize,
+ pub max_stream_data: usize,
+ pub stream_data_blocked: usize,
+
+ pub new_connection_id: usize,
+ pub retire_connection_id: usize,
+
+ pub path_challenge: usize,
+ pub path_response: usize,
+
+ pub connection_close: usize,
+ pub handshake_done: usize,
+ pub new_token: usize,
+}
+
+impl Debug for FrameStats {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ " crypto {} done {} token {} close {}",
+ self.crypto, self.handshake_done, self.new_token, self.connection_close,
+ )?;
+ writeln!(
+ f,
+ " ack {} (max {}) ping {} padding {}",
+ self.ack, self.largest_acknowledged, self.ping, self.padding
+ )?;
+ writeln!(
+ f,
+ " stream {} reset {} stop {}",
+ self.stream, self.reset_stream, self.stop_sending,
+ )?;
+ writeln!(
+ f,
+ " max: stream {} data {} stream_data {}",
+ self.max_streams, self.max_data, self.max_stream_data,
+ )?;
+ writeln!(
+ f,
+ " blocked: stream {} data {} stream_data {}",
+ self.streams_blocked, self.data_blocked, self.stream_data_blocked,
+ )?;
+ writeln!(
+ f,
+ " ncid {} rcid {} pchallenge {} presponse {}",
+ self.new_connection_id,
+ self.retire_connection_id,
+ self.path_challenge,
+ self.path_response,
+ )
+ }
+}
+
+/// Connection statistics
+#[derive(Default, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub struct Stats {
+ info: String,
+
+ /// Total packets received, including all the bad ones.
+ pub packets_rx: usize,
+ /// Duplicate packets received.
+ pub dups_rx: usize,
+ /// Dropped packets or dropped garbage.
+ pub dropped_rx: usize,
+ /// The number of packet that were saved for later processing.
+ pub saved_datagrams: usize,
+
+ /// Total packets sent.
+ pub packets_tx: usize,
+ /// Total number of packets that are declared lost.
+ pub lost: usize,
+ /// Late acknowledgments, for packets that were declared lost already.
+ pub late_ack: usize,
+ /// Acknowledgments for packets that contained data that was marked
+ /// for retransmission when the PTO timer popped.
+ pub pto_ack: usize,
+
+ /// Whether the connection was resumed successfully.
+ pub resumed: bool,
+
+ /// Count PTOs. Single PTOs, 2 PTOs in a row, 3 PTOs in row, etc. are counted
+ /// separately.
+ pub pto_counts: [usize; MAX_PTO_COUNTS],
+
+ /// Count frames received.
+ pub frame_rx: FrameStats,
+ /// Count frames sent.
+ pub frame_tx: FrameStats,
+}
+
+impl Stats {
+ pub fn init(&mut self, info: String) {
+ self.info = info;
+ }
+
+ pub fn pkt_dropped(&mut self, reason: impl AsRef<str>) {
+ self.dropped_rx += 1;
+ qinfo!(
+ [self.info],
+ "Dropped received packet: {}; Total: {}",
+ reason.as_ref(),
+ self.dropped_rx
+ )
+ }
+
+ pub fn add_pto_count(&mut self, count: usize) {
+ debug_assert!(count > 0);
+ if count >= MAX_PTO_COUNTS {
+ // We can't move this count any further, so stop.
+ return;
+ }
+ self.pto_counts[count - 1] += 1;
+ if count > 1 {
+ debug_assert!(self.pto_counts[count - 2] > 0);
+ self.pto_counts[count - 2] -= 1;
+ }
+ }
+}
+
+impl Debug for Stats {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "stats for {}", self.info)?;
+ writeln!(
+ f,
+ " rx: {} drop {} dup {} saved {}",
+ self.packets_rx, self.dropped_rx, self.dups_rx, self.saved_datagrams
+ )?;
+ writeln!(
+ f,
+ " tx: {} lost {} lateack {} ptoack {}",
+ self.packets_tx, self.lost, self.late_ack, self.pto_ack
+ )?;
+ writeln!(f, " resumed: {} ", self.resumed)?;
+ writeln!(f, " frames rx:")?;
+ self.frame_rx.fmt(f)?;
+ writeln!(f, " frames tx:")?;
+ self.frame_tx.fmt(f)
+ }
+}
+
+#[derive(Default, Clone)]
+#[allow(clippy::module_name_repetitions)]
+pub struct StatsCell {
+ stats: Rc<RefCell<Stats>>,
+}
+
+impl Deref for StatsCell {
+ type Target = RefCell<Stats>;
+ fn deref(&self) -> &Self::Target {
+ &*self.stats
+ }
+}
+
+impl Debug for StatsCell {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.stats.borrow().fmt(f)
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/stream_id.rs b/third_party/rust/neqo-transport/src/stream_id.rs
new file mode 100644
index 0000000000..486bfb937c
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/stream_id.rs
@@ -0,0 +1,205 @@
+// 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.
+
+// Stream ID and stream index handling.
+
+use std::ops::AddAssign;
+
+use neqo_common::Role;
+
+use crate::connection::{LOCAL_STREAM_LIMIT_BIDI, LOCAL_STREAM_LIMIT_UNI};
+use crate::frame::StreamType;
+
+pub struct StreamIndexes {
+ pub local_max_stream_uni: StreamIndex,
+ pub local_max_stream_bidi: StreamIndex,
+ pub local_next_stream_uni: StreamIndex,
+ pub local_next_stream_bidi: StreamIndex,
+ pub remote_max_stream_uni: StreamIndex,
+ pub remote_max_stream_bidi: StreamIndex,
+ pub remote_next_stream_uni: StreamIndex,
+ pub remote_next_stream_bidi: StreamIndex,
+}
+
+impl StreamIndexes {
+ pub fn new() -> Self {
+ Self {
+ local_max_stream_bidi: StreamIndex::new(LOCAL_STREAM_LIMIT_BIDI),
+ local_max_stream_uni: StreamIndex::new(LOCAL_STREAM_LIMIT_UNI),
+ local_next_stream_uni: StreamIndex::new(0),
+ local_next_stream_bidi: StreamIndex::new(0),
+ remote_max_stream_bidi: StreamIndex::new(0),
+ remote_max_stream_uni: StreamIndex::new(0),
+ remote_next_stream_uni: StreamIndex::new(0),
+ remote_next_stream_bidi: StreamIndex::new(0),
+ }
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Ord, PartialOrd, Hash)]
+pub struct StreamId(u64);
+
+impl StreamId {
+ pub const fn new(id: u64) -> Self {
+ Self(id)
+ }
+
+ pub fn as_u64(self) -> u64 {
+ self.0
+ }
+
+ pub fn is_bidi(self) -> bool {
+ self.as_u64() & 0x02 == 0
+ }
+
+ pub fn is_uni(self) -> bool {
+ !self.is_bidi()
+ }
+
+ pub fn stream_type(self) -> StreamType {
+ if self.is_bidi() {
+ StreamType::BiDi
+ } else {
+ StreamType::UniDi
+ }
+ }
+
+ pub fn is_client_initiated(self) -> bool {
+ self.as_u64() & 0x01 == 0
+ }
+
+ pub fn is_server_initiated(self) -> bool {
+ !self.is_client_initiated()
+ }
+
+ pub fn role(self) -> Role {
+ if self.is_client_initiated() {
+ Role::Client
+ } else {
+ Role::Server
+ }
+ }
+
+ pub fn is_self_initiated(self, my_role: Role) -> bool {
+ match my_role {
+ Role::Client if self.is_client_initiated() => true,
+ Role::Server if self.is_server_initiated() => true,
+ _ => false,
+ }
+ }
+
+ pub fn is_remote_initiated(self, my_role: Role) -> bool {
+ !self.is_self_initiated(my_role)
+ }
+
+ pub fn is_send_only(self, my_role: Role) -> bool {
+ self.is_uni() && self.is_self_initiated(my_role)
+ }
+
+ pub fn is_recv_only(self, my_role: Role) -> bool {
+ self.is_uni() && self.is_remote_initiated(my_role)
+ }
+}
+
+impl From<u64> for StreamId {
+ fn from(val: u64) -> Self {
+ Self::new(val)
+ }
+}
+
+impl PartialEq<u64> for StreamId {
+ fn eq(&self, other: &u64) -> bool {
+ self.as_u64() == *other
+ }
+}
+
+impl ::std::fmt::Display for StreamId {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{}", self.as_u64())
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Ord, PartialOrd, Hash)]
+pub struct StreamIndex(u64);
+
+impl StreamIndex {
+ pub fn new(val: u64) -> Self {
+ Self(val)
+ }
+
+ pub fn to_stream_id(self, stream_type: StreamType, role: Role) -> StreamId {
+ let type_val = match stream_type {
+ StreamType::BiDi => 0,
+ StreamType::UniDi => 2,
+ };
+ let role_val = match role {
+ Role::Server => 1,
+ Role::Client => 0,
+ };
+
+ StreamId::from((self.0 << 2) + type_val + role_val)
+ }
+
+ pub fn as_u64(self) -> u64 {
+ self.0
+ }
+}
+
+impl From<StreamId> for StreamIndex {
+ fn from(val: StreamId) -> Self {
+ Self(val.as_u64() >> 2)
+ }
+}
+
+impl AddAssign<u64> for StreamIndex {
+ fn add_assign(&mut self, other: u64) {
+ *self = Self::new(self.as_u64() + other)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{StreamIndex, StreamType};
+ use neqo_common::Role;
+
+ #[test]
+ fn bidi_stream_properties() {
+ let id1 = StreamIndex::new(4).to_stream_id(StreamType::BiDi, Role::Client);
+ assert_eq!(id1.is_bidi(), true);
+ assert_eq!(id1.is_uni(), false);
+ assert_eq!(id1.is_client_initiated(), true);
+ assert_eq!(id1.is_server_initiated(), false);
+ assert_eq!(id1.role(), Role::Client);
+ assert_eq!(id1.is_self_initiated(Role::Client), true);
+ assert_eq!(id1.is_self_initiated(Role::Server), false);
+ assert_eq!(id1.is_remote_initiated(Role::Client), false);
+ assert_eq!(id1.is_remote_initiated(Role::Server), true);
+ assert_eq!(id1.is_send_only(Role::Server), false);
+ assert_eq!(id1.is_send_only(Role::Client), false);
+ assert_eq!(id1.is_recv_only(Role::Server), false);
+ assert_eq!(id1.is_recv_only(Role::Client), false);
+ assert_eq!(id1.as_u64(), 16);
+ }
+
+ #[test]
+ fn uni_stream_properties() {
+ let id2 = StreamIndex::new(8).to_stream_id(StreamType::UniDi, Role::Server);
+ assert_eq!(id2.is_bidi(), false);
+ assert_eq!(id2.is_uni(), true);
+ assert_eq!(id2.is_client_initiated(), false);
+ assert_eq!(id2.is_server_initiated(), true);
+ assert_eq!(id2.role(), Role::Server);
+ assert_eq!(id2.is_self_initiated(Role::Client), false);
+ assert_eq!(id2.is_self_initiated(Role::Server), true);
+ assert_eq!(id2.is_remote_initiated(Role::Client), true);
+ assert_eq!(id2.is_remote_initiated(Role::Server), false);
+ assert_eq!(id2.is_send_only(Role::Server), true);
+ assert_eq!(id2.is_send_only(Role::Client), false);
+ assert_eq!(id2.is_recv_only(Role::Server), false);
+ assert_eq!(id2.is_recv_only(Role::Client), true);
+ assert_eq!(id2.as_u64(), 35);
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/tparams.rs b/third_party/rust/neqo-transport/src/tparams.rs
new file mode 100644
index 0000000000..f0cfbf2203
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/tparams.rs
@@ -0,0 +1,541 @@
+// 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.
+
+// Transport parameters. See -transport section 7.3.
+
+#![allow(dead_code)]
+use crate::{Error, Res};
+use neqo_common::{hex, qdebug, qinfo, qtrace, Decoder, Encoder};
+use neqo_crypto::constants::{TLS_HS_CLIENT_HELLO, TLS_HS_ENCRYPTED_EXTENSIONS};
+use neqo_crypto::ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult};
+use neqo_crypto::{HandshakeMessage, ZeroRttCheckResult, ZeroRttChecker};
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::rc::Rc;
+
+struct PreferredAddress {
+ // TODO(ekr@rtfm.com): Implement.
+}
+
+pub type TransportParameterId = u64;
+macro_rules! tpids {
+ { $($n:ident = $v:expr),+ $(,)? } => {
+ $(pub const $n: TransportParameterId = $v as TransportParameterId;)+
+ };
+ }
+tpids! {
+ ORIGINAL_DESTINATION_CONNECTION_ID = 0x00,
+ IDLE_TIMEOUT = 0x01,
+ STATELESS_RESET_TOKEN = 0x02,
+ MAX_UDP_PAYLOAD_SIZE = 0x03,
+ INITIAL_MAX_DATA = 0x04,
+ INITIAL_MAX_STREAM_DATA_BIDI_LOCAL = 0x05,
+ INITIAL_MAX_STREAM_DATA_BIDI_REMOTE = 0x06,
+ INITIAL_MAX_STREAM_DATA_UNI = 0x07,
+ INITIAL_MAX_STREAMS_BIDI = 0x08,
+ INITIAL_MAX_STREAMS_UNI = 0x09,
+ ACK_DELAY_EXPONENT = 0x0a,
+ MAX_ACK_DELAY = 0x0b,
+ DISABLE_MIGRATION = 0x0c,
+ PREFERRED_ADDRESS = 0x0d,
+ ACTIVE_CONNECTION_ID_LIMIT = 0x0e,
+ INITIAL_SOURCE_CONNECTION_ID = 0x0f,
+ RETRY_SOURCE_CONNECTION_ID = 0x10,
+ GREASE_QUIC_BIT = 0x2ab2,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum TransportParameter {
+ Bytes(Vec<u8>),
+ Integer(u64),
+ Empty,
+}
+
+impl TransportParameter {
+ fn encode(&self, enc: &mut Encoder, tp: TransportParameterId) {
+ qdebug!("TP encoded; type 0x{:02x} val {:?}", tp, self);
+ enc.encode_varint(tp);
+ match self {
+ Self::Bytes(a) => {
+ enc.encode_vvec(a);
+ }
+ Self::Integer(a) => {
+ enc.encode_vvec_with(|enc_inner| {
+ enc_inner.encode_varint(*a);
+ });
+ }
+ Self::Empty => {
+ enc.encode_varint(0_u64);
+ }
+ };
+ }
+
+ fn decode(dec: &mut Decoder) -> Res<Option<(TransportParameterId, Self)>> {
+ let tp = match dec.decode_varint() {
+ Some(v) => v,
+ _ => return Err(Error::NoMoreData),
+ };
+ let content = match dec.decode_vvec() {
+ Some(v) => v,
+ _ => return Err(Error::NoMoreData),
+ };
+ qtrace!("TP {:x} length {:x}", tp, content.len());
+ let mut d = Decoder::from(content);
+ let value = match tp {
+ ORIGINAL_DESTINATION_CONNECTION_ID
+ | INITIAL_SOURCE_CONNECTION_ID
+ | RETRY_SOURCE_CONNECTION_ID => Self::Bytes(d.decode_remainder().to_vec()),
+ STATELESS_RESET_TOKEN => {
+ if d.remaining() != 16 {
+ return Err(Error::TransportParameterError);
+ }
+ Self::Bytes(d.decode_remainder().to_vec())
+ }
+ IDLE_TIMEOUT
+ | INITIAL_MAX_DATA
+ | INITIAL_MAX_STREAM_DATA_BIDI_LOCAL
+ | INITIAL_MAX_STREAM_DATA_BIDI_REMOTE
+ | INITIAL_MAX_STREAM_DATA_UNI
+ | MAX_ACK_DELAY => match d.decode_varint() {
+ Some(v) => Self::Integer(v),
+ None => return Err(Error::TransportParameterError),
+ },
+
+ INITIAL_MAX_STREAMS_BIDI | INITIAL_MAX_STREAMS_UNI => match d.decode_varint() {
+ Some(v) if v <= (1 << 60) => Self::Integer(v),
+ _ => return Err(Error::StreamLimitError),
+ },
+
+ MAX_UDP_PAYLOAD_SIZE => match d.decode_varint() {
+ Some(v) if v >= 1200 => Self::Integer(v),
+ _ => return Err(Error::TransportParameterError),
+ },
+
+ ACK_DELAY_EXPONENT => match d.decode_varint() {
+ Some(v) if v <= 20 => Self::Integer(v),
+ _ => return Err(Error::TransportParameterError),
+ },
+ ACTIVE_CONNECTION_ID_LIMIT => match d.decode_varint() {
+ Some(v) if v >= 2 => Self::Integer(v),
+ _ => return Err(Error::TransportParameterError),
+ },
+
+ DISABLE_MIGRATION | GREASE_QUIC_BIT => Self::Empty,
+ // Skip.
+ _ => return Ok(None),
+ };
+ if d.remaining() > 0 {
+ return Err(Error::TooMuchData);
+ }
+ qdebug!("TP decoded; type 0x{:02x} val {:?}", tp, value);
+ Ok(Some((tp, value)))
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct TransportParameters {
+ params: HashMap<TransportParameterId, TransportParameter>,
+}
+
+impl TransportParameters {
+ /// Set a value.
+ pub fn set(&mut self, k: TransportParameterId, v: TransportParameter) {
+ self.params.insert(k, v);
+ }
+
+ /// Clear a key.
+ pub fn remove(&mut self, k: TransportParameterId) {
+ self.params.remove(&k);
+ }
+
+ /// Decode is a static function that parses transport parameters
+ /// using the provided decoder.
+ pub(crate) fn decode(d: &mut Decoder) -> Res<Self> {
+ let mut tps = Self::default();
+ qtrace!("Parsed fixed TP header");
+
+ while d.remaining() > 0 {
+ match TransportParameter::decode(d) {
+ Ok(Some((tipe, tp))) => {
+ tps.set(tipe, tp);
+ }
+ Ok(None) => {}
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(tps)
+ }
+
+ pub(crate) fn encode(&self, enc: &mut Encoder) {
+ for (tipe, tp) in &self.params {
+ tp.encode(enc, *tipe);
+ }
+ }
+
+ // Get an integer type or a default.
+ pub fn get_integer(&self, tp: TransportParameterId) -> u64 {
+ let default = match tp {
+ IDLE_TIMEOUT
+ | INITIAL_MAX_DATA
+ | INITIAL_MAX_STREAM_DATA_BIDI_LOCAL
+ | INITIAL_MAX_STREAM_DATA_BIDI_REMOTE
+ | INITIAL_MAX_STREAM_DATA_UNI
+ | INITIAL_MAX_STREAMS_BIDI
+ | INITIAL_MAX_STREAMS_UNI => 0,
+ MAX_UDP_PAYLOAD_SIZE => 65527,
+ ACK_DELAY_EXPONENT => 3,
+ MAX_ACK_DELAY => 25,
+ ACTIVE_CONNECTION_ID_LIMIT => 2,
+ _ => panic!("Transport parameter not known or not an Integer"),
+ };
+ match self.params.get(&tp) {
+ None => default,
+ Some(TransportParameter::Integer(x)) => *x,
+ _ => panic!("Internal error"),
+ }
+ }
+
+ // Set an integer type or a default.
+ pub fn set_integer(&mut self, tp: TransportParameterId, value: u64) {
+ match tp {
+ IDLE_TIMEOUT
+ | INITIAL_MAX_DATA
+ | INITIAL_MAX_STREAM_DATA_BIDI_LOCAL
+ | INITIAL_MAX_STREAM_DATA_BIDI_REMOTE
+ | INITIAL_MAX_STREAM_DATA_UNI
+ | INITIAL_MAX_STREAMS_BIDI
+ | INITIAL_MAX_STREAMS_UNI
+ | MAX_UDP_PAYLOAD_SIZE
+ | ACK_DELAY_EXPONENT
+ | MAX_ACK_DELAY
+ | ACTIVE_CONNECTION_ID_LIMIT => {
+ self.set(tp, TransportParameter::Integer(value));
+ }
+ _ => panic!("Transport parameter not known"),
+ }
+ }
+
+ pub fn get_bytes(&self, tp: TransportParameterId) -> Option<&[u8]> {
+ match tp {
+ ORIGINAL_DESTINATION_CONNECTION_ID
+ | INITIAL_SOURCE_CONNECTION_ID
+ | RETRY_SOURCE_CONNECTION_ID
+ | STATELESS_RESET_TOKEN => {}
+ _ => panic!("Transport parameter not known or not type bytes"),
+ }
+
+ match self.params.get(&tp) {
+ None => None,
+ Some(TransportParameter::Bytes(x)) => Some(&x),
+ _ => panic!("Internal error"),
+ }
+ }
+
+ pub fn set_bytes(&mut self, tp: TransportParameterId, value: Vec<u8>) {
+ match tp {
+ ORIGINAL_DESTINATION_CONNECTION_ID
+ | INITIAL_SOURCE_CONNECTION_ID
+ | RETRY_SOURCE_CONNECTION_ID
+ | STATELESS_RESET_TOKEN => {
+ self.set(tp, TransportParameter::Bytes(value));
+ }
+ _ => panic!("Transport parameter not known or not type bytes"),
+ }
+ }
+
+ pub fn set_empty(&mut self, tp: TransportParameterId) {
+ match tp {
+ DISABLE_MIGRATION | GREASE_QUIC_BIT => {
+ self.set(tp, TransportParameter::Empty);
+ }
+ _ => panic!("Transport parameter not known or not type empty"),
+ }
+ }
+
+ pub fn get_empty(&self, tipe: TransportParameterId) -> bool {
+ match self.params.get(&tipe) {
+ None => false,
+ Some(TransportParameter::Empty) => true,
+ _ => panic!("Internal error"),
+ }
+ }
+
+ /// Return true if the remembered transport parameters are OK for 0-RTT.
+ /// Generally this means that any value that is currently in effect is greater than
+ /// or equal to the promised value.
+ pub(crate) fn ok_for_0rtt(&self, remembered: &Self) -> bool {
+ for (k, v_rem) in &remembered.params {
+ // Skip checks for these, which don't affect 0-RTT.
+ if matches!(
+ *k,
+ ORIGINAL_DESTINATION_CONNECTION_ID
+ | INITIAL_SOURCE_CONNECTION_ID
+ | RETRY_SOURCE_CONNECTION_ID
+ | STATELESS_RESET_TOKEN
+ | IDLE_TIMEOUT
+ | ACK_DELAY_EXPONENT
+ | MAX_ACK_DELAY
+ | ACTIVE_CONNECTION_ID_LIMIT
+ ) {
+ continue;
+ }
+ match self.params.get(k) {
+ Some(v_self) => match (v_self, v_rem) {
+ (TransportParameter::Integer(i_self), TransportParameter::Integer(i_rem)) => {
+ if *i_self < *i_rem {
+ return false;
+ }
+ }
+ (TransportParameter::Empty, TransportParameter::Empty) => {}
+ _ => return false,
+ },
+ _ => return false,
+ }
+ }
+ true
+ }
+
+ fn was_sent(&self, tp: TransportParameterId) -> bool {
+ self.params.contains_key(&tp)
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct TransportParametersHandler {
+ pub(crate) local: TransportParameters,
+ pub(crate) remote: Option<TransportParameters>,
+ pub(crate) remote_0rtt: Option<TransportParameters>,
+}
+
+impl TransportParametersHandler {
+ pub fn remote(&self) -> &TransportParameters {
+ match (self.remote.as_ref(), self.remote_0rtt.as_ref()) {
+ (Some(tp), _) | (_, Some(tp)) => tp,
+ _ => panic!("no transport parameters from peer"),
+ }
+ }
+}
+
+impl ExtensionHandler for TransportParametersHandler {
+ fn write(&mut self, msg: HandshakeMessage, d: &mut [u8]) -> ExtensionWriterResult {
+ if !matches!(msg, TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS) {
+ return ExtensionWriterResult::Skip;
+ }
+
+ qdebug!("Writing transport parameters, msg={:?}", msg);
+
+ // TODO(ekr@rtfm.com): Modify to avoid a copy.
+ let mut enc = Encoder::default();
+ self.local.encode(&mut enc);
+ assert!(enc.len() <= d.len());
+ d[..enc.len()].copy_from_slice(&enc);
+ ExtensionWriterResult::Write(enc.len())
+ }
+
+ fn handle(&mut self, msg: HandshakeMessage, d: &[u8]) -> ExtensionHandlerResult {
+ qtrace!(
+ "Handling transport parameters, msg={:?} value={}",
+ msg,
+ hex(d),
+ );
+
+ if !matches!(msg, TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS) {
+ return ExtensionHandlerResult::Alert(110); // unsupported_extension
+ }
+
+ let mut dec = Decoder::from(d);
+ match TransportParameters::decode(&mut dec) {
+ Ok(tp) => {
+ self.remote = Some(tp);
+ ExtensionHandlerResult::Ok
+ }
+ _ => ExtensionHandlerResult::Alert(47), // illegal_parameter
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct TpZeroRttChecker<T> {
+ handler: Rc<RefCell<TransportParametersHandler>>,
+ app_checker: T,
+}
+
+impl<T> TpZeroRttChecker<T>
+where
+ T: ZeroRttChecker + 'static,
+{
+ pub fn wrap(
+ handler: Rc<RefCell<TransportParametersHandler>>,
+ app_checker: T,
+ ) -> Box<dyn ZeroRttChecker> {
+ Box::new(Self {
+ handler,
+ app_checker,
+ })
+ }
+}
+
+impl<T> ZeroRttChecker for TpZeroRttChecker<T>
+where
+ T: ZeroRttChecker,
+{
+ fn check(&self, token: &[u8]) -> ZeroRttCheckResult {
+ // Reject 0-RTT if there is no token.
+ if token.is_empty() {
+ qdebug!("0-RTT: no token, no 0-RTT");
+ return ZeroRttCheckResult::Reject;
+ }
+ let mut dec = Decoder::from(token);
+ let tpslice = if let Some(v) = dec.decode_vvec() {
+ v
+ } else {
+ qinfo!("0-RTT: token code error");
+ return ZeroRttCheckResult::Fail;
+ };
+ let mut dec_tp = Decoder::from(tpslice);
+ let remembered = if let Ok(v) = TransportParameters::decode(&mut dec_tp) {
+ v
+ } else {
+ qinfo!("0-RTT: transport parameter decode error");
+ return ZeroRttCheckResult::Fail;
+ };
+ if self.handler.borrow().local.ok_for_0rtt(&remembered) {
+ qinfo!("0-RTT: transport parameters OK, passing to application checker");
+ self.app_checker.check(dec.decode_remainder())
+ } else {
+ qinfo!("0-RTT: transport parameters bad, rejecting");
+ ZeroRttCheckResult::Reject
+ }
+ }
+}
+
+// TODO(ekr@rtfm.com): Need to write more TP unit tests.
+#[cfg(test)]
+#[allow(unused_variables)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn basic_tps() {
+ const RESET_TOKEN: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8];
+ let mut tps = TransportParameters::default();
+ tps.set(
+ STATELESS_RESET_TOKEN,
+ TransportParameter::Bytes(RESET_TOKEN.to_vec()),
+ );
+ tps.params
+ .insert(INITIAL_MAX_STREAMS_BIDI, TransportParameter::Integer(10));
+
+ let mut enc = Encoder::default();
+ tps.encode(&mut enc);
+
+ let tps2 = TransportParameters::decode(&mut enc.as_decoder()).expect("Couldn't decode");
+ assert_eq!(tps, tps2);
+
+ println!("TPS = {:?}", tps);
+ assert_eq!(tps2.get_integer(IDLE_TIMEOUT), 0); // Default
+ assert_eq!(tps2.get_integer(MAX_ACK_DELAY), 25); // Default
+ assert_eq!(tps2.get_integer(ACTIVE_CONNECTION_ID_LIMIT), 2); // Default
+ assert_eq!(tps2.get_integer(INITIAL_MAX_STREAMS_BIDI), 10); // Sent
+ assert_eq!(tps2.get_bytes(STATELESS_RESET_TOKEN), Some(RESET_TOKEN));
+ assert_eq!(tps2.get_bytes(ORIGINAL_DESTINATION_CONNECTION_ID), None);
+ assert_eq!(tps2.get_bytes(INITIAL_SOURCE_CONNECTION_ID), None);
+ assert_eq!(tps2.get_bytes(RETRY_SOURCE_CONNECTION_ID), None);
+ assert_eq!(tps2.was_sent(ORIGINAL_DESTINATION_CONNECTION_ID), false);
+ assert_eq!(tps2.was_sent(INITIAL_SOURCE_CONNECTION_ID), false);
+ assert_eq!(tps2.was_sent(RETRY_SOURCE_CONNECTION_ID), false);
+ assert_eq!(tps2.was_sent(STATELESS_RESET_TOKEN), true);
+
+ let mut enc = Encoder::default();
+ tps.encode(&mut enc);
+
+ let tps2 = TransportParameters::decode(&mut enc.as_decoder()).expect("Couldn't decode");
+ }
+
+ #[test]
+ fn compatible_0rtt_ignored_values() {
+ let mut tps_a = TransportParameters::default();
+ tps_a.set(
+ STATELESS_RESET_TOKEN,
+ TransportParameter::Bytes(vec![1, 2, 3]),
+ );
+ tps_a.set(IDLE_TIMEOUT, TransportParameter::Integer(10));
+ tps_a.set(MAX_ACK_DELAY, TransportParameter::Integer(22));
+ tps_a.set(ACTIVE_CONNECTION_ID_LIMIT, TransportParameter::Integer(33));
+
+ let mut tps_b = TransportParameters::default();
+ assert!(tps_a.ok_for_0rtt(&tps_b));
+ assert!(tps_b.ok_for_0rtt(&tps_a));
+
+ tps_b.set(
+ STATELESS_RESET_TOKEN,
+ TransportParameter::Bytes(vec![8, 9, 10]),
+ );
+ tps_b.set(IDLE_TIMEOUT, TransportParameter::Integer(100));
+ tps_b.set(MAX_ACK_DELAY, TransportParameter::Integer(2));
+ tps_b.set(ACTIVE_CONNECTION_ID_LIMIT, TransportParameter::Integer(44));
+ assert!(tps_a.ok_for_0rtt(&tps_b));
+ assert!(tps_b.ok_for_0rtt(&tps_a));
+ }
+
+ #[test]
+ fn compatible_0rtt_integers() {
+ let mut tps_a = TransportParameters::default();
+ const INTEGER_KEYS: &[TransportParameterId] = &[
+ INITIAL_MAX_DATA,
+ INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+ INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+ INITIAL_MAX_STREAM_DATA_UNI,
+ INITIAL_MAX_STREAMS_BIDI,
+ INITIAL_MAX_STREAMS_UNI,
+ MAX_UDP_PAYLOAD_SIZE,
+ ];
+ for i in INTEGER_KEYS {
+ tps_a.set(*i, TransportParameter::Integer(12));
+ }
+
+ let tps_b = tps_a.clone();
+ assert!(tps_a.ok_for_0rtt(&tps_b));
+ assert!(tps_b.ok_for_0rtt(&tps_a));
+
+ // For each integer key, increase the value by one.
+ for i in INTEGER_KEYS {
+ let mut tps_b = tps_a.clone();
+ tps_b.set(*i, TransportParameter::Integer(13));
+ // If an increased value is remembered, then we can't attempt 0-RTT with these parameters.
+ assert!(!tps_a.ok_for_0rtt(&tps_b));
+ // If an increased value is lower, then we can attempt 0-RTT with these parameters.
+ assert!(tps_b.ok_for_0rtt(&tps_a));
+ }
+
+ // Drop integer values and check.
+ for i in INTEGER_KEYS {
+ let mut tps_b = tps_a.clone();
+ tps_b.remove(*i);
+ // A value that is missing from what is rememebered is OK.
+ assert!(tps_a.ok_for_0rtt(&tps_b));
+ // A value that is rememebered, but not current is not OK.
+ assert!(!tps_b.ok_for_0rtt(&tps_a));
+ }
+ }
+
+ #[test]
+ fn active_connection_id_limit_lt_2_is_error() {
+ let mut tps = TransportParameters::default();
+
+ // Intentionally set an invalid value for the ACTIVE_CONNECTION_ID_LIMIT transport parameter.
+ tps.params
+ .insert(ACTIVE_CONNECTION_ID_LIMIT, TransportParameter::Integer(1));
+
+ let mut enc = Encoder::default();
+ tps.encode(&mut enc);
+
+ // When decoding a set of transport parameters with an invalid ACTIVE_CONNECTION_ID_LIMIT
+ // the result should be an error.
+ let invalid_decode_result = TransportParameters::decode(&mut enc.as_decoder());
+ assert!(invalid_decode_result.is_err());
+ }
+}
diff --git a/third_party/rust/neqo-transport/src/tracking.rs b/third_party/rust/neqo-transport/src/tracking.rs
new file mode 100644
index 0000000000..efd3b06069
--- /dev/null
+++ b/third_party/rust/neqo-transport/src/tracking.rs
@@ -0,0 +1,992 @@
+// 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.
+
+// Tracking of received packets and generating acks thereof.
+
+#![deny(clippy::pedantic)]
+
+use std::cmp::min;
+use std::collections::VecDeque;
+use std::convert::TryFrom;
+use std::ops::{Index, IndexMut};
+use std::time::{Duration, Instant};
+
+use neqo_common::{qdebug, qinfo, qtrace, qwarn};
+use neqo_crypto::{Epoch, TLS_EPOCH_HANDSHAKE, TLS_EPOCH_INITIAL};
+
+use crate::packet::{PacketBuilder, PacketNumber, PacketType};
+use crate::recovery::RecoveryToken;
+use crate::stats::FrameStats;
+
+use smallvec::{smallvec, SmallVec};
+
+// TODO(mt) look at enabling EnumMap for this: https://stackoverflow.com/a/44905797/1375574
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
+pub enum PNSpace {
+ Initial,
+ Handshake,
+ ApplicationData,
+}
+
+#[allow(clippy::use_self)] // https://github.com/rust-lang/rust-clippy/issues/3410
+impl PNSpace {
+ pub fn iter() -> impl Iterator<Item = &'static PNSpace> {
+ const SPACES: &[PNSpace] = &[
+ PNSpace::Initial,
+ PNSpace::Handshake,
+ PNSpace::ApplicationData,
+ ];
+ SPACES.iter()
+ }
+}
+
+impl From<Epoch> for PNSpace {
+ fn from(epoch: Epoch) -> Self {
+ match epoch {
+ TLS_EPOCH_INITIAL => Self::Initial,
+ TLS_EPOCH_HANDSHAKE => Self::Handshake,
+ _ => Self::ApplicationData,
+ }
+ }
+}
+
+impl From<PacketType> for PNSpace {
+ fn from(pt: PacketType) -> Self {
+ match pt {
+ PacketType::Initial => Self::Initial,
+ PacketType::Handshake => Self::Handshake,
+ PacketType::ZeroRtt | PacketType::Short => Self::ApplicationData,
+ _ => panic!("Attempted to get space from wrong packet type"),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Default)]
+pub struct PNSpaceSet {
+ initial: bool,
+ handshake: bool,
+ application_data: bool,
+}
+
+impl PNSpaceSet {
+ pub fn all() -> Self {
+ Self {
+ initial: true,
+ handshake: true,
+ application_data: true,
+ }
+ }
+}
+
+impl Index<PNSpace> for PNSpaceSet {
+ type Output = bool;
+
+ fn index(&self, space: PNSpace) -> &Self::Output {
+ match space {
+ PNSpace::Initial => &self.initial,
+ PNSpace::Handshake => &self.handshake,
+ PNSpace::ApplicationData => &self.application_data,
+ }
+ }
+}
+
+impl IndexMut<PNSpace> for PNSpaceSet {
+ fn index_mut(&mut self, space: PNSpace) -> &mut Self::Output {
+ match space {
+ PNSpace::Initial => &mut self.initial,
+ PNSpace::Handshake => &mut self.handshake,
+ PNSpace::ApplicationData => &mut self.application_data,
+ }
+ }
+}
+
+impl<T: AsRef<[PNSpace]>> From<T> for PNSpaceSet {
+ fn from(spaces: T) -> Self {
+ let mut v = Self::default();
+ for sp in spaces.as_ref() {
+ v[*sp] = true;
+ }
+ v
+ }
+}
+
+impl std::fmt::Debug for PNSpaceSet {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let mut first = true;
+ f.write_str("(")?;
+ for sp in PNSpace::iter() {
+ if self[*sp] {
+ if !first {
+ f.write_str("+")?;
+ first = false;
+ }
+ std::fmt::Display::fmt(sp, f)?;
+ }
+ }
+ f.write_str(")")
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct SentPacket {
+ pub pt: PacketType,
+ pub pn: PacketNumber,
+ ack_eliciting: bool,
+ pub time_sent: Instant,
+ pub tokens: Vec<RecoveryToken>,
+
+ time_declared_lost: Option<Instant>,
+ /// After a PTO, this is true when the packet has been released.
+ pto: bool,
+
+ pub size: usize,
+}
+
+impl SentPacket {
+ pub fn new(
+ pt: PacketType,
+ pn: PacketNumber,
+ time_sent: Instant,
+ ack_eliciting: bool,
+ tokens: Vec<RecoveryToken>,
+ size: usize,
+ ) -> Self {
+ Self {
+ pt,
+ pn,
+ time_sent,
+ ack_eliciting,
+ tokens,
+ time_declared_lost: None,
+ pto: false,
+ size,
+ }
+ }
+
+ /// Returns `true` if the packet will elicit an ACK.
+ pub fn ack_eliciting(&self) -> bool {
+ self.ack_eliciting
+ }
+
+ /// Whether the packet has been declared lost.
+ pub fn lost(&self) -> bool {
+ self.time_declared_lost.is_some()
+ }
+
+ /// Whether accounting for the loss or acknowledgement in the
+ /// congestion controller is pending.
+ /// Returns `true` if the packet counts as being "in flight",
+ /// and has not previously been declared lost.
+ /// Note that this should count packets that contain only ACK and PADDING,
+ /// but we don't send PADDING, so we don't track that.
+ pub fn cc_outstanding(&self) -> bool {
+ self.ack_eliciting() && !self.lost()
+ }
+
+ /// Declare the packet as lost. Returns `true` if this is the first time.
+ pub fn declare_lost(&mut self, now: Instant) -> bool {
+ if self.lost() {
+ false
+ } else {
+ self.time_declared_lost = Some(now);
+ true
+ }
+ }
+
+ /// Ask whether this tracked packet has been declared lost for long enough
+ /// that it can be expired and no longer tracked.
+ pub fn expired(&self, now: Instant, expiration_period: Duration) -> bool {
+ self.time_declared_lost
+ .map_or(false, |loss_time| (loss_time + expiration_period) <= now)
+ }
+
+ /// Whether the packet contents were cleared out after a PTO.
+ pub fn pto_fired(&self) -> bool {
+ self.pto
+ }
+
+ /// On PTO, we need to get the recovery tokens so that we can ensure that
+ /// the frames we sent can be sent again in the PTO packet(s). Do that just once.
+ pub fn pto(&mut self) -> bool {
+ if self.pto || self.lost() {
+ false
+ } else {
+ self.pto = true;
+ true
+ }
+ }
+}
+
+impl std::fmt::Display for PNSpace {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.write_str(match self {
+ Self::Initial => "in",
+ Self::Handshake => "hs",
+ Self::ApplicationData => "ap",
+ })
+ }
+}
+
+/// `InsertionResult` tracks whether something was inserted for `PacketRange::add()`.
+pub enum InsertionResult {
+ Largest,
+ Smallest,
+ NotInserted,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct PacketRange {
+ largest: PacketNumber,
+ smallest: PacketNumber,
+ ack_needed: bool,
+}
+
+impl PacketRange {
+ /// Make a single packet range.
+ pub fn new(pn: PacketNumber) -> Self {
+ Self {
+ largest: pn,
+ smallest: pn,
+ ack_needed: true,
+ }
+ }
+
+ /// Get the number of acknowleged packets in the range.
+ pub fn len(&self) -> u64 {
+ self.largest - self.smallest + 1
+ }
+
+ /// Returns whether this needs to be sent.
+ pub fn ack_needed(&self) -> bool {
+ self.ack_needed
+ }
+
+ /// Return whether the given number is in the range.
+ pub fn contains(&self, pn: PacketNumber) -> bool {
+ (pn >= self.smallest) && (pn <= self.largest)
+ }
+
+ /// Maybe add a packet number to the range. Returns true if it was added
+ /// at the small end (which indicates that this might need merging with a
+ /// preceding range).
+ pub fn add(&mut self, pn: PacketNumber) -> InsertionResult {
+ assert!(!self.contains(pn));
+ // Only insert if this is adjacent the current range.
+ if (self.largest + 1) == pn {
+ qtrace!([self], "Adding largest {}", pn);
+ self.largest += 1;
+ self.ack_needed = true;
+ InsertionResult::Largest
+ } else if self.smallest == (pn + 1) {
+ qtrace!([self], "Adding smallest {}", pn);
+ self.smallest -= 1;
+ self.ack_needed = true;
+ InsertionResult::Smallest
+ } else {
+ InsertionResult::NotInserted
+ }
+ }
+
+ /// Maybe merge a higher-numbered range into this.
+ fn merge_larger(&mut self, other: &Self) {
+ qinfo!([self], "Merging {}", other);
+ // This only works if they are immediately adjacent.
+ assert_eq!(self.largest + 1, other.smallest);
+
+ self.largest = other.largest;
+ self.ack_needed = self.ack_needed || other.ack_needed;
+ }
+
+ /// When a packet containing the range `other` is acknowledged,
+ /// clear the `ack_needed` attribute on this.
+ /// Requires that other is equal to this, or a larger range.
+ pub fn acknowledged(&mut self, other: &Self) {
+ if (other.smallest <= self.smallest) && (other.largest >= self.largest) {
+ self.ack_needed = false;
+ }
+ }
+}
+
+impl ::std::fmt::Display for PacketRange {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "{}->{}", self.largest, self.smallest)
+ }
+}
+
+/// The ACK delay we use.
+pub const ACK_DELAY: Duration = Duration::from_millis(20); // 20ms
+pub const MAX_UNACKED_PKTS: usize = 1;
+const MAX_TRACKED_RANGES: usize = 32;
+const MAX_ACKS_PER_FRAME: usize = 32;
+
+/// A structure that tracks what was included in an ACK.
+#[derive(Debug, Clone)]
+pub struct AckToken {
+ space: PNSpace,
+ ranges: Vec<PacketRange>,
+}
+
+/// A structure that tracks what packets have been received,
+/// and what needs acknowledgement for a packet number space.
+#[derive(Debug)]
+pub struct RecvdPackets {
+ space: PNSpace,
+ ranges: VecDeque<PacketRange>,
+ /// The packet number of the lowest number packet that we are tracking.
+ min_tracked: PacketNumber,
+ /// The time we got the largest acknowledged.
+ largest_pn_time: Option<Instant>,
+ // The time that we should be sending an ACK.
+ ack_time: Option<Instant>,
+ pkts_since_last_ack: usize,
+}
+
+impl RecvdPackets {
+ /// Make a new `RecvdPackets` for the indicated packet number space.
+ pub fn new(space: PNSpace) -> Self {
+ Self {
+ space,
+ ranges: VecDeque::new(),
+ min_tracked: 0,
+ largest_pn_time: None,
+ ack_time: None,
+ pkts_since_last_ack: 0,
+ }
+ }
+
+ /// Get the time at which the next ACK should be sent.
+ pub fn ack_time(&self) -> Option<Instant> {
+ self.ack_time
+ }
+
+ /// Returns true if an ACK frame should be sent now.
+ fn ack_now(&self, now: Instant) -> bool {
+ match self.ack_time {
+ Some(t) => t <= now,
+ None => false,
+ }
+ }
+
+ // A simple addition of a packet number to the tracked set.
+ // This doesn't do a binary search on the assumption that
+ // new packets will generally be added to the start of the list.
+ fn add(&mut self, pn: PacketNumber) {
+ for i in 0..self.ranges.len() {
+ match self.ranges[i].add(pn) {
+ InsertionResult::Largest => return,
+ InsertionResult::Smallest => {
+ // If this was the smallest, it might have filled a gap.
+ let nxt = i + 1;
+ if (nxt < self.ranges.len()) && (pn - 1 == self.ranges[nxt].largest) {
+ let larger = self.ranges.remove(i).unwrap();
+ self.ranges[i].merge_larger(&larger);
+ }
+ return;
+ }
+ InsertionResult::NotInserted => {
+ if self.ranges[i].largest < pn {
+ self.ranges.insert(i, PacketRange::new(pn));
+ return;
+ }
+ }
+ }
+ }
+ self.ranges.push_back(PacketRange::new(pn));
+ }
+
+ fn trim_ranges(&mut self) {
+ // Limit the number of ranges that are tracked to MAX_TRACKED_RANGES.
+ if self.ranges.len() > MAX_TRACKED_RANGES {
+ let oldest = self.ranges.pop_back().unwrap();
+ if oldest.ack_needed {
+ qwarn!([self], "Dropping unacknowledged ACK range: {}", oldest);
+ // TODO(mt) Record some statistics about this so we can tune MAX_TRACKED_RANGES.
+ } else {
+ qdebug!([self], "Drop ACK range: {}", oldest);
+ }
+ self.min_tracked = oldest.largest + 1;
+ }
+ }
+
+ /// Add the packet to the tracked set.
+ pub fn set_received(&mut self, now: Instant, pn: PacketNumber, ack_eliciting: bool) {
+ let next_in_order_pn = self.ranges.front().map_or(0, |pr| pr.largest + 1);
+ qdebug!(
+ [self],
+ "received {}, next in order pn: {}",
+ pn,
+ next_in_order_pn
+ );
+
+ self.add(pn);
+ self.trim_ranges();
+
+ // The new addition was the largest, so update the time we use for calculating ACK delay.
+ if pn >= next_in_order_pn {
+ self.largest_pn_time = Some(now);
+ }
+
+ if ack_eliciting {
+ self.pkts_since_last_ack += 1;
+
+ // Send ACK right away if out-of-order
+ // On the first in-order ack-eliciting packet since sending an ACK,
+ // set a delay.
+ // Count packets until we exceed MAX_UNACKED_PKTS, then remove the
+ // delay.
+ if pn != next_in_order_pn {
+ self.ack_time = Some(now);
+ } else if self.space == PNSpace::ApplicationData {
+ match &mut self.pkts_since_last_ack {
+ 0 => unreachable!(),
+ 1 => self.ack_time = Some(now + ACK_DELAY),
+ x if *x > MAX_UNACKED_PKTS => self.ack_time = Some(now),
+ _ => debug_assert!(self.ack_time.is_some()),
+ }
+ } else {
+ self.ack_time = Some(now);
+ }
+ qdebug!([self], "Set ACK timer to {:?}", self.ack_time);
+ }
+ }
+
+ /// Check if the packet is a duplicate.
+ pub fn is_duplicate(&self, pn: PacketNumber) -> bool {
+ if pn < self.min_tracked {
+ return true;
+ }
+ // TODO(mt) consider a binary search or early exit.
+ for range in &self.ranges {
+ if range.contains(pn) {
+ return true;
+ }
+ }
+ false
+ }
+
+ /// Mark the given range as having been acknowledged.
+ pub fn acknowledged(&mut self, acked: &[PacketRange]) {
+ let mut range_iter = self.ranges.iter_mut();
+ let mut cur = range_iter.next().expect("should have at least one range");
+ for ack in acked {
+ while cur.smallest > ack.largest {
+ cur = match range_iter.next() {
+ Some(c) => c,
+ None => return,
+ };
+ }
+ cur.acknowledged(&ack);
+ }
+ }
+
+ /// Generate an ACK frame for this packet number space.
+ ///
+ /// Unlike other frame generators this doesn't modify the underlying instance
+ /// to track what has been sent. This only clears the delayed ACK timer.
+ ///
+ /// When sending ACKs, we want to always send the most recent ranges,
+ /// even if they have been sent in other packets.
+ ///
+ /// We don't send ranges that have been acknowledged, but they still need
+ /// to be tracked so that duplicates can be detected.
+ fn write_frame(
+ &mut self,
+ now: Instant,
+ builder: &mut PacketBuilder,
+ stats: &mut FrameStats,
+ ) -> Option<RecoveryToken> {
+ // The worst possible ACK frame, assuming only one range.
+ // Note that this assumes one byte for the type and count of extra ranges.
+ const LONGEST_ACK_HEADER: usize = 1 + 8 + 8 + 1 + 8;
+
+ // Check that we aren't delaying ACKs.
+ if !self.ack_now(now) {
+ return None;
+ }
+
+ // Drop extra ACK ranges to fit the available space. Do this based on
+ // a worst-case estimate of frame size for simplicity.
+ //
+ // When congestion limited, ACK-only packets are 255 bytes at most
+ // (`recovery::ACK_ONLY_SIZE_LIMIT - 1`). This results in limiting the
+ // ranges to 13 here.
+ let max_ranges = if let Some(avail) = builder.remaining().checked_sub(LONGEST_ACK_HEADER) {
+ // Apply a hard maximum to keep plenty of space for other stuff.
+ min(1 + (avail / 16), MAX_ACKS_PER_FRAME)
+ } else {
+ return None;
+ };
+
+ let ranges = self
+ .ranges
+ .iter()
+ .filter(|r| r.ack_needed())
+ .take(max_ranges)
+ .cloned()
+ .collect::<Vec<_>>();
+
+ builder.encode_varint(crate::frame::FRAME_TYPE_ACK);
+ let mut iter = ranges.iter();
+ let first = match iter.next() {
+ Some(v) => v,
+ None => return None, // Nothing to send.
+ };
+ builder.encode_varint(first.largest);
+ stats.largest_acknowledged = first.largest;
+ stats.ack += 1;
+
+ let elapsed = now.duration_since(self.largest_pn_time.unwrap());
+ // We use the default exponent, so delay is in multiples of 8 microseconds.
+ let ack_delay = u64::try_from(elapsed.as_micros() / 8).unwrap_or(u64::MAX);
+ let ack_delay = min((1 << 62) - 1, ack_delay);
+ builder.encode_varint(ack_delay);
+ builder.encode_varint(u64::try_from(ranges.len() - 1).unwrap()); // extra ranges
+ builder.encode_varint(first.len() - 1); // first range
+
+ let mut last = first.smallest;
+ for r in iter {
+ // the difference must be at least 2 because 0-length gaps,
+ // (difference 1) are illegal.
+ builder.encode_varint(last - r.largest - 2); // Gap
+ builder.encode_varint(r.len() - 1); // Range
+ last = r.smallest;
+ }
+
+ // We've sent an ACK, reset the timer.
+ self.ack_time = None;
+ self.pkts_since_last_ack = 0;
+
+ Some(RecoveryToken::Ack(AckToken {
+ space: self.space,
+ ranges,
+ }))
+ }
+}
+
+impl ::std::fmt::Display for RecvdPackets {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Recvd-{}", self.space)
+ }
+}
+
+#[derive(Debug)]
+pub struct AckTracker {
+ /// This stores information about received packets in *reverse* order
+ /// by spaces. Why reverse? Because we ultimately only want to keep
+ /// `ApplicationData` and this allows us to drop other spaces easily.
+ spaces: SmallVec<[RecvdPackets; 1]>,
+}
+
+impl AckTracker {
+ pub fn drop_space(&mut self, space: PNSpace) {
+ let sp = match space {
+ PNSpace::Initial => self.spaces.pop(),
+ PNSpace::Handshake => {
+ let sp = self.spaces.pop();
+ self.spaces.shrink_to_fit();
+ sp
+ }
+ PNSpace::ApplicationData => panic!("discarding application space"),
+ };
+ assert_eq!(sp.unwrap().space, space, "dropping spaces out of order");
+ }
+
+ pub fn get_mut(&mut self, space: PNSpace) -> Option<&mut RecvdPackets> {
+ self.spaces.get_mut(match space {
+ PNSpace::ApplicationData => 0,
+ PNSpace::Handshake => 1,
+ PNSpace::Initial => 2,
+ })
+ }
+
+ /// Determine the earliest time that an ACK might be needed.
+ pub fn ack_time(&self, now: Instant) -> Option<Instant> {
+ if self.spaces.len() == 1 {
+ self.spaces[0].ack_time()
+ } else {
+ // Ignore any time that is in the past relative to `now`.
+ // That is something of a hack, but there are cases where we can't send ACK
+ // frames for all spaces, which can mean that one space is stuck in the past.
+ // That isn't a problem because we guarantee that earlier spaces will always
+ // be able to send ACK frames.
+ self.spaces
+ .iter()
+ .filter_map(|recvd| recvd.ack_time().filter(|t| *t > now))
+ .min()
+ }
+ }
+
+ pub fn acked(&mut self, token: &AckToken) {
+ if let Some(space) = self.get_mut(token.space) {
+ space.acknowledged(&token.ranges);
+ }
+ }
+
+ pub(crate) fn write_frame(
+ &mut self,
+ pn_space: PNSpace,
+ now: Instant,
+ builder: &mut PacketBuilder,
+ stats: &mut FrameStats,
+ ) -> Option<RecoveryToken> {
+ self.get_mut(pn_space)
+ .and_then(|space| space.write_frame(now, builder, stats))
+ }
+}
+
+impl Default for AckTracker {
+ fn default() -> Self {
+ Self {
+ spaces: smallvec![
+ RecvdPackets::new(PNSpace::ApplicationData),
+ RecvdPackets::new(PNSpace::Handshake),
+ RecvdPackets::new(PNSpace::Initial),
+ ],
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ AckTracker, Duration, Instant, PNSpace, PNSpaceSet, RecoveryToken, RecvdPackets, ACK_DELAY,
+ MAX_TRACKED_RANGES, MAX_UNACKED_PKTS,
+ };
+ use crate::frame::Frame;
+ use crate::packet::PacketBuilder;
+ use crate::stats::FrameStats;
+ use lazy_static::lazy_static;
+ use neqo_common::Encoder;
+ use std::collections::HashSet;
+ use std::convert::TryFrom;
+
+ lazy_static! {
+ static ref NOW: Instant = Instant::now();
+ }
+
+ fn test_ack_range(pns: &[u64], nranges: usize) {
+ let mut rp = RecvdPackets::new(PNSpace::Initial); // Any space will do.
+ let mut packets = HashSet::new();
+
+ for pn in pns {
+ rp.set_received(*NOW, *pn, true);
+ packets.insert(*pn);
+ }
+
+ assert_eq!(rp.ranges.len(), nranges);
+
+ // Check that all these packets will be detected as duplicates.
+ for pn in pns {
+ assert!(rp.is_duplicate(*pn));
+ }
+
+ // Check that the ranges decrease monotonically and don't overlap.
+ let mut iter = rp.ranges.iter();
+ let mut last = iter.next().expect("should have at least one");
+ for n in iter {
+ assert!(n.largest + 1 < last.smallest);
+ last = n;
+ }
+
+ // Check that the ranges include the right values.
+ let mut in_ranges = HashSet::new();
+ for range in &rp.ranges {
+ for included in range.smallest..=range.largest {
+ in_ranges.insert(included);
+ }
+ }
+ assert_eq!(packets, in_ranges);
+ }
+
+ #[test]
+ fn pn0() {
+ test_ack_range(&[0], 1);
+ }
+
+ #[test]
+ fn pn1() {
+ test_ack_range(&[1], 1);
+ }
+
+ #[test]
+ fn two_ranges() {
+ test_ack_range(&[0, 1, 2, 5, 6, 7], 2);
+ }
+
+ #[test]
+ fn fill_in_range() {
+ test_ack_range(&[0, 1, 2, 5, 6, 7, 3, 4], 1);
+ }
+
+ #[test]
+ fn too_many_ranges() {
+ let mut rp = RecvdPackets::new(PNSpace::Initial); // Any space will do.
+
+ // This will add one too many disjoint ranges.
+ for i in 0..=MAX_TRACKED_RANGES {
+ rp.set_received(*NOW, (i * 2) as u64, true);
+ }
+
+ assert_eq!(rp.ranges.len(), MAX_TRACKED_RANGES);
+ assert_eq!(rp.ranges.back().unwrap().largest, 2);
+
+ // Even though the range was dropped, we still consider it a duplicate.
+ assert!(rp.is_duplicate(0));
+ assert!(!rp.is_duplicate(1));
+ assert!(rp.is_duplicate(2));
+ }
+
+ #[test]
+ fn ack_delay() {
+ // Only application data packets are delayed.
+ let mut rp = RecvdPackets::new(PNSpace::ApplicationData);
+ assert!(rp.ack_time().is_none());
+ assert!(!rp.ack_now(*NOW));
+
+ // Some packets won't cause an ACK to be needed.
+ let max_unacked = u64::try_from(MAX_UNACKED_PKTS).unwrap();
+ for num in 0..max_unacked {
+ rp.set_received(*NOW, num, true);
+ assert_eq!(Some(*NOW + ACK_DELAY), rp.ack_time());
+ assert!(!rp.ack_now(*NOW));
+ assert!(rp.ack_now(*NOW + ACK_DELAY));
+ }
+
+ // Exceeding MAX_UNACKED_PKTS will move the ACK time to now.
+ rp.set_received(*NOW, max_unacked, true);
+ assert_eq!(Some(*NOW), rp.ack_time());
+ assert!(rp.ack_now(*NOW));
+ }
+
+ #[test]
+ fn no_ack_delay() {
+ for space in &[PNSpace::Initial, PNSpace::Handshake] {
+ let mut rp = RecvdPackets::new(*space);
+ assert!(rp.ack_time().is_none());
+ assert!(!rp.ack_now(*NOW));
+
+ // Any packet will be acknowledged straight away.
+ rp.set_received(*NOW, 0, true);
+ assert_eq!(Some(*NOW), rp.ack_time());
+ assert!(rp.ack_now(*NOW));
+ }
+ }
+
+ #[test]
+ fn ooo_no_ack_delay() {
+ for space in &[
+ PNSpace::Initial,
+ PNSpace::Handshake,
+ PNSpace::ApplicationData,
+ ] {
+ let mut rp = RecvdPackets::new(*space);
+ assert!(rp.ack_time().is_none());
+ assert!(!rp.ack_now(*NOW));
+
+ // Any OoO packet will be acknowledged straight away.
+ rp.set_received(*NOW, 3, true);
+ assert_eq!(Some(*NOW), rp.ack_time());
+ assert!(rp.ack_now(*NOW));
+ }
+ }
+
+ #[test]
+ fn aggregate_ack_time() {
+ let mut tracker = AckTracker::default();
+ // This packet won't trigger an ACK.
+ tracker
+ .get_mut(PNSpace::Handshake)
+ .unwrap()
+ .set_received(*NOW, 0, false);
+ assert_eq!(None, tracker.ack_time(*NOW));
+
+ // This should be delayed.
+ tracker
+ .get_mut(PNSpace::ApplicationData)
+ .unwrap()
+ .set_received(*NOW, 0, true);
+ assert_eq!(Some(*NOW + ACK_DELAY), tracker.ack_time(*NOW));
+
+ // This should move the time forward.
+ let later = *NOW + ACK_DELAY.checked_div(2).unwrap();
+ tracker
+ .get_mut(PNSpace::Initial)
+ .unwrap()
+ .set_received(later, 0, true);
+ assert_eq!(Some(later), tracker.ack_time(*NOW));
+ }
+
+ #[test]
+ #[should_panic(expected = "discarding application space")]
+ fn drop_app() {
+ let mut tracker = AckTracker::default();
+ tracker.drop_space(PNSpace::ApplicationData);
+ }
+
+ #[test]
+ #[should_panic(expected = "dropping spaces out of order")]
+ fn drop_out_of_order() {
+ let mut tracker = AckTracker::default();
+ tracker.drop_space(PNSpace::Handshake);
+ }
+
+ #[test]
+ fn drop_spaces() {
+ let mut tracker = AckTracker::default();
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ tracker
+ .get_mut(PNSpace::Initial)
+ .unwrap()
+ .set_received(*NOW, 0, true);
+ // The reference time for `ack_time` has to be in the past or we filter out the timer.
+ assert!(tracker.ack_time(*NOW - Duration::from_millis(1)).is_some());
+ let token = tracker.write_frame(
+ PNSpace::Initial,
+ *NOW,
+ &mut builder,
+ &mut FrameStats::default(),
+ );
+ assert!(token.is_some());
+
+ // Mark another packet as received so we have cause to send another ACK in that space.
+ tracker
+ .get_mut(PNSpace::Initial)
+ .unwrap()
+ .set_received(*NOW, 1, true);
+ assert!(tracker.ack_time(*NOW - Duration::from_millis(1)).is_some());
+
+ // Now drop that space.
+ tracker.drop_space(PNSpace::Initial);
+
+ assert!(tracker.get_mut(PNSpace::Initial).is_none());
+ assert!(tracker.ack_time(*NOW - Duration::from_millis(1)).is_none());
+ assert!(tracker
+ .write_frame(
+ PNSpace::Initial,
+ *NOW,
+ &mut builder,
+ &mut FrameStats::default()
+ )
+ .is_none());
+ if let RecoveryToken::Ack(tok) = token.unwrap() {
+ tracker.acked(&tok); // Should be a noop.
+ } else {
+ panic!("not an ACK token");
+ }
+ }
+
+ #[test]
+ fn no_room_for_ack() {
+ let mut tracker = AckTracker::default();
+ tracker
+ .get_mut(PNSpace::Initial)
+ .unwrap()
+ .set_received(*NOW, 0, true);
+ assert!(tracker.ack_time(*NOW - Duration::from_millis(1)).is_some());
+
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ builder.set_limit(10);
+
+ let token = tracker.write_frame(
+ PNSpace::Initial,
+ *NOW,
+ &mut builder,
+ &mut FrameStats::default(),
+ );
+ assert!(token.is_none());
+ assert_eq!(builder.len(), 1); // Only the short packet header has been added.
+ }
+
+ #[test]
+ fn no_room_for_extra_range() {
+ let mut tracker = AckTracker::default();
+ tracker
+ .get_mut(PNSpace::Initial)
+ .unwrap()
+ .set_received(*NOW, 0, true);
+ tracker
+ .get_mut(PNSpace::Initial)
+ .unwrap()
+ .set_received(*NOW, 2, true);
+ assert!(tracker.ack_time(*NOW - Duration::from_millis(1)).is_some());
+
+ let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
+ builder.set_limit(32);
+
+ let token = tracker.write_frame(
+ PNSpace::Initial,
+ *NOW,
+ &mut builder,
+ &mut FrameStats::default(),
+ );
+ assert!(token.is_some());
+
+ let mut dec = builder.as_decoder();
+ let _ = dec.decode_byte().unwrap(); // Skip the short header.
+ let frame = Frame::decode(&mut dec).unwrap();
+ if let Frame::Ack { ack_ranges, .. } = frame {
+ assert_eq!(ack_ranges.len(), 0);
+ } else {
+ panic!("not an ACK!");
+ }
+ }
+
+ #[test]
+ fn ack_time_elapsed() {
+ let mut tracker = AckTracker::default();
+
+ // While we have multiple PN spaces, we ignore ACK timers from the past.
+ // Send out of order to cause the delayed ack timer to be set to `*NOW`.
+ tracker
+ .get_mut(PNSpace::ApplicationData)
+ .unwrap()
+ .set_received(*NOW, 3, true);
+ assert!(tracker.ack_time(*NOW + Duration::from_millis(1)).is_none());
+
+ // When we are reduced to one space, that filter is off.
+ tracker.drop_space(PNSpace::Initial);
+ tracker.drop_space(PNSpace::Handshake);
+ assert_eq!(
+ tracker.ack_time(*NOW + Duration::from_millis(1)),
+ Some(*NOW)
+ );
+ }
+
+ #[test]
+ fn pnspaceset_default() {
+ let set = PNSpaceSet::default();
+ assert!(!set[PNSpace::Initial]);
+ assert!(!set[PNSpace::Handshake]);
+ assert!(!set[PNSpace::ApplicationData]);
+ }
+
+ #[test]
+ fn pnspaceset_from() {
+ let set = PNSpaceSet::from(&[PNSpace::Initial]);
+ assert!(set[PNSpace::Initial]);
+ assert!(!set[PNSpace::Handshake]);
+ assert!(!set[PNSpace::ApplicationData]);
+
+ let set = PNSpaceSet::from(&[PNSpace::Handshake, PNSpace::Initial]);
+ assert!(set[PNSpace::Initial]);
+ assert!(set[PNSpace::Handshake]);
+ assert!(!set[PNSpace::ApplicationData]);
+
+ let set = PNSpaceSet::from(&[PNSpace::ApplicationData, PNSpace::ApplicationData]);
+ assert!(!set[PNSpace::Initial]);
+ assert!(!set[PNSpace::Handshake]);
+ assert!(set[PNSpace::ApplicationData]);
+ }
+
+ #[test]
+ fn pnspaceset_copy() {
+ let set = PNSpaceSet::from(&[PNSpace::Handshake, PNSpace::ApplicationData]);
+ let copy = set;
+ assert!(!copy[PNSpace::Initial]);
+ assert!(copy[PNSpace::Handshake]);
+ assert!(copy[PNSpace::ApplicationData]);
+ }
+}
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..11cd37ec2e
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/conn_vectors.rs
@@ -0,0 +1,201 @@
+// 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)]
+
+use neqo_common::{Datagram, Encoder};
+use neqo_transport::{
+ Connection, ConnectionParameters, FixedConnectionIdManager, QuicVersion, State,
+};
+use test_fixture::{self, loopback, now};
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+const INITIAL_PACKET_27: &str = "c0ff00001b088394c8f03e5157080000\
+ 449e3b343aa8535064a4268a0d9d7b1c\
+ 9d250ae355162276e9b1e3011ef6bbc0\
+ ab48ad5bcc2681e953857ca62becd752\
+ 4daac473e68d7405fbba4e9ee616c870\
+ 38bdbe908c06d9605d9ac49030359eec\
+ b1d05a14e117db8cede2bb09d0dbbfee\
+ 271cb374d8f10abec82d0f59a1dee29f\
+ e95638ed8dd41da07487468791b719c5\
+ 5c46968eb3b54680037102a28e53dc1d\
+ 12903db0af5821794b41c4a93357fa59\
+ ce69cfe7f6bdfa629eef78616447e1d6\
+ 11c4baf71bf33febcb03137c2c75d253\
+ 17d3e13b684370f668411c0f00304b50\
+ 1c8fd422bd9b9ad81d643b20da89ca05\
+ 25d24d2b142041cae0af205092e43008\
+ 0cd8559ea4c5c6e4fa3f66082b7d303e\
+ 52ce0162baa958532b0bbc2bc785681f\
+ cf37485dff6595e01e739c8ac9efba31\
+ b985d5f656cc092432d781db95221724\
+ 87641c4d3ab8ece01e39bc85b1543661\
+ 4775a98ba8fa12d46f9b35e2a55eb72d\
+ 7f85181a366663387ddc20551807e007\
+ 673bd7e26bf9b29b5ab10a1ca87cbb7a\
+ d97e99eb66959c2a9bc3cbde4707ff77\
+ 20b110fa95354674e395812e47a0ae53\
+ b464dcb2d1f345df360dc227270c7506\
+ 76f6724eb479f0d2fbb6124429990457\
+ ac6c9167f40aab739998f38b9eccb24f\
+ d47c8410131bf65a52af841275d5b3d1\
+ 880b197df2b5dea3e6de56ebce3ffb6e\
+ 9277a82082f8d9677a6767089b671ebd\
+ 244c214f0bde95c2beb02cd1172d58bd\
+ f39dce56ff68eb35ab39b49b4eac7c81\
+ 5ea60451d6e6ab82119118df02a58684\
+ 4a9ffe162ba006d0669ef57668cab38b\
+ 62f71a2523a084852cd1d079b3658dc2\
+ f3e87949b550bab3e177cfc49ed190df\
+ f0630e43077c30de8f6ae081537f1e83\
+ da537da980afa668e7b7fb25301cf741\
+ 524be3c49884b42821f17552fbd1931a\
+ 813017b6b6590a41ea18b6ba49cd48a4\
+ 40bd9a3346a7623fb4ba34a3ee571e3c\
+ 731f35a7a3cf25b551a680fa68763507\
+ b7fde3aaf023c50b9d22da6876ba337e\
+ b5e9dd9ec3daf970242b6c5aab3aa4b2\
+ 96ad8b9f6832f686ef70fa938b31b4e5\
+ ddd7364442d3ea72e73d668fb0937796\
+ f462923a81a47e1cee7426ff6d922126\
+ 9b5a62ec03d6ec94d12606cb485560ba\
+ b574816009e96504249385bb61a819be\
+ 04f62c2066214d8360a2022beb316240\
+ b6c7d78bbe56c13082e0ca272661210a\
+ bf020bf3b5783f1426436cf9ff418405\
+ 93a5d0638d32fc51c5c65ff291a3a7a5\
+ 2fd6775e623a4439cc08dd25582febc9\
+ 44ef92d8dbd329c91de3e9c9582e41f1\
+ 7f3d186f104ad3f90995116c682a2a14\
+ a3b4b1f547c335f0be710fc9fc03e0e5\
+ 87b8cda31ce65b969878a4ad4283e6d5\
+ b0373f43da86e9e0ffe1ae0fddd35162\
+ 55bd74566f36a38703d5f34249ded1f6\
+ 6b3d9b45b9af2ccfefe984e13376b1b2\
+ c6404aa48c8026132343da3f3a33659e\
+ c1b3e95080540b28b7f3fcd35fa5d843\
+ b579a84c089121a60d8c1754915c344e\
+ eaf45a9bf27dc0c1e784161691220913\
+ 13eb0e87555abd706626e557fc36a04f\
+ cd191a58829104d6075c5594f627ca50\
+ 6bf181daec940f4a4f3af0074eee89da\
+ acde6758312622d4fa675b39f728e062\
+ d2bee680d8f41a597c262648bb18bcfc\
+ 13c8b3d97b1a77b2ac3af745d61a34cc\
+ 4709865bac824a94bb19058015e4e42d\
+ 38d3b779d72edc00c5cd088eff802b05";
+
+const INITIAL_PACKET_29: &str = "cdff00001d088394c8f03e5157080000\
+ 449e9cdb990bfb66bc6a93032b50dd89\
+ 73972d149421874d3849e3708d71354e\
+ a33bcdc356f3ea6e2a1a1bd7c3d14003\
+ 8d3e784d04c30a2cdb40c32523aba2da\
+ fe1c1bf3d27a6be38fe38ae033fbb071\
+ 3c1c73661bb6639795b42b97f77068ea\
+ d51f11fbf9489af2501d09481e6c64d4\
+ b8551cd3cea70d830ce2aeeec789ef55\
+ 1a7fbe36b3f7e1549a9f8d8e153b3fac\
+ 3fb7b7812c9ed7c20b4be190ebd89956\
+ 26e7f0fc887925ec6f0606c5d36aa81b\
+ ebb7aacdc4a31bb5f23d55faef5c5190\
+ 5783384f375a43235b5c742c78ab1bae\
+ 0a188b75efbde6b3774ed61282f9670a\
+ 9dea19e1566103ce675ab4e21081fb58\
+ 60340a1e88e4f10e39eae25cd685b109\
+ 29636d4f02e7fad2a5a458249f5c0298\
+ a6d53acbe41a7fc83fa7cc01973f7a74\
+ d1237a51974e097636b6203997f921d0\
+ 7bc1940a6f2d0de9f5a11432946159ed\
+ 6cc21df65c4ddd1115f86427259a196c\
+ 7148b25b6478b0dc7766e1c4d1b1f515\
+ 9f90eabc61636226244642ee148b464c\
+ 9e619ee50a5e3ddc836227cad938987c\
+ 4ea3c1fa7c75bbf88d89e9ada642b2b8\
+ 8fe8107b7ea375b1b64889a4e9e5c38a\
+ 1c896ce275a5658d250e2d76e1ed3a34\
+ ce7e3a3f383d0c996d0bed106c2899ca\
+ 6fc263ef0455e74bb6ac1640ea7bfedc\
+ 59f03fee0e1725ea150ff4d69a7660c5\
+ 542119c71de270ae7c3ecfd1af2c4ce5\
+ 51986949cc34a66b3e216bfe18b347e6\
+ c05fd050f85912db303a8f054ec23e38\
+ f44d1c725ab641ae929fecc8e3cefa56\
+ 19df4231f5b4c009fa0c0bbc60bc75f7\
+ 6d06ef154fc8577077d9d6a1d2bd9bf0\
+ 81dc783ece60111bea7da9e5a9748069\
+ d078b2bef48de04cabe3755b197d52b3\
+ 2046949ecaa310274b4aac0d008b1948\
+ c1082cdfe2083e386d4fd84c0ed0666d\
+ 3ee26c4515c4fee73433ac703b690a9f\
+ 7bf278a77486ace44c489a0c7ac8dfe4\
+ d1a58fb3a730b993ff0f0d61b4d89557\
+ 831eb4c752ffd39c10f6b9f46d8db278\
+ da624fd800e4af85548a294c1518893a\
+ 8778c4f6d6d73c93df200960104e062b\
+ 388ea97dcf4016bced7f62b4f062cb6c\
+ 04c20693d9a0e3b74ba8fe74cc012378\
+ 84f40d765ae56a51688d985cf0ceaef4\
+ 3045ed8c3f0c33bced08537f6882613a\
+ cd3b08d665fce9dd8aa73171e2d3771a\
+ 61dba2790e491d413d93d987e2745af2\
+ 9418e428be34941485c93447520ffe23\
+ 1da2304d6a0fd5d07d08372202369661\
+ 59bef3cf904d722324dd852513df39ae\
+ 030d8173908da6364786d3c1bfcb19ea\
+ 77a63b25f1e7fc661def480c5d00d444\
+ 56269ebd84efd8e3a8b2c257eec76060\
+ 682848cbf5194bc99e49ee75e4d0d254\
+ bad4bfd74970c30e44b65511d4ad0e6e\
+ c7398e08e01307eeeea14e46ccd87cf3\
+ 6b285221254d8fc6a6765c524ded0085\
+ dca5bd688ddf722e2c0faf9d0fb2ce7a\
+ 0c3f2cee19ca0ffba461ca8dc5d2c817\
+ 8b0762cf67135558494d2a96f1a139f0\
+ edb42d2af89a9c9122b07acbc29e5e72\
+ 2df8615c343702491098478a389c9872\
+ a10b0c9875125e257c7bfdf27eef4060\
+ bd3d00f4c14fd3e3496c38d3c5d1a566\
+ 8c39350effbc2d16ca17be4ce29f02ed\
+ 969504dda2a8c6b9ff919e693ee79e09\
+ 089316e7d1d89ec099db3b2b268725d8\
+ 88536a4b8bf9aee8fb43e82a4d919d48\
+ 1802771a449b30f3fa2289852607b660";
+
+fn make_server(quic_version: QuicVersion) -> Connection {
+ test_fixture::fixture_init();
+ Connection::new_server(
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(5))),
+ &ConnectionParameters::default().quic_version(quic_version),
+ )
+ .expect("create a default server")
+}
+
+fn process_client_initial(quic_version: QuicVersion, packet: &str) {
+ let mut server = make_server(quic_version);
+
+ let pkt: Vec<u8> = Encoder::from_hex(packet).into();
+ let dgram = Datagram::new(loopback(), loopback(), pkt);
+ 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_27() {
+ process_client_initial(QuicVersion::Draft27, &INITIAL_PACKET_27);
+}
+
+#[test]
+fn process_client_initial_29() {
+ process_client_initial(QuicVersion::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..aca7c3ce9b
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/connection.rs
@@ -0,0 +1,51 @@
+// 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)]
+
+use neqo_common::Datagram;
+use test_fixture::{self, default_client, default_server, now};
+
+#[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 dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_some());
+
+ // This will truncate the Handshake packet from the server.
+ let dupe = dgram.as_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[..(dupe.len() - tail)],
+ );
+ let dupe_ack = client.process(Some(truncated), now()).dgram();
+ assert!(dupe_ack.is_some());
+
+ // Now feed in the untruncated packet.
+ let dgram = client.process(dgram, now()).dgram();
+ assert!(dgram.is_some()); // Throw this ACK away.
+ assert!(test_fixture::maybe_authenticate(&mut client));
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+
+ assert!(client.state().connected());
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_some());
+ assert!(server.state().connected());
+}
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..480188739f
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/network.rs
@@ -0,0 +1,168 @@
+// 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)]
+
+#[macro_use]
+mod sim;
+
+use neqo_transport::{ConnectionError, Error, State};
+use sim::{
+ connection::{ConnectionNode, ReachState, ReceiveData, SendData},
+ network::{Delay, Drop, TailDrop},
+ Simulator,
+};
+use std::ops::Range;
+use std::time::Duration;
+
+/// 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);
+
+simulate!(
+ connect_direct,
+ [
+ ConnectionNode::new_client(boxed![ReachState::new(State::Confirmed)]),
+ ConnectionNode::new_server(boxed![ReachState::new(State::Confirmed)]),
+ ]
+);
+
+simulate!(
+ idle_timeout,
+ [
+ ConnectionNode::new_client(boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]),
+ ConnectionNode::new_server(boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]),
+ ]
+);
+
+simulate!(
+ idle_timeout_crazy_rtt,
+ [
+ ConnectionNode::new_client(boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]),
+ Delay::new(Duration::from_secs(15)..Duration::from_secs(15)),
+ Drop::percentage(10),
+ ConnectionNode::new_server(boxed![
+ ReachState::new(State::Confirmed),
+ ReachState::new(State::Closed(ConnectionError::Transport(
+ Error::IdleTimeout
+ )))
+ ]),
+ Delay::new(Duration::from_secs(10)..Duration::from_secs(10)),
+ Drop::percentage(10),
+ ],
+);
+
+simulate!(
+ transfer,
+ [
+ ConnectionNode::new_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ ConnectionNode::new_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ ]
+);
+
+simulate!(
+ connect_fixed_rtt,
+ [
+ ConnectionNode::new_client(boxed![ReachState::new(State::Confirmed)]),
+ Delay::new(DELAY..DELAY),
+ ConnectionNode::new_server(boxed![ReachState::new(State::Confirmed)]),
+ Delay::new(DELAY..DELAY),
+ ],
+);
+
+simulate!(
+ connect_taildrop_jitter,
+ [
+ ConnectionNode::new_client(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_uplink(),
+ Delay::new(ZERO..JITTER),
+ ConnectionNode::new_server(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_downlink(),
+ Delay::new(ZERO..JITTER),
+ ],
+);
+
+simulate!(
+ connect_taildrop,
+ [
+ ConnectionNode::new_client(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_uplink(),
+ ConnectionNode::new_server(boxed![ReachState::new(State::Confirmed)]),
+ TailDrop::dsl_downlink(),
+ ],
+);
+
+simulate!(
+ transfer_delay_drop,
+ [
+ ConnectionNode::new_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ Delay::new(DELAY_RANGE),
+ Drop::percentage(1),
+ ConnectionNode::new_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ Delay::new(DELAY_RANGE),
+ Drop::percentage(1),
+ ],
+);
+
+simulate!(
+ transfer_taildrop,
+ [
+ ConnectionNode::new_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_uplink(),
+ ConnectionNode::new_server(boxed![ReceiveData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_downlink(),
+ ],
+);
+
+simulate!(
+ transfer_taildrop_jitter,
+ [
+ ConnectionNode::new_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ TailDrop::dsl_uplink(),
+ Delay::new(ZERO..JITTER),
+ ConnectionNode::new_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::new_client(boxed![SendData::new(TRANSFER_AMOUNT)]),
+ Delay::new(ZERO..DELAY),
+ Drop::percentage(1),
+ ConnectionNode::new_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/server.rs b/third_party/rust/neqo-transport/tests/server.rs
new file mode 100644
index 0000000000..8cde456ee8
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/server.rs
@@ -0,0 +1,1011 @@
+// 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)]
+
+use neqo_common::{event::Provider, hex_with_len, qdebug, qtrace, Datagram, Decoder, Encoder};
+use neqo_crypto::{
+ aead::Aead,
+ constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
+ hkdf,
+ hp::HpKey,
+ AllowZeroRtt, AuthenticationStatus, ResumptionToken,
+};
+use neqo_transport::{
+ server::{ActiveConnectionRef, Server, ValidateAddress},
+ Connection, ConnectionError, ConnectionEvent, ConnectionParameters, Error,
+ FixedConnectionIdManager, Output, QuicVersion, State, StreamType, LOCAL_STREAM_LIMIT_BIDI,
+ LOCAL_STREAM_LIMIT_UNI,
+};
+use test_fixture::{self, assertions, default_client, loopback, now, split_datagram};
+
+use std::cell::RefCell;
+use std::convert::TryFrom;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+use std::ops::Range;
+use std::rc::Rc;
+use std::time::Duration;
+
+// Different than the one in the fixture, which is a single connection.
+fn default_server() -> Server {
+ Server::new(
+ now(),
+ test_fixture::DEFAULT_KEYS,
+ test_fixture::DEFAULT_ALPN,
+ test_fixture::anti_replay(),
+ Box::new(AllowZeroRtt {}),
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(9))),
+ ConnectionParameters::default(),
+ )
+ .expect("should create a server")
+}
+
+// Check that there is at least one connection. Returns a ref to the first confirmed connection.
+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.
+fn connect(client: &mut Connection, server: &mut Server) -> ActiveConnectionRef {
+ server.set_validation(ValidateAddress::Never);
+
+ assert_eq!(*client.state(), State::Init);
+ let dgram = client.process(None, now()).dgram(); // ClientHello
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram(); // ServerHello...
+ assert!(dgram.is_some());
+
+ // Ingest the server Certificate.
+ let dgram = client.process(dgram, now()).dgram();
+ assert!(dgram.is_some()); // This should just be an ACK.
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_none()); // So the server should have nothing to say.
+
+ // Now mark the server as authenticated.
+ client.authenticated(AuthenticationStatus::Ok, now());
+ let dgram = client.process(None, now()).dgram();
+ assert!(dgram.is_some());
+ assert_eq!(*client.state(), State::Connected);
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_some()); // ACK + HANDSHAKE_DONE + NST
+
+ // Have the client process the HANDSHAKE_DONE.
+ let dgram = client.process(dgram, now()).dgram();
+ assert!(dgram.is_none());
+ assert_eq!(*client.state(), State::Confirmed);
+
+ connected_server(server)
+}
+
+/// 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.
+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) {
+ let _ = test_fixture::maybe_authenticate(client);
+ let out = client.process(datagram, now());
+ let out = server.process(out.dgram(), 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 duplicate_initial() {
+ let mut server = default_server();
+ let mut client = default_client();
+
+ assert_eq!(*client.state(), State::Init);
+ let initial = client.process(None, now()).dgram();
+ assert!(initial.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.clone(), now()).dgram();
+ assert!(server_initial.is_some());
+ let dgram = server.process(initial, 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[..],
+ );
+
+ // 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()).dgram();
+ assert!(client_initial1.is_some());
+ let client_initial2 = client2.process(None, now()).dgram();
+ assert!(client_initial2.is_some());
+
+ // The server should respond to both as these came from different addresses.
+ let server_initial1 = server.process(client_initial1, now()).dgram();
+ assert!(server_initial1.is_some());
+
+ let server_initial2 = server.process(client_initial2, 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()).dgram();
+ assert!(client_initial.is_some());
+
+ let server_initial = server.process(client_initial.clone(), 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, 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, QuicVersion::default().as_u32())
+ .encode_vec(1, CID)
+ .encode_vec(1, CID);
+ let mut bogus_data: Vec<u8> = header.into();
+ bogus_data.resize(1200, 66);
+
+ let bogus = Datagram::new(
+ test_fixture::loopback(),
+ test_fixture::loopback(),
+ bogus_data,
+ );
+ assert!(server.process(Some(bogus), now()).dgram().is_none());
+}
+
+#[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, now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(&dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram, now()).dgram(); // Initial w/token
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram(); // Initial, HS
+ assert!(dgram.is_some());
+ let _ = client.process(dgram, 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, now()).dgram(); // (done)
+ assert!(dgram.is_some()); // Note that this packet will be dropped...
+ connected_server(&mut server);
+}
+
+#[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, now).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(&dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram, now).dgram(); // Initial w/token
+ assert!(dgram.is_some());
+
+ now += Duration::from_secs(60); // Too long for Retry.
+ let dgram = server.process(dgram, now).dgram(); // Initial, HS
+ assert!(dgram.is_none());
+}
+
+fn get_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 dgram = server.process(None, now()).dgram();
+ client.process_input(dgram.unwrap(), now()); // Consume ticket, ignore output.
+
+ // Calling active_connections clears the set of active connections.
+ assert_eq!(server.active_connections().len(), 1);
+ client
+ .events()
+ .find_map(|e| {
+ if let ConnectionEvent::ResumptionToken(token) = e {
+ Some(token)
+ } else {
+ None
+ }
+ })
+ .unwrap()
+}
+
+// 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 = get_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, 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, now()).dgram();
+ assert!(dgram.is_some());
+ assertions::assert_coalesced_0rtt(dgram.as_ref().unwrap());
+
+ let dgram = server.process(dgram, now()).dgram(); // Initial, HS
+ assert!(dgram.is_some());
+ let dgram = client.process(dgram, 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, now()).dgram(); // (done)
+ assert!(dgram.is_some());
+ connected_server(&mut server);
+ assert!(client.tls_info().unwrap().resumed());
+}
+
+#[test]
+fn new_token_0rtt() {
+ let mut server = default_server();
+ let token = get_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 dgram = client.process(None, now()).dgram(); // Initial w/0-RTT
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), true);
+ assertions::assert_coalesced_0rtt(dgram.as_ref().unwrap());
+ let dgram = server.process(dgram, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), false);
+
+ let dgram = client.process(dgram, 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, 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, now()).dgram(); // Initial
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(&dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram, 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[..]);
+ 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 = get_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[..]));
+ let dgram = server.process(dgram, now()).dgram(); // Retry
+ assert!(dgram.is_some());
+ assertions::assert_retry(dgram.as_ref().unwrap());
+}
+
+#[test]
+fn new_token_different_port() {
+ let mut server = default_server();
+ let token = get_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[..]));
+ let dgram = server.process(dgram, now()).dgram(); // Retry
+ assert!(dgram.is_some());
+ assertions::assert_initial(dgram.as_ref().unwrap(), false);
+}
+
+#[test]
+fn new_token_expired() {
+ let mut server = default_server();
+ let token = get_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[..]));
+ let dgram = server.process(dgram, 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.clone(), 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, 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, 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, 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, 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, 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(), 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, now()).dgram();
+ assert!(retry.is_some());
+ let client_initial2 = client.process(retry, now()).dgram();
+ assert!(client_initial2.is_some());
+
+ let dgram = server.process(client_initial2, 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 pto1 = client.process(None, now).dgram();
+ assert!(pto1.unwrap().len() >= 1200);
+ let pto2 = client.process(None, now).dgram();
+ assert!(pto2.unwrap().len() >= 1200);
+ let cb = client.process(None, now).callback();
+ assert_ne!(cb, Duration::new(0, 0));
+
+ let retry = server.process(ci, now).dgram();
+ assertions::assert_retry(&retry.as_ref().unwrap());
+
+ let ci2 = client.process(retry, 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, now()).dgram(); // Retry
+ assert!(dgram.is_some());
+
+ assertions::assert_retry(&dgram.as_ref().unwrap());
+
+ let dgram = client.process(dgram, 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::new(loopback(), loopback(), encoder);
+
+ assert_ne!(
+ client.process(Some(vn), now()).callback(),
+ Duration::from_secs(0)
+ );
+}
+
+// Generate an AEAD and header protection object for a client Initial.
+fn client_initial_aead_and_hp(dcid: &[u8]) -> (Aead, HpKey) {
+ const INITIAL_SALT: &[u8] = &[
+ 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11,
+ 0xe0, 0x43, 0x90, 0xa8, 0x99,
+ ];
+ let initial_secret = hkdf::extract(
+ TLS_VERSION_1_3,
+ TLS_AES_128_GCM_SHA256,
+ Some(
+ hkdf::import_key(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, INITIAL_SALT)
+ .as_ref()
+ .unwrap(),
+ ),
+ hkdf::import_key(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, dcid)
+ .as_ref()
+ .unwrap(),
+ )
+ .unwrap();
+
+ let secret = hkdf::expand_label(
+ TLS_VERSION_1_3,
+ TLS_AES_128_GCM_SHA256,
+ &initial_secret,
+ &[],
+ "client in",
+ )
+ .unwrap();
+ (
+ Aead::new(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(),
+ )
+}
+
+// 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.
+fn decode_initial_header(dgram: &Datagram) -> (&[u8], &[u8], &[u8], &[u8]) {
+ let mut dec = Decoder::new(&dgram[..]);
+ let type_and_ver = dec.decode(5).unwrap().to_vec();
+ assert_eq!(type_and_ver[0] & 0xf0, 0xc0);
+ 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(),
+ )
+}
+
+// Remove header protection, returning the unmasked header and the packet number.
+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)
+}
+
+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];
+ }
+}
+
+// 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, now()).dgram();
+ assert!(retry.is_some());
+ let client_initial2 = client.process(retry, 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);
+
+ // Now we have enough information to make keys.
+ let (aead, hp) = client_initial_aead_and_hp(&d_cid);
+ 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).to_vec();
+ qtrace!("notoken_header={}", hex_with_len(&notoken_header));
+
+ // Encrypt.
+ let mut notoken_packet = Encoder::with_capacity(1200)
+ .encode(&notoken_header)
+ .to_vec();
+ notoken_packet.resize_with(1200, u8::default);
+ aead.encrypt(
+ pn,
+ &notoken_header,
+ plaintext,
+ &mut notoken_packet[notoken_header.len()..],
+ )
+ .unwrap();
+ // Unlike with decryption, don't truncate.
+ // All 1200 bytes are needed to reach the minimum datagram size.
+
+ apply_header_protection(&hp, &mut notoken_packet, pn_offset..(pn_offset + pn_len));
+ qtrace!("packet={}", hex_with_len(&notoken_packet));
+
+ let new_datagram = Datagram::new(
+ client_initial2.source(),
+ client_initial2.destination(),
+ 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, now()).dgram(); // Generate an ACK.
+ assert!(dgram.is_some());
+ let dgram = server.process(dgram, now()).dgram();
+ assert!(dgram.is_none());
+ assert!(test_fixture::maybe_authenticate(&mut client));
+ let dgram = client.process(dgram, now()).dgram();
+ assert!(dgram.is_some()); // Client sending CLOSE_CONNECTIONs
+ assert!(matches!(
+ *client.state(),
+ State::Closing{
+ error: ConnectionError::Transport(Error::ProtocolViolation),
+ ..
+ }
+ ));
+}
+
+#[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);
+ let (aead, hp) = client_initial_aead_and_hp(d_cid);
+ 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, QuicVersion::default().as_u32())
+ .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.to_vec();
+ ciphertext.resize(header_enc.len() + payload_enc.len() + aead.expansion(), 0);
+ let v = aead
+ .encrypt(
+ pn,
+ &header_enc,
+ &payload_enc,
+ &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(), 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() {
+ 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(), 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(QuicVersion::default().as_u32());
+ }
+ 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]
+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: usize) {
+ 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(FixedConnectionIdManager::new(9))),
+ 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,
+ usize::try_from(MAX_STREAMS).unwrap(),
+ );
+ can_create_streams(
+ &mut client,
+ StreamType::BiDi,
+ usize::try_from(MAX_STREAMS).unwrap(),
+ );
+}
+
+#[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(FixedConnectionIdManager::new(9))),
+ ConnectionParameters::default(),
+ )
+ .expect("should create a server");
+
+ let mut client = default_client();
+ connect(&mut client, &mut server);
+
+ // Make sure that we can create LOCAL_STREAM_LIMIT_UNI unidirectional streams
+ can_create_streams(
+ &mut client,
+ StreamType::UniDi,
+ usize::try_from(LOCAL_STREAM_LIMIT_UNI).unwrap(),
+ );
+ can_create_streams(
+ &mut client,
+ StreamType::BiDi,
+ usize::try_from(LOCAL_STREAM_LIMIT_BIDI).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..d8656b8cac
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/connection.rs
@@ -0,0 +1,296 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(clippy::module_name_repetitions)]
+
+use super::{Node, Rng};
+use neqo_common::{event::Provider, qdebug, qtrace, Datagram};
+use neqo_crypto::AuthenticationStatus;
+use neqo_transport::{Connection, ConnectionEvent, Output, State, StreamId, StreamType};
+use std::cmp::min;
+use std::fmt::{self, Debug};
+use std::time::Instant;
+
+/// 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(goals: impl IntoIterator<Item = Box<dyn ConnectionGoal>>) -> Self {
+ Self {
+ c: test_fixture::default_client(),
+ goals: goals.into_iter().collect(),
+ }
+ }
+
+ pub fn new_server(goals: impl IntoIterator<Item = Box<dyn ConnectionGoal>>) -> Self {
+ Self {
+ c: test_fixture::default_server(),
+ goals: goals.into_iter().collect(),
+ }
+ }
+
+ #[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 {
+ let _ = self.process_goals(|goal, c| goal.process(c, now));
+ loop {
+ let res = self.c.process(d.take(), 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(StreamId::new(stream_id));
+ }
+ }
+ }
+
+ fn send(&mut self, c: &mut Connection, stream_id: StreamId) -> GoalStatus {
+ const DATA: &[u8] = &[0; 4096];
+ let stream_id = stream_id.as_u64();
+ 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.as_u64(), &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, StreamId::new(*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..95188c0562
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/delay.rs
@@ -0,0 +1,98 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(clippy::module_name_repetitions)]
+
+use super::{Node, Rng};
+use neqo_common::Datagram;
+use neqo_transport::Output;
+use std::collections::BTreeMap;
+use std::convert::TryFrom;
+use std::fmt::{self, Debug};
+use std::ops::Range;
+use std::time::{Duration, Instant};
+
+/// 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..d42913d99d
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/drop.rs
@@ -0,0 +1,71 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(clippy::module_name_repetitions)]
+
+use super::{Node, Rng};
+use neqo_common::{qtrace, Datagram};
+use neqo_transport::Output;
+use std::fmt::{self, Debug};
+use std::time::Instant;
+
+/// 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..f39f5603df
--- /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 neqo_common::{qdebug, qinfo, qtrace, Datagram, Encoder};
+use neqo_transport::Output;
+use rng::Random;
+use std::cell::RefCell;
+use std::cmp::min;
+use std::convert::TryFrom;
+use std::fmt::Debug;
+use std::rc::Rc;
+use std::time::{Duration, Instant};
+use test_fixture::{self, now};
+
+use NodeState::{Active, Idle, Waiting};
+
+pub mod network {
+ pub use super::delay::Delay;
+ pub use super::drop::Drop;
+ pub use super::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[..]).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 = Instant::now() - real_start;
+ 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..d314e8b36f
--- /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 neqo_common::Decoder;
+use std::convert::TryFrom;
+use std::ops::Range;
+
+/// 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..7346b27178
--- /dev/null
+++ b/third_party/rust/neqo-transport/tests/sim/taildrop.rs
@@ -0,0 +1,184 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(clippy::module_name_repetitions)]
+
+use super::Node;
+use neqo_common::{qtrace, Datagram};
+use neqo_transport::Output;
+use std::cmp::max;
+use std::collections::VecDeque;
+use std::convert::TryFrom;
+use std::fmt::{self, Debug};
+use std::time::{Duration, Instant};
+
+/// 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")
+ }
+}