1use crate::skew::SkewObservation;
4use rand::seq::IteratorRandom;
5use std::time::{Duration, Instant};
6use tor_linkspec::HasRelayIds;
7
8use super::{DirStatus, FallbackDir, FallbackDirBuilder};
9use crate::fallback::default_fallbacks;
10use crate::{ids::FallbackId, PickGuardError};
11use tor_basic_utils::iter::{FilterCount, IteratorExt as _};
12use tor_config::define_list_builder_helper;
13
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct FallbackList {
21 fallbacks: Vec<FallbackDir>,
23}
24
25impl<T: IntoIterator<Item = FallbackDir>> From<T> for FallbackList {
26 fn from(fallbacks: T) -> Self {
27 FallbackList {
28 fallbacks: fallbacks.into_iter().collect(),
29 }
30 }
31}
32
33define_list_builder_helper! {
34 pub struct FallbackListBuilder {
36 pub(crate) fallbacks: [FallbackDirBuilder],
37 }
38 built: FallbackList = FallbackList { fallbacks };
39 default = default_fallbacks();
40}
41
42impl FallbackList {
43 pub fn len(&self) -> usize {
45 self.fallbacks.len()
46 }
47 pub fn is_empty(&self) -> bool {
49 self.fallbacks.is_empty()
50 }
51 pub fn choose<R: rand::Rng>(&self, rng: &mut R) -> Result<&FallbackDir, PickGuardError> {
53 self.fallbacks
54 .iter()
55 .choose(rng)
56 .ok_or(PickGuardError::NoCandidatesAvailable)
57 }
58}
59
60#[derive(Debug, Clone)]
62pub(crate) struct FallbackState {
63 fallbacks: Vec<Entry>,
67}
68
69#[derive(Debug, Clone)]
75pub(super) struct Entry {
76 fallback: FallbackDir,
78
79 status: DirStatus,
82 clock_skew: Option<SkewObservation>,
85}
86
87const FALLBACK_RETRY_FLOOR: Duration = Duration::from_secs(150);
91
92impl From<FallbackDir> for Entry {
93 fn from(fallback: FallbackDir) -> Self {
94 let status = DirStatus::new(FALLBACK_RETRY_FLOOR);
95 Entry {
96 fallback,
97 status,
98 clock_skew: None,
99 }
100 }
101}
102
103impl HasRelayIds for Entry {
104 fn identity(
105 &self,
106 key_type: tor_linkspec::RelayIdType,
107 ) -> Option<tor_linkspec::RelayIdRef<'_>> {
108 self.fallback.identity(key_type)
109 }
110}
111
112impl From<&FallbackList> for FallbackState {
113 fn from(list: &FallbackList) -> Self {
114 let mut fallbacks: Vec<Entry> = list.fallbacks.iter().map(|fb| fb.clone().into()).collect();
115 fallbacks.sort_by(|x, y| x.cmp_by_relay_ids(y));
116 fallbacks.dedup_by(|x, y| x.same_relay_ids(y));
117 FallbackState { fallbacks }
118 }
119}
120
121impl FallbackState {
122 pub(crate) fn choose<R: rand::Rng>(
124 &self,
125 rng: &mut R,
126 now: Instant,
127 filter: &crate::GuardFilter,
128 ) -> Result<&FallbackDir, PickGuardError> {
129 if self.fallbacks.is_empty() {
130 return Err(PickGuardError::NoCandidatesAvailable);
131 }
132
133 let mut running = FilterCount::default();
134 let mut filtered = FilterCount::default();
135
136 self.fallbacks
137 .iter()
138 .filter_cnt(&mut running, |ent| ent.status.usable_at(now))
139 .filter_cnt(&mut filtered, |ent| filter.permits(&ent.fallback))
140 .choose(rng)
141 .map(|ent| &ent.fallback)
142 .ok_or_else(|| PickGuardError::AllFallbacksDown {
143 retry_at: self.next_retry(),
144 running,
145 filtered,
146 })
147 }
148
149 fn next_retry(&self) -> Option<Instant> {
153 self.fallbacks
154 .iter()
155 .filter_map(|ent| ent.status.next_retriable())
156 .min()
157 }
158
159 fn get(&self, id: &FallbackId) -> Option<&Entry> {
161 match self.fallbacks.binary_search_by(|e| e.cmp_by_relay_ids(id)) {
162 Ok(idx) => Some(&self.fallbacks[idx]),
163 Err(_) => None,
164 }
165 }
166
167 fn get_mut(&mut self, id: &FallbackId) -> Option<&mut Entry> {
169 match self.fallbacks.binary_search_by(|e| e.cmp_by_relay_ids(id)) {
170 Ok(idx) => Some(&mut self.fallbacks[idx]),
171 Err(_) => None,
172 }
173 }
174
175 pub(crate) fn contains(&self, id: &FallbackId) -> bool {
177 self.get(id).is_some()
178 }
179
180 pub(crate) fn note_success(&mut self, id: &FallbackId) {
186 if let Some(entry) = self.get_mut(id) {
187 entry.status.note_success();
188 }
189 }
190
191 pub(crate) fn note_failure(&mut self, id: &FallbackId, now: Instant) {
194 if let Some(entry) = self.get_mut(id) {
195 entry.status.note_failure(now);
196 }
197 }
198
199 pub(crate) fn take_status_from(&mut self, other: FallbackState) {
201 use itertools::EitherOrBoth::Both;
202
203 itertools::merge_join_by(self.fallbacks.iter_mut(), other.fallbacks, |a, b| {
204 a.fallback.cmp_by_relay_ids(&b.fallback)
205 })
206 .for_each(|entry| {
207 if let Both(entry, other) = entry {
208 debug_assert!(entry.fallback.same_relay_ids(&other.fallback));
209 entry.status = other.status;
210 }
211 });
212 }
213
214 pub(crate) fn note_skew(&mut self, id: &FallbackId, observation: SkewObservation) {
216 if let Some(entry) = self.get_mut(id) {
217 entry.clock_skew = Some(observation);
218 }
219 }
220
221 pub(crate) fn skew_observations(&self) -> impl Iterator<Item = &SkewObservation> {
223 self.fallbacks
224 .iter()
225 .filter_map(|fb| fb.clock_skew.as_ref())
226 }
227}
228
229#[cfg(test)]
230mod test {
231 #![allow(clippy::bool_assert_comparison)]
233 #![allow(clippy::clone_on_copy)]
234 #![allow(clippy::dbg_macro)]
235 #![allow(clippy::mixed_attributes_style)]
236 #![allow(clippy::print_stderr)]
237 #![allow(clippy::print_stdout)]
238 #![allow(clippy::single_char_pattern)]
239 #![allow(clippy::unwrap_used)]
240 #![allow(clippy::unchecked_duration_subtraction)]
241 #![allow(clippy::useless_vec)]
242 #![allow(clippy::needless_pass_by_value)]
243 use super::*;
246 use rand::Rng;
247 use tor_basic_utils::test_rng::testing_rng;
248
249 fn rand_fb<R: Rng>(rng: &mut R) -> FallbackDir {
254 let ed: [u8; 32] = rng.random();
255 let rsa: [u8; 20] = rng.random();
256 let ip: u32 = rng.random();
257 let mut bld = FallbackDir::builder();
258 bld.ed_identity(ed.into())
259 .rsa_identity(rsa.into())
260 .orports()
261 .push(std::net::SocketAddrV4::new(ip.into(), 9090).into());
262 bld.build().unwrap()
263 }
264
265 #[test]
266 fn construct_fallback_set() {
267 use rand::seq::SliceRandom;
268 use std::cmp::Ordering as O;
269
270 let mut rng = testing_rng();
272 let fbs = vec![
273 rand_fb(&mut rng),
274 rand_fb(&mut rng),
275 rand_fb(&mut rng),
276 rand_fb(&mut rng),
277 ];
278 let fb_other = rand_fb(&mut rng);
279 let id_other = FallbackId::from_relay_ids(&fb_other);
280
281 let list: FallbackList = fbs.clone().into();
283 assert!(!list.is_empty());
284 assert_eq!(list.len(), 4);
285 let mut set: FallbackState = (&list).into();
286
287 assert_eq!(set.fallbacks.len(), 4);
289 assert_eq!(
290 set.fallbacks[0].cmp_by_relay_ids(&set.fallbacks[1]),
291 O::Less
292 );
293 assert_eq!(
294 set.fallbacks[1].cmp_by_relay_ids(&set.fallbacks[2]),
295 O::Less
296 );
297 assert_eq!(
298 set.fallbacks[2].cmp_by_relay_ids(&set.fallbacks[3]),
299 O::Less
300 );
301
302 for fb in fbs.iter() {
304 let id = FallbackId::from_relay_ids(fb);
305 assert_eq!(set.get_mut(&id).unwrap().cmp_by_relay_ids(&id), O::Equal);
306 }
307 assert!(set.get_mut(&id_other).is_none());
308
309 let mut redundant_fbs = fbs.clone();
311 redundant_fbs.extend(fbs.clone());
312 redundant_fbs.extend(fbs[0..2].iter().map(Clone::clone));
313 redundant_fbs[..].shuffle(&mut testing_rng());
314 let list2 = redundant_fbs.into();
315 assert_ne!(&list, &list2);
316 let set2: FallbackState = (&list2).into();
317
318 assert_eq!(set.fallbacks.len(), set2.fallbacks.len());
320 assert!(set
321 .fallbacks
322 .iter()
323 .zip(set2.fallbacks.iter())
324 .all(|(ent1, ent2)| ent1.same_relay_ids(ent2)));
325 }
326
327 #[test]
328 fn set_choose() {
329 dbg!("X");
330
331 let mut rng = testing_rng();
332 let fbs = vec![
333 rand_fb(&mut rng),
334 rand_fb(&mut rng),
335 rand_fb(&mut rng),
336 rand_fb(&mut rng),
337 ];
338 let list: FallbackList = fbs.into();
339 let mut set: FallbackState = (&list).into();
340 let filter = crate::GuardFilter::unfiltered();
341
342 let mut counts = [0_usize; 4];
343 let now = Instant::now();
344 dbg!("A");
345 fn lookup_idx(set: &FallbackState, id: &impl HasRelayIds) -> Option<usize> {
346 set.fallbacks
347 .binary_search_by(|ent| ent.fallback.cmp_by_relay_ids(id))
348 .ok()
349 }
350 for _ in 0..100 {
352 let fb = set.choose(&mut rng, now, &filter).unwrap();
353 let idx = lookup_idx(&set, fb).unwrap();
354 counts[idx] += 1;
355 }
356 dbg!("B");
357 assert!(counts.iter().all(|v| *v > 0));
358
359 let ids: Vec<_> = set
361 .fallbacks
362 .iter()
363 .map(|ent| FallbackId::from_relay_ids(&ent.fallback))
364 .collect();
365 set.note_failure(&ids[2], now);
366 counts = [0; 4];
367 for _ in 0..100 {
368 let fb = set.choose(&mut rng, now, &filter).unwrap();
369 let idx = lookup_idx(&set, fb).unwrap();
370 counts[idx] += 1;
371 }
372 assert_eq!(counts.iter().filter(|v| **v > 0).count(), 3);
373 assert_eq!(counts[2], 0);
374
375 for id in ids.iter() {
377 set.note_failure(id, now);
378 }
379 assert!(matches!(
380 set.choose(&mut rng, now, &filter),
381 Err(PickGuardError::AllFallbacksDown { .. })
382 ));
383
384 let empty_set = FallbackState::from(&FallbackList::from(vec![]));
386 assert!(matches!(
387 empty_set.choose(&mut rng, now, &filter),
388 Err(PickGuardError::NoCandidatesAvailable)
389 ));
390
391 }
393
394 #[test]
395 fn test_status() {
396 let mut rng = testing_rng();
397 let fbs = vec![
398 rand_fb(&mut rng),
399 rand_fb(&mut rng),
400 rand_fb(&mut rng),
401 rand_fb(&mut rng),
402 ];
403 let list: FallbackList = fbs.clone().into();
404 let mut set: FallbackState = (&list).into();
405 let ids: Vec<_> = set
406 .fallbacks
407 .iter()
408 .map(|ent| FallbackId::from_relay_ids(&ent.fallback))
409 .collect();
410
411 let now = Instant::now();
412
413 assert!(set.next_retry().is_none());
415
416 set.note_failure(&ids[3], now);
418 assert!(set.fallbacks[3].status.next_retriable().unwrap() > now);
419 assert!(!set.fallbacks[3].status.usable_at(now));
420 assert_eq!(set.next_retry(), set.fallbacks[3].status.next_retriable());
421
422 set.note_failure(&ids[0], now);
424 assert!(set.fallbacks[0].status.next_retriable().unwrap() > now);
425 assert!(!set.fallbacks[0].status.usable_at(now));
426 assert_eq!(
427 set.next_retry().unwrap(),
428 std::cmp::min(
429 set.fallbacks[0].status.next_retriable().unwrap(),
430 set.fallbacks[3].status.next_retriable().unwrap()
431 )
432 );
433
434 set.note_success(&ids[0]);
436 assert!(set.fallbacks[0].status.next_retriable().is_none());
437 assert!(set.fallbacks[0].status.usable_at(now));
438
439 let mut fbs2: Vec<_> = fbs
441 .into_iter()
442 .filter(|fb| FallbackId::from_relay_ids(fb) != ids[2])
444 .collect();
445 let fbs_new = vec![rand_fb(&mut rng), rand_fb(&mut rng), rand_fb(&mut rng)];
447 fbs2.extend(fbs_new.clone());
448
449 let mut set2 = FallbackState::from(&FallbackList::from(fbs2.clone()));
450 set2.take_status_from(set); assert_eq!(set2.fallbacks.len(), 6); assert!(set2.get_mut(&ids[0]).unwrap().status.usable_at(now));
455 assert!(set2.get_mut(&ids[1]).unwrap().status.usable_at(now));
456 assert!(set2.get_mut(&ids[2]).is_none());
457 assert!(!set2.get_mut(&ids[3]).unwrap().status.usable_at(now));
458
459 for new_fb in fbs_new {
461 assert!(set2
462 .get_mut(&FallbackId::from_relay_ids(&new_fb))
463 .unwrap()
464 .status
465 .usable_at(now));
466 }
467 }
468}