1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use indexmap::IndexSet;
use num_bigint::BigUint;
use num_traits::ToPrimitive;
use rand::Rng;

/// Generate random password.
///
/// # Examples
/// ```
/// # use indexmap::IndexSet;
/// let pool = "0123456789".chars().collect::<IndexSet<char>>();
/// let password = upwd::generate_password(pool, 15);
///
/// assert_eq!(password.len(), 15);
/// ```
///
/// # Panics
/// Panics if `pool` is empty.
pub fn generate_password(pool: IndexSet<char>, length: usize) -> String {
    assert!(!pool.is_empty(), "Pool contains no elements!");

    let mut rng = rand::thread_rng();

    (0..length)
        .map(|_| {
            let idx = rng.gen_range(0, pool.len());
            pool[idx]
        })
        .collect()
}

/// Calculates entropy.
/// Returns `f64::NEG_INFINITY` if `pool_size` is 0
///
/// # Examples
/// ```
/// let entropy = upwd::calculate_entropy(12, 64);
///
/// assert_eq!(entropy, 72.0);
/// ```
///
/// # Panics
/// Panics when casting BigUint to f64.
/// This usually means that the entropy will be greater than 1024 bits.
// ToDo Нужно ли возвращать бесконечность?
pub fn calculate_entropy(length: usize, pool_size: usize) -> f64 {
    BigUint::from(pool_size)
        .pow(length as u32)
        .to_f64()
        .expect("Typecast error! Failed to convert BigUint to f64!")
        .log2()
}

/// Calculates the required password length to obtain the given entropy.
///
/// # Examples
/// ```
/// let length = upwd::calculate_length(128.0, 64.0);
///
/// assert_eq!(length.ceil(), 22.0);
/// ```
pub fn calculate_length(entropy: f64, pool_size: f64) -> f64 {
    entropy / pool_size.log2()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn generate_password_assert_len() {
        let pool = "0123456789".chars().collect::<IndexSet<char>>();
        let password = generate_password(pool, 15);

        assert_eq!(password.len(), 15);
    }

    #[test]
    #[should_panic(expected = "Pool contains no elements!")]
    fn generate_password_passed_empty_pool() {
        let pool = "".chars().collect::<IndexSet<char>>();

        generate_password(pool, 15);
    }

    #[test]
    fn calculate_entropy_assert_true() {
        let entropy = calculate_entropy(12, 64);

        assert_eq!(entropy, 72.0);
    }

    #[test]
    fn calculate_entropy_passed_length_is_0() {
        let entropy = calculate_entropy(0, 64);

        assert_eq!(entropy, 0.0)
    }

    #[test]
    fn calculate_entropy_passed_pool_size_is_0() {
        let entropy = calculate_entropy(12, 0);

        assert_eq!(entropy, f64::NEG_INFINITY)
    }

    #[test]
    fn calculate_entropy_passed_pool_size_is_1() {
        let entropy = calculate_entropy(12, 1);

        assert_eq!(entropy, 0.0)
    }

    #[test]
    fn calculate_length_assert_true() {
        let length = calculate_length(128.0, 64.0);

        assert_eq!(length.ceil(), 22.0);
    }

    #[test]
    fn calculate_length_entropy_is_0() {
        let length = calculate_length(0.0, 64.0);

        assert_eq!(length, 0.0);
    }

    #[test]
    fn calculate_length_pool_size_is_0() {
        let length = calculate_length(128.0, 0.0);

        assert_eq!(length, 0.0);
    }
}