1use std::collections::{HashMap, HashSet};
71
72use crate::config::LogLevel;
73use crate::logger::plog;
74
75pub 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
86pub 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#[derive(Debug, Clone, Default)]
121pub struct Layer1Stats {
122 pub use_count: u32,
124 pub conn_count: u32,
126}
127
128impl Layer1Stats {
129 pub fn new() -> Self {
131 Self {
132 use_count: 0,
133 conn_count: 1,
134 }
135 }
136}
137
138#[derive(Debug, Clone)]
163pub struct Layer1Guards {
164 pub guards: HashMap<String, Layer1Stats>,
166 pub num_layer1: u8,
168}
169
170impl Layer1Guards {
171 pub fn new(num_layer1: u8) -> Self {
173 Self {
174 guards: HashMap::new(),
175 num_layer1,
176 }
177 }
178
179 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 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 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 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 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 pub fn contains(&self, guard_fp: &str) -> bool {
308 self.guards.contains_key(guard_fp)
309 }
310}
311
312#[derive(Debug, Clone)]
368pub struct PathVerify {
369 pub layer1: Layer1Guards,
371 pub layer2: HashSet<String>,
373 pub layer3: HashSet<String>,
375 pub full_vanguards: bool,
377 pub num_layer1: u8,
379 pub num_layer2: u8,
381 pub num_layer3: u8,
383}
384
385impl PathVerify {
386 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 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 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 pub fn check_layer_counts(&self) -> bool {
440 let mut ret = true;
441
442 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 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 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 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 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 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 if let Some(expected_len) = self.routelen_for_purpose(purpose) {
556 if path.len() != expected_len {
557 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 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 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 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 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 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 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 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 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}