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    ids::GuardId, ExternalActivity, GuardParams, GuardUsage, GuardUsageKind, PickGuardError,
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 std::time::{Instant, SystemTime};
22use tracing::{debug, info};
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_duration_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::{RelayFlags, RelayWeight};
1035
1036    use super::*;
1037    use crate::FirstHopId;
1038    use std::time::Duration;
1039
1040    fn netdir() -> NetDir {
1041        use tor_netdir::testnet;
1042        testnet::construct_netdir().unwrap_if_sufficient().unwrap()
1043    }
1044
1045    #[test]
1046    fn sample_test() {
1047        // Make a test network that gives every relay equal weight, and which
1048        // has 20 viable (Guard + V2Dir + DirCache=2) candidates.  Otherwise the
1049        // calculation of collision probability at the end of this function is
1050        // too tricky.
1051        let netdir = tor_netdir::testnet::construct_custom_netdir(|idx, builder, _| {
1052            // Give every node equal bandwidth.
1053            builder.rs.weight(RelayWeight::Measured(1000));
1054            // The default network has 40 relays, and the first 10 are
1055            // not Guard by default.
1056            if idx >= 10 {
1057                builder.rs.add_flags(RelayFlags::GUARD);
1058                if idx >= 20 {
1059                    builder.rs.protos("DirCache=2".parse().unwrap());
1060                } else {
1061                    builder.rs.protos("".parse().unwrap());
1062                }
1063            }
1064        })
1065        .unwrap()
1066        .unwrap_if_sufficient()
1067        .unwrap();
1068        // Make sure that we got the numbers we expected.
1069        assert_eq!(40, netdir.relays().count());
1070        assert_eq!(
1071            30,
1072            netdir
1073                .relays()
1074                .filter(|r| r.low_level_details().is_suitable_as_guard())
1075                .count()
1076        );
1077        assert_eq!(
1078            20,
1079            netdir
1080                .relays()
1081                .filter(|r| r.low_level_details().is_suitable_as_guard()
1082                    && r.low_level_details().is_dir_cache())
1083                .count()
1084        );
1085
1086        let params = GuardParams {
1087            min_filtered_sample_size: 5,
1088            max_sample_bw_fraction: 1.0,
1089            ..GuardParams::default()
1090        };
1091
1092        let mut samples: Vec<HashSet<GuardId>> = Vec::new();
1093        for _ in 0..3 {
1094            let mut guards = GuardSet::default();
1095            guards.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
1096            assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1097            assert_eq!(guards.confirmed.len(), 0);
1098            assert_eq!(guards.primary.len(), 0);
1099            guards.assert_consistency();
1100
1101            // make sure all the guards are okay.
1102            for guard in guards.guards.values() {
1103                let id = FirstHopId::in_sample(GuardSetSelector::Default, guard.guard_id().clone());
1104                let relay = id.get_relay(&netdir).unwrap();
1105                assert!(relay.low_level_details().is_suitable_as_guard());
1106                assert!(relay.low_level_details().is_dir_cache());
1107                assert!(guards.guards.by_all_ids(&relay).is_some());
1108                {
1109                    assert!(!guard.is_expired(&params, SystemTime::now()));
1110                }
1111            }
1112
1113            // Make sure that the sample doesn't expand any further.
1114            guards.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
1115            assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1116            guards.assert_consistency();
1117
1118            samples.push(guards.sample.into_iter().collect());
1119        }
1120
1121        // The probability of getting the same sample 3 times in a row is (20 choose 5)^-2,
1122        // which is pretty low.  (About 1 in 240 million.)
1123        assert!(samples[0] != samples[1] || samples[1] != samples[2]);
1124    }
1125
1126    #[test]
1127    fn persistence() {
1128        let netdir = netdir();
1129        let params = GuardParams {
1130            min_filtered_sample_size: 5,
1131            ..GuardParams::default()
1132        };
1133
1134        let t1 = SystemTime::now();
1135        let t2 = t1 + Duration::from_secs(20);
1136
1137        let mut guards = GuardSet::default();
1138        guards.extend_sample_as_needed(t1, &params, &netdir);
1139
1140        // Pick a guard and mark it as confirmed.
1141        let id1 = guards.sample[0].clone();
1142        guards.record_success(&id1, &params, None, t2);
1143        assert_eq!(&guards.confirmed, &[id1.clone()]);
1144
1145        // Encode the guards, then decode them.
1146        let state: GuardSample = (&guards).into();
1147        let guards2: GuardSet = state.into();
1148
1149        assert_eq!(&guards2.sample, &guards.sample);
1150        assert_eq!(&guards2.confirmed, &guards.confirmed);
1151        assert_eq!(&guards2.confirmed, &[id1]);
1152        assert_eq!(
1153            guards
1154                .guards
1155                .values()
1156                .map(Guard::guard_id)
1157                .collect::<HashSet<_>>(),
1158            guards2
1159                .guards
1160                .values()
1161                .map(Guard::guard_id)
1162                .collect::<HashSet<_>>()
1163        );
1164        for g in guards.guards.values() {
1165            let g2 = guards2.guards.by_all_ids(g.guard_id()).unwrap();
1166            assert_eq!(format!("{:?}", g), format!("{:?}", g2));
1167        }
1168    }
1169
1170    #[test]
1171    fn select_primary() {
1172        let netdir = netdir();
1173        let params = GuardParams {
1174            min_filtered_sample_size: 5,
1175            n_primary: 4,
1176            ..GuardParams::default()
1177        };
1178        let t1 = SystemTime::now();
1179        let t2 = t1 + Duration::from_secs(20);
1180        let t3 = t2 + Duration::from_secs(30);
1181
1182        let mut guards = GuardSet::default();
1183        guards.extend_sample_as_needed(t1, &params, &netdir);
1184
1185        // Pick a guard and mark it as confirmed.
1186        let id3 = guards.sample[3].clone();
1187        guards.record_success(&id3, &params, None, t2);
1188        assert_eq!(&guards.confirmed, &[id3.clone()]);
1189        let id1 = guards.sample[1].clone();
1190        guards.record_success(&id1, &params, None, t3);
1191        assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone()]);
1192
1193        // Select primary guards and make sure we're obeying the rules.
1194        guards.select_primary_guards(&params);
1195        assert_eq!(guards.primary.len(), 4);
1196        assert_eq!(&guards.primary[0], &id3);
1197        assert_eq!(&guards.primary[1], &id1);
1198        let p3 = guards.primary[2].clone();
1199        let p4 = guards.primary[3].clone();
1200        assert_eq!(
1201            [id1.clone(), id3.clone(), p3.clone(), p4.clone()]
1202                .iter()
1203                .unique()
1204                .count(),
1205            4
1206        );
1207
1208        // Mark another guard as confirmed and see that the list changes to put
1209        // that guard right after the previously confirmed guards, but we keep
1210        // one of the previous unconfirmed primary guards.
1211        guards.record_success(&p4, &params, None, t3);
1212        assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone(), p4.clone()]);
1213        guards.select_primary_guards(&params);
1214        assert_eq!(guards.primary.len(), 4);
1215        assert_eq!(&guards.primary[0], &id3);
1216        assert_eq!(&guards.primary[1], &id1);
1217        assert_eq!(&guards.primary, &[id3, id1, p4, p3]);
1218    }
1219
1220    #[test]
1221    fn expiration() {
1222        let netdir = netdir();
1223        let params = GuardParams::default();
1224        let t1 = SystemTime::now();
1225
1226        let mut guards = GuardSet::default();
1227        guards.extend_sample_as_needed(t1, &params, &netdir);
1228        // note that there are only 10 Guard+V2Dir nodes in the netdir().
1229        assert_eq!(guards.sample.len(), 10);
1230
1231        // Mark one guard as confirmed; it will have a different timeout.
1232        // Pick a guard and mark it as confirmed.
1233        let id1 = guards.sample[0].clone();
1234        guards.record_success(&id1, &params, None, t1);
1235        assert_eq!(&guards.confirmed, &[id1]);
1236
1237        let one_day = Duration::from_secs(86400);
1238        guards.expire_old_guards(&params, t1 + one_day * 30);
1239        assert_eq!(guards.sample.len(), 10); // nothing has expired.
1240
1241        // This is long enough to make sure that the confirmed guard has expired.
1242        guards.expire_old_guards(&params, t1 + one_day * 70);
1243        assert_eq!(guards.sample.len(), 9);
1244
1245        guards.expire_old_guards(&params, t1 + one_day * 200);
1246        assert_eq!(guards.sample.len(), 0);
1247    }
1248
1249    #[test]
1250    #[allow(clippy::cognitive_complexity)]
1251    fn sampling_and_usage() {
1252        let netdir = netdir();
1253        let params = GuardParams {
1254            min_filtered_sample_size: 5,
1255            n_primary: 2,
1256            ..GuardParams::default()
1257        };
1258        let st1 = SystemTime::now();
1259        let i1 = Instant::now();
1260        let sec = Duration::from_secs(1);
1261
1262        let mut guards = GuardSet::default();
1263        guards.extend_sample_as_needed(st1, &params, &netdir);
1264        guards.select_primary_guards(&params);
1265
1266        // First guard: try it, and let it fail.
1267        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1268        let id1 = guards.primary[0].clone();
1269        let id2 = guards.primary[1].clone();
1270        let (src, id) = guards.pick_guard_id(&usage, &params, i1).unwrap();
1271        assert_eq!(src, ListKind::Primary);
1272        assert_eq!(&id, &id1);
1273
1274        guards.record_attempt(&id, i1);
1275        guards.record_failure(&id, None, i1 + sec);
1276
1277        // Second guard: try it, and try it again, and have it fail.
1278        let (src, id) = guards.pick_guard_id(&usage, &params, i1 + sec).unwrap();
1279        assert_eq!(src, ListKind::Primary);
1280        assert_eq!(&id, &id2);
1281        guards.record_attempt(&id, i1 + sec);
1282
1283        let (src, id_x) = guards.pick_guard_id(&usage, &params, i1 + sec).unwrap();
1284        // We get the same guard this (second) time that we pick it too, since
1285        // it is a primary guard, and is_pending won't block it.
1286        assert_eq!(id_x, id);
1287        assert_eq!(src, ListKind::Primary);
1288        guards.record_attempt(&id_x, i1 + sec * 2);
1289        guards.record_failure(&id_x, None, i1 + sec * 3);
1290        guards.record_failure(&id, None, i1 + sec * 4);
1291
1292        // Third guard: this one won't be primary.
1293        let (src, id3) = guards.pick_guard_id(&usage, &params, i1 + sec * 4).unwrap();
1294        assert_eq!(src, ListKind::Sample);
1295        assert!(!guards.primary.contains(&id3));
1296        guards.record_attempt(&id3, i1 + sec * 5);
1297
1298        // Fourth guard: Third guard will be pending, so a different one gets
1299        // handed out here.
1300        let (src, id4) = guards.pick_guard_id(&usage, &params, i1 + sec * 5).unwrap();
1301        assert_eq!(src, ListKind::Sample);
1302        assert!(id3 != id4);
1303        assert!(!guards.primary.contains(&id4));
1304        guards.record_attempt(&id4, i1 + sec * 6);
1305
1306        // Look at usability status: primary guards should be usable
1307        // immediately; third guard should be too (since primary
1308        // guards are down).  Fourth should not have a known status,
1309        // since third is pending.
1310        assert_eq!(
1311            guards.circ_usability_status(&id1, &usage, &params, i1 + sec * 6),
1312            Some(true)
1313        );
1314        assert_eq!(
1315            guards.circ_usability_status(&id2, &usage, &params, i1 + sec * 6),
1316            Some(true)
1317        );
1318        assert_eq!(
1319            guards.circ_usability_status(&id3, &usage, &params, i1 + sec * 6),
1320            Some(true)
1321        );
1322        assert_eq!(
1323            guards.circ_usability_status(&id4, &usage, &params, i1 + sec * 6),
1324            None
1325        );
1326
1327        // Have both guards succeed.
1328        guards.record_success(&id3, &params, None, st1 + sec * 7);
1329        guards.record_success(&id4, &params, None, st1 + sec * 8);
1330
1331        // Check the impact of having both guards succeed.
1332        assert!(guards.primary_guards_invalidated);
1333        guards.select_primary_guards(&params);
1334        assert_eq!(&guards.primary, &[id3.clone(), id4.clone()]);
1335
1336        // Next time we ask for a guard, we get a primary guard again.
1337        let (src, id) = guards
1338            .pick_guard_id(&usage, &params, i1 + sec * 10)
1339            .unwrap();
1340        assert_eq!(src, ListKind::Primary);
1341        assert_eq!(&id, &id3);
1342
1343        // If we ask for a directory guard, we get one of the primaries.
1344        let mut found = HashSet::new();
1345        let usage = crate::GuardUsageBuilder::default()
1346            .kind(crate::GuardUsageKind::OneHopDirectory)
1347            .build()
1348            .unwrap();
1349        for _ in 0..64 {
1350            let (src, id) = guards
1351                .pick_guard_id(&usage, &params, i1 + sec * 10)
1352                .unwrap();
1353            assert_eq!(src, ListKind::Primary);
1354            assert_eq!(
1355                guards.circ_usability_status(&id, &usage, &params, i1 + sec * 10),
1356                Some(true)
1357            );
1358            guards.record_attempt_abandoned(&id);
1359            found.insert(id);
1360        }
1361        assert!(found.len() == 2);
1362        assert!(found.contains(&id3));
1363        assert!(found.contains(&id4));
1364
1365        // Since the primaries are now up, other guards are not usable.
1366        assert_eq!(
1367            guards.circ_usability_status(&id1, &usage, &params, i1 + sec * 12),
1368            Some(false)
1369        );
1370        assert_eq!(
1371            guards.circ_usability_status(&id2, &usage, &params, i1 + sec * 12),
1372            Some(false)
1373        );
1374    }
1375
1376    #[test]
1377    fn everybodys_down() {
1378        let netdir = netdir();
1379        let params = GuardParams {
1380            min_filtered_sample_size: 5,
1381            n_primary: 2,
1382            max_sample_bw_fraction: 1.0,
1383            ..GuardParams::default()
1384        };
1385        let mut st = SystemTime::now();
1386        let mut inst = Instant::now();
1387        let sec = Duration::from_secs(1);
1388        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1389
1390        let mut guards = GuardSet::default();
1391
1392        guards.extend_sample_as_needed(st, &params, &netdir);
1393        guards.select_primary_guards(&params);
1394
1395        assert_eq!(guards.sample.len(), 5);
1396        for _ in 0..5 {
1397            let (_, id) = guards.pick_guard_id(&usage, &params, inst).unwrap();
1398            guards.record_attempt(&id, inst);
1399            guards.record_failure(&id, None, inst + sec);
1400
1401            inst += sec * 2;
1402            st += sec * 2;
1403        }
1404
1405        let e = guards.pick_guard_id(&usage, &params, inst);
1406        assert!(matches!(e, Err(PickGuardError::AllGuardsDown { .. })));
1407
1408        // Now in theory we should re-grow when we extend.
1409        guards.extend_sample_as_needed(st, &params, &netdir);
1410        guards.select_primary_guards(&params);
1411        assert_eq!(guards.sample.len(), 10);
1412    }
1413
1414    #[test]
1415    fn retry_primary() {
1416        let netdir = netdir();
1417        let params = GuardParams {
1418            min_filtered_sample_size: 5,
1419            n_primary: 2,
1420            max_sample_bw_fraction: 1.0,
1421            ..GuardParams::default()
1422        };
1423        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1424
1425        let mut guards = GuardSet::default();
1426
1427        guards.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
1428        guards.select_primary_guards(&params);
1429
1430        assert_eq!(guards.primary.len(), 2);
1431        assert!(!guards.all_primary_guards_are_unreachable());
1432
1433        // Let one primary guard fail.
1434        let (kind, p_id1) = guards
1435            .pick_guard_id(&usage, &params, Instant::now())
1436            .unwrap();
1437        assert_eq!(kind, ListKind::Primary);
1438        guards.record_failure(&p_id1, None, Instant::now());
1439        assert!(!guards.all_primary_guards_are_unreachable());
1440
1441        // Now let the other one fail.
1442        let (kind, p_id2) = guards
1443            .pick_guard_id(&usage, &params, Instant::now())
1444            .unwrap();
1445        assert_eq!(kind, ListKind::Primary);
1446        guards.record_failure(&p_id2, None, Instant::now());
1447        assert!(guards.all_primary_guards_are_unreachable());
1448
1449        // Now mark the guards retriable.
1450        guards.mark_primary_guards_retriable();
1451        assert!(!guards.all_primary_guards_are_unreachable());
1452        let (kind, p_id3) = guards
1453            .pick_guard_id(&usage, &params, Instant::now())
1454            .unwrap();
1455        assert_eq!(kind, ListKind::Primary);
1456        assert_eq!(p_id3, p_id1);
1457    }
1458
1459    #[test]
1460    fn count_missing_mds() {
1461        let netdir = netdir();
1462        let params = GuardParams {
1463            min_filtered_sample_size: 5,
1464            n_primary: 2,
1465            max_sample_bw_fraction: 1.0,
1466            ..GuardParams::default()
1467        };
1468        let usage = crate::GuardUsageBuilder::default().build().unwrap();
1469        let mut guards = GuardSet::default();
1470        guards.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
1471        guards.select_primary_guards(&params);
1472        assert_eq!(guards.primary.len(), 2);
1473
1474        let (_kind, p_id1) = guards
1475            .pick_guard_id(&usage, &params, Instant::now())
1476            .unwrap();
1477        guards.record_success(&p_id1, &params, None, SystemTime::now());
1478        assert_eq!(guards.n_primary_without_id_info_in(&netdir), 0);
1479
1480        use tor_netdir::testnet;
1481        let netdir2 = testnet::construct_custom_netdir(|_idx, bld, _| {
1482            let md_so_far = bld.md.testing_md().expect("Couldn't build md?");
1483            if &p_id1.0.identity(RelayIdType::Ed25519).unwrap() == md_so_far.ed25519_id() {
1484                bld.omit_md = true;
1485            }
1486        })
1487        .unwrap()
1488        .unwrap_if_sufficient()
1489        .unwrap();
1490
1491        assert_eq!(guards.n_primary_without_id_info_in(&netdir2), 1);
1492    }
1493
1494    #[test]
1495    fn copy_status() {
1496        let netdir = netdir();
1497        let params = GuardParams {
1498            min_filtered_sample_size: 5,
1499            n_primary: 2,
1500            max_sample_bw_fraction: 1.0,
1501            ..GuardParams::default()
1502        };
1503        let mut guards1 = GuardSet::default();
1504        guards1.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
1505        guards1.select_primary_guards(&params);
1506        let mut guards2 = guards1.clone();
1507
1508        // Make a persistent change in guards1, and a different persistent change in guards2.
1509        let id1 = guards1.primary[0].clone();
1510        let id2 = guards1.primary[1].clone();
1511        guards1.record_success(&id1, &params, None, SystemTime::now());
1512        guards2.record_success(&id2, &params, None, SystemTime::now());
1513        // Make a non-persistent change in guards2.
1514        guards2.record_failure(&id2, None, Instant::now());
1515
1516        // Copy status: make sure non-persistent status changed, and  persistent didn't.
1517        guards1.copy_ephemeral_status_into_newly_loaded_state(guards2);
1518        {
1519            let g1 = guards1.get(&id1).unwrap();
1520            let g2 = guards1.get(&id2).unwrap();
1521            assert!(g1.confirmed());
1522            assert!(!g2.confirmed());
1523            assert_eq!(g1.reachable(), Reachable::Untried);
1524            assert_eq!(g2.reachable(), Reachable::Unreachable);
1525        }
1526
1527        // Now make a new set of unrelated guards, and make sure that copying
1528        // from it doesn't change the membership of guards1.
1529        let mut guards3 = GuardSet::default();
1530        let g1_set: HashSet<_> = guards1
1531            .guards
1532            .values()
1533            .map(|g| g.guard_id().clone())
1534            .collect();
1535        let mut g3_set: HashSet<_> = HashSet::new();
1536        for _ in 0..4 {
1537            // There is roughly a 1-in-5000 chance of getting the same set
1538            // twice, so we loop until that doesn't happen.
1539            guards3.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
1540            guards3.select_primary_guards(&params);
1541            g3_set = guards3
1542                .guards
1543                .values()
1544                .map(|g| g.guard_id().clone())
1545                .collect();
1546
1547            // There is roughly a 1-in-5000 chance of getting the same set twice, so
1548            if g1_set == g3_set {
1549                guards3 = GuardSet::default();
1550                continue;
1551            }
1552            break;
1553        }
1554        assert_ne!(g1_set, g3_set);
1555        // Do the copy; make sure that the membership is unchanged.
1556        guards1.copy_ephemeral_status_into_newly_loaded_state(guards3);
1557        let g1_set_new: HashSet<_> = guards1
1558            .guards
1559            .values()
1560            .map(|g| g.guard_id().clone())
1561            .collect();
1562        assert_eq!(g1_set, g1_set_new);
1563    }
1564}