Bug 1664321 - Update neqo to 0.4.11 r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D89828
This commit is contained in:
Dragana Damjanovic 2020-09-14 07:50:57 +00:00
parent 0fd2537d8e
commit 93fcbece5c
50 changed files with 7781 additions and 6344 deletions

View File

@ -10,7 +10,7 @@ replace-with = "vendored-sources"
[source."https://github.com/mozilla/neqo"]
git = "https://github.com/mozilla/neqo"
replace-with = "vendored-sources"
tag = "v0.4.10"
tag = "v0.4.11"
[source."https://github.com/mozilla/mp4parse-rust"]
git = "https://github.com/mozilla/mp4parse-rust"

23
Cargo.lock generated
View File

@ -3181,21 +3181,20 @@ dependencies = [
[[package]]
name = "neqo-common"
version = "0.4.10"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.10#d41b4b3ee922f03eab32328a8163f0cece69a468"
version = "0.4.11"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.11#77956b5cf9f748450c0275fe1dd1dfb9b7db3c2d"
dependencies = [
"chrono",
"env_logger 0.6.2",
"lazy_static",
"log",
"num-traits",
"qlog",
]
[[package]]
name = "neqo-crypto"
version = "0.4.10"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.10#d41b4b3ee922f03eab32328a8163f0cece69a468"
version = "0.4.11"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.11#77956b5cf9f748450c0275fe1dd1dfb9b7db3c2d"
dependencies = [
"bindgen",
"log",
@ -3207,38 +3206,36 @@ dependencies = [
[[package]]
name = "neqo-http3"
version = "0.4.10"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.10#d41b4b3ee922f03eab32328a8163f0cece69a468"
version = "0.4.11"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.11#77956b5cf9f748450c0275fe1dd1dfb9b7db3c2d"
dependencies = [
"log",
"neqo-common",
"neqo-crypto",
"neqo-qpack",
"neqo-transport",
"num-traits",
"qlog",
"smallvec",
]
[[package]]
name = "neqo-qpack"
version = "0.4.10"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.10#d41b4b3ee922f03eab32328a8163f0cece69a468"
version = "0.4.11"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.11#77956b5cf9f748450c0275fe1dd1dfb9b7db3c2d"
dependencies = [
"lazy_static",
"log",
"neqo-common",
"neqo-crypto",
"neqo-transport",
"num-traits",
"qlog",
"static_assertions",
]
[[package]]
name = "neqo-transport"
version = "0.4.10"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.10#d41b4b3ee922f03eab32328a8163f0cece69a468"
version = "0.4.11"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.11#77956b5cf9f748450c0275fe1dd1dfb9b7db3c2d"
dependencies = [
"indexmap",
"lazy_static",

View File

@ -8,17 +8,17 @@ edition = "2018"
name = "neqo_glue"
[dependencies]
neqo-http3 = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
nserror = { path = "../../../xpcom/rust/nserror" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
xpcom = { path = "../../../xpcom/rust/xpcom" }
thin-vec = { version = "0.1.0", features = ["gecko-ffi"] }
[dependencies.neqo-crypto]
tag = "v0.4.10"
tag = "v0.4.11"
git = "https://github.com/mozilla/neqo"
default-features = false
features = ["gecko"]

View File

@ -5,16 +5,16 @@ authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
edition = "2018"
[dependencies]
neqo-transport = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.10", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.11", git = "https://github.com/mozilla/neqo" }
mio = "0.6.17"
mio-extras = "2.0.5"
log = "0.4.0"
[dependencies.neqo-crypto]
tag = "v0.4.10"
tag = "v0.4.11"
git = "https://github.com/mozilla/neqo"
default-features = false
features = ["gecko"]

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"7478b708c2659f8ba3567b6dd479f60094a4667445bf19b73513b3eab6c86539","src/codec.rs":"6c0f0138967cc927555241ff5b1266deffcf58b9b1d6d09c633a8d5294d148f2","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/incrdecoder.rs":"f65afa390317ab2a306e8cd7e08f33490dc824242f9be87b2a16b136715f8094","src/lib.rs":"611fc76c0650225ebe15fa2fd56fcaa77dbf0690b50f2c09bee0ea2dc6678572","src/log.rs":"b8da388073f72a21128d52b0d0c963e07a3d3cf3368438ae3a50be34b8add3a4","src/qlog.rs":"a8aa4f1f0110076b401f6e5a7057ec154c7ad3677374a21ceca1209469b9c07d","src/timer.rs":"66886b3697e1b4232d9d9892a12d93afd3381812a8ff901bceac4bb48b264607","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}
{"files":{"Cargo.toml":"55015f29db0097138e3e48d2bcff6df6487d08e48a1850be5efd25f72f82216f","src/codec.rs":"6c0f0138967cc927555241ff5b1266deffcf58b9b1d6d09c633a8d5294d148f2","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/incrdecoder.rs":"f65afa390317ab2a306e8cd7e08f33490dc824242f9be87b2a16b136715f8094","src/lib.rs":"611fc76c0650225ebe15fa2fd56fcaa77dbf0690b50f2c09bee0ea2dc6678572","src/log.rs":"b8da388073f72a21128d52b0d0c963e07a3d3cf3368438ae3a50be34b8add3a4","src/qlog.rs":"a8aa4f1f0110076b401f6e5a7057ec154c7ad3677374a21ceca1209469b9c07d","src/timer.rs":"66886b3697e1b4232d9d9892a12d93afd3381812a8ff901bceac4bb48b264607","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}

View File

@ -1,12 +1,11 @@
[package]
name = "neqo-common"
version = "0.4.10"
version = "0.4.11"
authors = ["Bobby Holley <bobbyholley@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
[dependencies]
num-traits = "0.2"
log = {version = "0.4.0", default-features = false}
env_logger = "0.6.1"
lazy_static = "1.3.0"

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"9a50a6a88f0f6611f9ec0b23878678779649d02cacfa5aa78df8a047fdb9f008","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"0ae7922bb20f2b8cf54b307cd642303e65b00cfbc3e681877e2a7a86f7b22530","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"0be611e6b25a18d5ed724071a3a471c2c5b584649b5fe36de28a56ed8caaf800","src/aead.rs":"e26ad34f7168f42aa87eb3a6455af3191a0e8555782b1d70032fe1d248922f98","src/agent.rs":"0cf454a0d272dacb31a0e49e9a97c880d1f18c16b9c158b1772ce34c4f15963a","src/agentio.rs":"cc562d09a09719b90b4e1d147fd579e3e89b683448709e920033bceaea108a61","src/auth.rs":"71ac7e297a5f872d26cf67b6bbd96e4548ea38374bdd84c1094f76a5de4ed1cb","src/cert.rs":"fd3fd2bbb38754bdcee3898549feae412943c9f719032531c1ad6e61783b5394","src/constants.rs":"c39ee506a10d685fda77c1d2ddf691b595b067b4e1044ac7a21e360119d6002b","src/err.rs":"04f38831ca62d29d8aadfe9daf95fd29e68ece184e6d3e00bfb9ee1d12744033","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"97cba23247e5f9656f27587214f7d7370a69174bae5960a012ce3e6fc99f9116","src/hkdf.rs":"40e44f4280497ef525c2b4c465f14f06d241150851668b264ee958f74321cfbe","src/hp.rs":"7fce64e0cc3a6a7e744bc797886bcfaa39679f0a81853b2e55ea0f54fb6bf700","src/lib.rs":"f66cd7a1c949eb47dfa33189d5e28ebbe653c812a93e9cd96583f82b0495707f","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"0b62ee5938aefb82e8faee5aa14e990a00442cc9744e8ba22eda80b32030c42c","src/prio.rs":"bc4e97049563b136cb7b39f5171e7909d56a77ed46690aaacb781eeb4a4743e0","src/replay.rs":"40924865994396441a68e6009ecbdf352d6a02fdf539aa65604124e26bffb4d3","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"acb5befa74e06281c6f80d7298efc58f568bb4e6d949b4225c335e3f392be741","src/selfencrypt.rs":"429cb889a4e9e2345888cc033115c0aa306d2ff90bdfe22b3067700eb1426c37","src/ssl.rs":"d64c20ed2a0b63c5fa3aee674a622408a89a764ee225098f18d0c61ce6c6df29","src/time.rs":"5b2ab4028b04b6245c666f33f1c1449816d3d1eb8141f723f5773f21f8fe4388","tests/aead.rs":"a1d8eb69f5672e064f84dce3d214b347a396718e3de56d57ccc108ee87f1cbc1","tests/agent.rs":"2312590910dc3cba4c446c0dee844773779d8a3870cf543fcc863821fcee50dd","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"6f12fb9a02d36f64254ffe49385de69fce8bc95b73af80be011f0e065d65a5a3","tests/hkdf.rs":"539235e9dcf2a56b72961a9a04f0080409adf6bf465bfad7c30026421b2d4326","tests/hp.rs":"e52a7d2f4387f2dfe8bfe1da5867e8e0d3eb51e171c6904e18b18c4343536af8","tests/init.rs":"20aad800ac793aaf83059cf860593750509fdedeeff0c08a648e7a5cb398dae0","tests/selfencrypt.rs":"46e9a1a09c2ae577eb106d23a5cdacf762575c0dea1948aedab06ef7389ce713"},"package":null}
{"files":{"Cargo.toml":"54af911fb9cd6e1628705623a15ecd371e1c5d0dad3664fe4180008beb46a1c9","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"0ae7922bb20f2b8cf54b307cd642303e65b00cfbc3e681877e2a7a86f7b22530","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"0be611e6b25a18d5ed724071a3a471c2c5b584649b5fe36de28a56ed8caaf800","src/aead.rs":"e26ad34f7168f42aa87eb3a6455af3191a0e8555782b1d70032fe1d248922f98","src/agent.rs":"4304185f0c26a416988e4ca8ba92df93e53a448e91da88fe7708341bfed5ce05","src/agentio.rs":"cc562d09a09719b90b4e1d147fd579e3e89b683448709e920033bceaea108a61","src/auth.rs":"71ac7e297a5f872d26cf67b6bbd96e4548ea38374bdd84c1094f76a5de4ed1cb","src/cert.rs":"fd3fd2bbb38754bdcee3898549feae412943c9f719032531c1ad6e61783b5394","src/constants.rs":"c39ee506a10d685fda77c1d2ddf691b595b067b4e1044ac7a21e360119d6002b","src/err.rs":"04f38831ca62d29d8aadfe9daf95fd29e68ece184e6d3e00bfb9ee1d12744033","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"97cba23247e5f9656f27587214f7d7370a69174bae5960a012ce3e6fc99f9116","src/hkdf.rs":"40e44f4280497ef525c2b4c465f14f06d241150851668b264ee958f74321cfbe","src/hp.rs":"7fce64e0cc3a6a7e744bc797886bcfaa39679f0a81853b2e55ea0f54fb6bf700","src/lib.rs":"f66cd7a1c949eb47dfa33189d5e28ebbe653c812a93e9cd96583f82b0495707f","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"0b62ee5938aefb82e8faee5aa14e990a00442cc9744e8ba22eda80b32030c42c","src/prio.rs":"bc4e97049563b136cb7b39f5171e7909d56a77ed46690aaacb781eeb4a4743e0","src/replay.rs":"40924865994396441a68e6009ecbdf352d6a02fdf539aa65604124e26bffb4d3","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"acb5befa74e06281c6f80d7298efc58f568bb4e6d949b4225c335e3f392be741","src/selfencrypt.rs":"429cb889a4e9e2345888cc033115c0aa306d2ff90bdfe22b3067700eb1426c37","src/ssl.rs":"d64c20ed2a0b63c5fa3aee674a622408a89a764ee225098f18d0c61ce6c6df29","src/time.rs":"5b2ab4028b04b6245c666f33f1c1449816d3d1eb8141f723f5773f21f8fe4388","tests/aead.rs":"a1d8eb69f5672e064f84dce3d214b347a396718e3de56d57ccc108ee87f1cbc1","tests/agent.rs":"6b8015a4fc5e08154bf77e54b73828a1e5776a7c1a6584ef4dabf43fc866f3e8","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"538e08e68aaf3de0a64d25bda85239acc9f6292207128c81997a9d2b7aa26c39","tests/hkdf.rs":"539235e9dcf2a56b72961a9a04f0080409adf6bf465bfad7c30026421b2d4326","tests/hp.rs":"e52a7d2f4387f2dfe8bfe1da5867e8e0d3eb51e171c6904e18b18c4343536af8","tests/init.rs":"20aad800ac793aaf83059cf860593750509fdedeeff0c08a648e7a5cb398dae0","tests/selfencrypt.rs":"46e9a1a09c2ae577eb106d23a5cdacf762575c0dea1948aedab06ef7389ce713"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-crypto"
version = "0.4.10"
version = "0.4.11"
authors = ["Martin Thomson <mt@lowentropy.net>"]
edition = "2018"
build = "build.rs"

View File

@ -33,6 +33,9 @@ use std::ptr::{null, null_mut, NonNull};
use std::rc::Rc;
use std::time::Instant;
/// The maximum number of tickets to remember for a given connection.
const MAX_TICKETS: usize = 4;
#[derive(Clone, Debug, PartialEq)]
pub enum HandshakeState {
New,
@ -685,11 +688,12 @@ impl ::std::fmt::Display for SecretAgent {
/// A TLS Client.
#[derive(Debug)]
#[allow(clippy::box_vec)] // We need the Box.
pub struct Client {
agent: SecretAgent,
/// Records the last resumption token.
resumption: Pin<Box<Option<Vec<u8>>>>,
/// Records the resumption tokens we've received.
resumption: Pin<Box<Vec<Vec<u8>>>>,
}
impl Client {
@ -704,7 +708,7 @@ impl Client {
agent.ready(false)?;
let mut client = Self {
agent,
resumption: Box::pin(None),
resumption: Box::pin(Vec::new()),
};
client.ready()?;
Ok(client)
@ -716,7 +720,7 @@ impl Client {
len: c_uint,
arg: *mut c_void,
) -> ssl::SECStatus {
let resumption_ptr = arg as *mut Option<Vec<u8>>;
let resumption_ptr = arg as *mut Vec<Vec<u8>>;
let resumption = resumption_ptr.as_mut().unwrap();
let len = usize::try_from(len).unwrap();
let mut v = Vec::with_capacity(len);
@ -726,7 +730,10 @@ impl Client {
"Got resumption token {}",
hex_snip_middle(&v)
);
*resumption = Some(v);
if resumption.len() >= MAX_TICKETS {
resumption.remove(0);
}
resumption.push(v);
ssl::SECSuccess
}
@ -741,10 +748,10 @@ impl Client {
}
}
/// Return the resumption token.
/// Take a resumption token.
#[must_use]
pub fn resumption_token(&self) -> Option<&Vec<u8>> {
(*self.resumption).as_ref()
pub fn resumption_token(&mut self) -> Option<Vec<u8>> {
(*self.resumption).pop()
}
/// Enable resumption, using a token previously provided.
@ -752,7 +759,7 @@ impl Client {
/// # Errors
/// Error returned when the resumption token is invalid or
/// the socket is not able to use the value.
pub fn set_resumption_token(&mut self, token: &[u8]) -> Res<()> {
pub fn enable_resumption(&mut self, token: &[u8]) -> Res<()> {
unsafe {
ssl::SSL_SetResumptionToken(
self.agent.fd,

View File

@ -271,7 +271,7 @@ fn resume() {
let mut server = Server::new(&["key"]).expect("should create second server");
client
.set_resumption_token(&token[..])
.enable_resumption(&token[..])
.expect("should accept token");
connect(&mut client, &mut server);
@ -287,7 +287,7 @@ fn zero_rtt() {
let mut client = Client::new("server.example").expect("should create client");
let mut server = Server::new(&["key"]).expect("should create server");
client
.set_resumption_token(&token[..])
.enable_resumption(&token[..])
.expect("should accept token");
client.enable_0rtt().expect("should enable 0-RTT");
server
@ -311,7 +311,7 @@ fn zero_rtt_no_eoed() {
let mut client = Client::new("server.example").expect("should create client");
let mut server = Server::new(&["key"]).expect("should create server");
client
.set_resumption_token(&token[..])
.enable_resumption(&token[..])
.expect("should accept token");
client.enable_0rtt().expect("should enable 0-RTT");
client
@ -350,7 +350,7 @@ fn reject_zero_rtt() {
let mut client = Client::new("server.example").expect("should create client");
let mut server = Server::new(&["key"]).expect("should create server");
client
.set_resumption_token(&token[..])
.enable_resumption(&token[..])
.expect("should accept token");
client.enable_0rtt().expect("should enable 0-RTT");
server

View File

@ -146,6 +146,6 @@ pub fn resumption_setup(mode: Resumption) -> (Option<AntiReplay>, Vec<u8>) {
// `client` is about to go out of scope,
// but we only need to keep the resumption token, so clone it.
let token = client.resumption_token().expect("token is present").clone();
let token = client.resumption_token().expect("token is present");
(anti_replay, token)
}

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"d2490bd4f6f9a771a730fd5b424339d15be5f41e27bb068e38ab78a51c958ddd","src/client_events.rs":"198015709e4ab2161fa43f03d0d9c7b5218d960b9e1f72f261f9a0fca2d1476a","src/connection.rs":"3e203b343f36bb54299425939838003310b8bf13e7d1aca37d2d18779345214c","src/connection_client.rs":"05190e458eafa50a7a5071fc04c700b8b95022735d3a00fcb89f078d96229d2a","src/connection_server.rs":"5d32fe8280e451c9853741d3910b1e0aa71220d35ba9cd672919fe74e621de1f","src/control_stream_local.rs":"03d6259599543da2154388d5e48efbc06271e079429d3d946278c4c58c0521c7","src/control_stream_remote.rs":"1dfac4956a7d6971e2cef2c83963d838e73aa3bf3286b7bde97099978c41d527","src/hframe.rs":"5b7349c8f6d18416d4159f0f8da46b5019e82b1eead7e95e151c2d0e1d4cd959","src/lib.rs":"b29d3140536fcf26cbf0c00d597b30ece4cffbba0fadc3616fabb0968453297c","src/push_controller.rs":"744372679db12ab179e10cf196397d8f0c2f4085cd627e8fbde07889f918638c","src/push_stream.rs":"5044e2d5a8a7aa0711901342f532a952be407327f844613afdae4f8a5e14d21a","src/qlog.rs":"29c0e3c4c9571eb7fe905967edeb1c4bc236b1e35a0e0f11a4a847f1d246681d","src/recv_message.rs":"ee1f20d6b96079c557f1d675b8f88d274eaa9b73626a9f2da152e0a0c091e3dd","src/send_message.rs":"7e3f5280eca9b007b3b189134ce6832d3996f10405d396790acbced06a72af81","src/server.rs":"29af820be014adc338c67284f4df0584757a7ff14d7202e0b9a7a5384ab7b4ed","src/server_connection_events.rs":"0c2b8831ce9d254a15a3af24d155a119ca1d4a29dd6d287114bf0607efe93076","src/server_events.rs":"27f23dc49f649fb66113c5a71345d9af30e7de04f791d4e1928d32c66b47d3f1","src/settings.rs":"92fb232a11d1f37f22dee99c1fa6e0becf287fc6c6996ce890e30b2a6e37cf56","src/stream_type_reader.rs":"aacb2e865f79b3ac55a887fd670f2286d8ffef94f7d8b3ecfa7e0cccbfa9ec04","tests/httpconn.rs":"57ef3a309ca3e9ac768c51ee4c98d16511b855ce4133760bf088e4ac0b967084"},"package":null}
{"files":{"Cargo.toml":"388b2ec4492f021063a03c0f20736e2728139011550859a55d8d44d10cae3902","src/client_events.rs":"198015709e4ab2161fa43f03d0d9c7b5218d960b9e1f72f261f9a0fca2d1476a","src/connection.rs":"3e203b343f36bb54299425939838003310b8bf13e7d1aca37d2d18779345214c","src/connection_client.rs":"be7416bdb13318c0ef2f825adea2124bfa3e067fea1a293df2bddbbfc5f58a45","src/connection_server.rs":"5d32fe8280e451c9853741d3910b1e0aa71220d35ba9cd672919fe74e621de1f","src/control_stream_local.rs":"03d6259599543da2154388d5e48efbc06271e079429d3d946278c4c58c0521c7","src/control_stream_remote.rs":"1dfac4956a7d6971e2cef2c83963d838e73aa3bf3286b7bde97099978c41d527","src/hframe.rs":"2ee3c6a8e4837be247845e8f3f87aa864cac1e2adf41e5f6e134568fff319a04","src/lib.rs":"b29d3140536fcf26cbf0c00d597b30ece4cffbba0fadc3616fabb0968453297c","src/push_controller.rs":"744372679db12ab179e10cf196397d8f0c2f4085cd627e8fbde07889f918638c","src/push_stream.rs":"5044e2d5a8a7aa0711901342f532a952be407327f844613afdae4f8a5e14d21a","src/qlog.rs":"29c0e3c4c9571eb7fe905967edeb1c4bc236b1e35a0e0f11a4a847f1d246681d","src/recv_message.rs":"ee1f20d6b96079c557f1d675b8f88d274eaa9b73626a9f2da152e0a0c091e3dd","src/send_message.rs":"7e3f5280eca9b007b3b189134ce6832d3996f10405d396790acbced06a72af81","src/server.rs":"ea61da714b1c33d5cb07e90f3144c208530ba0b932577d8f66aa44d618ed5d13","src/server_connection_events.rs":"0c2b8831ce9d254a15a3af24d155a119ca1d4a29dd6d287114bf0607efe93076","src/server_events.rs":"27f23dc49f649fb66113c5a71345d9af30e7de04f791d4e1928d32c66b47d3f1","src/settings.rs":"92fb232a11d1f37f22dee99c1fa6e0becf287fc6c6996ce890e30b2a6e37cf56","src/stream_type_reader.rs":"aacb2e865f79b3ac55a887fd670f2286d8ffef94f7d8b3ecfa7e0cccbfa9ec04","tests/httpconn.rs":"57ef3a309ca3e9ac768c51ee4c98d16511b855ce4133760bf088e4ac0b967084"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-http3"
version = "0.4.10"
version = "0.4.11"
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
@ -10,7 +10,6 @@ neqo-common = { path = "./../neqo-common" }
neqo-crypto = { path = "./../neqo-crypto" }
neqo-transport = { path = "./../neqo-transport" }
neqo-qpack = { path = "./../neqo-qpack" }
num-traits = "0.2"
log = {version = "0.4.0", default-features = false}
smallvec = "1.0.0"
qlog = "0.3.0"

View File

@ -154,7 +154,7 @@ impl Http3Client {
/// Returns a resumption token if present.
/// A resumption token encodes transport and settings parameter as well.
#[must_use]
pub fn resumption_token(&self) -> Option<Vec<u8>> {
pub fn resumption_token(&mut self) -> Option<Vec<u8>> {
if let Some(token) = self.conn.resumption_token() {
if let Some(settings) = self.base_handler.get_settings() {
let mut enc = Encoder::default();
@ -172,7 +172,7 @@ impl Http3Client {
/// This may be call if an application has a resumption token. This must be called before connection starts.
/// # Errors
/// An error is return if token cannot be decoded or a connection is is a wrong state.
pub fn set_resumption_token(&mut self, now: Instant, token: &[u8]) -> Res<()> {
pub fn enable_resumption(&mut self, now: Instant, token: &[u8]) -> Res<()> {
if self.base_handler.state != Http3State::Initializing {
return Err(Error::InvalidState);
}
@ -189,9 +189,7 @@ impl Http3Client {
.map_err(|_| Error::InvalidResumptionToken)?;
let tok = dec.decode_remainder();
qtrace!([self], " Transport token {}", hex(&tok));
self.conn
.set_resumption_token(now, tok)
.map_err(|e| Error::map_set_resumption_errors(&e))?;
self.conn.enable_resumption(now, tok)?;
if self.conn.state().closed() {
let state = self.conn.state().clone();
debug_assert!(
@ -763,6 +761,11 @@ mod tests {
// resumed settings.
const ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION: &[u8] = &[0x2, 0x3f, 0x45];
const ENCODER_STREAM_DATA_WITH_CAP_INST_AND_ENCODING_INST: &[u8] = &[
0x2, 0x3f, 0x45, 0x67, 0xa7, 0xd4, 0xe5, 0x1c, 0x85, 0xb1, 0x1f, 0x86, 0xa7, 0xd7, 0x71,
0xd1, 0x69, 0x7f,
];
// Decoder stream data
const DECODER_STREAM_DATA: &[u8] = &[0x3];
@ -801,7 +804,7 @@ mod tests {
encoder: QPackEncoder::new(
QpackSettings {
max_table_size_encoder: 128,
max_table_size_decoder: 0,
max_table_size_decoder: 128,
max_blocked_streams: 0,
},
true,
@ -821,7 +824,7 @@ mod tests {
encoder: QPackEncoder::new(
QpackSettings {
max_table_size_encoder: 128,
max_table_size_decoder: 0,
max_table_size_decoder: 128,
max_blocked_streams: 0,
},
true,
@ -874,16 +877,23 @@ mod tests {
}
pub fn check_client_control_qpack_streams_no_resumption(&mut self) {
self.check_client_control_qpack_streams(ENCODER_STREAM_DATA, false, true);
self.check_client_control_qpack_streams(
ENCODER_STREAM_DATA,
EXPECTED_REQUEST_HEADER_FRAME,
false,
true,
);
}
pub fn check_control_qpack_request_streams_resumption(
&mut self,
expect_encoder_stream_data: &[u8],
expect_request_header: &[u8],
expect_request: bool,
) {
self.check_client_control_qpack_streams(
expect_encoder_stream_data,
expect_request_header,
expect_request,
false,
);
@ -893,6 +903,7 @@ mod tests {
pub fn check_client_control_qpack_streams(
&mut self,
expect_encoder_stream_data: &[u8],
expect_request_header: &[u8],
expect_request: bool,
expect_connected: bool,
) {
@ -930,11 +941,7 @@ mod tests {
qpack_decoder_stream = true;
} else if stream_id == 0 {
assert!(expect_request);
self.read_and_check_stream_data(
stream_id,
EXPECTED_REQUEST_HEADER_FRAME,
true,
);
self.read_and_check_stream_data(stream_id, expect_request_header, true);
request = true;
} else {
panic!("unexpected event");
@ -1033,10 +1040,10 @@ mod tests {
(client, server)
}
// Fetch request fetch("GET", "https", "something.com", "/", &[]).
fn make_request(client: &mut Http3Client, close_sending_side: bool) -> u64 {
// Fetch request fetch("GET", "https", "something.com", "/", headers).
fn make_request(client: &mut Http3Client, close_sending_side: bool, headers: &[Header]) -> u64 {
let request_stream_id = client
.fetch(now(), "GET", "https", "something.com", "/", &[])
.fetch(now(), "GET", "https", "something.com", "/", headers)
.unwrap();
if close_sending_side {
let _ = client.stream_close_send(request_stream_id);
@ -1051,6 +1058,13 @@ mod tests {
0x43, 0xd3, 0xc1,
];
// For fetch request fetch("GET", "https", "something.com", "/", &[(String::from("myheaders"), String::from("myvalue"))])
// the following request header frame will be sent:
const EXPECTED_REQUEST_HEADER_FRAME_VERSION2: &[u8] = &[
0x01, 0x11, 0x02, 0x80, 0xd1, 0xd7, 0x50, 0x89, 0x41, 0xe9, 0x2a, 0x67, 0x35, 0x53, 0x2e,
0x43, 0xd3, 0xc1, 0x10,
];
const HTTP_HEADER_FRAME_0: &[u8] = &[0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x30];
// The response header from HTTP_HEADER_FRAME (0x01, 0x06, 0x00, 0x00, 0xd9, 0x54, 0x01, 0x30) are
@ -1124,7 +1138,7 @@ mod tests {
server: &mut TestServer,
close_sending_side: bool,
) -> u64 {
let request_stream_id = make_request(client, close_sending_side);
let request_stream_id = make_request(client, close_sending_side, &[]);
let out = client.process(None, now());
let _ = server.conn.process(out.dgram(), now());
@ -2514,11 +2528,11 @@ mod tests {
#[test]
fn test_goaway() {
let (mut client, mut server) = connect();
let request_stream_id_1 = make_request(&mut client, false);
let request_stream_id_1 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_1, 0);
let request_stream_id_2 = make_request(&mut client, false);
let request_stream_id_2 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_2, 4);
let request_stream_id_3 = make_request(&mut client, false);
let request_stream_id_3 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_3, 8);
let out = client.process(None, now());
@ -2588,11 +2602,11 @@ mod tests {
#[test]
fn multiple_goaways() {
let (mut client, mut server) = connect();
let request_stream_id_1 = make_request(&mut client, false);
let request_stream_id_1 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_1, 0);
let request_stream_id_2 = make_request(&mut client, false);
let request_stream_id_2 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_2, 4);
let request_stream_id_3 = make_request(&mut client, false);
let request_stream_id_3 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_3, 8);
let out = client.process(None, now());
@ -2666,11 +2680,11 @@ mod tests {
#[test]
fn multiple_goaways_stream_id_increased() {
let (mut client, mut server) = connect();
let request_stream_id_1 = make_request(&mut client, false);
let request_stream_id_1 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_1, 0);
let request_stream_id_2 = make_request(&mut client, false);
let request_stream_id_2 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_2, 4);
let request_stream_id_3 = make_request(&mut client, false);
let request_stream_id_3 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_3, 8);
// First send a Goaway frame with a smaller number
@ -3254,7 +3268,7 @@ mod tests {
assert_eq!(client.state(), Http3State::Initializing);
client
.set_resumption_token(now(), &token)
.enable_resumption(now(), &token)
.expect("Set resumption token.");
assert_eq!(client.state(), Http3State::ZeroRtt);
@ -3280,6 +3294,7 @@ mod tests {
// the peer settings already.
server.check_control_qpack_request_streams_resumption(
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
EXPECTED_REQUEST_HEADER_FRAME,
false,
);
@ -3298,7 +3313,11 @@ mod tests {
fn zero_rtt_send_request() {
let (mut client, mut server) = start_with_0rtt();
let request_stream_id = make_request(&mut client, true);
let request_stream_id = make_request(
&mut client,
true,
&[(String::from("myheaders"), String::from("myvalue"))],
);
assert_eq!(request_stream_id, 0);
let out = client.process(None, now());
@ -3312,7 +3331,8 @@ mod tests {
// Also qpack encoder stream will send "change capacity" instruction because it has
// the peer settings already.
server.check_control_qpack_request_streams_resumption(
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
ENCODER_STREAM_DATA_WITH_CAP_INST_AND_ENCODING_INST,
EXPECTED_REQUEST_HEADER_FRAME_VERSION2,
true,
);
@ -3366,7 +3386,7 @@ mod tests {
assert_eq!(client.state(), Http3State::Initializing);
client
.set_resumption_token(now(), &token)
.enable_resumption(now(), &token)
.expect("Set resumption token.");
let zerortt_event = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::ZeroRtt));
assert!(client.events().any(zerortt_event));
@ -3376,7 +3396,7 @@ mod tests {
assert!(client_hs.as_dgram_ref().is_some());
// Create a request
let request_stream_id = make_request(&mut client, false);
let request_stream_id = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id, 0);
let client_0rtt = client.process(None, now());
@ -3407,7 +3427,7 @@ mod tests {
TestServer::new_with_conn(server).check_client_control_qpack_streams_no_resumption();
// Check that we can send a request and that the stream_id starts again from 0.
assert_eq!(make_request(&mut client, false), 0);
assert_eq!(make_request(&mut client, false, &[]), 0);
}
// Connect to a server, get token and reconnect using 0-rtt. Seerver sends new Settings.
@ -3427,7 +3447,7 @@ mod tests {
let mut server = TestServer::new_with_settings(resumption_settings);
assert_eq!(client.state(), Http3State::Initializing);
client
.set_resumption_token(now(), &token)
.enable_resumption(now(), &token)
.expect("Set resumption token.");
assert_eq!(client.state(), Http3State::ZeroRtt);
let out = client.process(None, now());
@ -3439,7 +3459,11 @@ mod tests {
// Check that control and qpack streams anda SETTINGS frame are received.
// Also qpack encoder stream will send "change capacity" instruction because it has
// the peer settings already.
server.check_control_qpack_request_streams_resumption(expected_encoder_stream_data, false);
server.check_control_qpack_request_streams_resumption(
expected_encoder_stream_data,
EXPECTED_REQUEST_HEADER_FRAME,
false,
);
assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
@ -4024,7 +4048,7 @@ mod tests {
let qpack_pkt1 = server.conn.process(None, now());
// delay delivery of this packet.
let request_stream_id = make_request(&mut client, true);
let request_stream_id = make_request(&mut client, true, &[]);
let out = client.process(None, now());
let _ = server.conn.process(out.dgram(), now());
@ -4401,7 +4425,7 @@ mod tests {
send_push_promise_and_exchange_packets(&mut client, &mut server, request_stream_id, 5);
// make a second request.
let request_stream_id_2 = make_request(&mut client, false);
let request_stream_id_2 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_2, 4);
let out = client.process(None, now());
@ -4437,7 +4461,7 @@ mod tests {
send_push_data_and_exchange_packets(&mut client, &mut server, 5, true);
// make a second request.
let request_stream_id_2 = make_request(&mut client, false);
let request_stream_id_2 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_2, 4);
let out = client.process(None, now());
@ -4485,7 +4509,7 @@ mod tests {
);
// make a second request.
let request_stream_id_2 = make_request(&mut client, false);
let request_stream_id_2 = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id_2, 4);
let out = client.process(None, now());

View File

@ -340,7 +340,6 @@ mod tests {
use crate::settings::{HSetting, HSettingType};
use neqo_crypto::AuthenticationStatus;
use neqo_transport::{Connection, StreamType};
use num_traits::Num;
use test_fixture::{connect, default_client, default_server, now};
#[allow(clippy::many_single_char_names)]
@ -369,16 +368,8 @@ mod tests {
let mut fr: HFrameReader = HFrameReader::new();
// conver string into u8 vector
let mut buf: Vec<u8> = Vec::new();
if st.len() % 2 != 0 {
panic!("Needs to be even length");
}
for i in 0..st.len() / 2 {
let x = st.get(i * 2..i * 2 + 2);
let v = <u8 as Num>::from_str_radix(x.unwrap(), 16).unwrap();
buf.push(v);
}
conn_s.stream_send(stream_id, &buf).unwrap();
let buf = Encoder::from_hex(st);
conn_s.stream_send(stream_id, &buf[..]).unwrap();
let out = conn_s.process(None, now());
let _ = conn_c.process(out.dgram(), now());

View File

@ -972,13 +972,13 @@ mod tests {
/// Perform a handshake, then another with the token from the first.
/// The second should always resume, but it might not always accept early data.
fn zero_rtt_with_settings(settings: QpackSettings, zero_rtt: &ZeroRttState) {
let (_, client) = connect();
let (_, mut client) = connect();
let token = client.resumption_token();
assert!(token.is_some());
let mut server = create_server(settings);
let mut client = default_client();
client.set_resumption_token(now(), &token.unwrap()).unwrap();
client.enable_resumption(now(), &token.unwrap()).unwrap();
connect_transport(&mut server, &mut client, true);
assert!(client.tls_info().unwrap().resumed());

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"32ff72d5fa6d780185d6e2a4ac8f8af15376e13f5bbd718c7a3b1c7f74443c3f","src/decoder.rs":"e57f30c5e3a4e4f8b52b44c6bdf5f34ab8780cc4a18f89328af03c456eeef4f4","src/decoder_instructions.rs":"a8e04dff5fc4c658322a10daadab947dc2e41932c00c3f8d387671a86d0516af","src/encoder.rs":"511f53f32fe499f8956372a4c23d058c1f8017f396e9601788efbf26d541140d","src/encoder_instructions.rs":"1d4424bf21c0ac26b7c8fee6450b943346c5493ab86dd7ec2edc5f566454721e","src/header_block.rs":"f935872919154f678947732270d688be4790309f9e390a0c8eb6c9484a41a8dd","src/huffman.rs":"68fa0bada0c35d20f793980596accdcc548970214841f71789290fc334e51fc1","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"8ee2082fd94064e61286e24e5192ee02eee50b1ca82b9105a1e1945e596ccaa8","src/prefix.rs":"72c587c40aef4ed38cf13b2de91091d671611679be2a9da6f0b24abafaf50dc5","src/qlog.rs":"7618085e27bb3fb1f4d1c73ba501b9a293723293c4020b7cc4129676eb278131","src/qpack_send_buf.rs":"5170b93afaf0c1609463e6c5ae4dccb1a2ce4e4407296db1bcaf06c6b5bb97ab","src/reader.rs":"4bcea0de1d7dc09ec0cdff364d8f62da54bbbe1f6db55a495f943f31369b4074","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"bc82c8f86b54981be234948e285af3d778ba9d23ddaaf2fbedf9c03121eaada7","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}
{"files":{"Cargo.toml":"1f978404698514da919424659005d7ce1c3684f1cc435194a8c51ebe228393c2","src/decoder.rs":"e57f30c5e3a4e4f8b52b44c6bdf5f34ab8780cc4a18f89328af03c456eeef4f4","src/decoder_instructions.rs":"a8e04dff5fc4c658322a10daadab947dc2e41932c00c3f8d387671a86d0516af","src/encoder.rs":"0daf6111bb00dd93d724a664697f87bd3c92a0d154cfbd882016110995677466","src/encoder_instructions.rs":"1d4424bf21c0ac26b7c8fee6450b943346c5493ab86dd7ec2edc5f566454721e","src/header_block.rs":"f935872919154f678947732270d688be4790309f9e390a0c8eb6c9484a41a8dd","src/huffman.rs":"68fa0bada0c35d20f793980596accdcc548970214841f71789290fc334e51fc1","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"8ee2082fd94064e61286e24e5192ee02eee50b1ca82b9105a1e1945e596ccaa8","src/prefix.rs":"72c587c40aef4ed38cf13b2de91091d671611679be2a9da6f0b24abafaf50dc5","src/qlog.rs":"7618085e27bb3fb1f4d1c73ba501b9a293723293c4020b7cc4129676eb278131","src/qpack_send_buf.rs":"5170b93afaf0c1609463e6c5ae4dccb1a2ce4e4407296db1bcaf06c6b5bb97ab","src/reader.rs":"4bcea0de1d7dc09ec0cdff364d8f62da54bbbe1f6db55a495f943f31369b4074","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"bc82c8f86b54981be234948e285af3d778ba9d23ddaaf2fbedf9c03121eaada7","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-qpack"
version = "0.4.10"
version = "0.4.11"
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
@ -11,7 +11,6 @@ neqo-transport = { path = "./../neqo-transport" }
neqo-crypto = { path = "./../neqo-crypto" }
log = {version = "0.4.0", default-features = false}
static_assertions = "1.1.0"
num-traits = "0.2"
qlog = "0.3.0"
lazy_static = "1.3.0"

View File

@ -244,7 +244,6 @@ impl QPackEncoder {
value: &[u8],
) -> Res<u64> {
qdebug!([self], "insert {:?} {:?}.", name, value);
self.send(conn)?;
let entry_size = name.len() + value.len() + ADDITIONAL_TABLE_ENTRY_SIZE;
@ -359,6 +358,20 @@ impl QPackEncoder {
stream_id: u64,
) -> Res<HeaderEncoder> {
qdebug!([self], "encoding headers.");
let mut encoder_blocked = false;
// Try to send capacity instructions if present.
match self.send(conn) {
Ok(()) => {}
Err(Error::EncoderStreamBlocked) => {
encoder_blocked = true;
}
Err(e) => {
// `InternalError`, `ClosedCriticalStream`
return Err(e);
}
}
let mut encoded_h =
HeaderEncoder::new(self.table.base(), self.use_huffman, self.max_entries);
@ -367,8 +380,6 @@ impl QPackEncoder {
let mut ref_entries = HashSet::new();
let mut encoder_blocked = false;
for iter in h.iter() {
let name = iter.0.clone().into_bytes();
let value = iter.1.clone().into_bytes();

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"b4761ae5b3b0b812022adc67793ea05117659b10e998baccf900f2c586e41821","TODO":"d759cb804b32fa9d96ea8d3574a3c4073da9fe6a0b02b708a0e22cce5a5b4a0f","src/cc.rs":"99a60e7c22558007077f5194a80c5ab42cf0c44066e7926ada0145a43073e3c6","src/cid.rs":"936ed772eddf533cb962332a68034a0e209023991fa44ec72e2805cdff29a3c2","src/connection.rs":"6f5903093af360b3073129c51311b34aad4d0fa85160f7ff8323a29df29019a7","src/crypto.rs":"ee5ae4b73a8d55aa1e65fb83d86c03b4e0f2db27b6c0d410ca944da0268906e8","src/dump.rs":"d69ccb0e3b240823b886a791186afbac9f2e26d1f1f67a55dbf86f8cd3e6203e","src/events.rs":"3d545f4bf0625dee172c580c3881b3a87ece055335dd08780ec66780a5398547","src/flow_mgr.rs":"ff820f4f45fb5dbed5e4b1e9433483a36fc624bd5fa1a22dd7abb779ba723caf","src/frame.rs":"f56d38e67524e19c37cde41fe74c01831d45ebe76b595e8f6e275a34360b5126","src/lib.rs":"7f8b642ef73bbb5a498514a9617e2650ea4f73204311eca971f0c41b8ff06e77","src/pace.rs":"eb9094cfcae54162022f70f230b6a9811add0063b878100f147a9365473f6612","src/packet/mod.rs":"595a68d8bc25361dc847a45c9701482ab5130089eaa1f9df319e8366fac403dd","src/packet/retry.rs":"842b1fa864e03e2bbe76dd2daeaa727cf20440fed2b654ca3b50f62bc4df26a8","src/path.rs":"3ef5d00c247cfe0eced6885cdd283e25766ee979d5bdc662abfdf4d4b962dbf1","src/qlog.rs":"82cf1abaef3fcd24dc47d5965ec596789d92348328c48fab00b6f4954cb7dd62","src/recovery.rs":"9fb6349a0a6430bc034b1981039611e731b780fb8c7ae9b53adf1592b7c5ab7b","src/recv_stream.rs":"b1688b9300320e0aa2c6b862030bfda6b50d9a4e6d14a3d89dd8554e92b780e0","src/send_stream.rs":"223547f3140e9b459c303d46393760d7523b5b9d74c72c82d383b0ceb5a9f429","src/server.rs":"6dd25aa224da98ed970fd96ab5c462bf3a35376ed8f69352b686a70dc95f833f","src/stats.rs":"65927fbdd26632730e7029020ad02e9e6c59b2ec9ef26693369e81d4530149fd","src/stream_id.rs":"74c3523085bfdddfb009a33a747b329b27007b3f0ba728b18a5b6e8ab8ad1d26","src/tparams.rs":"d4a69bff044f51acab5dcbe56531bfd5eee79b99908c79074409d38ead8d3467","src/tracking.rs":"e6f19dc0cd876e0240bf7e951d850e3265e4220aba23726ee7d5a3ca4c331182","tests/conn_vectors.rs":"4ff98a0a65fe31e3503d1ec3d5fc9cdb99815a4e0343510a17d0a0ab122650ac","tests/connection.rs":"a93985c199a9ef987106f4a20b35ebf803cdbbb855c07b1362b403eed7101ef8","tests/network.rs":"a986c22da7132ec843a44c4bcb5a7d2726132aa27a47a8ea91634cd88e1b763b","tests/server.rs":"24ead84b0436daadbd22436dccf3625ab7ed83804829519e102ab7f226044a61","tests/sim/connection.rs":"d3c565990e1ca05d872bcdf83e48e66af79e6bac82a6a40d7cdd3ba193fee3c6","tests/sim/delay.rs":"9efa722adb89e37262369e9f3c67405f0acc8c24997271811e48df9e856e5a8d","tests/sim/drop.rs":"bd89e5c71cdd1b27cd755faaedd87d5feadf2f424df721a7df41a51bcebcbb58","tests/sim/mod.rs":"c6230ea030e69b9c6c4fcb30ba3f397c6da588498454f34f5075d16ce4db0542","tests/sim/net.rs":"597f4d37bc26c3d82eeeaa6d14dd03bc2be3930686df2b293748b43c07c497d7","tests/sim/rng.rs":"2c90b0bbaf0c952ebee232deb3594f7a86af387737b15474de3e97ee6b623d90","tests/sim/taildrop.rs":"5c505d150f0071e8cc2d540b3a817a6942fdf13df32f1fbc6822952f2e146176"},"package":null}
{"files":{"Cargo.toml":"f499f623eb67156a31b5eb43cb27fd9febef984b63d5ffd7b7c8852f53c37946","TODO":"d759cb804b32fa9d96ea8d3574a3c4073da9fe6a0b02b708a0e22cce5a5b4a0f","src/addr_valid.rs":"8306cc8926b7f605aa436fa9b05cde4049324179e46da0291939d8d4853035b3","src/cc.rs":"9118d0f874dce99c9f6e5ba6f586c902ad6af22a6c4afaacfa72c33e9f1c76f6","src/cid.rs":"936ed772eddf533cb962332a68034a0e209023991fa44ec72e2805cdff29a3c2","src/connection/idle.rs":"4892dbeeefb8d109a60003eb765f02ac21a244b852e6ba140bf3629dc6e386ac","src/connection/mod.rs":"4c01b26d0860f2547093613e7b67a46ffaf00442c5197fb85e4ae2dd770dced3","src/connection/saved.rs":"94f9823182e91de5919b5dfb3978a2e0655eb5748594bf24d324877039c78372","src/connection/state.rs":"ddb61eb78f4ff6be1dcb9eac986549a6918ca95beb24cabd223450b6132191bd","src/connection/tests/cc.rs":"9671484dc17d97129f2fd6af9b5b6cd8130a752f263db004feffbe21fdf20c96","src/connection/tests/close.rs":"037856e0de3ede9821ddd6245b440512889ba125c706484a7afed3b5d9609473","src/connection/tests/handshake.rs":"edbc79089ebe1bce21bebbe6b082ebda31697f0566826aa103345667cbf8dd01","src/connection/tests/idle.rs":"1d7d553cd2a23a85426795f9d8c9467bb117bb0cc74c3fee884e8d136b120ede","src/connection/tests/keys.rs":"aef9a913256c73e1305bb7f73cac917188b510cdccd07a2817cc0cbffeef091c","src/connection/tests/mod.rs":"542a647659f79e1397b39c521077d832340e12ef7dfc8253ca0a55f941557be4","src/connection/tests/recovery.rs":"66ee506c1681ba7b331888227cd9c0f2eb1daa2403aa2e72cb2d5e185ab7b9fd","src/connection/tests/resumption.rs":"61c72ea65410056e99c1479dc1c1aedb9542a35d99cd3a4be645182a52277189","src/connection/tests/stream.rs":"06f20238e5c5c9b60ee13ff5fd884336198f2c8565b81f0493e88be11735d86c","src/connection/tests/vn.rs":"628a49d8ce68f795e2bc41c8b653e410d64ed08fbeb77bdc9f3216de69c45e6e","src/connection/tests/zerortt.rs":"6b611b87cd01bd4e8b3ab186408149fa5fb8cc9b3aaf6a3095a96f155f74dbc3","src/crypto.rs":"2c68312f1e7e11ee31081e38c894882164929c5bcced6b63c414de856ec3f551","src/dump.rs":"d69ccb0e3b240823b886a791186afbac9f2e26d1f1f67a55dbf86f8cd3e6203e","src/events.rs":"3d545f4bf0625dee172c580c3881b3a87ece055335dd08780ec66780a5398547","src/flow_mgr.rs":"ff820f4f45fb5dbed5e4b1e9433483a36fc624bd5fa1a22dd7abb779ba723caf","src/frame.rs":"32dffc9d9abf1ad0b1445789bd6d4a431136496fe9f2997763ed95179657577d","src/lib.rs":"6e4d15b4112c919db50af2e6005bf0ae57fb39f6a0b90446fe3befe61c044fda","src/pace.rs":"eb9094cfcae54162022f70f230b6a9811add0063b878100f147a9365473f6612","src/packet/mod.rs":"c9d91a1f8c03a3de86ef1e9734500979d84adaf12784b374e17c9c48acb7ebfe","src/packet/retry.rs":"842b1fa864e03e2bbe76dd2daeaa727cf20440fed2b654ca3b50f62bc4df26a8","src/path.rs":"92b2701d1e486250f472b2cdc487b1e9e9c8753c9c40b626c2d6d1ccd6e70c38","src/qlog.rs":"82cf1abaef3fcd24dc47d5965ec596789d92348328c48fab00b6f4954cb7dd62","src/recovery.rs":"a48c0d091fcc5222f5d5579a9a91c5a523379eea45a83c1d2e81fc0082754b75","src/recv_stream.rs":"eec738684e38655ff701cb392740dd2d1fd3f462915e3a550f846cccd6e6cafa","src/send_stream.rs":"223547f3140e9b459c303d46393760d7523b5b9d74c72c82d383b0ceb5a9f429","src/server.rs":"ae8ec4aa961a3e1e73cb627e480ee1a557f31ea4e0cf9fb0af51cbc49266e2a3","src/stats.rs":"9ce382921a4f93498fdfb49def6b5d378ec3aa329e96ad199f594bb2c496bcbc","src/stream_id.rs":"98f656157e0eeb7f32802cf2b3dbd68ef8d7a89578a097a88db7157d4a712202","src/tparams.rs":"d4a69bff044f51acab5dcbe56531bfd5eee79b99908c79074409d38ead8d3467","src/tracking.rs":"e6f19dc0cd876e0240bf7e951d850e3265e4220aba23726ee7d5a3ca4c331182","tests/conn_vectors.rs":"4ff98a0a65fe31e3503d1ec3d5fc9cdb99815a4e0343510a17d0a0ab122650ac","tests/connection.rs":"a93985c199a9ef987106f4a20b35ebf803cdbbb855c07b1362b403eed7101ef8","tests/network.rs":"a986c22da7132ec843a44c4bcb5a7d2726132aa27a47a8ea91634cd88e1b763b","tests/server.rs":"71ebd86dcc2719a22f6a0b64c5bc449ab4cf69a307f31a0fa02ac33a01a7908e","tests/sim/connection.rs":"d3c565990e1ca05d872bcdf83e48e66af79e6bac82a6a40d7cdd3ba193fee3c6","tests/sim/delay.rs":"9efa722adb89e37262369e9f3c67405f0acc8c24997271811e48df9e856e5a8d","tests/sim/drop.rs":"bd89e5c71cdd1b27cd755faaedd87d5feadf2f424df721a7df41a51bcebcbb58","tests/sim/mod.rs":"c6230ea030e69b9c6c4fcb30ba3f397c6da588498454f34f5075d16ce4db0542","tests/sim/net.rs":"597f4d37bc26c3d82eeeaa6d14dd03bc2be3930686df2b293748b43c07c497d7","tests/sim/rng.rs":"2c90b0bbaf0c952ebee232deb3594f7a86af387737b15474de3e97ee6b623d90","tests/sim/taildrop.rs":"5c505d150f0071e8cc2d540b3a817a6942fdf13df32f1fbc6822952f2e146176"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-transport"
version = "0.4.10"
version = "0.4.11"
authors = ["EKR <ekr@rtfm.com>", "Andy Grover <agrover@mozilla.com>"]
edition = "2018"
license = "MIT/Apache-2.0"

View File

@ -0,0 +1,409 @@
// 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::frame::Frame;
use crate::recovery::RecoveryToken;
use crate::Res;
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.
const TOKEN_IDENTIFIER_NEW_TOKEN: &[u8] = &[0xad, 0x9a, 0x8b, 0x8d, 0x86];
/// The maximum number of tokens we'll save from NEW_TOKEN.
/// This should be the same as the value of MAX_TICKETS in neqo-crypto.
const MAX_NEW_TOKEN: usize = 4;
/// `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
}
}
}
}
pub enum NewTokenState {
Client(Vec<Vec<u8>>),
Server(NewTokenSender),
}
impl NewTokenState {
pub fn new(role: Role) -> Self {
match role {
Role::Client => Self::Client(Vec::new()),
Role::Server => Self::Server(NewTokenSender::default()),
}
}
/// 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<Vec<u8>> {
if let Self::Client(ref mut tokens) = self {
tokens.pop()
} 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 tokens) = self {
for t in tokens.iter().rev() {
if t == &token {
qinfo!("NewTokenState discarding duplicate NEW_TOKEN");
return;
}
}
if tokens.len() >= MAX_NEW_TOKEN {
tokens.remove(0);
}
tokens.push(token);
} else {
unreachable!();
}
}
/// If this is a server, maybe send a frame.
/// If this is a client, do nothing.
pub fn get_frame(&mut self, space: usize) -> Option<(Frame, Option<RecoveryToken>)> {
if let Self::Server(ref mut sender) = self {
sender.get_frame(space)
} else {
None
}
}
/// 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 fits(&self, space: usize) -> bool {
1 + Encoder::varint_len(u64::try_from(self.token.len()).unwrap()) + self.token.len()
<= space
}
}
#[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 get_frame(&mut self, space: usize) -> Option<(Frame, Option<RecoveryToken>)> {
for t in self.tokens.iter_mut() {
if t.needs_sending && t.fits(space) {
t.needs_sending = false;
return Some((
Frame::NewToken {
token: t.token.clone(),
},
Some(RecoveryToken::NewToken(t.seqno)),
));
}
}
None
}
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);
}
}

View File

@ -5,6 +5,7 @@
// except according to those terms.
// Congestion control
#![deny(clippy::pedantic)]
use std::cmp::{max, min};
use std::fmt::{self, Display};
@ -17,12 +18,12 @@ use crate::tracking::SentPacket;
use neqo_common::{const_max, const_min, qdebug, qinfo, qlog::NeqoQlog, qtrace};
pub const MAX_DATAGRAM_SIZE: usize = PATH_MTU_V6;
pub const INITIAL_CWND_PKTS: usize = 10;
const INITIAL_WINDOW: usize = const_min(
INITIAL_CWND_PKTS * MAX_DATAGRAM_SIZE,
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 MIN_CONG_WINDOW: usize = MAX_DATAGRAM_SIZE * 2;
pub const CWND_MIN: usize = MAX_DATAGRAM_SIZE * 2;
/// The number of packets we allow to burst from the pacer.
pub(crate) const PACING_BURST_SIZE: usize = 2;
const PERSISTENT_CONG_THRESH: u32 = 3;
@ -44,11 +45,11 @@ pub struct CongestionControl {
impl Default for CongestionControl {
fn default() -> Self {
Self {
congestion_window: INITIAL_WINDOW,
congestion_window: CWND_INITIAL,
bytes_in_flight: 0,
acked_bytes: 0,
congestion_recovery_start_time: None,
ssthresh: std::usize::MAX,
ssthresh: usize::MAX,
pacer: None,
in_recovery: false,
qlog: NeqoQlog::disabled(),
@ -166,17 +167,28 @@ impl CongestionControl {
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) <= prev_largest_acked_sent)
.skip_while(|p| Some(p.time_sent) < cutoff)
{
if p.pn != last_pn + 1 {
// Not a contiguous range of lost packets, start over.
@ -190,7 +202,7 @@ impl CongestionControl {
if let Some(t) = start {
if p.time_sent.duration_since(t) > pc_period {
// In persistent congestion. Stop.
self.congestion_window = MIN_CONG_WINDOW;
self.congestion_window = CWND_MIN;
self.acked_bytes = 0;
qlog::metrics_updated(
&mut self.qlog,
@ -208,6 +220,7 @@ impl CongestionControl {
pub fn on_packets_lost(
&mut self,
now: Instant,
first_rtt_sample_time: Option<Instant>,
prev_largest_acked_sent: Option<Instant>,
pto: Duration,
lost_packets: &[SentPacket],
@ -229,8 +242,12 @@ impl CongestionControl {
let last_lost_pkt = lost_packets.last().unwrap();
self.on_congestion_event(now, last_lost_pkt.time_sent);
self.detect_persistent_congestion(prev_largest_acked_sent, pto, lost_packets);
self.detect_persistent_congestion(
first_rtt_sample_time,
prev_largest_acked_sent,
pto,
lost_packets,
);
}
pub fn discard(&mut self, pkt: &SentPacket) {
@ -284,7 +301,7 @@ impl CongestionControl {
self.congestion_recovery_start_time = Some(now);
self.congestion_window /= 2; // kLossReductionFactor = 0.5
self.acked_bytes /= 2;
self.congestion_window = max(self.congestion_window, MIN_CONG_WINDOW);
self.congestion_window = max(self.congestion_window, CWND_MIN);
self.ssthresh = self.congestion_window;
qinfo!(
[self],
@ -306,11 +323,10 @@ impl CongestionControl {
&mut self.qlog_curr_cong_state,
CongestionState::Recovery,
);
} else {
qdebug!([self], "Cong event but already in recovery");
}
}
#[allow(clippy::unused_self)]
fn app_limited(&self) -> bool {
//TODO(agrover): how do we get this info??
false
@ -342,11 +358,12 @@ impl CongestionControl {
#[cfg(test)]
mod tests {
use super::{CongestionControl, INITIAL_WINDOW, MIN_CONG_WINDOW, PERSISTENT_CONG_THRESH};
use crate::cc::{CongestionControl, CWND_INITIAL, CWND_MIN, PERSISTENT_CONG_THRESH};
use crate::packet::{PacketNumber, PacketType};
use crate::tracking::SentPacket;
use std::convert::TryFrom;
use std::rc::Rc;
use std::time::Duration;
use std::time::{Duration, Instant};
use test_fixture::now;
const PTO: Duration = Duration::from_millis(100);
@ -400,43 +417,43 @@ mod tests {
cc.on_packet_sent(&sent_packets[0], RTT);
assert_eq!(cc.acked_bytes, 0);
assert_eq!(cc.cwnd(), INITIAL_WINDOW);
assert_eq!(cc.ssthresh(), std::usize::MAX);
assert_eq!(cc.cwnd(), CWND_INITIAL);
assert_eq!(cc.ssthresh(), usize::MAX);
assert_eq!(cc.bif(), 103);
cc.on_packet_sent(&sent_packets[1], RTT);
assert_eq!(cc.acked_bytes, 0);
assert_eq!(cc.cwnd(), INITIAL_WINDOW);
assert_eq!(cc.ssthresh(), std::usize::MAX);
assert_eq!(cc.cwnd(), CWND_INITIAL);
assert_eq!(cc.ssthresh(), usize::MAX);
assert_eq!(cc.bif(), 208);
cc.on_packets_lost(time_after1, None, PTO, &sent_packets[0..1]);
cc.on_packets_lost(time_after1, Some(time_now), None, PTO, &sent_packets[0..1]);
// We are now in recovery
assert_eq!(cc.acked_bytes, 0);
assert_eq!(cc.cwnd(), INITIAL_WINDOW / 2);
assert_eq!(cc.ssthresh(), INITIAL_WINDOW / 2);
assert_eq!(cc.cwnd(), CWND_INITIAL / 2);
assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
assert_eq!(cc.bif(), 105);
// Send a packet after recovery starts
cc.on_packet_sent(&sent_packets[2], RTT);
assert_eq!(cc.acked_bytes, 0);
assert_eq!(cc.cwnd(), INITIAL_WINDOW / 2);
assert_eq!(cc.ssthresh(), INITIAL_WINDOW / 2);
assert_eq!(cc.cwnd(), CWND_INITIAL / 2);
assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
assert_eq!(cc.bif(), 212);
// and ack it. cwnd increases slightly
cc.on_packets_acked(&sent_packets[2..3]);
assert_eq!(cc.acked_bytes, sent_packets[2].size);
assert_eq!(cc.cwnd(), INITIAL_WINDOW / 2);
assert_eq!(cc.ssthresh(), INITIAL_WINDOW / 2);
assert_eq!(cc.cwnd(), CWND_INITIAL / 2);
assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
assert_eq!(cc.bif(), 105);
// Packet from before is lost. Should not hurt cwnd.
cc.on_packets_lost(time_after3, None, PTO, &sent_packets[1..2]);
cc.on_packets_lost(time_after3, Some(time_now), None, PTO, &sent_packets[1..2]);
assert_eq!(cc.acked_bytes, sent_packets[2].size);
assert_eq!(cc.cwnd(), INITIAL_WINDOW / 2);
assert_eq!(cc.ssthresh(), INITIAL_WINDOW / 2);
assert_eq!(cc.cwnd(), CWND_INITIAL / 2);
assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
assert_eq!(cc.bif(), 0);
}
@ -458,10 +475,10 @@ mod tests {
cc.on_packet_sent(p, RTT);
}
cc.on_packets_lost(now(), None, PTO, lost_packets);
if cc.cwnd() == INITIAL_WINDOW / 2 {
cc.on_packets_lost(now(), Some(now()), None, PTO, lost_packets);
if cc.cwnd() == CWND_INITIAL / 2 {
false
} else if cc.cwnd() == MIN_CONG_WINDOW {
} else if cc.cwnd() == CWND_MIN {
true
} else {
panic!("unexpected cwnd");
@ -585,6 +602,13 @@ mod tests {
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),
@ -593,4 +617,116 @@ mod tests {
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,
Rc::default(),
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 = CongestionControl::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,
Rc::default(),
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 = CongestionControl::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));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
// 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,
New(Instant),
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(&mut self, now: Instant, pto: Duration) -> Instant {
let start = match self.state {
IdleTimeoutState::Init => {
self.state = IdleTimeoutState::New(now);
now
}
IdleTimeoutState::New(t)
| 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::New(_)
| IdleTimeoutState::PacketReceived(_) => {
self.state = IdleTimeoutState::AckElicitingPacketSent(now);
}
}
}
pub fn on_packet_received(&mut self, now: Instant) {
self.state = IdleTimeoutState::PacketReceived(now);
}
pub fn expired(&mut self, now: Instant, pto: Duration) -> bool {
now >= self.expiry(now, pto)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
// 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>,
}
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 take_saved(&mut self, cspace: CryptoSpace) -> Vec<SavedDatagram> {
mem::take(self.store(cspace))
}
}

View File

@ -0,0 +1,178 @@
// 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::recovery::RecoveryToken;
use crate::{CloseError, ConnectionError};
#[derive(Clone, Debug, PartialEq, Ord, 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 Ord 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!(),
})
}
}
/// `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(Frame),
Draining(Frame),
/// 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<Frame>),
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 send_done(&mut self) -> Option<(Frame, Option<RecoveryToken>)> {
if *self == Self::HandshakeDone {
*self = Self::Idle;
Some((Frame::HandshakeDone, Some(RecoveryToken::HandshakeDone)))
} else {
None
}
}
fn make_close_frame(
error: ConnectionError,
frame_type: FrameType,
message: impl AsRef<str>,
) -> Frame {
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<Frame> {
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;
}
}

View File

@ -0,0 +1,483 @@
// 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, connect_force_idle, connect_rtt_idle, cwnd_packets, default_client,
default_server, fill_cwnd, AT_LEAST_PTO, POST_HANDSHAKE_CWND,
};
use crate::cc::{CWND_MIN, MAX_DATAGRAM_SIZE, PACING_BURST_SIZE};
use crate::frame::{Frame, StreamType};
use crate::packet::PacketNumber;
use crate::recovery::ACK_ONLY_SIZE_LIMIT;
use crate::tparams::{self, TransportParameter};
use crate::tracking::{PNSpace, MAX_UNACKED_PKTS};
use neqo_common::{qdebug, 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;
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
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
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
// 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>, Vec<Frame>)
where
D: IntoIterator<Item = Datagram>,
D::IntoIter: ExactSizeIterator,
{
let mut srv_buf = [0; 4_096];
let mut recvd_frames = Vec::new();
let in_dgrams = in_dgrams.into_iter();
qdebug!([dest], "ack_bytes {} datagrams", in_dgrams.len());
for dgram in in_dgrams {
recvd_frames.extend(dest.test_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,
recvd_frames.into_iter().map(|(f, _e)| f).collect(),
)
}
#[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();
connect_force_idle(&mut client, &mut server);
let now = now();
// 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();
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, 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
let (s_tx_dgram, _) = ack_bytes(&mut server, 0, c_tx_dgrams, now);
// Client: Process ack
for dgram in s_tx_dgram {
let recvd_frames = client.test_process_input(dgram, now);
// Verify that server-sent frame was what we thought.
if let (
Frame::Ack {
largest_acknowledged,
..
},
PNSpace::ApplicationData,
) = recvd_frames[0]
{
assert_eq!(largest_acknowledged, flight1_largest);
} else {
panic!("Expected an application ACK");
}
}
// Client: send more
let (mut c_tx_dgrams, 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
c_tx_dgrams.remove(0);
let (s_tx_dgram, _) = ack_bytes(&mut server, 0, c_tx_dgrams, now);
// Client: Process ack
for dgram in s_tx_dgram {
let recvd_frames = client.test_process_input(dgram, now);
// Verify that server-sent frame was what we thought.
if let (
Frame::Ack {
largest_acknowledged,
..
},
PNSpace::ApplicationData,
) = recvd_frames[0]
{
assert_eq!(largest_acknowledged, flight2_largest);
} else {
panic!("Expected an application ACK");
}
}
// If we just triggered cong avoidance, these should be equal
assert_eq!(client.loss_recovery.cwnd(), client.loss_recovery.ssthresh());
}
#[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.test_process_input(dgram, now);
}
// If we just triggered cong avoidance, these should be equal
let cwnd1 = client.loss_recovery.cwnd();
assert_eq!(cwnd1, client.loss_recovery.ssthresh());
// 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.test_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]
/// 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();
connect(&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, 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
let (s_tx_dgram, _) = ack_bytes(&mut server, 0, c_tx_dgrams, now);
// Client: Process ack
for dgram in s_tx_dgram {
client.test_process_input(dgram, now);
}
// Should be in CARP now.
now += Duration::from_millis(10); // Time passes. CARP -> CA
// 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();
for i in 0..5 {
println!("{}", i);
// Client: Send more data
let (mut c_tx_dgrams, next_now) = fill_cwnd(&mut client, 0, now);
now = next_now;
let c_tx_size: usize = c_tx_dgrams.iter().map(|d| d.len()).sum();
println!(
"client sending {} bytes into cwnd of {}",
c_tx_size,
client.loss_recovery.cwnd()
);
assert_eq!(c_tx_size, expected_cwnd);
// 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);
}
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);
}
expected_cwnd += MAX_DATAGRAM_SIZE;
assert_eq!(client.loss_recovery.cwnd(), expected_cwnd);
}
}
#[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();
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);
// 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.test_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 frames = server.test_process_input(ack_pkt.unwrap(), now);
assert_eq!(frames.len(), 1);
assert!(matches!(
frames[0],
(Frame::Ack { .. }, PNSpace::ApplicationData)
));
}
#[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));
for _ in PACING_BURST_SIZE..cwnd_packets(POST_HANDSHAKE_CWND) {
assert_eq!(client.process_output(now).callback(), gap);
now += gap;
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);
}

View File

@ -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::{Output, State};
use super::{connect, connect_force_idle, default_client, default_server, send_something};
use crate::frame::{CloseError, Frame};
use crate::tparams::{self, TransportParameter};
use crate::tracking::PNSpace;
use crate::{AppError, ConnectionError, Error};
use neqo_common::Datagram;
use std::time::Duration;
use test_fixture::{self, loopback, now};
#[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);
let frames = server.test_process_input(out.dgram().unwrap(), now);
assert_eq!(frames.len(), 1);
assert!(matches!(
frames[0],
(
Frame::ConnectionClose {
error_code: CloseError::Application(42),
..
},
PNSpace::ApplicationData,
)
));
}
// 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());
let frames = client.test_process_input(dgram.unwrap(), now());
assert!(matches!(
frames[0],
(
Frame::ConnectionClose {
error_code: CloseError::Transport(code),
..
},
PNSpace::Initial,
) if code == Error::ApplicationError.code()
));
assert!(client.state().closed());
}
#[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());
let frames = client.test_process_input(dgram.unwrap(), now());
assert!(matches!(
frames[0],
(
Frame::ConnectionClose {
error_code: CloseError::Transport(_),
..
},
PNSpace::Initial,
)
));
}
/// 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!(matches!(client.state(), State::Draining { .. }));
}

View File

@ -0,0 +1,623 @@
// 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, handshake,
maybe_authenticate, send_something, split_datagram, AT_LEAST_PTO, DEFAULT_STREAM_DATA,
};
use crate::events::ConnectionEvent;
use crate::frame::StreamType;
use crate::path::PATH_MTU_V6;
use crate::{ConnectionError, Error, QuicVersion};
use neqo_common::{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};
#[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!("Output={:0x?}", out.as_dgram_ref());
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!("Output={:0x?}", out.as_dgram_ref());
qdebug!("---- client: cert verification");
let out = client.process(out.dgram(), 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(out.dgram(), now());
assert!(out.as_dgram_ref().is_some());
qdebug!("Output={:0x?}", out.as_dgram_ref());
assert_eq!(*client.state(), State::Connected);
qdebug!("---- server: FIN -> ACKS");
let out = server.process(out.dgram(), now());
assert!(out.as_dgram_ref().is_some());
qdebug!("Output={:0x?}", out.as_dgram_ref());
assert_eq!(*server.state(), State::Confirmed);
qdebug!("---- client: ACKS -> 0");
let out = client.process(out.dgram(), now());
assert!(out.as_dgram_ref().is_none());
qdebug!("Output={:0x?}", out.as_dgram_ref());
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!("Output={:0x?}", out.as_dgram_ref());
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!("Output={:0x?}", out.as_dgram_ref());
qdebug!("---- client: cert verification");
let out = client.process(out.dgram(), 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());
qdebug!("Output={:0x?}", out.as_dgram_ref());
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!("Output={:0x?}", out.as_dgram_ref());
qdebug!("---- server: Alert(certificate_revoked)");
let out = server.process(out.dgram(), now());
assert!(out.as_dgram_ref().is_some());
qdebug!("Output={:0x?}", out.as_dgram_ref());
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(),
QuicVersion::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!(2, client.stats().packets_rx);
assert_eq!(0, client.stats().dups_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.
assert_eq!(4, client.stats().packets_rx);
assert_eq!(1, client.stats().dups_rx);
assert_eq!(1, 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))),
QuicVersion::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(),
QuicVersion::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();
// 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 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 = client.resumption_token().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();
assert!(s2.is_some());
// 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);
assert_eq!(client.stats().packets_rx, 4);
assert_eq!(client.stats().saved_datagrams, 1);
// 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);
}
#[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.
now += RTT / 2;
let res = client.process(s_hs, now);
assert_ne!(res.callback(), Duration::new(0, 0));
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, 5);
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 packets = server
.events()
.filter_map(|e| {
if let ConnectionEvent::RecvStreamReadable { stream_id } = e {
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);
Some(())
} else {
None
}
})
.count();
assert_eq!(packets, PACKETS);
}
#[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, 1);
assert_eq!(server.stats().saved_datagrams, 1);
}
#[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);
}

View File

@ -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::{IdleTimeout, Output, State, LOCAL_IDLE_TIMEOUT};
use super::{
connect, connect_force_idle, connect_with_rtt, default_client, default_server, send_something,
};
use crate::frame::StreamType;
use crate::tparams::{self, TransportParameter};
use std::time::Duration;
use test_fixture::{self, now};
#[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(_)));
}

View File

@ -0,0 +1,231 @@
// 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, 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::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);
}
#[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.
check_discarded(&mut client, init_pkt_s.unwrap(), 1, 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)));
// TODO(mt) this needs to wait for handshake confirmation,
// but for now, we can do this immediately.
assert!(client.initiate_key_update().is_ok());
assert!(client.initiate_key_update().is_err());
// 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!(client.initiate_key_update().is_err());
// The server can't update until it receives an ACK for a packet.
assert!(server.initiate_key_update().is_err());
// 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!(server.initiate_key_update().is_err());
// 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!(client.initiate_key_update().is_err());
assert_eq!(client.get_epochs(), (Some(4), Some(3)));
// The server can't update until it gets something from the client.
assert!(server.initiate_key_update().is_err());
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!(client.initiate_key_update().is_err());
let mut server = default_server();
assert!(server.initiate_key_update().is_err());
// Client Initial
let dgram = client.process(None, now()).dgram();
assert!(dgram.is_some());
assert!(client.initiate_key_update().is_err());
// Server Initial + Handshake
let dgram = server.process(dgram, now()).dgram();
assert!(dgram.is_some());
assert!(server.initiate_key_update().is_err());
// Client Handshake
client.process_input(dgram.unwrap(), now());
assert!(client.initiate_key_update().is_err());
assert!(maybe_authenticate(&mut client));
assert!(client.initiate_key_update().is_err());
let dgram = client.process(None, now()).dgram();
assert!(dgram.is_some());
assert!(client.initiate_key_update().is_err());
// 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());
}

View File

@ -0,0 +1,321 @@
// 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::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::QuicVersion;
use std::cell::RefCell;
use std::mem;
use std::rc::Rc;
use std::time::{Duration, Instant};
use neqo_common::{qdebug, qtrace, Datagram, Decoder};
use neqo_crypto::{AllowZeroRtt, AuthenticationStatus};
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 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(),
QuicVersion::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))),
QuicVersion::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| match c.state() {
State::Confirmed | State::Closing { .. } | State::Closed(..) => true,
_ => false,
};
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) -> Vec<u8> {
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);
client.resumption_token().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 includes the initial congestion window, as increased as a result
/// receiving acknowledgments for Initial and Handshake packets, which is
/// at least one full packet (the first Initial) and a little extra.
///
/// 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 + 1) + 75;
/// 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()
}
/// Split the first packet off a coalesced packet.
fn split_packet(buf: &[u8]) -> (&[u8], Option<&[u8]>) {
if buf[0] & 0x80 == 0 {
// Short header: easy.
return (buf, None);
}
let mut dec = Decoder::from(buf);
let first = dec.decode_byte().unwrap();
assert_ne!(first & 0b0011_0000, 0b0011_0000, "retry not supported");
dec.skip(4); // Version.
dec.skip_vec(1); // DCID
dec.skip_vec(1); // SCID
if first & 0b0011_0000 == 0 {
dec.skip_vvec(); // Initial token
}
dec.skip_vvec(); // The rest of the packet.
let p1 = &buf[..dec.offset()];
let p2 = if dec.remaining() > 0 {
Some(dec.decode_remainder())
} else {
None
};
(p1, p2)
}
/// Split the first datagram off a coalesced datagram.
fn split_datagram(d: &Datagram) -> (Datagram, Option<Datagram>) {
let (a, b) = split_packet(&d[..]);
(
Datagram::new(d.source(), d.destination(), a),
b.map(|b| Datagram::new(d.source(), d.destination(), b)),
)
}

View File

@ -0,0 +1,635 @@
// 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,
split_datagram, AT_LEAST_PTO, POST_HANDSHAKE_CWND,
};
use crate::frame::{Frame, StreamType};
use crate::path::PATH_MTU_V6;
use crate::recovery::PTO_PACKET_COUNT;
use crate::tparams::TransportParameter;
use crate::tracking::{PNSpace, ACK_DELAY};
use neqo_common::qdebug;
use neqo_crypto::AuthenticationStatus;
use std::time::Duration;
use test_fixture::{self, now};
#[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 frames = server.test_process_input(out.dgram().unwrap(), now);
assert!(frames.iter().all(|(_, sp)| *sp == PNSpace::ApplicationData));
assert!(frames.iter().any(|(f, _)| *f == Frame::Ping));
assert!(frames
.iter()
.any(|(f, _)| matches!(f, Frame::Stream { stream_id, .. } if stream_id.as_u64() == 2)));
assert!(frames
.iter()
.any(|(f, _)| matches!(f, Frame::Stream { stream_id, .. } if stream_id.as_u64() == 6)));
}
#[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);
// Fill the CWND after waiting for a PTO.
let (dgrams, now) = fill_cwnd(&mut client, 2, now + AT_LEAST_PTO);
assert_eq!(dgrams.len(), 2); // Two packets in the PTO.
// All (2) datagrams contain one PING frame and at least one STREAM frame.
for d in dgrams {
assert_eq!(d.len(), PATH_MTU_V6);
let frames = server.test_process_input(d, now);
assert_eq!(
frames
.iter()
.filter(|i| matches!(i, (Frame::Ping, PNSpace::ApplicationData)))
.count(),
1
);
assert!(
frames
.iter()
.filter(|i| matches!(i, (Frame::Stream { .. }, PNSpace::ApplicationData)))
.count()
>= 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 frames = server.test_process_input(
pkt6.dgram().unwrap(),
now + Duration::from_secs(10) + Duration::from_millis(110),
);
assert_eq!(frames[0], (Frame::Ping, PNSpace::ApplicationData));
}
#[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));
// 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());
// 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));
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 = server.test_process_input(pkt2.unwrap(), now);
assert_eq!(1, server.stats().dropped_rx - dropped_before1);
assert!(frames.is_empty());
let dropped_before2 = server.stats().dropped_rx;
let frames = server.test_process_input(pkt3.unwrap(), now);
assert_eq!(1, server.stats().dropped_rx - dropped_before2);
assert!(frames.is_empty());
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.
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 frames = server.test_process_input(pkt2.unwrap(), now);
assert_eq!(frames.len(), 2);
assert_eq!(frames[0], (Frame::Ping, PNSpace::Handshake));
assert!(matches!(
frames[1],
(Frame::Crypto { .. }, PNSpace::Handshake)
));
}
/// 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);
// 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 frames = server.test_process_input(c3.unwrap(), now);
assert_eq!(frames, vec![(Frame::Ping, PNSpace::Handshake)]);
// 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);
}
#[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 ACK frames.
let frames = server.test_process_input(ack.unwrap(), now);
assert_eq!(frames.len(), 1);
for (frame, space) in frames {
assert_eq!(space, PNSpace::ApplicationData);
assert!(matches!(frame, Frame::Ack { .. }));
}
}
/// 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);
}

View File

@ -0,0 +1,146 @@
// 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, 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(mut token: Option<Vec<u8>>, initial_has_token: bool) {
let mut client = default_client();
client
.enable_resumption(now(), token.take().as_ref().unwrap())
.unwrap();
let initial = client.process_output(now()).dgram();
assertions::assert_initial(initial.as_ref().unwrap(), initial_has_token);
}
#[test]
fn two_tickets() {
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());
client.process_input(pkt, now());
let token1 = client.resumption_token();
assert!(token1.is_some());
let token2 = client.resumption_token();
assert!(token2.is_some());
assert_ne!(token1, token2);
assert!(client.resumption_token().is_none());
can_resume(token1, false);
can_resume(token2, false);
}
#[test]
fn two_tickets_and_tokens() {
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 token1 = client.resumption_token();
let token2 = client.resumption_token();
assert_ne!(token1, token2);
assert!(client.resumption_token().is_none());
can_resume(token1, true);
can_resume(token2, true);
}

View File

@ -0,0 +1,447 @@
// 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};
use crate::events::ConnectionEvent;
use crate::frame::{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;
use neqo_common::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 stream_id = stream_ids.next().expect("should have a new stream event");
let (received1, fin1) = server.stream_recv(stream_id.as_u64(), &mut buf).unwrap();
assert_eq!(received1, 4000);
assert_eq!(fin1, false);
let (received2, fin2) = server.stream_recv(stream_id.as_u64(), &mut buf).unwrap();
assert_eq!(received2, 140);
assert_eq!(fin2, false);
let stream_id = stream_ids
.next()
.expect("should have a second new stream event");
let (received3, fin3) = server.stream_recv(stream_id.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 now
// DataReadable events should 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 now
// DataReadable events should 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();
// Try to say we're blocked beyond the initial data window
server
.flow_mgr
.borrow_mut()
.stream_data_blocked(3.into(), RECV_BUFFER_SIZE as u64 * 4);
let out = server.process(None, now);
assert!(out.as_dgram_ref().is_some());
let frames = client.test_process_input(out.dgram().unwrap(), now);
assert!(frames
.iter()
.any(|(f, _)| matches!(f, Frame::StreamDataBlocked { .. })));
let out = client.process_output(now);
assert!(out.as_dgram_ref().is_some());
let frames = server.test_process_input(out.dgram().unwrap(), now);
// Client should have sent a MaxStreamData frame with just the initial
// window value.
assert!(frames.iter().any(
|(f, _)| matches!(f, Frame::MaxStreamData { maximum_stream_data, .. }
if *maximum_stream_data == RECV_BUFFER_SIZE as u64)
));
}

View File

@ -0,0 +1,178 @@
// 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};
#[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, Duration::from_millis(300));
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, Duration::from_millis(300));
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, Duration::from_millis(300));
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"),
}
}

View File

@ -0,0 +1,192 @@
// 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::{Error, QuicVersion};
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))),
QuicVersion::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));
}

View File

@ -11,7 +11,7 @@ use std::ops::{Index, IndexMut, Range};
use std::rc::Rc;
use std::time::Instant;
use neqo_common::{hex, qdebug, qerror, qinfo, qtrace, Role};
use neqo_common::{hex, qdebug, qinfo, qtrace, Role};
use neqo_crypto::{
aead::Aead, hkdf, hp::HpKey, Agent, AntiReplay, Cipher, Epoch, HandshakeState, Record,
RecordList, SymKey, ZeroRttChecker, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
@ -134,51 +134,48 @@ impl Crypto {
self.tls.read_secret(TLS_EPOCH_ZERO_RTT),
),
};
let secret = secret.ok_or(Error::KeysNotFound)?;
let secret = secret.ok_or(Error::InternalError)?;
self.states.set_0rtt_keys(dir, &secret, cipher.unwrap());
Ok(true)
}
pub fn install_keys(&mut self, role: Role) {
if self.tls.state().is_final() {
return;
}
// These functions only work once, but will usually return KeysNotFound.
// Anything else is unusual and worth logging.
if let Err(e) = self.install_handshake_keys() {
qerror!([self], "Error installing handshake keys: {:?}", e);
}
if role == Role::Server {
if let Err(e) = self.install_application_write_key() {
qerror!([self], "Error installing application write key: {:?}", e);
/// 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<()> {
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(());
return Ok(false);
};
let read_secret = self
.tls
.read_secret(TLS_EPOCH_HANDSHAKE)
.ok_or(Error::KeysNotFound)?;
.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::KeysNotFound)?;
.ok_or(Error::InternalError)?;
self.states
.set_handshake_keys(&write_secret, &read_secret, cipher);
qdebug!([self], "Handshake keys installed");
Ok(())
Ok(true)
}
fn install_application_write_key(&mut self) -> Res<()> {
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)?;
@ -188,15 +185,14 @@ impl Crypto {
}
pub fn install_application_keys(&mut self, expire_0rtt: Instant) -> Res<()> {
if let Err(e) = self.install_application_write_key() {
if e != Error::KeysNotFound {
return Err(e);
}
}
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::KeysNotFound)?;
.ok_or(Error::InternalError)?;
self.states
.set_application_read_key(read_secret, expire_0rtt)?;
qdebug!([self], "application read keys installed");
@ -356,11 +352,6 @@ impl CryptoDxState {
}
}
#[must_use]
pub fn is_0rtt(&self) -> bool {
self.epoch == usize::from(TLS_EPOCH_ZERO_RTT)
}
#[must_use]
pub fn key_phase(&self) -> bool {
// Epoch 3 => 0, 4 => 1, 5 => 0, 6 => 1, ...
@ -565,6 +556,14 @@ impl CryptoDxAppData {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum CryptoSpace {
Initial,
ZeroRtt,
Handshake,
ApplicationData,
}
#[derive(Debug, Default)]
pub struct CryptoStates {
initial: Option<CryptoState>,
@ -580,59 +579,89 @@ pub struct CryptoStates {
}
impl CryptoStates {
fn select_or_0rtt<'a>(
app: Option<&'a mut CryptoDxAppData>,
zero_rtt: Option<&'a mut CryptoDxState>,
dir: CryptoDxDirection,
) -> Option<&'a mut CryptoDxState> {
app.map(|a| &mut a.dx)
.or_else(|| zero_rtt.filter(|z| z.direction == dir))
}
pub fn tx<'a>(&'a mut self, space: PNSpace) -> Option<&'a mut CryptoDxState> {
let tx = |x: &'a mut Option<CryptoState>| x.as_mut().map(|dx| &mut dx.tx);
/// 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 => tx(&mut self.initial),
PNSpace::Handshake => tx(&mut self.handshake),
PNSpace::ApplicationData => Self::select_or_0rtt(
self.app_write.as_mut(),
self.zero_rtt.as_mut(),
CryptoDxDirection::Write,
),
}
}
pub fn rx_hp(&mut self, space: PNSpace) -> Option<&mut CryptoDxState> {
match space {
PNSpace::ApplicationData => Self::select_or_0rtt(
self.app_read.as_mut(),
self.zero_rtt.as_mut(),
CryptoDxDirection::Read,
),
_ => self.rx(space, false),
}
}
pub fn rx<'a>(&'a mut self, space: PNSpace, key_phase: bool) -> Option<&'a mut CryptoDxState> {
let rx = |x: &'a mut Option<CryptoState>| x.as_mut().map(|dx| &mut dx.rx);
match space {
PNSpace::Initial => rx(&mut self.initial),
PNSpace::Handshake => rx(&mut self.handshake),
PNSpace::Initial => self
.tx(CryptoSpace::Initial)
.map(|dx| (CryptoSpace::Initial, dx)),
PNSpace::Handshake => self
.tx(CryptoSpace::Handshake)
.map(|dx| (CryptoSpace::Handshake, dx)),
PNSpace::ApplicationData => {
let app = if let Some(arn) = &self.app_read_next {
if arn.dx.key_phase() == key_phase {
self.app_read_next.as_mut()
} else {
self.app_read.as_mut()
}
if let Some(app) = self.app_write.as_mut() {
Some((CryptoSpace::ApplicationData, &mut app.dx))
} else {
self.app_read.as_mut()
};
Self::select_or_0rtt(app, self.zero_rtt.as_mut(), CryptoDxDirection::Read)
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";
@ -666,6 +695,7 @@ impl CryptoStates {
}
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));
}
@ -679,6 +709,7 @@ impl CryptoStates {
}
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"
@ -854,13 +885,14 @@ impl CryptoStates {
/// Make some state for removing protection in tests.
#[cfg(test)]
pub(crate) fn test_default() -> Self {
let read = || {
let read = |epoch| {
let mut dx = CryptoDxState::test_default();
dx.direction = CryptoDxDirection::Read;
dx.epoch = epoch;
dx
};
let app_read = || CryptoDxAppData {
dx: read(),
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(),
@ -868,14 +900,15 @@ impl CryptoStates {
Self {
initial: Some(CryptoState {
tx: CryptoDxState::test_default(),
rx: read(),
rx: read(0),
}),
handshake: None,
zero_rtt: None,
cipher: TLS_AES_128_GCM_SHA256,
app_write: None,
app_read: Some(app_read()),
app_read_next: Some(app_read()),
// 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,
}
}
@ -889,10 +922,10 @@ impl CryptoStates {
];
let secret =
hkdf::import_key(TLS_VERSION_1_3, TLS_CHACHA20_POLY1305_SHA256, SECRET).unwrap();
let app_read = || CryptoDxAppData {
let app_read = |epoch| CryptoDxAppData {
dx: CryptoDxState {
direction: CryptoDxDirection::Read,
epoch: 0,
epoch,
aead: Aead::new(
TLS_VERSION_1_3,
TLS_CHACHA20_POLY1305_SHA256,
@ -918,9 +951,9 @@ impl CryptoStates {
handshake: None,
zero_rtt: None,
cipher: TLS_CHACHA20_POLY1305_SHA256,
app_write: None,
app_read: Some(app_read()),
app_read_next: Some(app_read()),
app_write: Some(app_read(3)),
app_read: Some(app_read(3)),
app_read_next: Some(app_read(4)),
read_update_time: None,
}
}

View File

@ -626,9 +626,11 @@ impl Frame {
})
}
FRAME_TYPE_NEW_TOKEN => {
Ok(Self::NewToken {
token: d!(dec.decode_vvec()).to_vec(), // TODO(mt) unnecessary copy
})
let token = d!(dec.decode_vvec()).to_vec();
if token.is_empty() {
return Err(Error::FrameEncodingError);
}
Ok(Self::NewToken { token })
}
FRAME_TYPE_STREAM..=FRAME_TYPE_STREAM_MAX => {
let s = dv!(dec);
@ -815,7 +817,7 @@ mod tests {
}
#[test]
fn test_new_token() {
fn new_token() {
let f = Frame::NewToken {
token: vec![0x12, 0x34, 0x56],
};
@ -823,6 +825,15 @@ mod tests {
enc_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 test_stream() {
// First, just set the length bit.

View File

@ -9,6 +9,7 @@
use neqo_common::qinfo;
mod addr_valid;
mod cc;
mod cid;
mod connection;
@ -39,7 +40,6 @@ pub use self::packet::QuicVersion;
pub use self::stats::Stats;
pub use self::stream_id::StreamId;
const LOCAL_IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); // 30 second
pub use self::recv_stream::RECV_BUFFER_SIZE;
pub use self::send_stream::SEND_BUFFER_SIZE;
@ -79,11 +79,12 @@ pub enum Error {
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, or they have been discarded.
KeysNotFound,
/// 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,

View File

@ -6,8 +6,7 @@
// Encoding and decoding packets off the wire.
use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef, MAX_CONNECTION_ID_LEN};
use crate::crypto::{CryptoDxState, CryptoStates};
use crate::tracking::PNSpace;
use crate::crypto::{CryptoDxState, CryptoSpace, CryptoStates};
use crate::{Error, Res};
use neqo_common::{hex, hex_with_len, qtrace, Decoder, Encoder};
@ -65,6 +64,29 @@ impl PacketType {
}
}
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,
@ -624,34 +646,34 @@ impl<'a> PublicPacket<'a> {
}
pub fn decrypt(&self, crypto: &mut CryptoStates, release_at: Instant) -> Res<DecryptedPacket> {
let space = PNSpace::from(self.packet_type);
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(space) {
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);
if let Some(rx) = crypto.rx(space, key_phase) {
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 {
Err(Error::KeysNotFound)
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 {
Err(Error::KeysNotFound)
qtrace!("keys for {:?} already discarded", cspace);
Err(Error::KeysDiscarded)
}
}

View File

@ -98,12 +98,12 @@ impl Path {
}
/// Get local address as `SocketAddr`
pub fn local_address(&self) -> &SocketAddr {
&self.local
pub fn local_address(&self) -> SocketAddr {
self.local
}
/// Get remote address as `SocketAddr`
pub fn remote_address(&self) -> &SocketAddr {
&self.remote
pub fn remote_address(&self) -> SocketAddr {
self.remote
}
}

View File

@ -19,13 +19,13 @@ use smallvec::{smallvec, SmallVec};
use neqo_common::{qdebug, qinfo, qlog::NeqoQlog, qtrace};
use crate::cc::CongestionControl;
use crate::connection::LOCAL_IDLE_TIMEOUT;
use crate::crypto::CryptoRecoveryToken;
use crate::flow_mgr::FlowControlRecoveryToken;
use crate::qlog::{self, QlogMetric};
use crate::send_stream::StreamRecoveryToken;
use crate::stats::{Stats, StatsCell};
use crate::tracking::{AckToken, PNSpace, PNSpaceSet, SentPacket};
use crate::LOCAL_IDLE_TIMEOUT;
pub const GRANULARITY: Duration = Duration::from_millis(20);
/// The default value for the maximum time a peer can delay acknowledgment
@ -49,11 +49,12 @@ pub enum RecoveryToken {
Crypto(CryptoRecoveryToken),
Flow(FlowControlRecoveryToken),
HandshakeDone,
NewToken(usize),
}
#[derive(Debug)]
struct RttVals {
samples: u64,
first_sample_time: Option<Instant>,
latest_rtt: Duration,
smoothed_rtt: Duration,
rttvar: Duration,
@ -64,30 +65,35 @@ struct RttVals {
impl RttVals {
pub fn set_initial_rtt(&mut self, rtt: Duration) {
// Only allow this when there are no samples.
debug_assert!(self.samples == 0);
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);
// Limit ack_delay by max_ack_delay
let ack_delay = min(ack_delay, self.max_ack_delay);
// Adjust for ack delay if it's plausible.
// 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.samples == 0 {
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 {
@ -100,7 +106,6 @@ impl RttVals {
self.rttvar = (self.rttvar * 3 + rttvar_sample) / 4;
self.smoothed_rtt = (self.smoothed_rtt * 7 + rtt_sample) / 8;
}
self.samples += 1;
qtrace!(
"RTT latest={:?} -> estimate={:?}~{:?}",
self.latest_rtt,
@ -130,12 +135,16 @@ impl RttVals {
Duration::from_millis(0)
}
}
fn first_sample_time(&self) -> Option<Instant> {
self.first_sample_time
}
}
impl Default for RttVals {
fn default() -> Self {
Self {
samples: 0,
first_sample_time: None,
latest_rtt: INITIAL_RTT,
smoothed_rtt: INITIAL_RTT,
rttvar: INITIAL_RTT / 2,
@ -617,6 +626,10 @@ impl LossRecovery {
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.cc.cwnd_avail()
}
@ -664,6 +677,23 @@ impl LossRecovery {
}
}
/// 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 = if let Some(confirmed) = self.confirmed_time {
if confirmed < send_time {
ack_delay
} else {
min(ack_delay, self.rtt_vals.max_ack_delay)
}
} else {
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,
@ -680,12 +710,12 @@ impl LossRecovery {
largest_acked
);
let stats = &mut *self.stats.borrow_mut();
let space = self
.spaces
.get_mut(pn_space)
.expect("ACK on discarded space");
let (acked_packets, any_ack_eliciting) = space.remove_acked(acked_ranges, stats);
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());
@ -701,9 +731,7 @@ impl LossRecovery {
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 {
let latest_rtt = now - largest_acked_pkt.time_sent;
self.rtt_vals
.update_rtt(&mut self.qlog, latest_rtt, ack_delay);
self.rtt_sample(largest_acked_pkt.time_sent, now, ack_delay);
}
}
@ -719,14 +747,15 @@ impl LossRecovery {
.get_mut(pn_space)
.unwrap()
.detect_lost_packets(now, loss_delay, cleanup, &mut lost);
stats.lost += lost.len();
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.cc
.on_packets_lost(now, prev_largest_acked, pto_raw, &lost);
.on_packets_lost(now, 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
@ -920,6 +949,8 @@ impl LossRecovery {
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.
@ -927,6 +958,7 @@ impl LossRecovery {
space.detect_lost_packets(now, loss_delay, pto, &mut lost_packets);
self.cc.on_packets_lost(
now,
first_rtt_sample,
space.largest_acked_sent_time,
Self::pto_raw_inner(&self.rtt_vals, space.space()),
&lost_packets[first..],

View File

@ -8,7 +8,7 @@
// incoming STREAM frames.
use std::cell::RefCell;
use std::cmp::{max, min};
use std::cmp::max;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::mem;
@ -224,39 +224,41 @@ impl RxStreamOrderer {
/// 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 buf_remaining = buf.len();
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 some new contig bytes after some old bytes
// Convert to offset into data vec and move past bytes we
// already have
// Frame data has new contiguous bytes.
let copy_offset =
usize::try_from(max(range_start, self.retired) - range_start).unwrap();
let copy_bytes = min(range_data.len() - copy_offset, buf_remaining);
let copy_slc = &mut range_data[copy_offset..copy_offset + copy_bytes];
buf[copied..copied + copy_bytes].copy_from_slice(copy_slc);
copied += copy_bytes;
buf_remaining -= copy_bytes;
self.retired += copy_bytes as u64;
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 {
break; // we're missing bytes
// 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;
}
}
// Remove map items that are consumed
let to_remove = self
.data_ranges
.iter()
.take_while(|(start, data)| self.retired >= *start + data.len() as u64)
.map(|(k, _)| *k)
.collect::<Vec<_>>();
for key in to_remove {
self.data_ranges.remove(&key);
}
self.data_ranges.clear();
copied
}
@ -683,6 +685,93 @@ mod tests {
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, vec![0; CHUNK_SIZE]).unwrap();
let offset = u64::try_from(CHUNK_SIZE).unwrap();
s.inbound_frame(offset, vec![0; EXTRA_SIZE]).unwrap();
let offset = u64::try_from(CHUNK_SIZE + EXTRA_SIZE).unwrap();
s.inbound_frame(offset, vec![0; EXTRA_SIZE]).unwrap();
// 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);
}
/// 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, vec![0; CHUNK_SIZE]).unwrap();
let offset = u64::try_from(CHUNK_SIZE + EXTRA_SIZE).unwrap();
s.inbound_frame(offset, vec![0; EXTRA_SIZE]).unwrap();
// Read, providing only enough space for the first chunk.
let mut buf = vec![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, vec![0; EXTRA_SIZE]).unwrap();
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, vec![0; CHUNK_SIZE]).unwrap();
let offset = u64::try_from(CHUNK_SIZE).unwrap();
s.inbound_frame(offset, vec![0; EXTRA_SIZE]).unwrap();
// Read, providing only enough space for some of the first chunk.
let mut buf = vec![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, vec![0; CHUNK_SIZE]).unwrap();
let offset = u64::try_from(CHUNK_SIZE).unwrap();
s.inbound_frame(offset, vec![0; EXTRA_SIZE]).unwrap();
let mut buf = vec![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 test_stream_rx() {
let flow_mgr = Rc::new(RefCell::new(FlowMgr::default()));

View File

@ -8,14 +8,12 @@
use neqo_common::{
self as common, hex, qdebug, qerror, qinfo, qlog::NeqoQlog, qtrace, qwarn, timer::Timer,
Datagram, Decoder, Encoder, Role,
};
use neqo_crypto::{
constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
selfencrypt::SelfEncrypt,
AntiReplay, ZeroRttCheckResult, ZeroRttChecker,
Datagram, Decoder, Role,
};
use neqo_crypto::{AntiReplay, 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};
@ -23,10 +21,9 @@ use crate::{QuicVersion, Res};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet, VecDeque};
use std::convert::TryFrom;
use std::fs::OpenOptions;
use std::mem;
use std::net::{IpAddr, SocketAddr};
use std::net::SocketAddr;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::rc::{Rc, Weak};
@ -68,120 +65,6 @@ impl DerefMut for ServerConnectionState {
}
}
enum RetryTokenResult {
Pass,
Valid(ConnectionId),
Validate,
Invalid,
}
struct RetryToken {
/// Whether to send a Retry.
require_retry: bool,
/// A self-encryption object used for protecting Retry tokens.
self_encrypt: SelfEncrypt,
/// When this object was created.
start_time: Instant,
}
impl RetryToken {
fn new(now: Instant) -> Res<Self> {
Ok(Self {
require_retry: false,
self_encrypt: SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256)?,
start_time: now,
})
}
fn encode_peer_address(peer_address: SocketAddr) -> Vec<u8> {
// 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 encoded_address = Encoder::default();
match peer_address.ip() {
IpAddr::V4(a) => {
encoded_address.encode_byte(4);
encoded_address.encode(&a.octets());
}
IpAddr::V6(a) => {
encoded_address.encode_byte(6);
encoded_address.encode(&a.octets());
}
}
encoded_address.encode_uint(2, peer_address.port());
encoded_address.into()
}
/// This generates a token for use with Retry.
pub fn generate_token(
&mut self,
dcid: &ConnectionId,
peer_address: SocketAddr,
now: Instant,
) -> Res<Vec<u8>> {
const EXPIRATION: Duration = Duration::from_secs(5);
// TODO(mt) rotate keys on a fixed schedule.
let mut token = Encoder::default();
let end = now + EXPIRATION;
let end_millis = u32::try_from(end.duration_since(self.start_time).as_millis())?;
token.encode_uint(4, end_millis);
token.encode(dcid);
let peer_addr = Self::encode_peer_address(peer_address);
Ok(self.self_encrypt.seal(&peer_addr, &token)?)
}
pub fn set_retry_required(&mut self, retry: bool) {
self.require_retry = retry;
}
/// Decrypts `token` and returns the connection Id it contains.
/// Returns `None` if the date is invalid in any way (such as it being expired or garbled).
fn decrypt_token(
&self,
token: &[u8],
peer_address: SocketAddr,
now: Instant,
) -> Option<ConnectionId> {
let peer_addr = Self::encode_peer_address(peer_address);
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 {
return None;
}
}
_ => return None,
}
Some(ConnectionId::from(dec.decode_remainder()))
}
pub fn validate(
&self,
token: &[u8],
peer_address: SocketAddr,
now: Instant,
) -> RetryTokenResult {
if token.is_empty() {
if self.require_retry {
RetryTokenResult::Validate
} else {
RetryTokenResult::Pass
}
} else if let Some(cid) = self.decrypt_token(token, peer_address, now) {
RetryTokenResult::Valid(cid)
} else {
RetryTokenResult::Invalid
}
}
}
/// 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)]
@ -256,9 +139,8 @@ pub struct Server {
waiting: VecDeque<StateRef>,
/// Outstanding timers for connections.
timers: Timer<StateRef>,
/// Whether a Retry packet will be sent in response to new
/// Initial packets.
retry: RetryToken,
/// 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>,
}
@ -282,6 +164,7 @@ impl Server {
zero_rtt_checker: Box<dyn ZeroRttChecker>,
cid_manager: CidMgr,
) -> 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(),
@ -293,7 +176,7 @@ impl Server {
active: HashSet::default(),
waiting: VecDeque::default(),
timers: Timer::new(now, TIMER_GRANULARITY, TIMER_CAPACITY),
retry: RetryToken::new(now)?,
address_validation: Rc::new(RefCell::new(validation)),
qlog_dir: None,
})
}
@ -303,8 +186,9 @@ impl Server {
self.qlog_dir = dir;
}
pub fn set_retry_required(&mut self, require_retry: bool) {
self.retry.set_retry_required(require_retry);
/// Set the policy for address validation.
pub fn set_validation(&mut self, v: ValidateAddress) {
self.address_validation.borrow_mut().set_validation(v);
}
fn remove_timer(&mut self, c: &StateRef) {
@ -374,18 +258,24 @@ impl Server {
now: Instant,
) -> Option<Datagram> {
qdebug!([self], "Handle initial");
match self.retry.validate(&initial.token, dgram.source(), now) {
RetryTokenResult::Invalid => None,
RetryTokenResult::Pass => self.connection_attempt(initial, dgram, None, now),
RetryTokenResult::Valid(orig_dcid) => {
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)
}
RetryTokenResult::Validate => {
AddressValidationResult::Validate => {
qinfo!([self], "Send retry for {:?}", initial.dst_cid);
let res = self
.retry
.generate_token(&initial.dst_cid, dgram.source(), now);
let res = self.address_validation.borrow().generate_retry_token(
&initial.dst_cid,
dgram.source(),
now,
);
let token = if let Ok(t) = res {
t
} else {
@ -519,6 +409,7 @@ impl Server {
// 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,

View File

@ -25,6 +25,8 @@ pub struct Stats {
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,
@ -61,8 +63,8 @@ impl Debug for Stats {
writeln!(f, "stats for {}", self.info)?;
writeln!(
f,
" rx: {} drop {} dup {}",
self.packets_rx, self.dropped_rx, self.dups_rx
" rx: {} drop {} dup {} saved {}",
self.packets_rx, self.dropped_rx, self.dups_rx, self.saved_datagrams
)?;
writeln!(
f,

View File

@ -159,3 +159,47 @@ impl AddAssign<u64> for StreamIndex {
*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);
}
}

View File

@ -16,7 +16,7 @@ use neqo_crypto::{
AllowZeroRtt, AuthenticationStatus,
};
use neqo_transport::{
server::{ActiveConnectionRef, Server},
server::{ActiveConnectionRef, Server, ValidateAddress},
Connection, ConnectionError, Error, FixedConnectionIdManager, Output, QuicVersion, State,
StreamType,
};
@ -56,7 +56,7 @@ fn connected_server(server: &mut Server) -> ActiveConnectionRef {
/// Connect. This returns a reference to the server connection.
fn connect(client: &mut Connection, server: &mut Server) -> ActiveConnectionRef {
server.set_retry_required(false);
server.set_validation(ValidateAddress::Never);
assert_eq!(*client.state(), State::Init);
let dgram = client.process(None, now()).dgram(); // ClientHello
@ -228,7 +228,7 @@ fn drop_non_initial() {
#[test]
fn retry_basic() {
let mut server = default_server();
server.set_retry_required(true);
server.set_validation(ValidateAddress::Always);
let mut client = default_client();
let dgram = client.process(None, now()).dgram(); // Initial
@ -252,28 +252,50 @@ fn retry_basic() {
connected_server(&mut server);
}
// attempt a retry with 0-RTT, and have 0-RTT packets sent with the second ClientHello
#[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) -> Vec<u8> {
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.resumption_token().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 mut client = default_client();
let token = get_ticket(&mut server);
server.set_validation(ValidateAddress::Always);
let mut server_conn = connect(&mut client, &mut server);
server_conn
.borrow_mut()
.send_ticket(now(), &[])
.expect("ticket should go out");
let dgram = server.process(None, now()).dgram();
client.process_input(dgram.unwrap(), now()); // Consume ticket, ignore output.
let token = client.resumption_token().expect("should get token");
// Calling active_connections clears the set of active connections.
assert_eq!(server.active_connections().len(), 1);
server.set_retry_required(true);
let mut client = default_client();
client
.set_resumption_token(now(), &token)
.expect("should set token");
client.enable_resumption(now(), &token).unwrap();
let client_stream = client.stream_create(StreamType::UniDi).unwrap();
client.stream_send(client_stream, &[1, 2, 3]).unwrap();
@ -303,10 +325,41 @@ fn retry_0rtt() {
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_retry_required(true);
server.set_validation(ValidateAddress::Always);
let mut client = default_client();
let dgram = client.process(None, now()).dgram(); // Initial
@ -328,11 +381,80 @@ fn retry_different_ip() {
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_retry_required(true);
retry_server.set_validation(ValidateAddress::Always);
let mut client = default_client();
let cinit = client.process(None, now()).dgram(); // Initial
@ -373,7 +495,7 @@ fn retry_after_initial() {
#[test]
fn retry_bad_integrity() {
let mut server = default_server();
server.set_retry_required(true);
server.set_validation(ValidateAddress::Always);
let mut client = default_client();
let dgram = client.process(None, now()).dgram(); // Initial
@ -397,7 +519,7 @@ fn retry_bad_integrity() {
fn retry_bad_token() {
let mut client = default_client();
let mut retry_server = default_server();
retry_server.set_retry_required(true);
retry_server.set_validation(ValidateAddress::Always);
let mut server = default_server();
// Send a retry to one server, then replay it to the other.
@ -421,7 +543,7 @@ fn retry_bad_token() {
fn retry_after_pto() {
let mut client = default_client();
let mut server = default_server();
server.set_retry_required(true);
server.set_validation(ValidateAddress::Always);
let mut now = now();
let ci = client.process(None, now).dgram();
@ -557,7 +679,7 @@ fn apply_header_protection(hp: &HpKey, packet: &mut [u8], pn_bytes: Range<usize>
fn mitm_retry() {
let mut client = default_client();
let mut retry_server = default_server();
retry_server.set_retry_required(true);
retry_server.set_validation(ValidateAddress::Always);
let mut server = default_server();
// Trigger initial and a second client Initial.