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
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
38
#![allow(clippy::uninlined_format_args)]
39
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
40
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
41
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
42
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
43

            
44
pub mod details;
45
mod err;
46
#[cfg(feature = "hs-common")]
47
mod hsdir_params;
48
#[cfg(feature = "hs-common")]
49
mod hsdir_ring;
50
pub mod params;
51
mod weight;
52

            
53
#[cfg(any(test, feature = "testing"))]
54
pub mod testnet;
55
#[cfg(feature = "testing")]
56
pub mod testprovider;
57

            
58
#[cfg(feature = "hs-service")]
59
use itertools::chain;
60
use static_assertions::const_assert;
61
use tor_linkspec::{
62
    ChanTarget, DirectChanMethodsHelper, HasAddrs, HasRelayIds, RelayIdRef, RelayIdType,
63
};
64
use tor_llcrypto as ll;
65
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
66
use tor_netdoc::doc::microdesc::{MdDigest, Microdesc};
67
use tor_netdoc::doc::netstatus::{self, MdConsensus, MdConsensusRouterStatus, RouterStatus};
68
#[cfg(feature = "hs-common")]
69
use {hsdir_ring::HsDirRing, std::iter};
70

            
71
use derive_more::{From, Into};
72
use futures::stream::BoxStream;
73
use num_enum::{IntoPrimitive, TryFromPrimitive};
74
use rand::seq::SliceRandom;
75
use serde::Deserialize;
76
use std::collections::HashMap;
77
use std::net::IpAddr;
78
use std::ops::Deref;
79
use std::sync::Arc;
80
use strum::{EnumCount, EnumIter};
81
use tracing::warn;
82
use typed_index_collections::{TiSlice, TiVec};
83

            
84
#[cfg(feature = "hs-common")]
85
use {
86
    itertools::Itertools,
87
    std::collections::HashSet,
88
    tor_error::{internal, Bug},
89
    tor_hscrypto::{pk::HsBlindId, time::TimePeriod},
90
};
91

            
92
pub use err::Error;
93
pub use weight::WeightRole;
94
/// A Result using the Error type from the tor-netdir crate
95
pub type Result<T> = std::result::Result<T, Error>;
96

            
97
#[cfg(feature = "hs-common")]
98
pub use err::OnionDirLookupError;
99

            
100
use params::NetParameters;
101
#[cfg(feature = "geoip")]
102
use tor_geoip::{CountryCode, GeoipDb, HasCountryCode};
103

            
104
#[cfg(feature = "hs-common")]
105
#[cfg_attr(docsrs, doc(cfg(feature = "hs-common")))]
106
pub use hsdir_params::HsDirParams;
107

            
108
/// Index into the consensus relays
109
///
110
/// This is an index into the list of relays returned by
111
/// [`.c_relays()`](ConsensusRelays::c_relays)
112
/// (on the corresponding consensus or netdir).
113
///
114
/// This is just a `usize` inside, but using a newtype prevents getting a relay index
115
/// confused with other kinds of slice indices or counts.
116
///
117
/// If you are in a part of the code which needs to work with multiple consensuses,
118
/// the typechecking cannot tell if you try to index into the wrong consensus.
119
#[derive(Debug, From, Into, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
120
pub(crate) struct RouterStatusIdx(usize);
121

            
122
/// Extension trait to provide index-type-safe `.c_relays()` method
123
//
124
// TODO: Really it would be better to have MdConsensns::relays() return TiSlice,
125
// but that would be an API break there.
126
pub(crate) trait ConsensusRelays {
127
    /// Obtain the list of relays in the consensus
128
    //
129
    fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdConsensusRouterStatus>;
130
}
131
impl ConsensusRelays for MdConsensus {
132
31425062
    fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdConsensusRouterStatus> {
133
31425062
        TiSlice::from_ref(MdConsensus::relays(self))
134
31425062
    }
135
}
136
impl ConsensusRelays for NetDir {
137
31358546
    fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdConsensusRouterStatus> {
138
31358546
        self.consensus.c_relays()
139
31358546
    }
140
}
141

            
142
/// Configuration for determining when two relays have addresses "too close" in
143
/// the network.
144
///
145
/// Used by [`Relay::low_level_details().in_same_subnet()`].
146
#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
147
#[serde(deny_unknown_fields)]
148
pub struct SubnetConfig {
149
    /// Consider IPv4 nodes in the same /x to be the same family.
150
    ///
151
    /// If this value is 0, all nodes with IPv4 addresses will be in the
152
    /// same family.  If this value is above 32, then no nodes will be
153
    /// placed im the same family based on their IPv4 addresses.
154
    subnets_family_v4: u8,
155
    /// Consider IPv6 nodes in the same /x to be the same family.
156
    ///
157
    /// If this value is 0, all nodes with IPv6 addresses will be in the
158
    /// same family.  If this value is above 128, then no nodes will be
159
    /// placed im the same family based on their IPv6 addresses.
160
    subnets_family_v6: u8,
161
}
162

            
163
impl Default for SubnetConfig {
164
323564
    fn default() -> Self {
165
323564
        Self::new(16, 32)
166
323564
    }
167
}
168

            
169
impl SubnetConfig {
170
    /// Construct a new SubnetConfig from a pair of bit prefix lengths.
171
    ///
172
    /// The values are clamped to the appropriate ranges if they are
173
    /// out-of-bounds.
174
988124
    pub fn new(subnets_family_v4: u8, subnets_family_v6: u8) -> Self {
175
988124
        Self {
176
988124
            subnets_family_v4,
177
988124
            subnets_family_v6,
178
988124
        }
179
988124
    }
180

            
181
    /// Construct a new SubnetConfig such that addresses are not in the same
182
    /// family with anything--not even with themselves.
183
1996602
    pub fn no_addresses_match() -> SubnetConfig {
184
1996602
        SubnetConfig {
185
1996602
            subnets_family_v4: 33,
186
1996602
            subnets_family_v6: 129,
187
1996602
        }
188
1996602
    }
189

            
190
    /// Return true if the two addresses in the same subnet, according to this
191
    /// configuration.
192
27703788
    pub fn addrs_in_same_subnet(&self, a: &IpAddr, b: &IpAddr) -> bool {
193
27703788
        match (a, b) {
194
27703756
            (IpAddr::V4(a), IpAddr::V4(b)) => {
195
27703756
                let bits = self.subnets_family_v4;
196
27703756
                if bits > 32 {
197
2968
                    return false;
198
27700788
                }
199
27700788
                let a = u32::from_be_bytes(a.octets());
200
27700788
                let b = u32::from_be_bytes(b.octets());
201
27700788
                (a >> (32 - bits)) == (b >> (32 - bits))
202
            }
203
20
            (IpAddr::V6(a), IpAddr::V6(b)) => {
204
20
                let bits = self.subnets_family_v6;
205
20
                if bits > 128 {
206
4
                    return false;
207
16
                }
208
16
                let a = u128::from_be_bytes(a.octets());
209
16
                let b = u128::from_be_bytes(b.octets());
210
16
                (a >> (128 - bits)) == (b >> (128 - bits))
211
            }
212
12
            _ => false,
213
        }
214
27703788
    }
215

            
216
    /// Return true if any of the addresses in `a` shares a subnet with any of
217
    /// the addresses in `b`, according to this configuration.
218
27703738
    pub fn any_addrs_in_same_subnet<T, U>(&self, a: &T, b: &U) -> bool
219
27703738
    where
220
27703738
        T: tor_linkspec::HasAddrs,
221
27703738
        U: tor_linkspec::HasAddrs,
222
27703738
    {
223
27703744
        a.addrs().iter().any(|aa| {
224
27703744
            b.addrs()
225
27703744
                .iter()
226
27703752
                .any(|bb| self.addrs_in_same_subnet(&aa.ip(), &bb.ip()))
227
27703744
        })
228
27703738
    }
229

            
230
    /// Return a new subnet configuration that is the union of `self` and
231
    /// `other`.
232
    ///
233
    /// That is, return a subnet configuration that puts all addresses in the
234
    /// same subnet if and only if at least one of `self` and `other` would put
235
    /// them in the same subnet.
236
998486
    pub fn union(&self, other: &Self) -> Self {
237
998486
        use std::cmp::min;
238
998486
        Self {
239
998486
            subnets_family_v4: min(self.subnets_family_v4, other.subnets_family_v4),
240
998486
            subnets_family_v6: min(self.subnets_family_v6, other.subnets_family_v6),
241
998486
        }
242
998486
    }
243
}
244

            
245
/// An opaque type representing the weight with which a relay or set of
246
/// relays will be selected for a given role.
247
///
248
/// Most users should ignore this type, and just use pick_relay instead.
249
#[derive(
250
    Copy,
