tor_netdir/
lib.rs

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