fake_torrent_client/
algorithm.rs1use rand::Rng;
2use std::convert::TryFrom;
3
4use crate::{KEY_LENGTH, PEER_ID_LENGTH};
5
6#[derive(Debug, Clone)]
7pub(crate) enum Algorithm {
8 Regex,
9 Hash,
10 HashNoLeadingZero,
11 DigitRangeTransformedToHexWithoutLeadingZeroes,
13 RandomPoolWithChecksum,
14}
15
16const HASH_SYMBOLS: &str = "abcdef0123456789ABCDEF";
17
18pub(crate) fn hash(no_leading_zero: bool, uppercase: Option<bool>) -> String {
19 let mut rng = rand::thread_rng();
20 let mut h = String::with_capacity(KEY_LENGTH);
21 while h.len() < KEY_LENGTH {
22 let i: usize = rng.gen_range(0usize..15usize);
23 if i == 0 && no_leading_zero {
24 continue;
25 }
26 if uppercase == None || uppercase.unwrap() {
27 h.push(HASH_SYMBOLS.chars().nth(i + 6).unwrap());
28 } else {
29 h.push(HASH_SYMBOLS.chars().nth(i).unwrap());
30 }
31 }
32 h
33}
34pub(crate) fn regex(pattern: String) -> String {
36 let mut rng = rand::thread_rng();
37 let gen = rand_regex::Regex::compile(&pattern, 64).unwrap();
38 (&mut rng).sample_iter(&gen).nth(64).unwrap()
39}
40pub(crate) fn digit_range_transformed_to_hex_without_leading_zero() -> String {
42 let mut rng = rand::thread_rng();
43 format!("{:x}", rng.gen_range(1u32..2147483647u32)).to_uppercase()
44}
45pub(crate) fn random_pool_with_checksum(prefix: &str, characters_pool: &str) -> String {
47 if prefix.is_empty() {
49 panic!("algorithm prefix must not be null or empty.");
50 }
51 if characters_pool.is_empty() {
52 panic!("algorithm character pool must not be null or empty.");
53 }
54 let mut out = String::with_capacity(PEER_ID_LENGTH);
55 out.push_str(prefix);
56 let random_bytes = rand::thread_rng().gen::<[u8; PEER_ID_LENGTH]>();
58 let base = characters_pool.chars().count();
59 for b in random_bytes {
60 out.push(characters_pool.chars().nth(usize::try_from(b).unwrap() % base).expect("Cannot get char in characters pool"));
61 if out.len() == PEER_ID_LENGTH {break;}
62 }
63 out
64}
65#[cfg(test)]
67mod tests {
68 use super::*;
69 use regex::Regex;
70 #[test]
71 fn is_hash_ok() {
72 for _ in 0usize..16usize {
73 assert_eq!(hash(false, None).len(), 8usize);
74 let h = hash(true, None);
75 assert_eq!(h.len(), 8usize);
76 assert!(!h.starts_with('0'));
77 }
78 }
79 #[test]
80 fn is_hash_case_ok() {
81 let re_uc = Regex::new(r"[A-Z0-9]{8}").unwrap();
82 let re_lc = Regex::new(r"[a-z0-9]{8}").unwrap();
83 for _ in 0usize..16usize {
84 assert!(re_uc.is_match(&hash(false, None)));
85 assert!(re_uc.is_match(&hash(false, Some(true))));
86 assert!(re_lc.is_match(&hash(false, Some(false))));
87 }
88 }
89 #[test]
90 fn is_regex_ok() {
91 let mut re = Regex::new("-lt0D60-[\u{0001}-\u{00ff}]{12}").unwrap();
92 assert!(re.is_match(®ex("-lt0D60-[\u{0001}-\u{00ff}]{12}".to_owned())));
93 re = Regex::new("-AZ5750-[a-zA-Z0-9]{12}").unwrap();
94 assert!(re.is_match(®ex("-AZ5750-[a-zA-Z0-9]{12}".to_owned())));
95 }
96
97 #[test]
98 fn is_regex_ok_2() {
99 let s = String::from(r"-DE13D0-[A-Za-z0-9_~\\(\\)\\!\\.\\*-]{12}");
100 let re = Regex::new(&s).unwrap();
101 assert!(re.is_match(®ex(s)));
102 }
103
104 #[test]
105 fn is_digit_range_to_hex_ok() {
106 let re = Regex::new(r"[A-Z0-9]+").unwrap();
107 for _ in 0usize..16usize {
108 assert!(re.is_match(&digit_range_transformed_to_hex_without_leading_zero()));
109 }
110 }
111}