Skip to main content

tor_guardmgr/
sample.rs

1//! Logic for manipulating a sampled set of guards, along with various
2//! orderings on that sample.
3
4mod candidate;
5
6use crate::filter::GuardFilter;
7use crate::guard::{Guard, NewlyConfirmed, Reachable};
8use crate::skew::SkewObservation;
9use crate::{
10    ExternalActivity, GuardParams, GuardUsage, GuardUsageKind, PickGuardError, ids::GuardId,
11};
12use crate::{FirstHop, GuardSetSelector};
13use tor_basic_utils::iter::{FilterCount, IteratorExt as _};
14use tor_linkspec::{ByRelayIds, HasRelayIds};
15
16use itertools::Itertools;
17use rand::seq::IndexedRandom;
18use serde::{Deserialize, Serialize};
19use std::borrow::Cow;
20use std::collections::{HashMap, HashSet};
21use tracing::{debug, info};
22use web_time_compat::{Instant, SystemTime};
23
24#[allow(unused_imports)]
25pub(crate) use candidate::{Candidate, CandidateStatus, Universe, UniverseRef, WeightThreshold};
26
27/// A set of sampled guards, along with various orderings on subsets
28/// of the sample.
29///
30/// Every guard in a `GuardSet` is considered to be "sampled": that
31/// is, selected from a network directory at some point in the past.
32/// The guards in the sample are ordered (roughly) by the time at
33/// which they were added.  This list is persistent.
34///
35/// Any guard which we've successfully used at least once is
36/// considered "confirmed".  Confirmed guards are ordered (roughly) by
37/// the time at which we first used them.  This list is persistent.
38///
39/// The guards which we would prefer to use are called "primary".
40/// Primary guards are ordered from most- to least-preferred.
41/// This list is not persistent, and is re-derived as needed.
42///
43/// These lists together define a "preference order".  All primary
44/// guards come first in preference order.  Then come the non-primary
45/// confirmed guards, in their confirmed order.  Finally come the
46/// non-primary, non-confirmed guards, in their sampled order.
47#[derive(Debug, Default, Clone, Deserialize)]
48#[serde(from = "GuardSample")]
49pub(crate) struct GuardSet {
50    /// Map from identities to guards, for every guard in this sample.
51    ///
52    /// The key for each entry is a set of identities which we have
53    /// good (trustworthy-enough) reason to link together.
54    ///
55    /// When we connect to a guard we require it to demonstrate
56    /// that it has *all* of these identities;
57    /// and we do pinning, so that we note down the other identities we discover it has,
58    /// with the intent that we will require them in future.
59    ///
60    /// ### Sources of linkage:
61    ///
62    ///  * If we connect to a relay and it proves a set of identities,
63    ///    that necessarily will include at least the ones we have already.
64    ///    We can add any other identities we have discovered.
65    ///    Justification: the owners of the old ids have made a statement
66    ///    (via the connection protocols) that these other ids are also theirs,
67    ///    and should be required in future.
68    ///
69    ///  * If we obtain a (full) descriptor for a relay, and check the
70    ///    self-signatures by all the identities we have already,
71    ///    we can add any other identities listed in the descriptor.
72    ///    Justification: the owners of the old ids have made an explicit statement
73    ///    that these other ids are also theirs,
74    ///    and should be required in future.
75    ///
76    ///  * For a relay in the netdir, if the netdir links some ids together,
77    ///    we can combine the entries.
78    ///    Justification: the netdir is authoritative for netdir-based relays.
79    ///
80    ///  * For a configured bridge, if our configuration links some identities,
81    ///    we must insist on all those identities.
82    ///    So we combine them.
83    ///
84    /// ### Handling of conflicting entries:
85    ///
86    /// `ByRelayIds` will implicitly delete conflicting entries,
87    /// simply forgetting about them.
88    /// This is OK for netdir relays, since we do not expect this to occur in practice.
89    ///
90    /// For bridges, conflicts may in fact occur,
91    /// since bridge lines are not issued by a single authority,
92    /// and should be afforded limited trust.
93    ///
94    ///  * If the configuration contains bridge lines that mutually conflict,
95    ///    affected bridge lines should be disregarded,
96    ///    or the configuration rejected.
97    ///
98    ///  * If the configuration contains information which is inconsistent with
99    ///    our past experience, we should discard the past experiences which
100    ///    aren't reconcilable with the configuration.
101    ///
102    ///  * We may discover a linkage which demonstrates that the configuration
103    ///    is wrong: for example, two bridge lines for identities X and Y,
104    ///    but in fact there is only one bridge with both identities.
105    ///    In this situation it is OK to effectively disregard some the configuration
106    ///    entries which are at variance with reality, maybe with a warning,
107    ///    but keeping at least one of every usable id set (actually existing bridge)
108    ///    would be good.
109    guards: ByRelayIds<Guard>,
110    /// Identities of all the guards in the sample, in sample order.
111    ///
112    /// This contains the same elements as the keys of `guards`
113    sample: Vec<GuardId>,
114    /// Identities of all the confirmed guards in the sample, in
115    /// confirmed order.
116    ///
117    /// This contains a subset of the values in `sample`.
118    confirmed: Vec<GuardId>,
119    /// Identities of all the primary guards, in preference order
120    /// (from best to worst).
121    ///
122    /// This contains a subset of the values in `sample`.
123    primary: Vec<GuardId>,
124    /// Currently active filter that restricts which guards we can use.
125    ///
126    /// Note that all of the lists above (with the exception of `primary`)
127    /// can hold guards that the filter doesn't permit.  This behavior
128    /// is meant to give good security behavior in the presence of filters
129    /// that change over time.
130    active_filter: GuardFilter,
131
132    /// If true, the active filter is "very restrictive".
133    filter_is_restrictive: bool,
134
135    /// Set to 'true' whenever something changes that would force us
136    /// to call 'select_primary_guards()', and cleared whenever we call it.
137    primary_guards_invalidated: bool,
138
139    /// Fields from the state file that was used to make this `GuardSet` that
140    /// this version of Arti doesn't understand.
141    unknown_fields: HashMap<String, JsonValue>,
142}
143
144/// Which of our lists did a given guard come from?
145#[derive(Debug, Copy, Clone, Eq, PartialEq)]
146pub(crate) enum ListKind {
147    /// A guard that came from the primary guard list.
148    Primary,
149    /// A non-primary guard that came from the confirmed guard list.
150    Confirmed,
151    /// A non-primary, non-confirmed guard.
152    Sample,
153    /// Not a guard at all, but a fallback directory.
154    Fallback,
155}
156
157impl ListKind {
158    /// Return true if this is a primary guard.
159    pub(crate) fn is_primary(&self) -> bool {
160        self == &ListKind::Primary
161    }
162
163    /// Return true if this guard's origin indicates that you can use successful
164    /// circuits built through it immediately without waiting for any other
165    /// circuits to succeed or fail.
166    pub(crate) fn usable_immediately(&self) -> bool {
167        match self {
168            ListKind::Primary | ListKind::Fallback => true,
169            ListKind::Confirmed | ListKind::Sample => false,
170        }
171    }
172}
173
174impl GuardSet {
175    /// Return the lengths of the different elements of the guard set.
176    ///
177    /// Used to report bugs or corruption in consistency.
178    fn inner_lengths(&self) -> (usize, usize, usize, usize) {
179        (
180            self.guards.len(),
181            self.sample.len(),
182            self.confirmed.len(),
183            self.primary.len(),
184        )
185    }
186
187    /// Remove all elements from this `GuardSet` that ought to be referenced by
188    /// another element, but which are not.
189    ///
190    /// This method only removes corrupted elements and updates IDs in the ID
191    /// list (possibly adding new IDs); it doesn't add guards or other data.
192    /// It won't do anything if the `GuardSet` is well-formed.
193    fn fix_consistency(&mut self) {
194        /// Remove every element of `id_list` that does not belong to some guard
195        /// in `guards`, and update the others to have any extra identities
196        /// listed in `guards`.
197        fn fix_id_list(guards: &ByRelayIds<Guard>, id_list: &mut Vec<GuardId>) {
198            id_list.retain_mut(|id| match guards.by_all_ids(id) {
199                Some(guard) => {
200                    *id = guard.guard_id().clone();
201                    true
202                }
203                None => false,
204            });
205        }
206
207        let sample_set: HashSet<_> = self.sample.iter().collect();
208        self.guards.retain(|g| sample_set.contains(g.guard_id()));
209        fix_id_list(&self.guards, &mut self.sample);
210        fix_id_list(&self.guards, &mut self.confirmed);
211        fix_id_list(&self.guards, &mut self.primary);
212    }
213
214    /// Assert that this `GuardSet` is internally consistent.
215    ///
216    /// Incidentally fixes the consistency of this `GuardSet` if needed.
217    fn assert_consistency(&mut self) {
218        let len_pre = self.inner_lengths();
219        self.fix_consistency();
220        let len_post = self.inner_lengths();
221        assert_eq!(len_pre, len_post);
222    }
223
224    /// Return the guard that has every identity in `id`, if any.
225    pub(crate) fn get(&self, id: &GuardId) -> Option<&Guard> {
226        self.guards.by_all_ids(id)
227    }
228
229    /// Replace the filter used by this `GuardSet` with `filter`.
230    ///
231    /// Removes all primary guards that the filter doesn't permit.
232    ///
233    /// If `restrictive` is true, this filter is treated as "extremely restrictive".
234    pub(crate) fn set_filter(&mut self, filter: GuardFilter, restrictive: bool) {
235        self.active_filter = filter;
236        self.filter_is_restrictive = restrictive;
237
238        self.assert_consistency();
239
240        let guards = &self.guards; // avoid borrow issues
241        let filt = &self.active_filter;
242        self.primary.retain(|id| {
243            guards
244                .by_all_ids(id)
245                .map(|g| g.usable() && filt.permits(g))
246                .unwrap_or(false)
247        });
248
249        self.primary_guards_invalidated = true;
250    }
251
252    /// Return the current filter for this `GuardSet`.
253    pub(crate) fn filter(&self) -> &GuardFilter {
254        &self.active_filter
255    }
256
257    /// Copy non-persistent status from every guard shared with `other`.
258    ///
259    /// This is used as part of our reload process when we don't own our state
260    /// files, and we're reloading in order to find out what the other Arti
261    /// instance thinks the guards are. At that point, `self` is the set of
262    /// guards that we just loaded from state, and `other` is our old guards,
263    /// which we are using only for their status information.
264    pub(crate) fn copy_ephemeral_status_into_newly_loaded_state(&mut self, mut other: GuardSet) {
265        let old_guards = std::mem::take(&mut self.guards);
266        self.guards = old_guards
267            .into_values()
268            .map(|guard| {
269                let id = guard.guard_id();
270
271                if let Some(other_guard) = other.guards.remove_exact(id) {
272                    guard.copy_ephemeral_status_into_newly_loaded_state(other_guard)
273                } else {
274                    guard
275                }
276            })
277            .collect();
278    }
279
280    /// Return a serializable state object that can be stored to disk
281    /// to capture the current state of this GuardSet.
282    fn get_state(&self) -> GuardSample<'_> {
283        let guards = self
284            .sample
285            .iter()
286            .map(|id| Cow::Borrowed(self.guards.by_all_ids(id).expect("Inconsistent state")))
287            .collect();
288
289        GuardSample {
290            guards,
291            confirmed: Cow::Borrowed(&self.confirmed),
292            remaining: self.unknown_fields.clone(),
293        }
294    }
295
296    /// Reconstruct a guard state from its serialized representation.
297    fn from_state(state: GuardSample<'_>) -> Self {
298        let mut guards = ByRelayIds::new();
299        let mut sample = Vec::new();
300        for guard in state.guards {
301            sample.push(guard.guard_id().clone());
302            guards.insert(guard.into_owned());
303        }
304        let confirmed = state.confirmed.into_owned();
305        let primary = Vec::new();
306        let mut guard_set = GuardSet {
307            guards,
308            sample,
309            confirmed,
310            primary,
311            active_filter: GuardFilter::default(),
312            filter_is_restrictive: false,
313            primary_guards_invalidated: true,
314            unknown_fields: state.remaining,
315        };
316
317        // Fix any inconsistencies in the stored representation.
318        let len_pre = guard_set.inner_lengths();
319        guard_set.fix_consistency();
320        let len_post = guard_set.inner_lengths();
321        if len_pre != len_post {
322            info!(
323                "Resolved a consistency issue in stored guard state. Diagnostic codes: {:?}, {:?}",
324                len_pre, len_post
325            );
326        }
327        debug!(
328            n_guards = len_post.0,
329            n_confirmed = len_post.2,
330            "Guard set loaded."
331        );
332
333        guard_set
334    }
335
336    /// Return `Ok(true)` if `id` is definitely a member of this set, and
337    /// `Ok(false)` if it is definitely not a member.
338    ///
339    /// If we cannot tell, it's because there is a guard in this sample that has
340    /// a _subset_ of the IDs in `id`. In that case, we return
341    /// `Err(guard_ident)`, where `guard_ident`  is the identity of that guard.
342    pub(crate) fn contains(&self, id: &GuardId) -> Result<bool, &GuardId> {
343        let overlapping = self.guards.all_overlapping(id);
344        match &overlapping[..] {
345            [singleton] => {
346                if singleton.has_all_relay_ids_from(id) {
347                    Ok(true)
348                } else {
349                    Err(singleton.guard_id())
350                }
351            }
352            _ => Ok(false),
353        }
354    }
355
356    /// If there are not enough filter-permitted usable guards in this
357    /// sample (according to the current active filter), then add
358    /// more, up to the limits allowed by the parameters.
359    ///
360    /// This is the only function that adds new guards to the sample.
361    ///
362    /// Guards always start out un-confirmed.
363    ///
364    /// Return true if any guards were added.
365    pub(crate) fn extend_sample_as_needed<U: Universe>(
366        &mut self,
367        now: SystemTime,
368        params: &GuardParams,
369        dir: &U,
370    ) -> crate::ExtendedStatus {
371        let mut any_added = crate::ExtendedStatus::No;
372        while self.extend_sample_inner(now, params, dir) {
373            any_added = crate::ExtendedStatus::Yes;
374        }
375        any_added
376    }
377
378    /// Implementation helper for extend_sample_as_needed.
379    ///
380    /// # Complications
381    ///
382    /// For spec conformance, we only consider our filter when selecting new
383    /// guards if the filter is "very restrictive". That makes it possible that
384    /// this function will add fewer filter-permitted guards than we had wanted.
385    /// Because of that, this is a separate function, and
386    /// extend_sample_as_needed runs it in a loop until it returns false.
387    fn extend_sample_inner<U: Universe>(
388        &mut self,
389        now: SystemTime,
390        params: &GuardParams,
391        dir: &U,
392    ) -> bool {
393        self.assert_consistency();
394        let n_filtered_usable = self
395            .guards
396            .values()
397            .filter(|g| {
398                g.usable()
399                    && self.active_filter.permits(*g)
400                    && g.reachable() != Reachable::Unreachable
401            })
402            .count();
403        if n_filtered_usable >= params.min_filtered_sample_size {
404            return false; // We have enough usage guards in our sample.
405        }
406        if self.guards.len() >= params.max_sample_size {
407            return false; // We can't add any more guards to our sample.
408        }
409
410        // What are the most guards we're willing to have in the sample?
411        let max_to_add = params.max_sample_size - self.sample.len();
412        let want_to_add = params.min_filtered_sample_size - n_filtered_usable;
413        let n_to_add = std::cmp::min(max_to_add, want_to_add);
414
415        let WeightThreshold {
416            mut current_weight,
417            maximum_weight,
418        } = dir.weight_threshold(&self.guards, params);
419
420        // Ask the netdir for a set of guards we could use.
421        let no_filter = GuardFilter::unfiltered();
422        let (n_candidates, pre_filter) =
423            if self.filter_is_restrictive || self.active_filter.is_unfiltered() {
424                (n_to_add, &self.active_filter)
425            } else {
426                // The filter will probably reject a bunch of guards, but we sample
427                // before filtering, so we make this larger on an ad-hoc basis.
428                (n_to_add * 3, &no_filter)
429            };
430
431        let candidates = dir.sample(&self.guards, pre_filter, n_candidates);
432
433        // Add those candidates to the sample.
434        let mut any_added = false;
435        let mut n_filtered_usable = n_filtered_usable;
436        for (candidate, weight) in candidates {
437            // Don't add any more if we have met the minimal sample size, and we
438            // have added too much weight.
439            if current_weight >= maximum_weight
440                && self.guards.len() >= params.min_filtered_sample_size
441            {
442                break;
443            }
444            if self.guards.len() >= params.max_sample_size {
445                // Can't add any more.
446                break;
447            }
448            if n_filtered_usable >= params.min_filtered_sample_size {
449                // We've reached our target; no need to add more.
450                break;
451            }
452            if self.active_filter.permits(&candidate.owned_target) {
453                n_filtered_usable += 1;
454            }
455            current_weight += weight;
456            self.add_guard(candidate, now, params);
457            any_added = true;
458        }
459        self.assert_consistency();
460        any_added
461    }
462
463    /// Add `relay` as a new guard.
464    ///
465    /// Does nothing if it is already a guard.
466    fn add_guard(&mut self, relay: Candidate, now: SystemTime, params: &GuardParams) {
467        let id = GuardId::from_relay_ids(&relay.owned_target);
468        if self.guards.by_all_ids(&id).is_some() {
469            return;
470        }
471        debug!(guard_id=?id, "Adding guard to sample.");
472        let guard = Guard::from_candidate(relay, now, params);
473        self.guards.insert(guard);
474        self.sample.push(id);
475        self.primary_guards_invalidated = true;
476    }
477
478    /// Return the number of our primary guards that are missing directory
479    /// information in `universe`.
480    ///
481    /// Note that "missing directory information" is not the same as "absent":
482    /// in this case, we  are counting the primary guards where we cannot tell
483    /// whether they appear in the universe or not because we have not yet
484    /// downloaded their descriptors.
485    pub(crate) fn n_primary_without_id_info_in<U: Universe>(&mut self, universe: &U) -> usize {
486        self.primary
487            .iter()
488            .filter(|id| {
489                let g = self
490                    .guards
491                    .by_all_ids(*id)
492                    .expect("Inconsistent guard state");
493                g.listed_in(universe).is_none()
494            })
495            .count()
496    }
497
498    /// Update the status of every guard  in this sample from a given source.
499    pub(crate) fn update_status_from_dir<U: Universe>(&mut self, dir: &U) {
500        let old_guards = std::mem::take(&mut self.guards);
501        self.guards = old_guards
502            .into_values()
503            .map(|mut guard| {
504                guard.update_from_universe(dir);
505                guard
506            })
507            .collect();
508        // Call "fix consistency", in case any guards got a new ID.
509        self.fix_consistency();
510    }
511
512    /// Re-build the list of primary guards.
513    ///
514    /// Primary guards are chosen according to preference order over all
515    /// the guards in the set, restricted by the current filter.
516    ///
517    /// TODO: Enumerate all the times when this function needs to be called.
518    ///
519    /// TODO: Make sure this is called enough.
520    pub(crate) fn select_primary_guards(&mut self, params: &GuardParams) {
521        // TODO-SPEC: This is not 100% what the spec says, but it does match what
522        // Tor does.  We pick first from the confirmed guards,
523        // then from any previous primary guards, and then from maybe-reachable
524        // guards in the sample.
525
526        // Only for logging.
527        let old_primary = self.primary.clone();
528
529        self.primary = self
530            // First, we look at the confirmed guards.
531            .confirmed
532            .iter()
533            // Then we consider existing primary guards.
534            .chain(self.primary.iter())
535            // Finally, we look at the rest of the sample for guards not marked
536            // as "unreachable".
537            .chain(self.reachable_sample_ids())
538            // We only consider each guard the first time it appears.
539            .unique()
540            // We only consider usable guards that the filter allows.
541            .filter_map(|id| {
542                let g = self
543                    .guards
544                    .by_all_ids(id)
545                    .expect("Inconsistent guard state");
546                if g.usable() && self.active_filter.permits(g) {
547                    Some(id.clone())
548                } else {
549                    None
550                }
551            })
552            // The first n_primary guards on that list are primary!
553            .take(params.n_primary)
554            .collect();
555
556        if self.primary != old_primary {
557            debug!(old=?old_primary, new=?self.primary, "Updated primary guards.");
558        }
559
560        // Clear exploratory_circ_pending for all primary guards.
561        for id in &self.primary {
562            self.guards.modify_by_all_ids(id, |guard| {
563                guard.note_exploratory_circ(false);
564            });
565        }
566
567        // TODO: Recalculate retry times, perhaps, since we may have changed
568        // the timeouts?
569
570        self.assert_consistency();
571        self.primary_guards_invalidated = false;
572    }
573
574    /// Remove all guards which should expire `now`, according to the settings
575    /// in `params`.
576    pub(crate) fn expire_old_guards(&mut self, params: &GuardParams, now: SystemTime) {
577        self.assert_consistency();
578        let n_pre = self.guards.len();
579        self.guards.retain(|g| !g.is_expired(params, now));
580        let guards = &self.guards;
581        self.sample.retain(|id| guards.by_all_ids(id).is_some());
582        self.confirmed.retain(|id| guards.by_all_ids(id).is_some());
583        self.primary.retain(|id| guards.by_all_ids(id).is_some());
584        self.assert_consistency();
585
586        if self.guards.len() < n_pre {
587            let n_expired = n_pre - self.guards.len();
588            debug!(n_expired, "Expired guards as too old.");
589            self.primary_guards_invalidated = true;
590        }
591    }
592
593    /// Return an iterator over the Id for every Guard in the sample that
594    /// is not known to be Unreachable.
595    fn reachable_sample_ids(&self) -> impl Iterator<Item = &GuardId> {
596        self.sample.iter().filter(move |id| {
597            let g = self
598                .guards
599                .by_all_ids(*id)
600                .expect("Inconsistent guard state");
601            g.reachable() != Reachable::Unreachable
602        })
603    }
604
605    /// Return an iterator that yields an element for every guard in
606    /// this set, in preference order.
607    ///
608    /// Each element contains a `ListKind` that describes which list the
609    /// guard was in, and a `&GuardId` that identifies the guard.
610    ///
611    /// Note that this function will return guards that are not
612    /// accepted by the current active filter: the caller must apply
613    /// that filter if appropriate.
614    fn preference_order_ids(&self) -> impl Iterator<Item = (ListKind, &GuardId)> {
615        self.primary
616            .iter()
617            .map(|id| (ListKind::Primary, id))
618            .chain(self.confirmed.iter().map(|id| (ListKind::Confirmed, id)))
619            .chain(self.sample.iter().map(|id| (ListKind::Sample, id)))
620            .unique_by(|(_, id)| *id)
621    }
622
623    /// Like `preference_order_ids`, but yields `&Guard` instead of `&GuardId`.
624    fn preference_order(&self) -> impl Iterator<Item = (ListKind, &Guard)> + '_ {
625        self.preference_order_ids()
626            .filter_map(move |(p, id)| self.guards.by_all_ids(id).map(|g| (p, g)))
627    }
628
629    /// Return true if `guard_id` is an identity subset for any primary guard in this set.
630    fn guard_is_primary(&self, guard_id: &GuardId) -> bool {
631        // (This could be yes/no/maybe.)
632
633        // This is O(n), but the list is short.
634        self.primary
635            .iter()
636            .any(|p| p.has_all_relay_ids_from(guard_id))
637    }
638
639    /// For every guard that has been marked as `Unreachable` for too long,
640    /// mark it as `Unknown`.
641    pub(crate) fn consider_all_retries(&mut self, now: Instant) {
642        let old_guards = std::mem::take(&mut self.guards);
643        self.guards = old_guards
644            .into_values()
645            .map(|mut guard| {
646                guard.consider_retry(now);
647                guard
648            })
649            .collect();
650    }
651
652    /// Return the earliest time at which any guard will be retriable.
653    pub(crate) fn next_retry(&self, usage: &GuardUsage) -> Option<Instant> {
654        self.guards
655            .values()
656            .filter_map(|g| g.next_retry(usage))
657            .min()
658    }
659
660    /// Mark every `Unreachable` primary guard as `Unknown`.
661    pub(crate) fn mark_primary_guards_retriable(&mut self) {
662        for id in &self.primary {
663            self.guards
664                .modify_by_all_ids(id, |guard| guard.mark_retriable());
665        }
666    }
667
668    /// Return true if all of our primary guards are currently marked
669    /// unreachable.
670    pub(crate) fn all_primary_guards_are_unreachable(&mut self) -> bool {
671        self.primary
672            .iter()
673            .flat_map(|id| self.guards.by_all_ids(id))
674            .all(|g| g.reachable() == Reachable::Unreachable)
675    }
676
677    /// Mark every `Unreachable` guard as `Unknown`.
678    pub(crate) fn mark_all_guards_retriable(&mut self) {
679        let old_guards = std::mem::take(&mut self.guards);
680        self.guards = old_guards
681            .into_values()
682            .map(|mut guard| {
683                guard.mark_retriable();
684                guard
685            })
686            .collect();
687    }
688
689    /// Record that an attempt has begun to use the guard with
690    /// `guard_id`.
691    pub(crate) fn record_attempt(&mut self, guard_id: &GuardId, now: Instant) {
692        let is_primary = self.guard_is_primary(guard_id);
693        self.guards.modify_by_all_ids(guard_id, |guard| {
694            guard.record_attempt(now);
695
696            if !is_primary {
697                guard.note_exploratory_circ(true);
698            }
699        });
700    }
701
702    /// Record that an attempt to use the guard with `guard_id` has just
703    /// succeeded.
704    ///
705    /// If `how` is provided, it's an operation from outside the crate that the
706    /// guard succeeded at doing.
707    pub(crate) fn record_success(
708        &mut self,
709        guard_id: &GuardId,
710        params: &GuardParams,
711        how: Option<ExternalActivity>,
712        now: SystemTime,
713    ) {
714        self.assert_consistency();
715        self.guards.modify_by_all_ids(guard_id, |guard| match how {
716            Some(external) => guard.record_external_success(external),
717            None => {
718                let newly_confirmed = guard.record_success(now, params);
719
720                if newly_confirmed == NewlyConfirmed::Yes {
721                    self.confirmed.push(guard_id.clone());
722                    self.primary_guards_invalidated = true;
723                }
724            }
725        });
726        self.assert_consistency();
727    }
728
729    /// Record that an attempt to use the guard with `guard_id` has just failed.
730    ///
731    pub(crate) fn record_failure(
732        &mut self,
733        guard_id: &GuardId,
734        how: Option<ExternalActivity>,
735        now: Instant,
736    ) {
737        // TODO use instant uniformly for in-process, and systemtime for storage?
738        let is_primary = self.guard_is_primary(guard_id);
739        self.guards.modify_by_all_ids(guard_id, |guard| match how {
740            Some(external) => guard.record_external_failure(external, now),
741            None => guard.record_failure(now, is_primary),
742        });
743    }
744
745    /// Record that an attempt to use the guard with `guard_id` has
746    /// just been abandoned, without learning whether it succeeded or failed.
747    pub(crate) fn record_attempt_abandoned(&mut self, guard_id: &GuardId) {
748        self.guards
749            .modify_by_all_ids(guard_id, |guard| guard.note_exploratory_circ(false));
750    }
751
752    /// Record that an attempt to use the guard with `guard_id` has
753    /// just failed in a way that we could not definitively attribute to
754    /// the guard.
755    pub(crate) fn record_indeterminate_result(&mut self, guard_id: &GuardId) {
756        self.guards.modify_by_all_ids(guard_id, |guard| {
757            guard.note_exploratory_circ(false);
758            guard.record_indeterminate_result();
759        });
760    }
761
762    /// Record that a given guard has told us about clock skew.
763    pub(crate) fn record_skew(&mut self, guard_id: &GuardId, observation: SkewObservation) {
764        self.guards
765            .modify_by_all_ids(guard_id, |guard| guard.note_skew(observation));
766    }
767
768    /// Return an iterator over all stored clock skew observations.
769    pub(crate) fn skew_observations(&self) -> impl Iterator<Item = &SkewObservation> {
770        self.guards.values().filter_map(|g| g.skew())
771    }
772
773    /// Return whether the circuit manager can be allowed to use a
774    /// circuit with the `guard_id`.
775    ///
776    /// Return `Some(bool)` if the circuit is usable, and `None` if we
777    /// cannot yet be sure.
778    pub(crate) fn circ_usability_status(
779        &self,
780        guard_id: &GuardId,
781        usage: &GuardUsage,
782        params: &GuardParams,
783        now: Instant,
784    ) -> Option<bool> {
785        // TODO-SPEC: This isn't what the spec says.  The spec is phrased
786        // in terms of circuits blocking circuits, whereas this algorithm is
787        // about guards blocking guards.
788        //
789        // Also notably, the spec also says:
790        //
791        // * Among guards that do not appear in {CONFIRMED_GUARDS},
792        // {is_pending}==true guards have higher priority.
793        // * Among those, the guard with earlier {last_tried_connect} time
794        // has higher priority.
795        // * Finally, among guards that do not appear in
796        // {CONFIRMED_GUARDS} with {is_pending==false}, all have equal
797        // priority.
798        //
799        // I believe this approach is fine too, but we ought to document it.
800
801        if self.guard_is_primary(guard_id) {
802            // Circuits built to primary guards are always usable immediately.
803            //
804            // This has to be a special case, since earlier primary guards
805            // don't block later ones.
806            return Some(true);
807        }
808
809        // Assuming that the guard is _not_ primary, then the rule is
810        // fairly simple: we can use the guard if all the guards we'd
811        // _rather_ use are either down, or have had their circuit
812        // attempts pending for too long.
813
814        let cutoff = now
815            .checked_sub(params.np_connect_timeout)
816            .expect("Can't subtract connect timeout from now.");
817
818        for (src, guard) in self.preference_order() {
819            if guard.guard_id() == guard_id {
820                return Some(true);
821            }
822            if guard.usable() && self.active_filter.permits(guard) && guard.conforms_to_usage(usage)
823            {
824                match (src, guard.reachable()) {
825                    (_, Reachable::Reachable) => return Some(false),
826                    (_, Reachable::Unreachable) => (),
827                    (ListKind::Primary, Reachable::Untried | Reachable::Retriable) => {
828                        return Some(false);
829                    }
830                    (_, Reachable::Untried | Reachable::Retriable) => {
831                        if guard.exploratory_attempt_after(cutoff) {
832                            return None;
833                        }
834                    }
835                }
836            }
837        }
838
839        // This guard is not even listed.
840        Some(false)
841    }
842
843    /// Try to select a guard for a given `usage`.
844    ///
845    /// On success, returns the kind of guard that we got, and its filtered
846    /// representation in a form suitable for use as a first hop.
847    ///
848    /// Label the returned guard as having come from `sample_id`.
849    //
850    // NOTE (nickm): I wish that we didn't have to take sample_id as an input,
851    // but the alternative would be storing it as a member of `GuardSet`, which
852    // makes things very complicated.
853    pub(crate) fn pick_guard(
854        &self,
855        sample_id: &GuardSetSelector,
856        usage: &GuardUsage,
857        params: &GuardParams,
858        now: Instant,
859    ) -> Result<(ListKind, FirstHop), PickGuardError> {
860        let (list_kind, id) = self.pick_guard_id(usage, params, now)?;
861        let first_hop = self
862            .get(&id)
863            .expect("Somehow selected a guard we don't know!")
864            .get_external_rep(sample_id.clone());
865        let first_hop = self.active_filter.modify_hop(first_hop)?;
866
867        Ok((list_kind, first_hop))
868    }
869
870    /// Try to select a guard for a given `usage`.
871    ///
872    /// On success, returns the kind of guard that we got, and its identity.
873    fn pick_guard_id(
874        &self,
875        usage: &GuardUsage,
876        params: &GuardParams,
877        now: Instant,
878    ) -> Result<(ListKind, GuardId), PickGuardError> {
879        debug_assert!(!self.primary_guards_invalidated);
880        let n_options = match usage.kind {
881            GuardUsageKind::OneHopDirectory => params.dir_parallelism,
882            GuardUsageKind::Data => params.data_parallelism,
883        };
884
885        // Counts of how many elements were rejected by which of the filters
886        // below.
887        //
888        // Note that since we use `Iterator::take`, these counts won't cover the
889        // whole guard sample on the successful case: only in the failing case,
890        // when we fail to find any candidates.
891        let mut running = FilterCount::default();
892        let mut pending = FilterCount::default();
893        let mut suitable = FilterCount::default();
894        let mut filtered = FilterCount::default();
895
896        let mut options: Vec<_> = self
897            .preference_order()
898            // Discard the guards that are down or unusable, and see if any
899            // are left.
900            .filter_cnt(&mut running, |(_, g)| {
901                g.usable()
902                    && g.reachable() != Reachable::Unreachable
903                    && g.ready_for_usage(usage, now)
904            })
905            // Now remove those that are excluded because we're already trying
906            // them on an exploratory basis.
907            .filter_cnt(&mut pending, |(_, g)| !g.exploratory_circ_pending())
908            // ...or because they don't support the operation we're
909            // attempting...
910            .filter_cnt(&mut suitable, |(_, g)| g.conforms_to_usage(usage))
911            // ... or because we specifically filtered them out.
912            .filter_cnt(&mut filtered, |(_, g)| self.active_filter.permits(*g))
913            // We only consider the first n_options such guards.
914            .take(n_options)
915            .collect();
916
917        if options.iter().any(|(src, _)| src.is_primary()) {
918            // If there are any primary guards, we only consider those.
919            options.retain(|(src, _)| src.is_primary());
920        } else {
921            // If there are no primary guards, parallelism doesn't apply.
922            options.truncate(1);
923        }
924
925        match options.choose(&mut rand::rng()) {
926            Some((src, g)) => Ok((*src, g.guard_id().clone())),
927            None => {
928                let retry_at = if running.n_accepted == 0 {
929                    self.next_retry(usage)
930                } else {
931                    None
932                };
933                Err(PickGuardError::AllGuardsDown {
934                    retry_at,
935                    running,
936                    pending,
937                    suitable,
938                    filtered,
939                })
940            }
941        }
942    }
943
944    /// Return the guards whose bridge descriptors we should request, given our
945    /// current configuration and status.
946    ///
947    /// (The output of this function is not reasonable unless this is a Bridge
948    /// sample.)
949    #[cfg(feature = "bridge-client")]
950    pub(crate) fn descriptors_to_request(&self, now: Instant, params: &GuardParams) -> Vec<&Guard> {
951        /// This constant is here to improve our odds that we can get a working
952        /// bridge if we have any per-circuit filters that would prevent us from
953        /// using our preferred bridge.
954        const MINIMUM: usize = 2;
955
956        let maximum = std::cmp::max(params.data_parallelism, MINIMUM);
957        let data_usage = GuardUsage::default();
958
959        // Here we duplicate some but not all of the restrictions above in
960        // pick_guard_id.  We skip those restrictions that are specific to only
961        // certain kinds of circuits, and those that are temporary restrictions
962        // encouraging us to try more guards.
963        //
964        // TODO: we may want to refactor this code and the code in pick_guard_id
965        // above to share a single function.  Before we do that, however, I want
966        // to experiment with this logic a bit to make sure that it works and
967        // doesn't give us surprising results.
968        self.preference_order()
969            .filter(|(_, g)| {
970                g.usable()
971                    && g.reachable() != Reachable::Unreachable
972                    && g.ready_for_usage(&data_usage, now)
973                    && self.active_filter.permits(*g)
974            })
975            .take(maximum)
976            .map(|(_, g)| g)
977            .collect()
978    }
979}
980
981use serde::Serializer;
982use tor_persist::JsonValue;
983
984/// State object used to serialize and deserialize a [`GuardSet`].
985#[derive(Default, Debug, Clone, Serialize, Deserialize)]
986pub(crate) struct GuardSample<'a> {
987    /// Equivalent to `GuardSet.guards.values()`, except in sample order.
988    guards: Vec<Cow<'a, Guard>>,
989    /// The identities for the confirmed members of `guards`, in confirmed order.
990    confirmed: Cow<'a, [GuardId]>,
991    /// Other data from the state file that this version of Arti doesn't recognize.
992    #[serde(flatten)]
993    remaining: HashMap<String, JsonValue>,
994}
995
996impl Serialize for GuardSet {
997    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
998    where
999        S: Serializer,
1000    {
1001        GuardSample::from(self).serialize(serializer)
1002    }
1003}
1004
1005impl<'a> From<&'a GuardSet> for GuardSample<'a> {
1006    fn from(guards: &'a GuardSet) -> Self {
1007        guards.get_state()
1008    }
1009}
1010
1011impl<'a> From<GuardSample<'a>> for GuardSet {
1012    fn from(sample: GuardSample) -> Self {
1013        GuardSet::from_state(sample)
1014    }
1015}
1016
1017#[cfg(test)]
1018mod test {
1019    // @@ begin test lint list maintained by maint/add_warning @@
1020    #![allow(clippy::bool_assert_comparison)]
1021    #![allow(clippy::clone_on_copy)]
1022    #![allow(clippy::dbg_macro)]
1023    #![allow(clippy::mixed_attributes_style)]
1024    #![allow(clippy::print_stderr)]
1025    #![allow(clippy::print_stdout)]
1026    #![allow(clippy::single_char_pattern)]
1027    #![allow(clippy::unwrap_used)]
1028    #![allow(clippy::unchecked_time_subtraction)]
1029    #![allow(clippy::useless_vec)]
1030    #![allow(clippy::needless_pass_by_value)]
1031    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
1032    use tor_linkspec::{HasRelayIds, RelayIdType};
1033    use tor_netdir::NetDir;
1034    use tor_netdoc::doc::netstatus::RelayWeight;
1035    use tor_netdoc::types::relay_flags::RelayFlag;
1036    use web_time_compat::{InstantExt, SystemTimeExt};
1037
1038    use super::*;
1039    use crate::FirstHopId;
1040    use std::time::Duration;
1041
1042    fn netdir() -> NetDir {
1043        use tor_netdir::testnet;
1044        testnet::construct_netdir().unwrap_if_sufficient().unwrap()
1045    }
1046
1047    #[test]
1048    fn sample_test() {
1049        // Make a test network that gives every relay equal weight, and which
1050        // has 20 viable (Guard + V2Dir + DirCache=2) candidates.  Otherwise the
1051        // calculation of collision probability at the end of this function is
1052        // too tricky.
1053        let netdir = tor_netdir::testnet::construct_custom_netdir(|idx, builder, _| {
1054            // Give every node equal bandwidth.
1055            builder.rs.weight(RelayWeight::Measured(1000));
1056            // The default network has 40 relays, and the first 10 are
1057            // not Guard by default.
1058            if idx >= 10 {
1059                builder.rs.add_flags(RelayFlag::Guard);
1060                if idx >= 20 {
1061                    builder.rs.protos("DirCache=2".parse().unwrap());
1062                } else {
1063                    builder.rs.protos("".parse().unwrap());
1064                }
1065            }
1066        })
1067        .unwrap()
1068        .unwrap_if_sufficient()
1069        .unwrap();
1070        // Make sure that we got the numbers we expected.
1071        assert_eq!(40, netdir.relays().count());
1072        assert_eq!(
1073            30,
1074            netdir
1075                .relays()
1076                .filter(|r| r.low_level_details().is_suitable_as_guard())
1077                .count()
1078        );
1079        assert_eq!(
1080            20,
1081            netdir
1082                .relays()
1083                .filter(|r| r.low_level_details().is_suitable_as_guard()
1084                    && r.low_level_details().is_dir_cache())
1085                .count()
1086        );
1087
1088        let params = GuardParams {
1089            min_filtered_sample_size: 5,
1090            max_sample_bw_fraction: 1.0,
1091            ..GuardParams::default()
1092        };
1093
1094        let mut samples: Vec<HashSet<GuardId>> = Vec::new();
1095        for _ in 0..3 {
1096            let mut guards = GuardSet::default();
1097            guards.extend_sample_as_needed(SystemTime::get(), &params, &netdir);
1098            assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1099            assert_eq!(guards.confirmed.len(), 0);
1100            assert_eq!(guards.primary.len(), 0);
1101            guards.assert_consistency();
1102
1103            // make sure all the guards are okay.
1104            for guard in guards.guards.values() {
1105                let id = FirstHopId::in_sample(GuardSetSelector::Default, guard.guard_id().clone());
1106                let relay = id.get_relay(&netdir).unwrap();
1107                assert!(relay.low_level_details().is_suitable_as_guard());
1108                assert!(relay.low_level_details().is_dir_cache());
1109                assert!(guards.guards.by_all_ids(&relay).is_some());
1110                {
1111                    assert!(!guard.is_expired(&params, SystemTime::get()));
1112                }
1113            }
1114
1115            // Make sure that the sample doesn't expand any further.
1116            guards.extend_sample_as_needed(SystemTime::get(), &params, &netdir);
1117            assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1118            guards.assert_consistency();
1119
1120            samples.push(guards.sample.into_iter().collect());
1121        }
1122
1123        // The probability of getting the same sample 3 times in a row is (20 choose 5)^-2,
1124        // which is pretty low.  (About 1 in 240 million.)
1125        assert!(samples[0] != samples[1] || samples[1] != samples[2]);
1126    }
1127
1128    #[test]
1129    fn persistence() {
1130        let netdir = netdir();
1131        let params = GuardParams {
1132            min_filtered_sample_size: 5,
1133            ..GuardParams::default()
1134        };
1135
1136        let t1 = SystemTime::get();
1137        let t2 = t1 + Duration::from_secs(20);
1138
1139        let mut guards = GuardSet::default();
1140        guards.extend_sample_as_needed(t1, &params, &netdir);
1141
1142        // Pick a guard and mark it as confirmed.
1143        let id1 = guards.sample[0].clone();
1144        guards.record_success(&id1, &params, None, t2);
1145        assert_eq!(&guards.confirmed, std::slice::from_ref(&id1));
1146
1147        // Encode the guards, then decode them.
1148        let state: GuardSample = (&guards).into();
1149        let guards2: GuardSet = state.into();
1150
1151        assert_eq!(&guards2.sample, &guards.sample);
1152        assert_eq!(&guards2.confirmed, &guards.confirmed);
1153        assert_eq!(&guards2.confirmed, &[id1]);
1154        assert_eq!(
1155            guards
1156                .guards
1157                .values()
1158                .map(Guard::guard_id)
1159                .collect::<HashSet<_>>(),
1160            guards2
1161                .guards
1162                .values()
1163                .map(Guard::guard_id)
1164                .collect::<HashSet<_>>()
1165        );
1166        for g in guards.guards.values() {
1167            let g2 = guards2.guards.by_all_ids(g.guard_id()).unwrap();
1168            assert_eq!(format!("{:?}", g), format!("{:?}", g2));
1169        }
1170    }
1171
1172    #[test]
1173    fn select_primary() {
1174        let netdir = netdir();
1175        let params = GuardParams {
1176            min_filtered_sample_size: 5,
1177            n_primary: 4,
1178            ..GuardParams::default()
1179        };
1180        let t1 = SystemTime::get();
1181        let t2 = t1 + Duration::from_secs(20);
1182        let t3 = t2 + Duration::from_secs(30);
1183
1184        let mut guards = GuardSet::default();
1185        guards.extend_sample_as_needed(t1, &params, &netdir);
1186
1187        // Pick a guard and mark it as confirmed.
1188        let id3 = guards.sample[3].clone();
1189        guards.record_success(&id3, &params, None, t2);
1190        assert_eq!(&guards.confirmed, std::slice::from_ref(&id3));
1191        let id1 = guards.sample[1].clone();
1192        guards.record_success(&id1, &params, None, t3);
1193        assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone()]);
1194
1195        // Select primary guards and make sure we're obeying the rules.
1196        guards.select_primary_guards(&params);
1197        assert_eq!(guards.primary.len(), 4);
1198        assert_eq!(&guards.primary[0], &id3);
1199        assert_eq!(&guards.primary[1], &id1);
1200        let p3 = guards.primary[2].clone();
1201        let p4 = guards.primary[3].clone();
1202        assert_eq!(
1203            [id1.clone(), id3.clone(), p3.clone(), p4.clone()]
1204                .iter()
1205                .unique()
1206                .count(),
1207            4
1208        );
1209
1210        // Mark another guard as confirmed and see that the list changes to put
1211        // that guard right after the previously confirmed guards, but we keep
1212        // one of the previous unconfirmed primary guards.
1213        guards.record_success(&p4, &params, None, t3);
1214        assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone(), p4.clone()]);
1215        guards.select_primary_guards(&params);
1216        assert_eq!(guards.primary.len(), 4);
1217        assert_eq!(&guards.primary[0], &id3);
1218        assert_eq!(&guards.primary[1], &id1);
1219        assert_eq!(&guards.primary, &[id3, id1, p4, p3]);
1220    }
1221
1222    #[test]
1223    fn expiration() {
1224        let netdir = netdir();
1225        let params = GuardParams::default();
1226        let t1 = SystemTime::get();
1227
1228        let mut guards = GuardSet::default();
1229        guards.extend_sample_as_needed(t1, &params, &netdir);
1230        // note that there are only 10 Guard+V2Dir nodes in the netdir().
1231        assert_eq!(guards.sample.len(), 10);
1232
1233        // Mark one guard as confirmed; it will have a different timeout.
1234        // Pick a guard and mark it as confirmed.
1235        let id1 = guards.sample[0].clone();
1236        guards.record_success(&id1, &params, None, t1);
1237        assert_eq!(&guards.confirmed, &[id1]);
1238
1239        let one_day = Duration::from_secs(86400);
1240        guards.expire_old_guards(&params, t1 + one_day * 30);
1241        assert_eq!(guards.sample.len(), 10); // nothing has expired.
1242
1243        // This is long enough to make sure that the confirmed guard has expired.
1244        guards.expire_old_guards(&params, t1 + one_day * 70);
1245        assert_eq!(guards.sample.len(), 9);
1246
1247        guards.expire_old_guards(&params, t1 + one_day * 200);
1248        assert_eq!(guards.sample.len(), 0);
1249    }
1250
1251    #[test]
1252    #[allow(clippy::cognitive_complexity)]
1253    fn sampling_and_usage() {
1254        let netdir = netdir();
1255        let params = GuardParams {
1256            min_filtered_sample_size: 5,
1257            n_primary: 2,
1258            ..GuardParams::default()
1259        };
1260        let st1 = SystemTime::get();
1261        let i1 = Instant::get();
1262        let sec = Duration::from_secs(1);
1263
1264        let mut guards = GuardSet::default();
1265        guards.extend_sample_as_needed(st1, &params, &netdir);
1266        guards.select_primary_guards(&params);
1267
1268        // First guard: try it, and let it fail.
1269        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1270        let id1 = guards.primary[0].clone();
1271        let id2 = guards.primary[1].clone();
1272        let (src, id) = guards.pick_guard_id(&usage, &params, i1).unwrap();
1273        assert_eq!(src, ListKind::Primary);
1274        assert_eq!(&id, &id1);
1275
1276        guards.record_attempt(&id, i1);
1277        guards.record_failure(&id, None, i1 + sec);
1278
1279        // Second guard: try it, and try it again, and have it fail.
1280        let (src, id) = guards.pick_guard_id(&usage, &params, i1 + sec).unwrap();
1281        assert_eq!(src, ListKind::Primary);
1282        assert_eq!(&id, &id2);
1283        guards.record_attempt(&id, i1 + sec);
1284
1285        let (src, id_x) = guards.pick_guard_id(&usage, &params, i1 + sec).unwrap();
1286        // We get the same guard this (second) time that we pick it too, since
1287        // it is a primary guard, and is_pending won't block it.
1288        assert_eq!(id_x, id);
1289        assert_eq!(src, ListKind::Primary);
1290        guards.record_attempt(&id_x, i1 + sec * 2);
1291        guards.record_failure(&id_x, None, i1 + sec * 3);
1292        guards.record_failure(&id, None, i1 + sec * 4);
1293
1294        // Third guard: this one won't be primary.
1295        let (src, id3) = guards.pick_guard_id(&usage, &params, i1 + sec * 4).unwrap();
1296        assert_eq!(src, ListKind::Sample);
1297        assert!(!guards.primary.contains(&id3));
1298        guards.record_attempt(&id3, i1 + sec * 5);
1299
1300        // Fourth guard: Third guard will be pending, so a different one gets
1301        // handed out here.
1302        let (src, id4) = guards.pick_guard_id(&usage, &params, i1 + sec * 5).unwrap();
1303        assert_eq!(src, ListKind::Sample);
1304        assert!(id3 != id4);
1305        assert!(!guards.primary.contains(&id4));
1306        guards.record_attempt(&id4, i1 + sec * 6);
1307
1308        // Look at usability status: primary guards should be usable
1309        // immediately; third guard should be too (since primary
1310        // guards are down).  Fourth should not have a known status,
1311        // since third is pending.
1312        assert_eq!(
1313            guards.circ_usability_status(&id1, &usage, &params, i1 + sec * 6),
1314            Some(true)
1315        );
1316        assert_eq!(
1317            guards.circ_usability_status(&id2, &usage, &params, i1 + sec * 6),
1318            Some(true)
1319        );
1320        assert_eq!(
1321            guards.circ_usability_status(&id3, &usage, &params, i1 + sec * 6),
1322            Some(true)
1323        );
1324        assert_eq!(
1325            guards.circ_usability_status(&id4, &usage, &params, i1 + sec * 6),
1326            None
1327        );
1328
1329        // Have both guards succeed.
1330        guards.record_success(&id3, &params, None, st1 + sec * 7);
1331        guards.record_success(&id4, &params, None, st1 + sec * 8);
1332
1333        // Check the impact of having both guards succeed.
1334        assert!(guards.primary_guards_invalidated);
1335        guards.select_primary_guards(&params);
1336        assert_eq!(&guards.primary, &[id3.clone(), id4.clone()]);
1337
1338        // Next time we ask for a guard, we get a primary guard again.
1339        let (src, id) = guards
1340            .pick_guard_id(&usage, &params, i1 + sec * 10)
1341            .unwrap();
1342        assert_eq!(src, ListKind::Primary);
1343        assert_eq!(&id, &id3);
1344
1345        // If we ask for a directory guard, we get one of the primaries.
1346        let mut found = HashSet::new();
1347        let usage = crate::GuardUsageBuilder::default()
1348            .kind(crate::GuardUsageKind::OneHopDirectory)
1349            .build()
1350            .unwrap();
1351        for _ in 0..64 {
1352            let (src, id) = guards
1353                .pick_guard_id(&usage, &params, i1 + sec * 10)
1354                .unwrap();
1355            assert_eq!(src, ListKind::Primary);
1356            assert_eq!(
1357                guards.circ_usability_status(&id, &usage, &params, i1 + sec * 10),
1358                Some(true)
1359            );
1360            guards.record_attempt_abandoned(&id);
1361            found.insert(id);
1362        }
1363        assert!(found.len() == 2);
1364        assert!(found.contains(&id3));
1365        assert!(found.contains(&id4));
1366
1367        // Since the primaries are now up, other guards are not usable.
1368        assert_eq!(
1369            guards.circ_usability_status(&id1, &usage, &params, i1 + sec * 12),
1370            Some(false)
1371        );
1372        assert_eq!(
1373            guards.circ_usability_status(&id2, &usage, &params, i1 + sec * 12),
1374            Some(false)
1375        );
1376    }
1377
1378    #[test]
1379    fn everybodys_down() {
1380        let netdir = netdir();
1381        let params = GuardParams {
1382            min_filtered_sample_size: 5,
1383            n_primary: 2,
1384            max_sample_bw_fraction: 1.0,
1385            ..GuardParams::default()
1386        };
1387        let mut st = SystemTime::get();
1388        let mut inst = Instant::get();
1389        let sec = Duration::from_secs(1);
1390        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1391
1392        let mut guards = GuardSet::default();
1393
1394        guards.extend_sample_as_needed(st, &params, &netdir);
1395        guards.select_primary_guards(&params);
1396
1397        assert_eq!(guards.sample.len(), 5);
1398        for _ in 0..5 {
1399            let (_, id) = guards.pick_guard_id(&usage, &params, inst).unwrap();
1400            guards.record_attempt(&id, inst);
1401            guards.record_failure(&id, None, inst + sec);
1402
1403            inst += sec * 2;
1404            st += sec * 2;
1405        }
1406
1407        let e = guards.pick_guard_id(&usage, &params, inst);
1408        assert!(matches!(e, Err(PickGuardError::AllGuardsDown { .. })));
1409
1410        // Now in theory we should re-grow when we extend.
1411        guards.extend_sample_as_needed(st, &params, &netdir);
1412        guards.select_primary_guards(&params);
1413        assert_eq!(guards.sample.len(), 10);
1414    }
1415
1416    #[test]
1417    fn retry_primary() {
1418        let netdir = netdir();
1419        let params = GuardParams {
1420            min_filtered_sample_size: 5,
1421            n_primary: 2,
1422            max_sample_bw_fraction: 1.0,
1423            ..GuardParams::default()
1424        };
1425        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1426
1427        let mut guards = GuardSet::default();
1428
1429        guards.extend_sample_as_needed(SystemTime::get(), &params, &netdir);
1430        guards.select_primary_guards(&params);
1431
1432        assert_eq!(guards.primary.len(), 2);
1433        assert!(!guards.all_primary_guards_are_unreachable());
1434
1435        // Let one primary guard fail.
1436        let (kind, p_id1) = guards
1437            .pick_guard_id(&usage, &params, Instant::get())
1438            .unwrap();
1439        assert_eq!(kind, ListKind::Primary);
1440        guards.record_failure(&p_id1, None, Instant::get());
1441        assert!(!guards.all_primary_guards_are_unreachable());
1442
1443        // Now let the other one fail.
1444        let (kind, p_id2) = guards
1445            .pick_guard_id(&usage, &params, Instant::get())
1446            .unwrap();
1447        assert_eq!(kind, ListKind::Primary);
1448        guards.record_failure(&p_id2, None, Instant::get());
1449        assert!(guards.all_primary_guards_are_unreachable());
1450
1451        // Now mark the guards retriable.
1452        guards.mark_primary_guards_retriable();
1453        assert!(!guards.all_primary_guards_are_unreachable());
1454        let (kind, p_id3) = guards
1455            .pick_guard_id(&usage, &params, Instant::get())
1456            .unwrap();
1457        assert_eq!(kind, ListKind::Primary);
1458        assert_eq!(p_id3, p_id1);
1459    }
1460
1461    #[test]
1462    fn count_missing_mds() {
1463        let netdir = netdir();
1464        let params = GuardParams {
1465            min_filtered_sample_size: 5,
1466            n_primary: 2,
1467            max_sample_bw_fraction: 1.0,
1468            ..GuardParams::default()
1469        };
1470        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1471        let mut guards = GuardSet::default();
1472        guards.extend_sample_as_needed(SystemTime::get(), &params, &netdir);
1473        guards.select_primary_guards(&params);
1474        assert_eq!(guards.primary.len(), 2);
1475
1476        let (_kind, p_id1) = guards
1477            .pick_guard_id(&usage, &params, Instant::get())
1478            .unwrap();
1479        guards.record_success(&p_id1, &params, None, SystemTime::get());
1480        assert_eq!(guards.n_primary_without_id_info_in(&netdir), 0);
1481
1482        use tor_netdir::testnet;
1483        let netdir2 = testnet::construct_custom_netdir(|_idx, bld, _| {
1484            let md_so_far = bld.md.testing_md().expect("Couldn't build md?");
1485            if &p_id1.0.identity(RelayIdType::Ed25519).unwrap() == md_so_far.ed25519_id() {
1486                bld.omit_md = true;
1487            }
1488        })
1489        .unwrap()
1490        .unwrap_if_sufficient()
1491        .unwrap();
1492
1493        assert_eq!(guards.n_primary_without_id_info_in(&netdir2), 1);
1494    }
1495
1496    #[test]
1497    fn copy_status() {
1498        let netdir = netdir();
1499        let params = GuardParams {
1500            min_filtered_sample_size: 5,
1501            n_primary: 2,
1502            max_sample_bw_fraction: 1.0,
1503            ..GuardParams::default()
1504        };
1505        let mut guards1 = GuardSet::default();
1506        guards1.extend_sample_as_needed(SystemTime::get(), &params, &netdir);
1507        guards1.select_primary_guards(&params);
1508        let mut guards2 = guards1.clone();
1509
1510        // Make a persistent change in guards1, and a different persistent change in guards2.
1511        let id1 = guards1.primary[0].clone();
1512        let id2 = guards1.primary[1].clone();
1513        guards1.record_success(&id1, &params, None, SystemTime::get());
1514        guards2.record_success(&id2, &params, None, SystemTime::get());
1515        // Make a non-persistent change in guards2.
1516        guards2.record_failure(&id2, None, Instant::get());
1517
1518        // Copy status: make sure non-persistent status changed, and  persistent didn't.
1519        guards1.copy_ephemeral_status_into_newly_loaded_state(guards2);
1520        {
1521            let g1 = guards1.get(&id1).unwrap();
1522            let g2 = guards1.get(&id2).unwrap();
1523            assert!(g1.confirmed());
1524            assert!(!g2.confirmed());
1525            assert_eq!(g1.reachable(), Reachable::Untried);
1526            assert_eq!(g2.reachable(), Reachable::Unreachable);
1527        }
1528
1529        // Now make a new set of unrelated guards, and make sure that copying
1530        // from it doesn't change the membership of guards1.
1531        let mut guards3 = GuardSet::default();
1532        let g1_set: HashSet<_> = guards1
1533            .guards
1534            .values()
1535            .map(|g| g.guard_id().clone())
1536            .collect();
1537        let mut g3_set: HashSet<_> = HashSet::new();
1538        for _ in 0..4 {
1539            // There is roughly a 1-in-5000 chance of getting the same set
1540            // twice, so we loop until that doesn't happen.
1541            guards3.extend_sample_as_needed(SystemTime::get(), &params, &netdir);
1542            guards3.select_primary_guards(&params);
1543            g3_set = guards3
1544                .guards
1545                .values()
1546                .map(|g| g.guard_id().clone())
1547                .collect();
1548
1549            // There is roughly a 1-in-5000 chance of getting the same set twice, so
1550            if g1_set == g3_set {
1551                guards3 = GuardSet::default();
1552                continue;
1553            }
1554            break;
1555        }
1556        assert_ne!(g1_set, g3_set);
1557        // Do the copy; make sure that the membership is unchanged.
1558        guards1.copy_ephemeral_status_into_newly_loaded_state(guards3);
1559        let g1_set_new: HashSet<_> = guards1
1560            .guards
1561            .values()
1562            .map(|g| g.guard_id().clone())
1563            .collect();
1564        assert_eq!(g1_set, g1_set_new);
1565    }
1566}