tor_basic_utils/
rand_hostname.rs1use crate::RngExt as _;
4use rand::{Rng, seq::IndexedRandom as _};
5
6const PREFIXES: &[&str] = &["www."];
8
9const SUFFIXES: &[&str] = &[".com", ".net", ".org"];
11
12const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz-0123456789";
14
15const MIN_LEN: usize = 16;
17const MAX_LEN: usize = 32;
19
20pub fn random_hostname<R: Rng>(rng: &mut R) -> String {
24 let prefix = PREFIXES.choose(rng).expect("TLDS was empty!?");
27 let suffix = SUFFIXES.choose(rng).expect("TLDS was empty!?");
28
29 let length: usize = rng
30 .gen_range_checked(MIN_LEN..=MAX_LEN)
31 .expect("Somehow MIN..=MAX wasn't a valid range?");
32 let center_length = length
33 .checked_sub(prefix.len() + suffix.len())
34 .expect("prefix and suffix exceeded MIN_LEN");
35
36 let mut output = String::from(*prefix);
37 for _ in 0..center_length {
38 output.push(*CHARSET.choose(rng).expect("CHARSET was empty!?") as char);
39 }
40 output.push_str(suffix);
41
42 assert_eq!(length, output.len());
43 output
44}
45
46#[cfg(test)]
47mod test {
48 #![allow(clippy::bool_assert_comparison)]
50 #![allow(clippy::clone_on_copy)]
51 #![allow(clippy::dbg_macro)]
52 #![allow(clippy::mixed_attributes_style)]
53 #![allow(clippy::print_stderr)]
54 #![allow(clippy::print_stdout)]
55 #![allow(clippy::single_char_pattern)]
56 #![allow(clippy::unwrap_used)]
57 #![allow(clippy::unchecked_duration_subtraction)]
58 #![allow(clippy::useless_vec)]
59 #![allow(clippy::needless_pass_by_value)]
60 use super::*;
63 use crate::test_rng::testing_rng;
64
65 #[test]
66 fn generate_names() {
67 let mut rng = testing_rng();
68
69 for _ in 0..100 {
70 let name = random_hostname(&mut rng);
71 assert!(PREFIXES.iter().any(|tld| name.starts_with(tld)));
72 assert!(SUFFIXES.iter().any(|tld| name.ends_with(tld)));
73 assert!(name.len() >= MIN_LEN);
74 assert!(name.len() <= MAX_LEN);
75 for ch in name.chars() {
76 assert!(matches!(ch, '.' | '-' | '0'..='9' | 'a'..='z'));
77 }
78 }
79 }
80}