251
    Clone,
252
    Debug,
253
    derive_more::Add,
254
    derive_more::Sum,
255
    derive_more::AddAssign,
256
    Eq,
257
    PartialEq,
258
    Ord,
259
    PartialOrd,
260
)]
261
pub struct RelayWeight(u64);
262

            
263
impl RelayWeight {
264
    /// Try to divide this weight by `rhs`.
265
    ///
266
    /// Return a ratio on success, or None on division-by-zero.
267
2084
    pub fn checked_div(&self, rhs: RelayWeight) -> Option<f64> {
268
2084
        if rhs.0 == 0 {
269
2
            None
270
        } else {
271
2082
            Some((self.0 as f64) / (rhs.0 as f64))
272
        }
273
2084
    }
274

            
275
    /// Compute a ratio `frac` of this weight.
276
    ///
277
    /// Return None if frac is less than zero, since negative weights
278
    /// are impossible.
279
21684
    pub fn ratio(&self, frac: f64) -> Option<RelayWeight> {
280
21684
        let product = (self.0 as f64) * frac;
281
21684
        if product >= 0.0 && product.is_finite() {
282
21682
            Some(RelayWeight(product as u64))
283
        } else {
284
2
            None
285
        }
286
21684
    }
287
}
288

            
289
impl From<u64> for RelayWeight {
290
4160
    fn from(val: u64) -> Self {
291
4160
        RelayWeight(val)
292
4160
    }
293
}
294

            
295
/// An operation for which we might be requesting a hidden service directory.
296
#[derive(Copy, Clone, Debug, PartialEq)]
297
// TODO: make this pub(crate) once NetDir::hs_dirs is removed
298
#[non_exhaustive]
299
pub enum HsDirOp {
300
    /// Uploading an onion service descriptor.
301
    #[cfg(feature = "hs-service")]
302
    Upload,
303
    /// Downloading an onion service descriptor.
304
    Download,
305
}
306

            
307
/// A view of the Tor directory, suitable for use in building circuits.
308
///
309
/// Abstractly, a [`NetDir`] is a set of usable public [`Relay`]s, each of which
310
/// has its own properties, identity, and correct weighted probability for use
311
/// under different circumstances.
312
///
313
/// A [`NetDir`] is constructed by making a [`PartialNetDir`] from a consensus
314
/// document, and then adding enough microdescriptors to that `PartialNetDir` so
315
/// that it can be used to build paths. (Thus, if you have a NetDir, it is
316
/// definitely adequate to build paths.)
317
///
318
/// # "Usable" relays
319
///
320
/// Many methods on NetDir are defined in terms of <a name="usable">"Usable"</a> relays.  Unless
321
/// otherwise stated, a relay is "usable" if it is listed in the consensus,
322
/// if we have full directory information for that relay (including a
323
/// microdescriptor), and if that relay does not have any flags indicating that
324
/// we should never use it. (Currently, `NoEdConsensus` is the only such flag.)
325
///
326
/// # Limitations
327
///
328
/// The current NetDir implementation assumes fairly strongly that every relay
329
/// has an Ed25519 identity and an RSA identity, that the consensus is indexed
330
/// by RSA identities, and that the Ed25519 identities are stored in
331
/// microdescriptors.
332
///
333
/// If these assumptions someday change, then we'll have to revise the
334
/// implementation.
335
#[derive(Debug, Clone)]
336
pub struct NetDir {
337
    /// A microdescriptor consensus that lists the members of the network,
338
    /// and maps each one to a 'microdescriptor' that has more information
339
    /// about it
340
    consensus: Arc<MdConsensus>,
341
    /// A map from keys to integer values, distributed in the consensus,
342
    /// and clamped to certain defaults.
343
    params: NetParameters,
344
    /// Map from routerstatus index, to that routerstatus's microdescriptor (if we have one.)
345
    mds: TiVec<RouterStatusIdx, Option<Arc<Microdesc>>>,
346
    /// Map from SHA256 of _missing_ microdescriptors to the index of their
347
    /// corresponding routerstatus.
348
    rsidx_by_missing: HashMap<MdDigest, RouterStatusIdx>,
349
    /// Map from ed25519 identity to index of the routerstatus.
350
    ///
351
    /// Note that we don't know the ed25519 identity of a relay until
352
    /// we get the microdescriptor for it, so this won't be filled in
353
    /// until we get the microdescriptors.
354
    ///
355
    /// # Implementation note
356
    ///
357
    /// For this field, and for `rsidx_by_rsa`,
358
    /// it might be cool to have references instead.
359
    /// But that would make this into a self-referential structure,
360
    /// which isn't possible in safe rust.
361
    rsidx_by_ed: HashMap<Ed25519Identity, RouterStatusIdx>,
362
    /// Map from RSA identity to index of the routerstatus.
363
    ///
364
    /// This is constructed at the same time as the NetDir object, so it
365
    /// can be immutable.
366
    rsidx_by_rsa: Arc<HashMap<RsaIdentity, RouterStatusIdx>>,
367

            
368
    /// Hash ring(s) describing the onion service directory.
369
    ///
370
    /// This is empty in a PartialNetDir, and is filled in before the NetDir is
371
    /// built.
372
    //
373
    // TODO hs: It is ugly to have this exist in a partially constructed state
374
    // in a PartialNetDir.
375
    // Ideally, a PartialNetDir would contain only an HsDirs<HsDirParams>,
376
    // or perhaps nothing at all, here.
377
    #[cfg(feature = "hs-common")]
378
    hsdir_rings: Arc<HsDirs<HsDirRing>>,
379

            
380
    /// Weight values to apply to a given relay when deciding how frequently
381
    /// to choose it for a given role.
382
    weights: weight::WeightSet,
383

            
384
    #[cfg(feature = "geoip")]
385
    /// Country codes for each router in our consensus.
386
    ///
387
    /// This is indexed by the `RouterStatusIdx` (i.e. a router idx of zero has
388
    /// the country code at position zero in this array).
389
    country_codes: Vec<Option<CountryCode>>,
390
}
391

            
392
/// Collection of hidden service directories (or parameters for them)
393
///
394
/// In [`NetDir`] this is used to store the actual hash rings.
395
/// (But, in a NetDir in a [`PartialNetDir`], it contains [`HsDirRing`]s
396
/// where only the `params` are populated, and the `ring` is empty.)
397
///
398
/// This same generic type is used as the return type from
399
/// [`HsDirParams::compute`](HsDirParams::compute),
400
/// where it contains the *parameters* for the primary and secondary rings.
401
#[derive(Debug, Clone)]
402
#[cfg(feature = "hs-common")]
403
pub(crate) struct HsDirs<D> {
404
    /// The current ring
405
    ///
406
    /// It corresponds to the time period containing the `valid-after` time in
407
    /// the consensus. Its SRV is whatever SRV was most current at the time when
408
    /// that time period began.
409
    ///
410
    /// This is the hash ring that we should use whenever we are fetching an
411
    /// onion service descriptor.
412
    current: D,
413

            
414
    /// Secondary rings (based on the parameters for the previous and next time periods)
415
    ///
416
    /// Onion services upload to positions on these ring as well, based on how
417
    /// far into the current time period this directory is, so that
418
    /// not-synchronized clients can still find their descriptor.
419
    ///
420
    /// Note that with the current (2023) network parameters, with
421
    /// `hsdir_interval = SRV lifetime = 24 hours` at most one of these
422
    /// secondary rings will be active at a time.  We have two here in order
423
    /// to conform with a more flexible regime in proposal 342.
424
    //
425
    // TODO: hs clients never need this; so I've made it not-present for thm.
426
    // But does that risk too much with respect to side channels?
427
    //
428
    // TODO: Perhaps we should refactor this so that it is clear that these
429
    // are immutable?  On the other hand, the documentation for this type
430
    // declares that it is immutable, so we are likely okay.
431
    //
432
    // TODO: this `Vec` is only ever 0,1,2 elements.
433
    // Maybe it should be an ArrayVec or something.
434
    #[cfg(feature = "hs-service")]
435
    secondary: Vec<D>,
436
}
437

            
438
#[cfg(feature = "hs-common")]
439
impl<D> HsDirs<D> {
440
    /// Convert an `HsDirs<D>` to `HsDirs<D2>` by mapping each contained `D`
441
13186
    pub(crate) fn map<D2>(self, mut f: impl FnMut(D) -> D2) -> HsDirs<D2> {
442
13186
        HsDirs {
443
13186
            current: f(self.current),
444
13186
            #[cfg(feature = "hs-service")]
445
13186
            secondary: self.secondary.into_iter().map(f).collect(),
446
13186
        }
447
13186
    }
448

            
449
    /// Iterate over some of the contained hsdirs, according to `secondary`
450
    ///
451
    /// The current ring is always included.
452
    /// Secondary rings are included iff `secondary` and the `hs-service` feature is enabled.
453
644
    fn iter_filter_secondary(&self, secondary: bool) -> impl Iterator<Item = &D> {
454
644
        let i = iter::once(&self.current);
455
644

            
456
644
        // With "hs-service" disabled, there are no secondary rings,
457
644
        // so we don't care.
458
644
        let _ = secondary;
459
644

            
460
644
        #[cfg(feature = "hs-service")]
461
644
        let i = chain!(i, self.secondary.iter().filter(move |_| secondary));
462
644

            
463
644
        i
464
644
    }
465

            
466
    /// Iterate over all the contained hsdirs
467
640
    pub(crate) fn iter(&self) -> impl Iterator<Item = &D> {
468
640
        self.iter_filter_secondary(true)
469
640
    }
470

            
471
    /// Iterate over the hsdirs relevant for `op`
472
4
    pub(crate) fn iter_for_op(&self, op: HsDirOp) -> impl Iterator<Item = &D> {
473
4
        self.iter_filter_secondary(match op {
474
            #[cfg(feature = "hs-service")]
475
2
            HsDirOp::Upload => true,
476
2
            HsDirOp::Download => false,
477
        })
478
4
    }
479
}
480

            
481
/// An event that a [`NetDirProvider`] can broadcast to indicate that a change in
482
/// the status of its directory.
483
#[derive(
484
35099
    Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount, IntoPrimitive, TryFromPrimitive,
