1
//! Utility to return a random hostname.
2

            
3
use crate::RngExt as _;
4
use rand::{Rng, seq::IndexedRandom as _};
5

            
6
/// The prefixes we put at the front of every random hostname, with terminating `.`.
7
const PREFIXES: &[&str] = &["www."];
8

            
9
/// The suffixes that we use when picking a random hostname, with preceding `.`.
10
const SUFFIXES: &[&str] = &[".com", ".net", ".org"];
11

            
12
/// The characters that we use for the middle part of a hostname.
13
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz-0123456789";
14

            
15
/// Lowest permissible hostname length.
16
const MIN_LEN: usize = 16;
17
/// Highest permissible hostname length.
18
const MAX_LEN: usize = 32;
19

            
20
/// Return a somewhat random-looking hostname.
21
///
22
/// The specific format of the hostname is not guaranteed.
23
202
pub 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
202
    let prefix = PREFIXES.choose(rng).expect("TLDS was empty!?");
27
202
    let suffix = SUFFIXES.choose(rng).expect("TLDS was empty!?");
28

            
29
202
    let length: usize = rng
30
202
        .gen_range_checked(MIN_LEN..=MAX_LEN)
31
202
        .expect("Somehow MIN..=MAX wasn't a valid range?");
32
202
    let center_length = length
33
202
        .checked_sub(prefix.len() + suffix.len())
34
202
        .expect("prefix and suffix exceeded MIN_LEN");
35

            
36
202
    let mut output = String::from(*prefix);
37
3234
    for _ in 0..center_length {
38
3234
        output.push(*CHARSET.choose(rng).expect("CHARSET was empty!?") as char);
39
3234
    }
40
202
    output.push_str(suffix);
41

            
42
202
    assert_eq!(length, output.len());
43
202
    output
44
202
}
45

            
46
#[cfg(test)]
47
mod 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_time_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
}