tor_basic_utils/
rand_hostname.rs

1//! Utility to return a random hostname.
2
3use crate::RngExt as _;
4use rand::{Rng, seq::IndexedRandom as _};
5
6/// The prefixes we put at the front of every random hostname, with terminating `.`.
7const PREFIXES: &[&str] = &["www."];
8
9/// The suffixes that we use when picking a random hostname, with preceding `.`.
10const SUFFIXES: &[&str] = &[".com", ".net", ".org"];
11
12/// The characters that we use for the middle part of a hostname.
13const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz-0123456789";
14
15/// Lowest permissible hostname length.
16const MIN_LEN: usize = 16;
17/// Highest permissible hostname length.
18const MAX_LEN: usize = 32;
19
20/// Return a somewhat random-looking hostname.
21///
22/// The specific format of the hostname is not guaranteed.
23pub fn random_hostname<R: Rng>(rng: &mut R) -> String {
24    // TODO: This is, roughly, what C tor does.
25    // But that doesn't mean it's remotely clever.
26    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    // @@ begin test lint list maintained by maint/add_warning @@
49    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
61
62    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}