use std::collections::HashMap;
use std::sync::Arc;
use crate::{
bridge::BridgeConfig,
sample::{Candidate, CandidateStatus, Universe, WeightThreshold},
};
use dyn_clone::DynClone;
use futures::stream::BoxStream;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum::{EnumCount, EnumIter};
use tor_error::{HasKind, HasRetryTime};
use tor_linkspec::{ChanTarget, HasChanMethod, HasRelayIds, OwnedChanTarget};
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
use tor_netdir::RelayWeight;
use tor_netdoc::doc::routerdesc::RouterDesc;
use web_time_compat::{SystemTime, SystemTimeExt};
use super::BridgeRelay;
#[derive(Clone, Debug)]
pub struct BridgeDesc {
desc: Arc<RouterDesc>,
}
impl AsRef<RouterDesc> for BridgeDesc {
fn as_ref(&self) -> &RouterDesc {
self.desc.as_ref()
}
}
impl BridgeDesc {
pub fn new(desc: Arc<RouterDesc>) -> Self {
Self { desc }
}
}
impl tor_linkspec::HasRelayIdsLegacy for BridgeDesc {
fn ed_identity(&self) -> &Ed25519Identity {
self.desc.ed_identity()
}
fn rsa_identity(&self) -> &RsaIdentity {
self.desc.rsa_identity()
}
}
pub trait BridgeDescProvider: DynClone + Send + Sync {
fn bridges(&self) -> Arc<BridgeDescList>;
fn events(&self) -> BoxStream<'static, BridgeDescEvent>;
fn set_bridges(&self, bridges: &[BridgeConfig]);
}
dyn_clone::clone_trait_object!(BridgeDescProvider);
#[derive(
Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount, IntoPrimitive, TryFromPrimitive,
)]
#[non_exhaustive]
#[repr(u16)]
pub enum BridgeDescEvent {
SomethingChanged,
}
pub trait BridgeDescError:
std::error::Error + DynClone + HasKind + HasRetryTime + Send + Sync + 'static
{
}
dyn_clone::clone_trait_object!(BridgeDescError);
pub type BridgeDescList = HashMap<BridgeConfig, Result<BridgeDesc, Box<dyn BridgeDescError>>>;
#[derive(Debug, Clone)]
pub(crate) struct BridgeSet {
config: Arc<[BridgeConfig]>,
descs: Option<Arc<BridgeDescList>>,
}
impl BridgeSet {
pub(crate) fn new(config: Arc<[BridgeConfig]>, descs: Option<Arc<BridgeDescList>>) -> Self {
Self { config, descs }
}
pub(crate) fn bridge_by_guard<T>(&self, guard: &T) -> Option<&BridgeConfig>
where
T: ChanTarget,
{
self.config.iter().find(|bridge| {
guard.has_all_relay_ids_from(*bridge)
&& bridge.chan_method().contained_by(&guard.chan_method())
})
}
fn relay_by_bridge<'a>(&'a self, bridge: &'a BridgeConfig) -> BridgeRelay<'a> {
let desc = match self.descs.as_ref().and_then(|d| d.get(bridge)) {
Some(Ok(b)) => Some(b.clone()),
_ => None,
};
BridgeRelay::new(bridge, desc)
}
pub(crate) fn bridge_relay_by_guard<T: tor_linkspec::ChanTarget>(
&self,
guard: &T,
) -> CandidateStatus<BridgeRelay> {
match self.bridge_by_guard(guard) {
Some(bridge) => {
let bridge_relay = self.relay_by_bridge(bridge);
if bridge_relay.has_all_relay_ids_from(guard) {
CandidateStatus::Present(bridge_relay)
} else if bridge_relay.has_descriptor() {
CandidateStatus::Absent
} else {
CandidateStatus::Uncertain
}
}
None => CandidateStatus::Absent,
}
}
}
impl Universe for BridgeSet {
fn contains<T: tor_linkspec::ChanTarget>(&self, guard: &T) -> Option<bool> {
match self.bridge_relay_by_guard(guard) {
CandidateStatus::Present(_) => Some(true),
CandidateStatus::Absent => Some(false),
CandidateStatus::Uncertain => None,
}
}
fn status<T: tor_linkspec::ChanTarget>(&self, guard: &T) -> CandidateStatus<Candidate> {
match self.bridge_relay_by_guard(guard) {
CandidateStatus::Present(bridge_relay) => CandidateStatus::Present(Candidate {
listed_as_guard: true,
is_dir_cache: true, full_dir_info: bridge_relay.has_descriptor(),
owned_target: OwnedChanTarget::from_chan_target(&bridge_relay),
sensitivity: crate::guard::DisplayRule::Redacted,
}),
CandidateStatus::Absent => CandidateStatus::Absent,
CandidateStatus::Uncertain => CandidateStatus::Uncertain,
}
}
fn timestamp(&self) -> SystemTime {
SystemTime::get()
}
fn weight_threshold<T>(
&self,
_sample: &tor_linkspec::ByRelayIds<T>,
_params: &crate::GuardParams,
) -> WeightThreshold
where
T: HasRelayIds,
{
WeightThreshold {
current_weight: RelayWeight::from(0),
maximum_weight: RelayWeight::from(u64::MAX),
}
}
fn sample<T>(
&self,
pre_existing: &tor_linkspec::ByRelayIds<T>,
filter: &crate::GuardFilter,
n: usize,
) -> Vec<(Candidate, tor_netdir::RelayWeight)>
where
T: HasRelayIds,
{
use rand::seq::IteratorRandom;
self.config
.iter()
.filter(|bridge_conf| {
filter.permits(*bridge_conf)
&& pre_existing.all_overlapping(*bridge_conf).is_empty()
})
.choose_multiple(&mut rand::rng(), n)
.into_iter()
.map(|bridge_config| {
let relay = self.relay_by_bridge(bridge_config);
(
Candidate {
listed_as_guard: true,
is_dir_cache: true,
full_dir_info: relay.has_descriptor(),
owned_target: OwnedChanTarget::from_chan_target(&relay),
sensitivity: crate::guard::DisplayRule::Redacted,
},
RelayWeight::from(0),
)
})
.collect()
}
}