vanguards_rs/
pathverify.rs

1//! Path verification for ensuring circuits use configured vanguards.
2//!
3//! This module verifies that Tor circuits are using the configured vanguard
4//! relays and have the expected path lengths for each circuit purpose.
5//!
6//! # Overview
7//!
8//! The path verifier monitors:
9//!
10//! - **Layer 1 guards**: Entry guard connections and usage
11//! - **Layer 2 guards**: Second-hop relay verification
12//! - **Layer 3 guards**: Third-hop relay verification
13//! - **Path lengths**: Expected hop counts for each circuit purpose
14//!
15//! # Path Length Mappings
16//!
17//! Circuit path lengths vary based on the vanguards mode and circuit purpose:
18//!
19//! ```text
20//! ┌─────────────────────────────────────────────────────────────────────┐
21//! │                    Path Length by Purpose                           │
22//! ├─────────────────────┬──────────────────┬───────────────────────────┤
23//! │ Circuit Purpose     │ Full Vanguards   │ Vanguards-Lite            │
24//! ├─────────────────────┼──────────────────┼───────────────────────────┤
25//! │ HS_VANGUARDS        │ 4 hops           │ 3 hops                    │
26//! │ HS_CLIENT_HSDIR     │ 5 hops           │ 4 hops                    │
27//! │ HS_CLIENT_INTRO     │ 5 hops           │ 4 hops                    │
28//! │ HS_CLIENT_REND      │ 4 hops           │ 3 hops                    │
29//! │ HS_SERVICE_HSDIR    │ 4 hops           │ 4 hops                    │
30//! │ HS_SERVICE_INTRO    │ 4 hops           │ 4 hops                    │
31//! │ HS_SERVICE_REND     │ 5 hops           │ 4 hops                    │
32//! └─────────────────────┴──────────────────┴───────────────────────────┘
33//! ```
34//!
35//! # Guard Layer Architecture
36//!
37//! ```text
38//! ┌─────────────────────────────────────────────────────────────────────┐
39//! │                    Vanguard Layer Structure                         │
40//! ├─────────────────────────────────────────────────────────────────────┤
41//! │                                                                     │
42//! │  Client ──▶ Layer 1 ──▶ Layer 2 ──▶ Layer 3 ──▶ Destination     │
43//! │             (Guard)     (Middle)    (Middle)                        │
44//! │                                                                     │
45//! │  Full Vanguards:                                                    │
46//! │    • Layer 1: 2 guards (long-term)                                  │
47//! │    • Layer 2: 4 guards (medium-term rotation)                       │
48//! │    • Layer 3: 8 guards (short-term rotation)                        │
49//! │                                                                     │
50//! │  Vanguards-Lite:                                                    │
51//! │    • Layer 1: 1 guard                                               │
52//! │    • Layer 2: 4 guards (managed by Tor)                             │
53//! │    • Layer 3: None                                                  │
54//! │                                                                     │
55//! └─────────────────────────────────────────────────────────────────────┘
56//! ```
57//!
58//! # What This Module Does NOT Do
59//!
60//! - **Guard selection**: Use [`crate::node_selection`] for selecting guards
61//! - **Guard rotation**: Use [`crate::vanguards`] for managing guard state
62//! - **Circuit building**: This module only verifies existing circuits
63//!
64//! # See Also
65//!
66//! - [`crate::vanguards`] - Vanguard state management
67//! - [`crate::control`] - Event handling that calls path verification
68//! - [Python vanguards pathverify](https://github.com/mikeperry-tor/vanguards)
69
70use std::collections::{HashMap, HashSet};
71
72use crate::config::LogLevel;
73use crate::logger::plog;
74
75/// Expected path lengths for full vanguards mode.
76pub const ROUTELEN_FOR_PURPOSE: &[(&str, usize)] = &[
77    ("HS_VANGUARDS", 4),
78    ("HS_CLIENT_HSDIR", 5),
79    ("HS_CLIENT_INTRO", 5),
80    ("HS_CLIENT_REND", 4),
81    ("HS_SERVICE_HSDIR", 4),
82    ("HS_SERVICE_INTRO", 4),
83    ("HS_SERVICE_REND", 5),
84];
85
86/// Expected path lengths for vanguards-lite mode.
87pub const ROUTELEN_FOR_PURPOSE_LITE: &[(&str, usize)] = &[
88    ("HS_VANGUARDS", 3),
89    ("HS_CLIENT_HSDIR", 4),
90    ("HS_CLIENT_INTRO", 4),
91    ("HS_CLIENT_REND", 3),
92    ("HS_SERVICE_HSDIR", 4),
93    ("HS_SERVICE_INTRO", 4),
94    ("HS_SERVICE_REND", 4),
95];
96
97/// Per-guard usage statistics.
98///
99/// Tracks how many times a guard has been used and how many connections
100/// have been made to it. This helps detect anomalies in guard usage patterns.
101///
102/// # Fields
103///
104/// * `use_count` - Number of times this guard has been used in circuits
105/// * `conn_count` - Number of active connections to this guard
106///
107/// # Example
108///
109/// ```rust
110/// use vanguards_rs::pathverify::Layer1Stats;
111///
112/// let stats = Layer1Stats::new();
113/// assert_eq!(stats.use_count, 0);
114/// assert_eq!(stats.conn_count, 1);
115/// ```
116///
117/// # See Also
118///
119/// - [`Layer1Guards`] - Container for guard statistics
120#[derive(Debug, Clone, Default)]
121pub struct Layer1Stats {
122    /// Number of times this guard has been used in circuits.
123    pub use_count: u32,
124    /// Number of connections to this guard.
125    pub conn_count: u32,
126}
127
128impl Layer1Stats {
129    /// Creates a new Layer1Stats with one connection.
130    pub fn new() -> Self {
131        Self {
132            use_count: 0,
133            conn_count: 1,
134        }
135    }
136}
137
138/// Layer 1 guard tracking.
139///
140/// Tracks connections and usage for entry guards. Monitors for anomalies
141/// such as too many or too few guard connections, or unexpected guard usage.
142///
143/// # Example
144///
145/// ```rust
146/// use vanguards_rs::pathverify::Layer1Guards;
147///
148/// let mut guards = Layer1Guards::new(2);
149///
150/// // Track a guard connection
151/// guards.add_conn("AABBCCDD00112233445566778899AABBCCDDEEFF");
152/// assert!(guards.contains("AABBCCDD00112233445566778899AABBCCDDEEFF"));
153///
154/// // Track guard usage
155/// guards.add_use_count("AABBCCDD00112233445566778899AABBCCDDEEFF");
156/// ```
157///
158/// # See Also
159///
160/// - [`Layer1Stats`] - Statistics for individual guards
161/// - [`PathVerify`] - Uses this for layer 1 tracking
162#[derive(Debug, Clone)]
163pub struct Layer1Guards {
164    /// Guard statistics by fingerprint.
165    pub guards: HashMap<String, Layer1Stats>,
166    /// Expected number of layer 1 guards.
167    pub num_layer1: u8,
168}
169
170impl Layer1Guards {
171    /// Creates a new Layer1Guards tracker.
172    pub fn new(num_layer1: u8) -> Self {
173        Self {
174            guards: HashMap::new(),
175            num_layer1,
176        }
177    }
178
179    /// Adds a connection to a guard.
180    pub fn add_conn(&mut self, guard_fp: &str) {
181        if let Some(stats) = self.guards.get_mut(guard_fp) {
182            stats.conn_count += 1;
183        } else {
184            self.guards.insert(guard_fp.to_string(), Layer1Stats::new());
185        }
186    }
187
188    /// Removes a connection from a guard.
189    pub fn del_conn(&mut self, guard_fp: &str) {
190        if let Some(stats) = self.guards.get_mut(guard_fp) {
191            if stats.conn_count > 1 {
192                stats.conn_count -= 1;
193            } else {
194                self.guards.remove(guard_fp);
195            }
196        }
197    }
198
199    /// Checks connection counts and logs warnings.
200    ///
201    /// Returns -1 when fewer than expected, 0 when correct, +1 when too many.
202    pub fn check_conn_counts(&self) -> i32 {
203        let mut ret = 0;
204
205        if self.guards.len() < self.num_layer1 as usize {
206            plog(
207                LogLevel::Notice,
208                &format!(
209                    "Fewer guard connections than configured. Connected to: {:?}",
210                    self.guards.keys().collect::<Vec<_>>()
211                ),
212            );
213            ret = -1;
214        } else if self.guards.len() > self.num_layer1 as usize {
215            plog(
216                LogLevel::Notice,
217                &format!(
218                    "More guard connections than configured. Connected to: {:?}",
219                    self.guards.keys().collect::<Vec<_>>()
220                ),
221            );
222            ret = 1;
223        }
224
225        for (guard_fp, stats) in &self.guards {
226            if stats.conn_count > 1 {
227                plog(
228                    LogLevel::Notice,
229                    &format!(
230                        "Extra connections to guard {}: {}",
231                        guard_fp, stats.conn_count
232                    ),
233                );
234                ret = 1;
235            }
236        }
237
238        ret
239    }
240
241    /// Adds a use count for a guard.
242    pub fn add_use_count(&mut self, guard_fp: &str) {
243        if !self.guards.contains_key(guard_fp) {
244            plog(
245                LogLevel::Warn,
246                &format!(
247                    "Guard {} not in {:?}",
248                    guard_fp,
249                    self.guards.keys().collect::<Vec<_>>()
250                ),
251            );
252        } else if let Some(stats) = self.guards.get_mut(guard_fp) {
253            stats.use_count += 1;
254        }
255    }
256
257    /// Checks use counts and logs warnings.
258    ///
259    /// Returns -1 when fewer than expected, 0 when correct, +1 when too many.
260    pub fn check_use_counts(&self) -> i32 {
261        let mut ret = 0;
262
263        let layer1_in_use: Vec<_> = self
264            .guards
265            .iter()
266            .filter(|(_, stats)| stats.use_count > 0)
267            .map(|(fp, _)| fp.clone())
268            .collect();
269
270        let layer1_counts: Vec<_> = layer1_in_use
271            .iter()
272            .map(|fp| {
273                format!(
274                    "{}: {}",
275                    fp,
276                    self.guards.get(fp).map(|s| s.use_count).unwrap_or(0)
277                )
278            })
279            .collect();
280
281        if layer1_in_use.len() > self.num_layer1 as usize {
282            plog(
283                LogLevel::Warn,
284                &format!(
285                    "Circuits are being used on more guards than configured. \
286                     Current guard use: {:?}",
287                    layer1_counts
288                ),
289            );
290            ret = 1;
291        } else if layer1_in_use.len() < self.num_layer1 as usize {
292            plog(
293                LogLevel::Notice,
294                &format!(
295                    "Circuits are being used on fewer guards than configured. \
296                     Current guard use: {:?}",
297                    layer1_counts
298                ),
299            );
300            ret = -1;
301        }
302
303        ret
304    }
305
306    /// Returns true if the guard is tracked.
307    pub fn contains(&self, guard_fp: &str) -> bool {
308        self.guards.contains_key(guard_fp)
309    }
310}
311
312/// Path verification state.
313///
314/// Verifies that circuits use the configured vanguard relays and have
315/// the expected path lengths. Monitors guard connections and usage patterns
316/// to detect potential attacks or misconfigurations.
317///
318/// # State Tracking
319///
320/// ```text
321/// ┌─────────────────────────────────────────────────────────────────────┐
322/// │                    PathVerify State                                 │
323/// ├─────────────────────────────────────────────────────────────────────┤
324/// │                                                                     │
325/// │  layer1: Layer1Guards                                               │
326/// │    └── guards: HashMap<fingerprint, Layer1Stats>                    │
327/// │                                                                     │
328/// │  layer2: HashSet<fingerprint>                                       │
329/// │    └── Expected layer 2 guard fingerprints                          │
330/// │                                                                     │
331/// │  layer3: HashSet<fingerprint>                                       │
332/// │    └── Expected layer 3 guard fingerprints                          │
333/// │                                                                     │
334/// └─────────────────────────────────────────────────────────────────────┘
335/// ```
336///
337/// # Example
338///
339/// ```rust
340/// use vanguards_rs::pathverify::PathVerify;
341///
342/// // Create verifier for full vanguards mode
343/// let mut verifier = PathVerify::new(true, 2, 4, 8);
344/// assert!(verifier.full_vanguards);
345/// assert_eq!(verifier.routelen_for_purpose("HS_VANGUARDS"), Some(4));
346///
347/// // Create verifier for vanguards-lite mode
348/// let mut verifier_lite = PathVerify::new(false, 1, 4, 0);
349/// assert!(!verifier_lite.full_vanguards);
350/// assert_eq!(verifier_lite.routelen_for_purpose("HS_VANGUARDS"), Some(3));
351/// ```
352///
353/// # Event Handling
354///
355/// The verifier responds to several Tor events:
356///
357/// - `ORCONN`: Track guard connection state changes
358/// - `GUARD`: Track layer 2 guard changes (vanguards-lite)
359/// - `CIRC`: Verify circuit paths when built
360/// - `CIRC_MINOR`: Detect suspicious purpose changes
361/// - `CONF_CHANGED`: Update layer configuration
362///
363/// # See Also
364///
365/// - [`Layer1Guards`] - Layer 1 guard tracking
366/// - [`crate::control`] - Event dispatch to path verification
367#[derive(Debug, Clone)]
368pub struct PathVerify {
369    /// Layer 1 guard tracking.
370    pub layer1: Layer1Guards,
371    /// Layer 2 guard fingerprints.
372    pub layer2: HashSet<String>,
373    /// Layer 3 guard fingerprints.
374    pub layer3: HashSet<String>,
375    /// Whether full vanguards mode is enabled.
376    pub full_vanguards: bool,
377    /// Expected number of layer 1 guards.
378    pub num_layer1: u8,
379    /// Expected number of layer 2 guards.
380    pub num_layer2: u8,
381    /// Expected number of layer 3 guards.
382    pub num_layer3: u8,
383}
384
385impl PathVerify {
386    /// Creates a new PathVerify with the specified configuration.
387    pub fn new(full_vanguards: bool, num_layer1: u8, num_layer2: u8, num_layer3: u8) -> Self {
388        Self {
389            layer1: Layer1Guards::new(num_layer1),
390            layer2: HashSet::new(),
391            layer3: HashSet::new(),
392            full_vanguards,
393            num_layer1,
394            num_layer2,
395            num_layer3,
396        }
397    }
398
399    /// Initializes layer 2 and layer 3 from configuration values.
400    ///
401    /// # Arguments
402    ///
403    /// * `layer2_nodes` - Comma-separated layer 2 fingerprints (or None)
404    /// * `layer3_nodes` - Comma-separated layer 3 fingerprints (or None)
405    pub fn init_layers(&mut self, layer2_nodes: Option<&str>, layer3_nodes: Option<&str>) {
406        if let Some(nodes) = layer2_nodes {
407            if !nodes.is_empty() {
408                self.layer2 = nodes.split(',').map(|s| s.trim().to_string()).collect();
409                self.full_vanguards = true;
410            }
411        }
412
413        if let Some(nodes) = layer3_nodes {
414            if !nodes.is_empty() {
415                self.layer3 = nodes.split(',').map(|s| s.trim().to_string()).collect();
416                self.full_vanguards = true;
417            }
418        }
419
420        // If layers are empty and vanguards disabled, we're monitoring vg-lite
421        if self.layer2.is_empty() && self.layer3.is_empty() && !self.full_vanguards {
422            plog(
423                LogLevel::Notice,
424                "Monitoring vanguards-lite with pathverify.",
425            );
426            self.num_layer1 = 1;
427            self.num_layer2 = 4;
428            self.num_layer3 = 0;
429        } else {
430            plog(LogLevel::Notice, "Monitoring vanguards with pathverify.");
431        }
432
433        self.check_layer_counts();
434    }
435
436    /// Checks layer counts and logs warnings.
437    ///
438    /// Returns true when counts are correct, false otherwise.
439    pub fn check_layer_counts(&self) -> bool {
440        let mut ret = true;
441
442        // Layer2 can become empty briefly on sighup and startup
443        if self.layer2.len() > 1 && self.layer2.len() != self.num_layer2 as usize {
444            plog(
445                LogLevel::Notice,
446                &format!(
447                    "Wrong number of layer2 guards. {} vs: {:?}",
448                    self.num_layer2, self.layer2
449                ),
450            );
451            ret = false;
452        }
453
454        if self.layer3.len() > 1 && self.layer3.len() != self.num_layer3 as usize {
455            plog(
456                LogLevel::Notice,
457                &format!(
458                    "Wrong number of layer3 guards. {} vs: {:?}",
459                    self.num_layer3, self.layer3
460                ),
461            );
462            ret = false;
463        }
464
465        ret
466    }
467
468    /// Handles a CONF_CHANGED event.
469    ///
470    /// Updates layer configuration when HSLayer2Nodes or HSLayer3Nodes change.
471    pub fn conf_changed_event(&mut self, changed: &HashMap<String, Vec<String>>) {
472        if let Some(values) = changed.get("HSLayer2Nodes") {
473            if let Some(first) = values.first() {
474                self.layer2 = first.split(',').map(|s| s.trim().to_string()).collect();
475                self.full_vanguards = true;
476            }
477        }
478
479        if let Some(values) = changed.get("HSLayer3Nodes") {
480            if let Some(first) = values.first() {
481                self.layer3 = first.split(',').map(|s| s.trim().to_string()).collect();
482                self.full_vanguards = true;
483            }
484        }
485
486        self.check_layer_counts();
487    }
488
489    /// Handles an ORCONN event.
490    ///
491    /// Tracks guard connection state changes.
492    pub fn orconn_event(&mut self, guard_fp: &str, status: &str) {
493        match status {
494            "CONNECTED" => {
495                self.layer1.add_conn(guard_fp);
496            }
497            "CLOSED" | "FAILED" => {
498                self.layer1.del_conn(guard_fp);
499            }
500            _ => {}
501        }
502
503        self.layer1.check_conn_counts();
504    }
505
506    /// Handles a GUARD event.
507    ///
508    /// Tracks layer 2 guard changes for vanguards-lite.
509    pub fn guard_event(&mut self, guard_fp: &str, status: &str) {
510        match status {
511            "GOOD_L2" => {
512                self.layer2.insert(guard_fp.to_string());
513            }
514            "BAD_L2" => {
515                self.layer2.remove(guard_fp);
516            }
517            _ => {}
518        }
519    }
520
521    /// Returns the expected path length for a circuit purpose.
522    pub fn routelen_for_purpose(&self, purpose: &str) -> Option<usize> {
523        let table = if self.full_vanguards {
524            ROUTELEN_FOR_PURPOSE
525        } else {
526            ROUTELEN_FOR_PURPOSE_LITE
527        };
528
529        table
530            .iter()
531            .find(|(p, _)| *p == purpose)
532            .map(|(_, len)| *len)
533    }
534
535    /// Handles a CIRC event.
536    ///
537    /// Verifies circuit paths when circuits are built.
538    pub fn circ_event(
539        &mut self,
540        _circ_id: &str,
541        status: &str,
542        purpose: &str,
543        hs_state: Option<&str>,
544        path: &[(String, Option<String>)],
545    ) {
546        if !purpose.starts_with("HS_") {
547            return;
548        }
549
550        if status != "BUILT" && status != "GUARD_WAIT" {
551            return;
552        }
553
554        // Check path length
555        if let Some(expected_len) = self.routelen_for_purpose(purpose) {
556            if path.len() != expected_len {
557                // Some cases are expected (cannibalized circuits, retries)
558                let is_expected = (purpose == "HS_SERVICE_HSDIR"
559                    && hs_state == Some("HSSI_CONNECTING"))
560                    || (purpose == "HS_CLIENT_INTRO" && hs_state == Some("HSCI_CONNECTING"));
561
562                let level = if is_expected {
563                    LogLevel::Info
564                } else {
565                    LogLevel::Notice
566                };
567
568                plog(
569                    level,
570                    &format!(
571                        "Tor made a {}-hop path, but I wanted a {}-hop path for purpose {}:{:?}",
572                        path.len(),
573                        expected_len,
574                        purpose,
575                        hs_state
576                    ),
577                );
578            }
579        }
580
581        // Check layer 1 guard
582        if !path.is_empty() {
583            let guard_fp = &path[0].0;
584            self.layer1.add_use_count(guard_fp);
585            self.layer1.check_use_counts();
586        }
587
588        // Check layer 2 guard
589        if path.len() > 1 && !self.layer2.contains(&path[1].0) {
590            plog(
591                LogLevel::Warn,
592                &format!("Layer2 {} not in {:?}", path[1].0, self.layer2),
593            );
594        }
595
596        // Check layer 3 guard
597        if self.num_layer3 > 0 && path.len() > 2 && !self.layer3.contains(&path[2].0) {
598            plog(
599                LogLevel::Warn,
600                &format!("Layer3 {} not in {:?}", path[2].0, self.layer3),
601            );
602        }
603
604        // Check layer counts
605        if self.layer2.len() != self.num_layer2 as usize {
606            plog(
607                LogLevel::Warn,
608                &format!(
609                    "Circuit built with different number of layer2 nodes than configured. \
610                     Currently using: {:?}",
611                    self.layer2
612                ),
613            );
614        }
615
616        if self.layer3.len() != self.num_layer3 as usize {
617            plog(
618                LogLevel::Warn,
619                &format!(
620                    "Circuit built with different number of layer3 nodes than configured. \
621                     Currently using: {:?}",
622                    self.layer3
623                ),
624            );
625        }
626    }
627
628    /// Handles a CIRC_MINOR event (purpose changes).
629    ///
630    /// Warns on suspicious purpose changes.
631    pub fn circ_minor_event(
632        &mut self,
633        _circ_id: &str,
634        purpose: &str,
635        old_purpose: Option<&str>,
636        path: &[(String, Option<String>)],
637    ) {
638        let is_hs = purpose.starts_with("HS_");
639        let was_hs = old_purpose.map(|p| p.starts_with("HS_")).unwrap_or(false);
640
641        // Warn on purpose changes between HS and non-HS
642        if is_hs && !was_hs {
643            plog(
644                LogLevel::Warn,
645                &format!(
646                    "Purpose switched from non-hs to hs: {:?} -> {}",
647                    old_purpose, purpose
648                ),
649            );
650        } else if !is_hs && was_hs {
651            // Some purpose changes are expected
652            if purpose != "CIRCUIT_PADDING"
653                && purpose != "MEASURE_TIMEOUT"
654                && purpose != "PATH_BIAS_TESTING"
655            {
656                plog(
657                    LogLevel::Warn,
658                    &format!(
659                        "Purpose switched from hs to non-hs: {:?} -> {}",
660                        old_purpose, purpose
661                    ),
662                );
663            }
664        }
665
666        // Verify guards for HS circuits
667        if is_hs || was_hs {
668            if !path.is_empty() && !self.layer1.contains(&path[0].0) {
669                plog(
670                    LogLevel::Warn,
671                    &format!(
672                        "Guard {} not in {:?}",
673                        path[0].0,
674                        self.layer1.guards.keys().collect::<Vec<_>>()
675                    ),
676                );
677            }
678
679            if path.len() > 1 && !self.layer2.contains(&path[1].0) {
680                plog(
681                    LogLevel::Warn,
682                    &format!("Layer2 {} not in {:?}", path[1].0, self.layer2),
683                );
684            }
685
686            if self.num_layer3 > 0 && path.len() > 2 && !self.layer3.contains(&path[2].0) {
687                plog(
688                    LogLevel::Warn,
689                    &format!("Layer3 {} not in {:?}", path[2].0, self.layer3),
690                );
691            }
692        }
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699
700    #[test]
701    fn test_layer1_stats_new() {
702        let stats = Layer1Stats::new();
703        assert_eq!(stats.use_count, 0);
704        assert_eq!(stats.conn_count, 1);
705    }
706
707    #[test]
708    fn test_layer1_guards_new() {
709        let guards = Layer1Guards::new(2);
710        assert!(guards.guards.is_empty());
711        assert_eq!(guards.num_layer1, 2);
712    }
713
714    #[test]
715    fn test_layer1_guards_add_conn() {
716        let mut guards = Layer1Guards::new(2);
717        let fp = "A".repeat(40);
718
719        guards.add_conn(&fp);
720        assert!(guards.guards.contains_key(&fp));
721        assert_eq!(guards.guards.get(&fp).unwrap().conn_count, 1);
722
723        guards.add_conn(&fp);
724        assert_eq!(guards.guards.get(&fp).unwrap().conn_count, 2);
725    }
726
727    #[test]
728    fn test_layer1_guards_del_conn() {
729        let mut guards = Layer1Guards::new(2);
730        let fp = "A".repeat(40);
731
732        guards.add_conn(&fp);
733        guards.add_conn(&fp);
734        assert_eq!(guards.guards.get(&fp).unwrap().conn_count, 2);
735
736        guards.del_conn(&fp);
737        assert_eq!(guards.guards.get(&fp).unwrap().conn_count, 1);
738
739        guards.del_conn(&fp);
740        assert!(!guards.guards.contains_key(&fp));
741    }
742
743    #[test]
744    fn test_layer1_guards_add_use_count() {
745        let mut guards = Layer1Guards::new(2);
746        let fp = "A".repeat(40);
747
748        guards.add_conn(&fp);
749        guards.add_use_count(&fp);
750        guards.add_use_count(&fp);
751
752        assert_eq!(guards.guards.get(&fp).unwrap().use_count, 2);
753    }
754
755    #[test]
756    fn test_path_verify_new() {
757        let verifier = PathVerify::new(true, 2, 4, 8);
758        assert!(verifier.full_vanguards);
759        assert_eq!(verifier.num_layer1, 2);
760        assert_eq!(verifier.num_layer2, 4);
761        assert_eq!(verifier.num_layer3, 8);
762        assert!(verifier.layer2.is_empty());
763        assert!(verifier.layer3.is_empty());
764    }
765
766    #[test]
767    fn test_path_verify_init_layers() {
768        let mut verifier = PathVerify::new(false, 2, 4, 8);
769
770        verifier.init_layers(
771            Some("AAAA,BBBB,CCCC,DDDD"),
772            Some("1111,2222,3333,4444,5555,6666,7777,8888"),
773        );
774
775        assert!(verifier.full_vanguards);
776        assert_eq!(verifier.layer2.len(), 4);
777        assert_eq!(verifier.layer3.len(), 8);
778        assert!(verifier.layer2.contains("AAAA"));
779        assert!(verifier.layer3.contains("1111"));
780    }
781
782    #[test]
783    fn test_routelen_for_purpose_full() {
784        let verifier = PathVerify::new(true, 2, 4, 8);
785
786        assert_eq!(verifier.routelen_for_purpose("HS_VANGUARDS"), Some(4));
787        assert_eq!(verifier.routelen_for_purpose("HS_CLIENT_HSDIR"), Some(5));
788        assert_eq!(verifier.routelen_for_purpose("HS_CLIENT_INTRO"), Some(5));
789        assert_eq!(verifier.routelen_for_purpose("HS_CLIENT_REND"), Some(4));
790        assert_eq!(verifier.routelen_for_purpose("HS_SERVICE_HSDIR"), Some(4));
791        assert_eq!(verifier.routelen_for_purpose("HS_SERVICE_INTRO"), Some(4));
792        assert_eq!(verifier.routelen_for_purpose("HS_SERVICE_REND"), Some(5));
793        assert_eq!(verifier.routelen_for_purpose("GENERAL"), None);
794    }
795
796    #[test]
797    fn test_routelen_for_purpose_lite() {
798        let verifier = PathVerify::new(false, 1, 4, 0);
799
800        assert_eq!(verifier.routelen_for_purpose("HS_VANGUARDS"), Some(3));
801        assert_eq!(verifier.routelen_for_purpose("HS_CLIENT_HSDIR"), Some(4));
802        assert_eq!(verifier.routelen_for_purpose("HS_CLIENT_INTRO"), Some(4));
803        assert_eq!(verifier.routelen_for_purpose("HS_CLIENT_REND"), Some(3));
804        assert_eq!(verifier.routelen_for_purpose("HS_SERVICE_HSDIR"), Some(4));
805        assert_eq!(verifier.routelen_for_purpose("HS_SERVICE_INTRO"), Some(4));
806        assert_eq!(verifier.routelen_for_purpose("HS_SERVICE_REND"), Some(4));
807    }
808
809    #[test]
810    fn test_orconn_event() {
811        let mut verifier = PathVerify::new(true, 2, 4, 8);
812        let fp = "A".repeat(40);
813
814        verifier.orconn_event(&fp, "CONNECTED");
815        assert!(verifier.layer1.guards.contains_key(&fp));
816
817        verifier.orconn_event(&fp, "CLOSED");
818        assert!(!verifier.layer1.guards.contains_key(&fp));
819    }
820
821    #[test]
822    fn test_guard_event() {
823        let mut verifier = PathVerify::new(true, 2, 4, 8);
824        let fp = "A".repeat(40);
825
826        verifier.guard_event(&fp, "GOOD_L2");
827        assert!(verifier.layer2.contains(&fp));
828
829        verifier.guard_event(&fp, "BAD_L2");
830        assert!(!verifier.layer2.contains(&fp));
831    }
832
833    #[test]
834    fn test_conf_changed_event() {
835        let mut verifier = PathVerify::new(false, 2, 4, 8);
836
837        let mut changed = HashMap::new();
838        changed.insert(
839            "HSLayer2Nodes".to_string(),
840            vec!["AAAA,BBBB,CCCC,DDDD".to_string()],
841        );
842
843        verifier.conf_changed_event(&changed);
844
845        assert!(verifier.full_vanguards);
846        assert_eq!(verifier.layer2.len(), 4);
847    }
848
849    #[test]
850    fn test_check_conn_counts_correct() {
851        let mut guards = Layer1Guards::new(2);
852        guards.add_conn(&"A".repeat(40));
853        guards.add_conn(&"B".repeat(40));
854
855        assert_eq!(guards.check_conn_counts(), 0);
856    }
857
858    #[test]
859    fn test_check_conn_counts_fewer() {
860        let mut guards = Layer1Guards::new(2);
861        guards.add_conn(&"A".repeat(40));
862
863        assert_eq!(guards.check_conn_counts(), -1);
864    }
865
866    #[test]
867    fn test_check_conn_counts_more() {
868        let mut guards = Layer1Guards::new(2);
869        guards.add_conn(&"A".repeat(40));
870        guards.add_conn(&"B".repeat(40));
871        guards.add_conn(&"C".repeat(40));
872
873        assert_eq!(guards.check_conn_counts(), 1);
874    }
875
876    #[test]
877    fn test_pathverify_init_correct_counts() {
878        let mut pv = PathVerify::new(true, 2, 3, 8);
879
880        pv.layer2
881            .insert("5416F3E8F80101A133B1970495B04FDBD1C7446B".to_string());
882        pv.layer2
883            .insert("855BC2DABE24C861CD887DB9B2E950424B49FC34".to_string());
884        pv.layer2
885            .insert("1F9544C0A80F1C5D8A5117FBFFB50694469CC7F4".to_string());
886
887        for i in 0..8 {
888            pv.layer3.insert(format!("{:0>40X}", i));
889        }
890
891        pv.layer1
892            .add_conn("66CA5474346F35E375C4D4514C51A540545347EE");
893        pv.layer1
894            .add_conn("5416F3E8F80101A133B1970495B04FDBD1C7446B");
895
896        assert_eq!(pv.layer1.check_conn_counts(), 0);
897        assert!(pv.check_layer_counts());
898    }
899
900    #[test]
901    fn test_pathverify_too_many_guards() {
902        let mut pv = PathVerify::new(true, 2, 3, 8);
903
904        pv.layer1
905            .add_conn("66CA5474346F35E375C4D4514C51A540545347EE");
906        pv.layer1
907            .add_conn("5416F3E8F80101A133B1970495B04FDBD1C7446B");
908        pv.layer1
909            .add_conn("3E53D3979DB07EFD736661C934A1DED14127B684");
910
911        assert_eq!(pv.layer1.check_conn_counts(), 1);
912    }
913
914    #[test]
915    fn test_pathverify_too_few_guards() {
916        let mut pv = PathVerify::new(true, 2, 3, 8);
917
918        pv.layer1
919            .add_conn("66CA5474346F35E375C4D4514C51A540545347EE");
920
921        assert_eq!(pv.layer1.check_conn_counts(), -1);
922    }
923
924    #[test]
925    fn test_layer1_use_counts() {
926        let mut pv = PathVerify::new(true, 2, 4, 8);
927
928        pv.layer1
929            .add_conn("5416F3E8F80101A133B1970495B04FDBD1C7446B");
930        pv.layer1
931            .add_conn("66CA5474346F35E375C4D4514C51A540545347EE");
932
933        pv.layer1
934            .add_use_count("5416F3E8F80101A133B1970495B04FDBD1C7446B");
935
936        assert_eq!(pv.layer1.check_use_counts(), -1);
937
938        pv.layer1
939            .add_use_count("66CA5474346F35E375C4D4514C51A540545347EE");
940
941        assert_eq!(pv.layer1.check_use_counts(), 0);
942    }
943
944    #[test]
945    fn test_layer1_too_many_in_use() {
946        let mut pv = PathVerify::new(true, 2, 4, 8);
947
948        pv.layer1
949            .add_conn("5416F3E8F80101A133B1970495B04FDBD1C7446B");
950        pv.layer1
951            .add_conn("66CA5474346F35E375C4D4514C51A540545347EE");
952        pv.layer1
953            .add_conn("5416F3E8F80101A133B1970495B04FDBD1C7446D");
954
955        pv.layer1
956            .add_use_count("5416F3E8F80101A133B1970495B04FDBD1C7446B");
957        pv.layer1
958            .add_use_count("66CA5474346F35E375C4D4514C51A540545347EE");
959        pv.layer1
960            .add_use_count("5416F3E8F80101A133B1970495B04FDBD1C7446D");
961
962        assert_eq!(pv.layer1.check_use_counts(), 1);
963    }
964
965    #[test]
966    fn test_conf_changed_event_both_layers() {
967        let mut pv = PathVerify::new(false, 2, 4, 8);
968
969        let mut changed = HashMap::new();
970        changed.insert(
971            "HSLayer2Nodes".to_string(),
972            vec!["5416F3E8F80101A133B1970495B04FDBD1C7446D".to_string()],
973        );
974        changed.insert(
975            "HSLayer3Nodes".to_string(),
976            vec!["5416F3E8F80101A133B1970495B04FDBD1C7446D".to_string()],
977        );
978
979        pv.conf_changed_event(&changed);
980
981        assert!(pv.full_vanguards);
982        assert_eq!(pv.layer2.len(), 1);
983        assert_eq!(pv.layer3.len(), 1);
984    }
985
986    #[test]
987    fn test_init_layers_vanguards_lite() {
988        let mut pv = PathVerify::new(false, 2, 4, 8);
989
990        pv.init_layers(None, None);
991
992        assert!(!pv.full_vanguards);
993        assert_eq!(pv.num_layer1, 1);
994        assert_eq!(pv.num_layer2, 4);
995        assert_eq!(pv.num_layer3, 0);
996    }
997}
998
999#[cfg(test)]
1000mod proptests {
1001    use super::*;
1002    use proptest::prelude::*;
1003
1004    proptest! {
1005        #![proptest_config(ProptestConfig::with_cases(100))]
1006
1007        #[test]
1008        fn path_length_verification(
1009            full_vanguards in any::<bool>(),
1010            purpose_idx in 0usize..7,
1011        ) {
1012            let purposes = [
1013                "HS_VANGUARDS",
1014                "HS_CLIENT_HSDIR",
1015                "HS_CLIENT_INTRO",
1016                "HS_CLIENT_REND",
1017                "HS_SERVICE_HSDIR",
1018                "HS_SERVICE_INTRO",
1019                "HS_SERVICE_REND",
1020            ];
1021
1022            let purpose = purposes[purpose_idx];
1023            let verifier = PathVerify::new(full_vanguards, 2, 4, 8);
1024
1025            let expected_len = if full_vanguards {
1026                ROUTELEN_FOR_PURPOSE.iter()
1027                    .find(|(p, _)| *p == purpose)
1028                    .map(|(_, len)| *len)
1029            } else {
1030                ROUTELEN_FOR_PURPOSE_LITE.iter()
1031                    .find(|(p, _)| *p == purpose)
1032                    .map(|(_, len)| *len)
1033            };
1034
1035            let actual_len = verifier.routelen_for_purpose(purpose);
1036
1037            prop_assert_eq!(actual_len, expected_len,
1038                "Path length for {} (full_vanguards={}): expected {:?}, got {:?}",
1039                purpose, full_vanguards, expected_len, actual_len);
1040
1041            if full_vanguards {
1042                match purpose {
1043                    "HS_VANGUARDS" => prop_assert_eq!(actual_len, Some(4)),
1044                    "HS_CLIENT_HSDIR" => prop_assert_eq!(actual_len, Some(5)),
1045                    "HS_CLIENT_INTRO" => prop_assert_eq!(actual_len, Some(5)),
1046                    "HS_CLIENT_REND" => prop_assert_eq!(actual_len, Some(4)),
1047                    "HS_SERVICE_HSDIR" => prop_assert_eq!(actual_len, Some(4)),
1048                    "HS_SERVICE_INTRO" => prop_assert_eq!(actual_len, Some(4)),
1049                    "HS_SERVICE_REND" => prop_assert_eq!(actual_len, Some(5)),
1050                    _ => {}
1051                }
1052            } else {
1053                match purpose {
1054                    "HS_VANGUARDS" => prop_assert_eq!(actual_len, Some(3)),
1055                    "HS_CLIENT_HSDIR" => prop_assert_eq!(actual_len, Some(4)),
1056                    "HS_CLIENT_INTRO" => prop_assert_eq!(actual_len, Some(4)),
1057                    "HS_CLIENT_REND" => prop_assert_eq!(actual_len, Some(3)),
1058                    "HS_SERVICE_HSDIR" => prop_assert_eq!(actual_len, Some(4)),
1059                    "HS_SERVICE_INTRO" => prop_assert_eq!(actual_len, Some(4)),
1060                    "HS_SERVICE_REND" => prop_assert_eq!(actual_len, Some(4)),
1061                    _ => {}
1062                }
1063            }
1064        }
1065    }
1066}