Skip to main content

tor_netdir/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![allow(clippy::cognitive_complexity)] // See arti#2556
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_time_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] // See arti#2571
48//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49
50pub mod details;
51mod err;
52#[cfg(feature = "hs-common")]
53mod hsdir_params;
54#[cfg(feature = "hs-common")]
55mod hsdir_ring;
56pub mod params;
57mod weight;
58
59#[cfg(any(test, feature = "testing"))]
60pub mod testnet;
61#[cfg(feature = "testing")]
62pub mod testprovider;
63
64use async_trait::async_trait;
65#[cfg(feature = "hs-service")]
66use itertools::chain;
67use tor_error::warn_report;
68#[cfg(feature = "hs-common")]
69use tor_linkspec::OwnedCircTarget;
70use tor_linkspec::{
71    ChanTarget, DirectChanMethodsHelper, HasAddrs, HasRelayIds, RelayIdRef, RelayIdType,
72};
73use tor_llcrypto as ll;
74use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
75use tor_netdoc::doc::microdesc::{MdDigest, MicrodescAndHash};
76use tor_netdoc::doc::netstatus::{self, MdConsensus, MdRouterStatus};
77#[cfg(feature = "hs-common")]
78use {hsdir_ring::HsDirRing, std::iter};
79
80use derive_more::{From, Into};
81use futures::{StreamExt, stream::BoxStream};
82use num_enum::{IntoPrimitive, TryFromPrimitive};
83use rand::seq::{IndexedRandom as _, SliceRandom as _, WeightError};
84use serde::Deserialize;
85use std::collections::HashMap;
86use std::net::IpAddr;
87use std::ops::Deref;
88use std::sync::Arc;
89use std::time::SystemTime;
90use strum::{EnumCount, EnumIter};
91use tracing::warn;
92use typed_index_collections::{TiSlice, TiVec};
93
94#[cfg(feature = "hs-common")]
95use {
96    itertools::Itertools,
97    std::collections::HashSet,
98    std::result::Result as StdResult,
99    tor_error::{Bug, internal},
100    tor_hscrypto::{pk::HsBlindId, time::TimePeriod},
101    tor_linkspec::{OwnedChanTargetBuilder, verbatim::VerbatimLinkSpecCircTarget},
102    tor_llcrypto::pk::curve25519,
103};
104
105pub use err::Error;
106pub use weight::WeightRole;
107/// A Result using the Error type from the tor-netdir crate
108pub type Result<T> = std::result::Result<T, Error>;
109
110#[cfg(feature = "hs-common")]
111pub use err::{OnionDirLookupError, VerbatimCircTargetDecodeError};
112
113use params::NetParameters;
114#[cfg(feature = "geoip")]
115use tor_geoip::{CountryCode, GeoipDb, HasCountryCode};
116
117#[cfg(feature = "hs-common")]
118pub use hsdir_params::HsDirParams;
119
120/// Index into the consensus relays
121///
122/// This is an index into the list of relays returned by
123/// [`.c_relays()`](ConsensusRelays::c_relays)
124/// (on the corresponding consensus or netdir).
125///
126/// This is just a `usize` inside, but using a newtype prevents getting a relay index
127/// confused with other kinds of slice indices or counts.
128///
129/// If you are in a part of the code which needs to work with multiple consensuses,
130/// the typechecking cannot tell if you try to index into the wrong consensus.
131#[derive(Debug, From, Into, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
132pub(crate) struct RouterStatusIdx(usize);
133
134/// Extension trait to provide index-type-safe `.c_relays()` method
135//
136// TODO: Really it would be better to have MdConsensns::relays() return TiSlice,
137// but that would be an API break there.
138pub(crate) trait ConsensusRelays {
139    /// Obtain the list of relays in the consensus
140    //
141    fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdRouterStatus>;
142}
143impl ConsensusRelays for MdConsensus {
144    fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdRouterStatus> {
145        TiSlice::from_ref(MdConsensus::relays(self))
146    }
147}
148impl ConsensusRelays for NetDir {
149    fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdRouterStatus> {
150        self.consensus.c_relays()
151    }
152}
153
154/// Configuration for determining when two relays have addresses "too close" in
155/// the network.
156///
157/// Used by `Relay::low_level_details().in_same_subnet()`.
158#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
159#[serde(deny_unknown_fields)]
160pub struct SubnetConfig {
161    /// Consider IPv4 nodes in the same /x to be the same family.
162    ///
163    /// If this value is 0, all nodes with IPv4 addresses will be in the
164    /// same family.  If this value is above 32, then no nodes will be
165    /// placed im the same family based on their IPv4 addresses.
166    subnets_family_v4: u8,
167    /// Consider IPv6 nodes in the same /x to be the same family.
168    ///
169    /// If this value is 0, all nodes with IPv6 addresses will be in the
170    /// same family.  If this value is above 128, then no nodes will be
171    /// placed im the same family based on their IPv6 addresses.
172    subnets_family_v6: u8,
173}
174
175impl Default for SubnetConfig {
176    fn default() -> Self {
177        Self::new(16, 32)
178    }
179}
180
181impl SubnetConfig {
182    /// Construct a new SubnetConfig from a pair of bit prefix lengths.
183    ///
184    /// The values are clamped to the appropriate ranges if they are
185    /// out-of-bounds.
186    pub fn new(subnets_family_v4: u8, subnets_family_v6: u8) -> Self {
187        Self {
188            subnets_family_v4,
189            subnets_family_v6,
190        }
191    }
192
193    /// Construct a new SubnetConfig such that addresses are not in the same
194    /// family with anything--not even with themselves.
195    pub fn no_addresses_match() -> SubnetConfig {
196        SubnetConfig {
197            subnets_family_v4: 33,
198            subnets_family_v6: 129,
199        }
200    }
201
202    /// Return true if the two addresses in the same subnet, according to this
203    /// configuration.
204    pub fn addrs_in_same_subnet(&self, a: &IpAddr, b: &IpAddr) -> bool {
205        match (a, b) {
206            (IpAddr::V4(a), IpAddr::V4(b)) => {
207                let bits = self.subnets_family_v4;
208                if bits > 32 {
209                    return false;
210                }
211                let a = u32::from_be_bytes(a.octets());
212                let b = u32::from_be_bytes(b.octets());
213                (a >> (32 - bits)) == (b >> (32 - bits))
214            }
215            (IpAddr::V6(a), IpAddr::V6(b)) => {
216                let bits = self.subnets_family_v6;
217                if bits > 128 {
218                    return false;
219                }
220                let a = u128::from_be_bytes(a.octets());
221                let b = u128::from_be_bytes(b.octets());
222                (a >> (128 - bits)) == (b >> (128 - bits))
223            }
224            _ => false,
225        }
226    }
227
228    /// Return true if any of the addresses in `a` shares a subnet with any of
229    /// the addresses in `b`, according to this configuration.
230    pub fn any_addrs_in_same_subnet<T, U>(&self, a: &T, b: &U) -> bool
231    where
232        T: tor_linkspec::HasAddrs,
233        U: tor_linkspec::HasAddrs,
234    {
235        a.addrs().any(|aa| {
236            b.addrs()
237                .any(|bb| self.addrs_in_same_subnet(&aa.ip(), &bb.ip()))
238        })
239    }
240
241    /// Return a new subnet configuration that is the union of `self` and
242    /// `other`.
243    ///
244    /// That is, return a subnet configuration that puts all addresses in the
245    /// same subnet if and only if at least one of `self` and `other` would put
246    /// them in the same subnet.
247    pub fn union(&self, other: &Self) -> Self {
248        use std::cmp::min;
249        Self {
250            subnets_family_v4: min(self.subnets_family_v4, other.subnets_family_v4),
251            subnets_family_v6: min(self.subnets_family_v6, other.subnets_family_v6),
252        }
253    }
254}
255
256/// Configuration for which listed family information to use when deciding
257/// whether relays belong to the same family.
258///
259/// Derived from network parameters.
260#[derive(Clone, Copy, Debug)]
261pub struct FamilyRules {
262    /// If true, we use family information from lists of family members.
263    use_family_lists: bool,
264    /// If true, we use family information from lists of family IDs and from family certs.
265    use_family_ids: bool,
266}
267
268impl<'a> From<&'a NetParameters> for FamilyRules {
269    fn from(params: &'a NetParameters) -> Self {
270        FamilyRules {
271            use_family_lists: bool::from(params.use_family_lists),
272            use_family_ids: bool::from(params.use_family_ids),
273        }
274    }
275}
276
277impl FamilyRules {
278    /// Return a `FamilyRules` that will use all recognized kinds of family information.
279    pub fn all_family_info() -> Self {
280        Self {
281            use_family_lists: true,
282            use_family_ids: true,
283        }
284    }
285
286    /// Return a `FamilyRules` that will ignore all family information declared by relays.
287    pub fn ignore_declared_families() -> Self {
288        Self {
289            use_family_lists: false,
290            use_family_ids: false,
291        }
292    }
293
294    /// Configure this `FamilyRules` to use (or not use) family information from
295    /// lists of family members.
296    pub fn use_family_lists(&mut self, val: bool) -> &mut Self {
297        self.use_family_lists = val;
298        self
299    }
300
301    /// Configure this `FamilyRules` to use (or not use) family information from
302    /// family IDs and family certs.
303    pub fn use_family_ids(&mut self, val: bool) -> &mut Self {
304        self.use_family_ids = val;
305        self
306    }
307
308    /// Return a `FamilyRules` that will look at every source of information
309    /// requested by `self` or by `other`.
310    pub fn union(&self, other: &Self) -> Self {
311        Self {
312            use_family_lists: self.use_family_lists || other.use_family_lists,
313            use_family_ids: self.use_family_ids || other.use_family_ids,
314        }
315    }
316}
317
318/// An opaque type representing the weight with which a relay or set of
319/// relays will be selected for a given role.
320///
321/// Most users should ignore this type, and just use pick_relay instead.
322#[derive(
323    Copy,
324    Clone,
325    Debug,
326    derive_more::Add,
327    derive_more::Sum,
328    derive_more::AddAssign,
329    Eq,
330    PartialEq,
331    Ord,
332    PartialOrd,
333)]
334pub struct RelayWeight(u64);
335
336impl RelayWeight {
337    /// Try to divide this weight by `rhs`.
338    ///
339    /// Return a ratio on success, or None on division-by-zero.
340    pub fn checked_div(&self, rhs: RelayWeight) -> Option<f64> {
341        if rhs.0 == 0 {
342            None
343        } else {
344            Some((self.0 as f64) / (rhs.0 as f64))
345        }
346    }
347
348    /// Compute a ratio `frac` of this weight.
349    ///
350    /// Return None if frac is less than zero, since negative weights
351    /// are impossible.
352    pub fn ratio(&self, frac: f64) -> Option<RelayWeight> {
353        let product = (self.0 as f64) * frac;
354        if product >= 0.0 && product.is_finite() {
355            Some(RelayWeight(product as u64))
356        } else {
357            None
358        }
359    }
360}
361
362impl From<u64> for RelayWeight {
363    fn from(val: u64) -> Self {
364        RelayWeight(val)
365    }
366}
367
368/// An operation for which we might be requesting a hidden service directory.
369#[derive(Copy, Clone, Debug, PartialEq)]
370// TODO: make this pub(crate) once NetDir::hs_dirs is removed
371#[non_exhaustive]
372pub enum HsDirOp {
373    /// Uploading an onion service descriptor.
374    #[cfg(feature = "hs-service")]
375    Upload,
376    /// Downloading an onion service descriptor.
377    Download,
378}
379
380/// A view of the Tor directory, suitable for use in building circuits.
381///
382/// Abstractly, a [`NetDir`] is a set of usable public [`Relay`]s, each of which
383/// has its own properties, identity, and correct weighted probability for use
384/// under different circumstances.
385///
386/// A [`NetDir`] is constructed by making a [`PartialNetDir`] from a consensus
387/// document, and then adding enough microdescriptors to that `PartialNetDir` so
388/// that it can be used to build paths. (Thus, if you have a NetDir, it is
389/// definitely adequate to build paths.)
390///
391/// # "Usable" relays
392///
393/// Many methods on NetDir are defined in terms of <a name="usable">"Usable"</a> relays.  Unless
394/// otherwise stated, a relay is "usable" if it is listed in the consensus,
395/// if we have full directory information for that relay (including a
396/// microdescriptor), and if that relay does not have any flags indicating that
397/// we should never use it. (Currently, `NoEdConsensus` is the only such flag.)
398///
399/// # Limitations
400///
401/// The current NetDir implementation assumes fairly strongly that every relay
402/// has an Ed25519 identity and an RSA identity, that the consensus is indexed
403/// by RSA identities, and that the Ed25519 identities are stored in
404/// microdescriptors.
405///
406/// If these assumptions someday change, then we'll have to revise the
407/// implementation.
408#[derive(Debug, Clone)]
409pub struct NetDir {
410    /// A microdescriptor consensus that lists the members of the network,
411    /// and maps each one to a 'microdescriptor' that has more information
412    /// about it
413    consensus: Arc<MdConsensus>,
414    /// A map from keys to integer values, distributed in the consensus,
415    /// and clamped to certain defaults.
416    params: NetParameters,
417    /// Map from routerstatus index, to that routerstatus's microdescriptor (if we have one.)
418    mds: TiVec<RouterStatusIdx, Option<Arc<MicrodescAndHash>>>,
419    /// Map from SHA256 of _missing_ microdescriptors to the index of their
420    /// corresponding routerstatus.
421    rsidx_by_missing: HashMap<MdDigest, RouterStatusIdx>,
422    /// Map from ed25519 identity to index of the routerstatus.
423    ///
424    /// Note that we don't know the ed25519 identity of a relay until
425    /// we get the microdescriptor for it, so this won't be filled in
426    /// until we get the microdescriptors.
427    ///
428    /// # Implementation note
429    ///
430    /// For this field, and for `rsidx_by_rsa`,
431    /// it might be cool to have references instead.
432    /// But that would make this into a self-referential structure,
433    /// which isn't possible in safe rust.
434    rsidx_by_ed: HashMap<Ed25519Identity, RouterStatusIdx>,
435    /// Map from RSA identity to index of the routerstatus.
436    ///
437    /// This is constructed at the same time as the NetDir object, so it
438    /// can be immutable.
439    rsidx_by_rsa: Arc<HashMap<RsaIdentity, RouterStatusIdx>>,
440
441    /// Hash ring(s) describing the onion service directory.
442    ///
443    /// This is empty in a PartialNetDir, and is filled in before the NetDir is
444    /// built.
445    //
446    // TODO hs: It is ugly to have this exist in a partially constructed state
447    // in a PartialNetDir.
448    // Ideally, a PartialNetDir would contain only an HsDirs<HsDirParams>,
449    // or perhaps nothing at all, here.
450    #[cfg(feature = "hs-common")]
451    hsdir_rings: Arc<HsDirs<HsDirRing>>,
452
453    /// Weight values to apply to a given relay when deciding how frequently
454    /// to choose it for a given role.
455    weights: weight::WeightSet,
456
457    #[cfg(feature = "geoip")]
458    /// Country codes for each router in our consensus.
459    ///
460    /// This is indexed by the `RouterStatusIdx` (i.e. a router idx of zero has
461    /// the country code at position zero in this array).
462    country_codes: Vec<Option<CountryCode>>,
463}
464
465/// Collection of hidden service directories (or parameters for them)
466///
467/// In [`NetDir`] this is used to store the actual hash rings.
468/// (But, in a NetDir in a [`PartialNetDir`], it contains [`HsDirRing`]s
469/// where only the `params` are populated, and the `ring` is empty.)
470///
471/// This same generic type is used as the return type from
472/// [`HsDirParams::compute`](HsDirParams::compute),
473/// where it contains the *parameters* for the primary and secondary rings.
474#[derive(Debug, Clone)]
475#[cfg(feature = "hs-common")]
476pub(crate) struct HsDirs<D> {
477    /// The current ring
478    ///
479    /// It corresponds to the time period containing the `valid-after` time in
480    /// the consensus. Its SRV is whatever SRV was most current at the time when
481    /// that time period began.
482    ///
483    /// This is the hash ring that we should use whenever we are fetching an
484    /// onion service descriptor.
485    current: D,
486
487    /// Secondary rings (based on the parameters for the previous and next time periods)
488    ///
489    /// Onion services upload to positions on these ring as well, based on how
490    /// far into the current time period this directory is, so that
491    /// not-synchronized clients can still find their descriptor.
492    ///
493    /// Note that with the current (2023) network parameters, with
494    /// `hsdir_interval = SRV lifetime = 24 hours` at most one of these
495    /// secondary rings will be active at a time.  We have two here in order
496    /// to conform with a more flexible regime in proposal 342.
497    //
498    // TODO: hs clients never need this; so I've made it not-present for them.
499    // But does that risk too much with respect to side channels?
500    //
501    // TODO: Perhaps we should refactor this so that it is clear that these
502    // are immutable?  On the other hand, the documentation for this type
503    // declares that it is immutable, so we are likely okay.
504    //
505    // TODO: this `Vec` is only ever 0,1,2 elements.
506    // Maybe it should be an ArrayVec or something.
507    #[cfg(feature = "hs-service")]
508    secondary: Vec<D>,
509}
510
511#[cfg(feature = "hs-common")]
512impl<D> HsDirs<D> {
513    /// Convert an `HsDirs<D>` to `HsDirs<D2>` by mapping each contained `D`
514    pub(crate) fn map<D2>(self, mut f: impl FnMut(D) -> D2) -> HsDirs<D2> {
515        HsDirs {
516            current: f(self.current),
517            #[cfg(feature = "hs-service")]
518            secondary: self.secondary.into_iter().map(f).collect(),
519        }
520    }
521
522    /// Iterate over some of the contained hsdirs, according to `secondary`
523    ///
524    /// The current ring is always included.
525    /// Secondary rings are included iff `secondary` and the `hs-service` feature is enabled.
526    fn iter_filter_secondary(&self, secondary: bool) -> impl Iterator<Item = &D> {
527        let i = iter::once(&self.current);
528
529        // With "hs-service" disabled, there are no secondary rings,
530        // so we don't care.
531        let _ = secondary;
532
533        #[cfg(feature = "hs-service")]
534        let i = chain!(i, self.secondary.iter().filter(move |_| secondary));
535
536        i
537    }
538
539    /// Iterate over all the contained hsdirs
540    pub(crate) fn iter(&self) -> impl Iterator<Item = &D> {
541        self.iter_filter_secondary(true)
542    }
543
544    /// Iterate over the hsdirs relevant for `op`
545    pub(crate) fn iter_for_op(&self, op: HsDirOp) -> impl Iterator<Item = &D> {
546        self.iter_filter_secondary(match op {
547            #[cfg(feature = "hs-service")]
548            HsDirOp::Upload => true,
549            HsDirOp::Download => false,
550        })
551    }
552}
553
554/// An event that a [`NetDirProvider`] can broadcast to indicate that a change in
555/// the status of its directory.
556#[derive(
557    Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount, IntoPrimitive, TryFromPrimitive,
558)]
559#[non_exhaustive]
560#[repr(u16)]
561pub enum DirEvent {
562    /// A new consensus has been received, and has enough information to be
563    /// used.
564    ///
565    /// This event is also broadcast when a new set of consensus parameters is
566    /// available, even if that set of parameters comes from a configuration
567    /// change rather than from the latest consensus.
568    NewConsensus,
569
570    /// New descriptors have been received for the current consensus.
571    ///
572    /// (This event is _not_ broadcast when receiving new descriptors for a
573    /// consensus which is not yet ready to replace the current consensus.)
574    NewDescriptors,
575
576    /// We have received updated recommendations and requirements
577    /// for which subprotocols we should have to use the network.
578    NewProtocolRecommendation,
579}
580
581/// The network directory provider is shutting down without giving us the
582/// netdir we asked for.
583#[derive(Clone, Copy, Debug, thiserror::Error)]
584#[error("Network directory provider is shutting down")]
585#[non_exhaustive]
586pub struct NetdirProviderShutdown;
587
588impl tor_error::HasKind for NetdirProviderShutdown {
589    fn kind(&self) -> tor_error::ErrorKind {
590        tor_error::ErrorKind::ArtiShuttingDown
591    }
592}
593
594/// How "timely" must a network directory be?
595///
596/// This enum is used as an argument when requesting a [`NetDir`] object from
597/// [`NetDirProvider`] and other APIs, to specify how recent the information
598/// must be in order to be useful.
599#[derive(Copy, Clone, Eq, PartialEq, Debug)]
600#[allow(clippy::exhaustive_enums)]
601pub enum Timeliness {
602    /// The network directory must be strictly timely.
603    ///
604    /// That is, it must be based on a consensus that valid right now, with no
605    /// tolerance for skew or consensus problems.
606    ///
607    /// Avoid using this option if you could use [`Timeliness::Timely`] instead.
608    Strict,
609    /// The network directory must be roughly timely.
610    ///
611    /// This is, it must be based on a consensus that is not _too_ far in the
612    /// future, and not _too_ far in the past.
613    ///
614    /// (The tolerances for "too far" will depend on configuration.)
615    ///
616    /// This is almost always the option that you want to use.
617    Timely,
618    /// Any network directory is permissible, regardless of how untimely.
619    ///
620    /// Avoid using this option if you could use [`Timeliness::Timely`] instead.
621    Unchecked,
622}
623
624/// An object that can provide [`NetDir`]s, as well as inform consumers when
625/// they might have changed.
626///
627/// It is the responsibility of the implementor of `NetDirProvider`
628/// to try to obtain an up-to-date `NetDir`,
629/// and continuously to maintain and update it.
630///
631/// In usual configurations, Arti uses `tor_dirmgr::DirMgr`
632/// as its `NetDirProvider`.
633#[async_trait]
634pub trait NetDirProvider: UpcastArcNetDirProvider + Send + Sync {
635    /// Return a network directory that's live according to the provided
636    /// `timeliness`.
637    fn netdir(&self, timeliness: Timeliness) -> Result<Arc<NetDir>>;
638
639    /// Return a reasonable netdir for general usage.
640    ///
641    /// This is an alias for
642    /// [`NetDirProvider::netdir`]`(`[`Timeliness::Timely`]`)`.
643    fn timely_netdir(&self) -> Result<Arc<NetDir>> {
644        self.netdir(Timeliness::Timely)
645    }
646
647    /// Return a new asynchronous stream that will receive notification
648    /// whenever the consensus has changed.
649    ///
650    /// Multiple events may be batched up into a single item: each time
651    /// this stream yields an event, all you can assume is that the event has
652    /// occurred at least once.
653    fn events(&self) -> BoxStream<'static, DirEvent>;
654
655    /// Return the latest network parameters.
656    ///
657    /// If we have no directory, return a reasonable set of defaults.
658    fn params(&self) -> Arc<dyn AsRef<NetParameters>>;
659
660    /// Get a NetDir from `provider`, waiting until one exists.
661    async fn wait_for_netdir(
662        &self,
663        timeliness: Timeliness,
664    ) -> std::result::Result<Arc<NetDir>, NetdirProviderShutdown> {
665        if let Ok(nd) = self.netdir(timeliness) {
666            return Ok(nd);
667        }
668
669        let mut stream = self.events();
670        loop {
671            // We need to retry `self.netdir()` before waiting for any stream events, to
672            // avoid deadlock.
673            //
674            // We ignore all errors here: they can all potentially be fixed by
675            // getting a fresh consensus, and they will all get warned about
676            // by the NetDirProvider itself.
677            if let Ok(nd) = self.netdir(timeliness) {
678                return Ok(nd);
679            }
680            match stream.next().await {
681                Some(_) => {}
682                None => {
683                    return Err(NetdirProviderShutdown);
684                }
685            }
686        }
687    }
688
689    /// Wait until `provider` lists `target`.
690    ///
691    /// NOTE: This might potentially wait indefinitely, if `target` is never actually
692    /// becomes listed in the directory.  It will exit if the `NetDirProvider` shuts down.
693    async fn wait_for_netdir_to_list(
694        &self,
695        target: &tor_linkspec::RelayIds,
696        timeliness: Timeliness,
697    ) -> std::result::Result<(), NetdirProviderShutdown> {
698        let mut events = self.events();
699        loop {
700            // See if the desired relay is in the netdir.
701            //
702            // We do this before waiting for any events, to avoid race conditions.
703            {
704                let netdir = self.wait_for_netdir(timeliness).await?;
705                if netdir.ids_listed(target) == Some(true) {
706                    return Ok(());
707                }
708                // If we reach this point, then ids_listed returned `Some(false)`,
709                // meaning "This relay is definitely not in the current directory";
710                // or it returned `None`, meaning "waiting for more information
711                // about this network directory.
712                // In both cases, it's reasonable to just wait for another netdir
713                // event and try again.
714            }
715            // We didn't find the relay; wait for the provider to have a new netdir
716            // or more netdir information.
717            if events.next().await.is_none() {
718                // The event stream is closed; the provider has shut down.
719                return Err(NetdirProviderShutdown);
720            }
721        }
722    }
723
724    /// Return the latest set of recommended and required protocols, if there is one.
725    ///
726    /// This may be more recent (or more available) than this provider's associated NetDir.
727    fn protocol_statuses(&self) -> Option<(SystemTime, Arc<netstatus::ProtoStatuses>)>;
728}
729
730#[async_trait]
731impl<T> NetDirProvider for Arc<T>
732where
733    T: NetDirProvider,
734{
735    fn netdir(&self, timeliness: Timeliness) -> Result<Arc<NetDir>> {
736        self.deref().netdir(timeliness)
737    }
738
739    fn timely_netdir(&self) -> Result<Arc<NetDir>> {
740        self.deref().timely_netdir()
741    }
742
743    fn events(&self) -> BoxStream<'static, DirEvent> {
744        self.deref().events()
745    }
746
747    fn params(&self) -> Arc<dyn AsRef<NetParameters>> {
748        self.deref().params()
749    }
750
751    async fn wait_for_netdir(
752        &self,
753        timeliness: Timeliness,
754    ) -> std::result::Result<Arc<NetDir>, NetdirProviderShutdown> {
755        self.deref().wait_for_netdir(timeliness).await
756    }
757
758    async fn wait_for_netdir_to_list(
759        &self,
760        target: &tor_linkspec::RelayIds,
761        timeliness: Timeliness,
762    ) -> std::result::Result<(), NetdirProviderShutdown> {
763        self.deref()
764            .wait_for_netdir_to_list(target, timeliness)
765            .await
766    }
767
768    fn protocol_statuses(&self) -> Option<(SystemTime, Arc<netstatus::ProtoStatuses>)> {
769        self.deref().protocol_statuses()
770    }
771}
772
773/// Helper trait: allows any `Arc<X>` to be upcast to a `Arc<dyn
774/// NetDirProvider>` if X is an implementation or supertrait of NetDirProvider.
775///
776/// This trait exists to work around a limitation in rust: when trait upcasting
777/// coercion is stable, this will be unnecessary.
778///
779/// The Rust tracking issue is <https://github.com/rust-lang/rust/issues/65991>.
780pub trait UpcastArcNetDirProvider {
781    /// Return a view of this object as an `Arc<dyn NetDirProvider>`
782    fn upcast_arc<'a>(self: Arc<Self>) -> Arc<dyn NetDirProvider + 'a>
783    where
784        Self: 'a;
785}
786
787impl<T> UpcastArcNetDirProvider for T
788where
789    T: NetDirProvider + Sized,
790{
791    fn upcast_arc<'a>(self: Arc<Self>) -> Arc<dyn NetDirProvider + 'a>
792    where
793        Self: 'a,
794    {
795        self
796    }
797}
798
799impl AsRef<NetParameters> for NetDir {
800    fn as_ref(&self) -> &NetParameters {
801        self.params()
802    }
803}
804
805/// A partially build NetDir -- it can't be unwrapped until it has
806/// enough information to build safe paths.
807#[derive(Debug, Clone)]
808pub struct PartialNetDir {
809    /// The netdir that's under construction.
810    netdir: NetDir,
811
812    /// The previous netdir, if we had one
813    ///
814    /// Used as a cache, so we can reuse information
815    #[cfg(feature = "hs-common")]
816    prev_netdir: Option<Arc<NetDir>>,
817}
818
819/// A view of a relay on the Tor network, suitable for building circuits.
820// TODO: This should probably be a more specific struct, with a trait
821// that implements it.
822#[derive(Clone)]
823pub struct Relay<'a> {
824    /// A router descriptor for this relay.
825    rs: &'a netstatus::MdRouterStatus,
826    /// A microdescriptor for this relay.
827    md: &'a MicrodescAndHash,
828    /// The country code this relay is in, if we know one.
829    #[cfg(feature = "geoip")]
830    cc: Option<CountryCode>,
831}
832
833/// A relay that we haven't checked for validity or usability in
834/// routing.
835#[derive(Debug)]
836pub struct UncheckedRelay<'a> {
837    /// A router descriptor for this relay.
838    rs: &'a netstatus::MdRouterStatus,
839    /// A microdescriptor for this relay, if there is one.
840    md: Option<&'a MicrodescAndHash>,
841    /// The country code this relay is in, if we know one.
842    #[cfg(feature = "geoip")]
843    cc: Option<CountryCode>,
844}
845
846/// A partial or full network directory that we can download
847/// microdescriptors for.
848pub trait MdReceiver {
849    /// Return an iterator over the digests for all of the microdescriptors
850    /// that this netdir is missing.
851    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_>;
852    /// Add a microdescriptor to this netdir, if it was wanted.
853    ///
854    /// Return true if it was indeed wanted.
855    fn add_microdesc(&mut self, md: MicrodescAndHash) -> bool;
856    /// Return the number of missing microdescriptors.
857    fn n_missing(&self) -> usize;
858}
859
860impl PartialNetDir {
861    /// Create a new PartialNetDir with a given consensus, and no
862    /// microdescriptors loaded.
863    ///
864    /// If `replacement_params` is provided, override network parameters from
865    /// the consensus with those from `replacement_params`.
866    pub fn new(
867        consensus: MdConsensus,
868        replacement_params: Option<&netstatus::NetParams<i32>>,
869    ) -> Self {
870        Self::new_inner(
871            consensus,
872            replacement_params,
873            #[cfg(feature = "geoip")]
874            None,
875        )
876    }
877
878    /// Create a new PartialNetDir with GeoIP support.
879    ///
880    /// This does the same thing as `new()`, except the provided GeoIP database is used to add
881    /// country codes to relays.
882    #[cfg(feature = "geoip")]
883    pub fn new_with_geoip(
884        consensus: MdConsensus,
885        replacement_params: Option<&netstatus::NetParams<i32>>,
886        geoip_db: &GeoipDb,
887    ) -> Self {
888        Self::new_inner(consensus, replacement_params, Some(geoip_db))
889    }
890
891    /// Implementation of the `new()` functions.
892    fn new_inner(
893        consensus: MdConsensus,
894        replacement_params: Option<&netstatus::NetParams<i32>>,
895        #[cfg(feature = "geoip")] geoip_db: Option<&GeoipDb>,
896    ) -> Self {
897        let mut params = NetParameters::default();
898
899        // (We ignore unrecognized options here, since they come from
900        // the consensus, and we don't expect to recognize everything
901        // there.)
902        let _ = params.saturating_update(consensus.params().iter());
903
904        // Now see if the user has any parameters to override.
905        // (We have to do this now, or else changes won't be reflected in our
906        // weights.)
907        if let Some(replacement) = replacement_params {
908            for u in params.saturating_update(replacement.iter()) {
909                warn!("Unrecognized option: override_net_params.{}", u);
910            }
911        }
912
913        // Compute the weights we'll want to use for these relays.
914        let weights = weight::WeightSet::from_consensus(&consensus, &params);
915
916        let n_relays = consensus.c_relays().len();
917
918        let rsidx_by_missing = consensus
919            .c_relays()
920            .iter_enumerated()
921            .map(|(rsidx, rs)| (*rs.md_digest(), rsidx))
922            .collect();
923
924        let rsidx_by_rsa = consensus
925            .c_relays()
926            .iter_enumerated()
927            .map(|(rsidx, rs)| (*rs.rsa_identity(), rsidx))
928            .collect();
929
930        #[cfg(feature = "geoip")]
931        let country_codes = if let Some(db) = geoip_db {
932            consensus
933                .c_relays()
934                .iter()
935                .map(|rs| {
936                    db.lookup_country_code_multi(rs.addrs().map(|x| x.ip()))
937                        .cloned()
938                })
939                .collect()
940        } else {
941            Default::default()
942        };
943
944        #[cfg(feature = "hs-common")]
945        let hsdir_rings = Arc::new({
946            let params = HsDirParams::compute(&consensus, &params).expect("Invalid consensus!");
947            // TODO: It's a bit ugly to use expect above, but this function does
948            // not return a Result. On the other hand, the error conditions under which
949            // HsDirParams::compute can return Err are _very_ narrow and hard to
950            // hit; see documentation in that function.  As such, we probably
951            // don't need to have this return a Result.
952
953            params.map(HsDirRing::empty_from_params)
954        });
955
956        let netdir = NetDir {
957            consensus: Arc::new(consensus),
958            params,
959            mds: vec![None; n_relays].into(),
960            rsidx_by_missing,
961            rsidx_by_rsa: Arc::new(rsidx_by_rsa),
962            rsidx_by_ed: HashMap::with_capacity(n_relays),
963            #[cfg(feature = "hs-common")]
964            hsdir_rings,
965            weights,
966            #[cfg(feature = "geoip")]
967            country_codes,
968        };
969
970        PartialNetDir {
971            netdir,
972            #[cfg(feature = "hs-common")]
973            prev_netdir: None,
974        }
975    }
976
977    /// Return the declared lifetime of this PartialNetDir.
978    pub fn lifetime(&self) -> &netstatus::Lifetime {
979        self.netdir.lifetime()
980    }
981
982    /// Record a previous netdir, which can be used for reusing cached information
983    //
984    // Fills in as many missing microdescriptors as possible in this
985    // netdir, using the microdescriptors from the previous netdir.
986    //
987    // With HS enabled, stores the netdir for reuse of relay hash ring index values.
988    #[allow(clippy::needless_pass_by_value)] // prev might, or might not, be stored
989    pub fn fill_from_previous_netdir(&mut self, prev: Arc<NetDir>) {
990        for md in prev.mds.iter().flatten() {
991            self.netdir.add_arc_microdesc(md.clone());
992        }
993
994        #[cfg(feature = "hs-common")]
995        {
996            self.prev_netdir = Some(prev);
997        }
998    }
999
1000    /// Compute the hash ring(s) for this NetDir
1001    #[cfg(feature = "hs-common")]
1002    fn compute_rings(&mut self) {
1003        let params = HsDirParams::compute(&self.netdir.consensus, &self.netdir.params)
1004            .expect("Invalid consensus");
1005        // TODO: see TODO by similar expect in new()
1006
1007        self.netdir.hsdir_rings =
1008            Arc::new(params.map(|params| {
1009                HsDirRing::compute(params, &self.netdir, self.prev_netdir.as_deref())
1010            }));
1011    }
1012
1013    /// Return true if this are enough information in this directory
1014    /// to build multihop paths.
1015    pub fn have_enough_paths(&self) -> bool {
1016        self.netdir.have_enough_paths()
1017    }
1018    /// If this directory has enough information to build multihop
1019    /// circuits, return it.
1020    pub fn unwrap_if_sufficient(
1021        #[allow(unused_mut)] mut self,
1022    ) -> std::result::Result<NetDir, PartialNetDir> {
1023        if self.netdir.have_enough_paths() {
1024            #[cfg(feature = "hs-common")]
1025            self.compute_rings();
1026            Ok(self.netdir)
1027        } else {
1028            Err(self)
1029        }
1030    }
1031}
1032
1033impl MdReceiver for PartialNetDir {
1034    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
1035        self.netdir.missing_microdescs()
1036    }
1037    fn add_microdesc(&mut self, md: MicrodescAndHash) -> bool {
1038        self.netdir.add_microdesc(md)
1039    }
1040    fn n_missing(&self) -> usize {
1041        self.netdir.n_missing()
1042    }
1043}
1044
1045impl NetDir {
1046    /// Return the declared lifetime of this NetDir.
1047    pub fn lifetime(&self) -> &netstatus::Lifetime {
1048        self.consensus.lifetime()
1049    }
1050
1051    /// Add `md` to this NetDir.
1052    ///
1053    /// Return true if we wanted it, and false otherwise.
1054    fn add_arc_microdesc(&mut self, md: Arc<MicrodescAndHash>) -> bool {
1055        if let Some(rsidx) = self.rsidx_by_missing.remove(md.digest()) {
1056            assert_eq!(self.c_relays()[rsidx].md_digest(), md.digest());
1057
1058            // There should never be two approved MDs in the same
1059            // consensus listing the same ID... but if there is,
1060            // we'll let the most recent one win.
1061            self.rsidx_by_ed.insert(*md.ed25519_id(), rsidx);
1062
1063            // Happy path: we did indeed want this one.
1064            self.mds[rsidx] = Some(md);
1065
1066            // Save some space in the missing-descriptor list.
1067            if self.rsidx_by_missing.len() < self.rsidx_by_missing.capacity() / 4 {
1068                self.rsidx_by_missing.shrink_to_fit();
1069            }
1070
1071            return true;
1072        }
1073
1074        // Either we already had it, or we never wanted it at all.
1075        false
1076    }
1077
1078    /// Construct a (possibly invalid) Relay object from a routerstatus and its
1079    /// index within the consensus.
1080    fn relay_from_rs_and_rsidx<'a>(
1081        &'a self,
1082        rs: &'a netstatus::MdRouterStatus,
1083        rsidx: RouterStatusIdx,
1084    ) -> UncheckedRelay<'a> {
1085        debug_assert_eq!(self.c_relays()[rsidx].rsa_identity(), rs.rsa_identity());
1086        let md = self.mds[rsidx].as_deref();
1087        if let Some(md) = md {
1088            debug_assert_eq!(rs.md_digest(), md.digest());
1089        }
1090
1091        UncheckedRelay {
1092            rs,
1093            md,
1094            #[cfg(feature = "geoip")]
1095            cc: self.country_codes.get(rsidx.0).copied().flatten(),
1096        }
1097    }
1098
1099    /// Return the value of the hsdir_n_replicas param.
1100    #[cfg(feature = "hs-common")]
1101    fn n_replicas(&self) -> u8 {
1102        self.params
1103            .hsdir_n_replicas
1104            .get()
1105            .try_into()
1106            .expect("BoundedInt did not enforce bounds")
1107    }
1108
1109    /// Return the spread parameter for the specified `op`.
1110    #[cfg(feature = "hs-common")]
1111    fn spread(&self, op: HsDirOp) -> usize {
1112        let spread = match op {
1113            HsDirOp::Download => self.params.hsdir_spread_fetch,
1114            #[cfg(feature = "hs-service")]
1115            HsDirOp::Upload => self.params.hsdir_spread_store,
1116        };
1117
1118        spread
1119            .get()
1120            .try_into()
1121            .expect("BoundedInt did not enforce bounds!")
1122    }
1123
1124    /// Select `spread` hsdir relays for the specified `hsid` from a given `ring`.
1125    ///
1126    /// Algorithm:
1127    ///
1128    /// for idx in 1..=n_replicas:
1129    ///       - let H = hsdir_ring::onion_service_index(id, replica, rand,
1130    ///         period).
1131    ///       - Find the position of H within hsdir_ring.
1132    ///       - Take elements from hsdir_ring starting at that position,
1133    ///         adding them to Dirs until we have added `spread` new elements
1134    ///         that were not there before.
1135    #[cfg(feature = "hs-common")]
1136    fn select_hsdirs<'h, 'r: 'h>(
1137        &'r self,
1138        hsid: HsBlindId,
1139        ring: &'h HsDirRing,
1140        spread: usize,
1141    ) -> impl Iterator<Item = Relay<'r>> + 'h {
1142        let n_replicas = self.n_replicas();
1143
1144        (1..=n_replicas) // 1-indexed !
1145            .flat_map({
1146                let mut selected_nodes = HashSet::new();
1147
1148                move |replica: u8| {
1149                    let hsdir_idx = hsdir_ring::service_hsdir_index(&hsid, replica, ring.params());
1150
1151                    ring.ring_items_at(hsdir_idx, spread, |(hsdir_idx, _)| {
1152                        // According to rend-spec 2.2.3:
1153                        //                                                  ... If any of those
1154                        // nodes have already been selected for a lower-numbered replica of the
1155                        // service, any nodes already chosen are disregarded (i.e. skipped over)
1156                        // when choosing a replica's hsdir_spread_store nodes.
1157                        selected_nodes.insert(*hsdir_idx)
1158                    })
1159                    .collect::<Vec<_>>()
1160                }
1161            })
1162            .filter_map(move |(_hsdir_idx, rs_idx)| {
1163                // This ought not to be None but let's not panic or bail if it is
1164                self.relay_by_rs_idx(*rs_idx)
1165            })
1166    }
1167
1168    /// Replace the overridden parameters in this netdir with `new_replacement`.
1169    ///
1170    /// After this function is done, the netdir's parameters will be those in
1171    /// the consensus, overridden by settings from `new_replacement`.  Any
1172    /// settings in the old replacement parameters will be discarded.
1173    pub fn replace_overridden_parameters(&mut self, new_replacement: &netstatus::NetParams<i32>) {
1174        // TODO(nickm): This is largely duplicate code from PartialNetDir::new().
1175        let mut new_params = NetParameters::default();
1176        let _ = new_params.saturating_update(self.consensus.params().iter());
1177        for u in new_params.saturating_update(new_replacement.iter()) {
1178            warn!("Unrecognized option: override_net_params.{}", u);
1179        }
1180
1181        self.params = new_params;
1182    }
1183
1184    /// Return an iterator over all Relay objects, including invalid ones
1185    /// that we can't use.
1186    pub fn all_relays(&self) -> impl Iterator<Item = UncheckedRelay<'_>> {
1187        // TODO: I'd like if we could memoize this so we don't have to
1188        // do so many hashtable lookups.
1189        self.c_relays()
1190            .iter_enumerated()
1191            .map(move |(rsidx, rs)| self.relay_from_rs_and_rsidx(rs, rsidx))
1192    }
1193    /// Return an iterator over all [usable](NetDir#usable) Relays.
1194    pub fn relays(&self) -> impl Iterator<Item = Relay<'_>> {
1195        self.all_relays().filter_map(UncheckedRelay::into_relay)
1196    }
1197
1198    /// Look up a relay's [`MicrodescAndHash`] by its [`RouterStatusIdx`]
1199    #[cfg_attr(not(feature = "hs-common"), allow(dead_code))]
1200    pub(crate) fn md_by_rsidx(&self, rsidx: RouterStatusIdx) -> Option<&MicrodescAndHash> {
1201        self.mds.get(rsidx)?.as_deref()
1202    }
1203
1204    /// Return a relay matching a given identity, if we have a
1205    /// _usable_ relay with that key.
1206    ///
1207    /// (Does not return [unusable](NetDir#usable) relays.)
1208    ///
1209    ///
1210    /// Note that a `None` answer is not always permanent: if a microdescriptor
1211    /// is subsequently added for a relay with this ID, the ID may become usable
1212    /// even if it was not usable before.
1213    pub fn by_id<'a, T>(&self, id: T) -> Option<Relay<'_>>
1214    where
1215        T: Into<RelayIdRef<'a>>,
1216    {
1217        let id = id.into();
1218        let answer = match id {
1219            RelayIdRef::Ed25519(ed25519) => {
1220                let rsidx = *self.rsidx_by_ed.get(ed25519)?;
1221                let rs = self.c_relays().get(rsidx).expect("Corrupt index");
1222
1223                self.relay_from_rs_and_rsidx(rs, rsidx).into_relay()?
1224            }
1225            RelayIdRef::Rsa(rsa) => self
1226                .by_rsa_id_unchecked(rsa)
1227                .and_then(UncheckedRelay::into_relay)?,
1228            other_type => self.relays().find(|r| r.has_identity(other_type))?,
1229        };
1230        assert!(answer.has_identity(id));
1231        Some(answer)
1232    }
1233
1234    /// Obtain a `Relay` given a `RouterStatusIdx`
1235    ///
1236    /// Differs from `relay_from_rs_and_rsi` as follows:
1237    ///  * That function expects the caller to already have an `MdRouterStatus`;
1238    ///    it checks with `debug_assert` that the relay in the netdir matches.
1239    ///  * That function panics if the `RouterStatusIdx` is invalid; this one returns `None`.
1240    ///  * That function returns an `UncheckedRelay`; this one a `Relay`.
1241    ///
1242    /// `None` could be returned here, even with a valid `rsi`,
1243    /// if `rsi` refers to an [unusable](NetDir#usable) relay.
1244    #[cfg_attr(not(feature = "hs-common"), allow(dead_code))]
1245    pub(crate) fn relay_by_rs_idx(&self, rs_idx: RouterStatusIdx) -> Option<Relay<'_>> {
1246        let rs = self.c_relays().get(rs_idx)?;
1247        let md = self.mds.get(rs_idx)?.as_deref();
1248        UncheckedRelay {
1249            rs,
1250            md,
1251            #[cfg(feature = "geoip")]
1252            cc: self.country_codes.get(rs_idx.0).copied().flatten(),
1253        }
1254        .into_relay()
1255    }
1256
1257    /// Return a relay with the same identities as those in `target`, if one
1258    /// exists.
1259    ///
1260    /// Does not return [unusable](NetDir#usable) relays.
1261    ///
1262    /// Note that a negative result from this method is not necessarily permanent:
1263    /// it may be the case that a relay exists,
1264    /// but we don't yet have enough information about it to know all of its IDs.
1265    /// To test whether a relay is *definitely* absent,
1266    /// use [`by_ids_detailed`](Self::by_ids_detailed)
1267    /// or [`ids_listed`](Self::ids_listed).
1268    ///
1269    /// # Limitations
1270    ///
1271    /// This will be very slow if `target` does not have an Ed25519 or RSA
1272    /// identity.
1273    pub fn by_ids<T>(&self, target: &T) -> Option<Relay<'_>>
1274    where
1275        T: HasRelayIds + ?Sized,
1276    {
1277        let mut identities = target.identities();
1278        // Don't try if there are no identities.
1279        let first_id = identities.next()?;
1280
1281        // Since there is at most one relay with each given ID type,
1282        // we only need to check the first relay we find.
1283        let candidate = self.by_id(first_id)?;
1284        if identities.all(|wanted_id| candidate.has_identity(wanted_id)) {
1285            Some(candidate)
1286        } else {
1287            None
1288        }
1289    }
1290
1291    /// Check whether there is a relay that has at least one identity from
1292    /// `target`, and which _could_ have every identity from `target`.
1293    /// If so, return such a relay.
1294    ///
1295    /// Return `Ok(None)` if we did not find a relay with any identity from `target`.
1296    ///
1297    /// Return `RelayLookupError::Impossible` if we found a relay with at least
1298    /// one identity from `target`, but that relay's other identities contradict
1299    /// what we learned from `target`.
1300    ///
1301    /// Does not return [unusable](NetDir#usable) relays.
1302    ///
1303    /// (This function is only useful if you need to distinguish the
1304    /// "impossible" case from the "no such relay known" case.)
1305    ///
1306    /// # Limitations
1307    ///
1308    /// This will be very slow if `target` does not have an Ed25519 or RSA
1309    /// identity.
1310    //
1311    // TODO HS: This function could use a better name.
1312    //
1313    // TODO: We could remove the feature restriction here once we think this API is
1314    // stable.
1315    #[cfg(feature = "hs-common")]
1316    pub fn by_ids_detailed<T>(
1317        &self,
1318        target: &T,
1319    ) -> std::result::Result<Option<Relay<'_>>, RelayLookupError>
1320    where
1321        T: HasRelayIds + ?Sized,
1322    {
1323        let candidate = target
1324            .identities()
1325            // Find all the relays that share any identity with this set of identities.
1326            .filter_map(|id| self.by_id(id))
1327            // We might find the same relay more than once under a different
1328            // identity, so we remove the duplicates.
1329            //
1330            // Since there is at most one relay per rsa identity per consensus,
1331            // this is a true uniqueness check under current construction rules.
1332            .unique_by(|r| r.rs.rsa_identity())
1333            // If we find two or more distinct relays, then have a contradiction.
1334            .at_most_one()
1335            .map_err(|_| RelayLookupError::Impossible)?;
1336
1337        // If we have no candidate, return None early.
1338        let candidate = match candidate {
1339            Some(relay) => relay,
1340            None => return Ok(None),
1341        };
1342
1343        // Now we know we have a single candidate.  Make sure that it does not have any
1344        // identity that does not match the target.
1345        if target
1346            .identities()
1347            .all(|wanted_id| match candidate.identity(wanted_id.id_type()) {
1348                None => true,
1349                Some(id) => id == wanted_id,
1350            })
1351        {
1352            Ok(Some(candidate))
1353        } else {
1354            Err(RelayLookupError::Impossible)
1355        }
1356    }
1357
1358    /// Return a boolean if this consensus definitely has (or does not have) a
1359    /// relay matching the listed identities.
1360    ///
1361    /// `Some(true)` indicates that the relay exists.
1362    /// `Some(false)` indicates that the relay definitely does not exist.
1363    /// `None` indicates that we can't yet tell whether such a relay exists,
1364    ///  due to missing information.
1365    fn id_pair_listed(&self, ed_id: &Ed25519Identity, rsa_id: &RsaIdentity) -> Option<bool> {
1366        let r = self.by_rsa_id_unchecked(rsa_id);
1367        match r {
1368            Some(unchecked) => {
1369                if !unchecked.rs.ed25519_id_is_usable() {
1370                    return Some(false);
1371                }
1372                // If md is present, then it's listed iff we have the right
1373                // ed id.  Otherwise we don't know if it's listed.
1374                unchecked.md.map(|md| md.ed25519_id() == ed_id)
1375            }
1376            None => {
1377                // Definitely not listed.
1378                Some(false)
1379            }
1380        }
1381    }
1382
1383    /// Check whether a relay exists (or may exist)
1384    /// with the same identities as those in `target`.
1385    ///
1386    /// `Some(true)` indicates that the relay exists.
1387    /// `Some(false)` indicates that the relay definitely does not exist.
1388    /// `None` indicates that we can't yet tell whether such a relay exists,
1389    ///  due to missing information.
1390    pub fn ids_listed<T>(&self, target: &T) -> Option<bool>
1391    where
1392        T: HasRelayIds + ?Sized,
1393    {
1394        let rsa_id = target.rsa_identity();
1395        let ed25519_id = target.ed_identity();
1396
1397        // TODO: If we later support more identity key types, this will
1398        // become incorrect.  This assertion might help us recognize that case.
1399        const _: () = assert!(RelayIdType::COUNT == 2);
1400
1401        match (rsa_id, ed25519_id) {
1402            (Some(r), Some(e)) => self.id_pair_listed(e, r),
1403            (Some(r), None) => Some(self.rsa_id_is_listed(r)),
1404            (None, Some(e)) => {
1405                if self.rsidx_by_ed.contains_key(e) {
1406                    Some(true)
1407                } else {
1408                    None
1409                }
1410            }
1411            (None, None) => None,
1412        }
1413    }
1414
1415    /// Return a (possibly [unusable](NetDir#usable)) relay with a given RSA identity.
1416    ///
1417    /// This API can be used to find information about a relay that is listed in
1418    /// the current consensus, even if we don't yet have enough information
1419    /// (like a microdescriptor) about the relay to use it.
1420    #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
1421    #[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
1422    fn by_rsa_id_unchecked(&self, rsa_id: &RsaIdentity) -> Option<UncheckedRelay<'_>> {
1423        let rsidx = *self.rsidx_by_rsa.get(rsa_id)?;
1424        let rs = self.c_relays().get(rsidx).expect("Corrupt index");
1425        assert_eq!(rs.rsa_identity(), rsa_id);
1426        Some(self.relay_from_rs_and_rsidx(rs, rsidx))
1427    }
1428    /// Return the relay with a given RSA identity, if we have one
1429    /// and it is [usable](NetDir#usable).
1430    fn by_rsa_id(&self, rsa_id: &RsaIdentity) -> Option<Relay<'_>> {
1431        self.by_rsa_id_unchecked(rsa_id)?.into_relay()
1432    }
1433    /// Return true if `rsa_id` is listed in this directory, even if it isn't
1434    /// currently usable.
1435    ///
1436    /// (An "[unusable](NetDir#usable)" relay in this context is one for which we don't have full
1437    /// directory information.)
1438    #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
1439    #[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
1440    fn rsa_id_is_listed(&self, rsa_id: &RsaIdentity) -> bool {
1441        self.by_rsa_id_unchecked(rsa_id).is_some()
1442    }
1443
1444    /// List the hsdirs in this NetDir, that should be in the HSDir rings
1445    ///
1446    /// The results are not returned in any particular order.
1447    #[cfg(feature = "hs-common")]
1448    fn all_hsdirs(&self) -> impl Iterator<Item = (RouterStatusIdx, Relay<'_>)> {
1449        self.c_relays().iter_enumerated().filter_map(|(rsidx, rs)| {
1450            let relay = self.relay_from_rs_and_rsidx(rs, rsidx);
1451            relay.is_hsdir_for_ring().then_some(())?;
1452            let relay = relay.into_relay()?;
1453            Some((rsidx, relay))
1454        })
1455    }
1456
1457    /// Return the parameters from the consensus, clamped to the
1458    /// correct ranges, with defaults filled in.
1459    ///
1460    /// NOTE: that unsupported parameters aren't returned here; only those
1461    /// values configured in the `params` module are available.
1462    pub fn params(&self) -> &NetParameters {
1463        &self.params
1464    }
1465
1466    /// Return a [`ProtoStatus`](netstatus::ProtoStatus) that lists the
1467    /// network's current requirements and recommendations for the list of
1468    /// protocols that every relay must implement.
1469    //
1470    // TODO HS: I am not sure this is the right API; other alternatives would be:
1471    //    * To expose the _required_ relay protocol list instead (since that's all that
1472    //      onion service implementations need).
1473    //    * To expose the client protocol list as well (for symmetry).
1474    //    * To expose the MdConsensus instead (since that's more general, although
1475    //      it restricts the future evolution of this API).
1476    //
1477    // I think that this is a reasonably good compromise for now, but I'm going
1478    // to put it behind the `hs-common` feature to give us time to consider more.
1479    #[cfg(feature = "hs-common")]
1480    pub fn relay_protocol_status(&self) -> &netstatus::ProtoStatus {
1481        self.consensus.relay_protocol_status()
1482    }
1483
1484    /// Return a [`ProtoStatus`](netstatus::ProtoStatus) that lists the
1485    /// network's current requirements and recommendations for the list of
1486    /// protocols that every relay must implement.
1487    //
1488    // TODO HS: See notes on relay_protocol_status above.
1489    #[cfg(feature = "hs-common")]
1490    pub fn client_protocol_status(&self) -> &netstatus::ProtoStatus {
1491        self.consensus.client_protocol_status()
1492    }
1493
1494    /// Construct a `CircTarget` from an externally provided list of link specifiers,
1495    /// and an externally provided onion key.
1496    ///
1497    /// This method is used in the onion service protocol,
1498    /// where introduction points and rendezvous points are specified using these inputs.
1499    ///
1500    /// This function is a member of `NetDir` so that it can provide a reasonable list of
1501    /// [`Protocols`](tor_protover::Protocols) capabilities for the generated `CircTarget`.
1502    /// It does not (and should not!) look up anything else from the directory.
1503    #[cfg(feature = "hs-common")]
1504    pub fn circ_target_from_verbatim_linkspecs(
1505        &self,
1506        linkspecs: &[tor_linkspec::EncodedLinkSpec],
1507        ntor_onion_key: &curve25519::PublicKey,
1508    ) -> StdResult<VerbatimLinkSpecCircTarget<OwnedCircTarget>, VerbatimCircTargetDecodeError> {
1509        use VerbatimCircTargetDecodeError as E;
1510        use tor_linkspec::CircTarget as _;
1511        use tor_linkspec::decode::Strictness;
1512
1513        let mut bld = OwnedCircTarget::builder();
1514        use tor_error::into_internal;
1515
1516        *bld.chan_target() =
1517            OwnedChanTargetBuilder::from_encoded_linkspecs(Strictness::Standard, linkspecs)?;
1518        let protocols = {
1519            let chan_target = bld.chan_target().build().map_err(into_internal!(
1520                "from_encoded_linkspecs gave an invalid output"
1521            ))?;
1522            match self
1523                .by_ids_detailed(&chan_target)
1524                .map_err(E::ImpossibleIds)?
1525            {
1526                Some(relay) => relay.protovers().clone(),
1527                None => self.relay_protocol_status().required_protocols().clone(),
1528            }
1529        };
1530        bld.protocols(protocols);
1531        bld.ntor_onion_key(*ntor_onion_key);
1532        Ok(VerbatimLinkSpecCircTarget::new(
1533            bld.build()
1534                .map_err(into_internal!("Failed to construct a valid circtarget"))?,
1535            linkspecs.to_vec(),
1536        ))
1537    }
1538
1539    /// Return weighted the fraction of relays we can use.  We only
1540    /// consider relays that match the predicate `usable`.  We weight
1541    /// this bandwidth according to the provided `role`.
1542    ///
1543    /// If _no_ matching relays in the consensus have a nonzero
1544    /// weighted bandwidth value, we fall back to looking at the
1545    /// unweighted fraction of matching relays.
1546    ///
1547    /// If there are no matching relays in the consensus, we return 0.0.
1548    fn frac_for_role<'a, F>(&'a self, role: WeightRole, usable: F) -> f64
1549    where
1550        F: Fn(&UncheckedRelay<'a>) -> bool,
1551    {
1552        let mut total_weight = 0_u64;
1553        let mut have_weight = 0_u64;
1554        let mut have_count = 0_usize;
1555        let mut total_count = 0_usize;
1556
1557        for r in self.all_relays() {
1558            if !usable(&r) {
1559                continue;
1560            }
1561            let w = self.weights.weight_rs_for_role(r.rs, role);
1562            total_weight += w;
1563            total_count += 1;
1564            if r.is_usable() {
1565                have_weight += w;
1566                have_count += 1;
1567            }
1568        }
1569
1570        if total_weight > 0 {
1571            // The consensus lists some weighted bandwidth so return the
1572            // fraction of the weighted bandwidth for which we have
1573            // descriptors.
1574            (have_weight as f64) / (total_weight as f64)
1575        } else if total_count > 0 {
1576            // The consensus lists no weighted bandwidth for these relays,
1577            // but at least it does list relays. Return the fraction of
1578            // relays for which it we have descriptors.
1579            (have_count as f64) / (total_count as f64)
1580        } else {
1581            // There are no relays of this kind in the consensus.  Return
1582            // 0.0, to avoid dividing by zero and giving NaN.
1583            0.0
1584        }
1585    }
1586    /// Return the estimated fraction of possible paths that we have
1587    /// enough microdescriptors to build.
1588    fn frac_usable_paths(&self) -> f64 {
1589        // TODO #504, TODO SPEC: We may want to add a set of is_flagged_fast() and/or
1590        // is_flagged_stable() checks here.  This will require spec clarification.
1591        let f_g = self.frac_for_role(WeightRole::Guard, |u| {
1592            u.low_level_details().is_suitable_as_guard()
1593        });
1594        let f_m = self.frac_for_role(WeightRole::Middle, |_| true);
1595        let f_e = if self.all_relays().any(|u| u.rs.is_flagged_exit()) {
1596            self.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit())
1597        } else {
1598            // If there are no exits at all, we use f_m here.
1599            f_m
1600        };
1601        f_g * f_m * f_e
1602    }
1603    /// Return true if there is enough information in this NetDir to build
1604    /// multihop circuits.
1605    fn have_enough_paths(&self) -> bool {
1606        // TODO-A001: This should check for our guards as well, and
1607        // make sure that if they're listed in the consensus, we have
1608        // the descriptors for them.
1609
1610        // If we can build a randomly chosen path with at least this
1611        // probability, we know enough information to participate
1612        // on the network.
1613
1614        let min_frac_paths: f64 = self.params().min_circuit_path_threshold.as_fraction();
1615
1616        // What fraction of paths can we build?
1617        let available = self.frac_usable_paths();
1618
1619        available >= min_frac_paths
1620    }
1621    /// Choose a relay at random.
1622    ///
1623    /// Each relay is chosen with probability proportional to its weight
1624    /// in the role `role`, and is only selected if the predicate `usable`
1625    /// returns true for it.
1626    ///
1627    /// This function returns None if (and only if) there are no relays
1628    /// where `usable` returned true.
1629    ///
1630    /// A relay with zero weight will be chosen iff all `usable` relays have
1631    /// zero weight.
1632    //
1633    // TODO this API, with the `usable` closure, invites mistakes where we fail to
1634    // check conditions that are implied by the role we have selected for the relay:
1635    // call sites must include a call to `Relay::is_polarity_inverter()` or whatever.
1636    // IMO the `WeightRole` ought to imply a condition (and it should therefore probably
1637    // be renamed.)  -Diziet
1638    pub fn pick_relay<'a, R, P>(
1639        &'a self,
1640        rng: &mut R,
1641        role: WeightRole,
1642        usable: P,
1643    ) -> Option<Relay<'a>>
1644    where
1645        R: rand::Rng,
1646        P: FnMut(&Relay<'a>) -> bool,
1647    {
1648        let relays: Vec<_> = self.relays().filter(usable).collect();
1649
1650        tracing::trace!(?role, "picking from {} relays", relays.len());
1651
1652        // Preemptively check for and handle an empty sequence ourselves, since it's
1653        // cheap to do so and the `choose_weighted` behavior for this edge-case
1654        // is a bit unpredictable.
1655        // See e.g. <https://github.com/rust-random/rand/issues/1783>
1656        if relays.is_empty() {
1657            tracing::debug!(?role, "No eligible relays");
1658            return None;
1659        }
1660
1661        // This algorithm uses rand::distr::WeightedIndex, and uses
1662        // gives O(n) time and space  to build the index, plus O(log n)
1663        // sampling time.
1664        //
1665        // We might be better off building a WeightedIndex in advance
1666        // for each `role`, and then sampling it repeatedly until we
1667        // get a relay that satisfies `usable`.  Or we might not --
1668        // that depends heavily on the actual particulars of our
1669        // inputs.  We probably shouldn't make any changes there
1670        // unless profiling tells us that this function is in a hot
1671        // path.
1672        //
1673        // The C Tor sampling implementation goes through some trouble
1674        // here to try to make its path selection constant-time.  I
1675        // believe that there is no actual remotely exploitable
1676        // side-channel here however.  It could be worth analyzing in
1677        // the future.
1678        //
1679        // This code will give the wrong result if the total of all weights
1680        // can exceed u64::MAX.  We make sure that can't happen when we
1681        // set up `self.weights`.
1682        match relays[..].choose_weighted(rng, |r| {
1683            let weight = self.weights.weight_rs_for_role(r.rs, role);
1684            tracing::trace!("relay:{id:?} role:{role:?} weight:{weight}", id = r.id());
1685            weight
1686        }) {
1687            Ok(relay) => Some(relay.clone()),
1688            Err(WeightError::InsufficientNonZero) => {
1689                warn!(?self.weights, ?role,
1690                        "After filtering, all {} relays had zero weight. Choosing one at random. See bug #1907.",
1691                        relays.len());
1692                relays.choose(rng).cloned()
1693            }
1694            Err(e) => {
1695                warn_report!(
1696                    e,
1697                    "Unexpected error while choosing from {} relays for role {:?}",
1698                    relays.len(),
1699                    role
1700                );
1701                None
1702            }
1703        }
1704    }
1705
1706    /// Choose `n` items (relays) at random, using the provided weights.
1707    ///
1708    /// This is intended as an internal, easier-to-test, implementation of
1709    /// `pick_n_relays`. `T` is generic for testing, but intended to be `Relay`.
1710    ///
1711    /// Items are chosen without replacement: no item will be returned twice.
1712    ///
1713    /// If *all* items have zero-weight, then up to `n` will be chosen randomly
1714    /// and returned. Otherwise, never returns items with zero-weight.
1715    ///
1716    /// May return fewer than `n` items if there are fewer than `n` with non-zero weight
1717    /// (or all have zero-weight but there are fewer than `n` total).
1718    fn pick_n_weighted<R, T>(rng: &mut R, n: usize, weighted_items: &[(T, u64)]) -> Vec<T>
1719    where
1720        R: rand::Rng,
1721        T: Clone,
1722    {
1723        let mut sampled_items = match weighted_items[..]
1724            .sample_weighted(rng, n, |(_r, w)| *w as f64)
1725        {
1726            Err(e) => {
1727                warn_report!(e, "Unexpected error while sampling a set of items");
1728                Vec::new()
1729            }
1730            Ok(sampled_items) => {
1731                if sampled_items.len() < n {
1732                    // Too few items had nonzero weights: return all of those that are okay.
1733                    let nonzero_weight_items: Vec<_> = weighted_items
1734                        .iter()
1735                        .filter_map(|(i, w)| if *w > 0 { Some(i) } else { None })
1736                        .cloned()
1737                        .collect();
1738                    if nonzero_weight_items.is_empty() {
1739                        tracing::debug!(
1740                            "All {} items had zero weight! Picking some at random. See bug #1907.",
1741                            weighted_items.len()
1742                        );
1743                        let items: Vec<_> =
1744                            weighted_items.iter().map(|(i, _w)| i.clone()).collect();
1745                        if items.len() >= n {
1746                            items.sample(rng, n).cloned().collect()
1747                        } else {
1748                            items
1749                        }
1750                    } else {
1751                        tracing::debug!(
1752                            "After filtering, only had {}/{} items with nonzero weight. Returning them all. See bug #1907.",
1753                            nonzero_weight_items.len(),
1754                            weighted_items.len()
1755                        );
1756                        nonzero_weight_items
1757                    }
1758                } else {
1759                    sampled_items.map(|(i, _w)| i.clone()).collect()
1760                }
1761            }
1762        };
1763        sampled_items.shuffle(rng);
1764        sampled_items
1765    }
1766
1767    /// Choose `n` relay at random.
1768    ///
1769    /// Each relay is chosen with probability proportional to its weight
1770    /// in the role `role`, and is only selected if the predicate `usable`
1771    /// returns true for it.
1772    ///
1773    /// Relays are chosen without replacement: no relay will be
1774    /// returned twice. Therefore, the resulting vector may be smaller
1775    /// than `n` if we happen to have fewer than `n` appropriate relays.
1776    ///
1777    /// Relays with zero-weight will be chosen only if there are *no* usable
1778    /// relays with nonzero-weight.
1779    ///
1780    /// This function returns an empty vector if (and only if) there are no
1781    /// relays where `usable` returned true.
1782    pub fn pick_n_relays<'a, R, P>(
1783        &'a self,
1784        rng: &mut R,
1785        n: usize,
1786        role: WeightRole,
1787        usable: P,
1788    ) -> Vec<Relay<'a>>
1789    where
1790        R: rand::Rng,
1791        P: FnMut(&Relay<'a>) -> bool,
1792    {
1793        let filtered_weighted_relays: Vec<(Relay<'a>, u64)> = self
1794            .relays()
1795            .filter(usable)
1796            .map(|r| (r.clone(), self.weights.weight_rs_for_role(r.rs, role)))
1797            .collect();
1798        let res = NetDir::pick_n_weighted(rng, n, filtered_weighted_relays.as_slice());
1799        let n_found = res.len();
1800        if n_found < n {
1801            warn!(?self.weights, ?role,
1802                "Requested {n} relays, but only {n_usable} were usable, and only {n_found} were chosen after weighting {role:?}.",
1803                n_usable=filtered_weighted_relays.len(),
1804            );
1805        }
1806        res
1807    }
1808
1809    /// Compute the weight with which `relay` will be selected for a given
1810    /// `role`.
1811    pub fn relay_weight<'a>(&'a self, relay: &Relay<'a>, role: WeightRole) -> RelayWeight {
1812        RelayWeight(self.weights.weight_rs_for_role(relay.rs, role))
1813    }
1814
1815    /// Compute the total weight with which any relay matching `usable`
1816    /// will be selected for a given `role`.
1817    ///
1818    /// Note: because this function is used to assess the total
1819    /// properties of the consensus, the `usable` predicate takes a
1820    /// [`MdRouterStatus`] rather than a [`Relay`].
1821    pub fn total_weight<P>(&self, role: WeightRole, usable: P) -> RelayWeight
1822    where
1823        P: Fn(&UncheckedRelay<'_>) -> bool,
1824    {
1825        self.all_relays()
1826            .filter_map(|unchecked| {
1827                if usable(&unchecked) {
1828                    Some(RelayWeight(
1829                        self.weights.weight_rs_for_role(unchecked.rs, role),
1830                    ))
1831                } else {
1832                    None
1833                }
1834            })
1835            .sum()
1836    }
1837
1838    /// Compute the weight with which a relay with ID `rsa_id` would be
1839    /// selected for a given `role`.
1840    ///
1841    /// Note that weight returned by this function assumes that the
1842    /// relay with that ID is actually [usable](NetDir#usable); if it isn't usable,
1843    /// then other weight-related functions will call its weight zero.
1844    pub fn weight_by_rsa_id(&self, rsa_id: &RsaIdentity, role: WeightRole) -> Option<RelayWeight> {
1845        self.by_rsa_id_unchecked(rsa_id)
1846            .map(|unchecked| RelayWeight(self.weights.weight_rs_for_role(unchecked.rs, role)))
1847    }
1848
1849    /// Return all relays in this NetDir known to be in the same family as
1850    /// `relay`.
1851    ///
1852    /// This list of members will **not** necessarily include `relay` itself.
1853    ///
1854    /// # Limitations
1855    ///
1856    /// Two relays only belong to the same family if _each_ relay
1857    /// claims to share a family with the other.  But if we are
1858    /// missing a microdescriptor for one of the relays listed by this
1859    /// relay, we cannot know whether it acknowledges family
1860    /// membership with this relay or not.  Therefore, this function
1861    /// can omit family members for which there is not (as yet) any
1862    /// Relay object.
1863    pub fn known_family_members<'a>(
1864        &'a self,
1865        relay: &'a Relay<'a>,
1866    ) -> impl Iterator<Item = Relay<'a>> {
1867        let relay_rsa_id = relay.rsa_id();
1868        relay.md.family().members().filter_map(move |other_rsa_id| {
1869            self.by_rsa_id(other_rsa_id)
1870                .filter(|other_relay| other_relay.md.family().contains(relay_rsa_id))
1871        })
1872    }
1873
1874    /// Return the current hidden service directory "time period".
1875    ///
1876    /// Specifically, this returns the time period that contains the beginning
1877    /// of the validity period of this `NetDir`'s consensus.  That time period
1878    /// is the one we use when acting as an hidden service client.
1879    #[cfg(feature = "hs-common")]
1880    pub fn hs_time_period(&self) -> TimePeriod {
1881        self.hsdir_rings.current.time_period()
1882    }
1883
1884    /// Return the [`HsDirParams`] of all the relevant hidden service directory "time periods"
1885    ///
1886    /// This includes the current time period (as from
1887    /// [`.hs_time_period`](NetDir::hs_time_period))
1888    /// plus additional time periods that we publish descriptors for when we are
1889    /// acting as a hidden service.
1890    #[cfg(feature = "hs-service")]
1891    pub fn hs_all_time_periods(&self) -> Vec<HsDirParams> {
1892        self.hsdir_rings
1893            .iter()
1894            .map(|r| r.params().clone())
1895            .collect()
1896    }
1897
1898    /// Return the relays in this network directory that will be used as hidden service directories
1899    ///
1900    /// These are suitable to retrieve a given onion service's descriptor at a given time period.
1901    #[cfg(feature = "hs-common")]
1902    pub fn hs_dirs_download<'r, R>(
1903        &'r self,
1904        hsid: HsBlindId,
1905        period: TimePeriod,
1906        rng: &mut R,
1907    ) -> std::result::Result<Vec<Relay<'r>>, Bug>
1908    where
1909        R: rand::Rng,
1910    {
1911        // Algorithm:
1912        //
1913        // 1. Determine which HsDirRing to use, based on the time period.
1914        // 2. Find the shared random value that's associated with that HsDirRing.
1915        // 3. Choose spread = the parameter `hsdir_spread_fetch`
1916        // 4. Let n_replicas = the parameter `hsdir_n_replicas`.
1917        // 5. Initialize Dirs = []
1918        // 6. for idx in 1..=n_replicas:
1919        //       - let H = hsdir_ring::onion_service_index(id, replica, rand,
1920        //         period).
1921        //       - Find the position of H within hsdir_ring.
1922        //       - Take elements from hsdir_ring starting at that position,
1923        //         adding them to Dirs until we have added `spread` new elements
1924        //         that were not there before.
1925        // 7. Shuffle Dirs
1926        // 8. return Dirs.
1927
1928        let spread = self.spread(HsDirOp::Download);
1929
1930        // When downloading, only look at relays on current ring.
1931        let ring = &self.hsdir_rings.current;
1932
1933        if ring.params().time_period != period {
1934            return Err(internal!(
1935                "our current ring is not associated with the requested time period!"
1936            ));
1937        }
1938
1939        let mut hs_dirs = self.select_hsdirs(hsid, ring, spread).collect_vec();
1940
1941        // When downloading, the order of the returned relays is random.
1942        hs_dirs.shuffle(rng);
1943
1944        Ok(hs_dirs)
1945    }
1946
1947    /// Return the relays in this network directory that will be used as hidden service directories
1948    ///
1949    /// Returns the relays that are suitable for storing a given onion service's descriptors at the
1950    /// given time period.
1951    #[cfg(feature = "hs-service")]
1952    pub fn hs_dirs_upload(
1953        &self,
1954        hsid: HsBlindId,
1955        period: TimePeriod,
1956    ) -> std::result::Result<impl Iterator<Item = Relay<'_>>, Bug> {
1957        // Algorithm:
1958        //
1959        // 1. Choose spread = the parameter `hsdir_spread_store`
1960        // 2. Determine which HsDirRing to use, based on the time period.
1961        // 3. Find the shared random value that's associated with that HsDirRing.
1962        // 4. Let n_replicas = the parameter `hsdir_n_replicas`.
1963        // 5. Initialize Dirs = []
1964        // 6. for idx in 1..=n_replicas:
1965        //       - let H = hsdir_ring::onion_service_index(id, replica, rand,
1966        //         period).
1967        //       - Find the position of H within hsdir_ring.
1968        //       - Take elements from hsdir_ring starting at that position,
1969        //         adding them to Dirs until we have added `spread` new elements
1970        //         that were not there before.
1971        // 3. return Dirs.
1972        let spread = self.spread(HsDirOp::Upload);
1973
1974        // For each HsBlindId, determine which HsDirRing to use.
1975        let rings = self
1976            .hsdir_rings
1977            .iter()
1978            .filter_map(move |ring| {
1979                // Make sure the ring matches the TP of the hsid it's matched with.
1980                (ring.params().time_period == period).then_some((ring, hsid, period))
1981            })
1982            .collect::<Vec<_>>();
1983
1984        // The specified period should have an associated ring.
1985        if !rings.iter().any(|(_, _, tp)| *tp == period) {
1986            return Err(internal!(
1987                "the specified time period does not have an associated ring"
1988            ));
1989        };
1990
1991        // Now that we've matched each `hsid` with the ring associated with its TP, we can start
1992        // selecting replicas from each ring.
1993        Ok(rings.into_iter().flat_map(move |(ring, hsid, period)| {
1994            assert_eq!(period, ring.params().time_period());
1995            self.select_hsdirs(hsid, ring, spread)
1996        }))
1997    }
1998
1999    /// Return the relays in this network directory that will be used as hidden service directories
2000    ///
2001    /// Depending on `op`,
2002    /// these are suitable to either store, or retrieve, a
2003    /// given onion service's descriptor at a given time period.
2004    ///
2005    /// When `op` is `Download`, the order is random.
2006    /// When `op` is `Upload`, the order is not specified.
2007    ///
2008    /// Return an error if the time period is not one returned by
2009    /// `onion_service_time_period` or `onion_service_secondary_time_periods`.
2010    //
2011    // TODO: make HsDirOp pub(crate) once this is removed
2012    #[cfg(feature = "hs-common")]
2013    #[deprecated(note = "Use hs_dirs_upload or hs_dirs_download instead")]
2014    pub fn hs_dirs<'r, R>(&'r self, hsid: &HsBlindId, op: HsDirOp, rng: &mut R) -> Vec<Relay<'r>>
2015    where
2016        R: rand::Rng,
2017    {
2018        // Algorithm:
2019        //
2020        // 1. Determine which HsDirRing to use, based on the time period.
2021        // 2. Find the shared random value that's associated with that HsDirRing.
2022        // 3. Choose spread = the parameter `hsdir_spread_store` or
2023        //    `hsdir_spread_fetch` based on `op`.
2024        // 4. Let n_replicas = the parameter `hsdir_n_replicas`.
2025        // 5. Initialize Dirs = []
2026        // 6. for idx in 1..=n_replicas:
2027        //       - let H = hsdir_ring::onion_service_index(id, replica, rand,
2028        //         period).
2029        //       - Find the position of H within hsdir_ring.
2030        //       - Take elements from hsdir_ring starting at that position,
2031        //         adding them to Dirs until we have added `spread` new elements
2032        //         that were not there before.
2033        // 7. return Dirs.
2034        let n_replicas = self
2035            .params
2036            .hsdir_n_replicas
2037            .get()
2038            .try_into()
2039            .expect("BoundedInt did not enforce bounds");
2040
2041        let spread = match op {
2042            HsDirOp::Download => self.params.hsdir_spread_fetch,
2043            #[cfg(feature = "hs-service")]
2044            HsDirOp::Upload => self.params.hsdir_spread_store,
2045        };
2046
2047        let spread = spread
2048            .get()
2049            .try_into()
2050            .expect("BoundedInt did not enforce bounds!");
2051
2052        // TODO: I may be wrong here but I suspect that this function may
2053        // need refactoring so that it does not look at _all_ of the HsDirRings,
2054        // but only at the ones that corresponds to time periods for which
2055        // HsBlindId is valid.  Or I could be mistaken, in which case we should
2056        // have a comment to explain why I am, since the logic is subtle.
2057        // (For clients, there is only one ring.) -nickm
2058        //
2059        // (Actually, there is no need to follow through with the above TODO,
2060        // since this function is deprecated, and not used anywhere but the
2061        // tests.)
2062
2063        let mut hs_dirs = self
2064            .hsdir_rings
2065            .iter_for_op(op)
2066            .cartesian_product(1..=n_replicas) // 1-indexed !
2067            .flat_map({
2068                let mut selected_nodes = HashSet::new();
2069
2070                move |(ring, replica): (&HsDirRing, u8)| {
2071                    let hsdir_idx = hsdir_ring::service_hsdir_index(hsid, replica, ring.params());
2072
2073                    ring.ring_items_at(hsdir_idx, spread, |(hsdir_idx, _)| {
2074                        // According to rend-spec 2.2.3:
2075                        //                                                  ... If any of those
2076                        // nodes have already been selected for a lower-numbered replica of the
2077                        // service, any nodes already chosen are disregarded (i.e. skipped over)
2078                        // when choosing a replica's hsdir_spread_store nodes.
2079                        selected_nodes.insert(*hsdir_idx)
2080                    })
2081                    .collect::<Vec<_>>()
2082                }
2083            })
2084            .filter_map(|(_hsdir_idx, rs_idx)| {
2085                // This ought not to be None but let's not panic or bail if it is
2086                self.relay_by_rs_idx(*rs_idx)
2087            })
2088            .collect_vec();
2089
2090        match op {
2091            HsDirOp::Download => {
2092                // When `op` is `Download`, the order is random.
2093                hs_dirs.shuffle(rng);
2094            }
2095            #[cfg(feature = "hs-service")]
2096            HsDirOp::Upload => {
2097                // When `op` is `Upload`, the order is not specified.
2098            }
2099        }
2100
2101        hs_dirs
2102    }
2103}
2104
2105impl MdReceiver for NetDir {
2106    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
2107        Box::new(self.rsidx_by_missing.keys())
2108    }
2109    fn add_microdesc(&mut self, md: MicrodescAndHash) -> bool {
2110        self.add_arc_microdesc(Arc::new(md))
2111    }
2112    fn n_missing(&self) -> usize {
2113        self.rsidx_by_missing.len()
2114    }
2115}
2116
2117impl<'a> UncheckedRelay<'a> {
2118    /// Return an [`UncheckedRelayDetails`](details::UncheckedRelayDetails) for this relay.
2119    ///
2120    /// Callers should generally avoid using this information directly if they can;
2121    /// it's better to use a higher-level function that exposes semantic information
2122    /// rather than these properties.
2123    pub fn low_level_details(&self) -> details::UncheckedRelayDetails<'_> {
2124        details::UncheckedRelayDetails(self)
2125    }
2126
2127    /// Return true if this relay is valid and [usable](NetDir#usable).
2128    ///
2129    /// This function should return `true` for every Relay we expose
2130    /// to the user.
2131    pub fn is_usable(&self) -> bool {
2132        // No need to check for 'valid' or 'running': they are implicit.
2133        self.md.is_some() && self.rs.ed25519_id_is_usable()
2134    }
2135    /// If this is [usable](NetDir#usable), return a corresponding Relay object.
2136    pub fn into_relay(self) -> Option<Relay<'a>> {
2137        if self.is_usable() {
2138            Some(Relay {
2139                rs: self.rs,
2140                md: self.md?,
2141                #[cfg(feature = "geoip")]
2142                cc: self.cc,
2143            })
2144        } else {
2145            None
2146        }
2147    }
2148
2149    /// Return true if this relay is a hidden service directory
2150    ///
2151    /// Ie, if it is to be included in the hsdir ring.
2152    #[cfg(feature = "hs-common")]
2153    pub(crate) fn is_hsdir_for_ring(&self) -> bool {
2154        // TODO are there any other flags should we check?
2155        // rend-spec-v3 2.2.3 says just
2156        //   "each node listed in the current consensus with the HSDir flag"
2157        // Do we need to check ed25519_id_is_usable ?
2158        // See also https://gitlab.torproject.org/tpo/core/arti/-/issues/504
2159        self.rs.is_flagged_hsdir()
2160    }
2161}
2162
2163impl<'a> Relay<'a> {
2164    /// Return a [`RelayDetails`](details::RelayDetails) for this relay.
2165    ///
2166    /// Callers should generally avoid using this information directly if they can;
2167    /// it's better to use a higher-level function that exposes semantic information
2168    /// rather than these properties.
2169    pub fn low_level_details(&self) -> details::RelayDetails<'_> {
2170        details::RelayDetails(self)
2171    }
2172
2173    /// Return the Ed25519 ID for this relay.
2174    pub fn id(&self) -> &Ed25519Identity {
2175        self.md.ed25519_id()
2176    }
2177    /// Return the RsaIdentity for this relay.
2178    pub fn rsa_id(&self) -> &RsaIdentity {
2179        self.rs.rsa_identity()
2180    }
2181
2182    /// Return a reference to this relay's "router status" entry in
2183    /// the consensus.
2184    ///
2185    /// The router status entry contains information about the relay
2186    /// that the authorities voted on directly.  For most use cases,
2187    /// you shouldn't need them.
2188    ///
2189    /// This function is only available if the crate was built with
2190    /// its `experimental-api` feature.
2191    #[cfg(feature = "experimental-api")]
2192    pub fn rs(&self) -> &netstatus::MdRouterStatus {
2193        self.rs
2194    }
2195    /// Return a reference to this relay's "microdescriptor" entry in
2196    /// the consensus.
2197    ///
2198    /// A "microdescriptor" is a synopsis of the information about a relay,
2199    /// used to determine its capabilities and route traffic through it.
2200    /// For most use cases, you shouldn't need it.
2201    ///
2202    /// This function is only available if the crate was built with
2203    /// its `experimental-api` feature.
2204    #[cfg(feature = "experimental-api")]
2205    pub fn md(&self) -> &MicrodescAndHash {
2206        self.md
2207    }
2208}
2209
2210/// An error value returned from [`NetDir::by_ids_detailed`].
2211#[cfg(feature = "hs-common")]
2212#[derive(Clone, Debug, thiserror::Error)]
2213#[non_exhaustive]
2214pub enum RelayLookupError {
2215    /// We found a relay whose presence indicates that the provided set of
2216    /// identities is impossible to resolve.
2217    #[error("Provided set of identities is impossible according to consensus.")]
2218    Impossible,
2219}
2220
2221impl<'a> HasAddrs for Relay<'a> {
2222    fn addrs(&self) -> impl Iterator<Item = std::net::SocketAddr> {
2223        self.rs.addrs()
2224    }
2225}
2226#[cfg(feature = "geoip")]
2227impl<'a> HasCountryCode for Relay<'a> {
2228    fn country_code(&self) -> Option<CountryCode> {
2229        self.cc
2230    }
2231}
2232impl<'a> tor_linkspec::HasRelayIdsLegacy for Relay<'a> {
2233    fn ed_identity(&self) -> &Ed25519Identity {
2234        self.id()
2235    }
2236    fn rsa_identity(&self) -> &RsaIdentity {
2237        self.rsa_id()
2238    }
2239}
2240
2241impl<'a> HasRelayIds for UncheckedRelay<'a> {
2242    fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
2243        match key_type {
2244            RelayIdType::Ed25519 if self.rs.ed25519_id_is_usable() => {
2245                self.md.map(|m| m.ed25519_id().into())
2246            }
2247            RelayIdType::Rsa => Some(self.rs.rsa_identity().into()),
2248            _ => None,
2249        }
2250    }
2251}
2252#[cfg(feature = "geoip")]
2253impl<'a> HasCountryCode for UncheckedRelay<'a> {
2254    fn country_code(&self) -> Option<CountryCode> {
2255        self.cc
2256    }
2257}
2258
2259impl<'a> DirectChanMethodsHelper for Relay<'a> {}
2260impl<'a> ChanTarget for Relay<'a> {}
2261
2262impl<'a> tor_linkspec::CircTarget for Relay<'a> {
2263    fn ntor_onion_key(&self) -> &ll::pk::curve25519::PublicKey {
2264        self.md.ntor_key()
2265    }
2266    fn protovers(&self) -> &tor_protover::Protocols {
2267        self.rs.protovers()
2268    }
2269}
2270
2271#[cfg(test)]
2272mod test {
2273    // @@ begin test lint list maintained by maint/add_warning @@
2274    #![allow(clippy::bool_assert_comparison)]
2275    #![allow(clippy::clone_on_copy)]
2276    #![allow(clippy::dbg_macro)]
2277    #![allow(clippy::mixed_attributes_style)]
2278    #![allow(clippy::print_stderr)]
2279    #![allow(clippy::print_stdout)]
2280    #![allow(clippy::single_char_pattern)]
2281    #![allow(clippy::unwrap_used)]
2282    #![allow(clippy::unchecked_time_subtraction)]
2283    #![allow(clippy::useless_vec)]
2284    #![allow(clippy::needless_pass_by_value)]
2285    #![allow(clippy::string_slice)] // See arti#2571
2286    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
2287    use super::*;
2288    use crate::testnet::*;
2289    use float_eq::assert_float_eq;
2290    use std::collections::HashSet;
2291    use std::time::Duration;
2292    use tor_basic_utils::test_rng::{self, testing_rng};
2293    use tor_linkspec::{RelayIdType, RelayIds};
2294
2295    #[cfg(feature = "hs-common")]
2296    fn dummy_hs_blind_id() -> HsBlindId {
2297        let hsid = [2, 1, 1, 1].iter().cycle().take(32).cloned().collect_vec();
2298        let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
2299        HsBlindId::from(hsid)
2300    }
2301
2302    // Basic functionality for a partial netdir: Add microdescriptors,
2303    // then you have a netdir.
2304    #[test]
2305    fn partial_netdir() {
2306        let (consensus, microdescs) = construct_network().unwrap();
2307        let dir = PartialNetDir::new(consensus, None);
2308
2309        // Check the lifetime
2310        let lifetime = dir.lifetime();
2311        assert_eq!(
2312            lifetime
2313                .valid_until()
2314                .duration_since(lifetime.valid_after())
2315                .unwrap(),
2316            Duration::new(86400, 0)
2317        );
2318
2319        // No microdescriptors, so we don't have enough paths, and can't
2320        // advance.
2321        assert!(!dir.have_enough_paths());
2322        let mut dir = match dir.unwrap_if_sufficient() {
2323            Ok(_) => panic!(),
2324            Err(d) => d,
2325        };
2326
2327        let missing: HashSet<_> = dir.missing_microdescs().collect();
2328        assert_eq!(missing.len(), 40);
2329        assert_eq!(missing.len(), dir.netdir.c_relays().len());
2330        for md in &microdescs {
2331            assert!(missing.contains(md.digest()));
2332        }
2333
2334        // Now add all the mds and try again.
2335        for md in microdescs {
2336            let wanted = dir.add_microdesc(md);
2337            assert!(wanted);
2338        }
2339
2340        let missing: HashSet<_> = dir.missing_microdescs().collect();
2341        assert!(missing.is_empty());
2342        assert!(dir.have_enough_paths());
2343        let _complete = match dir.unwrap_if_sufficient() {
2344            Ok(d) => d,
2345            Err(_) => panic!(),
2346        };
2347    }
2348
2349    #[test]
2350    fn override_params() {
2351        let (consensus, _microdescs) = construct_network().unwrap();
2352        let override_p = "bwweightscale=2 doesnotexist=77 circwindow=500"
2353            .parse()
2354            .unwrap();
2355        let dir = PartialNetDir::new(consensus.clone(), Some(&override_p));
2356        let params = &dir.netdir.params;
2357        assert_eq!(params.bw_weight_scale.get(), 2);
2358        assert_eq!(params.circuit_window.get(), 500_i32);
2359
2360        // try again without the override.
2361        let dir = PartialNetDir::new(consensus, None);
2362        let params = &dir.netdir.params;
2363        assert_eq!(params.bw_weight_scale.get(), 1_i32);
2364        assert_eq!(params.circuit_window.get(), 1000_i32);
2365    }
2366
2367    #[test]
2368    fn fill_from_previous() {
2369        let (consensus, microdescs) = construct_network().unwrap();
2370
2371        let mut dir = PartialNetDir::new(consensus.clone(), None);
2372        for md in microdescs.iter().skip(2) {
2373            let wanted = dir.add_microdesc(md.clone());
2374            assert!(wanted);
2375        }
2376        let dir1 = dir.unwrap_if_sufficient().unwrap();
2377        assert_eq!(dir1.missing_microdescs().count(), 2);
2378
2379        let mut dir = PartialNetDir::new(consensus, None);
2380        assert_eq!(dir.missing_microdescs().count(), 40);
2381        dir.fill_from_previous_netdir(Arc::new(dir1));
2382        assert_eq!(dir.missing_microdescs().count(), 2);
2383    }
2384
2385    #[test]
2386    fn path_count() {
2387        let low_threshold = "min_paths_for_circs_pct=64".parse().unwrap();
2388        let high_threshold = "min_paths_for_circs_pct=65".parse().unwrap();
2389
2390        let (consensus, microdescs) = construct_network().unwrap();
2391
2392        let mut dir = PartialNetDir::new(consensus.clone(), Some(&low_threshold));
2393        for (pos, md) in microdescs.iter().enumerate() {
2394            if pos % 7 == 2 {
2395                continue; // skip a few relays.
2396            }
2397            dir.add_microdesc(md.clone());
2398        }
2399        let dir = dir.unwrap_if_sufficient().unwrap();
2400
2401        // We  have 40 relays that we know about from the consensus.
2402        assert_eq!(dir.all_relays().count(), 40);
2403
2404        // But only 34 are usable.
2405        assert_eq!(dir.relays().count(), 34);
2406
2407        // For guards: mds 20..=39 correspond to Guard relays.
2408        // Their bandwidth is 2*(1000+2000+...10000) = 110_000.
2409        // We skipped 23, 30, and 37.  They have bandwidth
2410        // 4000 + 1000 + 8000 = 13_000.  So our fractional bandwidth
2411        // should be (110-13)/110.
2412        let f = dir.frac_for_role(WeightRole::Guard, |u| u.rs.is_flagged_guard());
2413        assert!(((97.0 / 110.0) - f).abs() < 0.000001);
2414
2415        // For exits: mds 10..=19 and 30..=39 correspond to Exit relays.
2416        // We skipped 16, 30,  and 37. Per above our fractional bandwidth is
2417        // (110-16)/110.
2418        let f = dir.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit());
2419        assert!(((94.0 / 110.0) - f).abs() < 0.000001);
2420
2421        // For middles: all relays are middles. We skipped 2, 9, 16,
2422        // 23, 30, and 37. Per above our fractional bandwidth is
2423        // (220-33)/220
2424        let f = dir.frac_for_role(WeightRole::Middle, |_| true);
2425        assert!(((187.0 / 220.0) - f).abs() < 0.000001);
2426
2427        // Multiplying those together, we get the fraction of paths we can
2428        // build at ~0.64052066, which is above the threshold we set above for
2429        // MinPathsForCircsPct.
2430        let f = dir.frac_usable_paths();
2431        assert!((f - 0.64052066).abs() < 0.000001);
2432
2433        // But if we try again with a slightly higher threshold...
2434        let mut dir = PartialNetDir::new(consensus, Some(&high_threshold));
2435        for (pos, md) in microdescs.into_iter().enumerate() {
2436            if pos % 7 == 2 {
2437                continue; // skip a few relays.
2438            }
2439            dir.add_microdesc(md);
2440        }
2441        assert!(dir.unwrap_if_sufficient().is_err());
2442    }
2443
2444    /// Return a 3-tuple for use by `test_pick_*()` of an Rng, a number of
2445    /// iterations, and a tolerance.
2446    ///
2447    /// If the Rng is deterministic (the default), we can use a faster setup,
2448    /// with a higher tolerance and fewer iterations.  But if you've explicitly
2449    /// opted into randomization (or are replaying a seed from an earlier
2450    /// randomized test), we give you more iterations and a tighter tolerance.
2451    fn testing_rng_with_tolerances() -> (impl rand::Rng, usize, f64) {
2452        // Use a deterministic RNG if none is specified, since this is slow otherwise.
2453        let config = test_rng::Config::from_env().unwrap_or(test_rng::Config::Deterministic);
2454        let (iters, tolerance) = match config {
2455            test_rng::Config::Deterministic => (5000, 0.02),
2456            _ => (50000, 0.01),
2457        };
2458        (config.into_rng(), iters, tolerance)
2459    }
2460
2461    #[test]
2462    fn test_pick() {
2463        let (consensus, microdescs) = construct_network().unwrap();
2464        let mut dir = PartialNetDir::new(consensus, None);
2465        for md in microdescs.into_iter() {
2466            let wanted = dir.add_microdesc(md.clone());
2467            assert!(wanted);
2468        }
2469        let dir = dir.unwrap_if_sufficient().unwrap();
2470
2471        let (mut rng, total, tolerance) = testing_rng_with_tolerances();
2472
2473        let mut picked = [0_isize; 40];
2474        for _ in 0..total {
2475            let r = dir.pick_relay(&mut rng, WeightRole::Middle, |r| {
2476                r.low_level_details().supports_exit_port_ipv4(80)
2477            });
2478            let r = r.unwrap();
2479            let id_byte = r.identity(RelayIdType::Rsa).unwrap().as_bytes()[0];
2480            picked[id_byte as usize] += 1;
2481        }
2482        // non-exits should never get picked.
2483        picked[0..10].iter().for_each(|x| assert_eq!(*x, 0));
2484        picked[20..30].iter().for_each(|x| assert_eq!(*x, 0));
2485
2486        let picked_f: Vec<_> = picked.iter().map(|x| *x as f64 / total as f64).collect();
2487
2488        // We didn't we any non-default weights, so the other relays get
2489        // weighted proportional to their bandwidth.
2490        assert_float_eq!(picked_f[19], (10.0 / 110.0), abs <= tolerance);
2491        assert_float_eq!(picked_f[38], (9.0 / 110.0), abs <= tolerance);
2492        assert_float_eq!(picked_f[39], (10.0 / 110.0), abs <= tolerance);
2493    }
2494
2495    #[test]
2496    fn test_pick_multiple() {
2497        // This is mostly a copy of test_pick, except that it uses
2498        // pick_n_relays to pick several relays at once.
2499
2500        let dir = construct_netdir().unwrap_if_sufficient().unwrap();
2501
2502        let (mut rng, total, tolerance) = testing_rng_with_tolerances();
2503
2504        let mut picked = [0_isize; 40];
2505        for _ in 0..total / 4 {
2506            let relays = dir.pick_n_relays(&mut rng, 4, WeightRole::Middle, |r| {
2507                r.low_level_details().supports_exit_port_ipv4(80)
2508            });
2509            assert_eq!(relays.len(), 4);
2510            for r in relays {
2511                let id_byte = r.identity(RelayIdType::Rsa).unwrap().as_bytes()[0];
2512                picked[id_byte as usize] += 1;
2513            }
2514        }
2515        // non-exits should never get picked.
2516        picked[0..10].iter().for_each(|x| assert_eq!(*x, 0));
2517        picked[20..30].iter().for_each(|x| assert_eq!(*x, 0));
2518
2519        let picked_f: Vec<_> = picked.iter().map(|x| *x as f64 / total as f64).collect();
2520
2521        // We didn't we any non-default weights, so the other relays get
2522        // weighted proportional to their bandwidth.
2523        assert_float_eq!(picked_f[19], (10.0 / 110.0), abs <= tolerance);
2524        assert_float_eq!(picked_f[36], (7.0 / 110.0), abs <= tolerance);
2525        assert_float_eq!(picked_f[39], (10.0 / 110.0), abs <= tolerance);
2526    }
2527
2528    #[test]
2529    fn test_pick_multiple_from_insufficient() {
2530        // This is intended to test `NetDir::pick_n_relays`, but targets the internal,
2531        // easier-to-test `NetDir::pick_n_weighted` that is used to implement it.
2532
2533        let mut rng = testing_rng();
2534
2535        // If *any* item (relay) has non-zero weight, then we return only those, even if we asked for more.
2536        let mostly_zeros = vec![("dud1", 0), ("dud2", 0), ("ok", 1), ("dud3", 0)];
2537        for n in [1, 2, 10] {
2538            assert_eq!(
2539                NetDir::pick_n_weighted(&mut rng, n, &mostly_zeros[..]),
2540                vec!["ok"],
2541                "where n={n}"
2542            );
2543        }
2544
2545        // If *all* items have zero weight, and we ask for as many or more than
2546        // we have, we get back all of them.
2547        let all_zeros = vec![("dud1", 0), ("dud2", 0), ("dud3", 0)];
2548        let all_zeros_items = all_zeros.iter().map(|(x, _w)| *x).collect::<Vec<_>>();
2549        for n in [all_zeros.len(), all_zeros.len() + 10] {
2550            let mut res = NetDir::pick_n_weighted(&mut rng, n, &all_zeros[..]);
2551            res.sort();
2552            assert_eq!(res, all_zeros_items, "where n={n}");
2553        }
2554
2555        // If *all* items have zero weight, and we ask for fewer than we have,
2556        // we get back as many as we asked for.
2557        let n = all_zeros.len() / 2;
2558        let res = NetDir::pick_n_weighted(&mut rng, n, &all_zeros[..]);
2559        assert_eq!(res.len(), n);
2560    }
2561
2562    #[test]
2563    fn subnets() {
2564        let cfg = SubnetConfig::default();
2565
2566        fn same_net(cfg: &SubnetConfig, a: &str, b: &str) -> bool {
2567            cfg.addrs_in_same_subnet(&a.parse().unwrap(), &b.parse().unwrap())
2568        }
2569
2570        assert!(same_net(&cfg, "127.15.3.3", "127.15.9.9"));
2571        assert!(!same_net(&cfg, "127.15.3.3", "127.16.9.9"));
2572
2573        assert!(!same_net(&cfg, "127.15.3.3", "127::"));
2574
2575        assert!(same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:91:34::"));
2576        assert!(!same_net(&cfg, "ffff:ffff:90:33::", "ffff:fffe:91:34::"));
2577
2578        let cfg = SubnetConfig {
2579            subnets_family_v4: 32,
2580            subnets_family_v6: 128,
2581        };
2582        assert!(!same_net(&cfg, "127.15.3.3", "127.15.9.9"));
2583        assert!(!same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:91:34::"));
2584
2585        assert!(same_net(&cfg, "127.0.0.1", "127.0.0.1"));
2586        assert!(!same_net(&cfg, "127.0.0.1", "127.0.0.2"));
2587        assert!(same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:90:33::"));
2588
2589        let cfg = SubnetConfig {
2590            subnets_family_v4: 33,
2591            subnets_family_v6: 129,
2592        };
2593        assert!(!same_net(&cfg, "127.0.0.1", "127.0.0.1"));
2594        assert!(!same_net(&cfg, "::", "::"));
2595    }
2596
2597    #[test]
2598    fn subnet_union() {
2599        let cfg1 = SubnetConfig {
2600            subnets_family_v4: 16,
2601            subnets_family_v6: 64,
2602        };
2603        let cfg2 = SubnetConfig {
2604            subnets_family_v4: 24,
2605            subnets_family_v6: 32,
2606        };
2607        let a1 = "1.2.3.4".parse().unwrap();
2608        let a2 = "1.2.10.10".parse().unwrap();
2609
2610        let a3 = "ffff:ffff::7".parse().unwrap();
2611        let a4 = "ffff:ffff:1234::8".parse().unwrap();
2612
2613        assert_eq!(cfg1.addrs_in_same_subnet(&a1, &a2), true);
2614        assert_eq!(cfg2.addrs_in_same_subnet(&a1, &a2), false);
2615
2616        assert_eq!(cfg1.addrs_in_same_subnet(&a3, &a4), false);
2617        assert_eq!(cfg2.addrs_in_same_subnet(&a3, &a4), true);
2618
2619        let cfg_u = cfg1.union(&cfg2);
2620        assert_eq!(
2621            cfg_u,
2622            SubnetConfig {
2623                subnets_family_v4: 16,
2624                subnets_family_v6: 32,
2625            }
2626        );
2627        assert_eq!(cfg_u.addrs_in_same_subnet(&a1, &a2), true);
2628        assert_eq!(cfg_u.addrs_in_same_subnet(&a3, &a4), true);
2629
2630        assert_eq!(cfg1.union(&cfg1), cfg1);
2631
2632        assert_eq!(cfg1.union(&SubnetConfig::no_addresses_match()), cfg1);
2633    }
2634
2635    #[test]
2636    fn relay_funcs() {
2637        let (consensus, microdescs) = construct_custom_network(
2638            |pos, nb, _| {
2639                if pos == 15 {
2640                    nb.rs.add_or_port("[f0f0::30]:9001".parse().unwrap());
2641                } else if pos == 20 {
2642                    nb.rs.add_or_port("[f0f0::3131]:9001".parse().unwrap());
2643                }
2644            },
2645            None,
2646        )
2647        .unwrap();
2648        let subnet_config = SubnetConfig::default();
2649        let all_family_info = FamilyRules::all_family_info();
2650        let mut dir = PartialNetDir::new(consensus, None);
2651        for md in microdescs.into_iter() {
2652            let wanted = dir.add_microdesc(md.clone());
2653            assert!(wanted);
2654        }
2655        let dir = dir.unwrap_if_sufficient().unwrap();
2656
2657        // Pick out a few relays by ID.
2658        let k0 = Ed25519Identity::from([0; 32]);
2659        let k1 = Ed25519Identity::from([1; 32]);
2660        let k2 = Ed25519Identity::from([2; 32]);
2661        let k3 = Ed25519Identity::from([3; 32]);
2662        let k10 = Ed25519Identity::from([10; 32]);
2663        let k15 = Ed25519Identity::from([15; 32]);
2664        let k20 = Ed25519Identity::from([20; 32]);
2665
2666        let r0 = dir.by_id(&k0).unwrap();
2667        let r1 = dir.by_id(&k1).unwrap();
2668        let r2 = dir.by_id(&k2).unwrap();
2669        let r3 = dir.by_id(&k3).unwrap();
2670        let r10 = dir.by_id(&k10).unwrap();
2671        let r15 = dir.by_id(&k15).unwrap();
2672        let r20 = dir.by_id(&k20).unwrap();
2673
2674        assert_eq!(r0.id(), &[0; 32].into());
2675        assert_eq!(r0.rsa_id(), &[0; 20].into());
2676        assert_eq!(r1.id(), &[1; 32].into());
2677        assert_eq!(r1.rsa_id(), &[1; 20].into());
2678
2679        assert!(r0.same_relay_ids(&r0));
2680        assert!(r1.same_relay_ids(&r1));
2681        assert!(!r1.same_relay_ids(&r0));
2682
2683        assert!(r0.low_level_details().is_dir_cache());
2684        assert!(!r1.low_level_details().is_dir_cache());
2685        assert!(r2.low_level_details().is_dir_cache());
2686        assert!(!r3.low_level_details().is_dir_cache());
2687
2688        assert!(!r0.low_level_details().supports_exit_port_ipv4(80));
2689        assert!(!r1.low_level_details().supports_exit_port_ipv4(80));
2690        assert!(!r2.low_level_details().supports_exit_port_ipv4(80));
2691        assert!(!r3.low_level_details().supports_exit_port_ipv4(80));
2692
2693        assert!(!r0.low_level_details().policies_allow_some_port());
2694        assert!(!r1.low_level_details().policies_allow_some_port());
2695        assert!(!r2.low_level_details().policies_allow_some_port());
2696        assert!(!r3.low_level_details().policies_allow_some_port());
2697        assert!(r10.low_level_details().policies_allow_some_port());
2698
2699        assert!(r0.low_level_details().in_same_family(&r0, all_family_info));
2700        assert!(r0.low_level_details().in_same_family(&r1, all_family_info));
2701        assert!(r1.low_level_details().in_same_family(&r0, all_family_info));
2702        assert!(r1.low_level_details().in_same_family(&r1, all_family_info));
2703        assert!(!r0.low_level_details().in_same_family(&r2, all_family_info));
2704        assert!(!r2.low_level_details().in_same_family(&r0, all_family_info));
2705        assert!(r2.low_level_details().in_same_family(&r2, all_family_info));
2706        assert!(r2.low_level_details().in_same_family(&r3, all_family_info));
2707
2708        assert!(r0.low_level_details().in_same_subnet(&r10, &subnet_config));
2709        assert!(r10.low_level_details().in_same_subnet(&r10, &subnet_config));
2710        assert!(r0.low_level_details().in_same_subnet(&r0, &subnet_config));
2711        assert!(r1.low_level_details().in_same_subnet(&r1, &subnet_config));
2712        assert!(!r1.low_level_details().in_same_subnet(&r2, &subnet_config));
2713        assert!(!r2.low_level_details().in_same_subnet(&r3, &subnet_config));
2714
2715        // Make sure IPv6 families work.
2716        let subnet_config = SubnetConfig {
2717            subnets_family_v4: 128,
2718            subnets_family_v6: 96,
2719        };
2720        assert!(r15.low_level_details().in_same_subnet(&r20, &subnet_config));
2721        assert!(!r15.low_level_details().in_same_subnet(&r1, &subnet_config));
2722
2723        // Make sure that subnet configs can be disabled.
2724        let subnet_config = SubnetConfig {
2725            subnets_family_v4: 255,
2726            subnets_family_v6: 255,
2727        };
2728        assert!(!r15.low_level_details().in_same_subnet(&r20, &subnet_config));
2729    }
2730
2731    #[test]
2732    fn test_badexit() {
2733        // make a netdir where relays 10-19 are badexit, and everybody
2734        // exits to 443 on IPv6.
2735        use tor_netdoc::types::relay_flags::RelayFlag;
2736        let netdir = construct_custom_netdir(|pos, nb, _| {
2737            if (10..20).contains(&pos) {
2738                nb.rs.add_flags(RelayFlag::BadExit);
2739            }
2740            nb.md.parse_ipv6_policy("accept 443").unwrap();
2741        })
2742        .unwrap()
2743        .unwrap_if_sufficient()
2744        .unwrap();
2745
2746        let e12 = netdir.by_id(&Ed25519Identity::from([12; 32])).unwrap();
2747        let e32 = netdir.by_id(&Ed25519Identity::from([32; 32])).unwrap();
2748
2749        assert!(!e12.low_level_details().supports_exit_port_ipv4(80));
2750        assert!(e32.low_level_details().supports_exit_port_ipv4(80));
2751
2752        assert!(!e12.low_level_details().supports_exit_port_ipv6(443));
2753        assert!(e32.low_level_details().supports_exit_port_ipv6(443));
2754        assert!(!e32.low_level_details().supports_exit_port_ipv6(555));
2755
2756        assert!(!e12.low_level_details().policies_allow_some_port());
2757        assert!(e32.low_level_details().policies_allow_some_port());
2758
2759        assert!(!e12.low_level_details().ipv4_policy().allows_some_port());
2760        assert!(!e12.low_level_details().ipv6_policy().allows_some_port());
2761        assert!(e32.low_level_details().ipv4_policy().allows_some_port());
2762        assert!(e32.low_level_details().ipv6_policy().allows_some_port());
2763
2764        assert!(
2765            e12.low_level_details()
2766                .ipv4_declared_policy()
2767                .allows_some_port()
2768        );
2769        assert!(
2770            e12.low_level_details()
2771                .ipv6_declared_policy()
2772                .allows_some_port()
2773        );
2774    }
2775
2776    #[cfg(feature = "experimental-api")]
2777    #[test]
2778    fn test_accessors() {
2779        let netdir = construct_netdir().unwrap_if_sufficient().unwrap();
2780
2781        let r4 = netdir.by_id(&Ed25519Identity::from([4; 32])).unwrap();
2782        let r16 = netdir.by_id(&Ed25519Identity::from([16; 32])).unwrap();
2783
2784        assert!(!r4.md().ipv4_policy().allows_some_port());
2785        assert!(r16.md().ipv4_policy().allows_some_port());
2786
2787        assert!(!r4.rs().is_flagged_exit());
2788        assert!(r16.rs().is_flagged_exit());
2789    }
2790
2791    #[test]
2792    fn test_by_id() {
2793        // Make a netdir that omits the microdescriptor for 0xDDDDDD...
2794        let netdir = construct_custom_netdir(|pos, nb, _| {
2795            nb.omit_md = pos == 13;
2796        })
2797        .unwrap();
2798
2799        let netdir = netdir.unwrap_if_sufficient().unwrap();
2800
2801        let r = netdir.by_id(&Ed25519Identity::from([0; 32])).unwrap();
2802        assert_eq!(r.id().as_bytes(), &[0; 32]);
2803
2804        assert!(netdir.by_id(&Ed25519Identity::from([13; 32])).is_none());
2805
2806        let r = netdir.by_rsa_id(&[12; 20].into()).unwrap();
2807        assert_eq!(r.rsa_id().as_bytes(), &[12; 20]);
2808        assert!(netdir.rsa_id_is_listed(&[12; 20].into()));
2809
2810        assert!(netdir.by_rsa_id(&[13; 20].into()).is_none());
2811
2812        assert!(netdir.by_rsa_id_unchecked(&[99; 20].into()).is_none());
2813        assert!(!netdir.rsa_id_is_listed(&[99; 20].into()));
2814
2815        let r = netdir.by_rsa_id_unchecked(&[13; 20].into()).unwrap();
2816        assert_eq!(r.rs.rsa_identity().as_bytes(), &[13; 20]);
2817        assert!(netdir.rsa_id_is_listed(&[13; 20].into()));
2818
2819        let pair_13_13 = RelayIds::builder()
2820            .ed_identity([13; 32].into())
2821            .rsa_identity([13; 20].into())
2822            .build()
2823            .unwrap();
2824        let pair_14_14 = RelayIds::builder()
2825            .ed_identity([14; 32].into())
2826            .rsa_identity([14; 20].into())
2827            .build()
2828            .unwrap();
2829        let pair_14_99 = RelayIds::builder()
2830            .ed_identity([14; 32].into())
2831            .rsa_identity([99; 20].into())
2832            .build()
2833            .unwrap();
2834
2835        let r = netdir.by_ids(&pair_13_13);
2836        assert!(r.is_none());
2837        let r = netdir.by_ids(&pair_14_14).unwrap();
2838        assert_eq!(r.identity(RelayIdType::Rsa).unwrap().as_bytes(), &[14; 20]);
2839        assert_eq!(
2840            r.identity(RelayIdType::Ed25519).unwrap().as_bytes(),
2841            &[14; 32]
2842        );
2843        let r = netdir.by_ids(&pair_14_99);
2844        assert!(r.is_none());
2845
2846        assert_eq!(
2847            netdir.id_pair_listed(&[13; 32].into(), &[13; 20].into()),
2848            None
2849        );
2850        assert_eq!(
2851            netdir.id_pair_listed(&[15; 32].into(), &[15; 20].into()),
2852            Some(true)
2853        );
2854        assert_eq!(
2855            netdir.id_pair_listed(&[15; 32].into(), &[99; 20].into()),
2856            Some(false)
2857        );
2858    }
2859
2860    #[test]
2861    #[cfg(feature = "hs-common")]
2862    fn test_by_ids_detailed() {
2863        // Make a netdir that omits the microdescriptor for 0xDDDDDD...
2864        let netdir = construct_custom_netdir(|pos, nb, _| {
2865            nb.omit_md = pos == 13;
2866        })
2867        .unwrap();
2868
2869        let netdir = netdir.unwrap_if_sufficient().unwrap();
2870
2871        let id13_13 = RelayIds::builder()
2872            .ed_identity([13; 32].into())
2873            .rsa_identity([13; 20].into())
2874            .build()
2875            .unwrap();
2876        let id15_15 = RelayIds::builder()
2877            .ed_identity([15; 32].into())
2878            .rsa_identity([15; 20].into())
2879            .build()
2880            .unwrap();
2881        let id15_99 = RelayIds::builder()
2882            .ed_identity([15; 32].into())
2883            .rsa_identity([99; 20].into())
2884            .build()
2885            .unwrap();
2886        let id99_15 = RelayIds::builder()
2887            .ed_identity([99; 32].into())
2888            .rsa_identity([15; 20].into())
2889            .build()
2890            .unwrap();
2891        let id99_99 = RelayIds::builder()
2892            .ed_identity([99; 32].into())
2893            .rsa_identity([99; 20].into())
2894            .build()
2895            .unwrap();
2896        let id15_xx = RelayIds::builder()
2897            .ed_identity([15; 32].into())
2898            .build()
2899            .unwrap();
2900        let idxx_15 = RelayIds::builder()
2901            .rsa_identity([15; 20].into())
2902            .build()
2903            .unwrap();
2904
2905        assert!(matches!(netdir.by_ids_detailed(&id13_13), Ok(None)));
2906        assert!(matches!(netdir.by_ids_detailed(&id15_15), Ok(Some(_))));
2907        assert!(matches!(
2908            netdir.by_ids_detailed(&id15_99),
2909            Err(RelayLookupError::Impossible)
2910        ));
2911        assert!(matches!(
2912            netdir.by_ids_detailed(&id99_15),
2913            Err(RelayLookupError::Impossible)
2914        ));
2915        assert!(matches!(netdir.by_ids_detailed(&id99_99), Ok(None)));
2916        assert!(matches!(netdir.by_ids_detailed(&id15_xx), Ok(Some(_))));
2917        assert!(matches!(netdir.by_ids_detailed(&idxx_15), Ok(Some(_))));
2918    }
2919
2920    #[test]
2921    fn weight_type() {
2922        let r0 = RelayWeight(0);
2923        let r100 = RelayWeight(100);
2924        let r200 = RelayWeight(200);
2925        let r300 = RelayWeight(300);
2926        assert_eq!(r100 + r200, r300);
2927        assert_eq!(r100.checked_div(r200), Some(0.5));
2928        assert!(r100.checked_div(r0).is_none());
2929        assert_eq!(r200.ratio(0.5), Some(r100));
2930        assert!(r200.ratio(-1.0).is_none());
2931    }
2932
2933    #[test]
2934    fn weight_accessors() {
2935        // Make a netdir that omits the microdescriptor for 0xDDDDDD...
2936        let netdir = construct_netdir().unwrap_if_sufficient().unwrap();
2937
2938        let g_total = netdir.total_weight(WeightRole::Guard, |r| r.rs.is_flagged_guard());
2939        // This is just the total guard weight, since all our Wxy = 1.
2940        assert_eq!(g_total, RelayWeight(110_000));
2941
2942        let g_total = netdir.total_weight(WeightRole::Guard, |_| false);
2943        assert_eq!(g_total, RelayWeight(0));
2944
2945        let relay = netdir.by_id(&Ed25519Identity::from([35; 32])).unwrap();
2946        assert!(relay.rs.is_flagged_guard());
2947        let w = netdir.relay_weight(&relay, WeightRole::Guard);
2948        assert_eq!(w, RelayWeight(6_000));
2949
2950        let w = netdir
2951            .weight_by_rsa_id(&[33; 20].into(), WeightRole::Guard)
2952            .unwrap();
2953        assert_eq!(w, RelayWeight(4_000));
2954
2955        assert!(
2956            netdir
2957                .weight_by_rsa_id(&[99; 20].into(), WeightRole::Guard)
2958                .is_none()
2959        );
2960    }
2961
2962    #[test]
2963    fn family_list() {
2964        let netdir = construct_custom_netdir(|pos, n, _| {
2965            if pos == 0x0a {
2966                n.md.family(
2967                    "$0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B \
2968                     $0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C \
2969                     $0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D"
2970                        .parse()
2971                        .unwrap(),
2972                );
2973            } else if pos == 0x0c {
2974                n.md.family("$0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A".parse().unwrap());
2975            }
2976        })
2977        .unwrap()
2978        .unwrap_if_sufficient()
2979        .unwrap();
2980
2981        // In the testing netdir, adjacent members are in the same family by default...
2982        let r0 = netdir.by_id(&Ed25519Identity::from([0; 32])).unwrap();
2983        let family: Vec<_> = netdir.known_family_members(&r0).collect();
2984        assert_eq!(family.len(), 1);
2985        assert_eq!(family[0].id(), &Ed25519Identity::from([1; 32]));
2986
2987        // But we've made this relay claim membership with several others.
2988        let r10 = netdir.by_id(&Ed25519Identity::from([10; 32])).unwrap();
2989        let family: HashSet<_> = netdir.known_family_members(&r10).map(|r| *r.id()).collect();
2990        assert_eq!(family.len(), 2);
2991        assert!(family.contains(&Ed25519Identity::from([11; 32])));
2992        assert!(family.contains(&Ed25519Identity::from([12; 32])));
2993        // Note that 13 doesn't get put in, even though it's listed, since it doesn't claim
2994        //  membership with 10.
2995    }
2996    #[test]
2997    #[cfg(feature = "geoip")]
2998    fn relay_has_country_code() {
2999        let src_v6 = r#"
3000        fe80:dead:beef::,fe80:dead:ffff::,US
3001        fe80:feed:eeee::1,fe80:feed:eeee::1,AT
3002        fe80:feed:eeee::2,fe80:feed:ffff::,DE
3003        "#;
3004        let db = GeoipDb::new_from_legacy_format("", src_v6, true).unwrap();
3005
3006        let netdir = construct_custom_netdir_with_geoip(
3007            |pos, n, _| {
3008                if pos == 0x01 {
3009                    n.rs.add_or_port("[fe80:dead:beef::1]:42".parse().unwrap());
3010                }
3011                if pos == 0x02 {
3012                    n.rs.add_or_port("[fe80:feed:eeee::1]:42".parse().unwrap());
3013                    n.rs.add_or_port("[fe80:feed:eeee::2]:42".parse().unwrap());
3014                }
3015                if pos == 0x03 {
3016                    n.rs.add_or_port("[fe80:dead:beef::1]:42".parse().unwrap());
3017                    n.rs.add_or_port("[fe80:dead:beef::2]:42".parse().unwrap());
3018                }
3019            },
3020            &db,
3021        )
3022        .unwrap()
3023        .unwrap_if_sufficient()
3024        .unwrap();
3025
3026        // No GeoIP data available -> None
3027        let r0 = netdir.by_id(&Ed25519Identity::from([0; 32])).unwrap();
3028        assert_eq!(r0.cc, None);
3029
3030        // Exactly one match -> Some
3031        let r1 = netdir.by_id(&Ed25519Identity::from([1; 32])).unwrap();
3032        assert_eq!(r1.cc.as_ref().map(|x| x.as_ref()), Some("US"));
3033
3034        // Conflicting matches -> None
3035        let r2 = netdir.by_id(&Ed25519Identity::from([2; 32])).unwrap();
3036        assert_eq!(r2.cc, None);
3037
3038        // Multiple agreeing matches -> Some
3039        let r3 = netdir.by_id(&Ed25519Identity::from([3; 32])).unwrap();
3040        assert_eq!(r3.cc.as_ref().map(|x| x.as_ref()), Some("US"));
3041    }
3042
3043    #[test]
3044    #[cfg(feature = "hs-common")]
3045    #[allow(deprecated)]
3046    fn hs_dirs_selection() {
3047        use tor_basic_utils::test_rng::testing_rng;
3048
3049        const HSDIR_SPREAD_STORE: i32 = 6;
3050        const HSDIR_SPREAD_FETCH: i32 = 2;
3051        const PARAMS: [(&str, i32); 2] = [
3052            ("hsdir_spread_store", HSDIR_SPREAD_STORE),
3053            ("hsdir_spread_fetch", HSDIR_SPREAD_FETCH),
3054        ];
3055
3056        let netdir: Arc<NetDir> =
3057            crate::testnet::construct_custom_netdir_with_params(|_, _, _| {}, PARAMS, None)
3058                .unwrap()
3059                .unwrap_if_sufficient()
3060                .unwrap()
3061                .into();
3062        let hsid = dummy_hs_blind_id();
3063
3064        const OP_RELAY_COUNT: &[(HsDirOp, usize)] = &[
3065            // We can't upload to (hsdir_n_replicas * hsdir_spread_store) = 12, relays because there
3066            // are only 10 relays with the HsDir flag in the consensus.
3067            #[cfg(feature = "hs-service")]
3068            (HsDirOp::Upload, 10),
3069            (HsDirOp::Download, 4),
3070        ];
3071
3072        for (op, relay_count) in OP_RELAY_COUNT {
3073            let relays = netdir.hs_dirs(&hsid, *op, &mut testing_rng());
3074
3075            assert_eq!(relays.len(), *relay_count);
3076
3077            // There should be no duplicates (the filtering function passed to
3078            // HsDirRing::ring_items_at() ensures the relays that are already in use for
3079            // lower-numbered replicas aren't considered a second time for a higher-numbered
3080            // replica).
3081            let unique = relays
3082                .iter()
3083                .map(|relay| relay.ed_identity())
3084                .collect::<HashSet<_>>();
3085            assert_eq!(unique.len(), relays.len());
3086        }
3087
3088        // TODO: come up with a test that checks that HsDirRing::ring_items_at() skips over the
3089        // expected relays.
3090        //
3091        // For example, let's say we have the following hsdir ring:
3092        //
3093        //         A  -  B
3094        //        /       \
3095        //       F         C
3096        //        \       /
3097        //         E  -  D
3098        //
3099        // Let's also assume that:
3100        //
3101        //   * hsdir_spread_store = 3
3102        //   * the ordering of the relays on the ring is [A, B, C, D, E, F]
3103        //
3104        // If we use relays [A, B, C] for replica 1, and hs_index(2) = E, then replica 2 _must_ get
3105        // relays [E, F, D]. We should have a test that checks this.
3106    }
3107
3108    #[test]
3109    fn zero_weights() {
3110        // Here we check the behavior of IndexedRandom::choose_weighted
3111        // in the presence of items whose weight is 0.
3112        //
3113        // We think that the behavior is:
3114        //   - If all items have weight 0, choose_weighted returns an error.
3115        //   - If any items have non-zero weight, one of them will be returned.
3116        let items = vec![1, 2, 3];
3117        let mut rng = testing_rng();
3118
3119        let a = items.choose_weighted(&mut rng, |_| 0);
3120        assert!(matches!(a, Err(WeightError::InsufficientNonZero)));
3121
3122        let only_one = |n: &i32| if *n == 1 { 1 } else { 0 };
3123        for _ in 0..100 {
3124            let a = items.choose_weighted(&mut rng, only_one);
3125            assert_eq!(a.unwrap(), &1);
3126        }
3127    }
3128}