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"abcdefghijklmnopqrstuvwxyz0123456789";
17
18const MIN_LEN: usize = 16;
20const MAX_LEN: usize = 32;
22
23pub fn random_hostname<R: Rng>(rng: &mut R) -> String {
27 let prefix = PREFIXES.choose(rng).expect("TLDS was empty!?");
30 let suffix = SUFFIXES.choose(rng).expect("TLDS was empty!?");
31
32 let length: usize = rng
33 .gen_range_checked(MIN_LEN..=MAX_LEN)
34 .expect("Somehow MIN..=MAX wasn't a valid range?");
35 let center_length = length
36 .checked_sub(prefix.len() + suffix.len())
37 .expect("prefix and suffix exceeded MIN_LEN");
38
39 let mut output = String::from(*prefix);
40 for _ in 0..center_length {
41 output.push(*CHARSET.choose(rng).expect("CHARSET was empty!?") as char);
42 }
43 output.push_str(suffix);
44
45 assert_eq!(length, output.len());
46 output
47}
48
49#[cfg(test)]
50mod test {
51 #![allow(clippy::bool_assert_comparison)]
53 #![allow(clippy::clone_on_copy)]
54 #![allow(clippy::dbg_macro)]
55 #![allow(clippy::mixed_attributes_style)]
56 #![allow(clippy::print_stderr)]
57 #![allow(clippy::print_stdout)]
58 #![allow(clippy::single_char_pattern)]
59 #![allow(clippy::unwrap_used)]
60 #![allow(clippy::unchecked_time_subtraction)]
61 #![allow(clippy::useless_vec)]
62 #![allow(clippy::needless_pass_by_value)]
63 #![allow(clippy::string_slice)] use super::*;
67 use crate::test_rng::testing_rng;
68
69 #[test]
70 fn generate_names() {
71 let mut rng = testing_rng();
72
73 for _ in 0..100 {
74 let name = random_hostname(&mut rng);
75 assert!(PREFIXES.iter().any(|tld| name.starts_with(tld)));
76 assert!(SUFFIXES.iter().any(|tld| name.ends_with(tld)));
77 assert!(name.len() >= MIN_LEN);
78 assert!(name.len() <= MAX_LEN);
79 for ch in name.chars() {
80 assert!(matches!(ch, '.' | '0'..='9' | 'a'..='z'));
81 }
82 }
83 }
84}