485
)]
486
#[non_exhaustive]
487
#[repr(u16)]
488
pub enum DirEvent {
489
    /// A new consensus has been received, and has enough information to be
490
    /// used.
491
    ///
492
    /// This event is also broadcast when a new set of consensus parameters is
493
    /// available, even if that set of parameters comes from a configuration
494
    /// change rather than from the latest consensus.
495
    NewConsensus,
496

            
497
    /// New descriptors have been received for the current consensus.
498
    ///
499
    /// (This event is _not_ broadcast when receiving new descriptors for a
500
    /// consensus which is not yet ready to replace the current consensus.)
501
    NewDescriptors,
502
}
503

            
504
/// How "timely" must a network directory be?
505
///
506
/// This enum is used as an argument when requesting a [`NetDir`] object from
507
/// [`NetDirProvider`] and other APIs, to specify how recent the information
508
/// must be in order to be useful.
509
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
510
#[allow(clippy::exhaustive_enums)]
511
pub enum Timeliness {
512
    /// The network directory must be strictly timely.
513
    ///
514
    /// That is, it must be based on a consensus that valid right now, with no
515
    /// tolerance for skew or consensus problems.
516
    ///
517
    /// Avoid using this option if you could use [`Timeliness::Timely`] instead.
518
    Strict,
519
    /// The network directory must be roughly timely.
520
    ///
521
    /// This is, it must be be based on a consensus that is not _too_ far in the
522
    /// future, and not _too_ far in the past.
523
    ///
524
    /// (The tolerances for "too far" will depend on configuration.)
525
    ///
526
    /// This is almost always the option that you want to use.
527
    Timely,
528
    /// Any network directory is permissible, regardless of how untimely.
529
    ///
530
    /// Avoid using this option if you could use [`Timeliness::Timely`] instead.
531
    Unchecked,
532
}
533

            
534
/// An object that can provide [`NetDir`]s, as well as inform consumers when
535
/// they might have changed.
536
///
537
/// It is the responsibility of the implementor of `NetDirProvider`
538
/// to try to obtain an up-to-date `NetDir`,
539
/// and continuously to maintain and update it.
540
///
541
/// In usual configurations, Arti uses `tor_dirmgr::DirMgr`
542
/// as its `NetDirProvider`.
543
pub trait NetDirProvider: UpcastArcNetDirProvider + Send + Sync {
544
    /// Return a network directory that's live according to the provided
545
    /// `timeliness`.
546
    fn netdir(&self, timeliness: Timeliness) -> Result<Arc<NetDir>>;
547

            
548
    /// Return a reasonable netdir for general usage.
549
    ///
550
    /// This is an alias for
551
    /// [`NetDirProvider::netdir`]`(`[`Timeliness::Timely`]`)`.
552
684
    fn timely_netdir(&self) -> Result<Arc<NetDir>> {
553
684
        self.netdir(Timeliness::Timely)
554
684
    }
555

            
556
    /// Return a new asynchronous stream that will receive notification
557
    /// whenever the consensus has changed.
558
    ///
559
    /// Multiple events may be batched up into a single item: each time
560
    /// this stream yields an event, all you can assume is that the event has
561
    /// occurred at least once.
562
    fn events(&self) -> BoxStream<'static, DirEvent>;
563

            
564
    /// Return the latest network parameters.
565
    ///
566
    /// If we have no directory, return a reasonable set of defaults.
567
    fn params(&self) -> Arc<dyn AsRef<NetParameters>>;
568
}
569

            
570
impl<T> NetDirProvider for Arc<T>
571
where
572
    T: NetDirProvider,
573
{
574
38
    fn netdir(&self, timeliness: Timeliness) -> Result<Arc<NetDir>> {
575
38
        self.deref().netdir(timeliness)
576
38
    }
577

            
578
    fn timely_netdir(&self) -> Result<Arc<NetDir>> {
579
        self.deref().timely_netdir()
580
    }
581

            
582
48
    fn events(&self) -> BoxStream<'static, DirEvent> {
583
48
        self.deref().events()
584
48
    }
585

            
586
10
    fn params(&self) -> Arc<dyn AsRef<NetParameters>> {
587
10
        self.deref().params()
588
10
    }
589
}
590

            
591
/// Helper trait: allows any `Arc<X>` to be upcast to a `Arc<dyn
592
/// NetDirProvider>` if X is an implementation or supertrait of NetDirProvider.
593
///
594
/// This trait exists to work around a limitation in rust: when trait upcasting
595
/// coercion is stable, this will be unnecessary.
596
///
597
/// The Rust tracking issue is <https://github.com/rust-lang/rust/issues/65991>.
598
pub trait UpcastArcNetDirProvider {
599
    /// Return a view of this object as an `Arc<dyn NetDirProvider>`
600
    fn upcast_arc<'a>(self: Arc<Self>) -> Arc<dyn NetDirProvider + 'a>
601
    where
602
        Self: 'a;
603
}
604

            
605
impl<T> UpcastArcNetDirProvider for T
606
where
607
    T: NetDirProvider + Sized,
608
{
609
38
    fn upcast_arc<'a>(self: Arc<Self>) -> Arc<dyn NetDirProvider + 'a>
610
38
    where
611
38
        Self: 'a,
612
38
    {
613
38
        self
614
38
    }
615
}
616

            
617
impl AsRef<NetParameters> for NetDir {
618
760
    fn as_ref(&self) -> &NetParameters {
619
760
        self.params()
620
760
    }
621
}
622

            
623
/// A partially build NetDir -- it can't be unwrapped until it has
624
/// enough information to build safe paths.
625
#[derive(Debug, Clone)]
626
pub struct PartialNetDir {
627
    /// The netdir that's under construction.
628
    netdir: NetDir,
629

            
630
    /// The previous netdir, if we had one
631
    ///
632
    /// Used as a cache, so we can reuse information
633
    #[cfg(feature = "hs-common")]
634
    prev_netdir: Option<Arc<NetDir>>,
635
}
636

            
637
/// A view of a relay on the Tor network, suitable for building circuits.
638
// TODO: This should probably be a more specific struct, with a trait
639
// that implements it.
640
#[derive(Clone)]
641
pub struct Relay<'a> {
642
    /// A router descriptor for this relay.
643
    rs: &'a netstatus::MdConsensusRouterStatus,
644
    /// A microdescriptor for this relay.
645
    md: &'a Microdesc,
646
    /// The country code this relay is in, if we know one.
647
    #[cfg(feature = "geoip")]
648
    cc: Option<CountryCode>,
649
}
650

            
651
/// A relay that we haven't checked for validity or usability in
652
/// routing.
653
#[derive(Debug)]
654
pub struct UncheckedRelay<'a> {
655
    /// A router descriptor for this relay.
656
    rs: &'a netstatus::MdConsensusRouterStatus,
657
    /// A microdescriptor for this relay, if there is one.
658
    md: Option<&'a Microdesc>,
659
    /// The country code this relay is in, if we know one.
660
    #[cfg(feature = "geoip")]
661
    cc: Option<CountryCode>,
662
}
663

            
664
/// A partial or full network directory that we can download
665
/// microdescriptors for.
666
pub trait MdReceiver {
667
    /// Return an iterator over the digests for all of the microdescriptors
668
    /// that this netdir is missing.
669
    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_>;
670
    /// Add a microdescriptor to this netdir, if it was wanted.
671
    ///
672
    /// Return true if it was indeed wanted.
673
    fn add_microdesc(&mut self, md: Microdesc) -> bool;
674
    /// Return the number of missing microdescriptors.
675
    fn n_missing(&self) -> usize;
676
}
677

            
678
impl PartialNetDir {
679
    /// Create a new PartialNetDir with a given consensus, and no
680
    /// microdescriptors loaded.
681
    ///
682
    /// If `replacement_params` is provided, override network parameters from
683
    /// the consensus with those from `replacement_params`.
684
6516
    pub fn new(
685
6516
        consensus: MdConsensus,
686
6516
        replacement_params: Option<&netstatus::NetParams<i32>>,
687
6516
    ) -> Self {
688
6516
        Self::new_inner(
689
6516
            consensus,
690
6516
            replacement_params,
691
6516
            #[cfg(feature = "geoip")]
692
6516
            None,
693
6516
        )
694
6516
    }
695

            
696
    /// Create a new PartialNetDir with GeoIP support.
697
    ///
698
    /// This does the same thing as `new()`, except the provided GeoIP database is used to add
699
    /// country codes to relays.
700
    #[cfg(feature = "geoip")]
701
    #[cfg_attr(docsrs, doc(cfg(feature = "geoip")))]
702
122
    pub fn new_with_geoip(
703
122
        consensus: MdConsensus,
704
122
        replacement_params: Option<&netstatus::NetParams<i32>>,
705
122
        geoip_db: &GeoipDb,
706
122
    ) -> Self {
707
122
        Self::new_inner(consensus, replacement_params, Some(geoip_db))
708
122
    }
709

            
710
    /// Implementation of the `new()` functions.
711
6638
    fn new_inner(
712
6638
        consensus: MdConsensus,
713
6638
        replacement_params: Option<&netstatus::NetParams<i32>>,
714
6638
        #[cfg(feature = "geoip")] geoip_db: Option<&GeoipDb>,
715
6638
    ) -> Self {
716
6638
        let mut params = NetParameters::default();
717
6638

            
718
6638
        // (We ignore unrecognized options here, since they come from
719
6638
        // the consensus, and we don't expect to recognize everything
720
6638
        // there.)
721
6638
        let _ = params.saturating_update(consensus.params().iter());
722

            
723
        // Now see if the user has any parameters to override.
724
        // (We have to do this now, or else changes won't be reflected in our
725
        // weights.)
726
6638
        if let Some(replacement) = replacement_params {
727
6626
            for u in params.saturating_update(replacement.iter()) {
728
2
                warn!("Unrecognized option: override_net_params.{}", u);
729
            }
730
12
        }
731

            
732
        // Compute the weights we'll want to use for these relays.
733
6638
        let weights = weight::WeightSet::from_consensus(&consensus, &params);
734
6638

            
735
6638
        let n_relays = consensus.c_relays().len();
736
6638

            
737
6638
        let rsidx_by_missing = consensus
738
6638
            .c_relays()
739
6638
            .iter_enumerated()
740
247504
            .map(|(rsidx, rs)| (*rs.md_digest(), rsidx))
741
6638
            .collect();
742
6638

            
743
6638
        let rsidx_by_rsa = consensus
744
6638
            .c_relays()
745
6638
            .iter_enumerated()
746
247504
            .map(|(rsidx, rs)| (*rs.rsa_identity(), rsidx))
747
6638
            .collect();
748

            
749
        #[cfg(feature = "geoip")]
750
6638
        let country_codes = if let Some(db) = geoip_db {
751
122
            consensus
752
122
                .c_relays()
753
122
                .iter()
754
644
                .map(|rs| {
755
640
                    let ret = db
756
890
                        .lookup_country_code_multi(rs.addrs().iter().map(|x| x.ip()))
757
640
                        .cloned();
758
640
                    ret
759
644
                })
760
122
                .collect()
761
        } else {
762
6516
            Default::default()
763
        };
764

            
765
        #[cfg(feature = "hs-common")]
766
6638
        let hsdir_rings = Arc::new({
767
6638
            let params = HsDirParams::compute(&consensus, &params).expect("Invalid consensus!");
768
6638
            // TODO: It's a bit ugly to use expect above, but this function does
769
6638
            // not return a Result. On the other hand, the error conditions under which
770
6638
            // HsDirParams::compute can return Err are _very_ narrow and hard to
771
6638
            // hit; see documentation in that function.  As such, we probably
772
6638
            // don't need to have this return a Result.
773
6638

            
774
6638
            params.map(HsDirRing::empty_from_params)
775
6638
        });
776
6638

            
777
6638
        let netdir = NetDir {
778
6638
            consensus: Arc::new(consensus),
779
6638
            params,
780
6638
            mds: vec![None; n_relays].into(),
781
6638
            rsidx_by_missing,
782
6638
            rsidx_by_rsa: Arc::new(rsidx_by_rsa),
783
6638
            rsidx_by_ed: HashMap::with_capacity(n_relays),
784
6638
            #[cfg(feature = "hs-common")]
785
6638
            hsdir_rings,
786
6638
            weights,
787
6638
            #[cfg(feature = "geoip")]
788
6638
            country_codes,
789
6638
        };
790
6638

            
791
6638
        PartialNetDir {
792
6638
            netdir,
793
6638
            #[cfg(feature = "hs-common")]
794
6638
            prev_netdir: None,
795
6638
        }
796
6638
    }
797

            
798
    /// Return the declared lifetime of this PartialNetDir.
799
2
    pub fn lifetime(&self) -> &netstatus::Lifetime {
800
2
        self.netdir.lifetime()
801
2
    }
802

            
803
    /// Record a previous netdir, which can be used for reusing cached information
804
    //
805
    // Fills in as many missing microdescriptors as possible in this
806
    // netdir, using the microdescriptors from the previous netdir.
807
    //
808
    // With HS enabled, stores the netdir for reuse of relay hash ring index values.
809
    #[allow(clippy::needless_pass_by_value)] // prev might, or might not, be stored
810
2
    pub fn fill_from_previous_netdir(&mut self, prev: Arc<NetDir>) {
811
76
        for md in prev.mds.iter().flatten() {
812
76
            self.netdir.add_arc_microdesc(md.clone());
813
76
        }
814

            
815
        #[cfg(feature = "hs-common")]
816
2
        {
817
2
            self.prev_netdir = Some(prev);
818
2
        }
819
2
    }
820

            
821
    /// Compute the hash ring(s) for this NetDir
822
    #[cfg(feature = "hs-common")]
823
6548
    fn compute_rings(&mut self) {
824
6548
        let params = HsDirParams::compute(&self.netdir.consensus, &self.netdir.params)
825
6548
            .expect("Invalid consensus");
826
6548
        // TODO: see TODO by similar expect in new()
827
6548

            
828
6548
        self.netdir.hsdir_rings =
829
7045
            Arc::new(params.map(|params| {
830
6868
                HsDirRing::compute(params, &self.netdir, self.prev_netdir.as_deref())
831
7045
            }));
832
6548
    }
833

            
834
    /// Return true if this are enough information in this directory
835
    /// to build multihop paths.
836
4
    pub fn have_enough_paths(&self) -> bool {
837
4
        self.netdir.have_enough_paths()
838
4
    }
839
    /// If this directory has enough information to build multihop
840
    /// circuits, return it.
841
6712
    pub fn unwrap_if_sufficient(
842
6712
        #[allow(unused_mut)] mut self,
843
6712
    ) -> std::result::Result<NetDir, PartialNetDir> {
844
6712
        if self.netdir.have_enough_paths() {
845
            #[cfg(feature = "hs-common")]
846
6548
            self.compute_rings();
847
6548
            Ok(self.netdir)
848
        } else {
849
164
            Err(self)
850
        }
851
6712
    }
852
}
853

            
854
impl MdReceiver for PartialNetDir {
855
88
    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
856
88
        self.netdir.missing_microdescs()
857
88
    }
858
260528
    fn add_microdesc(&mut self, md: Microdesc) -> bool {
859
260528
        self.netdir.add_microdesc(md)
860
260528
    }
861
240
    fn n_missing(&self) -> usize {
862
240
        self.netdir.n_missing()
863
240
    }
864
}
865

            
866
impl NetDir {
867
    /// Return the declared lifetime of this NetDir.
868
282
    pub fn lifetime(&self) -> &netstatus::Lifetime {
869
282
        self.consensus.lifetime()
870
282
    }
871

            
872
    /// Add `md` to this NetDir.
873
    ///
874
    /// Return true if we wanted it, and false otherwise.
875
260604
    fn add_arc_microdesc(&mut self, md: Arc<Microdesc>) -> bool {
876
260604
        if let Some(rsidx) = self.rsidx_by_missing.remove(md.digest()) {
877
246644
            assert_eq!(self.c_relays()[rsidx].md_digest(), md.digest());
878

            
879
            // There should never be two approved MDs in the same
880
            // consensus listing the same ID... but if there is,
881
            // we'll let the most recent one win.
882
246644
            self.rsidx_by_ed.insert(*md.ed25519_id(), rsidx);
883
246644

            
884
246644
            // Happy path: we did indeed want this one.
885
246644
            self.mds[rsidx] = Some(md);
886
246644

            
887
246644
            // Save some space in the missing-descriptor list.
888
246644
            if self.rsidx_by_missing.len() < self.rsidx_by_missing.capacity() / 4 {
889
12424
                self.rsidx_by_missing.shrink_to_fit();
890
234220
            }
891

            
892
246644
            return true;
893
13960
        }
894
13960

            
895
13960
        // Either we already had it, or we never wanted it at all.
896
13960
        false
897
260604
    }
898

            
899
    /// Construct a (possibly invalid) Relay object from a routerstatus and its
900
    /// index within the consensus.
901
29773058
    fn relay_from_rs_and_rsidx<'a>(
902
29773058
        &'a self,
903
29773058
        rs: &'a netstatus::MdConsensusRouterStatus,
904
29773058
        rsidx: RouterStatusIdx,
905
29773058
    ) -> UncheckedRelay<'a> {
906
29773058
        debug_assert_eq!(self.c_relays()[rsidx].rsa_identity(), rs.rsa_identity());
907
29773058
        let md = self.mds[rsidx].as_deref();
908
29773058
        if let Some(md) = md {
909
29769696
            debug_assert_eq!(rs.md_digest(), md.digest());
910
3362
        }
911

            
912
29773058
        UncheckedRelay {
913
29773058
            rs,
914
29773058
            md,
915
29773058
            #[cfg(feature = "geoip")]
916
29773058
            cc: self.country_codes.get(rsidx.0).copied().flatten(),
917
29773058
        }
918
29773058
    }
919

            
920
    /// Return the value of the hsdir_n_replicas param.
921
    #[cfg(feature = "hs-common")]
922
360
    fn n_replicas(&self) -> u8 {
923
360
        self.params
924
360
            .hsdir_n_replicas
925
360
            .get()
926
360
            .try_into()
927
360
            .expect("BoundedInt did not enforce bounds")
928
360
    }
929

            
930
    /// Return the spread parameter for the specified `op`.
931
    #[cfg(feature = "hs-common")]
932
360
    fn spread(&self, op: HsDirOp) -> usize {
933
360
        let spread = match op {
934
40
            HsDirOp::Download => self.params.hsdir_spread_fetch,
935
            #[cfg(feature = "hs-service")]
936
320
            HsDirOp::Upload => self.params.hsdir_spread_store,
937
        };
938

            
939
360
        spread
940
360
            .get()
941
360
            .try_into()
942
360
            .expect("BoundedInt did not enforce bounds!")
943
360
    }
944

            
945
    /// Select `spread` hsdir relays for the specified `hsid` from a given `ring`.
946
    ///
947
    /// Algorithm:
948
    ///
949
    /// for idx in 1..=n_replicas:
950
    ///       - let H = hsdir_ring::onion_service_index(id, replica, rand,
951
    ///         period).
952
    ///       - Find the position of H within hsdir_ring.
953
    ///       - Take elements from hsdir_ring starting at that position,
954
    ///         adding them to Dirs until we have added `spread` new elements
955
    ///         that were not there before.
956
    #[cfg(feature = "hs-common")]
957
360
    fn select_hsdirs<'h, 'r: 'h>(
958
360
        &'r self,
959
360
        hsid: HsBlindId,
960
360
        ring: &'h HsDirRing,
961
360
        spread: usize,
962
360
    ) -> impl Iterator<Item = Relay<'r>> + 'h {
963
360
        let n_replicas = self.n_replicas();
964
360

            
965
360
        (1..=n_replicas) // 1-indexed !
966
360
            .flat_map({
967
360
                let mut selected_nodes = HashSet::new();
968
360

            
969
385
                move |replica: u8| {
970
36
                    let hsdir_idx = hsdir_ring::service_hsdir_index(&hsid, replica, ring.params());
971
36

            
972
36
                    let items = ring
973
198
                        .ring_items_at(hsdir_idx, spread, |(hsdir_idx, _)| {
974
198
                            // According to rend-spec 2.2.3:
975
198
                            //                                                  ... If any of those
976
198
                            // nodes have already been selected for a lower-numbered replica of the
977
198
                            // service, any nodes already chosen are disregarded (i.e. skipped over)
978
198
                            // when choosing a replica's hsdir_spread_store nodes.
979
198
                            selected_nodes.insert(*hsdir_idx)
980
198
                        })
981
36
                        .collect::<Vec<_>>();
982
36

            
983
36
                    items
984
385
                }
985
360
            })
986
485
            .filter_map(move |(_hsdir_idx, rs_idx)| {
987
140
                // This ought not to be None but let's not panic or bail if it is
988
140
                self.relay_by_rs_idx(*rs_idx)
989
485
            })
990
360
    }
991

            
992
    /// Replace the overridden parameters in this netdir with `new_replacement`.
993
    ///
994
    /// After this function is done, the netdir's parameters will be those in
995
    /// the consensus, overridden by settings from `new_replacement`.  Any
996
    /// settings in the old replacement parameters will be discarded.
997
    pub fn replace_overridden_parameters(&mut self, new_replacement: &netstatus::NetParams<i32>) {
998
        // TODO(nickm): This is largely duplicate code from PartialNetDir::new().
999
        let mut new_params = NetParameters::default();
        let _ = new_params.saturating_update(self.consensus.params().iter());
        for u in new_params.saturating_update(new_replacement.iter()) {
            warn!("Unrecognized option: override_net_params.{}", u);
        }
        self.params = new_params;
    }
    /// Return an iterator over all Relay objects, including invalid ones
    /// that we can't use.
759266
    pub fn all_relays(&self) -> impl Iterator<Item = UncheckedRelay<'_>> {
759266
        // TODO: I'd like if we could memoize this so we don't have to
759266
        // do so many hashtable lookups.
759266
        self.c_relays()
759266
            .iter_enumerated()
28968856
            .map(move |(rsidx, rs)| self.relay_from_rs_and_rsidx(rs, rsidx))
759266
    }
    /// Return an iterator over all [usable](NetDir#usable) Relays.
710702
    pub fn relays(&self) -> impl Iterator<Item = Relay<'_>> {
710702
        self.all_relays().filter_map(UncheckedRelay::into_relay)
710702
    }
    /// Look up a relay's `MicroDesc` by its `RouterStatusIdx`
    #[cfg_attr(not(feature = "hs-common"), allow(dead_code))]
    pub(crate) fn md_by_rsidx(&self, rsidx: RouterStatusIdx) -> Option<&Microdesc> {
        self.mds.get(rsidx)?.as_deref()
    }
    /// Return a relay matching a given identity, if we have a
    /// _usable_ relay with that key.
    ///
    /// (Does not return [unusable](NetDir#usable) relays.)
    ///
    ///
    /// Note that a `None` answer is not always permanent: if a microdescriptor
    /// is subsequently added for a relay with this ID, the ID may become usable
    /// even if it was not usable before.
304881
    pub fn by_id<'a, T>(&self, id: T) -> Option<Relay<'_>>
304881
    where
304881
        T: Into<RelayIdRef<'a>>,
304881
    {
304881
        let id = id.into();
304881
        let answer = match id {
304758
            RelayIdRef::Ed25519(ed25519) => {
304758
                let rsidx = *self.rsidx_by_ed.get(ed25519)?;
301100
                let rs = self.c_relays().get(rsidx).expect("Corrupt index");
301100

            
301100
                self.relay_from_rs_and_rsidx(rs, rsidx).into_relay()?
            }
123
            RelayIdRef::Rsa(rsa) => self
123
                .by_rsa_id_unchecked(rsa)
123
                .and_then(UncheckedRelay::into_relay)?,
            other_type => self.relays().find(|r| r.has_identity(other_type))?,
        };
301112
        assert!(answer.has_identity(id));
301112
        Some(answer)
304881
    }
    /// Obtain a `Relay` given a `RouterStatusIdx`
    ///
    /// Differs from `relay_from_rs_and_rsi` as follows:
    ///  * That function expects the caller to already have an `MdConsensusRouterStatus`;
    ///    it checks with `debug_assert` that the relay in the netdir matches.
    ///  * That function panics if the `RouterStatusIdx` is invalid; this one returns `None`.
    ///  * That function returns an `UncheckedRelay`; this one a `Relay`.
    ///
    /// `None` could be returned here, even with a valid `rsi`,
    /// if `rsi` refers to an [unusable](NetDir#usable) relay.
    #[cfg_attr(not(feature = "hs-common"), allow(dead_code))]
2828
    pub(crate) fn relay_by_rs_idx(&self, rs_idx: RouterStatusIdx) -> Option<Relay<'_>> {
2828
        let rs = self.c_relays().get(rs_idx)?;
2828
        let md = self.mds.get(rs_idx)?.as_deref();
2828
        UncheckedRelay {
2828
            rs,
2828
            md,
2828
            #[cfg(feature = "geoip")]
2828
            cc: self.country_codes.get(rs_idx.0).copied().flatten(),
2828
        }
2828
        .into_relay()
2828
    }
    /// Return a relay with the same identities as those in `target`, if one
    /// exists.
    ///
    /// Does not return [unusable](NetDir#usable) relays.
    ///
    /// Note that a negative result from this method is not necessarily permanent:
    /// it may be the case that a relay exists,
    /// but we don't yet have enough information about it to know all of its IDs.
    /// To test whether a relay is *definitely* absent,
    /// use [`by_ids_detailed`](Self::by_ids_detailed)
    /// or [`ids_listed`](Self::ids_listed).
    ///
    /// # Limitations
    ///
    /// This will be very slow if `target` does not have an Ed25519 or RSA
    /// identity.
292932
    pub fn by_ids<T>(&self, target: &T) -> Option<Relay<'_>>
292932
    where
292932
        T: HasRelayIds + ?Sized,
292932
    {
292932
        let mut identities = target.identities();
        // Don't try if there are no identities.
292932
        let first_id = identities.next()?;
        // Since there is at most one relay with each given ID type,
        // we only need to check the first relay we find.
292932
        let candidate = self.by_id(first_id)?;
292720
        if identities.all(|wanted_id| candidate.has_identity(wanted_id)) {
292718
            Some(candidate)
        } else {
2
            None
        }
292932
    }
    /// Check whether there is a relay that has at least one identity from
    /// `target`, and which _could_ have every identity from `target`.
    /// If so, return such a relay.
    ///
    /// Return `Ok(None)` if we did not find a relay with any identity from `target`.
    ///
    /// Return `RelayLookupError::Impossible` if we found a relay with at least
    /// one identity from `target`, but that relay's other identities contradict
    /// what we learned from `target`.
    ///
    /// Does not return [unusable](NetDir#usable) relays.
    ///
    /// (This function is only useful if you need to distinguish the
    /// "impossible" case from the "no such relay known" case.)
    ///
    /// # Limitations
    ///
    /// This will be very slow if `target` does not have an Ed25519 or RSA
    /// identity.
    //
    // TODO HS: This function could use a better name.
    //
    // TODO: We could remove the feature restriction here once we think this API is
    // stable.
    #[cfg(feature = "hs-common")]
