зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 2 changesets (bug 1687787) for causing xpcshell failures in netwerk/test/unit/test_http3_large_post. CLOSED TREE
Backed out changeset 5ca2c2f951ce (bug 1687787) Backed out changeset 48f23619ddb8 (bug 1687787)
This commit is contained in:
Родитель
87d8f32132
Коммит
24d0effbb0
|
@ -25,7 +25,7 @@ rev = "4ea371049a9cca212cc13c19b7952c1c014085c6"
|
|||
[source."https://github.com/mozilla/neqo"]
|
||||
git = "https://github.com/mozilla/neqo"
|
||||
replace-with = "vendored-sources"
|
||||
tag = "v0.4.20"
|
||||
tag = "v0.4.19"
|
||||
|
||||
[source."https://github.com/mozilla/mp4parse-rust"]
|
||||
git = "https://github.com/mozilla/mp4parse-rust"
|
||||
|
|
|
@ -3392,8 +3392,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-common"
|
||||
version = "0.4.20"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.20#be938c6156e727428668379e7c038206183a6696"
|
||||
version = "0.4.19"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.19#7bed96666f977cfaef4288ebb792273e0ca0f41c"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"env_logger 0.8.2",
|
||||
|
@ -3404,8 +3404,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-crypto"
|
||||
version = "0.4.20"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.20#be938c6156e727428668379e7c038206183a6696"
|
||||
version = "0.4.19"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.19#7bed96666f977cfaef4288ebb792273e0ca0f41c"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"log",
|
||||
|
@ -3417,8 +3417,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-http3"
|
||||
version = "0.4.20"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.20#be938c6156e727428668379e7c038206183a6696"
|
||||
version = "0.4.19"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.19#7bed96666f977cfaef4288ebb792273e0ca0f41c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"neqo-common",
|
||||
|
@ -3431,8 +3431,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-qpack"
|
||||
version = "0.4.20"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.20#be938c6156e727428668379e7c038206183a6696"
|
||||
version = "0.4.19"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.19#7bed96666f977cfaef4288ebb792273e0ca0f41c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -3445,8 +3445,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-transport"
|
||||
version = "0.4.20"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.20#be938c6156e727428668379e7c038206183a6696"
|
||||
version = "0.4.19"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.19#7bed96666f977cfaef4288ebb792273e0ca0f41c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
|
|
|
@ -8,10 +8,10 @@ edition = "2018"
|
|||
name = "neqo_glue"
|
||||
|
||||
[dependencies]
|
||||
neqo-http3 = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-transport = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-http3 = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-transport = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring" }
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
||||
|
@ -20,7 +20,7 @@ log = "0.4.0"
|
|||
qlog = "0.4.0"
|
||||
|
||||
[dependencies.neqo-crypto]
|
||||
tag = "v0.4.20"
|
||||
tag = "v0.4.19"
|
||||
git = "https://github.com/mozilla/neqo"
|
||||
default-features = false
|
||||
features = ["gecko"]
|
||||
|
|
|
@ -9,7 +9,7 @@ use neqo_http3::Error as Http3Error;
|
|||
use neqo_http3::{Http3Client, Http3ClientEvent, Http3Parameters, Http3State};
|
||||
use neqo_qpack::QpackSettings;
|
||||
use neqo_transport::{
|
||||
ConnectionParameters, Error as TransportError, RandomConnectionIdGenerator, Output, QuicVersion,
|
||||
ConnectionParameters, Error as TransportError, FixedConnectionIdManager, Output, QuicVersion,
|
||||
};
|
||||
use nserror::*;
|
||||
use nsstring::*;
|
||||
|
@ -88,12 +88,10 @@ impl NeqoHttp3Conn {
|
|||
|
||||
let mut conn = match Http3Client::new(
|
||||
origin_conv,
|
||||
Rc::new(RefCell::new(RandomConnectionIdGenerator::new(3))),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(3))),
|
||||
local,
|
||||
remote,
|
||||
ConnectionParameters::default()
|
||||
.quic_version(quic_version)
|
||||
.disable_preferred_address(),
|
||||
&ConnectionParameters::default().quic_version(quic_version),
|
||||
&http3_settings,
|
||||
) {
|
||||
Ok(c) => c,
|
||||
|
@ -400,11 +398,11 @@ pub enum CloseError {
|
|||
Http3AppError(u64),
|
||||
}
|
||||
|
||||
impl From<neqo_transport::ConnectionError> for CloseError {
|
||||
fn from(error: neqo_transport::ConnectionError) -> CloseError {
|
||||
impl From<neqo_transport::CloseError> for CloseError {
|
||||
fn from(error: neqo_transport::CloseError) -> CloseError {
|
||||
match error {
|
||||
neqo_transport::ConnectionError::Transport(c) => CloseError::QuicTransportError(c.code()),
|
||||
neqo_transport::ConnectionError::Application(c) => CloseError::Http3AppError(c),
|
||||
neqo_transport::CloseError::Transport(c) => CloseError::QuicTransportError(c),
|
||||
neqo_transport::CloseError::Application(c) => CloseError::Http3AppError(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@ authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
neqo-transport = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-http3 = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.4.20", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-transport = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-http3 = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.4.19", 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.20"
|
||||
tag = "v0.4.19"
|
||||
git = "https://github.com/mozilla/neqo"
|
||||
default-features = false
|
||||
features = ["gecko"]
|
||||
|
|
|
@ -11,7 +11,7 @@ use neqo_crypto::{init_db, AllowZeroRtt, AntiReplay};
|
|||
use neqo_http3::{Error, Http3Server, Http3ServerEvent};
|
||||
use neqo_qpack::QpackSettings;
|
||||
use neqo_transport::server::Server;
|
||||
use neqo_transport::{ConnectionEvent, ConnectionParameters, RandomConnectionIdGenerator, Output};
|
||||
use neqo_transport::{ConnectionEvent, ConnectionParameters, FixedConnectionIdManager, Output};
|
||||
use std::env;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
@ -461,7 +461,7 @@ impl ServersRunner {
|
|||
fn create_server(&self, server_type: ServerType) -> Box<dyn HttpServer> {
|
||||
let anti_replay = AntiReplay::new(Instant::now(), Duration::from_secs(10), 7, 14)
|
||||
.expect("unable to setup anti-replay");
|
||||
let cid_mgr = Rc::new(RefCell::new(RandomConnectionIdGenerator::new(10)));
|
||||
let cid_mgr = Rc::new(RefCell::new(FixedConnectionIdManager::new(10)));
|
||||
|
||||
match server_type {
|
||||
ServerType::Http3 => Box::new(Http3TestServer::new(
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"0a7608c74ae9237dd857dea7b263262264ba692659eb1307a2c9330e28c0f96a","src/codec.rs":"1cba28857d97c1f9021941f1220cecaba736bd5d8907cee6e9a3055735c06568","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/event.rs":"f60fee9f4b09ef47ff5e4bfa21c07e45ffd5873c292f2605f24d834070127d62","src/incrdecoder.rs":"b97a40f89da6832ad92bd652cb6ceac82a0a5cc68a9b3d0c96f89d02e1ee9902","src/lib.rs":"5af4f0e7284b49d1b03b5eee78f2b814e5fa6eb9424507291e25b1955aebe007","src/log.rs":"b69e492af85e65866cb6588138e8a337dd897d3ce399cb4e9fb8cc04ac042b7f","src/qlog.rs":"a8aa4f1f0110076b401f6e5a7057ec154c7ad3677374a21ceca1209469b9c07d","src/timer.rs":"66886b3697e1b4232d9d9892a12d93afd3381812a8ff901bceac4bb48b264607","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}
|
||||
{"files":{"Cargo.toml":"7bcd27df6a2b73bb3b892c6961f19e29ecfd493a767a5566149799820baa7508","src/codec.rs":"1cba28857d97c1f9021941f1220cecaba736bd5d8907cee6e9a3055735c06568","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/event.rs":"f60fee9f4b09ef47ff5e4bfa21c07e45ffd5873c292f2605f24d834070127d62","src/incrdecoder.rs":"b97a40f89da6832ad92bd652cb6ceac82a0a5cc68a9b3d0c96f89d02e1ee9902","src/lib.rs":"5af4f0e7284b49d1b03b5eee78f2b814e5fa6eb9424507291e25b1955aebe007","src/log.rs":"b69e492af85e65866cb6588138e8a337dd897d3ce399cb4e9fb8cc04ac042b7f","src/qlog.rs":"a8aa4f1f0110076b401f6e5a7057ec154c7ad3677374a21ceca1209469b9c07d","src/timer.rs":"66886b3697e1b4232d9d9892a12d93afd3381812a8ff901bceac4bb48b264607","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "neqo-common"
|
||||
version = "0.4.20"
|
||||
version = "0.4.19"
|
||||
authors = ["Bobby Holley <bobbyholley@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"6ec9fee4ca5c1069cd0dee5d2c2a9b6722cb48f36610610f423a3a9ee3276611","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"a896b4accf5fbaf146a45a060142974bfa2f59d6a5ab18c5080753078ae39474","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":"5e7fa86d565707908611b37f9ec679d29afa33cdc4d0c589e398d4240e236f68","src/aead.rs":"e26ad34f7168f42aa87eb3a6455af3191a0e8555782b1d70032fe1d248922f98","src/agent.rs":"9dae2fa87a5a7b65dfc7fdd1b2ab1be0f75f1d9ee6704b44edd9bd406252b10a","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":"974844d885d23c480d5256621053d590b331067b43ee4061e519e58487f5852b","src/lib.rs":"3b22108a069c8c9f1b78e94d48c8759b17e0941b28e2def3fa343d6acace4b6d","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"0b62ee5938aefb82e8faee5aa14e990a00442cc9744e8ba22eda80b32030c42c","src/prio.rs":"2f0d86385941aaed7c710e6b82aa1f7adc6ded74b90efbbbcafba6dd9ea1ccdb","src/replay.rs":"40924865994396441a68e6009ecbdf352d6a02fdf539aa65604124e26bffb4d3","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"acb5befa74e06281c6f80d7298efc58f568bb4e6d949b4225c335e3f392be741","src/selfencrypt.rs":"429cb889a4e9e2345888cc033115c0aa306d2ff90bdfe22b3067700eb1426c37","src/ssl.rs":"3e3a4f539f3c4d18bd6e774dc34fca611db0c75bba00badcd2078c975db055bf","src/time.rs":"df2b14912f70b8262c76ec7907996da6993a31fbb1682fdf6fe51b421800dcfe","tests/aead.rs":"a1d8eb69f5672e064f84dce3d214b347a396718e3de56d57ccc108ee87f1cbc1","tests/agent.rs":"d43e5b05dcc845394d3c7312974faae0fdcbc325c07c970aeb7ef30c3ade652e","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"93c478fcd07d29691007abd6dcfcd2014c10c23b0206ba2d97d01594e4d64397","tests/hkdf.rs":"539235e9dcf2a56b72961a9a04f0080409adf6bf465bfad7c30026421b2d4326","tests/hp.rs":"e52a7d2f4387f2dfe8bfe1da5867e8e0d3eb51e171c6904e18b18c4343536af8","tests/init.rs":"20aad800ac793aaf83059cf860593750509fdedeeff0c08a648e7a5cb398dae0","tests/selfencrypt.rs":"46e9a1a09c2ae577eb106d23a5cdacf762575c0dea1948aedab06ef7389ce713"},"package":null}
|
||||
{"files":{"Cargo.toml":"e6d3afebdbe6246f297bad3091b1ed1712b3e3756f5b219671c89b74409649e3","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"a896b4accf5fbaf146a45a060142974bfa2f59d6a5ab18c5080753078ae39474","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":"5e7fa86d565707908611b37f9ec679d29afa33cdc4d0c589e398d4240e236f68","src/aead.rs":"e26ad34f7168f42aa87eb3a6455af3191a0e8555782b1d70032fe1d248922f98","src/agent.rs":"9dae2fa87a5a7b65dfc7fdd1b2ab1be0f75f1d9ee6704b44edd9bd406252b10a","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":"974844d885d23c480d5256621053d590b331067b43ee4061e519e58487f5852b","src/lib.rs":"3b22108a069c8c9f1b78e94d48c8759b17e0941b28e2def3fa343d6acace4b6d","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"0b62ee5938aefb82e8faee5aa14e990a00442cc9744e8ba22eda80b32030c42c","src/prio.rs":"2f0d86385941aaed7c710e6b82aa1f7adc6ded74b90efbbbcafba6dd9ea1ccdb","src/replay.rs":"40924865994396441a68e6009ecbdf352d6a02fdf539aa65604124e26bffb4d3","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"acb5befa74e06281c6f80d7298efc58f568bb4e6d949b4225c335e3f392be741","src/selfencrypt.rs":"429cb889a4e9e2345888cc033115c0aa306d2ff90bdfe22b3067700eb1426c37","src/ssl.rs":"3e3a4f539f3c4d18bd6e774dc34fca611db0c75bba00badcd2078c975db055bf","src/time.rs":"df2b14912f70b8262c76ec7907996da6993a31fbb1682fdf6fe51b421800dcfe","tests/aead.rs":"a1d8eb69f5672e064f84dce3d214b347a396718e3de56d57ccc108ee87f1cbc1","tests/agent.rs":"d43e5b05dcc845394d3c7312974faae0fdcbc325c07c970aeb7ef30c3ade652e","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"93c478fcd07d29691007abd6dcfcd2014c10c23b0206ba2d97d01594e4d64397","tests/hkdf.rs":"539235e9dcf2a56b72961a9a04f0080409adf6bf465bfad7c30026421b2d4326","tests/hp.rs":"e52a7d2f4387f2dfe8bfe1da5867e8e0d3eb51e171c6904e18b18c4343536af8","tests/init.rs":"20aad800ac793aaf83059cf860593750509fdedeeff0c08a648e7a5cb398dae0","tests/selfencrypt.rs":"46e9a1a09c2ae577eb106d23a5cdacf762575c0dea1948aedab06ef7389ce713"},"package":null}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "neqo-crypto"
|
||||
version = "0.4.20"
|
||||
version = "0.4.19"
|
||||
authors = ["Martin Thomson <mt@lowentropy.net>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"068d834a82d0e82df5ced9e1c0e7ef6e720d344ef10470be93e5052751b3fd9e","src/client_events.rs":"87e7c323ec2ceb759da7d6f2bf24e774f6f2e3833f63454d09cc09f47fccfa8e","src/connection.rs":"7ce294aa57c4ffbdf5f849ad28ca3e38a491abbd2ebbbada2a6f272356535ab9","src/connection_client.rs":"e35e38fb4a87d0eba1e327a3029cf75f8e53de0fa4754120210c265613b53155","src/connection_server.rs":"884016ac4a0e43e0b14146b057c4d8d906f5aac94d47e05312298f76b0818e85","src/control_stream_local.rs":"2e9483d79dc00a3e5ef51ea2b0f28fda2b67348996c47729947c70a8be3007ed","src/control_stream_remote.rs":"1dfac4956a7d6971e2cef2c83963d838e73aa3bf3286b7bde97099978c41d527","src/hframe.rs":"3620c6d114e6d5b98447a2dd2a7cb6872562ebda2cc6de828177b745c8dcbf4d","src/lib.rs":"5440e4e28b6c878a7f9701ceacc002d0c967269fca726957574e688c9907b9c2","src/push_controller.rs":"70811f87bf0562630a3a68dc0783fce3c4694ab12de9cac835a803e1115431a5","src/push_stream.rs":"5f3a5c6d72c0a8e4d1c1c5041125f7aff9a65950fa60c77f42cd4b35c768f585","src/qlog.rs":"29c0e3c4c9571eb7fe905967edeb1c4bc236b1e35a0e0f11a4a847f1d246681d","src/recv_message.rs":"975fe666de73493501987bdd285efbdef6efce8e721e3a3411af9baf9599c4aa","src/send_message.rs":"5a0214c728f181aaeaa99121fcda804e22866e5150d2f2a3aa8abeb006f1fe14","src/server.rs":"ed985fda127fd81f2d8adc9c4ba94ccf0f369d238cc532be59384dfaed709fa9","src/server_connection_events.rs":"762ddb87f700abe91ae1ae78ebbb87a88da0c0a341748b0751b01099870e9985","src/server_events.rs":"e780daa0d19d9a5594eee73f7ff27d56151a5a2ea969f2b4dcc64253e9570dab","src/settings.rs":"127a51fa7857b870718baa14340b0461d86a67e59bf1a8cb42d7bae0240c0ef1","src/stream_type_reader.rs":"aacb2e865f79b3ac55a887fd670f2286d8ffef94f7d8b3ecfa7e0cccbfa9ec04","tests/httpconn.rs":"1a97a80f7abe11c6ba0bd9b41003be6b293049164daa21e907365d93b00a782f"},"package":null}
|
||||
{"files":{"Cargo.toml":"68ef7bb6147bae527c3a803278291e93cfa5263692f1c3b7ecc90e978390aa35","src/client_events.rs":"87e7c323ec2ceb759da7d6f2bf24e774f6f2e3833f63454d09cc09f47fccfa8e","src/connection.rs":"1230a12e446ef029bd5987270cc54247124094c5420becb04ace84c7644b68cf","src/connection_client.rs":"e6bceaddc41ccf245c384dd055b27cfe5adb9a47fe155a5fb16bb43e6a35d524","src/connection_server.rs":"b3f4d42a984f7093fca737c64bb49c569f8f95d1586a6c02d4dba58f290ce92e","src/control_stream_local.rs":"2e9483d79dc00a3e5ef51ea2b0f28fda2b67348996c47729947c70a8be3007ed","src/control_stream_remote.rs":"1dfac4956a7d6971e2cef2c83963d838e73aa3bf3286b7bde97099978c41d527","src/hframe.rs":"3620c6d114e6d5b98447a2dd2a7cb6872562ebda2cc6de828177b745c8dcbf4d","src/lib.rs":"f0866bb1750c36a6413c48f1a7789599208d2a57842398b8355c22996a1455a4","src/push_controller.rs":"70811f87bf0562630a3a68dc0783fce3c4694ab12de9cac835a803e1115431a5","src/push_stream.rs":"5f3a5c6d72c0a8e4d1c1c5041125f7aff9a65950fa60c77f42cd4b35c768f585","src/qlog.rs":"29c0e3c4c9571eb7fe905967edeb1c4bc236b1e35a0e0f11a4a847f1d246681d","src/recv_message.rs":"975fe666de73493501987bdd285efbdef6efce8e721e3a3411af9baf9599c4aa","src/send_message.rs":"e83080b06748ce65ac737fab91f675c8292f84cdf43af64543ef1acf06ad502a","src/server.rs":"9be3d7df02ed14ec2e83b279f9a537ecf585cbe786dca26fd6b6ce4ca9e1ee81","src/server_connection_events.rs":"762ddb87f700abe91ae1ae78ebbb87a88da0c0a341748b0751b01099870e9985","src/server_events.rs":"e780daa0d19d9a5594eee73f7ff27d56151a5a2ea969f2b4dcc64253e9570dab","src/settings.rs":"127a51fa7857b870718baa14340b0461d86a67e59bf1a8cb42d7bae0240c0ef1","src/stream_type_reader.rs":"aacb2e865f79b3ac55a887fd670f2286d8ffef94f7d8b3ecfa7e0cccbfa9ec04","tests/httpconn.rs":"1a97a80f7abe11c6ba0bd9b41003be6b293049164daa21e907365d93b00a782f"},"package":null}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "neqo-http3"
|
||||
version = "0.4.20"
|
||||
version = "0.4.19"
|
||||
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
|
|
@ -17,7 +17,7 @@ use neqo_common::{qdebug, qerror, qinfo, qtrace, qwarn};
|
|||
use neqo_qpack::decoder::{QPackDecoder, QPACK_UNI_STREAM_TYPE_DECODER};
|
||||
use neqo_qpack::encoder::{QPackEncoder, QPACK_UNI_STREAM_TYPE_ENCODER};
|
||||
use neqo_qpack::QpackSettings;
|
||||
use neqo_transport::{AppError, Connection, ConnectionError, State, StreamType};
|
||||
use neqo_transport::{AppError, CloseError, Connection, State, StreamType};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
|
@ -47,8 +47,8 @@ pub enum Http3State {
|
|||
ZeroRtt,
|
||||
Connected,
|
||||
GoingAway(u64),
|
||||
Closing(ConnectionError),
|
||||
Closed(ConnectionError),
|
||||
Closing(CloseError),
|
||||
Closed(CloseError),
|
||||
}
|
||||
|
||||
impl Http3State {
|
||||
|
@ -401,7 +401,7 @@ impl Http3Connection {
|
|||
if matches!(self.state, Http3State::Closing(_)| Http3State::Closed(_)) {
|
||||
Ok(false)
|
||||
} else {
|
||||
self.state = Http3State::Closing(error.clone());
|
||||
self.state = Http3State::Closing(error.clone().into());
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
@ -409,7 +409,7 @@ impl Http3Connection {
|
|||
if matches!(self.state, Http3State::Closed(_)) {
|
||||
Ok(false)
|
||||
} else {
|
||||
self.state = Http3State::Closed(error.clone());
|
||||
self.state = Http3State::Closed(error.clone().into());
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
@ -434,7 +434,7 @@ impl Http3Connection {
|
|||
Ok(())
|
||||
} else {
|
||||
debug_assert!(false, "Zero rtt rejected in the wrong state.");
|
||||
Err(Error::HttpInternal(3))
|
||||
Err(Error::HttpInternal)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,7 +512,7 @@ impl Http3Connection {
|
|||
/// This is called when an application closes the connection.
|
||||
pub fn close(&mut self, error: AppError) {
|
||||
qinfo!([self], "Close connection error {:?}.", error);
|
||||
self.state = Http3State::Closing(ConnectionError::Application(error));
|
||||
self.state = Http3State::Closing(CloseError::Application(error));
|
||||
if (!self.send_streams.is_empty() || !self.recv_streams.is_empty()) && (error == 0) {
|
||||
qwarn!("close(0) called when streams still active");
|
||||
}
|
||||
|
|
|
@ -20,9 +20,8 @@ use neqo_common::{
|
|||
use neqo_crypto::{agent::CertificateInfo, AuthenticationStatus, ResumptionToken, SecretAgentInfo};
|
||||
use neqo_qpack::{QpackSettings, Stats as QpackStats};
|
||||
use neqo_transport::{
|
||||
AppError, Connection, ConnectionEvent, ConnectionId, ConnectionIdGenerator,
|
||||
ConnectionParameters, Output, QuicVersion, Stats as TransportStats, StreamId, StreamType,
|
||||
ZeroRttState,
|
||||
AppError, Connection, ConnectionEvent, ConnectionId, ConnectionIdManager, ConnectionParameters,
|
||||
Output, QuicVersion, Stats as TransportStats, StreamId, StreamType, ZeroRttState,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Display;
|
||||
|
@ -92,10 +91,10 @@ impl Http3Client {
|
|||
/// the socket can't be created or configured.
|
||||
pub fn new(
|
||||
server_name: &str,
|
||||
cid_manager: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
cid_manager: Rc<RefCell<dyn ConnectionIdManager>>,
|
||||
local_addr: SocketAddr,
|
||||
remote_addr: SocketAddr,
|
||||
conn_params: ConnectionParameters,
|
||||
conn_params: &ConnectionParameters,
|
||||
http3_parameters: &Http3Parameters,
|
||||
) -> Res<Self> {
|
||||
Ok(Self::new_with_conn(
|
||||
|
@ -743,20 +742,20 @@ mod tests {
|
|||
use neqo_qpack::encoder::QPackEncoder;
|
||||
use neqo_transport::tparams::{self, TransportParameter};
|
||||
use neqo_transport::{
|
||||
ConnectionError, ConnectionEvent, ConnectionParameters, Output, State, RECV_BUFFER_SIZE,
|
||||
SEND_BUFFER_SIZE,
|
||||
CloseError, ConnectionEvent, ConnectionParameters, FixedConnectionIdManager, Output, State,
|
||||
RECV_BUFFER_SIZE, SEND_BUFFER_SIZE,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Duration;
|
||||
use test_fixture::{
|
||||
addr, anti_replay, default_server_h3, fixture_init, now, CountingConnectionIdGenerator,
|
||||
DEFAULT_ALPN_H3, DEFAULT_KEYS, DEFAULT_SERVER_NAME,
|
||||
anti_replay, default_server_h3, fixture_init, loopback, now, DEFAULT_ALPN_H3, DEFAULT_KEYS,
|
||||
DEFAULT_SERVER_NAME,
|
||||
};
|
||||
|
||||
fn assert_closed(client: &Http3Client, expected: &Error) {
|
||||
match client.state() {
|
||||
Http3State::Closing(err) | Http3State::Closed(err) => {
|
||||
assert_eq!(err, ConnectionError::Application(expected.code()))
|
||||
assert_eq!(err, CloseError::Application(expected.code()))
|
||||
}
|
||||
_ => panic!("Wrong state {:?}", client.state()),
|
||||
};
|
||||
|
@ -771,10 +770,10 @@ mod tests {
|
|||
fixture_init();
|
||||
Http3Client::new(
|
||||
DEFAULT_SERVER_NAME,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
ConnectionParameters::default(),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(3))),
|
||||
loopback(),
|
||||
loopback(),
|
||||
&ConnectionParameters::default(),
|
||||
&Http3Parameters {
|
||||
qpack_settings: QpackSettings {
|
||||
max_table_size_encoder: max_table_size,
|
||||
|
@ -3554,8 +3553,8 @@ mod tests {
|
|||
let mut server = Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN_H3,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default(),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(10))),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// Using a freshly initialized anti-replay context
|
||||
|
@ -3705,7 +3704,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::BlockedStreams, 100),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 10000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(265)),
|
||||
&Http3State::Closing(CloseError::Application(265)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -3723,7 +3722,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::MaxTableCapacity, 100),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 10000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(265)),
|
||||
&Http3State::Closing(CloseError::Application(265)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -3760,7 +3759,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::BlockedStreams, 100),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 10000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(514)),
|
||||
&Http3State::Closing(CloseError::Application(514)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -3779,7 +3778,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::BlockedStreams, 100),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 10000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(265)),
|
||||
&Http3State::Closing(CloseError::Application(265)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -3817,7 +3816,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::BlockedStreams, 50),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 10000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(265)),
|
||||
&Http3State::Closing(CloseError::Application(265)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -3855,7 +3854,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::BlockedStreams, 100),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 5000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(265)),
|
||||
&Http3State::Closing(CloseError::Application(265)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -3912,7 +3911,7 @@ mod tests {
|
|||
HSetting::new(HSettingType::BlockedStreams, 100),
|
||||
HSetting::new(HSettingType::MaxHeaderListSize, 10000),
|
||||
],
|
||||
&Http3State::Closing(ConnectionError::Application(265)),
|
||||
&Http3State::Closing(CloseError::Application(265)),
|
||||
ENCODER_STREAM_DATA_WITH_CAP_INSTRUCTION,
|
||||
);
|
||||
}
|
||||
|
@ -6116,7 +6115,7 @@ mod tests {
|
|||
DEFAULT_KEYS,
|
||||
DEFAULT_ALPN_H3,
|
||||
anti_replay(),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(5))),
|
||||
QpackSettings {
|
||||
max_table_size_encoder: MAX_TABLE_SIZE,
|
||||
max_table_size_decoder: MAX_TABLE_SIZE,
|
||||
|
|
|
@ -169,7 +169,7 @@ impl Http3ServerHandler {
|
|||
}
|
||||
ConnectionEvent::AuthenticationNeeded
|
||||
| ConnectionEvent::ZeroRttRejected
|
||||
| ConnectionEvent::ResumptionToken(..) => return Err(Error::HttpInternal(4)),
|
||||
| ConnectionEvent::ResumptionToken(..) => return Err(Error::HttpInternal),
|
||||
ConnectionEvent::SendStreamWritable { .. }
|
||||
| ConnectionEvent::SendStreamComplete { .. }
|
||||
| ConnectionEvent::SendStreamCreatable { .. } => {}
|
||||
|
|
|
@ -48,9 +48,7 @@ pub enum Error {
|
|||
HttpNoError,
|
||||
HttpGeneralProtocol,
|
||||
HttpGeneralProtocolStream, //this is the same as the above but it should only close a stream not a connection.
|
||||
// When using this error, you need to provide a value that is unique, which
|
||||
// will allow the specific error to be identified. This will be validated in CI.
|
||||
HttpInternal(u16),
|
||||
HttpInternal,
|
||||
HttpStreamCreation,
|
||||
HttpClosedCriticalStream,
|
||||
HttpFrameUnexpected,
|
||||
|
@ -95,7 +93,7 @@ impl Error {
|
|||
Self::HttpGeneralProtocol | Self::HttpGeneralProtocolStream | Self::InvalidHeader => {
|
||||
0x101
|
||||
}
|
||||
Self::HttpInternal(..) => 0x102,
|
||||
Self::HttpInternal => 0x102,
|
||||
Self::HttpStreamCreation => 0x103,
|
||||
Self::HttpClosedCriticalStream => 0x104,
|
||||
Self::HttpFrameUnexpected => 0x105,
|
||||
|
@ -120,7 +118,7 @@ impl Error {
|
|||
matches!(
|
||||
self,
|
||||
Self::HttpGeneralProtocol
|
||||
| Self::HttpInternal(..)
|
||||
| Self::HttpInternal
|
||||
| Self::HttpStreamCreation
|
||||
| Self::HttpClosedCriticalStream
|
||||
| Self::HttpFrameUnexpected
|
||||
|
@ -194,8 +192,8 @@ impl Error {
|
|||
/// Any error is mapped to the indicated type.
|
||||
fn map_error<R>(r: Result<R, impl Into<Self>>, err: Self) -> Result<R, Self> {
|
||||
Ok(r.map_err(|e| {
|
||||
debug_assert!(!matches!(e.into(), Self::HttpInternal(..)));
|
||||
debug_assert!(!matches!(err, Self::HttpInternal(..)));
|
||||
debug_assert!(!matches!(e.into(), Self::HttpInternal));
|
||||
debug_assert!(!matches!(err, Self::HttpInternal));
|
||||
err
|
||||
})?)
|
||||
}
|
||||
|
@ -211,6 +209,7 @@ impl From<QpackError> for Error {
|
|||
fn from(err: QpackError) -> Self {
|
||||
match err {
|
||||
QpackError::ClosedCriticalStream => Error::HttpClosedCriticalStream,
|
||||
QpackError::InternalError => Error::HttpInternal,
|
||||
e => Self::QpackError(e),
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +236,7 @@ impl From<AppError> for Error {
|
|||
0x200 => Self::QpackError(QpackError::DecompressionFailed),
|
||||
0x201 => Self::QpackError(QpackError::EncoderStream),
|
||||
0x202 => Self::QpackError(QpackError::DecoderStream),
|
||||
_ => Self::HttpInternal(0),
|
||||
_ => Self::HttpInternal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,20 +238,15 @@ impl SendMessage {
|
|||
};
|
||||
|
||||
if let SendMessageState::SendingInitialMessage { ref mut buf, fin } = self.state {
|
||||
let sent = Error::map_error(
|
||||
conn.stream_send(self.stream_id, &buf),
|
||||
Error::HttpInternal(5),
|
||||
)?;
|
||||
let sent =
|
||||
Error::map_error(conn.stream_send(self.stream_id, &buf), Error::HttpInternal)?;
|
||||
qlog::h3_data_moved_down(&mut conn.qlog_mut(), self.stream_id, sent);
|
||||
|
||||
qtrace!([label], "{} bytes sent", sent);
|
||||
|
||||
if sent == buf.len() {
|
||||
if fin {
|
||||
Error::map_error(
|
||||
conn.stream_close_send(self.stream_id),
|
||||
Error::HttpInternal(6),
|
||||
)?;
|
||||
Error::map_error(conn.stream_close_send(self.stream_id), Error::HttpInternal)?;
|
||||
self.state = SendMessageState::Closed;
|
||||
qtrace!([label], "done sending request");
|
||||
} else {
|
||||
|
|
|
@ -16,9 +16,7 @@ use neqo_common::{qtrace, Datagram};
|
|||
use neqo_crypto::{AntiReplay, Cipher};
|
||||
use neqo_qpack::QpackSettings;
|
||||
use neqo_transport::server::{ActiveConnectionRef, Server, ValidateAddress};
|
||||
use neqo_transport::{
|
||||
tparams::PreferredAddress, ConnectionIdGenerator, ConnectionParameters, Output,
|
||||
};
|
||||
use neqo_transport::{ConnectionIdManager, ConnectionParameters, Output};
|
||||
use std::cell::RefCell;
|
||||
use std::cell::RefMut;
|
||||
use std::collections::HashMap;
|
||||
|
@ -52,7 +50,7 @@ impl Http3Server {
|
|||
certs: &[impl AsRef<str>],
|
||||
protocols: &[impl AsRef<str>],
|
||||
anti_replay: AntiReplay,
|
||||
cid_manager: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
cid_manager: Rc<RefCell<dyn ConnectionIdManager>>,
|
||||
qpack_settings: QpackSettings,
|
||||
) -> Res<Self> {
|
||||
Ok(Self {
|
||||
|
@ -83,10 +81,6 @@ impl Http3Server {
|
|||
self.server.set_ciphers(ciphers);
|
||||
}
|
||||
|
||||
pub fn set_preferred_address(&mut self, spa: PreferredAddress) {
|
||||
self.server.set_preferred_address(spa);
|
||||
}
|
||||
|
||||
pub fn process(&mut self, dgram: Option<Datagram>, now: Instant) -> Output {
|
||||
qtrace!([self], "Process.");
|
||||
let out = self.server.process(dgram, now);
|
||||
|
@ -238,12 +232,12 @@ mod tests {
|
|||
use neqo_qpack::encoder::QPackEncoder;
|
||||
use neqo_qpack::QpackSettings;
|
||||
use neqo_transport::{
|
||||
Connection, ConnectionError, ConnectionEvent, State, StreamType, ZeroRttState,
|
||||
CloseError, Connection, ConnectionEvent, FixedConnectionIdManager, State, StreamType,
|
||||
ZeroRttState,
|
||||
};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use test_fixture::{
|
||||
anti_replay, default_client, fixture_init, now, CountingConnectionIdGenerator,
|
||||
DEFAULT_ALPN, DEFAULT_KEYS,
|
||||
anti_replay, default_client, fixture_init, now, DEFAULT_ALPN, DEFAULT_KEYS,
|
||||
};
|
||||
|
||||
const DEFAULT_SETTINGS: QpackSettings = QpackSettings {
|
||||
|
@ -259,7 +253,7 @@ mod tests {
|
|||
DEFAULT_KEYS,
|
||||
DEFAULT_ALPN,
|
||||
anti_replay(),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(5))),
|
||||
settings,
|
||||
)
|
||||
.expect("create a server")
|
||||
|
@ -271,7 +265,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn assert_closed(hconn: &mut Http3Server, expected: &Error) {
|
||||
let err = ConnectionError::Application(expected.code());
|
||||
let err = CloseError::Application(expected.code());
|
||||
let closed = |e| {
|
||||
matches!(e,
|
||||
Http3ServerEvent::StateChange{ state: Http3State::Closing(e), .. }
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"1030e6dd7049afec3692c72fa8d711223d4eb4a1da904413b1d6de856db17e15","src/decoder.rs":"1dd87823a8aeb8673d2521d2f2735cea5aaf45713a4fdc3a01b3803c7a7c30f7","src/decoder_instructions.rs":"a8e04dff5fc4c658322a10daadab947dc2e41932c00c3f8d387671a86d0516af","src/encoder.rs":"30442c457d7fb8a63bc54bd68928383adb9fdeda14d6b082c2035736389d96c5","src/encoder_instructions.rs":"1d4424bf21c0ac26b7c8fee6450b943346c5493ab86dd7ec2edc5f566454721e","src/header_block.rs":"8477281843fb9c927cbf9cda488d4586605a64157e582a71e405f2bf1852f43b","src/huffman.rs":"68fa0bada0c35d20f793980596accdcc548970214841f71789290fc334e51fc1","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"bc9d936fe126d53ff752ae7fe17e8be59bb992f48b17e82d5811f274b492af3c","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":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}
|
||||
{"files":{"Cargo.toml":"a40b9b74114030968cfc0c80af11373136237f91d7680059856a430628e606d7","src/decoder.rs":"1dd87823a8aeb8673d2521d2f2735cea5aaf45713a4fdc3a01b3803c7a7c30f7","src/decoder_instructions.rs":"a8e04dff5fc4c658322a10daadab947dc2e41932c00c3f8d387671a86d0516af","src/encoder.rs":"ced950bb015d13ab90906824fba28ed7e0443d215ff505a758f3d51249c6f6a2","src/encoder_instructions.rs":"1d4424bf21c0ac26b7c8fee6450b943346c5493ab86dd7ec2edc5f566454721e","src/header_block.rs":"8477281843fb9c927cbf9cda488d4586605a64157e582a71e405f2bf1852f43b","src/huffman.rs":"68fa0bada0c35d20f793980596accdcc548970214841f71789290fc334e51fc1","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"1501db00b7575eb015138ba875d6f427919138b06b0825a23794242b9d9b5f47","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":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "neqo-qpack"
|
||||
version = "0.4.20"
|
||||
version = "0.4.19"
|
||||
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
|
|
@ -305,7 +305,7 @@ impl QPackEncoder {
|
|||
false,
|
||||
"can_evict_to should have checked and make sure this operation is possible"
|
||||
);
|
||||
return Err(Error::InternalError(1));
|
||||
return Err(Error::InternalError);
|
||||
}
|
||||
self.max_entries = cap / 32;
|
||||
self.next_capacity = None;
|
||||
|
@ -520,7 +520,7 @@ fn map_stream_send_atomic_error(err: &TransportError) -> Error {
|
|||
}
|
||||
_ => {
|
||||
debug_assert!(false, "Unexpected error");
|
||||
Error::InternalError(2)
|
||||
Error::InternalError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ pub enum Error {
|
|||
EncoderStream,
|
||||
DecoderStream,
|
||||
ClosedCriticalStream,
|
||||
InternalError(u16),
|
||||
InternalError,
|
||||
|
||||
// These are internal errors, they will be transformed into one of the above.
|
||||
NeedMoreData, // Return when an input stream does not have more data that a decoder needs.(It does not mean that a stream is closed.)
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "neqo-transport"
|
||||
version = "0.4.20"
|
||||
version = "0.4.19"
|
||||
authors = ["EKR <ekr@rtfm.com>", "Andy Grover <agrover@mozilla.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::cid::ConnectionId;
|
|||
use crate::packet::PacketBuilder;
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::stats::FrameStats;
|
||||
use crate::{Error, Res};
|
||||
use crate::Res;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::convert::TryFrom;
|
||||
|
@ -355,11 +355,10 @@ impl NewTokenState {
|
|||
builder: &mut PacketBuilder,
|
||||
tokens: &mut Vec<RecoveryToken>,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<()> {
|
||||
) {
|
||||
if let Self::Server(ref mut sender) = self {
|
||||
sender.write_frames(builder, tokens, stats)?;
|
||||
sender.write_frames(builder, tokens, stats);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If this a server, buffer a NEW_TOKEN for sending.
|
||||
|
@ -430,22 +429,18 @@ impl NewTokenSender {
|
|||
builder: &mut PacketBuilder,
|
||||
tokens: &mut Vec<RecoveryToken>,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<()> {
|
||||
) {
|
||||
for t in self.tokens.iter_mut() {
|
||||
if t.needs_sending && t.len() <= builder.remaining() {
|
||||
t.needs_sending = false;
|
||||
|
||||
builder.encode_varint(crate::frame::FRAME_TYPE_NEW_TOKEN);
|
||||
builder.encode_vvec(&t.token);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(7));
|
||||
}
|
||||
|
||||
tokens.push(RecoveryToken::NewToken(t.seqno));
|
||||
stats.new_token += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lost(&mut self, seqno: usize) {
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::tracking::SentPacket;
|
|||
use neqo_common::{const_max, const_min, qdebug, qinfo, qlog::NeqoQlog, qtrace};
|
||||
|
||||
pub const CWND_INITIAL_PKTS: usize = 10;
|
||||
pub const CWND_INITIAL: usize = const_min(
|
||||
const CWND_INITIAL: usize = const_min(
|
||||
CWND_INITIAL_PKTS * MAX_DATAGRAM_SIZE,
|
||||
const_max(2 * MAX_DATAGRAM_SIZE, 14720),
|
||||
);
|
||||
|
@ -76,27 +76,8 @@ impl State {
|
|||
}
|
||||
|
||||
pub trait WindowAdjustment: Display + Debug {
|
||||
/// This is called when an ack is received.
|
||||
/// The function calculates the amount of acked bytes congestion controller needs
|
||||
/// to collect before increasing its cwnd by `MAX_DATAGRAM_SIZE`.
|
||||
fn bytes_for_cwnd_increase(
|
||||
&mut self,
|
||||
curr_cwnd: usize,
|
||||
new_acked_bytes: usize,
|
||||
min_rtt: Duration,
|
||||
now: Instant,
|
||||
) -> usize;
|
||||
/// This function is called when a congestion event has beed detected and it
|
||||
/// returns new (decreased) values of `curr_cwnd` and `acked_bytes`.
|
||||
/// This value can be very small; the calling code is responsible for ensuring that the
|
||||
/// congestion window doesn't drop below the minimum of `CWND_MIN`.
|
||||
fn reduce_cwnd(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize);
|
||||
/// Cubic needs this signal to reset its epoch.
|
||||
fn on_app_limited(&mut self);
|
||||
#[cfg(test)]
|
||||
fn last_max_cwnd(&self) -> f64;
|
||||
#[cfg(test)]
|
||||
fn set_last_max_cwnd(&mut self, last_max_cwnd: f64);
|
||||
fn on_packets_acked(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize);
|
||||
fn on_congestion_event(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -146,7 +127,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
|
|||
}
|
||||
|
||||
// Multi-packet version of OnPacketAckedCC
|
||||
fn on_packets_acked(&mut self, acked_pkts: &[SentPacket], min_rtt: Duration, now: Instant) {
|
||||
fn on_packets_acked(&mut self, acked_pkts: &[SentPacket]) {
|
||||
// Check whether we are app limited before acked packets are removed
|
||||
// from bytes_in_flight.
|
||||
let is_app_limited = self.app_limited();
|
||||
|
@ -160,7 +141,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
|
|||
MAX_DATAGRAM_SIZE * PACING_BURST_SIZE,
|
||||
);
|
||||
|
||||
let mut new_acked = 0;
|
||||
let mut acked_bytes = 0;
|
||||
for pkt in acked_pkts.iter().filter(|pkt| pkt.cc_outstanding()) {
|
||||
assert!(self.bytes_in_flight >= pkt.size);
|
||||
self.bytes_in_flight -= pkt.size;
|
||||
|
@ -176,19 +157,17 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
|
|||
qlog::metrics_updated(&mut self.qlog, &[QlogMetric::InRecovery(false)]);
|
||||
}
|
||||
|
||||
new_acked += pkt.size;
|
||||
acked_bytes += pkt.size;
|
||||
}
|
||||
|
||||
if is_app_limited {
|
||||
self.cc_algorithm.on_app_limited();
|
||||
return;
|
||||
if !is_app_limited {
|
||||
self.acked_bytes += acked_bytes;
|
||||
}
|
||||
|
||||
qtrace!([self], "ACK received, acked_bytes = {}", self.acked_bytes);
|
||||
|
||||
// Slow start, up to the slow start threshold.
|
||||
if self.congestion_window < self.ssthresh {
|
||||
self.acked_bytes += new_acked;
|
||||
let increase = min(self.ssthresh - self.congestion_window, self.acked_bytes);
|
||||
self.congestion_window += increase;
|
||||
self.acked_bytes -= increase;
|
||||
|
@ -201,29 +180,11 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
|
|||
}
|
||||
// Congestion avoidance, above the slow start threshold.
|
||||
if self.congestion_window >= self.ssthresh {
|
||||
// The following function return the amount acked bytes a controller needs
|
||||
// to collect to be allowed to increase its cwnd by MAX_DATAGRAM_SIZE.
|
||||
let bytes_for_increase = self.cc_algorithm.bytes_for_cwnd_increase(
|
||||
self.congestion_window,
|
||||
new_acked,
|
||||
min_rtt,
|
||||
now,
|
||||
);
|
||||
// If enough credit has been accumulated already, apply them gradually.
|
||||
// If we have sudden increase in allowed rate we actually increase cwnd gently.
|
||||
if self.acked_bytes >= bytes_for_increase {
|
||||
self.acked_bytes = 0;
|
||||
self.congestion_window += MAX_DATAGRAM_SIZE;
|
||||
}
|
||||
self.acked_bytes += new_acked;
|
||||
if self.acked_bytes >= bytes_for_increase {
|
||||
self.acked_bytes -= bytes_for_increase;
|
||||
self.congestion_window += MAX_DATAGRAM_SIZE; // or is this the current MTU?
|
||||
}
|
||||
// The number of bytes we require can go down over time with Cubic.
|
||||
// That might result in an excessive rate of increase, so limit the number of unused
|
||||
// acknowledged bytes after increasing the congestion window twice.
|
||||
self.acked_bytes = min(bytes_for_increase, self.acked_bytes);
|
||||
let (cwnd, acked_bytes) = self
|
||||
.cc_algorithm
|
||||
.on_packets_acked(self.congestion_window, self.acked_bytes);
|
||||
self.congestion_window = cwnd;
|
||||
self.acked_bytes = acked_bytes;
|
||||
}
|
||||
qlog::metrics_updated(
|
||||
&mut self.qlog,
|
||||
|
@ -330,26 +291,6 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
|
|||
self.ssthresh
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_ssthresh(&mut self, v: usize) {
|
||||
self.ssthresh = v;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn last_max_cwnd(&self) -> f64 {
|
||||
self.cc_algorithm.last_max_cwnd()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_last_max_cwnd(&mut self, last_max_cwnd: f64) {
|
||||
self.cc_algorithm.set_last_max_cwnd(last_max_cwnd);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn acked_bytes(&self) -> usize {
|
||||
self.acked_bytes
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: State) {
|
||||
if self.state != state {
|
||||
qdebug!([self], "state -> {:?}", state);
|
||||
|
@ -436,7 +377,7 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
|
|||
if self.after_recovery_start(last_packet) {
|
||||
let (cwnd, acked_bytes) = self
|
||||
.cc_algorithm
|
||||
.reduce_cwnd(self.congestion_window, self.acked_bytes);
|
||||
.on_congestion_event(self.congestion_window, self.acked_bytes);
|
||||
self.congestion_window = max(cwnd, CWND_MIN);
|
||||
self.acked_bytes = acked_bytes;
|
||||
self.ssthresh = self.congestion_window;
|
||||
|
@ -477,15 +418,11 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
ClassicCongestionControl, WindowAdjustment, CWND_INITIAL, CWND_MIN, PERSISTENT_CONG_THRESH,
|
||||
};
|
||||
use crate::cc::cubic::{Cubic, CUBIC_BETA_USIZE_DIVISOR, CUBIC_BETA_USIZE_QUOTIENT};
|
||||
use super::{ClassicCongestionControl, CWND_INITIAL, CWND_MIN, PERSISTENT_CONG_THRESH};
|
||||
use crate::cc::new_reno::NewReno;
|
||||
use crate::cc::{CongestionControl, CWND_INITIAL_PKTS, MAX_DATAGRAM_SIZE};
|
||||
use crate::packet::{PacketNumber, PacketType};
|
||||
use crate::tracking::SentPacket;
|
||||
use crate::CongestionControlAlgorithm;
|
||||
use std::convert::TryFrom;
|
||||
use std::time::{Duration, Instant};
|
||||
use test_fixture::now;
|
||||
|
@ -511,6 +448,109 @@ mod tests {
|
|||
assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_876() {
|
||||
let mut cc = ClassicCongestionControl::new(NewReno::default());
|
||||
let time_now = now();
|
||||
let time_before = time_now - Duration::from_millis(100);
|
||||
let time_after = time_now + Duration::from_millis(150);
|
||||
|
||||
let sent_packets = &[
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
1, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE - 1, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
2, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE - 2, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
3, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
4, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
5, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
6, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
7, // pn
|
||||
time_after, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE - 3, // size
|
||||
),
|
||||
];
|
||||
|
||||
// Send some more packets so that the cc is not app-limited.
|
||||
for p in &sent_packets[..6] {
|
||||
cc.on_packet_sent(p);
|
||||
}
|
||||
assert_eq!(cc.acked_bytes, 0);
|
||||
cwnd_is_default(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 6 * MAX_DATAGRAM_SIZE - 3);
|
||||
|
||||
cc.on_packets_lost(Some(time_now), None, PTO, &sent_packets[0..1]);
|
||||
|
||||
// We are now in recovery
|
||||
assert!(cc.recovery_packet());
|
||||
assert_eq!(cc.acked_bytes, 0);
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 5 * MAX_DATAGRAM_SIZE - 2);
|
||||
|
||||
// Send a packet after recovery starts
|
||||
cc.on_packet_sent(&sent_packets[6]);
|
||||
assert!(!cc.recovery_packet());
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.acked_bytes, 0);
|
||||
assert_eq!(cc.bytes_in_flight(), 6 * MAX_DATAGRAM_SIZE - 5);
|
||||
|
||||
// and ack it. cwnd increases slightly
|
||||
cc.on_packets_acked(&sent_packets[6..]);
|
||||
assert_eq!(cc.acked_bytes, sent_packets[6].size);
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 5 * MAX_DATAGRAM_SIZE - 2);
|
||||
|
||||
// Packet from before is lost. Should not hurt cwnd.
|
||||
cc.on_packets_lost(Some(time_now), None, PTO, &sent_packets[1..2]);
|
||||
assert!(!cc.recovery_packet());
|
||||
assert_eq!(cc.acked_bytes, sent_packets[6].size);
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 4 * MAX_DATAGRAM_SIZE);
|
||||
}
|
||||
|
||||
fn lost(pn: PacketNumber, ack_eliciting: bool, t: Duration) -> SentPacket {
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
|
@ -522,195 +562,153 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
fn congestion_control(cc: CongestionControlAlgorithm) -> Box<dyn CongestionControl> {
|
||||
match cc {
|
||||
CongestionControlAlgorithm::NewReno => {
|
||||
Box::new(ClassicCongestionControl::new(NewReno::default()))
|
||||
}
|
||||
CongestionControlAlgorithm::Cubic => {
|
||||
Box::new(ClassicCongestionControl::new(Cubic::default()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn persistent_congestion_by_algorithm(
|
||||
cc_alg: CongestionControlAlgorithm,
|
||||
reduced_cwnd: usize,
|
||||
lost_packets: &[SentPacket],
|
||||
persistent_expected: bool,
|
||||
) {
|
||||
let mut cc = congestion_control(cc_alg);
|
||||
fn persistent_congestion(lost_packets: &[SentPacket]) -> bool {
|
||||
let mut cc = ClassicCongestionControl::new(NewReno::default());
|
||||
for p in lost_packets {
|
||||
cc.on_packet_sent(p);
|
||||
}
|
||||
|
||||
cc.on_packets_lost(Some(now()), None, PTO, lost_packets);
|
||||
|
||||
let persistent = if cc.cwnd() == reduced_cwnd {
|
||||
if cc.cwnd() == CWND_INITIAL / 2 {
|
||||
false
|
||||
} else if cc.cwnd() == CWND_MIN {
|
||||
true
|
||||
} else {
|
||||
panic!("unexpected cwnd");
|
||||
};
|
||||
assert_eq!(persistent, persistent_expected);
|
||||
}
|
||||
|
||||
fn persistent_congestion(lost_packets: &[SentPacket], persistent_expected: bool) {
|
||||
persistent_congestion_by_algorithm(
|
||||
CongestionControlAlgorithm::NewReno,
|
||||
CWND_INITIAL / 2,
|
||||
lost_packets,
|
||||
persistent_expected,
|
||||
);
|
||||
persistent_congestion_by_algorithm(
|
||||
CongestionControlAlgorithm::Cubic,
|
||||
CWND_INITIAL * CUBIC_BETA_USIZE_QUOTIENT / CUBIC_BETA_USIZE_DIVISOR,
|
||||
lost_packets,
|
||||
persistent_expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A span of exactly the PC threshold only reduces the window on loss.
|
||||
#[test]
|
||||
fn persistent_congestion_none() {
|
||||
persistent_congestion(&[lost(1, true, ZERO), lost(2, true, SUB_PC)], false);
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, SUB_PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// A span of just more than the PC threshold causes persistent congestion.
|
||||
#[test]
|
||||
fn persistent_congestion_simple() {
|
||||
persistent_congestion(&[lost(1, true, ZERO), lost(2, true, PC)], true);
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// Both packets need to be ack-eliciting.
|
||||
#[test]
|
||||
fn persistent_congestion_non_ack_eliciting() {
|
||||
persistent_congestion(&[lost(1, false, ZERO), lost(2, true, PC)], false);
|
||||
persistent_congestion(&[lost(1, true, ZERO), lost(2, false, PC)], false);
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, false, ZERO),
|
||||
lost(2, true, PC),
|
||||
]));
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, false, PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// Packets in the middle, of any type, are OK.
|
||||
#[test]
|
||||
fn persistent_congestion_middle() {
|
||||
persistent_congestion(
|
||||
&[lost(1, true, ZERO), lost(2, false, RTT), lost(3, true, PC)],
|
||||
true,
|
||||
);
|
||||
persistent_congestion(
|
||||
&[lost(1, true, ZERO), lost(2, true, RTT), lost(3, true, PC)],
|
||||
true,
|
||||
);
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, false, RTT),
|
||||
lost(3, true, PC),
|
||||
]));
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, RTT),
|
||||
lost(3, true, PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// Leading non-ack-eliciting packets are skipped.
|
||||
#[test]
|
||||
fn persistent_congestion_leading_non_ack_eliciting() {
|
||||
persistent_congestion(
|
||||
&[lost(1, false, ZERO), lost(2, true, RTT), lost(3, true, PC)],
|
||||
false,
|
||||
);
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, false, ZERO),
|
||||
lost(2, true, RTT),
|
||||
lost(3, true, RTT + PC),
|
||||
],
|
||||
true,
|
||||
);
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, false, ZERO),
|
||||
lost(2, true, RTT),
|
||||
lost(3, true, PC),
|
||||
]));
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, false, ZERO),
|
||||
lost(2, true, RTT),
|
||||
lost(3, true, RTT + PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// Trailing non-ack-eliciting packets aren't relevant.
|
||||
#[test]
|
||||
fn persistent_congestion_trailing_non_ack_eliciting() {
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PC),
|
||||
lost(3, false, PC + EPSILON),
|
||||
],
|
||||
true,
|
||||
);
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, SUB_PC),
|
||||
lost(3, false, PC),
|
||||
],
|
||||
false,
|
||||
);
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PC),
|
||||
lost(3, false, PC + EPSILON),
|
||||
]));
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, SUB_PC),
|
||||
lost(3, false, PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// Gaps in the middle, of any type, restart the count.
|
||||
#[test]
|
||||
fn persistent_congestion_gap_reset() {
|
||||
persistent_congestion(&[lost(1, true, ZERO), lost(3, true, PC)], false);
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, RTT),
|
||||
lost(4, true, GAP),
|
||||
lost(5, true, GAP + PTO * PERSISTENT_CONG_THRESH),
|
||||
],
|
||||
false,
|
||||
);
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(3, true, PC),
|
||||
]));
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, RTT),
|
||||
lost(4, true, GAP),
|
||||
lost(5, true, GAP + PTO * PERSISTENT_CONG_THRESH),
|
||||
]));
|
||||
}
|
||||
|
||||
/// A span either side of a gap will cause persistent congestion.
|
||||
#[test]
|
||||
fn persistent_congestion_gap_or() {
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PC),
|
||||
lost(4, true, GAP),
|
||||
lost(5, true, GAP + PTO),
|
||||
],
|
||||
true,
|
||||
);
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, true, GAP),
|
||||
lost(5, true, GAP + PC),
|
||||
],
|
||||
true,
|
||||
);
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PC),
|
||||
lost(4, true, GAP),
|
||||
lost(5, true, GAP + PTO),
|
||||
]));
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, true, GAP),
|
||||
lost(5, true, GAP + PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// A gap only restarts after an ack-eliciting packet.
|
||||
#[test]
|
||||
fn persistent_congestion_gap_non_ack_eliciting() {
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, false, GAP),
|
||||
lost(5, true, GAP + PC),
|
||||
],
|
||||
false,
|
||||
);
|
||||
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),
|
||||
],
|
||||
false,
|
||||
);
|
||||
persistent_congestion(
|
||||
&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, false, GAP),
|
||||
lost(5, true, GAP + RTT),
|
||||
lost(6, true, GAP + RTT + PC),
|
||||
],
|
||||
true,
|
||||
);
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, false, GAP),
|
||||
lost(5, true, GAP + PC),
|
||||
]));
|
||||
assert!(!persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, false, GAP),
|
||||
lost(5, true, GAP + RTT),
|
||||
lost(6, true, GAP + RTT + SUB_PC),
|
||||
]));
|
||||
assert!(persistent_congestion(&[
|
||||
lost(1, true, ZERO),
|
||||
lost(2, true, PTO),
|
||||
lost(4, false, GAP),
|
||||
lost(5, true, GAP + RTT),
|
||||
lost(6, true, GAP + RTT + PC),
|
||||
]));
|
||||
}
|
||||
|
||||
/// Get a time, in multiples of `PTO`, relative to `now()`.
|
||||
|
@ -740,12 +738,8 @@ mod tests {
|
|||
/// 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<T: WindowAdjustment>(
|
||||
mut cc: ClassicCongestionControl<T>,
|
||||
last_ack: u32,
|
||||
rtt_time: u32,
|
||||
lost: &[SentPacket],
|
||||
) -> bool {
|
||||
fn persistent_congestion_by_pto(last_ack: u32, rtt_time: u32, lost: &[SentPacket]) -> bool {
|
||||
let mut cc = ClassicCongestionControl::new(NewReno::default());
|
||||
assert_eq!(cc.cwnd(), CWND_INITIAL);
|
||||
|
||||
let last_ack = Some(by_pto(last_ack));
|
||||
|
@ -765,36 +759,14 @@ mod tests {
|
|||
#[test]
|
||||
fn persistent_congestion_no_lost() {
|
||||
let lost = make_lost(&[]);
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
0,
|
||||
0,
|
||||
&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(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(0, 0, &lost));
|
||||
}
|
||||
|
||||
/// Persistent congestion can't happen based on old packets.
|
||||
|
@ -803,42 +775,9 @@ mod tests {
|
|||
// 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(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
1,
|
||||
1,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
0,
|
||||
1,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
1,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
1,
|
||||
1,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
0,
|
||||
1,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
1,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
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.
|
||||
|
@ -853,18 +792,7 @@ mod tests {
|
|||
Vec::new(),
|
||||
lost[0].size,
|
||||
);
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(0, 0, &lost));
|
||||
}
|
||||
|
||||
/// Detect persistent congestion. Note that the first lost packet needs to have a time
|
||||
|
@ -873,63 +801,26 @@ mod tests {
|
|||
#[test]
|
||||
fn persistent_congestion_min() {
|
||||
let lost = make_lost(&[1, PERSISTENT_CONG_THRESH + 2]);
|
||||
assert!(persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
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_newreno() {
|
||||
fn persistent_congestion_no_prev_ack() {
|
||||
let lost = make_lost(&[1, PERSISTENT_CONG_THRESH + 2]);
|
||||
let mut cc = ClassicCongestionControl::new(NewReno::default());
|
||||
cc.detect_persistent_congestion(Some(by_pto(0)), None, PTO, &lost);
|
||||
assert_eq!(cc.cwnd(), CWND_MIN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persistent_congestion_no_prev_ack_cubic() {
|
||||
let lost = make_lost(&[1, PERSISTENT_CONG_THRESH + 2]);
|
||||
let mut cc = ClassicCongestionControl::new(Cubic::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_newreno() {
|
||||
fn persistent_congestion_unsorted() {
|
||||
let lost = make_lost(&[PERSISTENT_CONG_THRESH + 2, 1]);
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(NewReno::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
}
|
||||
|
||||
/// The code asserts on ordering errors.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn persistent_congestion_unsorted_cubic() {
|
||||
let lost = make_lost(&[PERSISTENT_CONG_THRESH + 2, 1]);
|
||||
assert!(!persistent_congestion_by_pto(
|
||||
ClassicCongestionControl::new(Cubic::default()),
|
||||
0,
|
||||
0,
|
||||
&lost
|
||||
));
|
||||
assert!(!persistent_congestion_by_pto(0, 0, &lost));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -959,7 +850,7 @@ mod tests {
|
|||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
);
|
||||
cc.on_packets_acked(&[acked], RTT, now());
|
||||
cc.on_packets_acked(&[acked]);
|
||||
|
||||
assert_eq!(
|
||||
cc.bytes_in_flight(),
|
||||
|
@ -978,7 +869,7 @@ mod tests {
|
|||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
)];
|
||||
cc.on_packets_acked(&p, RTT, now());
|
||||
cc.on_packets_acked(&p);
|
||||
|
||||
assert_eq!(
|
||||
cc.bytes_in_flight(),
|
||||
|
@ -1017,7 +908,7 @@ mod tests {
|
|||
MAX_DATAGRAM_SIZE, // size
|
||||
);
|
||||
cc.on_packet_sent(&p_not_lost);
|
||||
cc.on_packets_acked(&[p_not_lost], RTT, now());
|
||||
cc.on_packets_acked(&[p_not_lost]);
|
||||
cwnd_is_halved(&cc);
|
||||
// cc is app limited therefore cwnd in not increased.
|
||||
assert_eq!(cc.acked_bytes, 0);
|
||||
|
@ -1039,7 +930,7 @@ mod tests {
|
|||
assert_eq!(cc.bytes_in_flight(), CWND_INITIAL / 2);
|
||||
|
||||
for i in 0..CWND_PKTS_CA - 2 {
|
||||
cc.on_packets_acked(&pkts[i..=i], RTT, now());
|
||||
cc.on_packets_acked(&pkts[i..=i]);
|
||||
|
||||
assert_eq!(
|
||||
cc.bytes_in_flight(),
|
||||
|
@ -1051,7 +942,7 @@ mod tests {
|
|||
|
||||
// Now we are app limited
|
||||
for i in CWND_PKTS_CA - 2..CWND_PKTS_CA {
|
||||
cc.on_packets_acked(&pkts[i..=i], RTT, now());
|
||||
cc.on_packets_acked(&pkts[i..=i]);
|
||||
|
||||
assert_eq!(
|
||||
cc.bytes_in_flight(),
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
// 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 std::fmt::{self, Display};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::cc::{classic_cc::WindowAdjustment, MAX_DATAGRAM_SIZE_F64};
|
||||
use neqo_common::qtrace;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// CUBIC congestion control
|
||||
|
||||
// C is a constant fixed to determine the aggressiveness of window
|
||||
// increase in high BDP networks.
|
||||
pub const CUBIC_C: f64 = 0.4;
|
||||
pub const CUBIC_ALPHA: f64 = 3.0 * (1.0 - 0.7) / (1.0 + 0.7);
|
||||
|
||||
// CUBIC_BETA = 0.7;
|
||||
pub const CUBIC_BETA_USIZE_QUOTIENT: usize = 7;
|
||||
pub const CUBIC_BETA_USIZE_DIVISOR: usize = 10;
|
||||
|
||||
/// The fast convergence ratio further reduces the congestion window when a congestion event
|
||||
/// occurs before reaching the previous `W_max`.
|
||||
pub const CUBIC_FAST_CONVERGENCE: f64 = 0.85; // (1.0 + CUBIC_BETA) / 2.0;
|
||||
|
||||
/// The minimum number of multiples of the datagram size that need
|
||||
/// to be received to cause an increase in the congestion window.
|
||||
/// When there is no loss, Cubic can return to exponential increase, but
|
||||
/// this value reduces the magnitude of the resulting growth by a constant factor.
|
||||
/// A value of 1.0 would mean a return to the rate used in slow start.
|
||||
const EXPONENTIAL_GROWTH_REDUCTION: f64 = 2.0;
|
||||
|
||||
fn convert_to_f64(v: usize) -> f64 {
|
||||
assert!(v < (1 << 53));
|
||||
let mut f_64 = f64::try_from(u32::try_from(v >> 21).unwrap()).unwrap();
|
||||
f_64 *= 2_097_152.0; // f_64 <<= 21
|
||||
f_64 += f64::try_from(u32::try_from(v & 0x1f_ffff).unwrap()).unwrap();
|
||||
f_64
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cubic {
|
||||
last_max_cwnd: f64,
|
||||
estimated_tcp_cwnd: f64,
|
||||
k: f64,
|
||||
w_max: f64,
|
||||
ca_epoch_start: Option<Instant>,
|
||||
last_phase_was_tcp: bool,
|
||||
tcp_acked_bytes: f64,
|
||||
}
|
||||
|
||||
impl Default for Cubic {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last_max_cwnd: 0.0,
|
||||
estimated_tcp_cwnd: 0.0,
|
||||
k: 0.0,
|
||||
w_max: 0.0,
|
||||
ca_epoch_start: None,
|
||||
last_phase_was_tcp: false,
|
||||
tcp_acked_bytes: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Cubic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Cubic [last_max_cwnd: {}, k: {}, w_max: {}, ca_epoch_start: {:?}]",
|
||||
self.last_max_cwnd, self.k, self.w_max, self.ca_epoch_start
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::doc_markdown)]
|
||||
impl Cubic {
|
||||
/// Original equations is:
|
||||
/// K = cubic_root(W_max*(1-beta_cubic)/C) (Eq. 2 RFC8312)
|
||||
/// W_max is number of segments of the maximum segment size (MSS).
|
||||
///
|
||||
/// K is actually the time that W_cubic(t) = C*(t-K)^3 + W_max (Eq. 1) would
|
||||
/// take to increase to W_max. We use bytes not MSS units, therefore this
|
||||
/// equation will be: W_cubic(t) = C*MSS*(t-K)^3 + W_max.
|
||||
///
|
||||
/// From that equation we can calculate K as:
|
||||
/// K = cubic_root((W_max - W_cubic) / C / MSS);
|
||||
fn calc_k(&self, curr_cwnd: f64) -> f64 {
|
||||
((self.w_max - curr_cwnd) / CUBIC_C / MAX_DATAGRAM_SIZE_F64).cbrt()
|
||||
}
|
||||
|
||||
/// W_cubic(t) = C*(t-K)^3 + W_max (Eq. 1)
|
||||
/// t is relative to the start of the congestion avoidance phase and it is in seconds.
|
||||
fn w_cubic(&self, t: f64) -> f64 {
|
||||
CUBIC_C * (t - self.k).powi(3) * MAX_DATAGRAM_SIZE_F64 + self.w_max
|
||||
}
|
||||
|
||||
fn start_epoch(&mut self, curr_cwnd_f64: f64, new_acked_f64: f64, now: Instant) {
|
||||
self.ca_epoch_start = Some(now);
|
||||
// reset tcp_acked_bytes and estimated_tcp_cwnd;
|
||||
self.tcp_acked_bytes = new_acked_f64;
|
||||
self.estimated_tcp_cwnd = curr_cwnd_f64;
|
||||
if self.last_max_cwnd <= curr_cwnd_f64 {
|
||||
self.w_max = curr_cwnd_f64;
|
||||
self.k = 0.0;
|
||||
} else {
|
||||
self.w_max = self.last_max_cwnd;
|
||||
self.k = self.calc_k(curr_cwnd_f64);
|
||||
}
|
||||
qtrace!([self], "New epoch");
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowAdjustment for Cubic {
|
||||
// This is because of the cast in the last line from f64 to usize.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
fn bytes_for_cwnd_increase(
|
||||
&mut self,
|
||||
curr_cwnd: usize,
|
||||
new_acked_bytes: usize,
|
||||
min_rtt: Duration,
|
||||
now: Instant,
|
||||
) -> usize {
|
||||
let curr_cwnd_f64 = convert_to_f64(curr_cwnd);
|
||||
let new_acked_f64 = convert_to_f64(new_acked_bytes);
|
||||
if self.ca_epoch_start.is_none() {
|
||||
// This is a start of a new congestion avoidance phase.
|
||||
self.start_epoch(curr_cwnd_f64, new_acked_f64, now);
|
||||
} else {
|
||||
self.tcp_acked_bytes += new_acked_f64;
|
||||
}
|
||||
|
||||
let time_ca = self
|
||||
.ca_epoch_start
|
||||
.map_or(min_rtt, |t| now + min_rtt - t)
|
||||
.as_secs_f64();
|
||||
let target_cubic = self.w_cubic(time_ca);
|
||||
|
||||
let tcp_cnt = self.estimated_tcp_cwnd / CUBIC_ALPHA;
|
||||
while self.tcp_acked_bytes > tcp_cnt {
|
||||
self.tcp_acked_bytes -= tcp_cnt;
|
||||
self.estimated_tcp_cwnd += MAX_DATAGRAM_SIZE_F64;
|
||||
}
|
||||
|
||||
let target_cwnd = target_cubic.max(self.estimated_tcp_cwnd);
|
||||
|
||||
// Calculate the number of bytes that would need to be acknowledged for an increase
|
||||
// of `MAX_DATAGRAM_SIZE` to match the increase of `target - cwnd / cwnd` as defined
|
||||
// in the specification (Sections 4.4 and 4.5).
|
||||
// The amount of data required therefore reduces asymptotically as the target increases.
|
||||
// If the target is not significantly higher than the congestion window, require a very large
|
||||
// amount of acknowledged data (effectively block increases).
|
||||
let mut acked_to_increase =
|
||||
MAX_DATAGRAM_SIZE_F64 * curr_cwnd_f64 / (target_cwnd - curr_cwnd_f64).max(1.0);
|
||||
|
||||
// Limit increase to max 1 MSS per EXPONENTIAL_GROWTH_REDUCTION ack packets.
|
||||
// This effectively limits target_cwnd to (1 + 1 / EXPONENTIAL_GROWTH_REDUCTION) cwnd.
|
||||
acked_to_increase =
|
||||
acked_to_increase.max(EXPONENTIAL_GROWTH_REDUCTION * MAX_DATAGRAM_SIZE_F64);
|
||||
acked_to_increase as usize
|
||||
}
|
||||
|
||||
fn reduce_cwnd(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize) {
|
||||
let curr_cwnd_f64 = convert_to_f64(curr_cwnd);
|
||||
// Fast Convergence
|
||||
// If congestion event occurs before the maximum congestion window before the last congestion event,
|
||||
// we reduce the the maximum congestion window and thereby W_max.
|
||||
// check cwnd + MAX_DATAGRAM_SIZE instead of cwnd because with cwnd in bytes, cwnd may be slightly off.
|
||||
self.last_max_cwnd = if curr_cwnd_f64 + MAX_DATAGRAM_SIZE_F64 < self.last_max_cwnd {
|
||||
curr_cwnd_f64 * CUBIC_FAST_CONVERGENCE
|
||||
} else {
|
||||
curr_cwnd_f64
|
||||
};
|
||||
self.ca_epoch_start = None;
|
||||
(
|
||||
curr_cwnd * CUBIC_BETA_USIZE_QUOTIENT / CUBIC_BETA_USIZE_DIVISOR,
|
||||
acked_bytes * CUBIC_BETA_USIZE_QUOTIENT / CUBIC_BETA_USIZE_DIVISOR,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_app_limited(&mut self) {
|
||||
// Reset ca_epoch_start. Let it start again when the congestion controller
|
||||
// exits the app-limited period.
|
||||
self.ca_epoch_start = None;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn last_max_cwnd(&self) -> f64 {
|
||||
self.last_max_cwnd
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_last_max_cwnd(&mut self, last_max_cwnd: f64) {
|
||||
self.last_max_cwnd = last_max_cwnd;
|
||||
}
|
||||
}
|
|
@ -15,16 +15,13 @@ use std::fmt::{Debug, Display};
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
mod classic_cc;
|
||||
mod cubic;
|
||||
mod new_reno;
|
||||
|
||||
pub use classic_cc::ClassicCongestionControl;
|
||||
pub use classic_cc::{CWND_INITIAL, CWND_INITIAL_PKTS, CWND_MIN};
|
||||
pub use cubic::Cubic;
|
||||
pub use classic_cc::{CWND_INITIAL_PKTS, CWND_MIN};
|
||||
pub use new_reno::NewReno;
|
||||
|
||||
pub const MAX_DATAGRAM_SIZE: usize = PATH_MTU_V6;
|
||||
pub const MAX_DATAGRAM_SIZE_F64: f64 = 1337.0;
|
||||
|
||||
pub trait CongestionControl: Display + Debug {
|
||||
fn set_qlog(&mut self, qlog: NeqoQlog);
|
||||
|
@ -35,7 +32,7 @@ pub trait CongestionControl: Display + Debug {
|
|||
|
||||
fn cwnd_avail(&self) -> usize;
|
||||
|
||||
fn on_packets_acked(&mut self, acked_pkts: &[SentPacket], min_rtt: Duration, now: Instant);
|
||||
fn on_packets_acked(&mut self, acked_pkts: &[SentPacket]);
|
||||
|
||||
fn on_packets_lost(
|
||||
&mut self,
|
||||
|
@ -52,11 +49,7 @@ pub trait CongestionControl: Display + Debug {
|
|||
fn on_packet_sent(&mut self, pkt: &SentPacket);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CongestionControlAlgorithm {
|
||||
NewReno,
|
||||
Cubic,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::cc::classic_cc::WindowAdjustment;
|
||||
use std::time::{Duration, Instant};
|
||||
use crate::cc::{classic_cc::WindowAdjustment, MAX_DATAGRAM_SIZE};
|
||||
use neqo_common::qinfo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NewReno {}
|
||||
|
@ -29,27 +29,16 @@ impl Display for NewReno {
|
|||
}
|
||||
|
||||
impl WindowAdjustment for NewReno {
|
||||
fn bytes_for_cwnd_increase(
|
||||
&mut self,
|
||||
curr_cwnd: usize,
|
||||
_new_acked_bytes: usize,
|
||||
_min_rtt: Duration,
|
||||
_now: Instant,
|
||||
) -> usize {
|
||||
curr_cwnd
|
||||
fn on_packets_acked(&mut self, mut curr_cwnd: usize, mut acked_bytes: usize) -> (usize, usize) {
|
||||
if acked_bytes >= curr_cwnd {
|
||||
acked_bytes -= curr_cwnd;
|
||||
curr_cwnd += MAX_DATAGRAM_SIZE;
|
||||
qinfo!([self], "congestion avoidance += {}", MAX_DATAGRAM_SIZE);
|
||||
}
|
||||
(curr_cwnd, acked_bytes)
|
||||
}
|
||||
|
||||
fn reduce_cwnd(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize) {
|
||||
fn on_congestion_event(&mut self, curr_cwnd: usize, acked_bytes: usize) -> (usize, usize) {
|
||||
(curr_cwnd / 2, acked_bytes / 2)
|
||||
}
|
||||
|
||||
fn on_app_limited(&mut self) {}
|
||||
|
||||
#[cfg(test)]
|
||||
fn last_max_cwnd(&self) -> f64 {
|
||||
0.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_last_max_cwnd(&mut self, _last_max_cwnd: f64) {}
|
||||
}
|
||||
|
|
|
@ -1,303 +0,0 @@
|
|||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(clippy::cast_sign_loss)]
|
||||
|
||||
use crate::cc::{
|
||||
classic_cc::{ClassicCongestionControl, CWND_INITIAL},
|
||||
cubic::{
|
||||
Cubic, CUBIC_ALPHA, CUBIC_BETA_USIZE_DIVISOR, CUBIC_BETA_USIZE_QUOTIENT, CUBIC_C,
|
||||
CUBIC_FAST_CONVERGENCE,
|
||||
},
|
||||
CongestionControl, MAX_DATAGRAM_SIZE, MAX_DATAGRAM_SIZE_F64,
|
||||
};
|
||||
use crate::packet::PacketType;
|
||||
use crate::tracking::SentPacket;
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Sub;
|
||||
use std::time::{Duration, Instant};
|
||||
use test_fixture::now;
|
||||
|
||||
const RTT: Duration = Duration::from_millis(100);
|
||||
const CWND_INITIAL_F64: f64 = 10.0 * MAX_DATAGRAM_SIZE_F64;
|
||||
const CWND_INITIAL_10_F64: f64 = 10.0 * CWND_INITIAL_F64;
|
||||
const CWND_INITIAL_10: usize = 10 * CWND_INITIAL;
|
||||
const CWND_AFTER_LOSS: usize = CWND_INITIAL * CUBIC_BETA_USIZE_QUOTIENT / CUBIC_BETA_USIZE_DIVISOR;
|
||||
const CWND_AFTER_LOSS_SLOW_START: usize =
|
||||
(CWND_INITIAL + MAX_DATAGRAM_SIZE) * CUBIC_BETA_USIZE_QUOTIENT / CUBIC_BETA_USIZE_DIVISOR;
|
||||
|
||||
fn fill_cwnd(cc: &mut ClassicCongestionControl<Cubic>, mut next_pn: u64, now: Instant) -> u64 {
|
||||
while cc.bytes_in_flight() < cc.cwnd() {
|
||||
let sent = SentPacket::new(
|
||||
PacketType::Short,
|
||||
next_pn, // pn
|
||||
now, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
);
|
||||
cc.on_packet_sent(&sent);
|
||||
next_pn += 1;
|
||||
}
|
||||
next_pn
|
||||
}
|
||||
|
||||
fn ack_packet(cc: &mut ClassicCongestionControl<Cubic>, pn: u64, now: Instant) {
|
||||
let acked = SentPacket::new(
|
||||
PacketType::Short,
|
||||
pn, // pn
|
||||
now, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
);
|
||||
cc.on_packets_acked(&[acked], RTT, now);
|
||||
}
|
||||
|
||||
fn packet_lost(cc: &mut ClassicCongestionControl<Cubic>, pn: u64) {
|
||||
const PTO: Duration = Duration::from_millis(120);
|
||||
let p_lost = SentPacket::new(
|
||||
PacketType::Short,
|
||||
pn, // pn
|
||||
now(), // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
);
|
||||
cc.on_packets_lost(None, None, PTO, &[p_lost]);
|
||||
}
|
||||
|
||||
fn expected_tcp_acks(cwnd_rtt_start: usize) -> u64 {
|
||||
(f64::try_from(i32::try_from(cwnd_rtt_start).unwrap()).unwrap()
|
||||
/ MAX_DATAGRAM_SIZE_F64
|
||||
/ CUBIC_ALPHA)
|
||||
.round() as u64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_phase() {
|
||||
let mut cubic = ClassicCongestionControl::new(Cubic::default());
|
||||
|
||||
// change to congestion avoidance state.
|
||||
cubic.set_ssthresh(1);
|
||||
|
||||
let mut now = now();
|
||||
let start_time = now;
|
||||
// helper variables to remember the next packet number to be sent/acked.
|
||||
let mut next_pn_send = 0;
|
||||
let mut next_pn_ack = 0;
|
||||
|
||||
next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now);
|
||||
|
||||
// This will start with TCP phase.
|
||||
// in this phase cwnd is increase by CUBIC_ALPHA every RTT. We can look at it as
|
||||
// increase of MAX_DATAGRAM_SIZE every 1 / CUBIC_ALPHA RTTs.
|
||||
// The phase will end when cwnd calculated with cubic equation is equal to TCP estimate:
|
||||
// CUBIC_C * (n * RTT / CUBIC_ALPHA)^3 * MAX_DATAGRAM_SIZE = n * MAX_DATAGRAM_SIZE
|
||||
// from this n = sqrt(CUBIC_ALPHA^3/ (CUBIC_C * RTT^3)).
|
||||
let num_tcp_increases = (CUBIC_ALPHA.powi(3) / (CUBIC_C * RTT.as_secs_f64().powi(3)))
|
||||
.sqrt()
|
||||
.floor() as u64;
|
||||
|
||||
for _ in 0..num_tcp_increases {
|
||||
let cwnd_rtt_start = cubic.cwnd();
|
||||
//Expected acks during a period of RTT / CUBIC_ALPHA.
|
||||
let acks = expected_tcp_acks(cwnd_rtt_start);
|
||||
// The time between acks if they are ideally paced over a RTT.
|
||||
let time_increase = RTT / u32::try_from(cwnd_rtt_start / MAX_DATAGRAM_SIZE).unwrap();
|
||||
|
||||
for _ in 0..acks {
|
||||
now += time_increase;
|
||||
ack_packet(&mut cubic, next_pn_ack, now);
|
||||
next_pn_ack += 1;
|
||||
next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now);
|
||||
}
|
||||
|
||||
assert_eq!(cubic.cwnd() - cwnd_rtt_start, MAX_DATAGRAM_SIZE);
|
||||
}
|
||||
|
||||
// The next increase will be according to the cubic equation.
|
||||
|
||||
let cwnd_rtt_start = cubic.cwnd();
|
||||
// cwnd_rtt_start has change, therefore calculate new time_increase (the time
|
||||
// between acks if they are ideally paced over a RTT).
|
||||
let time_increase = RTT / u32::try_from(cwnd_rtt_start / MAX_DATAGRAM_SIZE).unwrap();
|
||||
let mut num_acks = 0; // count the number of acks. until cwnd is increased by MAX_DATAGRAM_SIZE.
|
||||
|
||||
while cwnd_rtt_start == cubic.cwnd() {
|
||||
num_acks += 1;
|
||||
now += time_increase;
|
||||
ack_packet(&mut cubic, next_pn_ack, now);
|
||||
next_pn_ack += 1;
|
||||
next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now);
|
||||
}
|
||||
|
||||
// Make sure that the increase is not according to TCP equation, i.e., that it took
|
||||
// less than RTT / CUBIC_ALPHA.
|
||||
let expected_ack_tcp_increase = expected_tcp_acks(cwnd_rtt_start);
|
||||
assert!(num_acks < expected_ack_tcp_increase);
|
||||
|
||||
// This first increase after a TCP phase may be shorter than what it would take by a regular cubic phase,
|
||||
// because of the proper byte counting and the credit it already had before entering this phase. Therefore
|
||||
// We will perform another round and compare it to expected increase using the cubic equation.
|
||||
|
||||
let cwnd_rtt_start_after_tcp = cubic.cwnd();
|
||||
let elapsed_time = now - start_time;
|
||||
|
||||
// calculate new time_increase.
|
||||
let time_increase = RTT / u32::try_from(cwnd_rtt_start_after_tcp / MAX_DATAGRAM_SIZE).unwrap();
|
||||
let mut num_acks2 = 0; // count the number of acks. until cwnd is increased by MAX_DATAGRAM_SIZE.
|
||||
|
||||
while cwnd_rtt_start_after_tcp == cubic.cwnd() {
|
||||
num_acks2 += 1;
|
||||
now += time_increase;
|
||||
ack_packet(&mut cubic, next_pn_ack, now);
|
||||
next_pn_ack += 1;
|
||||
next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now);
|
||||
}
|
||||
|
||||
let expected_ack_tcp_increase2 = expected_tcp_acks(cwnd_rtt_start_after_tcp);
|
||||
assert!(num_acks2 < expected_ack_tcp_increase2);
|
||||
|
||||
// The time needed to increase cwnd by MAX_DATAGRAM_SIZE using the cubic equation will be calculates from:
|
||||
// W_cubic(elapsed_time + t_to_increase) - W_cubis(elapsed_time) = MAX_DATAGRAM_SIZE =>
|
||||
// CUBIC_C * (elapsed_time + t_to_increase)^3 * MAX_DATAGRAM_SIZE + CWND_INITIAL -
|
||||
// CUBIC_C * elapsed_time^3 * MAX_DATAGRAM_SIZE + CWND_INITIAL = MAX_DATAGRAM_SIZE =>
|
||||
// t_to_increase = cbrt((1 + CUBIC_C * elapsed_time^3) / CUBIC_C) - elapsed_time
|
||||
// (t_to_increase is in seconds)
|
||||
// number of ack needed is t_to_increase / time_increase.
|
||||
let expected_ack_cubic_increase =
|
||||
((((1.0 + CUBIC_C * (elapsed_time).as_secs_f64().powi(3)) / CUBIC_C).cbrt()
|
||||
- elapsed_time.as_secs_f64())
|
||||
/ time_increase.as_secs_f64())
|
||||
.ceil() as u64;
|
||||
// num_acks is very close to the calculated value. The exact value is hard to calculate
|
||||
// because the proportional increase(i.e. curr_cwnd_f64 / (target - curr_cwnd_f64) * MAX_DATAGRAM_SIZE_F64)
|
||||
// and the byte counting.
|
||||
assert_eq!(num_acks2, expected_ack_cubic_increase + 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cubic_phase() {
|
||||
let mut cubic = ClassicCongestionControl::new(Cubic::default());
|
||||
// Set last_max_cwnd to a higher number make sure that cc is the cubic phase (cwnd is calculated by the cubic equation).
|
||||
cubic.set_last_max_cwnd(CWND_INITIAL_10_F64);
|
||||
// Set ssthresh to something small to make sure that cc is in the congection avoidance phase.
|
||||
cubic.set_ssthresh(1);
|
||||
let mut now = now();
|
||||
let mut next_pn_send = 0;
|
||||
let mut next_pn_ack = 0;
|
||||
|
||||
next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now);
|
||||
|
||||
let k = ((CWND_INITIAL_10_F64 - CWND_INITIAL_F64) / CUBIC_C / MAX_DATAGRAM_SIZE_F64).cbrt();
|
||||
let epoch_start = now;
|
||||
|
||||
// The number of RTT until W_max is reached.
|
||||
let num_rtts_w_max = (k / RTT.as_secs_f64()).round() as u64;
|
||||
for _ in 0..num_rtts_w_max {
|
||||
let cwnd_rtt_start = cubic.cwnd();
|
||||
//Expected acks
|
||||
let acks = cwnd_rtt_start / MAX_DATAGRAM_SIZE;
|
||||
let time_increase = RTT / u32::try_from(acks).unwrap();
|
||||
for _ in 0..acks {
|
||||
now += time_increase;
|
||||
ack_packet(&mut cubic, next_pn_ack, now);
|
||||
next_pn_ack += 1;
|
||||
next_pn_send = fill_cwnd(&mut cubic, next_pn_send, now);
|
||||
}
|
||||
|
||||
let expected =
|
||||
(CUBIC_C * ((now - epoch_start).as_secs_f64() - k).powi(3) * MAX_DATAGRAM_SIZE_F64
|
||||
+ CWND_INITIAL_10_F64)
|
||||
.round() as usize;
|
||||
|
||||
assert_within(cubic.cwnd(), expected, MAX_DATAGRAM_SIZE);
|
||||
}
|
||||
assert_eq!(cubic.cwnd(), CWND_INITIAL_10);
|
||||
}
|
||||
|
||||
fn assert_within<T: Sub<Output = T> + PartialOrd + Copy>(value: T, expected: T, margin: T) {
|
||||
if value >= expected {
|
||||
assert!(value - expected < margin);
|
||||
} else {
|
||||
assert!(expected - value < margin);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congestion_event_slow_start() {
|
||||
let mut cubic = ClassicCongestionControl::new(Cubic::default());
|
||||
|
||||
let _ = fill_cwnd(&mut cubic, 0, now());
|
||||
ack_packet(&mut cubic, 0, now());
|
||||
|
||||
assert_within(cubic.last_max_cwnd(), 0.0, f64::EPSILON);
|
||||
|
||||
// cwnd is increased by 1 in slow start phase, after an ack.
|
||||
assert_eq!(cubic.cwnd(), CWND_INITIAL + MAX_DATAGRAM_SIZE);
|
||||
|
||||
// Trigger a congestion_event in slow start phase
|
||||
packet_lost(&mut cubic, 1);
|
||||
|
||||
// last_max_cwnd is equal to cwnd before decrease.
|
||||
assert_within(
|
||||
cubic.last_max_cwnd(),
|
||||
CWND_INITIAL_F64 + MAX_DATAGRAM_SIZE_F64,
|
||||
f64::EPSILON,
|
||||
);
|
||||
assert_eq!(cubic.cwnd(), CWND_AFTER_LOSS_SLOW_START);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congestion_event_congestion_avoidance() {
|
||||
let mut cubic = ClassicCongestionControl::new(Cubic::default());
|
||||
|
||||
// Set ssthresh to something small to make sure that cc is in the congection avoidance phase.
|
||||
cubic.set_ssthresh(1);
|
||||
|
||||
// Set last_max_cwnd to something smaller than cwnd so that the fast convergence is not triggered.
|
||||
cubic.set_last_max_cwnd(3.0 * MAX_DATAGRAM_SIZE_F64);
|
||||
|
||||
let _ = fill_cwnd(&mut cubic, 0, now());
|
||||
ack_packet(&mut cubic, 0, now());
|
||||
|
||||
assert_eq!(cubic.cwnd(), CWND_INITIAL);
|
||||
|
||||
// Trigger a congestion_event in slow start phase
|
||||
packet_lost(&mut cubic, 1);
|
||||
|
||||
assert_within(cubic.last_max_cwnd(), CWND_INITIAL_F64, f64::EPSILON);
|
||||
assert_eq!(cubic.cwnd(), CWND_AFTER_LOSS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congestion_event_congestion_avoidance_2() {
|
||||
let mut cubic = ClassicCongestionControl::new(Cubic::default());
|
||||
|
||||
// Set ssthresh to something small to make sure that cc is in the congection avoidance phase.
|
||||
cubic.set_ssthresh(1);
|
||||
|
||||
// Set last_max_cwnd to something higher than cwnd so that the fast convergence is triggered.
|
||||
cubic.set_last_max_cwnd(CWND_INITIAL_10_F64);
|
||||
|
||||
let _ = fill_cwnd(&mut cubic, 0, now());
|
||||
ack_packet(&mut cubic, 0, now());
|
||||
|
||||
assert_within(cubic.last_max_cwnd(), CWND_INITIAL_10_F64, f64::EPSILON);
|
||||
assert_eq!(cubic.cwnd(), CWND_INITIAL);
|
||||
|
||||
// Trigger a congestion_event.
|
||||
packet_lost(&mut cubic, 1);
|
||||
|
||||
assert_within(
|
||||
cubic.last_max_cwnd(),
|
||||
CWND_INITIAL_F64 * CUBIC_FAST_CONVERGENCE,
|
||||
f64::EPSILON,
|
||||
);
|
||||
assert_eq!(cubic.cwnd(), CWND_AFTER_LOSS);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// 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.
|
||||
|
||||
mod cubic;
|
||||
mod new_reno;
|
|
@ -1,131 +0,0 @@
|
|||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// Congestion control
|
||||
#![deny(clippy::pedantic)]
|
||||
|
||||
use crate::cc::new_reno::NewReno;
|
||||
use crate::cc::{ClassicCongestionControl, CongestionControl, CWND_INITIAL, MAX_DATAGRAM_SIZE};
|
||||
use crate::packet::PacketType;
|
||||
use crate::tracking::SentPacket;
|
||||
use std::time::Duration;
|
||||
use test_fixture::now;
|
||||
|
||||
const PTO: Duration = Duration::from_millis(100);
|
||||
const RTT: Duration = Duration::from_millis(98);
|
||||
|
||||
fn cwnd_is_default(cc: &ClassicCongestionControl<NewReno>) {
|
||||
assert_eq!(cc.cwnd(), CWND_INITIAL);
|
||||
assert_eq!(cc.ssthresh(), usize::MAX);
|
||||
}
|
||||
|
||||
fn cwnd_is_halved(cc: &ClassicCongestionControl<NewReno>) {
|
||||
assert_eq!(cc.cwnd(), CWND_INITIAL / 2);
|
||||
assert_eq!(cc.ssthresh(), CWND_INITIAL / 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_876() {
|
||||
let mut cc = ClassicCongestionControl::new(NewReno::default());
|
||||
let time_now = now();
|
||||
let time_before = time_now - Duration::from_millis(100);
|
||||
let time_after = time_now + Duration::from_millis(150);
|
||||
|
||||
let sent_packets = &[
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
1, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE - 1, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
2, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE - 2, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
3, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
4, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
5, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
6, // pn
|
||||
time_before, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE, // size
|
||||
),
|
||||
SentPacket::new(
|
||||
PacketType::Short,
|
||||
7, // pn
|
||||
time_after, // time sent
|
||||
true, // ack eliciting
|
||||
Vec::new(), // tokens
|
||||
MAX_DATAGRAM_SIZE - 3, // size
|
||||
),
|
||||
];
|
||||
|
||||
// Send some more packets so that the cc is not app-limited.
|
||||
for p in &sent_packets[..6] {
|
||||
cc.on_packet_sent(p);
|
||||
}
|
||||
assert_eq!(cc.acked_bytes(), 0);
|
||||
cwnd_is_default(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 6 * MAX_DATAGRAM_SIZE - 3);
|
||||
|
||||
cc.on_packets_lost(Some(time_now), None, PTO, &sent_packets[0..1]);
|
||||
|
||||
// We are now in recovery
|
||||
assert!(cc.recovery_packet());
|
||||
assert_eq!(cc.acked_bytes(), 0);
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 5 * MAX_DATAGRAM_SIZE - 2);
|
||||
|
||||
// Send a packet after recovery starts
|
||||
cc.on_packet_sent(&sent_packets[6]);
|
||||
assert!(!cc.recovery_packet());
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.acked_bytes(), 0);
|
||||
assert_eq!(cc.bytes_in_flight(), 6 * MAX_DATAGRAM_SIZE - 5);
|
||||
|
||||
// and ack it. cwnd increases slightly
|
||||
cc.on_packets_acked(&sent_packets[6..], RTT, time_now);
|
||||
assert_eq!(cc.acked_bytes(), sent_packets[6].size);
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 5 * MAX_DATAGRAM_SIZE - 2);
|
||||
|
||||
// Packet from before is lost. Should not hurt cwnd.
|
||||
cc.on_packets_lost(Some(time_now), None, PTO, &sent_packets[1..2]);
|
||||
assert!(!cc.recovery_packet());
|
||||
assert_eq!(cc.acked_bytes(), sent_packets[6].size);
|
||||
cwnd_is_halved(&cc);
|
||||
assert_eq!(cc.bytes_in_flight(), 4 * MAX_DATAGRAM_SIZE);
|
||||
}
|
|
@ -4,45 +4,26 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// Representation and management of connection IDs.
|
||||
// Encoding and decoding packets off the wire.
|
||||
|
||||
use crate::frame::FRAME_TYPE_NEW_CONNECTION_ID;
|
||||
use crate::packet::PacketBuilder;
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::stats::FrameStats;
|
||||
use crate::{Error, Res};
|
||||
|
||||
use neqo_common::{hex, hex_with_len, qinfo, Decoder, Encoder};
|
||||
use neqo_common::{hex, hex_with_len, Decoder};
|
||||
use neqo_crypto::random;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::cmp::max;
|
||||
use std::cmp::min;
|
||||
use std::convert::AsRef;
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub const MAX_CONNECTION_ID_LEN: usize = 20;
|
||||
pub const LOCAL_ACTIVE_CID_LIMIT: usize = 8;
|
||||
pub const CONNECTION_ID_SEQNO_INITIAL: u64 = 0;
|
||||
pub const CONNECTION_ID_SEQNO_PREFERRED: u64 = 1;
|
||||
/// A special value. See `ConnectionIdManager::add_odcid`.
|
||||
const CONNECTION_ID_SEQNO_ODCID: u64 = u64::MAX;
|
||||
/// A special value. See `ConnectionIdEntry::empty_remote`.
|
||||
const CONNECTION_ID_SEQNO_EMPTY: u64 = u64::MAX - 1;
|
||||
|
||||
#[derive(Clone, Default, Eq, Hash, PartialEq)]
|
||||
pub struct ConnectionId {
|
||||
pub(crate) cid: SmallVec<[u8; MAX_CONNECTION_ID_LEN]>,
|
||||
pub(crate) cid: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ConnectionId {
|
||||
pub fn generate(len: usize) -> Self {
|
||||
assert!(matches!(len, 0..=MAX_CONNECTION_ID_LEN));
|
||||
Self::from(random(len))
|
||||
Self { cid: random(len) }
|
||||
}
|
||||
|
||||
// Apply a wee bit of greasing here in picking a length between 8 and 20 bytes long.
|
||||
|
@ -70,27 +51,19 @@ impl Borrow<[u8]> for ConnectionId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<SmallVec<[u8; MAX_CONNECTION_ID_LEN]>> for ConnectionId {
|
||||
fn from(cid: SmallVec<[u8; MAX_CONNECTION_ID_LEN]>) -> Self {
|
||||
Self { cid }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ConnectionId {
|
||||
fn from(cid: Vec<u8>) -> Self {
|
||||
Self::from(SmallVec::from(cid))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + ?Sized> From<&T> for ConnectionId {
|
||||
fn from(buf: &T) -> Self {
|
||||
Self::from(SmallVec::from(buf.as_ref()))
|
||||
impl From<&[u8]> for ConnectionId {
|
||||
fn from(buf: &[u8]) -> Self {
|
||||
Self {
|
||||
cid: Vec::from(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&ConnectionIdRef<'a>> for ConnectionId {
|
||||
fn from(cidref: &ConnectionIdRef<'a>) -> Self {
|
||||
Self::from(SmallVec::from(cidref.cid))
|
||||
Self {
|
||||
cid: Vec::from(cidref.cid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,9 +110,9 @@ impl<'a> ::std::fmt::Display for ConnectionIdRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for ConnectionIdRef<'a> {
|
||||
fn from(cid: &'a T) -> Self {
|
||||
Self { cid: cid.as_ref() }
|
||||
impl<'a> From<&'a [u8]> for ConnectionIdRef<'a> {
|
||||
fn from(cid: &'a [u8]) -> Self {
|
||||
Self { cid }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,418 +131,14 @@ impl<'a> PartialEq<ConnectionId> for ConnectionIdRef<'a> {
|
|||
}
|
||||
|
||||
pub trait ConnectionIdDecoder {
|
||||
/// Decodes a connection ID from the provided decoder.
|
||||
fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>>;
|
||||
}
|
||||
|
||||
pub trait ConnectionIdGenerator: ConnectionIdDecoder {
|
||||
/// Generates a connection ID. This can return `None` if the generator
|
||||
/// is exhausted.
|
||||
fn generate_cid(&mut self) -> Option<ConnectionId>;
|
||||
/// Indicates whether the connection IDs are zero-length.
|
||||
/// If this returns true, `generate_cid` must always produce an empty value
|
||||
/// and never `None`.
|
||||
/// If this returns false, `generate_cid` must never produce an empty value,
|
||||
/// though it can return `None`.
|
||||
///
|
||||
/// You should not need to implement this: if you want zero-length connection IDs,
|
||||
/// use `EmptyConnectionIdGenerator` instead.
|
||||
fn generates_empty_cids(&self) -> bool {
|
||||
false
|
||||
}
|
||||
pub trait ConnectionIdManager: ConnectionIdDecoder {
|
||||
fn generate_cid(&mut self) -> ConnectionId;
|
||||
fn as_decoder(&self) -> &dyn ConnectionIdDecoder;
|
||||
}
|
||||
|
||||
/// An `EmptyConnectionIdGenerator` generates empty connection IDs.
|
||||
#[derive(Default)]
|
||||
pub struct EmptyConnectionIdGenerator {}
|
||||
|
||||
impl ConnectionIdDecoder for EmptyConnectionIdGenerator {
|
||||
fn decode_cid<'a>(&self, _: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> {
|
||||
Some(ConnectionIdRef::from(&[]))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdGenerator for EmptyConnectionIdGenerator {
|
||||
fn generate_cid(&mut self) -> Option<ConnectionId> {
|
||||
Some(ConnectionId::from(&[]))
|
||||
}
|
||||
fn as_decoder(&self) -> &dyn ConnectionIdDecoder {
|
||||
self
|
||||
}
|
||||
fn generates_empty_cids(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// An RandomConnectionIdGenerator produces connection IDs of
|
||||
/// a fixed length and random content. No effort is made to
|
||||
/// prevent collisions.
|
||||
pub struct RandomConnectionIdGenerator {
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl RandomConnectionIdGenerator {
|
||||
pub fn new(len: usize) -> Self {
|
||||
Self { len }
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdDecoder for RandomConnectionIdGenerator {
|
||||
fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> {
|
||||
dec.decode(self.len).map(ConnectionIdRef::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdGenerator for RandomConnectionIdGenerator {
|
||||
fn generate_cid(&mut self) -> Option<ConnectionId> {
|
||||
Some(ConnectionId::from(&random(self.len)))
|
||||
}
|
||||
|
||||
fn as_decoder(&self) -> &dyn ConnectionIdDecoder {
|
||||
self
|
||||
}
|
||||
|
||||
fn generates_empty_cids(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// A single connection ID, as saved from NEW_CONNECTION_ID.
|
||||
/// This is templated so that the connection ID entries from a peer can be
|
||||
/// saved with a stateless reset token. Local entries don't need that.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct ConnectionIdEntry<SRT: Clone + PartialEq> {
|
||||
/// The sequence number.
|
||||
seqno: u64,
|
||||
/// The connection ID.
|
||||
cid: ConnectionId,
|
||||
/// The corresponding stateless reset token.
|
||||
srt: SRT,
|
||||
}
|
||||
|
||||
impl ConnectionIdEntry<[u8; 16]> {
|
||||
/// Create a random stateless reset token so that it is hard to guess the correct
|
||||
/// value and reset the connection.
|
||||
fn random_srt() -> [u8; 16] {
|
||||
<[u8; 16]>::try_from(&random(16)[..]).unwrap()
|
||||
}
|
||||
|
||||
/// Create the first entry, which won't have a stateless reset token.
|
||||
pub fn initial_remote(cid: ConnectionId) -> Self {
|
||||
Self::new(CONNECTION_ID_SEQNO_INITIAL, cid, Self::random_srt())
|
||||
}
|
||||
|
||||
/// Create an empty for when the peer chooses empty connection IDs.
|
||||
/// This uses a special sequence number just because it can.
|
||||
pub fn empty_remote() -> Self {
|
||||
Self::new(
|
||||
CONNECTION_ID_SEQNO_EMPTY,
|
||||
ConnectionId::from(&[]),
|
||||
Self::random_srt(),
|
||||
)
|
||||
}
|
||||
|
||||
fn token_equal(a: &[u8; 16], b: &[u8; 16]) -> bool {
|
||||
// rustc might decide to optimize this and make this non-constant-time
|
||||
// with respect to `t`, but it doesn't appear to currently.
|
||||
let mut c = 0;
|
||||
for (&a, &b) in a.iter().zip(b) {
|
||||
c |= a ^ b;
|
||||
}
|
||||
c == 0
|
||||
}
|
||||
|
||||
/// Determine whether this is a valid stateless reset.
|
||||
pub fn is_stateless_reset(&self, token: &[u8; 16]) -> bool {
|
||||
// A sequence number of 2^62 or more has no corresponding stateless reset token.
|
||||
(self.seqno < (1 << 62)) && Self::token_equal(&self.srt, token)
|
||||
}
|
||||
|
||||
/// Return true if the two contain any equal parts.
|
||||
fn any_part_equal(&self, other: &Self) -> bool {
|
||||
self.seqno == other.seqno || self.cid == other.cid || self.srt == other.srt
|
||||
}
|
||||
|
||||
/// The sequence number of this entry.
|
||||
pub fn sequence_number(&self) -> u64 {
|
||||
self.seqno
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdEntry<()> {
|
||||
/// Create an initial entry.
|
||||
pub fn initial_local(cid: ConnectionId) -> Self {
|
||||
Self::new(0, cid, ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SRT: Clone + PartialEq> ConnectionIdEntry<SRT> {
|
||||
pub fn new(seqno: u64, cid: ConnectionId, srt: SRT) -> Self {
|
||||
Self { seqno, cid, srt }
|
||||
}
|
||||
|
||||
/// Update the stateless reset token. This panics if the sequence number is non-zero.
|
||||
pub fn set_stateless_reset_token(&mut self, srt: SRT) {
|
||||
assert_eq!(self.seqno, CONNECTION_ID_SEQNO_INITIAL);
|
||||
self.srt = srt;
|
||||
}
|
||||
|
||||
/// Replace the connection ID. This panics if the sequence number is non-zero.
|
||||
pub fn update_cid(&mut self, cid: ConnectionId) {
|
||||
assert_eq!(self.seqno, CONNECTION_ID_SEQNO_INITIAL);
|
||||
self.cid = cid;
|
||||
}
|
||||
|
||||
pub fn connection_id(&self) -> &ConnectionId {
|
||||
&self.cid
|
||||
}
|
||||
}
|
||||
|
||||
pub type RemoteConnectionIdEntry = ConnectionIdEntry<[u8; 16]>;
|
||||
|
||||
/// A collection of connection IDs that are indexed by a sequence number.
|
||||
/// Used to store connection IDs that are provided by a peer.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ConnectionIdStore<SRT: Clone + PartialEq> {
|
||||
cids: SmallVec<[ConnectionIdEntry<SRT>; 8]>,
|
||||
}
|
||||
|
||||
impl<SRT: Clone + PartialEq> ConnectionIdStore<SRT> {
|
||||
pub fn retire(&mut self, seqno: u64) {
|
||||
self.cids.retain(|c| c.seqno != seqno);
|
||||
}
|
||||
|
||||
pub fn contains(&self, cid: &ConnectionIdRef) -> bool {
|
||||
self.cids.iter().any(|c| &c.cid == cid)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<ConnectionIdEntry<SRT>> {
|
||||
if self.cids.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.cids.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.cids.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdStore<[u8; 16]> {
|
||||
pub fn add_remote(&mut self, entry: ConnectionIdEntry<[u8; 16]>) -> Res<()> {
|
||||
// It's OK if this perfectly matches an existing entry.
|
||||
if self.cids.iter().any(|c| c == &entry) {
|
||||
return Ok(());
|
||||
}
|
||||
// It's not OK if any individual piece matches though.
|
||||
if self.cids.iter().any(|c| c.any_part_equal(&entry)) {
|
||||
qinfo!("ConnectionIdStore found reused part in NEW_CONNECTION_ID");
|
||||
return Err(Error::ProtocolViolation);
|
||||
}
|
||||
if self.cids.len() >= LOCAL_ACTIVE_CID_LIMIT {
|
||||
qinfo!("ConnectionIdStore received too many connection IDs");
|
||||
return Err(Error::ConnectionIdLimitExceeded);
|
||||
}
|
||||
|
||||
self.cids.push(entry);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdStore<()> {
|
||||
fn add_local(&mut self, entry: ConnectionIdEntry<()>) {
|
||||
self.cids.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectionIdDecoderRef<'a> {
|
||||
generator: Ref<'a, dyn ConnectionIdGenerator>,
|
||||
}
|
||||
|
||||
// Ideally this would be an implementation of `Deref`, but it doesn't
|
||||
// seem to be possible to convince the compiler to build anything useful.
|
||||
impl<'a: 'b, 'b> ConnectionIdDecoderRef<'a> {
|
||||
pub fn as_ref(&'a self) -> &'b dyn ConnectionIdDecoder {
|
||||
self.generator.as_decoder()
|
||||
}
|
||||
}
|
||||
|
||||
/// A connection ID manager looks after the generation of connection IDs,
|
||||
/// the set of connection IDs that are valid for the connection, and the
|
||||
/// generation of `NEW_CONNECTION_ID` frames.
|
||||
pub struct ConnectionIdManager {
|
||||
/// The `ConnectionIdGenerator` instance that is used to create connection IDs.
|
||||
generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
/// The connection IDs that we will accept.
|
||||
/// This includes any we advertise in `NEW_CONNECTION_ID` that haven't been bound to a path yet.
|
||||
/// During the handshake at the server, it also includes the randomized DCID pick by the client.
|
||||
connection_ids: ConnectionIdStore<()>,
|
||||
/// The maximum number of connection IDs this will accept. This is at least 2 and won't
|
||||
/// be more than `LOCAL_ACTIVE_CID_LIMIT`.
|
||||
limit: usize,
|
||||
/// The next sequence number that will be used for sending `NEW_CONNECTION_ID` frames.
|
||||
next_seqno: u64,
|
||||
/// Outstanding, but lost NEW_CONNECTION_ID frames will be stored here.
|
||||
lost_new_connection_id: Vec<ConnectionIdEntry<[u8; 16]>>,
|
||||
}
|
||||
|
||||
impl ConnectionIdManager {
|
||||
pub fn new(generator: Rc<RefCell<dyn ConnectionIdGenerator>>, initial: ConnectionId) -> Self {
|
||||
let mut connection_ids = ConnectionIdStore::default();
|
||||
connection_ids.add_local(ConnectionIdEntry::initial_local(initial));
|
||||
Self {
|
||||
generator,
|
||||
connection_ids,
|
||||
// A note about initializing the limit to 2.
|
||||
// For a server, the number of connection IDs that are tracked at the point that
|
||||
// it is first possible to send `NEW_CONNECTION_ID` is 2. One is the client-generated
|
||||
// destination connection (stored with a sequence number of `HANDSHAKE_SEQNO`); the
|
||||
// other being the handshake value (seqno 0). As a result, `NEW_CONNECTION_ID`
|
||||
// won't be sent until until after the handshake completes, because this initial
|
||||
// value remains until the connection completes and transport parameters are handled.
|
||||
limit: 2,
|
||||
next_seqno: 2, // A different value.
|
||||
lost_new_connection_id: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decoder(&self) -> ConnectionIdDecoderRef {
|
||||
ConnectionIdDecoderRef {
|
||||
generator: self.generator.deref().borrow(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a connection ID and stateless reset token for a preferred address.
|
||||
pub fn preferred_address_cid(&mut self) -> Res<(ConnectionId, [u8; 16])> {
|
||||
if self.generator.deref().borrow().generates_empty_cids() {
|
||||
return Err(Error::ConnectionIdsExhausted);
|
||||
}
|
||||
if let Some(cid) = self.generator.borrow_mut().generate_cid() {
|
||||
assert_ne!(cid.len(), 0);
|
||||
self.connection_ids.add_local(ConnectionIdEntry::new(
|
||||
CONNECTION_ID_SEQNO_PREFERRED,
|
||||
cid.clone(),
|
||||
(),
|
||||
));
|
||||
|
||||
let srt = <[u8; 16]>::try_from(&random(16)[..]).unwrap();
|
||||
Ok((cid, srt))
|
||||
} else {
|
||||
Err(Error::ConnectionIdsExhausted)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self, cid: &ConnectionIdRef) -> bool {
|
||||
self.connection_ids.contains(cid)
|
||||
}
|
||||
|
||||
pub fn retire(&mut self, seqno: u64) {
|
||||
// TODO(mt) - consider keeping connection IDs around for a short while.
|
||||
|
||||
self.connection_ids.retire(seqno);
|
||||
self.lost_new_connection_id.retain(|cid| cid.seqno != seqno);
|
||||
}
|
||||
|
||||
/// During the handshake, a server needs to regard the client's choice of destination
|
||||
/// connection ID as valid. This function saves it in the store in a special place.
|
||||
pub fn add_odcid(&mut self, cid: ConnectionId) {
|
||||
let entry = ConnectionIdEntry::new(CONNECTION_ID_SEQNO_ODCID, cid, ());
|
||||
self.connection_ids.add_local(entry);
|
||||
}
|
||||
|
||||
/// Stop treating the original destination connection ID as valid.
|
||||
pub fn remove_odcid(&mut self) {
|
||||
self.connection_ids.retire(CONNECTION_ID_SEQNO_ODCID);
|
||||
}
|
||||
|
||||
pub fn set_limit(&mut self, limit: u64) {
|
||||
debug_assert!(limit >= 2);
|
||||
self.limit = min(
|
||||
LOCAL_ACTIVE_CID_LIMIT,
|
||||
usize::try_from(limit).unwrap_or(LOCAL_ACTIVE_CID_LIMIT),
|
||||
);
|
||||
}
|
||||
|
||||
fn write_entry(
|
||||
&mut self,
|
||||
entry: &ConnectionIdEntry<[u8; 16]>,
|
||||
builder: &mut PacketBuilder,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<bool> {
|
||||
let len = 1 + Encoder::varint_len(entry.seqno) + 1 + 1 + entry.cid.len() + 16;
|
||||
if builder.remaining() < len {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
builder.encode_varint(FRAME_TYPE_NEW_CONNECTION_ID);
|
||||
builder.encode_varint(entry.seqno);
|
||||
builder.encode_varint(0u64);
|
||||
builder.encode_vec(1, &entry.cid);
|
||||
builder.encode(&entry.srt);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(8));
|
||||
}
|
||||
|
||||
stats.new_connection_id += 1;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn write_frames(
|
||||
&mut self,
|
||||
builder: &mut PacketBuilder,
|
||||
tokens: &mut Vec<RecoveryToken>,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<()> {
|
||||
if self.generator.deref().borrow().generates_empty_cids() {
|
||||
debug_assert_eq!(self.generator.borrow_mut().generate_cid().unwrap().len(), 0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
while let Some(entry) = self.lost_new_connection_id.pop() {
|
||||
if self.write_entry(&entry, builder, stats)? {
|
||||
tokens.push(RecoveryToken::NewConnectionId(entry));
|
||||
} else {
|
||||
// This shouldn't happen often.
|
||||
self.lost_new_connection_id.push(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep writing while we have fewer than the limit of active connection IDs
|
||||
// and while there is room for more. This uses the longest connection ID
|
||||
// length to simplify (assuming Retire Prior To is just 1 byte).
|
||||
while self.connection_ids.len() < self.limit && builder.remaining() >= 47 {
|
||||
let maybe_cid = self.generator.borrow_mut().generate_cid();
|
||||
if let Some(cid) = maybe_cid {
|
||||
assert_ne!(cid.len(), 0);
|
||||
// TODO: generate the stateless reset tokens from the connection ID and a key.
|
||||
let srt = <[u8; 16]>::try_from(&random(16)[..]).unwrap();
|
||||
|
||||
let seqno = self.next_seqno;
|
||||
self.next_seqno += 1;
|
||||
self.connection_ids
|
||||
.add_local(ConnectionIdEntry::new(seqno, cid.clone(), ()));
|
||||
|
||||
let entry = ConnectionIdEntry::new(seqno, cid, srt);
|
||||
debug_assert!(self.write_entry(&entry, builder, stats)?);
|
||||
tokens.push(RecoveryToken::NewConnectionId(entry));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lost(&mut self, entry: &ConnectionIdEntry<[u8; 16]>) {
|
||||
self.lost_new_connection_id.push(entry.clone());
|
||||
}
|
||||
|
||||
pub fn acked(&mut self, entry: &ConnectionIdEntry<[u8; 16]>) {
|
||||
self.lost_new_connection_id
|
||||
.retain(|e| e.seqno != entry.seqno);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,33 +4,19 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::stream_id::{StreamIndex, StreamType};
|
||||
use crate::tparams::PreferredAddress;
|
||||
use crate::{CongestionControlAlgorithm, QuicVersion};
|
||||
|
||||
const LOCAL_STREAM_LIMIT_BIDI: StreamIndex = StreamIndex::new(16);
|
||||
const LOCAL_STREAM_LIMIT_UNI: StreamIndex = StreamIndex::new(16);
|
||||
|
||||
/// What to do with preferred addresses.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PreferredAddressConfig {
|
||||
/// Disabled, whether for client or server.
|
||||
Disabled,
|
||||
/// Enabled at a client, disabled at a server.
|
||||
Default,
|
||||
/// Enabled at both client and server.
|
||||
Address(PreferredAddress),
|
||||
}
|
||||
use crate::frame::StreamType;
|
||||
use crate::{
|
||||
CongestionControlAlgorithm, QuicVersion, LOCAL_STREAM_LIMIT_BIDI, LOCAL_STREAM_LIMIT_UNI,
|
||||
};
|
||||
|
||||
/// ConnectionParameters use for setting intitial value for QUIC parameters.
|
||||
/// This collect like initial limits, protocol version and congestion control.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectionParameters {
|
||||
quic_version: QuicVersion,
|
||||
cc_algorithm: CongestionControlAlgorithm,
|
||||
max_streams_bidi: StreamIndex,
|
||||
max_streams_uni: StreamIndex,
|
||||
preferred_address: PreferredAddressConfig,
|
||||
max_streams_bidi: u64,
|
||||
max_streams_uni: u64,
|
||||
}
|
||||
|
||||
impl Default for ConnectionParameters {
|
||||
|
@ -40,7 +26,6 @@ impl Default for ConnectionParameters {
|
|||
cc_algorithm: CongestionControlAlgorithm::NewReno,
|
||||
max_streams_bidi: LOCAL_STREAM_LIMIT_BIDI,
|
||||
max_streams_uni: LOCAL_STREAM_LIMIT_UNI,
|
||||
preferred_address: PreferredAddressConfig::Default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,15 +49,15 @@ impl ConnectionParameters {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn get_max_streams(&self, stream_type: StreamType) -> StreamIndex {
|
||||
pub fn get_max_streams(&self, stream_type: StreamType) -> u64 {
|
||||
match stream_type {
|
||||
StreamType::BiDi => self.max_streams_bidi,
|
||||
StreamType::UniDi => self.max_streams_uni,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_streams(mut self, stream_type: StreamType, v: StreamIndex) -> Self {
|
||||
assert!(v.as_u64() <= (1 << 60), "max_streams is too large");
|
||||
pub fn max_streams(mut self, stream_type: StreamType, v: u64) -> Self {
|
||||
assert!(v <= (1 << 60), "max_streams's parameter too big");
|
||||
match stream_type {
|
||||
StreamType::BiDi => {
|
||||
self.max_streams_bidi = v;
|
||||
|
@ -83,20 +68,4 @@ impl ConnectionParameters {
|
|||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a preferred address (which only has an effect for a server).
|
||||
pub fn preferred_address(mut self, preferred: PreferredAddress) -> Self {
|
||||
self.preferred_address = PreferredAddressConfig::Address(preferred);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable the use of preferred addresses.
|
||||
pub fn disable_preferred_address(mut self) -> Self {
|
||||
self.preferred_address = PreferredAddressConfig::Disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_preferred_address(&self) -> &PreferredAddressConfig {
|
||||
&self.preferred_address
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,20 +4,14 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use neqo_common::Encoder;
|
||||
use std::cmp::{min, Ordering};
|
||||
use std::cmp::Ordering;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::frame::{
|
||||
FrameType, FRAME_TYPE_CONNECTION_CLOSE_APPLICATION, FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT,
|
||||
FRAME_TYPE_HANDSHAKE_DONE,
|
||||
};
|
||||
use crate::frame::{Frame, FrameType};
|
||||
use crate::packet::PacketBuilder;
|
||||
use crate::path::PathRef;
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::{ConnectionError, Error, Res};
|
||||
use crate::{CloseError, ConnectionError};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
/// The state of the Connection.
|
||||
|
@ -52,8 +46,28 @@ impl State {
|
|||
|
||||
// Implement `PartialOrd` so that we can enforce monotonic state progression.
|
||||
impl PartialOrd for State {
|
||||
#[allow(clippy::match_same_arms)] // Lint bug: rust-lang/rust-clippy#860
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
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!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +76,6 @@ impl Ord for State {
|
|||
if mem::discriminant(self) == mem::discriminant(other) {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
#[allow(clippy::match_same_arms)] // Lint bug: rust-lang/rust-clippy#860
|
||||
match (self, other) {
|
||||
(Self::Init, _) => Ordering::Less,
|
||||
(_, Self::Init) => Ordering::Greater,
|
||||
|
@ -83,77 +96,7 @@ impl Ord for State {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClosingFrame {
|
||||
path: PathRef,
|
||||
error: ConnectionError,
|
||||
frame_type: FrameType,
|
||||
reason_phrase: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ClosingFrame {
|
||||
fn new(
|
||||
path: PathRef,
|
||||
error: ConnectionError,
|
||||
frame_type: FrameType,
|
||||
message: impl AsRef<str>,
|
||||
) -> Self {
|
||||
let reason_phrase = message.as_ref().as_bytes().to_vec();
|
||||
Self {
|
||||
path,
|
||||
error,
|
||||
frame_type,
|
||||
reason_phrase,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &PathRef {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn sanitize(&self) -> Option<Self> {
|
||||
if let ConnectionError::Application(_) = self.error {
|
||||
// The default CONNECTION_CLOSE frame that is sent when an application
|
||||
// error code needs to be sent in an Initial or Handshake packet.
|
||||
Some(Self {
|
||||
path: Rc::clone(&self.path),
|
||||
error: ConnectionError::Transport(Error::ApplicationError),
|
||||
frame_type: 0,
|
||||
reason_phrase: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_frame(&self, builder: &mut PacketBuilder) {
|
||||
// Allow 8 bytes for the reason phrase to ensure that if it needs to be
|
||||
// truncated there is still at least a few bytes of the value.
|
||||
if builder.remaining() < 1 + 8 + 8 + 2 + 8 {
|
||||
return;
|
||||
}
|
||||
match &self.error {
|
||||
ConnectionError::Transport(e) => {
|
||||
builder.encode_varint(FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT);
|
||||
builder.encode_varint(e.code());
|
||||
builder.encode_varint(self.frame_type);
|
||||
}
|
||||
ConnectionError::Application(code) => {
|
||||
builder.encode_varint(FRAME_TYPE_CONNECTION_CLOSE_APPLICATION);
|
||||
builder.encode_varint(*code);
|
||||
}
|
||||
}
|
||||
// Truncate the reason phrase if it doesn't fit. As we send this frame in
|
||||
// multiple packet number spaces, limit the overall size to 256.
|
||||
let available = min(256, builder.remaining());
|
||||
let reason = if available < Encoder::vvec_len(self.reason_phrase.len()) {
|
||||
&self.reason_phrase[..available - 2]
|
||||
} else {
|
||||
&self.reason_phrase
|
||||
};
|
||||
builder.encode_vvec(reason);
|
||||
}
|
||||
}
|
||||
type ClosingFrame = Frame<'static>;
|
||||
|
||||
/// `StateSignaling` manages whether we need to send HANDSHAKE_DONE and CONNECTION_CLOSE.
|
||||
/// Valid state transitions are:
|
||||
|
@ -163,7 +106,7 @@ impl ClosingFrame {
|
|||
/// * 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)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StateSignaling {
|
||||
Idle,
|
||||
HandshakeDone,
|
||||
|
@ -178,47 +121,55 @@ pub enum StateSignaling {
|
|||
|
||||
impl StateSignaling {
|
||||
pub fn handshake_done(&mut self) {
|
||||
if !matches!(self, Self::Idle) {
|
||||
if *self != Self::Idle {
|
||||
debug_assert!(false, "StateSignaling must be in Idle state.");
|
||||
return;
|
||||
}
|
||||
*self = Self::HandshakeDone
|
||||
}
|
||||
|
||||
pub fn write_done(&mut self, builder: &mut PacketBuilder) -> Res<Option<RecoveryToken>> {
|
||||
if matches!(self, Self::HandshakeDone) && builder.remaining() >= 1 {
|
||||
pub fn write_done(&mut self, builder: &mut PacketBuilder) -> Option<RecoveryToken> {
|
||||
if *self == Self::HandshakeDone && builder.remaining() >= 1 {
|
||||
*self = Self::Idle;
|
||||
builder.encode_varint(FRAME_TYPE_HANDSHAKE_DONE);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(14));
|
||||
}
|
||||
Ok(Some(RecoveryToken::HandshakeDone))
|
||||
builder.encode_varint(Frame::HandshakeDone.get_type());
|
||||
Some(RecoveryToken::HandshakeDone)
|
||||
} else {
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn make_close_frame(
|
||||
error: ConnectionError,
|
||||
frame_type: FrameType,
|
||||
message: impl AsRef<str>,
|
||||
) -> ClosingFrame {
|
||||
let reason_phrase = message.as_ref().as_bytes().to_owned();
|
||||
Frame::ConnectionClose {
|
||||
error_code: CloseError::from(error),
|
||||
frame_type,
|
||||
reason_phrase,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(
|
||||
&mut self,
|
||||
path: PathRef,
|
||||
error: ConnectionError,
|
||||
frame_type: FrameType,
|
||||
message: impl AsRef<str>,
|
||||
) {
|
||||
if !matches!(self, Self::Reset) {
|
||||
*self = Self::Closing(ClosingFrame::new(path, error, frame_type, message));
|
||||
if *self != Self::Reset {
|
||||
*self = Self::Closing(Self::make_close_frame(error, frame_type, message));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain(
|
||||
&mut self,
|
||||
path: PathRef,
|
||||
error: ConnectionError,
|
||||
frame_type: FrameType,
|
||||
message: impl AsRef<str>,
|
||||
) {
|
||||
if !matches!(self, Self::Reset) {
|
||||
*self = Self::Draining(ClosingFrame::new(path, error, frame_type, message));
|
||||
if *self != Self::Reset {
|
||||
*self = Self::Draining(Self::make_close_frame(error, frame_type, message));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,15 +178,15 @@ impl StateSignaling {
|
|||
match self {
|
||||
Self::Closing(frame) => {
|
||||
// When we are closing, we might need to send the close frame again.
|
||||
let res = Some(frame.clone());
|
||||
let frame = mem::replace(frame, Frame::Padding);
|
||||
*self = Self::CloseSent(Some(frame.clone()));
|
||||
res
|
||||
Some(frame)
|
||||
}
|
||||
Self::Draining(frame) => {
|
||||
// When we are draining, just send once.
|
||||
let res = Some(frame.clone());
|
||||
let frame = mem::replace(frame, Frame::Padding);
|
||||
*self = Self::CloseSent(None);
|
||||
res
|
||||
Some(frame)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
@ -244,7 +195,8 @@ impl StateSignaling {
|
|||
/// 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 {
|
||||
*self = Self::Closing(frame.clone());
|
||||
let frame = mem::replace(frame, Frame::Padding);
|
||||
*self = Self::Closing(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,20 +6,22 @@
|
|||
|
||||
use super::super::{Connection, Output};
|
||||
use super::{
|
||||
assert_full_cwnd, connect_rtt_idle, cwnd_packets, default_client, default_server, fill_cwnd,
|
||||
send_something, AT_LEAST_PTO, DEFAULT_RTT, FORCE_IDLE_CLIENT_1RTT_PACKETS, POST_HANDSHAKE_CWND,
|
||||
assert_full_cwnd, connect_force_idle, connect_rtt_idle, cwnd_packets, default_client,
|
||||
default_server, fill_cwnd, send_something, AT_LEAST_PTO, DEFAULT_RTT, POST_HANDSHAKE_CWND,
|
||||
};
|
||||
use crate::cc::{CWND_MIN, MAX_DATAGRAM_SIZE};
|
||||
use crate::frame::StreamType;
|
||||
use crate::packet::PacketNumber;
|
||||
use crate::recovery::{ACK_ONLY_SIZE_LIMIT, PACKET_THRESHOLD};
|
||||
use crate::sender::PACING_BURST_SIZE;
|
||||
use crate::stats::MAX_PTO_COUNTS;
|
||||
use crate::stream_id::StreamType;
|
||||
use crate::tparams::{self, TransportParameter};
|
||||
use crate::tracking::MAX_UNACKED_PKTS;
|
||||
|
||||
use neqo_common::{qdebug, qinfo, qtrace, Datagram};
|
||||
use std::convert::TryFrom;
|
||||
use std::time::{Duration, Instant};
|
||||
use test_fixture::{self, now};
|
||||
|
||||
fn induce_persistent_congestion(
|
||||
client: &mut Connection,
|
||||
|
@ -109,11 +111,18 @@ where
|
|||
fn cc_slow_start() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
|
||||
server
|
||||
.set_local_tparam(
|
||||
tparams::INITIAL_MAX_DATA,
|
||||
TransportParameter::Integer(65536),
|
||||
)
|
||||
.unwrap();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
|
||||
// Try to send a lot of data
|
||||
let stream_id = client.stream_create(StreamType::UniDi).unwrap();
|
||||
let (c_tx_dgrams, _) = fill_cwnd(&mut client, stream_id, now);
|
||||
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);
|
||||
}
|
||||
|
@ -132,10 +141,9 @@ fn cc_slow_start_to_cong_avoidance_recovery_period() {
|
|||
let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, 0, now);
|
||||
assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND);
|
||||
// Predict the packet number of the last packet sent.
|
||||
// We have already sent packets in `connect_rtt_idle`,
|
||||
// so include a fudge factor.
|
||||
let flight1_largest =
|
||||
PacketNumber::try_from(c_tx_dgrams.len() + FORCE_IDLE_CLIENT_1RTT_PACKETS).unwrap();
|
||||
// We have already sent one packet in `connect_force_idle` (an ACK),
|
||||
// so this will be equal to the number of packets in this flight.
|
||||
let flight1_largest = PacketNumber::try_from(c_tx_dgrams.len()).unwrap();
|
||||
|
||||
// Server: Receive and generate ack
|
||||
now += DEFAULT_RTT / 2;
|
||||
|
@ -186,13 +194,13 @@ fn cc_slow_start_to_cong_avoidance_recovery_period() {
|
|||
fn cc_cong_avoidance_recovery_period_unchanged() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
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);
|
||||
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.
|
||||
|
@ -228,31 +236,31 @@ fn cc_cong_avoidance_recovery_period_unchanged() {
|
|||
fn single_packet_on_recovery() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
// Drop a few packets, up to the reordering threshold.
|
||||
for _ in 0..PACKET_THRESHOLD {
|
||||
let _dropped = send_something(&mut client, now);
|
||||
let _dropped = send_something(&mut client, now());
|
||||
}
|
||||
let delivered = send_something(&mut client, now);
|
||||
let delivered = send_something(&mut client, now());
|
||||
|
||||
// Now fill the congestion window.
|
||||
assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
|
||||
let (_, now) = fill_cwnd(&mut client, 0, now);
|
||||
let _ = fill_cwnd(&mut client, 0, now());
|
||||
assert!(client.loss_recovery.cwnd_avail() < ACK_ONLY_SIZE_LIMIT);
|
||||
|
||||
// Acknowledge just one packet and cause one packet to be declared lost.
|
||||
// The length is the amount of credit the client should have.
|
||||
let ack = server.process(Some(delivered), now).dgram();
|
||||
let ack = server.process(Some(delivered), now()).dgram();
|
||||
assert!(ack.is_some());
|
||||
|
||||
// The client should see the loss and enter recovery.
|
||||
// As there are many outstanding packets, there should be no available cwnd.
|
||||
client.process_input(ack.unwrap(), now);
|
||||
client.process_input(ack.unwrap(), now());
|
||||
assert_eq!(client.loss_recovery.cwnd_avail(), 0);
|
||||
|
||||
// The client should send one packet, ignoring the cwnd.
|
||||
let dgram = client.process_output(now).dgram();
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
}
|
||||
|
||||
|
@ -367,13 +375,13 @@ fn cc_slow_start_to_persistent_congestion_no_acks() {
|
|||
fn cc_slow_start_to_persistent_congestion_some_acks() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
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);
|
||||
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
|
||||
|
@ -398,13 +406,13 @@ fn cc_slow_start_to_persistent_congestion_some_acks() {
|
|||
fn cc_persistent_congestion_to_slow_start() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
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);
|
||||
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
|
||||
|
@ -442,13 +450,13 @@ fn cc_persistent_congestion_to_slow_start() {
|
|||
fn ack_are_not_cc() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
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);
|
||||
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
|
||||
|
@ -477,10 +485,11 @@ fn ack_are_not_cc() {
|
|||
|
||||
#[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, DEFAULT_RTT);
|
||||
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();
|
||||
|
@ -492,32 +501,24 @@ fn pace() {
|
|||
}
|
||||
let mut count = 0;
|
||||
// We should get a burst at first.
|
||||
// The first packet is not subject to pacing as there are no bytes in flight.
|
||||
// After that we allow the burst to continue up to a number of packets (2).
|
||||
for _ in 0..=PACING_BURST_SIZE {
|
||||
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 (1 + PACING_BURST_SIZE)..cwnd_packets(POST_HANDSHAKE_CWND) {
|
||||
match client.process_output(now) {
|
||||
Output::Callback(t) => assert_eq!(t, gap),
|
||||
Output::Datagram(_) => {
|
||||
// The last packet might not be paced.
|
||||
count += 1;
|
||||
break;
|
||||
}
|
||||
Output::None => panic!(),
|
||||
}
|
||||
// The last one will not be paced.
|
||||
for _ in PACING_BURST_SIZE..cwnd_packets(POST_HANDSHAKE_CWND) - 1 {
|
||||
assert_eq!(client.process_output(now).callback(), gap);
|
||||
now += gap;
|
||||
let dgram = client.process_output(now).dgram();
|
||||
assert!(dgram.is_some());
|
||||
count += 1;
|
||||
}
|
||||
let dgram = client.process_output(now).dgram();
|
||||
assert!(dgram.is_none());
|
||||
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));
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{AppError, ConnectionError, Error, ERROR_APPLICATION_CLOSE};
|
|||
|
||||
use neqo_common::Datagram;
|
||||
use std::time::Duration;
|
||||
use test_fixture::{self, addr, now};
|
||||
use test_fixture::{self, loopback, now};
|
||||
|
||||
fn assert_draining(c: &Connection, expected: &Error) {
|
||||
assert!(c.state().closed());
|
||||
|
@ -201,6 +201,6 @@ fn stateless_reset_client() {
|
|||
.unwrap();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
client.process_input(Datagram::new(addr(), addr(), vec![77; 21]), now());
|
||||
client.process_input(Datagram::new(loopback(), loopback(), vec![77; 21]), now());
|
||||
assert_draining(&client, &Error::StatelessReset);
|
||||
}
|
||||
|
|
|
@ -4,25 +4,24 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::super::{Connection, Output, State, LOCAL_IDLE_TIMEOUT};
|
||||
use super::super::{Connection, FixedConnectionIdManager, Output, State, LOCAL_IDLE_TIMEOUT};
|
||||
use super::{
|
||||
assert_error, connect_force_idle, connect_with_rtt, default_client, default_server, get_tokens,
|
||||
handshake, maybe_authenticate, send_something, CountingConnectionIdGenerator, AT_LEAST_PTO,
|
||||
DEFAULT_RTT, DEFAULT_STREAM_DATA,
|
||||
handshake, maybe_authenticate, send_something, AT_LEAST_PTO, DEFAULT_RTT, DEFAULT_STREAM_DATA,
|
||||
};
|
||||
use crate::connection::AddressValidation;
|
||||
use crate::events::ConnectionEvent;
|
||||
use crate::frame::StreamType;
|
||||
use crate::path::PATH_MTU_V6;
|
||||
use crate::server::ValidateAddress;
|
||||
use crate::tparams::TransportParameter;
|
||||
use crate::{ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error, StreamType};
|
||||
use crate::{ConnectionError, ConnectionParameters, Error};
|
||||
|
||||
use neqo_common::{event::Provider, qdebug, Datagram};
|
||||
use neqo_crypto::{constants::TLS_CHACHA20_POLY1305_SHA256, AuthenticationStatus};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use test_fixture::{self, addr, assertions, fixture_init, now, split_datagram};
|
||||
use test_fixture::{self, assertions, fixture_init, loopback, now, split_datagram};
|
||||
|
||||
#[test]
|
||||
fn full_handshake() {
|
||||
|
@ -104,10 +103,10 @@ fn no_alpn() {
|
|||
let mut client = Connection::new_client(
|
||||
"example.com",
|
||||
&["bad-alpn"],
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
ConnectionParameters::default(),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(9))),
|
||||
loopback(),
|
||||
loopback(),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut server = default_server();
|
||||
|
@ -177,8 +176,8 @@ fn crypto_frame_split() {
|
|||
let mut server = Connection::new_server(
|
||||
test_fixture::LONG_CERT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default(),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(6))),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.expect("create a server");
|
||||
|
||||
|
@ -232,10 +231,10 @@ fn chacha20poly1305() {
|
|||
let mut client = Connection::new_client(
|
||||
test_fixture::DEFAULT_SERVER_NAME,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
ConnectionParameters::default(),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(0))),
|
||||
loopback(),
|
||||
loopback(),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.expect("create a default client");
|
||||
client.set_ciphers(&[TLS_CHACHA20_POLY1305_SHA256]).unwrap();
|
||||
|
@ -552,9 +551,8 @@ fn reorder_1rtt() {
|
|||
now += RTT / 2;
|
||||
let s2 = server.process(c2, now).dgram();
|
||||
// The server has now received those packets, and saved them.
|
||||
// The three additional are: an Initial ACK, a Handshake,
|
||||
// and a 1-RTT (containing NEW_CONNECTION_ID).
|
||||
assert_eq!(server.stats().packets_rx, PACKETS * 2 + 5);
|
||||
// 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);
|
||||
|
@ -620,8 +618,8 @@ fn verify_pkt_honors_mtu() {
|
|||
assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
|
||||
|
||||
// Try to send a large stream and verify first packet is correctly sized
|
||||
let stream_id = client.stream_create(StreamType::UniDi).unwrap();
|
||||
assert_eq!(client.stream_send(stream_id, &[0xbb; 2000]).unwrap(), 2000);
|
||||
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);
|
||||
|
@ -697,52 +695,3 @@ fn extra_initial_invalid_cid() {
|
|||
let nothing = client.process(Some(dgram_copy), now).dgram();
|
||||
assert!(nothing.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anti_amplification() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let mut now = now();
|
||||
|
||||
// With a gigantic transport parameter, the server is unable to complete
|
||||
// the handshake within the amplification limit.
|
||||
let very_big = TransportParameter::Bytes(vec![0; PATH_MTU_V6 * 3]);
|
||||
server.set_local_tparam(0xce16, very_big).unwrap();
|
||||
|
||||
let c_init = client.process_output(now).dgram();
|
||||
now += DEFAULT_RTT / 2;
|
||||
let s_init1 = server.process(c_init, now).dgram().unwrap();
|
||||
assert_eq!(s_init1.len(), PATH_MTU_V6);
|
||||
let s_init2 = server.process_output(now).dgram().unwrap();
|
||||
assert_eq!(s_init2.len(), PATH_MTU_V6);
|
||||
let s_init3 = server.process_output(now).dgram().unwrap();
|
||||
assert_eq!(s_init3.len(), PATH_MTU_V6);
|
||||
let cb = server.process_output(now).callback();
|
||||
assert_ne!(cb, Duration::new(0, 0));
|
||||
|
||||
now += DEFAULT_RTT / 2;
|
||||
client.process_input(s_init1, now);
|
||||
client.process_input(s_init2, now);
|
||||
let ack_count = client.stats().frame_tx.ack;
|
||||
let frame_count = client.stats().frame_tx.all;
|
||||
let ack = client.process(Some(s_init3), now).dgram().unwrap();
|
||||
assert!(!maybe_authenticate(&mut client)); // No need yet.
|
||||
|
||||
// The client sends a padded datagram, with just ACK for Initial + Handshake.
|
||||
assert_eq!(client.stats().frame_tx.ack, ack_count + 2);
|
||||
assert_eq!(client.stats().frame_tx.all, frame_count + 2);
|
||||
assert_ne!(ack.len(), PATH_MTU_V6); // Not padded (it includes Handshake).
|
||||
|
||||
now += DEFAULT_RTT / 2;
|
||||
let remainder = server.process(Some(ack), now).dgram();
|
||||
|
||||
now += DEFAULT_RTT / 2;
|
||||
client.process_input(remainder.unwrap(), now);
|
||||
assert!(maybe_authenticate(&mut client)); // OK, we have all of it.
|
||||
let fin = client.process_output(now).dgram();
|
||||
assert_eq!(*client.state(), State::Connected);
|
||||
|
||||
now += DEFAULT_RTT / 2;
|
||||
server.process_input(fin.unwrap(), now);
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ use super::{
|
|||
connect, connect_force_idle, connect_with_rtt, default_client, default_server,
|
||||
maybe_authenticate, send_something, AT_LEAST_PTO,
|
||||
};
|
||||
use crate::frame::StreamType;
|
||||
use crate::packet::PacketBuilder;
|
||||
use crate::tparams::{self, TransportParameter};
|
||||
use crate::tracking::PNSpace;
|
||||
use crate::StreamType;
|
||||
|
||||
use neqo_common::Encoder;
|
||||
use std::time::Duration;
|
||||
|
@ -58,19 +58,13 @@ fn asymmetric_idle_timeout() {
|
|||
server.idle_timeout = IdleTimeout::new(LOWER_TIMEOUT);
|
||||
|
||||
// Now connect and force idleness manually.
|
||||
// We do that by following what `force_idle` does and have each endpoint
|
||||
// send two packets, which are delivered out of order. See `force_idle`.
|
||||
connect(&mut client, &mut server);
|
||||
let c1 = send_something(&mut client, now());
|
||||
let c2 = send_something(&mut client, now());
|
||||
server.process_input(c2, now());
|
||||
server.process_input(c1, now());
|
||||
let s1 = send_something(&mut server, now());
|
||||
let s2 = send_something(&mut server, now());
|
||||
client.process_input(s2, now());
|
||||
let ack = client.process(Some(s1), now()).dgram();
|
||||
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 both should have received ACK frames so should be idle.
|
||||
// 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));
|
||||
}
|
||||
|
@ -97,16 +91,11 @@ fn tiny_idle_timeout() {
|
|||
|
||||
// Now connect with an RTT and force idleness manually.
|
||||
let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT);
|
||||
let c1 = send_something(&mut client, now);
|
||||
let c2 = send_something(&mut client, now);
|
||||
let p1 = send_something(&mut server, now);
|
||||
let p2 = send_something(&mut server, now);
|
||||
now += RTT / 2;
|
||||
server.process_input(c2, now);
|
||||
server.process_input(c1, now);
|
||||
let s1 = send_something(&mut server, now);
|
||||
let s2 = send_something(&mut server, now);
|
||||
now += RTT / 2;
|
||||
client.process_input(s2, now);
|
||||
let ack = client.process(Some(s1), now).dgram();
|
||||
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.
|
||||
|
@ -136,8 +125,8 @@ fn idle_send_packet1() {
|
|||
let res = client.process(None, now);
|
||||
assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
|
||||
|
||||
let stream_id = client.stream_create(StreamType::UniDi).unwrap();
|
||||
assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 5);
|
||||
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));
|
||||
|
@ -167,12 +156,12 @@ fn idle_send_packet2() {
|
|||
let res = client.process(None, now);
|
||||
assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
|
||||
|
||||
let stream_id = client.stream_create(StreamType::UniDi).unwrap();
|
||||
assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 5);
|
||||
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(stream_id, b"there").unwrap(), 5);
|
||||
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.
|
||||
|
@ -253,14 +242,12 @@ fn idle_caching() {
|
|||
let crypto = server
|
||||
.crypto
|
||||
.streams
|
||||
.write_frame(PNSpace::Initial, &mut builder)
|
||||
.unwrap();
|
||||
.write_frame(PNSpace::Initial, &mut builder);
|
||||
assert!(crypto.is_some());
|
||||
let crypto = server
|
||||
.crypto
|
||||
.streams
|
||||
.write_frame(PNSpace::Initial, &mut builder)
|
||||
.unwrap();
|
||||
.write_frame(PNSpace::Initial, &mut builder);
|
||||
assert!(crypto.is_none());
|
||||
let dgram = server.process_output(middle).dgram();
|
||||
|
||||
|
@ -277,7 +264,6 @@ fn idle_caching() {
|
|||
// Now let the server Initial through, with the CRYPTO frame.
|
||||
let dgram = server.process_output(end).dgram();
|
||||
let (initial, _) = split_datagram(&dgram.unwrap());
|
||||
neqo_common::qwarn!("client ingests initial, finally");
|
||||
let _ = client.process(Some(initial), end);
|
||||
maybe_authenticate(&mut client);
|
||||
let dgram = client.process_output(end).dgram();
|
||||
|
|
|
@ -1,741 +0,0 @@
|
|||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::super::{Connection, Output, State, StreamType};
|
||||
use super::{
|
||||
connect_fail, connect_force_idle, default_client, default_server, maybe_authenticate,
|
||||
new_client, new_server, send_something,
|
||||
};
|
||||
use crate::path::{PATH_MTU_V4, PATH_MTU_V6};
|
||||
use crate::tparams::{self, PreferredAddress, TransportParameter};
|
||||
use crate::{ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error};
|
||||
|
||||
use neqo_common::Datagram;
|
||||
use std::cell::RefCell;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use test_fixture::{self, addr, fixture_init, now};
|
||||
|
||||
/// This should be a valid-seeming transport parameter.
|
||||
/// And it should have different values to `addr` and `addr_v4`.
|
||||
const SAMPLE_PREFERRED_ADDRESS: &[u8] = &[
|
||||
0xc0, 0x00, 0x02, 0x02, 0x01, 0xbb, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0xbb, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
];
|
||||
|
||||
// These tests generally use two paths:
|
||||
// The connection is established on a path with the same IPv6 address on both ends.
|
||||
// Migrations move to a path with the same IPv4 address on both ends.
|
||||
// This simplifies validation as the same assertions can be used for client and server.
|
||||
// The risk is that there is a place where source/destination local/remote is inverted.
|
||||
fn addr_v4() -> SocketAddr {
|
||||
let localhost_v4 = IpAddr::V4(Ipv4Addr::from(0xc000_0201));
|
||||
SocketAddr::new(localhost_v4, addr().port())
|
||||
}
|
||||
|
||||
fn loopback() -> SocketAddr {
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::from(1)), 443)
|
||||
}
|
||||
|
||||
fn change_path(d: &Datagram, a: SocketAddr) -> Datagram {
|
||||
Datagram::new(a, a, &d[..])
|
||||
}
|
||||
|
||||
fn new_port(a: SocketAddr) -> SocketAddr {
|
||||
let (port, _) = a.port().overflowing_add(410);
|
||||
SocketAddr::new(a.ip(), port)
|
||||
}
|
||||
|
||||
fn change_source_port(d: &Datagram) -> Datagram {
|
||||
Datagram::new(new_port(d.source()), d.destination(), &d[..])
|
||||
}
|
||||
|
||||
fn assert_path(dgram: &Datagram, path_addr: SocketAddr) {
|
||||
assert_eq!(dgram.source(), path_addr);
|
||||
assert_eq!(dgram.destination(), path_addr);
|
||||
}
|
||||
|
||||
fn assert_v4_path(dgram: &Datagram, padded: bool) {
|
||||
assert_path(dgram, addr_v4());
|
||||
if padded {
|
||||
assert_eq!(dgram.len(), PATH_MTU_V4);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_v6_path(dgram: &Datagram, padded: bool) {
|
||||
assert_path(dgram, addr());
|
||||
if padded {
|
||||
assert_eq!(dgram.len(), PATH_MTU_V6);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebinding_port() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
let dgram = send_something(&mut client, now());
|
||||
let dgram = change_source_port(&dgram);
|
||||
|
||||
server.process_input(dgram, now());
|
||||
// Have the server send something so that it generates a packet.
|
||||
let stream_id = server.stream_create(StreamType::UniDi).unwrap();
|
||||
server.stream_close_send(stream_id).unwrap();
|
||||
let dgram = server.process_output(now()).dgram();
|
||||
let dgram = dgram.unwrap();
|
||||
assert_eq!(dgram.source(), addr());
|
||||
assert_eq!(dgram.destination(), new_port(addr()));
|
||||
}
|
||||
|
||||
/// This simulates an attack where a valid packet is forwarded on
|
||||
/// a different path. This shows how both paths are probed and the
|
||||
/// server eventually returns to the original path.
|
||||
#[test]
|
||||
fn path_forwarding_attack() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
let dgram = send_something(&mut client, now());
|
||||
let dgram = change_path(&dgram, addr_v4());
|
||||
server.process_input(dgram, now());
|
||||
|
||||
// The server now probes the new (primary) path.
|
||||
let new_probe = server.process_output(now()).dgram().unwrap();
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 1);
|
||||
assert_v4_path(&new_probe, false); // Can't be padded.
|
||||
|
||||
// The server also probes the old path.
|
||||
let old_probe = server.process_output(now()).dgram().unwrap();
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 2);
|
||||
assert_v6_path(&old_probe, true);
|
||||
|
||||
// New data from the server is sent on the new path, but that is
|
||||
// now constrained by the amplification limit.
|
||||
let stream_id = server.stream_create(StreamType::UniDi).unwrap();
|
||||
server.stream_close_send(stream_id).unwrap();
|
||||
assert!(server.process_output(now()).dgram().is_none());
|
||||
|
||||
// The client should respond to the challenge on the new path.
|
||||
// The server couldn't pad, so the client is also amplification limited.
|
||||
let new_resp = client.process(Some(new_probe), now()).dgram().unwrap();
|
||||
assert_eq!(client.stats().frame_rx.path_challenge, 1);
|
||||
assert_eq!(client.stats().frame_tx.path_challenge, 1);
|
||||
assert_eq!(client.stats().frame_tx.path_response, 1);
|
||||
assert_v4_path(&new_resp, false);
|
||||
|
||||
// The client also responds to probes on the old path.
|
||||
let old_resp = client.process(Some(old_probe), now()).dgram().unwrap();
|
||||
assert_eq!(client.stats().frame_rx.path_challenge, 2);
|
||||
assert_eq!(client.stats().frame_tx.path_challenge, 1);
|
||||
assert_eq!(client.stats().frame_tx.path_response, 2);
|
||||
assert_v6_path(&old_resp, true);
|
||||
|
||||
// But the client still sends data on the old path.
|
||||
let client_data1 = send_something(&mut client, now());
|
||||
assert_v6_path(&client_data1, false); // Just data.
|
||||
|
||||
// Receiving the PATH_RESPONSE from the client opens the amplification
|
||||
// limit enough for the server to respond.
|
||||
// This is padded because it includes PATH_CHALLENGE.
|
||||
let server_data1 = server.process(Some(new_resp), now()).dgram().unwrap();
|
||||
assert_v4_path(&server_data1, true);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 3);
|
||||
|
||||
// The client responds to this probe on the new path.
|
||||
client.process_input(server_data1, now());
|
||||
let stream_before = client.stats().frame_tx.stream;
|
||||
let padded_resp = send_something(&mut client, now());
|
||||
assert_eq!(stream_before, client.stats().frame_tx.stream);
|
||||
assert_v4_path(&padded_resp, true); // This is padded!
|
||||
|
||||
// But new data from the client stays on the old path.
|
||||
let client_data2 = client.process_output(now()).dgram().unwrap();
|
||||
assert_v6_path(&client_data2, false);
|
||||
|
||||
// The server keeps sending on the new path.
|
||||
let server_data2 = send_something(&mut server, now());
|
||||
assert_v4_path(&server_data2, false);
|
||||
|
||||
// Until new data is received from the client on the old path.
|
||||
server.process_input(client_data2, now());
|
||||
// The server sends a probe on the "old" path.
|
||||
let server_data3 = send_something(&mut server, now());
|
||||
assert_v4_path(&server_data3, true);
|
||||
// But switches data transmission to the "new" path.
|
||||
let server_data4 = server.process_output(now()).dgram().unwrap();
|
||||
assert_v6_path(&server_data4, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_immediate() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
client
|
||||
.migrate(Some(addr_v4()), Some(addr_v4()), true, now())
|
||||
.unwrap();
|
||||
|
||||
let client1 = send_something(&mut client, now());
|
||||
assert_v4_path(&client1, true); // Contains PATH_CHALLENGE.
|
||||
let client2 = send_something(&mut client, now());
|
||||
assert_v4_path(&client2, false); // Doesn't.
|
||||
|
||||
let server_delayed = send_something(&mut server, now());
|
||||
|
||||
// The server accepts the first packet and migrates (but probes).
|
||||
let server1 = server.process(Some(client1), now()).dgram().unwrap();
|
||||
assert_v4_path(&server1, true);
|
||||
let server2 = server.process_output(now()).dgram().unwrap();
|
||||
assert_v6_path(&server2, true);
|
||||
|
||||
// The second packet has no real effect, it just elicits an ACK.
|
||||
let all_before = server.stats().frame_tx.all;
|
||||
let ack_before = server.stats().frame_tx.ack;
|
||||
let server3 = server.process(Some(client2), now()).dgram();
|
||||
assert!(server3.is_some());
|
||||
assert_eq!(server.stats().frame_tx.all, all_before + 1);
|
||||
assert_eq!(server.stats().frame_tx.ack, ack_before + 1);
|
||||
|
||||
// Receiving a packet sent by the server before migration doesn't change path.
|
||||
client.process_input(server_delayed, now());
|
||||
let client3 = send_something(&mut client, now());
|
||||
assert_v4_path(&client3, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_immediate_fail() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
let mut now = now();
|
||||
|
||||
client
|
||||
.migrate(Some(addr_v4()), Some(addr_v4()), true, now)
|
||||
.unwrap();
|
||||
|
||||
let probe = client.process_output(now).dgram().unwrap();
|
||||
assert_v4_path(&probe, true); // Contains PATH_CHALLENGE.
|
||||
|
||||
for _ in 0..2 {
|
||||
let cb = client.process_output(now).callback();
|
||||
assert_ne!(cb, Duration::new(0, 0));
|
||||
now += cb;
|
||||
|
||||
let before = client.stats().frame_tx;
|
||||
let probe = client.process_output(now).dgram().unwrap();
|
||||
assert_v4_path(&probe, true); // Contains PATH_CHALLENGE.
|
||||
let after = client.stats().frame_tx;
|
||||
assert_eq!(after.path_challenge, before.path_challenge + 1);
|
||||
assert_eq!(after.padding, before.padding + 1);
|
||||
assert_eq!(after.all, before.all + 2);
|
||||
|
||||
// This might be a PTO, which will result in sending a probe.
|
||||
if let Some(probe) = client.process_output(now).dgram() {
|
||||
assert_v4_path(&probe, false); // Contains PATH_CHALLENGE.
|
||||
let after = client.stats().frame_tx;
|
||||
assert_eq!(after.ping, before.ping + 1);
|
||||
assert_eq!(after.all, before.all + 3);
|
||||
}
|
||||
}
|
||||
|
||||
let pto = client.process_output(now).callback();
|
||||
assert_ne!(pto, Duration::new(0, 0));
|
||||
now += pto;
|
||||
|
||||
// The client should fall back to the original path and retire the connection ID.
|
||||
let fallback = client.process_output(now).dgram();
|
||||
assert_v6_path(&fallback.unwrap(), false);
|
||||
assert_eq!(client.stats().frame_tx.retire_connection_id, 1);
|
||||
}
|
||||
|
||||
/// Migrating to the same path shouldn't do anything special,
|
||||
/// except that the path is probed.
|
||||
#[test]
|
||||
fn migrate_same() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
let now = now();
|
||||
|
||||
client
|
||||
.migrate(Some(addr()), Some(addr()), true, now)
|
||||
.unwrap();
|
||||
|
||||
let probe = client.process_output(now).dgram().unwrap();
|
||||
assert_v6_path(&probe, true); // Contains PATH_CHALLENGE.
|
||||
assert_eq!(client.stats().frame_tx.path_challenge, 1);
|
||||
|
||||
let resp = server.process(Some(probe), now).dgram().unwrap();
|
||||
assert_v6_path(&resp, true);
|
||||
assert_eq!(server.stats().frame_tx.path_response, 1);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 0);
|
||||
|
||||
// Everything continues happily.
|
||||
client.process_input(resp, now);
|
||||
let contd = send_something(&mut client, now);
|
||||
assert_v6_path(&contd, false);
|
||||
}
|
||||
|
||||
/// Migrating to the same path, if it fails, causes the connection to fail.
|
||||
#[test]
|
||||
fn migrate_same_fail() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
let mut now = now();
|
||||
|
||||
client
|
||||
.migrate(Some(addr()), Some(addr()), true, now)
|
||||
.unwrap();
|
||||
|
||||
let probe = client.process_output(now).dgram().unwrap();
|
||||
assert_v6_path(&probe, true); // Contains PATH_CHALLENGE.
|
||||
|
||||
for _ in 0..2 {
|
||||
let cb = client.process_output(now).callback();
|
||||
assert_ne!(cb, Duration::new(0, 0));
|
||||
now += cb;
|
||||
|
||||
let before = client.stats().frame_tx;
|
||||
let probe = client.process_output(now).dgram().unwrap();
|
||||
assert_v6_path(&probe, true); // Contains PATH_CHALLENGE.
|
||||
let after = client.stats().frame_tx;
|
||||
assert_eq!(after.path_challenge, before.path_challenge + 1);
|
||||
assert_eq!(after.padding, before.padding + 1);
|
||||
assert_eq!(after.all, before.all + 2);
|
||||
|
||||
// This might be a PTO, which will result in sending a probe.
|
||||
if let Some(probe) = client.process_output(now).dgram() {
|
||||
assert_v6_path(&probe, false); // Contains PATH_CHALLENGE.
|
||||
let after = client.stats().frame_tx;
|
||||
assert_eq!(after.ping, before.ping + 1);
|
||||
assert_eq!(after.all, before.all + 3);
|
||||
}
|
||||
}
|
||||
|
||||
let pto = client.process_output(now).callback();
|
||||
assert_ne!(pto, Duration::new(0, 0));
|
||||
now += pto;
|
||||
|
||||
// The client should mark this path as failed and close immediately.
|
||||
let res = client.process_output(now);
|
||||
assert!(matches!(res, Output::None));
|
||||
assert!(matches!(
|
||||
client.state(),
|
||||
State::Closed(ConnectionError::Transport(Error::NoAvailablePath))
|
||||
));
|
||||
}
|
||||
|
||||
fn migration(mut client: Connection) {
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
let now = now();
|
||||
|
||||
client
|
||||
.migrate(Some(addr_v4()), Some(addr_v4()), false, now)
|
||||
.unwrap();
|
||||
|
||||
let probe = client.process_output(now).dgram().unwrap();
|
||||
assert_v4_path(&probe, true); // Contains PATH_CHALLENGE.
|
||||
assert_eq!(client.stats().frame_tx.path_challenge, 1);
|
||||
|
||||
let resp = server.process(Some(probe), now).dgram().unwrap();
|
||||
assert_v4_path(&resp, true);
|
||||
assert_eq!(server.stats().frame_tx.path_response, 1);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 1);
|
||||
|
||||
// Data continues to be exchanged on the new path.
|
||||
let client_data = send_something(&mut client, now);
|
||||
assert_v6_path(&client_data, false);
|
||||
server.process_input(client_data, now);
|
||||
let server_data = send_something(&mut server, now);
|
||||
assert_v6_path(&server_data, false);
|
||||
|
||||
// Once the client receives the probe response, it migrates to the new path.
|
||||
client.process_input(resp, now);
|
||||
assert_eq!(client.stats().frame_rx.path_challenge, 1);
|
||||
let migrate_client = send_something(&mut client, now);
|
||||
assert_v4_path(&migrate_client, true); // Responds to server probe.
|
||||
|
||||
// The server now sees the migration and will switch over.
|
||||
// However, it will probe the old path again, even though it has just
|
||||
// received a response to its last probe, because it needs to verify
|
||||
// that the migration is genuine.
|
||||
server.process_input(migrate_client, now);
|
||||
let stream_before = server.stats().frame_tx.stream;
|
||||
let probe_old_server = send_something(&mut server, now);
|
||||
// This is just the double-check probe; no STREAM frames.
|
||||
assert_v6_path(&probe_old_server, true);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 2);
|
||||
assert_eq!(server.stats().frame_tx.stream, stream_before);
|
||||
|
||||
// The server then sends data on the new path.
|
||||
let migrate_server = server.process_output(now).dgram().unwrap();
|
||||
assert_v4_path(&migrate_server, false);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 2);
|
||||
assert_eq!(server.stats().frame_tx.stream, stream_before + 1);
|
||||
|
||||
// The client receives these checks and responds to the probe, but uses the new path.
|
||||
client.process_input(migrate_server, now);
|
||||
client.process_input(probe_old_server, now);
|
||||
let old_probe_resp = send_something(&mut client, now);
|
||||
assert_v6_path(&old_probe_resp, true);
|
||||
let client_confirmation = client.process_output(now).dgram().unwrap();
|
||||
assert_v4_path(&client_confirmation, false);
|
||||
|
||||
let server_confirmation = send_something(&mut server, now);
|
||||
assert_v4_path(&server_confirmation, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_graceful() {
|
||||
migration(default_client());
|
||||
}
|
||||
|
||||
/// A client should be able to migrate when it has a zero-length connection ID.
|
||||
#[test]
|
||||
fn migration_client_empty_cid() {
|
||||
fixture_init();
|
||||
let client = Connection::new_client(
|
||||
test_fixture::DEFAULT_SERVER_NAME,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
ConnectionParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
migration(client);
|
||||
}
|
||||
|
||||
/// Drive the handshake in the most expeditious fashion.
|
||||
/// Returns the packet containing `HANDSHAKE_DONE` from the server.
|
||||
fn fast_handshake(client: &mut Connection, server: &mut Connection) -> Option<Datagram> {
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
assert!(maybe_authenticate(client));
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
server.process(dgram, now()).dgram()
|
||||
}
|
||||
|
||||
fn preferred_address(hs_client: SocketAddr, hs_server: SocketAddr, preferred: SocketAddr) {
|
||||
let mtu = match hs_client.ip() {
|
||||
IpAddr::V4(_) => PATH_MTU_V4,
|
||||
IpAddr::V6(_) => PATH_MTU_V6,
|
||||
};
|
||||
let assert_orig_path = |d: &Datagram, full_mtu: bool| {
|
||||
assert_eq!(
|
||||
d.destination(),
|
||||
if d.source() == hs_client {
|
||||
hs_server
|
||||
} else if d.source() == hs_server {
|
||||
hs_client
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
);
|
||||
if full_mtu {
|
||||
assert_eq!(d.len(), mtu);
|
||||
}
|
||||
};
|
||||
let assert_toward_spa = |d: &Datagram, full_mtu: bool| {
|
||||
assert_eq!(d.destination(), preferred);
|
||||
assert_eq!(d.source(), hs_client);
|
||||
if full_mtu {
|
||||
assert_eq!(d.len(), mtu);
|
||||
}
|
||||
};
|
||||
let assert_from_spa = |d: &Datagram, full_mtu: bool| {
|
||||
assert_eq!(d.destination(), hs_client);
|
||||
assert_eq!(d.source(), preferred);
|
||||
if full_mtu {
|
||||
assert_eq!(d.len(), mtu);
|
||||
}
|
||||
};
|
||||
|
||||
fixture_init();
|
||||
let mut client = Connection::new_client(
|
||||
test_fixture::DEFAULT_SERVER_NAME,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())),
|
||||
hs_client,
|
||||
hs_server,
|
||||
ConnectionParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let spa = if preferred.ip().is_ipv6() {
|
||||
PreferredAddress::new(None, Some(preferred))
|
||||
} else {
|
||||
PreferredAddress::new(Some(preferred), None)
|
||||
};
|
||||
let mut server = new_server(ConnectionParameters::default().preferred_address(spa));
|
||||
|
||||
let dgram = fast_handshake(&mut client, &mut server);
|
||||
|
||||
// The client is about to process HANDSHAKE_DONE.
|
||||
// It should start probing toward the server's preferred address.
|
||||
let probe = client.process(dgram, now()).dgram().unwrap();
|
||||
assert_toward_spa(&probe, true);
|
||||
assert_eq!(client.stats().frame_tx.path_challenge, 1);
|
||||
assert_ne!(client.process_output(now()).callback(), Duration::new(0, 0));
|
||||
|
||||
// Data continues on the main path for the client.
|
||||
let data = send_something(&mut client, now());
|
||||
assert_orig_path(&data, false);
|
||||
|
||||
// The server responds to the probe.
|
||||
let resp = server.process(Some(probe), now()).dgram().unwrap();
|
||||
assert_from_spa(&resp, true);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 1);
|
||||
assert_eq!(server.stats().frame_tx.path_response, 1);
|
||||
|
||||
// Data continues on the main path for the server.
|
||||
server.process_input(data, now());
|
||||
let data = send_something(&mut server, now());
|
||||
assert_orig_path(&data, false);
|
||||
|
||||
// Client gets the probe response back and it migrates.
|
||||
client.process_input(resp, now());
|
||||
client.process_input(data, now());
|
||||
let data = send_something(&mut client, now());
|
||||
assert_toward_spa(&data, true);
|
||||
assert_eq!(client.stats().frame_tx.stream, 2);
|
||||
assert_eq!(client.stats().frame_tx.path_response, 1);
|
||||
|
||||
// The server sees the migration and probes the old path.
|
||||
let probe = server.process(Some(data), now()).dgram().unwrap();
|
||||
assert_orig_path(&probe, true);
|
||||
assert_eq!(server.stats().frame_tx.path_challenge, 2);
|
||||
|
||||
// But data now goes on the new path.
|
||||
let data = send_something(&mut server, now());
|
||||
assert_from_spa(&data, false);
|
||||
}
|
||||
|
||||
/// Migration works for a new port number.
|
||||
#[test]
|
||||
fn preferred_address_new_port() {
|
||||
let a = addr();
|
||||
preferred_address(a, a, new_port(a));
|
||||
}
|
||||
|
||||
/// Migration works for a new address too.
|
||||
#[test]
|
||||
fn preferred_address_new_address() {
|
||||
let mut preferred = addr();
|
||||
preferred.set_ip(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2)));
|
||||
preferred_address(addr(), addr(), preferred);
|
||||
}
|
||||
|
||||
/// Migration works for IPv4 addresses.
|
||||
#[test]
|
||||
fn preferred_address_new_port_v4() {
|
||||
let a = addr_v4();
|
||||
preferred_address(a, a, new_port(a));
|
||||
}
|
||||
|
||||
/// Migrating to a loopback address is OK if we started there.
|
||||
#[test]
|
||||
fn preferred_address_loopback() {
|
||||
let a = loopback();
|
||||
preferred_address(a, a, new_port(a));
|
||||
}
|
||||
|
||||
fn expect_no_migration(client: &mut Connection, server: &mut Connection) {
|
||||
let dgram = fast_handshake(client, server);
|
||||
|
||||
// The client won't probe now, though it could; it remains idle.
|
||||
let out = client.process(dgram, now());
|
||||
assert_ne!(out.callback(), Duration::new(0, 0));
|
||||
|
||||
// Data continues on the main path for the client.
|
||||
let data = send_something(client, now());
|
||||
assert_v6_path(&data, false);
|
||||
assert_eq!(client.stats().frame_tx.path_challenge, 0);
|
||||
}
|
||||
|
||||
fn preferred_address_ignored(spa: PreferredAddress) {
|
||||
let mut client = default_client();
|
||||
let mut server = new_server(ConnectionParameters::default().preferred_address(spa));
|
||||
|
||||
expect_no_migration(&mut client, &mut server);
|
||||
}
|
||||
|
||||
/// Using a loopback address in the preferred address is ignored.
|
||||
#[test]
|
||||
fn preferred_address_ignore_loopback() {
|
||||
preferred_address_ignored(PreferredAddress::new(None, Some(loopback())));
|
||||
}
|
||||
|
||||
/// A preferred address in the wrong address family is ignored.
|
||||
#[test]
|
||||
fn preferred_address_ignore_different_family() {
|
||||
preferred_address_ignored(PreferredAddress::new(Some(addr_v4()), None));
|
||||
}
|
||||
|
||||
/// Disabling preferred addresses at the client means that it ignores a perfectly
|
||||
/// good preferred address.
|
||||
#[test]
|
||||
fn preferred_address_disabled_client() {
|
||||
let mut client = new_client(ConnectionParameters::default().disable_preferred_address());
|
||||
let mut preferred = addr();
|
||||
preferred.set_ip(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2)));
|
||||
let spa = PreferredAddress::new(None, Some(preferred));
|
||||
let mut server = new_server(ConnectionParameters::default().preferred_address(spa));
|
||||
|
||||
expect_no_migration(&mut client, &mut server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_address_empty_cid() {
|
||||
fixture_init();
|
||||
|
||||
let spa = PreferredAddress::new(None, Some(new_port(addr())));
|
||||
let res = Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default().preferred_address(spa),
|
||||
);
|
||||
assert_eq!(res.unwrap_err(), Error::ConnectionIdsExhausted);
|
||||
}
|
||||
|
||||
/// A server cannot include a preferred address if it chooses an empty connection ID.
|
||||
#[test]
|
||||
fn preferred_address_server_empty_cid() {
|
||||
let mut client = default_client();
|
||||
let mut server = Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
server
|
||||
.set_local_tparam(
|
||||
tparams::PREFERRED_ADDRESS,
|
||||
TransportParameter::Bytes(SAMPLE_PREFERRED_ADDRESS.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
connect_fail(
|
||||
&mut client,
|
||||
&mut server,
|
||||
Error::TransportParameterError,
|
||||
Error::PeerError(Error::TransportParameterError.code()),
|
||||
);
|
||||
}
|
||||
|
||||
/// A client shouldn't send a preferred address transport parameter.
|
||||
#[test]
|
||||
fn preferred_address_client() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
|
||||
client
|
||||
.set_local_tparam(
|
||||
tparams::PREFERRED_ADDRESS,
|
||||
TransportParameter::Bytes(SAMPLE_PREFERRED_ADDRESS.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
connect_fail(
|
||||
&mut client,
|
||||
&mut server,
|
||||
Error::PeerError(Error::TransportParameterError.code()),
|
||||
Error::TransportParameterError,
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that migration isn't permitted if the connection isn't in the right state.
|
||||
#[test]
|
||||
fn migration_invalid_state() {
|
||||
let mut client = default_client();
|
||||
assert!(client
|
||||
.migrate(Some(addr()), Some(addr()), false, now())
|
||||
.is_err());
|
||||
|
||||
let mut server = default_server();
|
||||
assert!(server
|
||||
.migrate(Some(addr()), Some(addr()), false, now())
|
||||
.is_err());
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
assert!(server
|
||||
.migrate(Some(addr()), Some(addr()), false, now())
|
||||
.is_err());
|
||||
|
||||
client.close(now(), 0, "closing");
|
||||
assert!(client
|
||||
.migrate(Some(addr()), Some(addr()), false, now())
|
||||
.is_err());
|
||||
let close = client.process(None, now()).dgram();
|
||||
|
||||
let dgram = server.process(close, now()).dgram();
|
||||
assert!(server
|
||||
.migrate(Some(addr()), Some(addr()), false, now())
|
||||
.is_err());
|
||||
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
assert!(client
|
||||
.migrate(Some(addr()), Some(addr()), false, now())
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_invalid_address() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
let mut cant_migrate = |local, remote| {
|
||||
assert_eq!(
|
||||
client.migrate(local, remote, true, now()).unwrap_err(),
|
||||
Error::InvalidMigration
|
||||
)
|
||||
};
|
||||
|
||||
// Providing neither address is pointless and therefore an error.
|
||||
cant_migrate(None, None);
|
||||
|
||||
// Providing a zero port number isn't valid.
|
||||
let mut zero_port = addr();
|
||||
zero_port.set_port(0);
|
||||
cant_migrate(None, Some(zero_port));
|
||||
cant_migrate(Some(zero_port), None);
|
||||
|
||||
// An unspecified remote address is bad.
|
||||
let mut remote_unspecified = addr();
|
||||
remote_unspecified.set_ip(IpAddr::V6(Ipv6Addr::from(0)));
|
||||
cant_migrate(None, Some(remote_unspecified));
|
||||
|
||||
// Mixed address families is bad.
|
||||
cant_migrate(Some(addr()), Some(addr_v4()));
|
||||
cant_migrate(Some(addr_v4()), Some(addr()));
|
||||
|
||||
// Loopback to non-loopback is bad.
|
||||
cant_migrate(Some(addr()), Some(loopback()));
|
||||
cant_migrate(Some(loopback()), Some(addr()));
|
||||
assert_eq!(
|
||||
client
|
||||
.migrate(Some(addr()), Some(loopback()), true, now())
|
||||
.unwrap_err(),
|
||||
Error::InvalidMigration
|
||||
);
|
||||
assert_eq!(
|
||||
client
|
||||
.migrate(Some(loopback()), Some(addr()), true, now())
|
||||
.unwrap_err(),
|
||||
Error::InvalidMigration
|
||||
);
|
||||
}
|
|
@ -7,24 +7,24 @@
|
|||
#![deny(clippy::pedantic)]
|
||||
|
||||
use super::{
|
||||
Connection, ConnectionError, ConnectionId, ConnectionIdRef, Output, State, LOCAL_IDLE_TIMEOUT,
|
||||
Connection, ConnectionError, FixedConnectionIdManager, Output, State, LOCAL_IDLE_TIMEOUT,
|
||||
};
|
||||
use crate::addr_valid::{AddressValidation, ValidateAddress};
|
||||
use crate::cc::CWND_INITIAL_PKTS;
|
||||
use crate::events::ConnectionEvent;
|
||||
use crate::frame::StreamType;
|
||||
use crate::path::PATH_MTU_V6;
|
||||
use crate::recovery::ACK_ONLY_SIZE_LIMIT;
|
||||
use crate::{ConnectionIdDecoder, ConnectionIdGenerator, ConnectionParameters, Error, StreamType};
|
||||
use crate::ConnectionParameters;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use neqo_common::{event::Provider, qdebug, qtrace, Datagram, Decoder};
|
||||
use neqo_crypto::{random, AllowZeroRtt, AuthenticationStatus, ResumptionToken};
|
||||
use test_fixture::{self, addr, fixture_init, now};
|
||||
use neqo_common::{event::Provider, qdebug, qtrace, Datagram};
|
||||
use neqo_crypto::{AllowZeroRtt, AuthenticationStatus, ResumptionToken};
|
||||
use test_fixture::{self, fixture_init, loopback, now};
|
||||
|
||||
// All the tests.
|
||||
mod cc;
|
||||
|
@ -32,7 +32,6 @@ mod close;
|
|||
mod handshake;
|
||||
mod idle;
|
||||
mod keys;
|
||||
mod migration;
|
||||
mod recovery;
|
||||
mod resumption;
|
||||
mod stream;
|
||||
|
@ -42,84 +41,39 @@ mod zerortt;
|
|||
const DEFAULT_RTT: Duration = Duration::from_millis(100);
|
||||
const AT_LEAST_PTO: Duration = Duration::from_secs(1);
|
||||
const DEFAULT_STREAM_DATA: &[u8] = b"message";
|
||||
/// The number of 1-RTT packets sent in `force_idle` by a client.
|
||||
const FORCE_IDLE_CLIENT_1RTT_PACKETS: usize = 3;
|
||||
|
||||
/// WARNING! In this module, this version of the generator needs to be used.
|
||||
/// This copies the implementation from
|
||||
/// `test_fixture::CountingConnectionIdGenerator`, but it uses the different
|
||||
/// types that are exposed to this module. See also `default_client`.
|
||||
///
|
||||
/// This version doesn't randomize the length; as the congestion control tests
|
||||
/// count the amount of data sent precisely.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CountingConnectionIdGenerator {
|
||||
counter: u32,
|
||||
}
|
||||
|
||||
impl ConnectionIdDecoder for CountingConnectionIdGenerator {
|
||||
fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> {
|
||||
let len = usize::from(dec.peek_byte().unwrap());
|
||||
dec.decode(len).map(ConnectionIdRef::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdGenerator for CountingConnectionIdGenerator {
|
||||
fn generate_cid(&mut self) -> Option<ConnectionId> {
|
||||
let mut r = random(20);
|
||||
r[0] = 8;
|
||||
r[1] = u8::try_from(self.counter >> 24).unwrap();
|
||||
r[2] = u8::try_from((self.counter >> 16) & 0xff).unwrap();
|
||||
r[3] = u8::try_from((self.counter >> 8) & 0xff).unwrap();
|
||||
r[4] = u8::try_from(self.counter & 0xff).unwrap();
|
||||
self.counter += 1;
|
||||
Some(ConnectionId::from(&r[..8]))
|
||||
}
|
||||
|
||||
fn as_decoder(&self) -> &dyn ConnectionIdDecoder {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// 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 - and incompatible - types.
|
||||
// test_fixture because they produce different types.
|
||||
//
|
||||
// These are a direct copy of those functions.
|
||||
pub fn new_client(params: ConnectionParameters) -> Connection {
|
||||
pub fn default_client() -> Connection {
|
||||
fixture_init();
|
||||
Connection::new_client(
|
||||
test_fixture::DEFAULT_SERVER_NAME,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
params,
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(3))),
|
||||
loopback(),
|
||||
loopback(),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.expect("create a default client")
|
||||
}
|
||||
pub fn default_client() -> Connection {
|
||||
new_client(ConnectionParameters::default())
|
||||
}
|
||||
|
||||
pub fn new_server(params: ConnectionParameters) -> Connection {
|
||||
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(CountingConnectionIdGenerator::default())),
|
||||
params,
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(5))),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.expect("create a default server");
|
||||
c.server_enable_0rtt(&test_fixture::anti_replay(), AllowZeroRtt {})
|
||||
.expect("enable 0-RTT");
|
||||
c
|
||||
}
|
||||
pub fn default_server() -> Connection {
|
||||
new_server(ConnectionParameters::default())
|
||||
}
|
||||
|
||||
/// If state is `AuthenticationNeeded` call `authenticated()`. This function will
|
||||
/// consume all outstanding events on the connection.
|
||||
|
@ -152,7 +106,7 @@ fn handshake(
|
|||
let output = a.process(input, now).dgram();
|
||||
assert!(had_input || output.is_some());
|
||||
input = output;
|
||||
qtrace!("handshake: t += {:?}", rtt / 2);
|
||||
qtrace!("t += {:?}", rtt / 2);
|
||||
now += rtt / 2;
|
||||
mem::swap(&mut a, &mut b);
|
||||
}
|
||||
|
@ -160,17 +114,6 @@ fn handshake(
|
|||
now
|
||||
}
|
||||
|
||||
fn connect_fail(
|
||||
client: &mut Connection,
|
||||
server: &mut Connection,
|
||||
client_error: Error,
|
||||
server_error: Error,
|
||||
) {
|
||||
handshake(client, server, now(), Duration::new(0, 0));
|
||||
assert_error(client, &ConnectionError::Transport(client_error));
|
||||
assert_error(server, &ConnectionError::Transport(server_error));
|
||||
}
|
||||
|
||||
fn connect_with_rtt(
|
||||
client: &mut Connection,
|
||||
server: &mut Connection,
|
||||
|
@ -179,7 +122,7 @@ fn connect_with_rtt(
|
|||
) -> Instant {
|
||||
let now = handshake(client, server, now, rtt);
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
|
||||
assert_eq!(client.loss_recovery.rtt(), rtt);
|
||||
assert_eq!(server.loss_recovery.rtt(), rtt);
|
||||
|
@ -215,53 +158,28 @@ fn exchange_ticket(
|
|||
get_tokens(client).pop().expect("should have token")
|
||||
}
|
||||
|
||||
/// Connect with an RTT and then force both peers to be idle.
|
||||
/// Getting the client and server to reach an idle state is surprisingly hard.
|
||||
/// The server sends `HANDSHAKE_DONE` at the end of the handshake, and the client
|
||||
/// doesn't immediately acknowledge it. Reordering packets does the trick.
|
||||
fn force_idle(
|
||||
client: &mut Connection,
|
||||
server: &mut Connection,
|
||||
rtt: Duration,
|
||||
mut now: Instant,
|
||||
) -> Instant {
|
||||
// The client has sent NEW_CONNECTION_ID, so ensure that the server generates
|
||||
// an acknowledgment by sending some reordered packets.
|
||||
qtrace!("force_idle: send reordered client packets");
|
||||
let c1 = send_something(client, now);
|
||||
let c2 = send_something(client, now);
|
||||
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;
|
||||
server.process_input(c2, now);
|
||||
server.process_input(c1, now);
|
||||
|
||||
// Now do the same for the server. (The ACK is in the first one.)
|
||||
qtrace!("force_idle: send reordered server packets");
|
||||
let s1 = send_something(server, now);
|
||||
let s2 = send_something(server, now);
|
||||
now += rtt / 2;
|
||||
// Delivering s2 first at the client causes it to want to ACK.
|
||||
client.process_input(s2, now);
|
||||
// Delivering s1 should not have the client change its mind about the ACK.
|
||||
let ack = client.process(Some(s1), now).dgram();
|
||||
// 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!(
|
||||
client.process_output(now),
|
||||
Output::Callback(LOCAL_IDLE_TIMEOUT)
|
||||
);
|
||||
now += rtt / 2;
|
||||
assert_eq!(
|
||||
server.process(ack, now),
|
||||
Output::Callback(LOCAL_IDLE_TIMEOUT)
|
||||
);
|
||||
now
|
||||
}
|
||||
|
||||
/// Connect with an RTT and then force both peers to be idle.
|
||||
fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Duration) -> Instant {
|
||||
let now = connect_with_rtt(client, server, now(), rtt);
|
||||
let now = force_idle(client, server, rtt, now);
|
||||
// Drain events from both as well.
|
||||
let _ = client.events().count();
|
||||
let _ = server.events().count();
|
||||
assert_eq!(
|
||||
client.process_output(now),
|
||||
Output::Callback(LOCAL_IDLE_TIMEOUT)
|
||||
);
|
||||
now
|
||||
}
|
||||
|
||||
|
@ -329,8 +247,7 @@ const POST_HANDSHAKE_CWND: usize = PATH_MTU_V6 * CWND_INITIAL_PKTS;
|
|||
|
||||
/// Determine the number of packets required to fill the CWND.
|
||||
const fn cwnd_packets(data: usize) -> usize {
|
||||
// Add one if the last chunk is >= ACK_ONLY_SIZE_LIMIT.
|
||||
(data + PATH_MTU_V6 - ACK_ONLY_SIZE_LIMIT) / PATH_MTU_V6
|
||||
(data + ACK_ONLY_SIZE_LIMIT - 1) / PATH_MTU_V6
|
||||
}
|
||||
|
||||
/// Determine the size of the last packet.
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
|
||||
use super::super::{Output, State, LOCAL_IDLE_TIMEOUT};
|
||||
use super::{
|
||||
assert_full_cwnd, connect, connect_force_idle, connect_rtt_idle, connect_with_rtt,
|
||||
default_client, default_server, fill_cwnd, maybe_authenticate, send_and_receive,
|
||||
send_something, AT_LEAST_PTO, DEFAULT_RTT, POST_HANDSHAKE_CWND,
|
||||
assert_full_cwnd, connect, connect_force_idle, connect_with_rtt, default_client,
|
||||
default_server, fill_cwnd, maybe_authenticate, send_and_receive, send_something, AT_LEAST_PTO,
|
||||
POST_HANDSHAKE_CWND,
|
||||
};
|
||||
use crate::frame::StreamType;
|
||||
use crate::path::PATH_MTU_V6;
|
||||
use crate::recovery::PTO_PACKET_COUNT;
|
||||
use crate::stats::MAX_PTO_COUNTS;
|
||||
use crate::tparams::TransportParameter;
|
||||
use crate::tracking::ACK_DELAY;
|
||||
use crate::StreamType;
|
||||
|
||||
use neqo_common::qdebug;
|
||||
use neqo_crypto::AuthenticationStatus;
|
||||
|
@ -34,12 +34,12 @@ fn pto_works_basic() {
|
|||
assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
|
||||
|
||||
// Send data on two streams
|
||||
let stream1 = client.stream_create(StreamType::UniDi).unwrap();
|
||||
assert_eq!(client.stream_send(stream1, b"hello").unwrap(), 5);
|
||||
assert_eq!(client.stream_send(stream1, b" world!").unwrap(), 7);
|
||||
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);
|
||||
|
||||
let stream2 = client.stream_create(StreamType::UniDi).unwrap();
|
||||
assert_eq!(client.stream_send(stream2, b"there!").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);
|
||||
|
@ -63,15 +63,19 @@ fn pto_works_basic() {
|
|||
fn pto_works_full_cwnd() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT);
|
||||
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.
|
||||
let stream_id = client.stream_create(StreamType::UniDi).unwrap();
|
||||
let (dgrams, now) = fill_cwnd(&mut client, stream_id, now);
|
||||
assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2);
|
||||
let (dgrams, now) = fill_cwnd(&mut client, 2, now());
|
||||
assert_full_cwnd(&dgrams, POST_HANDSHAKE_CWND);
|
||||
|
||||
neqo_common::qwarn!("waiting over");
|
||||
// Fill the CWND after waiting for a PTO.
|
||||
let (dgrams, now) = fill_cwnd(&mut client, stream_id, now + AT_LEAST_PTO);
|
||||
let (dgrams, now) = fill_cwnd(&mut client, 2, now + AT_LEAST_PTO);
|
||||
// Two packets in the PTO.
|
||||
// The first should be full sized; the second might be small.
|
||||
assert_eq!(dgrams.len(), 2);
|
||||
|
@ -86,65 +90,94 @@ fn pto_works_full_cwnd() {
|
|||
}
|
||||
|
||||
#[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 mut now = now();
|
||||
|
||||
let now = now();
|
||||
|
||||
let res = client.process(None, now);
|
||||
assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
|
||||
|
||||
now += Duration::from_secs(10);
|
||||
// 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 a few packets from the client.
|
||||
let pkt0 = send_something(&mut client, now);
|
||||
let pkt1 = send_something(&mut client, now);
|
||||
let pkt2 = send_something(&mut client, now);
|
||||
let pkt3 = send_something(&mut client, now);
|
||||
// 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 cb = client.process(None, now).callback();
|
||||
assert_eq!(cb, Duration::from_millis(45));
|
||||
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 = server.process(Some(pkt1), now).dgram();
|
||||
assert!(srv0.is_some()); // ooo, ack client pkt1
|
||||
|
||||
now += Duration::from_millis(20);
|
||||
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 srv1 = server.process(Some(pkt2), now).dgram();
|
||||
assert!(srv1.is_none());
|
||||
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(Some(pkt3), now).dgram();
|
||||
let srv2 = server.process(
|
||||
pkt3.dgram(),
|
||||
now + Duration::from_secs(10) + Duration::from_millis(20),
|
||||
);
|
||||
// ack client pkt 2 & 3
|
||||
assert!(srv2.is_some());
|
||||
assert!(matches!(srv2, Output::Datagram(_)));
|
||||
|
||||
now += Duration::from_millis(20);
|
||||
// client processes ack
|
||||
let pkt4 = client.process(srv2, now).dgram();
|
||||
let pkt4 = client.process(
|
||||
srv2.dgram(),
|
||||
now + Duration::from_secs(10) + Duration::from_millis(40),
|
||||
);
|
||||
// client resends data from pkt0
|
||||
assert!(pkt4.is_some());
|
||||
assert!(matches!(pkt4, Output::Datagram(_)));
|
||||
|
||||
// server sees ooo pkt0 and generates ack
|
||||
let srv3 = server.process(Some(pkt0), now).dgram();
|
||||
assert!(srv3.is_some());
|
||||
let srv_pkt2 = server.process(
|
||||
pkt0.dgram(),
|
||||
now + Duration::from_secs(10) + Duration::from_millis(40),
|
||||
);
|
||||
assert!(matches!(srv_pkt2, Output::Datagram(_)));
|
||||
|
||||
// Accept the acknowledgment.
|
||||
let pkt5 = client.process(srv3, now).dgram();
|
||||
assert!(pkt5.is_none());
|
||||
// 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(_)));
|
||||
|
||||
now += Duration::from_millis(70);
|
||||
// PTO expires. No unacked data. Only send PING.
|
||||
let client_pings = client.stats().frame_tx.ping;
|
||||
let pkt6 = client.process(None, now).dgram();
|
||||
assert_eq!(client.stats().frame_tx.ping, client_pings + 1);
|
||||
let pkt6 = client.process(
|
||||
None,
|
||||
now + Duration::from_secs(10) + Duration::from_millis(110),
|
||||
);
|
||||
|
||||
let server_pings = server.stats().frame_rx.ping;
|
||||
server.process_input(pkt6.unwrap(), now);
|
||||
assert_eq!(server.stats().frame_rx.ping, server_pings + 1);
|
||||
let ping_before = server.stats().frame_rx.ping;
|
||||
server.process_input(
|
||||
pkt6.dgram().unwrap(),
|
||||
now + Duration::from_secs(10) + Duration::from_millis(110),
|
||||
);
|
||||
assert_eq!(server.stats().frame_rx.ping, ping_before + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -228,7 +261,6 @@ fn pto_handshake_complete() {
|
|||
qdebug!("---- client: SH..FIN -> FIN");
|
||||
let pkt1 = client.process(None, now).dgram();
|
||||
assert!(pkt1.is_some());
|
||||
assert_eq!(*client.state(), State::Connected);
|
||||
|
||||
let cb = client.process(None, now).callback();
|
||||
assert_eq!(cb, Duration::from_millis(60));
|
||||
|
@ -236,22 +268,17 @@ fn pto_handshake_complete() {
|
|||
let mut pto_counts = [0; MAX_PTO_COUNTS];
|
||||
assert_eq!(client.stats.borrow().pto_counts, pto_counts);
|
||||
|
||||
// Wait for PTO to expire and resend a handshake packet.
|
||||
// Wait long enough that the 1-RTT PTO also fires.
|
||||
qdebug!("---- client: PTO");
|
||||
// Wait for PTO to expire and resend a handshake packet
|
||||
now += Duration::from_millis(60);
|
||||
let pkt2 = client.process(None, now).dgram();
|
||||
assert!(pkt2.is_some());
|
||||
|
||||
pto_counts[0] = 1;
|
||||
assert_eq!(client.stats.borrow().pto_counts, pto_counts);
|
||||
|
||||
// Get a second PTO packet.
|
||||
// Add some application data to this datagram, then split the 1-RTT off.
|
||||
// We'll use that packet to force the server to acknowledge 1-RTT.
|
||||
let stream_id = client.stream_create(StreamType::UniDi).unwrap();
|
||||
client.stream_close_send(stream_id).unwrap();
|
||||
let pkt3 = client.process(None, now).dgram();
|
||||
let (pkt3_hs, pkt3_1rtt) = split_datagram(&pkt3.unwrap());
|
||||
assert!(pkt3.is_some());
|
||||
|
||||
// PTO has been doubled.
|
||||
let cb = client.process(None, now).callback();
|
||||
|
@ -260,42 +287,33 @@ fn pto_handshake_complete() {
|
|||
// We still have only a single PTO
|
||||
assert_eq!(client.stats.borrow().pto_counts, pto_counts);
|
||||
|
||||
qdebug!("---- server: receive FIN and send ACK");
|
||||
now += Duration::from_millis(10);
|
||||
// Now let the server have pkt1 and expect an immediate Handshake ACK.
|
||||
// The output will be a Handshake packet with ACK and 1-RTT packet with
|
||||
// HANDSHAKE_DONE and (because of pkt3_1rtt) an ACK.
|
||||
// This should remove the 1-RTT PTO from messing this test up.
|
||||
let server_acks = server.stats().frame_tx.ack;
|
||||
let server_done = server.stats().frame_tx.handshake_done;
|
||||
server.process_input(pkt3_1rtt.unwrap(), now);
|
||||
let ack = server.process(pkt1, now).dgram();
|
||||
assert!(ack.is_some());
|
||||
assert_eq!(server.stats().frame_tx.ack, server_acks + 2);
|
||||
assert_eq!(server.stats().frame_tx.handshake_done, server_done + 1);
|
||||
// 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 other packets (pkt2, pkt3) are Handshake packets.
|
||||
// Check that the PTO packets (pkt2, pkt3) are Handshake packets.
|
||||
// The server discarded the Handshake keys already, therefore they are dropped.
|
||||
// Note that these don't include 1-RTT packets, because 1-RTT isn't send on PTO.
|
||||
let dropped_before1 = server.stats().dropped_rx;
|
||||
let server_frames = server.stats().frame_rx.all;
|
||||
let frames_before = server.stats().frame_rx.all;
|
||||
server.process_input(pkt2.unwrap(), now);
|
||||
assert_eq!(1, server.stats().dropped_rx - dropped_before1);
|
||||
assert_eq!(server.stats().frame_rx.all, server_frames);
|
||||
assert_eq!(server.stats().frame_rx.all, frames_before);
|
||||
|
||||
let dropped_before2 = server.stats().dropped_rx;
|
||||
server.process_input(pkt3_hs, now);
|
||||
server.process_input(pkt3.unwrap(), now);
|
||||
assert_eq!(1, server.stats().dropped_rx - dropped_before2);
|
||||
assert_eq!(server.stats().frame_rx.all, server_frames);
|
||||
assert_eq!(server.stats().frame_rx.all, frames_before);
|
||||
|
||||
now += Duration::from_millis(10);
|
||||
|
||||
// Let the client receive the ACK.
|
||||
// It should now be wait to acknowledge the HANDSHAKE_DONE.
|
||||
let cb = client.process(ack, now).callback();
|
||||
// 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 delay timer expire.
|
||||
// Let the ack timer expire.
|
||||
now += cb;
|
||||
let out = client.process(None, now).dgram();
|
||||
assert!(out.is_some());
|
||||
|
@ -304,6 +322,8 @@ fn pto_handshake_complete() {
|
|||
// We don't send another PING because the handshake space is done and there
|
||||
// is nothing to probe for.
|
||||
|
||||
pto_counts[0] = 1;
|
||||
assert_eq!(client.stats.borrow().pto_counts, pto_counts);
|
||||
assert_eq!(cb, LOCAL_IDLE_TIMEOUT - ACK_DELAY);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
|
||||
use super::super::State;
|
||||
use super::{
|
||||
connect, connect_force_idle, default_client, default_server, maybe_authenticate,
|
||||
send_something, DEFAULT_STREAM_DATA,
|
||||
connect, default_client, default_server, maybe_authenticate, send_something,
|
||||
DEFAULT_STREAM_DATA,
|
||||
};
|
||||
use crate::events::ConnectionEvent;
|
||||
use crate::frame::StreamType;
|
||||
use crate::recv_stream::RECV_BUFFER_SIZE;
|
||||
use crate::send_stream::SEND_BUFFER_SIZE;
|
||||
use crate::tparams::{self, TransportParameter};
|
||||
use crate::tracking::MAX_UNACKED_PKTS;
|
||||
use crate::{Error, StreamId, StreamType};
|
||||
use crate::{Error, StreamId};
|
||||
|
||||
use neqo_common::{event::Provider, qdebug};
|
||||
use std::convert::TryFrom;
|
||||
|
@ -48,13 +49,52 @@ fn stream_create() {
|
|||
}
|
||||
|
||||
#[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();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
|
||||
qdebug!("---- client sends");
|
||||
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();
|
||||
|
@ -68,15 +108,15 @@ fn transfer() {
|
|||
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_output(now());
|
||||
let mut out = client.process(out.dgram(), now());
|
||||
while let Some(d) = out.dgram() {
|
||||
datagrams.push(d);
|
||||
out = client.process_output(now());
|
||||
out = client.process(None, now());
|
||||
}
|
||||
assert_eq!(datagrams.len(), 4);
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
|
||||
qdebug!("---- server receives");
|
||||
qdebug!("---- server");
|
||||
for (d_num, d) in datagrams.into_iter().enumerate() {
|
||||
let out = server.process(Some(d), now());
|
||||
assert_eq!(
|
||||
|
@ -127,7 +167,7 @@ fn report_fin_when_stream_closed_wo_data() {
|
|||
let out = client.process(None, now());
|
||||
let _ = server.process(out.dgram(), now());
|
||||
|
||||
server.stream_close_send(stream_id).unwrap();
|
||||
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 {..});
|
||||
|
@ -249,6 +289,8 @@ fn do_not_accept_data_after_stop_sending() {
|
|||
#[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);
|
||||
|
@ -257,32 +299,30 @@ fn simultaneous_stop_sending_and_reset() {
|
|||
let stream_id = client.stream_create(StreamType::BiDi).unwrap();
|
||||
client.stream_send(stream_id, &[0x00]).unwrap();
|
||||
let out = client.process(None, now());
|
||||
let ack = server.process(out.dgram(), now()).dgram();
|
||||
let _ = server.process(out.dgram(), now());
|
||||
|
||||
let stream_readable =
|
||||
|e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id: id } if id == stream_id);
|
||||
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, 0).unwrap();
|
||||
let out_reset_frame = client.process(ack, now()).dgram();
|
||||
|
||||
// Send something out of order to force the server to generate an
|
||||
// acknowledgment at the next opportunity.
|
||||
let force_ack = send_something(&mut client, now());
|
||||
server.process_input(force_ack, now());
|
||||
|
||||
client
|
||||
.stream_reset_send(stream_id, Error::NoError.code())
|
||||
.unwrap();
|
||||
let out_reset_frame = client.process(None, now());
|
||||
// Call stop sending.
|
||||
server.stream_stop_sending(stream_id, 0).unwrap();
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
server.stream_stop_sending(stream_id, Error::NoError.code())
|
||||
);
|
||||
|
||||
// Receive the second data frame. The frame should be ignored and
|
||||
// DataReadable events shouldn't be posted.
|
||||
let ack = server.process(out_reset_frame, now()).dgram();
|
||||
assert!(ack.is_some());
|
||||
let out = server.process(out_reset_frame.dgram(), now());
|
||||
assert!(!server.events().any(stream_readable));
|
||||
|
||||
// The client gets the STOP_SENDING frame.
|
||||
client.process_input(ack.unwrap(), now());
|
||||
let _ = client.process(out.dgram(), now());
|
||||
assert_eq!(
|
||||
Err(Error::InvalidStreamId),
|
||||
client.stream_send(stream_id, &[0x00])
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{Error, QuicVersion};
|
|||
|
||||
use neqo_common::{Datagram, Decoder, Encoder};
|
||||
use std::time::Duration;
|
||||
use test_fixture::{self, addr, now};
|
||||
use test_fixture::{self, loopback, now};
|
||||
|
||||
// The expected PTO duration after the first Initial is sent.
|
||||
const INITIAL_PTO: Duration = Duration::from_millis(300);
|
||||
|
@ -25,7 +25,11 @@ fn unknown_version() {
|
|||
let mut unknown_version_packet = vec![0x80, 0x1a, 0x1a, 0x1a, 0x1a];
|
||||
unknown_version_packet.resize(1200, 0x0);
|
||||
let _ = client.process(
|
||||
Some(Datagram::new(addr(), addr(), unknown_version_packet)),
|
||||
Some(Datagram::new(
|
||||
loopback(),
|
||||
loopback(),
|
||||
unknown_version_packet,
|
||||
)),
|
||||
now(),
|
||||
);
|
||||
assert_eq!(1, client.stats().dropped_rx);
|
||||
|
@ -40,7 +44,11 @@ fn server_receive_unknown_first_packet() {
|
|||
|
||||
assert_eq!(
|
||||
server.process(
|
||||
Some(Datagram::new(addr(), addr(), unknown_version_packet,)),
|
||||
Some(Datagram::new(
|
||||
loopback(),
|
||||
loopback(),
|
||||
unknown_version_packet,
|
||||
)),
|
||||
now(),
|
||||
),
|
||||
Output::None
|
||||
|
@ -81,7 +89,7 @@ fn version_negotiation_current_version() {
|
|||
&[0x1a1a_1a1a, QuicVersion::default().as_u32()],
|
||||
);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
let dgram = Datagram::new(loopback(), loopback(), vn);
|
||||
let delay = client.process(Some(dgram), now()).callback();
|
||||
assert_eq!(delay, INITIAL_PTO);
|
||||
assert_eq!(*client.state(), State::WaitInitial);
|
||||
|
@ -100,7 +108,7 @@ fn version_negotiation_only_reserved() {
|
|||
|
||||
let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a]);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
let dgram = Datagram::new(loopback(), loopback(), vn);
|
||||
assert_eq!(client.process(Some(dgram), now()), Output::None);
|
||||
match client.state() {
|
||||
State::Closed(err) => {
|
||||
|
@ -122,7 +130,7 @@ fn version_negotiation_corrupted() {
|
|||
|
||||
let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a]);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), &vn[..vn.len() - 1]);
|
||||
let dgram = Datagram::new(loopback(), loopback(), &vn[..vn.len() - 1]);
|
||||
let delay = client.process(Some(dgram), now()).callback();
|
||||
assert_eq!(delay, INITIAL_PTO);
|
||||
assert_eq!(*client.state(), State::WaitInitial);
|
||||
|
@ -141,7 +149,7 @@ fn version_negotiation_empty() {
|
|||
|
||||
let vn = create_vn(&initial_pkt, &[]);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
let dgram = Datagram::new(loopback(), loopback(), vn);
|
||||
let delay = client.process(Some(dgram), now()).callback();
|
||||
assert_eq!(delay, INITIAL_PTO);
|
||||
assert_eq!(*client.state(), State::WaitInitial);
|
||||
|
@ -161,7 +169,7 @@ fn version_negotiation_not_supported() {
|
|||
let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
|
||||
|
||||
assert_eq!(
|
||||
client.process(Some(Datagram::new(addr(), addr(), vn)), now(),),
|
||||
client.process(Some(Datagram::new(loopback(), loopback(), vn)), now(),),
|
||||
Output::None
|
||||
);
|
||||
match client.state() {
|
||||
|
@ -185,7 +193,7 @@ fn version_negotiation_bad_cid() {
|
|||
let mut vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
|
||||
vn[6] ^= 0xc4;
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
let dgram = Datagram::new(loopback(), loopback(), vn);
|
||||
let delay = client.process(Some(dgram), now()).callback();
|
||||
assert_eq!(delay, INITIAL_PTO);
|
||||
assert_eq!(*client.state(), State::WaitInitial);
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::super::Connection;
|
||||
use super::{
|
||||
connect, default_client, default_server, exchange_ticket, CountingConnectionIdGenerator,
|
||||
};
|
||||
use super::super::{Connection, FixedConnectionIdManager};
|
||||
use super::{connect, default_client, default_server, exchange_ticket};
|
||||
use crate::events::ConnectionEvent;
|
||||
use crate::{ConnectionParameters, Error, StreamType};
|
||||
use crate::frame::StreamType;
|
||||
use crate::{ConnectionParameters, Error};
|
||||
|
||||
use neqo_common::event::Provider;
|
||||
use neqo_crypto::{AllowZeroRtt, AntiReplay};
|
||||
|
@ -63,13 +62,8 @@ fn zero_rtt_send_recv() {
|
|||
|
||||
let server_hs = server.process(client_hs.dgram(), now());
|
||||
assert!(server_hs.as_dgram_ref().is_some()); // ServerHello, etc...
|
||||
|
||||
let all_frames = server.stats().frame_tx.all;
|
||||
let ack_frames = server.stats().frame_tx.ack;
|
||||
let server_process_0rtt = server.process(client_0rtt.dgram(), now());
|
||||
assert!(server_process_0rtt.as_dgram_ref().is_some());
|
||||
assert_eq!(server.stats().frame_tx.all, all_frames + 1);
|
||||
assert_eq!(server.stats().frame_tx.ack, ack_frames + 1);
|
||||
assert!(server_process_0rtt.as_dgram_ref().is_none());
|
||||
|
||||
let server_stream_id = server
|
||||
.events()
|
||||
|
@ -138,8 +132,8 @@ fn zero_rtt_send_reject() {
|
|||
let mut server = Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default(),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(10))),
|
||||
&ConnectionParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
// Using a freshly initialized anti-replay context
|
||||
|
@ -189,10 +183,11 @@ fn zero_rtt_send_reject() {
|
|||
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()).dgram();
|
||||
assert!(client_after_reject.is_some());
|
||||
let client_after_reject = client.process(None, now());
|
||||
assert!(client_after_reject.as_dgram_ref().is_some());
|
||||
|
||||
// The server should receive new stream
|
||||
server.process_input(client_after_reject.unwrap(), now());
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ impl Crypto {
|
|||
self.tls.read_secret(TLS_EPOCH_ZERO_RTT),
|
||||
),
|
||||
};
|
||||
let secret = secret.ok_or(Error::InternalError(1))?;
|
||||
let secret = secret.ok_or(Error::InternalError)?;
|
||||
self.states.set_0rtt_keys(dir, &secret, cipher.unwrap());
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -177,12 +177,12 @@ impl Crypto {
|
|||
let read_secret = self
|
||||
.tls
|
||||
.read_secret(TLS_EPOCH_HANDSHAKE)
|
||||
.ok_or(Error::InternalError(2))?;
|
||||
.ok_or(Error::InternalError)?;
|
||||
let cipher = match self.tls.info() {
|
||||
None => self.tls.preinfo()?.cipher_suite(),
|
||||
Some(info) => Some(info.cipher_suite()),
|
||||
}
|
||||
.ok_or(Error::InternalError(3))?;
|
||||
.ok_or(Error::InternalError)?;
|
||||
self.states
|
||||
.set_handshake_keys(&write_secret, &read_secret, cipher);
|
||||
qdebug!([self], "Handshake keys installed");
|
||||
|
@ -206,7 +206,7 @@ impl Crypto {
|
|||
let read_secret = self
|
||||
.tls
|
||||
.read_secret(TLS_EPOCH_APPLICATION_DATA)
|
||||
.ok_or(Error::InternalError(4))?;
|
||||
.ok_or(Error::InternalError)?;
|
||||
self.states
|
||||
.set_application_read_key(read_secret, expire_0rtt)?;
|
||||
qdebug!([self], "application read keys installed");
|
||||
|
@ -1241,14 +1241,14 @@ impl CryptoStreams {
|
|||
&mut self,
|
||||
space: PNSpace,
|
||||
builder: &mut PacketBuilder,
|
||||
) -> Res<Option<RecoveryToken>> {
|
||||
) -> Option<RecoveryToken> {
|
||||
let cs = self.get_mut(space).unwrap();
|
||||
if let Some((offset, data)) = cs.tx.next_bytes() {
|
||||
let mut header_len = 1 + Encoder::varint_len(offset) + 1;
|
||||
|
||||
// Don't bother if there isn't room for the header and some data.
|
||||
if builder.remaining() < header_len + 1 {
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
// Calculate length of data based on the minimum of:
|
||||
// - available data
|
||||
|
@ -1261,20 +1261,16 @@ impl CryptoStreams {
|
|||
builder.encode_varint(crate::frame::FRAME_TYPE_CRYPTO);
|
||||
builder.encode_varint(offset);
|
||||
builder.encode_vvec(&data[..length]);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(15));
|
||||
}
|
||||
|
||||
cs.tx.mark_as_sent(offset, length);
|
||||
|
||||
qdebug!("CRYPTO for {} offset={}, len={}", space, offset, length);
|
||||
Ok(Some(RecoveryToken::Crypto(CryptoRecoveryToken {
|
||||
Some(RecoveryToken::Crypto(CryptoRecoveryToken {
|
||||
space,
|
||||
offset,
|
||||
length,
|
||||
})))
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,10 @@
|
|||
use crate::connection::Connection;
|
||||
use crate::frame::Frame;
|
||||
use crate::packet::{PacketNumber, PacketType};
|
||||
use crate::path::PathRef;
|
||||
use neqo_common::{qdebug, Decoder};
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn dump_packet(
|
||||
conn: &Connection,
|
||||
path: &PathRef,
|
||||
dir: &str,
|
||||
pt: PacketType,
|
||||
pn: PacketNumber,
|
||||
payload: &[u8],
|
||||
) {
|
||||
pub fn dump_packet(conn: &Connection, dir: &str, pt: PacketType, pn: PacketNumber, payload: &[u8]) {
|
||||
let mut s = String::from("");
|
||||
let mut d = Decoder::from(payload);
|
||||
while d.remaining() > 0 {
|
||||
|
@ -36,5 +28,5 @@ pub fn dump_packet(
|
|||
s.push_str(&format!("\n {} {}", dir, &x));
|
||||
}
|
||||
}
|
||||
qdebug!([conn], "pn={} type={:?} {}{}", pn, pt, path.borrow(), s);
|
||||
qdebug!([conn], "pn={} type={:?}{}", pn, pt, s);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ use std::collections::VecDeque;
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::connection::State;
|
||||
use crate::stream_id::{StreamId, StreamType};
|
||||
use crate::frame::StreamType;
|
||||
use crate::stream_id::StreamId;
|
||||
use crate::AppError;
|
||||
use neqo_common::event::Provider as EventProvider;
|
||||
use neqo_crypto::ResumptionToken;
|
||||
|
|
|
@ -13,14 +13,14 @@ use std::mem;
|
|||
use neqo_common::{qinfo, qwarn, Encoder};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::frame::Frame;
|
||||
use crate::frame::{Frame, StreamType};
|
||||
use crate::packet::PacketBuilder;
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::recv_stream::RecvStreams;
|
||||
use crate::send_stream::SendStreams;
|
||||
use crate::stats::FrameStats;
|
||||
use crate::stream_id::{StreamId, StreamIndex, StreamIndexes, StreamType};
|
||||
use crate::{AppError, Error, Res};
|
||||
use crate::stream_id::{StreamId, StreamIndex, StreamIndexes};
|
||||
use crate::AppError;
|
||||
|
||||
type FlowFrame = Frame<'static>;
|
||||
pub type FlowControlRecoveryToken = FlowFrame;
|
||||
|
@ -77,6 +77,11 @@ impl FlowMgr {
|
|||
self.from_conn.insert(mem::discriminant(&frame), frame);
|
||||
}
|
||||
|
||||
pub fn path_response(&mut self, data: [u8; 8]) {
|
||||
let frame = Frame::PathResponse { data };
|
||||
self.from_conn.insert(mem::discriminant(&frame), frame);
|
||||
}
|
||||
|
||||
pub fn max_data(&mut self, maximum_data: u64) {
|
||||
let frame = Frame::MaxData { maximum_data };
|
||||
self.from_conn.insert(mem::discriminant(&frame), frame);
|
||||
|
@ -268,6 +273,7 @@ impl FlowMgr {
|
|||
}
|
||||
}
|
||||
}
|
||||
Frame::PathResponse { .. } => qinfo!("Path Response lost, not re-sent"),
|
||||
_ => qwarn!("Unexpected Flow frame {:?} lost, not re-sent", token),
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +283,7 @@ impl FlowMgr {
|
|||
builder: &mut PacketBuilder,
|
||||
tokens: &mut Vec<RecoveryToken>,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<()> {
|
||||
) {
|
||||
while let Some(frame) = self.peek() {
|
||||
// All these frames are bags of varints, so we can just extract the
|
||||
// varints and use common code for writing.
|
||||
|
@ -333,6 +339,19 @@ impl FlowMgr {
|
|||
smallvec![stream_id.as_u64(), *stream_data_limit]
|
||||
}
|
||||
|
||||
// A special case, just write it out and move on..
|
||||
Frame::PathResponse { data } => {
|
||||
stats.path_response += 1;
|
||||
if builder.remaining() >= Encoder::varint_len(frame.get_type()) + data.len() {
|
||||
builder.encode_varint(frame.get_type());
|
||||
builder.encode(data);
|
||||
tokens.push(RecoveryToken::Flow(self.next().unwrap()));
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ => unreachable!("{:?}", frame),
|
||||
};
|
||||
debug_assert!(!values.spilled());
|
||||
|
@ -348,15 +367,11 @@ impl FlowMgr {
|
|||
for v in values {
|
||||
builder.encode_varint(v);
|
||||
}
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(16));
|
||||
}
|
||||
tokens.push(RecoveryToken::Flow(self.next().unwrap()));
|
||||
} else {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ use neqo_common::{qtrace, Decoder};
|
|||
|
||||
use crate::cid::MAX_CONNECTION_ID_LEN;
|
||||
use crate::packet::PacketType;
|
||||
use crate::stream_id::{StreamId, StreamIndex, StreamType};
|
||||
use crate::{AppError, ConnectionError, Error, Res, TransportError};
|
||||
use crate::stream_id::{StreamId, StreamIndex};
|
||||
use crate::{AppError, ConnectionError, Error, Res, TransportError, ERROR_APPLICATION_CLOSE};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::RangeInclusive;
|
||||
|
@ -37,18 +37,50 @@ const FRAME_TYPE_DATA_BLOCKED: FrameType = 0x14;
|
|||
const FRAME_TYPE_STREAM_DATA_BLOCKED: FrameType = 0x15;
|
||||
const FRAME_TYPE_STREAMS_BLOCKED_BIDI: FrameType = 0x16;
|
||||
const FRAME_TYPE_STREAMS_BLOCKED_UNIDI: FrameType = 0x17;
|
||||
pub const FRAME_TYPE_NEW_CONNECTION_ID: FrameType = 0x18;
|
||||
pub const FRAME_TYPE_RETIRE_CONNECTION_ID: FrameType = 0x19;
|
||||
pub const FRAME_TYPE_PATH_CHALLENGE: FrameType = 0x1a;
|
||||
pub const FRAME_TYPE_PATH_RESPONSE: FrameType = 0x1b;
|
||||
const FRAME_TYPE_NEW_CONNECTION_ID: FrameType = 0x18;
|
||||
const FRAME_TYPE_RETIRE_CONNECTION_ID: FrameType = 0x19;
|
||||
const FRAME_TYPE_PATH_CHALLENGE: FrameType = 0x1a;
|
||||
const FRAME_TYPE_PATH_RESPONSE: FrameType = 0x1b;
|
||||
pub const FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT: FrameType = 0x1c;
|
||||
pub const FRAME_TYPE_CONNECTION_CLOSE_APPLICATION: FrameType = 0x1d;
|
||||
pub const FRAME_TYPE_HANDSHAKE_DONE: FrameType = 0x1e;
|
||||
const FRAME_TYPE_HANDSHAKE_DONE: FrameType = 0x1e;
|
||||
|
||||
const STREAM_FRAME_BIT_FIN: u64 = 0x01;
|
||||
const STREAM_FRAME_BIT_LEN: u64 = 0x02;
|
||||
const STREAM_FRAME_BIT_OFF: u64 = 0x04;
|
||||
|
||||
/// `FRAME_APPLICATION_CLOSE` is the default CONNECTION_CLOSE frame that
|
||||
/// is sent when an application error code needs to be sent in an
|
||||
/// Initial or Handshake packet.
|
||||
const FRAME_APPLICATION_CLOSE: &Frame = &Frame::ConnectionClose {
|
||||
error_code: CloseError::Transport(ERROR_APPLICATION_CLOSE),
|
||||
frame_type: 0,
|
||||
reason_phrase: Vec::new(),
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone, PartialOrd, Eq, Ord, Hash)]
|
||||
/// Bi-Directional or Uni-Directional.
|
||||
pub enum StreamType {
|
||||
BiDi,
|
||||
UniDi,
|
||||
}
|
||||
|
||||
impl StreamType {
|
||||
fn frame_type_bit(self) -> u64 {
|
||||
match self {
|
||||
Self::BiDi => 0,
|
||||
Self::UniDi => 1,
|
||||
}
|
||||
}
|
||||
fn from_type_bit(bit: u64) -> Self {
|
||||
if (bit & 0x01) == 0 {
|
||||
Self::BiDi
|
||||
} else {
|
||||
Self::UniDi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Clone, Copy)]
|
||||
pub enum CloseError {
|
||||
Transport(TransportError),
|
||||
|
@ -174,21 +206,6 @@ pub enum Frame<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Frame<'a> {
|
||||
fn get_stream_type_bit(stream_type: StreamType) -> u64 {
|
||||
match stream_type {
|
||||
StreamType::BiDi => 0,
|
||||
StreamType::UniDi => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn stream_type_from_bit(bit: u64) -> StreamType {
|
||||
if (bit & 0x01) == 0 {
|
||||
StreamType::BiDi
|
||||
} else {
|
||||
StreamType::UniDi
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> FrameType {
|
||||
match self {
|
||||
Self::Padding => FRAME_TYPE_PADDING,
|
||||
|
@ -204,12 +221,12 @@ impl<'a> Frame<'a> {
|
|||
Self::MaxData { .. } => FRAME_TYPE_MAX_DATA,
|
||||
Self::MaxStreamData { .. } => FRAME_TYPE_MAX_STREAM_DATA,
|
||||
Self::MaxStreams { stream_type, .. } => {
|
||||
FRAME_TYPE_MAX_STREAMS_BIDI + Self::get_stream_type_bit(*stream_type)
|
||||
FRAME_TYPE_MAX_STREAMS_BIDI + stream_type.frame_type_bit()
|
||||
}
|
||||
Self::DataBlocked { .. } => FRAME_TYPE_DATA_BLOCKED,
|
||||
Self::StreamDataBlocked { .. } => FRAME_TYPE_STREAM_DATA_BLOCKED,
|
||||
Self::StreamsBlocked { stream_type, .. } => {
|
||||
FRAME_TYPE_STREAMS_BLOCKED_BIDI + Self::get_stream_type_bit(*stream_type)
|
||||
FRAME_TYPE_STREAMS_BLOCKED_BIDI + stream_type.frame_type_bit()
|
||||
}
|
||||
Self::NewConnectionId { .. } => FRAME_TYPE_NEW_CONNECTION_ID,
|
||||
Self::RetireConnectionId { .. } => FRAME_TYPE_RETIRE_CONNECTION_ID,
|
||||
|
@ -236,20 +253,21 @@ impl<'a> Frame<'a> {
|
|||
t
|
||||
}
|
||||
|
||||
/// If the frame causes a recipient to generate an ACK within its
|
||||
/// advertised maximum acknowledgement delay.
|
||||
pub fn ack_eliciting(&self) -> bool {
|
||||
!matches!(self, Self::Ack { .. } | Self::Padding | Self::ConnectionClose { .. })
|
||||
/// Convert a CONNECTION_CLOSE into a nicer CONNECTION_CLOSE.
|
||||
pub fn sanitize_close(&self) -> &Self {
|
||||
if let Self::ConnectionClose { error_code, .. } = &self {
|
||||
if let CloseError::Application(_) = error_code {
|
||||
FRAME_APPLICATION_CLOSE
|
||||
} else {
|
||||
self
|
||||
}
|
||||
} else {
|
||||
panic!("Attempted to sanitize a non-close frame");
|
||||
}
|
||||
}
|
||||
|
||||
/// If the frame can be sent in a path probe
|
||||
/// without initiating migration to that path.
|
||||
pub fn path_probing(&self) -> bool {
|
||||
matches!(self,
|
||||
Self::Padding
|
||||
| Self::NewConnectionId { .. }
|
||||
| Self::PathChallenge { .. }
|
||||
| Self::PathResponse { .. })
|
||||
pub fn ack_eliciting(&self) -> bool {
|
||||
!matches!(self, Self::Ack { .. } | Self::Padding | Self::ConnectionClose { .. })
|
||||
}
|
||||
|
||||
/// Converts AckRanges as encoded in a ACK frame (see -transport
|
||||
|
@ -442,7 +460,7 @@ impl<'a> Frame<'a> {
|
|||
return Err(Error::StreamLimitError);
|
||||
}
|
||||
Ok(Self::MaxStreams {
|
||||
stream_type: Self::stream_type_from_bit(t),
|
||||
stream_type: StreamType::from_type_bit(t),
|
||||
maximum_streams: StreamIndex::new(m),
|
||||
})
|
||||
}
|
||||
|
@ -455,7 +473,7 @@ impl<'a> Frame<'a> {
|
|||
}),
|
||||
FRAME_TYPE_STREAMS_BLOCKED_BIDI | FRAME_TYPE_STREAMS_BLOCKED_UNIDI => {
|
||||
Ok(Self::StreamsBlocked {
|
||||
stream_type: Self::stream_type_from_bit(t),
|
||||
stream_type: StreamType::from_type_bit(t),
|
||||
stream_limit: StreamIndex::new(dv(dec)?),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -28,22 +28,22 @@ mod send_stream;
|
|||
mod sender;
|
||||
pub mod server;
|
||||
mod stats;
|
||||
pub mod stream_id;
|
||||
mod stream_id;
|
||||
pub mod tparams;
|
||||
mod tracking;
|
||||
|
||||
pub use self::cc::CongestionControlAlgorithm;
|
||||
pub use self::cid::{
|
||||
ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef,
|
||||
EmptyConnectionIdGenerator, RandomConnectionIdGenerator,
|
||||
pub use self::cid::{ConnectionId, ConnectionIdManager};
|
||||
pub use self::connection::{
|
||||
params::ConnectionParameters, Connection, FixedConnectionIdManager, Output, State,
|
||||
ZeroRttState, LOCAL_STREAM_LIMIT_BIDI, LOCAL_STREAM_LIMIT_UNI,
|
||||
};
|
||||
pub use self::connection::{params::ConnectionParameters, Connection, Output, State, ZeroRttState};
|
||||
pub use self::events::{ConnectionEvent, ConnectionEvents};
|
||||
pub use self::frame::CloseError;
|
||||
pub use self::frame::{CloseError, StreamType};
|
||||
pub use self::packet::QuicVersion;
|
||||
pub use self::sender::PacketSender;
|
||||
pub use self::stats::Stats;
|
||||
pub use self::stream_id::{StreamId, StreamType};
|
||||
pub use self::stream_id::StreamId;
|
||||
|
||||
pub use self::recv_stream::RECV_BUFFER_SIZE;
|
||||
pub use self::send_stream::SEND_BUFFER_SIZE;
|
||||
|
@ -56,9 +56,7 @@ const ERROR_AEAD_LIMIT_REACHED: TransportError = 15;
|
|||
#[allow(clippy::pub_enum_variant_names)]
|
||||
pub enum Error {
|
||||
NoError,
|
||||
// Each time tihe error is return a different parameter is supply.
|
||||
// This will be use to distinguish each occurance of this error.
|
||||
InternalError(u16),
|
||||
InternalError,
|
||||
ConnectionRefused,
|
||||
FlowControlError,
|
||||
StreamLimitError,
|
||||
|
@ -73,10 +71,8 @@ pub enum Error {
|
|||
QlogError,
|
||||
CryptoAlert(u8),
|
||||
|
||||
// All internal errors from here. Please keep these sorted.
|
||||
// All internal errors from here.
|
||||
AckedUnsentPacket,
|
||||
ConnectionIdLimitExceeded,
|
||||
ConnectionIdsExhausted,
|
||||
ConnectionState,
|
||||
DecodingFrame,
|
||||
DecryptError,
|
||||
|
@ -98,7 +94,6 @@ pub enum Error {
|
|||
/// An attempt to update keys can be blocked if
|
||||
/// a packet sent with the current keys hasn't been acknowledged.
|
||||
KeyUpdateBlocked,
|
||||
NoAvailablePath,
|
||||
NoMoreData,
|
||||
NotConnected,
|
||||
PacketNumberOverlap,
|
||||
|
@ -107,7 +102,6 @@ pub enum Error {
|
|||
StatelessReset,
|
||||
TooMuchData,
|
||||
UnexpectedMessage,
|
||||
UnknownConnectionId,
|
||||
UnknownFrameType,
|
||||
VersionNegotiation,
|
||||
WrongRole,
|
||||
|
@ -131,7 +125,6 @@ impl Error {
|
|||
Self::InvalidToken => 11,
|
||||
Self::KeysExhausted => ERROR_AEAD_LIMIT_REACHED,
|
||||
Self::ApplicationError => ERROR_APPLICATION_CLOSE,
|
||||
Self::NoAvailablePath => 16,
|
||||
Self::CryptoAlert(a) => 0x100 + u64::from(*a),
|
||||
// All the rest are internal errors.
|
||||
_ => 1,
|
||||
|
|
|
@ -231,10 +231,6 @@ impl PacketBuilder {
|
|||
self.limit = limit;
|
||||
}
|
||||
|
||||
pub fn limit(&mut self) -> usize {
|
||||
self.limit
|
||||
}
|
||||
|
||||
/// How many bytes remain against the size limit for the builder.
|
||||
#[must_use]
|
||||
pub fn remaining(&self) -> usize {
|
||||
|
@ -242,14 +238,8 @@ impl PacketBuilder {
|
|||
}
|
||||
|
||||
/// Pad with "PADDING" frames.
|
||||
pub fn pad(&mut self) -> Res<()> {
|
||||
pub fn pad(&mut self) {
|
||||
self.encoder.pad_to(self.limit, 0);
|
||||
if self.len() > self.limit {
|
||||
qwarn!("Packet contents are more than the limit");
|
||||
debug_assert!(false);
|
||||
return Err(Error::InternalError(17));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add unpredictable values for unprotected parts of the packet.
|
||||
|
@ -262,25 +252,18 @@ impl PacketBuilder {
|
|||
|
||||
/// For an Initial packet, encode the token.
|
||||
/// If you fail to do this, then you will not get a valid packet.
|
||||
pub fn initial_token(&mut self, token: &[u8]) -> Res<()> {
|
||||
pub fn initial_token(&mut self, token: &[u8]) {
|
||||
debug_assert_eq!(
|
||||
self.encoder[self.header.start] & 0xb0,
|
||||
PACKET_BIT_LONG | PACKET_TYPE_INITIAL << 4
|
||||
);
|
||||
self.encoder.encode_vvec(token);
|
||||
|
||||
if self.len() > self.limit {
|
||||
qwarn!("Packet contents are more than the limit");
|
||||
debug_assert!(false);
|
||||
return Err(Error::InternalError(18));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a packet number of the given size.
|
||||
/// For a long header packet, this also inserts a dummy length.
|
||||
/// The length is filled in after calling `build`.
|
||||
pub fn pn(&mut self, pn: PacketNumber, pn_len: usize) -> Res<()> {
|
||||
pub fn pn(&mut self, pn: PacketNumber, pn_len: usize) {
|
||||
// Reserve space for a length in long headers.
|
||||
if self.is_long() {
|
||||
self.offsets.len = self.encoder.len();
|
||||
|
@ -299,13 +282,6 @@ impl PacketBuilder {
|
|||
self.encoder[self.header.start] |= u8::try_from(pn_len - 1).unwrap();
|
||||
self.header.end = self.encoder.len();
|
||||
self.pn = pn;
|
||||
|
||||
if self.len() > self.limit {
|
||||
qwarn!("Packet contents are more than the limit");
|
||||
debug_assert!(false);
|
||||
return Err(Error::InternalError(19));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_len(&mut self, expansion: usize) {
|
||||
|
@ -331,7 +307,7 @@ impl PacketBuilder {
|
|||
if self.len() > self.limit {
|
||||
qwarn!("Packet contents are more than the limit");
|
||||
debug_assert!(false);
|
||||
return Err(Error::InternalError(5));
|
||||
return Err(Error::InternalError);
|
||||
}
|
||||
|
||||
self.pad_for_crypto(crypto);
|
||||
|
@ -524,11 +500,6 @@ impl<'a> PublicPacket<'a> {
|
|||
let first = Self::opt(decoder.decode_byte())?;
|
||||
|
||||
if first & 0x80 == PACKET_BIT_SHORT {
|
||||
// Conveniently, this also guarantees that there is enough space
|
||||
// for a connection ID of any size.
|
||||
if decoder.remaining() < SAMPLE_OFFSET + SAMPLE_SIZE {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
let dcid = Self::opt(dcid_decoder.decode_cid(&mut decoder))?;
|
||||
if decoder.remaining() < SAMPLE_OFFSET + SAMPLE_SIZE {
|
||||
return Err(Error::InvalidPacket);
|
||||
|
@ -828,7 +799,7 @@ impl Deref for DecryptedPacket {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::crypto::{CryptoDxState, CryptoStates};
|
||||
use crate::{EmptyConnectionIdGenerator, QuicVersion, RandomConnectionIdGenerator};
|
||||
use crate::{FixedConnectionIdManager, QuicVersion};
|
||||
use neqo_common::Encoder;
|
||||
use test_fixture::{fixture_init, now};
|
||||
|
||||
|
@ -836,8 +807,8 @@ mod tests {
|
|||
const SERVER_CID: &[u8] = &[0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5];
|
||||
|
||||
/// This is a connection ID manager, which is only used for decoding short header packets.
|
||||
fn cid_mgr() -> RandomConnectionIdGenerator {
|
||||
RandomConnectionIdGenerator::new(SERVER_CID.len())
|
||||
fn cid_mgr() -> FixedConnectionIdManager {
|
||||
FixedConnectionIdManager::new(SERVER_CID.len())
|
||||
}
|
||||
|
||||
const SAMPLE_INITIAL_PAYLOAD: &[u8] = &[
|
||||
|
@ -878,8 +849,8 @@ mod tests {
|
|||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
);
|
||||
builder.initial_token(&[]).unwrap();
|
||||
builder.pn(1, 2).unwrap();
|
||||
builder.initial_token(&[]);
|
||||
builder.pn(1, 2);
|
||||
builder.encode(&SAMPLE_INITIAL_PAYLOAD);
|
||||
let packet = builder.build(&mut prot).expect("build");
|
||||
assert_eq!(&packet[..], SAMPLE_INITIAL);
|
||||
|
@ -940,7 +911,7 @@ mod tests {
|
|||
fixture_init();
|
||||
let mut builder =
|
||||
PacketBuilder::short(Encoder::new(), true, &ConnectionId::from(SERVER_CID));
|
||||
builder.pn(0, 1).unwrap();
|
||||
builder.pn(0, 1);
|
||||
builder.encode(SAMPLE_SHORT_PAYLOAD); // Enough payload for sampling.
|
||||
let packet = builder
|
||||
.build(&mut CryptoDxState::test_default())
|
||||
|
@ -956,7 +927,7 @@ mod tests {
|
|||
let mut builder =
|
||||
PacketBuilder::short(Encoder::new(), true, &ConnectionId::from(SERVER_CID));
|
||||
builder.scramble(true);
|
||||
builder.pn(0, 1).unwrap();
|
||||
builder.pn(0, 1);
|
||||
firsts.push(builder[0]);
|
||||
}
|
||||
let is_set = |bit| move |v| v & bit == bit;
|
||||
|
@ -988,7 +959,7 @@ mod tests {
|
|||
fixture_init();
|
||||
let (packet, remainder) = PublicPacket::decode(
|
||||
SAMPLE_SHORT,
|
||||
&RandomConnectionIdGenerator::new(SERVER_CID.len() - 1),
|
||||
&FixedConnectionIdManager::new(SERVER_CID.len() - 1),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(packet.packet_type(), PacketType::Short);
|
||||
|
@ -1003,7 +974,7 @@ mod tests {
|
|||
fn decode_short_long_cid() {
|
||||
assert!(PublicPacket::decode(
|
||||
SAMPLE_SHORT,
|
||||
&RandomConnectionIdGenerator::new(SERVER_CID.len() + 1)
|
||||
&FixedConnectionIdManager::new(SERVER_CID.len() + 1)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
@ -1019,14 +990,14 @@ mod tests {
|
|||
&ConnectionId::from(SERVER_CID),
|
||||
&ConnectionId::from(CLIENT_CID),
|
||||
);
|
||||
builder.pn(0, 1).unwrap();
|
||||
builder.pn(0, 1);
|
||||
builder.encode(&[0; 3]);
|
||||
let encoder = builder.build(&mut prot).expect("build");
|
||||
assert_eq!(encoder.len(), 45);
|
||||
let first = encoder.clone();
|
||||
|
||||
let mut builder = PacketBuilder::short(encoder, false, &ConnectionId::from(SERVER_CID));
|
||||
builder.pn(1, 3).unwrap();
|
||||
builder.pn(1, 3);
|
||||
builder.encode(&[0]); // Minimal size (packet number is big enough).
|
||||
let encoder = builder.build(&mut prot).expect("build");
|
||||
assert_eq!(
|
||||
|
@ -1053,7 +1024,7 @@ mod tests {
|
|||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(&[][..]),
|
||||
);
|
||||
builder.pn(0, 1).unwrap();
|
||||
builder.pn(0, 1);
|
||||
builder.encode(&[1, 2, 3]);
|
||||
let packet = builder.build(&mut CryptoDxState::test_default()).unwrap();
|
||||
assert_eq!(&packet[..], EXPECTED);
|
||||
|
@ -1072,7 +1043,7 @@ mod tests {
|
|||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(&[][..]),
|
||||
);
|
||||
builder.pn(0, 1).unwrap();
|
||||
builder.pn(0, 1);
|
||||
builder.scramble(true);
|
||||
if (builder[0] & PACKET_BIT_FIXED_QUIC) == 0 {
|
||||
found_unset = true;
|
||||
|
@ -1093,8 +1064,8 @@ mod tests {
|
|||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
);
|
||||
builder.initial_token(&[]).unwrap();
|
||||
builder.pn(1, 2).unwrap();
|
||||
builder.initial_token(&[]);
|
||||
builder.pn(1, 2);
|
||||
let encoder = builder.abort();
|
||||
assert!(encoder.is_empty());
|
||||
}
|
||||
|
@ -1204,7 +1175,7 @@ mod tests {
|
|||
fn decode_retry(quic_version: QuicVersion, sample_retry: &[u8]) {
|
||||
fixture_init();
|
||||
let (packet, remainder) =
|
||||
PublicPacket::decode(sample_retry, &RandomConnectionIdGenerator::new(5)).unwrap();
|
||||
PublicPacket::decode(sample_retry, &FixedConnectionIdManager::new(5)).unwrap();
|
||||
assert!(packet.is_valid_retry(&ConnectionId::from(CLIENT_CID)));
|
||||
assert_eq!(Some(quic_version), packet.quic_version);
|
||||
assert!(packet.dcid().is_empty());
|
||||
|
@ -1247,7 +1218,7 @@ mod tests {
|
|||
#[test]
|
||||
fn invalid_retry() {
|
||||
fixture_init();
|
||||
let cid_mgr = RandomConnectionIdGenerator::new(5);
|
||||
let cid_mgr = FixedConnectionIdManager::new(5);
|
||||
let odcid = ConnectionId::from(CLIENT_CID);
|
||||
|
||||
assert!(PublicPacket::decode(&[], &cid_mgr).is_err());
|
||||
|
@ -1299,7 +1270,7 @@ mod tests {
|
|||
#[test]
|
||||
fn parse_vn() {
|
||||
let (packet, remainder) =
|
||||
PublicPacket::decode(SAMPLE_VN, &EmptyConnectionIdGenerator::default()).unwrap();
|
||||
PublicPacket::decode(SAMPLE_VN, &FixedConnectionIdManager::new(5)).unwrap();
|
||||
assert!(remainder.is_empty());
|
||||
assert_eq!(&packet.dcid[..], SERVER_CID);
|
||||
assert!(packet.scid.is_some());
|
||||
|
@ -1320,7 +1291,7 @@ mod tests {
|
|||
enc.encode_uint(4, 0x5a6a_7a8a_u64);
|
||||
|
||||
let (packet, remainder) =
|
||||
PublicPacket::decode(&enc, &EmptyConnectionIdGenerator::default()).unwrap();
|
||||
PublicPacket::decode(&enc, &FixedConnectionIdManager::new(5)).unwrap();
|
||||
assert!(remainder.is_empty());
|
||||
assert_eq!(&packet.dcid[..], BIG_DCID);
|
||||
assert!(packet.scid.is_some());
|
||||
|
@ -1356,7 +1327,7 @@ mod tests {
|
|||
];
|
||||
fixture_init();
|
||||
let (packet, slice) =
|
||||
PublicPacket::decode(PACKET, &EmptyConnectionIdGenerator::default()).unwrap();
|
||||
PublicPacket::decode(PACKET, &FixedConnectionIdManager::new(0)).unwrap();
|
||||
assert!(slice.is_empty());
|
||||
let decrypted = packet
|
||||
.decrypt(&mut CryptoStates::test_chacha(), now())
|
||||
|
|
|
@ -49,7 +49,7 @@ where
|
|||
.try_with(|aead| f(&aead.borrow()))
|
||||
.map_err(|e| {
|
||||
qerror!("Unable to access Retry AEAD: {:?}", e);
|
||||
Error::InternalError(6)
|
||||
Error::InternalError
|
||||
})?
|
||||
}
|
||||
|
||||
|
|
|
@ -4,27 +4,11 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![deny(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
use crate::cid::{ConnectionId, ConnectionIdRef};
|
||||
|
||||
use crate::cid::{ConnectionId, ConnectionIdRef, RemoteConnectionIdEntry};
|
||||
use crate::frame::{
|
||||
FRAME_TYPE_PATH_CHALLENGE, FRAME_TYPE_PATH_RESPONSE, FRAME_TYPE_RETIRE_CONNECTION_ID,
|
||||
};
|
||||
use crate::packet::PacketBuilder;
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::stats::FrameStats;
|
||||
use crate::{Error, Res};
|
||||
|
||||
use neqo_common::{hex, qdebug, qinfo, qtrace, Datagram, Encoder};
|
||||
use neqo_crypto::random;
|
||||
use neqo_common::Datagram;
|
||||
|
||||
/// This is the MTU that we assume when using IPv6.
|
||||
/// We use this size for Initial packets, so we don't need to worry about probing for support.
|
||||
|
@ -34,501 +18,78 @@ use neqo_crypto::random;
|
|||
pub const PATH_MTU_V6: usize = 1337;
|
||||
/// The path MTU for IPv4 can be 20 bytes larger than for v6.
|
||||
pub const PATH_MTU_V4: usize = PATH_MTU_V6 + 20;
|
||||
/// The number of times that a path will be probed before it is considered failed.
|
||||
const MAX_PATH_PROBES: usize = 3;
|
||||
/// The maximum number of paths that `Paths` will track.
|
||||
const MAX_PATHS: usize = 15;
|
||||
|
||||
pub type PathRef = Rc<RefCell<Path>>;
|
||||
|
||||
/// A collection for network paths.
|
||||
/// This holds a collection of paths that have been used for sending or
|
||||
/// receiving, plus an additional "temporary" path that is held only while
|
||||
/// processing a packet.
|
||||
/// This structure limits its storage and will forget about paths if it
|
||||
/// is exposed to too many paths.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Paths {
|
||||
/// All of the paths.
|
||||
paths: Vec<PathRef>,
|
||||
/// This is the primary path. This will only be `None` initially, so
|
||||
/// care needs to be taken regarding that only during the handshake.
|
||||
/// This path will also be in `paths`.
|
||||
primary: Option<PathRef>,
|
||||
|
||||
/// The path that we would prefer to migrate to.
|
||||
migration_target: Option<PathRef>,
|
||||
|
||||
/// Connection IDs that need to be retired.
|
||||
to_retire: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Paths {
|
||||
/// Find the path for the given addresses.
|
||||
/// This might be a temporary path.
|
||||
pub fn find_path(&self, local: SocketAddr, remote: SocketAddr) -> PathRef {
|
||||
self.paths
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
if p.borrow().received_on(local, remote, false) {
|
||||
Some(Rc::clone(p))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| Rc::new(RefCell::new(Path::temporary(local, remote))))
|
||||
}
|
||||
|
||||
/// Find the path, but allow for rebinding. That matches the pair of addresses
|
||||
/// to paths that match the remote address only based on IP addres, not port.
|
||||
/// We use this when the other side migrates to skip address validation and
|
||||
/// creating a new path.
|
||||
pub fn find_path_with_rebinding(&self, local: SocketAddr, remote: SocketAddr) -> PathRef {
|
||||
self.paths
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
if p.borrow().received_on(local, remote, false) {
|
||||
Some(Rc::clone(p))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
self.paths.iter().find_map(|p| {
|
||||
if p.borrow().received_on(local, remote, true) {
|
||||
Some(Rc::clone(p))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| Rc::new(RefCell::new(Path::temporary(local, remote))))
|
||||
}
|
||||
|
||||
/// Get a reference to the primary path. This will assert if there is no primary
|
||||
/// path, which happens at a server prior to receiving a valid Initial packet
|
||||
/// from a client. So be careful using this method.
|
||||
pub fn primary(&self) -> PathRef {
|
||||
self.primary_fallible().unwrap()
|
||||
}
|
||||
|
||||
/// Get a reference to the primary path. Use this prior to handshake completion.
|
||||
pub fn primary_fallible(&self) -> Option<PathRef> {
|
||||
self.primary.as_ref().map(Rc::clone)
|
||||
}
|
||||
|
||||
/// Returns true if the path is not permanent.
|
||||
pub fn is_temporary(&self, path: &PathRef) -> bool {
|
||||
// Ask the path first, which is simpler.
|
||||
path.borrow().is_temporary() || !self.paths.iter().any(|p| Rc::ptr_eq(p, path))
|
||||
}
|
||||
|
||||
fn retire(to_retire: &mut Vec<u64>, retired: &PathRef) {
|
||||
let seqno = retired
|
||||
.borrow()
|
||||
.remote_cid
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.sequence_number();
|
||||
to_retire.push(seqno);
|
||||
}
|
||||
|
||||
/// Adopt a temporary path as permanent.
|
||||
/// The first path that is made permanent is made primary.
|
||||
pub fn make_permanent(
|
||||
&mut self,
|
||||
path: &PathRef,
|
||||
local_cid: Option<ConnectionId>,
|
||||
remote_cid: RemoteConnectionIdEntry,
|
||||
) {
|
||||
debug_assert!(self.is_temporary(&path));
|
||||
|
||||
// Make sure not to track too many paths.
|
||||
// This protects index 0, which contains the primary path.
|
||||
if self.paths.len() >= MAX_PATHS {
|
||||
debug_assert_eq!(self.paths.len(), MAX_PATHS);
|
||||
let removed = self.paths.remove(1);
|
||||
Self::retire(&mut self.to_retire, &removed);
|
||||
debug_assert_eq!(Rc::strong_count(&removed), 1);
|
||||
}
|
||||
|
||||
qdebug!([path.borrow()], "Make permanent");
|
||||
path.borrow_mut().make_permanent(local_cid, remote_cid);
|
||||
self.paths.push(Rc::clone(&path));
|
||||
if self.primary.is_none() {
|
||||
assert!(self.select_primary(&path).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
/// Select a path as the primary. Returns the old primary path.
|
||||
/// The old path is only necessary if this change in path is a reaction to a
|
||||
/// migration from a peer, in which case the old path needs to be probed.
|
||||
#[must_use]
|
||||
fn select_primary(&mut self, path: &PathRef) -> Option<PathRef> {
|
||||
qinfo!([path.borrow()], "set as primary path");
|
||||
let old_path = self.primary.replace(Rc::clone(path)).map(|old| {
|
||||
old.borrow_mut().set_primary(false);
|
||||
old
|
||||
});
|
||||
|
||||
// Swap the primary path into slot 0, so that it is protected from eviction.
|
||||
let idx = self
|
||||
.paths
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, p)| if Rc::ptr_eq(p, path) { Some(i) } else { None })
|
||||
.expect("migration target should be permanent");
|
||||
self.paths.swap(0, idx);
|
||||
|
||||
path.borrow_mut().set_primary(true);
|
||||
old_path
|
||||
}
|
||||
|
||||
/// Migrate to the identified path. If `force` is true, the path
|
||||
/// is forcibly marked as valid and the path is used immediately.
|
||||
/// Otherwise, migration will occur after probing succeeds.
|
||||
/// The path is always probed and will be abandoned if probing fails.
|
||||
pub fn migrate(&mut self, path: &PathRef, force: bool, now: Instant) {
|
||||
debug_assert!(!self.is_temporary(path));
|
||||
if force || path.borrow().is_valid() {
|
||||
path.borrow_mut().set_valid(now);
|
||||
let _ = self.select_primary(path);
|
||||
} else {
|
||||
self.migration_target = Some(Rc::clone(path));
|
||||
}
|
||||
path.borrow_mut().probe();
|
||||
}
|
||||
|
||||
/// Process elapsed time for active paths.
|
||||
/// Returns an true if there are viable paths remaining after tidying up.
|
||||
///
|
||||
/// TODO(mt) - the paths should own the RTT estimator, so they can find the PTO
|
||||
/// for themselves.
|
||||
pub fn process_timeout(&mut self, now: Instant, pto: Duration) -> bool {
|
||||
let to_retire = &mut self.to_retire;
|
||||
let mut primary_failed = false;
|
||||
self.paths.retain(|p| {
|
||||
if p.borrow_mut().process_timeout(now, pto) {
|
||||
true
|
||||
} else {
|
||||
qdebug!([p.borrow()], "Retiring path");
|
||||
if p.borrow().is_primary() {
|
||||
primary_failed = true;
|
||||
}
|
||||
Self::retire(to_retire, p);
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if primary_failed {
|
||||
self.primary = None;
|
||||
// Find a valid path to fall back to.
|
||||
if let Some(fallback) = self
|
||||
.paths
|
||||
.iter()
|
||||
.rev() // More recent paths are toward the end.
|
||||
.find(|p| p.borrow().is_valid())
|
||||
{
|
||||
// Need a clone as `fallback` is borrowed from `self`.
|
||||
let path = Rc::clone(fallback);
|
||||
qinfo!([path.borrow()], "Failing over after primary path failed");
|
||||
let _ = self.select_primary(&path);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Get when the next call to `process_timeout()` should be scheduled.
|
||||
pub fn next_timeout(&self, pto: Duration) -> Option<Instant> {
|
||||
self.paths
|
||||
.iter()
|
||||
.filter_map(|p| p.borrow().next_timeout(pto))
|
||||
.min()
|
||||
}
|
||||
|
||||
/// Set the identified path to be primary.
|
||||
/// This panics if `make_permanent` hasn't been called.
|
||||
pub fn handle_migration(&mut self, path: &PathRef, remote: SocketAddr, now: Instant) {
|
||||
qtrace!([self.primary().borrow()], "handle_migration");
|
||||
// The update here needs to match the checks in `Path::received_on`.
|
||||
// Here, we update the remote port number to match the source port on the
|
||||
// datagram that was received. This ensures that we send subsequent
|
||||
// packets back to the right place.
|
||||
path.borrow_mut().update_port(remote.port());
|
||||
|
||||
if path.borrow().is_primary() {
|
||||
// Update when the path was last regarded as valid.
|
||||
path.borrow_mut().update(now);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(old_path) = self.select_primary(path) {
|
||||
// Need to probe the old path if the peer migrates.
|
||||
old_path.borrow_mut().probe();
|
||||
// TODO(mt) - suppress probing if the path was valid within 3PTO.
|
||||
}
|
||||
}
|
||||
|
||||
/// Select a path to send on. This will select the first path that has
|
||||
/// probes to send, then fall back to the primary path.
|
||||
pub fn select_path(&self) -> Option<PathRef> {
|
||||
self.paths
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
if p.borrow().has_probe() {
|
||||
Some(Rc::clone(p))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| self.primary.as_ref().map(Rc::clone))
|
||||
}
|
||||
|
||||
/// A `PATH_RESPONSE` was received.
|
||||
pub fn path_response(&mut self, response: [u8; 8], now: Instant) {
|
||||
for p in &self.paths {
|
||||
if p.borrow_mut().path_response(response, now) {
|
||||
// The response was accepted. If this path is one we intend
|
||||
// to migrate to, then migrate.
|
||||
if self
|
||||
.migration_target
|
||||
.as_ref()
|
||||
.map_or(false, |target| Rc::ptr_eq(target, p))
|
||||
{
|
||||
let primary = self.migration_target.take();
|
||||
let _ = self.select_primary(&primary.unwrap());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write out any `RETIRE_CONNECTION_ID` frames that are outstanding.
|
||||
pub fn write_frames(
|
||||
&mut self,
|
||||
builder: &mut PacketBuilder,
|
||||
tokens: &mut Vec<RecoveryToken>,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<()> {
|
||||
while let Some(seqno) = self.to_retire.pop() {
|
||||
if builder.remaining() < 1 + Encoder::varint_len(seqno) {
|
||||
self.to_retire.push(seqno);
|
||||
break;
|
||||
}
|
||||
builder.encode_varint(FRAME_TYPE_RETIRE_CONNECTION_ID);
|
||||
builder.encode_varint(seqno);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(20));
|
||||
}
|
||||
tokens.push(RecoveryToken::RetireConnectionId(seqno));
|
||||
stats.retire_connection_id += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lost_retire_cid(&mut self, lost: u64) {
|
||||
self.to_retire.push(lost);
|
||||
}
|
||||
|
||||
pub fn acked_retire_cid(&mut self, acked: u64) {
|
||||
self.to_retire.retain(|&seqno| seqno != acked);
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a path with respect to address validation.
|
||||
#[derive(Debug)]
|
||||
enum ProbeState {
|
||||
/// The path was last valid at the indicated time.
|
||||
Valid,
|
||||
/// The path was previously valid, but a new probe is needed.
|
||||
ProbeNeeded { probe_count: usize },
|
||||
/// The path hasn't been validated, but a probe has been sent.
|
||||
Probing {
|
||||
/// The number of probes that have been sent.
|
||||
probe_count: usize,
|
||||
/// The probe that was last sent.
|
||||
data: [u8; 8],
|
||||
/// Whether the probe was sent in a datagram padded to the path MTU.
|
||||
mtu: bool,
|
||||
/// When the probe was sent.
|
||||
sent: Instant,
|
||||
},
|
||||
/// Validation failed the last time it was attempted.
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl ProbeState {
|
||||
/// Determine whether the current state requires probing.
|
||||
fn probe_needed(&self) -> bool {
|
||||
matches!(self, Self::ProbeNeeded { .. })
|
||||
}
|
||||
}
|
||||
|
||||
/// A network path.
|
||||
///
|
||||
/// Paths are used a little bit strangely by connections:
|
||||
/// they need to encapsulate all the state for a path (which
|
||||
/// is normal), but that information is not propagated to the
|
||||
/// `Paths` instance that holds them. This is because the packet
|
||||
/// processing where changes occur can't hold a reference to the
|
||||
/// `Paths` instance that owns the `Path`. Any changes to the
|
||||
/// path are communicated to `Paths` afterwards.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Path {
|
||||
/// A local socket address.
|
||||
local: SocketAddr,
|
||||
/// A remote socket address.
|
||||
remote: SocketAddr,
|
||||
/// The connection IDs that we use when sending on this path.
|
||||
/// This is only needed during the handshake.
|
||||
local_cid: Option<ConnectionId>,
|
||||
/// The current connection ID that we are using and its details.
|
||||
remote_cid: Option<RemoteConnectionIdEntry>,
|
||||
|
||||
/// Whether this is the primary path.
|
||||
primary: bool,
|
||||
/// Whether the current path is considered valid.
|
||||
state: ProbeState,
|
||||
/// For a path that is not validated, this is `None`. For a validated
|
||||
/// path, the time that the path was last valid.
|
||||
validated: Option<Instant>,
|
||||
/// A path challenge was received and PATH_RESPONSE has not been sent.
|
||||
challenge: Option<[u8; 8]>,
|
||||
|
||||
/// The number of bytes received on this path.
|
||||
/// Note that this value might saturate on a long-lived connection,
|
||||
/// but we only use it before the path is validated.
|
||||
received_bytes: usize,
|
||||
/// The number of bytes sent on this path.
|
||||
sent_bytes: usize,
|
||||
local_cids: Vec<ConnectionId>,
|
||||
remote_cid: ConnectionId,
|
||||
reset_token: Option<[u8; 16]>,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
/// Create a path from addresses and a remote connection ID.
|
||||
/// This is used for migration and for new datagrams.
|
||||
pub fn temporary(local: SocketAddr, remote: SocketAddr) -> Self {
|
||||
/// Create a path from addresses and connection IDs.
|
||||
pub fn new(
|
||||
local: SocketAddr,
|
||||
remote: SocketAddr,
|
||||
local_cid: ConnectionId,
|
||||
remote_cid: ConnectionId,
|
||||
) -> Self {
|
||||
Self {
|
||||
local,
|
||||
remote,
|
||||
local_cid: None,
|
||||
remote_cid: None,
|
||||
primary: false,
|
||||
state: ProbeState::ProbeNeeded { probe_count: 0 },
|
||||
validated: None,
|
||||
challenge: None,
|
||||
received_bytes: 0,
|
||||
sent_bytes: 0,
|
||||
local_cids: vec![local_cid],
|
||||
remote_cid,
|
||||
reset_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this path is the primary or current path for the connection.
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.primary
|
||||
pub fn received_on(&self, d: &Datagram) -> bool {
|
||||
self.local == d.destination() && self.remote == d.source()
|
||||
}
|
||||
|
||||
/// Whether this path is a temporary one.
|
||||
pub fn is_temporary(&self) -> bool {
|
||||
self.remote_cid.is_none()
|
||||
}
|
||||
|
||||
/// By adding a remote connection ID, we make the path permanent
|
||||
/// and one that we will later send packets on.
|
||||
/// If `local_cid` is `None`, the existing value will be kept.
|
||||
fn make_permanent(
|
||||
&mut self,
|
||||
local_cid: Option<ConnectionId>,
|
||||
remote_cid: RemoteConnectionIdEntry,
|
||||
) {
|
||||
if self.local_cid.is_none() {
|
||||
self.local_cid = local_cid;
|
||||
}
|
||||
self.remote_cid.replace(remote_cid);
|
||||
}
|
||||
|
||||
/// Determine if this path was the one that the provided datagram was received on.
|
||||
/// This uses the full local socket address, but ignores the port number on the peer
|
||||
/// if `flexible` is true, allowing for NAT rebinding that retains the same IP.
|
||||
fn received_on(&self, local: SocketAddr, remote: SocketAddr, flexible: bool) -> bool {
|
||||
self.local == local
|
||||
&& self.remote.ip() == remote.ip()
|
||||
&& (flexible || self.remote.port() == remote.port())
|
||||
}
|
||||
|
||||
/// Update the remote port number. Any flexibility we allow in `received_on`
|
||||
/// need to be adjusted at this point.
|
||||
fn update_port(&mut self, port: u16) {
|
||||
self.remote.set_port(port);
|
||||
}
|
||||
|
||||
/// Set whether this path is primary.
|
||||
fn set_primary(&mut self, primary: bool) {
|
||||
qtrace!([self], "Make primary {}", primary);
|
||||
self.primary = primary;
|
||||
}
|
||||
|
||||
/// Set the current path as valid. This updates the time that the path was
|
||||
/// last validated and cancels any path validation.
|
||||
pub fn set_valid(&mut self, now: Instant) {
|
||||
qdebug!([self], "Path validated {:?}", now);
|
||||
self.state = ProbeState::Valid;
|
||||
self.validated = Some(now);
|
||||
}
|
||||
|
||||
/// Update the last use of this path, if it is valid.
|
||||
/// This will keep the path active slightly longer.
|
||||
pub fn update(&mut self, now: Instant) {
|
||||
if self.validated.is_some() {
|
||||
self.validated = Some(now);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path MTU. This is currently a fixed value.
|
||||
pub fn mtu(&self) -> usize {
|
||||
match self.local.ip() {
|
||||
IpAddr::V4(_) => PATH_MTU_V4,
|
||||
IpAddr::V6(_) => PATH_MTU_V6,
|
||||
if self.local.is_ipv4() {
|
||||
PATH_MTU_V4
|
||||
} else {
|
||||
PATH_MTU_V6 // IPv6
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a connection ID to the local set.
|
||||
pub fn add_local_cid(&mut self, cid: ConnectionId) {
|
||||
self.local_cids.push(cid);
|
||||
}
|
||||
|
||||
/// Determine if the given connection ID is valid.
|
||||
pub fn valid_local_cid(&self, cid: &ConnectionIdRef) -> bool {
|
||||
self.local_cids.iter().any(|c| c == cid)
|
||||
}
|
||||
|
||||
/// Get the first local connection ID.
|
||||
/// Only do this for the primary path during the handshake.
|
||||
pub fn local_cid(&self) -> &ConnectionId {
|
||||
self.local_cid.as_ref().unwrap()
|
||||
self.local_cids.first().as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Set the remote connection ID based on the peer's choice.
|
||||
/// This is only valid during the handshake.
|
||||
pub fn set_remote_cid(&mut self, cid: &ConnectionIdRef) {
|
||||
self.remote_cid
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.update_cid(ConnectionId::from(cid));
|
||||
self.remote_cid = ConnectionId::from(cid);
|
||||
}
|
||||
|
||||
/// Access the remote connection ID.
|
||||
pub fn remote_cid(&self) -> &ConnectionId {
|
||||
self.remote_cid.as_ref().unwrap().connection_id()
|
||||
&self.remote_cid
|
||||
}
|
||||
|
||||
/// Set the stateless reset token for the connection ID that is currently in use.
|
||||
/// Panics if the sequence number is non-zero as this is only necessary during
|
||||
/// the handshake; all other connection IDs are initialized with a token.
|
||||
pub fn set_reset_token(&mut self, token: [u8; 16]) {
|
||||
self.remote_cid
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_stateless_reset_token(token);
|
||||
self.reset_token = Some(token);
|
||||
}
|
||||
|
||||
/// Determine if the provided token is a stateless reset token.
|
||||
pub fn is_stateless_reset(&self, token: &[u8; 16]) -> bool {
|
||||
self.remote_cid
|
||||
.as_ref()
|
||||
.map_or(false, |rcid| rcid.is_stateless_reset(token))
|
||||
/// Access the reset token.
|
||||
pub fn reset_token(&self) -> Option<&[u8; 16]> {
|
||||
self.reset_token.as_ref()
|
||||
}
|
||||
|
||||
/// Make a datagram.
|
||||
|
@ -545,202 +106,4 @@ impl Path {
|
|||
pub fn remote_address(&self) -> SocketAddr {
|
||||
self.remote
|
||||
}
|
||||
|
||||
/// Whether the path has been validated.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.validated.is_some()
|
||||
}
|
||||
|
||||
/// Handle a `PATH_RESPONSE` frame. Returns true if the response was accepted.
|
||||
pub fn path_response(&mut self, response: [u8; 8], now: Instant) -> bool {
|
||||
if let ProbeState::Probing { data, mtu, .. } = &mut self.state {
|
||||
if response == *data {
|
||||
let need_full_probe = !*mtu;
|
||||
self.set_valid(now);
|
||||
if need_full_probe {
|
||||
qdebug!([self], "Sub-MTU probe successful, reset probe count");
|
||||
self.probe();
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The path has been challenged. This generates a response.
|
||||
/// This only generates a single response at a time.
|
||||
pub fn challenged(&mut self, challenge: [u8; 8]) {
|
||||
self.challenge = Some(challenge.to_owned());
|
||||
}
|
||||
|
||||
/// At the next opportunity, send a probe.
|
||||
/// If the probe count has been exhausted already, marks the path as failed.
|
||||
fn probe(&mut self) {
|
||||
let probe_count = match &self.state {
|
||||
ProbeState::Probing { probe_count, .. } => *probe_count + 1,
|
||||
ProbeState::ProbeNeeded { probe_count, .. } => *probe_count,
|
||||
_ => 0,
|
||||
};
|
||||
self.state = if probe_count >= MAX_PATH_PROBES {
|
||||
qinfo!([self], "Probing failed");
|
||||
ProbeState::Failed
|
||||
} else {
|
||||
qdebug!([self], "Initiating probe");
|
||||
ProbeState::ProbeNeeded { probe_count }
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns true if this path have any probing frames to send.
|
||||
pub fn has_probe(&self) -> bool {
|
||||
self.challenge.is_some() || self.state.probe_needed()
|
||||
}
|
||||
|
||||
pub fn write_frames(
|
||||
&mut self,
|
||||
builder: &mut PacketBuilder,
|
||||
stats: &mut FrameStats,
|
||||
mtu: bool, // Whether the packet we're writing into will be a full MTU.
|
||||
now: Instant,
|
||||
) -> Res<bool> {
|
||||
if builder.remaining() < 9 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Send PATH_RESPONSE.
|
||||
let resp_sent = if let Some(challenge) = self.challenge.take() {
|
||||
qtrace!([self], "Responding to path challenge {}", hex(&challenge));
|
||||
builder.encode_varint(FRAME_TYPE_PATH_RESPONSE);
|
||||
builder.encode(&challenge[..]);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(21));
|
||||
}
|
||||
|
||||
// These frames are not retransmitted in the usual fashion.
|
||||
// There is no token, therefore we need to count `all` specially.
|
||||
stats.path_response += 1;
|
||||
stats.all += 1;
|
||||
|
||||
if builder.remaining() < 9 {
|
||||
return Ok(true);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Send PATH_CHALLENGE.
|
||||
if let ProbeState::ProbeNeeded { probe_count } = self.state {
|
||||
qtrace!([self], "Initiating path challenge {}", probe_count);
|
||||
let data = <[u8; 8]>::try_from(&random(8)[..]).unwrap();
|
||||
builder.encode_varint(FRAME_TYPE_PATH_CHALLENGE);
|
||||
builder.encode(&data);
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(22));
|
||||
}
|
||||
|
||||
// As above, no recovery token.
|
||||
stats.path_challenge += 1;
|
||||
stats.all += 1;
|
||||
|
||||
self.state = ProbeState::Probing {
|
||||
probe_count,
|
||||
data,
|
||||
mtu,
|
||||
sent: now,
|
||||
};
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(resp_sent)
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a timer for this path.
|
||||
/// This returns true if the path is viable and can be kept alive.
|
||||
pub fn process_timeout(&mut self, now: Instant, pto: Duration) -> bool {
|
||||
if let ProbeState::Probing { sent, .. } = &self.state {
|
||||
if now >= *sent + pto {
|
||||
self.probe();
|
||||
}
|
||||
}
|
||||
if let ProbeState::Failed = self.state {
|
||||
// Retire failed paths immediately.
|
||||
false
|
||||
} else if self.primary {
|
||||
// Keep valid primary paths otherwise.
|
||||
true
|
||||
} else if let ProbeState::Valid = self.state {
|
||||
// Retire validated, non-primary paths.
|
||||
// Allow more than `MAX_PATH_PROBES` times the PTO so that an old
|
||||
// path remains around until after a previous path fails.
|
||||
let count = u32::try_from(MAX_PATH_PROBES + 1).unwrap();
|
||||
self.validated.unwrap() + (pto * count) > now
|
||||
} else {
|
||||
// Keep paths that are being actively probed.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the next time that this path needs servicing.
|
||||
/// This only considers retransmissions of probes, not cleanup of the path.
|
||||
/// If there is no other activity, then there is no real need to schedule a
|
||||
/// timer to cleanup old paths.
|
||||
pub fn next_timeout(&self, pto: Duration) -> Option<Instant> {
|
||||
if let ProbeState::Probing { sent, .. } = &self.state {
|
||||
Some(*sent + pto)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Record received bytes for the path.
|
||||
pub fn add_received(&mut self, count: usize) {
|
||||
self.received_bytes = self.received_bytes.saturating_add(count);
|
||||
}
|
||||
|
||||
/// Record sent bytes for the path.
|
||||
pub fn add_sent(&mut self, count: usize) {
|
||||
self.sent_bytes = self.sent_bytes.saturating_add(count);
|
||||
}
|
||||
|
||||
/// Get the number of bytes that can be written to this path.
|
||||
pub fn amplification_limit(&self) -> usize {
|
||||
if matches!(self.state, ProbeState::Failed) {
|
||||
0
|
||||
} else if self.is_valid() {
|
||||
usize::MAX
|
||||
} else {
|
||||
self.received_bytes
|
||||
.checked_mul(3)
|
||||
.map_or(usize::MAX, |limit| {
|
||||
let budget = if limit == 0 {
|
||||
// If we have received absolutely nothing thus far, then this endpoint
|
||||
// is the one initiating communication on this path. Allow enough space for probing.
|
||||
self.mtu() * 5
|
||||
} else {
|
||||
limit
|
||||
};
|
||||
budget.saturating_sub(self.sent_bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Path {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_primary() {
|
||||
write!(f, "pri-")?; // primary
|
||||
}
|
||||
if !self.is_valid() {
|
||||
write!(f, "unv-")?; // unvalidated
|
||||
}
|
||||
write!(f, "path")?;
|
||||
if let Some(entry) = self.remote_cid.as_ref() {
|
||||
write!(f, ":{}", entry.connection_id())?;
|
||||
}
|
||||
write!(f, " {}->{}", self.local, self.remote)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// Functions that handle capturing QLOG traces.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::{Deref, RangeInclusive};
|
||||
use std::ops::RangeInclusive;
|
||||
use std::string::String;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -16,10 +16,9 @@ use qlog::{self, event::Event, PacketHeader, QuicFrame};
|
|||
use neqo_common::{hex, qinfo, qlog::NeqoQlog, Decoder};
|
||||
|
||||
use crate::connection::State;
|
||||
use crate::frame::{CloseError, Frame};
|
||||
use crate::frame::{self, Frame};
|
||||
use crate::packet::{DecryptedPacket, PacketNumber, PacketType, PublicPacket};
|
||||
use crate::path::PathRef;
|
||||
use crate::stream_id::StreamType as NeqoStreamType;
|
||||
use crate::path::Path;
|
||||
use crate::tparams::{self, TransportParametersHandler};
|
||||
use crate::tracking::SentPacket;
|
||||
use crate::QuicVersion;
|
||||
|
@ -84,31 +83,30 @@ pub fn connection_tparams_set(qlog: &mut NeqoQlog, tph: &TransportParametersHand
|
|||
})
|
||||
}
|
||||
|
||||
pub fn server_connection_started(qlog: &mut NeqoQlog, path: &PathRef) {
|
||||
pub fn server_connection_started(qlog: &mut NeqoQlog, path: &Path) {
|
||||
connection_started(qlog, path)
|
||||
}
|
||||
|
||||
pub fn client_connection_started(qlog: &mut NeqoQlog, path: &PathRef) {
|
||||
pub fn client_connection_started(qlog: &mut NeqoQlog, path: &Path) {
|
||||
connection_started(qlog, path)
|
||||
}
|
||||
|
||||
fn connection_started(qlog: &mut NeqoQlog, path: &PathRef) {
|
||||
fn connection_started(qlog: &mut NeqoQlog, path: &Path) {
|
||||
qlog.add_event(|| {
|
||||
let p = path.deref().borrow();
|
||||
Some(Event::connection_started(
|
||||
if p.local_address().ip().is_ipv4() {
|
||||
if path.local_address().ip().is_ipv4() {
|
||||
"ipv4".into()
|
||||
} else {
|
||||
"ipv6".into()
|
||||
},
|
||||
format!("{}", p.local_address().ip()),
|
||||
format!("{}", p.remote_address().ip()),
|
||||
format!("{}", path.local_address().ip()),
|
||||
format!("{}", path.remote_address().ip()),
|
||||
Some("QUIC".into()),
|
||||
p.local_address().port().into(),
|
||||
p.remote_address().port().into(),
|
||||
path.local_address().port().into(),
|
||||
path.remote_address().port().into(),
|
||||
Some(format!("{:x}", QuicVersion::default().as_u32())),
|
||||
Some(format!("{}", p.local_cid())),
|
||||
Some(format!("{}", p.remote_cid())),
|
||||
Some(format!("{}", path.local_cid())),
|
||||
Some(format!("{}", path.remote_cid())),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
@ -373,8 +371,8 @@ fn frame_to_qlogframe(frame: &Frame) -> QuicFrame {
|
|||
maximum_streams,
|
||||
} => QuicFrame::max_streams(
|
||||
match stream_type {
|
||||
NeqoStreamType::BiDi => qlog::StreamType::Bidirectional,
|
||||
NeqoStreamType::UniDi => qlog::StreamType::Unidirectional,
|
||||
frame::StreamType::BiDi => qlog::StreamType::Bidirectional,
|
||||
frame::StreamType::UniDi => qlog::StreamType::Unidirectional,
|
||||
},
|
||||
maximum_streams.as_u64().to_string(),
|
||||
),
|
||||
|
@ -391,8 +389,8 @@ fn frame_to_qlogframe(frame: &Frame) -> QuicFrame {
|
|||
stream_limit,
|
||||
} => QuicFrame::streams_blocked(
|
||||
match stream_type {
|
||||
NeqoStreamType::BiDi => qlog::StreamType::Bidirectional,
|
||||
NeqoStreamType::UniDi => qlog::StreamType::Unidirectional,
|
||||
frame::StreamType::BiDi => qlog::StreamType::Bidirectional,
|
||||
frame::StreamType::UniDi => qlog::StreamType::Unidirectional,
|
||||
},
|
||||
stream_limit.as_u64().to_string(),
|
||||
),
|
||||
|
@ -419,8 +417,8 @@ fn frame_to_qlogframe(frame: &Frame) -> QuicFrame {
|
|||
reason_phrase,
|
||||
} => QuicFrame::connection_close(
|
||||
match error_code {
|
||||
CloseError::Transport(_) => qlog::ErrorSpace::TransportError,
|
||||
CloseError::Application(_) => qlog::ErrorSpace::ApplicationError,
|
||||
frame::CloseError::Transport(_) => qlog::ErrorSpace::TransportError,
|
||||
frame::CloseError::Application(_) => qlog::ErrorSpace::ApplicationError,
|
||||
},
|
||||
error_code.code(),
|
||||
0,
|
||||
|
|
|
@ -19,7 +19,6 @@ use smallvec::{smallvec, SmallVec};
|
|||
use neqo_common::{qdebug, qinfo, qlog::NeqoQlog, qtrace};
|
||||
|
||||
use crate::cc::CongestionControlAlgorithm;
|
||||
use crate::cid::ConnectionIdEntry;
|
||||
use crate::connection::LOCAL_IDLE_TIMEOUT;
|
||||
use crate::crypto::CryptoRecoveryToken;
|
||||
use crate::flow_mgr::FlowControlRecoveryToken;
|
||||
|
@ -53,8 +52,6 @@ pub enum RecoveryToken {
|
|||
Flow(FlowControlRecoveryToken),
|
||||
HandshakeDone,
|
||||
NewToken(usize),
|
||||
NewConnectionId(ConnectionIdEntry<[u8; 16]>),
|
||||
RetireConnectionId(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -162,13 +159,9 @@ impl Default for RttVals {
|
|||
/// `SendProfile` tells a sender how to send packets.
|
||||
#[derive(Debug)]
|
||||
pub struct SendProfile {
|
||||
/// The limit on the size of the packet.
|
||||
limit: usize,
|
||||
/// Whether this is a PTO, and what space the PTO is for.
|
||||
pto: Option<PNSpace>,
|
||||
/// What spaces should be probed.
|
||||
probe: PNSpaceSet,
|
||||
/// Whether pacing is active.
|
||||
paced: bool,
|
||||
}
|
||||
|
||||
|
@ -589,14 +582,13 @@ impl PtoState {
|
|||
}
|
||||
|
||||
/// Generate a sending profile, indicating what space it should be from.
|
||||
/// This takes a packet from the supply if one remains, or returns `None`.
|
||||
pub fn send_profile(&mut self, mtu: usize) -> Option<SendProfile> {
|
||||
/// This takes a packet from the supply or returns an ack-only profile if it can't.
|
||||
pub fn send_profile(&mut self, mtu: usize) -> SendProfile {
|
||||
if self.packets > 0 {
|
||||
// This is a PTO, so ignore the limit.
|
||||
self.packets -= 1;
|
||||
Some(SendProfile::new_pto(self.space, mtu, self.probe))
|
||||
SendProfile::new_pto(self.space, mtu, self.probe)
|
||||
} else {
|
||||
None
|
||||
SendProfile::new_limited(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -771,8 +763,7 @@ impl LossRecovery {
|
|||
// This must happen after on_packets_lost. If in recovery, this could
|
||||
// take us out, and then lost packets will start a new recovery period
|
||||
// when it shouldn't.
|
||||
self.packet_sender
|
||||
.on_packets_acked(&acked_packets, self.rtt_vals.min_rtt, now);
|
||||
self.packet_sender.on_packets_acked(&acked_packets);
|
||||
|
||||
self.pto_state = None;
|
||||
|
||||
|
@ -1001,22 +992,13 @@ impl LossRecovery {
|
|||
/// Check how packets should be sent, based on whether there is a PTO,
|
||||
/// what the current congestion window is, and what the pacer says.
|
||||
#[allow(clippy::option_if_let_else, clippy::unknown_clippy_lints)]
|
||||
pub fn send_profile(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
mtu: usize,
|
||||
amplification_limit: usize,
|
||||
) -> SendProfile {
|
||||
pub fn send_profile(&mut self, now: Instant, mtu: usize) -> SendProfile {
|
||||
qdebug!([self], "get send profile {:?}", now);
|
||||
if let Some(profile) = self
|
||||
.pto_state
|
||||
.as_mut()
|
||||
.and_then(|pto| pto.send_profile(mtu))
|
||||
{
|
||||
profile
|
||||
if let Some(pto) = self.pto_state.as_mut() {
|
||||
pto.send_profile(mtu)
|
||||
} else {
|
||||
let limit = min(self.cwnd_avail(), amplification_limit);
|
||||
if limit > mtu {
|
||||
let cwnd = self.cwnd_avail();
|
||||
if cwnd > mtu {
|
||||
// More than an MTU available; we might need to pace.
|
||||
if self.next_paced().map_or(false, |t| t > now) {
|
||||
SendProfile::new_paced()
|
||||
|
@ -1029,7 +1011,7 @@ impl LossRecovery {
|
|||
// result in a PING being sent in every active space.
|
||||
SendProfile::new_pto(PNSpace::Initial, mtu, PNSpaceSet::all())
|
||||
} else {
|
||||
SendProfile::new_limited(limit)
|
||||
SendProfile::new_limited(cwnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1479,33 +1461,10 @@ mod tests {
|
|||
// expired should result in setting a PTO state.
|
||||
let expected_pto = pn_time(2) + (INITIAL_RTT * 3) + MAX_ACK_DELAY;
|
||||
lr.discard(PNSpace::Handshake, expected_pto);
|
||||
let profile = lr.send_profile(expected_pto, 10000, 10000);
|
||||
let profile = lr.send_profile(expected_pto, 10000);
|
||||
assert!(profile.pto.is_some());
|
||||
assert!(!profile.should_probe(PNSpace::Initial));
|
||||
assert!(!profile.should_probe(PNSpace::Handshake));
|
||||
assert!(profile.should_probe(PNSpace::ApplicationData));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_pto_if_amplification_limited() {
|
||||
let mut lr = LossRecovery::new(CongestionControlAlgorithm::NewReno, StatsCell::default());
|
||||
lr.start_pacer(now());
|
||||
lr.on_packet_sent(SentPacket::new(
|
||||
PacketType::Initial,
|
||||
0,
|
||||
now(),
|
||||
true,
|
||||
Vec::new(),
|
||||
ON_SENT_SIZE,
|
||||
));
|
||||
|
||||
let expected_pto = now() + (INITIAL_RTT * 3);
|
||||
assert_eq!(lr.pto_time(PNSpace::Initial), Some(expected_pto));
|
||||
let profile = lr.send_profile(expected_pto, 10000, 10);
|
||||
assert!(profile.ack_only(PNSpace::Initial));
|
||||
assert!(profile.pto.is_none());
|
||||
assert!(!profile.should_probe(PNSpace::Initial));
|
||||
assert!(!profile.should_probe(PNSpace::Handshake));
|
||||
assert!(!profile.should_probe(PNSpace::ApplicationData));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -536,7 +536,7 @@ impl SendStream {
|
|||
(length, false)
|
||||
}
|
||||
|
||||
pub fn write_frame(&mut self, builder: &mut PacketBuilder) -> Res<Option<RecoveryToken>> {
|
||||
pub fn write_frame(&mut self, builder: &mut PacketBuilder) -> Option<RecoveryToken> {
|
||||
let id = self.stream_id;
|
||||
let final_size = self.final_size();
|
||||
if let Some((offset, data)) = self.next_bytes() {
|
||||
|
@ -549,14 +549,14 @@ impl SendStream {
|
|||
};
|
||||
if overhead > builder.remaining() {
|
||||
qtrace!("SendStream::write_frame no space for header");
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
|
||||
let (length, fill) = Self::length_and_fill(data.len(), builder.remaining() - overhead);
|
||||
let fin = final_size.map_or(false, |fs| fs == offset + u64::try_from(length).unwrap());
|
||||
if length == 0 && !fin {
|
||||
qtrace!("SendStream::write_frame no data, no fin");
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Write the stream out.
|
||||
|
@ -571,19 +571,15 @@ impl SendStream {
|
|||
builder.encode_vvec(&data[..length]);
|
||||
}
|
||||
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(23));
|
||||
}
|
||||
|
||||
self.mark_as_sent(offset, length, fin);
|
||||
Ok(Some(RecoveryToken::Stream(StreamRecoveryToken {
|
||||
Some(RecoveryToken::Stream(StreamRecoveryToken {
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
fin,
|
||||
})))
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -872,14 +868,13 @@ impl SendStreams {
|
|||
builder: &mut PacketBuilder,
|
||||
tokens: &mut Vec<RecoveryToken>,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<()> {
|
||||
) {
|
||||
for (_, stream) in self {
|
||||
if let Some(t) = stream.write_frame(builder)? {
|
||||
if let Some(t) = stream.write_frame(builder) {
|
||||
tokens.push(t);
|
||||
stats.stream += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1320,8 +1315,7 @@ mod tests {
|
|||
// Write a small frame: no fin.
|
||||
let written = builder.len();
|
||||
builder.set_limit(written + 6);
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
assert_eq!(builder.len(), written + 6);
|
||||
assert_eq!(tokens.len(), 1);
|
||||
let f1_token = tokens.remove(0);
|
||||
|
@ -1330,8 +1324,7 @@ mod tests {
|
|||
// Write the rest: fin.
|
||||
let written = builder.len();
|
||||
builder.set_limit(written + 200);
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
assert_eq!(builder.len(), written + 10);
|
||||
assert_eq!(tokens.len(), 1);
|
||||
let f2_token = tokens.remove(0);
|
||||
|
@ -1339,8 +1332,7 @@ mod tests {
|
|||
|
||||
// Should be no more data to frame.
|
||||
let written = builder.len();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
assert_eq!(builder.len(), written);
|
||||
assert!(tokens.is_empty());
|
||||
|
||||
|
@ -1354,8 +1346,7 @@ mod tests {
|
|||
// Next frame should not set fin even though stream has fin but frame
|
||||
// does not include end of stream
|
||||
let written = builder.len();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
assert_eq!(builder.len(), written + 7); // Needs a length this time.
|
||||
assert_eq!(tokens.len(), 1);
|
||||
let f4_token = tokens.remove(0);
|
||||
|
@ -1370,8 +1361,7 @@ mod tests {
|
|||
|
||||
// Next frame should set fin because it includes end of stream
|
||||
let written = builder.len();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
assert_eq!(builder.len(), written + 10);
|
||||
assert_eq!(tokens.len(), 1);
|
||||
let f5_token = tokens.remove(0);
|
||||
|
@ -1394,22 +1384,19 @@ mod tests {
|
|||
|
||||
let mut tokens = Vec::new();
|
||||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
let f1_token = tokens.remove(0);
|
||||
assert!(matches!(&f1_token, RecoveryToken::Stream(x) if x.offset == 0));
|
||||
assert!(matches!(&f1_token, RecoveryToken::Stream(x) if x.length == 10));
|
||||
assert!(matches!(&f1_token, RecoveryToken::Stream(x) if !x.fin));
|
||||
|
||||
// Should be no more data to frame
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
assert!(tokens.is_empty());
|
||||
|
||||
ss.get_mut(StreamId::from(0)).unwrap().close();
|
||||
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
let f2_token = tokens.remove(0);
|
||||
assert!(matches!(&f2_token, RecoveryToken::Stream(x) if x.offset == 10));
|
||||
assert!(matches!(&f2_token, RecoveryToken::Stream(x) if x.length == 0));
|
||||
|
@ -1423,8 +1410,7 @@ mod tests {
|
|||
}
|
||||
|
||||
// Next frame should set fin
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
let f3_token = tokens.remove(0);
|
||||
assert!(matches!(&f3_token, RecoveryToken::Stream(x) if x.offset == 10));
|
||||
assert!(matches!(&f3_token, RecoveryToken::Stream(x) if x.length == 0));
|
||||
|
@ -1438,8 +1424,7 @@ mod tests {
|
|||
}
|
||||
|
||||
// Next frame should set fin and include all data
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default())
|
||||
.unwrap();
|
||||
ss.write_frames(&mut builder, &mut tokens, &mut FrameStats::default());
|
||||
let f4_token = tokens.remove(0);
|
||||
assert!(matches!(&f4_token, RecoveryToken::Stream(x) if x.offset == 0));
|
||||
assert!(matches!(&f4_token, RecoveryToken::Stream(x) if x.length == 10));
|
||||
|
@ -1566,7 +1551,7 @@ mod tests {
|
|||
|
||||
// No frame should be sent here.
|
||||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
assert!(s.write_frame(&mut builder).unwrap().is_none());
|
||||
assert!(s.write_frame(&mut builder).is_none());
|
||||
}
|
||||
|
||||
/// Create a `SendStream` and force it into a state where it believes that
|
||||
|
@ -1606,7 +1591,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
let header_len = builder.len();
|
||||
builder.set_limit(header_len + space);
|
||||
let token = s.write_frame(&mut builder).unwrap();
|
||||
let token = s.write_frame(&mut builder);
|
||||
qtrace!("STREAM frame: {}", hex_with_len(&builder[header_len..]));
|
||||
token.is_some()
|
||||
}
|
||||
|
@ -1700,7 +1685,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
let header_len = builder.len();
|
||||
builder.set_limit(header_len + DATA16384.len() + 2);
|
||||
let token = s.write_frame(&mut builder).unwrap();
|
||||
let token = s.write_frame(&mut builder);
|
||||
assert!(token.is_some());
|
||||
// Expect STREAM + FIN only.
|
||||
assert_eq!(&builder[header_len..header_len + 2], &[0b1001, 0]);
|
||||
|
@ -1715,7 +1700,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
let header_len = builder.len();
|
||||
builder.set_limit(header_len + DATA16384.len() + 3);
|
||||
let token = s.write_frame(&mut builder).unwrap();
|
||||
let token = s.write_frame(&mut builder);
|
||||
assert!(token.is_some());
|
||||
// Expect STREAM + LEN + FIN.
|
||||
assert_eq!(
|
||||
|
@ -1741,7 +1726,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
let header_len = builder.len();
|
||||
builder.set_limit(header_len + 66);
|
||||
let token = s.write_frame(&mut builder).unwrap();
|
||||
let token = s.write_frame(&mut builder);
|
||||
assert!(token.is_some());
|
||||
// Expect STREAM + FIN only.
|
||||
assert_eq!(&builder[header_len..header_len + 2], &[0b1001, 0]);
|
||||
|
@ -1752,7 +1737,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
let header_len = builder.len();
|
||||
builder.set_limit(header_len + 67);
|
||||
let token = s.write_frame(&mut builder).unwrap();
|
||||
let token = s.write_frame(&mut builder);
|
||||
assert!(token.is_some());
|
||||
// Expect STREAM + LEN, not FIN.
|
||||
assert_eq!(&builder[header_len..header_len + 3], &[0b1010, 0, 63]);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use crate::cc::{
|
||||
ClassicCongestionControl, CongestionControl, CongestionControlAlgorithm, Cubic, NewReno,
|
||||
ClassicCongestionControl, CongestionControl, CongestionControlAlgorithm, NewReno,
|
||||
MAX_DATAGRAM_SIZE,
|
||||
};
|
||||
use crate::pace::Pacer;
|
||||
|
@ -46,9 +46,6 @@ impl PacketSender {
|
|||
CongestionControlAlgorithm::NewReno => {
|
||||
Box::new(ClassicCongestionControl::new(NewReno::default()))
|
||||
}
|
||||
CongestionControlAlgorithm::Cubic => {
|
||||
Box::new(ClassicCongestionControl::new(Cubic::default()))
|
||||
}
|
||||
},
|
||||
pacer: None,
|
||||
}
|
||||
|
@ -70,8 +67,8 @@ impl PacketSender {
|
|||
}
|
||||
|
||||
// Multi-packet version of OnPacketAckedCC
|
||||
pub fn on_packets_acked(&mut self, acked_pkts: &[SentPacket], min_rtt: Duration, now: Instant) {
|
||||
self.cc.on_packets_acked(acked_pkts, min_rtt, now);
|
||||
pub fn on_packets_acked(&mut self, acked_pkts: &[SentPacket]) {
|
||||
self.cc.on_packets_acked(acked_pkts);
|
||||
}
|
||||
|
||||
pub fn on_packets_lost(
|
||||
|
|
|
@ -14,10 +14,9 @@ use neqo_crypto::{AntiReplay, Cipher, ZeroRttCheckResult, ZeroRttChecker};
|
|||
|
||||
pub use crate::addr_valid::ValidateAddress;
|
||||
use crate::addr_valid::{AddressValidation, AddressValidationResult};
|
||||
use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef};
|
||||
use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdManager, ConnectionIdRef};
|
||||
use crate::connection::{Connection, Output, State};
|
||||
use crate::packet::{PacketBuilder, PacketType, PublicPacket};
|
||||
use crate::tparams::PreferredAddress;
|
||||
use crate::{ConnectionParameters, QuicVersion, Res};
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
@ -43,6 +42,7 @@ const TIMER_GRANULARITY: Duration = Duration::from_millis(10);
|
|||
const TIMER_CAPACITY: usize = 16384;
|
||||
|
||||
type StateRef = Rc<RefCell<ServerConnectionState>>;
|
||||
type CidMgr = Rc<RefCell<dyn ConnectionIdManager>>;
|
||||
type ConnectionTableRef = Rc<RefCell<HashMap<ConnectionId, StateRef>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -127,10 +127,8 @@ pub struct Server {
|
|||
anti_replay: AntiReplay,
|
||||
/// A function for determining if 0-RTT can be accepted.
|
||||
zero_rtt_checker: ServerZeroRttChecker,
|
||||
/// A connection ID generator.
|
||||
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
/// The preferred address(es).
|
||||
preferred_address: Option<PreferredAddress>,
|
||||
/// A connection ID manager.
|
||||
cid_manager: CidMgr,
|
||||
/// Connection parameters.
|
||||
conn_params: ConnectionParameters,
|
||||
/// Active connection attempts, keyed by `AttemptKey`. Initial packets with
|
||||
|
@ -160,7 +158,7 @@ impl Server {
|
|||
/// * `zero_rtt_checker` determines whether 0-RTT should be accepted. This
|
||||
/// will be passed the value of the `extra` argument that was passed to
|
||||
/// `Connection::send_ticket` to see if it is OK.
|
||||
/// * `cid_generator` is responsible for generating connection IDs and parsing them;
|
||||
/// * `cid_manager` is responsible for generating connection IDs and parsing them;
|
||||
/// connection IDs produced by the manager cannot be zero-length.
|
||||
pub fn new(
|
||||
now: Instant,
|
||||
|
@ -168,7 +166,7 @@ impl Server {
|
|||
protocols: &[impl AsRef<str>],
|
||||
anti_replay: AntiReplay,
|
||||
zero_rtt_checker: Box<dyn ZeroRttChecker>,
|
||||
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
cid_manager: CidMgr,
|
||||
conn_params: ConnectionParameters,
|
||||
) -> Res<Self> {
|
||||
let validation = AddressValidation::new(now, ValidateAddress::Never)?;
|
||||
|
@ -178,8 +176,7 @@ impl Server {
|
|||
ciphers: Vec::new(),
|
||||
anti_replay,
|
||||
zero_rtt_checker: ServerZeroRttChecker::new(zero_rtt_checker),
|
||||
cid_generator,
|
||||
preferred_address: None,
|
||||
cid_manager,
|
||||
conn_params,
|
||||
active_attempts: HashMap::default(),
|
||||
connections: Rc::default(),
|
||||
|
@ -207,11 +204,6 @@ impl Server {
|
|||
self.ciphers = Vec::from(ciphers.as_ref());
|
||||
}
|
||||
|
||||
/// Set a preferred address.
|
||||
pub fn set_preferred_address(&mut self, spa: PreferredAddress) {
|
||||
self.preferred_address = Some(spa);
|
||||
}
|
||||
|
||||
fn remove_timer(&mut self, c: &StateRef) {
|
||||
let last = c.borrow().last_timer;
|
||||
self.timers.remove(last, |t| Rc::ptr_eq(t, c));
|
||||
|
@ -303,23 +295,19 @@ impl Server {
|
|||
qerror!([self], "unable to generate token, dropping packet");
|
||||
return None;
|
||||
};
|
||||
if let Some(new_dcid) = self.cid_generator.borrow_mut().generate_cid() {
|
||||
let packet = PacketBuilder::retry(
|
||||
initial.quic_version,
|
||||
&initial.src_cid,
|
||||
&new_dcid,
|
||||
&token,
|
||||
&initial.dst_cid,
|
||||
);
|
||||
if let Ok(p) = packet {
|
||||
let retry = Datagram::new(dgram.destination(), dgram.source(), p);
|
||||
Some(retry)
|
||||
} else {
|
||||
qerror!([self], "unable to encode retry, dropping packet");
|
||||
None
|
||||
}
|
||||
let new_dcid = self.cid_manager.borrow_mut().generate_cid();
|
||||
let packet = PacketBuilder::retry(
|
||||
initial.quic_version,
|
||||
&initial.src_cid,
|
||||
&new_dcid,
|
||||
&token,
|
||||
&initial.dst_cid,
|
||||
);
|
||||
if let Ok(p) = packet {
|
||||
let retry = Datagram::new(dgram.destination(), dgram.source(), p);
|
||||
Some(retry)
|
||||
} else {
|
||||
qerror!([self], "no connection ID for retry, dropping packet");
|
||||
qerror!([self], "unable to encode retry, dropping packet");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -399,25 +387,6 @@ impl Server {
|
|||
}
|
||||
}
|
||||
|
||||
fn setup_connection(
|
||||
&mut self,
|
||||
c: &mut Connection,
|
||||
attempt_key: &AttemptKey,
|
||||
initial: InitialDetails,
|
||||
orig_dcid: Option<ConnectionId>,
|
||||
) {
|
||||
let zcheck = self.zero_rtt_checker.clone();
|
||||
if c.server_enable_0rtt(&self.anti_replay, zcheck).is_err() {
|
||||
qwarn!([self], "Unable to enable 0-RTT");
|
||||
}
|
||||
if let Some(odcid) = orig_dcid {
|
||||
// There was a retry, so set the connection IDs for.
|
||||
c.set_retry_cids(odcid, initial.src_cid, initial.dst_cid);
|
||||
}
|
||||
c.set_validation(Rc::clone(&self.address_validation));
|
||||
c.set_qlog(self.create_qlog_trace(attempt_key));
|
||||
}
|
||||
|
||||
fn accept_connection(
|
||||
&mut self,
|
||||
attempt_key: AttemptKey,
|
||||
|
@ -430,9 +399,9 @@ impl Server {
|
|||
// The internal connection ID manager that we use is not used directly.
|
||||
// Instead, wrap it so that we can save connection IDs.
|
||||
|
||||
let cid_mgr = Rc::new(RefCell::new(ServerConnectionIdGenerator {
|
||||
let cid_mgr = Rc::new(RefCell::new(ServerConnectionIdManager {
|
||||
c: Weak::new(),
|
||||
cid_generator: Rc::clone(&self.cid_generator),
|
||||
cid_manager: Rc::clone(&self.cid_manager),
|
||||
connections: Rc::clone(&self.connections),
|
||||
saved_cids: Vec::new(),
|
||||
}));
|
||||
|
@ -441,11 +410,20 @@ impl Server {
|
|||
&self.certs,
|
||||
&self.protocols,
|
||||
Rc::clone(&cid_mgr) as _,
|
||||
self.conn_params.clone().quic_version(initial.quic_version),
|
||||
&self.conn_params.clone().quic_version(initial.quic_version),
|
||||
);
|
||||
|
||||
if let Ok(mut c) = sconn {
|
||||
self.setup_connection(&mut c, &attempt_key, initial, orig_dcid);
|
||||
let zcheck = self.zero_rtt_checker.clone();
|
||||
if c.server_enable_0rtt(&self.anti_replay, zcheck).is_err() {
|
||||
qwarn!([self], "Unable to enable 0-RTT");
|
||||
}
|
||||
if let Some(odcid) = orig_dcid {
|
||||
// There was a retry, so set the connection IDs for.
|
||||
c.set_retry_cids(odcid, initial.src_cid, initial.dst_cid);
|
||||
}
|
||||
c.set_validation(Rc::clone(&self.address_validation));
|
||||
c.set_qlog(self.create_qlog_trace(&attempt_key));
|
||||
let c = Rc::new(RefCell::new(ServerConnectionState {
|
||||
c,
|
||||
last_timer: now,
|
||||
|
@ -461,39 +439,12 @@ impl Server {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle 0-RTT packets that were sent with the client's choice of connection ID.
|
||||
/// Most 0-RTT will arrive this way. A client can usually send 1-RTT after it
|
||||
/// receives a connection ID from the server.
|
||||
fn handle_0rtt(
|
||||
&mut self,
|
||||
dgram: Datagram,
|
||||
dcid: ConnectionId,
|
||||
now: Instant,
|
||||
) -> Option<Datagram> {
|
||||
let attempt_key = AttemptKey {
|
||||
remote_address: dgram.source(),
|
||||
odcid: dcid,
|
||||
};
|
||||
if let Some(c) = self.active_attempts.get(&attempt_key) {
|
||||
qdebug!(
|
||||
[self],
|
||||
"Handle 0-RTT for existing connection attempt {:?}",
|
||||
attempt_key
|
||||
);
|
||||
let c = Rc::clone(c);
|
||||
self.process_connection(c, Some(dgram), now)
|
||||
} else {
|
||||
qdebug!([self], "Dropping 0-RTT for unknown connection");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn process_input(&mut self, dgram: Datagram, now: Instant) -> Option<Datagram> {
|
||||
qtrace!("Process datagram: {}", hex(&dgram[..]));
|
||||
|
||||
// This is only looking at the first packet header in the datagram.
|
||||
// All packets in the datagram are routed to the same connection.
|
||||
let res = PublicPacket::decode(&dgram[..], self.cid_generator.borrow().as_decoder());
|
||||
let res = PublicPacket::decode(&dgram[..], self.cid_manager.borrow().as_decoder());
|
||||
let (packet, _remainder) = match res {
|
||||
Ok(res) => res,
|
||||
_ => {
|
||||
|
@ -513,25 +464,17 @@ impl Server {
|
|||
return None;
|
||||
}
|
||||
|
||||
if dgram.len() < MIN_INITIAL_PACKET_SIZE {
|
||||
qtrace!([self], "Bogus packet: too short");
|
||||
return None;
|
||||
}
|
||||
match packet.packet_type() {
|
||||
PacketType::Initial => {
|
||||
if dgram.len() < MIN_INITIAL_PACKET_SIZE {
|
||||
qdebug!([self], "Drop initial: too short");
|
||||
return None;
|
||||
}
|
||||
// Copy values from `packet` because they are currently still borrowing from `dgram`.
|
||||
let initial = InitialDetails::new(&packet);
|
||||
self.handle_initial(initial, dgram, now)
|
||||
}
|
||||
PacketType::ZeroRtt => {
|
||||
let dcid = ConnectionId::from(packet.dcid());
|
||||
self.handle_0rtt(dgram, dcid, now)
|
||||
}
|
||||
PacketType::OtherVersion => {
|
||||
if dgram.len() < MIN_INITIAL_PACKET_SIZE {
|
||||
qdebug!([self], "Unsupported version: too short");
|
||||
return None;
|
||||
}
|
||||
let vn = PacketBuilder::version_negotiation(packet.scid(), packet.dcid());
|
||||
Some(Datagram::new(dgram.destination(), dgram.source(), vn))
|
||||
}
|
||||
|
@ -638,18 +581,18 @@ impl PartialEq for ActiveConnectionRef {
|
|||
|
||||
impl Eq for ActiveConnectionRef {}
|
||||
|
||||
struct ServerConnectionIdGenerator {
|
||||
struct ServerConnectionIdManager {
|
||||
c: Weak<RefCell<ServerConnectionState>>,
|
||||
connections: ConnectionTableRef,
|
||||
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
cid_manager: CidMgr,
|
||||
saved_cids: Vec<ConnectionId>,
|
||||
}
|
||||
|
||||
impl ServerConnectionIdGenerator {
|
||||
impl ServerConnectionIdManager {
|
||||
pub fn set_connection(&mut self, c: StateRef) {
|
||||
let saved = std::mem::replace(&mut self.saved_cids, Vec::with_capacity(0));
|
||||
for cid in saved {
|
||||
qtrace!("ServerConnectionIdGenerator inserting saved cid {}", cid);
|
||||
qtrace!("ServerConnectionIdManager inserting saved cid {}", cid);
|
||||
self.insert_cid(cid, Rc::clone(&c));
|
||||
}
|
||||
self.c = Rc::downgrade(&c);
|
||||
|
@ -661,28 +604,24 @@ impl ServerConnectionIdGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdDecoder for ServerConnectionIdGenerator {
|
||||
impl ConnectionIdDecoder for ServerConnectionIdManager {
|
||||
fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> {
|
||||
self.cid_generator.borrow_mut().decode_cid(dec)
|
||||
self.cid_manager.borrow_mut().decode_cid(dec)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionIdGenerator for ServerConnectionIdGenerator {
|
||||
fn generate_cid(&mut self) -> Option<ConnectionId> {
|
||||
let maybe_cid = self.cid_generator.borrow_mut().generate_cid();
|
||||
if let Some(cid) = maybe_cid {
|
||||
if let Some(rc) = self.c.upgrade() {
|
||||
self.insert_cid(cid.clone(), rc);
|
||||
} else {
|
||||
// This function can be called before the connection is set.
|
||||
// So save any connection IDs until that hookup happens.
|
||||
qtrace!("ServerConnectionIdGenerator saving cid {}", cid);
|
||||
self.saved_cids.push(cid.clone());
|
||||
}
|
||||
Some(cid)
|
||||
impl ConnectionIdManager for ServerConnectionIdManager {
|
||||
fn generate_cid(&mut self) -> ConnectionId {
|
||||
let cid = self.cid_manager.borrow_mut().generate_cid();
|
||||
if let Some(rc) = self.c.upgrade() {
|
||||
self.insert_cid(cid.clone(), rc);
|
||||
} else {
|
||||
None
|
||||
// This function can be called before the connection is set.
|
||||
// So save any connection IDs until that hookup happens.
|
||||
qtrace!("ServerConnectionIdManager saving cid {}", cid);
|
||||
self.saved_cids.push(cid.clone());
|
||||
}
|
||||
cid
|
||||
}
|
||||
|
||||
fn as_decoder(&self) -> &dyn ConnectionIdDecoder {
|
||||
|
|
|
@ -10,15 +10,10 @@ use std::ops::AddAssign;
|
|||
|
||||
use neqo_common::Role;
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone, PartialOrd, Eq, Ord, Hash)]
|
||||
use crate::connection::{LOCAL_STREAM_LIMIT_BIDI, LOCAL_STREAM_LIMIT_UNI};
|
||||
use crate::frame::StreamType;
|
||||
|
||||
/// The type of stream, either Bi-Directional or Uni-Directional.
|
||||
pub enum StreamType {
|
||||
BiDi,
|
||||
UniDi,
|
||||
}
|
||||
|
||||
pub(crate) struct StreamIndexes {
|
||||
pub struct StreamIndexes {
|
||||
pub local_max_stream_uni: StreamIndex,
|
||||
pub local_max_stream_bidi: StreamIndex,
|
||||
pub local_next_stream_uni: StreamIndex,
|
||||
|
@ -30,10 +25,10 @@ pub(crate) struct StreamIndexes {
|
|||
}
|
||||
|
||||
impl StreamIndexes {
|
||||
pub fn new(local_max_stream_bidi: StreamIndex, local_max_stream_uni: StreamIndex) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
local_max_stream_bidi,
|
||||
local_max_stream_uni,
|
||||
local_max_stream_bidi: StreamIndex::new(LOCAL_STREAM_LIMIT_BIDI),
|
||||
local_max_stream_uni: StreamIndex::new(LOCAL_STREAM_LIMIT_UNI),
|
||||
local_next_stream_uni: StreamIndex::new(0),
|
||||
local_next_stream_bidi: StreamIndex::new(0),
|
||||
remote_max_stream_bidi: StreamIndex::new(0),
|
||||
|
@ -131,7 +126,7 @@ impl ::std::fmt::Display for StreamId {
|
|||
pub struct StreamIndex(u64);
|
||||
|
||||
impl StreamIndex {
|
||||
pub const fn new(val: u64) -> Self {
|
||||
pub fn new(val: u64) -> Self {
|
||||
Self(val)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,22 +6,20 @@
|
|||
|
||||
// Transport parameters. See -transport section 7.3.
|
||||
|
||||
use crate::cid::{
|
||||
ConnectionId, ConnectionIdEntry, CONNECTION_ID_SEQNO_PREFERRED, MAX_CONNECTION_ID_LEN,
|
||||
};
|
||||
#![allow(dead_code)]
|
||||
use crate::{Error, Res};
|
||||
|
||||
use neqo_common::{hex, qdebug, qinfo, qtrace, Decoder, Encoder};
|
||||
use neqo_crypto::constants::{TLS_HS_CLIENT_HELLO, TLS_HS_ENCRYPTED_EXTENSIONS};
|
||||
use neqo_crypto::ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult};
|
||||
use neqo_crypto::{HandshakeMessage, ZeroRttCheckResult, ZeroRttChecker};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
|
||||
struct PreferredAddress {
|
||||
// TODO(ekr@rtfm.com): Implement.
|
||||
}
|
||||
|
||||
pub type TransportParameterId = u64;
|
||||
macro_rules! tpids {
|
||||
{ $($n:ident = $v:expr),+ $(,)? } => {
|
||||
|
@ -49,60 +47,11 @@ tpids! {
|
|||
GREASE_QUIC_BIT = 0x2ab2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PreferredAddress {
|
||||
v4: Option<SocketAddr>,
|
||||
v6: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl PreferredAddress {
|
||||
/// Make a new preferred address configuration.
|
||||
///
|
||||
/// # Panics
|
||||
/// If neither address is provided, or if either address is of the wrong type.
|
||||
#[must_use]
|
||||
pub fn new(v4: Option<SocketAddr>, v6: Option<SocketAddr>) -> Self {
|
||||
assert!(v4.is_some() || v6.is_some());
|
||||
if let Some(a) = v4 {
|
||||
if let IpAddr::V4(addr) = a.ip() {
|
||||
assert!(!addr.is_unspecified());
|
||||
} else {
|
||||
panic!("invalid address type for v4 address");
|
||||
}
|
||||
assert_ne!(a.port(), 0);
|
||||
}
|
||||
if let Some(a) = v6 {
|
||||
if let IpAddr::V6(addr) = a.ip() {
|
||||
assert!(!addr.is_unspecified());
|
||||
} else {
|
||||
panic!("invalid address type for v6 address");
|
||||
}
|
||||
assert_ne!(a.port(), 0);
|
||||
}
|
||||
Self { v4, v6 }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn ipv4(&self) -> Option<SocketAddr> {
|
||||
self.v4
|
||||
}
|
||||
#[must_use]
|
||||
pub fn ipv6(&self) -> Option<SocketAddr> {
|
||||
self.v6
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TransportParameter {
|
||||
Bytes(Vec<u8>),
|
||||
Integer(u64),
|
||||
Empty,
|
||||
PreferredAddress {
|
||||
v4: Option<SocketAddr>,
|
||||
v6: Option<SocketAddr>,
|
||||
cid: ConnectionId,
|
||||
srt: [u8; 16],
|
||||
},
|
||||
}
|
||||
|
||||
impl TransportParameter {
|
||||
|
@ -121,85 +70,18 @@ impl TransportParameter {
|
|||
Self::Empty => {
|
||||
enc.encode_varint(0_u64);
|
||||
}
|
||||
Self::PreferredAddress { v4, v6, cid, srt } => {
|
||||
enc.encode_vvec_with(|enc_inner| {
|
||||
if let Some(v4) = v4 {
|
||||
debug_assert!(v4.is_ipv4());
|
||||
if let IpAddr::V4(a) = v4.ip() {
|
||||
enc_inner.encode(&a.octets()[..]);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
enc_inner.encode_uint(2, v4.port());
|
||||
} else {
|
||||
enc_inner.encode(&[0; 6]);
|
||||
}
|
||||
if let Some(v6) = v6 {
|
||||
debug_assert!(v6.is_ipv6());
|
||||
if let IpAddr::V6(a) = v6.ip() {
|
||||
enc_inner.encode(&a.octets()[..]);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
enc_inner.encode_uint(2, v6.port());
|
||||
} else {
|
||||
enc_inner.encode(&[0; 18]);
|
||||
}
|
||||
enc_inner.encode_vec(1, &cid[..]);
|
||||
enc_inner.encode(&srt[..]);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn decode_preferred_address(d: &mut Decoder) -> Res<Self> {
|
||||
// IPv4 address (maybe)
|
||||
let v4ip =
|
||||
Ipv4Addr::from(<[u8; 4]>::try_from(d.decode(4).ok_or(Error::NoMoreData)?).unwrap());
|
||||
let v4port = u16::try_from(d.decode_uint(2).ok_or(Error::NoMoreData)?).unwrap();
|
||||
// Can't have non-zero IP and zero port, or vice versa.
|
||||
if v4ip.is_unspecified() ^ (v4port == 0) {
|
||||
return Err(Error::TransportParameterError);
|
||||
}
|
||||
let v4 = if v4port == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(SocketAddr::new(IpAddr::V4(v4ip), v4port))
|
||||
};
|
||||
|
||||
// IPv6 address (mostly the same as v4)
|
||||
let v6ip =
|
||||
Ipv6Addr::from(<[u8; 16]>::try_from(d.decode(16).ok_or(Error::NoMoreData)?).unwrap());
|
||||
let v6port = u16::try_from(d.decode_uint(2).ok_or(Error::NoMoreData)?).unwrap();
|
||||
if v6ip.is_unspecified() ^ (v6port == 0) {
|
||||
return Err(Error::TransportParameterError);
|
||||
}
|
||||
let v6 = if v6port == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(SocketAddr::new(IpAddr::V6(v6ip), v6port))
|
||||
};
|
||||
// Need either v4 or v6 to be present.
|
||||
if v4.is_none() && v6.is_none() {
|
||||
return Err(Error::TransportParameterError);
|
||||
}
|
||||
|
||||
// Connection ID (non-zero length)
|
||||
let cid = ConnectionId::from(d.decode_vec(1).ok_or(Error::NoMoreData)?);
|
||||
if cid.len() == 0 || cid.len() > MAX_CONNECTION_ID_LEN {
|
||||
return Err(Error::TransportParameterError);
|
||||
}
|
||||
|
||||
// Stateless reset token
|
||||
let srtbuf = d.decode(16).ok_or(Error::NoMoreData)?;
|
||||
let srt = <[u8; 16]>::try_from(srtbuf).unwrap();
|
||||
|
||||
Ok(Self::PreferredAddress { v4, v6, cid, srt })
|
||||
}
|
||||
|
||||
fn decode(dec: &mut Decoder) -> Res<Option<(TransportParameterId, Self)>> {
|
||||
let tp = dec.decode_varint().ok_or(Error::NoMoreData)?;
|
||||
let content = dec.decode_vvec().ok_or(Error::NoMoreData)?;
|
||||
let tp = match dec.decode_varint() {
|
||||
Some(v) => v,
|
||||
_ => return Err(Error::NoMoreData),
|
||||
};
|
||||
let content = match dec.decode_vvec() {
|
||||
Some(v) => v,
|
||||
_ => return Err(Error::NoMoreData),
|
||||
};
|
||||
qtrace!("TP {:x} length {:x}", tp, content.len());
|
||||
let mut d = Decoder::from(content);
|
||||
let value = match tp {
|
||||
|
@ -242,9 +124,6 @@ impl TransportParameter {
|
|||
},
|
||||
|
||||
DISABLE_MIGRATION | GREASE_QUIC_BIT => Self::Empty,
|
||||
|
||||
PREFERRED_ADDRESS => Self::decode_preferred_address(&mut d)?,
|
||||
|
||||
// Skip.
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
@ -400,12 +279,11 @@ impl TransportParameters {
|
|||
| ACK_DELAY_EXPONENT
|
||||
| MAX_ACK_DELAY
|
||||
| ACTIVE_CONNECTION_ID_LIMIT
|
||||
| PREFERRED_ADDRESS
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if let Some(v_self) = self.params.get(k) {
|
||||
match (v_self, v_rem) {
|
||||
match self.params.get(k) {
|
||||
Some(v_self) => match (v_self, v_rem) {
|
||||
(TransportParameter::Integer(i_self), TransportParameter::Integer(i_rem)) => {
|
||||
if *i_self < *i_rem {
|
||||
return false;
|
||||
|
@ -413,31 +291,13 @@ impl TransportParameters {
|
|||
}
|
||||
(TransportParameter::Empty, TransportParameter::Empty) => {}
|
||||
_ => return false,
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
},
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Get the preferred address in a usable form.
|
||||
#[must_use]
|
||||
pub fn get_preferred_address(&self) -> Option<(PreferredAddress, ConnectionIdEntry<[u8; 16]>)> {
|
||||
if let Some(TransportParameter::PreferredAddress { v4, v6, cid, srt }) =
|
||||
self.params.get(&PREFERRED_ADDRESS)
|
||||
{
|
||||
Some((
|
||||
PreferredAddress::new(*v4, *v6),
|
||||
ConnectionIdEntry::new(CONNECTION_ID_SEQNO_PREFERRED, cid.clone(), *srt),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[must_use]
|
||||
fn was_sent(&self, tp: TransportParameterId) -> bool {
|
||||
self.params.contains_key(&tp)
|
||||
}
|
||||
|
@ -595,214 +455,6 @@ mod tests {
|
|||
let tps2 = TransportParameters::decode(&mut enc.as_decoder()).expect("Couldn't decode");
|
||||
}
|
||||
|
||||
fn make_spa() -> TransportParameter {
|
||||
TransportParameter::PreferredAddress {
|
||||
v4: Some(SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::from(0xc000_0201)),
|
||||
443,
|
||||
)),
|
||||
v6: Some(SocketAddr::new(
|
||||
IpAddr::V6(Ipv6Addr::from(0xfe80_0000_0000_0000_0000_0000_0000_0001)),
|
||||
443,
|
||||
)),
|
||||
cid: ConnectionId::from(&[1, 2, 3, 4, 5]),
|
||||
srt: [3; 16],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_address_encode_decode() {
|
||||
const ENCODED: &[u8] = &[
|
||||
0x0d, 0x2e, 0xc0, 0x00, 0x02, 0x01, 0x01, 0xbb, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xbb, 0x05, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
];
|
||||
let spa = make_spa();
|
||||
let mut enc = Encoder::new();
|
||||
spa.encode(&mut enc, PREFERRED_ADDRESS);
|
||||
assert_eq!(&enc[..], ENCODED);
|
||||
|
||||
let mut dec = enc.as_decoder();
|
||||
let (id, decoded) = TransportParameter::decode(&mut dec).unwrap().unwrap();
|
||||
assert_eq!(id, PREFERRED_ADDRESS);
|
||||
assert_eq!(decoded, spa);
|
||||
}
|
||||
|
||||
fn mutate_spa<F>(wrecker: F) -> TransportParameter
|
||||
where
|
||||
F: FnOnce(&mut Option<SocketAddr>, &mut Option<SocketAddr>, &mut ConnectionId),
|
||||
{
|
||||
let mut spa = make_spa();
|
||||
if let TransportParameter::PreferredAddress {
|
||||
ref mut v4,
|
||||
ref mut v6,
|
||||
ref mut cid,
|
||||
..
|
||||
} = &mut spa
|
||||
{
|
||||
wrecker(v4, v6, cid);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
spa
|
||||
}
|
||||
|
||||
/// This takes a `TransportParameter::PreferredAddress` that has been mutilated.
|
||||
/// It then encodes it, working from the knowledge that the `encode` function
|
||||
/// doesn't care about validity, and decodes it. The result should be failure.
|
||||
fn assert_invalid_spa(spa: TransportParameter) {
|
||||
let mut enc = Encoder::new();
|
||||
spa.encode(&mut enc, PREFERRED_ADDRESS);
|
||||
assert_eq!(
|
||||
TransportParameter::decode(&mut enc.as_decoder()).unwrap_err(),
|
||||
Error::TransportParameterError
|
||||
);
|
||||
}
|
||||
|
||||
/// This is for those rare mutations that are acceptable.
|
||||
fn assert_valid_spa(spa: TransportParameter) {
|
||||
let mut enc = Encoder::new();
|
||||
spa.encode(&mut enc, PREFERRED_ADDRESS);
|
||||
let mut dec = enc.as_decoder();
|
||||
let (id, decoded) = TransportParameter::decode(&mut dec).unwrap().unwrap();
|
||||
assert_eq!(id, PREFERRED_ADDRESS);
|
||||
assert_eq!(decoded, spa);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_address_zero_address() {
|
||||
// Either port being zero is bad.
|
||||
assert_invalid_spa(mutate_spa(|v4, _, _| {
|
||||
v4.as_mut().unwrap().set_port(0);
|
||||
}));
|
||||
assert_invalid_spa(mutate_spa(|_, v6, _| {
|
||||
v6.as_mut().unwrap().set_port(0);
|
||||
}));
|
||||
// Either IP being zero is bad.
|
||||
assert_invalid_spa(mutate_spa(|v4, _, _| {
|
||||
v4.as_mut().unwrap().set_ip(IpAddr::V4(Ipv4Addr::from(0)));
|
||||
}));
|
||||
assert_invalid_spa(mutate_spa(|_, v6, _| {
|
||||
v6.as_mut().unwrap().set_ip(IpAddr::V6(Ipv6Addr::from(0)));
|
||||
}));
|
||||
// Either address being absent is OK.
|
||||
assert_valid_spa(mutate_spa(|v4, _, _| {
|
||||
*v4 = None;
|
||||
}));
|
||||
assert_valid_spa(mutate_spa(|_, v6, _| {
|
||||
*v6 = None;
|
||||
}));
|
||||
// Both addresses being absent is bad.
|
||||
assert_invalid_spa(mutate_spa(|v4, v6, _| {
|
||||
*v4 = None;
|
||||
*v6 = None;
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_address_bad_cid() {
|
||||
assert_invalid_spa(mutate_spa(|_, _, cid| {
|
||||
*cid = ConnectionId::from(&[]);
|
||||
}));
|
||||
assert_invalid_spa(mutate_spa(|_, _, cid| {
|
||||
*cid = ConnectionId::from(&[0x0c; 21]);
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_address_truncated() {
|
||||
let spa = make_spa();
|
||||
let mut enc = Encoder::new();
|
||||
spa.encode(&mut enc, PREFERRED_ADDRESS);
|
||||
let mut dec = Decoder::from(&enc[..enc.len() - 1]);
|
||||
assert_eq!(
|
||||
TransportParameter::decode(&mut dec).unwrap_err(),
|
||||
Error::NoMoreData
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_wrong_family_v4() {
|
||||
mutate_spa(|v4, _, _| {
|
||||
v4.as_mut().unwrap().set_ip(IpAddr::V6(Ipv6Addr::from(0)));
|
||||
})
|
||||
.encode(&mut Encoder::new(), PREFERRED_ADDRESS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_wrong_family_v6() {
|
||||
mutate_spa(|_, v6, _| {
|
||||
v6.as_mut().unwrap().set_ip(IpAddr::V4(Ipv4Addr::from(0)));
|
||||
})
|
||||
.encode(&mut Encoder::new(), PREFERRED_ADDRESS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_neither() {
|
||||
let _ = PreferredAddress::new(None, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_v4_unspecified() {
|
||||
let _ = PreferredAddress::new(
|
||||
Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(0)), 443)),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_v4_zero_port() {
|
||||
let _ = PreferredAddress::new(
|
||||
Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(0xc000_0201)), 0)),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_v6_unspecified() {
|
||||
let _ = PreferredAddress::new(
|
||||
None,
|
||||
Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(0)), 443)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_v6_zero_port() {
|
||||
let _ = PreferredAddress::new(
|
||||
None,
|
||||
Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(1)), 0)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_v4_is_v6() {
|
||||
let _ = PreferredAddress::new(
|
||||
Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(1)), 443)),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn preferred_address_v6_is_v4() {
|
||||
let _ = PreferredAddress::new(
|
||||
None,
|
||||
Some(SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::from(0xc000_0201)),
|
||||
443,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compatible_0rtt_ignored_values() {
|
||||
let mut tps_a = TransportParameters::default();
|
||||
|
|
|
@ -20,7 +20,6 @@ use neqo_crypto::{Epoch, TLS_EPOCH_HANDSHAKE, TLS_EPOCH_INITIAL};
|
|||
use crate::packet::{PacketBuilder, PacketNumber, PacketType};
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::stats::FrameStats;
|
||||
use crate::{Error, Res};
|
||||
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
|
@ -413,8 +412,7 @@ impl RecvdPackets {
|
|||
}
|
||||
|
||||
/// Add the packet to the tracked set.
|
||||
/// Return true if the packet was the largest received so far.
|
||||
pub fn set_received(&mut self, now: Instant, pn: PacketNumber, ack_eliciting: bool) -> bool {
|
||||
pub fn set_received(&mut self, now: Instant, pn: PacketNumber, ack_eliciting: bool) {
|
||||
let next_in_order_pn = self.ranges.front().map_or(0, |pr| pr.largest + 1);
|
||||
qdebug!(
|
||||
[self],
|
||||
|
@ -427,12 +425,9 @@ impl RecvdPackets {
|
|||
self.trim_ranges();
|
||||
|
||||
// The new addition was the largest, so update the time we use for calculating ACK delay.
|
||||
let largest = if pn >= next_in_order_pn {
|
||||
if pn >= next_in_order_pn {
|
||||
self.largest_pn_time = Some(now);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
if ack_eliciting {
|
||||
self.pkts_since_last_ack += 1;
|
||||
|
@ -456,7 +451,6 @@ impl RecvdPackets {
|
|||
}
|
||||
qdebug!([self], "Set ACK timer to {:?}", self.ack_time);
|
||||
}
|
||||
largest
|
||||
}
|
||||
|
||||
/// Check if the packet is a duplicate.
|
||||
|
@ -637,15 +631,9 @@ impl AckTracker {
|
|||
now: Instant,
|
||||
builder: &mut PacketBuilder,
|
||||
stats: &mut FrameStats,
|
||||
) -> Res<Option<RecoveryToken>> {
|
||||
let res = self
|
||||
.get_mut(pn_space)
|
||||
.and_then(|space| space.write_frame(now, builder, stats));
|
||||
|
||||
if builder.len() > builder.limit() {
|
||||
return Err(Error::InternalError(24));
|
||||
}
|
||||
Ok(res)
|
||||
) -> Option<RecoveryToken> {
|
||||
self.get_mut(pn_space)
|
||||
.and_then(|space| space.write_frame(now, builder, stats))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -855,14 +843,12 @@ mod tests {
|
|||
.set_received(*NOW, 0, true);
|
||||
// The reference time for `ack_time` has to be in the past or we filter out the timer.
|
||||
assert!(tracker.ack_time(*NOW - Duration::from_millis(1)).is_some());
|
||||
let token = tracker
|
||||
.write_frame(
|
||||
PNSpace::Initial,
|
||||
*NOW,
|
||||
&mut builder,
|
||||
&mut FrameStats::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let token = tracker.write_frame(
|
||||
PNSpace::Initial,
|
||||
*NOW,
|
||||
&mut builder,
|
||||
&mut FrameStats::default(),
|
||||
);
|
||||
assert!(token.is_some());
|
||||
|
||||
// Mark another packet as received so we have cause to send another ACK in that space.
|
||||
|
@ -884,7 +870,6 @@ mod tests {
|
|||
&mut builder,
|
||||
&mut FrameStats::default()
|
||||
)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
if let RecoveryToken::Ack(tok) = token.unwrap() {
|
||||
tracker.acked(&tok); // Should be a noop.
|
||||
|
@ -905,14 +890,12 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
builder.set_limit(10);
|
||||
|
||||
let token = tracker
|
||||
.write_frame(
|
||||
PNSpace::Initial,
|
||||
*NOW,
|
||||
&mut builder,
|
||||
&mut FrameStats::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let token = tracker.write_frame(
|
||||
PNSpace::Initial,
|
||||
*NOW,
|
||||
&mut builder,
|
||||
&mut FrameStats::default(),
|
||||
);
|
||||
assert!(token.is_none());
|
||||
assert_eq!(builder.len(), 1); // Only the short packet header has been added.
|
||||
}
|
||||
|
@ -933,14 +916,12 @@ mod tests {
|
|||
let mut builder = PacketBuilder::short(Encoder::new(), false, &[]);
|
||||
builder.set_limit(32);
|
||||
|
||||
let token = tracker
|
||||
.write_frame(
|
||||
PNSpace::Initial,
|
||||
*NOW,
|
||||
&mut builder,
|
||||
&mut FrameStats::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let token = tracker.write_frame(
|
||||
PNSpace::Initial,
|
||||
*NOW,
|
||||
&mut builder,
|
||||
&mut FrameStats::default(),
|
||||
);
|
||||
assert!(token.is_some());
|
||||
|
||||
let mut dec = builder.as_decoder();
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
|
||||
use neqo_common::{Datagram, Encoder};
|
||||
use neqo_transport::{
|
||||
Connection, ConnectionParameters, QuicVersion, RandomConnectionIdGenerator, State,
|
||||
Connection, ConnectionParameters, FixedConnectionIdManager, QuicVersion, State,
|
||||
};
|
||||
use test_fixture::{self, addr, now};
|
||||
use test_fixture::{self, loopback, now};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
@ -173,8 +173,8 @@ fn make_server(quic_version: QuicVersion) -> Connection {
|
|||
Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(RandomConnectionIdGenerator::new(5))),
|
||||
ConnectionParameters::default().quic_version(quic_version),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(5))),
|
||||
&ConnectionParameters::default().quic_version(quic_version),
|
||||
)
|
||||
.expect("create a default server")
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ fn process_client_initial(quic_version: QuicVersion, packet: &str) {
|
|||
let mut server = make_server(quic_version);
|
||||
|
||||
let pkt: Vec<u8> = Encoder::from_hex(packet).into();
|
||||
let dgram = Datagram::new(addr(), addr(), pkt);
|
||||
let dgram = Datagram::new(loopback(), loopback(), pkt);
|
||||
assert_eq!(*server.state(), State::Init);
|
||||
let out = server.process(Some(dgram), now());
|
||||
assert_eq!(*server.state(), State::Handshaking);
|
||||
|
|
|
@ -13,17 +13,15 @@ use neqo_crypto::{
|
|||
constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
|
||||
hkdf,
|
||||
hp::HpKey,
|
||||
AllowZeroRtt, AuthenticationStatus, ResumptionToken, ZeroRttCheckResult, ZeroRttChecker,
|
||||
AllowZeroRtt, AuthenticationStatus, ResumptionToken,
|
||||
};
|
||||
use neqo_transport::{
|
||||
server::{ActiveConnectionRef, Server, ValidateAddress},
|
||||
stream_id::StreamIndex,
|
||||
Connection, ConnectionError, ConnectionEvent, ConnectionParameters, Error, Output, QuicVersion,
|
||||
State, StreamType,
|
||||
};
|
||||
use test_fixture::{
|
||||
self, addr, assertions, default_client, now, split_datagram, CountingConnectionIdGenerator,
|
||||
Connection, ConnectionError, ConnectionEvent, ConnectionParameters, Error,
|
||||
FixedConnectionIdManager, Output, QuicVersion, State, StreamType, LOCAL_STREAM_LIMIT_BIDI,
|
||||
LOCAL_STREAM_LIMIT_UNI,
|
||||
};
|
||||
use test_fixture::{self, assertions, default_client, loopback, now, split_datagram};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
|
@ -40,7 +38,7 @@ fn default_server() -> Server {
|
|||
test_fixture::DEFAULT_ALPN,
|
||||
test_fixture::anti_replay(),
|
||||
Box::new(AllowZeroRtt {}),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(9))),
|
||||
ConnectionParameters::default(),
|
||||
)
|
||||
.expect("should create a server")
|
||||
|
@ -221,95 +219,14 @@ fn drop_non_initial() {
|
|||
let mut bogus_data: Vec<u8> = header.into();
|
||||
bogus_data.resize(1200, 66);
|
||||
|
||||
let bogus = Datagram::new(test_fixture::addr(), test_fixture::addr(), bogus_data);
|
||||
let bogus = Datagram::new(
|
||||
test_fixture::loopback(),
|
||||
test_fixture::loopback(),
|
||||
bogus_data,
|
||||
);
|
||||
assert!(server.process(Some(bogus), now()).dgram().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_short_initial() {
|
||||
const CID: &[u8] = &[55; 8]; // not a real connection ID
|
||||
let mut server = default_server();
|
||||
|
||||
// This too small to be an Initial, but it is otherwise plausible.
|
||||
let mut header = neqo_common::Encoder::with_capacity(1199);
|
||||
header
|
||||
.encode_byte(0xca)
|
||||
.encode_uint(4, QuicVersion::default().as_u32())
|
||||
.encode_vec(1, CID)
|
||||
.encode_vec(1, CID);
|
||||
let mut bogus_data: Vec<u8> = header.into();
|
||||
bogus_data.resize(1199, 66);
|
||||
|
||||
let bogus = Datagram::new(test_fixture::addr(), test_fixture::addr(), bogus_data);
|
||||
assert!(server.process(Some(bogus), now()).dgram().is_none());
|
||||
}
|
||||
|
||||
/// Verify that the server can read 0-RTT properly. A more robust server would buffer
|
||||
/// 0-RTT before the handshake begins and let 0-RTT arrive for a short periiod after
|
||||
/// the handshake completes, but ours is for testing so it only allows 0-RTT while
|
||||
/// the handshake is running.
|
||||
#[test]
|
||||
fn zero_rtt() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
|
||||
// Discharge the old connection so that we don't have to worry about it.
|
||||
let mut now = now();
|
||||
let t = server.process(None, now).callback();
|
||||
now += t;
|
||||
assert_eq!(server.process(None, now), Output::None);
|
||||
assert_eq!(server.active_connections().len(), 1);
|
||||
|
||||
let start_time = now;
|
||||
let mut client = default_client();
|
||||
client.enable_resumption(now, &token).unwrap();
|
||||
|
||||
let mut client_send = || {
|
||||
let client_stream = client.stream_create(StreamType::UniDi).unwrap();
|
||||
client.stream_send(client_stream, &[1, 2, 3]).unwrap();
|
||||
match client.process(None, now) {
|
||||
Output::Datagram(d) => d,
|
||||
Output::Callback(t) => {
|
||||
// Pacing...
|
||||
now += t;
|
||||
client.process(None, now).dgram().unwrap()
|
||||
}
|
||||
Output::None => panic!(),
|
||||
}
|
||||
};
|
||||
|
||||
// Now generate a bunch of 0-RTT packets...
|
||||
let c1 = client_send();
|
||||
assertions::assert_coalesced_0rtt(&c1);
|
||||
let c2 = client_send();
|
||||
let c3 = client_send();
|
||||
let c4 = client_send();
|
||||
|
||||
// 0-RTT packets that arrive before the handshake get dropped.
|
||||
let _ = server.process(Some(c2), now);
|
||||
assert!(server.active_connections().is_empty());
|
||||
|
||||
// Now handshake and let another 0-RTT packet in.
|
||||
let shs = server.process(Some(c1), now).dgram();
|
||||
let _ = server.process(Some(c3), now);
|
||||
// The server will have received two STREAM frames now if it processed both packets.
|
||||
let active = server.active_connections();
|
||||
assert_eq!(active.len(), 1);
|
||||
assert_eq!(active[0].borrow().stats().frame_rx.stream, 2);
|
||||
|
||||
// Complete the handshake. As the client was pacing 0-RTT packets, extend the time
|
||||
// a little so that the pacer doesn't prevent the Finished from being sent.
|
||||
now += now - start_time;
|
||||
let cfin = client.process(shs, now).dgram();
|
||||
let _ = server.process(cfin, now);
|
||||
|
||||
// The server will drop this last 0-RTT packet.
|
||||
let _ = server.process(Some(c4), now);
|
||||
let active = server.active_connections();
|
||||
assert_eq!(active.len(), 1);
|
||||
assert_eq!(active[0].borrow().stats().frame_rx.stream, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_basic() {
|
||||
let mut server = default_server();
|
||||
|
@ -367,7 +284,9 @@ fn get_ticket(server: &mut Server) -> ResumptionToken {
|
|||
let dgram = server.process(None, now()).dgram();
|
||||
client.process_input(dgram.unwrap(), now()); // Consume ticket, ignore output.
|
||||
|
||||
let ticket = client
|
||||
// Calling active_connections clears the set of active connections.
|
||||
assert_eq!(server.active_connections().len(), 1);
|
||||
client
|
||||
.events()
|
||||
.find_map(|e| {
|
||||
if let ConnectionEvent::ResumptionToken(token) = e {
|
||||
|
@ -376,15 +295,7 @@ fn get_ticket(server: &mut Server) -> ResumptionToken {
|
|||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Have the client close the connection and then let the server clean up.
|
||||
client.close(now(), 0, "got a ticket");
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
let _ = server.process(dgram, now());
|
||||
// Calling active_connections clears the set of active connections.
|
||||
assert_eq!(server.active_connections().len(), 1);
|
||||
ticket
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Attempt a retry with 0-RTT, and have 0-RTT packets sent with the second ClientHello.
|
||||
|
@ -684,7 +595,7 @@ fn vn_after_retry() {
|
|||
encoder.encode_vec(1, &client.odcid().unwrap()[..]);
|
||||
encoder.encode_vec(1, &[]);
|
||||
encoder.encode_uint(4, 0x5a5a_6a6a_u64);
|
||||
let vn = Datagram::new(addr(), addr(), encoder);
|
||||
let vn = Datagram::new(loopback(), loopback(), encoder);
|
||||
|
||||
assert_ne!(
|
||||
client.process(Some(vn), now()).callback(),
|
||||
|
@ -1031,7 +942,7 @@ fn closed() {
|
|||
assert_eq!(res, Output::None);
|
||||
}
|
||||
|
||||
fn can_create_streams(c: &mut Connection, t: StreamType, n: u64) {
|
||||
fn can_create_streams(c: &mut Connection, t: StreamType, n: usize) {
|
||||
for _ in 0..n {
|
||||
c.stream_create(t).unwrap();
|
||||
}
|
||||
|
@ -1047,10 +958,10 @@ fn max_streams() {
|
|||
test_fixture::DEFAULT_ALPN,
|
||||
test_fixture::anti_replay(),
|
||||
Box::new(AllowZeroRtt {}),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(9))),
|
||||
ConnectionParameters::default()
|
||||
.max_streams(StreamType::BiDi, StreamIndex::new(MAX_STREAMS))
|
||||
.max_streams(StreamType::UniDi, StreamIndex::new(MAX_STREAMS)),
|
||||
.max_streams(StreamType::BiDi, MAX_STREAMS)
|
||||
.max_streams(StreamType::UniDi, MAX_STREAMS),
|
||||
)
|
||||
.expect("should create a server");
|
||||
|
||||
|
@ -1058,8 +969,16 @@ fn max_streams() {
|
|||
connect(&mut client, &mut server);
|
||||
|
||||
// Make sure that we can create MAX_STREAMS uni- and bidirectional streams.
|
||||
can_create_streams(&mut client, StreamType::UniDi, MAX_STREAMS);
|
||||
can_create_streams(&mut client, StreamType::BiDi, MAX_STREAMS);
|
||||
can_create_streams(
|
||||
&mut client,
|
||||
StreamType::UniDi,
|
||||
usize::try_from(MAX_STREAMS).unwrap(),
|
||||
);
|
||||
can_create_streams(
|
||||
&mut client,
|
||||
StreamType::BiDi,
|
||||
usize::try_from(MAX_STREAMS).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1070,7 +989,7 @@ fn max_streams_default() {
|
|||
test_fixture::DEFAULT_ALPN,
|
||||
test_fixture::anti_replay(),
|
||||
Box::new(AllowZeroRtt {}),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
Rc::new(RefCell::new(FixedConnectionIdManager::new(9))),
|
||||
ConnectionParameters::default(),
|
||||
)
|
||||
.expect("should create a server");
|
||||
|
@ -1078,47 +997,15 @@ fn max_streams_default() {
|
|||
let mut client = default_client();
|
||||
connect(&mut client, &mut server);
|
||||
|
||||
// Make sure that we can create streams up to the local limit.
|
||||
let local_limit_unidi = ConnectionParameters::default().get_max_streams(StreamType::UniDi);
|
||||
can_create_streams(&mut client, StreamType::UniDi, local_limit_unidi.as_u64());
|
||||
let local_limit_bidi = ConnectionParameters::default().get_max_streams(StreamType::BiDi);
|
||||
can_create_streams(&mut client, StreamType::BiDi, local_limit_bidi.as_u64());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RejectZeroRtt {}
|
||||
impl ZeroRttChecker for RejectZeroRtt {
|
||||
fn check(&self, _token: &[u8]) -> ZeroRttCheckResult {
|
||||
ZeroRttCheckResult::Reject
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_streams_after_0rtt_rejection() {
|
||||
const MAX_STREAMS: u64 = 40;
|
||||
let mut server = Server::new(
|
||||
now(),
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
test_fixture::anti_replay(),
|
||||
Box::new(RejectZeroRtt {}),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default()
|
||||
.max_streams(StreamType::BiDi, StreamIndex::new(MAX_STREAMS))
|
||||
.max_streams(StreamType::UniDi, StreamIndex::new(MAX_STREAMS)),
|
||||
)
|
||||
.expect("should create a server");
|
||||
let token = get_ticket(&mut server);
|
||||
|
||||
let mut client = default_client();
|
||||
client.enable_resumption(now(), &token).unwrap();
|
||||
let _ = client.stream_create(StreamType::BiDi).unwrap();
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
let dgram = client.process(dgram, now()).dgram();
|
||||
assert!(dgram.is_some()); // We're far enough along to complete the test now.
|
||||
|
||||
// Make sure that we can create MAX_STREAMS uni- and bidirectional streams.
|
||||
can_create_streams(&mut client, StreamType::UniDi, MAX_STREAMS);
|
||||
can_create_streams(&mut client, StreamType::BiDi, MAX_STREAMS);
|
||||
// Make sure that we can create LOCAL_STREAM_LIMIT_UNI unidirectional streams
|
||||
can_create_streams(
|
||||
&mut client,
|
||||
StreamType::UniDi,
|
||||
usize::try_from(LOCAL_STREAM_LIMIT_UNI).unwrap(),
|
||||
);
|
||||
can_create_streams(
|
||||
&mut client,
|
||||
StreamType::BiDi,
|
||||
usize::try_from(LOCAL_STREAM_LIMIT_BIDI).unwrap(),
|
||||
);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче