зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1772092 - Neqo version 0.6.1 r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D156603
This commit is contained in:
Родитель
ddf51b273c
Коммит
f29ba14bfb
|
@ -20,7 +20,7 @@ rev = "bb2039f077a29dba0879372a67e764e6ace8e33f"
|
|||
[source."https://github.com/mozilla/neqo"]
|
||||
git = "https://github.com/mozilla/neqo"
|
||||
replace-with = "vendored-sources"
|
||||
tag = "v0.5.7"
|
||||
tag = "v0.6.1"
|
||||
|
||||
[source."https://github.com/mozilla/mp4parse-rust"]
|
||||
git = "https://github.com/mozilla/mp4parse-rust"
|
||||
|
|
|
@ -191,7 +191,7 @@ dependencies = [
|
|||
"quote",
|
||||
"serde",
|
||||
"syn",
|
||||
"toml 0.5.9",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -418,13 +418,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.56.999"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.59.2"
|
||||
|
@ -901,7 +894,7 @@ version = "0.2.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dff444d80630d7073077d38d40b4501fd518bd2b922c2a55edcc8b0f7be57e6"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1988,7 +1981,7 @@ name = "gecko-profiler"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bindgen 0.59.2",
|
||||
"bindgen",
|
||||
"lazy_static",
|
||||
"mozbuild",
|
||||
"profiler-macros",
|
||||
|
@ -2515,7 +2508,7 @@ name = "http3server"
|
|||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bindgen 0.59.2",
|
||||
"bindgen",
|
||||
"log",
|
||||
"mio 0.6.23",
|
||||
"mio-extras",
|
||||
|
@ -3446,7 +3439,7 @@ dependencies = [
|
|||
name = "mozilla-central-workspace-hack"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"bindgen",
|
||||
"libc",
|
||||
"quote",
|
||||
"serde",
|
||||
|
@ -3565,10 +3558,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-common"
|
||||
version = "0.5.7"
|
||||
version = "0.6.1"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.6.1#e71e860489447fed25b64bd8ec31a804dccb0dad"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"env_logger 0.8.999",
|
||||
"env_logger 0.9.0",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"qlog",
|
||||
|
@ -3577,22 +3571,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-crypto"
|
||||
version = "0.5.7"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.5.7#f3de275b12c40f45718ce43a0482e771ba6cd4b8"
|
||||
version = "0.6.1"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.6.1#e71e860489447fed25b64bd8ec31a804dccb0dad"
|
||||
dependencies = [
|
||||
"bindgen 0.56.999",
|
||||
"bindgen",
|
||||
"log",
|
||||
"mozbuild",
|
||||
"neqo-common",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml 0.4.999",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neqo-http3"
|
||||
version = "0.5.7"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.5.7#f3de275b12c40f45718ce43a0482e771ba6cd4b8"
|
||||
version = "0.6.1"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.6.1#e71e860489447fed25b64bd8ec31a804dccb0dad"
|
||||
dependencies = [
|
||||
"enumset",
|
||||
"lazy_static",
|
||||
|
@ -3609,8 +3603,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-qpack"
|
||||
version = "0.5.7"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.5.7#f3de275b12c40f45718ce43a0482e771ba6cd4b8"
|
||||
version = "0.6.1"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.6.1#e71e860489447fed25b64bd8ec31a804dccb0dad"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -3623,8 +3617,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "neqo-transport"
|
||||
version = "0.5.7"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.5.7#f3de275b12c40f45718ce43a0482e771ba6cd4b8"
|
||||
version = "0.6.1"
|
||||
source = "git+https://github.com/mozilla/neqo?tag=v0.6.1#e71e860489447fed25b64bd8ec31a804dccb0dad"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
|
@ -4074,7 +4068,7 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "054f65db435bb7cf99db2f38bbcf568dfa66c342201df54e700c772b178e2f20"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4957,7 +4951,7 @@ dependencies = [
|
|||
"app_units",
|
||||
"arrayvec",
|
||||
"atomic_refcell",
|
||||
"bindgen 0.59.2",
|
||||
"bindgen",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"cssparser",
|
||||
|
@ -4998,7 +4992,7 @@ dependencies = [
|
|||
"time 0.1.44",
|
||||
"to_shmem",
|
||||
"to_shmem_derive",
|
||||
"toml 0.5.9",
|
||||
"toml",
|
||||
"uluru",
|
||||
"unicode-bidi",
|
||||
"unicode-segmentation",
|
||||
|
@ -5409,13 +5403,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.4.999"
|
||||
dependencies = [
|
||||
"toml 0.5.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
|
@ -5645,7 +5632,7 @@ dependencies = [
|
|||
"extend",
|
||||
"heck",
|
||||
"serde",
|
||||
"toml 0.5.9",
|
||||
"toml",
|
||||
"uniffi_bindgen",
|
||||
]
|
||||
|
||||
|
@ -5731,7 +5718,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"paste",
|
||||
"serde",
|
||||
"toml 0.5.9",
|
||||
"toml",
|
||||
"weedle2",
|
||||
]
|
||||
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -91,9 +91,6 @@ vcpkg = { path = "build/rust/vcpkg" }
|
|||
# Helper crate for integration in the gecko build system.
|
||||
mozbuild = { path = "build/rust/mozbuild" }
|
||||
|
||||
# Patch bindgen 0.56 to 0.59.
|
||||
bindgen = { path = "build/rust/bindgen" }
|
||||
|
||||
# Patch cfg-if 0.1 to 1.0
|
||||
cfg-if = { path = "build/rust/cfg-if" }
|
||||
|
||||
|
@ -112,9 +109,6 @@ tokio-util = { path = "build/rust/tokio-util" }
|
|||
# Patch env_logger 0.8 to 0.9
|
||||
env_logger = { path = "build/rust/env_logger" }
|
||||
|
||||
# Patch toml 0.4 to 0.5
|
||||
toml = { path = "build/rust/toml" }
|
||||
|
||||
# Patch parking_lot 0.12 down to 0.11, which is compatible for most crates that use it, to avoid
|
||||
# dependencies on windows-sys.
|
||||
parking_lot = { path = "build/rust/parking_lot" }
|
||||
|
@ -164,11 +158,6 @@ webext-storage = { git = "https://github.com/mozilla/application-services", rev
|
|||
[patch.crates-io.mio]
|
||||
path = "third_party/rust/mio-0.6.23"
|
||||
|
||||
# Patch neqo 0.5.7 to be compatible with newer versions of the log crate.
|
||||
# https://github.com/mozilla/neqo/pull/1350
|
||||
[patch."https://github.com/mozilla/neqo"]
|
||||
neqo-common = { path = "third_party/rust/neqo-common" }
|
||||
|
||||
# These are used to test UniFFI functionality. We haven't figured out how we
|
||||
# want to publish these yet, so they are only accessible via git. This works
|
||||
# okay, but it means that their dependencies on UniFFI crates will normally
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "bindgen"
|
||||
version = "0.56.999"
|
||||
edition = "2018"
|
||||
license = "BSD-3-Clause"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.bindgen]
|
||||
version = "0.59"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
default = ["bindgen/default"]
|
||||
logging = ["bindgen/logging"]
|
||||
runtime = ["bindgen/runtime"]
|
||||
static = ["bindgen/static"]
|
||||
which-rustfmt = ["bindgen/which-rustfmt"]
|
|
@ -1,26 +0,0 @@
|
|||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
pub use bindgen::*;
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "toml"
|
||||
version = "0.4.999"
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
toml = "0.5"
|
|
@ -1,5 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use toml::*;
|
|
@ -8,10 +8,10 @@ edition = "2018"
|
|||
name = "neqo_glue"
|
||||
|
||||
[dependencies]
|
||||
neqo-http3 = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-transport = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-http3 = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-transport = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring" }
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
||||
|
@ -25,7 +25,7 @@ static_prefs = { path = "../../../modules/libpref/init/static_prefs", optional =
|
|||
winapi = {version = "0.3", features = ["ws2def"] }
|
||||
|
||||
[dependencies.neqo-crypto]
|
||||
tag = "v0.5.7"
|
||||
tag = "v0.6.1"
|
||||
git = "https://github.com/mozilla/neqo"
|
||||
default-features = false
|
||||
features = ["gecko"]
|
||||
|
|
|
@ -5,17 +5,17 @@ authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
neqo-transport = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-http3 = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.5.7", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-transport = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-common = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-http3 = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
neqo-qpack = { tag = "v0.6.1", git = "https://github.com/mozilla/neqo" }
|
||||
mio = "0.6.17"
|
||||
mio-extras = "2.0.5"
|
||||
log = "0.4.0"
|
||||
base64 = "0.13"
|
||||
|
||||
[dependencies.neqo-crypto]
|
||||
tag = "v0.5.7"
|
||||
tag = "v0.6.1"
|
||||
git = "https://github.com/mozilla/neqo"
|
||||
default-features = false
|
||||
features = ["gecko"]
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"9013a62945e20404cfc3624df017feeb3b86e096b6882b0fd2254c4e87d24b6b","build.rs":"a17b1bb1bd3de3fc958f72d4d1357f7bc4432faa26640c95b5fbfccf40579d67","src/codec.rs":"876fe7da558964046765aa2a2d7ebad9d53e1d4b31a1bf233d47b939f417dba1","src/datagram.rs":"569f8d9e34d7ee17144bf63d34136ecd9778da0d337e513f338738c50284615e","src/event.rs":"f60fee9f4b09ef47ff5e4bfa21c07e45ffd5873c292f2605f24d834070127d62","src/header.rs":"b7d4eeb40952b36f71ae1f37ce82c9617af8b84c171576de4eca9d50a3071103","src/hrtime.rs":"45a608ce9f00e2666ce95422a278c6dc0ff4e229b114e7bcf0b4c0d9dc61ad56","src/incrdecoder.rs":"97eb93502afabf13d46de37ca05430c49e876bfa9f013ce264231639eaf9df64","src/lib.rs":"0a3679ab0bc67817097701010881e1c2f48ad1ab0700f12babc46cc59c5c788b","src/log.rs":"b69e492af85e65866cb6588138e8a337dd897d3ce399cb4e9fb8cc04ac042b7f","src/qlog.rs":"ca323c91d61810ebef2ebeb967836dda384a60a9fb492c2b8d1b235a98f2e4bf","src/timer.rs":"e63af7e7df968bf702583f263cfb63e6dca4e599bacffa2de0a6383d85333636","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}
|
||||
{"files":{"Cargo.toml":"1e35362eb965155414173a29d9f0fbe1af3c6a66aa158af6701f235688dacd5c","build.rs":"a17b1bb1bd3de3fc958f72d4d1357f7bc4432faa26640c95b5fbfccf40579d67","src/codec.rs":"5aeb997fd0eca77241d1cc173f0d37aa8d2ec1725d5e3739c28f1999bf7c4bed","src/datagram.rs":"742aa0f39ac24d63431b58e23ebf925e27ec42340e5911020475de5f7f457a6d","src/event.rs":"f60fee9f4b09ef47ff5e4bfa21c07e45ffd5873c292f2605f24d834070127d62","src/header.rs":"b7d4eeb40952b36f71ae1f37ce82c9617af8b84c171576de4eca9d50a3071103","src/hrtime.rs":"c5f9f65a642b5782589a4caaf1e758845daa7ebf56c58dc9ef9f836e48026d29","src/incrdecoder.rs":"458f0228e41018d58f5a83c7895c9ea283f310fcb91c969e22026eb8ca3c84c9","src/lib.rs":"bf3e9922196f09554ef55d24c3331bb4c249d583fbbd9f5815670d9d23160838","src/log.rs":"f1ba46a9c2ef10b27211561f4851f933db941ec85ccd3425376d060034aea051","src/qlog.rs":"ca323c91d61810ebef2ebeb967836dda384a60a9fb492c2b8d1b235a98f2e4bf","src/timer.rs":"e63af7e7df968bf702583f263cfb63e6dca4e599bacffa2de0a6383d85333636","tests/log.rs":"480b165b7907ec642c508b303d63005eee1427115d6973a349eaf6b2242ed18d"},"package":null}
|
|
@ -1,14 +1,15 @@
|
|||
[package]
|
||||
name = "neqo-common"
|
||||
version = "0.5.7"
|
||||
version = "0.6.1"
|
||||
authors = ["Bobby Holley <bobbyholley@gmail.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.57.0"
|
||||
license = "MIT/Apache-2.0"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
log = {version = "0.4.0", default-features = false}
|
||||
env_logger = {version = "0.8", default-features = false}
|
||||
env_logger = {version = "0.9", default-features = false}
|
||||
lazy_static = "1.3.0"
|
||||
qlog = "0.4.0"
|
||||
chrono = "0.4.10"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::hex_with_len;
|
||||
|
||||
|
@ -159,18 +158,18 @@ impl<'a> Decoder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// Implement `Deref` for `Decoder` so that values can be examined without moving the cursor.
|
||||
impl<'a> Deref for Decoder<'a> {
|
||||
type Target = [u8];
|
||||
// Implement `AsRef` for `Decoder` so that values can be examined without
|
||||
// moving the cursor.
|
||||
impl<'a> AsRef<[u8]> for Decoder<'a> {
|
||||
#[must_use]
|
||||
fn deref(&self) -> &[u8] {
|
||||
fn as_ref(&self) -> &'a [u8] {
|
||||
&self.buf[self.offset..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Debug for Decoder<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str(&hex_with_len(&self[..]))
|
||||
f.write_str(&hex_with_len(self.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,7 +198,7 @@ impl<'a, 'b> PartialEq<Decoder<'b>> for Decoder<'a> {
|
|||
}
|
||||
|
||||
/// Encoder is good for building data structures.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
pub struct Encoder {
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
@ -248,11 +247,24 @@ impl Encoder {
|
|||
self.buf.capacity()
|
||||
}
|
||||
|
||||
/// Get the length of the underlying buffer: the number of bytes that have
|
||||
/// been written to the buffer.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
/// Returns true if the encoder buffer contains no elements.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
}
|
||||
|
||||
/// Create a view of the current contents of the buffer.
|
||||
/// Note: for a view of a slice, use `Decoder::new(&enc[s..e])`
|
||||
#[must_use]
|
||||
pub fn as_decoder(&self) -> Decoder {
|
||||
Decoder::new(self)
|
||||
Decoder::new(self.as_ref())
|
||||
}
|
||||
|
||||
/// Don't use this except in testing.
|
||||
|
@ -275,7 +287,7 @@ impl Encoder {
|
|||
|
||||
/// Generic encode routine for arbitrary data.
|
||||
pub fn encode(&mut self, data: &[u8]) -> &mut Self {
|
||||
self.buf.extend_from_slice(data);
|
||||
self.buf.extend_from_slice(data.as_ref());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -317,7 +329,7 @@ impl Encoder {
|
|||
/// # Panics
|
||||
/// When `v` is longer than 2^64.
|
||||
pub fn encode_vec(&mut self, n: usize, v: &[u8]) -> &mut Self {
|
||||
self.encode_uint(n, u64::try_from(v.len()).unwrap())
|
||||
self.encode_uint(n, u64::try_from(v.as_ref().len()).unwrap())
|
||||
.encode(v)
|
||||
}
|
||||
|
||||
|
@ -341,7 +353,7 @@ impl Encoder {
|
|||
/// # Panics
|
||||
/// When `v` is longer than 2^64.
|
||||
pub fn encode_vvec(&mut self, v: &[u8]) -> &mut Self {
|
||||
self.encode_varint(u64::try_from(v.len()).unwrap())
|
||||
self.encode_varint(u64::try_from(v.as_ref().len()).unwrap())
|
||||
.encode(v)
|
||||
}
|
||||
|
||||
|
@ -411,6 +423,12 @@ impl AsRef<[u8]> for Encoder {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for Encoder {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.buf.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Decoder<'a>> for Encoder {
|
||||
#[must_use]
|
||||
fn from(dec: Decoder<'a>) -> Self {
|
||||
|
@ -434,20 +452,6 @@ impl From<Encoder> for Vec<u8> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Deref for Encoder {
|
||||
type Target = [u8];
|
||||
#[must_use]
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.buf[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Encoder {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buf[..]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Decoder, Encoder};
|
||||
|
@ -749,7 +753,7 @@ mod tests {
|
|||
fn encode_vec_with() {
|
||||
let mut enc = Encoder::default();
|
||||
enc.encode_vec_with(2, |enc_inner| {
|
||||
enc_inner.encode(&Encoder::from_hex("02"));
|
||||
enc_inner.encode(Encoder::from_hex("02").as_ref());
|
||||
});
|
||||
assert_eq!(enc, Encoder::from_hex("000102"));
|
||||
}
|
||||
|
@ -774,7 +778,7 @@ mod tests {
|
|||
fn encode_vvec_with() {
|
||||
let mut enc = Encoder::default();
|
||||
enc.encode_vvec_with(|enc_inner| {
|
||||
enc_inner.encode(&Encoder::from_hex("02"));
|
||||
enc_inner.encode(Encoder::from_hex("02").as_ref());
|
||||
});
|
||||
assert_eq!(enc, Encoder::from_hex("0102"));
|
||||
}
|
||||
|
@ -794,7 +798,7 @@ mod tests {
|
|||
fn encode_builder() {
|
||||
let mut enc = Encoder::from_hex("ff");
|
||||
let enc2 = Encoder::from_hex("010234");
|
||||
enc.encode(&enc2);
|
||||
enc.encode(enc2.as_ref());
|
||||
assert_eq!(enc, Encoder::from_hex("ff010234"));
|
||||
}
|
||||
|
||||
|
@ -804,14 +808,14 @@ mod tests {
|
|||
let mut enc = Encoder::from_hex("ff");
|
||||
let enc2 = Encoder::from_hex("010234");
|
||||
let v = enc2.as_decoder();
|
||||
enc.encode(&v);
|
||||
enc.encode(v.as_ref());
|
||||
assert_eq!(enc, Encoder::from_hex("ff010234"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_mutate() {
|
||||
let mut enc = Encoder::from_hex("010234");
|
||||
enc[0] = 0xff;
|
||||
enc.as_mut()[0] = 0xff;
|
||||
assert_eq!(enc, Encoder::from_hex("ff0234"));
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::ops::Deref;
|
|||
|
||||
use crate::hex_with_len;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct Datagram {
|
||||
src: SocketAddr,
|
||||
dst: SocketAddr,
|
||||
|
|
|
@ -82,6 +82,7 @@ impl PeriodSet {
|
|||
#[allow(non_camel_case_types)]
|
||||
mod mac {
|
||||
use std::mem::size_of;
|
||||
use std::ptr::addr_of_mut;
|
||||
|
||||
// These are manually extracted from the many bindings generated
|
||||
// by bindgen when provided with the simple header:
|
||||
|
@ -162,7 +163,7 @@ mod mac {
|
|||
thread_policy_set(
|
||||
pthread_mach_thread_np(pthread_self()),
|
||||
THREAD_TIME_CONSTRAINT_POLICY,
|
||||
&mut policy as *mut thread_time_constraint_policy as *mut _,
|
||||
addr_of_mut!(policy) as _, // horror!
|
||||
THREAD_TIME_CONSTRAINT_POLICY_COUNT,
|
||||
)
|
||||
};
|
||||
|
@ -197,7 +198,7 @@ mod mac {
|
|||
thread_policy_get(
|
||||
pthread_mach_thread_np(pthread_self()),
|
||||
THREAD_TIME_CONSTRAINT_POLICY,
|
||||
&mut policy as *mut thread_time_constraint_policy as *mut _,
|
||||
addr_of_mut!(policy) as _, // horror!
|
||||
&mut count,
|
||||
&mut get_default,
|
||||
)
|
||||
|
|
|
@ -178,7 +178,7 @@ mod tests {
|
|||
|
||||
for tail in 1..db.len() {
|
||||
let split = db.len() - tail;
|
||||
let mut dv = Decoder::from(&db[0..split]);
|
||||
let mut dv = Decoder::from(&db.as_ref()[0..split]);
|
||||
eprintln!(" split at {}: {:?}", split, dv);
|
||||
|
||||
// Clone the basic decoder for each iteration of the loop.
|
||||
|
@ -192,7 +192,7 @@ mod tests {
|
|||
if tail > 1 {
|
||||
assert_eq!(res, None);
|
||||
assert!(dec.min_remaining() > 0);
|
||||
let mut dv = Decoder::from(&db[split..]);
|
||||
let mut dv = Decoder::from(&db.as_ref()[split..]);
|
||||
eprintln!(" split remainder {}: {:?}", split, dv);
|
||||
res = dec.consume(&mut dv);
|
||||
assert_eq!(dv.remaining(), 1);
|
||||
|
@ -230,7 +230,7 @@ mod tests {
|
|||
#[test]
|
||||
fn zero_len() {
|
||||
let enc = Encoder::from_hex("ff");
|
||||
let mut dec = Decoder::new(&enc);
|
||||
let mut dec = Decoder::new(enc.as_ref());
|
||||
let mut incr = IncrementalDecoderBuffer::new(0);
|
||||
assert_eq!(incr.consume(&mut dec), Some(Vec::new()));
|
||||
assert_eq!(dec.remaining(), enc.len());
|
||||
|
@ -244,7 +244,7 @@ mod tests {
|
|||
|
||||
for tail in 1..db.len() {
|
||||
let split = db.len() - tail;
|
||||
let mut dv = Decoder::from(&db[0..split]);
|
||||
let mut dv = Decoder::from(&db.as_ref()[0..split]);
|
||||
eprintln!(" split at {}: {:?}", split, dv);
|
||||
|
||||
// Clone the basic decoder for each iteration of the loop.
|
||||
|
@ -256,7 +256,7 @@ mod tests {
|
|||
if tail > 1 {
|
||||
assert!(!res);
|
||||
assert!(dec.min_remaining() > 0);
|
||||
let mut dv = Decoder::from(&db[split..]);
|
||||
let mut dv = Decoder::from(&db.as_ref()[split..]);
|
||||
eprintln!(" split remainder {}: {:?}", split, dv);
|
||||
res = dec.consume(&mut dv);
|
||||
assert_eq!(dv.remaining(), 1);
|
||||
|
|
|
@ -27,11 +27,13 @@ pub use self::incrdecoder::{
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[must_use]
|
||||
pub fn hex(buf: impl AsRef<[u8]>) -> String {
|
||||
let mut ret = String::with_capacity(buf.as_ref().len() * 2);
|
||||
for b in buf.as_ref() {
|
||||
ret.push_str(&format!("{:02x}", b));
|
||||
write!(&mut ret, "{:02x}", b).unwrap();
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
@ -44,13 +46,13 @@ pub fn hex_snip_middle(buf: impl AsRef<[u8]>) -> String {
|
|||
hex_with_len(buf)
|
||||
} else {
|
||||
let mut ret = String::with_capacity(SHOW_LEN * 2 + 16);
|
||||
ret.push_str(&format!("[{}]: ", buf.len()));
|
||||
write!(&mut ret, "[{}]: ", buf.len()).unwrap();
|
||||
for b in &buf[..SHOW_LEN] {
|
||||
ret.push_str(&format!("{:02x}", b));
|
||||
write!(&mut ret, "{:02x}", b).unwrap();
|
||||
}
|
||||
ret.push_str("..");
|
||||
for b in &buf[buf.len() - SHOW_LEN..] {
|
||||
ret.push_str(&format!("{:02x}", b));
|
||||
write!(&mut ret, "{:02x}", b).unwrap();
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
@ -60,9 +62,9 @@ pub fn hex_snip_middle(buf: impl AsRef<[u8]>) -> String {
|
|||
pub fn hex_with_len(buf: impl AsRef<[u8]>) -> String {
|
||||
let buf = buf.as_ref();
|
||||
let mut ret = String::with_capacity(10 + buf.len() * 2);
|
||||
ret.push_str(&format!("[{}]: ", buf.len()));
|
||||
write!(&mut ret, "[{}]: ", buf.len()).unwrap();
|
||||
for b in buf {
|
||||
ret.push_str(&format!("{:02x}", b));
|
||||
write!(&mut ret, "{:02x}", b).unwrap();
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
@ -76,7 +78,7 @@ pub const fn const_min(a: usize, b: usize) -> usize {
|
|||
[a, b][(a >= b) as usize]
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
/// Client or Server.
|
||||
pub enum Role {
|
||||
Client,
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::Once;
|
||||
use std::time::Instant;
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"561d34c14380159b08a88a2461a31355e63fbf410725dd28a12bbb3769596675","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"d136f82a333b0ee1499e7858fdfc3d630f7ff37501a3c51028a4eeb7e2f136b4","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":"659943cfae3c65be50e92d4b5b499c4580b4c522e9905ba567b434c2081784bc","src/aead.rs":"140f77ffb5016836c970c39c6c3a42db9581a14b797b9cd05386d0dd0831fe63","src/aead_fuzzing.rs":"4e60d5a2ee6dedfd08602fa36318239e731244825df2cb801ca1d88f5f2a41c1","src/agent.rs":"5caad7dfe81b0afbadb4c12e46634961880d12daa4820d9adcfa84c240a36ac2","src/agentio.rs":"bce4c3dfcfa433209a409ac0c0752f8c95ab37bb6239a42f99b83858e8747bd1","src/auth.rs":"e821dac1511691151a6e64b7c7130a07d941dffad4529b2631f20ddd07d3f20c","src/cert.rs":"04d7328ab59a5268f2f48b3f880192bf28d42c09c362ef5906ee66e087c754d1","src/constants.rs":"998e77bee88197a240032c1bfbddcff417a25ba82e576a0d2fe18ee9b63cefc7","src/ech.rs":"1b6ee298855d34310a0d65367b21fdc38678a9c37fc7e1d9579c3c8dfd753377","src/err.rs":"d4dbe63e2faba3a1f08dca015549c32550cb18907592abc3831e05e330f0a93b","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"361277879194dc32f741b8d1894afe5fd3fcc8eb244f7dd5914eeb959b85717d","src/hkdf.rs":"3ff432cc9d40e1dc56e9f983b54b593647c4063a5ae0f16de0a64d033ac9bd94","src/hp.rs":"46a2023c421d89fda8d09b356b648272857fd20ee5cf5829143ac88402b32e4b","src/lib.rs":"db8cbe315dbfd32c187d63000b15a2fc758104b844714a96e47330bbf746be57","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"ae054861719fdead8227220dd5a28b92882756683a436676470b672ee26b9a4e","src/prio.rs":"4224a65f25d7de9bf7d6cb18b15597a39650b3c4fcf7d184a4e4bd7f65cebccd","src/replay.rs":"c9bc0261fe1ae22e7212774c315a2669784e57762ca975a15250d4a33dbf3ea3","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"48790a330994d892742048000bd12460b7eee2c3daaa444481b8527406d0a4c7","src/selfencrypt.rs":"4a9af42ccefbc77c65baedf00ef389de4fa7ed855d7ab3b60542b5931050667d","src/ssl.rs":"32e934e6dc5df4e4b4cbe96bae53921cf09a684959cb5ad3469cd65965f3164c","src/time.rs":"ddecb9f6cb6b3367852943d27fc89fd36d3c0ca0c9b5c9797494b74de2d8b5c7","tests/aead.rs":"a0fe826aa3bfcce22dbe1b06b74823cb2334331ffe6ce6152952613e9e1ccae5","tests/agent.rs":"94819f9eeba2afa0c25adc821755900f1488fd47af6d84d9507a112c29d1752a","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"0fcfa8958686aacb42c56c51c6b234842fe990470d2069a67509869baaa18452","tests/hkdf.rs":"47830c1ea58a02d100522bdde6fabc02bb447ccb85affa0cdc44bc25da1be32a","tests/hp.rs":"92e062538c01fa7a474225714ed238d846ceb8c8feb9d79eb05be6111b00fb1e","tests/init.rs":"fc9e392b1efa0d8efb28952f73ffc05e5348e7b2b69207b60e375c3888a252a2","tests/selfencrypt.rs":"1125c858ec4e0a6994f34d162aa066cb003c61b324f268529ea04bcb641347cb"},"package":null}
|
||||
{"files":{"Cargo.toml":"372b8108f43f8df1f6f26e72e434a2ae0ba00b8b4dba193451eb427738734069","TODO":"ac0f1c2ebcca03f5b3c0cc56c5aedbb030a4b511e438bc07a57361c789f91e9f","bindings/bindings.toml":"cb3e29ac6d1fd7083ffab81494afe1b9a2d9e41c60774438fd9681974c87c252","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":"99df86d0c11da5a8cbcc3be2560e6d764234a21b5d9b5f7ab1b70696e4f8670e","src/aead.rs":"140f77ffb5016836c970c39c6c3a42db9581a14b797b9cd05386d0dd0831fe63","src/aead_fuzzing.rs":"e11ea62b4605b84400edbd5fc394f008fc26d3b67467ba7fe1597b90db4c6a12","src/agent.rs":"1dfab420ca00789f745d440476b272e672ae756536f743d1faa9aa214bc0bcda","src/agentio.rs":"bce4c3dfcfa433209a409ac0c0752f8c95ab37bb6239a42f99b83858e8747bd1","src/auth.rs":"ced1a18f691894984244088020ea25dc1ee678603317f0c7dfc8b8842fa750b4","src/cert.rs":"628fc2384cdeb2a32bfbecb83c99af393df97fd6a76c6e8827e8240c0d47328b","src/constants.rs":"998e77bee88197a240032c1bfbddcff417a25ba82e576a0d2fe18ee9b63cefc7","src/ech.rs":"447f6297f50914249d0c92ec61d74df6c65ce4affceecb8cafe40927e855fe1d","src/err.rs":"f2cc71de2b40d7bba8119eeaee200337db9a0126176ba06e4902d7312facdb58","src/exp.rs":"61586662407359c1ecb8ed4987bc3c702f26ba2e203a091a51b6d6363cbd510f","src/ext.rs":"361277879194dc32f741b8d1894afe5fd3fcc8eb244f7dd5914eeb959b85717d","src/hkdf.rs":"3ff432cc9d40e1dc56e9f983b54b593647c4063a5ae0f16de0a64d033ac9bd94","src/hp.rs":"722a798a7d280b66bd0f8adc6f9135f7d8130f7f0ef45ea12b85d47a88adefda","src/lib.rs":"8d1bfe33999b20eeb5f7eef70d4c634de560de5376029f81b2412bca60176c39","src/once.rs":"b9850384899a1a016e839743d3489c0d4d916e1973746ef8c89872105d7d9736","src/p11.rs":"2ff35a2512bcf894684f24b94640e7fe9d4df6a20264aa3714f2f02fdb99e73d","src/prio.rs":"d9bd43a4d84db70fd552239d0852ea60b960957912f614b929d27dadccca803a","src/replay.rs":"c9bc0261fe1ae22e7212774c315a2669784e57762ca975a15250d4a33dbf3ea3","src/result.rs":"cef34dfcb907723e195b56501132e4560e250b327783cb5e41201da5b63e9b5c","src/secrets.rs":"d5d98e34b100568fecade932832379c52530c70a51ad2d37ee6b2dd008865f01","src/selfencrypt.rs":"f11ae7f6f1e6b602b6e729d71597116fb172190b57102af4f76b22fbe78c8b6a","src/ssl.rs":"ace1a162c3b189d52b5e5f9827c4300b7d47d3b2ae29a976d3bbc46c52328253","src/time.rs":"9bacbed11b0e40402731a9803fd1531272e00e199217c1560130ede50d7020f6","tests/aead.rs":"31b5b4374cc5ca2deee6267c4d5b4858defc74e694ec85af196339a76548a17c","tests/agent.rs":"94819f9eeba2afa0c25adc821755900f1488fd47af6d84d9507a112c29d1752a","tests/ext.rs":"eba9f03accdd598e38292ac88263a81b367d60d5a736a43117a3663de105ec48","tests/handshake.rs":"0fcfa8958686aacb42c56c51c6b234842fe990470d2069a67509869baaa18452","tests/hkdf.rs":"47830c1ea58a02d100522bdde6fabc02bb447ccb85affa0cdc44bc25da1be32a","tests/hp.rs":"316973740210fc1f8166920582795a41347a0aec9024fdc480e2ee83a7a5332d","tests/init.rs":"fc9e392b1efa0d8efb28952f73ffc05e5348e7b2b69207b60e375c3888a252a2","tests/selfencrypt.rs":"1125c858ec4e0a6994f34d162aa066cb003c61b324f268529ea04bcb641347cb"},"package":null}
|
|
@ -1,8 +1,9 @@
|
|||
[package]
|
||||
name = "neqo-crypto"
|
||||
version = "0.5.7"
|
||||
version = "0.6.1"
|
||||
authors = ["Martin Thomson <mt@lowentropy.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.57.0"
|
||||
build = "build.rs"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
|
@ -11,10 +12,10 @@ neqo-common = { path = "../neqo-common" }
|
|||
log = {version = "0.4.0", default-features = false}
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = {version = "0.56", default-features = false, features= ["runtime"]}
|
||||
bindgen = {version = "0.59", default-features = false, features= ["runtime"]}
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
toml = "0.4"
|
||||
toml = "0.5"
|
||||
mozbuild = {version = "0.1", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -137,6 +137,7 @@ variables = [
|
|||
types = [
|
||||
"CERTCertList",
|
||||
"CERTCertListNode",
|
||||
"CK_CHACHA20_PARAMS",
|
||||
"CK_ATTRIBUTE_TYPE",
|
||||
"CK_FLAGS",
|
||||
"CK_MECHANISM_TYPE",
|
||||
|
@ -150,6 +151,9 @@ functions = [
|
|||
"CERT_DestroyCertificate",
|
||||
"CERT_DestroyCertList",
|
||||
"CERT_GetCertificateDer",
|
||||
"PK11_CipherOp",
|
||||
"PK11_CreateContextBySymKey",
|
||||
"PK11_DestroyContext",
|
||||
"PK11_Encrypt",
|
||||
"PK11_ExtractKeyValue",
|
||||
"PK11_FindCertFromNickname",
|
||||
|
@ -184,6 +188,7 @@ enums = [
|
|||
]
|
||||
opaque = [
|
||||
"CERTCertificate",
|
||||
"PK11Context",
|
||||
"PK11SlotInfo",
|
||||
"PK11SymKey",
|
||||
"SECKEYPrivateKey",
|
||||
|
@ -191,15 +196,16 @@ opaque = [
|
|||
]
|
||||
variables = [
|
||||
"CKA_DERIVE",
|
||||
"CKA_ENCRYPT",
|
||||
"CKA_VALUE",
|
||||
"CKF_DERIVE",
|
||||
"CKM_AES_ECB",
|
||||
"CKM_AES_GCM",
|
||||
"CKM_CHACHA20",
|
||||
"CKM_CHACHA20_POLY1305",
|
||||
"CKM_EC_KEY_PAIR_GEN",
|
||||
"CKM_HKDF_DERIVE",
|
||||
"CKM_INVALID_MECHANISM",
|
||||
"CKM_NSS_CHACHA20_CTR",
|
||||
"CKM_NSS_CHACHA20_POLY1305",
|
||||
"PK11_ATTR_INSENSITIVE",
|
||||
"PK11_ATTR_PRIVATE",
|
||||
"PK11_ATTR_PUBLIC",
|
||||
|
|
|
@ -91,7 +91,12 @@ fn setup_clang() {
|
|||
|
||||
fn nss_dir() -> PathBuf {
|
||||
let dir = if let Ok(dir) = env::var("NSS_DIR") {
|
||||
PathBuf::from(dir.trim())
|
||||
let path = PathBuf::from(dir.trim());
|
||||
assert!(
|
||||
!path.is_relative(),
|
||||
"The NSS_DIR environment variable is expected to be an absolute path."
|
||||
);
|
||||
path
|
||||
} else {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dir = Path::new(&out_dir).join("nss");
|
||||
|
@ -256,16 +261,16 @@ fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool
|
|||
|
||||
// Apply the configuration.
|
||||
for v in &bindings.types {
|
||||
builder = builder.whitelist_type(v);
|
||||
builder = builder.allowlist_type(v);
|
||||
}
|
||||
for v in &bindings.functions {
|
||||
builder = builder.whitelist_function(v);
|
||||
builder = builder.allowlist_function(v);
|
||||
}
|
||||
for v in &bindings.variables {
|
||||
builder = builder.whitelist_var(v);
|
||||
builder = builder.allowlist_var(v);
|
||||
}
|
||||
for v in &bindings.exclude {
|
||||
builder = builder.blacklist_item(v);
|
||||
builder = builder.blocklist_item(v);
|
||||
}
|
||||
for v in &bindings.opaque {
|
||||
builder = builder.opaque_type(v);
|
||||
|
|
|
@ -15,9 +15,6 @@ pub struct Aead {}
|
|||
#[allow(clippy::unused_self)]
|
||||
impl Aead {
|
||||
pub fn new(_version: Version, _cipher: Cipher, _secret: &SymKey, _prefix: &str) -> Res<Self> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
panic!("Trying to run a non-debug fuzzing build.");
|
||||
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ use std::time::Instant;
|
|||
/// The maximum number of tickets to remember for a given connection.
|
||||
const MAX_TICKETS: usize = 4;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum HandshakeState {
|
||||
New,
|
||||
InProgress,
|
||||
|
@ -87,8 +87,11 @@ fn get_alpn(fd: *mut ssl::PRFileDesc, pre: bool) -> Res<Option<String>> {
|
|||
|
||||
let alpn = match (pre, alpn_state) {
|
||||
(true, ssl::SSLNextProtoState::SSL_NEXT_PROTO_EARLY_VALUE)
|
||||
| (false, ssl::SSLNextProtoState::SSL_NEXT_PROTO_NEGOTIATED)
|
||||
| (false, ssl::SSLNextProtoState::SSL_NEXT_PROTO_SELECTED) => {
|
||||
| (
|
||||
false,
|
||||
ssl::SSLNextProtoState::SSL_NEXT_PROTO_NEGOTIATED
|
||||
| ssl::SSLNextProtoState::SSL_NEXT_PROTO_SELECTED,
|
||||
) => {
|
||||
chosen.truncate(usize::try_from(chosen_len)?);
|
||||
Some(match String::from_utf8(chosen) {
|
||||
Ok(a) => a,
|
||||
|
@ -191,7 +194,7 @@ impl SecretAgentPreInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SecretAgentInfo {
|
||||
version: Version,
|
||||
cipher: Cipher,
|
||||
|
@ -802,6 +805,8 @@ impl ResumptionToken {
|
|||
pub struct Client {
|
||||
agent: SecretAgent,
|
||||
|
||||
/// The name of the server we're attempting a connection to.
|
||||
server_name: String,
|
||||
/// Records the resumption tokens we've received.
|
||||
resumption: Pin<Box<Vec<ResumptionToken>>>,
|
||||
}
|
||||
|
@ -811,13 +816,15 @@ impl Client {
|
|||
///
|
||||
/// # Errors
|
||||
/// Errors returned if the socket can't be created or configured.
|
||||
pub fn new(server_name: &str) -> Res<Self> {
|
||||
pub fn new(server_name: impl Into<String>) -> Res<Self> {
|
||||
let server_name = server_name.into();
|
||||
let mut agent = SecretAgent::new()?;
|
||||
let url = CString::new(server_name)?;
|
||||
let url = CString::new(server_name.as_bytes())?;
|
||||
secstatus_to_res(unsafe { ssl::SSL_SetURL(agent.fd, url.as_ptr()) })?;
|
||||
agent.ready(false)?;
|
||||
let mut client = Self {
|
||||
agent,
|
||||
server_name,
|
||||
resumption: Box::pin(Vec::new()),
|
||||
};
|
||||
client.ready()?;
|
||||
|
@ -866,6 +873,11 @@ impl Client {
|
|||
ssl::SECSuccess
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn server_name(&self) -> &str {
|
||||
&self.server_name
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> Res<()> {
|
||||
let fd = self.fd;
|
||||
unsafe {
|
||||
|
@ -955,7 +967,7 @@ impl ::std::fmt::Display for Client {
|
|||
}
|
||||
|
||||
/// `ZeroRttCheckResult` encapsulates the options for handling a `ClientHello`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ZeroRttCheckResult {
|
||||
/// Accept 0-RTT.
|
||||
Accept,
|
||||
|
@ -1177,8 +1189,8 @@ impl Deref for Agent {
|
|||
#[must_use]
|
||||
fn deref(&self) -> &SecretAgent {
|
||||
match self {
|
||||
Self::Client(c) => &*c,
|
||||
Self::Server(s) => &*s,
|
||||
Self::Client(c) => &**c,
|
||||
Self::Server(s) => &**s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1186,8 +1198,8 @@ impl Deref for Agent {
|
|||
impl DerefMut for Agent {
|
||||
fn deref_mut(&mut self) -> &mut SecretAgent {
|
||||
match self {
|
||||
Self::Client(c) => &mut *c,
|
||||
Self::Server(s) => &mut *s,
|
||||
Self::Client(c) => &mut **c,
|
||||
Self::Server(s) => &mut **s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
use crate::err::{mozpkix, sec, ssl, PRErrorCode};
|
||||
|
||||
/// The outcome of authentication.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum AuthenticationStatus {
|
||||
Ok,
|
||||
CaInvalid,
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
// except according to those terms.
|
||||
|
||||
use crate::err::secstatus_to_res;
|
||||
use crate::p11::{
|
||||
CERTCertListNode, CERT_GetCertificateDer, CertList, Item, PRCList, SECItem, SECItemArray,
|
||||
};
|
||||
use crate::p11::{CERTCertListNode, CERT_GetCertificateDer, CertList, Item, SECItem, SECItemArray};
|
||||
use crate::ssl::{
|
||||
PRFileDesc, SSL_PeerCertificateChain, SSL_PeerSignedCertTimestamps,
|
||||
SSL_PeerStapledOCSPResponses,
|
||||
|
@ -15,7 +13,7 @@ use crate::ssl::{
|
|||
use neqo_common::qerror;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::ptr::NonNull;
|
||||
use std::ptr::{addr_of, NonNull};
|
||||
|
||||
use std::slice;
|
||||
|
||||
|
@ -90,7 +88,7 @@ impl CertificateInfo {
|
|||
|
||||
fn head(certs: &CertList) -> *const CERTCertListNode {
|
||||
// Three stars: one for the reference, one for the wrapper, one to deference the pointer.
|
||||
unsafe { (&(***certs).list as *const PRCList).cast() }
|
||||
unsafe { addr_of!((***certs).list).cast() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ 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;
|
||||
use std::ptr::{addr_of_mut, null_mut};
|
||||
|
||||
pub use crate::p11::{HpkeAeadId as AeadId, HpkeKdfId as KdfId, HpkeKemId as KemId};
|
||||
pub use crate::ssl::HpkeSymmetricSuite as SymmetricSuite;
|
||||
|
@ -98,6 +98,7 @@ pub fn generate_keys() -> Res<(PrivateKey, PublicKey)> {
|
|||
params.extend_from_slice(oid_slc);
|
||||
|
||||
let mut public_ptr: *mut SECKEYPublicKey = null_mut();
|
||||
let mut param_item = Item::wrap(¶ms);
|
||||
|
||||
// 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) {
|
||||
|
@ -105,7 +106,7 @@ pub fn generate_keys() -> Res<(PrivateKey, PublicKey)> {
|
|||
p11::PK11_GenerateKeyPairWithOpFlags(
|
||||
*slot,
|
||||
p11::CK_MECHANISM_TYPE::from(p11::CKM_EC_KEY_PAIR_GEN),
|
||||
(&mut Item::wrap(¶ms) as *mut SECItem).cast(),
|
||||
addr_of_mut!(param_item).cast(),
|
||||
&mut public_ptr,
|
||||
p11::PK11_ATTR_SESSION | p11::PK11_ATTR_INSENSITIVE | p11::PK11_ATTR_PUBLIC,
|
||||
p11::CK_FLAGS::from(p11::CKF_DERIVE),
|
||||
|
@ -122,7 +123,7 @@ pub fn generate_keys() -> Res<(PrivateKey, PublicKey)> {
|
|||
p11::PK11_GenerateKeyPairWithOpFlags(
|
||||
*slot,
|
||||
p11::CK_MECHANISM_TYPE::from(p11::CKM_EC_KEY_PAIR_GEN),
|
||||
(&mut Item::wrap(¶ms) as *mut SECItem).cast(),
|
||||
addr_of_mut!(param_item).cast(),
|
||||
&mut public_ptr,
|
||||
p11::PK11_ATTR_SESSION | p11::PK11_ATTR_SENSITIVE | p11::PK11_ATTR_PRIVATE,
|
||||
p11::CK_FLAGS::from(p11::CKF_DERIVE),
|
||||
|
|
|
@ -29,11 +29,10 @@ pub mod nspr {
|
|||
pub type Res<T> = Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
|
||||
#[allow(renamed_and_removed_lints, clippy::pub_enum_variant_names)] // rust 1.54 will require a different approach
|
||||
pub enum Error {
|
||||
AeadInitFailure,
|
||||
AeadError,
|
||||
CertificateLoading,
|
||||
CipherInitFailure,
|
||||
CreateSslSocket,
|
||||
EchRetry(Vec<u8>),
|
||||
HkdfError,
|
||||
|
|
|
@ -9,14 +9,17 @@ use crate::constants::{
|
|||
};
|
||||
use crate::err::{secstatus_to_res, Error, Res};
|
||||
use crate::p11::{
|
||||
Item, PK11SymKey, PK11_Encrypt, PK11_GetBlockSize, PK11_GetMechanism, SECItem, SymKey,
|
||||
CKM_AES_ECB, CKM_NSS_CHACHA20_CTR, CK_MECHANISM_TYPE,
|
||||
Context, Item, PK11SymKey, PK11_CipherOp, PK11_CreateContextBySymKey, PK11_Encrypt,
|
||||
PK11_GetBlockSize, SymKey, CKA_ENCRYPT, CKM_AES_ECB, CKM_CHACHA20, CK_ATTRIBUTE_TYPE,
|
||||
CK_CHACHA20_PARAMS, CK_MECHANISM_TYPE,
|
||||
};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::os::raw::{c_char, c_uint};
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::os::raw::{c_char, c_int, c_uint};
|
||||
use std::ptr::{addr_of_mut, null, null_mut};
|
||||
use std::rc::Rc;
|
||||
|
||||
experimental_api!(SSL_HkdfExpandLabelWithMech(
|
||||
version: Version,
|
||||
|
@ -32,29 +35,43 @@ experimental_api!(SSL_HkdfExpandLabelWithMech(
|
|||
));
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HpKey(SymKey);
|
||||
pub enum HpKey {
|
||||
/// An AES encryption context.
|
||||
/// Note: as we need to clone this object, we clone the pointer and
|
||||
/// track references using `Rc`. `PK11Context` can't be used with `PK11_CloneContext`
|
||||
/// as that is not supported for these contexts.
|
||||
Aes(Rc<RefCell<Context>>),
|
||||
/// The ChaCha20 mask has to invoke a new PK11_Encrypt every time as it needs to
|
||||
/// change the counter and nonce on each invocation.
|
||||
Chacha(SymKey),
|
||||
}
|
||||
|
||||
impl Debug for HpKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "HP-{:?}", self.0)
|
||||
write!(f, "HpKey")
|
||||
}
|
||||
}
|
||||
|
||||
impl HpKey {
|
||||
const SAMPLE_SIZE: usize = 16;
|
||||
|
||||
/// QUIC-specific API for extracting a header-protection key.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if HKDF fails or if the label is too long to fit in a `c_uint`.
|
||||
/// # Panics
|
||||
/// When `cipher` is not known to this code.
|
||||
#[allow(clippy::cast_sign_loss)] // Cast for PK11_GetBlockSize is safe.
|
||||
pub fn extract(version: Version, cipher: Cipher, prk: &SymKey, label: &str) -> Res<Self> {
|
||||
const ZERO: &[u8] = &[0; 12];
|
||||
|
||||
let l = label.as_bytes();
|
||||
let mut secret: *mut PK11SymKey = null_mut();
|
||||
|
||||
let (mech, key_size) = match cipher {
|
||||
TLS_AES_128_GCM_SHA256 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 16),
|
||||
TLS_AES_256_GCM_SHA384 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 32),
|
||||
TLS_CHACHA20_POLY1305_SHA256 => (CK_MECHANISM_TYPE::from(CKM_NSS_CHACHA20_CTR), 32),
|
||||
TLS_CHACHA20_POLY1305_SHA256 => (CK_MECHANISM_TYPE::from(CKM_CHACHA20), 32),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
@ -74,18 +91,44 @@ impl HpKey {
|
|||
&mut secret,
|
||||
)
|
||||
}?;
|
||||
let sym_key = SymKey::from_ptr(secret).or(Err(Error::HkdfError))?;
|
||||
Ok(HpKey(sym_key))
|
||||
let key = SymKey::from_ptr(secret).or(Err(Error::HkdfError))?;
|
||||
|
||||
let res = match cipher {
|
||||
TLS_AES_128_GCM_SHA256 | TLS_AES_256_GCM_SHA384 => {
|
||||
let context_ptr = unsafe {
|
||||
PK11_CreateContextBySymKey(
|
||||
mech,
|
||||
CK_ATTRIBUTE_TYPE::from(CKA_ENCRYPT),
|
||||
*key,
|
||||
&Item::wrap(&ZERO[..0]), // Borrow a zero-length slice of ZERO.
|
||||
)
|
||||
};
|
||||
let context = Context::from_ptr(context_ptr).or(Err(Error::CipherInitFailure))?;
|
||||
Self::Aes(Rc::new(RefCell::new(context)))
|
||||
}
|
||||
TLS_CHACHA20_POLY1305_SHA256 => Self::Chacha(key),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
debug_assert_eq!(
|
||||
res.block_size(),
|
||||
usize::try_from(unsafe { PK11_GetBlockSize(mech, null_mut()) }).unwrap()
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get the sample size, which is also the output size.
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
#[must_use]
|
||||
#[allow(clippy::unused_self)] // To maintain an API contract.
|
||||
pub fn sample_size(&self) -> usize {
|
||||
let k: *mut PK11SymKey = *self.0;
|
||||
let mech = unsafe { PK11_GetMechanism(k) };
|
||||
// Cast is safe because block size is always greater than or equal to 0
|
||||
(unsafe { PK11_GetBlockSize(mech, null_mut()) }) as usize
|
||||
Self::SAMPLE_SIZE
|
||||
}
|
||||
|
||||
fn block_size(&self) -> usize {
|
||||
match self {
|
||||
Self::Aes(_) => 16,
|
||||
Self::Chacha(_) => 64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a header protection mask for QUIC.
|
||||
|
@ -96,36 +139,49 @@ impl HpKey {
|
|||
/// # Panics
|
||||
/// When the mechanism for our key is not supported.
|
||||
pub fn mask(&self, sample: &[u8]) -> Res<Vec<u8>> {
|
||||
let k: *mut PK11SymKey = *self.0;
|
||||
let mech = unsafe { PK11_GetMechanism(k) };
|
||||
let block_size = self.sample_size();
|
||||
let mut output = vec![0_u8; self.block_size()];
|
||||
|
||||
let mut output = vec![0_u8; block_size];
|
||||
let output_slice = &mut output[..];
|
||||
let mut output_len: c_uint = 0;
|
||||
|
||||
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 wrapped_sample as *mut SECItem, &zero[..])
|
||||
match self {
|
||||
Self::Aes(context) => {
|
||||
let mut output_len: c_int = 0;
|
||||
secstatus_to_res(unsafe {
|
||||
PK11_CipherOp(
|
||||
**context.borrow_mut(),
|
||||
output.as_mut_ptr(),
|
||||
&mut output_len,
|
||||
c_int::try_from(output.len())?,
|
||||
(&sample[..Self::SAMPLE_SIZE]).as_ptr().cast(),
|
||||
c_int::try_from(Self::SAMPLE_SIZE).unwrap(),
|
||||
)
|
||||
})?;
|
||||
assert_eq!(usize::try_from(output_len).unwrap(), output.len());
|
||||
Ok(output)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
secstatus_to_res(unsafe {
|
||||
PK11_Encrypt(
|
||||
k,
|
||||
mech,
|
||||
iv,
|
||||
output_slice.as_mut_ptr(),
|
||||
&mut output_len,
|
||||
c_uint::try_from(output.len())?,
|
||||
inbuf.as_ptr().cast(),
|
||||
c_uint::try_from(inbuf.len())?,
|
||||
)
|
||||
})?;
|
||||
assert_eq!(output_len as usize, block_size);
|
||||
Ok(output)
|
||||
|
||||
Self::Chacha(key) => {
|
||||
let params: CK_CHACHA20_PARAMS = CK_CHACHA20_PARAMS {
|
||||
pBlockCounter: sample.as_ptr() as *mut u8,
|
||||
blockCounterBits: 32,
|
||||
pNonce: (&sample[4..Self::SAMPLE_SIZE]).as_ptr() as *mut _,
|
||||
ulNonceBits: 96,
|
||||
};
|
||||
let mut output_len: c_uint = 0;
|
||||
let mut param_item = Item::wrap_struct(¶ms);
|
||||
secstatus_to_res(unsafe {
|
||||
PK11_Encrypt(
|
||||
**key,
|
||||
CK_MECHANISM_TYPE::from(CKM_CHACHA20),
|
||||
addr_of_mut!(param_item),
|
||||
(&mut output[..]).as_mut_ptr(),
|
||||
&mut output_len,
|
||||
c_uint::try_from(output.len())?,
|
||||
(&output[..]).as_ptr(),
|
||||
c_uint::try_from(output.len())?,
|
||||
)
|
||||
})?;
|
||||
assert_eq!(usize::try_from(output_len).unwrap(), output.len());
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,10 +73,11 @@ use std::ffi::CString;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::ptr::null;
|
||||
|
||||
const MINIMUM_NSS_VERSION: &str = "3.66";
|
||||
const MINIMUM_NSS_VERSION: &str = "3.74";
|
||||
|
||||
#[allow(non_upper_case_globals, clippy::redundant_static_lifetimes)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[allow(unknown_lints, clippy::borrow_as_ptr)]
|
||||
mod nss {
|
||||
include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
|
||||
}
|
||||
|
|
|
@ -14,13 +14,15 @@ use crate::err::{secstatus_to_res, Error, Res};
|
|||
use neqo_common::hex_with_len;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::raw::{c_int, c_uint};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[allow(unknown_lints, deref_nullptr)] // Until we require rust 1.53 or bindgen#1651 is fixed.
|
||||
#[allow(unknown_lints, deref_nullptr)] // Until bindgen#1651 is fixed.
|
||||
#[allow(clippy::unreadable_literal)]
|
||||
#[allow(unknown_lints, clippy::borrow_as_ptr)]
|
||||
mod nss_p11 {
|
||||
include!(concat!(env!("OUT_DIR"), "/nss_p11.rs"));
|
||||
}
|
||||
|
@ -217,6 +219,11 @@ impl std::fmt::Debug for SymKey {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe fn destroy_pk11_context(ctxt: *mut PK11Context) {
|
||||
PK11_DestroyContext(ctxt, PRBool::from(true));
|
||||
}
|
||||
scoped_ptr!(Context, PK11Context, destroy_pk11_context);
|
||||
|
||||
unsafe fn destroy_secitem(item: *mut SECItem) {
|
||||
SECITEM_FreeItem(item, PRBool::from(true));
|
||||
}
|
||||
|
@ -225,7 +232,8 @@ 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.
|
||||
/// Minimally, it can only be passed as a `const SECItem*` argument to functions,
|
||||
/// or those that treat their argument as `const`.
|
||||
pub fn wrap(buf: &[u8]) -> SECItem {
|
||||
SECItem {
|
||||
type_: SECItemType::siBuffer,
|
||||
|
@ -234,6 +242,18 @@ impl Item {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a wrapper for a struct.
|
||||
/// 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,
|
||||
/// or those that treat their argument as `const`.
|
||||
pub fn wrap_struct<T>(v: &T) -> SECItem {
|
||||
SECItem {
|
||||
type_: SECItemType::siBuffer,
|
||||
data: (v as *const T as *mut T).cast(),
|
||||
len: c_uint::try_from(mem::size_of::<T>()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make an empty `SECItem` for passing as a mutable `SECItem*` argument.
|
||||
pub fn make_empty() -> SECItem {
|
||||
SECItem {
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
// except according to those terms.
|
||||
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![allow(unknown_lints, deref_nullptr)] // Until we require rust 1.53 or bindgen#1651 is fixed.
|
||||
#![allow(unknown_lints, deref_nullptr)] // Until 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::too_many_lines,
|
||||
unknown_lints,
|
||||
clippy::borrow_as_ptr
|
||||
)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/nspr_io.rs"));
|
||||
|
|
|
@ -87,7 +87,7 @@ impl Secrets {
|
|||
}
|
||||
|
||||
fn put(&mut self, dir: SecretDirection, epoch: Epoch, key: SymKey) {
|
||||
qdebug!("{:?} secret available for {:?}", dir, epoch);
|
||||
qdebug!("{:?} secret available for {:?}: {:?}", dir, epoch, key);
|
||||
let keys = match dir {
|
||||
SecretDirection::Read => &mut self.r,
|
||||
SecretDirection::Write => &mut self.w,
|
||||
|
|
|
@ -90,7 +90,7 @@ impl SelfEncrypt {
|
|||
let offset = enc.len();
|
||||
let mut output: Vec<u8> = enc.into();
|
||||
output.resize(encoded_len, 0);
|
||||
cipher.encrypt(0, &extended_aad, plaintext, &mut output[offset..])?;
|
||||
cipher.encrypt(0, extended_aad.as_ref(), plaintext, &mut output[offset..])?;
|
||||
qtrace!(
|
||||
["SelfEncrypt"],
|
||||
"seal {} {} -> {}",
|
||||
|
@ -139,7 +139,8 @@ impl SelfEncrypt {
|
|||
// NSS insists on having extra space available for decryption.
|
||||
let padded_len = ciphertext.len() - offset;
|
||||
let mut output = vec![0; padded_len];
|
||||
let decrypted = aead.decrypt(0, &extended_aad, &ciphertext[offset..], &mut output)?;
|
||||
let decrypted =
|
||||
aead.decrypt(0, extended_aad.as_ref(), &ciphertext[offset..], &mut output)?;
|
||||
let final_len = decrypted.len();
|
||||
output.truncate(final_len);
|
||||
qtrace!(
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
non_snake_case,
|
||||
clippy::cognitive_complexity,
|
||||
clippy::too_many_lines,
|
||||
clippy::upper_case_acronyms
|
||||
clippy::upper_case_acronyms,
|
||||
unknown_lints,
|
||||
clippy::borrow_as_ptr
|
||||
)]
|
||||
#![allow(unknown_lints, deref_nullptr)] // Until we require rust 1.53 or bindgen#1651 is fixed.
|
||||
#![allow(unknown_lints, deref_nullptr)] // Until bindgen#1651 is fixed.
|
||||
|
||||
use crate::constants::Epoch;
|
||||
use crate::err::{secstatus_to_res, Res};
|
||||
|
|
|
@ -78,7 +78,7 @@ pub(crate) fn init() {
|
|||
}
|
||||
|
||||
/// Time wraps Instant and provides conversion functions into `PRTime`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Time {
|
||||
t: Instant,
|
||||
}
|
||||
|
@ -119,8 +119,10 @@ impl TryInto<PRTime> for Time {
|
|||
type Error = Error;
|
||||
fn try_into(self) -> Res<PRTime> {
|
||||
let base = get_base();
|
||||
// TODO(mt) use checked_duration_since when that is available.
|
||||
let delta = self.t.duration_since(base.instant);
|
||||
let delta = self
|
||||
.t
|
||||
.checked_duration_since(base.instant)
|
||||
.ok_or(Error::TimeTravelError)?;
|
||||
if let Ok(d) = PRTime::try_from(delta.as_micros()) {
|
||||
d.checked_add(base.prtime).ok_or(Error::TimeTravelError)
|
||||
} else {
|
||||
|
@ -137,7 +139,7 @@ impl From<Time> for Instant {
|
|||
}
|
||||
|
||||
/// Interval wraps Duration and provides conversion functions into `PRTime`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Interval {
|
||||
d: Duration,
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ fn make_aead(cipher: Cipher) -> Aead {
|
|||
TLS_VERSION_1_3,
|
||||
cipher,
|
||||
&secret,
|
||||
"quic ", // Note the trailing space here.
|
||||
"quic ", // QUICv1 label prefix; note the trailing space here.
|
||||
)
|
||||
.expect("can make an AEAD")
|
||||
}
|
||||
|
|
|
@ -2,18 +2,34 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
|
||||
use neqo_crypto::constants::{
|
||||
Cipher, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_VERSION_1_3,
|
||||
Cipher, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256,
|
||||
TLS_VERSION_1_3,
|
||||
};
|
||||
use neqo_crypto::hkdf;
|
||||
use neqo_crypto::hp::HpKey;
|
||||
use std::mem;
|
||||
use test_fixture::fixture_init;
|
||||
|
||||
fn make_hp(cipher: Cipher) -> HpKey {
|
||||
fixture_init();
|
||||
let ikm = hkdf::import_key(TLS_VERSION_1_3, &[0; 16]).expect("import IKM");
|
||||
let prk = hkdf::extract(TLS_VERSION_1_3, cipher, None, &ikm).expect("extract works");
|
||||
HpKey::extract(TLS_VERSION_1_3, cipher, &prk, "hp").expect("extract label works")
|
||||
}
|
||||
|
||||
fn hp_test(cipher: Cipher, expected: &[u8]) {
|
||||
let hp = make_hp(cipher);
|
||||
let mask = hp.mask(&[0; 16]).expect("should produce a mask");
|
||||
assert_eq!(mask, expected, "first invocation should be correct");
|
||||
|
||||
let hp2 = hp.clone();
|
||||
let mask = hp2.mask(&[0; 16]).expect("clone produces mask");
|
||||
assert_eq!(mask, expected, "clone should produce the same mask");
|
||||
|
||||
let mask = hp.mask(&[0; 16]).expect("should produce a mask again");
|
||||
assert_eq!(mask, expected, "second invocation should be the same");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes128() {
|
||||
const EXPECTED: &[u8] = &[
|
||||
|
@ -21,11 +37,7 @@ fn aes128() {
|
|||
0x14,
|
||||
];
|
||||
|
||||
fixture_init();
|
||||
let mask = make_hp(TLS_AES_128_GCM_SHA256)
|
||||
.mask(&[0; 16])
|
||||
.expect("should produce a mask");
|
||||
assert_eq!(mask, EXPECTED);
|
||||
hp_test(TLS_AES_128_GCM_SHA256, EXPECTED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -35,14 +47,9 @@ fn aes256() {
|
|||
0x2b,
|
||||
];
|
||||
|
||||
fixture_init();
|
||||
let mask = make_hp(TLS_AES_256_GCM_SHA384)
|
||||
.mask(&[0; 16])
|
||||
.expect("should produce a mask");
|
||||
assert_eq!(mask, EXPECTED);
|
||||
hp_test(TLS_AES_256_GCM_SHA384, EXPECTED);
|
||||
}
|
||||
|
||||
#[cfg(feature = "chacha")]
|
||||
#[test]
|
||||
fn chacha20_ctr() {
|
||||
const EXPECTED: &[u8] = &[
|
||||
|
@ -53,9 +60,19 @@ fn chacha20_ctr() {
|
|||
0x2f, 0x52, 0x46, 0x89,
|
||||
];
|
||||
|
||||
fixture_init();
|
||||
let mask = make_hp(TLS_CHACHA20_POLY1305_SHA256)
|
||||
.mask(&[0; 16])
|
||||
.expect("should produce a mask");
|
||||
assert_eq!(mask, EXPECTED);
|
||||
hp_test(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn aes_short() {
|
||||
let hp = make_hp(TLS_AES_128_GCM_SHA256);
|
||||
mem::drop(hp.mask(&[0; 15]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn chacha20_short() {
|
||||
let hp = make_hp(TLS_CHACHA20_POLY1305_SHA256);
|
||||
mem::drop(hp.mask(&[0; 15]));
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"9ff10a0f34e30e86b9c005058e50ba588b39a78330ece04e29e8b1e3ef060471","src/buffered_send_stream.rs":"0e4ad4914451943e49c88565661d88475ab2bbd8756e200e2a19312bafd64847","src/client_events.rs":"9d86145febad2f3fb05007eae3f5ad4834c78dd709fe388f05590405e34a614b","src/conn_params.rs":"00e3f42636f482f91cd6b86d7bebaa85a9f0407143503767fffb66a3cdfbbe43","src/connection.rs":"ef2e70c08a59a8a60c7852c8beb6545e3cb37d8da94302798bdc2fc38e4f76c0","src/connection_client.rs":"fabd645653c5991b8d2ec6f2a3c4e16e32c4faa292da47f5c5eb405ef39f8a7a","src/connection_server.rs":"de1e0359b902b1e98c923a8d5488302a68a3312b466590fdddaee6ec8327813b","src/control_stream_local.rs":"9ceb1aae8079dfca8e2f38fb555d47e84f3001d9501f2c909e8245841974f49c","src/control_stream_remote.rs":"7a261ac7df77e90a428ab0f92457a934a92a8c581462fc1818efd3de0c0ebd69","src/features/extended_connect/mod.rs":"2bc2f0570b11318f3225173001dad1a5f05e4bf60dee49a2bf9d40e3a411e138","src/features/extended_connect/webtransport_session.rs":"791734e1891bd35541be33cbf744d6edee6278e760fedb12839dd052a0cf91ba","src/features/extended_connect/webtransport_streams.rs":"784c5e317bb6af33f653ba82c1a5666b657c2a210263a415e913494f61613464","src/features/mod.rs":"a981ebbd03e7bb7ea2313e883452e44f052c48f28edb7fd53a0825911b490230","src/frames/hframe.rs":"67018ad85ecb9ec0476dafc4b25f6b7015e49531cd19f8e81d204af0e29ee3ea","src/frames/mod.rs":"258dd4bdf2daca19a62cd697d2c7f4709a35668b2b4dce3203675e814c9b40b8","src/frames/reader.rs":"0802cd8b41204bcec424fc6ed704a3bdbed0e5d38444f7a9b0550ad877b076a6","src/frames/tests/hframe.rs":"33a30bb98bb512606a06ae1752e1ed9e4588b7d3f5e9439ec83bb2e779d4ac80","src/frames/tests/mod.rs":"fd2e9d4a28c3bd2fd349f4e3844cefa37e9addb09561e9261b393ca7a37e6c6e","src/frames/tests/reader.rs":"312a3deda7b3a4bbd7afed879c94d0644fce8e34435365ef9cae1fbaa62496af","src/frames/tests/wtframe.rs":"589ebe1e62ce4da63b37b7d22cde7ba572ddbf29336fdcdbbcd0a745f79dacd8","src/frames/wtframe.rs":"1d87964fe76945bfe3e59834632ce1e3a000b5e26164b71bdcd129f8a4e73ae3","src/headers_checks.rs":"0893d48fde97687b712e86457e75f2a1b802e7589ce38df30ff65684d8cf59c0","src/lib.rs":"4876915dd7f03021cce3a166e12e0a3763ac2c44e6ad81d223cda1f555b7a2c2","src/priority.rs":"ae0fa461031893b4f7e0d12666072e7a4da80b1e8a1c0663ab9f9e27b3242754","src/push_controller.rs":"aa2a64180d8cb1b87682d0d8bbc42167188e8e1890261cb4cabb76de1fcc708b","src/qlog.rs":"44b6cdbb1d9d6ca47b793e9dbe531b8fdbd40147375f7e4c89aeab536c5d286b","src/qpack_decoder_receiver.rs":"75008d8ea5d538ee34aca4df72e58489417604ccafb61b064280782d6754dd0d","src/qpack_encoder_receiver.rs":"f95cc7d49e4d442b93d522f14ddfc581629664d84d6e13d03a520e855bbe442d","src/recv_message.rs":"5f70fb474e387653d7982374131b3b0c08417509469f273ccebf842bfcee836f","src/request_target.rs":"9182b641f7a7b55272e0e2e872d24a35b1207f35399221b08b899857c3e873ab","src/send_message.rs":"f3503bf135af5acdb3663a8b591b1db2d160e3dcec37aafd2053f2f150f68d2a","src/server.rs":"3cde23011de0a63ee4900e41368e9319ce100a1584f90bf5463e054adcc8875c","src/server_connection_events.rs":"3d89c2d9a30ee719acfbaae4b7720cb354eb73b11bc6ceb44571d68b05192b8b","src/server_events.rs":"3081fdd1e1950aeecae031452cd683335fb0a9dcec51722e614c5939f747b9d9","src/settings.rs":"8a8919cd31683f476dec281b8b545ea3cedb0c7d60cd1e29b097bae605822d47","src/stream_type_reader.rs":"d63727341d925241ec17c7373d81145aba1464cac4c9eedfc05f24c453435f67","tests/httpconn.rs":"f8d6e6a693d17cf2eb192a730e6fc929bd2814552356ce8d4423a0e3eac8c59d","tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","tests/priority.rs":"a606e5fa03451e09e28c7d5f1820ee85a4567e3969a1690c979761e62123bf54","tests/send_message.rs":"673ae1d0bf2dce46c21ee8353f45f189d2cb64a2f6e137ae38da6b2262ad066e","tests/webtransport/mod.rs":"635c0b0fe682a844f4366335a40b8b3a6539abe30843ee1bcfaf87a34b1d476c","tests/webtransport/negotiation.rs":"fd46a3a77c75dfb701ac075cdb0aabb58f82b5d5c03c5a965412bbf6ad020f00","tests/webtransport/sessions.rs":"5b4d8483ac018ad5a28adad5e778e2ed48db9c441d1354f6cf21d8e5c6f1a8b3","tests/webtransport/streams.rs":"fd5f075d93f0241290566f59f747d95530d2df579890fd0f6b9e79a557c89a67"},"package":null}
|
||||
{"files":{"Cargo.toml":"7dd3f73b8b65f903607977de05ae02595340dd5e7afe6ac5010e400fc440c506","src/buffered_send_stream.rs":"4bc45ca03252dc34ab421a2af3499191b182a619143c61d5609a46377c9a0f3d","src/client_events.rs":"9d86145febad2f3fb05007eae3f5ad4834c78dd709fe388f05590405e34a614b","src/conn_params.rs":"7e33526de9c83c163049a2caf2bff0f997351cc61fad76fb6e8c6ec4b9f09938","src/connection.rs":"72c3c2a3c19481d519f4c1928c51dc7c1d4ab6e6cb2bd9ecfdfc5d5c09f485bb","src/connection_client.rs":"e4914a8e44eb6045615382eacf2499b0195574f19ce36e161ad3d3e186f69ebb","src/connection_server.rs":"de1e0359b902b1e98c923a8d5488302a68a3312b466590fdddaee6ec8327813b","src/control_stream_local.rs":"b86e1f869ad59bf2663501942a1a65d94c1dbc3e8770982459e0b620be4b6cf0","src/control_stream_remote.rs":"7a261ac7df77e90a428ab0f92457a934a92a8c581462fc1818efd3de0c0ebd69","src/features/extended_connect/mod.rs":"2bc2f0570b11318f3225173001dad1a5f05e4bf60dee49a2bf9d40e3a411e138","src/features/extended_connect/webtransport_session.rs":"abf84892c429c2ee79efd8e215bfd9da182163ba859cd24b6ee4ba6becceb6bd","src/features/extended_connect/webtransport_streams.rs":"784c5e317bb6af33f653ba82c1a5666b657c2a210263a415e913494f61613464","src/features/mod.rs":"a981ebbd03e7bb7ea2313e883452e44f052c48f28edb7fd53a0825911b490230","src/frames/hframe.rs":"8206e1a27ad805899f7e722c05dffa92649704bbaf98ff2a70a7ca1d6a55395e","src/frames/mod.rs":"258dd4bdf2daca19a62cd697d2c7f4709a35668b2b4dce3203675e814c9b40b8","src/frames/reader.rs":"0802cd8b41204bcec424fc6ed704a3bdbed0e5d38444f7a9b0550ad877b076a6","src/frames/tests/hframe.rs":"33a30bb98bb512606a06ae1752e1ed9e4588b7d3f5e9439ec83bb2e779d4ac80","src/frames/tests/mod.rs":"4933c519069ee4dac23587588f2b792c12d1363e92d0105e1eb169082e213559","src/frames/tests/reader.rs":"312a3deda7b3a4bbd7afed879c94d0644fce8e34435365ef9cae1fbaa62496af","src/frames/tests/wtframe.rs":"589ebe1e62ce4da63b37b7d22cde7ba572ddbf29336fdcdbbcd0a745f79dacd8","src/frames/wtframe.rs":"0eebdf9a275cd53ee6525f7387941601d119d933203d9b2425377adf8348d425","src/headers_checks.rs":"b80c1da2d9f336fa88f7b7f2a834d8e90e826260811771c7729785fdc92b20d4","src/lib.rs":"58d23d794cf5c68d6f2b68e93e3e7d1c5546d64d5d2357ca7cb7858aeb434124","src/priority.rs":"ae0fa461031893b4f7e0d12666072e7a4da80b1e8a1c0663ab9f9e27b3242754","src/push_controller.rs":"aa2a64180d8cb1b87682d0d8bbc42167188e8e1890261cb4cabb76de1fcc708b","src/qlog.rs":"44b6cdbb1d9d6ca47b793e9dbe531b8fdbd40147375f7e4c89aeab536c5d286b","src/qpack_decoder_receiver.rs":"75008d8ea5d538ee34aca4df72e58489417604ccafb61b064280782d6754dd0d","src/qpack_encoder_receiver.rs":"f95cc7d49e4d442b93d522f14ddfc581629664d84d6e13d03a520e855bbe442d","src/recv_message.rs":"5f70fb474e387653d7982374131b3b0c08417509469f273ccebf842bfcee836f","src/request_target.rs":"9182b641f7a7b55272e0e2e872d24a35b1207f35399221b08b899857c3e873ab","src/send_message.rs":"9e1b22ede2a105a79d7c02178801e1a46b06a80dc1c0d2a7d69b0eea7e89f319","src/server.rs":"ab00f395f7767d733091af3e3317527e5c302b2e5062b33943211ede75f10109","src/server_connection_events.rs":"3d89c2d9a30ee719acfbaae4b7720cb354eb73b11bc6ceb44571d68b05192b8b","src/server_events.rs":"3081fdd1e1950aeecae031452cd683335fb0a9dcec51722e614c5939f747b9d9","src/settings.rs":"e7babcce34c49d897c7d5ed93ef8e9ad02524cebff96a249c2ce84f1b968be21","src/stream_type_reader.rs":"f790b2aaa6758ad85487d98376895b5ee2c3098ffd4586825e1bb0b3c2375c75","tests/httpconn.rs":"f8d6e6a693d17cf2eb192a730e6fc929bd2814552356ce8d4423a0e3eac8c59d","tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","tests/priority.rs":"a606e5fa03451e09e28c7d5f1820ee85a4567e3969a1690c979761e62123bf54","tests/send_message.rs":"673ae1d0bf2dce46c21ee8353f45f189d2cb64a2f6e137ae38da6b2262ad066e","tests/webtransport/mod.rs":"635c0b0fe682a844f4366335a40b8b3a6539abe30843ee1bcfaf87a34b1d476c","tests/webtransport/negotiation.rs":"2da85dfd45e3dfdbab7608768d734e1f150e1b0ba14e982cbb6de16ba62789c2","tests/webtransport/sessions.rs":"5b4d8483ac018ad5a28adad5e778e2ed48db9c441d1354f6cf21d8e5c6f1a8b3","tests/webtransport/streams.rs":"fd5f075d93f0241290566f59f747d95530d2df579890fd0f6b9e79a557c89a67"},"package":null}
|
|
@ -1,8 +1,9 @@
|
|||
[package]
|
||||
name = "neqo-http3"
|
||||
version = "0.5.7"
|
||||
version = "0.6.1"
|
||||
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.57.0"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::Res;
|
|||
use neqo_common::qtrace;
|
||||
use neqo_transport::{Connection, StreamId};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BufferedStream {
|
||||
Uninitialized,
|
||||
Initialized { stream_id: StreamId, buf: Vec<u8> },
|
||||
|
|
|
@ -14,7 +14,7 @@ const QPACK_MAX_BLOCKED_STREAMS_DEFAULT: u16 = 20;
|
|||
const MAX_PUSH_STREAM_DEFAULT: u64 = 0;
|
||||
const WEBTRANSPORT_DEFAULT: bool = false;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Http3Parameters {
|
||||
conn_params: ConnectionParameters,
|
||||
qpack_settings: QpackSettings,
|
||||
|
|
|
@ -203,8 +203,8 @@ impl Http3Connection {
|
|||
self.qpack_decoder.borrow_mut().send(conn)?;
|
||||
match self.qpack_encoder.borrow_mut().send_encoder_updates(conn) {
|
||||
Ok(())
|
||||
| Err(neqo_qpack::Error::EncoderStreamBlocked)
|
||||
| Err(neqo_qpack::Error::DynamicTableFull) => {}
|
||||
| Err(neqo_qpack::Error::EncoderStreamBlocked | neqo_qpack::Error::DynamicTableFull) => {
|
||||
}
|
||||
Err(e) => return Err(Error::QpackError(e)),
|
||||
}
|
||||
Ok(())
|
||||
|
@ -300,9 +300,9 @@ impl Http3Connection {
|
|||
}
|
||||
Ok(ReceiveOutput::ControlFrames(rest))
|
||||
}
|
||||
ReceiveOutput::NewStream(NewStreamType::Push(_))
|
||||
| ReceiveOutput::NewStream(NewStreamType::Http)
|
||||
| ReceiveOutput::NewStream(NewStreamType::WebTransportStream(_)) => Ok(output),
|
||||
ReceiveOutput::NewStream(
|
||||
NewStreamType::Push(_) | NewStreamType::Http | NewStreamType::WebTransportStream(_),
|
||||
) => Ok(output),
|
||||
ReceiveOutput::NewStream(_) => {
|
||||
unreachable!("NewStream should have been handled already")
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ impl Http3Connection {
|
|||
match state {
|
||||
State::Handshaking => {
|
||||
if self.role == Role::Server
|
||||
&& conn.zero_rtt_state() == &ZeroRttState::AcceptedServer
|
||||
&& conn.zero_rtt_state() == ZeroRttState::AcceptedServer
|
||||
{
|
||||
self.state = Http3State::ZeroRtt;
|
||||
self.initialize_http3_connection(conn)?;
|
||||
|
|
|
@ -22,7 +22,7 @@ use neqo_crypto::{agent::CertificateInfo, AuthenticationStatus, ResumptionToken,
|
|||
use neqo_qpack::Stats as QpackStats;
|
||||
use neqo_transport::{
|
||||
AppError, Connection, ConnectionEvent, ConnectionId, ConnectionIdGenerator, Output,
|
||||
QuicVersion, Stats as TransportStats, StreamId, StreamType, ZeroRttState,
|
||||
Stats as TransportStats, StreamId, StreamType, Version, ZeroRttState,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Debug;
|
||||
|
@ -49,13 +49,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn alpn_from_quic_version(version: QuicVersion) -> &'static str {
|
||||
fn alpn_from_quic_version(version: Version) -> &'static str {
|
||||
match version {
|
||||
QuicVersion::Version1 => "h3",
|
||||
QuicVersion::Draft29 => "h3-29",
|
||||
QuicVersion::Draft30 => "h3-30",
|
||||
QuicVersion::Draft31 => "h3-31",
|
||||
QuicVersion::Draft32 => "h3-32",
|
||||
Version::Version2 | Version::Version1 => "h3",
|
||||
Version::Draft29 => "h3-29",
|
||||
Version::Draft30 => "h3-30",
|
||||
Version::Draft31 => "h3-31",
|
||||
Version::Draft32 => "h3-32",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ impl Http3Client {
|
|||
/// Making a `neqo-transport::connection` may produce an error. This can only be a crypto error if
|
||||
/// the socket can't be created or configured.
|
||||
pub fn new(
|
||||
server_name: &str,
|
||||
server_name: impl Into<String>,
|
||||
cid_manager: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
local_addr: SocketAddr,
|
||||
remote_addr: SocketAddr,
|
||||
|
@ -90,12 +90,13 @@ impl Http3Client {
|
|||
&[alpn_from_quic_version(
|
||||
http3_parameters
|
||||
.get_connection_parameters()
|
||||
.get_quic_version(),
|
||||
.get_versions()
|
||||
.initial(),
|
||||
)],
|
||||
cid_manager,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
*http3_parameters.get_connection_parameters(),
|
||||
http3_parameters.get_connection_parameters().clone(),
|
||||
now,
|
||||
)?,
|
||||
http3_parameters,
|
||||
|
@ -105,17 +106,16 @@ impl Http3Client {
|
|||
#[must_use]
|
||||
pub fn new_with_conn(c: Connection, http3_parameters: Http3Parameters) -> Self {
|
||||
let events = Http3ClientEvents::default();
|
||||
let webtransport = http3_parameters.get_webtransport();
|
||||
let push_streams = http3_parameters.get_max_concurrent_push_streams();
|
||||
let mut base_handler = Http3Connection::new(http3_parameters, Role::Client);
|
||||
if http3_parameters.get_webtransport() {
|
||||
if webtransport {
|
||||
base_handler.set_features_listener(events.clone());
|
||||
}
|
||||
Self {
|
||||
conn: c,
|
||||
events: events.clone(),
|
||||
push_handler: Rc::new(RefCell::new(PushController::new(
|
||||
http3_parameters.get_max_concurrent_push_streams(),
|
||||
events,
|
||||
))),
|
||||
push_handler: Rc::new(RefCell::new(PushController::new(push_streams, events))),
|
||||
base_handler,
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +219,7 @@ impl Http3Client {
|
|||
debug_assert_eq!(Ok(true), res);
|
||||
return Err(Error::FatalError);
|
||||
}
|
||||
if *self.conn.zero_rtt_state() == ZeroRttState::Sending {
|
||||
if self.conn.zero_rtt_state() == ZeroRttState::Sending {
|
||||
self.base_handler
|
||||
.set_0rtt_settings(&mut self.conn, settings)?;
|
||||
self.events
|
||||
|
@ -834,17 +834,16 @@ mod tests {
|
|||
use neqo_common::{event::Provider, qtrace, Datagram, Decoder, Encoder};
|
||||
use neqo_crypto::{AllowZeroRtt, AntiReplay, ResumptionToken};
|
||||
use neqo_qpack::{encoder::QPackEncoder, QpackSettings};
|
||||
use neqo_transport::tparams::{self, TransportParameter};
|
||||
use neqo_transport::{
|
||||
ConnectionError, ConnectionEvent, ConnectionParameters, Output, State, StreamId,
|
||||
StreamType, RECV_BUFFER_SIZE, SEND_BUFFER_SIZE,
|
||||
StreamType, Version, RECV_BUFFER_SIZE, SEND_BUFFER_SIZE,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::mem;
|
||||
use std::time::Duration;
|
||||
use test_fixture::{
|
||||
addr, anti_replay, default_server_h3, fixture_init, now, CountingConnectionIdGenerator,
|
||||
DEFAULT_ALPN_H3, DEFAULT_KEYS, DEFAULT_SERVER_NAME,
|
||||
addr, anti_replay, default_server_h3, fixture_init, new_server, now,
|
||||
CountingConnectionIdGenerator, DEFAULT_ALPN_H3, DEFAULT_KEYS, DEFAULT_SERVER_NAME,
|
||||
};
|
||||
|
||||
fn assert_closed(client: &Http3Client, expected: &Error) {
|
||||
|
@ -869,6 +868,11 @@ mod tests {
|
|||
addr(),
|
||||
addr(),
|
||||
Http3Parameters::default()
|
||||
.connection_parameters(
|
||||
// Disable compatible upgrade, which complicates tests.
|
||||
ConnectionParameters::default()
|
||||
.versions(Version::default(), vec![Version::default()]),
|
||||
)
|
||||
.max_table_size_encoder(max_table_size)
|
||||
.max_table_size_decoder(max_table_size)
|
||||
.max_blocked_streams(100)
|
||||
|
@ -1017,9 +1021,9 @@ mod tests {
|
|||
self.settings.encode(&mut enc);
|
||||
assert_eq!(
|
||||
self.conn
|
||||
.stream_send(self.control_stream_id.unwrap(), &enc[..])
|
||||
.stream_send(self.control_stream_id.unwrap(), enc.as_ref())
|
||||
.unwrap(),
|
||||
enc[..].len()
|
||||
enc.len()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1160,19 +1164,10 @@ mod tests {
|
|||
.borrow_mut()
|
||||
.encode_header_block(&mut self.conn, headers, stream_id);
|
||||
let hframe = HFrame::Headers {
|
||||
header_block: header_block.to_vec(),
|
||||
header_block: header_block.as_ref().to_vec(),
|
||||
};
|
||||
hframe.encode(encoder);
|
||||
}
|
||||
|
||||
pub fn set_max_uni_stream(&mut self, max_stream: u64) {
|
||||
self.conn
|
||||
.set_local_tparam(
|
||||
tparams::INITIAL_MAX_STREAMS_UNI,
|
||||
TransportParameter::Integer(max_stream),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn handshake_only(client: &mut Http3Client, server: &mut TestServer) -> Output {
|
||||
|
@ -1424,10 +1419,13 @@ mod tests {
|
|||
client: &mut Http3Client,
|
||||
server: &mut TestServer,
|
||||
stream_id: StreamId,
|
||||
response: &[u8],
|
||||
response: impl AsRef<[u8]>,
|
||||
close_stream: bool,
|
||||
) {
|
||||
let _ = server.conn.stream_send(stream_id, response).unwrap();
|
||||
let _ = server
|
||||
.conn
|
||||
.stream_send(stream_id, response.as_ref())
|
||||
.unwrap();
|
||||
if close_stream {
|
||||
server.conn.stream_close_send(stream_id).unwrap();
|
||||
}
|
||||
|
@ -1459,7 +1457,7 @@ mod tests {
|
|||
};
|
||||
let mut d = Encoder::default();
|
||||
frame.encode(&mut d);
|
||||
let _ = conn.stream_send(stream_id, &d).unwrap();
|
||||
let _ = conn.stream_send(stream_id, d.as_ref()).unwrap();
|
||||
}
|
||||
|
||||
fn send_push_data_and_exchange_packets(
|
||||
|
@ -1500,7 +1498,7 @@ mod tests {
|
|||
frame.encode(&mut d);
|
||||
server
|
||||
.conn
|
||||
.stream_send(server.control_stream_id.unwrap(), &d)
|
||||
.stream_send(server.control_stream_id.unwrap(), d.as_ref())
|
||||
.unwrap();
|
||||
|
||||
let out = server.conn.process(None, now());
|
||||
|
@ -1536,13 +1534,13 @@ mod tests {
|
|||
conn: &mut Connection,
|
||||
push_stream_id: StreamId,
|
||||
push_id: u8,
|
||||
data: &[u8],
|
||||
data: impl AsRef<[u8]>,
|
||||
close_push_stream: bool,
|
||||
) {
|
||||
// send data
|
||||
let _ = conn.stream_send(push_stream_id, PUSH_STREAM_TYPE).unwrap();
|
||||
let _ = conn.stream_send(push_stream_id, &[push_id]).unwrap();
|
||||
let _ = conn.stream_send(push_stream_id, data).unwrap();
|
||||
let _ = conn.stream_send(push_stream_id, data.as_ref()).unwrap();
|
||||
if close_push_stream {
|
||||
conn.stream_close_send(push_stream_id).unwrap();
|
||||
}
|
||||
|
@ -2412,7 +2410,7 @@ mod tests {
|
|||
let mut enc = Encoder::default();
|
||||
data_frame.encode(&mut enc);
|
||||
|
||||
(vec![0_u8; size], enc.to_vec())
|
||||
(vec![0_u8; size], enc.as_ref().to_vec())
|
||||
}
|
||||
|
||||
// Send 2 frames. For the second one we can only send 63 bytes.
|
||||
|
@ -3892,7 +3890,7 @@ mod tests {
|
|||
server.settings.encode(&mut enc);
|
||||
let mut sent = server.conn.stream_send(control_stream, CONTROL_STREAM_TYPE);
|
||||
assert_eq!(sent.unwrap(), CONTROL_STREAM_TYPE.len());
|
||||
sent = server.conn.stream_send(control_stream, &enc);
|
||||
sent = server.conn.stream_send(control_stream, enc.as_ref());
|
||||
assert_eq!(sent.unwrap(), enc.len());
|
||||
|
||||
let out = server.conn.process(None, now());
|
||||
|
@ -4531,7 +4529,10 @@ mod tests {
|
|||
let d_frame = HFrame::Data { len: 3 };
|
||||
d_frame.encode(&mut d);
|
||||
d.encode(&[0x61, 0x62, 0x63]);
|
||||
let _ = server.conn.stream_send(request_stream_id, &d[..]).unwrap();
|
||||
let _ = server
|
||||
.conn
|
||||
.stream_send(request_stream_id, d.as_ref())
|
||||
.unwrap();
|
||||
server.conn.stream_close_send(request_stream_id).unwrap();
|
||||
|
||||
let out = server.conn.process(None, now());
|
||||
|
@ -6087,9 +6088,9 @@ mod tests {
|
|||
for f in H3_RESERVED_FRAME_TYPES {
|
||||
let mut enc = Encoder::default();
|
||||
enc.encode_varint(*f);
|
||||
test_wrong_frame_on_control_stream(&enc);
|
||||
test_wrong_frame_on_push_stream(&enc);
|
||||
test_wrong_frame_on_request_stream(&enc);
|
||||
test_wrong_frame_on_control_stream(enc.as_ref());
|
||||
test_wrong_frame_on_push_stream(enc.as_ref());
|
||||
test_wrong_frame_on_request_stream(enc.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6110,7 +6111,7 @@ mod tests {
|
|||
// The settings frame contains a reserved settings type and some value (0x1).
|
||||
enc.encode_varint(*s);
|
||||
enc.encode_varint(1_u64);
|
||||
let sent = server.conn.stream_send(control_stream, &enc);
|
||||
let sent = server.conn.stream_send(control_stream, enc.as_ref());
|
||||
assert_eq!(sent, Ok(4));
|
||||
let out = server.conn.process(None, now());
|
||||
client.process(out.dgram(), now());
|
||||
|
@ -6369,8 +6370,10 @@ mod tests {
|
|||
#[test]
|
||||
fn client_control_stream_create_failed() {
|
||||
let mut client = default_http3_client();
|
||||
let mut server = TestServer::new();
|
||||
server.set_max_uni_stream(0);
|
||||
let mut server = TestServer::new_with_conn(new_server(
|
||||
DEFAULT_ALPN_H3,
|
||||
ConnectionParameters::default().max_streams(StreamType::UniDi, 0),
|
||||
));
|
||||
handshake_client_error(&mut client, &mut server, &Error::StreamLimitError);
|
||||
}
|
||||
|
||||
|
@ -6378,8 +6381,10 @@ mod tests {
|
|||
#[test]
|
||||
fn client_qpack_stream_create_failed() {
|
||||
let mut client = default_http3_client();
|
||||
let mut server = TestServer::new();
|
||||
server.set_max_uni_stream(2);
|
||||
let mut server = TestServer::new_with_conn(new_server(
|
||||
DEFAULT_ALPN_H3,
|
||||
ConnectionParameters::default().max_streams(StreamType::UniDi, 2),
|
||||
));
|
||||
handshake_client_error(&mut client, &mut server, &Error::StreamLimitError);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ impl ControlStreamLocal {
|
|||
pub fn queue_frame(&mut self, f: &HFrame) {
|
||||
let mut enc = Encoder::default();
|
||||
f.encode(&mut enc);
|
||||
self.stream.buffer(&enc);
|
||||
self.stream.buffer(enc.as_ref());
|
||||
}
|
||||
|
||||
pub fn queue_update_priority(&mut self, stream_id: StreamId) {
|
||||
|
@ -80,7 +80,7 @@ impl ControlStreamLocal {
|
|||
if let Some(hframe) = stream.priority_update_frame() {
|
||||
let mut enc = Encoder::new();
|
||||
hframe.encode(&mut enc);
|
||||
if self.stream.send_atomic(conn, &enc)? {
|
||||
if self.stream.send_atomic(conn, enc.as_ref())? {
|
||||
stream.priority_update_sent();
|
||||
} else {
|
||||
self.outstanding_priority_update.push_front(update_id);
|
||||
|
|
|
@ -377,7 +377,8 @@ impl WebTransportSession {
|
|||
};
|
||||
let mut encoder = Encoder::default();
|
||||
close_frame.encode(&mut encoder);
|
||||
self.control_stream_send.send_data_atomic(conn, &encoder)?;
|
||||
self.control_stream_send
|
||||
.send_data_atomic(conn, encoder.as_ref())?;
|
||||
self.control_stream_send.close(conn)?;
|
||||
self.state = if self.control_stream_send.done() {
|
||||
SessionState::Done
|
||||
|
|
|
@ -26,7 +26,7 @@ pub const H3_FRAME_TYPE_PRIORITY_UPDATE_PUSH: HFrameType = 0xf0701;
|
|||
pub const H3_RESERVED_FRAME_TYPES: &[HFrameType] = &[0x2, 0x6, 0x8, 0x9];
|
||||
|
||||
// data for DATA frame is not read into HFrame::Data.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum HFrame {
|
||||
Data {
|
||||
len: u64, // length of the data
|
||||
|
@ -138,7 +138,7 @@ impl HFrame {
|
|||
|
||||
update_frame.encode(&priority_enc);
|
||||
enc.encode_varint(update_frame.len() as u64);
|
||||
enc.encode(&update_frame);
|
||||
enc.encode(update_frame.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use test_fixture::{default_client, default_server, now};
|
|||
pub fn enc_dec<T: FrameDecoder<T>>(d: &Encoder, st: &str, remaining: usize) -> T {
|
||||
// For data, headers and push_promise we do not read all bytes from the buffer
|
||||
let d2 = Encoder::from_hex(st);
|
||||
assert_eq!(&d[..], &d2[..d.len()]);
|
||||
assert_eq!(d.as_ref(), &d2.as_ref()[..d.as_ref().len()]);
|
||||
|
||||
let mut conn_c = default_client();
|
||||
let mut conn_s = default_server();
|
||||
|
@ -36,7 +36,7 @@ pub fn enc_dec<T: FrameDecoder<T>>(d: &Encoder, st: &str, remaining: usize) -> T
|
|||
|
||||
// conver string into u8 vector
|
||||
let buf = Encoder::from_hex(st);
|
||||
conn_s.stream_send(stream_id, &buf[..]).unwrap();
|
||||
conn_s.stream_send(stream_id, buf.as_ref()).unwrap();
|
||||
let out = conn_s.process(None, now());
|
||||
mem::drop(conn_c.process(out.dgram(), now()));
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ pub(crate) type WebTransportFrameType = u64;
|
|||
const WT_FRAME_CLOSE_SESSION: WebTransportFrameType = 0x2843;
|
||||
const WT_FRAME_CLOSE_MAX_MESSAGE_SIZE: u64 = 1024;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum WebTransportFrame {
|
||||
CloseSession { error: u32, message: String },
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ pub fn headers_valid(headers: &[Header], message_type: MessageType) -> Res<()> {
|
|||
let pseudo_header_mask = match message_type {
|
||||
MessageType::Response => enum_set!(PseudoHeaderState::Status),
|
||||
MessageType::Request => {
|
||||
if method_value == Some(&"CONNECT".to_string()) {
|
||||
if method_value == Some("CONNECT") {
|
||||
PseudoHeaderState::Method | PseudoHeaderState::Authority
|
||||
} else {
|
||||
PseudoHeaderState::Method | PseudoHeaderState::Scheme | PseudoHeaderState::Path
|
||||
|
@ -124,7 +124,7 @@ pub fn headers_valid(headers: &[Header], message_type: MessageType) -> Res<()> {
|
|||
|
||||
if (MessageType::Request == message_type)
|
||||
&& pseudo_state.contains(PseudoHeaderState::Protocol)
|
||||
&& method_value != Some(&"CONNECT".to_string())
|
||||
&& method_value != Some("CONNECT")
|
||||
{
|
||||
return Err(Error::InvalidHeader);
|
||||
}
|
||||
|
|
|
@ -59,12 +59,7 @@ pub use stream_type_reader::NewStreamType;
|
|||
|
||||
type Res<T> = Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(
|
||||
renamed_and_removed_lints,
|
||||
clippy::pub_enum_variant_names,
|
||||
clippy::enum_variant_names
|
||||
)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
HttpNoError,
|
||||
HttpGeneralProtocol,
|
||||
|
@ -153,8 +148,7 @@ impl Error {
|
|||
| Self::HttpId
|
||||
| Self::HttpSettings
|
||||
| Self::HttpMissingSettings
|
||||
| Self::QpackError(QpackError::EncoderStream)
|
||||
| Self::QpackError(QpackError::DecoderStream)
|
||||
| Self::QpackError(QpackError::EncoderStream | QpackError::DecoderStream)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -168,10 +162,9 @@ impl Error {
|
|||
#[must_use]
|
||||
pub fn map_stream_send_errors(err: &Error) -> Self {
|
||||
match err {
|
||||
Self::TransportError(TransportError::InvalidStreamId)
|
||||
| Self::TransportError(TransportError::FinalSizeError) => {
|
||||
Error::TransportStreamDoesNotExist
|
||||
}
|
||||
Self::TransportError(
|
||||
TransportError::InvalidStreamId | TransportError::FinalSizeError,
|
||||
) => Error::TransportStreamDoesNotExist,
|
||||
Self::TransportError(TransportError::InvalidInput) => Error::InvalidInput,
|
||||
_ => {
|
||||
debug_assert!(false, "Unexpected error");
|
||||
|
@ -305,7 +298,7 @@ pub enum Http3StreamType {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum ReceiveOutput {
|
||||
NoOutput,
|
||||
PushStream,
|
||||
|
|
|
@ -210,7 +210,7 @@ impl SendStream for SendMessage {
|
|||
data_frame.encode(&mut enc);
|
||||
let sent_fh = self
|
||||
.stream
|
||||
.send_atomic(conn, &enc)
|
||||
.send_atomic(conn, enc.as_ref())
|
||||
.map_err(|e| Error::map_stream_send_errors(&e))?;
|
||||
debug_assert!(sent_fh);
|
||||
|
||||
|
@ -300,7 +300,7 @@ impl SendStream for SendMessage {
|
|||
};
|
||||
let mut enc = Encoder::default();
|
||||
data_frame.encode(&mut enc);
|
||||
self.stream.buffer(&enc);
|
||||
self.stream.buffer(enc.as_ref());
|
||||
self.stream.buffer(buf);
|
||||
mem::drop(self.stream.send_buffer(conn)?);
|
||||
Ok(())
|
||||
|
|
|
@ -62,9 +62,9 @@ impl Http3Server {
|
|||
protocols,
|
||||
anti_replay,
|
||||
zero_rtt_checker
|
||||
.unwrap_or_else(|| Box::new(HttpZeroRttChecker::new(http3_parameters))),
|
||||
.unwrap_or_else(|| Box::new(HttpZeroRttChecker::new(http3_parameters.clone()))),
|
||||
cid_manager,
|
||||
*http3_parameters.get_connection_parameters(),
|
||||
http3_parameters.get_connection_parameters().clone(),
|
||||
)?,
|
||||
http3_parameters,
|
||||
http3_handlers: HashMap::new(),
|
||||
|
@ -150,10 +150,12 @@ impl Http3Server {
|
|||
|
||||
fn process_events(&mut self, conn: &mut ActiveConnectionRef, now: Instant) {
|
||||
let mut remove = false;
|
||||
let http3_parameters = self.http3_parameters;
|
||||
let http3_parameters = &self.http3_parameters;
|
||||
{
|
||||
let handler = self.http3_handlers.entry(conn.clone()).or_insert_with(|| {
|
||||
Rc::new(RefCell::new(Http3ServerHandler::new(http3_parameters)))
|
||||
Rc::new(RefCell::new(Http3ServerHandler::new(
|
||||
http3_parameters.clone(),
|
||||
)))
|
||||
});
|
||||
handler
|
||||
.borrow_mut()
|
||||
|
@ -342,12 +344,7 @@ mod tests {
|
|||
|
||||
fn assert_closed(hconn: &mut Http3Server, expected: &Error) {
|
||||
let err = ConnectionError::Application(expected.code());
|
||||
let closed = |e| {
|
||||
matches!(e,
|
||||
Http3ServerEvent::StateChange{ state: Http3State::Closing(e), .. }
|
||||
| Http3ServerEvent::StateChange{ state: Http3State::Closed(e), .. }
|
||||
if e == err)
|
||||
};
|
||||
let closed = |e| matches!(e, Http3ServerEvent::StateChange{ state: Http3State::Closing(e) | Http3State::Closed(e), .. } if e == err);
|
||||
assert!(hconn.events().any(closed));
|
||||
}
|
||||
|
||||
|
@ -614,7 +611,7 @@ mod tests {
|
|||
};
|
||||
let mut e = Encoder::default();
|
||||
frame.encode(&mut e);
|
||||
peer_conn.control_send(&e);
|
||||
peer_conn.control_send(e.as_ref());
|
||||
let out = peer_conn.process(None, now());
|
||||
hconn.process(out.dgram(), now());
|
||||
// check if the given connection got closed on invalid stream ids
|
||||
|
@ -1143,7 +1140,7 @@ mod tests {
|
|||
|
||||
/// Perform a handshake, then another with the token from the first.
|
||||
/// The second should always resume, but it might not always accept early data.
|
||||
fn zero_rtt_with_settings(conn_params: Http3Parameters, zero_rtt: &ZeroRttState) {
|
||||
fn zero_rtt_with_settings(conn_params: Http3Parameters, zero_rtt: ZeroRttState) {
|
||||
let (_, mut client) = connect();
|
||||
let token = client.events().find_map(|e| {
|
||||
if let ConnectionEvent::ResumptionToken(token) = e {
|
||||
|
@ -1165,7 +1162,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn zero_rtt() {
|
||||
zero_rtt_with_settings(http3params(DEFAULT_SETTINGS), &ZeroRttState::AcceptedClient);
|
||||
zero_rtt_with_settings(http3params(DEFAULT_SETTINGS), ZeroRttState::AcceptedClient);
|
||||
}
|
||||
|
||||
/// A larger QPACK decoder table size isn't an impediment to 0-RTT.
|
||||
|
@ -1176,7 +1173,7 @@ mod tests {
|
|||
max_table_size_decoder: DEFAULT_SETTINGS.max_table_size_decoder + 1,
|
||||
..DEFAULT_SETTINGS
|
||||
}),
|
||||
&ZeroRttState::AcceptedClient,
|
||||
ZeroRttState::AcceptedClient,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1188,7 +1185,7 @@ mod tests {
|
|||
max_table_size_decoder: DEFAULT_SETTINGS.max_table_size_decoder - 1,
|
||||
..DEFAULT_SETTINGS
|
||||
}),
|
||||
&ZeroRttState::Rejected,
|
||||
ZeroRttState::Rejected,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1200,7 +1197,7 @@ mod tests {
|
|||
max_blocked_streams: DEFAULT_SETTINGS.max_blocked_streams + 1,
|
||||
..DEFAULT_SETTINGS
|
||||
}),
|
||||
&ZeroRttState::AcceptedClient,
|
||||
ZeroRttState::AcceptedClient,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1212,7 +1209,7 @@ mod tests {
|
|||
max_blocked_streams: DEFAULT_SETTINGS.max_blocked_streams - 1,
|
||||
..DEFAULT_SETTINGS
|
||||
}),
|
||||
&ZeroRttState::Rejected,
|
||||
ZeroRttState::Rejected,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1224,7 +1221,7 @@ mod tests {
|
|||
max_table_size_encoder: DEFAULT_SETTINGS.max_table_size_encoder - 1,
|
||||
..DEFAULT_SETTINGS
|
||||
}),
|
||||
&ZeroRttState::AcceptedClient,
|
||||
ZeroRttState::AcceptedClient,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1304,6 +1301,6 @@ mod tests {
|
|||
|
||||
connect_transport(&mut server, &mut client, true);
|
||||
assert!(client.tls_info().unwrap().resumed());
|
||||
assert_eq!(client.zero_rtt_state(), &ZeroRttState::Rejected);
|
||||
assert_eq!(client.zero_rtt_state(), ZeroRttState::Rejected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ const SETTINGS_ENABLE_WEB_TRANSPORT: SettingsType = 0x2b60_3742;
|
|||
|
||||
pub const H3_RESERVED_SETTINGS: &[SettingsType] = &[0x2, 0x3, 0x4, 0x5];
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Copy)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Copy)]
|
||||
pub enum HSettingType {
|
||||
MaxHeaderListSize,
|
||||
MaxTableCapacity,
|
||||
|
@ -41,7 +41,7 @@ fn hsetting_default(setting_type: HSettingType) -> u64 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HSetting {
|
||||
pub setting_type: HSettingType,
|
||||
pub value: u64,
|
||||
|
@ -57,7 +57,7 @@ impl HSetting {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct HSettings {
|
||||
settings: Vec<HSetting>,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ pub const HTTP3_UNI_STREAM_TYPE_PUSH: u64 = 0x1;
|
|||
pub const WEBTRANSPORT_UNI_STREAM: u64 = 0x54;
|
||||
pub const WEBTRANSPORT_STREAM: u64 = 0x41;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NewStreamType {
|
||||
Control,
|
||||
Decoder,
|
||||
|
@ -59,7 +59,6 @@ impl NewStreamType {
|
|||
}
|
||||
(_, StreamType::BiDi, Role::Server) => Err(Error::HttpFrame),
|
||||
(HTTP3_UNI_STREAM_TYPE_PUSH, StreamType::UniDi, Role::Server)
|
||||
| (H3_FRAME_TYPE_HEADERS, StreamType::BiDi, Role::Client)
|
||||
| (_, StreamType::BiDi, Role::Client) => Err(Error::HttpStreamCreation),
|
||||
_ => Ok(Some(NewStreamType::Unknown)),
|
||||
}
|
||||
|
@ -197,13 +196,13 @@ impl NewStreamHeadReader {
|
|||
|
||||
fn map_stream_fin(decoded: Option<NewStreamType>) -> Res<Option<NewStreamType>> {
|
||||
match decoded {
|
||||
Some(NewStreamType::Control)
|
||||
| Some(NewStreamType::Encoder)
|
||||
| Some(NewStreamType::Decoder) => Err(Error::HttpClosedCriticalStream),
|
||||
Some(NewStreamType::Control | NewStreamType::Encoder | NewStreamType::Decoder) => {
|
||||
Err(Error::HttpClosedCriticalStream)
|
||||
}
|
||||
None => Err(Error::HttpStreamCreation),
|
||||
Some(NewStreamType::Http) => Err(Error::HttpFrame),
|
||||
Some(NewStreamType::Unknown) => Ok(decoded),
|
||||
Some(NewStreamType::Push(_)) | Some(NewStreamType::WebTransportStream(_)) => {
|
||||
Some(NewStreamType::Push(_) | NewStreamType::WebTransportStream(_)) => {
|
||||
unreachable!("PushStream and WebTransport are mapped to None at this stage.")
|
||||
}
|
||||
}
|
||||
|
@ -318,7 +317,7 @@ mod tests {
|
|||
for i in to_encode {
|
||||
enc.encode_varint(*i);
|
||||
}
|
||||
self.decode_buffer(&enc[..], fin, outcome, done);
|
||||
self.decode_buffer(enc.as_ref(), fin, outcome, done);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -139,8 +139,8 @@ fn wrong_setting_value() {
|
|||
};
|
||||
settings.encode(&mut enc);
|
||||
assert_eq!(
|
||||
server.stream_send(control, &enc[..]).unwrap(),
|
||||
enc[..].len()
|
||||
server.stream_send(control, enc.as_ref()).unwrap(),
|
||||
enc.as_ref().len()
|
||||
);
|
||||
|
||||
exchange_packets2(&mut client, &mut server);
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"ffbf61e358a81f58d8b4f0ca89031d53879ec8e45851a0f1bc2b583229feb815","src/decoder.rs":"8bd336c91cca989883106a9d0bf26b117d224e0e7643960c3e97d0168d1853c4","src/decoder_instructions.rs":"19d47158bc09551b449be205f5cd5ea83e6984c4e4d3e7d4b95938b09617015e","src/encoder.rs":"e72cbcdbe24cbe13ad5cbcbb0df8981a2ea67331f296ec7784480bc28ae01eef","src/encoder_instructions.rs":"a7f1d3a4f8ae941286d0aba81037a8df3ef85e275392ef31d9938e9314c706db","src/header_block.rs":"7910ddc28b44d2065070cb2d87ab3cfbb905cce912b23d8b12b0f0add5691ceb","src/huffman.rs":"3a9edaf827343ec6e43cfd50fcc0d0077287947160ae630da5c3ddaaefedd010","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"29c5e47f8a4cf9c0a5dfdc614594868db22bc25b9688e5efdbc041cd222a17e5","src/prefix.rs":"72c587c40aef4ed38cf13b2de91091d671611679be2a9da6f0b24abafaf50dc5","src/qlog.rs":"7618085e27bb3fb1f4d1c73ba501b9a293723293c4020b7cc4129676eb278131","src/qpack_send_buf.rs":"49ded6607ec0859cb3edc5a38ff48f4d2d292f0721673d4e20700d07ac324557","src/reader.rs":"be265cc8c317512f266fafdcc835d0e413caf5280a7cc945bfe6e7e849529d67","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}
|
||||
{"files":{"Cargo.toml":"9c7bc0f5de79c42f0957ab114f8a4ca38c276cc9f90e3823dc3b5e3a85226b66","src/decoder.rs":"8bd336c91cca989883106a9d0bf26b117d224e0e7643960c3e97d0168d1853c4","src/decoder_instructions.rs":"2205c7635b8f0c568f6fe9a63c17028eaf8d29a9b5ac7136b6554cc7fbf35038","src/encoder.rs":"b888a819595fec47037d508b943f5ff04ed52ea376bef1f90e08edc6576e773c","src/encoder_instructions.rs":"1eb4f6eee2d9ff16f96dc5bf80dae9bc04316126f6eca933fb51dbd9218a439c","src/header_block.rs":"76f4c8fad6a13d4d24530cf067d20622cdbd345f7d9779b0be9691a77fa8fb63","src/huffman.rs":"3a9edaf827343ec6e43cfd50fcc0d0077287947160ae630da5c3ddaaefedd010","src/huffman_decode_helper.rs":"2970c57f052878b727c2f764490c54184f5c2608e1d6aa961c3b01509e290122","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"6e25612bb30f0e4361566662da1e5353131ae12f97938c6ac3b2dafbf6a8bc86","src/prefix.rs":"72c587c40aef4ed38cf13b2de91091d671611679be2a9da6f0b24abafaf50dc5","src/qlog.rs":"7618085e27bb3fb1f4d1c73ba501b9a293723293c4020b7cc4129676eb278131","src/qpack_send_buf.rs":"49ded6607ec0859cb3edc5a38ff48f4d2d292f0721673d4e20700d07ac324557","src/reader.rs":"be265cc8c317512f266fafdcc835d0e413caf5280a7cc945bfe6e7e849529d67","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"f7091bdd9ad1f8fe3b2298a7dbfd3d285c212d69569cda54f9bcf251cb758a21"},"package":null}
|
|
@ -1,8 +1,9 @@
|
|||
[package]
|
||||
name = "neqo-qpack"
|
||||
version = "0.5.7"
|
||||
version = "0.6.1"
|
||||
authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.57.0"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -14,7 +14,7 @@ use neqo_common::{qdebug, qtrace};
|
|||
use neqo_transport::StreamId;
|
||||
use std::mem;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DecoderInstruction {
|
||||
InsertCountIncrement { increment: u64 },
|
||||
HeaderAck { stream_id: StreamId },
|
||||
|
|
|
@ -516,7 +516,7 @@ mod tests {
|
|||
use crate::QpackSettings;
|
||||
use neqo_transport::{ConnectionParameters, StreamId, StreamType};
|
||||
use std::mem;
|
||||
use test_fixture::{configure_server, default_client, default_server, handshake, now};
|
||||
use test_fixture::{default_client, default_server, handshake, new_server, now, DEFAULT_ALPN};
|
||||
|
||||
struct TestEncoder {
|
||||
encoder: QPackEncoder,
|
||||
|
@ -571,7 +571,8 @@ mod tests {
|
|||
fn connect_generic(huffman: bool, max_data: Option<u64>) -> TestEncoder {
|
||||
let mut conn = default_client();
|
||||
let mut peer_conn = max_data.map_or_else(default_server, |max| {
|
||||
configure_server(
|
||||
new_server(
|
||||
DEFAULT_ALPN,
|
||||
ConnectionParameters::default()
|
||||
.max_stream_data(StreamType::UniDi, true, max)
|
||||
.max_stream_data(StreamType::BiDi, true, max)
|
||||
|
|
|
@ -18,7 +18,7 @@ use std::mem;
|
|||
// We may decide to use othe instruction in the future.
|
||||
// All instructions are used for testing, therefore they are defined.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum EncoderInstruction<'a> {
|
||||
Capacity { value: u64 },
|
||||
InsertWithNameRefStatic { index: u64, value: &'a [u8] },
|
||||
|
@ -63,7 +63,7 @@ enum EncoderInstructionReaderState {
|
|||
Done,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DecodedEncoderInstruction {
|
||||
Capacity { value: u64 },
|
||||
InsertWithNameRefStatic { index: u64, value: Vec<u8> },
|
||||
|
|
|
@ -177,7 +177,7 @@ impl<'a> ::std::fmt::Display for HeaderDecoder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum HeaderDecoderResult {
|
||||
Blocked(u64),
|
||||
Headers(Vec<Header>),
|
||||
|
|
|
@ -38,12 +38,7 @@ pub struct QpackSettings {
|
|||
pub max_blocked_streams: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(
|
||||
renamed_and_removed_lints,
|
||||
clippy::pub_enum_variant_names,
|
||||
clippy::enum_variant_names
|
||||
)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
DecompressionFailed,
|
||||
EncoderStream,
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,8 +1,9 @@
|
|||
[package]
|
||||
name = "neqo-transport"
|
||||
version = "0.5.7"
|
||||
version = "0.6.1"
|
||||
authors = ["EKR <ekr@rtfm.com>", "Andy Grover <agrover@mozilla.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.57.0"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -127,7 +127,7 @@ impl AddressValidation {
|
|||
|
||||
// Include the token identifier ("Retry"/~) in the AAD, then keep it for plaintext.
|
||||
let mut buf = Self::encode_aad(peer_address, retry);
|
||||
let encrypted = self.self_encrypt.seal(&buf, &data)?;
|
||||
let encrypted = self.self_encrypt.seal(buf.as_ref(), data.as_ref())?;
|
||||
buf.truncate(TOKEN_IDENTIFIER_RETRY.len());
|
||||
buf.encode(&encrypted);
|
||||
Ok(buf.into())
|
||||
|
@ -165,7 +165,7 @@ impl AddressValidation {
|
|||
now: Instant,
|
||||
) -> Option<ConnectionId> {
|
||||
let peer_addr = Self::encode_aad(peer_address, retry);
|
||||
let data = self.self_encrypt.open(&peer_addr, token).ok()?;
|
||||
let data = self.self_encrypt.open(peer_addr.as_ref(), token).ok()?;
|
||||
let mut dec = Decoder::new(&data);
|
||||
match dec.decode_uint(4) {
|
||||
Some(d) => {
|
||||
|
|
|
@ -152,7 +152,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
|
|||
let is_app_limited = self.app_limited();
|
||||
qtrace!(
|
||||
[self],
|
||||
"app limited={}, bytes_in_flight:{}, cwnd: {}, state: {:?} pacing_burst_size: {}",
|
||||
"limited={}, bytes_in_flight={}, cwnd={}, state={:?} pacing_burst_size={}",
|
||||
is_app_limited,
|
||||
self.bytes_in_flight,
|
||||
self.congestion_window,
|
||||
|
@ -413,7 +413,7 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
|
|||
continue;
|
||||
}
|
||||
if let Some(t) = start {
|
||||
if p.time_sent.duration_since(t) > pc_period {
|
||||
if p.time_sent.checked_duration_since(t).unwrap() > pc_period {
|
||||
qinfo!([self], "persistent congestion");
|
||||
self.congestion_window = CWND_MIN;
|
||||
self.acked_bytes = 0;
|
||||
|
|
|
@ -450,6 +450,10 @@ impl ConnectionIdManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn generator(&self) -> Rc<RefCell<dyn ConnectionIdGenerator>> {
|
||||
Rc::clone(&self.generator)
|
||||
}
|
||||
|
||||
pub fn decoder(&self) -> ConnectionIdDecoderRef {
|
||||
ConnectionIdDecoderRef {
|
||||
generator: self.generator.deref().borrow(),
|
||||
|
@ -488,6 +492,8 @@ impl ConnectionIdManager {
|
|||
|
||||
/// During the handshake, a server needs to regard the client's choice of destination
|
||||
/// connection ID as valid. This function saves it in the store in a special place.
|
||||
/// Note that this is only done *after* an Initial packet from the client is
|
||||
/// successfully processed.
|
||||
pub fn add_odcid(&mut self, cid: ConnectionId) {
|
||||
let entry = ConnectionIdEntry::new(CONNECTION_ID_SEQNO_ODCID, cid, ());
|
||||
self.connection_ids.add_local(entry);
|
||||
|
|
|
@ -41,9 +41,7 @@ use crate::frame::{
|
|||
CloseError, Frame, FrameType, FRAME_TYPE_CONNECTION_CLOSE_APPLICATION,
|
||||
FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT,
|
||||
};
|
||||
use crate::packet::{
|
||||
DecryptedPacket, PacketBuilder, PacketNumber, PacketType, PublicPacket, QuicVersion,
|
||||
};
|
||||
use crate::packet::{DecryptedPacket, PacketBuilder, PacketNumber, PacketType, PublicPacket};
|
||||
use crate::path::{Path, PathRef, Paths};
|
||||
use crate::quic_datagrams::{DatagramTracking, QuicDatagrams};
|
||||
use crate::recovery::{LossRecovery, RecoveryToken, SendProfile};
|
||||
|
@ -52,10 +50,12 @@ pub use crate::send_stream::{RetransmissionPriority, TransmissionPriority};
|
|||
use crate::stats::{Stats, StatsCell};
|
||||
use crate::stream_id::StreamType;
|
||||
use crate::streams::Streams;
|
||||
use crate::tparams::{self, TransportParameter, TransportParameters, TransportParametersHandler};
|
||||
use crate::tparams::{
|
||||
self, TransportParameter, TransportParameterId, TransportParameters, TransportParametersHandler,
|
||||
};
|
||||
use crate::tracking::{AckTracker, PacketNumberSpace, SentPacket};
|
||||
use crate::{qlog, StreamId};
|
||||
use crate::{AppError, ConnectionError, Error, Res};
|
||||
use crate::version::{Version, WireVersion};
|
||||
use crate::{qlog, AppError, ConnectionError, Error, Res, StreamId};
|
||||
|
||||
mod idle;
|
||||
pub mod params;
|
||||
|
@ -79,7 +79,7 @@ struct Packet(Vec<u8>);
|
|||
/// handshake. This is a hack, but a useful one.
|
||||
const EXTRA_INITIALS: usize = 4;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ZeroRttState {
|
||||
Init,
|
||||
Sending,
|
||||
|
@ -88,7 +88,7 @@ pub enum ZeroRttState {
|
|||
Rejected,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
/// Type returned from process() and `process_output()`. Users are required to
|
||||
/// call these repeatedly until `Callback` or `None` is returned.
|
||||
pub enum Output {
|
||||
|
@ -218,6 +218,7 @@ impl AddressValidationInfo {
|
|||
/// remote) continue processing until `state()` returns `Closed`.
|
||||
pub struct Connection {
|
||||
role: Role,
|
||||
version: Version,
|
||||
state: State,
|
||||
tps: Rc<RefCell<TransportParametersHandler>>,
|
||||
/// What we are doing with 0-RTT.
|
||||
|
@ -246,6 +247,8 @@ pub struct Connection {
|
|||
/// when they are either just reordered or we haven't been able to install keys yet.
|
||||
/// In particular, this occurs when asynchronous certificate validation happens.
|
||||
saved_datagrams: SavedDatagrams,
|
||||
/// Some packets were received, but not tracked.
|
||||
received_untracked: bool,
|
||||
|
||||
/// This is responsible for the QuicDatagrams' handling:
|
||||
/// https://datatracker.ietf.org/doc/html/draft-ietf-quic-datagram
|
||||
|
@ -293,7 +296,7 @@ impl Connection {
|
|||
|
||||
/// Create a new QUIC connection with Client role.
|
||||
pub fn new_client(
|
||||
server_name: &str,
|
||||
server_name: impl Into<String>,
|
||||
protocols: &[impl AsRef<str>],
|
||||
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
local_addr: SocketAddr,
|
||||
|
@ -304,12 +307,16 @@ impl Connection {
|
|||
let dcid = ConnectionId::generate_initial();
|
||||
let mut c = Self::new(
|
||||
Role::Client,
|
||||
Client::new(server_name)?.into(),
|
||||
Agent::from(Client::new(server_name.into())?),
|
||||
cid_generator,
|
||||
protocols,
|
||||
conn_params,
|
||||
)?;
|
||||
c.crypto.states.init(c.version(), Role::Client, &dcid);
|
||||
c.crypto.states.init(
|
||||
c.conn_params.get_versions().compatible(),
|
||||
Role::Client,
|
||||
&dcid,
|
||||
);
|
||||
c.original_destination_cid = Some(dcid);
|
||||
let path = Path::temporary(
|
||||
local_addr,
|
||||
|
@ -331,18 +338,18 @@ impl Connection {
|
|||
) -> Res<Self> {
|
||||
Self::new(
|
||||
Role::Server,
|
||||
Server::new(certs)?.into(),
|
||||
Agent::from(Server::new(certs)?),
|
||||
cid_generator,
|
||||
protocols,
|
||||
conn_params,
|
||||
)
|
||||
}
|
||||
|
||||
fn new(
|
||||
fn new<P: AsRef<str>>(
|
||||
role: Role,
|
||||
agent: Agent,
|
||||
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
|
||||
protocols: &[impl AsRef<str>],
|
||||
protocols: &[P],
|
||||
conn_params: ConnectionParameters,
|
||||
) -> Res<Self> {
|
||||
// Setup the local connection ID.
|
||||
|
@ -360,9 +367,9 @@ impl Connection {
|
|||
|
||||
let tphandler = Rc::new(RefCell::new(tps));
|
||||
let crypto = Crypto::new(
|
||||
conn_params.get_quic_version(),
|
||||
conn_params.get_versions().initial(),
|
||||
agent,
|
||||
protocols,
|
||||
protocols.iter().map(P::as_ref).map(String::from).collect(),
|
||||
Rc::clone(&tphandler),
|
||||
)?;
|
||||
|
||||
|
@ -377,6 +384,7 @@ impl Connection {
|
|||
|
||||
let c = Self {
|
||||
role,
|
||||
version: conn_params.get_versions().initial(),
|
||||
state: State::Init,
|
||||
paths: Paths::default(),
|
||||
cid_manager,
|
||||
|
@ -387,6 +395,7 @@ impl Connection {
|
|||
remote_initial_source_cid: None,
|
||||
original_destination_cid: None,
|
||||
saved_datagrams: SavedDatagrams::default(),
|
||||
received_untracked: false,
|
||||
crypto,
|
||||
acks: AckTracker::default(),
|
||||
idle_timeout: IdleTimeout::new(conn_params.get_idle_timeout()),
|
||||
|
@ -457,13 +466,15 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Set a local transport parameter, possibly overriding a default value.
|
||||
/// In general, this method should not be used. This only sets transport parameters
|
||||
/// without dealing with other aspects of setting the value.
|
||||
pub fn set_local_tparam(
|
||||
&self,
|
||||
tp: crate::tparams::TransportParameterId,
|
||||
value: TransportParameter,
|
||||
) -> Res<()> {
|
||||
/// This only sets transport parameters without dealing with other aspects of
|
||||
/// setting the value.
|
||||
/// # Panics
|
||||
/// This panics if the transport parameter is known to this crate.
|
||||
pub fn set_local_tparam(&self, tp: TransportParameterId, value: TransportParameter) -> Res<()> {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
assert!(!tparams::INTERNAL_TRANSPORT_PARAMETERS.contains(&tp));
|
||||
}
|
||||
if *self.state() == State::Init {
|
||||
self.tps.borrow_mut().local.set(tp, value);
|
||||
Ok(())
|
||||
|
@ -538,6 +549,7 @@ impl Connection {
|
|||
.remote
|
||||
.as_ref()
|
||||
.expect("should have transport parameters"),
|
||||
self.version,
|
||||
u64::try_from(rtt.as_millis()).unwrap_or(0),
|
||||
)
|
||||
.unwrap()
|
||||
|
@ -638,6 +650,13 @@ impl Connection {
|
|||
);
|
||||
let mut dec = Decoder::from(token.as_ref());
|
||||
|
||||
let version =
|
||||
Version::try_from(dec.decode_uint(4).ok_or(Error::InvalidResumptionToken)? as u32)?;
|
||||
qtrace!([self], " version {:?}", version);
|
||||
if !self.conn_params.get_versions().all().contains(&version) {
|
||||
return Err(Error::DisabledVersion);
|
||||
}
|
||||
|
||||
let rtt = Duration::from_millis(dec.decode_varint().ok_or(Error::InvalidResumptionToken)?);
|
||||
qtrace!([self], " RTT {:?}", rtt);
|
||||
|
||||
|
@ -652,6 +671,7 @@ impl Connection {
|
|||
|
||||
let tok = dec.decode_remainder();
|
||||
qtrace!([self], " TLS token {}", hex(&tok));
|
||||
|
||||
match self.crypto.tls {
|
||||
Agent::Client(ref mut c) => {
|
||||
let res = c.enable_resumption(&tok);
|
||||
|
@ -663,6 +683,9 @@ impl Connection {
|
|||
Agent::Server(_) => return Err(Error::WrongRole),
|
||||
}
|
||||
|
||||
self.version = version;
|
||||
self.conn_params.get_versions_mut().set_initial(version);
|
||||
self.tps.borrow_mut().set_version(version);
|
||||
self.tps.borrow_mut().remote_0rtt = Some(tp);
|
||||
if !init_token.is_empty() {
|
||||
self.address_validation = AddressValidationInfo::NewToken(init_token.to_vec());
|
||||
|
@ -695,7 +718,7 @@ impl Connection {
|
|||
tps.borrow().local.encode(enc_inner);
|
||||
});
|
||||
enc.encode(extra);
|
||||
let records = s.send_ticket(now, &enc)?;
|
||||
let records = s.send_ticket(now, enc.as_ref())?;
|
||||
qinfo!([self], "send session ticket {}", hex(&enc));
|
||||
self.crypto.buffer_records(records)?;
|
||||
} else {
|
||||
|
@ -740,7 +763,7 @@ impl Connection {
|
|||
pub fn authenticated(&mut self, status: AuthenticationStatus, now: Instant) {
|
||||
qinfo!([self], "Authenticated {:?}", status);
|
||||
self.crypto.tls.authenticated(status);
|
||||
let res = self.handshake(now, PacketNumberSpace::Handshake, None);
|
||||
let res = self.handshake(now, self.version, PacketNumberSpace::Handshake, None);
|
||||
self.absorb_error(now, res);
|
||||
self.process_saved(now);
|
||||
}
|
||||
|
@ -756,13 +779,13 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// The QUIC version in use.
|
||||
pub fn version(&self) -> QuicVersion {
|
||||
self.conn_params.get_quic_version()
|
||||
pub fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Get the 0-RTT state of the connection.
|
||||
pub fn zero_rtt_state(&self) -> &ZeroRttState {
|
||||
&self.zero_rtt_state
|
||||
pub fn zero_rtt_state(&self) -> ZeroRttState {
|
||||
self.zero_rtt_state
|
||||
}
|
||||
|
||||
/// Get a snapshot of collected statistics.
|
||||
|
@ -1000,7 +1023,7 @@ impl Connection {
|
|||
self.process_output(now)
|
||||
}
|
||||
|
||||
fn handle_retry(&mut self, packet: &PublicPacket) {
|
||||
fn handle_retry(&mut self, packet: &PublicPacket, now: Instant) {
|
||||
qinfo!([self], "received Retry");
|
||||
if matches!(self.address_validation, AddressValidationInfo::Retry { .. }) {
|
||||
self.stats.borrow_mut().pkt_dropped("Extra Retry");
|
||||
|
@ -1029,12 +1052,14 @@ impl Connection {
|
|||
retry_scid
|
||||
);
|
||||
|
||||
let lost_packets = self.loss_recovery.retry(&path);
|
||||
let lost_packets = self.loss_recovery.retry(&path, now);
|
||||
self.handle_lost_packets(&lost_packets);
|
||||
|
||||
self.crypto
|
||||
.states
|
||||
.init(self.version(), self.role, &retry_scid);
|
||||
self.crypto.states.init(
|
||||
self.conn_params.get_versions().compatible(),
|
||||
self.role,
|
||||
&retry_scid,
|
||||
);
|
||||
self.address_validation = AddressValidationInfo::Retry {
|
||||
token: packet.token().to_vec(),
|
||||
retry_source_cid: retry_scid,
|
||||
|
@ -1086,7 +1111,7 @@ impl Connection {
|
|||
fn process_saved(&mut self, now: Instant) {
|
||||
while let Some(cspace) = self.saved_datagrams.available() {
|
||||
qdebug!([self], "process saved for space {:?}", cspace);
|
||||
debug_assert!(self.crypto.states.rx_hp(cspace).is_some());
|
||||
debug_assert!(self.crypto.states.rx_hp(self.version, cspace).is_some());
|
||||
for saved in self.saved_datagrams.take_saved() {
|
||||
qtrace!([self], "input saved @{:?}: {:?}", saved.t, saved.d);
|
||||
self.input(saved.d, saved.t, now);
|
||||
|
@ -1106,6 +1131,44 @@ impl Connection {
|
|||
self.stats.borrow_mut().saved_datagrams += 1;
|
||||
}
|
||||
|
||||
/// Perform version negotiation.
|
||||
fn version_negotiation(&mut self, supported: &[WireVersion], now: Instant) -> Res<()> {
|
||||
debug_assert_eq!(self.role, Role::Client);
|
||||
|
||||
if let Some(version) = self.conn_params.get_versions().preferred(supported) {
|
||||
assert_ne!(self.version, version);
|
||||
|
||||
qinfo!([self], "Version negotiation: trying {:?}", version);
|
||||
let local_addr = self.paths.primary().borrow().local_address();
|
||||
let remote_addr = self.paths.primary().borrow().remote_address();
|
||||
let conn_params = self
|
||||
.conn_params
|
||||
.clone()
|
||||
.versions(version, self.conn_params.get_versions().all().to_vec());
|
||||
let mut c = Self::new_client(
|
||||
self.crypto.server_name().unwrap(),
|
||||
self.crypto.protocols(),
|
||||
self.cid_manager.generator(),
|
||||
local_addr,
|
||||
remote_addr,
|
||||
conn_params,
|
||||
now,
|
||||
)?;
|
||||
c.conn_params
|
||||
.get_versions_mut()
|
||||
.set_initial(self.conn_params.get_versions().initial());
|
||||
mem::swap(self, &mut c);
|
||||
Ok(())
|
||||
} else {
|
||||
qinfo!([self], "Version negotiation: failed with {:?}", supported);
|
||||
// This error goes straight to closed.
|
||||
self.set_state(State::Closed(ConnectionError::Transport(
|
||||
Error::VersionNegotiation,
|
||||
)));
|
||||
Err(Error::VersionNegotiation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform any processing that we might have to do on packets prior to
|
||||
/// attempting to remove protection.
|
||||
fn preprocess_packet(
|
||||
|
@ -1135,7 +1198,10 @@ impl Connection {
|
|||
|
||||
match (packet.packet_type(), &self.state, &self.role) {
|
||||
(PacketType::Initial, State::Init, Role::Server) => {
|
||||
if !packet.is_valid_initial() {
|
||||
let version = *packet.version().as_ref().unwrap();
|
||||
if !packet.is_valid_initial()
|
||||
|| !self.conn_params.get_versions().all().contains(&version)
|
||||
{
|
||||
self.stats.borrow_mut().pkt_dropped("Invalid Initial");
|
||||
return Ok(PreprocessResult::Next);
|
||||
}
|
||||
|
@ -1145,10 +1211,12 @@ impl Connection {
|
|||
packet.scid(),
|
||||
packet.dcid()
|
||||
);
|
||||
// Record the client's selected CID so that it can be accepted until
|
||||
// the client starts using a real connection ID.
|
||||
let dcid = ConnectionId::from(packet.dcid());
|
||||
self.crypto.states.init_server(version, &dcid);
|
||||
self.original_destination_cid = Some(dcid);
|
||||
self.set_state(State::WaitInitial);
|
||||
self.crypto
|
||||
.states
|
||||
.init(self.version(), self.role, packet.dcid());
|
||||
|
||||
// We need to make sure that we set this transport parameter.
|
||||
// This has to happen prior to processing the packet so that
|
||||
|
@ -1164,8 +1232,9 @@ impl Connection {
|
|||
match packet.supported_versions() {
|
||||
Ok(versions) => {
|
||||
if versions.is_empty()
|
||||
|| versions.contains(&self.version().as_u32())
|
||||
|| packet.dcid() != self.odcid().unwrap()
|
||||
|| versions.contains(&self.version().wire_version())
|
||||
|| versions.contains(&0)
|
||||
|| packet.scid() != self.odcid().unwrap()
|
||||
|| matches!(
|
||||
self.address_validation,
|
||||
AddressValidationInfo::Retry { .. }
|
||||
|
@ -1178,19 +1247,17 @@ impl Connection {
|
|||
return Ok(PreprocessResult::End);
|
||||
}
|
||||
|
||||
self.set_state(State::Closed(ConnectionError::Transport(
|
||||
Error::VersionNegotiation,
|
||||
)));
|
||||
return Err(Error::VersionNegotiation);
|
||||
self.version_negotiation(&versions, now)?;
|
||||
return Ok(PreprocessResult::End);
|
||||
}
|
||||
Err(_) => {
|
||||
self.stats.borrow_mut().pkt_dropped("Invalid VN");
|
||||
self.stats.borrow_mut().pkt_dropped("VN with no versions");
|
||||
return Ok(PreprocessResult::End);
|
||||
}
|
||||
}
|
||||
}
|
||||
(PacketType::Retry, State::WaitInitial, Role::Client) => {
|
||||
self.handle_retry(packet);
|
||||
self.handle_retry(packet, now);
|
||||
return Ok(PreprocessResult::Next);
|
||||
}
|
||||
(PacketType::Handshake, State::WaitInitial, Role::Client)
|
||||
|
@ -1226,7 +1293,7 @@ impl Connection {
|
|||
PreprocessResult::Next
|
||||
}
|
||||
State::WaitInitial => PreprocessResult::Continue,
|
||||
State::Handshaking | State::Connected | State::Confirmed => {
|
||||
State::WaitVersion | State::Handshaking | State::Connected | State::Confirmed => {
|
||||
if !self.cid_manager.is_valid(packet.dcid()) {
|
||||
self.stats
|
||||
.borrow_mut()
|
||||
|
@ -1269,6 +1336,7 @@ impl Connection {
|
|||
if self.state == State::WaitInitial {
|
||||
self.start_handshake(path, packet, now);
|
||||
}
|
||||
|
||||
if self.state.connected() {
|
||||
self.handle_migration(path, d, migrate, now);
|
||||
} else if self.role != Role::Client
|
||||
|
@ -1365,6 +1433,12 @@ impl Connection {
|
|||
// Exhausting read keys is fatal.
|
||||
return Err(e);
|
||||
}
|
||||
Error::KeysDiscarded(cspace) => {
|
||||
// This was a valid-appearing Initial packet: maybe probe with
|
||||
// a Handshake packet to keep the handshake moving.
|
||||
self.received_untracked |=
|
||||
self.role == Role::Client && cspace == CryptoSpace::Initial;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
// Decryption failure, or not having keys is not fatal.
|
||||
|
@ -1418,15 +1492,30 @@ impl Connection {
|
|||
ack_eliciting |= f.ack_eliciting();
|
||||
probing &= f.path_probing();
|
||||
let t = f.get_type();
|
||||
if let Err(e) = self.input_frame(path, packet.packet_type(), f, now) {
|
||||
if let Err(e) = self.input_frame(path, packet.version(), packet.packet_type(), f, now) {
|
||||
self.capture_error(Some(Rc::clone(path)), now, t, Err(e))?;
|
||||
}
|
||||
}
|
||||
let largest_received = self
|
||||
|
||||
let largest_received = if let Some(space) = self
|
||||
.acks
|
||||
.get_mut(PacketNumberSpace::from(packet.packet_type()))
|
||||
.unwrap()
|
||||
.set_received(now, packet.pn(), ack_eliciting);
|
||||
{
|
||||
space.set_received(now, packet.pn(), ack_eliciting)
|
||||
} else {
|
||||
qdebug!(
|
||||
[self],
|
||||
"processed a {:?} packet without tracking it",
|
||||
packet.packet_type(),
|
||||
);
|
||||
// This was a valid packet that caused the same packet number to be
|
||||
// discarded. This happens when the client discards the Initial packet
|
||||
// number space after receiving the ServerHello. Remember this so
|
||||
// that we guarantee that we send a Handshake packet.
|
||||
self.received_untracked = true;
|
||||
// We don't migrate during the handshake, so return false.
|
||||
false
|
||||
};
|
||||
|
||||
Ok(largest_received && !probing)
|
||||
}
|
||||
|
@ -1434,6 +1523,7 @@ impl Connection {
|
|||
/// During connection setup, the first path needs to be setup.
|
||||
/// This uses the connection IDs that were provided during the handshake
|
||||
/// to setup that path.
|
||||
#[allow(clippy::or_fun_call)] // Remove when MSRV >= 1.59
|
||||
fn setup_handshake_path(&mut self, path: &PathRef, now: Instant) {
|
||||
self.paths.make_permanent(
|
||||
path,
|
||||
|
@ -1443,7 +1533,7 @@ impl Connection {
|
|||
ConnectionIdEntry::initial_remote(
|
||||
self.remote_initial_source_cid
|
||||
.as_ref()
|
||||
.or_else(|| self.original_destination_cid.as_ref())
|
||||
.or(self.original_destination_cid.as_ref())
|
||||
.unwrap()
|
||||
.clone(),
|
||||
),
|
||||
|
@ -1495,29 +1585,35 @@ impl Connection {
|
|||
debug_assert_eq!(packet.packet_type(), PacketType::Initial);
|
||||
self.remote_initial_source_cid = Some(ConnectionId::from(packet.scid()));
|
||||
|
||||
if self.role == Role::Server {
|
||||
// Record the client's selected CID so that it can be accepted until
|
||||
// the client starts using a real connection ID.
|
||||
let dcid = ConnectionId::from(packet.dcid());
|
||||
self.original_destination_cid = Some(dcid.clone());
|
||||
self.cid_manager.add_odcid(dcid);
|
||||
let got_version = if self.role == Role::Server {
|
||||
self.cid_manager
|
||||
.add_odcid(self.original_destination_cid.as_ref().unwrap().clone());
|
||||
// Make a path on which to run the handshake.
|
||||
self.setup_handshake_path(path, now);
|
||||
|
||||
self.zero_rtt_state = match self.crypto.enable_0rtt(self.role) {
|
||||
self.zero_rtt_state = match self.crypto.enable_0rtt(self.version, self.role) {
|
||||
Ok(true) => {
|
||||
qdebug!([self], "Accepted 0-RTT");
|
||||
ZeroRttState::AcceptedServer
|
||||
}
|
||||
_ => ZeroRttState::Rejected,
|
||||
};
|
||||
|
||||
// The server knows the final version if it has remote transport parameters.
|
||||
self.tps.borrow().remote.is_some()
|
||||
} else {
|
||||
qdebug!([self], "Changing to use Server CID={}", packet.scid());
|
||||
debug_assert!(path.borrow().is_primary());
|
||||
path.borrow_mut().set_remote_cid(packet.scid());
|
||||
}
|
||||
|
||||
self.set_state(State::Handshaking);
|
||||
// The client knows the final version if it processed a CRYPTO frame.
|
||||
self.stats.borrow().frame_rx.crypto > 0
|
||||
};
|
||||
if got_version {
|
||||
self.set_state(State::Handshaking);
|
||||
} else {
|
||||
self.set_state(State::WaitVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate to the provided path.
|
||||
|
@ -1647,6 +1743,7 @@ impl Connection {
|
|||
let res = match &self.state {
|
||||
State::Init
|
||||
| State::WaitInitial
|
||||
| State::WaitVersion
|
||||
| State::Handshaking
|
||||
| State::Connected
|
||||
| State::Confirmed => {
|
||||
|
@ -1676,7 +1773,7 @@ impl Connection {
|
|||
encoder: Encoder,
|
||||
tx: &CryptoDxState,
|
||||
address_validation: &AddressValidationInfo,
|
||||
quic_version: QuicVersion,
|
||||
version: Version,
|
||||
grease_quic_bit: bool,
|
||||
) -> (PacketType, PacketBuilder) {
|
||||
let pt = PacketType::from(cspace);
|
||||
|
@ -1691,13 +1788,7 @@ impl Connection {
|
|||
path.local_cid(),
|
||||
);
|
||||
|
||||
PacketBuilder::long(
|
||||
encoder,
|
||||
pt,
|
||||
quic_version,
|
||||
path.remote_cid(),
|
||||
path.local_cid(),
|
||||
)
|
||||
PacketBuilder::long(encoder, pt, version, path.remote_cid(), path.local_cid())
|
||||
};
|
||||
if builder.remaining() > 0 {
|
||||
builder.scramble(grease_quic_bit);
|
||||
|
@ -1748,11 +1839,12 @@ impl Connection {
|
|||
let grease_quic_bit = self.can_grease_quic_bit();
|
||||
let version = self.version();
|
||||
for space in PacketNumberSpace::iter() {
|
||||
let (cspace, tx) = if let Some(crypto) = self.crypto.states.select_tx_mut(*space) {
|
||||
crypto
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let (cspace, tx) =
|
||||
if let Some(crypto) = self.crypto.states.select_tx_mut(self.version, *space) {
|
||||
crypto
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = close.path().borrow();
|
||||
let (_, mut builder) = Self::build_packet_header(
|
||||
|
@ -1885,13 +1977,18 @@ impl Connection {
|
|||
tokens: &mut Vec<RecoveryToken>,
|
||||
now: Instant,
|
||||
) -> bool {
|
||||
let untracked = self.received_untracked && !self.state.connected();
|
||||
self.received_untracked = false;
|
||||
|
||||
// Anything written after an ACK already elicits acknowledgment.
|
||||
// If we need to probe and nothing has been written, send a PING.
|
||||
if builder.len() > ack_end {
|
||||
return true;
|
||||
}
|
||||
let probe = if force_probe {
|
||||
// The packet might be empty, but we need to probe.
|
||||
|
||||
let probe = if untracked && builder.packet_empty() || force_probe {
|
||||
// If we received an untracked packet and we aren't probing already
|
||||
// or the PTO timer fired: probe.
|
||||
true
|
||||
} else {
|
||||
let pto = path.borrow().rtt().pto(PacketNumberSpace::ApplicationData);
|
||||
|
@ -2012,11 +2109,12 @@ impl Connection {
|
|||
let mut encoder = Encoder::with_capacity(profile.limit());
|
||||
for space in PacketNumberSpace::iter() {
|
||||
// Ensure we have tx crypto state for this epoch, or skip it.
|
||||
let (cspace, tx) = if let Some(crypto) = self.crypto.states.select_tx_mut(*space) {
|
||||
crypto
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let (cspace, tx) =
|
||||
if let Some(crypto) = self.crypto.states.select_tx_mut(self.version, *space) {
|
||||
crypto
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let header_start = encoder.len();
|
||||
let (pt, mut builder) = Self::build_packet_header(
|
||||
|
@ -2059,17 +2157,25 @@ impl Connection {
|
|||
continue;
|
||||
}
|
||||
|
||||
dump_packet(self, path, "TX ->", pt, pn, &builder[payload_start..]);
|
||||
dump_packet(
|
||||
self,
|
||||
path,
|
||||
"TX ->",
|
||||
pt,
|
||||
pn,
|
||||
&builder.as_ref()[payload_start..],
|
||||
);
|
||||
qlog::packet_sent(
|
||||
&mut self.qlog,
|
||||
pt,
|
||||
pn,
|
||||
builder.len() - header_start + aead_expansion,
|
||||
&builder[payload_start..],
|
||||
&builder.as_ref()[payload_start..],
|
||||
);
|
||||
|
||||
self.stats.borrow_mut().packets_tx += 1;
|
||||
encoder = builder.build(self.crypto.states.tx_mut(cspace).unwrap())?;
|
||||
let tx = self.crypto.states.tx_mut(self.version, cspace).unwrap();
|
||||
encoder = builder.build(tx)?;
|
||||
debug_assert!(encoder.len() <= mtu);
|
||||
self.crypto.states.auto_update()?;
|
||||
|
||||
|
@ -2099,14 +2205,13 @@ impl Connection {
|
|||
self.loss_recovery.on_packet_sent(path, sent);
|
||||
}
|
||||
|
||||
if *space == PacketNumberSpace::Handshake {
|
||||
if self.role == Role::Client {
|
||||
// Client can send Handshake packets -> discard Initial keys and states
|
||||
self.discard_keys(PacketNumberSpace::Initial, now);
|
||||
} else if self.state == State::Confirmed {
|
||||
// We could discard handshake keys in set_state, but wait until after sending an ACK.
|
||||
self.discard_keys(PacketNumberSpace::Handshake, now);
|
||||
}
|
||||
if *space == PacketNumberSpace::Handshake
|
||||
&& self.role == Role::Server
|
||||
&& self.state == State::Confirmed
|
||||
{
|
||||
// We could discard handshake keys in set_state,
|
||||
// but wait until after sending an ACK.
|
||||
self.discard_keys(PacketNumberSpace::Handshake, now);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2150,9 +2255,9 @@ impl Connection {
|
|||
debug_assert_eq!(self.role, Role::Client);
|
||||
qlog::client_connection_started(&mut self.qlog, &self.paths.primary());
|
||||
|
||||
self.handshake(now, PacketNumberSpace::Initial, None)?;
|
||||
self.handshake(now, self.version, PacketNumberSpace::Initial, None)?;
|
||||
self.set_state(State::WaitInitial);
|
||||
self.zero_rtt_state = if self.crypto.enable_0rtt(self.role)? {
|
||||
self.zero_rtt_state = if self.crypto.enable_0rtt(self.version, self.role)? {
|
||||
qdebug!([self], "Enabled 0-RTT");
|
||||
ZeroRttState::Sending
|
||||
} else {
|
||||
|
@ -2205,6 +2310,7 @@ impl Connection {
|
|||
/// Process the final set of transport parameters.
|
||||
fn process_tps(&mut self) -> Res<()> {
|
||||
self.validate_cids()?;
|
||||
self.validate_versions()?;
|
||||
{
|
||||
let tps = self.tps.borrow();
|
||||
let remote = tps.remote.as_ref().unwrap();
|
||||
|
@ -2315,9 +2421,91 @@ impl Connection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate the `version_negotiation` transport parameter from the peer.
|
||||
fn validate_versions(&mut self) -> Res<()> {
|
||||
let tph = self.tps.borrow();
|
||||
let remote_tps = tph.remote.as_ref().unwrap();
|
||||
// `current` and `other` are the value from the peer's transport parameters.
|
||||
// We're checking that these match our expectations.
|
||||
if let Some((current, other)) = remote_tps.get_versions() {
|
||||
qtrace!(
|
||||
[self],
|
||||
"validate_versions: current={:x} chosen={:x} other={:x?}",
|
||||
self.version.wire_version(),
|
||||
current,
|
||||
other,
|
||||
);
|
||||
if self.role == Role::Server {
|
||||
// 1. A server acts on transport parameters, with validation
|
||||
// of `current` happening in the transport parameter handler.
|
||||
// All we need to do is confirm that the transport parameter
|
||||
// was provided.
|
||||
Ok(())
|
||||
} else if self.version().wire_version() != current {
|
||||
qinfo!([self], "validate_versions: current version mismatch");
|
||||
Err(Error::VersionNegotiation)
|
||||
} else if self
|
||||
.conn_params
|
||||
.get_versions()
|
||||
.initial()
|
||||
.is_compatible(self.version)
|
||||
{
|
||||
// 2. The current version is compatible with what we attempted.
|
||||
// That's a compatible upgrade and that's OK.
|
||||
Ok(())
|
||||
} else {
|
||||
// 3. The initial version we attempted isn't compatible. Check that
|
||||
// the one we would have chosen is compatible with this one.
|
||||
let mut all_versions = other.to_owned();
|
||||
all_versions.push(current);
|
||||
if self
|
||||
.conn_params
|
||||
.get_versions()
|
||||
.preferred(&all_versions)
|
||||
.ok_or(Error::VersionNegotiation)?
|
||||
.is_compatible(self.version)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
qinfo!([self], "validate_versions: failed");
|
||||
Err(Error::VersionNegotiation)
|
||||
}
|
||||
}
|
||||
} else if self.version != Version::Version1 && !self.version.is_draft() {
|
||||
qinfo!([self], "validate_versions: missing extension");
|
||||
Err(Error::VersionNegotiation)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_version(&mut self, v: Version) {
|
||||
if self.version != v {
|
||||
qinfo!([self], "Compatible upgrade {:?} ==> {:?}", self.version, v);
|
||||
}
|
||||
self.crypto.confirm_version(v);
|
||||
self.version = v;
|
||||
}
|
||||
|
||||
fn compatible_upgrade(&mut self, packet_version: Version) {
|
||||
if !matches!(self.state, State::WaitInitial | State::WaitVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.role == Role::Client {
|
||||
self.confirm_version(packet_version);
|
||||
} else if self.tps.borrow().remote.is_some() {
|
||||
let version = self.tps.borrow().version();
|
||||
let dcid = self.original_destination_cid.as_ref().unwrap();
|
||||
self.crypto.states.init_server(version, dcid);
|
||||
self.confirm_version(version);
|
||||
}
|
||||
}
|
||||
|
||||
fn handshake(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
packet_version: Version,
|
||||
space: PacketNumberSpace,
|
||||
data: Option<&[u8]>,
|
||||
) -> Res<()> {
|
||||
|
@ -2341,13 +2529,19 @@ impl Connection {
|
|||
}
|
||||
|
||||
// There is a chance that this could be called less often, but getting the
|
||||
// conditions right is a little tricky, so call it on every CRYPTO frame.
|
||||
// conditions right is a little tricky, so call whenever CRYPTO data is used.
|
||||
if try_update {
|
||||
self.compatible_upgrade(packet_version);
|
||||
// We have transport parameters, it's go time.
|
||||
if self.tps.borrow().remote.is_some() {
|
||||
self.set_initial_limits();
|
||||
}
|
||||
if self.crypto.install_keys(self.role)? {
|
||||
if self.role == Role::Client {
|
||||
// We won't acknowledge Initial packets as a result of this, but the
|
||||
// server can rely on implicit acknowledgment.
|
||||
self.discard_keys(PacketNumberSpace::Initial, now);
|
||||
}
|
||||
self.saved_datagrams.make_available(CryptoSpace::Handshake);
|
||||
}
|
||||
}
|
||||
|
@ -2358,16 +2552,17 @@ impl Connection {
|
|||
fn input_frame(
|
||||
&mut self,
|
||||
path: &PathRef,
|
||||
ptype: PacketType,
|
||||
packet_version: Version,
|
||||
packet_type: PacketType,
|
||||
frame: Frame,
|
||||
now: Instant,
|
||||
) -> Res<()> {
|
||||
if !frame.is_allowed(ptype) {
|
||||
qinfo!("frame not allowed: {:?} {:?}", frame, ptype);
|
||||
if !frame.is_allowed(packet_type) {
|
||||
qinfo!("frame not allowed: {:?} {:?}", frame, packet_type);
|
||||
return Err(Error::ProtocolViolation);
|
||||
}
|
||||
self.stats.borrow_mut().frame_rx.all += 1;
|
||||
let space = PacketNumberSpace::from(ptype);
|
||||
let space = PacketNumberSpace::from(packet_type);
|
||||
if frame.is_stream() {
|
||||
return self
|
||||
.streams
|
||||
|
@ -2412,7 +2607,7 @@ impl Connection {
|
|||
let mut buf = Vec::new();
|
||||
let read = self.crypto.streams.read_to_end(space, &mut buf);
|
||||
qdebug!("Read {} bytes", read);
|
||||
self.handshake(now, space, Some(&buf))?;
|
||||
self.handshake(now, packet_version, space, Some(&buf))?;
|
||||
self.create_resumption_token(now);
|
||||
} else {
|
||||
// If we get a useless CRYPTO frame send outstanding CRYPTO frames again.
|
||||
|
@ -2616,14 +2811,14 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// When the server rejects 0-RTT we need to drop a bunch of stuff.
|
||||
fn client_0rtt_rejected(&mut self) {
|
||||
fn client_0rtt_rejected(&mut self, now: Instant) {
|
||||
if !matches!(self.zero_rtt_state, ZeroRttState::Sending) {
|
||||
return;
|
||||
}
|
||||
qdebug!([self], "0-RTT rejected");
|
||||
|
||||
// Tell 0-RTT packets that they were "lost".
|
||||
let dropped = self.loss_recovery.drop_0rtt(&self.paths.primary());
|
||||
let dropped = self.loss_recovery.drop_0rtt(&self.paths.primary(), now);
|
||||
self.handle_lost_packets(&dropped);
|
||||
|
||||
self.streams.zero_rtt_rejected();
|
||||
|
@ -2651,14 +2846,15 @@ impl Connection {
|
|||
self.zero_rtt_state = if self.crypto.tls.info().unwrap().early_data_accepted() {
|
||||
ZeroRttState::AcceptedClient
|
||||
} else {
|
||||
self.client_0rtt_rejected();
|
||||
self.client_0rtt_rejected(now);
|
||||
ZeroRttState::Rejected
|
||||
};
|
||||
}
|
||||
|
||||
// Setting application keys has to occur after 0-RTT rejection.
|
||||
let pto = self.pto();
|
||||
self.crypto.install_application_keys(now + pto)?;
|
||||
self.crypto
|
||||
.install_application_keys(self.version, now + pto)?;
|
||||
self.process_tps()?;
|
||||
self.set_state(State::Connected);
|
||||
self.create_resumption_token(now);
|
||||
|
@ -2844,7 +3040,7 @@ impl Connection {
|
|||
let (cspace, tx) = if let Some(crypto) = self
|
||||
.crypto
|
||||
.states
|
||||
.select_tx(PacketNumberSpace::ApplicationData)
|
||||
.select_tx(self.version, PacketNumberSpace::ApplicationData)
|
||||
{
|
||||
crypto
|
||||
} else {
|
||||
|
|
|
@ -11,7 +11,8 @@ use crate::rtt::GRANULARITY;
|
|||
use crate::stream_id::StreamType;
|
||||
use crate::tparams::{self, PreferredAddress, TransportParameter, TransportParametersHandler};
|
||||
use crate::tracking::DEFAULT_ACK_DELAY;
|
||||
use crate::{CongestionControlAlgorithm, QuicVersion, Res};
|
||||
use crate::version::{Version, VersionConfig};
|
||||
use crate::{CongestionControlAlgorithm, Res};
|
||||
use std::cmp::max;
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Duration;
|
||||
|
@ -42,9 +43,9 @@ pub enum PreferredAddressConfig {
|
|||
/// ConnectionParameters use for setting intitial value for QUIC parameters.
|
||||
/// This collects configuration like initial limits, protocol version, and
|
||||
/// congestion control algorithm.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectionParameters {
|
||||
quic_version: QuicVersion,
|
||||
versions: VersionConfig,
|
||||
cc_algorithm: CongestionControlAlgorithm,
|
||||
/// Initial connection-level flow control limit.
|
||||
max_data: u64,
|
||||
|
@ -77,7 +78,7 @@ pub struct ConnectionParameters {
|
|||
impl Default for ConnectionParameters {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
quic_version: QuicVersion::default(),
|
||||
versions: VersionConfig::default(),
|
||||
cc_algorithm: CongestionControlAlgorithm::NewReno,
|
||||
max_data: LOCAL_MAX_DATA,
|
||||
max_stream_data_bidi_remote: u64::try_from(RECV_BUFFER_SIZE).unwrap(),
|
||||
|
@ -97,12 +98,20 @@ impl Default for ConnectionParameters {
|
|||
}
|
||||
|
||||
impl ConnectionParameters {
|
||||
pub fn get_quic_version(&self) -> QuicVersion {
|
||||
self.quic_version
|
||||
pub fn get_versions(&self) -> &VersionConfig {
|
||||
&self.versions
|
||||
}
|
||||
|
||||
pub fn quic_version(mut self, v: QuicVersion) -> Self {
|
||||
self.quic_version = v;
|
||||
pub(crate) fn get_versions_mut(&mut self) -> &mut VersionConfig {
|
||||
&mut self.versions
|
||||
}
|
||||
|
||||
/// Describe the initial version that should be attempted and all the
|
||||
/// versions that should be enabled. This list should contain the initial
|
||||
/// version and be in order of preference, with more preferred versions
|
||||
/// before less preferred.
|
||||
pub fn versions(mut self, initial: Version, all: Vec<Version>) -> Self {
|
||||
self.versions = VersionConfig::new(initial, all);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -278,7 +287,7 @@ impl ConnectionParameters {
|
|||
role: Role,
|
||||
cid_manager: &mut ConnectionIdManager,
|
||||
) -> Res<TransportParametersHandler> {
|
||||
let mut tps = TransportParametersHandler::default();
|
||||
let mut tps = TransportParametersHandler::new(role, self.versions.clone());
|
||||
// default parameters
|
||||
tps.local.set_integer(
|
||||
tparams::ACTIVE_CONNECTION_ID_LIMIT,
|
||||
|
|
|
@ -22,8 +22,17 @@ use crate::{ConnectionError, Error, Res};
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
/// The state of the Connection.
|
||||
pub enum State {
|
||||
/// A newly created connection.
|
||||
Init,
|
||||
/// Waiting for the first Initial packet.
|
||||
WaitInitial,
|
||||
/// Waiting to confirm which version was selected.
|
||||
/// For a client, this is confirmed when a CRYPTO frame is received;
|
||||
/// the version of the packet determines the version.
|
||||
/// For a server, this is confirmed when transport parameters are
|
||||
/// received and processed.
|
||||
WaitVersion,
|
||||
/// Exchanging Handshake packets.
|
||||
Handshaking,
|
||||
Connected,
|
||||
Confirmed,
|
||||
|
@ -81,6 +90,8 @@ impl Ord for State {
|
|||
(_, Self::Init) => Ordering::Greater,
|
||||
(Self::WaitInitial, _) => Ordering::Less,
|
||||
(_, Self::WaitInitial) => Ordering::Greater,
|
||||
(Self::WaitVersion, _) => Ordering::Less,
|
||||
(_, Self::WaitVersion) => Ordering::Greater,
|
||||
(Self::Handshaking, _) => Ordering::Less,
|
||||
(_, Self::Handshaking) => Ordering::Greater,
|
||||
(Self::Connected, _) => Ordering::Less,
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
use super::super::{Connection, Output, State};
|
||||
use super::{
|
||||
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,
|
||||
get_tokens, handshake, maybe_authenticate, resumed_server, send_something,
|
||||
CountingConnectionIdGenerator, AT_LEAST_PTO, DEFAULT_RTT, DEFAULT_STREAM_DATA,
|
||||
};
|
||||
use crate::connection::AddressValidation;
|
||||
use crate::events::ConnectionEvent;
|
||||
|
@ -17,8 +17,7 @@ use crate::server::ValidateAddress;
|
|||
use crate::tparams::{TransportParameter, MIN_ACK_DELAY};
|
||||
use crate::tracking::DEFAULT_ACK_DELAY;
|
||||
use crate::{
|
||||
ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error, QuicVersion,
|
||||
StreamType,
|
||||
ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error, StreamType, Version,
|
||||
};
|
||||
|
||||
use neqo_common::{event::Provider, qdebug, Datagram};
|
||||
|
@ -356,7 +355,7 @@ fn reorder_05rtt_with_0rtt() {
|
|||
let token = get_tokens(&mut client).pop().unwrap();
|
||||
let mut client = default_client();
|
||||
client.enable_resumption(now, token).unwrap();
|
||||
let mut server = default_server();
|
||||
let mut server = resumed_server(&client);
|
||||
|
||||
// Send ClientHello and some 0-RTT.
|
||||
let c1 = send_something(&mut client, now);
|
||||
|
@ -398,7 +397,9 @@ fn reorder_05rtt_with_0rtt() {
|
|||
now += RTT / 2;
|
||||
server.process_input(c4.unwrap(), now);
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
assert_eq!(server.paths.rtt(), RTT);
|
||||
// Don't check server RTT as it will be massively inflated by a
|
||||
// poor initial estimate received when the server dropped the
|
||||
// Initial packet number space.
|
||||
}
|
||||
|
||||
/// Test that a server that coalesces 0.5 RTT with handshake packets
|
||||
|
@ -522,7 +523,9 @@ fn reorder_handshake() {
|
|||
now += RTT / 2;
|
||||
let s3 = server.process(c3, now).dgram();
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
assert_eq!(server.paths.rtt(), RTT);
|
||||
// Don't check server RTT estimate as it will be inflated due to
|
||||
// it making a guess based on retransmissions when it dropped
|
||||
// the Initial packet number space.
|
||||
|
||||
now += RTT / 2;
|
||||
client.process_input(s3.unwrap(), now);
|
||||
|
@ -566,9 +569,8 @@ fn reorder_1rtt() {
|
|||
now += RTT / 2;
|
||||
let s2 = server.process(c2, now).dgram();
|
||||
// The server has now received those packets, and saved them.
|
||||
// The three additional are: an Initial ACK, a Handshake,
|
||||
// and a 1-RTT (containing NEW_CONNECTION_ID).
|
||||
assert_eq!(server.stats().packets_rx, PACKETS * 2 + 5);
|
||||
// The two additional are a Handshake and a 1-RTT (w/ NEW_CONNECTION_ID).
|
||||
assert_eq!(server.stats().packets_rx, PACKETS * 2 + 4);
|
||||
assert_eq!(server.stats().saved_datagrams, PACKETS);
|
||||
assert_eq!(server.stats().dropped_rx, 1);
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
|
@ -714,51 +716,36 @@ fn extra_initial_invalid_cid() {
|
|||
assert!(nothing.is_none());
|
||||
}
|
||||
|
||||
fn connect_version(version: QuicVersion) {
|
||||
fixture_init();
|
||||
let mut client = Connection::new_client(
|
||||
test_fixture::DEFAULT_SERVER_NAME,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
ConnectionParameters::default().quic_version(version),
|
||||
now(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut server = Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default().quic_version(version),
|
||||
)
|
||||
.unwrap();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_v1() {
|
||||
connect_version(QuicVersion::Version1);
|
||||
}
|
||||
fn connect_one_version() {
|
||||
fn connect_v(version: Version) {
|
||||
fixture_init();
|
||||
let mut client = Connection::new_client(
|
||||
test_fixture::DEFAULT_SERVER_NAME,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
addr(),
|
||||
addr(),
|
||||
ConnectionParameters::default().versions(version, vec![version]),
|
||||
now(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut server = Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default().versions(version, vec![version]),
|
||||
)
|
||||
.unwrap();
|
||||
connect_force_idle(&mut client, &mut server);
|
||||
assert_eq!(client.version(), version);
|
||||
assert_eq!(server.version(), version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_29() {
|
||||
connect_version(QuicVersion::Draft29);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_30() {
|
||||
connect_version(QuicVersion::Draft30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_31() {
|
||||
connect_version(QuicVersion::Draft31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_32() {
|
||||
connect_version(QuicVersion::Draft32);
|
||||
for v in Version::all() {
|
||||
println!("Connecting with {:?}", v);
|
||||
connect_v(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -797,9 +784,9 @@ fn anti_amplification() {
|
|||
let ack = client.process(Some(s_init3), now).dgram().unwrap();
|
||||
assert!(!maybe_authenticate(&mut client)); // No need yet.
|
||||
|
||||
// The client sends a padded datagram, with just ACK for Initial + Handshake.
|
||||
assert_eq!(client.stats().frame_tx.ack, ack_count + 2);
|
||||
assert_eq!(client.stats().frame_tx.all, frame_count + 2);
|
||||
// The client sends a padded datagram, with just ACK for Handshake.
|
||||
assert_eq!(client.stats().frame_tx.ack, ack_count + 1);
|
||||
assert_eq!(client.stats().frame_tx.all, frame_count + 1);
|
||||
assert_ne!(ack.len(), PATH_MTU_V6); // Not padded (it includes Handshake).
|
||||
|
||||
now += DEFAULT_RTT / 2;
|
||||
|
@ -1028,3 +1015,115 @@ fn bad_min_ack_delay() {
|
|||
)))
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that the client probes correctly if it only receives Initial packets
|
||||
/// from the server.
|
||||
#[test]
|
||||
fn only_server_initial() {
|
||||
let mut server = default_server();
|
||||
let mut client = default_client();
|
||||
let mut now = now();
|
||||
|
||||
let client_dgram = client.process_output(now).dgram();
|
||||
|
||||
// Now fetch two flights of messages from the server.
|
||||
let server_dgram1 = server.process(client_dgram, now).dgram();
|
||||
let server_dgram2 = server.process_output(now + AT_LEAST_PTO).dgram();
|
||||
|
||||
// Only pass on the Initial from the first. We should get a Handshake in return.
|
||||
let (initial, handshake) = split_datagram(&server_dgram1.unwrap());
|
||||
assert!(handshake.is_some());
|
||||
|
||||
// The client will not acknowledge the Initial as it discards keys.
|
||||
// It sends a Handshake probe instead, containing just a PING frame.
|
||||
assert_eq!(client.stats().frame_tx.ping, 0);
|
||||
let probe = client.process(Some(initial), now).dgram();
|
||||
assertions::assert_handshake(&probe.unwrap());
|
||||
assert_eq!(client.stats().dropped_rx, 0);
|
||||
assert_eq!(client.stats().frame_tx.ping, 1);
|
||||
|
||||
let (initial, handshake) = split_datagram(&server_dgram2.unwrap());
|
||||
assert!(handshake.is_some());
|
||||
|
||||
// The same happens after a PTO, even though the client will discard the Initial packet.
|
||||
now += AT_LEAST_PTO;
|
||||
assert_eq!(client.stats().frame_tx.ping, 1);
|
||||
let discarded = client.stats().dropped_rx;
|
||||
let probe = client.process(Some(initial), now).dgram();
|
||||
assertions::assert_handshake(&probe.unwrap());
|
||||
assert_eq!(client.stats().frame_tx.ping, 2);
|
||||
assert_eq!(client.stats().dropped_rx, discarded + 1);
|
||||
|
||||
// Pass the Handshake packet and complete the handshake.
|
||||
client.process_input(handshake.unwrap(), now);
|
||||
maybe_authenticate(&mut client);
|
||||
let dgram = client.process_output(now).dgram();
|
||||
let dgram = server.process(dgram, now).dgram();
|
||||
client.process_input(dgram.unwrap(), now);
|
||||
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
}
|
||||
|
||||
// Collect a few spare Initial packets as the handshake is exchanged.
|
||||
// Later, replay those packets to see if they result in additional probes; they should not.
|
||||
#[test]
|
||||
fn no_extra_probes_after_confirmed() {
|
||||
let mut server = default_server();
|
||||
let mut client = default_client();
|
||||
let mut now = now();
|
||||
|
||||
// First, collect a client Initial.
|
||||
let spare_initial = client.process_output(now).dgram();
|
||||
assert!(spare_initial.is_some());
|
||||
|
||||
// Collect ANOTHER client Initial.
|
||||
now += AT_LEAST_PTO;
|
||||
let dgram = client.process_output(now).dgram();
|
||||
let (replay_initial, _) = split_datagram(dgram.as_ref().unwrap());
|
||||
|
||||
// Finally, run the handshake.
|
||||
now += AT_LEAST_PTO * 2;
|
||||
let dgram = client.process_output(now).dgram();
|
||||
let dgram = server.process(dgram, now).dgram();
|
||||
|
||||
// The server should have dropped the Initial keys now, so passing in the Initial
|
||||
// should elicit a retransmit rather than having it completely ignored.
|
||||
let spare_handshake = server.process(Some(replay_initial), now).dgram();
|
||||
assert!(spare_handshake.is_some());
|
||||
|
||||
client.process_input(dgram.unwrap(), now);
|
||||
maybe_authenticate(&mut client);
|
||||
let dgram = client.process_output(now).dgram();
|
||||
let dgram = server.process(dgram, now).dgram();
|
||||
client.process_input(dgram.unwrap(), now);
|
||||
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
|
||||
let probe = server.process(spare_initial, now).dgram();
|
||||
assert!(probe.is_none());
|
||||
let probe = client.process(spare_handshake, now).dgram();
|
||||
assert!(probe.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_rtt_server() {
|
||||
const RTT: Duration = Duration::from_secs(2);
|
||||
let mut server = default_server();
|
||||
let mut client = default_client();
|
||||
let mut now = now();
|
||||
|
||||
let dgram = client.process_output(now).dgram();
|
||||
now += RTT / 2;
|
||||
let dgram = server.process(dgram, now).dgram();
|
||||
now += RTT / 2;
|
||||
let dgram = client.process(dgram, now).dgram();
|
||||
assertions::assert_handshake(dgram.as_ref().unwrap());
|
||||
now += RTT / 2;
|
||||
server.process_input(dgram.unwrap(), now);
|
||||
|
||||
// The server doesn't receive any acknowledgments, but it can infer
|
||||
// an RTT estimate from having discarded the Initial packet number space.
|
||||
assert_eq!(server.stats().rtt, RTT);
|
||||
}
|
||||
|
|
|
@ -18,13 +18,19 @@ 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) {
|
||||
fn check_discarded(
|
||||
peer: &mut Connection,
|
||||
pkt: Datagram,
|
||||
response: bool,
|
||||
dropped: usize,
|
||||
dups: usize,
|
||||
) {
|
||||
// Make sure to flush any saved datagrams before doing this.
|
||||
mem::drop(peer.process_output(now()));
|
||||
|
||||
let before = peer.stats();
|
||||
let out = peer.process(Some(pkt), now());
|
||||
assert!(out.as_dgram_ref().is_none());
|
||||
assert_eq!(out.as_dgram_ref().is_some(), response);
|
||||
let after = peer.stats();
|
||||
assert_eq!(dropped, after.dropped_rx - before.dropped_rx);
|
||||
assert_eq!(dups, after.dups_rx - before.dups_rx);
|
||||
|
@ -60,11 +66,12 @@ fn discarded_initial_keys() {
|
|||
let out = client.process(init_pkt_s.clone(), now()).dgram();
|
||||
assert!(out.is_some());
|
||||
|
||||
// The client has received handshake packet. It will remove the Initial keys.
|
||||
// The client has received a handshake packet. It will remove the Initial keys.
|
||||
// We will check this by processing init_pkt_s a second time.
|
||||
// The initial packet should be dropped. The packet contains a Handshake packet as well, which
|
||||
// will be marked as dup. And it will contain padding, which will be "dropped".
|
||||
check_discarded(&mut client, init_pkt_s.unwrap(), 2, 1);
|
||||
// The client will generate a Handshake packet here to avoid stalling.
|
||||
check_discarded(&mut client, init_pkt_s.unwrap(), true, 2, 1);
|
||||
|
||||
assert!(maybe_authenticate(&mut client));
|
||||
|
||||
|
@ -72,7 +79,7 @@ fn discarded_initial_keys() {
|
|||
// packet from the client.
|
||||
// We will check this by processing init_pkt_c a second time.
|
||||
// The dropped packet is padding. The Initial packet has been mark dup.
|
||||
check_discarded(&mut server, init_pkt_c.clone().unwrap(), 1, 1);
|
||||
check_discarded(&mut server, init_pkt_c.clone().unwrap(), false, 1, 1);
|
||||
|
||||
qdebug!("---- client: SH..FIN -> FIN");
|
||||
let out = client.process(None, now()).dgram();
|
||||
|
@ -87,7 +94,7 @@ fn discarded_initial_keys() {
|
|||
// We will check this by processing init_pkt_c a third time.
|
||||
// The Initial packet has been dropped and padding that follows it.
|
||||
// There is no dups, everything has been dropped.
|
||||
check_discarded(&mut server, init_pkt_c.unwrap(), 1, 0);
|
||||
check_discarded(&mut server, init_pkt_c.unwrap(), false, 1, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -200,7 +207,7 @@ fn key_update_consecutive() {
|
|||
|
||||
// However, as the server didn't wait long enough to update again, the
|
||||
// client hasn't rotated its keys, so the packet gets dropped.
|
||||
check_discarded(&mut client, dgram, 1, 0);
|
||||
check_discarded(&mut client, dgram, false, 1, 0);
|
||||
}
|
||||
|
||||
// Key updates can't be initiated too early.
|
||||
|
|
|
@ -16,6 +16,7 @@ use crate::recovery::ACK_ONLY_SIZE_LIMIT;
|
|||
use crate::stats::{FrameStats, Stats, MAX_PTO_COUNTS};
|
||||
use crate::{
|
||||
ConnectionIdDecoder, ConnectionIdGenerator, ConnectionParameters, Error, StreamId, StreamType,
|
||||
Version,
|
||||
};
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
@ -128,6 +129,9 @@ pub fn new_server(params: ConnectionParameters) -> Connection {
|
|||
pub fn default_server() -> Connection {
|
||||
new_server(ConnectionParameters::default())
|
||||
}
|
||||
pub fn resumed_server(client: &Connection) -> Connection {
|
||||
new_server(ConnectionParameters::default().versions(client.version(), Version::all()))
|
||||
}
|
||||
|
||||
/// If state is `AuthenticationNeeded` call `authenticated()`. This function will
|
||||
/// consume all outstanding events on the connection.
|
||||
|
@ -211,10 +215,10 @@ fn connect(client: &mut Connection, server: &mut Connection) {
|
|||
connect_with_rtt(client, server, now(), Duration::new(0, 0));
|
||||
}
|
||||
|
||||
fn assert_error(c: &Connection, err: &ConnectionError) {
|
||||
fn assert_error(c: &Connection, expected: &ConnectionError) {
|
||||
match c.state() {
|
||||
State::Closing { error, .. } | State::Draining { error, .. } | State::Closed(error) => {
|
||||
assert_eq!(*error, *err);
|
||||
assert_eq!(*error, *expected, "{} error mismatch", c);
|
||||
}
|
||||
_ => panic!("bad state {:?}", c.state()),
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
use super::{
|
||||
connect, connect_with_rtt, default_client, default_server, exchange_ticket, get_tokens,
|
||||
send_something, AT_LEAST_PTO,
|
||||
new_client, resumed_server, send_something, AT_LEAST_PTO,
|
||||
};
|
||||
use crate::addr_valid::{AddressValidation, ValidateAddress};
|
||||
use crate::{ConnectionParameters, Error, Version};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
@ -27,7 +28,7 @@ fn resume() {
|
|||
client
|
||||
.enable_resumption(now(), token)
|
||||
.expect("should set token");
|
||||
let mut server = default_server();
|
||||
let mut server = resumed_server(&client);
|
||||
connect(&mut client, &mut server);
|
||||
assert!(client.tls_info().unwrap().resumed());
|
||||
assert!(server.tls_info().unwrap().resumed());
|
||||
|
@ -58,13 +59,13 @@ fn remember_smoothed_rtt() {
|
|||
let token = get_tokens(&mut client).pop().unwrap();
|
||||
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
client.enable_resumption(now, token).unwrap();
|
||||
assert_eq!(
|
||||
client.paths.rtt(),
|
||||
RTT1,
|
||||
"client should remember previous RTT"
|
||||
);
|
||||
let mut server = resumed_server(&client);
|
||||
|
||||
connect_with_rtt(&mut client, &mut server, now, RTT2);
|
||||
assert_eq!(
|
||||
|
@ -89,7 +90,7 @@ fn address_validation_token_resume() {
|
|||
let token = exchange_ticket(&mut client, &mut server, now);
|
||||
let mut client = default_client();
|
||||
client.enable_resumption(now, token).unwrap();
|
||||
let mut server = default_server();
|
||||
let mut server = resumed_server(&client);
|
||||
|
||||
// Grab an Initial packet from the client.
|
||||
let dgram = client.process(None, now).dgram();
|
||||
|
@ -193,3 +194,53 @@ fn take_token() {
|
|||
let token = client.take_resumption_token(now()).unwrap();
|
||||
can_resume(&token, false);
|
||||
}
|
||||
|
||||
/// If a version is selected and subsequently disabled, resumption fails.
|
||||
#[test]
|
||||
fn resume_disabled_version() {
|
||||
let mut client = new_client(
|
||||
ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
|
||||
);
|
||||
let mut server = default_server();
|
||||
connect(&mut client, &mut server);
|
||||
let token = exchange_ticket(&mut client, &mut server, now());
|
||||
|
||||
let mut client = new_client(
|
||||
ConnectionParameters::default().versions(Version::Version2, vec![Version::Version2]),
|
||||
);
|
||||
assert_eq!(
|
||||
client.enable_resumption(now(), token).unwrap_err(),
|
||||
Error::DisabledVersion
|
||||
);
|
||||
}
|
||||
|
||||
/// It's not possible to resume once a packet has been sent.
|
||||
#[test]
|
||||
fn resume_after_packet() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect(&mut client, &mut server);
|
||||
let token = exchange_ticket(&mut client, &mut server, now());
|
||||
|
||||
let mut client = default_client();
|
||||
mem::drop(client.process_output(now()).dgram().unwrap());
|
||||
assert_eq!(
|
||||
client.enable_resumption(now(), token).unwrap_err(),
|
||||
Error::ConnectionState
|
||||
);
|
||||
}
|
||||
|
||||
/// It's not possible to resume at the server.
|
||||
#[test]
|
||||
fn resume_server() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
connect(&mut client, &mut server);
|
||||
let token = exchange_ticket(&mut client, &mut server, now());
|
||||
|
||||
let mut server = default_server();
|
||||
assert_eq!(
|
||||
server.enable_resumption(now(), token).unwrap_err(),
|
||||
Error::ConnectionState
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,15 +4,19 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::super::{ConnectionError, Output, State};
|
||||
use super::{default_client, default_server};
|
||||
use super::super::{ConnectionError, ConnectionEvent, Output, State, ZeroRttState};
|
||||
use super::{
|
||||
connect, connect_fail, default_client, default_server, exchange_ticket, new_client, new_server,
|
||||
send_something,
|
||||
};
|
||||
use crate::packet::PACKET_BIT_LONG;
|
||||
use crate::{Error, QuicVersion};
|
||||
use crate::tparams::{self, TransportParameter};
|
||||
use crate::{ConnectionParameters, Error, Version};
|
||||
|
||||
use neqo_common::{Datagram, Decoder, Encoder};
|
||||
use neqo_common::{event::Provider, Datagram, Decoder, Encoder};
|
||||
use std::mem;
|
||||
use std::time::Duration;
|
||||
use test_fixture::{self, addr, now};
|
||||
use test_fixture::{self, addr, assertions, now};
|
||||
|
||||
// The expected PTO duration after the first Initial is sent.
|
||||
const INITIAL_PTO: Duration = Duration::from_millis(300);
|
||||
|
@ -58,8 +62,8 @@ fn create_vn(initial_pkt: &[u8], versions: &[u32]) -> Vec<u8> {
|
|||
let mut encoder = Encoder::default();
|
||||
encoder.encode_byte(PACKET_BIT_LONG);
|
||||
encoder.encode(&[0; 4]); // Zero version == VN.
|
||||
encoder.encode_vec(1, dst_cid);
|
||||
encoder.encode_vec(1, src_cid);
|
||||
encoder.encode_vec(1, dst_cid);
|
||||
|
||||
for v in versions {
|
||||
encoder.encode_uint(4, *v);
|
||||
|
@ -79,7 +83,7 @@ fn version_negotiation_current_version() {
|
|||
|
||||
let vn = create_vn(
|
||||
&initial_pkt,
|
||||
&[0x1a1a_1a1a, QuicVersion::default().as_u32()],
|
||||
&[0x1a1a_1a1a, Version::default().wire_version()],
|
||||
);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
|
@ -89,6 +93,25 @@ fn version_negotiation_current_version() {
|
|||
assert_eq!(1, client.stats().dropped_rx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_negotiation_version0() {
|
||||
let mut client = default_client();
|
||||
// Start the handshake.
|
||||
let initial_pkt = client
|
||||
.process(None, now())
|
||||
.dgram()
|
||||
.expect("a datagram")
|
||||
.to_vec();
|
||||
|
||||
let vn = create_vn(&initial_pkt, &[0, 0x1a1a_1a1a]);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
let delay = client.process(Some(dgram), now()).callback();
|
||||
assert_eq!(delay, INITIAL_PTO);
|
||||
assert_eq!(*client.state(), State::WaitInitial);
|
||||
assert_eq!(1, client.stats().dropped_rx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_negotiation_only_reserved() {
|
||||
let mut client = default_client();
|
||||
|
@ -160,11 +183,8 @@ fn version_negotiation_not_supported() {
|
|||
.to_vec();
|
||||
|
||||
let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
|
||||
|
||||
assert_eq!(
|
||||
client.process(Some(Datagram::new(addr(), addr(), vn)), now(),),
|
||||
Output::None
|
||||
);
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
assert_eq!(client.process(Some(dgram), now()), Output::None);
|
||||
match client.state() {
|
||||
State::Closed(err) => {
|
||||
assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation));
|
||||
|
@ -177,14 +197,14 @@ fn version_negotiation_not_supported() {
|
|||
fn version_negotiation_bad_cid() {
|
||||
let mut client = default_client();
|
||||
// Start the handshake.
|
||||
let initial_pkt = client
|
||||
let mut initial_pkt = client
|
||||
.process(None, now())
|
||||
.dgram()
|
||||
.expect("a datagram")
|
||||
.to_vec();
|
||||
|
||||
let mut vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
|
||||
vn[6] ^= 0xc4;
|
||||
initial_pkt[6] ^= 0xc4;
|
||||
let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
let delay = client.process(Some(dgram), now()).callback();
|
||||
|
@ -192,3 +212,275 @@ fn version_negotiation_bad_cid() {
|
|||
assert_eq!(*client.state(), State::WaitInitial);
|
||||
assert_eq!(1, client.stats().dropped_rx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compatible_upgrade() {
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
|
||||
connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), Version::Version2);
|
||||
assert_eq!(server.version(), Version::Version2);
|
||||
}
|
||||
|
||||
/// When the first packet from the client is gigantic, the server might generate acknowledgment packets in
|
||||
/// version 1. Both client and server need to handle that gracefully.
|
||||
#[test]
|
||||
fn compatible_upgrade_large_initial() {
|
||||
let params = ConnectionParameters::default().versions(
|
||||
Version::Version1,
|
||||
vec![Version::Version2, Version::Version1],
|
||||
);
|
||||
let mut client = new_client(params.clone());
|
||||
client
|
||||
.set_local_tparam(
|
||||
0x0845_de37_00ac_a5f9,
|
||||
TransportParameter::Bytes(vec![0; 2048]),
|
||||
)
|
||||
.unwrap();
|
||||
let mut server = new_server(params);
|
||||
|
||||
// Client Initial should take 2 packets.
|
||||
// Each should elicit a Version 1 ACK from the server.
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
// The following uses the Version from *outside* this crate.
|
||||
assertions::assert_version(dgram.as_ref().unwrap(), Version::Version1.wire_version());
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
|
||||
connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), Version::Version2);
|
||||
assert_eq!(server.version(), Version::Version2);
|
||||
// Only handshake padding is "dropped".
|
||||
assert_eq!(client.stats().dropped_rx, 1);
|
||||
assert_eq!(server.stats().dropped_rx, 1);
|
||||
}
|
||||
|
||||
/// A server that supports versions 1 and 2 might prefer version 1 and that's OK.
|
||||
/// This one starts with version 1 and stays there.
|
||||
#[test]
|
||||
fn compatible_no_upgrade() {
|
||||
let mut client = new_client(ConnectionParameters::default().versions(
|
||||
Version::Version1,
|
||||
vec![Version::Version2, Version::Version1],
|
||||
));
|
||||
let mut server = new_server(ConnectionParameters::default().versions(
|
||||
Version::Version1,
|
||||
vec![Version::Version1, Version::Version2],
|
||||
));
|
||||
|
||||
connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), Version::Version1);
|
||||
assert_eq!(server.version(), Version::Version1);
|
||||
}
|
||||
|
||||
/// A server that supports versions 1 and 2 might prefer version 1 and that's OK.
|
||||
/// This one starts with version 2 and downgrades to version 1.
|
||||
#[test]
|
||||
fn compatible_downgrade() {
|
||||
let mut client = new_client(ConnectionParameters::default().versions(
|
||||
Version::Version2,
|
||||
vec![Version::Version2, Version::Version1],
|
||||
));
|
||||
let mut server = new_server(ConnectionParameters::default().versions(
|
||||
Version::Version2,
|
||||
vec![Version::Version1, Version::Version2],
|
||||
));
|
||||
|
||||
connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), Version::Version1);
|
||||
assert_eq!(server.version(), Version::Version1);
|
||||
}
|
||||
|
||||
/// Inject a Version Negotiation packet, which the client detects when it validates the
|
||||
/// server `version_negotiation` transport parameter.
|
||||
#[test]
|
||||
fn version_negotiation_downgrade() {
|
||||
const DOWNGRADE: Version = Version::Draft29;
|
||||
|
||||
let mut client = default_client();
|
||||
// The server sets the current version in the transport parameter and
|
||||
// protects Initial packets with the version in its configuration.
|
||||
// When a server `Connection` is created by a `Server`, the configuration is set
|
||||
// to match the version of the packet it first receives. This replicates that.
|
||||
let mut server =
|
||||
new_server(ConnectionParameters::default().versions(DOWNGRADE, Version::all()));
|
||||
|
||||
// Start the handshake and spoof a VN packet.
|
||||
let initial = client.process_output(now()).dgram().unwrap();
|
||||
let vn = create_vn(&initial, &[DOWNGRADE.wire_version()]);
|
||||
let dgram = Datagram::new(addr(), addr(), vn);
|
||||
client.process_input(dgram, now());
|
||||
|
||||
connect_fail(
|
||||
&mut client,
|
||||
&mut server,
|
||||
Error::VersionNegotiation,
|
||||
Error::PeerError(Error::VersionNegotiation.code()),
|
||||
);
|
||||
}
|
||||
|
||||
/// A server connection needs to be configured with the version that the client attempts.
|
||||
/// Otherwise, it will object to the client transport parameters and not do anything.
|
||||
#[test]
|
||||
fn invalid_server_version() {
|
||||
let mut client =
|
||||
new_client(ConnectionParameters::default().versions(Version::Version1, Version::all()));
|
||||
let mut server =
|
||||
new_server(ConnectionParameters::default().versions(Version::Version2, Version::all()));
|
||||
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
server.process_input(dgram.unwrap(), now());
|
||||
|
||||
// One packet received.
|
||||
assert_eq!(server.stats().packets_rx, 1);
|
||||
// None dropped; the server will have decrypted it successfully.
|
||||
assert_eq!(server.stats().dropped_rx, 0);
|
||||
assert_eq!(server.stats().saved_datagrams, 0);
|
||||
// The server effectively hasn't reacted here.
|
||||
match server.state() {
|
||||
State::Closed(err) => {
|
||||
assert_eq!(*err, ConnectionError::Transport(Error::CryptoAlert(47)));
|
||||
}
|
||||
_ => panic!("invalid server state"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_current_version_client() {
|
||||
const OTHER_VERSION: Version = Version::Draft29;
|
||||
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
|
||||
assert_ne!(OTHER_VERSION, client.version());
|
||||
client
|
||||
.set_local_tparam(
|
||||
tparams::VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions {
|
||||
current: OTHER_VERSION.wire_version(),
|
||||
other: Version::all()
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Version::wire_version)
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
connect_fail(
|
||||
&mut client,
|
||||
&mut server,
|
||||
Error::PeerError(Error::CryptoAlert(47).code()),
|
||||
Error::CryptoAlert(47),
|
||||
);
|
||||
}
|
||||
|
||||
/// To test this, we need to disable compatible upgrade so that the server doesn't update
|
||||
/// its transport parameters. Then, we can overwrite its transport parameters without
|
||||
/// them being overwritten. Otherwise, it would be hard to find a window during which
|
||||
/// the transport parameter can be modified.
|
||||
#[test]
|
||||
fn invalid_current_version_server() {
|
||||
const OTHER_VERSION: Version = Version::Draft29;
|
||||
|
||||
let mut client = default_client();
|
||||
let mut server = new_server(
|
||||
ConnectionParameters::default().versions(Version::default(), vec![Version::default()]),
|
||||
);
|
||||
|
||||
assert!(!Version::default().is_compatible(OTHER_VERSION));
|
||||
server
|
||||
.set_local_tparam(
|
||||
tparams::VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions {
|
||||
current: OTHER_VERSION.wire_version(),
|
||||
other: vec![OTHER_VERSION.wire_version()],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
connect_fail(
|
||||
&mut client,
|
||||
&mut server,
|
||||
Error::CryptoAlert(47),
|
||||
Error::PeerError(Error::CryptoAlert(47).code()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_compatible_version() {
|
||||
const OTHER_VERSION: Version = Version::Draft29;
|
||||
|
||||
let mut client = default_client();
|
||||
let mut server = default_server();
|
||||
|
||||
assert_ne!(OTHER_VERSION, client.version());
|
||||
client
|
||||
.set_local_tparam(
|
||||
tparams::VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions {
|
||||
current: Version::default().wire_version(),
|
||||
other: vec![OTHER_VERSION.wire_version()],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
connect_fail(
|
||||
&mut client,
|
||||
&mut server,
|
||||
Error::PeerError(Error::CryptoAlert(47).code()),
|
||||
Error::CryptoAlert(47),
|
||||
);
|
||||
}
|
||||
|
||||
/// When a compatible upgrade chooses a different version, 0-RTT is rejected.
|
||||
#[test]
|
||||
fn compatible_upgrade_0rtt_rejected() {
|
||||
// This is the baseline configuration where v1 is attempted and v2 preferred.
|
||||
let prefer_v2 = ConnectionParameters::default().versions(
|
||||
Version::Version1,
|
||||
vec![Version::Version2, Version::Version1],
|
||||
);
|
||||
let mut client = new_client(prefer_v2.clone());
|
||||
// The server will start with this so that the client resumes with v1.
|
||||
let just_v1 =
|
||||
ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]);
|
||||
let mut server = new_server(just_v1);
|
||||
|
||||
connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), Version::Version1);
|
||||
let token = exchange_ticket(&mut client, &mut server, now());
|
||||
|
||||
// Now upgrade the server to the preferred configuration.
|
||||
let mut client = new_client(prefer_v2.clone());
|
||||
let mut server = new_server(prefer_v2);
|
||||
client.enable_resumption(now(), token).unwrap();
|
||||
|
||||
// Create a packet with 0-RTT from the client.
|
||||
let initial = send_something(&mut client, now());
|
||||
assertions::assert_version(&initial, Version::Version1.wire_version());
|
||||
assertions::assert_coalesced_0rtt(&initial);
|
||||
server.process_input(initial, now());
|
||||
assert!(!server
|
||||
.events()
|
||||
.any(|e| matches!(e, ConnectionEvent::NewStream { .. })));
|
||||
|
||||
// Finalize the connection. Don't use connect() because it uses
|
||||
// maybe_authenticate() too liberally and that eats the events we want to check.
|
||||
let dgram = server.process_output(now()).dgram(); // ServerHello flight
|
||||
let dgram = client.process(dgram, now()).dgram(); // Client Finished (note: no authentication)
|
||||
let dgram = server.process(dgram, now()).dgram(); // HANDSHAKE_DONE
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
|
||||
assert!(matches!(client.state(), State::Confirmed));
|
||||
assert!(matches!(server.state(), State::Confirmed));
|
||||
|
||||
assert!(client.events().any(|e| {
|
||||
println!(" client event: {:?}", e);
|
||||
matches!(e, ConnectionEvent::ZeroRttRejected)
|
||||
}));
|
||||
assert_eq!(client.zero_rtt_state(), ZeroRttState::Rejected);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
use super::super::Connection;
|
||||
use super::{
|
||||
connect, default_client, default_server, exchange_ticket, new_server,
|
||||
connect, default_client, default_server, exchange_ticket, new_server, resumed_server,
|
||||
CountingConnectionIdGenerator,
|
||||
};
|
||||
use crate::events::ConnectionEvent;
|
||||
use crate::{ConnectionParameters, Error, StreamType};
|
||||
use crate::{ConnectionParameters, Error, StreamType, Version};
|
||||
|
||||
use neqo_common::event::Provider;
|
||||
use neqo_crypto::{AllowZeroRtt, AntiReplay};
|
||||
|
@ -31,7 +31,7 @@ fn zero_rtt_negotiate() {
|
|||
client
|
||||
.enable_resumption(now(), token)
|
||||
.expect("should set token");
|
||||
let mut server = default_server();
|
||||
let mut server = resumed_server(&client);
|
||||
connect(&mut client, &mut server);
|
||||
assert!(client.tls_info().unwrap().early_data_accepted());
|
||||
assert!(server.tls_info().unwrap().early_data_accepted());
|
||||
|
@ -48,7 +48,7 @@ fn zero_rtt_send_recv() {
|
|||
client
|
||||
.enable_resumption(now(), token)
|
||||
.expect("should set token");
|
||||
let mut server = default_server();
|
||||
let mut server = resumed_server(&client);
|
||||
|
||||
// Send ClientHello.
|
||||
let client_hs = client.process(None, now());
|
||||
|
@ -93,7 +93,7 @@ fn zero_rtt_send_coalesce() {
|
|||
client
|
||||
.enable_resumption(now(), token)
|
||||
.expect("should set token");
|
||||
let mut server = default_server();
|
||||
let mut server = resumed_server(&client);
|
||||
|
||||
// Write 0-RTT before generating any packets.
|
||||
// This should result in a datagram that coalesces Initial and 0-RTT.
|
||||
|
@ -140,7 +140,7 @@ fn zero_rtt_send_reject() {
|
|||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default(),
|
||||
ConnectionParameters::default().versions(client.version(), Version::all()),
|
||||
)
|
||||
.unwrap();
|
||||
// Using a freshly initialized anti-replay context
|
||||
|
@ -221,7 +221,8 @@ fn zero_rtt_update_flow_control() {
|
|||
let mut server = new_server(
|
||||
ConnectionParameters::default()
|
||||
.max_stream_data(StreamType::UniDi, true, HIGH)
|
||||
.max_stream_data(StreamType::BiDi, true, HIGH),
|
||||
.max_stream_data(StreamType::BiDi, true, HIGH)
|
||||
.versions(client.version, Version::all()),
|
||||
);
|
||||
|
||||
// Stream limits should be low for 0-RTT.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::mem;
|
||||
use std::ops::{Index, IndexMut, Range};
|
||||
|
@ -22,13 +23,15 @@ use neqo_crypto::{
|
|||
TLS_VERSION_1_3,
|
||||
};
|
||||
|
||||
use crate::packet::{PacketBuilder, PacketNumber, QuicVersion};
|
||||
use crate::cid::ConnectionIdRef;
|
||||
use crate::packet::{PacketBuilder, PacketNumber};
|
||||
use crate::recovery::RecoveryToken;
|
||||
use crate::recv_stream::RxStreamOrderer;
|
||||
use crate::send_stream::TxBuffer;
|
||||
use crate::stats::FrameStats;
|
||||
use crate::tparams::{TpZeroRttChecker, TransportParameters, TransportParametersHandler};
|
||||
use crate::tracking::PacketNumberSpace;
|
||||
use crate::version::Version;
|
||||
use crate::{Error, Res};
|
||||
|
||||
const MAX_AUTH_TAG: usize = 32;
|
||||
|
@ -48,6 +51,8 @@ thread_local!(pub(crate) static OVERWRITE_INVOCATIONS: RefCell<Option<PacketNumb
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Crypto {
|
||||
version: Version,
|
||||
protocols: Vec<String>,
|
||||
pub(crate) tls: Agent,
|
||||
pub(crate) streams: CryptoStreams,
|
||||
pub(crate) states: CryptoStates,
|
||||
|
@ -57,9 +62,9 @@ type TpHandler = Rc<RefCell<TransportParametersHandler>>;
|
|||
|
||||
impl Crypto {
|
||||
pub fn new(
|
||||
version: QuicVersion,
|
||||
version: Version,
|
||||
mut agent: Agent,
|
||||
protocols: &[impl AsRef<str>],
|
||||
protocols: Vec<String>,
|
||||
tphandler: TpHandler,
|
||||
) -> Res<Self> {
|
||||
agent.set_version_range(TLS_VERSION_1_3, TLS_VERSION_1_3)?;
|
||||
|
@ -68,7 +73,7 @@ impl Crypto {
|
|||
TLS_AES_256_GCM_SHA384,
|
||||
TLS_CHACHA20_POLY1305_SHA256,
|
||||
])?;
|
||||
agent.set_alpn(protocols)?;
|
||||
agent.set_alpn(&protocols)?;
|
||||
agent.disable_end_of_early_data()?;
|
||||
// Always enable 0-RTT on the client, but the server needs
|
||||
// more configuration passed to server_enable_0rtt.
|
||||
|
@ -76,20 +81,33 @@ impl Crypto {
|
|||
c.enable_0rtt()?;
|
||||
}
|
||||
let extension = match version {
|
||||
QuicVersion::Version1 => 0x39,
|
||||
QuicVersion::Draft29
|
||||
| QuicVersion::Draft30
|
||||
| QuicVersion::Draft31
|
||||
| QuicVersion::Draft32 => 0xffa5,
|
||||
Version::Version2 | Version::Version1 => 0x39,
|
||||
Version::Draft29 | Version::Draft30 | Version::Draft31 | Version::Draft32 => 0xffa5,
|
||||
};
|
||||
agent.extension_handler(extension, tphandler)?;
|
||||
Ok(Self {
|
||||
version,
|
||||
protocols,
|
||||
tls: agent,
|
||||
streams: Default::default(),
|
||||
states: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the name of the server. (Only works for the client currently).
|
||||
pub fn server_name(&self) -> Option<&str> {
|
||||
if let Agent::Client(c) = &self.tls {
|
||||
Some(c.server_name())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the set of enabled protocols.
|
||||
pub fn protocols(&self) -> &[String] {
|
||||
&self.protocols
|
||||
}
|
||||
|
||||
pub fn server_enable_0rtt(
|
||||
&mut self,
|
||||
tphandler: TpHandler,
|
||||
|
@ -174,7 +192,7 @@ impl Crypto {
|
|||
}
|
||||
|
||||
/// Enable 0-RTT and return `true` if it is enabled successfully.
|
||||
pub fn enable_0rtt(&mut self, role: Role) -> Res<bool> {
|
||||
pub fn enable_0rtt(&mut self, version: Version, role: Role) -> Res<bool> {
|
||||
let info = self.tls.preinfo()?;
|
||||
// `info.early_data()` returns false for a server,
|
||||
// so use `early_data_cipher()` to tell if 0-RTT is enabled.
|
||||
|
@ -193,16 +211,23 @@ impl Crypto {
|
|||
),
|
||||
};
|
||||
let secret = secret.ok_or(Error::InternalError(1))?;
|
||||
self.states.set_0rtt_keys(dir, &secret, cipher.unwrap());
|
||||
self.states
|
||||
.set_0rtt_keys(version, dir, &secret, cipher.unwrap());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Lock in a compatible upgrade.
|
||||
pub fn confirm_version(&mut self, confirmed: Version) {
|
||||
self.states.confirm_version(self.version, confirmed);
|
||||
self.version = confirmed;
|
||||
}
|
||||
|
||||
/// Returns true if new handshake keys were installed.
|
||||
pub fn install_keys(&mut self, role: Role) -> Res<bool> {
|
||||
if !self.tls.state().is_final() {
|
||||
let installed_hs = self.install_handshake_keys()?;
|
||||
if role == Role::Server {
|
||||
self.maybe_install_application_write_key()?;
|
||||
self.maybe_install_application_write_key(self.version)?;
|
||||
}
|
||||
Ok(installed_hs)
|
||||
} else {
|
||||
|
@ -228,22 +253,22 @@ impl Crypto {
|
|||
}
|
||||
.ok_or(Error::InternalError(3))?;
|
||||
self.states
|
||||
.set_handshake_keys(&write_secret, &read_secret, cipher);
|
||||
.set_handshake_keys(self.version, &write_secret, &read_secret, cipher);
|
||||
qdebug!([self], "Handshake keys installed");
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn maybe_install_application_write_key(&mut self) -> Res<()> {
|
||||
fn maybe_install_application_write_key(&mut self, version: Version) -> Res<()> {
|
||||
qtrace!([self], "Attempt to install application write key");
|
||||
if let Some(secret) = self.tls.write_secret(TLS_EPOCH_APPLICATION_DATA) {
|
||||
self.states.set_application_write_key(secret)?;
|
||||
self.states.set_application_write_key(version, secret)?;
|
||||
qdebug!([self], "Application write key installed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_application_keys(&mut self, expire_0rtt: Instant) -> Res<()> {
|
||||
self.maybe_install_application_write_key()?;
|
||||
pub fn install_application_keys(&mut self, version: Version, expire_0rtt: Instant) -> Res<()> {
|
||||
self.maybe_install_application_write_key(version)?;
|
||||
// The write key might have been installed earlier, but it should
|
||||
// always be installed now.
|
||||
debug_assert!(self.states.app_write.is_some());
|
||||
|
@ -252,7 +277,7 @@ impl Crypto {
|
|||
.read_secret(TLS_EPOCH_APPLICATION_DATA)
|
||||
.ok_or(Error::InternalError(4))?;
|
||||
self.states
|
||||
.set_application_read_key(read_secret, expire_0rtt)?;
|
||||
.set_application_read_key(version, read_secret, expire_0rtt)?;
|
||||
qdebug!([self], "application read keys installed");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -316,19 +341,21 @@ impl Crypto {
|
|||
&mut self,
|
||||
new_token: Option<&[u8]>,
|
||||
tps: &TransportParameters,
|
||||
version: Version,
|
||||
rtt: u64,
|
||||
) -> Option<ResumptionToken> {
|
||||
if let Agent::Client(ref mut c) = self.tls {
|
||||
if let Some(ref t) = c.resumption_token() {
|
||||
qtrace!("TLS token {}", hex(t.as_ref()));
|
||||
let mut enc = Encoder::default();
|
||||
enc.encode_uint(4, version.wire_version());
|
||||
enc.encode_varint(rtt);
|
||||
enc.encode_vvec_with(|enc_inner| {
|
||||
tps.encode(enc_inner);
|
||||
});
|
||||
enc.encode_vvec(new_token.unwrap_or(&[]));
|
||||
enc.encode(t.as_ref());
|
||||
qinfo!("resumption token {}", hex_snip_middle(&enc[..]));
|
||||
qinfo!("resumption token {}", hex_snip_middle(enc.as_ref()));
|
||||
Some(ResumptionToken::new(enc.into(), t.expiration_time()))
|
||||
} else {
|
||||
None
|
||||
|
@ -361,6 +388,9 @@ pub enum CryptoDxDirection {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct CryptoDxState {
|
||||
/// The QUIC version.
|
||||
version: Version,
|
||||
/// Whether packets protected with this state will be read or written.
|
||||
direction: CryptoDxDirection,
|
||||
/// The epoch of this crypto state. This initially tracks TLS epochs
|
||||
/// via DTLS: 0 = initial, 1 = 0-RTT, 2 = handshake, 3 = application.
|
||||
|
@ -383,22 +413,26 @@ pub struct CryptoDxState {
|
|||
impl CryptoDxState {
|
||||
#[allow(clippy::reversed_empty_ranges)] // To initialize an empty range.
|
||||
pub fn new(
|
||||
version: Version,
|
||||
direction: CryptoDxDirection,
|
||||
epoch: Epoch,
|
||||
secret: &SymKey,
|
||||
cipher: Cipher,
|
||||
) -> Self {
|
||||
qinfo!(
|
||||
"Making {:?} {} CryptoDxState, cipher={}",
|
||||
"Making {:?} {} CryptoDxState, v={:?} cipher={}",
|
||||
direction,
|
||||
epoch,
|
||||
cipher
|
||||
version,
|
||||
cipher,
|
||||
);
|
||||
let hplabel = String::from(version.label_prefix()) + "hp";
|
||||
Self {
|
||||
version,
|
||||
direction,
|
||||
epoch: usize::from(epoch),
|
||||
aead: Aead::new(TLS_VERSION_1_3, cipher, secret, "quic ").unwrap(),
|
||||
hpkey: HpKey::extract(TLS_VERSION_1_3, cipher, secret, "quic hp").unwrap(),
|
||||
aead: Aead::new(TLS_VERSION_1_3, cipher, secret, version.label_prefix()).unwrap(),
|
||||
hpkey: HpKey::extract(TLS_VERSION_1_3, cipher, secret, &hplabel).unwrap(),
|
||||
used_pn: 0..0,
|
||||
min_pn: 0,
|
||||
invocations: Self::limit(direction, cipher),
|
||||
|
@ -406,27 +440,13 @@ impl CryptoDxState {
|
|||
}
|
||||
|
||||
pub fn new_initial(
|
||||
quic_version: QuicVersion,
|
||||
version: Version,
|
||||
direction: CryptoDxDirection,
|
||||
label: &str,
|
||||
dcid: &[u8],
|
||||
) -> Self {
|
||||
const INITIAL_SALT_V1: &[u8] = &[
|
||||
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8,
|
||||
0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
|
||||
];
|
||||
const INITIAL_SALT_29_32: &[u8] = &[
|
||||
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
|
||||
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
|
||||
];
|
||||
qtrace!("new_initial for {:?}", quic_version);
|
||||
let salt = match quic_version {
|
||||
QuicVersion::Version1 => INITIAL_SALT_V1,
|
||||
QuicVersion::Draft29
|
||||
| QuicVersion::Draft30
|
||||
| QuicVersion::Draft31
|
||||
| QuicVersion::Draft32 => INITIAL_SALT_29_32,
|
||||
};
|
||||
qtrace!("new_initial {:?} {}", version, ConnectionIdRef::from(dcid));
|
||||
let salt = version.initial_salt();
|
||||
let cipher = TLS_AES_128_GCM_SHA256;
|
||||
let initial_secret = hkdf::extract(
|
||||
TLS_VERSION_1_3,
|
||||
|
@ -439,7 +459,7 @@ impl CryptoDxState {
|
|||
let secret =
|
||||
hkdf::expand_label(TLS_VERSION_1_3, cipher, &initial_secret, &[], label).unwrap();
|
||||
|
||||
Self::new(direction, TLS_EPOCH_INITIAL, &secret, cipher)
|
||||
Self::new(version, direction, TLS_EPOCH_INITIAL, &secret, cipher)
|
||||
}
|
||||
|
||||
/// Determine the confidentiality and integrity limits for the cipher.
|
||||
|
@ -495,9 +515,16 @@ impl CryptoDxState {
|
|||
Self::limit(CryptoDxDirection::Write, cipher)
|
||||
};
|
||||
Self {
|
||||
version: self.version,
|
||||
direction: self.direction,
|
||||
epoch: self.epoch + 1,
|
||||
aead: Aead::new(TLS_VERSION_1_3, cipher, next_secret, "quic ").unwrap(),
|
||||
aead: Aead::new(
|
||||
TLS_VERSION_1_3,
|
||||
cipher,
|
||||
next_secret,
|
||||
self.version.label_prefix(),
|
||||
)
|
||||
.unwrap(),
|
||||
hpkey: self.hpkey.clone(),
|
||||
used_pn: pn..pn,
|
||||
min_pn: pn,
|
||||
|
@ -505,6 +532,11 @@ impl CryptoDxState {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn key_phase(&self) -> bool {
|
||||
// Epoch 3 => 0, 4 => 1, 5 => 0, 6 => 1, ...
|
||||
|
@ -517,8 +549,7 @@ impl CryptoDxState {
|
|||
debug_assert_eq!(self.direction, prev.direction);
|
||||
let next = prev.next_pn();
|
||||
self.min_pn = next;
|
||||
// TODO(mt) use Range::is_empty() when available
|
||||
if self.used_pn.start == self.used_pn.end {
|
||||
if self.used_pn.is_empty() {
|
||||
self.used_pn = next..next;
|
||||
Ok(())
|
||||
} else if prev.used_pn.end > self.used_pn.start {
|
||||
|
@ -638,7 +669,7 @@ impl CryptoDxState {
|
|||
// This matches the value in packet.rs
|
||||
const CLIENT_CID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
|
||||
Self::new_initial(
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
CryptoDxDirection::Write,
|
||||
"server in",
|
||||
CLIENT_CID,
|
||||
|
@ -698,9 +729,14 @@ pub(crate) struct CryptoDxAppData {
|
|||
}
|
||||
|
||||
impl CryptoDxAppData {
|
||||
pub fn new(dir: CryptoDxDirection, secret: SymKey, cipher: Cipher) -> Res<Self> {
|
||||
pub fn new(
|
||||
version: Version,
|
||||
dir: CryptoDxDirection,
|
||||
secret: SymKey,
|
||||
cipher: Cipher,
|
||||
) -> Res<Self> {
|
||||
Ok(Self {
|
||||
dx: CryptoDxState::new(dir, TLS_EPOCH_APPLICATION_DATA, &secret, cipher),
|
||||
dx: CryptoDxState::new(version, dir, TLS_EPOCH_APPLICATION_DATA, &secret, cipher),
|
||||
cipher,
|
||||
next_secret: Self::update_secret(cipher, &secret)?,
|
||||
})
|
||||
|
@ -737,9 +773,14 @@ pub enum CryptoSpace {
|
|||
ApplicationData,
|
||||
}
|
||||
|
||||
/// All of the keying material needed for a connection.
|
||||
///
|
||||
/// Note that the methods on this struct take a version but those are only ever
|
||||
/// used for Initial keys; a version has been selected at the time we need to
|
||||
/// get other keys, so those have fixed versions.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CryptoStates {
|
||||
initial: Option<CryptoState>,
|
||||
initials: HashMap<Version, CryptoState>,
|
||||
handshake: Option<CryptoState>,
|
||||
zero_rtt: Option<CryptoDxState>, // One direction only!
|
||||
cipher: Cipher,
|
||||
|
@ -757,14 +798,15 @@ impl CryptoStates {
|
|||
/// not yet available.
|
||||
pub fn select_tx_mut(
|
||||
&mut self,
|
||||
version: Version,
|
||||
space: PacketNumberSpace,
|
||||
) -> Option<(CryptoSpace, &mut CryptoDxState)> {
|
||||
match space {
|
||||
PacketNumberSpace::Initial => self
|
||||
.tx_mut(CryptoSpace::Initial)
|
||||
.tx_mut(version, CryptoSpace::Initial)
|
||||
.map(|dx| (CryptoSpace::Initial, dx)),
|
||||
PacketNumberSpace::Handshake => self
|
||||
.tx_mut(CryptoSpace::Handshake)
|
||||
.tx_mut(version, CryptoSpace::Handshake)
|
||||
.map(|dx| (CryptoSpace::Handshake, dx)),
|
||||
PacketNumberSpace::ApplicationData => {
|
||||
if let Some(app) = self.app_write.as_mut() {
|
||||
|
@ -776,10 +818,14 @@ impl CryptoStates {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tx_mut<'a>(&'a mut self, cspace: CryptoSpace) -> Option<&'a mut CryptoDxState> {
|
||||
pub fn tx_mut<'a>(
|
||||
&'a mut self,
|
||||
version: Version,
|
||||
cspace: CryptoSpace,
|
||||
) -> Option<&'a mut CryptoDxState> {
|
||||
let tx = |k: Option<&'a mut CryptoState>| k.map(|dx| &mut dx.tx);
|
||||
match cspace {
|
||||
CryptoSpace::Initial => tx(self.initial.as_mut()),
|
||||
CryptoSpace::Initial => tx(self.initials.get_mut(&version)),
|
||||
CryptoSpace::ZeroRtt => self
|
||||
.zero_rtt
|
||||
.as_mut()
|
||||
|
@ -789,10 +835,10 @@ impl CryptoStates {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tx<'a>(&'a self, cspace: CryptoSpace) -> Option<&'a CryptoDxState> {
|
||||
pub fn tx<'a>(&'a self, version: Version, cspace: CryptoSpace) -> Option<&'a CryptoDxState> {
|
||||
let tx = |k: Option<&'a CryptoState>| k.map(|dx| &dx.tx);
|
||||
match cspace {
|
||||
CryptoSpace::Initial => tx(self.initial.as_ref()),
|
||||
CryptoSpace::Initial => tx(self.initials.get(&version)),
|
||||
CryptoSpace::ZeroRtt => self
|
||||
.zero_rtt
|
||||
.as_ref()
|
||||
|
@ -802,13 +848,17 @@ impl CryptoStates {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn select_tx(&self, space: PacketNumberSpace) -> Option<(CryptoSpace, &CryptoDxState)> {
|
||||
pub fn select_tx(
|
||||
&self,
|
||||
version: Version,
|
||||
space: PacketNumberSpace,
|
||||
) -> Option<(CryptoSpace, &CryptoDxState)> {
|
||||
match space {
|
||||
PacketNumberSpace::Initial => self
|
||||
.tx(CryptoSpace::Initial)
|
||||
.tx(version, CryptoSpace::Initial)
|
||||
.map(|dx| (CryptoSpace::Initial, dx)),
|
||||
PacketNumberSpace::Handshake => self
|
||||
.tx(CryptoSpace::Handshake)
|
||||
.tx(version, CryptoSpace::Handshake)
|
||||
.map(|dx| (CryptoSpace::Handshake, dx)),
|
||||
PacketNumberSpace::ApplicationData => {
|
||||
if let Some(app) = self.app_write.as_ref() {
|
||||
|
@ -820,22 +870,23 @@ impl CryptoStates {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn rx_hp(&mut self, cspace: CryptoSpace) -> Option<&mut CryptoDxState> {
|
||||
pub fn rx_hp(&mut self, version: Version, cspace: CryptoSpace) -> Option<&mut CryptoDxState> {
|
||||
if let CryptoSpace::ApplicationData = cspace {
|
||||
self.app_read.as_mut().map(|ar| &mut ar.dx)
|
||||
} else {
|
||||
self.rx(cspace, false)
|
||||
self.rx(version, cspace, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rx<'a>(
|
||||
&'a mut self,
|
||||
version: Version,
|
||||
cspace: CryptoSpace,
|
||||
key_phase: bool,
|
||||
) -> Option<&'a mut CryptoDxState> {
|
||||
let rx = |x: Option<&'a mut CryptoState>| x.map(|dx| &mut dx.rx);
|
||||
match cspace {
|
||||
CryptoSpace::Initial => rx(self.initial.as_mut()),
|
||||
CryptoSpace::Initial => rx(self.initials.get_mut(&version)),
|
||||
CryptoSpace::ZeroRtt => self
|
||||
.zero_rtt
|
||||
.as_mut()
|
||||
|
@ -864,52 +915,101 @@ impl CryptoStates {
|
|||
pub fn rx_pending(&self, space: CryptoSpace) -> bool {
|
||||
match space {
|
||||
CryptoSpace::Initial | CryptoSpace::ZeroRtt => false,
|
||||
CryptoSpace::Handshake => self.handshake.is_none() && self.initial.is_some(),
|
||||
CryptoSpace::Handshake => self.handshake.is_none() && !self.initials.is_empty(),
|
||||
CryptoSpace::ApplicationData => self.app_read.is_none(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the initial crypto state.
|
||||
pub fn init(&mut self, quic_version: QuicVersion, role: Role, dcid: &[u8]) {
|
||||
/// Note that the version here can change and that's OK.
|
||||
pub fn init<'v, V>(&mut self, versions: V, role: Role, dcid: &[u8])
|
||||
where
|
||||
V: IntoIterator<Item = &'v Version>,
|
||||
{
|
||||
const CLIENT_INITIAL_LABEL: &str = "client in";
|
||||
const SERVER_INITIAL_LABEL: &str = "server in";
|
||||
|
||||
qinfo!(
|
||||
[self],
|
||||
"Creating initial cipher state role={:?} dcid={}",
|
||||
role,
|
||||
hex(dcid)
|
||||
);
|
||||
|
||||
let (write, read) = match role {
|
||||
Role::Client => (CLIENT_INITIAL_LABEL, SERVER_INITIAL_LABEL),
|
||||
Role::Server => (SERVER_INITIAL_LABEL, CLIENT_INITIAL_LABEL),
|
||||
};
|
||||
|
||||
let mut initial = CryptoState {
|
||||
tx: CryptoDxState::new_initial(quic_version, CryptoDxDirection::Write, write, dcid),
|
||||
rx: CryptoDxState::new_initial(quic_version, CryptoDxDirection::Read, read, dcid),
|
||||
};
|
||||
if let Some(prev) = &self.initial {
|
||||
for v in versions {
|
||||
qinfo!(
|
||||
[self],
|
||||
"Continue packet numbers for initial after retry (write is {:?})",
|
||||
prev.rx.used_pn,
|
||||
"Creating initial cipher state v={:?}, role={:?} dcid={}",
|
||||
v,
|
||||
role,
|
||||
hex(dcid)
|
||||
);
|
||||
initial.tx.continuation(&prev.tx).unwrap();
|
||||
|
||||
let mut initial = CryptoState {
|
||||
tx: CryptoDxState::new_initial(*v, CryptoDxDirection::Write, write, dcid),
|
||||
rx: CryptoDxState::new_initial(*v, CryptoDxDirection::Read, read, dcid),
|
||||
};
|
||||
if let Some(prev) = self.initials.get(v) {
|
||||
qinfo!(
|
||||
[self],
|
||||
"Continue packet numbers for initial after retry (write is {:?})",
|
||||
prev.rx.used_pn,
|
||||
);
|
||||
initial.tx.continuation(&prev.tx).unwrap();
|
||||
}
|
||||
self.initials.insert(*v, initial);
|
||||
}
|
||||
self.initial = Some(initial);
|
||||
}
|
||||
|
||||
pub fn set_0rtt_keys(&mut self, dir: CryptoDxDirection, secret: &SymKey, cipher: Cipher) {
|
||||
/// At a server, we can be more targeted in initializing.
|
||||
/// Initialize on demand: either to decrypt Initial packets that we receive
|
||||
/// or after a version has been selected.
|
||||
/// This is maybe slightly inefficient in the first case, because we might
|
||||
/// not need the send keys if the packet is subsequently discarded, but
|
||||
/// the overall effort is small enough to write off.
|
||||
pub fn init_server(&mut self, version: Version, dcid: &[u8]) {
|
||||
if !self.initials.contains_key(&version) {
|
||||
self.init(&[version], Role::Server, dcid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn confirm_version(&mut self, orig: Version, confirmed: Version) {
|
||||
if orig != confirmed {
|
||||
// This part where the old data is removed and then re-added is to
|
||||
// appease the borrow checker.
|
||||
// Note that on the server, we might not have initials for |orig| if it
|
||||
// was configured for |orig| and only |confirmed| Initial packets arrived.
|
||||
if let Some(prev) = self.initials.remove(&orig) {
|
||||
let next = self.initials.get_mut(&confirmed).unwrap();
|
||||
next.tx.continuation(&prev.tx).unwrap();
|
||||
self.initials.insert(orig, prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_0rtt_keys(
|
||||
&mut self,
|
||||
version: Version,
|
||||
dir: CryptoDxDirection,
|
||||
secret: &SymKey,
|
||||
cipher: Cipher,
|
||||
) {
|
||||
qtrace!([self], "install 0-RTT keys");
|
||||
self.zero_rtt = Some(CryptoDxState::new(dir, TLS_EPOCH_ZERO_RTT, secret, cipher));
|
||||
self.zero_rtt = Some(CryptoDxState::new(
|
||||
version,
|
||||
dir,
|
||||
TLS_EPOCH_ZERO_RTT,
|
||||
secret,
|
||||
cipher,
|
||||
));
|
||||
}
|
||||
|
||||
/// Discard keys and return true if that happened.
|
||||
pub fn discard(&mut self, space: PacketNumberSpace) -> bool {
|
||||
match space {
|
||||
PacketNumberSpace::Initial => self.initial.take().is_some(),
|
||||
PacketNumberSpace::Initial => {
|
||||
let empty = self.initials.is_empty();
|
||||
self.initials.clear();
|
||||
!empty
|
||||
}
|
||||
PacketNumberSpace::Handshake => self.handshake.take().is_some(),
|
||||
PacketNumberSpace::ApplicationData => panic!("Can't drop application data keys"),
|
||||
}
|
||||
|
@ -926,6 +1026,7 @@ impl CryptoStates {
|
|||
|
||||
pub fn set_handshake_keys(
|
||||
&mut self,
|
||||
version: Version,
|
||||
write_secret: &SymKey,
|
||||
read_secret: &SymKey,
|
||||
cipher: Cipher,
|
||||
|
@ -933,12 +1034,14 @@ impl CryptoStates {
|
|||
self.cipher = cipher;
|
||||
self.handshake = Some(CryptoState {
|
||||
tx: CryptoDxState::new(
|
||||
version,
|
||||
CryptoDxDirection::Write,
|
||||
TLS_EPOCH_HANDSHAKE,
|
||||
write_secret,
|
||||
cipher,
|
||||
),
|
||||
rx: CryptoDxState::new(
|
||||
version,
|
||||
CryptoDxDirection::Read,
|
||||
TLS_EPOCH_HANDSHAKE,
|
||||
read_secret,
|
||||
|
@ -947,10 +1050,10 @@ impl CryptoStates {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn set_application_write_key(&mut self, secret: SymKey) -> Res<()> {
|
||||
pub fn set_application_write_key(&mut self, version: Version, secret: SymKey) -> Res<()> {
|
||||
debug_assert!(self.app_write.is_none());
|
||||
debug_assert_ne!(self.cipher, 0);
|
||||
let mut app = CryptoDxAppData::new(CryptoDxDirection::Write, secret, self.cipher)?;
|
||||
let mut app = CryptoDxAppData::new(version, CryptoDxDirection::Write, secret, self.cipher)?;
|
||||
if let Some(z) = &self.zero_rtt {
|
||||
if z.direction == CryptoDxDirection::Write {
|
||||
app.dx.continuation(z)?;
|
||||
|
@ -961,10 +1064,15 @@ impl CryptoStates {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_application_read_key(&mut self, secret: SymKey, expire_0rtt: Instant) -> Res<()> {
|
||||
pub fn set_application_read_key(
|
||||
&mut self,
|
||||
version: Version,
|
||||
secret: SymKey,
|
||||
expire_0rtt: Instant,
|
||||
) -> Res<()> {
|
||||
debug_assert!(self.app_write.is_some(), "should have write keys installed");
|
||||
debug_assert!(self.app_read.is_none());
|
||||
let mut app = CryptoDxAppData::new(CryptoDxDirection::Read, secret, self.cipher)?;
|
||||
let mut app = CryptoDxAppData::new(version, CryptoDxDirection::Read, secret, self.cipher)?;
|
||||
if let Some(z) = &self.zero_rtt {
|
||||
if z.direction == CryptoDxDirection::Read {
|
||||
app.dx.continuation(z)?;
|
||||
|
@ -1120,11 +1228,16 @@ impl CryptoStates {
|
|||
cipher: TLS_AES_128_GCM_SHA256,
|
||||
next_secret: hkdf::import_key(TLS_VERSION_1_3, &[0xaa; 32]).unwrap(),
|
||||
};
|
||||
Self {
|
||||
initial: Some(CryptoState {
|
||||
let mut initials = HashMap::new();
|
||||
initials.insert(
|
||||
Version::Version1,
|
||||
CryptoState {
|
||||
tx: CryptoDxState::test_default(),
|
||||
rx: read(0),
|
||||
}),
|
||||
},
|
||||
);
|
||||
Self {
|
||||
initials,
|
||||
handshake: None,
|
||||
zero_rtt: None,
|
||||
cipher: TLS_AES_128_GCM_SHA256,
|
||||
|
@ -1146,13 +1259,14 @@ impl CryptoStates {
|
|||
let secret = hkdf::import_key(TLS_VERSION_1_3, SECRET).unwrap();
|
||||
let app_read = |epoch| CryptoDxAppData {
|
||||
dx: CryptoDxState {
|
||||
version: Version::Version1,
|
||||
direction: CryptoDxDirection::Read,
|
||||
epoch,
|
||||
aead: Aead::new(
|
||||
TLS_VERSION_1_3,
|
||||
TLS_CHACHA20_POLY1305_SHA256,
|
||||
&secret,
|
||||
"quic ",
|
||||
"quic ", // This is a v1 test so hard-code the label.
|
||||
)
|
||||
.unwrap(),
|
||||
hpkey: HpKey::extract(
|
||||
|
@ -1170,7 +1284,7 @@ impl CryptoStates {
|
|||
next_secret: secret.clone(),
|
||||
};
|
||||
Self {
|
||||
initial: None,
|
||||
initials: HashMap::new(),
|
||||
handshake: None,
|
||||
zero_rtt: None,
|
||||
cipher: TLS_CHACHA20_POLY1305_SHA256,
|
||||
|
|
|
@ -13,6 +13,8 @@ use crate::packet::{PacketNumber, PacketType};
|
|||
use crate::path::PathRef;
|
||||
use neqo_common::{qdebug, Decoder};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn dump_packet(
|
||||
conn: &Connection,
|
||||
|
@ -37,7 +39,7 @@ pub fn dump_packet(
|
|||
}
|
||||
};
|
||||
if let Some(x) = f.dump() {
|
||||
s.push_str(&format!("\n {} {}", dir, &x));
|
||||
write!(&mut s, "\n {} {}", dir, &x).unwrap();
|
||||
}
|
||||
}
|
||||
qdebug!([conn], "pn={} type={:?} {}{}", pn, pt, path.borrow(), s);
|
||||
|
|
|
@ -93,13 +93,13 @@ impl From<ConnectionError> for CloseError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Default, Clone)]
|
||||
#[derive(PartialEq, Eq, Debug, Default, Clone)]
|
||||
pub struct AckRange {
|
||||
pub(crate) gap: u64,
|
||||
pub(crate) range: u64,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Frame<'a> {
|
||||
Padding,
|
||||
Ping,
|
||||
|
|
|
@ -36,6 +36,7 @@ pub mod stream_id;
|
|||
pub mod streams;
|
||||
pub mod tparams;
|
||||
mod tracking;
|
||||
pub mod version;
|
||||
|
||||
pub use self::cc::CongestionControlAlgorithm;
|
||||
pub use self::cid::{
|
||||
|
@ -47,9 +48,9 @@ pub use self::connection::{
|
|||
};
|
||||
pub use self::events::{ConnectionEvent, ConnectionEvents};
|
||||
pub use self::frame::CloseError;
|
||||
pub use self::packet::QuicVersion;
|
||||
pub use self::stats::Stats;
|
||||
pub use self::stream_id::{StreamId, StreamType};
|
||||
pub use self::version::Version;
|
||||
|
||||
pub use self::recv_stream::RECV_BUFFER_SIZE;
|
||||
pub use self::send_stream::SEND_BUFFER_SIZE;
|
||||
|
@ -86,6 +87,7 @@ pub enum Error {
|
|||
ConnectionState,
|
||||
DecodingFrame,
|
||||
DecryptError,
|
||||
DisabledVersion,
|
||||
HandshakeFailed,
|
||||
IdleTimeout,
|
||||
IntegerOverflow,
|
||||
|
@ -95,7 +97,7 @@ pub enum Error {
|
|||
InvalidResumptionToken,
|
||||
InvalidRetry,
|
||||
InvalidStreamId,
|
||||
KeysDiscarded,
|
||||
KeysDiscarded(crypto::CryptoSpace),
|
||||
/// Packet protection keys are exhausted.
|
||||
/// Also used when too many key updates have happened.
|
||||
KeysExhausted,
|
||||
|
@ -143,6 +145,7 @@ impl Error {
|
|||
// As we have a special error code for ECH fallbacks, we lose the alert.
|
||||
// Send the server "ech_required" directly.
|
||||
Self::EchRetry(_) => 0x100 + 121,
|
||||
Self::VersionNegotiation => 0x53f8,
|
||||
// All the rest are internal errors.
|
||||
_ => 1,
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
// Encoding and decoding packets off the wire.
|
||||
use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef, MAX_CONNECTION_ID_LEN};
|
||||
use crate::crypto::{CryptoDxState, CryptoSpace, CryptoStates};
|
||||
use crate::version::{Version, WireVersion};
|
||||
use crate::{Error, Res};
|
||||
|
||||
use neqo_common::{hex, hex_with_len, qtrace, qwarn, Decoder, Encoder};
|
||||
|
@ -19,11 +20,6 @@ use std::iter::ExactSizeIterator;
|
|||
use std::ops::{Deref, DerefMut, Range};
|
||||
use std::time::Instant;
|
||||
|
||||
const PACKET_TYPE_INITIAL: u8 = 0x0;
|
||||
const PACKET_TYPE_0RTT: u8 = 0x01;
|
||||
const PACKET_TYPE_HANDSHAKE: u8 = 0x2;
|
||||
const PACKET_TYPE_RETRY: u8 = 0x03;
|
||||
|
||||
pub const PACKET_BIT_LONG: u8 = 0x80;
|
||||
const PACKET_BIT_SHORT: u8 = 0x00;
|
||||
const PACKET_BIT_FIXED_QUIC: u8 = 0x40;
|
||||
|
@ -40,7 +36,6 @@ const MAX_PACKET_NUMBER_LEN: usize = 4;
|
|||
mod retry;
|
||||
|
||||
pub type PacketNumber = u64;
|
||||
type Version = u32;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PacketType {
|
||||
|
@ -55,15 +50,29 @@ pub enum PacketType {
|
|||
|
||||
impl PacketType {
|
||||
#[must_use]
|
||||
fn code(self) -> u8 {
|
||||
match self {
|
||||
Self::Initial => PACKET_TYPE_INITIAL,
|
||||
Self::ZeroRtt => PACKET_TYPE_0RTT,
|
||||
Self::Handshake => PACKET_TYPE_HANDSHAKE,
|
||||
Self::Retry => PACKET_TYPE_RETRY,
|
||||
_ => panic!("shouldn't be here"),
|
||||
fn from_byte(t: u8, v: Version) -> Self {
|
||||
// Version2 adds one to the type, modulo 4
|
||||
match t.wrapping_sub(u8::from(v == Version::Version2)) & 3 {
|
||||
0 => Self::Initial,
|
||||
1 => Self::ZeroRtt,
|
||||
2 => Self::Handshake,
|
||||
3 => Self::Retry,
|
||||
_ => panic!("packet type out of range"),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn to_byte(self, v: Version) -> u8 {
|
||||
let t = match self {
|
||||
Self::Initial => 0,
|
||||
Self::ZeroRtt => 1,
|
||||
Self::Handshake => 2,
|
||||
Self::Retry => 3,
|
||||
_ => panic!("not a long header packet type"),
|
||||
};
|
||||
// Version2 adds one to the type, modulo 4
|
||||
(t + u8::from(v == Version::Version2)) & 3
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PacketType> for CryptoSpace {
|
||||
|
@ -89,53 +98,6 @@ impl From<CryptoSpace> for PacketType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum QuicVersion {
|
||||
Version1,
|
||||
Draft29,
|
||||
Draft30,
|
||||
Draft31,
|
||||
Draft32,
|
||||
}
|
||||
|
||||
impl QuicVersion {
|
||||
pub fn as_u32(self) -> Version {
|
||||
match self {
|
||||
Self::Version1 => 1,
|
||||
Self::Draft29 => 0xff00_0000 + 29,
|
||||
Self::Draft30 => 0xff00_0000 + 30,
|
||||
Self::Draft31 => 0xff00_0000 + 31,
|
||||
Self::Draft32 => 0xff00_0000 + 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QuicVersion {
|
||||
fn default() -> Self {
|
||||
Self::Version1
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Version> for QuicVersion {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(ver: Version) -> Res<Self> {
|
||||
if ver == 1 {
|
||||
Ok(Self::Version1)
|
||||
} else if ver == 0xff00_0000 + 29 {
|
||||
Ok(Self::Draft29)
|
||||
} else if ver == 0xff00_0000 + 30 {
|
||||
Ok(Self::Draft30)
|
||||
} else if ver == 0xff00_0000 + 31 {
|
||||
Ok(Self::Draft31)
|
||||
} else if ver == 0xff00_0000 + 32 {
|
||||
Ok(Self::Draft32)
|
||||
} else {
|
||||
Err(Error::VersionNegotiation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PacketBuilderOffsets {
|
||||
/// The bits of the first octet that need masking.
|
||||
first_byte_mask: u8,
|
||||
|
@ -214,7 +176,7 @@ impl PacketBuilder {
|
|||
pub fn long(
|
||||
mut encoder: Encoder,
|
||||
pt: PacketType,
|
||||
quic_version: QuicVersion,
|
||||
version: Version,
|
||||
dcid: impl AsRef<[u8]>,
|
||||
scid: impl AsRef<[u8]>,
|
||||
) -> Self {
|
||||
|
@ -225,8 +187,8 @@ impl PacketBuilder {
|
|||
if limit > encoder.len()
|
||||
&& 11 + dcid.as_ref().len() + scid.as_ref().len() < limit - encoder.len()
|
||||
{
|
||||
encoder.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC | pt.code() << 4);
|
||||
encoder.encode_uint(4, quic_version.as_u32());
|
||||
encoder.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC | pt.to_byte(version) << 4);
|
||||
encoder.encode_uint(4, version.wire_version());
|
||||
encoder.encode_vec(1, dcid.as_ref());
|
||||
encoder.encode_vec(1, scid.as_ref());
|
||||
} else {
|
||||
|
@ -248,7 +210,7 @@ impl PacketBuilder {
|
|||
}
|
||||
|
||||
fn is_long(&self) -> bool {
|
||||
self[self.header.start] & 0x80 == PACKET_BIT_LONG
|
||||
self.as_ref()[self.header.start] & 0x80 == PACKET_BIT_LONG
|
||||
}
|
||||
|
||||
/// This stores a value that can be used as a limit. This does not cause
|
||||
|
@ -305,16 +267,12 @@ impl PacketBuilder {
|
|||
let mask = if quic_bit { PACKET_BIT_FIXED_QUIC } else { 0 }
|
||||
| if self.is_long() { 0 } else { PACKET_BIT_SPIN };
|
||||
let first = self.header.start;
|
||||
self[first] ^= random(1)[0] & mask;
|
||||
self.encoder.as_mut()[first] ^= random(1)[0] & mask;
|
||||
}
|
||||
|
||||
/// For an Initial packet, encode the token.
|
||||
/// If you fail to do this, then you will not get a valid packet.
|
||||
pub fn initial_token(&mut self, token: &[u8]) {
|
||||
debug_assert_eq!(
|
||||
self.encoder[self.header.start] & 0xb0,
|
||||
PACKET_BIT_LONG | PACKET_TYPE_INITIAL << 4
|
||||
);
|
||||
if Encoder::vvec_len(token.len()) < self.remaining() {
|
||||
self.encoder.encode_vvec(token);
|
||||
} else {
|
||||
|
@ -348,15 +306,15 @@ impl PacketBuilder {
|
|||
self.offsets.pn = pn_offset..self.encoder.len();
|
||||
|
||||
// Now encode the packet number length and save the header length.
|
||||
self.encoder[self.header.start] |= u8::try_from(pn_len - 1).unwrap();
|
||||
self.encoder.as_mut()[self.header.start] |= u8::try_from(pn_len - 1).unwrap();
|
||||
self.header.end = self.encoder.len();
|
||||
self.pn = pn;
|
||||
}
|
||||
|
||||
fn write_len(&mut self, expansion: usize) {
|
||||
let len = self.encoder.len() - (self.offsets.len + 2) + expansion;
|
||||
self.encoder[self.offsets.len] = 0x40 | ((len >> 8) & 0x3f) as u8;
|
||||
self.encoder[self.offsets.len + 1] = (len & 0xff) as u8;
|
||||
self.encoder.as_mut()[self.offsets.len] = 0x40 | ((len >> 8) & 0x3f) as u8;
|
||||
self.encoder.as_mut()[self.offsets.len + 1] = (len & 0xff) as u8;
|
||||
}
|
||||
|
||||
fn pad_for_crypto(&mut self, crypto: &mut CryptoDxState) {
|
||||
|
@ -402,8 +360,8 @@ impl PacketBuilder {
|
|||
self.write_len(crypto.expansion());
|
||||
}
|
||||
|
||||
let hdr = &self.encoder[self.header.clone()];
|
||||
let body = &self.encoder[self.header.end..];
|
||||
let hdr = &self.encoder.as_ref()[self.header.clone()];
|
||||
let body = &self.encoder.as_ref()[self.header.end..];
|
||||
qtrace!(
|
||||
"Packet build pn={} hdr={} body={}",
|
||||
self.pn,
|
||||
|
@ -419,9 +377,9 @@ impl PacketBuilder {
|
|||
let mask = crypto.compute_mask(sample)?;
|
||||
|
||||
// Apply the mask.
|
||||
self.encoder[self.header.start] ^= mask[0] & self.offsets.first_byte_mask;
|
||||
self.encoder.as_mut()[self.header.start] ^= mask[0] & self.offsets.first_byte_mask;
|
||||
for (i, j) in (1..=self.offsets.pn.len()).zip(self.offsets.pn) {
|
||||
self.encoder[j] ^= mask[i];
|
||||
self.encoder.as_mut()[j] ^= mask[i];
|
||||
}
|
||||
|
||||
// Finally, cut off the plaintext and add back the ciphertext.
|
||||
|
@ -449,7 +407,7 @@ impl PacketBuilder {
|
|||
/// As Retry is odd (it has to be constructed with leading bytes),
|
||||
/// this returns a Vec<u8> rather than building on an encoder.
|
||||
pub fn retry(
|
||||
quic_version: QuicVersion,
|
||||
version: Version,
|
||||
dcid: &[u8],
|
||||
scid: &[u8],
|
||||
token: &[u8],
|
||||
|
@ -461,17 +419,17 @@ impl PacketBuilder {
|
|||
encoder.encode_byte(
|
||||
PACKET_BIT_LONG
|
||||
| PACKET_BIT_FIXED_QUIC
|
||||
| (PACKET_TYPE_RETRY << 4)
|
||||
| (PacketType::Retry.to_byte(version) << 4)
|
||||
| (random(1)[0] & 0xf),
|
||||
);
|
||||
encoder.encode_uint(4, quic_version.as_u32());
|
||||
encoder.encode_uint(4, version.wire_version());
|
||||
encoder.encode_vec(1, dcid);
|
||||
encoder.encode_vec(1, scid);
|
||||
debug_assert_ne!(token.len(), 0);
|
||||
encoder.encode(token);
|
||||
let tag = retry::use_aead(quic_version, |aead| {
|
||||
let tag = retry::use_aead(version, |aead| {
|
||||
let mut buf = vec![0; aead.expansion()];
|
||||
Ok(aead.encrypt(0, &encoder, &[], &mut buf)?.to_vec())
|
||||
Ok(aead.encrypt(0, encoder.as_ref(), &[], &mut buf)?.to_vec())
|
||||
})?;
|
||||
encoder.encode(&tag);
|
||||
let mut complete: Vec<u8> = encoder.into();
|
||||
|
@ -479,25 +437,34 @@ impl PacketBuilder {
|
|||
}
|
||||
|
||||
/// Make a Version Negotiation packet.
|
||||
pub fn version_negotiation(dcid: &[u8], scid: &[u8]) -> Vec<u8> {
|
||||
pub fn version_negotiation(
|
||||
dcid: &[u8],
|
||||
scid: &[u8],
|
||||
client_version: u32,
|
||||
versions: &[Version],
|
||||
) -> Vec<u8> {
|
||||
let mut encoder = Encoder::default();
|
||||
let mut grease = random(5);
|
||||
let mut grease = random(4);
|
||||
// This will not include the "QUIC bit" sometimes. Intentionally.
|
||||
encoder.encode_byte(PACKET_BIT_LONG | (grease[4] & 0x7f));
|
||||
encoder.encode_byte(PACKET_BIT_LONG | (grease[3] & 0x7f));
|
||||
encoder.encode(&[0; 4]); // Zero version == VN.
|
||||
encoder.encode_vec(1, dcid);
|
||||
encoder.encode_vec(1, scid);
|
||||
encoder.encode_uint(4, QuicVersion::Version1.as_u32());
|
||||
encoder.encode_uint(4, QuicVersion::Draft29.as_u32());
|
||||
encoder.encode_uint(4, QuicVersion::Draft30.as_u32());
|
||||
encoder.encode_uint(4, QuicVersion::Draft31.as_u32());
|
||||
encoder.encode_uint(4, QuicVersion::Draft32.as_u32());
|
||||
|
||||
for v in versions {
|
||||
encoder.encode_uint(4, v.wire_version());
|
||||
}
|
||||
// Add a greased version, using the randomness already generated.
|
||||
for g in &mut grease[..4] {
|
||||
for g in &mut grease[..3] {
|
||||
*g = *g & 0xf0 | 0x0a;
|
||||
}
|
||||
encoder.encode(&grease[0..4]);
|
||||
encoder.into()
|
||||
|
||||
// Ensure our greased version does not collide with the client version
|
||||
// by making the last byte differ from the client initial.
|
||||
grease[3] = (client_version.wrapping_add(0x10) & 0xf0) as u8 | 0x0a;
|
||||
encoder.encode(&grease[..4]);
|
||||
|
||||
Vec::from(encoder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,7 +503,7 @@ pub struct PublicPacket<'a> {
|
|||
/// The size of the header, not including the packet number.
|
||||
header_len: usize,
|
||||
/// Protocol version, if present in header.
|
||||
quic_version: Option<QuicVersion>,
|
||||
version: Option<WireVersion>,
|
||||
/// A reference to the entire packet, including the header.
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
@ -556,11 +523,11 @@ impl<'a> PublicPacket<'a> {
|
|||
fn decode_long(
|
||||
decoder: &mut Decoder<'a>,
|
||||
packet_type: PacketType,
|
||||
quic_version: QuicVersion,
|
||||
version: Version,
|
||||
) -> Res<(&'a [u8], usize)> {
|
||||
if packet_type == PacketType::Retry {
|
||||
let header_len = decoder.offset();
|
||||
let expansion = retry::expansion(quic_version);
|
||||
let expansion = retry::expansion(version);
|
||||
let token = Self::opt(decoder.decode(decoder.remaining() - expansion))?;
|
||||
if token.is_empty() {
|
||||
return Err(Error::InvalidPacket);
|
||||
|
@ -603,7 +570,7 @@ impl<'a> PublicPacket<'a> {
|
|||
scid: None,
|
||||
token: &[],
|
||||
header_len,
|
||||
quic_version: None,
|
||||
version: None,
|
||||
data,
|
||||
},
|
||||
&[],
|
||||
|
@ -611,7 +578,7 @@ impl<'a> PublicPacket<'a> {
|
|||
}
|
||||
|
||||
// Generic long header.
|
||||
let version = Version::try_from(Self::opt(decoder.decode_uint(4))?).unwrap();
|
||||
let version = WireVersion::try_from(Self::opt(decoder.decode_uint(4))?).unwrap();
|
||||
let dcid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?);
|
||||
let scid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?);
|
||||
|
||||
|
@ -624,7 +591,7 @@ impl<'a> PublicPacket<'a> {
|
|||
scid: Some(scid),
|
||||
token: &[],
|
||||
header_len: decoder.offset(),
|
||||
quic_version: None,
|
||||
version: None,
|
||||
data,
|
||||
},
|
||||
&[],
|
||||
|
@ -632,7 +599,7 @@ impl<'a> PublicPacket<'a> {
|
|||
}
|
||||
|
||||
// Check that this is a long header from a supported version.
|
||||
let quic_version = if let Ok(v) = QuicVersion::try_from(version) {
|
||||
let version = if let Ok(v) = Version::try_from(version) {
|
||||
v
|
||||
} else {
|
||||
return Ok((
|
||||
|
@ -642,7 +609,7 @@ impl<'a> PublicPacket<'a> {
|
|||
scid: Some(scid),
|
||||
token: &[],
|
||||
header_len: decoder.offset(),
|
||||
quic_version: None,
|
||||
version: Some(version),
|
||||
data,
|
||||
},
|
||||
&[],
|
||||
|
@ -652,16 +619,10 @@ impl<'a> PublicPacket<'a> {
|
|||
if dcid.len() > MAX_CONNECTION_ID_LEN || scid.len() > MAX_CONNECTION_ID_LEN {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
let packet_type = match (first >> 4) & 3 {
|
||||
PACKET_TYPE_INITIAL => PacketType::Initial,
|
||||
PACKET_TYPE_0RTT => PacketType::ZeroRtt,
|
||||
PACKET_TYPE_HANDSHAKE => PacketType::Handshake,
|
||||
PACKET_TYPE_RETRY => PacketType::Retry,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let packet_type = PacketType::from_byte((first >> 4) & 3, version);
|
||||
|
||||
// The type-specific code includes a token. This consumes the remainder of the packet.
|
||||
let (token, header_len) = Self::decode_long(&mut decoder, packet_type, quic_version)?;
|
||||
let (token, header_len) = Self::decode_long(&mut decoder, packet_type, version)?;
|
||||
let end = data.len() - decoder.remaining();
|
||||
let (data, remainder) = data.split_at(end);
|
||||
Ok((
|
||||
|
@ -671,7 +632,7 @@ impl<'a> PublicPacket<'a> {
|
|||
scid: Some(scid),
|
||||
token,
|
||||
header_len,
|
||||
quic_version: Some(quic_version),
|
||||
version: Some(version.wire_version()),
|
||||
data,
|
||||
},
|
||||
remainder,
|
||||
|
@ -683,7 +644,7 @@ impl<'a> PublicPacket<'a> {
|
|||
if self.packet_type != PacketType::Retry {
|
||||
return false;
|
||||
}
|
||||
let version = self.quic_version.unwrap();
|
||||
let version = self.version().unwrap();
|
||||
let expansion = retry::expansion(version);
|
||||
if self.data.len() <= expansion {
|
||||
return false;
|
||||
|
@ -694,7 +655,7 @@ impl<'a> PublicPacket<'a> {
|
|||
encoder.encode(header);
|
||||
retry::use_aead(version, |aead| {
|
||||
let mut buf = vec![0; expansion];
|
||||
Ok(aead.decrypt(0, &encoder, tag, &mut buf)?.is_empty())
|
||||
Ok(aead.decrypt(0, encoder.as_ref(), tag, &mut buf)?.is_empty())
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
@ -724,8 +685,13 @@ impl<'a> PublicPacket<'a> {
|
|||
self.token
|
||||
}
|
||||
|
||||
pub fn version(&self) -> Option<QuicVersion> {
|
||||
self.quic_version
|
||||
pub fn version(&self) -> Option<Version> {
|
||||
self.version.and_then(|v| Version::try_from(v).ok())
|
||||
}
|
||||
|
||||
pub fn wire_version(&self) -> WireVersion {
|
||||
debug_assert!(self.version.is_some());
|
||||
self.version.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
|
@ -808,16 +774,20 @@ impl<'a> PublicPacket<'a> {
|
|||
|
||||
pub fn decrypt(&self, crypto: &mut CryptoStates, release_at: Instant) -> Res<DecryptedPacket> {
|
||||
let cspace: CryptoSpace = self.packet_type.into();
|
||||
// When we don't have a version, the crypto code doesn't need a version
|
||||
// for lookup, so use the default, but fix it up if decryption succeeds.
|
||||
let version = self.version().unwrap_or_default();
|
||||
// This has to work in two stages because we need to remove header protection
|
||||
// before picking the keys to use.
|
||||
if let Some(rx) = crypto.rx_hp(cspace) {
|
||||
if let Some(rx) = crypto.rx_hp(version, cspace) {
|
||||
// Note that this will dump early, which creates a side-channel.
|
||||
// This is OK in this case because we the only reason this can
|
||||
// fail is if the cryptographic module is bad or the packet is
|
||||
// too small (which is public information).
|
||||
let (key_phase, pn, header, body) = self.decrypt_header(rx)?;
|
||||
qtrace!([rx], "decoded header: {:?}", header);
|
||||
let rx = crypto.rx(cspace, key_phase).unwrap();
|
||||
let rx = crypto.rx(version, cspace, key_phase).unwrap();
|
||||
let version = rx.version(); // Version fixup; see above.
|
||||
let d = rx.decrypt(pn, &header, body)?;
|
||||
// If this is the first packet ever successfully decrypted
|
||||
// using `rx`, make sure to initiate a key update.
|
||||
|
@ -826,6 +796,7 @@ impl<'a> PublicPacket<'a> {
|
|||
}
|
||||
crypto.check_pn_overlap()?;
|
||||
Ok(DecryptedPacket {
|
||||
version,
|
||||
pt: self.packet_type,
|
||||
pn,
|
||||
data: d,
|
||||
|
@ -834,16 +805,16 @@ impl<'a> PublicPacket<'a> {
|
|||
Err(Error::KeysPending(cspace))
|
||||
} else {
|
||||
qtrace!("keys for {:?} already discarded", cspace);
|
||||
Err(Error::KeysDiscarded)
|
||||
Err(Error::KeysDiscarded(cspace))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_versions(&self) -> Res<Vec<Version>> {
|
||||
pub fn supported_versions(&self) -> Res<Vec<WireVersion>> {
|
||||
assert_eq!(self.packet_type, PacketType::VersionNegotiation);
|
||||
let mut decoder = Decoder::new(&self.data[self.header_len..]);
|
||||
let mut res = Vec::new();
|
||||
while decoder.remaining() > 0 {
|
||||
let version = Version::try_from(Self::opt(decoder.decode_uint(4))?)?;
|
||||
let version = WireVersion::try_from(Self::opt(decoder.decode_uint(4))?)?;
|
||||
res.push(version);
|
||||
}
|
||||
Ok(res)
|
||||
|
@ -863,12 +834,17 @@ impl fmt::Debug for PublicPacket<'_> {
|
|||
}
|
||||
|
||||
pub struct DecryptedPacket {
|
||||
version: Version,
|
||||
pt: PacketType,
|
||||
pn: PacketNumber,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DecryptedPacket {
|
||||
pub fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn packet_type(&self) -> PacketType {
|
||||
self.pt
|
||||
}
|
||||
|
@ -890,7 +866,7 @@ impl Deref for DecryptedPacket {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::crypto::{CryptoDxState, CryptoStates};
|
||||
use crate::{EmptyConnectionIdGenerator, QuicVersion, RandomConnectionIdGenerator};
|
||||
use crate::{EmptyConnectionIdGenerator, RandomConnectionIdGenerator, Version};
|
||||
use neqo_common::Encoder;
|
||||
use test_fixture::{fixture_init, now};
|
||||
|
||||
|
@ -936,7 +912,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::long(
|
||||
Encoder::new(),
|
||||
PacketType::Initial,
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
);
|
||||
|
@ -944,7 +920,7 @@ mod tests {
|
|||
builder.pn(1, 2);
|
||||
builder.encode(SAMPLE_INITIAL_PAYLOAD);
|
||||
let packet = builder.build(&mut prot).expect("build");
|
||||
assert_eq!(&packet[..], SAMPLE_INITIAL);
|
||||
assert_eq!(packet.as_ref(), SAMPLE_INITIAL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -971,24 +947,24 @@ mod tests {
|
|||
fn disallow_long_dcid() {
|
||||
let mut enc = Encoder::new();
|
||||
enc.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC);
|
||||
enc.encode_uint(4, QuicVersion::default().as_u32());
|
||||
enc.encode_uint(4, Version::default().wire_version());
|
||||
enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 1]);
|
||||
enc.encode_vec(1, &[]);
|
||||
enc.encode(&[0xff; 40]); // junk
|
||||
|
||||
assert!(PublicPacket::decode(&enc, &cid_mgr()).is_err());
|
||||
assert!(PublicPacket::decode(enc.as_ref(), &cid_mgr()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_long_scid() {
|
||||
let mut enc = Encoder::new();
|
||||
enc.encode_byte(PACKET_BIT_LONG | PACKET_BIT_FIXED_QUIC);
|
||||
enc.encode_uint(4, QuicVersion::default().as_u32());
|
||||
enc.encode_uint(4, Version::default().wire_version());
|
||||
enc.encode_vec(1, &[]);
|
||||
enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 2]);
|
||||
enc.encode(&[0xff; 40]); // junk
|
||||
|
||||
assert!(PublicPacket::decode(&enc, &cid_mgr()).is_err());
|
||||
assert!(PublicPacket::decode(enc.as_ref(), &cid_mgr()).is_err());
|
||||
}
|
||||
|
||||
const SAMPLE_SHORT: &[u8] = &[
|
||||
|
@ -1007,7 +983,7 @@ mod tests {
|
|||
let packet = builder
|
||||
.build(&mut CryptoDxState::test_default())
|
||||
.expect("build");
|
||||
assert_eq!(&packet[..], SAMPLE_SHORT);
|
||||
assert_eq!(packet.as_ref(), SAMPLE_SHORT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1019,7 +995,7 @@ mod tests {
|
|||
PacketBuilder::short(Encoder::new(), true, &ConnectionId::from(SERVER_CID));
|
||||
builder.scramble(true);
|
||||
builder.pn(0, 1);
|
||||
firsts.push(builder[0]);
|
||||
firsts.push(builder.as_ref()[0]);
|
||||
}
|
||||
let is_set = |bit| move |v| v & bit == bit;
|
||||
// There should be at least one value with the QUIC bit set:
|
||||
|
@ -1077,7 +1053,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::long(
|
||||
Encoder::new(),
|
||||
PacketType::Handshake,
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
&ConnectionId::from(CLIENT_CID),
|
||||
);
|
||||
|
@ -1092,8 +1068,8 @@ mod tests {
|
|||
builder.encode(&[0]); // Minimal size (packet number is big enough).
|
||||
let encoder = builder.build(&mut prot).expect("build");
|
||||
assert_eq!(
|
||||
&first[..],
|
||||
&encoder[..first.len()],
|
||||
first.as_ref(),
|
||||
&encoder.as_ref()[..first.len()],
|
||||
"the first packet should be a prefix"
|
||||
);
|
||||
assert_eq!(encoder.len(), 45 + 29);
|
||||
|
@ -1111,14 +1087,14 @@ mod tests {
|
|||
let mut builder = PacketBuilder::long(
|
||||
Encoder::new(),
|
||||
PacketType::Handshake,
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(&[][..]),
|
||||
);
|
||||
builder.pn(0, 1);
|
||||
builder.encode(&[1, 2, 3]);
|
||||
let packet = builder.build(&mut CryptoDxState::test_default()).unwrap();
|
||||
assert_eq!(&packet[..], EXPECTED);
|
||||
assert_eq!(packet.as_ref(), EXPECTED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1130,13 +1106,13 @@ mod tests {
|
|||
let mut builder = PacketBuilder::long(
|
||||
Encoder::new(),
|
||||
PacketType::Handshake,
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(&[][..]),
|
||||
);
|
||||
builder.pn(0, 1);
|
||||
builder.scramble(true);
|
||||
if (builder[0] & PACKET_BIT_FIXED_QUIC) == 0 {
|
||||
if (builder.as_ref()[0] & PACKET_BIT_FIXED_QUIC) == 0 {
|
||||
found_unset = true;
|
||||
} else {
|
||||
found_set = true;
|
||||
|
@ -1151,7 +1127,7 @@ mod tests {
|
|||
let mut builder = PacketBuilder::long(
|
||||
Encoder::new(),
|
||||
PacketType::Initial,
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
&ConnectionId::from(&[][..]),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
);
|
||||
|
@ -1185,7 +1161,7 @@ mod tests {
|
|||
let builder = PacketBuilder::long(
|
||||
encoder,
|
||||
PacketType::Initial,
|
||||
QuicVersion::default(),
|
||||
Version::default(),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
&ConnectionId::from(SERVER_CID),
|
||||
);
|
||||
|
@ -1193,6 +1169,12 @@ mod tests {
|
|||
assert_eq!(builder.abort(), encoder_copy);
|
||||
}
|
||||
|
||||
const SAMPLE_RETRY_V2: &[u8] = &[
|
||||
0xcf, 0x70, 0x9a, 0x50, 0xc4, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x1d, 0xc7, 0x11, 0x30, 0xcd, 0x1e, 0xd3, 0x9d, 0x6e, 0xfc,
|
||||
0xee, 0x5c, 0x85, 0x80, 0x65, 0x01,
|
||||
];
|
||||
|
||||
const SAMPLE_RETRY_V1: &[u8] = &[
|
||||
0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x04, 0xa2, 0x65, 0xba, 0x2e, 0xff, 0x4d, 0x82, 0x90, 0x58,
|
||||
|
@ -1225,10 +1207,10 @@ mod tests {
|
|||
|
||||
const RETRY_TOKEN: &[u8] = b"token";
|
||||
|
||||
fn build_retry_single(quic_version: QuicVersion, sample_retry: &[u8]) {
|
||||
fn build_retry_single(version: Version, sample_retry: &[u8]) {
|
||||
fixture_init();
|
||||
let retry =
|
||||
PacketBuilder::retry(quic_version, &[], SERVER_CID, RETRY_TOKEN, CLIENT_CID).unwrap();
|
||||
PacketBuilder::retry(version, &[], SERVER_CID, RETRY_TOKEN, CLIENT_CID).unwrap();
|
||||
|
||||
let (packet, remainder) = PublicPacket::decode(&retry, &cid_mgr()).unwrap();
|
||||
assert!(packet.is_valid_retry(&ConnectionId::from(CLIENT_CID)));
|
||||
|
@ -1240,35 +1222,43 @@ mod tests {
|
|||
assert_eq!(&retry, &sample_retry);
|
||||
} else {
|
||||
// Otherwise, just check that the header is OK.
|
||||
assert_eq!(retry[0] & 0xf0, 0xf0);
|
||||
assert_eq!(
|
||||
retry[0] & 0xf0,
|
||||
0xc0 | (PacketType::Retry.to_byte(version) << 4)
|
||||
);
|
||||
let header_range = 1..retry.len() - 16;
|
||||
assert_eq!(&retry[header_range.clone()], &sample_retry[header_range]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_retry_v2() {
|
||||
build_retry_single(Version::Version2, SAMPLE_RETRY_V2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_retry_v1() {
|
||||
build_retry_single(QuicVersion::Version1, SAMPLE_RETRY_V1);
|
||||
build_retry_single(Version::Version1, SAMPLE_RETRY_V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_retry_29() {
|
||||
build_retry_single(QuicVersion::Draft29, SAMPLE_RETRY_29);
|
||||
build_retry_single(Version::Draft29, SAMPLE_RETRY_29);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_retry_30() {
|
||||
build_retry_single(QuicVersion::Draft30, SAMPLE_RETRY_30);
|
||||
build_retry_single(Version::Draft30, SAMPLE_RETRY_30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_retry_31() {
|
||||
build_retry_single(QuicVersion::Draft31, SAMPLE_RETRY_31);
|
||||
build_retry_single(Version::Draft31, SAMPLE_RETRY_31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_retry_32() {
|
||||
build_retry_single(QuicVersion::Draft32, SAMPLE_RETRY_32);
|
||||
build_retry_single(Version::Draft32, SAMPLE_RETRY_32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1277,6 +1267,7 @@ mod tests {
|
|||
// Odds are approximately 1 in 8 that the full comparison doesn't happen
|
||||
// for a given version.
|
||||
for _ in 0..32 {
|
||||
build_retry_v2();
|
||||
build_retry_v1();
|
||||
build_retry_29();
|
||||
build_retry_30();
|
||||
|
@ -1285,36 +1276,46 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn decode_retry(quic_version: QuicVersion, sample_retry: &[u8]) {
|
||||
fn decode_retry(version: Version, sample_retry: &[u8]) {
|
||||
fixture_init();
|
||||
let (packet, remainder) =
|
||||
PublicPacket::decode(sample_retry, &RandomConnectionIdGenerator::new(5)).unwrap();
|
||||
assert!(packet.is_valid_retry(&ConnectionId::from(CLIENT_CID)));
|
||||
assert_eq!(Some(quic_version), packet.quic_version);
|
||||
assert_eq!(Some(version), packet.version());
|
||||
assert!(packet.dcid().is_empty());
|
||||
assert_eq!(&packet.scid()[..], SERVER_CID);
|
||||
assert_eq!(packet.token(), RETRY_TOKEN);
|
||||
assert!(remainder.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_retry_v2() {
|
||||
decode_retry(Version::Version2, SAMPLE_RETRY_V2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_retry_v1() {
|
||||
decode_retry(Version::Version1, SAMPLE_RETRY_V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_retry_29() {
|
||||
decode_retry(QuicVersion::Draft29, SAMPLE_RETRY_29);
|
||||
decode_retry(Version::Draft29, SAMPLE_RETRY_29);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_retry_30() {
|
||||
decode_retry(QuicVersion::Draft30, SAMPLE_RETRY_30);
|
||||
decode_retry(Version::Draft30, SAMPLE_RETRY_30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_retry_31() {
|
||||
decode_retry(QuicVersion::Draft31, SAMPLE_RETRY_31);
|
||||
decode_retry(Version::Draft31, SAMPLE_RETRY_31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_retry_32() {
|
||||
decode_retry(QuicVersion::Draft32, SAMPLE_RETRY_32);
|
||||
decode_retry(Version::Draft32, SAMPLE_RETRY_32);
|
||||
}
|
||||
|
||||
/// Check some packets that are clearly not valid Retry packets.
|
||||
|
@ -1326,11 +1327,11 @@ mod tests {
|
|||
|
||||
assert!(PublicPacket::decode(&[], &cid_mgr).is_err());
|
||||
|
||||
let (packet, remainder) = PublicPacket::decode(SAMPLE_RETRY_29, &cid_mgr).unwrap();
|
||||
let (packet, remainder) = PublicPacket::decode(SAMPLE_RETRY_V1, &cid_mgr).unwrap();
|
||||
assert!(remainder.is_empty());
|
||||
assert!(packet.is_valid_retry(&odcid));
|
||||
|
||||
let mut damaged_retry = SAMPLE_RETRY_29.to_vec();
|
||||
let mut damaged_retry = SAMPLE_RETRY_V1.to_vec();
|
||||
let last = damaged_retry.len() - 1;
|
||||
damaged_retry[last] ^= 66;
|
||||
let (packet, remainder) = PublicPacket::decode(&damaged_retry, &cid_mgr).unwrap();
|
||||
|
@ -1352,15 +1353,16 @@ mod tests {
|
|||
|
||||
const SAMPLE_VN: &[u8] = &[
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x08,
|
||||
0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00,
|
||||
0x1d, 0xff, 0x00, 0x00, 0x1e, 0xff, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x20, 0x0a, 0x0a,
|
||||
0x0a, 0x0a,
|
||||
0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x70, 0x9a, 0x50, 0xc4, 0x00, 0x00, 0x00,
|
||||
0x01, 0xff, 0x00, 0x00, 0x20, 0xff, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x1e, 0xff, 0x00,
|
||||
0x00, 0x1d, 0x0a, 0x0a, 0x0a, 0x0a,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn build_vn() {
|
||||
fixture_init();
|
||||
let mut vn = PacketBuilder::version_negotiation(SERVER_CID, CLIENT_CID);
|
||||
let mut vn =
|
||||
PacketBuilder::version_negotiation(SERVER_CID, CLIENT_CID, 0x0a0a0a0a, &Version::all());
|
||||
// Erase randomness from greasing...
|
||||
assert_eq!(vn.len(), SAMPLE_VN.len());
|
||||
vn[0] &= 0x80;
|
||||
|
@ -1370,6 +1372,14 @@ mod tests {
|
|||
assert_eq!(&vn, &SAMPLE_VN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vn_do_not_repeat_client_grease() {
|
||||
fixture_init();
|
||||
let vn =
|
||||
PacketBuilder::version_negotiation(SERVER_CID, CLIENT_CID, 0x0a0a0a0a, &Version::all());
|
||||
assert_ne!(&vn[SAMPLE_VN.len() - 4..], &[0x0a, 0x0a, 0x0a, 0x0a]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_vn() {
|
||||
let (packet, remainder) =
|
||||
|
@ -1390,11 +1400,11 @@ mod tests {
|
|||
enc.encode_vec(1, BIG_DCID);
|
||||
enc.encode_vec(1, BIG_SCID);
|
||||
enc.encode_uint(4, 0x1a2a_3a4a_u64);
|
||||
enc.encode_uint(4, QuicVersion::default().as_u32());
|
||||
enc.encode_uint(4, Version::default().wire_version());
|
||||
enc.encode_uint(4, 0x5a6a_7a8a_u64);
|
||||
|
||||
let (packet, remainder) =
|
||||
PublicPacket::decode(&enc, &EmptyConnectionIdGenerator::default()).unwrap();
|
||||
PublicPacket::decode(enc.as_ref(), &EmptyConnectionIdGenerator::default()).unwrap();
|
||||
assert!(remainder.is_empty());
|
||||
assert_eq!(&packet.dcid[..], BIG_DCID);
|
||||
assert!(packet.scid.is_some());
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#![deny(clippy::pedantic)]
|
||||
|
||||
use crate::packet::QuicVersion;
|
||||
use crate::version::Version;
|
||||
use crate::{Error, Res};
|
||||
|
||||
use neqo_common::qerror;
|
||||
|
@ -14,37 +14,33 @@ use neqo_crypto::{hkdf, Aead, TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3};
|
|||
|
||||
use std::cell::RefCell;
|
||||
|
||||
const RETRY_SECRET_29: &[u8] = &[
|
||||
0x8b, 0x0d, 0x37, 0xeb, 0x85, 0x35, 0x02, 0x2e, 0xbc, 0x8d, 0x76, 0xa2, 0x07, 0xd8, 0x0d, 0xf2,
|
||||
0x26, 0x46, 0xec, 0x06, 0xdc, 0x80, 0x96, 0x42, 0xc3, 0x0a, 0x8b, 0xaa, 0x2b, 0xaa, 0xff, 0x4c,
|
||||
];
|
||||
const RETRY_SECRET_V1: &[u8] = &[
|
||||
0xd9, 0xc9, 0x94, 0x3e, 0x61, 0x01, 0xfd, 0x20, 0x00, 0x21, 0x50, 0x6b, 0xcc, 0x02, 0x81, 0x4c,
|
||||
0x73, 0x03, 0x0f, 0x25, 0xc7, 0x9d, 0x71, 0xce, 0x87, 0x6e, 0xca, 0x87, 0x6e, 0x6f, 0xca, 0x8e,
|
||||
];
|
||||
|
||||
/// The AEAD used for Retry is fixed, so use thread local storage.
|
||||
fn make_aead(secret: &[u8]) -> Aead {
|
||||
fn make_aead(version: Version) -> Aead {
|
||||
#[cfg(debug_assertions)]
|
||||
::neqo_crypto::assert_initialized();
|
||||
|
||||
let secret = hkdf::import_key(TLS_VERSION_1_3, secret).unwrap();
|
||||
Aead::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic ").unwrap()
|
||||
let secret = hkdf::import_key(TLS_VERSION_1_3, version.retry_secret()).unwrap();
|
||||
Aead::new(
|
||||
TLS_VERSION_1_3,
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
&secret,
|
||||
version.label_prefix(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
thread_local!(static RETRY_AEAD_29: RefCell<Aead> = RefCell::new(make_aead(RETRY_SECRET_29)));
|
||||
thread_local!(static RETRY_AEAD_V1: RefCell<Aead> = RefCell::new(make_aead(RETRY_SECRET_V1)));
|
||||
thread_local!(static RETRY_AEAD_29: RefCell<Aead> = RefCell::new(make_aead(Version::Draft29)));
|
||||
thread_local!(static RETRY_AEAD_V1: RefCell<Aead> = RefCell::new(make_aead(Version::Version1)));
|
||||
thread_local!(static RETRY_AEAD_V2: RefCell<Aead> = RefCell::new(make_aead(Version::Version2)));
|
||||
|
||||
/// Run a function with the appropriate Retry AEAD.
|
||||
pub fn use_aead<F, T>(quic_version: QuicVersion, f: F) -> Res<T>
|
||||
pub fn use_aead<F, T>(version: Version, f: F) -> Res<T>
|
||||
where
|
||||
F: FnOnce(&Aead) -> Res<T>,
|
||||
{
|
||||
match quic_version {
|
||||
QuicVersion::Version1 => &RETRY_AEAD_V1,
|
||||
QuicVersion::Draft29
|
||||
| QuicVersion::Draft30
|
||||
| QuicVersion::Draft31
|
||||
| QuicVersion::Draft32 => &RETRY_AEAD_29,
|
||||
match version {
|
||||
Version::Version2 => &RETRY_AEAD_V2,
|
||||
Version::Version1 => &RETRY_AEAD_V1,
|
||||
Version::Draft29 | Version::Draft30 | Version::Draft31 | Version::Draft32 => &RETRY_AEAD_29,
|
||||
}
|
||||
.try_with(|aead| f(&aead.borrow()))
|
||||
.map_err(|e| {
|
||||
|
@ -54,8 +50,8 @@ where
|
|||
}
|
||||
|
||||
/// Determine how large the expansion is for a given key.
|
||||
pub fn expansion(quic_version: QuicVersion) -> usize {
|
||||
if let Ok(ex) = use_aead(quic_version, |aead| Ok(aead.expansion())) {
|
||||
pub fn expansion(version: Version) -> usize {
|
||||
if let Ok(ex) = use_aead(version, |aead| Ok(aead.expansion())) {
|
||||
ex
|
||||
} else {
|
||||
panic!("Unable to access Retry AEAD")
|
||||
|
|
|
@ -539,6 +539,9 @@ pub struct Path {
|
|||
received_bytes: usize,
|
||||
/// The number of bytes sent on this path.
|
||||
sent_bytes: usize,
|
||||
|
||||
/// For logging of events.
|
||||
qlog: NeqoQlog,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
|
@ -552,7 +555,7 @@ impl Path {
|
|||
now: Instant,
|
||||
) -> Self {
|
||||
let mut sender = PacketSender::new(cc, Self::mtu_by_addr(remote.ip()), now);
|
||||
sender.set_qlog(qlog);
|
||||
sender.set_qlog(qlog.clone());
|
||||
Self {
|
||||
local,
|
||||
remote,
|
||||
|
@ -566,6 +569,7 @@ impl Path {
|
|||
sender,
|
||||
received_bytes: 0,
|
||||
sent_bytes: 0,
|
||||
qlog,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -928,7 +932,27 @@ impl Path {
|
|||
}
|
||||
|
||||
/// Discard a packet that previously might have been in-flight.
|
||||
pub fn discard_packet(&mut self, sent: &SentPacket) {
|
||||
pub fn discard_packet(&mut self, sent: &SentPacket, now: Instant) {
|
||||
if self.rtt.first_sample_time().is_none() {
|
||||
// When discarding a packet there might not be a good RTT estimate.
|
||||
// But discards only occur after receiving something, so that means
|
||||
// that there is some RTT information, which is better than nothing.
|
||||
// Two cases: 1. at the client when handling a Retry and
|
||||
// 2. at the server when disposing the Initial packet number space.
|
||||
qinfo!(
|
||||
[self],
|
||||
"discarding a packet without an RTT estimate; guessing RTT={:?}",
|
||||
now - sent.time_sent
|
||||
);
|
||||
self.rtt.update(
|
||||
&mut self.qlog,
|
||||
now - sent.time_sent,
|
||||
Duration::new(0, 0),
|
||||
false,
|
||||
now,
|
||||
);
|
||||
}
|
||||
|
||||
self.sender.discard(sent);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::path::PathRef;
|
|||
use crate::stream_id::StreamType as NeqoStreamType;
|
||||
use crate::tparams::{self, TransportParametersHandler};
|
||||
use crate::tracking::SentPacket;
|
||||
use crate::QuicVersion;
|
||||
use crate::Version;
|
||||
|
||||
pub fn connection_tparams_set(qlog: &mut NeqoQlog, tph: &TransportParametersHandler) {
|
||||
qlog.add_event(|| {
|
||||
|
@ -98,7 +98,7 @@ fn connection_started(qlog: &mut NeqoQlog, path: &PathRef) {
|
|||
Some("QUIC".into()),
|
||||
p.local_address().port().into(),
|
||||
p.remote_address().port().into(),
|
||||
Some(format!("{:x}", QuicVersion::default().as_u32())),
|
||||
Some(format!("{:x}", Version::default().wire_version())),
|
||||
Some(format!("{}", p.local_cid())),
|
||||
Some(format!("{}", p.remote_cid())),
|
||||
))
|
||||
|
@ -110,7 +110,7 @@ pub fn connection_state_updated(qlog: &mut NeqoQlog, new: &State) {
|
|||
Some(Event::connection_state_updated_min(match new {
|
||||
State::Init => qlog::ConnectionState::Attempted,
|
||||
State::WaitInitial => qlog::ConnectionState::Attempted,
|
||||
State::Handshaking => qlog::ConnectionState::Handshake,
|
||||
State::WaitVersion | State::Handshaking => qlog::ConnectionState::Handshake,
|
||||
State::Connected => qlog::ConnectionState::Active,
|
||||
State::Confirmed => qlog::ConnectionState::Active,
|
||||
State::Closing { .. } => qlog::ConnectionState::Draining,
|
||||
|
|
|
@ -14,7 +14,6 @@ use neqo_common::Encoder;
|
|||
use std::cmp::min;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub const MAX_QUIC_DATAGRAM: u64 = 65535;
|
||||
|
||||
|
@ -53,10 +52,9 @@ impl QuicDatagram {
|
|||
}
|
||||
}
|
||||
|
||||
impl Deref for QuicDatagram {
|
||||
type Target = [u8];
|
||||
impl AsRef<[u8]> for QuicDatagram {
|
||||
#[must_use]
|
||||
fn deref(&self) -> &[u8] {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.data[..]
|
||||
}
|
||||
}
|
||||
|
@ -110,17 +108,17 @@ impl QuicDatagrams {
|
|||
stats: &mut Stats,
|
||||
) {
|
||||
while let Some(dgram) = self.datagrams.pop_front() {
|
||||
let len = dgram.len();
|
||||
let len = dgram.as_ref().len();
|
||||
if builder.remaining() > len {
|
||||
// We need 1 more than `len` for the Frame type.
|
||||
let length_len = Encoder::varint_len(u64::try_from(len).unwrap());
|
||||
// Include a length if there is space for another frame after this one.
|
||||
if builder.remaining() >= 1 + length_len + len + PacketBuilder::MINIMUM_FRAME_SIZE {
|
||||
builder.encode_varint(FRAME_TYPE_DATAGRAM_WITH_LEN);
|
||||
builder.encode_vvec(&dgram);
|
||||
builder.encode_vvec(dgram.as_ref());
|
||||
} else {
|
||||
builder.encode_varint(FRAME_TYPE_DATAGRAM);
|
||||
builder.encode(&dgram);
|
||||
builder.encode(dgram.as_ref());
|
||||
builder.mark_full();
|
||||
}
|
||||
debug_assert!(builder.len() <= builder.limit());
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use neqo_common::{qdebug, qlog::NeqoQlog, qtrace, qwarn};
|
||||
use neqo_common::{qdebug, qinfo, qlog::NeqoQlog, qtrace, qwarn};
|
||||
|
||||
use crate::ackrate::AckRate;
|
||||
use crate::cid::ConnectionIdEntry;
|
||||
|
@ -590,7 +590,7 @@ impl LossRecovery {
|
|||
self.qlog = qlog;
|
||||
}
|
||||
|
||||
pub fn drop_0rtt(&mut self, primary_path: &PathRef) -> Vec<SentPacket> {
|
||||
pub fn drop_0rtt(&mut self, primary_path: &PathRef, now: Instant) -> Vec<SentPacket> {
|
||||
// The largest acknowledged or loss_time should still be unset.
|
||||
// The client should not have received any ACK frames when it drops 0-RTT.
|
||||
assert!(self
|
||||
|
@ -607,7 +607,7 @@ impl LossRecovery {
|
|||
.collect::<Vec<_>>();
|
||||
let mut path = primary_path.borrow_mut();
|
||||
for p in &mut dropped {
|
||||
path.discard_packet(p);
|
||||
path.discard_packet(p, now);
|
||||
}
|
||||
dropped
|
||||
}
|
||||
|
@ -670,10 +670,14 @@ impl LossRecovery {
|
|||
largest_acked
|
||||
);
|
||||
|
||||
let space = self
|
||||
.spaces
|
||||
.get_mut(pn_space)
|
||||
.expect("ACK on discarded space");
|
||||
let space = self.spaces.get_mut(pn_space);
|
||||
let space = if let Some(sp) = space {
|
||||
sp
|
||||
} else {
|
||||
qinfo!("ACK on discarded space");
|
||||
return (Vec::new(), Vec::new());
|
||||
};
|
||||
|
||||
let (acked_packets, any_ack_eliciting) =
|
||||
space.remove_acked(acked_ranges, &mut *self.stats.borrow_mut());
|
||||
if acked_packets.is_empty() {
|
||||
|
@ -737,7 +741,7 @@ impl LossRecovery {
|
|||
|
||||
/// When receiving a retry, get all the sent packets so that they can be flushed.
|
||||
/// We also need to pretend that they never happened for the purposes of congestion control.
|
||||
pub fn retry(&mut self, primary_path: &PathRef) -> Vec<SentPacket> {
|
||||
pub fn retry(&mut self, primary_path: &PathRef, now: Instant) -> Vec<SentPacket> {
|
||||
self.pto_state = None;
|
||||
let mut dropped = self
|
||||
.spaces
|
||||
|
@ -746,7 +750,7 @@ impl LossRecovery {
|
|||
.collect::<Vec<_>>();
|
||||
let mut path = primary_path.borrow_mut();
|
||||
for p in &mut dropped {
|
||||
path.discard_packet(p);
|
||||
path.discard_packet(p, now);
|
||||
}
|
||||
dropped
|
||||
}
|
||||
|
@ -779,7 +783,7 @@ impl LossRecovery {
|
|||
qdebug!([self], "Reset loss recovery state for {}", space);
|
||||
let mut path = primary_path.borrow_mut();
|
||||
for p in self.spaces.drop_space(space) {
|
||||
path.discard_packet(&p);
|
||||
path.discard_packet(&p, now);
|
||||
}
|
||||
|
||||
// We just made progress, so discard PTO count.
|
||||
|
@ -1411,17 +1415,18 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "ACK on discarded space")]
|
||||
fn ack_after_drop() {
|
||||
let mut lr = Fixture::default();
|
||||
lr.discard(PacketNumberSpace::Initial, now());
|
||||
lr.on_ack_received(
|
||||
let (acked, lost) = lr.on_ack_received(
|
||||
PacketNumberSpace::Initial,
|
||||
0,
|
||||
vec![],
|
||||
Duration::from_millis(0),
|
||||
pn_time(0),
|
||||
);
|
||||
assert!(acked.is_empty());
|
||||
assert!(lost.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1494,6 +1499,25 @@ mod tests {
|
|||
#[test]
|
||||
fn rearm_pto_after_confirmed() {
|
||||
let mut lr = Fixture::default();
|
||||
lr.on_packet_sent(SentPacket::new(
|
||||
PacketType::Initial,
|
||||
0,
|
||||
now(),
|
||||
true,
|
||||
Vec::new(),
|
||||
ON_SENT_SIZE,
|
||||
));
|
||||
// Set the RTT to the initial value so that discarding doesn't
|
||||
// alter the estimate.
|
||||
let rtt = lr.path.borrow().rtt().estimate();
|
||||
lr.on_ack_received(
|
||||
PacketNumberSpace::Initial,
|
||||
0,
|
||||
vec![0..=0],
|
||||
Duration::new(0, 0),
|
||||
now() + rtt,
|
||||
);
|
||||
|
||||
lr.on_packet_sent(SentPacket::new(
|
||||
PacketType::Handshake,
|
||||
0,
|
||||
|
|
|
@ -1966,7 +1966,10 @@ mod tests {
|
|||
&mut tokens,
|
||||
&mut stats,
|
||||
);
|
||||
qtrace!("STREAM frame: {}", hex_with_len(&builder[header_len..]));
|
||||
qtrace!(
|
||||
"STREAM frame: {}",
|
||||
hex_with_len(&builder.as_ref()[header_len..])
|
||||
);
|
||||
stats.stream > 0
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::addr_valid::{AddressValidation, AddressValidationResult};
|
|||
use crate::cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef};
|
||||
use crate::connection::{Connection, Output, State};
|
||||
use crate::packet::{PacketBuilder, PacketType, PublicPacket};
|
||||
use crate::{ConnectionParameters, QuicVersion, Res};
|
||||
use crate::{ConnectionParameters, Res, Version};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
@ -109,7 +109,7 @@ struct InitialDetails {
|
|||
src_cid: ConnectionId,
|
||||
dst_cid: ConnectionId,
|
||||
token: Vec<u8>,
|
||||
quic_version: QuicVersion,
|
||||
version: Version,
|
||||
}
|
||||
|
||||
impl InitialDetails {
|
||||
|
@ -118,7 +118,7 @@ impl InitialDetails {
|
|||
src_cid: ConnectionId::from(packet.scid()),
|
||||
dst_cid: ConnectionId::from(packet.dcid()),
|
||||
token: packet.token().to_vec(),
|
||||
quic_version: packet.version().unwrap(),
|
||||
version: packet.version().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,7 +339,7 @@ impl Server {
|
|||
};
|
||||
if let Some(new_dcid) = self.cid_generator.borrow_mut().generate_cid() {
|
||||
let packet = PacketBuilder::retry(
|
||||
initial.quic_version,
|
||||
initial.version,
|
||||
&initial.src_cid,
|
||||
&new_dcid,
|
||||
&token,
|
||||
|
@ -478,11 +478,13 @@ impl Server {
|
|||
saved_cids: Vec::new(),
|
||||
}));
|
||||
|
||||
let mut params = self.conn_params.clone();
|
||||
params.get_versions_mut().set_initial(initial.version);
|
||||
let sconn = Connection::new_server(
|
||||
&self.certs,
|
||||
&self.protocols,
|
||||
Rc::clone(&cid_mgr) as _,
|
||||
self.conn_params.quic_version(initial.quic_version),
|
||||
params,
|
||||
);
|
||||
|
||||
if let Ok(mut c) = sconn {
|
||||
|
@ -554,6 +556,29 @@ impl Server {
|
|||
return None;
|
||||
}
|
||||
|
||||
if packet.packet_type() == PacketType::OtherVersion
|
||||
|| (packet.packet_type() == PacketType::Initial
|
||||
&& !self
|
||||
.conn_params
|
||||
.get_versions()
|
||||
.all()
|
||||
.contains(&packet.version().unwrap()))
|
||||
{
|
||||
if dgram.len() < MIN_INITIAL_PACKET_SIZE {
|
||||
qdebug!([self], "Unsupported version: too short");
|
||||
return None;
|
||||
}
|
||||
|
||||
qdebug!([self], "Unsupported version: {:x}", packet.wire_version());
|
||||
let vn = PacketBuilder::version_negotiation(
|
||||
packet.scid(),
|
||||
packet.dcid(),
|
||||
packet.wire_version(),
|
||||
self.conn_params.get_versions().all(),
|
||||
);
|
||||
return Some(Datagram::new(dgram.destination(), dgram.source(), vn));
|
||||
}
|
||||
|
||||
match packet.packet_type() {
|
||||
PacketType::Initial => {
|
||||
if dgram.len() < MIN_INITIAL_PACKET_SIZE {
|
||||
|
@ -568,14 +593,7 @@ impl Server {
|
|||
let dcid = ConnectionId::from(packet.dcid());
|
||||
self.handle_0rtt(dgram, dcid, now)
|
||||
}
|
||||
PacketType::OtherVersion => {
|
||||
if dgram.len() < MIN_INITIAL_PACKET_SIZE {
|
||||
qdebug!([self], "Unsupported version: too short");
|
||||
return None;
|
||||
}
|
||||
let vn = PacketBuilder::version_negotiation(packet.scid(), packet.dcid());
|
||||
Some(Datagram::new(dgram.destination(), dgram.source(), vn))
|
||||
}
|
||||
PacketType::OtherVersion => unreachable!(),
|
||||
_ => {
|
||||
qtrace!([self], "Not an initial packet");
|
||||
None
|
||||
|
|
|
@ -18,7 +18,7 @@ use std::time::Duration;
|
|||
pub(crate) const MAX_PTO_COUNTS: usize = 16;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct FrameStats {
|
||||
pub all: usize,
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
use crate::cid::{
|
||||
ConnectionId, ConnectionIdEntry, CONNECTION_ID_SEQNO_PREFERRED, MAX_CONNECTION_ID_LEN,
|
||||
};
|
||||
use crate::version::{Version, VersionConfig, WireVersion};
|
||||
use crate::{Error, Res};
|
||||
|
||||
use neqo_common::{hex, qdebug, qinfo, qtrace, Decoder, Encoder};
|
||||
use neqo_common::{hex, qdebug, qinfo, qtrace, Decoder, Encoder, Role};
|
||||
use neqo_crypto::constants::{TLS_HS_CLIENT_HELLO, TLS_HS_ENCRYPTED_EXTENSIONS};
|
||||
use neqo_crypto::ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult};
|
||||
use neqo_crypto::{HandshakeMessage, ZeroRttCheckResult, ZeroRttChecker};
|
||||
use neqo_crypto::{random, HandshakeMessage, ZeroRttCheckResult, ZeroRttChecker};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
@ -26,6 +27,10 @@ pub type TransportParameterId = u64;
|
|||
macro_rules! tpids {
|
||||
{ $($n:ident = $v:expr),+ $(,)? } => {
|
||||
$(pub const $n: TransportParameterId = $v as TransportParameterId;)+
|
||||
|
||||
/// A complete list of internal transport parameters.
|
||||
#[cfg(not(test))]
|
||||
pub(crate) const INTERNAL_TRANSPORT_PARAMETERS: &[TransportParameterId] = &[ $($n),+ ];
|
||||
};
|
||||
}
|
||||
tpids! {
|
||||
|
@ -49,6 +54,7 @@ tpids! {
|
|||
GREASE_QUIC_BIT = 0x2ab2,
|
||||
MIN_ACK_DELAY = 0xff02_de1a,
|
||||
MAX_DATAGRAM_FRAME_SIZE = 0x0020,
|
||||
VERSION_NEGOTIATION = 0xff73db,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
|
@ -94,7 +100,7 @@ impl PreferredAddress {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TransportParameter {
|
||||
Bytes(Vec<u8>),
|
||||
Integer(u64),
|
||||
|
@ -105,6 +111,10 @@ pub enum TransportParameter {
|
|||
cid: ConnectionId,
|
||||
srt: [u8; 16],
|
||||
},
|
||||
Versions {
|
||||
current: WireVersion,
|
||||
other: Vec<WireVersion>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TransportParameter {
|
||||
|
@ -151,6 +161,14 @@ impl TransportParameter {
|
|||
enc_inner.encode(&srt[..]);
|
||||
});
|
||||
}
|
||||
Self::Versions { current, other } => {
|
||||
enc.encode_vvec_with(|enc_inner| {
|
||||
enc_inner.encode_uint(4, *current);
|
||||
for v in other {
|
||||
enc_inner.encode_uint(4, *v);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -199,6 +217,26 @@ impl TransportParameter {
|
|||
Ok(Self::PreferredAddress { v4, v6, cid, srt })
|
||||
}
|
||||
|
||||
fn decode_versions(dec: &mut Decoder) -> Res<Self> {
|
||||
fn dv(dec: &mut Decoder) -> Res<WireVersion> {
|
||||
let v = dec.decode_uint(4).ok_or(Error::NoMoreData)?;
|
||||
if v == 0 {
|
||||
Err(Error::TransportParameterError)
|
||||
} else {
|
||||
Ok(v as WireVersion)
|
||||
}
|
||||
}
|
||||
|
||||
let current = dv(dec)?;
|
||||
// This rounding down is OK because `decode` checks for left over data.
|
||||
let count = dec.remaining() / 4;
|
||||
let mut other = Vec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
other.push(dv(dec)?);
|
||||
}
|
||||
Ok(Self::Versions { current, other })
|
||||
}
|
||||
|
||||
fn decode(dec: &mut Decoder) -> Res<Option<(TransportParameterId, Self)>> {
|
||||
let tp = dec.decode_varint().ok_or(Error::NoMoreData)?;
|
||||
let content = dec.decode_vvec().ok_or(Error::NoMoreData)?;
|
||||
|
@ -253,6 +291,8 @@ impl TransportParameter {
|
|||
_ => return Err(Error::TransportParameterError),
|
||||
},
|
||||
|
||||
VERSION_NEGOTIATION => Self::decode_versions(&mut d)?,
|
||||
|
||||
// Skip.
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
@ -264,7 +304,7 @@ impl TransportParameter {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct TransportParameters {
|
||||
params: HashMap<TransportParameterId, TransportParameter>,
|
||||
}
|
||||
|
@ -388,6 +428,37 @@ impl TransportParameters {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set version information.
|
||||
pub fn set_versions(&mut self, role: Role, versions: &VersionConfig) {
|
||||
let rbuf = random(4);
|
||||
let mut other = Vec::with_capacity(versions.all().len() + 1);
|
||||
let mut dec = Decoder::new(&rbuf);
|
||||
let grease = (dec.decode_uint(4).unwrap() as u32) & 0xf0f0_f0f0 | 0x0a0a0a0a;
|
||||
other.push(grease);
|
||||
for &v in versions.all() {
|
||||
if role == Role::Client && !versions.initial().is_compatible(v) {
|
||||
continue;
|
||||
}
|
||||
other.push(v.wire_version());
|
||||
}
|
||||
let current = versions.initial().wire_version();
|
||||
self.set(
|
||||
VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions { current, other },
|
||||
);
|
||||
}
|
||||
|
||||
fn compatible_upgrade(&mut self, v: Version) {
|
||||
if let Some(TransportParameter::Versions {
|
||||
ref mut current, ..
|
||||
}) = self.params.get_mut(&VERSION_NEGOTIATION)
|
||||
{
|
||||
*current = v.wire_version();
|
||||
} else {
|
||||
unreachable!("Compatible upgrade without transport parameters set!");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_empty(&self, tipe: TransportParameterId) -> bool {
|
||||
match self.params.get(&tipe) {
|
||||
None => false,
|
||||
|
@ -416,23 +487,30 @@ impl TransportParameters {
|
|||
) {
|
||||
continue;
|
||||
}
|
||||
if let Some(v_self) = self.params.get(k) {
|
||||
let ok = if let Some(v_self) = self.params.get(k) {
|
||||
match (v_self, v_rem) {
|
||||
(TransportParameter::Integer(i_self), TransportParameter::Integer(i_rem)) => {
|
||||
if *k == MIN_ACK_DELAY {
|
||||
// MIN_ACK_DELAY is backwards:
|
||||
// it can only be reduced safely.
|
||||
if *i_self > *i_rem {
|
||||
return false;
|
||||
}
|
||||
} else if *i_self < *i_rem {
|
||||
return false;
|
||||
*i_self <= *i_rem
|
||||
} else {
|
||||
*i_self >= *i_rem
|
||||
}
|
||||
}
|
||||
(TransportParameter::Empty, TransportParameter::Empty) => {}
|
||||
_ => return false,
|
||||
(TransportParameter::Empty, TransportParameter::Empty) => true,
|
||||
(
|
||||
TransportParameter::Versions {
|
||||
current: v_self, ..
|
||||
},
|
||||
TransportParameter::Versions { current: v_rem, .. },
|
||||
) => v_self == v_rem,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !ok {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -454,26 +532,117 @@ impl TransportParameters {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the version negotiation values for validation.
|
||||
#[must_use]
|
||||
pub fn get_versions(&self) -> Option<(WireVersion, &[WireVersion])> {
|
||||
if let Some(TransportParameter::Versions { current, other }) =
|
||||
self.params.get(&VERSION_NEGOTIATION)
|
||||
{
|
||||
Some((*current, other))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn has_value(&self, tp: TransportParameterId) -> bool {
|
||||
self.params.contains_key(&tp)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct TransportParametersHandler {
|
||||
role: Role,
|
||||
versions: VersionConfig,
|
||||
pub(crate) local: TransportParameters,
|
||||
pub(crate) remote: Option<TransportParameters>,
|
||||
pub(crate) remote_0rtt: Option<TransportParameters>,
|
||||
}
|
||||
|
||||
impl TransportParametersHandler {
|
||||
pub fn new(role: Role, versions: VersionConfig) -> Self {
|
||||
let mut local = TransportParameters::default();
|
||||
local.set_versions(role, &versions);
|
||||
Self {
|
||||
role,
|
||||
versions,
|
||||
local,
|
||||
remote: None,
|
||||
remote_0rtt: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// When resuming, the version is set based on the ticket.
|
||||
/// That needs to be done to override the default choice from configuration.
|
||||
pub fn set_version(&mut self, version: Version) {
|
||||
debug_assert_eq!(self.role, Role::Client);
|
||||
self.versions.set_initial(version);
|
||||
self.local.set_versions(self.role, &self.versions)
|
||||
}
|
||||
|
||||
pub fn remote(&self) -> &TransportParameters {
|
||||
match (self.remote.as_ref(), self.remote_0rtt.as_ref()) {
|
||||
(Some(tp), _) | (_, Some(tp)) => tp,
|
||||
_ => panic!("no transport parameters from peer"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the version as set (or as determined by a compatible upgrade).
|
||||
pub fn version(&self) -> Version {
|
||||
self.versions.initial()
|
||||
}
|
||||
|
||||
fn compatible_upgrade(&mut self, remote_tp: &TransportParameters) -> Res<()> {
|
||||
if let Some((current, other)) = remote_tp.get_versions() {
|
||||
qtrace!(
|
||||
"Peer versions: {:x} {:x?}; config {:?}",
|
||||
current,
|
||||
other,
|
||||
self.versions,
|
||||
);
|
||||
|
||||
if self.role == Role::Client {
|
||||
let chosen = Version::try_from(current)?;
|
||||
if self.versions.compatible().any(|&v| v == chosen) {
|
||||
Ok(())
|
||||
} else {
|
||||
qinfo!(
|
||||
"Chosen version {:x} is not compatible with initial version {:x}",
|
||||
current,
|
||||
self.versions.initial().wire_version(),
|
||||
);
|
||||
Err(Error::TransportParameterError)
|
||||
}
|
||||
} else {
|
||||
if current != self.versions.initial().wire_version() {
|
||||
qinfo!(
|
||||
"Current version {:x} != own version {:x}",
|
||||
current,
|
||||
self.versions.initial().wire_version(),
|
||||
);
|
||||
return Err(Error::TransportParameterError);
|
||||
}
|
||||
|
||||
if let Some(preferred) = self.versions.preferred_compatible(other) {
|
||||
if preferred != self.versions.initial() {
|
||||
qinfo!(
|
||||
"Compatible upgrade {:?} ==> {:?}",
|
||||
self.versions.initial(),
|
||||
preferred
|
||||
);
|
||||
self.versions.set_initial(preferred);
|
||||
self.local.compatible_upgrade(preferred);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
qinfo!("Unable to find any compatible version");
|
||||
Err(Error::TransportParameterError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionHandler for TransportParametersHandler {
|
||||
|
@ -488,7 +657,7 @@ impl ExtensionHandler for TransportParametersHandler {
|
|||
let mut enc = Encoder::default();
|
||||
self.local.encode(&mut enc);
|
||||
assert!(enc.len() <= d.len());
|
||||
d[..enc.len()].copy_from_slice(&enc);
|
||||
d[..enc.len()].copy_from_slice(enc.as_ref());
|
||||
ExtensionWriterResult::Write(enc.len())
|
||||
}
|
||||
|
||||
|
@ -506,8 +675,12 @@ impl ExtensionHandler for TransportParametersHandler {
|
|||
let mut dec = Decoder::from(d);
|
||||
match TransportParameters::decode(&mut dec) {
|
||||
Ok(tp) => {
|
||||
self.remote = Some(tp);
|
||||
ExtensionHandlerResult::Ok
|
||||
if self.compatible_upgrade(&tp).is_ok() {
|
||||
self.remote = Some(tp);
|
||||
ExtensionHandlerResult::Ok
|
||||
} else {
|
||||
ExtensionHandlerResult::Alert(47)
|
||||
}
|
||||
}
|
||||
_ => ExtensionHandlerResult::Alert(47), // illegal_parameter
|
||||
}
|
||||
|
@ -569,7 +742,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(ekr@rtfm.com): Need to write more TP unit tests.
|
||||
#[cfg(test)]
|
||||
#[allow(unused_variables)]
|
||||
mod tests {
|
||||
|
@ -639,7 +811,7 @@ mod tests {
|
|||
let spa = make_spa();
|
||||
let mut enc = Encoder::new();
|
||||
spa.encode(&mut enc, PREFERRED_ADDRESS);
|
||||
assert_eq!(&enc[..], ENCODED);
|
||||
assert_eq!(enc.as_ref(), ENCODED);
|
||||
|
||||
let mut dec = enc.as_decoder();
|
||||
let (id, decoded) = TransportParameter::decode(&mut dec).unwrap().unwrap();
|
||||
|
@ -733,7 +905,7 @@ mod tests {
|
|||
let spa = make_spa();
|
||||
let mut enc = Encoder::new();
|
||||
spa.encode(&mut enc, PREFERRED_ADDRESS);
|
||||
let mut dec = Decoder::from(&enc[..enc.len() - 1]);
|
||||
let mut dec = Decoder::from(&enc.as_ref()[..enc.len() - 1]);
|
||||
assert_eq!(
|
||||
TransportParameter::decode(&mut dec).unwrap_err(),
|
||||
Error::NoMoreData
|
||||
|
@ -911,4 +1083,97 @@ mod tests {
|
|||
let invalid_decode_result = TransportParameters::decode(&mut enc.as_decoder());
|
||||
assert!(invalid_decode_result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versions_encode_decode() {
|
||||
const ENCODED: &[u8] = &[
|
||||
0x80, 0xff, 0x73, 0xdb, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x1a, 0x2a, 0x3a, 0x4a, 0x5a,
|
||||
0x6a, 0x7a, 0x8a,
|
||||
];
|
||||
let vn = TransportParameter::Versions {
|
||||
current: Version::Version1.wire_version(),
|
||||
other: vec![0x1a2a_3a4a, 0x5a6a_7a8a],
|
||||
};
|
||||
|
||||
let mut enc = Encoder::new();
|
||||
vn.encode(&mut enc, VERSION_NEGOTIATION);
|
||||
assert_eq!(enc.as_ref(), ENCODED);
|
||||
|
||||
let mut dec = enc.as_decoder();
|
||||
let (id, decoded) = TransportParameter::decode(&mut dec).unwrap().unwrap();
|
||||
assert_eq!(id, VERSION_NEGOTIATION);
|
||||
assert_eq!(decoded, vn);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versions_truncated() {
|
||||
const TRUNCATED: &[u8] = &[
|
||||
0x80, 0xff, 0x73, 0xdb, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x1a, 0x2a, 0x3a, 0x4a, 0x5a,
|
||||
0x6a, 0x7a,
|
||||
];
|
||||
let mut dec = Decoder::from(&TRUNCATED);
|
||||
assert_eq!(
|
||||
TransportParameter::decode(&mut dec).unwrap_err(),
|
||||
Error::NoMoreData
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versions_zero() {
|
||||
const ZERO1: &[u8] = &[0x80, 0xff, 0x73, 0xdb, 0x04, 0x00, 0x00, 0x00, 0x00];
|
||||
const ZERO2: &[u8] = &[
|
||||
0x80, 0xff, 0x73, 0xdb, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut dec = Decoder::from(&ZERO1);
|
||||
assert_eq!(
|
||||
TransportParameter::decode(&mut dec).unwrap_err(),
|
||||
Error::TransportParameterError
|
||||
);
|
||||
let mut dec = Decoder::from(&ZERO2);
|
||||
assert_eq!(
|
||||
TransportParameter::decode(&mut dec).unwrap_err(),
|
||||
Error::TransportParameterError
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versions_equal_0rtt() {
|
||||
let mut current = TransportParameters::default();
|
||||
current.set(
|
||||
VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions {
|
||||
current: Version::Version1.wire_version(),
|
||||
other: vec![0x1a2a_3a4a],
|
||||
},
|
||||
);
|
||||
|
||||
let mut remembered = TransportParameters::default();
|
||||
// It's OK to not remember having versions.
|
||||
assert!(current.ok_for_0rtt(&remembered));
|
||||
// But it is bad in the opposite direction.
|
||||
assert!(!remembered.ok_for_0rtt(¤t));
|
||||
|
||||
// If the version matches, it's OK to use 0-RTT.
|
||||
remembered.set(
|
||||
VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions {
|
||||
current: Version::Version1.wire_version(),
|
||||
other: vec![0x5a6a_7a8a, 0x9aaa_baca],
|
||||
},
|
||||
);
|
||||
assert!(current.ok_for_0rtt(&remembered));
|
||||
assert!(remembered.ok_for_0rtt(¤t));
|
||||
|
||||
// An apparent "upgrade" is still cause to reject 0-RTT.
|
||||
remembered.set(
|
||||
VERSION_NEGOTIATION,
|
||||
TransportParameter::Versions {
|
||||
current: Version::Version1.wire_version() + 1,
|
||||
other: vec![],
|
||||
},
|
||||
);
|
||||
assert!(!current.ok_for_0rtt(&remembered));
|
||||
assert!(!remembered.ok_for_0rtt(¤t));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
// 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::{Error, Res};
|
||||
use neqo_common::qdebug;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub type WireVersion = u32;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Version {
|
||||
Version2,
|
||||
Version1,
|
||||
Draft29,
|
||||
Draft30,
|
||||
Draft31,
|
||||
Draft32,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub const fn wire_version(self) -> WireVersion {
|
||||
match self {
|
||||
Self::Version2 => 0x709a50c4,
|
||||
Self::Version1 => 1,
|
||||
Self::Draft29 => 0xff00_0000 + 29,
|
||||
Self::Draft30 => 0xff00_0000 + 30,
|
||||
Self::Draft31 => 0xff00_0000 + 31,
|
||||
Self::Draft32 => 0xff00_0000 + 32,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn initial_salt(self) -> &'static [u8] {
|
||||
const INITIAL_SALT_V2: &[u8] = &[
|
||||
0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04,
|
||||
0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3,
|
||||
];
|
||||
const INITIAL_SALT_V1: &[u8] = &[
|
||||
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8,
|
||||
0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
|
||||
];
|
||||
const INITIAL_SALT_29_32: &[u8] = &[
|
||||
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
|
||||
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
|
||||
];
|
||||
match self {
|
||||
Self::Version2 => INITIAL_SALT_V2,
|
||||
Self::Version1 => INITIAL_SALT_V1,
|
||||
Self::Draft29 | Self::Draft30 | Self::Draft31 | Self::Draft32 => INITIAL_SALT_29_32,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn label_prefix(self) -> &'static str {
|
||||
match self {
|
||||
Self::Version2 => "quicv2 ",
|
||||
Self::Version1 | Self::Draft29 | Self::Draft30 | Self::Draft31 | Self::Draft32 => {
|
||||
"quic "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn retry_secret(self) -> &'static [u8] {
|
||||
const RETRY_SECRET_29: &[u8] = &[
|
||||
0x8b, 0x0d, 0x37, 0xeb, 0x85, 0x35, 0x02, 0x2e, 0xbc, 0x8d, 0x76, 0xa2, 0x07, 0xd8,
|
||||
0x0d, 0xf2, 0x26, 0x46, 0xec, 0x06, 0xdc, 0x80, 0x96, 0x42, 0xc3, 0x0a, 0x8b, 0xaa,
|
||||
0x2b, 0xaa, 0xff, 0x4c,
|
||||
];
|
||||
const RETRY_SECRET_V1: &[u8] = &[
|
||||
0xd9, 0xc9, 0x94, 0x3e, 0x61, 0x01, 0xfd, 0x20, 0x00, 0x21, 0x50, 0x6b, 0xcc, 0x02,
|
||||
0x81, 0x4c, 0x73, 0x03, 0x0f, 0x25, 0xc7, 0x9d, 0x71, 0xce, 0x87, 0x6e, 0xca, 0x87,
|
||||
0x6e, 0x6f, 0xca, 0x8e,
|
||||
];
|
||||
const RETRY_SECRET_V2: &[u8] = &[
|
||||
0x34, 0x25, 0xc2, 0x0c, 0xf8, 0x87, 0x79, 0xdf, 0x2f, 0xf7, 0x1e, 0x8a, 0xbf, 0xa7,
|
||||
0x82, 0x49, 0x89, 0x1e, 0x76, 0x3b, 0xbe, 0xd2, 0xf1, 0x3c, 0x04, 0x83, 0x43, 0xd3,
|
||||
0x48, 0xc0, 0x60, 0xe2,
|
||||
];
|
||||
match self {
|
||||
Self::Version2 => RETRY_SECRET_V2,
|
||||
Self::Version1 => RETRY_SECRET_V1,
|
||||
Self::Draft29 | Self::Draft30 | Self::Draft31 | Self::Draft32 => RETRY_SECRET_29,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_draft(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Draft29 | Self::Draft30 | Self::Draft31 | Self::Draft32,
|
||||
)
|
||||
}
|
||||
|
||||
/// Determine if `self` can be upgraded to `other` compatibly.
|
||||
pub fn is_compatible(self, other: Self) -> bool {
|
||||
self == other
|
||||
|| matches!(
|
||||
(self, other),
|
||||
(Self::Version1, Self::Version2) | (Self::Version2, Self::Version1)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
Self::Version2,
|
||||
Self::Version1,
|
||||
Self::Draft32,
|
||||
Self::Draft31,
|
||||
Self::Draft30,
|
||||
Self::Draft29,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn compatible<'a>(
|
||||
self,
|
||||
all: impl IntoIterator<Item = &'a Self>,
|
||||
) -> impl Iterator<Item = &'a Self> {
|
||||
all.into_iter().filter(move |&v| self.is_compatible(*v))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Self::Version1
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WireVersion> for Version {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(wire: WireVersion) -> Res<Self> {
|
||||
if wire == 1 {
|
||||
Ok(Self::Version1)
|
||||
} else if wire == 0x709a50c4 {
|
||||
Ok(Self::Version2)
|
||||
} else if wire == 0xff00_0000 + 29 {
|
||||
Ok(Self::Draft29)
|
||||
} else if wire == 0xff00_0000 + 30 {
|
||||
Ok(Self::Draft30)
|
||||
} else if wire == 0xff00_0000 + 31 {
|
||||
Ok(Self::Draft31)
|
||||
} else if wire == 0xff00_0000 + 32 {
|
||||
Ok(Self::Draft32)
|
||||
} else {
|
||||
Err(Error::VersionNegotiation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VersionConfig {
|
||||
/// The version that a client uses to establish a connection.
|
||||
///
|
||||
/// For a client, this is the version that is sent out in an Initial packet.
|
||||
/// A client that resumes will set this to the version from the original
|
||||
/// connection.
|
||||
/// A client that handles a Version Negotiation packet will be initialized with
|
||||
/// a version chosen from the packet, but it will then have this value overridden
|
||||
/// to match the original configuration so that the version negotiation can be
|
||||
/// authenticated.
|
||||
///
|
||||
/// For a server `Connection`, this is the only type of Initial packet that
|
||||
/// can be accepted; the correct value is set by `Server`, see below.
|
||||
///
|
||||
/// For a `Server`, this value is not used; if an Initial packet is received
|
||||
/// in a supported version (as listed in `versions`), new instances of
|
||||
/// `Connection` will be created with this value set to match what was received.
|
||||
///
|
||||
/// An invariant here is that this version is always listed in `all`.
|
||||
initial: Version,
|
||||
/// The set of versions that are enabled, in preference order. For a server,
|
||||
/// only the relative order of compatible versions matters.
|
||||
all: Vec<Version>,
|
||||
}
|
||||
|
||||
impl VersionConfig {
|
||||
pub fn new(initial: Version, all: Vec<Version>) -> Self {
|
||||
assert!(all.contains(&initial));
|
||||
Self { initial, all }
|
||||
}
|
||||
|
||||
pub fn initial(&self) -> Version {
|
||||
self.initial
|
||||
}
|
||||
|
||||
pub fn all(&self) -> &[Version] {
|
||||
&self.all
|
||||
}
|
||||
|
||||
/// Overwrite the initial value; used by the `Server` when handling new connections
|
||||
/// and by the client on resumption.
|
||||
pub(crate) fn set_initial(&mut self, initial: Version) {
|
||||
qdebug!(
|
||||
"Overwrite initial version {:?} ==> {:?}",
|
||||
self.initial,
|
||||
initial
|
||||
);
|
||||
assert!(self.all.contains(&initial));
|
||||
self.initial = initial;
|
||||
}
|
||||
|
||||
pub fn compatible(&self) -> impl Iterator<Item = &Version> {
|
||||
self.initial.compatible(&self.all)
|
||||
}
|
||||
|
||||
fn find_preferred<'a>(
|
||||
preferences: impl IntoIterator<Item = &'a Version>,
|
||||
vn: &[WireVersion],
|
||||
) -> Option<Version> {
|
||||
for v in preferences {
|
||||
if vn.contains(&v.wire_version()) {
|
||||
return Some(*v);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Determine the preferred version based on a version negotiation packet.
|
||||
pub(crate) fn preferred(&self, vn: &[WireVersion]) -> Option<Version> {
|
||||
Self::find_preferred(&self.all, vn)
|
||||
}
|
||||
|
||||
/// Determine the preferred version based on a set of compatible versions.
|
||||
pub(crate) fn preferred_compatible(&self, vn: &[WireVersion]) -> Option<Version> {
|
||||
Self::find_preferred(self.compatible(), vn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VersionConfig {
|
||||
fn default() -> Self {
|
||||
Self::new(Version::default(), Version::all())
|
||||
}
|
||||
}
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(unused)]
|
||||
|
||||
use neqo_common::{event::Provider, hex_with_len, qtrace, Datagram, Decoder};
|
||||
use neqo_common::{event::Provider, hex_with_len, qtrace, Datagram, Decoder, Role};
|
||||
use neqo_crypto::{
|
||||
constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
|
||||
hkdf,
|
||||
|
@ -26,8 +27,8 @@ use std::mem;
|
|||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
||||
// Different than the one in the fixture, which is a single connection.
|
||||
pub fn default_server() -> Server {
|
||||
/// Create a server. This is different than the one in the fixture, which is a single connection.
|
||||
pub fn new_server(params: ConnectionParameters) -> Server {
|
||||
Server::new(
|
||||
now(),
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
|
@ -35,11 +36,16 @@ pub fn default_server() -> Server {
|
|||
test_fixture::anti_replay(),
|
||||
Box::new(AllowZeroRtt {}),
|
||||
Rc::new(RefCell::new(CountingConnectionIdGenerator::default())),
|
||||
ConnectionParameters::default(),
|
||||
params,
|
||||
)
|
||||
.expect("should create a server")
|
||||
}
|
||||
|
||||
/// Create a server. This is different than the one in the fixture, which is a single connection.
|
||||
pub fn default_server() -> Server {
|
||||
new_server(ConnectionParameters::default())
|
||||
}
|
||||
|
||||
// Check that there is at least one connection. Returns a ref to the first confirmed connection.
|
||||
pub fn connected_server(server: &mut Server) -> ActiveConnectionRef {
|
||||
let server_connections = server.active_connections();
|
||||
|
@ -91,10 +97,14 @@ pub fn connect(client: &mut Connection, server: &mut Server) -> ActiveConnection
|
|||
// * the protected payload including the packet number.
|
||||
// Any token is thrown away.
|
||||
#[must_use]
|
||||
pub fn decode_initial_header(dgram: &Datagram) -> (&[u8], &[u8], &[u8], &[u8]) {
|
||||
pub fn decode_initial_header(dgram: &Datagram, role: Role) -> (&[u8], &[u8], &[u8], &[u8]) {
|
||||
let mut dec = Decoder::new(&dgram[..]);
|
||||
let type_and_ver = dec.decode(5).unwrap().to_vec();
|
||||
assert_eq!(type_and_ver[0] & 0xf0, 0xc0);
|
||||
// The client sets the QUIC bit, the server might not.
|
||||
match role {
|
||||
Role::Client => assert_eq!(type_and_ver[0] & 0xf0, 0xc0),
|
||||
Role::Server => assert_eq!(type_and_ver[0] & 0xb0, 0x80),
|
||||
}
|
||||
let dest_cid = dec.decode_vec(1).unwrap();
|
||||
let src_cid = dec.decode_vec(1).unwrap();
|
||||
dec.skip_vvec(); // Ignore any the token.
|
||||
|
@ -110,9 +120,10 @@ pub fn decode_initial_header(dgram: &Datagram) -> (&[u8], &[u8], &[u8], &[u8]) {
|
|||
)
|
||||
}
|
||||
|
||||
// Generate an AEAD and header protection object for a client Initial.
|
||||
/// Generate an AEAD and header protection object for a client Initial.
|
||||
/// Note that this works for QUIC version 1 only.
|
||||
#[must_use]
|
||||
pub fn client_initial_aead_and_hp(dcid: &[u8]) -> (Aead, HpKey) {
|
||||
pub fn initial_aead_and_hp(dcid: &[u8], role: Role) -> (Aead, HpKey) {
|
||||
const INITIAL_SALT: &[u8] = &[
|
||||
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c,
|
||||
0xad, 0xcc, 0xbb, 0x7f, 0x0a,
|
||||
|
@ -134,7 +145,10 @@ pub fn client_initial_aead_and_hp(dcid: &[u8]) -> (Aead, HpKey) {
|
|||
TLS_AES_128_GCM_SHA256,
|
||||
&initial_secret,
|
||||
&[],
|
||||
"client in",
|
||||
match role {
|
||||
Role::Client => "client in",
|
||||
Role::Server => "server in",
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
(
|
||||
|
@ -184,15 +198,9 @@ pub fn apply_header_protection(hp: &HpKey, packet: &mut [u8], pn_bytes: Range<us
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_ticket(server: &mut Server) -> ResumptionToken {
|
||||
let mut client = default_client();
|
||||
let mut server_conn = connect(&mut client, server);
|
||||
|
||||
server_conn.borrow_mut().send_ticket(now(), &[]).unwrap();
|
||||
let dgram = server.process(None, now()).dgram();
|
||||
client.process_input(dgram.unwrap(), now()); // Consume ticket, ignore output.
|
||||
|
||||
let ticket = client
|
||||
/// Scrub through client events to find a resumption token.
|
||||
pub fn find_ticket(client: &mut Connection) -> ResumptionToken {
|
||||
client
|
||||
.events()
|
||||
.find_map(|e| {
|
||||
if let ConnectionEvent::ResumptionToken(token) = e {
|
||||
|
@ -201,7 +209,18 @@ pub fn get_ticket(server: &mut Server) -> ResumptionToken {
|
|||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Connect to the server and have it generate a ticket.
|
||||
pub fn generate_ticket(server: &mut Server) -> ResumptionToken {
|
||||
let mut client = default_client();
|
||||
let mut server_conn = connect(&mut client, server);
|
||||
|
||||
server_conn.borrow_mut().send_ticket(now(), &[]).unwrap();
|
||||
let dgram = server.process(None, now()).dgram();
|
||||
client.process_input(dgram.unwrap(), now()); // Consume ticket, ignore output.
|
||||
let ticket = find_ticket(&mut client);
|
||||
|
||||
// Have the client close the connection and then let the server clean up.
|
||||
client.close(now(), 0, "got a ticket");
|
||||
|
|
|
@ -10,13 +10,91 @@
|
|||
|
||||
use neqo_common::Datagram;
|
||||
use neqo_transport::{
|
||||
Connection, ConnectionParameters, QuicVersion, RandomConnectionIdGenerator, State,
|
||||
Connection, ConnectionParameters, RandomConnectionIdGenerator, State, Version,
|
||||
};
|
||||
use test_fixture::{self, addr, now};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
const INITIAL_PACKET_V2: &[u8] = &[
|
||||
0xdd, 0x70, 0x9a, 0x50, 0xc4, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, 0x00,
|
||||
0x44, 0x9e, 0x43, 0x91, 0xd8, 0x48, 0x23, 0xb8, 0xe6, 0x10, 0x58, 0x9c, 0x83, 0xc9, 0x2d, 0x0e,
|
||||
0x97, 0xeb, 0x7a, 0x6e, 0x50, 0x03, 0xf5, 0x77, 0x64, 0xc5, 0xc7, 0xf0, 0x09, 0x5b, 0xa5, 0x4b,
|
||||
0x90, 0x81, 0x8f, 0x1b, 0xfe, 0xec, 0xc1, 0xc9, 0x7c, 0x54, 0xfc, 0x73, 0x1e, 0xdb, 0xd2, 0xa2,
|
||||
0x44, 0xe3, 0xb1, 0xe6, 0x39, 0xa9, 0xbc, 0x75, 0xed, 0x54, 0x5b, 0x98, 0x64, 0x93, 0x43, 0xb2,
|
||||
0x53, 0x61, 0x5e, 0xc6, 0xb3, 0xe4, 0xdf, 0x0f, 0xd2, 0xe7, 0xfe, 0x9d, 0x69, 0x1a, 0x09, 0xe6,
|
||||
0xa1, 0x44, 0xb4, 0x36, 0xd8, 0xa2, 0xc0, 0x88, 0xa4, 0x04, 0x26, 0x23, 0x40, 0xdf, 0xd9, 0x95,
|
||||
0xec, 0x38, 0x65, 0x69, 0x4e, 0x30, 0x26, 0xec, 0xd8, 0xc6, 0xd2, 0x56, 0x1a, 0x5a, 0x36, 0x67,
|
||||
0x2a, 0x10, 0x05, 0x01, 0x81, 0x68, 0xc0, 0xf0, 0x81, 0xc1, 0x0e, 0x2b, 0xf1, 0x4d, 0x55, 0x0c,
|
||||
0x97, 0x7e, 0x28, 0xbb, 0x9a, 0x75, 0x9c, 0x57, 0xd0, 0xf7, 0xff, 0xb1, 0xcd, 0xfb, 0x40, 0xbd,
|
||||
0x77, 0x4d, 0xec, 0x58, 0x96, 0x57, 0x54, 0x20, 0x47, 0xdf, 0xfe, 0xfa, 0x56, 0xfc, 0x80, 0x89,
|
||||
0xa4, 0xd1, 0xef, 0x37, 0x9c, 0x81, 0xba, 0x3d, 0xf7, 0x1a, 0x05, 0xdd, 0xc7, 0x92, 0x83, 0x40,
|
||||
0x77, 0x59, 0x10, 0xfe, 0xb3, 0xce, 0x4c, 0xbc, 0xfd, 0x8d, 0x25, 0x3e, 0xdd, 0x05, 0xf1, 0x61,
|
||||
0x45, 0x8f, 0x9d, 0xc4, 0x4b, 0xea, 0x01, 0x7c, 0x31, 0x17, 0xcc, 0xa7, 0x06, 0x5a, 0x31, 0x5d,
|
||||
0xed, 0xa9, 0x46, 0x4e, 0x67, 0x2e, 0xc8, 0x0c, 0x3f, 0x79, 0xac, 0x99, 0x34, 0x37, 0xb4, 0x41,
|
||||
0xef, 0x74, 0x22, 0x7e, 0xcc, 0x4d, 0xc9, 0xd5, 0x97, 0xf6, 0x6a, 0xb0, 0xab, 0x8d, 0x21, 0x4b,
|
||||
0x55, 0x84, 0x0c, 0x70, 0x34, 0x9d, 0x76, 0x16, 0xcb, 0xe3, 0x8e, 0x5e, 0x1d, 0x05, 0x2d, 0x07,
|
||||
0xf1, 0xfe, 0xdb, 0x3d, 0xd3, 0xc4, 0xd8, 0xce, 0x29, 0x57, 0x24, 0x94, 0x5e, 0x67, 0xed, 0x2e,
|
||||
0xef, 0xcd, 0x9f, 0xb5, 0x24, 0x72, 0x38, 0x7f, 0x31, 0x8e, 0x3d, 0x9d, 0x23, 0x3b, 0xe7, 0xdf,
|
||||
0xc7, 0x9d, 0x6b, 0xf6, 0x08, 0x0d, 0xcb, 0xbb, 0x41, 0xfe, 0xb1, 0x80, 0xd7, 0x85, 0x88, 0x49,
|
||||
0x7c, 0x3e, 0x43, 0x9d, 0x38, 0xc3, 0x34, 0x74, 0x8d, 0x2b, 0x56, 0xfd, 0x19, 0xab, 0x36, 0x4d,
|
||||
0x05, 0x7a, 0x9b, 0xd5, 0xa6, 0x99, 0xae, 0x14, 0x5d, 0x7f, 0xdb, 0xc8, 0xf5, 0x77, 0x75, 0x18,
|
||||
0x1b, 0x0a, 0x97, 0xc3, 0xbd, 0xed, 0xc9, 0x1a, 0x55, 0x5d, 0x6c, 0x9b, 0x86, 0x34, 0xe1, 0x06,
|
||||
0xd8, 0xc9, 0xca, 0x45, 0xa9, 0xd5, 0x45, 0x0a, 0x76, 0x79, 0xed, 0xc5, 0x45, 0xda, 0x91, 0x02,
|
||||
0x5b, 0xc9, 0x3a, 0x7c, 0xf9, 0xa0, 0x23, 0xa0, 0x66, 0xff, 0xad, 0xb9, 0x71, 0x7f, 0xfa, 0xf3,
|
||||
0x41, 0x4c, 0x3b, 0x64, 0x6b, 0x57, 0x38, 0xb3, 0xcc, 0x41, 0x16, 0x50, 0x2d, 0x18, 0xd7, 0x9d,
|
||||
0x82, 0x27, 0x43, 0x63, 0x06, 0xd9, 0xb2, 0xb3, 0xaf, 0xc6, 0xc7, 0x85, 0xce, 0x3c, 0x81, 0x7f,
|
||||
0xeb, 0x70, 0x3a, 0x42, 0xb9, 0xc8, 0x3b, 0x59, 0xf0, 0xdc, 0xef, 0x12, 0x45, 0xd0, 0xb3, 0xe4,
|
||||
0x02, 0x99, 0x82, 0x1e, 0xc1, 0x95, 0x49, 0xce, 0x48, 0x97, 0x14, 0xfe, 0x26, 0x11, 0xe7, 0x2c,
|
||||
0xd8, 0x82, 0xf4, 0xf7, 0x0d, 0xce, 0x7d, 0x36, 0x71, 0x29, 0x6f, 0xc0, 0x45, 0xaf, 0x5c, 0x9f,
|
||||
0x63, 0x0d, 0x7b, 0x49, 0xa3, 0xeb, 0x82, 0x1b, 0xbc, 0xa6, 0x0f, 0x19, 0x84, 0xdc, 0xe6, 0x64,
|
||||
0x91, 0x71, 0x3b, 0xfe, 0x06, 0x00, 0x1a, 0x56, 0xf5, 0x1b, 0xb3, 0xab, 0xe9, 0x2f, 0x79, 0x60,
|
||||
0x54, 0x7c, 0x4d, 0x0a, 0x70, 0xf4, 0xa9, 0x62, 0xb3, 0xf0, 0x5d, 0xc2, 0x5a, 0x34, 0xbb, 0xe8,
|
||||
0x30, 0xa7, 0xea, 0x47, 0x36, 0xd3, 0xb0, 0x16, 0x17, 0x23, 0x50, 0x0d, 0x82, 0xbe, 0xda, 0x9b,
|
||||
0xe3, 0x32, 0x7a, 0xf2, 0xaa, 0x41, 0x38, 0x21, 0xff, 0x67, 0x8b, 0x2a, 0x87, 0x6e, 0xc4, 0xb0,
|
||||
0x0b, 0xb6, 0x05, 0xff, 0xcc, 0x39, 0x17, 0xff, 0xdc, 0x27, 0x9f, 0x18, 0x7d, 0xaa, 0x2f, 0xce,
|
||||
0x8c, 0xde, 0x12, 0x19, 0x80, 0xbb, 0xa8, 0xec, 0x8f, 0x44, 0xca, 0x56, 0x2b, 0x0f, 0x13, 0x19,
|
||||
0x14, 0xc9, 0x01, 0xcf, 0xbd, 0x84, 0x74, 0x08, 0xb7, 0x78, 0xe6, 0x73, 0x8c, 0x7b, 0xb5, 0xb1,
|
||||
0xb3, 0xf9, 0x7d, 0x01, 0xb0, 0xa2, 0x4d, 0xcc, 0xa4, 0x0e, 0x3b, 0xed, 0x29, 0x41, 0x1b, 0x1b,
|
||||
0xa8, 0xf6, 0x08, 0x43, 0xc4, 0xa2, 0x41, 0x02, 0x1b, 0x23, 0x13, 0x2b, 0x95, 0x00, 0x50, 0x9b,
|
||||
0x9a, 0x35, 0x16, 0xd4, 0xa9, 0xdd, 0x41, 0xd3, 0xba, 0xcb, 0xcd, 0x42, 0x6b, 0x45, 0x13, 0x93,
|
||||
0x52, 0x18, 0x28, 0xaf, 0xed, 0xcf, 0x20, 0xfa, 0x46, 0xac, 0x24, 0xf4, 0x4a, 0x8e, 0x29, 0x73,
|
||||
0x30, 0xb1, 0x67, 0x05, 0xd5, 0xd5, 0xf7, 0x98, 0xef, 0xf9, 0xe9, 0x13, 0x4a, 0x06, 0x59, 0x79,
|
||||
0x87, 0xa1, 0xdb, 0x46, 0x17, 0xca, 0xa2, 0xd9, 0x38, 0x37, 0x73, 0x08, 0x29, 0xd4, 0xd8, 0x9e,
|
||||
0x16, 0x41, 0x3b, 0xe4, 0xd8, 0xa8, 0xa3, 0x8a, 0x7e, 0x62, 0x26, 0x62, 0x3b, 0x64, 0xa8, 0x20,
|
||||
0x17, 0x8e, 0xc3, 0xa6, 0x69, 0x54, 0xe1, 0x07, 0x10, 0xe0, 0x43, 0xae, 0x73, 0xdd, 0x3f, 0xb2,
|
||||
0x71, 0x5a, 0x05, 0x25, 0xa4, 0x63, 0x43, 0xfb, 0x75, 0x90, 0xe5, 0xea, 0xc7, 0xee, 0x55, 0xfc,
|
||||
0x81, 0x0e, 0x0d, 0x8b, 0x4b, 0x8f, 0x7b, 0xe8, 0x2c, 0xd5, 0xa2, 0x14, 0x57, 0x5a, 0x1b, 0x99,
|
||||
0x62, 0x9d, 0x47, 0xa9, 0xb2, 0x81, 0xb6, 0x13, 0x48, 0xc8, 0x62, 0x7c, 0xab, 0x38, 0xe2, 0xa6,
|
||||
0x4d, 0xb6, 0x62, 0x6e, 0x97, 0xbb, 0x8f, 0x77, 0xbd, 0xcb, 0x0f, 0xee, 0x47, 0x6a, 0xed, 0xd7,
|
||||
0xba, 0x8f, 0x54, 0x41, 0xac, 0xaa, 0xb0, 0x0f, 0x44, 0x32, 0xed, 0xab, 0x37, 0x91, 0x04, 0x7d,
|
||||
0x90, 0x91, 0xb2, 0xa7, 0x53, 0xf0, 0x35, 0x64, 0x84, 0x31, 0xf6, 0xd1, 0x2f, 0x7d, 0x6a, 0x68,
|
||||
0x1e, 0x64, 0xc8, 0x61, 0xf4, 0xac, 0x91, 0x1a, 0x0f, 0x7d, 0x6e, 0xc0, 0x49, 0x1a, 0x78, 0xc9,
|
||||
0xf1, 0x92, 0xf9, 0x6b, 0x3a, 0x5e, 0x75, 0x60, 0xa3, 0xf0, 0x56, 0xbc, 0x1c, 0xa8, 0x59, 0x83,
|
||||
0x67, 0xad, 0x6a, 0xcb, 0x6f, 0x2e, 0x03, 0x4c, 0x7f, 0x37, 0xbe, 0xeb, 0x9e, 0xd4, 0x70, 0xc4,
|
||||
0x30, 0x4a, 0xf0, 0x10, 0x7f, 0x0e, 0xb9, 0x19, 0xbe, 0x36, 0xa8, 0x6f, 0x68, 0xf3, 0x7f, 0xa6,
|
||||
0x1d, 0xae, 0x7a, 0xff, 0x14, 0xde, 0xcd, 0x67, 0xec, 0x31, 0x57, 0xa1, 0x14, 0x88, 0xa1, 0x4f,
|
||||
0xed, 0x01, 0x42, 0x82, 0x83, 0x48, 0xf5, 0xf6, 0x08, 0xb0, 0xfe, 0x03, 0xe1, 0xf3, 0xc0, 0xaf,
|
||||
0x3a, 0xcc, 0xa0, 0xce, 0x36, 0x85, 0x2e, 0xd4, 0x2e, 0x22, 0x0a, 0xe9, 0xab, 0xf8, 0xf8, 0x90,
|
||||
0x6f, 0x00, 0xf1, 0xb8, 0x6b, 0xff, 0x85, 0x04, 0xc8, 0xf1, 0x6c, 0x78, 0x4f, 0xd5, 0x2d, 0x25,
|
||||
0xe0, 0x13, 0xff, 0x4f, 0xda, 0x90, 0x3e, 0x9e, 0x1e, 0xb4, 0x53, 0xc1, 0x46, 0x4b, 0x11, 0x96,
|
||||
0x6d, 0xb9, 0xb2, 0x8e, 0x8f, 0x26, 0xa3, 0xfc, 0x41, 0x9e, 0x6a, 0x60, 0xa4, 0x8d, 0x4c, 0x72,
|
||||
0x14, 0xee, 0x9c, 0x6c, 0x6a, 0x12, 0xb6, 0x8a, 0x32, 0xca, 0xc8, 0xf6, 0x15, 0x80, 0xc6, 0x4f,
|
||||
0x29, 0xcb, 0x69, 0x22, 0x40, 0x87, 0x83, 0xc6, 0xd1, 0x2e, 0x72, 0x5b, 0x01, 0x4f, 0xe4, 0x85,
|
||||
0xcd, 0x17, 0xe4, 0x84, 0xc5, 0x95, 0x2b, 0xf9, 0x9b, 0xc9, 0x49, 0x41, 0xd4, 0xb1, 0x91, 0x9d,
|
||||
0x04, 0x31, 0x7b, 0x8a, 0xa1, 0xbd, 0x37, 0x54, 0xec, 0xba, 0xa1, 0x0e, 0xc2, 0x27, 0xde, 0x85,
|
||||
0x40, 0x69, 0x5b, 0xf2, 0xfb, 0x8e, 0xe5, 0x6f, 0x6d, 0xc5, 0x26, 0xef, 0x36, 0x66, 0x25, 0xb9,
|
||||
0x1a, 0xa4, 0x97, 0x0b, 0x6f, 0xfa, 0x5c, 0x82, 0x84, 0xb9, 0xb5, 0xab, 0x85, 0x2b, 0x90, 0x5f,
|
||||
0x9d, 0x83, 0xf5, 0x66, 0x9c, 0x05, 0x35, 0xbc, 0x37, 0x7b, 0xcc, 0x05, 0xad, 0x5e, 0x48, 0xe2,
|
||||
0x81, 0xec, 0x0e, 0x19, 0x17, 0xca, 0x3c, 0x6a, 0x47, 0x1f, 0x8d, 0xa0, 0x89, 0x4b, 0xc8, 0x2a,
|
||||
0xc2, 0xa8, 0x96, 0x54, 0x05, 0xd6, 0xee, 0xf3, 0xb5, 0xe2, 0x93, 0xa8, 0x8f, 0xda, 0x20, 0x3f,
|
||||
0x09, 0xbd, 0xc7, 0x27, 0x57, 0xb1, 0x07, 0xab, 0x14, 0x88, 0x0e, 0xaa, 0x3e, 0xf7, 0x04, 0x5b,
|
||||
0x58, 0x0f, 0x48, 0x21, 0xce, 0x6d, 0xd3, 0x25, 0xb5, 0xa9, 0x06, 0x55, 0xd8, 0xc5, 0xb5, 0x5f,
|
||||
0x76, 0xfb, 0x84, 0x62, 0x79, 0xa9, 0xb5, 0x18, 0xc5, 0xe9, 0xb9, 0xa2, 0x11, 0x65, 0xc5, 0x09,
|
||||
0x3e, 0xd4, 0x9b, 0xaa, 0xac, 0xad, 0xf1, 0xf2, 0x18, 0x73, 0x26, 0x6c, 0x76, 0x7f, 0x67, 0x69,
|
||||
];
|
||||
|
||||
const INITIAL_PACKET_V1: &[u8] = &[
|
||||
0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x00, 0x00,
|
||||
0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34, 0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, 0x9f, 0xb8, 0xec, 0x11,
|
||||
|
@ -173,19 +251,19 @@ const INITIAL_PACKET_29: &[u8] = &[
|
|||
0x18, 0x02, 0x77, 0x1a, 0x44, 0x9b, 0x30, 0xf3, 0xfa, 0x22, 0x89, 0x85, 0x26, 0x07, 0xb6, 0x60,
|
||||
];
|
||||
|
||||
fn make_server(quic_version: QuicVersion) -> Connection {
|
||||
fn make_server(v: Version) -> Connection {
|
||||
test_fixture::fixture_init();
|
||||
Connection::new_server(
|
||||
test_fixture::DEFAULT_KEYS,
|
||||
test_fixture::DEFAULT_ALPN,
|
||||
Rc::new(RefCell::new(RandomConnectionIdGenerator::new(5))),
|
||||
ConnectionParameters::default().quic_version(quic_version),
|
||||
ConnectionParameters::default().versions(v, vec![v]),
|
||||
)
|
||||
.expect("create a default server")
|
||||
}
|
||||
|
||||
fn process_client_initial(quic_version: QuicVersion, packet: &[u8]) {
|
||||
let mut server = make_server(quic_version);
|
||||
fn process_client_initial(v: Version, packet: &[u8]) {
|
||||
let mut server = make_server(v);
|
||||
|
||||
let dgram = Datagram::new(addr(), addr(), packet);
|
||||
assert_eq!(*server.state(), State::Init);
|
||||
|
@ -194,12 +272,17 @@ fn process_client_initial(quic_version: QuicVersion, packet: &[u8]) {
|
|||
assert!(out.dgram().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_client_initial_v2() {
|
||||
process_client_initial(Version::Version2, INITIAL_PACKET_V2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_client_initial_v1() {
|
||||
process_client_initial(QuicVersion::Version1, INITIAL_PACKET_V1);
|
||||
process_client_initial(Version::Version1, INITIAL_PACKET_V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_client_initial_29() {
|
||||
process_client_initial(QuicVersion::Draft29, INITIAL_PACKET_29);
|
||||
process_client_initial(Version::Draft29, INITIAL_PACKET_29);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,14 @@
|
|||
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
||||
#![warn(clippy::use_self)]
|
||||
|
||||
use neqo_common::Datagram;
|
||||
use test_fixture::{self, default_client, default_server, now};
|
||||
mod common;
|
||||
|
||||
use common::{
|
||||
apply_header_protection, decode_initial_header, initial_aead_and_hp, remove_header_protection,
|
||||
};
|
||||
use neqo_common::{Datagram, Decoder, Role};
|
||||
use neqo_transport::{ConnectionParameters, State, Version};
|
||||
use test_fixture::{self, default_client, default_server, new_client, now, split_datagram};
|
||||
|
||||
#[test]
|
||||
fn connect() {
|
||||
|
@ -34,8 +40,8 @@ fn truncate_long_packet() {
|
|||
dupe.destination(),
|
||||
&dupe[..(dupe.len() - tail)],
|
||||
);
|
||||
let dupe_ack = client.process(Some(truncated), now()).dgram();
|
||||
assert!(dupe_ack.is_some());
|
||||
let hs_probe = client.process(Some(truncated), now()).dgram();
|
||||
assert!(hs_probe.is_some());
|
||||
|
||||
// Now feed in the untruncated packet.
|
||||
let dgram = client.process(dgram, now()).dgram();
|
||||
|
@ -49,3 +55,73 @@ fn truncate_long_packet() {
|
|||
assert!(dgram.is_some());
|
||||
assert!(server.state().connected());
|
||||
}
|
||||
|
||||
/// Test that reordering parts of the server Initial doesn't change things.
|
||||
#[test]
|
||||
fn reorder_server_initial() {
|
||||
// A simple ACK frame for a single packet with packet number 0.
|
||||
const ACK_FRAME: &[u8] = &[0x02, 0x00, 0x00, 0x00, 0x00];
|
||||
|
||||
let mut client = new_client(
|
||||
ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
|
||||
);
|
||||
let mut server = default_server();
|
||||
|
||||
let client_initial = client.process_output(now()).dgram();
|
||||
let (_, client_dcid, _, _) =
|
||||
decode_initial_header(client_initial.as_ref().unwrap(), Role::Client);
|
||||
let client_dcid = client_dcid.to_owned();
|
||||
|
||||
let server_packet = server.process(client_initial, now()).dgram();
|
||||
let (server_initial, server_hs) = split_datagram(server_packet.as_ref().unwrap());
|
||||
let (protected_header, _, _, payload) = decode_initial_header(&server_initial, Role::Server);
|
||||
|
||||
// Now decrypt the packet.
|
||||
let (aead, hp) = initial_aead_and_hp(&client_dcid, Role::Server);
|
||||
let (header, pn) = remove_header_protection(&hp, protected_header, payload);
|
||||
assert_eq!(pn, 0);
|
||||
let pn_len = header.len() - protected_header.len();
|
||||
let mut buf = vec![0; payload.len()];
|
||||
let mut plaintext = aead
|
||||
.decrypt(pn, &header, &payload[pn_len..], &mut buf)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
// Now we need to find the frames. Make some really strong assumptions.
|
||||
let mut dec = Decoder::new(&plaintext[..]);
|
||||
assert_eq!(dec.decode(ACK_FRAME.len()), Some(ACK_FRAME));
|
||||
assert_eq!(dec.decode_varint(), Some(0x06)); // CRYPTO
|
||||
assert_eq!(dec.decode_varint(), Some(0x00)); // offset
|
||||
dec.skip_vvec(); // Skip over the payload.
|
||||
let end = dec.offset();
|
||||
|
||||
// Move the ACK frame after the CRYPTO frame.
|
||||
plaintext[..end].rotate_left(ACK_FRAME.len());
|
||||
|
||||
// And rebuild a packet.
|
||||
let mut packet = header.clone();
|
||||
packet.resize(1200, 0);
|
||||
aead.encrypt(pn, &header, &plaintext, &mut packet[header.len()..])
|
||||
.unwrap();
|
||||
apply_header_protection(&hp, &mut packet, protected_header.len()..header.len());
|
||||
let reordered = Datagram::new(
|
||||
server_initial.source(),
|
||||
server_initial.destination(),
|
||||
packet,
|
||||
);
|
||||
|
||||
// Now a connection can be made successfully.
|
||||
// Though we modified the server's Initial packet, we get away with it.
|
||||
// TLS only authenticates the content of the CRYPTO frame, which was untouched.
|
||||
client.process_input(reordered, now());
|
||||
client.process_input(server_hs.unwrap(), now());
|
||||
assert!(test_fixture::maybe_authenticate(&mut client));
|
||||
let finished = client.process_output(now()).dgram();
|
||||
assert_eq!(*client.state(), State::Connected);
|
||||
|
||||
let done = server.process(finished, now()).dgram();
|
||||
assert_eq!(*server.state(), State::Confirmed);
|
||||
|
||||
client.process_input(done.unwrap(), now());
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
mod common;
|
||||
|
||||
use common::{
|
||||
apply_header_protection, client_initial_aead_and_hp, connected_server, decode_initial_header,
|
||||
default_server, get_ticket, remove_header_protection,
|
||||
apply_header_protection, connected_server, decode_initial_header, default_server,
|
||||
generate_ticket, initial_aead_and_hp, remove_header_protection,
|
||||
};
|
||||
use neqo_common::{hex_with_len, qdebug, qtrace, Datagram, Encoder};
|
||||
use neqo_common::{hex_with_len, qdebug, qtrace, Datagram, Encoder, Role};
|
||||
use neqo_crypto::AuthenticationStatus;
|
||||
use neqo_transport::{server::ValidateAddress, ConnectionError, Error, State, StreamType};
|
||||
use std::convert::TryFrom;
|
||||
|
@ -50,6 +50,26 @@ fn retry_basic() {
|
|||
connected_server(&mut server);
|
||||
}
|
||||
|
||||
/// Receiving a Retry is enough to infer something about the RTT.
|
||||
/// Probably.
|
||||
#[test]
|
||||
fn implicit_rtt_retry() {
|
||||
const RTT: Duration = Duration::from_secs(2);
|
||||
let mut server = default_server();
|
||||
server.set_validation(ValidateAddress::Always);
|
||||
let mut client = default_client();
|
||||
let mut now = now();
|
||||
|
||||
let dgram = client.process(None, now).dgram();
|
||||
now += RTT / 2;
|
||||
let dgram = server.process(dgram, now).dgram();
|
||||
assertions::assert_retry(dgram.as_ref().unwrap());
|
||||
now += RTT / 2;
|
||||
client.process_input(dgram.unwrap(), now);
|
||||
|
||||
assert_eq!(client.stats().rtt, RTT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_expired() {
|
||||
let mut server = default_server();
|
||||
|
@ -76,7 +96,7 @@ fn retry_expired() {
|
|||
#[test]
|
||||
fn retry_0rtt() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
server.set_validation(ValidateAddress::Always);
|
||||
|
||||
let mut client = default_client();
|
||||
|
@ -138,7 +158,7 @@ fn retry_different_ip() {
|
|||
#[test]
|
||||
fn new_token_different_ip() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
server.set_validation(ValidateAddress::NoToken);
|
||||
|
||||
let mut client = default_client();
|
||||
|
@ -160,7 +180,7 @@ fn new_token_different_ip() {
|
|||
#[test]
|
||||
fn new_token_expired() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
server.set_validation(ValidateAddress::NoToken);
|
||||
|
||||
let mut client = default_client();
|
||||
|
@ -353,10 +373,11 @@ fn mitm_retry() {
|
|||
// Now to start the epic process of decrypting the packet,
|
||||
// rewriting the header to remove the token, and then re-encrypting.
|
||||
let client_initial2 = client_initial2.unwrap();
|
||||
let (protected_header, d_cid, s_cid, payload) = decode_initial_header(&client_initial2);
|
||||
let (protected_header, d_cid, s_cid, payload) =
|
||||
decode_initial_header(&client_initial2, Role::Client);
|
||||
|
||||
// Now we have enough information to make keys.
|
||||
let (aead, hp) = client_initial_aead_and_hp(d_cid);
|
||||
let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
|
||||
let (header, pn) = remove_header_protection(&hp, protected_header, payload);
|
||||
let pn_len = header.len() - protected_header.len();
|
||||
|
||||
|
@ -375,12 +396,13 @@ fn mitm_retry() {
|
|||
.encode_vvec(&[])
|
||||
.encode_varint(u64::try_from(payload.len()).unwrap());
|
||||
let pn_offset = enc.len();
|
||||
let notoken_header = enc.encode_uint(pn_len, pn).to_vec();
|
||||
let notoken_header = enc.encode_uint(pn_len, pn).as_ref().to_vec();
|
||||
qtrace!("notoken_header={}", hex_with_len(¬oken_header));
|
||||
|
||||
// Encrypt.
|
||||
let mut notoken_packet = Encoder::with_capacity(1200)
|
||||
.encode(¬oken_header)
|
||||
.as_ref()
|
||||
.to_vec();
|
||||
notoken_packet.resize_with(1200, u8::default);
|
||||
aead.encrypt(
|
||||
|
|
|
@ -10,19 +10,21 @@
|
|||
mod common;
|
||||
|
||||
use common::{
|
||||
apply_header_protection, client_initial_aead_and_hp, connect, connected_server,
|
||||
decode_initial_header, default_server, get_ticket, remove_header_protection,
|
||||
apply_header_protection, connect, connected_server, decode_initial_header, default_server,
|
||||
find_ticket, generate_ticket, initial_aead_and_hp, new_server, remove_header_protection,
|
||||
};
|
||||
|
||||
use neqo_common::{qtrace, Datagram, Decoder, Encoder};
|
||||
use neqo_crypto::{generate_ech_keys, AllowZeroRtt, ZeroRttCheckResult, ZeroRttChecker};
|
||||
use neqo_common::{qtrace, Datagram, Decoder, Encoder, Role};
|
||||
use neqo_crypto::{
|
||||
generate_ech_keys, AllowZeroRtt, AuthenticationStatus, ZeroRttCheckResult, ZeroRttChecker,
|
||||
};
|
||||
use neqo_transport::{
|
||||
server::{ActiveConnectionRef, Server, ValidateAddress},
|
||||
Connection, ConnectionError, ConnectionParameters, Error, Output, QuicVersion, State,
|
||||
StreamType,
|
||||
Connection, ConnectionError, ConnectionParameters, Error, Output, State, StreamType, Version,
|
||||
};
|
||||
use test_fixture::{
|
||||
self, assertions, default_client, now, split_datagram, CountingConnectionIdGenerator,
|
||||
self, assertions, default_client, new_client, now, split_datagram,
|
||||
CountingConnectionIdGenerator,
|
||||
};
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
@ -66,6 +68,70 @@ fn single_client() {
|
|||
connect(&mut client, &mut server);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_single_version_both() {
|
||||
fn connect_one_version(version: Version) {
|
||||
let params = ConnectionParameters::default().versions(version, vec![version]);
|
||||
let mut server = new_server(params.clone());
|
||||
|
||||
let mut client = new_client(params);
|
||||
let server_conn = connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), version);
|
||||
assert_eq!(server_conn.borrow().version(), version);
|
||||
}
|
||||
|
||||
for v in Version::all() {
|
||||
println!("Connecting with {:?}", v);
|
||||
connect_one_version(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_single_version_client() {
|
||||
fn connect_one_version(version: Version) {
|
||||
let mut server = default_server();
|
||||
|
||||
let mut client =
|
||||
new_client(ConnectionParameters::default().versions(version, vec![version]));
|
||||
let server_conn = connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), version);
|
||||
assert_eq!(server_conn.borrow().version(), version);
|
||||
}
|
||||
|
||||
for v in Version::all() {
|
||||
println!("Connecting with {:?}", v);
|
||||
connect_one_version(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_single_version_server() {
|
||||
fn connect_one_version(version: Version) {
|
||||
let mut server =
|
||||
new_server(ConnectionParameters::default().versions(version, vec![version]));
|
||||
|
||||
let mut client = default_client();
|
||||
|
||||
if client.version() != version {
|
||||
// Run the version negotiation exchange if necessary.
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
assertions::assert_vn(dgram.as_ref().unwrap());
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
}
|
||||
|
||||
let server_conn = connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), version);
|
||||
assert_eq!(server_conn.borrow().version(), version);
|
||||
}
|
||||
|
||||
for v in Version::all() {
|
||||
println!("Connecting with {:?}", v);
|
||||
connect_one_version(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_initial() {
|
||||
let mut server = default_server();
|
||||
|
@ -164,7 +230,7 @@ fn drop_non_initial() {
|
|||
let mut header = neqo_common::Encoder::with_capacity(1200);
|
||||
header
|
||||
.encode_byte(0xfa)
|
||||
.encode_uint(4, QuicVersion::default().as_u32())
|
||||
.encode_uint(4, Version::default().wire_version())
|
||||
.encode_vec(1, CID)
|
||||
.encode_vec(1, CID);
|
||||
let mut bogus_data: Vec<u8> = header.into();
|
||||
|
@ -183,7 +249,7 @@ fn drop_short_initial() {
|
|||
let mut header = neqo_common::Encoder::with_capacity(1199);
|
||||
header
|
||||
.encode_byte(0xca)
|
||||
.encode_uint(4, QuicVersion::default().as_u32())
|
||||
.encode_uint(4, Version::default().wire_version())
|
||||
.encode_vec(1, CID)
|
||||
.encode_vec(1, CID);
|
||||
let mut bogus_data: Vec<u8> = header.into();
|
||||
|
@ -200,7 +266,7 @@ fn drop_short_initial() {
|
|||
#[test]
|
||||
fn zero_rtt() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
|
||||
// Discharge the old connection so that we don't have to worry about it.
|
||||
let mut now = now();
|
||||
|
@ -262,7 +328,7 @@ fn zero_rtt() {
|
|||
#[test]
|
||||
fn new_token_0rtt() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
server.set_validation(ValidateAddress::NoToken);
|
||||
|
||||
let mut client = default_client();
|
||||
|
@ -293,7 +359,7 @@ fn new_token_0rtt() {
|
|||
#[test]
|
||||
fn new_token_different_port() {
|
||||
let mut server = default_server();
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
server.set_validation(ValidateAddress::NoToken);
|
||||
|
||||
let mut client = default_client();
|
||||
|
@ -318,8 +384,8 @@ fn bad_client_initial() {
|
|||
let mut server = default_server();
|
||||
|
||||
let dgram = client.process(None, now()).dgram().expect("a datagram");
|
||||
let (header, d_cid, s_cid, payload) = decode_initial_header(&dgram);
|
||||
let (aead, hp) = client_initial_aead_and_hp(d_cid);
|
||||
let (header, d_cid, s_cid, payload) = decode_initial_header(&dgram, Role::Client);
|
||||
let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client);
|
||||
let (fixed_header, pn) = remove_header_protection(&hp, header, payload);
|
||||
let payload = &payload[(fixed_header.len() - header.len())..];
|
||||
|
||||
|
@ -335,20 +401,20 @@ fn bad_client_initial() {
|
|||
let mut header_enc = Encoder::new();
|
||||
header_enc
|
||||
.encode_byte(0xc0) // Initial with 1 byte packet number.
|
||||
.encode_uint(4, QuicVersion::default().as_u32())
|
||||
.encode_uint(4, Version::default().wire_version())
|
||||
.encode_vec(1, d_cid)
|
||||
.encode_vec(1, s_cid)
|
||||
.encode_vvec(&[])
|
||||
.encode_varint(u64::try_from(payload_enc.len() + aead.expansion() + 1).unwrap())
|
||||
.encode_byte(u8::try_from(pn).unwrap());
|
||||
|
||||
let mut ciphertext = header_enc.to_vec();
|
||||
let mut ciphertext = header_enc.as_ref().to_vec();
|
||||
ciphertext.resize(header_enc.len() + payload_enc.len() + aead.expansion(), 0);
|
||||
let v = aead
|
||||
.encrypt(
|
||||
pn,
|
||||
&header_enc,
|
||||
&payload_enc,
|
||||
header_enc.as_ref(),
|
||||
payload_enc.as_ref(),
|
||||
&mut ciphertext[header_enc.len()..],
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -401,7 +467,7 @@ fn bad_client_initial() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn version_negotiation() {
|
||||
fn version_negotiation_ignored() {
|
||||
let mut server = default_server();
|
||||
let mut client = default_client();
|
||||
|
||||
|
@ -426,7 +492,7 @@ fn version_negotiation() {
|
|||
let mut found = false;
|
||||
while dec.remaining() > 0 {
|
||||
let v = dec.decode_uint(4).expect("supported version");
|
||||
found |= v == u64::from(QuicVersion::default().as_u32());
|
||||
found |= v == u64::from(Version::default().wire_version());
|
||||
}
|
||||
assert!(found, "valid version not found");
|
||||
|
||||
|
@ -436,6 +502,128 @@ fn version_negotiation() {
|
|||
assert_eq!(client.state(), &State::WaitInitial);
|
||||
}
|
||||
|
||||
/// Test that if the server doesn't support a version it will signal with a
|
||||
/// Version Negotiation packet and the client will use that version.
|
||||
#[test]
|
||||
fn version_negotiation() {
|
||||
const VN_VERSION: Version = Version::Draft29;
|
||||
assert_ne!(VN_VERSION, Version::default());
|
||||
assert!(!Version::default().is_compatible(VN_VERSION));
|
||||
|
||||
let mut server =
|
||||
new_server(ConnectionParameters::default().versions(VN_VERSION, vec![VN_VERSION]));
|
||||
let mut client = default_client();
|
||||
|
||||
// `connect()` runs a fixed exchange, so manually run the Version Negotiation.
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
assertions::assert_vn(dgram.as_ref().unwrap());
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
|
||||
let sconn = connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), VN_VERSION);
|
||||
assert_eq!(sconn.borrow().version(), VN_VERSION);
|
||||
}
|
||||
|
||||
/// Test that the client can pick a version from a Version Negotiation packet,
|
||||
/// which is then subsequently upgraded to a compatible version by the server.
|
||||
#[test]
|
||||
fn version_negotiation_and_compatible() {
|
||||
const ORIG_VERSION: Version = Version::Draft29;
|
||||
const VN_VERSION: Version = Version::Version1;
|
||||
const COMPAT_VERSION: Version = Version::Version2;
|
||||
assert!(!ORIG_VERSION.is_compatible(VN_VERSION));
|
||||
assert!(!ORIG_VERSION.is_compatible(COMPAT_VERSION));
|
||||
assert!(VN_VERSION.is_compatible(COMPAT_VERSION));
|
||||
|
||||
let mut server = new_server(
|
||||
ConnectionParameters::default().versions(VN_VERSION, vec![COMPAT_VERSION, VN_VERSION]),
|
||||
);
|
||||
// Note that the order of versions at the client only determines what it tries first.
|
||||
// The server will pick between VN_VERSION and COMPAT_VERSION.
|
||||
let mut client = new_client(
|
||||
ConnectionParameters::default()
|
||||
.versions(ORIG_VERSION, vec![ORIG_VERSION, VN_VERSION, COMPAT_VERSION]),
|
||||
);
|
||||
|
||||
// Run the full exchange so that we can observe the versions in use.
|
||||
|
||||
// Version Negotiation
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
assertions::assert_version(dgram.as_ref().unwrap(), ORIG_VERSION.wire_version());
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
assertions::assert_vn(dgram.as_ref().unwrap());
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
|
||||
let dgram = client.process(None, now()).dgram(); // ClientHello
|
||||
assertions::assert_version(dgram.as_ref().unwrap(), VN_VERSION.wire_version());
|
||||
let dgram = server.process(dgram, now()).dgram(); // ServerHello...
|
||||
assertions::assert_version(dgram.as_ref().unwrap(), COMPAT_VERSION.wire_version());
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
|
||||
client.authenticated(AuthenticationStatus::Ok, now());
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assertions::assert_version(dgram.as_ref().unwrap(), COMPAT_VERSION.wire_version());
|
||||
assert_eq!(*client.state(), State::Connected);
|
||||
let dgram = server.process(dgram, now()).dgram(); // ACK + HANDSHAKE_DONE + NST
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
assert_eq!(*client.state(), State::Confirmed);
|
||||
|
||||
let sconn = connected_server(&mut server);
|
||||
assert_eq!(client.version(), COMPAT_VERSION);
|
||||
assert_eq!(sconn.borrow().version(), COMPAT_VERSION);
|
||||
}
|
||||
|
||||
/// When a client resumes it remembers the version that the connection last used.
|
||||
/// A subsequent connection will use that version, but if it then receives
|
||||
/// a version negotiation packet, it should validate based on what it attempted
|
||||
/// not what it was originally configured for.
|
||||
#[test]
|
||||
fn compatible_upgrade_resumption_and_vn() {
|
||||
// Start at v1, compatible upgrade to v2.
|
||||
const ORIG_VERSION: Version = Version::Version1;
|
||||
const COMPAT_VERSION: Version = Version::Version2;
|
||||
const RESUMPTION_VERSION: Version = Version::Draft29;
|
||||
|
||||
let client_params = ConnectionParameters::default().versions(
|
||||
ORIG_VERSION,
|
||||
vec![COMPAT_VERSION, ORIG_VERSION, RESUMPTION_VERSION],
|
||||
);
|
||||
let mut client = new_client(client_params.clone());
|
||||
assert_eq!(client.version(), ORIG_VERSION);
|
||||
|
||||
let mut server = default_server();
|
||||
let mut server_conn = connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), COMPAT_VERSION);
|
||||
assert_eq!(server_conn.borrow().version(), COMPAT_VERSION);
|
||||
|
||||
server_conn.borrow_mut().send_ticket(now(), &[]).unwrap();
|
||||
let dgram = server.process(None, now()).dgram();
|
||||
client.process_input(dgram.unwrap(), now()); // Consume ticket, ignore output.
|
||||
let ticket = find_ticket(&mut client);
|
||||
|
||||
// This new server will reject the ticket, but it will also generate a VN packet.
|
||||
let mut client = new_client(client_params);
|
||||
let mut server = new_server(
|
||||
ConnectionParameters::default().versions(RESUMPTION_VERSION, vec![RESUMPTION_VERSION]),
|
||||
);
|
||||
client.enable_resumption(now(), ticket).unwrap();
|
||||
|
||||
// The version negotiation exchange.
|
||||
let dgram = client.process_output(now()).dgram();
|
||||
assert!(dgram.is_some());
|
||||
assertions::assert_version(dgram.as_ref().unwrap(), COMPAT_VERSION.wire_version());
|
||||
let dgram = server.process(dgram, now()).dgram();
|
||||
assertions::assert_vn(dgram.as_ref().unwrap());
|
||||
client.process_input(dgram.unwrap(), now());
|
||||
|
||||
let server_conn = connect(&mut client, &mut server);
|
||||
assert_eq!(client.version(), RESUMPTION_VERSION);
|
||||
assert_eq!(server_conn.borrow().version(), RESUMPTION_VERSION);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closed() {
|
||||
// Let a server connection idle and it should be removed.
|
||||
|
@ -533,7 +721,7 @@ fn max_streams_after_0rtt_rejection() {
|
|||
.max_streams(StreamType::UniDi, MAX_STREAMS_UNIDI),
|
||||
)
|
||||
.expect("should create a server");
|
||||
let token = get_ticket(&mut server);
|
||||
let token = generate_ticket(&mut server);
|
||||
|
||||
let mut client = default_client();
|
||||
client.enable_resumption(now(), &token).unwrap();
|
||||
|
|
|
@ -59,7 +59,7 @@ macro_rules! simulate {
|
|||
#[test]
|
||||
fn $n() {
|
||||
let fixture = $setup;
|
||||
let mut nodes: Vec<Box<dyn crate::sim::Node>> = Vec::new();
|
||||
let mut nodes: Vec<Box<dyn $crate::sim::Node>> = Vec::new();
|
||||
$(
|
||||
let f: Box<dyn FnOnce(&_) -> _> = Box::new($v);
|
||||
nodes.push(Box::new(f(&fixture)));
|
||||
|
@ -149,7 +149,7 @@ impl Simulator {
|
|||
/// Though this is convenient, it panics if this isn't a 64 character hex string.
|
||||
pub fn seed_str(&mut self, seed: impl AsRef<str>) {
|
||||
let seed = Encoder::from_hex(seed);
|
||||
self.seed(<[u8; 32]>::try_from(&seed[..]).unwrap());
|
||||
self.seed(<[u8; 32]>::try_from(seed.as_ref()).unwrap());
|
||||
}
|
||||
|
||||
fn next_time(&self, now: Instant) -> Instant {
|
||||
|
|
Загрузка…
Ссылка в новой задаче