20
    pub fn by_ids_detailed<T>(
20
        &self,
20
        target: &T,
20
    ) -> std::result::Result<Option<Relay<'_>>, RelayLookupError>
20
    where
20
        T: HasRelayIds + ?Sized,
20
    {
20
        let candidate = target
20
            .identities()
20
            // Find all the relays that share any identity with this set of identities.
36
            .filter_map(|id| self.by_id(id))
20
            // We might find the same relay more than once under a different
20
            // identity, so we remove the duplicates.
20
            //
20
            // Since there is at most one relay per rsa identity per consensus,
20
            // this is a true uniqueness check under current construction rules.
20
            .unique_by(|r| r.rs.rsa_identity())
20
            // If we find two or more distinct relays, then have a contradiction.
20
            .at_most_one()
20
            .map_err(|_| RelayLookupError::Impossible)?;
        // If we have no candidate, return None early.
20
        let candidate = match candidate {
10
            Some(relay) => relay,
10
            None => return Ok(None),
        };
        // Now we know we have a single candidate.  Make sure that it does not have any
        // identity that does not match the target.
10
        if target
10
            .identities()
14
            .all(|wanted_id| match candidate.identity(wanted_id.id_type()) {
                None => true,
14
                Some(id) => id == wanted_id,
14
            })
        {
6
            Ok(Some(candidate))
        } else {
4
            Err(RelayLookupError::Impossible)
        }
20
    }
    /// Return a boolean if this consensus definitely has (or does not have) a
    /// relay matching the listed identities.
    ///
    /// `Some(true)` indicates that the relay exists.
    /// `Some(false)` indicates that the relay definitely does not exist.
    /// `None` indicates that we can't yet tell whether such a relay exists,
    ///  due to missing information.
21166
    fn id_pair_listed(&self, ed_id: &Ed25519Identity, rsa_id: &RsaIdentity) -> Option<bool> {
21166
        let r = self.by_rsa_id_unchecked(rsa_id);
21166
        match r {
20924
            Some(unchecked) => {
20924
                if !unchecked.rs.ed25519_id_is_usable() {
                    return Some(false);
20924
                }
20924
                // If md is present, then it's listed iff we have the right
20924
                // ed id.  Otherwise we don't know if it's listed.
21445
                unchecked.md.map(|md| md.ed25519_id() == ed_id)
            }
            None => {
                // Definitely not listed.
242
                Some(false)
            }
        }
21166
    }
    /// Check whether a relay exists (or may exist)
    /// with the same identities as those in `target`.
    ///
    /// `Some(true)` indicates that the relay exists.
    /// `Some(false)` indicates that the relay definitely does not exist.
    /// `None` indicates that we can't yet tell whether such a relay exists,
    ///  due to missing information.
6965
    pub fn ids_listed<T>(&self, target: &T) -> Option<bool>
6965
    where
6965
        T: HasRelayIds + ?Sized,
6965
    {
6965
        let rsa_id = target.rsa_identity();
6965
        let ed25519_id = target.ed_identity();
6965

            
6965
        // TODO: If we later support more identity key types, this will
6965
        // become incorrect.  This assertion might help us recognize that case.
6965
        const_assert!(RelayIdType::COUNT == 2);
6965

            
6965
        match (rsa_id, ed25519_id) {
6965
            (Some(r), Some(e)) => self.id_pair_listed(e, r),
            (Some(r), None) => Some(self.rsa_id_is_listed(r)),
            (None, Some(e)) => {
                if self.rsidx_by_ed.contains_key(e) {
                    Some(true)
                } else {
                    None
                }
            }
            (None, None) => None,
        }
6965
    }
    /// Return a (possibly [unusable](NetDir#usable)) relay with a given RSA identity.
    ///
    /// This API can be used to find information about a relay that is listed in
    /// the current consensus, even if we don't yet have enough information
    /// (like a microdescriptor) about the relay to use it.
222524
    #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
222524
    #[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
222524
    fn by_rsa_id_unchecked(&self, rsa_id: &RsaIdentity) -> Option<UncheckedRelay<'_>> {
222524
        let rsidx = *self.rsidx_by_rsa.get(rsa_id)?;
222152
        let rs = self.c_relays().get(rsidx).expect("Corrupt index");
222152
        assert_eq!(rs.rsa_identity(), rsa_id);
222152
        Some(self.relay_from_rs_and_rsidx(rs, rsidx))
222524
    }
    /// Return the relay with a given RSA identity, if we have one
    /// and it is [usable](NetDir#usable).
12
    fn by_rsa_id(&self, rsa_id: &RsaIdentity) -> Option<Relay<'_>> {
12
        self.by_rsa_id_unchecked(rsa_id)?.into_relay()
12
    }
    /// Return true if `rsa_id` is listed in this directory, even if it isn't
    /// currently usable.
    ///
    /// (An "[unusable](NetDir#usable)" relay in this context is one for which we don't have full
    /// directory information.)
6
    #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
6
    #[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
6
    fn rsa_id_is_listed(&self, rsa_id: &RsaIdentity) -> bool {
6
        self.by_rsa_id_unchecked(rsa_id).is_some()
6
    }
    /// List the hsdirs in this NetDir, that should be in the HSDir rings
    ///
    /// The results are not returned in any particular order.
    #[cfg(feature = "hs-common")]
6868
    fn all_hsdirs(&self) -> impl Iterator<Item = (RouterStatusIdx, Relay<'_>)> {
259505
        self.c_relays().iter_enumerated().filter_map(|(rsidx, rs)| {
259320
            let relay = self.relay_from_rs_and_rsidx(rs, rsidx);
259320
            relay.is_hsdir_for_ring().then_some(())?;
63240
            let relay = relay.into_relay()?;
63232
            Some((rsidx, relay))
259505
        })
6868
    }
    /// Return the parameters from the consensus, clamped to the
    /// correct ranges, with defaults filled in.
    ///
    /// NOTE: that unsupported parameters aren't returned here; only those
    /// values configured in the `params` module are available.
16236
    pub fn params(&self) -> &NetParameters {
16236
        &self.params
16236
    }
    /// Return a [`ProtoStatus`](netstatus::ProtoStatus) that lists the
    /// network's current requirements and recommendations for the list of
    /// protocols that every relay must implement.
    //
    // TODO HS: I am not sure this is the right API; other alternatives would be:
    //    * To expose the _required_ relay protocol list instead (since that's all that
    //      onion service implementations need).
    //    * To expose the client protocol list as well (for symmetry).
    //    * To expose the MdConsensus instead (since that's more general, although
    //      it restricts the future evolution of this API).
    //
    // I think that this is a reasonably good compromise for now, but I'm going
    // to put it behind the `hs-common` feature to give us time to consider more.
    #[cfg(feature = "hs-common")]
120
    pub fn relay_protocol_status(&self) -> &netstatus::ProtoStatus {
120
        self.consensus.relay_protocol_status()
120
    }
    /// Return weighted the fraction of relays we can use.  We only
    /// consider relays that match the predicate `usable`.  We weight
    /// this bandwidth according to the provided `role`.
    ///
    /// If _no_ matching relays in the consensus have a nonzero
    /// weighted bandwidth value, we fall back to looking at the
    /// unweighted fraction of matching relays.
    ///
    /// If there are no matching relays in the consensus, we return 0.0.
19640
    fn frac_for_role<'a, F>(&'a self, role: WeightRole, usable: F) -> f64
19640
    where
