use web_time_compat::{Duration, Instant};
use crate::{
AbstractTunnel,
hspool::{HsCircStem, HsCircStemKind},
};
use rand::Rng;
use tor_basic_utils::RngExt as _;
pub(super) struct Pool<C: AbstractTunnel> {
circuits: Vec<HsCircStem<C>>,
stem_target: usize,
guarded_stem_target: usize,
have_been_exhausted: bool,
have_been_under_highwater: bool,
last_changed_target: Option<Instant>,
}
const DEFAULT_NAIVE_STEM_TARGET: usize = 3;
const DEFAULT_GUARDED_STEM_TARGET: usize = 1;
const MAX_NAIVE_STEM_TARGET: usize = 384;
const MAX_GUARDED_STEM_TARGET: usize = 128;
pub(super) struct ForLaunch<'a> {
kind: HsCircStemKind,
count: &'a mut usize,
}
impl<'a> ForLaunch<'a> {
pub(super) fn note_circ_launched(self) {
*self.count -= 1;
}
pub(super) fn kind(&self) -> HsCircStemKind {
self.kind
}
}
pub(super) struct CircsToLaunch {
stem_target: usize,
guarded_stem_target: usize,
}
impl CircsToLaunch {
pub(super) fn for_launch(&mut self) -> ForLaunch {
if self.stem_target > 0 {
ForLaunch {
kind: HsCircStemKind::Naive,
count: &mut self.stem_target,
}
} else {
ForLaunch {
kind: HsCircStemKind::Guarded,
count: &mut self.guarded_stem_target,
}
}
}
pub(super) fn stem(&self) -> usize {
self.stem_target
}
pub(super) fn guarded_stem(&self) -> usize {
self.guarded_stem_target
}
pub(super) fn n_to_launch(&self) -> usize {
self.stem_target + self.guarded_stem_target
}
}
impl<C: AbstractTunnel> Default for Pool<C> {
fn default() -> Self {
Self {
circuits: Vec::new(),
stem_target: DEFAULT_NAIVE_STEM_TARGET,
guarded_stem_target: DEFAULT_GUARDED_STEM_TARGET,
have_been_exhausted: false,
have_been_under_highwater: false,
last_changed_target: None,
}
}
}
impl<C: AbstractTunnel> Pool<C> {
pub(super) fn insert(&mut self, circ: HsCircStem<C>) {
self.circuits.push(circ);
}
pub(super) fn retain<F>(&mut self, f: F)
where
F: FnMut(&HsCircStem<C>) -> bool,
{
self.circuits.retain(f);
}
pub(super) fn very_low(&self) -> bool {
self.circuits.len() <= self.target() / 3
}
pub(super) fn circs_to_launch(&self) -> CircsToLaunch {
CircsToLaunch {
stem_target: self.stems_to_launch(),
guarded_stem_target: self.guarded_stems_to_launch(),
}
}
fn stems_to_launch(&self) -> usize {
let circ_count = self
.circuits
.iter()
.filter(|c| c.kind == HsCircStemKind::Naive)
.count();
self.stem_target.saturating_sub(circ_count)
}
fn guarded_stems_to_launch(&self) -> usize {
let circ_count = self
.circuits
.iter()
.filter(|c| c.kind == HsCircStemKind::Guarded)
.count();
self.guarded_stem_target.saturating_sub(circ_count)
}
fn target(&self) -> usize {
self.stem_target + self.guarded_stem_target
}
pub(super) fn take_one_where<R, F>(
&mut self,
rng: &mut R,
f: F,
prefs: &HsCircPrefs,
) -> Option<HsCircStem<C>>
where
R: Rng,
F: Fn(&HsCircStem<C>) -> bool,
{
let rv = match random_idx_where(rng, &mut self.circuits[..], |circ_stem| {
circ_stem.satisfies_prefs(prefs) && f(circ_stem)
})
.or_else(|| {
random_idx_where(rng, &mut self.circuits[..], f)
}) {
Some(idx) => Some(self.circuits.swap_remove(idx)),
None => None,
};
if self.circuits.is_empty() {
self.have_been_exhausted = true;
self.have_been_under_highwater = true;
} else if self.circuits.len() < self.target() * 4 / 5 {
self.have_been_under_highwater = true;
}
rv
}
pub(super) fn update_target_size(&mut self, now: Instant) {
const MIN_TIME_TO_GROW: Duration = Duration::from_secs(120);
const MIN_TIME_TO_SHRINK: Duration = Duration::from_secs(600);
let last_changed = self.last_changed_target.get_or_insert(now);
let time_since_last_change = now.saturating_duration_since(*last_changed);
if self.have_been_exhausted {
if time_since_last_change < MIN_TIME_TO_GROW {
return;
}
self.stem_target *= 2;
self.guarded_stem_target *= 2;
} else if !self.have_been_under_highwater {
if time_since_last_change < MIN_TIME_TO_SHRINK {
return;
}
self.stem_target /= 2;
self.guarded_stem_target /= 2;
}
self.last_changed_target = Some(now);
self.stem_target = self
.stem_target
.clamp(DEFAULT_NAIVE_STEM_TARGET, MAX_NAIVE_STEM_TARGET);
self.guarded_stem_target = self
.guarded_stem_target
.clamp(DEFAULT_GUARDED_STEM_TARGET, MAX_GUARDED_STEM_TARGET);
self.have_been_exhausted = false;
self.have_been_under_highwater = false;
}
#[allow(clippy::unnecessary_wraps)] pub(super) fn retire_all_circuits(&mut self) -> Result<(), tor_config::ReconfigureError> {
self.have_been_exhausted = true;
self.circuits.clear();
Ok(())
}
}
#[derive(Default, Debug, Clone)]
pub(super) struct HsCircPrefs {
pub(super) kind_prefs: Option<HsCircStemKind>,
}
impl HsCircPrefs {
pub(super) fn preferred_stem_kind(&mut self, kind: HsCircStemKind) -> &mut Self {
self.kind_prefs = Some(kind);
self
}
}
fn random_idx_where<R, T, P>(rng: &mut R, mut slice: &mut [T], predicate: P) -> Option<usize>
where
R: Rng,
P: Fn(&T) -> bool,
{
while !slice.is_empty() {
let idx = rng
.gen_range_checked(0..slice.len())
.expect("slice was not empty but is now empty");
if predicate(&slice[idx]) {
return Some(idx);
}
let last_idx = slice.len() - 1;
slice.swap(idx, last_idx);
slice = &mut slice[..last_idx];
}
None
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use tor_basic_utils::test_rng::testing_rng;
#[test]
fn random_idx() {
let mut rng = testing_rng();
let mut orig_numbers: Vec<i32> = vec![1, 3, 4, 8, 11, 19, 12, 6, 27];
let mut numbers = orig_numbers.clone();
let mut found: std::collections::HashMap<i32, bool> =
numbers.iter().map(|n| (*n, false)).collect();
for _ in 0..1000 {
let idx = random_idx_where(&mut rng, &mut numbers[..], |n| n & 1 == 1).unwrap();
assert!(numbers[idx] & 1 == 1);
found.insert(numbers[idx], true);
}
for num in numbers.iter() {
assert!(found[num] == (num & 1 == 1));
}
numbers.sort();
orig_numbers.sort();
assert_eq!(numbers, orig_numbers);
}
#[test]
fn random_idx_empty() {
let mut rng = testing_rng();
let idx = random_idx_where(&mut rng, &mut [], |_: &i32| panic!());
assert_eq!(idx, None);
}
#[test]
fn random_idx_none() {
let mut rng = testing_rng();
let mut numbers: Vec<i32> = vec![1, 3, 4, 8, 11, 19, 12, 6, 27];
assert_eq!(
random_idx_where(&mut rng, &mut numbers[..], |_: &i32| false),
None
);
}
}