1mod candidate;
5
6use crate::filter::GuardFilter;
7use crate::guard::{Guard, NewlyConfirmed, Reachable};
8use crate::skew::SkewObservation;
9use crate::{
10 ExternalActivity, GuardParams, GuardUsage, GuardUsageKind, PickGuardError, ids::GuardId,
11};
12use crate::{FirstHop, GuardSetSelector};
13use tor_basic_utils::iter::{FilterCount, IteratorExt as _};
14use tor_linkspec::{ByRelayIds, HasRelayIds};
15
16use itertools::Itertools;
17use rand::seq::IndexedRandom;
18use serde::{Deserialize, Serialize};
19use std::borrow::Cow;
20use std::collections::{HashMap, HashSet};
21use tracing::{debug, info};
22use web_time_compat::{Instant, SystemTime};
23
24#[allow(unused_imports)]
25pub(crate) use candidate::{Candidate, CandidateStatus, Universe, UniverseRef, WeightThreshold};
26
27#[derive(Debug, Default, Clone, Deserialize)]
48#[serde(from = "GuardSample")]
49pub(crate) struct GuardSet {
50 guards: ByRelayIds<Guard>,
110 sample: Vec<GuardId>,
114 confirmed: Vec<GuardId>,
119 primary: Vec<GuardId>,
124 active_filter: GuardFilter,
131
132 filter_is_restrictive: bool,
134
135 primary_guards_invalidated: bool,
138
139 unknown_fields: HashMap<String, JsonValue>,
142}
143
144#[derive(Debug, Copy, Clone, Eq, PartialEq)]
146pub(crate) enum ListKind {
147 Primary,
149 Confirmed,
151 Sample,
153 Fallback,
155}
156
157impl ListKind {
158 pub(crate) fn is_primary(&self) -> bool {
160 self == &ListKind::Primary
161 }
162
163 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 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 fn fix_consistency(&mut self) {
194 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 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 pub(crate) fn get(&self, id: &GuardId) -> Option<&Guard> {
226 self.guards.by_all_ids(id)
227 }
228
229 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; 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 pub(crate) fn filter(&self) -> &GuardFilter {
254 &self.active_filter
255 }
256
257 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 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 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 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 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 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 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; }
406 if self.guards.len() >= params.max_sample_size {
407 return false; }
409
410 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 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 (n_to_add * 3, &no_filter)
429 };
430
431 let candidates = dir.sample(&self.guards, pre_filter, n_candidates);
432
433 let mut any_added = false;
435 let mut n_filtered_usable = n_filtered_usable;
436 for (candidate, weight) in candidates {
437 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 break;
447 }
448 if n_filtered_usable >= params.min_filtered_sample_size {
449 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 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 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 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 self.fix_consistency();
510 }
511
512 pub(crate) fn select_primary_guards(&mut self, params: &GuardParams) {
521 let old_primary = self.primary.clone();
528
529 self.primary = self
530 .confirmed
532 .iter()
533 .chain(self.primary.iter())
535 .chain(self.reachable_sample_ids())
538 .unique()
540 .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 .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 for id in &self.primary {
562 self.guards.modify_by_all_ids(id, |guard| {
563 guard.note_exploratory_circ(false);
564 });
565 }
566
567 self.assert_consistency();
571 self.primary_guards_invalidated = false;
572 }
573
574 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 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 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 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 fn guard_is_primary(&self, guard_id: &GuardId) -> bool {
631 self.primary
635 .iter()
636 .any(|p| p.has_all_relay_ids_from(guard_id))
637 }
638
639 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 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 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 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 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 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 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 pub(crate) fn record_failure(
732 &mut self,
733 guard_id: &GuardId,
734 how: Option<ExternalActivity>,
735 now: Instant,
736 ) {
737 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 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 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 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 pub(crate) fn skew_observations(&self) -> impl Iterator<Item = &SkewObservation> {
770 self.guards.values().filter_map(|g| g.skew())
771 }
772
773 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 if self.guard_is_primary(guard_id) {
802 return Some(true);
807 }
808
809 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 Some(false)
841 }
842
843 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 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 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 .filter_cnt(&mut running, |(_, g)| {
901 g.usable()
902 && g.reachable() != Reachable::Unreachable
903 && g.ready_for_usage(usage, now)
904 })
905 .filter_cnt(&mut pending, |(_, g)| !g.exploratory_circ_pending())
908 .filter_cnt(&mut suitable, |(_, g)| g.conforms_to_usage(usage))
911 .filter_cnt(&mut filtered, |(_, g)| self.active_filter.permits(*g))
913 .take(n_options)
915 .collect();
916
917 if options.iter().any(|(src, _)| src.is_primary()) {
918 options.retain(|(src, _)| src.is_primary());
920 } else {
921 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 #[cfg(feature = "bridge-client")]
950 pub(crate) fn descriptors_to_request(&self, now: Instant, params: &GuardParams) -> Vec<&Guard> {
951 const MINIMUM: usize = 2;
955
956 let maximum = std::cmp::max(params.data_parallelism, MINIMUM);
957 let data_usage = GuardUsage::default();
958
959 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#[derive(Default, Debug, Clone, Serialize, Deserialize)]
986pub(crate) struct GuardSample<'a> {
987 guards: Vec<Cow<'a, Guard>>,
989 confirmed: Cow<'a, [GuardId]>,
991 #[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 #![allow(clippy::bool_assert_comparison)]
1021 #![allow(clippy::clone_on_copy)]
1022 #![allow(clippy::dbg_macro)]
1023 #![allow(clippy::mixed_attributes_style)]
1024 #![allow(clippy::print_stderr)]
1025 #![allow(clippy::print_stdout)]
1026 #![allow(clippy::single_char_pattern)]
1027 #![allow(clippy::unwrap_used)]
1028 #![allow(clippy::unchecked_time_subtraction)]
1029 #![allow(clippy::useless_vec)]
1030 #![allow(clippy::needless_pass_by_value)]
1031 use tor_linkspec::{HasRelayIds, RelayIdType};
1033 use tor_netdir::NetDir;
1034 use tor_netdoc::doc::netstatus::RelayWeight;
1035 use tor_netdoc::types::relay_flags::RelayFlag;
1036 use web_time_compat::{InstantExt, SystemTimeExt};
1037
1038 use super::*;
1039 use crate::FirstHopId;
1040 use std::time::Duration;
1041
1042 fn netdir() -> NetDir {
1043 use tor_netdir::testnet;
1044 testnet::construct_netdir().unwrap_if_sufficient().unwrap()
1045 }
1046
1047 #[test]
1048 fn sample_test() {
1049 let netdir = tor_netdir::testnet::construct_custom_netdir(|idx, builder, _| {
1054 builder.rs.weight(RelayWeight::Measured(1000));
1056 if idx >= 10 {
1059 builder.rs.add_flags(RelayFlag::Guard);
1060 if idx >= 20 {
1061 builder.rs.protos("DirCache=2".parse().unwrap());
1062 } else {
1063 builder.rs.protos("".parse().unwrap());
1064 }
1065 }
1066 })
1067 .unwrap()
1068 .unwrap_if_sufficient()
1069 .unwrap();
1070 assert_eq!(40, netdir.relays().count());
1072 assert_eq!(
1073 30,
1074 netdir
1075 .relays()
1076 .filter(|r| r.low_level_details().is_suitable_as_guard())
1077 .count()
1078 );
1079 assert_eq!(
1080 20,
1081 netdir
1082 .relays()
1083 .filter(|r| r.low_level_details().is_suitable_as_guard()
1084 && r.low_level_details().is_dir_cache())
1085 .count()
1086 );
1087
1088 let params = GuardParams {
1089 min_filtered_sample_size: 5,
1090 max_sample_bw_fraction: 1.0,
1091 ..GuardParams::default()
1092 };
1093
1094 let mut samples: Vec<HashSet<GuardId>> = Vec::new();
1095 for _ in 0..3 {
1096 let mut guards = GuardSet::default();
1097 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1098 assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1099 assert_eq!(guards.confirmed.len(), 0);
1100 assert_eq!(guards.primary.len(), 0);
1101 guards.assert_consistency();
1102
1103 for guard in guards.guards.values() {
1105 let id = FirstHopId::in_sample(GuardSetSelector::Default, guard.guard_id().clone());
1106 let relay = id.get_relay(&netdir).unwrap();
1107 assert!(relay.low_level_details().is_suitable_as_guard());
1108 assert!(relay.low_level_details().is_dir_cache());
1109 assert!(guards.guards.by_all_ids(&relay).is_some());
1110 {
1111 assert!(!guard.is_expired(¶ms, SystemTime::get()));
1112 }
1113 }
1114
1115 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1117 assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1118 guards.assert_consistency();
1119
1120 samples.push(guards.sample.into_iter().collect());
1121 }
1122
1123 assert!(samples[0] != samples[1] || samples[1] != samples[2]);
1126 }
1127
1128 #[test]
1129 fn persistence() {
1130 let netdir = netdir();
1131 let params = GuardParams {
1132 min_filtered_sample_size: 5,
1133 ..GuardParams::default()
1134 };
1135
1136 let t1 = SystemTime::get();
1137 let t2 = t1 + Duration::from_secs(20);
1138
1139 let mut guards = GuardSet::default();
1140 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1141
1142 let id1 = guards.sample[0].clone();
1144 guards.record_success(&id1, ¶ms, None, t2);
1145 assert_eq!(&guards.confirmed, std::slice::from_ref(&id1));
1146
1147 let state: GuardSample = (&guards).into();
1149 let guards2: GuardSet = state.into();
1150
1151 assert_eq!(&guards2.sample, &guards.sample);
1152 assert_eq!(&guards2.confirmed, &guards.confirmed);
1153 assert_eq!(&guards2.confirmed, &[id1]);
1154 assert_eq!(
1155 guards
1156 .guards
1157 .values()
1158 .map(Guard::guard_id)
1159 .collect::<HashSet<_>>(),
1160 guards2
1161 .guards
1162 .values()
1163 .map(Guard::guard_id)
1164 .collect::<HashSet<_>>()
1165 );
1166 for g in guards.guards.values() {
1167 let g2 = guards2.guards.by_all_ids(g.guard_id()).unwrap();
1168 assert_eq!(format!("{:?}", g), format!("{:?}", g2));
1169 }
1170 }
1171
1172 #[test]
1173 fn select_primary() {
1174 let netdir = netdir();
1175 let params = GuardParams {
1176 min_filtered_sample_size: 5,
1177 n_primary: 4,
1178 ..GuardParams::default()
1179 };
1180 let t1 = SystemTime::get();
1181 let t2 = t1 + Duration::from_secs(20);
1182 let t3 = t2 + Duration::from_secs(30);
1183
1184 let mut guards = GuardSet::default();
1185 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1186
1187 let id3 = guards.sample[3].clone();
1189 guards.record_success(&id3, ¶ms, None, t2);
1190 assert_eq!(&guards.confirmed, std::slice::from_ref(&id3));
1191 let id1 = guards.sample[1].clone();
1192 guards.record_success(&id1, ¶ms, None, t3);
1193 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone()]);
1194
1195 guards.select_primary_guards(¶ms);
1197 assert_eq!(guards.primary.len(), 4);
1198 assert_eq!(&guards.primary[0], &id3);
1199 assert_eq!(&guards.primary[1], &id1);
1200 let p3 = guards.primary[2].clone();
1201 let p4 = guards.primary[3].clone();
1202 assert_eq!(
1203 [id1.clone(), id3.clone(), p3.clone(), p4.clone()]
1204 .iter()
1205 .unique()
1206 .count(),
1207 4
1208 );
1209
1210 guards.record_success(&p4, ¶ms, None, t3);
1214 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone(), p4.clone()]);
1215 guards.select_primary_guards(¶ms);
1216 assert_eq!(guards.primary.len(), 4);
1217 assert_eq!(&guards.primary[0], &id3);
1218 assert_eq!(&guards.primary[1], &id1);
1219 assert_eq!(&guards.primary, &[id3, id1, p4, p3]);
1220 }
1221
1222 #[test]
1223 fn expiration() {
1224 let netdir = netdir();
1225 let params = GuardParams::default();
1226 let t1 = SystemTime::get();
1227
1228 let mut guards = GuardSet::default();
1229 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1230 assert_eq!(guards.sample.len(), 10);
1232
1233 let id1 = guards.sample[0].clone();
1236 guards.record_success(&id1, ¶ms, None, t1);
1237 assert_eq!(&guards.confirmed, &[id1]);
1238
1239 let one_day = Duration::from_secs(86400);
1240 guards.expire_old_guards(¶ms, t1 + one_day * 30);
1241 assert_eq!(guards.sample.len(), 10); guards.expire_old_guards(¶ms, t1 + one_day * 70);
1245 assert_eq!(guards.sample.len(), 9);
1246
1247 guards.expire_old_guards(¶ms, t1 + one_day * 200);
1248 assert_eq!(guards.sample.len(), 0);
1249 }
1250
1251 #[test]
1252 #[allow(clippy::cognitive_complexity)]
1253 fn sampling_and_usage() {
1254 let netdir = netdir();
1255 let params = GuardParams {
1256 min_filtered_sample_size: 5,
1257 n_primary: 2,
1258 ..GuardParams::default()
1259 };
1260 let st1 = SystemTime::get();
1261 let i1 = Instant::get();
1262 let sec = Duration::from_secs(1);
1263
1264 let mut guards = GuardSet::default();
1265 guards.extend_sample_as_needed(st1, ¶ms, &netdir);
1266 guards.select_primary_guards(¶ms);
1267
1268 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1270 let id1 = guards.primary[0].clone();
1271 let id2 = guards.primary[1].clone();
1272 let (src, id) = guards.pick_guard_id(&usage, ¶ms, i1).unwrap();
1273 assert_eq!(src, ListKind::Primary);
1274 assert_eq!(&id, &id1);
1275
1276 guards.record_attempt(&id, i1);
1277 guards.record_failure(&id, None, i1 + sec);
1278
1279 let (src, id) = guards.pick_guard_id(&usage, ¶ms, i1 + sec).unwrap();
1281 assert_eq!(src, ListKind::Primary);
1282 assert_eq!(&id, &id2);
1283 guards.record_attempt(&id, i1 + sec);
1284
1285 let (src, id_x) = guards.pick_guard_id(&usage, ¶ms, i1 + sec).unwrap();
1286 assert_eq!(id_x, id);
1289 assert_eq!(src, ListKind::Primary);
1290 guards.record_attempt(&id_x, i1 + sec * 2);
1291 guards.record_failure(&id_x, None, i1 + sec * 3);
1292 guards.record_failure(&id, None, i1 + sec * 4);
1293
1294 let (src, id3) = guards.pick_guard_id(&usage, ¶ms, i1 + sec * 4).unwrap();
1296 assert_eq!(src, ListKind::Sample);
1297 assert!(!guards.primary.contains(&id3));
1298 guards.record_attempt(&id3, i1 + sec * 5);
1299
1300 let (src, id4) = guards.pick_guard_id(&usage, ¶ms, i1 + sec * 5).unwrap();
1303 assert_eq!(src, ListKind::Sample);
1304 assert!(id3 != id4);
1305 assert!(!guards.primary.contains(&id4));
1306 guards.record_attempt(&id4, i1 + sec * 6);
1307
1308 assert_eq!(
1313 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 6),
1314 Some(true)
1315 );
1316 assert_eq!(
1317 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 6),
1318 Some(true)
1319 );
1320 assert_eq!(
1321 guards.circ_usability_status(&id3, &usage, ¶ms, i1 + sec * 6),
1322 Some(true)
1323 );
1324 assert_eq!(
1325 guards.circ_usability_status(&id4, &usage, ¶ms, i1 + sec * 6),
1326 None
1327 );
1328
1329 guards.record_success(&id3, ¶ms, None, st1 + sec * 7);
1331 guards.record_success(&id4, ¶ms, None, st1 + sec * 8);
1332
1333 assert!(guards.primary_guards_invalidated);
1335 guards.select_primary_guards(¶ms);
1336 assert_eq!(&guards.primary, &[id3.clone(), id4.clone()]);
1337
1338 let (src, id) = guards
1340 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1341 .unwrap();
1342 assert_eq!(src, ListKind::Primary);
1343 assert_eq!(&id, &id3);
1344
1345 let mut found = HashSet::new();
1347 let usage = crate::GuardUsageBuilder::default()
1348 .kind(crate::GuardUsageKind::OneHopDirectory)
1349 .build()
1350 .unwrap();
1351 for _ in 0..64 {
1352 let (src, id) = guards
1353 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1354 .unwrap();
1355 assert_eq!(src, ListKind::Primary);
1356 assert_eq!(
1357 guards.circ_usability_status(&id, &usage, ¶ms, i1 + sec * 10),
1358 Some(true)
1359 );
1360 guards.record_attempt_abandoned(&id);
1361 found.insert(id);
1362 }
1363 assert!(found.len() == 2);
1364 assert!(found.contains(&id3));
1365 assert!(found.contains(&id4));
1366
1367 assert_eq!(
1369 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 12),
1370 Some(false)
1371 );
1372 assert_eq!(
1373 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 12),
1374 Some(false)
1375 );
1376 }
1377
1378 #[test]
1379 fn everybodys_down() {
1380 let netdir = netdir();
1381 let params = GuardParams {
1382 min_filtered_sample_size: 5,
1383 n_primary: 2,
1384 max_sample_bw_fraction: 1.0,
1385 ..GuardParams::default()
1386 };
1387 let mut st = SystemTime::get();
1388 let mut inst = Instant::get();
1389 let sec = Duration::from_secs(1);
1390 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1391
1392 let mut guards = GuardSet::default();
1393
1394 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1395 guards.select_primary_guards(¶ms);
1396
1397 assert_eq!(guards.sample.len(), 5);
1398 for _ in 0..5 {
1399 let (_, id) = guards.pick_guard_id(&usage, ¶ms, inst).unwrap();
1400 guards.record_attempt(&id, inst);
1401 guards.record_failure(&id, None, inst + sec);
1402
1403 inst += sec * 2;
1404 st += sec * 2;
1405 }
1406
1407 let e = guards.pick_guard_id(&usage, ¶ms, inst);
1408 assert!(matches!(e, Err(PickGuardError::AllGuardsDown { .. })));
1409
1410 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1412 guards.select_primary_guards(¶ms);
1413 assert_eq!(guards.sample.len(), 10);
1414 }
1415
1416 #[test]
1417 fn retry_primary() {
1418 let netdir = netdir();
1419 let params = GuardParams {
1420 min_filtered_sample_size: 5,
1421 n_primary: 2,
1422 max_sample_bw_fraction: 1.0,
1423 ..GuardParams::default()
1424 };
1425 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1426
1427 let mut guards = GuardSet::default();
1428
1429 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1430 guards.select_primary_guards(¶ms);
1431
1432 assert_eq!(guards.primary.len(), 2);
1433 assert!(!guards.all_primary_guards_are_unreachable());
1434
1435 let (kind, p_id1) = guards
1437 .pick_guard_id(&usage, ¶ms, Instant::get())
1438 .unwrap();
1439 assert_eq!(kind, ListKind::Primary);
1440 guards.record_failure(&p_id1, None, Instant::get());
1441 assert!(!guards.all_primary_guards_are_unreachable());
1442
1443 let (kind, p_id2) = guards
1445 .pick_guard_id(&usage, ¶ms, Instant::get())
1446 .unwrap();
1447 assert_eq!(kind, ListKind::Primary);
1448 guards.record_failure(&p_id2, None, Instant::get());
1449 assert!(guards.all_primary_guards_are_unreachable());
1450
1451 guards.mark_primary_guards_retriable();
1453 assert!(!guards.all_primary_guards_are_unreachable());
1454 let (kind, p_id3) = guards
1455 .pick_guard_id(&usage, ¶ms, Instant::get())
1456 .unwrap();
1457 assert_eq!(kind, ListKind::Primary);
1458 assert_eq!(p_id3, p_id1);
1459 }
1460
1461 #[test]
1462 fn count_missing_mds() {
1463 let netdir = netdir();
1464 let params = GuardParams {
1465 min_filtered_sample_size: 5,
1466 n_primary: 2,
1467 max_sample_bw_fraction: 1.0,
1468 ..GuardParams::default()
1469 };
1470 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1471 let mut guards = GuardSet::default();
1472 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1473 guards.select_primary_guards(¶ms);
1474 assert_eq!(guards.primary.len(), 2);
1475
1476 let (_kind, p_id1) = guards
1477 .pick_guard_id(&usage, ¶ms, Instant::get())
1478 .unwrap();
1479 guards.record_success(&p_id1, ¶ms, None, SystemTime::get());
1480 assert_eq!(guards.n_primary_without_id_info_in(&netdir), 0);
1481
1482 use tor_netdir::testnet;
1483 let netdir2 = testnet::construct_custom_netdir(|_idx, bld, _| {
1484 let md_so_far = bld.md.testing_md().expect("Couldn't build md?");
1485 if &p_id1.0.identity(RelayIdType::Ed25519).unwrap() == md_so_far.ed25519_id() {
1486 bld.omit_md = true;
1487 }
1488 })
1489 .unwrap()
1490 .unwrap_if_sufficient()
1491 .unwrap();
1492
1493 assert_eq!(guards.n_primary_without_id_info_in(&netdir2), 1);
1494 }
1495
1496 #[test]
1497 fn copy_status() {
1498 let netdir = netdir();
1499 let params = GuardParams {
1500 min_filtered_sample_size: 5,
1501 n_primary: 2,
1502 max_sample_bw_fraction: 1.0,
1503 ..GuardParams::default()
1504 };
1505 let mut guards1 = GuardSet::default();
1506 guards1.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1507 guards1.select_primary_guards(¶ms);
1508 let mut guards2 = guards1.clone();
1509
1510 let id1 = guards1.primary[0].clone();
1512 let id2 = guards1.primary[1].clone();
1513 guards1.record_success(&id1, ¶ms, None, SystemTime::get());
1514 guards2.record_success(&id2, ¶ms, None, SystemTime::get());
1515 guards2.record_failure(&id2, None, Instant::get());
1517
1518 guards1.copy_ephemeral_status_into_newly_loaded_state(guards2);
1520 {
1521 let g1 = guards1.get(&id1).unwrap();
1522 let g2 = guards1.get(&id2).unwrap();
1523 assert!(g1.confirmed());
1524 assert!(!g2.confirmed());
1525 assert_eq!(g1.reachable(), Reachable::Untried);
1526 assert_eq!(g2.reachable(), Reachable::Unreachable);
1527 }
1528
1529 let mut guards3 = GuardSet::default();
1532 let g1_set: HashSet<_> = guards1
1533 .guards
1534 .values()
1535 .map(|g| g.guard_id().clone())
1536 .collect();
1537 let mut g3_set: HashSet<_> = HashSet::new();
1538 for _ in 0..4 {
1539 guards3.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1542 guards3.select_primary_guards(¶ms);
1543 g3_set = guards3
1544 .guards
1545 .values()
1546 .map(|g| g.guard_id().clone())
1547 .collect();
1548
1549 if g1_set == g3_set {
1551 guards3 = GuardSet::default();
1552 continue;
1553 }
1554 break;
1555 }
1556 assert_ne!(g1_set, g3_set);
1557 guards1.copy_ephemeral_status_into_newly_loaded_state(guards3);
1559 let g1_set_new: HashSet<_> = guards1
1560 .guards
1561 .values()
1562 .map(|g| g.guard_id().clone())
1563 .collect();
1564 assert_eq!(g1_set, g1_set_new);
1565 }
1566}