19640
        F: Fn(&UncheckedRelay<'a>) -> bool,
19640
    {
19640
        let mut total_weight = 0_u64;
19640
        let mut have_weight = 0_u64;
19640
        let mut have_count = 0_usize;
19640
        let mut total_count = 0_usize;
736080
        for r in self.all_relays() {
736080
            if !usable(&r) {
243600
                continue;
492480
            }
492480
            let w = self.weights.weight_rs_for_role(r.rs, role);
492480
            total_weight += w;
492480
            total_count += 1;
492480
            if r.is_usable() {
489972
                have_weight += w;
489972
                have_count += 1;
489972
            }
        }
19640
        if total_weight > 0 {
            // The consensus lists some weighted bandwidth so return the
            // fraction of the weighted bandwidth for which we have
            // descriptors.
19640
            (have_weight as f64) / (total_weight as f64)
        } else if total_count > 0 {
            // The consensus lists no weighted bandwidth for these relays,
            // but at least it does list relays. Return the fraction of
            // relays for which it we have descriptors.
            (have_count as f64) / (total_count as f64)
        } else {
            // There are no relays of this kind in the consensus.  Return
            // 0.0, to avoid dividing by zero and giving NaN.
            0.0
        }
19640
    }
    /// Return the estimated fraction of possible paths that we have
    /// enough microdescriptors to build.
6718
    fn frac_usable_paths(&self) -> f64 {
6718
        // TODO #504, TODO SPEC: We may want to add a set of is_flagged_fast() and/or
6718
        // is_flagged_stable() checks here.  This will require spec clarification.
247826
        let f_g = self.frac_for_role(WeightRole::Guard, |u| {
247640
            u.low_level_details().is_suitable_as_guard()
247826
        });
247826
        let f_m = self.frac_for_role(WeightRole::Middle, |_| true);
73444
        let f_e = if self.all_relays().any(|u| u.rs.is_flagged_exit()) {
240733
            self.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit())
        } else {
            // If there are no exits at all, we use f_m here.
520
            f_m
        };
6718
        f_g * f_m * f_e
6718
    }
    /// Return true if there is enough information in this NetDir to build
    /// multihop circuits.
6716
    fn have_enough_paths(&self) -> bool {
6716
        // TODO-A001: This should check for our guards as well, and
6716
        // make sure that if they're listed in the consensus, we have
6716
        // the descriptors for them.
6716

            
6716
        // If we can build a randomly chosen path with at least this
6716
        // probability, we know enough information to participate
6716
        // on the network.
6716

            
6716
        let min_frac_paths: f64 = self.params().min_circuit_path_threshold.as_fraction();
6716

            
6716
        // What fraction of paths can we build?
6716
        let available = self.frac_usable_paths();
6716

            
6716
        available >= min_frac_paths
6716
    }
    /// Choose a relay at random.
    ///
    /// Each relay is chosen with probability proportional to its weight
    /// in the role `role`, and is only selected if the predicate `usable`
    /// returns true for it.
    ///
    /// This function returns None if (and only if) there are no relays
    /// with nonzero weight where `usable` returned true.
    //
    // TODO this API, with the `usable` closure, invites mistakes where we fail to
    // check conditions that are implied by the role we have selected for the relay:
    // call sites must include a call to `Relay::is_polarity_inverter()` or whatever.
    // IMO the `WeightRole` ought to imply a condition (and it should therefore probably
    // be renamed.)  -Diziet
43396
    pub fn pick_relay<'a, R, P>(
43396
        &'a self,
43396
        rng: &mut R,
43396
        role: WeightRole,
43396
        usable: P,
43396
    ) -> Option<Relay<'a>>
43396
    where
43396
        R: rand::Rng,
43396
        P: FnMut(&Relay<'a>) -> bool,
43396
    {
43396
        let relays: Vec<_> = self.relays().filter(usable).collect();
43396
        // This algorithm uses rand::distributions::WeightedIndex, and uses
43396
        // gives O(n) time and space  to build the index, plus O(log n)
43396
        // sampling time.
43396
        //
43396
        // We might be better off building a WeightedIndex in advance
43396
        // for each `role`, and then sampling it repeatedly until we
43396
        // get a relay that satisfies `usable`.  Or we might not --
43396
        // that depends heavily on the actual particulars of our
43396
        // inputs.  We probably shouldn't make any changes there
43396
        // unless profiling tells us that this function is in a hot
43396
        // path.
43396
        //
43396
        // The C Tor sampling implementation goes through some trouble
43396
        // here to try to make its path selection constant-time.  I
43396
        // believe that there is no actual remotely exploitable
43396
        // side-channel here however.  It could be worth analyzing in
43396
        // the future.
43396
        //
43396
        // This code will give the wrong result if the total of all weights
43396
        // can exceed u64::MAX.  We make sure that can't happen when we
43396
        // set up `self.weights`.
43396
        relays[..]
767636
            .choose_weighted(rng, |r| self.weights.weight_rs_for_role(r.rs, role))
43396
            .ok()
43396
            .cloned()
43396
    }
    /// Choose `n` relay at random.
    ///
    /// Each relay is chosen with probability proportional to its weight
    /// in the role `role`, and is only selected if the predicate `usable`
    /// returns true for it.
    ///
    /// Relays are chosen without replacement: no relay will be
    /// returned twice. Therefore, the resulting vector may be smaller
    /// than `n` if we happen to have fewer than `n` appropriate relays.
    ///
    /// This function returns an empty vector if (and only if) there
    /// are no relays with nonzero weight where `usable` returned
    /// true.
22740
    pub fn pick_n_relays<'a, R, P>(
22740
        &'a self,
22740
        rng: &mut R,
22740
        n: usize,
22740
        role: WeightRole,
22740
        usable: P,
22740
    ) -> Vec<Relay<'a>>
22740
    where
22740
        R: rand::Rng,
22740
        P: FnMut(&Relay<'a>) -> bool,
22740
    {
22740
        let relays: Vec<_> = self.relays().filter(usable).collect();
        // NOTE: See discussion in pick_relay().
170300
        let mut relays = match relays[..].choose_multiple_weighted(rng, n, |r| {
170300
            self.weights.weight_rs_for_role(r.rs, role) as f64
170300
        }) {
            Err(_) => Vec::new(),
22740
            Ok(iter) => iter.map(Relay::clone).collect(),
        };
22740
        relays.shuffle(rng);
22740
        relays
22740
    }
    /// Compute the weight with which `relay` will be selected for a given
    /// `role`.
20802
    pub fn relay_weight<'a>(&'a self, relay: &Relay<'a>, role: WeightRole) -> RelayWeight {
20802
        RelayWeight(self.weights.weight_rs_for_role(relay.rs, role))
20802
    }
    /// Compute the total weight with which any relay matching `usable`
    /// will be selected for a given `role`.
    ///
    /// Note: because this function is used to assess the total
    /// properties of the consensus, the `usable` predicate takes a
    /// [`RouterStatus`] rather than a [`Relay`].
17852
    pub fn total_weight<P>(&self, role: WeightRole, usable: P) -> RelayWeight
17852
    where
17852
        P: Fn(&UncheckedRelay<'_>) -> bool,
17852
    {
17852
        self.all_relays()
383050
            .filter_map(|unchecked| {
383050
                if usable(&unchecked) {
171090
                    Some(RelayWeight(
171090
                        self.weights.weight_rs_for_role(unchecked.rs, role),
171090
                    ))
                } else {
211960
                    None
                }
383050
            })
17852
            .sum()
17852
    }
    /// Compute the weight with which a relay with ID `rsa_id` would be
    /// selected for a given `role`.
    ///
    /// Note that weight returned by this function assumes that the
    /// relay with that ID is actually [usable](NetDir#usable); if it isn't usable,
    /// then other weight-related functions will call its weight zero.
201084
    pub fn weight_by_rsa_id(&self, rsa_id: &RsaIdentity, role: WeightRole) -> Option<RelayWeight> {
201084
        self.by_rsa_id_unchecked(rsa_id)
206112
            .map(|unchecked| RelayWeight(self.weights.weight_rs_for_role(unchecked.rs, role)))
201084
    }
1