Bug 1717505 - Update neqo to 0.4.26 r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D118495
This commit is contained in:
Dragana Damjanovic 2021-06-25 10:39:54 +00:00
parent 06bbd55be6
commit 778175efc9
87 changed files with 2957 additions and 1261 deletions

View File

@ -15,7 +15,7 @@ rev = "5cea1c9a3d8ed3ed2d7bdd5be3285e7821400b7f"
[source."https://github.com/mozilla/neqo"]
git = "https://github.com/mozilla/neqo"
replace-with = "vendored-sources"
tag = "v0.4.25"
tag = "v0.4.26"
[source."https://github.com/mozilla/mp4parse-rust"]
git = "https://github.com/mozilla/mp4parse-rust"

20
Cargo.lock generated
View File

@ -3207,8 +3207,8 @@ dependencies = [
[[package]]
name = "neqo-common"
version = "0.4.25"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.25#13b5a13022419babba7183f768a974edfe2bd58b"
version = "0.4.26"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.26#2ad6f5f694e5bf11c61d5fd38ced8685d127fbb7"
dependencies = [
"chrono",
"env_logger",
@ -3220,8 +3220,8 @@ dependencies = [
[[package]]
name = "neqo-crypto"
version = "0.4.25"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.25#13b5a13022419babba7183f768a974edfe2bd58b"
version = "0.4.26"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.26#2ad6f5f694e5bf11c61d5fd38ced8685d127fbb7"
dependencies = [
"bindgen",
"log",
@ -3233,8 +3233,8 @@ dependencies = [
[[package]]
name = "neqo-http3"
version = "0.4.25"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.25#13b5a13022419babba7183f768a974edfe2bd58b"
version = "0.4.26"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.26#2ad6f5f694e5bf11c61d5fd38ced8685d127fbb7"
dependencies = [
"log",
"neqo-common",
@ -3247,8 +3247,8 @@ dependencies = [
[[package]]
name = "neqo-qpack"
version = "0.4.25"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.25#13b5a13022419babba7183f768a974edfe2bd58b"
version = "0.4.26"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.26#2ad6f5f694e5bf11c61d5fd38ced8685d127fbb7"
dependencies = [
"lazy_static",
"log",
@ -3261,8 +3261,8 @@ dependencies = [
[[package]]
name = "neqo-transport"
version = "0.4.25"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.25#13b5a13022419babba7183f768a974edfe2bd58b"
version = "0.4.26"
source = "git+https://github.com/mozilla/neqo?tag=v0.4.26#2ad6f5f694e5bf11c61d5fd38ced8685d127fbb7"
dependencies = [
"indexmap",
"lazy_static",

View File

@ -8,10 +8,10 @@ edition = "2018"
name = "neqo_glue"
[dependencies]
neqo-http3 = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.4.26", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.4.26", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.26", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.26", 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.25"
tag = "v0.4.26"
git = "https://github.com/mozilla/neqo"
default-features = false
features = ["gecko"]

View File

@ -5,16 +5,16 @@ authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
edition = "2018"
[dependencies]
neqo-transport = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.25", git = "https://github.com/mozilla/neqo" }
neqo-transport = { tag = "v0.4.26", git = "https://github.com/mozilla/neqo" }
neqo-common = { tag = "v0.4.26", git = "https://github.com/mozilla/neqo" }
neqo-http3 = { tag = "v0.4.26", git = "https://github.com/mozilla/neqo" }
neqo-qpack = { tag = "v0.4.26", 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.25"
tag = "v0.4.26"
git = "https://github.com/mozilla/neqo"
default-features = false
features = ["gecko"]

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"7f9bef3e34f1caf9fdc9a21d929cf282ee285384933f59d987e69dcc6f37f412","build.rs":"a17b1bb1bd3de3fc958f72d4d1357f7bc4432faa26640c95b5fbfccf40579d67","src/codec.rs":"a20011436df6c4c5620b2fc9d45c10b8f4ce0922b8593c8bfb2355a41670687d","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/event.rs":"f60fee9f4b09ef47ff5e4bfa21c07e45ffd5873c292f2605f24d834070127d62","src/hrtime.rs":"45a608ce9f00e2666ce95422a278c6dc0ff4e229b114e7bcf0b4c0d9dc61ad56","src/incrdecoder.rs":"896099bc2abf788d58b444e214db79a4b57156541b80faa0749c4be2f6ea7e7a","src/lib.rs":"659d5397175c1645eab07a999b6ccf0730d02d9614b58720955af83afef0f0e4","src/log.rs":"b69e492af85e65866cb6588138e8a337dd897d3ce399cb4e9fb8cc04ac042b7f","src/qlog.rs":"e59c4e6dcf9c70553dd6f58da41ff2053ea67b008cac186742140352f5044130","src/timer.rs":"147d82795f0f5c660d93ffb3249524461a34c58bef73c0f6bcbae365e7ae2f2d","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}
{"files":{"Cargo.toml":"89e7ca6166c8edacac1f65b9ddac4af0c8e556ae813352d2441df56abfe135b9","build.rs":"a17b1bb1bd3de3fc958f72d4d1357f7bc4432faa26640c95b5fbfccf40579d67","src/codec.rs":"a20011436df6c4c5620b2fc9d45c10b8f4ce0922b8593c8bfb2355a41670687d","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/event.rs":"f60fee9f4b09ef47ff5e4bfa21c07e45ffd5873c292f2605f24d834070127d62","src/hrtime.rs":"45a608ce9f00e2666ce95422a278c6dc0ff4e229b114e7bcf0b4c0d9dc61ad56","src/incrdecoder.rs":"ddbeadb4712133281f706cdf828047ca97502e9fe26b7359961040ebe3535e09","src/lib.rs":"659d5397175c1645eab07a999b6ccf0730d02d9614b58720955af83afef0f0e4","src/log.rs":"b69e492af85e65866cb6588138e8a337dd897d3ce399cb4e9fb8cc04ac042b7f","src/qlog.rs":"e59c4e6dcf9c70553dd6f58da41ff2053ea67b008cac186742140352f5044130","src/timer.rs":"147d82795f0f5c660d93ffb3249524461a34c58bef73c0f6bcbae365e7ae2f2d","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-common"
version = "0.4.25"
version = "0.4.26"
authors = ["Bobby Holley <bobbyholley@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"

View File

@ -96,7 +96,7 @@ impl IncrementalDecoderBuffer {
self.v.extend_from_slice(b);
self.remaining -= amount;
if self.remaining == 0 {
Some(mem::replace(&mut self.v, Vec::new()))
Some(mem::take(&mut self.v))
} else {
None
}
@ -251,7 +251,7 @@ mod tests {
assert!(dec.min_remaining() < tail);
if tail > 1 {
assert_eq!(res, false);
assert!(!res);
assert!(dec.min_remaining() > 0);
let mut dv = Decoder::from(&db[split..]);
eprintln!(" split remainder {}: {:?}", split, dv);
@ -260,7 +260,7 @@ mod tests {
}
assert_eq!(dec.min_remaining(), 0);
assert_eq!(res, true);
assert!(res);
}
}
}

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"97510db649c0b541f1744a089443207db49e234c764a469036881eebaaec4bae","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":"641b9795c27041524924a14ab5b632683573fba02ddb92d09edaa6c96b175108","src/aead.rs":"0fffa96ff69a9aa04dec5c74636648cef1878565ccecc40acf76ac1c14286e05","src/aead_fuzzing.rs":"4e60d5a2ee6dedfd08602fa36318239e731244825df2cb801ca1d88f5f2a41c1","src/agent.rs":"5dd7dff9220fcef3e55794e946135d1c30a51172605c70fc4bf5b33664bf81bd","src/agentio.rs":"ef0a84367b18ad5696a74da8a2c560159586f8d7a696ab6353f36d7e3610084d","src/auth.rs":"e821dac1511691151a6e64b7c7130a07d941dffad4529b2631f20ddd07d3f20c","src/cert.rs":"db83d8f968993c72ae4bf6454d251488fc6c92b182680f3faf267e0eb058376a","src/constants.rs":"998e77bee88197a240032c1bfbddcff417a25ba82e576a0d2fe18ee9b63cefc7","src/err.rs":"fa3bc074a510a6d20fbc28412dfbd026e70986a6c5c82b2b591611bcf7c4392d","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"361277879194dc32f741b8d1894afe5fd3fcc8eb244f7dd5914eeb959b85717d","src/hkdf.rs":"22af86e54125b285f83e5de1aa12694c028f234e23bf6da3f7ea01324d92b74e","src/hp.rs":"0ae41f9683c90943e53b090592d8b700a681d82103a26de7cc9b040cd759ec75","src/lib.rs":"50e2862964e8372b9503c613c3be3e250dd323ff87215e931b787fa23e512f06","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"32a25f45c21e13d9b2b1ff18a7d403f776adc014dd3a3bd0471a1ac083dbfd99","src/prio.rs":"23ef1913263769d95e7fff7a6cd3079b62e210617c406976b3e71ceec686c3fa","src/replay.rs":"143d8b9f8172b20cb579283fb2eb719a8ab80ef946eb0b0ab9fcc68776cbd905","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"4882ba860581d2cd3f5275c17cb371d27252501cd19f2fd8d878cf9bca2c976c","src/selfencrypt.rs":"036a6a22bd0ce9ee849a986f6faad4a550de3551bd53de1f71a7d7d9b4206e5b","src/ssl.rs":"a09f47d6807db38c39150efad46b94414f413738a9c5b6224ae8a9c403f17387","src/time.rs":"8e95ddca8df8494d464619b01f583839f6ce4b9c8404f083258937d53ffa30fc","tests/aead.rs":"98a737643ca41b2f36f6eda5a5dcb2acd420650ef22ab0a8cbed16c423734cc7","tests/agent.rs":"d43e5b05dcc845394d3c7312974faae0fdcbc325c07c970aeb7ef30c3ade652e","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"93c478fcd07d29691007abd6dcfcd2014c10c23b0206ba2d97d01594e4d64397","tests/hkdf.rs":"539235e9dcf2a56b72961a9a04f0080409adf6bf465bfad7c30026421b2d4326","tests/hp.rs":"e52a7d2f4387f2dfe8bfe1da5867e8e0d3eb51e171c6904e18b18c4343536af8","tests/init.rs":"baf680de62f5b06f38a112192a2e9a2ac9492f2cdbdf5f4b749ef18c94c9ac35","tests/selfencrypt.rs":"1125c858ec4e0a6994f34d162aa066cb003c61b324f268529ea04bcb641347cb"},"package":null}
{"files":{"Cargo.toml":"d1fc64df3adcef6a148e7ef543e39b0fffb9d4955358444390bd2068d6610fc7","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"26f85b25967a21522c7185914c8a31afee3e93bf5c5548341b27f708ea1ecede","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":"238238eca9a6428996b96ac2a4d6aa5f206b2892f6e9922e12e74e34fe39d47e","src/aead.rs":"140f77ffb5016836c970c39c6c3a42db9581a14b797b9cd05386d0dd0831fe63","src/aead_fuzzing.rs":"4e60d5a2ee6dedfd08602fa36318239e731244825df2cb801ca1d88f5f2a41c1","src/agent.rs":"9c413275bfa0a6f0c736d9925b4d5978d6b8c8a8ddb1c047b60e69ae1820858e","src/agentio.rs":"995e54772d6000d2773a2c57d67fc80756cab47dacfb4915e1ee49c5906d8495","src/auth.rs":"e821dac1511691151a6e64b7c7130a07d941dffad4529b2631f20ddd07d3f20c","src/cert.rs":"94450b248eed218b9227861ed81e557a543c0c88868fe1a434dc9c9f0f9651ae","src/constants.rs":"998e77bee88197a240032c1bfbddcff417a25ba82e576a0d2fe18ee9b63cefc7","src/ech.rs":"1d7b8760cd4e3cb2800fc9ff5fb2b1c89170fd379e43a9e1c626b7df0a59c6d3","src/err.rs":"38482dc0184802a5a503f540456f3af829641179eba32ed8ee7cc5d6a0afc6b3","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"361277879194dc32f741b8d1894afe5fd3fcc8eb244f7dd5914eeb959b85717d","src/hkdf.rs":"8d05bffddbd9950baa1d4920d42d29e3970caa308b32c1e59b9704dc257d87ab","src/hp.rs":"46a2023c421d89fda8d09b356b648272857fd20ee5cf5829143ac88402b32e4b","src/lib.rs":"2e486b5b18dcc6bf624080396e5f401fb0bed63db6dcd5e11c7614b7ce1bc196","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"3e01b513b982fbc0b75bd66deeab8a9a355ede753091d2076c06111d36ecaf02","src/prio.rs":"38664072cafc4f7ce2dfe2e1e029afe87c423e01a60066c25a736644cb0ce379","src/replay.rs":"6c6a41c4d837ecd14e0dda05e9bf9a2eb6f3f4c3cc6eb8e41156dbd6bf3b1113","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"48790a330994d892742048000bd12460b7eee2c3daaa444481b8527406d0a4c7","src/selfencrypt.rs":"036a6a22bd0ce9ee849a986f6faad4a550de3551bd53de1f71a7d7d9b4206e5b","src/ssl.rs":"821dbe19590a8716327628a1df7ba4184a9df454227eac60f0e793bc426fc315","src/time.rs":"b71fa74ad979d78765dd037c12f5e97eefb9fefc91be8f7c6f45e74b66ac11fc","tests/aead.rs":"98a737643ca41b2f36f6eda5a5dcb2acd420650ef22ab0a8cbed16c423734cc7","tests/agent.rs":"c191782187cb344186195fe377d9f351f2454e5b437f8d4ad88ec3edc8608a5d","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"6ea3e5b3bc889d201b55f959b658a848c0ada54c956bda087b2ac8897a24a786","tests/hkdf.rs":"539235e9dcf2a56b72961a9a04f0080409adf6bf465bfad7c30026421b2d4326","tests/hp.rs":"e52a7d2f4387f2dfe8bfe1da5867e8e0d3eb51e171c6904e18b18c4343536af8","tests/init.rs":"baf680de62f5b06f38a112192a2e9a2ac9492f2cdbdf5f4b749ef18c94c9ac35","tests/selfencrypt.rs":"1125c858ec4e0a6994f34d162aa066cb003c61b324f268529ea04bcb641347cb"},"package":null}

View File

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

View File

@ -3,6 +3,7 @@
[nss_ssl]
types = [
"HpkeSymmetricSuite",
"PRCList",
"PRUint16",
"PRUint64",
@ -53,6 +54,8 @@ functions = [
"SSL_VersionRangeSet",
]
enums = [
"HpkeAeadId",
"HpkeKdfId",
"SSLAuthType",
"SSLCipherAlgorithm",
"SSLCompressionMethod",
@ -123,6 +126,7 @@ functions = [
"NSS_NoDB_Init",
"NSS_SetDomesticPolicy",
"NSS_Shutdown",
"NSS_VersionCheck",
]
variables = [
"NSS_INIT_READONLY",
@ -133,10 +137,14 @@ variables = [
types = [
"CERTCertList",
"CERTCertListNode",
"CK_ATTRIBUTE_TYPE",
"CK_FLAGS",
"CK_MECHANISM_TYPE",
"HpkeAeadId",
"HpkeKdfId",
"HpkeKemId",
"SECItem",
"SECItemArray",
"CK_ATTRIBUTE_TYPE",
"CK_MECHANISM_TYPE",
]
functions = [
"CERT_DestroyCertificate",
@ -148,35 +156,57 @@ functions = [
"PK11_FindKeyByAnyCert",
"PK11_FreeSlot",
"PK11_FreeSymKey",
"PK11_GenerateKeyPairWithOpFlags",
"PK11_GenerateRandom",
"PK11_GetBlockSize",
"PK11_GetInternalSlot",
"PK11_GetKeyData",
"PK11_GetMechanism",
"PK11_HPKE_Serialize",
"PK11_ImportSymKey",
"PK11_ReadRawAttribute",
"PK11_ReferenceSymKey",
"SECITEM_FreeItem",
"SECKEY_CopyPrivateKey",
"SECKEY_CopyPublicKey",
"SECKEY_DestroyPrivateKey",
"SECKEY_DestroyPublicKey",
"SECOID_FindOIDByTag",
]
enums = [
"HpkeAeadId",
"HpkeKdfId",
"HpkeKemId",
"PK11ObjectType",
"PK11Origin",
"SECItemType",
"SECOidTag",
]
opaque = [
"CERTCertificate",
"PK11SlotInfo",
"PK11SymKey",
"SECKEYPublicKey",
"SECKEYPrivateKey",
"SECKEYPublicKey",
]
variables = [
"CKA_DERIVE",
"CKA_VALUE",
"CKF_DERIVE",
"CKM_AES_ECB",
"CKM_AES_GCM",
"CKM_EC_KEY_PAIR_GEN",
"CKM_INVALID_MECHANISM",
"CKM_NSS_CHACHA20_POLY1305",
"CKM_NSS_CHACHA20_CTR",
"CKM_NSS_CHACHA20_POLY1305",
"CKM_NSS_HKDF_SHA256",
"CKM_NSS_HKDF_SHA384",
"PK11_ATTR_INSENSITIVE",
"PK11_ATTR_PRIVATE",
"PK11_ATTR_PUBLIC",
"PK11_ATTR_SENSITIVE",
"PK11_ATTR_SESSION",
"SEC_ASN1_OBJECT_ID",
]
[nspr_err]

View File

@ -99,8 +99,6 @@ fn nss_dir() -> PathBuf {
Command::new("hg")
.args(&[
"clone",
"-u",
"NSS_3_53_RTM",
"https://hg.mozilla.org/projects/nss",
dir.to_str().unwrap(),
])
@ -112,8 +110,6 @@ fn nss_dir() -> PathBuf {
Command::new("hg")
.args(&[
"clone",
"-u",
"NSPR_4_25_RTM",
"https://hg.mozilla.org/projects/nspr",
nspr_dir.to_str().unwrap(),
])
@ -322,7 +318,7 @@ fn setup_standalone() -> Vec<String> {
fn setup_for_gecko() -> Vec<String> {
let mut flags: Vec<String> = Vec::new();
let fold_libs = env::var("MOZ_FOLD_LIBS").unwrap_or("".to_string()) == "1";
let fold_libs = env::var("MOZ_FOLD_LIBS").unwrap_or_default() == "1";
let libs = if fold_libs {
vec!["nss3"]
} else {

View File

@ -5,7 +5,7 @@
// except according to those terms.
use crate::constants::{Cipher, Version};
use crate::err::{Error, Res};
use crate::err::Res;
use crate::p11::{PK11SymKey, SymKey};
use crate::ssl;
use crate::ssl::{PRUint16, PRUint64, PRUint8, SSLAeadContext};
@ -14,7 +14,7 @@ use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_uint};
use std::ptr::{null_mut, NonNull};
use std::ptr::null_mut;
experimental_api!(SSL_MakeAead(
version: PRUint16,
@ -85,12 +85,9 @@ impl Aead {
c_uint::try_from(p.len())?,
&mut ctx,
)?;
match NonNull::new(ctx) {
Some(ctx_ptr) => Ok(Self {
ctx: AeadContext::new(ctx_ptr),
}),
None => Err(Error::InternalError),
}
Ok(Self {
ctx: AeadContext::from_ptr(ctx)?,
})
}
/// Decrypt a plaintext.

View File

@ -12,24 +12,25 @@ pub use crate::cert::CertificateInfo;
use crate::constants::{
Alert, Cipher, Epoch, Extension, Group, SignatureScheme, Version, TLS_VERSION_1_3,
};
use crate::ech;
use crate::err::{is_blocked, secstatus_to_res, Error, PRErrorCode, Res};
use crate::ext::{ExtensionHandler, ExtensionTracker};
use crate::p11;
use crate::p11::{self, PrivateKey, PublicKey};
use crate::prio;
use crate::replay::AntiReplay;
use crate::secrets::SecretHolder;
use crate::ssl::{self, PRBool};
use crate::time::{Time, TimeHolder};
use neqo_common::{hex_snip_middle, qdebug, qinfo, qtrace, qwarn};
use neqo_common::{hex_snip_middle, hex_with_len, qdebug, qinfo, qtrace, qwarn};
use std::cell::RefCell;
use std::convert::TryFrom;
use std::ffi::CString;
use std::ffi::{CStr, CString};
use std::mem::{self, MaybeUninit};
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_uint, c_void};
use std::pin::Pin;
use std::ptr::{null, null_mut, NonNull};
use std::ptr::{null, null_mut};
use std::rc::Rc;
use std::time::Instant;
@ -41,6 +42,10 @@ pub enum HandshakeState {
New,
InProgress,
AuthenticationPending,
/// When encrypted client hello is enabled, the server might engage a fallback.
/// This is the status that is returned. The included value is the public
/// name of the server, which should be used to validated the certificate.
EchFallbackAuthenticationPending(String),
Authenticated(PRErrorCode),
Complete(SecretAgentInfo),
Failed(Error),
@ -56,8 +61,22 @@ impl HandshakeState {
pub fn is_final(&self) -> bool {
matches!(self, Self::Complete(_) | Self::Failed(_))
}
#[must_use]
pub fn authentication_needed(&self) -> bool {
matches!(
self,
Self::AuthenticationPending | Self::EchFallbackAuthenticationPending(_)
)
}
}
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::unnested_or_patterns
)] // Until we require rust 1.53 we can't use or_patterns.
fn get_alpn(fd: *mut ssl::PRFileDesc, pre: bool) -> Res<Option<String>> {
let mut alpn_state = ssl::SSLNextProtoState::SSL_NEXT_PROTO_NO_SUPPORT;
let mut chosen = vec![0_u8; 255];
@ -99,7 +118,7 @@ macro_rules! preinfo_arg {
pub fn $v(&self) -> Option<$t> {
match self.info.valuesSet & ssl::$m {
0 => None,
_ => Some($t::from(self.info.$f)),
_ => Some($t::try_from(self.info.$f).unwrap()),
}
}
};
@ -124,6 +143,12 @@ impl SecretAgentPreInfo {
preinfo_arg!(version, ssl_preinfo_version, protocolVersion: Version);
preinfo_arg!(cipher_suite, ssl_preinfo_cipher_suite, cipherSuite: Cipher);
preinfo_arg!(
early_data_cipher,
ssl_preinfo_0rtt_cipher_suite,
zeroRttCipherSuite: Cipher,
);
#[must_use]
pub fn early_data(&self) -> bool {
self.info.canSendEarlyData != 0
@ -135,16 +160,41 @@ impl SecretAgentPreInfo {
pub fn max_early_data(&self) -> usize {
usize::try_from(self.info.maxEarlyDataSize).unwrap()
}
/// Was ECH accepted.
#[must_use]
pub fn ech_accepted(&self) -> Option<bool> {
if self.info.valuesSet & ssl::ssl_preinfo_ech == 0 {
None
} else {
Some(self.info.echAccepted != 0)
}
}
/// Get the ECH public name that was used. This will only be available
/// (that is, not `None`) if `ech_accepted()` returns `false`.
/// In this case, certificate validation needs to use this name rather
/// than the original name to validate the certificate. If
/// that validation passes (that is, `SecretAgent::authenticated` is called
/// with `AuthenticationStatus::Ok`), then the handshake will still fail.
/// After the failed handshake, the state will be `Error::EchRetry`,
/// which contains a valid ECH configuration.
///
/// # Errors
/// When the public name is not valid UTF-8. (Note: names should be ASCII.)
pub fn ech_public_name(&self) -> Res<Option<&str>> {
if self.info.valuesSet & ssl::ssl_preinfo_ech == 0 || self.info.echPublicName.is_null() {
Ok(None)
} else {
let n = unsafe { CStr::from_ptr(self.info.echPublicName) };
Ok(Some(n.to_str()?))
}
}
#[must_use]
pub fn alpn(&self) -> Option<&String> {
self.alpn.as_ref()
}
preinfo_arg!(
early_data_cipher,
ssl_preinfo_0rtt_cipher_suite,
zeroRttCipherSuite: Cipher,
);
}
#[derive(Clone, Debug, Default, PartialEq)]
@ -154,6 +204,7 @@ pub struct SecretAgentInfo {
group: Group,
resumed: bool,
early_data: bool,
ech_accepted: bool,
alpn: Option<String>,
signature_scheme: SignatureScheme,
}
@ -175,6 +226,7 @@ impl SecretAgentInfo {
group: Group::try_from(info.keaGroup)?,
resumed: info.resumed != 0,
early_data: info.earlyDataAccepted != 0,
ech_accepted: info.echAccepted != 0,
alpn: get_alpn(fd, false)?,
signature_scheme: SignatureScheme::try_from(info.signatureScheme)?,
})
@ -200,6 +252,10 @@ impl SecretAgentInfo {
self.early_data
}
#[must_use]
pub fn ech_accepted(&self) -> bool {
self.ech_accepted
}
#[must_use]
pub fn alpn(&self) -> Option<&String> {
self.alpn.as_ref()
}
@ -228,6 +284,10 @@ pub struct SecretAgent {
extension_handlers: Vec<ExtensionTracker>,
inf: Option<SecretAgentInfo>,
/// The encrypted client hello (ECH) configuration that is in use.
/// Empty if ECH is not enabled.
ech_config: Vec<u8>,
}
impl SecretAgent {
@ -247,6 +307,8 @@ impl SecretAgent {
extension_handlers: Vec::new(),
inf: None,
ech_config: Vec::new(),
})
}
@ -541,28 +603,34 @@ impl SecretAgent {
}
/// Call this function to mark the peer as authenticated.
/// Only call this function if `handshake/handshake_raw` returns
/// `HandshakeState::AuthenticationPending`, or it will panic.
/// # Panics
/// If the handshake doesn't need to be authenticated.
pub fn authenticated(&mut self, status: AuthenticationStatus) {
assert_eq!(self.state, HandshakeState::AuthenticationPending);
assert!(self.state.authentication_needed());
*self.auth_required = false;
self.state = HandshakeState::Authenticated(status.into());
}
fn capture_error<T>(&mut self, res: Res<T>) -> Res<T> {
if let Err(e) = &res {
if let Err(e) = res {
let e = ech::convert_ech_error(self.fd, e);
qwarn!([self], "error: {:?}", e);
self.state = HandshakeState::Failed(e.clone());
Err(e)
} else {
res
}
res
}
fn update_state(&mut self, res: Res<()>) -> Res<()> {
self.state = if is_blocked(&res) {
if *self.auth_required {
HandshakeState::AuthenticationPending
self.preinfo()?.ech_public_name()?.map_or(
HandshakeState::AuthenticationPending,
|public_name| {
HandshakeState::EchFallbackAuthenticationPending(public_name.to_owned())
},
)
} else {
HandshakeState::InProgress
}
@ -669,16 +737,24 @@ impl SecretAgent {
pub fn state(&self) -> &HandshakeState {
&self.state
}
/// Take a read secret. This will only return a non-`None` value once.
#[must_use]
pub fn read_secret(&mut self, epoch: Epoch) -> Option<p11::SymKey> {
self.secrets.take_read(epoch)
}
/// Take a write secret.
#[must_use]
pub fn write_secret(&mut self, epoch: Epoch) -> Option<p11::SymKey> {
self.secrets.take_write(epoch)
}
/// Get the active ECH configuration, which is empty if ECH is disabled.
#[must_use]
pub fn ech_config(&self) -> &[u8] {
&self.ech_config
}
}
impl Drop for SecretAgent {
@ -827,6 +903,35 @@ impl Client {
)
}
}
/// Enable encrypted client hello (ECH), using the encoded `ECHConfigList`.
///
/// When ECH is enabled, a client needs to look for `Error::EchRetry` as a
/// failure code. If `Error::EchRetry` is received when connecting, the
/// connection attempt should be retried and the included value provided
/// to this function (instead of what is received from DNS).
///
/// Calling this function with an empty value for `ech_config_list` enables
/// ECH greasing. When that is done, there is no need to look for `EchRetry`
///
/// # Errors
/// Error returned when the configuration is invalid.
pub fn enable_ech(&mut self, ech_config_list: impl AsRef<[u8]>) -> Res<()> {
let config = ech_config_list.as_ref();
qdebug!([self], "Enable ECH for a server: {}", hex_with_len(&config));
self.ech_config = Vec::from(config);
if config.is_empty() {
unsafe { ech::SSL_EnableTls13GreaseEch(self.agent.fd, PRBool::from(true)) }
} else {
unsafe {
ech::SSL_SetClientEchConfigs(
self.agent.fd,
config.as_ptr(),
c_uint::try_from(config.len())?,
)
}
}
}
}
impl Deref for Client {
@ -843,6 +948,12 @@ impl DerefMut for Client {
}
}
impl ::std::fmt::Display for Client {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "Client {:p}", self.agent.fd)
}
}
/// `ZeroRttCheckResult` encapsulates the options for handling a `ClientHello`.
#[derive(Clone, Debug, PartialEq)]
pub enum ZeroRttCheckResult {
@ -905,17 +1016,17 @@ impl Server {
for n in certificates {
let c = CString::new(n.as_ref())?;
let cert = match NonNull::new(unsafe {
p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut())
}) {
None => return Err(Error::CertificateLoading),
Some(ptr) => p11::Certificate::new(ptr),
let cert_ptr = unsafe { p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut()) };
let cert = if let Ok(c) = p11::Certificate::from_ptr(cert_ptr) {
c
} else {
return Err(Error::CertificateLoading);
};
let key = match NonNull::new(unsafe {
p11::PK11_FindKeyByAnyCert(*cert.deref(), null_mut())
}) {
None => return Err(Error::CertificateLoading),
Some(ptr) => p11::PrivateKey::new(ptr),
let key_ptr = unsafe { p11::PK11_FindKeyByAnyCert(*cert.deref(), null_mut()) };
let key = if let Ok(k) = p11::PrivateKey::from_ptr(key_ptr) {
k
} else {
return Err(Error::CertificateLoading);
};
secstatus_to_res(unsafe {
ssl::SSL_ConfigServerCert(agent.fd, *cert.deref(), *key.deref(), null(), 0)
@ -1008,6 +1119,32 @@ impl Server {
Ok(*Pin::into_inner(records))
}
/// Enable encrypted client hello (ECH).
///
/// # Errors
/// Fails when NSS cannot create a key pair.
pub fn enable_ech(
&mut self,
config: u8,
public_name: &str,
sk: &PrivateKey,
pk: &PublicKey,
) -> Res<()> {
let cfg = ech::encode_config(config, public_name, pk)?;
qdebug!([self], "Enable ECH for a server: {}", hex_with_len(&cfg));
unsafe {
ech::SSL_SetServerEchConfigs(
self.agent.fd,
**pk,
**sk,
cfg.as_ptr(),
c_uint::try_from(cfg.len())?,
)?;
};
self.ech_config = cfg;
Ok(())
}
}
impl Deref for Server {
@ -1024,6 +1161,12 @@ impl DerefMut for Server {
}
}
impl ::std::fmt::Display for Server {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "Server {:p}", self.agent.fd)
}
}
/// A generic container for Client or Server.
#[derive(Debug)]
pub enum Agent {

View File

@ -242,7 +242,7 @@ impl AgentIo {
pub fn take_output(&mut self) -> Vec<u8> {
qtrace!([self], "take output");
mem::replace(&mut self.output, Vec::new())
mem::take(&mut self.output)
}
}

View File

@ -6,8 +6,7 @@
use crate::err::secstatus_to_res;
use crate::p11::{
CERTCertList, CERTCertListNode, CERT_GetCertificateDer, CertList, PRCList, SECItem,
SECItemArray, SECItemType,
CERTCertListNode, CERT_GetCertificateDer, CertList, Item, PRCList, SECItem, SECItemArray,
};
use crate::ssl::{
PRFileDesc, SSL_PeerCertificateChain, SSL_PeerSignedCertTimestamps,
@ -16,7 +15,7 @@ use crate::ssl::{
use neqo_common::qerror;
use std::convert::TryFrom;
use std::ptr::{null_mut, NonNull};
use std::ptr::NonNull;
use std::slice;
@ -32,12 +31,10 @@ pub struct CertificateInfo {
fn peer_certificate_chain(fd: *mut PRFileDesc) -> Option<(CertList, *const CERTCertListNode)> {
let chain = unsafe { SSL_PeerCertificateChain(fd) };
let certs = match NonNull::new(chain.cast::<CERTCertList>()) {
Some(certs_ptr) => CertList::new(certs_ptr),
None => return None,
};
let cursor = CertificateInfo::head(&certs);
Some((certs, cursor))
CertList::from_ptr(chain.cast()).ok().map(|certs| {
let cursor = CertificateInfo::head(&certs);
(certs, cursor)
})
}
// As explained in rfc6961, an OCSPResponseList can have at most
@ -79,15 +76,12 @@ fn signed_cert_timestamp(fd: *mut PRFileDesc) -> Option<Vec<u8>> {
impl CertificateInfo {
pub(crate) fn new(fd: *mut PRFileDesc) -> Option<Self> {
match peer_certificate_chain(fd) {
Some((certs, cursor)) => Some(Self {
certs,
cursor,
stapled_ocsp_responses: stapled_ocsp_responses(fd),
signed_cert_timestamp: signed_cert_timestamp(fd),
}),
None => None,
}
peer_certificate_chain(fd).map(|(certs, cursor)| Self {
certs,
cursor,
stapled_ocsp_responses: stapled_ocsp_responses(fd),
signed_cert_timestamp: signed_cert_timestamp(fd),
})
}
fn head(certs: &CertList) -> *const CERTCertListNode {
@ -103,11 +97,7 @@ impl<'a> Iterator for &'a mut CertificateInfo {
if self.cursor == CertificateInfo::head(&self.certs) {
return None;
}
let mut item = SECItem {
type_: SECItemType::siBuffer,
data: null_mut(),
len: 0,
};
let mut item = Item::make_empty();
let cert = unsafe { *self.cursor }.cert;
secstatus_to_res(unsafe { CERT_GetCertificateDer(cert, &mut item) })
.expect("getting DER from certificate should work");

189
third_party/rust/neqo-crypto/src/ech.rs vendored Normal file
View File

@ -0,0 +1,189 @@
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::err::{ssl::SSL_ERROR_ECH_RETRY_WITH_ECH, Error, Res};
use crate::p11::{
self, Item, PrivateKey, PublicKey, SECITEM_FreeItem, SECItem, SECKEYPrivateKey,
SECKEYPublicKey, Slot,
};
use crate::ssl::{PRBool, PRFileDesc};
use neqo_common::qtrace;
use std::convert::TryFrom;
use std::ffi::CString;
use std::os::raw::{c_char, c_uint};
use std::ptr::null_mut;
pub use crate::p11::{HpkeAeadId as AeadId, HpkeKdfId as KdfId, HpkeKemId as KemId};
pub use crate::ssl::HpkeSymmetricSuite as SymmetricSuite;
experimental_api!(SSL_EnableTls13GreaseEch(
fd: *mut PRFileDesc,
enabled: PRBool,
));
experimental_api!(SSL_GetEchRetryConfigs(
fd: *mut PRFileDesc,
config: *mut SECItem,
));
experimental_api!(SSL_SetClientEchConfigs(
fd: *mut PRFileDesc,
config_list: *const u8,
config_list_len: c_uint,
));
experimental_api!(SSL_SetServerEchConfigs(
fd: *mut PRFileDesc,
pk: *const SECKEYPublicKey,
sk: *const SECKEYPrivateKey,
record: *const u8,
record_len: c_uint,
));
experimental_api!(SSL_EncodeEchConfigId(
config_id: u8,
public_name: *const c_char,
max_name_len: c_uint,
kem_id: KemId::Type,
pk: *const SECKEYPublicKey,
hpke_suites: *const SymmetricSuite,
hpke_suite_count: c_uint,
out: *mut u8,
out_len: *mut c_uint,
max_len: c_uint,
));
/// Convert any result that contains an ECH error into a result with an `EchRetry`.
pub fn convert_ech_error(fd: *mut PRFileDesc, err: Error) -> Error {
if let Error::NssError {
code: SSL_ERROR_ECH_RETRY_WITH_ECH,
..
} = &err
{
let mut item = Item::make_empty();
if unsafe { SSL_GetEchRetryConfigs(fd, &mut item).is_err() } {
return Error::InternalError;
}
let buf = unsafe {
let slc = std::slice::from_raw_parts(item.data, usize::try_from(item.len).unwrap());
let buf = Vec::from(slc);
SECITEM_FreeItem(&mut item, PRBool::from(false));
buf
};
Error::EchRetry(buf)
} else {
err
}
}
/// Generate a key pair for encrypted client hello (ECH).
///
/// # Errors
/// When NSS fails to generate a key pair or when the KEM is not supported.
/// # Panics
/// When underlying types aren't large enough to hold keys. So never.
pub fn generate_keys() -> Res<(PrivateKey, PublicKey)> {
let slot = Slot::internal()?;
let oid_data = unsafe { p11::SECOID_FindOIDByTag(p11::SECOidTag::SEC_OID_CURVE25519) };
let oid = unsafe { oid_data.as_ref() }.ok_or(Error::InternalError)?;
let oid_slc =
unsafe { std::slice::from_raw_parts(oid.oid.data, usize::try_from(oid.oid.len).unwrap()) };
let mut params: Vec<u8> = Vec::with_capacity(oid_slc.len() + 2);
params.push(u8::try_from(p11::SEC_ASN1_OBJECT_ID).unwrap());
params.push(u8::try_from(oid.oid.len).unwrap());
params.extend_from_slice(oid_slc);
let mut public_ptr: *mut SECKEYPublicKey = null_mut();
// If we have tracing on, try to ensure that key data can be read.
let insensitive_secret_ptr = if log::log_enabled!(log::Level::Trace) {
unsafe {
p11::PK11_GenerateKeyPairWithOpFlags(
*slot,
p11::CK_MECHANISM_TYPE::from(p11::CKM_EC_KEY_PAIR_GEN),
(&mut Item::wrap(&params) as *mut SECItem).cast(),
&mut public_ptr,
p11::PK11_ATTR_SESSION | p11::PK11_ATTR_INSENSITIVE | p11::PK11_ATTR_PUBLIC,
p11::CK_FLAGS::from(p11::CKF_DERIVE),
p11::CK_FLAGS::from(p11::CKF_DERIVE),
null_mut(),
)
}
} else {
null_mut()
};
assert_eq!(insensitive_secret_ptr.is_null(), public_ptr.is_null());
let secret_ptr = if insensitive_secret_ptr.is_null() {
unsafe {
p11::PK11_GenerateKeyPairWithOpFlags(
*slot,
p11::CK_MECHANISM_TYPE::from(p11::CKM_EC_KEY_PAIR_GEN),
(&mut Item::wrap(&params) as *mut SECItem).cast(),
&mut public_ptr,
p11::PK11_ATTR_SESSION | p11::PK11_ATTR_SENSITIVE | p11::PK11_ATTR_PRIVATE,
p11::CK_FLAGS::from(p11::CKF_DERIVE),
p11::CK_FLAGS::from(p11::CKF_DERIVE),
null_mut(),
)
}
} else {
insensitive_secret_ptr
};
assert_eq!(secret_ptr.is_null(), public_ptr.is_null());
let sk = PrivateKey::from_ptr(secret_ptr)?;
let pk = PublicKey::from_ptr(public_ptr)?;
qtrace!("Generated key pair: sk={:?} pk={:?}", sk, pk);
Ok((sk, pk))
}
/// Encode a configuration for encrypted client hello (ECH).
///
/// # Errors
/// When NSS fails to generate a valid configuration encoding (i.e., unlikely).
pub fn encode_config(config: u8, public_name: &str, pk: &PublicKey) -> Res<Vec<u8>> {
// A sensible fixed value for the maximum length of a name.
const MAX_NAME_LEN: c_uint = 64;
// Enable a selection of suites.
// NSS supports SHA-512 as well, which could be added here.
const SUITES: &[SymmetricSuite] = &[
SymmetricSuite {
kdfId: KdfId::HpkeKdfHkdfSha256,
aeadId: AeadId::HpkeAeadAes128Gcm,
},
SymmetricSuite {
kdfId: KdfId::HpkeKdfHkdfSha256,
aeadId: AeadId::HpkeAeadChaCha20Poly1305,
},
SymmetricSuite {
kdfId: KdfId::HpkeKdfHkdfSha384,
aeadId: AeadId::HpkeAeadAes128Gcm,
},
SymmetricSuite {
kdfId: KdfId::HpkeKdfHkdfSha384,
aeadId: AeadId::HpkeAeadChaCha20Poly1305,
},
];
let name = CString::new(public_name)?;
let mut encoded = [0; 1024];
let mut encoded_len = 0;
unsafe {
SSL_EncodeEchConfigId(
config,
name.as_ptr(),
MAX_NAME_LEN,
KemId::HpkeDhKemX25519Sha256,
**pk,
SUITES.as_ptr(),
c_uint::try_from(SUITES.len())?,
encoded.as_mut_ptr(),
&mut encoded_len,
c_uint::try_from(encoded.len())?,
)?
};
Ok(Vec::from(&encoded[..usize::try_from(encoded_len)?]))
}

View File

@ -4,10 +4,16 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code, clippy::upper_case_acronyms)]
#![allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
#![allow(dead_code)]
#![allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
use std::os::raw::c_char;
use std::str::Utf8Error;
use crate::ssl::{SECStatus, SECSuccess};
@ -34,6 +40,7 @@ pub enum Error {
AeadError,
CertificateLoading,
CreateSslSocket,
EchRetry(Vec<u8>),
HkdfError,
InternalError,
IntegerOverflow,
@ -47,11 +54,18 @@ pub enum Error {
},
OverrunError,
SelfEncryptFailure,
StringError,
TimeTravelError,
UnsupportedCipher,
UnsupportedVersion,
}
impl Error {
pub(crate) fn last_nss_error() -> Self {
Self::from(unsafe { PR_GetError() })
}
}
impl std::error::Error for Error {
#[must_use]
fn cause(&self) -> Option<&dyn std::error::Error> {
@ -81,9 +95,12 @@ impl From<std::ffi::NulError> for Error {
Self::InternalError
}
}
impl From<i32> for Error {
#[must_use]
impl From<Utf8Error> for Error {
fn from(_: Utf8Error) -> Self {
Self::StringError
}
}
impl From<PRErrorCode> for Error {
fn from(code: PRErrorCode) -> Self {
let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
let desc = wrap_str_fn(
@ -111,11 +128,10 @@ where
pub fn secstatus_to_res(rv: SECStatus) -> Res<()> {
if rv == SECSuccess {
return Ok(());
Ok(())
} else {
Err(Error::last_nss_error())
}
let code = unsafe { PR_GetError() };
Err(Error::from(code))
}
pub fn is_blocked(result: &Res<()>) -> bool {

View File

@ -10,14 +10,13 @@ use crate::constants::{
};
use crate::err::{Error, Res};
use crate::p11::{
random, PK11Origin, PK11SymKey, PK11_GetInternalSlot, PK11_ImportSymKey, SECItem, SECItemType,
Slot, SymKey, CKA_DERIVE, CKM_NSS_HKDF_SHA256, CKM_NSS_HKDF_SHA384, CK_ATTRIBUTE_TYPE,
CK_MECHANISM_TYPE,
random, Item, PK11Origin, PK11SymKey, PK11_ImportSymKey, Slot, SymKey, CKA_DERIVE,
CKM_NSS_HKDF_SHA256, CKM_NSS_HKDF_SHA384, CK_ATTRIBUTE_TYPE, CK_MECHANISM_TYPE,
};
use std::convert::TryFrom;
use std::os::raw::{c_char, c_uchar, c_uint};
use std::ptr::{null_mut, NonNull};
use std::os::raw::{c_char, c_uint};
use std::ptr::null_mut;
experimental_api!(SSL_HkdfExtract(
version: Version,
@ -69,30 +68,18 @@ pub fn import_key(version: Version, cipher: Cipher, buf: &[u8]) -> Res<SymKey> {
TLS_AES_256_GCM_SHA384 => CKM_NSS_HKDF_SHA384,
_ => return Err(Error::UnsupportedCipher),
};
let mut item = SECItem {
type_: SECItemType::siBuffer,
data: buf.as_ptr() as *mut c_uchar,
len: c_uint::try_from(buf.len())?,
};
let slot_ptr = unsafe { PK11_GetInternalSlot() };
let slot = match NonNull::new(slot_ptr) {
Some(p) => Slot::new(p),
None => return Err(Error::InternalError),
};
let slot = Slot::internal()?;
let key_ptr = unsafe {
PK11_ImportSymKey(
*slot,
CK_MECHANISM_TYPE::from(mech),
PK11Origin::PK11_OriginUnwrap,
CK_ATTRIBUTE_TYPE::from(CKA_DERIVE),
&mut item,
&mut Item::wrap(buf),
null_mut(),
)
};
match NonNull::new(key_ptr) {
Some(p) => Ok(SymKey::new(p)),
None => Err(Error::InternalError),
}
SymKey::from_ptr(key_ptr)
}
/// Extract a PRK from the given salt and IKM using the algorithm defined in RFC 5869.
@ -111,10 +98,7 @@ pub fn extract(
None => null_mut(),
};
unsafe { SSL_HkdfExtract(version, cipher, salt_ptr, **ikm, &mut prk) }?;
match NonNull::new(prk) {
Some(p) => Ok(SymKey::new(p)),
None => Err(Error::InternalError),
}
SymKey::from_ptr(prk)
}
/// Expand a PRK using the HKDF-Expand-Label function defined in RFC 8446.
@ -145,8 +129,5 @@ pub fn expand_label(
&mut secret,
)
}?;
match NonNull::new(secret) {
Some(p) => Ok(SymKey::new(p)),
None => Err(Error::HkdfError),
}
SymKey::from_ptr(secret)
}

View File

@ -9,14 +9,14 @@ use crate::constants::{
};
use crate::err::{secstatus_to_res, Error, Res};
use crate::p11::{
PK11SymKey, PK11_Encrypt, PK11_GetBlockSize, PK11_GetMechanism, SECItem, SECItemType, SymKey,
Item, PK11SymKey, PK11_Encrypt, PK11_GetBlockSize, PK11_GetMechanism, SECItem, SymKey,
CKM_AES_ECB, CKM_NSS_CHACHA20_CTR, CK_MECHANISM_TYPE,
};
use std::convert::TryFrom;
use std::fmt::{self, Debug};
use std::os::raw::{c_char, c_uint};
use std::ptr::{null, null_mut, NonNull};
use std::ptr::{null, null_mut};
experimental_api!(SSL_HkdfExpandLabelWithMech(
version: Version,
@ -74,10 +74,8 @@ impl HpKey {
&mut secret,
)
}?;
match NonNull::new(secret) {
None => Err(Error::HkdfError),
Some(p) => Ok(Self(SymKey::new(p))),
}
let sym_key = SymKey::from_ptr(secret).or(Err(Error::HkdfError))?;
Ok(HpKey(sym_key))
}
/// Get the sample size, which is also the output size.
@ -106,16 +104,12 @@ impl HpKey {
let output_slice = &mut output[..];
let mut output_len: c_uint = 0;
let mut item = SECItem {
type_: SECItemType::siBuffer,
data: sample.as_ptr() as *mut u8,
len: c_uint::try_from(sample.len())?,
};
let zero = vec![0_u8; block_size];
let mut wrapped_sample = Item::wrap(sample);
let (iv, inbuf) = match () {
_ if mech == CK_MECHANISM_TYPE::from(CKM_AES_ECB) => (null_mut(), sample),
_ if mech == CK_MECHANISM_TYPE::from(CKM_NSS_CHACHA20_CTR) => {
(&mut item as *mut SECItem, &zero[..])
(&mut wrapped_sample as *mut SECItem, &zero[..])
}
_ => unreachable!(),
};

View File

@ -28,6 +28,7 @@ mod agentio;
mod auth;
mod cert;
pub mod constants;
mod ech;
mod err;
pub mod ext;
pub mod hkdf;
@ -55,9 +56,13 @@ pub use self::agent::{
};
pub use self::auth::AuthenticationStatus;
pub use self::constants::*;
pub use self::ech::{
encode_config as encode_ech_config, generate_keys as generate_ech_keys, AeadId, KdfId, KemId,
SymmetricSuite,
};
pub use self::err::{Error, PRErrorCode, Res};
pub use self::ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult};
pub use self::p11::{random, SymKey};
pub use self::p11::{random, PrivateKey, PublicKey, SymKey};
pub use self::replay::AntiReplay;
pub use self::secrets::SecretDirection;
pub use self::ssl::Opt;
@ -68,13 +73,16 @@ use std::ffi::CString;
use std::path::{Path, PathBuf};
use std::ptr::null;
const MINIMUM_NSS_VERSION: &str = "3.66";
#[allow(non_upper_case_globals, clippy::redundant_static_lifetimes)]
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
mod nss {
#![allow(
non_upper_case_globals,
clippy::redundant_static_lifetimes,
clippy::upper_case_acronyms
)]
#![allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
}
@ -91,11 +99,8 @@ enum NssLoaded {
impl Drop for NssLoaded {
fn drop(&mut self) {
match self {
Self::NoDb | Self::Db(_) => unsafe {
secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed")
},
_ => {}
if !matches!(self, Self::External) {
unsafe { secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed") }
}
}
}
@ -106,12 +111,23 @@ fn already_initialized() -> bool {
unsafe { nss::NSS_IsInitialized() != 0 }
}
fn version_check() {
let min_ver = CString::new(MINIMUM_NSS_VERSION).unwrap();
assert_ne!(
unsafe { nss::NSS_VersionCheck(min_ver.as_ptr()) },
0,
"Minimum NSS version of {} not supported",
MINIMUM_NSS_VERSION,
);
}
/// Initialize NSS. This only executes the initialization routines once, so if there is any chance that
pub fn init() {
// Set time zero.
time::init();
unsafe {
INITIALIZED.call_once(|| {
version_check();
if already_initialized() {
return NssLoaded::External;
}
@ -143,6 +159,7 @@ pub fn init_db<P: Into<PathBuf>>(dir: P) {
time::init();
unsafe {
INITIALIZED.call_once(|| {
version_check();
if already_initialized() {
return NssLoaded::External;
}
@ -150,8 +167,8 @@ pub fn init_db<P: Into<PathBuf>>(dir: P) {
let path = dir.into();
assert!(path.is_dir());
let pathstr = path.to_str().expect("path converts to string").to_string();
let dircstr = CString::new(pathstr).expect("new CString");
let empty = CString::new("").expect("new empty CString");
let dircstr = CString::new(pathstr).unwrap();
let empty = CString::new("").unwrap();
secstatus_to_res(nss::NSS_Initialize(
dircstr.as_ptr(),
empty.as_ptr(),

View File

@ -13,12 +13,19 @@ use crate::err::{secstatus_to_res, Error, Res};
use neqo_common::hex_with_len;
use std::convert::TryInto;
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::os::raw::{c_int, c_uint};
use std::ptr::null_mut;
#[allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
#[allow(clippy::unreadable_literal, clippy::upper_case_acronyms)]
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
#[allow(unknown_lints, deref_nullptr)] // Until we require rust 1.53 or bindgen#1651 is fixed.
#[allow(clippy::unreadable_literal)]
mod nss_p11 {
include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
}
@ -32,9 +39,16 @@ macro_rules! scoped_ptr {
}
impl $scoped {
#[must_use]
pub fn new(ptr: NonNull<$target>) -> Self {
Self { ptr: ptr.as_ptr() }
/// Create a new instance of `$scoped` from a pointer.
///
/// # Errors
/// When passed a null pointer generates an error.
pub fn from_ptr(ptr: *mut $target) -> Result<Self, crate::err::Error> {
if ptr.is_null() {
Err(crate::err::Error::last_nss_error())
} else {
Ok(Self { ptr })
}
}
}
@ -53,8 +67,9 @@ macro_rules! scoped_ptr {
}
impl Drop for $scoped {
#[allow(unused_must_use)]
fn drop(&mut self) {
let _ = unsafe { $dtor(self.ptr) };
unsafe { $dtor(self.ptr) };
}
}
};
@ -62,10 +77,113 @@ macro_rules! scoped_ptr {
scoped_ptr!(Certificate, CERTCertificate, CERT_DestroyCertificate);
scoped_ptr!(CertList, CERTCertList, CERT_DestroyCertList);
scoped_ptr!(PublicKey, SECKEYPublicKey, SECKEY_DestroyPublicKey);
impl PublicKey {
/// Get the HPKE serialization of the public key.
///
/// # Errors
/// When the key cannot be exported, which can be because the type is not supported.
/// # Panics
/// When keys are too large to fit in `c_uint/usize`. So only on programming error.
pub fn key_data(&self) -> Res<Vec<u8>> {
let mut buf = vec![0; 100];
let mut len: c_uint = 0;
secstatus_to_res(unsafe {
PK11_HPKE_Serialize(
**self,
buf.as_mut_ptr(),
&mut len,
c_uint::try_from(buf.len()).unwrap(),
)
})?;
buf.truncate(usize::try_from(len).unwrap());
Ok(buf)
}
}
impl Clone for PublicKey {
#[must_use]
fn clone(&self) -> Self {
let ptr = unsafe { SECKEY_CopyPublicKey(self.ptr) };
assert!(!ptr.is_null());
Self { ptr }
}
}
impl std::fmt::Debug for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Ok(b) = self.key_data() {
write!(f, "PublicKey {}", hex_with_len(b))
} else {
write!(f, "Opaque PublicKey")
}
}
}
scoped_ptr!(PrivateKey, SECKEYPrivateKey, SECKEY_DestroyPrivateKey);
scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
impl PrivateKey {
/// Get the bits of the private key.
///
/// # Errors
/// When the key cannot be exported, which can be because the type is not supported
/// or because the key data cannot be extracted from the PKCS#11 module.
/// # Panics
/// When the values are too large to fit. So never.
pub fn key_data(&self) -> Res<Vec<u8>> {
let mut key_item = Item::make_empty();
secstatus_to_res(unsafe {
PK11_ReadRawAttribute(
PK11ObjectType::PK11_TypePrivKey,
(**self).cast(),
CK_ATTRIBUTE_TYPE::from(CKA_VALUE),
&mut key_item,
)
})?;
let slc = unsafe {
std::slice::from_raw_parts(key_item.data, usize::try_from(key_item.len).unwrap())
};
let key = Vec::from(slc);
// The data that `key_item` refers to needs to be freed, but we can't
// use the scoped `Item` implementation. This is OK as long as nothing
// panics between `PK11_ReadRawAttribute` succeeding and here.
unsafe { SECITEM_FreeItem(&mut key_item, PRBool::from(false)) };
Ok(key)
}
}
unsafe impl Send for PrivateKey {}
impl Clone for PrivateKey {
#[must_use]
fn clone(&self) -> Self {
let ptr = unsafe { SECKEY_CopyPrivateKey(self.ptr) };
assert!(!ptr.is_null());
Self { ptr }
}
}
impl std::fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Ok(b) = self.key_data() {
write!(f, "PrivateKey {}", hex_with_len(b))
} else {
write!(f, "Opaque PrivateKey")
}
}
}
scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot);
impl Slot {
pub fn internal() -> Res<Self> {
let p = unsafe { PK11_GetInternalSlot() };
Slot::from_ptr(p)
}
}
scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
impl SymKey {
/// You really don't want to use this.
///
@ -102,6 +220,46 @@ impl std::fmt::Debug for SymKey {
}
}
unsafe fn destroy_secitem(item: *mut SECItem) {
SECITEM_FreeItem(item, PRBool::from(true));
}
scoped_ptr!(Item, SECItem, destroy_secitem);
impl Item {
/// Create a wrapper for a slice of this object.
/// Creating this object is technically safe, but using it is extremely dangerous.
/// Minimally, it can only be passed as a `const SECItem*` argument to functions.
pub fn wrap(buf: &[u8]) -> SECItem {
SECItem {
type_: SECItemType::siBuffer,
data: buf.as_ptr() as *mut u8,
len: c_uint::try_from(buf.len()).unwrap(),
}
}
/// Make an empty `SECItem` for passing as a mutable `SECItem*` argument.
pub fn make_empty() -> SECItem {
SECItem {
type_: SECItemType::siBuffer,
data: null_mut(),
len: 0,
}
}
/// This dereferences the pointer held by the item and makes a copy of the
/// content that is referenced there.
///
/// # Safety
/// This dereferences two pointers. It doesn't get much less safe.
pub unsafe fn into_vec(self) -> Vec<u8> {
let b = self.ptr.as_ref().unwrap();
// Sanity check the type, as some types don't count bytes in `Item::len`.
assert_eq!(b.type_, SECItemType::siBuffer);
let slc = std::slice::from_raw_parts(b.data, usize::try_from(b.len).unwrap());
Vec::from(slc)
}
}
/// Generate a randomized buffer.
/// # Panics
/// When `size` is too large or NSS fails.
@ -109,7 +267,7 @@ impl std::fmt::Debug for SymKey {
pub fn random(size: usize) -> Vec<u8> {
let mut buf = vec![0; size];
secstatus_to_res(unsafe {
PK11_GenerateRandom(buf.as_mut_ptr(), buf.len().try_into().unwrap())
PK11_GenerateRandom(buf.as_mut_ptr(), c_int::try_from(buf.len()).unwrap())
})
.unwrap();
buf

View File

@ -4,16 +4,21 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
#![allow(unknown_lints, deref_nullptr)] // Until we require rust 1.53 or bindgen#1651 is fixed.
#![allow(
dead_code,
non_upper_case_globals,
non_snake_case,
clippy::cognitive_complexity,
clippy::empty_enum,
clippy::too_many_lines,
clippy::upper_case_acronyms
clippy::too_many_lines
)]
#![allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
include!(concat!(env!("OUT_DIR"), "/nspr_io.rs"));

View File

@ -4,19 +4,24 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::err::{Error, Res};
use crate::err::Res;
use crate::ssl::PRFileDesc;
use crate::time::{Interval, PRTime, Time};
use std::convert::{TryFrom, TryInto};
use std::ops::{Deref, DerefMut};
use std::os::raw::c_uint;
use std::ptr::{null_mut, NonNull};
use std::ptr::null_mut;
use std::time::{Duration, Instant};
// This is an opaque struct in NSS.
#[allow(clippy::empty_enum, clippy::upper_case_acronyms)]
#[allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
#[allow(clippy::empty_enum)]
pub enum SSLAntiReplayContext {}
experimental_api!(SSL_CreateAntiReplayContext(
@ -66,12 +71,9 @@ impl AntiReplay {
)
}?;
match NonNull::new(ctx) {
Some(ctx_nn) => Ok(Self {
ctx: AntiReplayContext::new(ctx_nn),
}),
None => Err(Error::InternalError),
}
Ok(Self {
ctx: AntiReplayContext::from_ptr(ctx)?,
})
}
/// Configure the provided socket with this anti-replay context.

View File

@ -13,7 +13,6 @@ use crate::ssl::{PRFileDesc, SSLSecretCallback, SSLSecretDirection};
use neqo_common::qdebug;
use std::os::raw::c_void;
use std::pin::Pin;
use std::ptr::NonNull;
experimental_api!(SSL_SecretCallback(
fd: *mut PRFileDesc,
@ -83,10 +82,7 @@ impl Secrets {
fn put_raw(&mut self, epoch: Epoch, dir: SSLSecretDirection::Type, key_ptr: *mut PK11SymKey) {
let key_ptr = unsafe { PK11_ReferenceSymKey(key_ptr) };
let key = match NonNull::new(key_ptr) {
None => panic!("NSS shouldn't be passing out NULL secrets"),
Some(p) => SymKey::new(p),
};
let key = SymKey::from_ptr(key_ptr).expect("NSS shouldn't be passing out NULL secrets");
self.put(SecretDirection::from(dir), epoch, key);
}

View File

@ -4,15 +4,20 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
#![allow(
dead_code,
non_upper_case_globals,
non_snake_case,
clippy::cognitive_complexity,
clippy::too_many_lines,
clippy::upper_case_acronyms
clippy::too_many_lines
)]
#![allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
#![allow(unknown_lints, deref_nullptr)] // Until we require rust 1.53 or bindgen#1651 is fixed.
use crate::constants::Epoch;
use crate::err::{secstatus_to_res, Res};

View File

@ -4,8 +4,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(clippy::upper_case_acronyms)]
#![allow(unknown_lints, renamed_and_removed_lints, clippy::unknown_clippy_lints)] // Until we require rust 1.51.
#![allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::upper_case_acronyms
)] // Until we require rust 1.51.
use crate::agentio::as_c_void;
use crate::err::{Error, Res};

View File

@ -2,9 +2,9 @@
#![warn(clippy::pedantic)]
use neqo_crypto::{
AuthenticationStatus, Client, HandshakeState, SecretAgentPreInfo, Server, ZeroRttCheckResult,
ZeroRttChecker, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_GRP_EC_SECP256R1,
TLS_VERSION_1_3,
generate_ech_keys, AuthenticationStatus, Client, Error, HandshakeState, SecretAgentPreInfo,
Server, ZeroRttCheckResult, ZeroRttChecker, TLS_AES_128_GCM_SHA256,
TLS_CHACHA20_POLY1305_SHA256, TLS_GRP_EC_SECP256R1, TLS_VERSION_1_3,
};
use std::boxed::Box;
@ -74,7 +74,7 @@ fn basic() {
fn check_client_preinfo(client_preinfo: &SecretAgentPreInfo) {
assert_eq!(client_preinfo.version(), None);
assert_eq!(client_preinfo.cipher_suite(), None);
assert_eq!(client_preinfo.early_data(), false);
assert!(!client_preinfo.early_data());
assert_eq!(client_preinfo.early_data_cipher(), None);
assert_eq!(client_preinfo.max_early_data(), 0);
assert_eq!(client_preinfo.alpn(), None);
@ -83,7 +83,7 @@ fn check_client_preinfo(client_preinfo: &SecretAgentPreInfo) {
fn check_server_preinfo(server_preinfo: &SecretAgentPreInfo) {
assert_eq!(server_preinfo.version(), Some(TLS_VERSION_1_3));
assert_eq!(server_preinfo.cipher_suite(), Some(TLS_AES_128_GCM_SHA256));
assert_eq!(server_preinfo.early_data(), false);
assert!(!server_preinfo.early_data());
assert_eq!(server_preinfo.early_data_cipher(), None);
assert_eq!(server_preinfo.max_early_data(), 0);
assert_eq!(server_preinfo.alpn(), None);
@ -385,3 +385,91 @@ fn close_client_twice() {
client.close();
client.close(); // Should be a noop.
}
#[test]
fn ech() {
fixture_init();
let mut server = Server::new(&["key"]).expect("should create server");
let (sk, pk) = generate_ech_keys().expect("ECH keys");
server
.enable_ech(88, "public.example", &sk, &pk)
.expect("should enable server ECH");
let mut client = Client::new("server.example").expect("should create client");
client
.enable_ech(server.ech_config())
.expect("should enable client ECH");
connect(&mut client, &mut server);
assert!(client.info().unwrap().ech_accepted());
assert!(server.info().unwrap().ech_accepted());
assert!(client.preinfo().unwrap().ech_accepted().unwrap());
assert!(server.preinfo().unwrap().ech_accepted().unwrap());
}
#[test]
fn ech_retry() {
const PUBLIC_NAME: &str = "public.example";
const PRIVATE_NAME: &str = "private.example";
const CONFIG_ID: u8 = 7;
fixture_init();
let mut server = Server::new(&["key"]).unwrap();
let (sk, pk) = generate_ech_keys().unwrap();
server.enable_ech(CONFIG_ID, PUBLIC_NAME, &sk, &pk).unwrap();
let mut client = Client::new(PRIVATE_NAME).unwrap();
let mut cfg = Vec::from(server.ech_config());
// Ensure that the version and config_id is correct.
assert_eq!(cfg[2], 0xfe);
assert_eq!(cfg[3], 0x0a);
assert_eq!(cfg[6], CONFIG_ID);
// Change the config_id so that the server doesn't recognize this.
cfg[6] ^= 0x94;
client.enable_ech(&cfg).unwrap();
// Long version of connect() so that we can check the state.
let records = client.handshake_raw(now(), None).unwrap(); // ClientHello
let records = forward_records(now(), &mut server, records).unwrap(); // ServerHello...
let records = forward_records(now(), &mut client, records).unwrap(); // (empty)
assert!(records.is_empty());
// The client should now be expecting authentication.
assert_eq!(
*client.state(),
HandshakeState::EchFallbackAuthenticationPending(String::from(PUBLIC_NAME))
);
client.authenticated(AuthenticationStatus::Ok);
let updated_config = if let Err(Error::EchRetry(c)) = client.handshake_raw(now(), None) {
c
} else {
panic!(
"Handshake should fail with EchRetry, state is instead {:?}",
client.state()
);
};
assert_eq!(
client
.preinfo()
.unwrap()
.ech_public_name()
.unwrap()
.unwrap(),
PUBLIC_NAME
);
// We don't forward alerts, so we can't tell the server about them.
// An ech_required alert should be set though.
assert_eq!(client.alert(), Some(&121));
let mut server = Server::new(&["key"]).unwrap();
server.enable_ech(CONFIG_ID, PUBLIC_NAME, &sk, &pk).unwrap();
let mut client = Client::new(PRIVATE_NAME).unwrap();
client.enable_ech(&updated_config).unwrap();
connect(&mut client, &mut server);
assert!(client.info().unwrap().ech_accepted());
assert!(server.info().unwrap().ech_accepted());
assert!(client.preinfo().unwrap().ech_accepted().unwrap());
assert!(server.preinfo().unwrap().ech_accepted().unwrap());
}

View File

@ -9,6 +9,7 @@ use std::mem;
use std::time::Instant;
use test_fixture::{anti_replay, fixture_init, now};
/// Consume records until the handshake state changes.
pub fn forward_records(
now: Instant,
agent: &mut SecretAgent,
@ -45,7 +46,12 @@ fn handshake(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
if *b.state() == HandshakeState::AuthenticationPending {
b.authenticated(AuthenticationStatus::Ok);
records = b.handshake_raw(now, None).unwrap();
records = if let Ok(r) = b.handshake_raw(now, None) {
r
} else {
// TODO(mt) - as above.
return;
}
}
mem::swap(&mut a, &mut b);
}

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"a2ed040c308920faf9344073474708a553606b89b3f85556699c24ee68814c90","src/client_events.rs":"87e7c323ec2ceb759da7d6f2bf24e774f6f2e3833f63454d09cc09f47fccfa8e","src/connection.rs":"eb1e58ca22429b2f5b441f9a99e26c68c76d7d67b11ece250b95fe81e3e39f3e","src/connection_client.rs":"0db4e303c05df1b84e9aaab920e1ebbd34ecbd6f9b5a556f94a282e2d04d1c24","src/connection_server.rs":"884016ac4a0e43e0b14146b057c4d8d906f5aac94d47e05312298f76b0818e85","src/control_stream_local.rs":"2e9483d79dc00a3e5ef51ea2b0f28fda2b67348996c47729947c70a8be3007ed","src/control_stream_remote.rs":"1dfac4956a7d6971e2cef2c83963d838e73aa3bf3286b7bde97099978c41d527","src/hframe.rs":"d6d8365a333e33d41ae37d2e774f5dffb7a9676ddfb50de56bb98a50bfe49cbb","src/lib.rs":"4c26d5652fb749f07b8dad31ef2417b8daf9f71ae18e0c18b1bc22093a06290a","src/push_controller.rs":"fcf7873e8c41560b16d443e77f897e5e8b3fccb55f35f6d9997bac935e7744c3","src/push_stream.rs":"5f3a5c6d72c0a8e4d1c1c5041125f7aff9a65950fa60c77f42cd4b35c768f585","src/qlog.rs":"29c0e3c4c9571eb7fe905967edeb1c4bc236b1e35a0e0f11a4a847f1d246681d","src/recv_message.rs":"f42a73276c02c506d6046c578fc8ccef42debf1178742379c80e2fdcc2b43afb","src/send_message.rs":"7276268e5b190a1dc019d74034dad8a3c0c4b8cb409a135c733c038f9ccff6ff","src/server.rs":"99147a014be95976af54eddf3874f67a22970dc194d239685bc83a5adef3dd8c","src/server_connection_events.rs":"762ddb87f700abe91ae1ae78ebbb87a88da0c0a341748b0751b01099870e9985","src/server_events.rs":"c8cdd838129ef6b1e77ba63608d20f6d297bca4e67f26a077d1c015a5e2a3b82","src/settings.rs":"467bd6991ec30e483195855f997f0bea3671a15b1a5b82e271f62ae424e0b2c3","src/stream_type_reader.rs":"aacb2e865f79b3ac55a887fd670f2286d8ffef94f7d8b3ecfa7e0cccbfa9ec04","tests/httpconn.rs":"1a97a80f7abe11c6ba0bd9b41003be6b293049164daa21e907365d93b00a782f"},"package":null}
{"files":{"Cargo.toml":"e80144dc6a6b2579ddbea6ebc12544d05853123c79e970b3462d5793dbb7a3f7","src/client_events.rs":"18a8df593a7bfca802b401b97815436727a6a5b605be66dd3ab345b3ff645f9a","src/connection.rs":"8942eee838c78d8fde23c02e4699d17da586a25b9ccbc3d5d4675501cb2e2ee6","src/connection_client.rs":"9c0b33bee86e8886a8d85027c1fd3dfeee59d1d5a579dc2ce39688dc027d9758","src/connection_server.rs":"259293df507af4257b00a654066902c5c5ecf41ec5ca04956905f12df784fa1f","src/control_stream_local.rs":"9be459bf678ebf5d9ffe73b5c0a47188ed4dc5b2780483c0ae7a0f9e90d9edb7","src/control_stream_remote.rs":"cc04bb2a6d622e841dbbab0611ca2e704172dcbb970192736dce96aecb97ab1f","src/hframe.rs":"8c8d9d646b3a6cd889e654a2a137f9e128432e4bbdcb151683bacb8182f7ebde","src/lib.rs":"1c7b91ea14b4427bd39f88989eb979b26635e001383bccef8ff4f9cca7e8d8b0","src/push_controller.rs":"35c5688e053157830c55b9133f6190349826143590f13a6b55768a79320313a7","src/push_stream.rs":"52024d01dbda0492e1487441a15edf17d24cf7ff4fe660707974b1e060eba1b5","src/qlog.rs":"29c0e3c4c9571eb7fe905967edeb1c4bc236b1e35a0e0f11a4a847f1d246681d","src/qpack_decoder_receiver.rs":"c8e1b84b52c097a798d6fb03c8321f676f31d68bc4000041b5dcb5797f8d66ac","src/qpack_encoder_receiver.rs":"11f6bc3e9170e952d92b583659c48764c375fa66cfa97fb115298a6021e84cfb","src/recv_message.rs":"7e1fa263132637cb933ddb6b9d4e92a211be1fb47c23ec475efdb5e8da98f530","src/send_message.rs":"7276268e5b190a1dc019d74034dad8a3c0c4b8cb409a135c733c038f9ccff6ff","src/server.rs":"79a86cf201bc083de95bfddb367998f0dd119a932138c668b0a5c020fabbcccd","src/server_connection_events.rs":"762ddb87f700abe91ae1ae78ebbb87a88da0c0a341748b0751b01099870e9985","src/server_events.rs":"c8cdd838129ef6b1e77ba63608d20f6d297bca4e67f26a077d1c015a5e2a3b82","src/settings.rs":"467bd6991ec30e483195855f997f0bea3671a15b1a5b82e271f62ae424e0b2c3","src/stream_type_reader.rs":"9d00ce19947aba78b1f6e6b5ec8e125dca80af842b431b14f93a22d3772ecad2","tests/httpconn.rs":"741faab7ca184898b83a096ea445accdba378d8dc47ee5843a0a92cfce6fbbc4"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-http3"
version = "0.4.25"
version = "0.4.26"
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"

View File

@ -64,6 +64,10 @@ pub enum Http3ClientEvent {
RequestsCreatable,
/// Cert authentication needed
AuthenticationNeeded,
/// Encrypted client hello fallback occurred. The certificate for the
/// name `public_name` needs to be authenticated in order to get
/// an updated ECH configuration.
EchFallbackAuthenticationNeeded { public_name: String },
/// A new resumption token.
ResumptionToken(ResumptionToken),
/// Zero Rtt has been rejected.
@ -165,6 +169,11 @@ impl Http3ClientEvents {
self.insert(Http3ClientEvent::AuthenticationNeeded);
}
/// Add a new `AuthenticationNeeded` event
pub(crate) fn ech_fallback_authentication_needed(&self, public_name: String) {
self.insert(Http3ClientEvent::EchFallbackAuthenticationNeeded { public_name });
}
/// Add a new resumption token event.
pub(crate) fn resumption_token(&self, token: ResumptionToken) {
self.insert(Http3ClientEvent::ResumptionToken(token));

View File

@ -6,34 +6,30 @@
#![allow(clippy::module_name_repetitions)]
use crate::control_stream_local::{ControlStreamLocal, HTTP3_UNI_STREAM_TYPE_CONTROL};
use crate::control_stream_local::ControlStreamLocal;
use crate::control_stream_remote::ControlStreamRemote;
use crate::hframe::HFrame;
use crate::qpack_decoder_receiver::DecoderRecvStream;
use crate::qpack_encoder_receiver::EncoderRecvStream;
use crate::send_message::SendMessage;
use crate::settings::{HSetting, HSettingType, HSettings, HttpZeroRttChecker};
use crate::stream_type_reader::NewStreamTypeReader;
use crate::{RecvStream, ResetType};
use crate::{Http3StreamType, ReceiveOutput, RecvStream, ResetType};
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::decoder::QPackDecoder;
use neqo_qpack::encoder::QPackEncoder;
use neqo_qpack::QpackSettings;
use neqo_transport::{AppError, Connection, ConnectionError, State, StreamType};
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::fmt::Debug;
use std::mem;
use std::rc::Rc;
use crate::{Error, Res};
const HTTP3_UNI_STREAM_TYPE_PUSH: u64 = 0x1;
const QPACK_TABLE_SIZE_LIMIT: u64 = 1 << 30;
pub(crate) enum HandleReadableOutput {
StreamNotFound,
NoOutput,
PushStream,
ControlFrames(Vec<HFrame>),
}
#[derive(Debug)]
enum Http3RemoteSettingsState {
NotReceived,
@ -66,10 +62,8 @@ pub(crate) struct Http3Connection {
pub state: Http3State,
local_qpack_settings: QpackSettings,
control_stream_local: ControlStreamLocal,
control_stream_remote: ControlStreamRemote,
new_streams: HashMap<u64, NewStreamTypeReader>,
pub qpack_encoder: QPackEncoder,
pub qpack_decoder: QPackDecoder,
pub qpack_encoder: Rc<RefCell<QPackEncoder>>,
pub qpack_decoder: Rc<RefCell<QPackDecoder>>,
settings_state: Http3RemoteSettingsState,
streams_have_data_to_send: BTreeSet<u64>,
pub send_streams: HashMap<u64, SendMessage>,
@ -94,10 +88,8 @@ impl Http3Connection {
state: Http3State::Initializing,
local_qpack_settings,
control_stream_local: ControlStreamLocal::new(),
control_stream_remote: ControlStreamRemote::new(),
new_streams: HashMap::new(),
qpack_encoder: QPackEncoder::new(local_qpack_settings, true),
qpack_decoder: QPackDecoder::new(local_qpack_settings),
qpack_encoder: Rc::new(RefCell::new(QPackEncoder::new(local_qpack_settings, true))),
qpack_decoder: Rc::new(RefCell::new(QPackDecoder::new(local_qpack_settings))),
settings_state: Http3RemoteSettingsState::NotReceived,
streams_have_data_to_send: BTreeSet::new(),
send_streams: HashMap::new(),
@ -120,11 +112,11 @@ impl Http3Connection {
settings: HSettings::new(&[
HSetting {
setting_type: HSettingType::MaxTableCapacity,
value: self.qpack_decoder.get_max_table_size(),
value: self.qpack_decoder.borrow().get_max_table_size(),
},
HSetting {
setting_type: HSettingType::BlockedStreams,
value: self.qpack_decoder.get_blocked_streams().into(),
value: self.qpack_decoder.borrow().get_blocked_streams().into(),
},
]),
});
@ -139,8 +131,10 @@ impl Http3Connection {
fn create_qpack_streams(&mut self, conn: &mut Connection) -> Res<()> {
qdebug!([self], "create_qpack_streams.");
self.qpack_encoder
.borrow_mut()
.add_send_stream(conn.stream_create(StreamType::UniDi)?);
self.qpack_decoder
.borrow_mut()
.add_send_stream(conn.stream_create(StreamType::UniDi)?);
Ok(())
}
@ -156,15 +150,21 @@ impl Http3Connection {
}
/// Call `send` for all streams that need to send data.
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::unnested_or_patterns
)] // Until we require rust 1.53 we can't use or_patterns.
pub fn process_sending(&mut self, conn: &mut Connection) -> Res<()> {
// check if control stream has data to send.
self.control_stream_local.send(conn)?;
let to_send = mem::replace(&mut self.streams_have_data_to_send, BTreeSet::new());
let to_send = mem::take(&mut self.streams_have_data_to_send);
for stream_id in to_send {
let mut remove = false;
if let Some(s) = &mut self.send_streams.get_mut(&stream_id) {
s.send(conn, &mut self.qpack_encoder)?;
s.send(conn, &mut self.qpack_encoder.borrow_mut())?;
if s.has_data_to_send() {
self.streams_have_data_to_send.insert(stream_id);
}
@ -174,8 +174,8 @@ impl Http3Connection {
self.send_streams.remove(&stream_id);
}
}
self.qpack_decoder.send(conn)?;
match self.qpack_encoder.send(conn) {
self.qpack_decoder.borrow_mut().send(conn)?;
match self.qpack_encoder.borrow_mut().send(conn) {
Ok(())
| Err(neqo_qpack::Error::EncoderStreamBlocked)
| Err(neqo_qpack::Error::DynamicTableFull) => {}
@ -202,71 +202,23 @@ impl Http3Connection {
}
}
/// This function adds a new unidi stream and try to read its type. `Http3Connection` can handle
/// a Http3 Control stream, Qpack streams and an unknown stream, but it cannot handle a Push stream.
/// If a Push stream has been discovered, return true and let the `Http3Client`/`Server` handle it.
pub fn handle_new_unidi_stream(&mut self, conn: &mut Connection, stream_id: u64) -> Res<bool> {
pub fn handle_new_unidi_stream(&mut self, stream_id: u64) {
qtrace!([self], "A new stream: {}.", stream_id);
let stream_type;
let fin;
{
let ns = self
.new_streams
.entry(stream_id)
.or_insert_with(NewStreamTypeReader::new);
stream_type = ns.get_type(conn, stream_id);
fin = ns.fin();
}
if fin {
self.new_streams.remove(&stream_id);
Ok(false)
} else {
stream_type.map_or(Ok(false), |t| {
self.new_streams.remove(&stream_id);
self.decode_new_stream(conn, t, stream_id)
})
}
self.recv_streams
.insert(stream_id, Box::new(NewStreamTypeReader::new(stream_id)));
}
fn recv_decoder(&mut self, conn: &mut Connection, stream_id: u64) -> Res<bool> {
if self.qpack_decoder.is_recv_stream(stream_id) {
qdebug!(
[self],
"The qpack decoder stream ({}) is readable.",
stream_id
);
let unblocked_streams = self.qpack_decoder.receive(conn, stream_id)?;
for stream_id in unblocked_streams {
qdebug!([self], "Stream {} is unblocked", stream_id);
self.handle_read_stream(conn, stream_id, true)?;
fn stream_receive(&mut self, conn: &mut Connection, stream_id: u64) -> Res<ReceiveOutput> {
qtrace!([self], "Readable stream {}.", stream_id);
if let Some(recv_stream) = self.recv_streams.get_mut(&stream_id) {
let output = recv_stream.receive(conn);
if recv_stream.done() {
self.recv_streams.remove(&stream_id);
}
Ok(true)
output
} else {
Ok(false)
}
}
#[allow(
clippy::vec_init_then_push,
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints
)]
fn recv_control(&mut self, conn: &mut Connection, stream_id: u64) -> Res<HandleReadableOutput> {
assert!(self.control_stream_remote.is_recv_stream(stream_id));
let mut control_frames = Vec::new();
loop {
if let Some(f) = self.control_stream_remote.receive(conn)? {
if let Some(f) = self.handle_control_frame(f)? {
control_frames.push(f);
}
} else if control_frames.is_empty() {
return Ok(HandleReadableOutput::NoOutput);
} else {
return Ok(HandleReadableOutput::ControlFrames(control_frames));
}
Ok(ReceiveOutput::NoOutput)
}
}
@ -275,81 +227,53 @@ impl Http3Connection {
/// The function cannot handle:
/// 1) a Push stream (if an unknown unidi stream is decoded to be a push stream)
/// 2) frames `MaxPushId` or `Goaway` must be handled by `Http3Client`/`Server`.
/// The function returns `HandleReadableOutput`.
/// The function returns `ReceiveOutput`.
pub fn handle_stream_readable(
&mut self,
conn: &mut Connection,
stream_id: u64,
) -> Res<HandleReadableOutput> {
qtrace!([self], "Readable stream {}.", stream_id);
) -> Res<ReceiveOutput> {
let mut output = self.stream_receive(conn, stream_id)?;
let label = ::neqo_common::log_subject!(::log::Level::Debug, self);
if self.handle_read_stream(conn, stream_id, false)? {
qdebug!([label], "Request/response stream {} read.", stream_id);
Ok(HandleReadableOutput::NoOutput)
} else if self.control_stream_remote.is_recv_stream(stream_id) {
qdebug!(
[self],
"The remote control stream ({}) is readable.",
stream_id
);
self.recv_control(conn, stream_id)
} else if self.qpack_encoder.recv_if_encoder_stream(conn, stream_id)? {
qdebug!(
[self],
"The qpack encoder stream ({}) is readable.",
stream_id
);
Ok(HandleReadableOutput::NoOutput)
} else if self.recv_decoder(conn, stream_id)? {
Ok(HandleReadableOutput::NoOutput)
} else if let Some(ns) = self.new_streams.get_mut(&stream_id) {
let stream_type = ns.get_type(conn, stream_id);
let fin = ns.fin();
if fin {
self.new_streams.remove(&stream_id);
}
if let Some(t) = stream_type {
self.new_streams.remove(&stream_id);
self.decode_new_stream(conn, t, stream_id)?;
// Make sure to read from this stream because DataReadable will not be set again.
return match t {
HTTP3_UNI_STREAM_TYPE_CONTROL => self.recv_control(conn, stream_id),
QPACK_UNI_STREAM_TYPE_DECODER => {
self.qpack_encoder.recv_if_encoder_stream(conn, stream_id)?;
Ok(HandleReadableOutput::NoOutput)
}
QPACK_UNI_STREAM_TYPE_ENCODER => {
self.recv_decoder(conn, stream_id)?;
Ok(HandleReadableOutput::NoOutput)
}
HTTP3_UNI_STREAM_TYPE_PUSH => Ok(HandleReadableOutput::PushStream),
_ => Ok(HandleReadableOutput::NoOutput),
};
}
if let ReceiveOutput::NewStream(stream_type) = output {
output = self.handle_new_stream(conn, stream_type, stream_id)?;
}
Ok(HandleReadableOutput::NoOutput)
} else {
// For a new stream we receive NewStream event and a
// RecvStreamReadable event.
// In most cases we decode a new stream already on the NewStream
// event and remove it from self.new_streams.
// Therefore, while processing RecvStreamReadable there will be no
// entry for the stream in self.new_streams.
// This can be a push stream as well.
Ok(HandleReadableOutput::StreamNotFound)
match output {
ReceiveOutput::UnblockedStreams(unblocked_streams) => {
for stream_id in unblocked_streams {
qdebug!([self], "Stream {} is unblocked", stream_id);
if let Some(r) = self.recv_streams.get_mut(&stream_id) {
r.http_stream()
.ok_or(Error::HttpInternal(10))?
.header_unblocked(conn)?;
}
}
Ok(ReceiveOutput::NoOutput)
}
ReceiveOutput::ControlFrames(mut control_frames) => {
let mut rest = Vec::new();
for cf in control_frames.drain(..) {
if let Some(not_handled) = self.handle_control_frame(cf)? {
rest.push(not_handled);
}
}
Ok(ReceiveOutput::ControlFrames(rest))
}
ReceiveOutput::NewStream(_) => {
unreachable!("NewStream should have been handled already")
}
_ => Ok(output),
}
}
fn is_critical_stream(&self, stream_id: u64) -> bool {
self.qpack_encoder
.borrow()
.local_stream_id()
.iter()
.chain(self.qpack_encoder.remote_stream_id().iter())
.chain(self.qpack_decoder.local_stream_id().iter())
.chain(self.qpack_decoder.remote_stream_id().iter())
.chain(self.qpack_decoder.borrow().local_stream_id().iter())
.chain(self.control_stream_local.stream_id().iter())
.chain(self.control_stream_remote.stream_id().iter())
.any(|id| stream_id == *id)
}
@ -362,14 +286,9 @@ impl Http3Connection {
app_error
);
if let Some(s) = self.recv_streams.remove(&stream_id) {
s.stream_reset(app_error, &mut self.qpack_decoder, ResetType::Remote);
Ok(())
} else if self.is_critical_stream(stream_id) {
Err(Error::HttpClosedCriticalStream)
} else {
Ok(())
}
self.recv_streams
.remove(&stream_id)
.map_or(Ok(()), |mut s| s.stream_reset(app_error, ResetType::Remote))
}
pub fn handle_stream_stop_sending(&mut self, stream_id: u64, app_error: AppError) -> Res<()> {
@ -431,10 +350,12 @@ impl Http3Connection {
if self.state == Http3State::ZeroRtt {
self.state = Http3State::Initializing;
self.control_stream_local = ControlStreamLocal::new();
self.control_stream_remote = ControlStreamRemote::new();
self.new_streams.clear();
self.qpack_encoder = QPackEncoder::new(self.local_qpack_settings, true);
self.qpack_decoder = QPackDecoder::new(self.local_qpack_settings);
self.qpack_encoder = Rc::new(RefCell::new(QPackEncoder::new(
self.local_qpack_settings,
true,
)));
self.qpack_decoder =
Rc::new(RefCell::new(QPackDecoder::new(self.local_qpack_settings)));
self.settings_state = Http3RemoteSettingsState::NotReceived;
self.streams_have_data_to_send.clear();
// TODO: investigate whether this code can automatically retry failed transactions.
@ -447,74 +368,68 @@ impl Http3Connection {
}
}
fn handle_read_stream(
&mut self,
conn: &mut Connection,
stream_id: u64,
header_unblocked: bool,
) -> Res<bool> {
let label = ::neqo_common::log_subject!(::log::Level::Info, self);
let r = self.recv_streams.get_mut(&stream_id);
if r.is_none() {
return Ok(false);
}
let recv_stream = r.unwrap();
qinfo!(
[label],
"Request/response stream {} is readable.",
stream_id
);
if header_unblocked {
recv_stream.header_unblocked(conn, &mut self.qpack_decoder)?;
fn check_stream_exists(&self, stream_type: Http3StreamType) -> Res<()> {
if self
.recv_streams
.values()
.any(|c| c.stream_type() == stream_type)
{
Err(Error::HttpStreamCreation)
} else {
recv_stream.receive(conn, &mut self.qpack_decoder)?;
Ok(())
}
if recv_stream.done() {
self.recv_streams.remove(&stream_id);
}
Ok(true)
}
/// Returns true if it is a push stream.
fn decode_new_stream(
/// If the new stream is a control stream, this function creates a proper handler
/// and perform a read.
/// if the new stream is a push stream, the function returns `ReceiveOutput::PushStream`
/// and the caller will handle it.
/// If the sttream is of a unknown type the stream will be closed.
fn handle_new_stream(
&mut self,
conn: &mut Connection,
stream_type: u64,
stream_type: Http3StreamType,
stream_id: u64,
) -> Res<bool> {
match stream_type {
HTTP3_UNI_STREAM_TYPE_CONTROL => {
self.control_stream_remote.add_remote_stream(stream_id)?;
Ok(false)
) -> Res<ReceiveOutput> {
let recv_stream: Option<Box<dyn RecvStream>> = match stream_type {
Http3StreamType::Control => {
self.check_stream_exists(Http3StreamType::Control)?;
Some(Box::new(ControlStreamRemote::new(stream_id)))
}
HTTP3_UNI_STREAM_TYPE_PUSH => {
Http3StreamType::Push => {
qinfo!([self], "A new push stream {}.", stream_id);
Ok(true)
None
}
QPACK_UNI_STREAM_TYPE_ENCODER => {
Http3StreamType::Decoder => {
qinfo!([self], "A new remote qpack encoder stream {}", stream_id);
Error::map_error(
self.qpack_decoder.add_recv_stream(stream_id),
Error::HttpStreamCreation,
)?;
Ok(false)
self.check_stream_exists(Http3StreamType::Decoder)?;
Some(Box::new(DecoderRecvStream::new(
stream_id,
Rc::clone(&self.qpack_decoder),
)))
}
QPACK_UNI_STREAM_TYPE_DECODER => {
Http3StreamType::Encoder => {
qinfo!([self], "A new remote qpack decoder stream {}", stream_id);
Error::map_error(
self.qpack_encoder.add_recv_stream(stream_id),
Error::HttpStreamCreation,
)?;
Ok(false)
self.check_stream_exists(Http3StreamType::Encoder)?;
Some(Box::new(EncoderRecvStream::new(
stream_id,
Rc::clone(&self.qpack_encoder),
)))
}
_ => {
conn.stream_stop_sending(stream_id, Error::HttpStreamCreation.code())?;
Ok(false)
None
}
};
match (recv_stream, stream_type) {
(Some(rs), _) => {
self.recv_streams.insert(stream_id, rs);
self.stream_receive(conn, stream_id)
}
(None, Http3StreamType::Push) => Ok(ReceiveOutput::PushStream),
(None, _) => Ok(ReceiveOutput::NoOutput),
}
}
@ -539,21 +454,30 @@ impl Http3Connection {
) -> Res<()> {
qinfo!([self], "Reset stream {} error={}.", stream_id, error);
let mut found = self.send_streams.remove(&stream_id).is_some();
if let Some(s) = self.recv_streams.remove(&stream_id) {
s.stream_reset(error, &mut self.qpack_decoder, ResetType::App);
found = true;
// Make sure that an application cannot reset a control stream.
// self.send_streams holds only http streams therefore if the stream
// is present in self.send_streams we don not need to check recv_streams.
if self.send_streams.get(&stream_id).is_none()
&& !matches!(
self.recv_streams
.get(&stream_id)
.ok_or(Error::InvalidStreamId)?
.stream_type(),
Http3StreamType::Http | Http3StreamType::Push
)
{
return Err(Error::InvalidStreamId);
}
self.send_streams.remove(&stream_id);
if let Some(mut s) = self.recv_streams.remove(&stream_id) {
s.stream_reset(error, ResetType::App)?;
}
// Stream maybe already be closed and we may get an error here, but we do not care.
let _ = conn.stream_reset_send(stream_id, error);
// Stream maybe already be closed and we may get an error here, but we do not care.
let _ = conn.stream_stop_sending(stream_id, error);
if found {
Ok(())
} else {
Err(Error::InvalidStreamId)
}
// Stream may be already be closed and we may get an error here, but we do not care.
mem::drop(conn.stream_reset_send(stream_id, error));
// Stream may be already be closed and we may get an error here, but we do not care.
mem::drop(conn.stream_stop_sending(stream_id, error));
Ok(())
}
/// This is called when an application wants to close the sending side of a stream.
@ -564,9 +488,9 @@ impl Http3Connection {
.send_streams
.get_mut(&stream_id)
.ok_or(Error::InvalidStreamId)?;
// The following funcion may return InvalidStreamId from the transport layer if the stream has been cloesd
// The following function may return InvalidStreamId from the transport layer if the stream has been cloesd
// already. It is ok to ignore it here.
let _ = send_stream.close(conn);
mem::drop(send_stream.close(conn));
if send_stream.done() {
self.send_streams.remove(&stream_id);
}
@ -601,10 +525,13 @@ impl Http3Connection {
for s in settings {
qinfo!([self], " {:?} = {:?}", s.setting_type, s.value);
match s.setting_type {
HSettingType::MaxTableCapacity => self.qpack_encoder.set_max_capacity(s.value)?,
HSettingType::BlockedStreams => {
self.qpack_encoder.set_max_blocked_streams(s.value)?
HSettingType::MaxTableCapacity => {
self.qpack_encoder.borrow_mut().set_max_capacity(s.value)?
}
HSettingType::BlockedStreams => self
.qpack_encoder
.borrow_mut()
.set_max_blocked_streams(s.value)?,
HSettingType::MaxHeaderListSize => (),
}
}
@ -650,7 +577,7 @@ impl Http3Connection {
qpack_changed = true;
}
HSettingType::BlockedStreams => qpack_changed = true,
_ => (),
HSettingType::MaxHeaderListSize => (),
}
}
if qpack_changed {

File diff suppressed because it is too large Load Diff

View File

@ -4,15 +4,16 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::connection::{HandleReadableOutput, Http3Connection, Http3State};
use crate::connection::{Http3Connection, Http3State};
use crate::hframe::HFrame;
use crate::recv_message::{MessageType, RecvMessage};
use crate::send_message::SendMessage;
use crate::server_connection_events::{Http3ServerConnEvent, Http3ServerConnEvents};
use crate::{Error, Header, Res};
use crate::{Error, Header, ReceiveOutput, Res};
use neqo_common::{event::Provider, qdebug, qinfo, qtrace};
use neqo_qpack::QpackSettings;
use neqo_transport::{AppError, Connection, ConnectionEvent, StreamType};
use std::rc::Rc;
use std::time::Instant;
#[derive(Debug)]
@ -128,18 +129,14 @@ impl Http3ServerHandler {
Box::new(RecvMessage::new(
MessageType::Request,
stream_id.as_u64(),
Rc::clone(&self.base_handler.qpack_decoder),
Box::new(self.events.clone()),
None,
)),
),
StreamType::UniDi => {
if self
.base_handler
.handle_new_unidi_stream(conn, stream_id.as_u64())?
{
return Err(Error::HttpStreamCreation);
}
}
StreamType::UniDi => self
.base_handler
.handle_new_unidi_stream(stream_id.as_u64()),
},
ConnectionEvent::RecvStreamReadable { stream_id } => {
self.handle_stream_readable(conn, stream_id)?
@ -168,6 +165,7 @@ impl Http3ServerHandler {
}
}
ConnectionEvent::AuthenticationNeeded
| ConnectionEvent::EchFallbackAuthenticationNeeded { .. }
| ConnectionEvent::ZeroRttRejected
| ConnectionEvent::ResumptionToken(..) => return Err(Error::HttpInternal(4)),
ConnectionEvent::SendStreamWritable { .. }
@ -180,8 +178,8 @@ impl Http3ServerHandler {
fn handle_stream_readable(&mut self, conn: &mut Connection, stream_id: u64) -> Res<()> {
match self.base_handler.handle_stream_readable(conn, stream_id)? {
HandleReadableOutput::PushStream => Err(Error::HttpStreamCreation),
HandleReadableOutput::ControlFrames(control_frames) => {
ReceiveOutput::PushStream => Err(Error::HttpStreamCreation),
ReceiveOutput::ControlFrames(control_frames) => {
for f in control_frames {
match f {
HFrame::MaxPushId { .. } => {
@ -215,25 +213,20 @@ impl Http3ServerHandler {
buf: &mut [u8],
) -> Res<(usize, bool)> {
qinfo!([self], "read_data from stream {}.", stream_id);
match self.base_handler.recv_streams.get_mut(&stream_id) {
None => {
self.close(conn, now, &Error::Internal);
Err(Error::Internal)
}
Some(recv_stream) => {
match recv_stream.read_data(conn, &mut self.base_handler.qpack_decoder, buf) {
Ok((amount, fin)) => {
if recv_stream.done() {
self.base_handler.recv_streams.remove(&stream_id);
}
Ok((amount, fin))
}
Err(e) => {
self.close(conn, now, &e);
Err(e)
}
}
}
let recv_stream = self
.base_handler
.recv_streams
.get_mut(&stream_id)
.ok_or(Error::InvalidStreamId)?
.http_stream()
.ok_or(Error::InvalidStreamId)?;
let res = recv_stream.read_data(conn, buf);
if let Err(e) = &res {
self.close(conn, now, e);
} else if recv_stream.done() {
self.base_handler.recv_streams.remove(&stream_id);
}
res
}
}

View File

@ -5,13 +5,11 @@
// except according to those terms.
use crate::hframe::HFrame;
use crate::Res;
use crate::{Res, HTTP3_UNI_STREAM_TYPE_CONTROL};
use neqo_common::{qtrace, Encoder};
use neqo_transport::{Connection, StreamType};
use std::convert::TryFrom;
pub const HTTP3_UNI_STREAM_TYPE_CONTROL: u64 = 0x0;
// The local control stream, responsible for encoding frames and sending them
#[derive(Debug)]
pub(crate) struct ControlStreamLocal {

View File

@ -5,16 +5,17 @@
// except according to those terms.
use crate::hframe::{HFrame, HFrameReader};
use crate::{Error, Res};
use neqo_common::{qdebug, qinfo};
use crate::{
AppError, Error, Http3StreamType, HttpRecvStream, ReceiveOutput, RecvStream, Res, ResetType,
};
use neqo_common::qdebug;
use neqo_transport::Connection;
/// The remote control stream is responsible only for reading frames. The frames are handled by `Http3Connection`.
#[derive(Debug)]
pub(crate) struct ControlStreamRemote {
stream_id: Option<u64>,
stream_id: u64,
frame_reader: HFrameReader,
fin: bool,
}
impl ::std::fmt::Display for ControlStreamRemote {
@ -24,45 +25,49 @@ impl ::std::fmt::Display for ControlStreamRemote {
}
impl ControlStreamRemote {
pub fn new() -> Self {
pub fn new(stream_id: u64) -> Self {
Self {
stream_id: None,
stream_id,
frame_reader: HFrameReader::new(),
fin: false,
}
}
/// A remote control stream has been received. Inform `ControlStreamRemote`.
pub fn add_remote_stream(&mut self, stream_id: u64) -> Res<()> {
qinfo!([self], "A new control stream {}.", stream_id);
if self.stream_id.is_some() {
qdebug!([self], "A control stream already exists");
return Err(Error::HttpStreamCreation);
}
self.stream_id = Some(stream_id);
Ok(())
}
/// Check if `stream_id` is the remote control stream.
pub fn is_recv_stream(&self, stream_id: u64) -> bool {
matches!(self.stream_id, Some(id) if id == stream_id)
}
/// Check if a stream is the control stream and read received data.
pub fn receive(&mut self, conn: &mut Connection) -> Res<Option<HFrame>> {
assert!(self.stream_id.is_some());
pub fn receive_single(&mut self, conn: &mut Connection) -> Res<Option<HFrame>> {
qdebug!([self], "Receiving data.");
match self.frame_reader.receive(conn, self.stream_id.unwrap())? {
(_, true) => {
self.fin = true;
Err(Error::HttpClosedCriticalStream)
}
match self.frame_reader.receive(conn, self.stream_id)? {
(_, true) => Err(Error::HttpClosedCriticalStream),
(s, false) => Ok(s),
}
}
}
#[must_use]
pub fn stream_id(&self) -> Option<u64> {
self.stream_id
impl RecvStream for ControlStreamRemote {
fn stream_reset(&mut self, _error: AppError, _reset_type: ResetType) -> Res<()> {
Err(Error::HttpClosedCriticalStream)
}
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput> {
let mut control_frames = Vec::new();
loop {
if let Some(f) = self.receive_single(conn)? {
control_frames.push(f);
} else {
return Ok(ReceiveOutput::ControlFrames(control_frames));
}
}
}
fn done(&self) -> bool {
false
}
fn stream_type(&self) -> Http3StreamType {
Http3StreamType::Control
}
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> {
None
}
}

View File

@ -311,7 +311,7 @@ impl HFrameReader {
/// # Errors
/// May return `HttpFrame` if a frame cannot be decoded.
fn get_frame(&mut self) -> Res<HFrame> {
let payload = mem::replace(&mut self.payload, Vec::new());
let payload = mem::take(&mut self.payload);
let mut dec = Decoder::from(&payload[..]);
let f = match self.hframe_type {
H3_FRAME_TYPE_DATA => HFrame::Data {
@ -357,6 +357,7 @@ mod tests {
use crate::settings::{HSetting, HSettingType};
use neqo_crypto::AuthenticationStatus;
use neqo_transport::{Connection, StreamType};
use std::mem;
use test_fixture::{connect, default_client, default_server, fixture_init, now};
#[allow(clippy::many_single_char_names)]
@ -374,10 +375,10 @@ mod tests {
let out = conn_c.process(None, now());
let out = conn_s.process(out.dgram(), now());
let out = conn_c.process(out.dgram(), now());
let _ = conn_s.process(out.dgram(), now());
mem::drop(conn_s.process(out.dgram(), now()));
conn_c.authenticated(AuthenticationStatus::Ok, now());
let out = conn_c.process(None, now());
let _ = conn_s.process(out.dgram(), now());
mem::drop(conn_s.process(out.dgram(), now()));
// create a stream
let stream_id = conn_s.stream_create(StreamType::BiDi).unwrap();
@ -388,10 +389,10 @@ mod tests {
let buf = Encoder::from_hex(st);
conn_s.stream_send(stream_id, &buf[..]).unwrap();
let out = conn_s.process(None, now());
let _ = conn_c.process(out.dgram(), now());
mem::drop(conn_c.process(out.dgram(), now()));
let (frame, fin) = fr.receive(&mut conn_c, stream_id).unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert!(frame.is_some());
assert_eq!(*f, frame.unwrap());
@ -491,9 +492,9 @@ mod tests {
fn process(&mut self, v: &[u8]) -> Option<HFrame> {
self.conn_s.stream_send(self.stream_id, v).unwrap();
let out = self.conn_s.process(None, now());
let _ = self.conn_c.process(out.dgram(), now());
mem::drop(self.conn_c.process(out.dgram(), now()));
let (frame, fin) = self.fr.receive(&mut self.conn_c, self.stream_id).unwrap();
assert_eq!(fin, false);
assert!(!fin);
frame
}
}
@ -632,12 +633,12 @@ mod tests {
}
let out = fr.conn_s.process(None, now());
let _ = fr.conn_c.process(out.dgram(), now());
mem::drop(fr.conn_c.process(out.dgram(), now()));
if let FrameReadingTestSend::DataThenFin = test_to_send {
fr.conn_s.stream_close_send(fr.stream_id).unwrap();
let out = fr.conn_s.process(None, now());
let _ = fr.conn_c.process(out.dgram(), now());
mem::drop(fr.conn_c.process(out.dgram(), now()));
}
let rv = fr.fr.receive(&mut fr.conn_c, fr.stream_id);
@ -649,17 +650,17 @@ mod tests {
}
FrameReadingTestExpect::FrameComplete => {
let (f, fin) = rv.unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert!(f.is_some());
}
FrameReadingTestExpect::FrameAndStreamComplete => {
let (f, fin) = rv.unwrap();
assert_eq!(fin, true);
assert!(fin);
assert!(f.is_some());
}
FrameReadingTestExpect::StreamDoneWithoutFrame => {
let (f, fin) = rv.unwrap();
assert_eq!(fin, true);
assert!(fin);
assert!(f.is_none());
}
};
@ -859,11 +860,11 @@ mod tests {
fr.conn_s.stream_send(fr.stream_id, &[0x00]).unwrap();
let out = fr.conn_s.process(None, now());
let _ = fr.conn_c.process(out.dgram(), now());
mem::drop(fr.conn_c.process(out.dgram(), now()));
assert_eq!(Ok(()), fr.conn_c.stream_close_send(fr.stream_id));
let out = fr.conn_c.process(None, now());
let _ = fr.conn_s.process(out.dgram(), now());
mem::drop(fr.conn_s.process(out.dgram(), now()));
assert_eq!(
Ok((None, true)),
fr.fr.receive(&mut fr.conn_s, fr.stream_id)

View File

@ -18,6 +18,8 @@ pub mod hframe;
mod push_controller;
mod push_stream;
mod qlog;
mod qpack_decoder_receiver;
mod qpack_encoder_receiver;
mod recv_message;
mod send_message;
pub mod server;
@ -26,7 +28,8 @@ mod server_events;
mod settings;
mod stream_type_reader;
use neqo_qpack::decoder::QPackDecoder;
use neqo_qpack::decoder::QPACK_UNI_STREAM_TYPE_DECODER;
use neqo_qpack::encoder::QPACK_UNI_STREAM_TYPE_ENCODER;
use neqo_qpack::Error as QpackError;
pub use neqo_transport::Output;
use neqo_transport::{AppError, Connection, Error as TransportError};
@ -36,7 +39,7 @@ pub use client_events::Http3ClientEvent;
pub use connection::Http3State;
pub use connection_client::Http3Client;
pub use connection_client::Http3Parameters;
pub use hframe::HFrameReader;
pub use hframe::{HFrame, HFrameReader};
pub use neqo_qpack::Header;
pub use server::Http3Server;
pub use server_events::{ClientRequestStream, Http3ServerEvent};
@ -117,6 +120,12 @@ impl Error {
}
#[must_use]
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::unnested_or_patterns
)] // Until we require rust 1.53 we can't use or_patterns.
pub fn connection_error(&self) -> bool {
matches!(
self,
@ -267,23 +276,60 @@ impl ::std::fmt::Display for Error {
}
}
pub const HTTP3_UNI_STREAM_TYPE_CONTROL: u64 = 0x0;
pub const HTTP3_UNI_STREAM_TYPE_PUSH: u64 = 0x1;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Http3StreamType {
Control,
Decoder,
Encoder,
NewStream,
Http,
Push,
Unknown,
}
impl From<u64> for Http3StreamType {
fn from(stream_type: u64) -> Self {
match stream_type {
HTTP3_UNI_STREAM_TYPE_CONTROL => Self::Control,
HTTP3_UNI_STREAM_TYPE_PUSH => Self::Push,
QPACK_UNI_STREAM_TYPE_ENCODER => Self::Decoder,
QPACK_UNI_STREAM_TYPE_DECODER => Self::Encoder,
_ => Self::Unknown,
}
}
}
#[derive(PartialEq, Debug)]
pub enum ReceiveOutput {
NoOutput,
PushStream,
ControlFrames(Vec<HFrame>),
UnblockedStreams(Vec<u64>),
NewStream(Http3StreamType),
}
pub trait RecvStream: Debug {
fn stream_reset(&self, error: AppError, decoder: &mut QPackDecoder, reset_type: ResetType);
/// # Errors
/// An error may happen while reading a stream, e.g. early close, etc.
fn stream_reset(&mut self, error: AppError, reset_type: ResetType) -> Res<()>;
/// # Errors
/// An error may happen while reading a stream, e.g. early close, protocol error, etc.
fn receive(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()>;
/// # Errors
/// An error may happen while reading a stream, e.g. early close, protocol error, etc.
fn header_unblocked(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()>;
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput>;
fn done(&self) -> bool;
fn stream_type(&self) -> Http3StreamType;
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream>;
}
pub trait HttpRecvStream: RecvStream {
/// # Errors
/// An error may happen while reading a stream, e.g. early close, protocol error, etc.
fn read_data(
&mut self,
conn: &mut Connection,
decoder: &mut QPackDecoder,
buf: &mut [u8],
) -> Res<(usize, bool)>;
fn header_unblocked(&mut self, conn: &mut Connection) -> Res<()>;
/// # Errors
/// An error may happen while reading a stream, e.g. early close, protocol error, etc.
fn read_data(&mut self, conn: &mut Connection, buf: &mut [u8]) -> Res<(usize, bool)>;
}
pub(crate) trait RecvMessageEvents: Debug {

View File

@ -254,7 +254,7 @@ impl PushController {
Ok(true)
}
PushState::PushPromise { headers } => {
let tmp = mem::replace(headers, Vec::new());
let tmp = mem::take(headers);
*push_state = PushState::Active {
stream_id,
headers: tmp,
@ -305,11 +305,11 @@ impl PushController {
}
PushState::OnlyPushStream { stream_id, .. }
| PushState::Active { stream_id, .. } => {
let _ = base_handler.stream_reset(
mem::drop(base_handler.stream_reset(
conn,
stream_id,
Error::HttpRequestCancelled.code(),
);
));
self.conn_events.remove_events_for_push_id(push_id);
self.conn_events.push_canceled(push_id);
Ok(())
@ -359,8 +359,11 @@ impl PushController {
Some(PushState::Active { stream_id, .. }) => {
self.conn_events.remove_events_for_push_id(push_id);
// Cancel the stream. the transport steam may already be done, so ignore an error.
let _ =
base_handler.stream_reset(conn, *stream_id, Error::HttpRequestCancelled.code());
mem::drop(base_handler.stream_reset(
conn,
*stream_id,
Error::HttpRequestCancelled.code(),
));
self.push_streams.close(push_id);
Ok(())
}

View File

@ -7,17 +7,18 @@
use crate::client_events::Http3ClientEvents;
use crate::push_controller::{PushController, RecvPushEvents};
use crate::recv_message::{MessageType, RecvMessage};
use crate::stream_type_reader::NewStreamTypeReader;
use crate::{Error, RecvStream, Res, ResetType};
use crate::{Error, Http3StreamType, HttpRecvStream, ReceiveOutput, RecvStream, Res, ResetType};
use neqo_common::{Decoder, IncrementalDecoderUint};
use neqo_qpack::decoder::QPackDecoder;
use neqo_transport::{AppError, Connection};
use std::cell::RefCell;
use std::fmt::Display;
use std::mem;
use std::rc::Rc;
#[derive(Debug)]
enum PushStreamState {
ReadPushId(NewStreamTypeReader),
ReadPushId(IncrementalDecoderUint),
ReadResponse { push_id: u64, response: RecvMessage },
Closed,
}
@ -60,6 +61,7 @@ pub(crate) struct PushStream {
state: PushStreamState,
stream_id: u64,
push_handler: Rc<RefCell<PushController>>,
qpack_decoder: Rc<RefCell<QPackDecoder>>,
events: Http3ClientEvents,
}
@ -67,15 +69,40 @@ impl PushStream {
pub fn new(
stream_id: u64,
push_handler: Rc<RefCell<PushController>>,
qpack_decoder: Rc<RefCell<QPackDecoder>>,
events: Http3ClientEvents,
) -> Self {
Self {
state: PushStreamState::ReadPushId(NewStreamTypeReader::new()),
state: PushStreamState::ReadPushId(IncrementalDecoderUint::default()),
stream_id,
push_handler,
qpack_decoder,
events,
}
}
fn push_id_decoded(&mut self, push_id: u64, conn: &mut Connection) -> Res<()> {
if self
.push_handler
.borrow_mut()
.add_new_push_stream(push_id, self.stream_id)?
{
self.state = PushStreamState::ReadResponse {
push_id,
response: RecvMessage::new(
MessageType::Response,
self.stream_id,
Rc::clone(&self.qpack_decoder),
Box::new(RecvPushEvents::new(push_id, Rc::clone(&self.push_handler))),
None,
),
};
} else {
mem::drop(conn.stream_stop_sending(self.stream_id, Error::HttpRequestCancelled.code()));
self.state = PushStreamState::Closed;
}
Ok(())
}
}
impl Display for PushStream {
@ -85,64 +112,50 @@ impl Display for PushStream {
}
impl RecvStream for PushStream {
fn receive(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()> {
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput> {
loop {
match &mut self.state {
PushStreamState::ReadPushId(id_reader) => {
let push_id = id_reader.get_type(conn, self.stream_id);
let fin = id_reader.fin();
if fin {
self.state = PushStreamState::Closed;
return Ok(());
}
if let Some(p) = push_id {
if self
.push_handler
.borrow_mut()
.add_new_push_stream(p, self.stream_id)?
{
self.state = PushStreamState::ReadResponse {
push_id: p,
response: RecvMessage::new(
MessageType::Response,
self.stream_id,
Box::new(RecvPushEvents::new(p, self.push_handler.clone())),
None,
),
};
} else {
let _ = conn.stream_stop_sending(
self.stream_id,
Error::HttpRequestCancelled.code(),
);
let to_read = id_reader.min_remaining();
let mut buf = vec![0; to_read];
match conn.stream_recv(self.stream_id, &mut buf[..])? {
(_, true) => {
self.state = PushStreamState::Closed;
return Ok(());
return Err(Error::HttpGeneralProtocol);
}
(0, false) => return Ok(ReceiveOutput::NoOutput),
(amount, false) => {
if let Some(p) = id_reader.consume(&mut Decoder::from(&buf[..amount])) {
self.push_id_decoded(p, conn)?;
if self.done() {
return Ok(ReceiveOutput::NoOutput);
}
}
}
}
}
PushStreamState::ReadResponse { response, push_id } => {
response.receive(conn, decoder)?;
response.receive(conn)?;
if response.done() {
self.push_handler.borrow_mut().close(*push_id);
self.state = PushStreamState::Closed;
}
return Ok(());
return Ok(ReceiveOutput::NoOutput);
}
_ => return Ok(()),
PushStreamState::Closed => return Ok(ReceiveOutput::NoOutput),
}
}
}
fn header_unblocked(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()> {
self.receive(conn, decoder)
}
fn done(&self) -> bool {
matches!(self.state, PushStreamState::Closed)
}
fn stream_reset(&self, app_error: AppError, decoder: &mut QPackDecoder, reset_type: ResetType) {
fn stream_reset(&mut self, app_error: AppError, reset_type: ResetType) -> Res<()> {
if !self.done() {
decoder.cancel_stream(self.stream_id);
self.qpack_decoder
.borrow_mut()
.cancel_stream(self.stream_id);
}
match reset_type {
ResetType::App => {}
@ -154,16 +167,28 @@ impl RecvStream for PushStream {
}
}
}
self.state = PushStreamState::Closed;
Ok(())
}
fn read_data(
&mut self,
conn: &mut Connection,
decoder: &mut QPackDecoder,
buf: &mut [u8],
) -> Res<(usize, bool)> {
fn stream_type(&self) -> Http3StreamType {
Http3StreamType::Push
}
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> {
Some(self)
}
}
impl HttpRecvStream for PushStream {
fn header_unblocked(&mut self, conn: &mut Connection) -> Res<()> {
self.receive(conn)?;
Ok(())
}
fn read_data(&mut self, conn: &mut Connection, buf: &mut [u8]) -> Res<(usize, bool)> {
if let PushStreamState::ReadResponse { response, push_id } = &mut self.state {
let res = response.read_data(conn, decoder, buf);
let res = response.read_data(conn, buf);
if response.done() {
self.push_handler.borrow_mut().close(*push_id);
}

View File

@ -0,0 +1,49 @@
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{
AppError, Error, Http3StreamType, HttpRecvStream, ReceiveOutput, RecvStream, Res, ResetType,
};
use neqo_qpack::QPackDecoder;
use neqo_transport::Connection;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
pub struct DecoderRecvStream {
stream_id: u64,
decoder: Rc<RefCell<QPackDecoder>>,
}
impl DecoderRecvStream {
pub fn new(stream_id: u64, decoder: Rc<RefCell<QPackDecoder>>) -> Self {
Self { stream_id, decoder }
}
}
impl RecvStream for DecoderRecvStream {
fn stream_reset(&mut self, _error: AppError, _reset_type: ResetType) -> Res<()> {
Err(Error::HttpClosedCriticalStream)
}
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput> {
Ok(ReceiveOutput::UnblockedStreams(
self.decoder.borrow_mut().receive(conn, self.stream_id)?,
))
}
fn done(&self) -> bool {
false
}
fn stream_type(&self) -> Http3StreamType {
Http3StreamType::Decoder
}
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> {
None
}
}

View File

@ -0,0 +1,48 @@
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{
AppError, Error, Http3StreamType, HttpRecvStream, ReceiveOutput, RecvStream, Res, ResetType,
};
use neqo_qpack::QPackEncoder;
use neqo_transport::Connection;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
pub struct EncoderRecvStream {
stream_id: u64,
encoder: Rc<RefCell<QPackEncoder>>,
}
impl EncoderRecvStream {
pub fn new(stream_id: u64, encoder: Rc<RefCell<QPackEncoder>>) -> Self {
Self { stream_id, encoder }
}
}
impl RecvStream for EncoderRecvStream {
fn stream_reset(&mut self, _error: AppError, _reset_type: ResetType) -> Res<()> {
Err(Error::HttpClosedCriticalStream)
}
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput> {
self.encoder.borrow_mut().receive(conn, self.stream_id)?;
Ok(ReceiveOutput::NoOutput)
}
fn done(&self) -> bool {
false
}
fn stream_type(&self) -> Http3StreamType {
Http3StreamType::Encoder
}
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> {
None
}
}

View File

@ -7,8 +7,10 @@
use crate::hframe::{HFrame, HFrameReader};
use crate::push_controller::PushController;
use crate::qlog;
use crate::{Error, Header, Res};
use crate::{RecvMessageEvents, RecvStream, ResetType};
use crate::{
Error, Header, Http3StreamType, HttpRecvStream, ReceiveOutput, RecvMessageEvents, RecvStream,
Res, ResetType,
};
use neqo_common::{qdebug, qinfo, qtrace};
use neqo_qpack::decoder::QPackDecoder;
@ -70,6 +72,7 @@ struct PushInfo {
pub(crate) struct RecvMessage {
state: RecvMessageState,
message_type: MessageType,
qpack_decoder: Rc<RefCell<QPackDecoder>>,
conn_events: Box<dyn RecvMessageEvents>,
push_handler: Option<Rc<RefCell<PushController>>>,
stream_id: u64,
@ -86,6 +89,7 @@ impl RecvMessage {
pub fn new(
message_type: MessageType,
stream_id: u64,
qpack_decoder: Rc<RefCell<QPackDecoder>>,
conn_events: Box<dyn RecvMessageEvents>,
push_handler: Option<Rc<RefCell<PushController>>>,
) -> Self {
@ -94,6 +98,7 @@ impl RecvMessage {
frame_reader: HFrameReader::new(),
},
message_type,
qpack_decoder,
conn_events,
push_handler,
stream_id,
@ -141,12 +146,8 @@ impl RecvMessage {
Ok(())
}
fn add_headers(
&mut self,
headers: Vec<Header>,
fin: bool,
decoder: &mut QPackDecoder,
) -> Res<()> {
fn add_headers(&mut self, headers: Vec<Header>, fin: bool) -> Res<()> {
qtrace!([self], "Add new headers fin={}", fin);
let interim = self.is_interim(&headers)?;
let _valid = self.headers_valid(&headers)?;
if fin && interim {
@ -155,9 +156,8 @@ impl RecvMessage {
self.conn_events
.header_ready(self.stream_id, headers, interim, fin);
if fin {
self.set_closed(decoder);
self.set_closed();
} else {
self.state = if interim {
RecvMessageState::WaitingForResponseHeaders {
@ -196,12 +196,7 @@ impl RecvMessage {
Ok(())
}
fn handle_push_promise(
&mut self,
push_id: u64,
header_block: Vec<u8>,
decoder: &mut QPackDecoder,
) -> Res<()> {
fn handle_push_promise(&mut self, push_id: u64, header_block: Vec<u8>) -> Res<()> {
if self.push_handler.is_none() {
return Err(Error::HttpFrameUnexpected);
}
@ -211,7 +206,11 @@ impl RecvMessage {
push_id,
header_block,
});
} else if let Some(headers) = decoder.decode_header_block(&header_block, self.stream_id)? {
} else if let Some(headers) = self
.qpack_decoder
.borrow_mut()
.decode_header_block(&header_block, self.stream_id)?
{
self.push_handler
.as_ref()
.ok_or(Error::HttpFrameUnexpected)?
@ -226,12 +225,7 @@ impl RecvMessage {
Ok(())
}
fn receive_internal(
&mut self,
conn: &mut Connection,
decoder: &mut QPackDecoder,
post_readable_event: bool,
) -> Res<()> {
fn receive_internal(&mut self, conn: &mut Connection, post_readable_event: bool) -> Res<()> {
let label = ::neqo_common::log_subject!(::log::Level::Debug, self);
loop {
qdebug!([label], "state={:?}.", self.state);
@ -261,7 +255,7 @@ impl RecvMessage {
HFrame::PushPromise {
push_id,
header_block,
} => self.handle_push_promise(push_id, header_block, decoder)?,
} => self.handle_push_promise(push_id, header_block)?,
_ => break Err(Error::HttpFrameUnexpected),
}
if matches!(self.state, RecvMessageState::Closed) {
@ -279,7 +273,10 @@ impl RecvMessage {
ref header_block,
fin,
} => {
if decoder.refers_dynamic_table(header_block)?
if self
.qpack_decoder
.borrow()
.refers_dynamic_table(header_block)?
&& !self.blocked_push_promise.is_empty()
{
qinfo!(
@ -289,10 +286,12 @@ impl RecvMessage {
break Ok(());
}
let done = *fin;
if let Some(headers) =
decoder.decode_header_block(header_block, self.stream_id)?
{
self.add_headers(headers, done, decoder)?;
let d_headers = self
.qpack_decoder
.borrow_mut()
.decode_header_block(header_block, self.stream_id)?;
if let Some(headers) = d_headers {
self.add_headers(headers, done)?;
if matches!(self.state, RecvMessageState::Closed) {
break Ok(());
}
@ -314,9 +313,11 @@ impl RecvMessage {
}
}
fn set_closed(&mut self, decoder: &mut QPackDecoder) {
fn set_closed(&mut self) {
if !self.blocked_push_promise.is_empty() {
decoder.cancel_stream(self.stream_id);
self.qpack_decoder
.borrow_mut()
.cancel_stream(self.stream_id);
}
self.state = RecvMessageState::Closed;
}
@ -428,35 +429,20 @@ impl RecvMessage {
}
impl RecvStream for RecvMessage {
fn receive(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()> {
self.receive_internal(conn, decoder, true)
}
fn header_unblocked(&mut self, conn: &mut Connection, decoder: &mut QPackDecoder) -> Res<()> {
while let Some(p) = self.blocked_push_promise.front() {
if let Some(headers) = decoder.decode_header_block(&p.header_block, self.stream_id)? {
self.push_handler
.as_ref()
.ok_or(Error::HttpFrameUnexpected)?
.borrow_mut()
.new_push_promise(p.push_id, self.stream_id, headers)?;
self.blocked_push_promise.pop_front();
}
}
if self.blocked_push_promise.is_empty() {
return self.receive_internal(conn, decoder, true);
}
Ok(())
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput> {
self.receive_internal(conn, true)?;
Ok(ReceiveOutput::NoOutput)
}
fn done(&self) -> bool {
matches!(self.state, RecvMessageState::Closed)
}
fn stream_reset(&self, app_error: AppError, decoder: &mut QPackDecoder, reset_type: ResetType) {
fn stream_reset(&mut self, app_error: AppError, reset_type: ResetType) -> Res<()> {
if !self.closing() || !self.blocked_push_promise.is_empty() {
decoder.cancel_stream(self.stream_id);
self.qpack_decoder
.borrow_mut()
.cancel_stream(self.stream_id);
}
match reset_type {
ResetType::Local => {
@ -467,14 +453,42 @@ impl RecvStream for RecvMessage {
}
ResetType::App => {}
}
self.state = RecvMessageState::Closed;
Ok(())
}
fn read_data(
&mut self,
conn: &mut Connection,
decoder: &mut QPackDecoder,
buf: &mut [u8],
) -> Res<(usize, bool)> {
fn stream_type(&self) -> Http3StreamType {
Http3StreamType::Http
}
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> {
Some(self)
}
}
impl HttpRecvStream for RecvMessage {
fn header_unblocked(&mut self, conn: &mut Connection) -> Res<()> {
while let Some(p) = self.blocked_push_promise.front() {
if let Some(headers) = self
.qpack_decoder
.borrow_mut()
.decode_header_block(&p.header_block, self.stream_id)?
{
self.push_handler
.as_ref()
.ok_or(Error::HttpFrameUnexpected)?
.borrow_mut()
.new_push_promise(p.push_id, self.stream_id, headers)?;
self.blocked_push_promise.pop_front();
} else {
return Ok(());
}
}
self.receive_internal(conn, true)
}
fn read_data(&mut self, conn: &mut Connection, buf: &mut [u8]) -> Res<(usize, bool)> {
let mut written = 0;
loop {
match self.state {
@ -495,19 +509,19 @@ impl RecvStream for RecvMessage {
if *remaining_data_len > 0 {
return Err(Error::HttpFrame);
}
self.set_closed(decoder);
self.set_closed();
break Ok((written, fin));
} else if *remaining_data_len == 0 {
self.state = RecvMessageState::WaitingForData {
frame_reader: HFrameReader::new(),
};
self.receive_internal(conn, decoder, false)?;
self.receive_internal(conn, false)?;
} else {
break Ok((written, false));
}
}
RecvMessageState::ClosePending => {
self.set_closed(decoder);
self.set_closed();
break Ok((written, true));
}
_ => break Ok((written, false)),

View File

@ -13,7 +13,7 @@ use crate::server_events::{ClientRequestStream, Http3ServerEvent, Http3ServerEve
use crate::settings::HttpZeroRttChecker;
use crate::Res;
use neqo_common::{qtrace, Datagram};
use neqo_crypto::{AntiReplay, Cipher, ZeroRttChecker};
use neqo_crypto::{AntiReplay, Cipher, PrivateKey, PublicKey, ZeroRttChecker};
use neqo_qpack::QpackSettings;
use neqo_transport::server::{ActiveConnectionRef, Server, ValidateAddress};
use neqo_transport::{
@ -89,6 +89,26 @@ impl Http3Server {
self.server.set_preferred_address(spa);
}
/// Enable encrypted client hello (ECH).
///
/// # Errors
/// Only when NSS can't serialize a configuration.
pub fn enable_ech(
&mut self,
config: u8,
public_name: &str,
sk: &PrivateKey,
pk: &PublicKey,
) -> Res<()> {
self.server.enable_ech(config, public_name, sk, pk)?;
Ok(())
}
#[must_use]
pub fn ech_config(&self) -> &[u8] {
self.server.ech_config()
}
pub fn process(&mut self, dgram: Option<Datagram>, now: Instant) -> Output {
qtrace!([self], "Process.");
let out = self.server.process(dgram, now);
@ -243,6 +263,7 @@ mod tests {
Connection, ConnectionError, ConnectionEvent, State, StreamType, ZeroRttState,
};
use std::collections::HashMap;
use std::mem;
use std::ops::{Deref, DerefMut};
use test_fixture::{
anti_replay, default_client, fixture_init, now, CountingConnectionIdGenerator,
@ -274,6 +295,12 @@ mod tests {
create_server(DEFAULT_SETTINGS)
}
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::unnested_or_patterns
)] // Until we require rust 1.53 we can't use or_patterns.
fn assert_closed(hconn: &mut Http3Server, expected: &Error) {
let err = ConnectionError::Application(expected.code());
let closed = |e| {
@ -369,7 +396,7 @@ mod tests {
// the control stream
let mut buf = [0_u8; 100];
let (amount, fin) = client.stream_recv(stream_id, &mut buf).unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert_eq!(amount, CONTROL_STREAM_DATA.len());
assert_eq!(&buf[..9], CONTROL_STREAM_DATA);
} else if stream_id == CLIENT_SIDE_ENCODER_STREAM_ID
@ -377,7 +404,7 @@ mod tests {
{
let mut buf = [0_u8; 100];
let (amount, fin) = client.stream_recv(stream_id, &mut buf).unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert_eq!(amount, 1);
assert_eq!(buf[..1], [0x2]);
} else if stream_id == CLIENT_SIDE_DECODER_STREAM_ID
@ -385,7 +412,7 @@ mod tests {
{
let mut buf = [0_u8; 100];
let (amount, fin) = client.stream_recv(stream_id, &mut buf).unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert_eq!(amount, 1);
assert_eq!(buf[..1], [0x3]);
} else {
@ -424,7 +451,7 @@ mod tests {
// The server will open the control and qpack streams and send SETTINGS frame.
#[test]
fn test_server_connect() {
let _ = connect_and_receive_settings();
mem::drop(connect_and_receive_settings());
}
struct PeerConnection {
@ -477,7 +504,7 @@ mod tests {
assert_eq!(sent, Ok(1));
let out1 = neqo_trans_conn.process(None, now());
let out2 = server.process(out1.dgram(), now());
let _ = neqo_trans_conn.process(out2.dgram(), now());
mem::drop(neqo_trans_conn.process(out2.dgram(), now()));
// assert no error occured.
assert_not_closed(server);
@ -497,7 +524,7 @@ mod tests {
// Server: Test receiving a new control stream and a SETTINGS frame.
#[test]
fn test_server_receive_control_frame() {
let _ = connect();
mem::drop(connect());
}
// Server: Test that the connection will be closed if control stream
@ -576,12 +603,14 @@ mod tests {
// create a stream with unknown type.
let new_stream_id = peer_conn.stream_create(StreamType::UniDi).unwrap();
let _ = peer_conn.stream_send(new_stream_id, &[0x41, 0x19, 0x4, 0x4, 0x6, 0x0, 0x8, 0x0]);
let _ = peer_conn
.stream_send(new_stream_id, &[0x41, 0x19, 0x4, 0x4, 0x6, 0x0, 0x8, 0x0])
.unwrap();
let out = peer_conn.process(None, now());
let out = hconn.process(out.dgram(), now());
let _ = peer_conn.process(out.dgram(), now());
mem::drop(peer_conn.process(out.dgram(), now()));
let out = hconn.process(None, now());
let _ = peer_conn.process(out.dgram(), now());
mem::drop(peer_conn.process(out.dgram(), now()));
// check for stop-sending with Error::HttpStreamCreation.
let mut stop_sending_event_found = false;
@ -607,10 +636,10 @@ mod tests {
// create a push stream.
let push_stream_id = peer_conn.stream_create(StreamType::UniDi).unwrap();
let _ = peer_conn.stream_send(push_stream_id, &[0x1]);
let _ = peer_conn.stream_send(push_stream_id, &[0x1]).unwrap();
let out = peer_conn.process(None, now());
let out = hconn.process(out.dgram(), now());
let _ = peer_conn.conn.process(out.dgram(), now());
mem::drop(peer_conn.conn.process(out.dgram(), now()));
assert_closed(&mut hconn, &Error::HttpStreamCreation);
}
@ -772,7 +801,7 @@ mod tests {
match event {
Http3ServerEvent::Headers { headers, fin, .. } => {
check_request_header(&headers);
assert_eq!(fin, false);
assert!(!fin);
headers_frames += 1;
}
Http3ServerEvent::Data {
@ -781,7 +810,7 @@ mod tests {
fin,
} => {
assert_eq!(data, REQUEST_BODY);
assert_eq!(fin, true);
assert!(fin);
request
.set_response(
&[
@ -793,7 +822,7 @@ mod tests {
.unwrap();
data_received += 1;
}
_ => {}
Http3ServerEvent::StateChange { .. } => {}
}
}
assert_eq!(headers_frames, 1);
@ -823,7 +852,7 @@ mod tests {
fin,
} => {
check_request_header(&headers);
assert_eq!(fin, false);
assert!(!fin);
headers_frames += 1;
request
.stream_stop_sending(Error::HttpNoError.code())
@ -841,7 +870,7 @@ mod tests {
Http3ServerEvent::Data { .. } => {
panic!("We should not have a Data event");
}
_ => {}
Http3ServerEvent::StateChange { .. } => {}
}
}
let out = hconn.process(None, now());
@ -863,7 +892,7 @@ mod tests {
Http3ServerEvent::Data { .. } => {
panic!("We should not have a Data event");
}
_ => {}
Http3ServerEvent::StateChange { .. } => {}
}
}
assert_eq!(headers_frames, 1);
@ -893,7 +922,7 @@ mod tests {
fin,
} => {
check_request_header(&headers);
assert_eq!(fin, false);
assert!(!fin);
headers_frames += 1;
request
.stream_reset(Error::HttpRequestRejected.code())
@ -902,7 +931,7 @@ mod tests {
Http3ServerEvent::Data { .. } => {
panic!("We should not have a Data event");
}
_ => {}
Http3ServerEvent::StateChange { .. } => {}
}
}
let out = hconn.process(None, now());
@ -1126,7 +1155,7 @@ mod tests {
Http3ServerEvent::Data { request, .. } => {
assert!(requests.get(&request).is_some());
}
_ => {}
Http3ServerEvent::StateChange { .. } => {}
}
}
assert_eq!(requests.len(), 2);

View File

@ -6,56 +6,211 @@
#![allow(clippy::module_name_repetitions)]
use neqo_common::{qdebug, Decoder, IncrementalDecoderUint};
use crate::{AppError, Http3StreamType, HttpRecvStream, ReceiveOutput, RecvStream, Res, ResetType};
use neqo_common::{Decoder, IncrementalDecoderUint};
use neqo_transport::Connection;
#[derive(Debug)]
pub(crate) struct NewStreamTypeReader {
reader: IncrementalDecoderUint,
fin: bool,
pub enum NewStreamTypeReader {
Read {
reader: IncrementalDecoderUint,
stream_id: u64,
},
Done,
}
impl NewStreamTypeReader {
pub fn new() -> Self {
Self {
pub fn new(stream_id: u64) -> Self {
NewStreamTypeReader::Read {
reader: IncrementalDecoderUint::default(),
fin: false,
}
}
pub fn get_type(&mut self, conn: &mut Connection, stream_id: u64) -> Option<u64> {
// On any error we will only close this stream!
loop {
let to_read = self.reader.min_remaining();
let mut buf = vec![0; to_read];
match conn.stream_recv(stream_id, &mut buf[..]) {
Ok((_, true)) => {
self.fin = true;
return None;
}
Ok((0, false)) => {
return None;
}
Ok((amount, false)) => {
let res = self.reader.consume(&mut Decoder::from(&buf[..amount]));
if res.is_some() {
return res;
}
}
Err(e) => {
qdebug!(
[conn],
"Error reading stream type for stream {}: {:?}",
stream_id,
e
);
self.fin = true;
return None;
}
}
stream_id,
}
}
pub fn fin(&self) -> bool {
self.fin
pub fn get_type(&mut self, conn: &mut Connection) -> Option<Http3StreamType> {
// On any error we will only close this stream!
loop {
if let NewStreamTypeReader::Read {
ref mut reader,
stream_id,
} = self
{
let to_read = reader.min_remaining();
let mut buf = vec![0; to_read];
match conn.stream_recv(*stream_id, &mut buf[..]) {
Ok((0, false)) => {
return None;
}
Ok((amount, false)) => {
if let Some(res) = reader.consume(&mut Decoder::from(&buf[..amount])) {
*self = NewStreamTypeReader::Done;
return Some(res.into());
}
}
Ok((_, true)) | Err(_) => {
*self = NewStreamTypeReader::Done;
return None;
}
}
} else {
return None;
}
}
}
}
impl RecvStream for NewStreamTypeReader {
fn stream_reset(&mut self, _error: AppError, _reset_type: ResetType) -> Res<()> {
*self = NewStreamTypeReader::Done;
Ok(())
}
fn receive(&mut self, conn: &mut Connection) -> Res<ReceiveOutput> {
Ok(self
.get_type(conn)
.map_or(ReceiveOutput::NoOutput, ReceiveOutput::NewStream))
}
fn done(&self) -> bool {
matches!(*self, NewStreamTypeReader::Done)
}
fn stream_type(&self) -> Http3StreamType {
Http3StreamType::NewStream
}
fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> {
None
}
}
#[cfg(test)]
mod tests {
use super::NewStreamTypeReader;
use neqo_transport::{Connection, StreamType};
use std::mem;
use test_fixture::{connect, now};
use crate::{
Http3StreamType, ReceiveOutput, RecvStream, ResetType, HTTP3_UNI_STREAM_TYPE_CONTROL,
HTTP3_UNI_STREAM_TYPE_PUSH,
};
use neqo_common::Encoder;
use neqo_qpack::decoder::QPACK_UNI_STREAM_TYPE_DECODER;
use neqo_qpack::encoder::QPACK_UNI_STREAM_TYPE_ENCODER;
struct Test {
conn_c: Connection,
conn_s: Connection,
stream_id: u64,
decoder: NewStreamTypeReader,
}
impl Test {
fn new() -> Self {
let (mut conn_c, mut conn_s) = connect();
// create a stream
let stream_id = conn_s.stream_create(StreamType::UniDi).unwrap();
let out = conn_s.process(None, now());
mem::drop(conn_c.process(out.dgram(), now()));
Self {
conn_c,
conn_s,
stream_id,
decoder: NewStreamTypeReader::new(stream_id),
}
}
fn decode_buffer(&mut self, enc: &[u8], outcome: &ReceiveOutput, done: bool) {
let len = enc.len() - 1;
for i in 0..len {
self.conn_s
.stream_send(self.stream_id, &enc[i..=i])
.unwrap();
let out = self.conn_s.process(None, now());
mem::drop(self.conn_c.process(out.dgram(), now()));
assert_eq!(
self.decoder.receive(&mut self.conn_c).unwrap(),
ReceiveOutput::NoOutput
);
assert_eq!(self.decoder.done(), false);
}
self.conn_s
.stream_send(self.stream_id, &enc[enc.len() - 1..])
.unwrap();
let out = self.conn_s.process(None, now());
mem::drop(self.conn_c.process(out.dgram(), now()));
assert_eq!(self.decoder.receive(&mut self.conn_c).unwrap(), *outcome);
assert_eq!(self.decoder.done(), done);
}
fn decode(&mut self, stream_type: u64, outcome: &ReceiveOutput, done: bool) {
let mut enc = Encoder::default();
enc.encode_varint(stream_type);
self.decode_buffer(&enc[..], outcome, done);
}
}
#[test]
fn decode_streams() {
let mut t = Test::new();
t.decode(
QPACK_UNI_STREAM_TYPE_DECODER,
&ReceiveOutput::NewStream(Http3StreamType::Encoder),
true,
);
let mut t = Test::new();
t.decode(
QPACK_UNI_STREAM_TYPE_ENCODER,
&ReceiveOutput::NewStream(Http3StreamType::Decoder),
true,
);
let mut t = Test::new();
t.decode(
HTTP3_UNI_STREAM_TYPE_CONTROL,
&ReceiveOutput::NewStream(Http3StreamType::Control),
true,
);
let mut t = Test::new();
t.decode(
HTTP3_UNI_STREAM_TYPE_PUSH,
&ReceiveOutput::NewStream(Http3StreamType::Push),
true,
);
let mut t = Test::new();
t.decode(
0x3fff_ffff_ffff_ffff,
&ReceiveOutput::NewStream(Http3StreamType::Unknown),
true,
);
}
#[test]
fn done_decoding() {
let mut t = Test::new();
t.decode(
0x3fff,
&ReceiveOutput::NewStream(Http3StreamType::Unknown),
true,
);
t.decode(HTTP3_UNI_STREAM_TYPE_PUSH, &ReceiveOutput::NoOutput, true);
}
#[test]
fn decoding_truncate() {
let mut t = Test::new();
t.decode_buffer(&[0xff], &ReceiveOutput::NoOutput, false);
}
#[test]
fn reset() {
let mut t = Test::new();
t.decoder.stream_reset(0x100, ResetType::Remote).unwrap();
t.decode(HTTP3_UNI_STREAM_TYPE_PUSH, &ReceiveOutput::NoOutput, true);
}
}

View File

@ -9,6 +9,7 @@
use neqo_common::{event::Provider, Datagram};
use neqo_crypto::AuthenticationStatus;
use neqo_http3::{Http3Client, Http3ClientEvent, Http3Server, Http3ServerEvent, Http3State};
use std::mem;
use test_fixture::*;
const RESPONSE_DATA: &[u8] = &[0x61, 0x62, 0x63];
@ -31,7 +32,7 @@ fn process_server_events(server: &mut Http3Server) {
(String::from(":path"), String::from("/"))
]
);
assert_eq!(fin, true);
assert!(fin);
request
.set_response(
&[
@ -44,7 +45,7 @@ fn process_server_events(server: &mut Http3Server) {
request_found = true;
}
}
assert_eq!(request_found, true);
assert!(request_found);
}
fn process_client_events(conn: &mut Http3Client) {
@ -60,13 +61,13 @@ fn process_client_events(conn: &mut Http3Client) {
(String::from("content-length"), String::from("3")),
]
);
assert_eq!(fin, false);
assert!(!fin);
response_header_found = true;
}
Http3ClientEvent::DataReadable { stream_id } => {
let mut buf = [0u8; 100];
let (amount, fin) = conn.read_response_data(now(), stream_id, &mut buf).unwrap();
assert_eq!(fin, true);
assert!(fin);
assert_eq!(amount, RESPONSE_DATA.len());
assert_eq!(&buf[..RESPONSE_DATA.len()], RESPONSE_DATA);
response_data_found = true;
@ -74,8 +75,8 @@ fn process_client_events(conn: &mut Http3Client) {
_ => {}
}
}
assert_eq!(response_header_found, true);
assert_eq!(response_data_found, true)
assert!(response_header_found);
assert!(response_data_found);
}
fn connect() -> (Http3Client, Http3Server, Option<Datagram>) {
@ -86,7 +87,7 @@ fn connect() -> (Http3Client, Http3Server, Option<Datagram>) {
let out = hconn_c.process(None, now()); // Initial
let out = hconn_s.process(out.dgram(), now()); // Initial + Handshake
let out = hconn_c.process(out.dgram(), now()); // ACK
let _ = hconn_s.process(out.dgram(), now()); //consume ACK
mem::drop(hconn_s.process(out.dgram(), now())); //consume ACK
let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
assert!(hconn_c.events().any(authentication_needed));
hconn_c.authenticated(AuthenticationStatus::Ok, now());
@ -95,9 +96,9 @@ fn connect() -> (Http3Client, Http3Server, Option<Datagram>) {
let out = hconn_s.process(out.dgram(), now()); // Handshake
let out = hconn_c.process(out.dgram(), now());
let out = hconn_s.process(out.dgram(), now());
// assert_eq!(hconn_s.settings_received, true);
// assert!(hconn_s.settings_received);
let out = hconn_c.process(out.dgram(), now());
// assert_eq!(hconn_c.settings_received, true);
// assert!(hconn_c.settings_received);
(hconn_c, hconn_s, out.dgram())
}
@ -120,13 +121,13 @@ fn test_fetch() {
let out = hconn_c.process(dgram, now());
eprintln!("-----server");
let out = hconn_s.process(out.dgram(), now());
let _ = hconn_c.process(out.dgram(), now());
mem::drop(hconn_c.process(out.dgram(), now()));
process_server_events(&mut hconn_s);
let out = hconn_s.process(None, now());
eprintln!("-----client");
let _ = hconn_c.process(out.dgram(), now());
mem::drop(hconn_c.process(out.dgram(), now()));
let out = hconn_s.process(None, now());
let _ = hconn_c.process(out.dgram(), now());
mem::drop(hconn_c.process(out.dgram(), now()));
process_client_events(&mut hconn_c);
}

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"92c4ec92146cf8663581a9c34c652ee63d4dbb6e1b35687a5fc8132ce05ef1e3","src/decoder.rs":"8e9dd33b9908e73cd131dc8c572537a2c32617d3e859dd29adbb37c89c19ae4c","src/decoder_instructions.rs":"16c5f6dbf5b80f343f3aec0a1ab17e6e8c2d23a312bc99369990712657db7360","src/encoder.rs":"9462988c5e8d8533a7341632dbec47cf634c660336798d0350a37e15b40088b2","src/encoder_instructions.rs":"56474d63efdc441e012efa23ed447a38503581442a49c1306e1b4f7acade79d7","src/header_block.rs":"4a2755ebea44cf214fe881cd2674ec244306cc89099172a3105ca7f0d36847ef","src/huffman.rs":"e275c4b6dfd8503fc710c0fcb38f1be1bafbd8577b310026aad0e284653bebdd","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"55a0c2717d6fbb84733f6cfd31cbbc05f2c89eeff250a122a983ed8c31e86a92","src/prefix.rs":"72c587c40aef4ed38cf13b2de91091d671611679be2a9da6f0b24abafaf50dc5","src/qlog.rs":"7618085e27bb3fb1f4d1c73ba501b9a293723293c4020b7cc4129676eb278131","src/qpack_send_buf.rs":"c59309ca5a14bf6ca5e9206a5c1add78843b9d125b83b15767754c884ded58b0","src/reader.rs":"d01015cd7f1100c916bc81a9ca171b102699a93706fe9d3d9b3e4fb26a68882b","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}
{"files":{"Cargo.toml":"446d5bd7492ae58635c02a581532ce2578cefc65ba43271aedba8cebdbf6a2b9","src/decoder.rs":"7f9425049e50ac5d402aee9c367aed827150a4c52825f3da8bf3b4ae7428381b","src/decoder_instructions.rs":"b2e466fc35272ab94772ae15c293dc4f85dc25a14888fc254a18650706876aac","src/encoder.rs":"be4f2aaf3d9d0d4feed36316963505b9fa7ab6a5b332ebc7504fa5473b87576a","src/encoder_instructions.rs":"56474d63efdc441e012efa23ed447a38503581442a49c1306e1b4f7acade79d7","src/header_block.rs":"4a2755ebea44cf214fe881cd2674ec244306cc89099172a3105ca7f0d36847ef","src/huffman.rs":"e275c4b6dfd8503fc710c0fcb38f1be1bafbd8577b310026aad0e284653bebdd","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"55a0c2717d6fbb84733f6cfd31cbbc05f2c89eeff250a122a983ed8c31e86a92","src/prefix.rs":"72c587c40aef4ed38cf13b2de91091d671611679be2a9da6f0b24abafaf50dc5","src/qlog.rs":"7618085e27bb3fb1f4d1c73ba501b9a293723293c4020b7cc4129676eb278131","src/qpack_send_buf.rs":"c59309ca5a14bf6ca5e9206a5c1add78843b9d125b83b15767754c884ded58b0","src/reader.rs":"a97c5d94cba1756735e389b127b9397b5ee673855cedbe7a34894e306ee64434","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}

View File

@ -1,6 +1,6 @@
[package]
name = "neqo-qpack"
version = "0.4.25"
version = "0.4.26"
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"

View File

@ -26,7 +26,6 @@ pub struct QPackDecoder {
max_entries: u64,
send_buf: QpackData,
local_stream_id: Option<u64>,
remote_stream_id: Option<u64>,
max_table_size: u64,
max_blocked_streams: usize,
blocked_streams: Vec<(u64, u64)>, //stream_id and requested inserts count.
@ -48,7 +47,6 @@ impl QPackDecoder {
max_entries: qpack_settings.max_table_size_decoder >> 5,
send_buf,
local_stream_id: None,
remote_stream_id: None,
max_table_size: qpack_settings.max_table_size_decoder,
max_blocked_streams: usize::try_from(qpack_settings.max_blocked_streams).unwrap(),
blocked_streams: Vec::new(),
@ -236,14 +234,6 @@ impl QPackDecoder {
}
}
#[must_use]
pub fn is_recv_stream(&self, stream_id: u64) -> bool {
match self.remote_stream_id {
Some(id) => id == stream_id,
None => false,
}
}
/// # Panics
/// When a stream has already been added.
pub fn add_send_stream(&mut self, stream_id: u64) {
@ -253,27 +243,11 @@ impl QPackDecoder {
self.local_stream_id = Some(stream_id);
}
/// # Errors
/// May return `WrongStreamCount` if HTTP/3 has received multiple encoder streams.
pub fn add_recv_stream(&mut self, stream_id: u64) -> Res<()> {
if self.remote_stream_id.is_some() {
Err(Error::WrongStreamCount)
} else {
self.remote_stream_id = Some(stream_id);
Ok(())
}
}
#[must_use]
pub fn local_stream_id(&self) -> Option<u64> {
self.local_stream_id
}
#[must_use]
pub fn remote_stream_id(&self) -> Option<u64> {
self.remote_stream_id
}
#[must_use]
pub fn stats(&self) -> Stats {
self.stats.clone()
@ -300,6 +274,7 @@ mod tests {
use crate::QpackSettings;
use neqo_transport::StreamType;
use std::convert::TryInto;
use std::mem;
use test_fixture::now;
struct TestDecoder {
@ -337,9 +312,10 @@ mod tests {
fn recv_instruction(decoder: &mut TestDecoder, encoder_instruction: &[u8], res: &Res<()>) {
let _ = decoder
.peer_conn
.stream_send(decoder.recv_stream_id, encoder_instruction);
.stream_send(decoder.recv_stream_id, encoder_instruction)
.unwrap();
let out = decoder.peer_conn.process(None, now());
let _ = decoder.conn.process(out.dgram(), now());
mem::drop(decoder.conn.process(out.dgram(), now()));
assert_eq!(
decoder
.decoder
@ -351,13 +327,13 @@ mod tests {
fn send_instructions_and_check(decoder: &mut TestDecoder, decoder_instruction: &[u8]) {
decoder.decoder.send(&mut decoder.conn).unwrap();
let out = decoder.conn.process(None, now());
let _ = decoder.peer_conn.process(out.dgram(), now());
mem::drop(decoder.peer_conn.process(out.dgram(), now()));
let mut buf = [0_u8; 100];
let (amount, fin) = decoder
.peer_conn
.stream_recv(decoder.send_stream_id, &mut buf)
.unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert_eq!(&buf[..amount], decoder_instruction);
}

View File

@ -112,7 +112,9 @@ impl DecoderInstructionReader {
DecoderInstruction::NoInstruction,
));
}
_ => unreachable!("This instruction cannot be in this state."),
DecoderInstruction::NoInstruction => {
unreachable!("This instruction cannot be in this state.")
}
}
}
}

View File

@ -12,8 +12,7 @@ use crate::qpack_send_buf::QpackData;
use crate::reader::ReceiverConnWrapper;
use crate::stats::Stats;
use crate::table::{HeaderTable, LookupResult, ADDITIONAL_TABLE_ENTRY_SIZE};
use crate::Header;
use crate::{Error, QpackSettings, Res};
use crate::{Error, Header, QpackSettings, Res};
use neqo_common::{qdebug, qerror, qlog::NeqoQlog, qtrace};
use neqo_transport::{Connection, Error as TransportError, StreamId};
use std::collections::{HashMap, HashSet, VecDeque};
@ -44,7 +43,6 @@ pub struct QPackEncoder {
max_entries: u64,
instruction_reader: DecoderInstructionReader,
local_stream: LocalStreamState,
remote_stream_id: Option<u64>,
max_blocked_streams: u16,
// Remember header blocks that are referring to dynamic table.
// There can be multiple header blocks in one stream, headers, trailer, push stream request, etc.
@ -66,7 +64,6 @@ impl QPackEncoder {
max_entries: 0,
instruction_reader: DecoderInstructionReader::new(),
local_stream: LocalStreamState::NoStream,
remote_stream_id: None,
max_blocked_streams: 0,
unacked_header_blocks: HashMap::new(),
blocked_stream_cnt: 0,
@ -117,19 +114,9 @@ impl QPackEncoder {
/// # Errors
/// May return: `ClosedCriticalStream` if stream has been closed or `DecoderStream`
/// in case of any other transport error.
pub fn recv_if_encoder_stream(&mut self, conn: &mut Connection, stream_id: u64) -> Res<bool> {
match self.remote_stream_id {
Some(id) => {
if id == stream_id {
self.read_instructions(conn, stream_id)
.map_err(|e| map_error(&e))?;
Ok(true)
} else {
Ok(false)
}
}
None => Ok(false),
}
pub fn receive(&mut self, conn: &mut Connection, stream_id: u64) -> Res<()> {
self.read_instructions(conn, stream_id)
.map_err(|e| map_error(&e))
}
fn read_instructions(&mut self, conn: &mut Connection, stream_id: u64) -> Res<()> {
@ -235,7 +222,7 @@ impl QPackEncoder {
self.stream_cancellation(stream_id);
Ok(())
}
_ => Ok(()),
DecoderInstruction::NoInstruction => Ok(()),
}
}
@ -364,6 +351,12 @@ impl QPackEncoder {
/// `InternalError` if an unexpected error occurred.
/// # Panics
/// If there is a programming error.
#[allow(
unknown_lints,
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::unnested_or_patterns
)] // Until we require rust 1.53 we can't use or_patterns.
pub fn encode_header_block(
&mut self,
conn: &mut Connection,
@ -479,18 +472,6 @@ impl QPackEncoder {
}
}
/// We have received a remote decoder stream. Remember its stream id.
/// # Errors
/// If we receive multiple decoder streams this function will return `WrongStreamCount`.
pub fn add_recv_stream(&mut self, stream_id: u64) -> Res<()> {
if self.remote_stream_id.is_some() {
Err(Error::WrongStreamCount)
} else {
self.remote_stream_id = Some(stream_id);
Ok(())
}
}
#[must_use]
pub fn stats(&self) -> Stats {
self.stats.clone()
@ -501,11 +482,6 @@ impl QPackEncoder {
self.local_stream.stream_id().map(StreamId::as_u64)
}
#[must_use]
pub fn remote_stream_id(&self) -> Option<u64> {
self.remote_stream_id
}
#[cfg(test)]
fn blocked_stream_cnt(&self) -> u16 {
self.blocked_stream_cnt
@ -543,6 +519,7 @@ mod tests {
use super::{Connection, Error, Header, QPackEncoder, Res};
use crate::QpackSettings;
use neqo_transport::{ConnectionParameters, StreamType};
use std::mem;
use test_fixture::{configure_server, default_client, default_server, handshake, now};
struct TestEncoder {
@ -585,13 +562,13 @@ mod tests {
self.encoder.send(&mut self.conn).unwrap();
let out = self.conn.process(None, now());
let out2 = self.peer_conn.process(out.dgram(), now());
let _ = self.conn.process(out2.dgram(), now());
mem::drop(self.conn.process(out2.dgram(), now()));
let mut buf = [0_u8; 100];
let (amount, fin) = self
.peer_conn
.stream_recv(self.send_stream_id, &mut buf)
.unwrap();
assert_eq!(fin, false);
assert!(!fin);
assert_eq!(buf[..amount], encoder_instruction[..]);
}
}
@ -646,7 +623,7 @@ mod tests {
.stream_send(encoder.recv_stream_id, decoder_instruction)
.unwrap();
let out = encoder.peer_conn.process(None, now());
let _ = encoder.conn.process(out.dgram(), now());
mem::drop(encoder.conn.process(out.dgram(), now()));
assert!(encoder
.encoder
.read_instructions(&mut encoder.conn, encoder.recv_stream_id)
@ -1643,7 +1620,7 @@ mod tests {
// exchange a flow control update.
let out = encoder.peer_conn.process(None, now());
let _ = encoder.conn.process(out.dgram(), now());
mem::drop(encoder.conn.process(out.dgram(), now()));
// Try writing a new header block. Now, headers will be added to the dynamic table again, because
// instructions can be sent.
@ -1690,7 +1667,7 @@ mod tests {
encoder.encoder.send(&mut encoder.conn).unwrap();
let out = encoder.conn.process(None, now());
let _ = encoder.peer_conn.process(out.dgram(), now());
mem::drop(encoder.peer_conn.process(out.dgram(), now()));
// receive an insert count increment.
recv_instruction(&mut encoder, &[0x01]);

View File

@ -299,7 +299,7 @@ impl LiteralReader {
if self.use_huffman {
break Ok(decode_huffman(&self.literal)?);
}
break Ok(mem::replace(&mut self.literal, Vec::new()));
break Ok(mem::take(&mut self.literal));
}
break Err(Error::NeedMoreData);
}

File diff suppressed because one or more lines are too long

View File

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

View File

@ -8,7 +8,7 @@
#![deny(clippy::pedantic)]
use crate::connection::params::ACK_RATIO_SCALE;
use crate::frame::{write_varint_frame, FRAME_TYPE_ACK_FREQUENCY};
use crate::frame::FRAME_TYPE_ACK_FREQUENCY;
use crate::packet::PacketBuilder;
use crate::recovery::RecoveryToken;
use crate::stats::FrameStats;
@ -45,16 +45,13 @@ impl AckRate {
}
pub fn write_frame(&self, builder: &mut PacketBuilder, seqno: u64) -> bool {
write_varint_frame(
builder,
&[
FRAME_TYPE_ACK_FREQUENCY,
seqno,
u64::try_from(self.packets + 1).unwrap(),
u64::try_from(self.delay.as_micros()).unwrap(),
0,
],
)
builder.write_varint_frame(&[
FRAME_TYPE_ACK_FREQUENCY,
seqno,
u64::try_from(self.packets + 1).unwrap(),
u64::try_from(self.delay.as_micros()).unwrap(),
0,
])
}
/// Determine whether to send an update frame.

View File

@ -139,7 +139,15 @@ impl WindowAdjustment for Cubic {
let time_ca = self
.ca_epoch_start
.map_or(min_rtt, |t| now + min_rtt - t)
.map_or(min_rtt, |t| {
if now + min_rtt < t {
// This only happens when processing old packets
// that were saved and replayed with old timestamps.
min_rtt
} else {
now + min_rtt - t
}
})
.as_secs_f64();
let target_cubic = self.w_cubic(time_ca);

View File

@ -301,3 +301,24 @@ fn congestion_event_congestion_avoidance_2() {
);
assert_eq!(cubic.cwnd(), CWND_AFTER_LOSS);
}
#[test]
fn congestion_event_congestion_avoidance_test_no_overflow() {
const PTO: Duration = Duration::from_millis(120);
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, 1, now());
assert_within(cubic.last_max_cwnd(), CWND_INITIAL_10_F64, f64::EPSILON);
assert_eq!(cubic.cwnd(), CWND_INITIAL);
// Now ack packet that was send earlier.
ack_packet(&mut cubic, 0, now() - PTO);
}

View File

@ -333,6 +333,7 @@ pub type RemoteConnectionIdEntry = ConnectionIdEntry<[u8; 16]>;
#[derive(Debug, Default)]
pub struct ConnectionIdStore<SRT: Clone + PartialEq> {
cids: SmallVec<[ConnectionIdEntry<SRT>; 8]>,
retired: Vec<[ConnectionIdEntry<SRT>; 8]>,
}
impl<SRT: Clone + PartialEq> ConnectionIdStore<SRT> {
@ -368,13 +369,28 @@ impl ConnectionIdStore<[u8; 16]> {
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(())
// Insert in order so that we use them in order where possible.
if let Err(idx) = self.cids.binary_search_by_key(&entry.seqno, |e| e.seqno) {
self.cids.insert(idx, entry);
Ok(())
} else {
Err(Error::ProtocolViolation)
}
}
// Retire connection IDs and return the sequence numbers of those that were retired.
pub fn retire_prior_to(&mut self, retire_prior: u64) -> Vec<u64> {
let mut retired = Vec::new();
self.cids.retain(|e| {
if e.seqno < retire_prior {
retired.push(e.seqno);
false
} else {
true
}
});
retired
}
}

View File

@ -24,7 +24,8 @@ use neqo_common::{
};
use neqo_crypto::{
agent::CertificateInfo, random, Agent, AntiReplay, AuthenticationStatus, Cipher, Client,
HandshakeState, ResumptionToken, SecretAgentInfo, Server, ZeroRttChecker,
HandshakeState, PrivateKey, PublicKey, ResumptionToken, SecretAgentInfo, SecretAgentPreInfo,
Server, ZeroRttChecker,
};
use crate::addr_valid::{AddressValidation, NewTokenState};
@ -58,6 +59,8 @@ mod idle;
pub mod params;
mod saved;
mod state;
#[cfg(test)]
pub mod test_internal;
use idle::IdleTimeout;
pub use idle::LOCAL_IDLE_TIMEOUT;
@ -258,6 +261,12 @@ pub struct Connection {
release_resumption_token_timer: Option<Instant>,
conn_params: ConnectionParameters,
hrtime: hrtime::Handle,
/// For testing purposes it is sometimes necessary to inject frames that wouldn't
/// otherwise be sent, just to see how a connection handles them. Inserting them
/// into packets proper mean that the frames follow the entire processing path.
#[cfg(test)]
pub test_frame_writer: Option<Box<dyn test_internal::FrameWriter>>,
}
impl Debug for Connection {
@ -324,15 +333,6 @@ impl Connection {
)
}
pub fn server_enable_0rtt(
&mut self,
anti_replay: &AntiReplay,
zero_rtt_checker: impl ZeroRttChecker + 'static,
) -> Res<()> {
self.crypto
.server_enable_0rtt(self.tps.clone(), anti_replay, zero_rtt_checker)
}
fn new(
role: Role,
agent: Agent,
@ -390,11 +390,41 @@ impl Connection {
release_resumption_token_timer: None,
conn_params,
hrtime: hrtime::Time::get(Self::LOOSE_TIMER_RESOLUTION),
#[cfg(test)]
test_frame_writer: None,
};
c.stats.borrow_mut().init(format!("{}", c));
Ok(c)
}
pub fn server_enable_0rtt(
&mut self,
anti_replay: &AntiReplay,
zero_rtt_checker: impl ZeroRttChecker + 'static,
) -> Res<()> {
self.crypto
.server_enable_0rtt(self.tps.clone(), anti_replay, zero_rtt_checker)
}
pub fn server_enable_ech(
&mut self,
config: u8,
public_name: &str,
sk: &PrivateKey,
pk: &PublicKey,
) -> Res<()> {
self.crypto.server_enable_ech(config, public_name, sk, pk)
}
/// Get the active ECH configuration, which is empty if ECH is disabled.
pub fn ech_config(&self) -> &[u8] {
self.crypto.ech_config()
}
pub fn client_enable_ech(&mut self, ech_config_list: impl AsRef<[u8]>) -> Res<()> {
self.crypto.client_enable_ech(ech_config_list)
}
/// Set or clear the qlog for this connection.
pub fn set_qlog(&mut self, qlog: NeqoQlog) {
self.loss_recovery.set_qlog(qlog.clone());
@ -680,6 +710,10 @@ impl Connection {
self.crypto.tls.info()
}
pub fn tls_preinfo(&self) -> Res<SecretAgentPreInfo> {
Ok(self.crypto.tls.preinfo()?)
}
/// Get the peer's certificate chain and other info.
pub fn peer_certificate(&self) -> Option<CertificateInfo> {
self.crypto.tls.peer_certificate()
@ -1438,7 +1472,7 @@ impl Connection {
self.setup_handshake_path(&path, now);
} else {
// Otherwise try to get a usable connection ID.
let _ = self.ensure_permanent(&path);
mem::drop(self.ensure_permanent(&path));
}
}
}
@ -1813,6 +1847,14 @@ impl Connection {
self.streams
.write_frames(TransmissionPriority::Low, builder, tokens, stats);
#[cfg(test)]
{
if let Some(w) = &mut self.test_frame_writer {
w.write_frames(builder);
}
}
Ok(())
}
@ -2262,6 +2304,9 @@ impl Connection {
match self.crypto.handshake(now, space, data)? {
HandshakeState::Authenticated(_) | HandshakeState::InProgress => (),
HandshakeState::AuthenticationPending => self.events.authentication_needed(),
HandshakeState::EchFallbackAuthenticationPending(public_name) => self
.events
.ech_fallback_authentication_needed(public_name.clone()),
HandshakeState::Complete(_) => {
if !self.state.connected() {
self.set_connected(now)?;
@ -2360,7 +2405,7 @@ impl Connection {
sequence_number,
connection_id,
stateless_reset_token,
..
retire_prior,
} => {
self.stats.borrow_mut().frame_rx.new_connection_id += 1;
self.connection_ids.add_remote(ConnectionIdEntry::new(
@ -2368,6 +2413,12 @@ impl Connection {
ConnectionId::from(connection_id),
stateless_reset_token.to_owned(),
))?;
self.paths
.retire_cids(retire_prior, &mut self.connection_ids);
if self.connection_ids.len() >= LOCAL_ACTIVE_CID_LIMIT {
qinfo!([self], "received too many connection IDs");
return Err(Error::ConnectionIdLimitExceeded);
}
}
Frame::RetireConnectionId { sequence_number } => {
self.stats.borrow_mut().frame_rx.retire_connection_id += 1;

View File

@ -51,6 +51,16 @@ impl State {
Self::Closing { .. } | Self::Draining { .. } | Self::Closed(_)
)
}
pub fn error(&self) -> Option<&ConnectionError> {
if let Self::Closing { error, .. } | Self::Draining { error, .. } | Self::Closed(error) =
self
{
Some(error)
} else {
None
}
}
}
// Implement `PartialOrd` so that we can enforce monotonic state progression.

View File

@ -0,0 +1,13 @@
// 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.
// Some access to internal connection stuff for testing purposes.
use crate::packet::PacketBuilder;
pub trait FrameWriter {
fn write_frames(&mut self, builder: &mut PacketBuilder);
}

View File

@ -6,9 +6,9 @@
use super::super::{Connection, 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,
assert_error, connect, 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,
};
use crate::connection::AddressValidation;
use crate::events::ConnectionEvent;
@ -21,13 +21,19 @@ use crate::{
};
use neqo_common::{event::Provider, qdebug, Datagram};
use neqo_crypto::{constants::TLS_CHACHA20_POLY1305_SHA256, AuthenticationStatus};
use neqo_crypto::{
constants::TLS_CHACHA20_POLY1305_SHA256, generate_ech_keys, AuthenticationStatus,
};
use std::cell::RefCell;
use std::mem;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::rc::Rc;
use std::time::Duration;
use test_fixture::{self, addr, assertions, fixture_init, now, split_datagram};
const ECH_CONFIG_ID: u8 = 7;
const ECH_PUBLIC_NAME: &str = "public.example";
#[test]
fn full_handshake() {
qdebug!("---- client: generate CH");
@ -223,8 +229,8 @@ fn crypto_frame_split() {
// after the first or second server packet.
assert!(client3.as_dgram_ref().is_some() ^ client4.as_dgram_ref().is_some());
let _ = server.process(client3.dgram(), now());
let _ = server.process(client4.dgram(), now());
mem::drop(server.process(client3.dgram(), now()));
mem::drop(server.process(client4.dgram(), now()));
assert_eq!(*client.state(), State::Connected);
assert_eq!(*server.state(), State::Confirmed);
@ -360,7 +366,7 @@ fn reorder_05rtt_with_0rtt() {
// Handle the first packet and send 0.5-RTT in response. Drop the response.
now += RTT / 2;
let _ = server.process(Some(c1), now).dgram().unwrap();
mem::drop(server.process(Some(c1), now).dgram().unwrap());
// The gap in 0-RTT will result in this 0.5 RTT containing an ACK.
server.process_input(c2, now);
let s2 = send_something(&mut server, now);
@ -431,7 +437,7 @@ fn coalesce_05rtt() {
// packet until authentication completes though. So it saves it.
now += RTT / 2;
assert_eq!(client.stats().dropped_rx, 0);
let _ = client.process(s2, now).dgram();
mem::drop(client.process(s2, now).dgram());
// This packet will contain an ACK, but we can ignore it.
assert_eq!(client.stats().dropped_rx, 0);
assert_eq!(client.stats().packets_rx, 3);
@ -452,7 +458,7 @@ fn coalesce_05rtt() {
assert!(s3.is_some());
assert_eq!(*server.state(), State::Confirmed);
now += RTT / 2;
let _ = client.process(s3, now).dgram();
mem::drop(client.process(s3, now).dgram());
assert_eq!(*client.state(), State::Confirmed);
assert_eq!(client.stats().dropped_rx, 0); // No dropped packets.
@ -866,7 +872,7 @@ fn drop_handshake_packet_from_wrong_address() {
let (s_in, s_hs) = split_datagram(&out.dgram().unwrap());
// Pass the initial packet.
let _ = client.process(Some(s_in), now()).dgram();
mem::drop(client.process(Some(s_in), now()).dgram());
let p = s_hs.unwrap();
let dgram = Datagram::new(
@ -878,3 +884,127 @@ fn drop_handshake_packet_from_wrong_address() {
let out = client.process(Some(dgram), now());
assert!(out.as_dgram_ref().is_none());
}
#[test]
fn ech() {
let mut server = default_server();
let (sk, pk) = generate_ech_keys().unwrap();
server
.server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk)
.unwrap();
let mut client = default_client();
client.client_enable_ech(server.ech_config()).unwrap();
connect(&mut client, &mut server);
assert!(client.tls_info().unwrap().ech_accepted());
assert!(server.tls_info().unwrap().ech_accepted());
assert!(client.tls_preinfo().unwrap().ech_accepted().unwrap());
assert!(server.tls_preinfo().unwrap().ech_accepted().unwrap());
}
fn damaged_ech_config(config: &[u8]) -> Vec<u8> {
let mut cfg = Vec::from(config);
// Ensure that the version and config_id is correct.
assert_eq!(cfg[2], 0xfe);
assert_eq!(cfg[3], 0x0a);
assert_eq!(cfg[6], ECH_CONFIG_ID);
// Change the config_id so that the server doesn't recognize it.
cfg[6] ^= 0x94;
cfg
}
#[test]
fn ech_retry() {
fixture_init();
let mut server = default_server();
let (sk, pk) = generate_ech_keys().unwrap();
server
.server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk)
.unwrap();
let mut client = default_client();
client
.client_enable_ech(&damaged_ech_config(server.ech_config()))
.unwrap();
let dgram = client.process_output(now()).dgram();
let dgram = server.process(dgram, now()).dgram();
client.process_input(dgram.unwrap(), now());
let auth_event = ConnectionEvent::EchFallbackAuthenticationNeeded {
public_name: String::from(ECH_PUBLIC_NAME),
};
assert!(client.events().any(|e| e == auth_event));
client.authenticated(AuthenticationStatus::Ok, now());
assert!(client.state().error().is_some());
// Tell the server about the error.
let dgram = client.process_output(now()).dgram();
server.process_input(dgram.unwrap(), now());
assert_eq!(
server.state().error(),
Some(&ConnectionError::Transport(Error::PeerError(0x100 + 121)))
);
let updated_config =
if let Some(ConnectionError::Transport(Error::EchRetry(c))) = client.state().error() {
c
} else {
panic!(
"Client state should be failed with EchRetry, is {:?}",
client.state()
);
};
let mut server = default_server();
server
.server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk)
.unwrap();
let mut client = default_client();
client.client_enable_ech(updated_config).unwrap();
connect(&mut client, &mut server);
assert!(client.tls_info().unwrap().ech_accepted());
assert!(server.tls_info().unwrap().ech_accepted());
assert!(client.tls_preinfo().unwrap().ech_accepted().unwrap());
assert!(server.tls_preinfo().unwrap().ech_accepted().unwrap());
}
#[test]
fn ech_retry_fallback_rejected() {
fixture_init();
let mut server = default_server();
let (sk, pk) = generate_ech_keys().unwrap();
server
.server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk)
.unwrap();
let mut client = default_client();
client
.client_enable_ech(&damaged_ech_config(server.ech_config()))
.unwrap();
let dgram = client.process_output(now()).dgram();
let dgram = server.process(dgram, now()).dgram();
client.process_input(dgram.unwrap(), now());
let auth_event = ConnectionEvent::EchFallbackAuthenticationNeeded {
public_name: String::from(ECH_PUBLIC_NAME),
};
assert!(client.events().any(|e| e == auth_event));
client.authenticated(AuthenticationStatus::PolicyRejection, now());
assert!(client.state().error().is_some());
if let Some(ConnectionError::Transport(Error::EchRetry(_))) = client.state().error() {
panic!("Client should not get EchRetry error");
}
// Pass the error on.
let dgram = client.process_output(now()).dgram();
server.process_input(dgram.unwrap(), now());
assert_eq!(
server.state().error(),
Some(&ConnectionError::Transport(Error::PeerError(298)))
); // A bad_certificate alert.
}

View File

@ -16,6 +16,7 @@ use crate::tracking::PacketNumberSpace;
use crate::StreamType;
use neqo_common::Encoder;
use std::mem;
use std::time::Duration;
use test_fixture::{self, now, split_datagram};
@ -31,10 +32,10 @@ fn idle_timeout() {
assert_eq!(res, Output::Callback(LOCAL_IDLE_TIMEOUT));
// Still connected after 29 seconds. Idle timer not reset
let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT - Duration::from_secs(1));
mem::drop(client.process(None, now + LOCAL_IDLE_TIMEOUT - Duration::from_secs(1)));
assert!(matches!(client.state(), State::Confirmed));
let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT);
mem::drop(client.process(None, now + LOCAL_IDLE_TIMEOUT));
// Not connected after LOCAL_IDLE_TIMEOUT seconds.
assert!(matches!(client.state(), State::Closed(_)));
@ -172,10 +173,10 @@ fn idle_send_packet2() {
// First transmission at t=GAP.
now += GAP;
let _ = send_something(&mut client, now);
mem::drop(send_something(&mut client, now));
// Second transmission at t=2*GAP.
let _ = send_something(&mut client, now + GAP);
mem::drop(send_something(&mut client, now + GAP));
assert!((GAP * 2 + DELTA) < LOCAL_IDLE_TIMEOUT);
// Still connected just before GAP + LOCAL_IDLE_TIMEOUT.
@ -213,16 +214,16 @@ fn idle_recv_packet() {
let out = server.process_output(now + Duration::from_secs(10));
assert_ne!(out.as_dgram_ref(), None);
let _ = client.process(out.dgram(), now + Duration::from_secs(20));
mem::drop(client.process(out.dgram(), now + Duration::from_secs(20)));
assert!(matches!(client.state(), State::Confirmed));
// Still connected after 49 seconds because idle timer reset by received
// packet
let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(19));
mem::drop(client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(19)));
assert!(matches!(client.state(), State::Confirmed));
// Not connected after 50 seconds.
let _ = client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(20));
mem::drop(client.process(None, now + LOCAL_IDLE_TIMEOUT + Duration::from_secs(20)));
assert!(matches!(client.state(), State::Closed(_)));
}
@ -247,11 +248,11 @@ fn idle_caching() {
// Perform an exchange and keep the connection alive.
// Only allow a packet containing a PING to pass.
let middle = start + AT_LEAST_PTO;
let _ = client.process_output(middle);
mem::drop(client.process_output(middle));
let dgram = client.process_output(middle).dgram();
// Get the server to send its first probe and throw that away.
let _ = server.process_output(middle).dgram();
mem::drop(server.process_output(middle).dgram());
// Now let the server process the client PING. This causes the server
// to send CRYPTO frames again, so manually extract and discard those.
let ping_before_s = server.stats().frame_rx.ping;
@ -297,7 +298,7 @@ fn idle_caching() {
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);
mem::drop(client.process(Some(initial), end));
maybe_authenticate(&mut client);
let dgram = client.process_output(end).dgram();
let dgram = server.process(dgram, end).dgram();

View File

@ -15,11 +15,12 @@ use crate::packet::PacketNumber;
use crate::path::PATH_MTU_V6;
use neqo_common::{qdebug, Datagram};
use std::mem;
use test_fixture::{self, now};
fn check_discarded(peer: &mut Connection, pkt: Datagram, dropped: usize, dups: usize) {
// Make sure to flush any saved datagrams before doing this.
let _ = peer.process_output(now());
mem::drop(peer.process_output(now()));
let before = peer.stats();
let out = peer.process(Some(pkt), now());
@ -134,7 +135,7 @@ fn key_update_client() {
let dgram = client.process(None, now).dgram();
assert!(dgram.is_some()); // Drop this packet.
assert_eq!(client.get_epochs(), (Some(4), Some(3)));
let _ = server.process(None, now);
mem::drop(server.process(None, now));
assert_eq!(server.get_epochs(), (Some(4), Some(4)));
// Even though the server has updated, it hasn't received an ACK yet.
@ -159,7 +160,7 @@ fn key_update_client() {
assert_update_blocked(&mut server);
now += AT_LEAST_PTO;
let _ = client.process(None, now);
mem::drop(client.process(None, now));
assert_eq!(client.get_epochs(), (Some(4), Some(4)));
}
@ -175,7 +176,7 @@ fn key_update_consecutive() {
// Server sends something.
// Send twice and drop the first to induce an ACK from the client.
let _ = send_something(&mut server, now); // Drop this.
mem::drop(send_something(&mut server, now)); // Drop this.
// Another packet from the server will cause the client to ACK and update keys.
let dgram = send_and_receive(&mut server, &mut client, now);
@ -187,7 +188,7 @@ fn key_update_consecutive() {
assert_eq!(server.get_epochs(), (Some(4), Some(3)));
// Now move the server temporarily into the future so that it
// rotates the keys. The client stays in the present.
let _ = server.process(None, now + AT_LEAST_PTO);
mem::drop(server.process(None, now + AT_LEAST_PTO));
assert_eq!(server.get_epochs(), (Some(4), Some(4)));
} else {
panic!("server should have a timer set");
@ -293,7 +294,7 @@ fn automatic_update_write_keys() {
connect_force_idle(&mut client, &mut server);
overwrite_invocations(UPDATE_WRITE_KEYS_AT);
let _ = send_something(&mut client, now());
mem::drop(send_something(&mut client, now()));
assert_eq!(client.get_epochs(), (Some(4), Some(3)));
}
@ -305,10 +306,10 @@ fn automatic_update_write_keys_later() {
overwrite_invocations(UPDATE_WRITE_KEYS_AT + 2);
// No update after the first.
let _ = send_something(&mut client, now());
mem::drop(send_something(&mut client, now()));
assert_eq!(client.get_epochs(), (Some(3), Some(3)));
// The second will update though.
let _ = send_something(&mut client, now());
mem::drop(send_something(&mut client, now()));
assert_eq!(client.get_epochs(), (Some(4), Some(3)));
}

View File

@ -7,13 +7,19 @@
use super::super::{Connection, Output, State, StreamType};
use super::{
connect_fail, connect_force_idle, connect_rtt_idle, default_client, default_server,
maybe_authenticate, new_client, new_server, send_something,
maybe_authenticate, new_client, new_server, send_something, CountingConnectionIdGenerator,
};
use crate::cid::LOCAL_ACTIVE_CID_LIMIT;
use crate::frame::FRAME_TYPE_NEW_CONNECTION_ID;
use crate::packet::PacketBuilder;
use crate::path::{PATH_MTU_V4, PATH_MTU_V6};
use crate::tparams::{self, PreferredAddress, TransportParameter};
use crate::{ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error};
use crate::{
ConnectionError, ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef,
ConnectionParameters, EmptyConnectionIdGenerator, Error,
};
use neqo_common::Datagram;
use neqo_common::{Datagram, Decoder};
use std::cell::RefCell;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::rc::Rc;
@ -344,6 +350,14 @@ fn migrate_same_fail() {
));
}
/// This gets the connection ID from a datagram using the default
/// connection ID generator/decoder.
fn get_cid(d: &Datagram) -> ConnectionIdRef {
let gen = CountingConnectionIdGenerator::default();
assert_eq!(d[0] & 0x80, 0); // Only support short packets for now.
gen.decode_cid(&mut Decoder::from(&d[1..])).unwrap()
}
fn migration(mut client: Connection) {
let mut server = default_server();
connect_force_idle(&mut client, &mut server);
@ -356,6 +370,7 @@ fn migration(mut client: Connection) {
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 probe_cid = ConnectionId::from(&get_cid(&probe));
let resp = server.process(Some(probe), now).dgram().unwrap();
assert_v4_path(&resp, true);
@ -364,6 +379,7 @@ fn migration(mut client: Connection) {
// Data continues to be exchanged on the new path.
let client_data = send_something(&mut client, now);
assert_ne!(get_cid(&client_data), probe_cid);
assert_v6_path(&client_data, false);
server.process_input(client_data, now);
let server_data = send_something(&mut server, now);
@ -755,3 +771,171 @@ fn migration_invalid_address() {
Error::InvalidMigration
);
}
/// This inserts a frame into packets that provides a single new
/// connection ID and retires all others.
struct RetireAll {
cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>>,
}
impl crate::connection::test_internal::FrameWriter for RetireAll {
fn write_frames(&mut self, builder: &mut PacketBuilder) {
// Use a sequence number that is large enough that all existing values
// will be lower (so they get retired). As the code doesn't care about
// gaps in sequence numbers, this is safe, even though the gap might
// hint that there are more outstanding connection IDs that are allowed.
const SEQNO: u64 = 100;
let cid = self.cid_gen.borrow_mut().generate_cid().unwrap();
builder
.encode_varint(FRAME_TYPE_NEW_CONNECTION_ID)
.encode_varint(SEQNO)
.encode_varint(SEQNO) // Retire Prior To
.encode_vec(1, &cid)
.encode(&[0x7f; 16]);
}
}
/// Test that forcing retirement of connection IDs forces retirement of all active
/// connection IDs and the use of of newer one.
#[test]
fn retire_all() {
let mut client = default_client();
let cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>> =
Rc::new(RefCell::new(CountingConnectionIdGenerator::default()));
let mut server = Connection::new_server(
test_fixture::DEFAULT_KEYS,
test_fixture::DEFAULT_ALPN,
Rc::clone(&cid_gen),
ConnectionParameters::default(),
)
.unwrap();
connect_force_idle(&mut client, &mut server);
let original_cid = ConnectionId::from(&get_cid(&send_something(&mut client, now())));
server.test_frame_writer = Some(Box::new(RetireAll { cid_gen }));
let ncid = send_something(&mut server, now());
server.test_frame_writer = None;
let new_cid_before = client.stats().frame_rx.new_connection_id;
let retire_cid_before = client.stats().frame_tx.retire_connection_id;
client.process_input(ncid, now());
let retire = send_something(&mut client, now());
assert_eq!(
client.stats().frame_rx.new_connection_id,
new_cid_before + 1
);
assert_eq!(
client.stats().frame_tx.retire_connection_id,
retire_cid_before + LOCAL_ACTIVE_CID_LIMIT
);
assert_ne!(get_cid(&retire), original_cid);
}
/// During a graceful migration, if the probed path can't get a new connection ID due
/// to being forced to retire the one it is using, the migration will fail.
#[test]
fn retire_prior_to_migration_failure() {
let mut client = default_client();
let cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>> =
Rc::new(RefCell::new(CountingConnectionIdGenerator::default()));
let mut server = Connection::new_server(
test_fixture::DEFAULT_KEYS,
test_fixture::DEFAULT_ALPN,
Rc::clone(&cid_gen),
ConnectionParameters::default(),
)
.unwrap();
connect_force_idle(&mut client, &mut server);
let original_cid = ConnectionId::from(&get_cid(&send_something(&mut client, now())));
client
.migrate(Some(addr_v4()), Some(addr_v4()), false, now())
.unwrap();
// The client now probes the new path.
let probe = client.process_output(now()).dgram().unwrap();
assert_v4_path(&probe, true);
assert_eq!(client.stats().frame_tx.path_challenge, 1);
let probe_cid = ConnectionId::from(&get_cid(&probe));
assert_ne!(original_cid, probe_cid);
// Have the server receive the probe, but separately have it decide to
// retire all of the available connection IDs.
server.test_frame_writer = Some(Box::new(RetireAll { cid_gen }));
let retire_all = send_something(&mut server, now());
server.test_frame_writer = None;
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);
// Have the client receive the NEW_CONNECTION_ID with Retire Prior To.
client.process_input(retire_all, now());
// This packet contains the probe response, which should be fine, but it
// also includes PATH_CHALLENGE for the new path, and the client can't
// respond without a connection ID. We treat this as a connection error.
client.process_input(resp, now());
assert!(matches!(
client.state(),
State::Closing {
error: ConnectionError::Transport(Error::InvalidMigration),
..
}
));
}
/// The timing of when frames arrive can mean that the migration path can
/// get the last available connection ID.
#[test]
fn retire_prior_to_migration_success() {
let mut client = default_client();
let cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>> =
Rc::new(RefCell::new(CountingConnectionIdGenerator::default()));
let mut server = Connection::new_server(
test_fixture::DEFAULT_KEYS,
test_fixture::DEFAULT_ALPN,
Rc::clone(&cid_gen),
ConnectionParameters::default(),
)
.unwrap();
connect_force_idle(&mut client, &mut server);
let original_cid = ConnectionId::from(&get_cid(&send_something(&mut client, now())));
client
.migrate(Some(addr_v4()), Some(addr_v4()), false, now())
.unwrap();
// The client now probes the new path.
let probe = client.process_output(now()).dgram().unwrap();
assert_v4_path(&probe, true);
assert_eq!(client.stats().frame_tx.path_challenge, 1);
let probe_cid = ConnectionId::from(&get_cid(&probe));
assert_ne!(original_cid, probe_cid);
// Have the server receive the probe, but separately have it decide to
// retire all of the available connection IDs.
server.test_frame_writer = Some(Box::new(RetireAll { cid_gen }));
let retire_all = send_something(&mut server, now());
server.test_frame_writer = None;
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);
// Have the client receive the NEW_CONNECTION_ID with Retire Prior To second.
// As this occurs in a very specific order, migration succeeds.
client.process_input(resp, now());
client.process_input(retire_all, now());
// Migration succeeds and the new path gets the last connection ID.
let dgram = send_something(&mut client, now());
assert_v4_path(&dgram, false);
assert_ne!(get_cid(&dgram), original_cid);
assert_ne!(get_cid(&dgram), probe_cid);
}

View File

@ -384,7 +384,7 @@ fn low() {
// The resulting CRYPTO frame beats out the stream data.
let stats_before = server.stats().frame_tx;
server.send_ticket(now, &[0; 2048]).unwrap();
let _ = server.process_output(now);
mem::drop(server.process_output(now));
let stats_after = server.stats().frame_tx;
assert_eq!(stats_after.crypto, stats_before.crypto + 1);
assert_eq!(stats_after.stream, stats_before.stream);
@ -393,7 +393,7 @@ fn low() {
// it is very hard to ensure that the STREAM frame won't also fit.
// However, we can ensure that the next packet doesn't consist of just STREAM.
let stats_before = server.stats().frame_tx;
let _ = server.process_output(now);
mem::drop(server.process_output(now));
let stats_after = server.stats().frame_tx;
assert_eq!(stats_after.crypto, stats_before.crypto + 1);
assert_eq!(stats_after.new_token, 1);

View File

@ -20,6 +20,7 @@ use crate::StreamType;
use neqo_common::qdebug;
use neqo_crypto::AuthenticationStatus;
use std::mem;
use std::time::{Duration, Instant};
use test_fixture::{self, now, split_datagram};
@ -333,7 +334,7 @@ fn pto_handshake_frames() {
let pkt = client.process(pkt.dgram(), now);
now += Duration::from_millis(10);
let _ = server.process(pkt.dgram(), now);
mem::drop(server.process(pkt.dgram(), now));
now += Duration::from_millis(10);
client.authenticated(AuthenticationStatus::Ok, now);
@ -429,7 +430,7 @@ fn loss_recovery_crash() {
let now = now();
// The server sends something, but we will drop this.
let _ = send_something(&mut server, now);
mem::drop(send_something(&mut server, now));
// Then send something again, but let it through.
let ack = send_and_receive(&mut server, &mut client, now);
@ -445,7 +446,7 @@ fn loss_recovery_crash() {
assert!(dgram.is_some());
// This crashes.
let _ = send_something(&mut server, now + AT_LEAST_PTO);
mem::drop(send_something(&mut server, now + AT_LEAST_PTO));
}
// If we receive packets after the PTO timer has fired, we won't clear
@ -460,7 +461,7 @@ fn ack_after_pto() {
let mut now = now();
// The client sends and is forced into a PTO.
let _ = send_something(&mut client, now);
mem::drop(send_something(&mut client, now));
// Jump forward to the PTO and drain the PTO packets.
now += AT_LEAST_PTO;
@ -475,7 +476,7 @@ fn ack_after_pto() {
// delivery is just the thing.
// Note: The server can't ACK anything here, but none of what
// the client has sent so far has been transferred.
let _ = send_something(&mut server, now);
mem::drop(send_something(&mut server, now));
let dgram = send_something(&mut server, now);
// The client is now after a PTO, but if it receives something

View File

@ -11,6 +11,7 @@ use super::{
use crate::addr_valid::{AddressValidation, ValidateAddress};
use std::cell::RefCell;
use std::mem;
use std::rc::Rc;
use std::time::Duration;
use test_fixture::{self, assertions, now};
@ -127,19 +128,19 @@ fn two_tickets_on_timer() {
// We need to wait for release_resumption_token_timer to expire. The timer will be
// set to 3 * PTO
let mut now = now() + 3 * client.pto();
let _ = client.process(None, now);
mem::drop(client.process(None, now));
let mut recv_tokens = get_tokens(&mut client);
assert_eq!(recv_tokens.len(), 1);
let token1 = recv_tokens.pop().unwrap();
// Wai for anottheer 3 * PTO to get the nex okeen.
now += 3 * client.pto();
let _ = client.process(None, now);
mem::drop(client.process(None, now));
let mut recv_tokens = get_tokens(&mut client);
assert_eq!(recv_tokens.len(), 1);
let token2 = recv_tokens.pop().unwrap();
// Wait for 3 * PTO, but now there are no more tokens.
now += 3 * client.pto();
let _ = client.process(None, now);
mem::drop(client.process(None, now));
assert_eq!(get_tokens(&mut client).len(), 0);
assert_ne!(token1.as_ref(), token2.as_ref());

View File

@ -20,6 +20,7 @@ use crate::{Error, StreamId, StreamType};
use neqo_common::{event::Provider, qdebug};
use std::cmp::max;
use std::convert::TryFrom;
use std::mem;
use test_fixture::now;
#[test]
@ -31,7 +32,7 @@ fn stream_create() {
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let _ = server.process(out.dgram(), now());
mem::drop(server.process(out.dgram(), now()));
assert!(maybe_authenticate(&mut client));
let out = client.process(None, now());
@ -41,7 +42,7 @@ fn stream_create() {
assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0);
assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 4);
let _ = server.process(out.dgram(), now());
mem::drop(server.process(out.dgram(), now()));
// server now in State::Connected
assert_eq!(server.stream_create(StreamType::UniDi).unwrap(), 3);
assert_eq!(server.stream_create(StreamType::UniDi).unwrap(), 7);
@ -102,16 +103,16 @@ fn transfer() {
assert!(stream_ids.next().is_none());
let (received1, fin1) = server.stream_recv(first_stream.as_u64(), &mut buf).unwrap();
assert_eq!(received1, 4000);
assert_eq!(fin1, false);
assert!(!fin1);
let (received2, fin2) = server.stream_recv(first_stream.as_u64(), &mut buf).unwrap();
assert_eq!(received2, 140);
assert_eq!(fin2, false);
assert!(!fin2);
let (received3, fin3) = server
.stream_recv(second_stream.as_u64(), &mut buf)
.unwrap();
assert_eq!(received3, 60);
assert_eq!(fin3, true);
assert!(fin3);
}
#[test]
@ -127,11 +128,11 @@ fn report_fin_when_stream_closed_wo_data() {
let stream_id = client.stream_create(StreamType::BiDi).unwrap();
client.stream_send(stream_id, &[0x00]).unwrap();
let out = client.process(None, now());
let _ = server.process(out.dgram(), now());
mem::drop(server.process(out.dgram(), now()));
server.stream_close_send(stream_id).unwrap();
let out = server.process(None, now());
let _ = client.process(out.dgram(), now());
mem::drop(client.process(out.dgram(), now()));
let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. });
assert!(client.events().any(stream_readable));
}
@ -224,7 +225,7 @@ fn do_not_accept_data_after_stop_sending() {
let stream_id = client.stream_create(StreamType::BiDi).unwrap();
client.stream_send(stream_id, &[0x00]).unwrap();
let out = client.process(None, now());
let _ = server.process(out.dgram(), now());
mem::drop(server.process(out.dgram(), now()));
let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. });
assert!(server.events().any(stream_readable));
@ -244,7 +245,7 @@ fn do_not_accept_data_after_stop_sending() {
let out = server.process(out_second_data_frame.dgram(), now());
assert!(!server.events().any(stream_readable));
let _ = client.process(out.dgram(), now());
mem::drop(client.process(out.dgram(), now()));
assert_eq!(
Err(Error::FinalSizeError),
client.stream_send(stream_id, &[0x00])
@ -344,12 +345,12 @@ fn after_fin_is_read_conn_events_for_stream_should_be_removed() {
let out = server.process(None, now()).dgram();
assert!(out.is_some());
let _ = client.process(out, now());
mem::drop(client.process(out, now()));
// read from the stream before checking connection events.
let mut buf = vec![0; 4000];
let (_, fin) = client.stream_recv(id, &mut buf).unwrap();
assert_eq!(fin, true);
assert!(fin);
// Make sure we do not have RecvStreamReadable events for the stream when fin has been read.
let readable_stream_evt =
@ -369,7 +370,7 @@ fn after_stream_stop_sending_is_called_conn_events_for_stream_should_be_removed(
let out = server.process(None, now()).dgram();
assert!(out.is_some());
let _ = client.process(out, now());
mem::drop(client.process(out, now()));
// send stop seending.
client
@ -506,7 +507,7 @@ fn no_dupdata_readable_events() {
let stream_id = client.stream_create(StreamType::BiDi).unwrap();
client.stream_send(stream_id, &[0x00]).unwrap();
let out = client.process(None, now());
let _ = server.process(out.dgram(), now());
mem::drop(server.process(out.dgram(), now()));
// We have a data_readable event.
let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. });
@ -516,7 +517,7 @@ fn no_dupdata_readable_events() {
// therefore there should not be a new DataReadable event.
client.stream_send(stream_id, &[0x00]).unwrap();
let out_second_data_frame = client.process(None, now());
let _ = server.process(out_second_data_frame.dgram(), now());
mem::drop(server.process(out_second_data_frame.dgram(), now()));
assert!(!server.events().any(stream_readable));
// One more frame with a fin will not produce a new DataReadable event, because the
@ -524,7 +525,7 @@ fn no_dupdata_readable_events() {
client.stream_send(stream_id, &[0x00]).unwrap();
client.stream_close_send(stream_id).unwrap();
let out_third_data_frame = client.process(None, now());
let _ = server.process(out_third_data_frame.dgram(), now());
mem::drop(server.process(out_third_data_frame.dgram(), now()));
assert!(!server.events().any(stream_readable));
}
@ -538,7 +539,7 @@ fn no_dupdata_readable_events_empty_last_frame() {
let stream_id = client.stream_create(StreamType::BiDi).unwrap();
client.stream_send(stream_id, &[0x00]).unwrap();
let out = client.process(None, now());
let _ = server.process(out.dgram(), now());
mem::drop(server.process(out.dgram(), now()));
// We have a data_readable event.
let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. });
@ -548,7 +549,7 @@ fn no_dupdata_readable_events_empty_last_frame() {
// the previous stream data has not been read yet.
client.stream_close_send(stream_id).unwrap();
let out_second_data_frame = client.process(None, now());
let _ = server.process(out_second_data_frame.dgram(), now());
mem::drop(server.process(out_second_data_frame.dgram(), now()));
assert!(!server.events().any(stream_readable));
}
@ -570,7 +571,7 @@ fn change_flow_control(stream_type: StreamType, new_fc: u64) {
// Send the stream to the client.
let out = server.process(None, now());
let _ = client.process(out.dgram(), now());
mem::drop(client.process(out.dgram(), now()));
// change max_stream_data for stream_id.
client.set_stream_max_data(stream_id, new_fc).unwrap();
@ -592,7 +593,7 @@ fn change_flow_control(stream_type: StreamType, new_fc: u64) {
// Exchange packets so that client gets all data.
let out4 = client.process(out3.dgram(), now());
let out5 = server.process(out4.dgram(), now());
let _ = client.process(out5.dgram(), now());
mem::drop(client.process(out5.dgram(), now()));
// read all data by client
let mut buf = [0x0; 10000];
@ -600,7 +601,7 @@ fn change_flow_control(stream_type: StreamType, new_fc: u64) {
assert_eq!(u64::try_from(read).unwrap(), max(RECV_BUFFER_START, new_fc));
let out4 = client.process(None, now());
let _ = server.process(out4.dgram(), now());
mem::drop(server.process(out4.dgram(), now()));
let written3 = server.stream_send(stream_id, &[0x0; 10000]).unwrap();
assert_eq!(u64::try_from(written3).unwrap(), new_fc);

View File

@ -10,6 +10,7 @@ use crate::packet::PACKET_BIT_LONG;
use crate::{Error, QuicVersion};
use neqo_common::{Datagram, Decoder, Encoder};
use std::mem;
use std::time::Duration;
use test_fixture::{self, addr, now};
@ -20,14 +21,14 @@ const INITIAL_PTO: Duration = Duration::from_millis(300);
fn unknown_version() {
let mut client = default_client();
// Start the handshake.
let _ = client.process(None, now()).dgram();
mem::drop(client.process(None, now()).dgram());
let mut unknown_version_packet = vec![0x80, 0x1a, 0x1a, 0x1a, 0x1a];
unknown_version_packet.resize(1200, 0x0);
let _ = client.process(
mem::drop(client.process(
Some(Datagram::new(addr(), addr(), unknown_version_packet)),
now(),
);
));
assert_eq!(1, client.stats().dropped_rx);
}

View File

@ -15,10 +15,11 @@ use std::time::Instant;
use neqo_common::{hex, hex_snip_middle, qdebug, qinfo, qtrace, Encoder, Role};
use neqo_crypto::{
hkdf, hp::HpKey, Aead, Agent, AntiReplay, Cipher, Epoch, HandshakeState, Record, RecordList,
ResumptionToken, SymKey, ZeroRttChecker, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
TLS_CHACHA20_POLY1305_SHA256, TLS_CT_HANDSHAKE, TLS_EPOCH_APPLICATION_DATA,
TLS_EPOCH_HANDSHAKE, TLS_EPOCH_INITIAL, TLS_EPOCH_ZERO_RTT, TLS_VERSION_1_3,
hkdf, hp::HpKey, Aead, Agent, AntiReplay, Cipher, Epoch, Error as CryptoError, HandshakeState,
PrivateKey, PublicKey, Record, RecordList, ResumptionToken, SymKey, ZeroRttChecker,
TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_CT_HANDSHAKE,
TLS_EPOCH_APPLICATION_DATA, TLS_EPOCH_HANDSHAKE, TLS_EPOCH_INITIAL, TLS_EPOCH_ZERO_RTT,
TLS_VERSION_1_3,
};
use crate::packet::{PacketBuilder, PacketNumber, QuicVersion};
@ -108,6 +109,35 @@ impl Crypto {
}
}
pub fn server_enable_ech(
&mut self,
config: u8,
public_name: &str,
sk: &PrivateKey,
pk: &PublicKey,
) -> Res<()> {
if let Agent::Server(s) = &mut self.tls {
s.enable_ech(config, public_name, sk, pk)?;
Ok(())
} else {
panic!("not a client");
}
}
pub fn client_enable_ech(&mut self, ech_config_list: impl AsRef<[u8]>) -> Res<()> {
if let Agent::Client(c) = &mut self.tls {
c.enable_ech(ech_config_list)?;
Ok(())
} else {
panic!("not a client");
}
}
/// Get the active ECH configuration, which is empty if ECH is disabled.
pub fn ech_config(&self) -> &[u8] {
self.tls.ech_config()
}
pub fn handshake(
&mut self,
now: Instant,
@ -134,8 +164,9 @@ impl Crypto {
self.buffer_records(output)?;
Ok(self.tls.state())
}
Err(CryptoError::EchRetry(v)) => Err(Error::EchRetry(v)),
Err(e) => {
qinfo!("Handshake failed");
qinfo!("Handshake failed {:?}", e);
Err(match self.tls.alert() {
Some(a) => Error::CryptoAlert(*a),
_ => Error::CryptoError(e),

View File

@ -20,6 +20,11 @@ use neqo_crypto::ResumptionToken;
pub enum ConnectionEvent {
/// Cert authentication needed
AuthenticationNeeded,
/// Encrypted client hello fallback occurred. The certificate for the
/// public name needs to be authenticated.
EchFallbackAuthenticationNeeded {
public_name: String,
},
/// A new uni (read) or bidi stream has been opened by the peer.
NewStream {
stream_id: StreamId,
@ -70,6 +75,10 @@ impl ConnectionEvents {
self.insert(ConnectionEvent::AuthenticationNeeded);
}
pub fn ech_fallback_authentication_needed(&self, public_name: String) {
self.insert(ConnectionEvent::EchFallbackAuthenticationNeeded { public_name });
}
pub fn new_stream(&self, stream_id: StreamId) {
self.insert(ConnectionEvent::NewStream { stream_id });
}

View File

@ -8,7 +8,7 @@
// into flow control frames needing to be sent to the remote.
use crate::frame::{
write_varint_frame, FRAME_TYPE_DATA_BLOCKED, FRAME_TYPE_MAX_DATA, FRAME_TYPE_MAX_STREAMS_BIDI,
FRAME_TYPE_DATA_BLOCKED, FRAME_TYPE_MAX_DATA, FRAME_TYPE_MAX_STREAMS_BIDI,
FRAME_TYPE_MAX_STREAMS_UNIDI, FRAME_TYPE_MAX_STREAM_DATA, FRAME_TYPE_STREAMS_BLOCKED_BIDI,
FRAME_TYPE_STREAMS_BLOCKED_UNIDI, FRAME_TYPE_STREAM_DATA_BLOCKED,
};
@ -134,7 +134,7 @@ impl SenderFlowControl<()> {
stats: &mut FrameStats,
) {
if let Some(limit) = self.blocked_needed() {
if write_varint_frame(builder, &[FRAME_TYPE_DATA_BLOCKED, limit]) {
if builder.write_varint_frame(&[FRAME_TYPE_DATA_BLOCKED, limit]) {
stats.data_blocked += 1;
tokens.push(RecoveryToken::DataBlocked(limit));
self.blocked_sent();
@ -151,10 +151,11 @@ impl SenderFlowControl<StreamId> {
stats: &mut FrameStats,
) {
if let Some(limit) = self.blocked_needed() {
if write_varint_frame(
builder,
&[FRAME_TYPE_STREAM_DATA_BLOCKED, self.subject.as_u64(), limit],
) {
if builder.write_varint_frame(&[
FRAME_TYPE_STREAM_DATA_BLOCKED,
self.subject.as_u64(),
limit,
]) {
stats.stream_data_blocked += 1;
tokens.push(RecoveryToken::StreamDataBlocked {
stream_id: self.subject,
@ -179,7 +180,7 @@ impl SenderFlowControl<StreamType> {
} else {
FRAME_TYPE_STREAMS_BLOCKED_UNIDI
};
if write_varint_frame(builder, &[frame, limit]) {
if builder.write_varint_frame(&[frame, limit]) {
stats.streams_blocked += 1;
tokens.push(RecoveryToken::StreamsBlocked {
stream_type: self.subject,
@ -286,7 +287,7 @@ impl ReceiverFlowControl<()> {
stats: &mut FrameStats,
) {
if let Some(max_allowed) = self.frame_needed() {
if write_varint_frame(builder, &[FRAME_TYPE_MAX_DATA, max_allowed]) {
if builder.write_varint_frame(&[FRAME_TYPE_MAX_DATA, max_allowed]) {
stats.max_data += 1;
tokens.push(RecoveryToken::MaxData(max_allowed));
self.frame_sent(max_allowed);
@ -303,14 +304,11 @@ impl ReceiverFlowControl<StreamId> {
stats: &mut FrameStats,
) {
if let Some(max_allowed) = self.frame_needed() {
if write_varint_frame(
builder,
&[
FRAME_TYPE_MAX_STREAM_DATA,
self.subject.as_u64(),
max_allowed,
],
) {
if builder.write_varint_frame(&[
FRAME_TYPE_MAX_STREAM_DATA,
self.subject.as_u64(),
max_allowed,
]) {
stats.max_stream_data += 1;
tokens.push(RecoveryToken::MaxStreamData {
stream_id: self.subject,
@ -335,7 +333,7 @@ impl ReceiverFlowControl<StreamType> {
} else {
FRAME_TYPE_MAX_STREAMS_UNIDI
};
if write_varint_frame(builder, &[frame, max_streams]) {
if builder.write_varint_frame(&[frame, max_streams]) {
stats.max_streams += 1;
tokens.push(RecoveryToken::MaxStreams {
stream_type: self.subject,

View File

@ -6,10 +6,10 @@
// Directly relating to QUIC frames.
use neqo_common::{qtrace, Decoder, Encoder};
use neqo_common::{qtrace, Decoder};
use crate::cid::MAX_CONNECTION_ID_LEN;
use crate::packet::{PacketBuilder, PacketType};
use crate::packet::PacketType;
use crate::stream_id::{StreamId, StreamType};
use crate::{AppError, ConnectionError, Error, Res, TransportError};
@ -95,24 +95,6 @@ pub struct AckRange {
pub(crate) range: u64,
}
/// A lot of frames here are just a collection of varints.
/// This helper functions writes a frame like that safely, returning `true` if
/// a frame was written.
pub fn write_varint_frame(builder: &mut PacketBuilder, values: &[u64]) -> bool {
let write = builder.remaining()
>= values
.iter()
.map(|&v| Encoder::varint_len(v))
.sum::<usize>();
if write {
for v in values {
builder.encode_varint(*v);
}
debug_assert!(builder.len() <= builder.limit());
};
write
}
#[derive(PartialEq, Debug, Clone)]
pub enum Frame<'a> {
Padding,

View File

@ -8,6 +8,7 @@
#![warn(clippy::use_self)]
use neqo_common::qinfo;
use neqo_crypto::Error as CryptoError;
mod ackrate;
mod addr_valid;
@ -73,9 +74,10 @@ pub enum Error {
ProtocolViolation,
InvalidToken,
ApplicationError,
CryptoError(neqo_crypto::Error),
CryptoError(CryptoError),
QlogError,
CryptoAlert(u8),
EchRetry(Vec<u8>),
// All internal errors from here. Please keep these sorted.
AckedUnsentPacket,
@ -137,16 +139,22 @@ impl Error {
Self::ApplicationError => ERROR_APPLICATION_CLOSE,
Self::NoAvailablePath => 16,
Self::CryptoAlert(a) => 0x100 + u64::from(*a),
// As we have a special error code for ECH fallbacks, we lose the alert.
// Send the server "ech_required" directly.
Self::EchRetry(_) => 0x100 + 121,
// All the rest are internal errors.
_ => 1,
}
}
}
impl From<neqo_crypto::Error> for Error {
fn from(err: neqo_crypto::Error) -> Self {
impl From<CryptoError> for Error {
fn from(err: CryptoError) -> Self {
qinfo!("Crypto operation failed {:?}", err);
Self::CryptoError(err)
match err {
CryptoError::EchRetry(config) => Self::EchRetry(config),
_ => Self::CryptoError(err),
}
}
}

View File

@ -372,6 +372,24 @@ impl PacketBuilder {
);
}
/// A lot of frames here are just a collection of varints.
/// This helper functions writes a frame like that safely, returning `true` if
/// a frame was written.
pub fn write_varint_frame(&mut self, values: &[u64]) -> bool {
let write = self.remaining()
>= values
.iter()
.map(|&v| Encoder::varint_len(v))
.sum::<usize>();
if write {
for v in values {
self.encode_varint(*v);
}
debug_assert!(self.len() <= self.limit());
};
write
}
/// Build the packet and return the encoder.
pub fn build(mut self, crypto: &mut CryptoDxState) -> Res<Encoder> {
if self.len() > self.limit {

View File

@ -17,7 +17,7 @@ use std::time::{Duration, Instant};
use crate::ackrate::{AckRate, PeerAckDelay};
use crate::cc::CongestionControlAlgorithm;
use crate::cid::{ConnectionId, ConnectionIdRef, RemoteConnectionIdEntry};
use crate::cid::{ConnectionId, ConnectionIdRef, ConnectionIdStore, RemoteConnectionIdEntry};
use crate::frame::{
FRAME_TYPE_PATH_CHALLENGE, FRAME_TYPE_PATH_RESPONSE, FRAME_TYPE_RETIRE_CONNECTION_ID,
};
@ -55,7 +55,7 @@ pub type PathRef = Rc<RefCell<Path>>;
/// is exposed to too many paths.
#[derive(Debug, Default)]
pub struct Paths {
/// All of the paths.
/// All of the paths. All of these paths will be permanent.
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.
@ -184,6 +184,17 @@ impl Paths {
debug_assert_eq!(self.paths.len(), MAX_PATHS);
let removed = self.paths.remove(1);
Self::retire(&mut self.to_retire, &removed);
if self
.migration_target
.as_ref()
.map_or(false, |target| Rc::ptr_eq(target, &removed))
{
qinfo!(
[path.borrow()],
"The migration target path had to be removed"
);
self.migration_target = None;
}
debug_assert_eq!(Rc::strong_count(&removed), 1);
}
@ -196,8 +207,8 @@ impl Paths {
}
/// 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.
/// Using 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");
@ -351,6 +362,46 @@ impl Paths {
false
}
/// Retire all of the connection IDs prior to the indicated sequence number.
/// Keep active paths if possible by pulling new connection IDs from the provided store.
/// One slightly non-obvious consequence of this is that if migration is being attempted
/// and the new path cannot obtain a new connection ID, the migration attempt will fail.
pub fn retire_cids(&mut self, retire_prior: u64, store: &mut ConnectionIdStore<[u8; 16]>) {
let to_retire = &mut self.to_retire;
let migration_target = &mut self.migration_target;
// First, tell the store to release any connection IDs that are too old.
let mut retired = store.retire_prior_to(retire_prior);
to_retire.append(&mut retired);
self.paths.retain(|p| {
let current = p.borrow().remote_cid.as_ref().unwrap().sequence_number();
if current < retire_prior {
to_retire.push(current);
let new_cid = store.next();
let has_replacement = new_cid.is_some();
// There must be a connection ID available for the primary path as we
// keep that path at the first index.
debug_assert!(!p.borrow().is_primary() || has_replacement);
p.borrow_mut().remote_cid = new_cid;
if !has_replacement
&& migration_target
.as_ref()
.map_or(false, |target| Rc::ptr_eq(target, p))
{
qinfo!(
[p.borrow()],
"NEW_CONNECTION_ID with Retire Prior To forced migration to fail"
);
*migration_target = None;
}
has_replacement
} else {
true
}
});
}
/// Write out any `RETIRE_CONNECTION_ID` frames that are outstanding.
pub fn write_frames(
&mut self,

View File

@ -34,18 +34,10 @@ pub fn connection_tparams_set(qlog: &mut NeqoQlog, tph: &TransportParametersHand
None,
None,
None,
if let Some(ocid) = remote.get_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID) {
// Cannot use packet::ConnectionId's Display trait implementation
// because it does not include the 0x prefix.
Some(hex(ocid))
} else {
None
},
if let Some(srt) = remote.get_bytes(tparams::STATELESS_RESET_TOKEN) {
Some(hex(srt))
} else {
None
},
remote
.get_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID)
.map(hex),
remote.get_bytes(tparams::STATELESS_RESET_TOKEN).map(hex),
if remote.get_empty(tparams::DISABLE_MIGRATION) {
Some(true)
} else {

View File

@ -401,14 +401,11 @@ impl LossRecoverySpace {
// Housekeeping.
self.remove_old_lost(now, cleanup_delay);
// Packets sent before this time are deemed lost.
let lost_deadline = now - loss_delay;
qtrace!(
"detect lost {}: now={:?} delay={:?} deadline={:?}",
"detect lost {}: now={:?} delay={:?}",
self.space,
now,
loss_delay,
lost_deadline
);
self.first_ooo_time = None;
@ -423,12 +420,13 @@ impl LossRecoverySpace {
// BTreeMap iterates in order of ascending PN
.take_while(|(&k, _)| Some(k) < largest_acked)
{
if packet.time_sent <= lost_deadline {
// Packets sent before now - loss_delay are deemed lost.
if packet.time_sent + loss_delay <= now {
qtrace!(
"lost={}, time sent {:?} is before lost_deadline {:?}",
"lost={}, time sent {:?} is before lost_delay {:?}",
pn,
packet.time_sent,
lost_deadline
loss_delay
);
} else if largest_acked >= Some(*pn + PACKET_THRESHOLD) {
qtrace!(
@ -865,7 +863,7 @@ impl LossRecovery {
.iter()
.chain(self.pto_time(rtt, PacketNumberSpace::Handshake).iter())
.min()
.cloned()
.copied()
}
}

View File

@ -16,7 +16,7 @@ use smallvec::SmallVec;
use crate::events::ConnectionEvents;
use crate::fc::ReceiverFlowControl;
use crate::frame::{write_varint_frame, FRAME_TYPE_STOP_SENDING};
use crate::frame::FRAME_TYPE_STOP_SENDING;
use crate::packet::PacketBuilder;
use crate::recovery::RecoveryToken;
use crate::send_stream::SendStreams;
@ -31,7 +31,9 @@ const RX_STREAM_DATA_WINDOW: u64 = 0x10_0000; // 1MiB
pub const RECV_BUFFER_SIZE: usize = RX_STREAM_DATA_WINDOW as usize;
#[derive(Debug, Default)]
pub(crate) struct RecvStreams(BTreeMap<StreamId, RecvStream>);
pub(crate) struct RecvStreams {
streams: BTreeMap<StreamId, RecvStream>,
}
impl RecvStreams {
pub fn write_frames(
@ -40,7 +42,7 @@ impl RecvStreams {
tokens: &mut Vec<RecoveryToken>,
stats: &mut FrameStats,
) {
for stream in self.0.values_mut() {
for stream in self.streams.values_mut() {
stream.write_frame(builder, tokens, stats);
if builder.is_full() {
return;
@ -49,20 +51,20 @@ impl RecvStreams {
}
pub fn insert(&mut self, id: StreamId, stream: RecvStream) {
self.0.insert(id, stream);
self.streams.insert(id, stream);
}
pub fn get_mut(&mut self, id: StreamId) -> Res<&mut RecvStream> {
self.0.get_mut(&id).ok_or(Error::InvalidStreamId)
self.streams.get_mut(&id).ok_or(Error::InvalidStreamId)
}
pub fn clear(&mut self) {
self.0.clear();
self.streams.clear();
}
pub fn clear_terminal(&mut self, send_streams: &SendStreams, role: Role) -> (u64, u64) {
let recv_to_remove = self
.0
.streams
.iter()
.filter_map(|(id, stream)| {
// Remove all streams for which the receiving is done (or aborted).
@ -78,7 +80,7 @@ impl RecvStreams {
let mut removed_bidi = 0;
let mut removed_uni = 0;
for id in &recv_to_remove {
self.0.remove(&id);
self.streams.remove(&id);
if id.is_remote_initiated(role) {
if id.is_bidi() {
removed_bidi += 1;
@ -596,10 +598,11 @@ impl RecvStream {
// Maybe send STOP_SENDING
RecvStreamState::AbortReading { frame_needed, err } => {
if *frame_needed
&& write_varint_frame(
builder,
&[FRAME_TYPE_STOP_SENDING, self.stream_id.as_u64(), *err],
)
&& builder.write_varint_frame(&[
FRAME_TYPE_STOP_SENDING,
self.stream_id.as_u64(),
*err,
])
{
tokens.push(RecoveryToken::StopSending {
stream_id: self.stream_id,
@ -891,7 +894,7 @@ mod tests {
// test receiving a contig frame and reading it works
s.inbound_stream_frame(false, 0, &[1; 10]).unwrap();
assert_eq!(s.data_ready(), true);
assert!(s.data_ready());
let mut buf = vec![0u8; 100];
assert_eq!(s.read(&mut buf).unwrap(), (10, false));
assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
@ -899,27 +902,27 @@ mod tests {
// test receiving a noncontig frame
s.inbound_stream_frame(false, 12, &[2; 12]).unwrap();
assert_eq!(s.data_ready(), false);
assert!(!s.data_ready());
assert_eq!(s.read(&mut buf).unwrap(), (0, false));
assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
assert_eq!(s.state.recv_buf().unwrap().buffered(), 12);
// another frame that overlaps the first
s.inbound_stream_frame(false, 14, &[3; 8]).unwrap();
assert_eq!(s.data_ready(), false);
assert!(!s.data_ready());
assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
assert_eq!(s.state.recv_buf().unwrap().buffered(), 12);
// fill in the gap, but with a FIN
s.inbound_stream_frame(true, 10, &[4; 6]).unwrap_err();
assert_eq!(s.data_ready(), false);
assert!(!s.data_ready());
assert_eq!(s.read(&mut buf).unwrap(), (0, false));
assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
assert_eq!(s.state.recv_buf().unwrap().buffered(), 12);
// fill in the gap
s.inbound_stream_frame(false, 10, &[5; 10]).unwrap();
assert_eq!(s.data_ready(), true);
assert!(s.data_ready());
assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
assert_eq!(s.state.recv_buf().unwrap().buffered(), 14);
@ -927,7 +930,7 @@ mod tests {
s.inbound_stream_frame(true, 24, &[6; 18]).unwrap();
assert_eq!(s.state.recv_buf().unwrap().retired(), 10);
assert_eq!(s.state.recv_buf().unwrap().buffered(), 32);
assert_eq!(s.data_ready(), true);
assert!(s.data_ready());
assert_eq!(s.read(&mut buf).unwrap(), (32, true));
// Stream now no longer readable (is in DataRead state)
@ -1076,7 +1079,7 @@ mod tests {
s.maybe_send_flowc_update();
assert!(!s.has_frames_to_write());
assert_eq!(s.read(&mut buf).unwrap(), (RECV_BUFFER_SIZE, false));
assert_eq!(s.data_ready(), false);
assert!(!s.data_ready());
s.maybe_send_flowc_update();
// flow msg generated!

View File

@ -21,7 +21,7 @@ use neqo_common::{qdebug, qerror, qinfo, qtrace, Encoder, Role};
use crate::events::ConnectionEvents;
use crate::fc::SenderFlowControl;
use crate::frame::{write_varint_frame, Frame, FRAME_TYPE_RESET_STREAM};
use crate::frame::{Frame, FRAME_TYPE_RESET_STREAM};
use crate::packet::PacketBuilder;
use crate::recovery::RecoveryToken;
use crate::stats::FrameStats;
@ -776,15 +776,12 @@ impl SendStream {
if *priority != Some(p) {
return false;
}
if write_varint_frame(
builder,
&[
FRAME_TYPE_RESET_STREAM,
self.stream_id.as_u64(),
err,
final_size,
],
) {
if builder.write_varint_frame(&[
FRAME_TYPE_RESET_STREAM,
self.stream_id.as_u64(),
err,
final_size,
]) {
tokens.push(RecoveryToken::ResetStream {
stream_id: self.stream_id,
});
@ -1525,7 +1522,7 @@ mod tests {
// Increasing conn max (conn:4, stream:4) will unblock but not emit
// event b/c that happens in Connection::emit_frame() (tested in
// connection.rs)
assert_eq!(conn_fc.borrow_mut().update(4), true);
assert!(conn_fc.borrow_mut().update(4));
let evts = conn_events.events().collect::<Vec<_>>();
assert_eq!(evts.len(), 0);
assert_eq!(s.avail(), 2);

View File

@ -10,7 +10,10 @@ use neqo_common::{
self as common, event::Provider, hex, qdebug, qerror, qinfo, qlog::NeqoQlog, qtrace, qwarn,
timer::Timer, Datagram, Decoder, Role,
};
use neqo_crypto::{AntiReplay, Cipher, ZeroRttCheckResult, ZeroRttChecker};
use neqo_crypto::{
encode_ech_config, AntiReplay, Cipher, PrivateKey, PublicKey, ZeroRttCheckResult,
ZeroRttChecker,
};
pub use crate::addr_valid::ValidateAddress;
use crate::addr_valid::{AddressValidation, AddressValidationResult};
@ -121,6 +124,27 @@ impl InitialDetails {
}
}
struct EchConfig {
config: u8,
public_name: String,
sk: PrivateKey,
pk: PublicKey,
encoded: Vec<u8>,
}
impl EchConfig {
fn new(config: u8, public_name: &str, sk: &PrivateKey, pk: &PublicKey) -> Res<Self> {
let encoded = encode_ech_config(config, public_name, pk)?;
Ok(Self {
config,
public_name: String::from(public_name),
sk: sk.clone(),
pk: pk.clone(),
encoded,
})
}
}
pub struct Server {
/// The names of certificates.
certs: Vec<String>,
@ -154,6 +178,8 @@ pub struct Server {
address_validation: Rc<RefCell<AddressValidation>>,
/// Directory to create qlog traces in
qlog_dir: Option<PathBuf>,
/// Encrypted client hello (ECH) configuration.
ech_config: Option<EchConfig>,
}
impl Server {
@ -193,6 +219,7 @@ impl Server {
timers: Timer::new(now, TIMER_GRANULARITY, TIMER_CAPACITY),
address_validation: Rc::new(RefCell::new(validation)),
qlog_dir: None,
ech_config: None,
})
}
@ -217,6 +244,21 @@ impl Server {
self.preferred_address = Some(spa);
}
pub fn enable_ech(
&mut self,
config: u8,
public_name: &str,
sk: &PrivateKey,
pk: &PublicKey,
) -> Res<()> {
self.ech_config = Some(EchConfig::new(config, public_name, sk, pk)?);
Ok(())
}
pub fn ech_config(&self) -> &[u8] {
self.ech_config.as_ref().map_or(&[], |cfg| &cfg.encoded)
}
fn remove_timer(&mut self, c: &StateRef) {
let last = c.borrow().last_timer;
self.timers.remove(last, |t| Rc::ptr_eq(t, c));
@ -270,11 +312,7 @@ impl Server {
}
fn connection(&self, cid: &ConnectionIdRef) -> Option<StateRef> {
if let Some(c) = self.connections.borrow().get(&cid[..]) {
Some(Rc::clone(&c))
} else {
None
}
self.connections.borrow().get(&cid[..]).map(Rc::clone)
}
fn handle_initial(
@ -421,6 +459,13 @@ impl Server {
}
c.set_validation(Rc::clone(&self.address_validation));
c.set_qlog(self.create_qlog_trace(attempt_key));
if let Some(cfg) = &self.ech_config {
if c.server_enable_ech(cfg.config, &cfg.public_name, &cfg.sk, &cfg.pk)
.is_err()
{
qwarn!([self], "Unable to enable ECH");
}
}
}
fn accept_connection(

View File

@ -127,38 +127,38 @@ mod test {
#[test]
fn bidi_stream_properties() {
let id1 = StreamId::from(16);
assert_eq!(id1.is_bidi(), true);
assert_eq!(id1.is_uni(), false);
assert_eq!(id1.is_client_initiated(), true);
assert_eq!(id1.is_server_initiated(), false);
assert!(id1.is_bidi());
assert!(!id1.is_uni());
assert!(id1.is_client_initiated());
assert!(!id1.is_server_initiated());
assert_eq!(id1.role(), Role::Client);
assert_eq!(id1.is_self_initiated(Role::Client), true);
assert_eq!(id1.is_self_initiated(Role::Server), false);
assert_eq!(id1.is_remote_initiated(Role::Client), false);
assert_eq!(id1.is_remote_initiated(Role::Server), true);
assert_eq!(id1.is_send_only(Role::Server), false);
assert_eq!(id1.is_send_only(Role::Client), false);
assert_eq!(id1.is_recv_only(Role::Server), false);
assert_eq!(id1.is_recv_only(Role::Client), false);
assert!(id1.is_self_initiated(Role::Client));
assert!(!id1.is_self_initiated(Role::Server));
assert!(!id1.is_remote_initiated(Role::Client));
assert!(id1.is_remote_initiated(Role::Server));
assert!(!id1.is_send_only(Role::Server));
assert!(!id1.is_send_only(Role::Client));
assert!(!id1.is_recv_only(Role::Server));
assert!(!id1.is_recv_only(Role::Client));
assert_eq!(id1.as_u64(), 16);
}
#[test]
fn uni_stream_properties() {
let id2 = StreamId::from(35);
assert_eq!(id2.is_bidi(), false);
assert_eq!(id2.is_uni(), true);
assert_eq!(id2.is_client_initiated(), false);
assert_eq!(id2.is_server_initiated(), true);
assert!(!id2.is_bidi());
assert!(id2.is_uni());
assert!(!id2.is_client_initiated());
assert!(id2.is_server_initiated());
assert_eq!(id2.role(), Role::Server);
assert_eq!(id2.is_self_initiated(Role::Client), false);
assert_eq!(id2.is_self_initiated(Role::Server), true);
assert_eq!(id2.is_remote_initiated(Role::Client), true);
assert_eq!(id2.is_remote_initiated(Role::Server), false);
assert_eq!(id2.is_send_only(Role::Server), true);
assert_eq!(id2.is_send_only(Role::Client), false);
assert_eq!(id2.is_recv_only(Role::Server), false);
assert_eq!(id2.is_recv_only(Role::Client), true);
assert!(!id2.is_self_initiated(Role::Client));
assert!(id2.is_self_initiated(Role::Server));
assert!(id2.is_remote_initiated(Role::Client));
assert!(!id2.is_remote_initiated(Role::Server));
assert!(id2.is_send_only(Role::Server));
assert!(!id2.is_send_only(Role::Client));
assert!(!id2.is_recv_only(Role::Server));
assert!(id2.is_recv_only(Role::Client));
assert_eq!(id2.as_u64(), 35);
}
}

View File

@ -570,6 +570,7 @@ where
#[allow(unused_variables)]
mod tests {
use super::*;
use std::mem;
#[test]
fn basic_tps() {
@ -597,10 +598,10 @@ mod tests {
assert_eq!(tps2.get_bytes(ORIGINAL_DESTINATION_CONNECTION_ID), None);
assert_eq!(tps2.get_bytes(INITIAL_SOURCE_CONNECTION_ID), None);
assert_eq!(tps2.get_bytes(RETRY_SOURCE_CONNECTION_ID), None);
assert_eq!(tps2.has_value(ORIGINAL_DESTINATION_CONNECTION_ID), false);
assert_eq!(tps2.has_value(INITIAL_SOURCE_CONNECTION_ID), false);
assert_eq!(tps2.has_value(RETRY_SOURCE_CONNECTION_ID), false);
assert_eq!(tps2.has_value(STATELESS_RESET_TOKEN), true);
assert!(!tps2.has_value(ORIGINAL_DESTINATION_CONNECTION_ID));
assert!(!tps2.has_value(INITIAL_SOURCE_CONNECTION_ID));
assert!(!tps2.has_value(RETRY_SOURCE_CONNECTION_ID));
assert!(tps2.has_value(STATELESS_RESET_TOKEN));
let mut enc = Encoder::default();
tps.encode(&mut enc);
@ -756,7 +757,7 @@ mod tests {
#[test]
#[should_panic]
fn preferred_address_neither() {
let _ = PreferredAddress::new(None, None);
mem::drop(PreferredAddress::new(None, None));
}
#[test]

View File

@ -22,6 +22,7 @@ use test_fixture::{self, default_client, now, CountingConnectionIdGenerator};
use std::cell::RefCell;
use std::convert::TryFrom;
use std::mem;
use std::ops::Range;
use std::rc::Rc;
@ -207,7 +208,7 @@ pub fn get_ticket(server: &mut Server) -> ResumptionToken {
// 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());
mem::drop(server.process(dgram, now()));
// Calling active_connections clears the set of active connections.
assert_eq!(server.active_connections().len(), 1);
ticket

View File

@ -18,6 +18,7 @@ use neqo_common::{hex_with_len, qdebug, qtrace, Datagram, Encoder};
use neqo_crypto::AuthenticationStatus;
use neqo_transport::{server::ValidateAddress, ConnectionError, Error, State, StreamType};
use std::convert::TryFrom;
use std::mem;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::Duration;
use test_fixture::{self, addr, assertions, default_client, now, split_datagram};
@ -39,7 +40,7 @@ fn retry_basic() {
assert!(dgram.is_some());
let dgram = server.process(dgram, now()).dgram(); // Initial, HS
assert!(dgram.is_some());
let _ = client.process(dgram, now()).dgram(); // Ingest, drop any ACK.
mem::drop(client.process(dgram, now()).dgram()); // Ingest, drop any ACK.
client.authenticated(AuthenticationStatus::Ok, now());
let dgram = client.process(None, now()).dgram(); // Send Finished
assert!(dgram.is_some());

View File

@ -13,8 +13,9 @@ use common::{
apply_header_protection, client_initial_aead_and_hp, connect, connected_server,
decode_initial_header, default_server, get_ticket, remove_header_protection,
};
use neqo_common::{qtrace, Datagram, Decoder, Encoder};
use neqo_crypto::{AllowZeroRtt, ZeroRttCheckResult, ZeroRttChecker};
use neqo_crypto::{generate_ech_keys, AllowZeroRtt, ZeroRttCheckResult, ZeroRttChecker};
use neqo_transport::{
server::{ActiveConnectionRef, Server, ValidateAddress},
Connection, ConnectionError, ConnectionParameters, Error, Output, QuicVersion, State,
@ -26,7 +27,7 @@ use test_fixture::{
use std::cell::RefCell;
use std::convert::TryFrom;
use std::mem;
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::Duration;
@ -234,12 +235,12 @@ fn zero_rtt() {
let c4 = client_send();
// 0-RTT packets that arrive before the handshake get dropped.
let _ = server.process(Some(c2), now);
mem::drop(server.process(Some(c2), now));
assert!(server.active_connections().is_empty());
// Now handshake and let another 0-RTT packet in.
let shs = server.process(Some(c1), now).dgram();
let _ = server.process(Some(c3), now);
mem::drop(server.process(Some(c3), now));
// The server will have received two STREAM frames now if it processed both packets.
let active = server.active_connections();
assert_eq!(active.len(), 1);
@ -249,10 +250,10 @@ fn zero_rtt() {
// 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);
mem::drop(server.process(cfin, now));
// The server will drop this last 0-RTT packet.
let _ = server.process(Some(c4), now);
mem::drop(server.process(Some(c4), now));
let active = server.active_connections();
assert_eq!(active.len(), 1);
assert_eq!(active[0].borrow().stats().frame_rx.stream, 2);
@ -546,3 +547,25 @@ fn max_streams_after_0rtt_rejection() {
can_create_streams(&mut client, StreamType::UniDi, MAX_STREAMS_UNIDI);
can_create_streams(&mut client, StreamType::BiDi, MAX_STREAMS_BIDI);
}
#[test]
fn ech() {
// Check that ECH can be used.
let mut server = default_server();
let (sk, pk) = generate_ech_keys().unwrap();
server.enable_ech(0x4a, "public.example", &sk, &pk).unwrap();
let mut client = default_client();
client.client_enable_ech(server.ech_config()).unwrap();
let server_instance = connect(&mut client, &mut server);
assert!(client.tls_info().unwrap().ech_accepted());
assert!(server_instance.borrow().tls_info().unwrap().ech_accepted());
assert!(client.tls_preinfo().unwrap().ech_accepted().unwrap());
assert!(server_instance
.borrow()
.tls_preinfo()
.unwrap()
.ech_accepted()
.unwrap());
}