1mod 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#[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_duration_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::{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 let netdir = tor_netdir::testnet::construct_custom_netdir(|idx, builder, _| {
1052 builder.rs.weight(RelayWeight::Measured(1000));
1054 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 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(), ¶ms, &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 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(¶ms, SystemTime::now()));
1110 }
1111 }
1112
1113 guards.extend_sample_as_needed(SystemTime::now(), ¶ms, &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 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, ¶ms, &netdir);
1139
1140 let id1 = guards.sample[0].clone();
1142 guards.record_success(&id1, ¶ms, None, t2);
1143 assert_eq!(&guards.confirmed, &[id1.clone()]);
1144
1145 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, ¶ms, &netdir);
1184
1185 let id3 = guards.sample[3].clone();
1187 guards.record_success(&id3, ¶ms, None, t2);
1188 assert_eq!(&guards.confirmed, &[id3.clone()]);
1189 let id1 = guards.sample[1].clone();
1190 guards.record_success(&id1, ¶ms, None, t3);
1191 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone()]);
1192
1193 guards.select_primary_guards(¶ms);
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 guards.record_success(&p4, ¶ms, None, t3);
1212 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone(), p4.clone()]);
1213 guards.select_primary_guards(¶ms);
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, ¶ms, &netdir);
1228 assert_eq!(guards.sample.len(), 10);
1230
1231 let id1 = guards.sample[0].clone();
1234 guards.record_success(&id1, ¶ms, None, t1);
1235 assert_eq!(&guards.confirmed, &[id1]);
1236
1237 let one_day = Duration::from_secs(86400);
1238 guards.expire_old_guards(¶ms, t1 + one_day * 30);
1239 assert_eq!(guards.sample.len(), 10); guards.expire_old_guards(¶ms, t1 + one_day * 70);
1243 assert_eq!(guards.sample.len(), 9);
1244
1245 guards.expire_old_guards(¶ms, 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, ¶ms, &netdir);
1264 guards.select_primary_guards(¶ms);
1265
1266 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, ¶ms, 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 let (src, id) = guards.pick_guard_id(&usage, ¶ms, 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, ¶ms, i1 + sec).unwrap();
1284 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 let (src, id3) = guards.pick_guard_id(&usage, ¶ms, 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 let (src, id4) = guards.pick_guard_id(&usage, ¶ms, 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 assert_eq!(
1311 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 6),
1312 Some(true)
1313 );
1314 assert_eq!(
1315 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 6),
1316 Some(true)
1317 );
1318 assert_eq!(
1319 guards.circ_usability_status(&id3, &usage, ¶ms, i1 + sec * 6),
1320 Some(true)
1321 );
1322 assert_eq!(
1323 guards.circ_usability_status(&id4, &usage, ¶ms, i1 + sec * 6),
1324 None
1325 );
1326
1327 guards.record_success(&id3, ¶ms, None, st1 + sec * 7);
1329 guards.record_success(&id4, ¶ms, None, st1 + sec * 8);
1330
1331 assert!(guards.primary_guards_invalidated);
1333 guards.select_primary_guards(¶ms);
1334 assert_eq!(&guards.primary, &[id3.clone(), id4.clone()]);
1335
1336 let (src, id) = guards
1338 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1339 .unwrap();
1340 assert_eq!(src, ListKind::Primary);
1341 assert_eq!(&id, &id3);
1342
1343 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, ¶ms, i1 + sec * 10)
1352 .unwrap();
1353 assert_eq!(src, ListKind::Primary);
1354 assert_eq!(
1355 guards.circ_usability_status(&id, &usage, ¶ms, 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 assert_eq!(
1367 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 12),
1368 Some(false)
1369 );
1370 assert_eq!(
1371 guards.circ_usability_status(&id2, &usage, ¶ms, 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, ¶ms, &netdir);
1393 guards.select_primary_guards(¶ms);
1394
1395 assert_eq!(guards.sample.len(), 5);
1396 for _ in 0..5 {
1397 let (_, id) = guards.pick_guard_id(&usage, ¶ms, 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, ¶ms, inst);
1406 assert!(matches!(e, Err(PickGuardError::AllGuardsDown { .. })));
1407
1408 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1410 guards.select_primary_guards(¶ms);
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(), ¶ms, &netdir);
1428 guards.select_primary_guards(¶ms);
1429
1430 assert_eq!(guards.primary.len(), 2);
1431 assert!(!guards.all_primary_guards_are_unreachable());
1432
1433 let (kind, p_id1) = guards
1435 .pick_guard_id(&usage, ¶ms, 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 let (kind, p_id2) = guards
1443 .pick_guard_id(&usage, ¶ms, 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 guards.mark_primary_guards_retriable();
1451 assert!(!guards.all_primary_guards_are_unreachable());
1452 let (kind, p_id3) = guards
1453 .pick_guard_id(&usage, ¶ms, 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(), ¶ms, &netdir);
1471 guards.select_primary_guards(¶ms);
1472 assert_eq!(guards.primary.len(), 2);
1473
1474 let (_kind, p_id1) = guards
1475 .pick_guard_id(&usage, ¶ms, Instant::now())
1476 .unwrap();
1477 guards.record_success(&p_id1, ¶ms, 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(), ¶ms, &netdir);
1505 guards1.select_primary_guards(¶ms);
1506 let mut guards2 = guards1.clone();
1507
1508 let id1 = guards1.primary[0].clone();
1510 let id2 = guards1.primary[1].clone();
1511 guards1.record_success(&id1, ¶ms, None, SystemTime::now());
1512 guards2.record_success(&id2, ¶ms, None, SystemTime::now());
1513 guards2.record_failure(&id2, None, Instant::now());
1515
1516 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 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 guards3.extend_sample_as_needed(SystemTime::now(), ¶ms, &netdir);
1540 guards3.select_primary_guards(¶ms);
1541 g3_set = guards3
1542 .guards
1543 .values()
1544 .map(|g| g.guard_id().clone())
1545 .collect();
1546
1547 if g1_set == g3_set {
1549 guards3 = GuardSet::default();
1550 continue;
1551 }
1552 break;
1553 }
1554 assert_ne!(g1_set, g3_set);
1555 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}