use crate::{PreemptiveCircuitConfig, TargetCircUsage, TargetPort};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use tracing::warn;
pub(crate) struct PreemptiveCircuitPredictor {
usages: HashMap<Option<TargetPort>, Instant>,
config: tor_config::MutCfg<PreemptiveCircuitConfig>,
}
impl PreemptiveCircuitPredictor {
pub(crate) fn new(config: PreemptiveCircuitConfig) -> Self {
let mut usages = HashMap::new();
for port in &config.initial_predicted_ports {
usages.insert(Some(TargetPort::ipv4(*port)), Instant::now());
}
usages.insert(None, Instant::now());
Self {
usages,
config: config.into(),
}
}
pub(crate) fn config(&self) -> Arc<PreemptiveCircuitConfig> {
self.config.get()
}
pub(crate) fn set_config(&self, mut new_config: PreemptiveCircuitConfig) {
self.config.map_and_replace(|cfg| {
new_config.initial_predicted_ports = cfg.initial_predicted_ports.clone();
new_config
});
}
pub(crate) fn predict(&self) -> Vec<TargetCircUsage> {
let config = self.config();
let now = Instant::now();
let circs = config.min_exit_circs_for_port;
self.usages
.iter()
.filter(|(_, &time)| {
time.checked_add(config.prediction_lifetime)
.map(|t| t > now)
.unwrap_or_else(|| {
warn!("failed to represent preemptive circuit prediction lifetime as an Instant");
false
})
})
.map(|(&port, _)| TargetCircUsage::Preemptive { port, circs })
.collect()
}
pub(crate) fn note_usage(&mut self, port: Option<TargetPort>, time: Instant) {
self.usages.insert(port, time);
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use crate::{PreemptiveCircuitConfig, PreemptiveCircuitPredictor, TargetCircUsage, TargetPort};
use std::time::{Duration, Instant};
use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
#[test]
fn predicts_starting_ports() {
let mut cfg = PreemptiveCircuitConfig::builder();
cfg.set_initial_predicted_ports(vec![]);
cfg.prediction_lifetime(Duration::from_secs(2));
let predictor = PreemptiveCircuitPredictor::new(cfg.build().unwrap());
assert_isoleq!(
predictor.predict(),
vec![TargetCircUsage::Preemptive {
port: None,
circs: 2
}]
);
let mut cfg = PreemptiveCircuitConfig::builder();
cfg.set_initial_predicted_ports(vec![80]);
cfg.prediction_lifetime(Duration::from_secs(2));
let predictor = PreemptiveCircuitPredictor::new(cfg.build().unwrap());
let results = predictor.predict();
assert_eq!(results.len(), 2);
assert!(results
.iter()
.any(|r| r.isol_eq(&TargetCircUsage::Preemptive {
port: None,
circs: 2
})));
assert!(results
.iter()
.any(|r| r.isol_eq(&TargetCircUsage::Preemptive {
port: Some(TargetPort::ipv4(80)),
circs: 2
})));
}
#[test]
fn predicts_used_ports() {
let mut cfg = PreemptiveCircuitConfig::builder();
cfg.set_initial_predicted_ports(vec![]);
cfg.prediction_lifetime(Duration::from_secs(2));
let mut predictor = PreemptiveCircuitPredictor::new(cfg.build().unwrap());
assert_isoleq!(
predictor.predict(),
vec![TargetCircUsage::Preemptive {
port: None,
circs: 2
}]
);
predictor.note_usage(Some(TargetPort::ipv4(1234)), Instant::now());
let results = predictor.predict();
assert_eq!(results.len(), 2);
assert!(results
.iter()
.any(|r| r.isol_eq(&TargetCircUsage::Preemptive {
port: None,
circs: 2
})));
assert!(results
.iter()
.any(|r| r.isol_eq(&TargetCircUsage::Preemptive {
port: Some(TargetPort::ipv4(1234)),
circs: 2
})));
}
#[test]
fn does_not_predict_old_ports() {
let mut cfg = PreemptiveCircuitConfig::builder();
cfg.set_initial_predicted_ports(vec![]);
cfg.prediction_lifetime(Duration::from_secs(2));
let mut predictor = PreemptiveCircuitPredictor::new(cfg.build().unwrap());
let now = Instant::now();
let three_seconds_ago = now - Duration::from_secs(2 + 1);
predictor.note_usage(Some(TargetPort::ipv4(2345)), three_seconds_ago);
assert_isoleq!(
predictor.predict(),
vec![TargetCircUsage::Preemptive {
port: None,
circs: 2
}]
);
}
}