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 #![allow(clippy::string_slice)] use tor_linkspec::{HasRelayIds, RelayIdType};
1034 use tor_netdir::NetDir;
1035 use tor_netdoc::doc::netstatus::RelayWeight;
1036 use tor_netdoc::types::relay_flags::RelayFlag;
1037 use web_time_compat::{InstantExt, SystemTimeExt};
1038
1039 use super::*;
1040 use crate::FirstHopId;
1041 use std::time::Duration;
1042
1043 fn netdir() -> NetDir {
1044 use tor_netdir::testnet;
1045 testnet::construct_netdir().unwrap_if_sufficient().unwrap()
1046 }
1047
1048 #[test]
1049 fn sample_test() {
1050 let netdir = tor_netdir::testnet::construct_custom_netdir(|idx, builder, _| {
1055 builder.rs.weight(RelayWeight::Measured(1000));
1057 if idx >= 10 {
1060 builder.rs.add_flags(RelayFlag::Guard);
1061 if idx >= 20 {
1062 builder.rs.protos("DirCache=2".parse().unwrap());
1063 } else {
1064 builder.rs.protos("".parse().unwrap());
1065 }
1066 }
1067 })
1068 .unwrap()
1069 .unwrap_if_sufficient()
1070 .unwrap();
1071 assert_eq!(40, netdir.relays().count());
1073 assert_eq!(
1074 30,
1075 netdir
1076 .relays()
1077 .filter(|r| r.low_level_details().is_suitable_as_guard())
1078 .count()
1079 );
1080 assert_eq!(
1081 20,
1082 netdir
1083 .relays()
1084 .filter(|r| r.low_level_details().is_suitable_as_guard()
1085 && r.low_level_details().is_dir_cache())
1086 .count()
1087 );
1088
1089 let params = GuardParams {
1090 min_filtered_sample_size: 5,
1091 max_sample_bw_fraction: 1.0,
1092 ..GuardParams::default()
1093 };
1094
1095 let mut samples: Vec<HashSet<GuardId>> = Vec::new();
1096 for _ in 0..3 {
1097 let mut guards = GuardSet::default();
1098 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1099 assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1100 assert_eq!(guards.confirmed.len(), 0);
1101 assert_eq!(guards.primary.len(), 0);
1102 guards.assert_consistency();
1103
1104 for guard in guards.guards.values() {
1106 let id = FirstHopId::in_sample(GuardSetSelector::Default, guard.guard_id().clone());
1107 let relay = id.get_relay(&netdir).unwrap();
1108 assert!(relay.low_level_details().is_suitable_as_guard());
1109 assert!(relay.low_level_details().is_dir_cache());
1110 assert!(guards.guards.by_all_ids(&relay).is_some());
1111 {
1112 assert!(!guard.is_expired(¶ms, SystemTime::get()));
1113 }
1114 }
1115
1116 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1118 assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1119 guards.assert_consistency();
1120
1121 samples.push(guards.sample.into_iter().collect());
1122 }
1123
1124 assert!(samples[0] != samples[1] || samples[1] != samples[2]);
1127 }
1128
1129 #[test]
1130 fn persistence() {
1131 let netdir = netdir();
1132 let params = GuardParams {
1133 min_filtered_sample_size: 5,
1134 ..GuardParams::default()
1135 };
1136
1137 let t1 = SystemTime::get();
1138 let t2 = t1 + Duration::from_secs(20);
1139
1140 let mut guards = GuardSet::default();
1141 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1142
1143 let id1 = guards.sample[0].clone();
1145 guards.record_success(&id1, ¶ms, None, t2);
1146 assert_eq!(&guards.confirmed, std::slice::from_ref(&id1));
1147
1148 let state: GuardSample = (&guards).into();
1150 let guards2: GuardSet = state.into();
1151
1152 assert_eq!(&guards2.sample, &guards.sample);
1153 assert_eq!(&guards2.confirmed, &guards.confirmed);
1154 assert_eq!(&guards2.confirmed, &[id1]);
1155 assert_eq!(
1156 guards
1157 .guards
1158 .values()
1159 .map(Guard::guard_id)
1160 .collect::<HashSet<_>>(),
1161 guards2
1162 .guards
1163 .values()
1164 .map(Guard::guard_id)
1165 .collect::<HashSet<_>>()
1166 );
1167 for g in guards.guards.values() {
1168 let g2 = guards2.guards.by_all_ids(g.guard_id()).unwrap();
1169 assert_eq!(format!("{:?}", g), format!("{:?}", g2));
1170 }
1171 }
1172
1173 #[test]
1174 fn select_primary() {
1175 let netdir = netdir();
1176 let params = GuardParams {
1177 min_filtered_sample_size: 5,
1178 n_primary: 4,
1179 ..GuardParams::default()
1180 };
1181 let t1 = SystemTime::get();
1182 let t2 = t1 + Duration::from_secs(20);
1183 let t3 = t2 + Duration::from_secs(30);
1184
1185 let mut guards = GuardSet::default();
1186 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1187
1188 let id3 = guards.sample[3].clone();
1190 guards.record_success(&id3, ¶ms, None, t2);
1191 assert_eq!(&guards.confirmed, std::slice::from_ref(&id3));
1192 let id1 = guards.sample[1].clone();
1193 guards.record_success(&id1, ¶ms, None, t3);
1194 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone()]);
1195
1196 guards.select_primary_guards(¶ms);
1198 assert_eq!(guards.primary.len(), 4);
1199 assert_eq!(&guards.primary[0], &id3);
1200 assert_eq!(&guards.primary[1], &id1);
1201 let p3 = guards.primary[2].clone();
1202 let p4 = guards.primary[3].clone();
1203 assert_eq!(
1204 [id1.clone(), id3.clone(), p3.clone(), p4.clone()]
1205 .iter()
1206 .unique()
1207 .count(),
1208 4
1209 );
1210
1211 guards.record_success(&p4, ¶ms, None, t3);
1215 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone(), p4.clone()]);
1216 guards.select_primary_guards(¶ms);
1217 assert_eq!(guards.primary.len(), 4);
1218 assert_eq!(&guards.primary[0], &id3);
1219 assert_eq!(&guards.primary[1], &id1);
1220 assert_eq!(&guards.primary, &[id3, id1, p4, p3]);
1221 }
1222
1223 #[test]
1224 fn expiration() {
1225 let netdir = netdir();
1226 let params = GuardParams::default();
1227 let t1 = SystemTime::get();
1228
1229 let mut guards = GuardSet::default();
1230 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1231 assert_eq!(guards.sample.len(), 10);
1233
1234 let id1 = guards.sample[0].clone();
1237 guards.record_success(&id1, ¶ms, None, t1);
1238 assert_eq!(&guards.confirmed, &[id1]);
1239
1240 let one_day = Duration::from_secs(86400);
1241 guards.expire_old_guards(¶ms, t1 + one_day * 30);
1242 assert_eq!(guards.sample.len(), 10); guards.expire_old_guards(¶ms, t1 + one_day * 70);
1246 assert_eq!(guards.sample.len(), 9);
1247
1248 guards.expire_old_guards(¶ms, t1 + one_day * 200);
1249 assert_eq!(guards.sample.len(), 0);
1250 }
1251
1252 #[test]
1253 #[allow(clippy::cognitive_complexity)]
1254 fn sampling_and_usage() {
1255 let netdir = netdir();
1256 let params = GuardParams {
1257 min_filtered_sample_size: 5,
1258 n_primary: 2,
1259 ..GuardParams::default()
1260 };
1261 let st1 = SystemTime::get();
1262 let i1 = Instant::get();
1263 let sec = Duration::from_secs(1);
1264
1265 let mut guards = GuardSet::default();
1266 guards.extend_sample_as_needed(st1, ¶ms, &netdir);
1267 guards.select_primary_guards(¶ms);
1268
1269 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1271 let id1 = guards.primary[0].clone();
1272 let id2 = guards.primary[1].clone();
1273 let (src, id) = guards.pick_guard_id(&usage, ¶ms, i1).unwrap();
1274 assert_eq!(src, ListKind::Primary);
1275 assert_eq!(&id, &id1);
1276
1277 guards.record_attempt(&id, i1);
1278 guards.record_failure(&id, None, i1 + sec);
1279
1280 let (src, id) = guards.pick_guard_id(&usage, ¶ms, i1 + sec).unwrap();
1282 assert_eq!(src, ListKind::Primary);
1283 assert_eq!(&id, &id2);
1284 guards.record_attempt(&id, i1 + sec);
1285
1286 let (src, id_x) = guards.pick_guard_id(&usage, ¶ms, i1 + sec).unwrap();
1287 assert_eq!(id_x, id);
1290 assert_eq!(src, ListKind::Primary);
1291 guards.record_attempt(&id_x, i1 + sec * 2);
1292 guards.record_failure(&id_x, None, i1 + sec * 3);
1293 guards.record_failure(&id, None, i1 + sec * 4);
1294
1295 let (src, id3) = guards.pick_guard_id(&usage, ¶ms, i1 + sec * 4).unwrap();
1297 assert_eq!(src, ListKind::Sample);
1298 assert!(!guards.primary.contains(&id3));
1299 guards.record_attempt(&id3, i1 + sec * 5);
1300
1301 let (src, id4) = guards.pick_guard_id(&usage, ¶ms, i1 + sec * 5).unwrap();
1304 assert_eq!(src, ListKind::Sample);
1305 assert!(id3 != id4);
1306 assert!(!guards.primary.contains(&id4));
1307 guards.record_attempt(&id4, i1 + sec * 6);
1308
1309 assert_eq!(
1314 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 6),
1315 Some(true)
1316 );
1317 assert_eq!(
1318 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 6),
1319 Some(true)
1320 );
1321 assert_eq!(
1322 guards.circ_usability_status(&id3, &usage, ¶ms, i1 + sec * 6),
1323 Some(true)
1324 );
1325 assert_eq!(
1326 guards.circ_usability_status(&id4, &usage, ¶ms, i1 + sec * 6),
1327 None
1328 );
1329
1330 guards.record_success(&id3, ¶ms, None, st1 + sec * 7);
1332 guards.record_success(&id4, ¶ms, None, st1 + sec * 8);
1333
1334 assert!(guards.primary_guards_invalidated);
1336 guards.select_primary_guards(¶ms);
1337 assert_eq!(&guards.primary, &[id3.clone(), id4.clone()]);
1338
1339 let (src, id) = guards
1341 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1342 .unwrap();
1343 assert_eq!(src, ListKind::Primary);
1344 assert_eq!(&id, &id3);
1345
1346 let mut found = HashSet::new();
1348 let usage = crate::GuardUsageBuilder::default()
1349 .kind(crate::GuardUsageKind::OneHopDirectory)
1350 .build()
1351 .unwrap();
1352 for _ in 0..64 {
1353 let (src, id) = guards
1354 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1355 .unwrap();
1356 assert_eq!(src, ListKind::Primary);
1357 assert_eq!(
1358 guards.circ_usability_status(&id, &usage, ¶ms, i1 + sec * 10),
1359 Some(true)
1360 );
1361 guards.record_attempt_abandoned(&id);
1362 found.insert(id);
1363 }
1364 assert!(found.len() == 2);
1365 assert!(found.contains(&id3));
1366 assert!(found.contains(&id4));
1367
1368 assert_eq!(
1370 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 12),
1371 Some(false)
1372 );
1373 assert_eq!(
1374 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 12),
1375 Some(false)
1376 );
1377 }
1378
1379 #[test]
1380 fn everybodys_down() {
1381 let netdir = netdir();
1382 let params = GuardParams {
1383 min_filtered_sample_size: 5,
1384 n_primary: 2,
1385 max_sample_bw_fraction: 1.0,
1386 ..GuardParams::default()
1387 };
1388 let mut st = SystemTime::get();
1389 let mut inst = Instant::get();
1390 let sec = Duration::from_secs(1);
1391 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1392
1393 let mut guards = GuardSet::default();
1394
1395 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1396 guards.select_primary_guards(¶ms);
1397
1398 assert_eq!(guards.sample.len(), 5);
1399 for _ in 0..5 {
1400 let (_, id) = guards.pick_guard_id(&usage, ¶ms, inst).unwrap();
1401 guards.record_attempt(&id, inst);
1402 guards.record_failure(&id, None, inst + sec);
1403
1404 inst += sec * 2;
1405 st += sec * 2;
1406 }
1407
1408 let e = guards.pick_guard_id(&usage, ¶ms, inst);
1409 assert!(matches!(e, Err(PickGuardError::AllGuardsDown { .. })));
1410
1411 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1413 guards.select_primary_guards(¶ms);
1414 assert_eq!(guards.sample.len(), 10);
1415 }
1416
1417 #[test]
1418 fn retry_primary() {
1419 let netdir = netdir();
1420 let params = GuardParams {
1421 min_filtered_sample_size: 5,
1422 n_primary: 2,
1423 max_sample_bw_fraction: 1.0,
1424 ..GuardParams::default()
1425 };
1426 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1427
1428 let mut guards = GuardSet::default();
1429
1430 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1431 guards.select_primary_guards(¶ms);
1432
1433 assert_eq!(guards.primary.len(), 2);
1434 assert!(!guards.all_primary_guards_are_unreachable());
1435
1436 let (kind, p_id1) = guards
1438 .pick_guard_id(&usage, ¶ms, Instant::get())
1439 .unwrap();
1440 assert_eq!(kind, ListKind::Primary);
1441 guards.record_failure(&p_id1, None, Instant::get());
1442 assert!(!guards.all_primary_guards_are_unreachable());
1443
1444 let (kind, p_id2) = guards
1446 .pick_guard_id(&usage, ¶ms, Instant::get())
1447 .unwrap();
1448 assert_eq!(kind, ListKind::Primary);
1449 guards.record_failure(&p_id2, None, Instant::get());
1450 assert!(guards.all_primary_guards_are_unreachable());
1451
1452 guards.mark_primary_guards_retriable();
1454 assert!(!guards.all_primary_guards_are_unreachable());
1455 let (kind, p_id3) = guards
1456 .pick_guard_id(&usage, ¶ms, Instant::get())
1457 .unwrap();
1458 assert_eq!(kind, ListKind::Primary);
1459 assert_eq!(p_id3, p_id1);
1460 }
1461
1462 #[test]
1463 fn count_missing_mds() {
1464 let netdir = netdir();
1465 let params = GuardParams {
1466 min_filtered_sample_size: 5,
1467 n_primary: 2,
1468 max_sample_bw_fraction: 1.0,
1469 ..GuardParams::default()
1470 };
1471 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1472 let mut guards = GuardSet::default();
1473 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1474 guards.select_primary_guards(¶ms);
1475 assert_eq!(guards.primary.len(), 2);
1476
1477 let (_kind, p_id1) = guards
1478 .pick_guard_id(&usage, ¶ms, Instant::get())
1479 .unwrap();
1480 guards.record_success(&p_id1, ¶ms, None, SystemTime::get());
1481 assert_eq!(guards.n_primary_without_id_info_in(&netdir), 0);
1482
1483 use tor_netdir::testnet;
1484 let netdir2 = testnet::construct_custom_netdir(|_idx, bld, _| {
1485 let md_so_far = bld.md.testing_md().expect("Couldn't build md?");
1486 if &p_id1.0.identity(RelayIdType::Ed25519).unwrap() == md_so_far.ed25519_id() {
1487 bld.omit_md = true;
1488 }
1489 })
1490 .unwrap()
1491 .unwrap_if_sufficient()
1492 .unwrap();
1493
1494 assert_eq!(guards.n_primary_without_id_info_in(&netdir2), 1);
1495 }
1496
1497 #[test]
1498 fn copy_status() {
1499 let netdir = netdir();
1500 let params = GuardParams {
1501 min_filtered_sample_size: 5,
1502 n_primary: 2,
1503 max_sample_bw_fraction: 1.0,
1504 ..GuardParams::default()
1505 };
1506 let mut guards1 = GuardSet::default();
1507 guards1.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1508 guards1.select_primary_guards(¶ms);
1509 let mut guards2 = guards1.clone();
1510
1511 let id1 = guards1.primary[0].clone();
1513 let id2 = guards1.primary[1].clone();
1514 guards1.record_success(&id1, ¶ms, None, SystemTime::get());
1515 guards2.record_success(&id2, ¶ms, None, SystemTime::get());
1516 guards2.record_failure(&id2, None, Instant::get());
1518
1519 guards1.copy_ephemeral_status_into_newly_loaded_state(guards2);
1521 {
1522 let g1 = guards1.get(&id1).unwrap();
1523 let g2 = guards1.get(&id2).unwrap();
1524 assert!(g1.confirmed());
1525 assert!(!g2.confirmed());
1526 assert_eq!(g1.reachable(), Reachable::Untried);
1527 assert_eq!(g2.reachable(), Reachable::Unreachable);
1528 }
1529
1530 let mut guards3 = GuardSet::default();
1533 let g1_set: HashSet<_> = guards1
1534 .guards
1535 .values()
1536 .map(|g| g.guard_id().clone())
1537 .collect();
1538 let mut g3_set: HashSet<_> = HashSet::new();
1539 for _ in 0..4 {
1540 guards3.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1543 guards3.select_primary_guards(¶ms);
1544 g3_set = guards3
1545 .guards
1546 .values()
1547 .map(|g| g.guard_id().clone())
1548 .collect();
1549
1550 if g1_set == g3_set {
1552 guards3 = GuardSet::default();
1553 continue;
1554 }
1555 break;
1556 }
1557 assert_ne!(g1_set, g3_set);
1558 guards1.copy_ephemeral_status_into_newly_loaded_state(guards3);
1560 let g1_set_new: HashSet<_> = guards1
1561 .guards
1562 .values()
1563 .map(|g| g.guard_id().clone())
1564 .collect();
1565 assert_eq!(g1_set, g1_set_new);
1566 }
1567}