tor-proto: padding: Test padding timer distribution
This commit is contained in:
parent
bbcdf9dd8b
commit
3f2e164bc5
|
@ -63,6 +63,15 @@ version = "1.0.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -1801,6 +1810,15 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matrixmultiply"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
|
||||||
|
dependencies = [
|
||||||
|
"rawpointer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -1898,6 +1916,35 @@ dependencies = [
|
||||||
"ws2_32-sys",
|
"ws2_32-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"matrixmultiply",
|
||||||
|
"nalgebra-macros",
|
||||||
|
"num-complex",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"rand_distr",
|
||||||
|
"simba",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
@ -1995,6 +2042,15 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
@ -2016,6 +2072,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.1.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -2478,6 +2545,16 @@ dependencies = [
|
||||||
"getrandom 0.2.6",
|
"getrandom 0.2.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_distr"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_hc"
|
name = "rand_hc"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2487,6 +2564,12 @@ dependencies = [
|
||||||
"rand_core 0.5.1",
|
"rand_core 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rawpointer"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
|
@ -2925,6 +3008,18 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4"
|
checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simba"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"num-complex",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple_asn1"
|
name = "simple_asn1"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -2986,6 +3081,19 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "statrs"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"lazy_static",
|
||||||
|
"nalgebra",
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -3721,9 +3829,11 @@ dependencies = [
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"hmac",
|
"hmac",
|
||||||
|
"itertools",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rand_core 0.6.3",
|
"rand_core 0.6.3",
|
||||||
|
"statrs",
|
||||||
"subtle",
|
"subtle",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -54,5 +54,7 @@ zeroize = "1"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
hex-literal = "0.3"
|
hex-literal = "0.3"
|
||||||
|
itertools = "0.10.1"
|
||||||
|
statrs = "0.15.0"
|
||||||
tokio-crate = { package = "tokio", version = "1.7", features = ["full"] }
|
tokio-crate = { package = "tokio", version = "1.7", features = ["full"] }
|
||||||
tor-rtcompat = { path = "../tor-rtcompat", version = "0.4.0", features = ["tokio", "native-tls"] }
|
tor-rtcompat = { path = "../tor-rtcompat", version = "0.4.0", features = ["tokio", "native-tls"] }
|
||||||
|
|
|
@ -300,6 +300,8 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use futures::future::ready;
|
use futures::future::ready;
|
||||||
use futures::select_biased;
|
use futures::select_biased;
|
||||||
|
use itertools::{izip, Itertools};
|
||||||
|
use statrs::distribution::ContinuousCDF;
|
||||||
use tokio::pin;
|
use tokio::pin;
|
||||||
use tokio_crate as tokio;
|
use tokio_crate as tokio;
|
||||||
use tor_rtcompat::*;
|
use tor_rtcompat::*;
|
||||||
|
@ -400,4 +402,125 @@ mod test {
|
||||||
assert_eq! { false, timer.is_enabled() }
|
assert_eq! { false, timer.is_enabled() }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timeout_distribution() {
|
||||||
|
// Test that the distribution of padding intervals is as we expect. This is not so
|
||||||
|
// straightforward. We need to deal with true randomness (since we can't plumb a
|
||||||
|
// testing RNG into the padding timer, and perhaps don't even *want* to make that a
|
||||||
|
// mockable interface). Measuring a distribution of random variables involves some
|
||||||
|
// statistics.
|
||||||
|
|
||||||
|
// The overall approach is:
|
||||||
|
// Use a fixed (but nontrivial) low to high range
|
||||||
|
// Sample N times into n equal sized buckes
|
||||||
|
// Calculate the expected number of samples in each bucket
|
||||||
|
// Do a chi^2 test. If it doesn't spot a potential difference, declare OK.
|
||||||
|
// If the chi^2 test does definitely declare a difference, declare failure.
|
||||||
|
// Otherwise increase N and go round again.
|
||||||
|
//
|
||||||
|
// This allows most runs to be fast without having an appreciable possibility of a
|
||||||
|
// false test failure and while being able to detect even quite small deviations.
|
||||||
|
|
||||||
|
// Notation from
|
||||||
|
// https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test#Calculating_the_test-statistic
|
||||||
|
// I haven't done a formal power calculation but empirically
|
||||||
|
// this detects the following most of the time:
|
||||||
|
// deviation of the CDF power from B^2 to B^1.98
|
||||||
|
// wrong minimum value by 25ms out of 12s, low_ms = min + 25
|
||||||
|
// wrong maximum value by 10ms out of 12s, high_ms = max -1 - 10
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let mut N = 100_0000;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
const n: usize = 100;
|
||||||
|
|
||||||
|
const P_GOOD: f64 = 0.05; // Investigate further 5% of times (if all is actually well)
|
||||||
|
const P_BAD: f64 = 1e-12;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
eprintln!("padding distribution test, n={} N={}", n, N);
|
||||||
|
|
||||||
|
let min = 5000;
|
||||||
|
let max = 17000; // Exclusive
|
||||||
|
assert_eq!(0, (max - min) % (n as u32)); // buckets must match up to integer boundaries
|
||||||
|
|
||||||
|
let cdf = (0..=n)
|
||||||
|
.into_iter()
|
||||||
|
.map(|bi| {
|
||||||
|
let b = (bi as f64) / (n as f64);
|
||||||
|
// expected distribution:
|
||||||
|
// with B = bi / n
|
||||||
|
// P(X) < B == B
|
||||||
|
// P(max(X1,X1)) < B = B^2
|
||||||
|
b.powi(2)
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
let pdf = cdf
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.tuple_windows()
|
||||||
|
.map(|(p, q)| q - p)
|
||||||
|
.collect_vec();
|
||||||
|
let exp = pdf.iter().cloned().map(|p| p * f64::from(N)).collect_vec();
|
||||||
|
|
||||||
|
// chi-squared test only valid if every cell expects at lesat 5
|
||||||
|
assert!(exp.iter().cloned().all(|ei| ei >= 5.));
|
||||||
|
|
||||||
|
let mut obs = [0_u32; n];
|
||||||
|
|
||||||
|
let params = Parameters {
|
||||||
|
low_ms: min,
|
||||||
|
high_ms: max - 1, // convert exclusive to inclusive
|
||||||
|
}
|
||||||
|
.prepare();
|
||||||
|
|
||||||
|
for _ in 0..N {
|
||||||
|
let xx = params.select_timeout();
|
||||||
|
let ms = xx.as_millis();
|
||||||
|
let ms = u32::try_from(ms).unwrap();
|
||||||
|
assert!(ms >= min);
|
||||||
|
assert!(ms < max);
|
||||||
|
// Integer arithmetic ensures that we classify exactly
|
||||||
|
let bi = ((ms - min) * (n as u32)) / (max - min);
|
||||||
|
obs[bi as usize] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let chi2 = izip!(&obs, &exp)
|
||||||
|
.map(|(&oi, &ei)| (f64::from(oi) - ei).powi(2) / ei)
|
||||||
|
.sum::<f64>();
|
||||||
|
|
||||||
|
// n degrees of freedom, one-tailed test
|
||||||
|
// (since distro parameters are all fixed, not estimated from the sample)
|
||||||
|
let chi2_distr = statrs::distribution::ChiSquared::new(n as _).unwrap();
|
||||||
|
|
||||||
|
// probability of good code generating a result at least this bad
|
||||||
|
let p = 1. - chi2_distr.cdf(chi2);
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"padding distribution test, n={} N={} chi2={} p={}",
|
||||||
|
n, N, chi2, p
|
||||||
|
);
|
||||||
|
|
||||||
|
if p >= P_GOOD {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, (&oi, &ei)) in izip!(&obs, &exp).enumerate() {
|
||||||
|
eprintln!("bi={:4} OI={:4} EI={}", i, oi, ei);
|
||||||
|
}
|
||||||
|
|
||||||
|
if p < P_BAD {
|
||||||
|
panic!("distribution is wrong (p < {:e})", P_BAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is statistically rather cheaty: we keep trying until we get a definite
|
||||||
|
// answer! But we radically increase the power of the test each time.
|
||||||
|
// If the distribution is really wrong, this test ought to find it soon enough,
|
||||||
|
// especially since we run this repeatedly in CI.
|
||||||
|
N *= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue