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