2026-09-10 13:43:23 -04:00
|
|
|
//! HS publish wait and CBT floor — 180s fail-closed cuts a working HsDir upload.
|
|
|
|
|
|
2026-09-10 20:30:18 -04:00
|
|
|
use std::os::unix::fs::PermissionsExt;
|
2026-09-10 13:43:23 -04:00
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
use onionwire::hs;
|
2026-09-10 16:14:10 -04:00
|
|
|
use tor_hsservice::status::State;
|
2026-09-10 13:43:23 -04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn publish_wait_covers_hsdir_retries() {
|
|
|
|
|
assert!(
|
|
|
|
|
hs::PUBLISH_WAIT >= Duration::from_secs(360),
|
|
|
|
|
"180s cuts a working HsDir publish while status is still Bootstrapping"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn cbt_min_timeout_floor_is_at_least_10s() {
|
2026-09-10 13:50:57 -04:00
|
|
|
// Drive the comparison through a runtime value: asserting on the constant
|
|
|
|
|
// directly is folded away by the compiler and clippy rejects it under
|
|
|
|
|
// -D warnings (clippy::assertions_on_constants).
|
|
|
|
|
let floor_ms = hs::CBT_MIN_TIMEOUT_MS;
|
|
|
|
|
assert!(floor_ms >= 10_000, "learned CBT ~1s kills HsDir circuits");
|
2026-09-10 13:43:23 -04:00
|
|
|
}
|
2026-09-10 16:14:10 -04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn cbt_initial_timeout_matches_the_floor() {
|
|
|
|
|
// Consensus cbtinitialtimeout can be ~2s; 4-hop vanguard HS circuits
|
|
|
|
|
// need the same floor as cbtmintimeout before the estimator has samples.
|
|
|
|
|
let initial = hs::CBT_INITIAL_TIMEOUT_MS;
|
|
|
|
|
let floor = hs::CBT_MIN_TIMEOUT_MS;
|
|
|
|
|
assert!(initial >= floor, "initial CBT below min floor");
|
|
|
|
|
assert!(initial >= 10_000, "initial CBT too low for HsDir circuits");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn no_preemptive_exit_ports() {
|
|
|
|
|
// Default predicted 80/443 circuits compete with IPT + HsDir builds.
|
|
|
|
|
// OnionWire never exits; keep the predicted list empty.
|
|
|
|
|
assert!(
|
|
|
|
|
hs::PREEMPTIVE_PREDICTED_PORTS.is_empty(),
|
|
|
|
|
"preemptive exit circuits starve HS publish"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn arti_status_running_is_ready_without_probe() {
|
|
|
|
|
assert!(hs::hs_is_ready(State::Running, false));
|
|
|
|
|
assert!(hs::hs_is_ready(State::DegradedReachable, false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn bootstrapping_is_not_ready_until_a_probe_connects() {
|
|
|
|
|
// Combined status stays Bootstrapping through Arti's 5 min HsDir upload
|
|
|
|
|
// round even after some descriptors are already fetchable.
|
|
|
|
|
assert!(!hs::hs_is_ready(State::Bootstrapping, false));
|
|
|
|
|
assert!(hs::hs_is_ready(State::Bootstrapping, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn degraded_unreachable_is_ready_only_if_probe_connects() {
|
|
|
|
|
assert!(!hs::hs_is_ready(State::DegradedUnreachable, false));
|
|
|
|
|
assert!(hs::hs_is_ready(State::DegradedUnreachable, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn broken_or_shutdown_never_ready() {
|
|
|
|
|
assert!(!hs::hs_is_ready(State::Broken, true));
|
|
|
|
|
assert!(!hs::hs_is_ready(State::Shutdown, true));
|
|
|
|
|
}
|
2026-09-10 20:30:18 -04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn hs_log_label_is_not_the_full_v3_onion() {
|
|
|
|
|
// Public v3 address; checksum is valid so HsId::from_str works.
|
|
|
|
|
let onion = "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion";
|
|
|
|
|
let label = hs::log_label(onion);
|
|
|
|
|
assert_ne!(label, onion, "status/probe logs must not use the locator");
|
|
|
|
|
assert!(
|
|
|
|
|
!label.contains("facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd"),
|
|
|
|
|
"log label leaked the onion body: {label}"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
label.contains('…') || label.contains("[scrubbed]"),
|
|
|
|
|
"expected safelog redaction, got {label}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn client_config_mkdirs_state_and_cache_0700() {
|
|
|
|
|
let root = tempfile::tempdir().expect("tempdir");
|
|
|
|
|
let state = root.path().join("arti");
|
|
|
|
|
let cache = root.path().join("cache");
|
|
|
|
|
let _cfg = hs::client_config(&state, &cache);
|
|
|
|
|
let mode = |p: &std::path::Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
|
|
|
|
|
assert_eq!(mode(&state), 0o700);
|
|
|
|
|
assert_eq!(mode(&cache), 0o700);
|
|
|
|
|
}
|