use crate::usage::{SupportedCircUsage, TargetCircUsage}; use crate::{timeouts, DirInfo, Error, PathConfig, Result}; #[cfg(feature = "vanguards")] use tor_guardmgr::vanguards::VanguardMgr; use tor_guardmgr::{GuardMgr, TestConfig, VanguardConfig}; use tor_linkspec::CircTarget; use tor_persist::StateMgr; use tor_proto::circuit::{CircParameters, Path, UniqId}; use tor_rtcompat::Runtime; use async_trait::async_trait; use std::sync::{self, Arc}; use std::time::Duration; use crate::isolation::test::IsolationTokenEq; use crate::usage::ExitPolicy; use crate::{StreamIsolation, TargetPorts}; use std::sync::atomic::{self, AtomicUsize}; use tracing::trace; use super::mgr::{AbstractCirc, AbstractCircBuilder, MockablePlan}; #[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] pub(crate) struct FakeId { pub(crate) id: usize, } static NEXT_FAKE_ID: AtomicUsize = AtomicUsize::new(0); impl FakeId { pub(crate) fn next() -> Self { let id = NEXT_FAKE_ID.fetch_add(1, atomic::Ordering::SeqCst); FakeId { id } } } #[derive(Debug, PartialEq, Clone, Eq)] pub(crate) struct FakeCirc { pub(crate) id: FakeId, } #[async_trait] impl AbstractCirc for FakeCirc { type Id = FakeId; fn id(&self) -> FakeId { self.id } fn usable(&self) -> bool { true } fn path_ref(&self) -> Arc { todo!() } fn n_hops(&self) -> usize { todo!() } fn is_closing(&self) -> bool { todo!() } fn unique_id(&self) -> UniqId { todo!() } async fn extend_ntor( &self, _target: &T, _params: &CircParameters, ) -> tor_proto::Result<()> { todo!() } } #[derive(Debug, Clone)] pub(crate) struct FakePlan { spec: SupportedCircUsage, op: FakeOp, } pub(crate) struct FakeBuilder { runtime: RT, guardmgr: GuardMgr, #[cfg(feature = "vanguards")] vanguardmgr: Arc>, pub(crate) script: sync::Mutex>, } #[derive(Debug, Clone)] pub(crate) enum FakeOp { Succeed, Fail, Delay(Duration), Timeout, TimeoutReleaseAdvance(String), NoPlan, WrongSpec(SupportedCircUsage), } impl MockablePlan for FakePlan { fn add_blocked_advance_reason(&mut self, reason: String) { if let FakeOp::Timeout = self.op { self.op = FakeOp::TimeoutReleaseAdvance(reason); } } } const FAKE_CIRC_DELAY: Duration = Duration::from_millis(30); #[async_trait] impl AbstractCircBuilder for FakeBuilder { type Circ = FakeCirc; type Plan = FakePlan; fn plan_circuit( &self, spec: &TargetCircUsage, _dir: DirInfo<'_>, ) -> Result<(FakePlan, SupportedCircUsage)> { let next_op = self.next_op(spec); if matches!(next_op, FakeOp::NoPlan) { return Err(Error::NoRelay { path_kind: "example", role: "example", problem: "called with no plan".to_string(), }); } let supported_circ_usage = match spec { TargetCircUsage::Exit { ports, isolation, country_code, require_stability, } => SupportedCircUsage::Exit { policy: ExitPolicy::from_target_ports(&TargetPorts::from(&ports[..])), isolation: if isolation.isol_eq(&StreamIsolation::no_isolation()) { None } else { Some(isolation.clone()) }, country_code: *country_code, all_relays_stable: *require_stability, }, #[cfg(feature = "hs-common")] TargetCircUsage::HsCircBase { .. } => SupportedCircUsage::HsOnly, _ => unimplemented!(), }; let plan = FakePlan { spec: supported_circ_usage.clone(), op: next_op, }; Ok((plan, supported_circ_usage)) } async fn build_circuit(&self, plan: FakePlan) -> Result<(SupportedCircUsage, Arc)> { let op = plan.op; let sl = self.runtime.sleep(FAKE_CIRC_DELAY); self.runtime.allow_one_advance(FAKE_CIRC_DELAY); sl.await; match op { FakeOp::Succeed => Ok((plan.spec, Arc::new(FakeCirc { id: FakeId::next() }))), FakeOp::WrongSpec(s) => Ok((s, Arc::new(FakeCirc { id: FakeId::next() }))), FakeOp::Fail => Err(Error::CircTimeout(None)), FakeOp::Delay(d) => { let sl = self.runtime.sleep(d); self.runtime.allow_one_advance(d); sl.await; Err(Error::PendingCanceled) } FakeOp::Timeout => unreachable!(), // should be converted to the below FakeOp::TimeoutReleaseAdvance(reason) => { trace!("releasing advance to fake a timeout"); self.runtime.release_advance(reason); let () = futures::future::pending().await; unreachable!() } FakeOp::NoPlan => unreachable!(), } } fn learning_timeouts(&self) -> bool { false } fn save_state(&self) -> Result { // We don't actually store persistent state since this is a test, just pretend we do. Ok(true) } fn path_config(&self) -> Arc { todo!() } fn set_path_config(&self, _new_config: PathConfig) { todo!() } fn estimator(&self) -> &timeouts::Estimator { todo!() } #[cfg(feature = "vanguards")] fn vanguardmgr(&self) -> &Arc> { &self.vanguardmgr } fn upgrade_to_owned_state(&self) -> Result<()> { todo!() } fn reload_state(&self) -> Result<()> { todo!() } fn guardmgr(&self) -> &tor_guardmgr::GuardMgr { &self.guardmgr } fn update_network_parameters(&self, _p: &tor_netdir::params::NetParameters) { todo!() } } impl FakeBuilder { pub(crate) fn new(rt: &RT, state_mgr: S, guard_config: &TestConfig) -> Self where S: StateMgr + Send + Sync + 'static, { FakeBuilder { runtime: rt.clone(), guardmgr: GuardMgr::new(rt.clone(), state_mgr.clone(), guard_config) .expect("Create GuardMgr"), #[cfg(feature = "vanguards")] vanguardmgr: Arc::new( VanguardMgr::new(&VanguardConfig::default(), rt.clone(), state_mgr, false) .expect("Create VanguardMgr"), ), script: sync::Mutex::new(vec![]), } } /// set a plan for a given TargetCircUsage. pub(crate) fn set(&self, spec: &TargetCircUsage, v: I) where I: IntoIterator, { let mut ops: Vec<_> = v.into_iter().collect(); ops.reverse(); let mut lst = self.script.lock().expect("Couldn't get lock on script"); for op in ops { lst.push((spec.clone(), op)); } } fn next_op(&self, spec: &TargetCircUsage) -> FakeOp { let mut script = self.script.lock().expect("Couldn't get lock on script"); let idx = script .iter() .enumerate() .find_map(|(i, s)| spec.isol_eq(&s.0).then_some(i)); if let Some(i) = idx { let (_, op) = script.remove(i); op } else { FakeOp::Succeed } } }