upwd_lib/
lib.rs

1use indexmap::set::Iter;
2use indexmap::IndexSet;
3use rand::Rng;
4use std::char::ParseCharError;
5use std::fmt;
6use std::iter::FromIterator;
7use std::ops::{Deref, DerefMut};
8use std::str::FromStr;
9
10/// Collection of unique chars. This is wrapper for [`IndexSet<char>`]
11#[derive(Debug, Clone, Eq, PartialEq)]
12pub struct Pool(IndexSet<char>);
13
14impl Deref for Pool {
15    type Target = IndexSet<char>;
16
17    fn deref(&self) -> &Self::Target {
18        &self.0
19    }
20}
21
22impl DerefMut for Pool {
23    fn deref_mut(&mut self) -> &mut Self::Target {
24        &mut self.0
25    }
26}
27
28impl FromIterator<char> for Pool {
29    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
30        let mut pool = Pool::new();
31        pool.0 = IndexSet::from_iter(iter);
32
33        pool
34    }
35}
36
37impl Extend<char> for Pool {
38    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
39        self.0.extend(iter)
40    }
41}
42
43impl FromStr for Pool {
44    type Err = ParseCharError;
45
46    fn from_str(s: &str) -> Result<Self, Self::Err> {
47        Ok(Pool(s.chars().collect::<IndexSet<char>>()))
48    }
49}
50
51impl fmt::Display for Pool {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{}", self.0.iter().collect::<String>())
54    }
55}
56
57impl Pool {
58    /// Create new empty pool
59    pub fn new() -> Self {
60        Pool(IndexSet::new())
61    }
62
63    /// Return number of chars in the pool
64    ///
65    /// # Examples
66    /// ```
67    /// # use upwd_lib::Pool;
68    /// let pool: Pool = "0123456789".parse().unwrap();
69    ///
70    /// assert_eq!(pool.len(), 10)
71    /// ```
72    pub fn len(&self) -> usize {
73        self.0.len()
74    }
75
76    /// Extracts all chars from string and adds them to the pool
77    pub fn extend_from_string(&mut self, s: &str) -> &mut Self {
78        self.0.extend(s.chars().collect::<IndexSet<char>>());
79
80        self
81    }
82
83    /// Returns true if pool contains no elements
84    ///
85    /// # Examples
86    /// ```
87    /// # use upwd_lib::Pool;
88    /// let pool = Pool::new();
89    ///
90    /// assert!(pool.is_empty())
91    /// ```
92    pub fn is_empty(&self) -> bool {
93        self.0.is_empty()
94    }
95
96    /// Get char by index
97    pub(crate) fn get(&self, index: usize) -> Option<&char> {
98        self.0.get_index(index)
99    }
100
101    /// Check if char exists in the pool
102    ///
103    /// # Examples
104    /// ```
105    /// # use upwd_lib::Pool;
106    /// let pool: Pool = "ABCDEFG".parse().unwrap();
107    ///
108    /// assert!(pool.contains('D'))
109    /// ```
110    pub fn contains(&self, ch: char) -> bool {
111        self.0.contains(&ch)
112    }
113
114    /// Returns true if pool contains each char from the string `elements`
115    ///
116    /// # Examples
117    /// ```
118    /// # use upwd_lib::Pool;
119    /// let pool: Pool = "ABCDEFG".parse().unwrap();
120    ///
121    /// assert!(pool.contains_all("DAG"))
122    /// ```
123    pub fn contains_all(&self, elements: &str) -> bool {
124        self.0
125            .is_superset(&elements.chars().collect::<IndexSet<char>>())
126    }
127
128    /// Insert char to pool.
129    /// If an equivalent char already exists in the pool, then the pool is not changed.
130    #[allow(dead_code)]
131    pub(crate) fn insert(&mut self, ch: char) {
132        self.0.insert(ch);
133    }
134
135    /// Returns iterator
136    pub fn iter(&self) -> Iter<'_, char> {
137        self.0.iter()
138    }
139
140    /// Remove char from pool. Like a [Vec::swap_remove]
141    pub fn swap_remove(&mut self, ch: &char) -> bool {
142        self.0.swap_remove(ch)
143    }
144
145    /// Remove char from pool. Like a [Vec::remove]
146    pub fn shift_remove(&mut self, ch: &char) -> bool {
147        self.0.shift_remove(ch)
148    }
149
150    /// Remove all chars of the string `elements` from pool
151    pub fn remove_all(&mut self, elements: &str) {
152        elements.chars().for_each(|ch| {
153            self.swap_remove(&ch);
154        });
155    }
156
157    /// Sorts the chars in the pool
158    ///
159    /// # Examples
160    /// ```
161    /// # use upwd_lib::Pool;
162    /// # use std::str::FromStr;
163    /// let mut pool = Pool::from_str("31524").unwrap();
164    /// pool.sort();
165    ///
166    /// assert_eq!(pool, Pool::from_str("12345").unwrap())
167    /// ```
168    pub fn sort(&mut self) {
169        self.0.sort()
170    }
171}
172
173/// Generate random password.
174///
175/// # Examples
176/// ```
177/// # use upwd_lib::{Pool, generate_password};
178/// let pool = "0123456789".parse().unwrap();
179/// let password = generate_password(&pool, 15);
180///
181/// assert_eq!(password.chars().count(), 15);
182/// ```
183///
184/// # Panics
185/// Panics if `pool` is empty.
186pub fn generate_password(pool: &Pool, length: usize) -> String {
187    assert!(!pool.is_empty(), "Pool contains no elements!");
188
189    let mut rng = rand::thread_rng();
190
191    (0..length)
192        .map(|_| {
193            let idx = rng.gen_range(0, pool.len());
194            *pool.get(idx).unwrap()
195        })
196        .collect()
197}
198
199/// Calculates entropy.
200///
201/// # Examples
202/// ```
203/// # use upwd_lib::calculate_entropy;
204///
205/// assert_eq!(calculate_entropy(12, 64), 72_f64);
206/// ```
207pub fn calculate_entropy(length: usize, pool_size: usize) -> f64 {
208    length as f64 * (pool_size as f64).log2()
209}
210
211/// Calculates the minimum password length required to obtain a given entropy.
212///
213/// # Examples
214/// ```
215/// # use upwd_lib::calculate_length;
216///
217/// assert_eq!(calculate_length(128_f64, 64_f64), 22_f64);
218/// ```
219pub fn calculate_length(entropy: f64, pool_size: f64) -> f64 {
220    (entropy / pool_size.log2()).ceil()
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn pool_deref_mut() {
229        let mut pool = Pool::from_str("12345").unwrap();
230        *pool = "abcde".chars().collect::<IndexSet<char>>();
231
232        assert_eq!(*pool, "abcde".chars().collect::<IndexSet<char>>())
233    }
234
235    #[test]
236    fn pool_deref() {
237        let pool = Pool::from_str("12345").unwrap();
238
239        assert_eq!(*pool, "12345".chars().collect::<IndexSet<char>>())
240    }
241
242    #[test]
243    fn pool_sort() {
244        let mut pool = Pool::from_str("31524").unwrap();
245        pool.sort();
246
247        assert_eq!(pool, Pool::from_str("12345").unwrap())
248    }
249
250    #[test]
251    fn pool_extend() {
252        let mut pool = Pool::from_str("abc").unwrap();
253        pool.extend(vec!['d', 'e']);
254
255        assert_eq!(pool, Pool::from_str("abcde").unwrap())
256    }
257
258    #[test]
259    fn pool_from_iter() {
260        let iter = vec!['a', 'b', 'c'].into_iter();
261
262        assert_eq!(iter.collect::<Pool>(), Pool::from_str("abc").unwrap());
263    }
264
265    #[test]
266    fn pool_remove_all() {
267        let mut pool: Pool = "abcde".parse().unwrap();
268        pool.remove_all("ace");
269
270        assert_eq!(pool, "bd".parse::<Pool>().unwrap());
271    }
272
273    #[test]
274    fn pool_swap_remove() {
275        let mut pool: Pool = "abcdefz".parse().unwrap();
276
277        assert!(pool.swap_remove(&'b'));
278        assert_eq!(pool.get(1), Some(&'z'));
279        assert_eq!(pool.get(6), None);
280    }
281
282    #[test]
283    fn pool_shift_remove() {
284        let mut pool: Pool = "abcdefz".parse().unwrap();
285
286        assert!(pool.shift_remove(&'b'));
287        assert_eq!(pool.get(1), Some(&'c'));
288        assert_eq!(pool.get(6), None);
289    }
290
291    #[test]
292    fn pool_iter() {
293        let pool: Pool = "abcdefz".parse().unwrap();
294        let mut iter = pool.iter();
295
296        assert_eq!(iter.next(), Some(&'a'));
297        assert_eq!(iter.next(), Some(&'b'));
298        assert_eq!(iter.last(), Some(&'z'));
299    }
300
301    #[test]
302    fn pool_display() {
303        let pool: Pool = "0123456789".parse().unwrap();
304
305        assert_eq!(pool.to_string(), "0123456789".to_owned());
306    }
307
308    #[test]
309    fn pool_contains_all() {
310        let pool: Pool = "0123456789".parse().unwrap();
311
312        assert!(pool.contains_all("2357"));
313    }
314
315    #[test]
316    fn pool_contains_all_assert_false() {
317        let pool: Pool = "0123456789".parse().unwrap();
318
319        assert!(!pool.contains_all("0123F"));
320    }
321
322    #[test]
323    fn pool_contains() {
324        let pool: Pool = "0123456789".parse().unwrap();
325
326        assert!(pool.contains('5'));
327    }
328
329    #[test]
330    fn pool_contains_assert_false() {
331        let pool: Pool = "0123456789".parse().unwrap();
332
333        assert!(!pool.contains('A'));
334    }
335
336    #[test]
337    fn pool_get() {
338        let pool: Pool = "ABCD".parse().unwrap();
339
340        assert_eq!(pool.get(0), Some(&'A'))
341    }
342
343    #[test]
344    fn pool_is_empty() {
345        let pool = Pool::new();
346
347        assert!(pool.is_empty());
348    }
349
350    #[test]
351    fn pool_is_empty_assert_false() {
352        let pool = Pool::from_str("0123456789").unwrap();
353
354        assert!(!pool.is_empty());
355    }
356
357    #[test]
358    fn pool_len() {
359        let pool: Pool = "0123456789".parse().unwrap();
360
361        assert_eq!(pool.len(), 10)
362    }
363
364    #[test]
365    fn pool_insert() {
366        let mut pool = "ABC".parse::<Pool>().unwrap();
367        pool.insert('D');
368
369        assert_eq!(pool, "ABCD".parse::<Pool>().unwrap())
370    }
371
372    #[test]
373    fn pool_extend_from_string() {
374        let mut pool = "ABC".parse::<Pool>().unwrap();
375        let mut other_pool = pool.clone();
376
377        other_pool.insert('D');
378        pool.extend_from_string("D");
379
380        assert_eq!(other_pool, pool)
381    }
382
383    #[test]
384    fn pool_from_string() {
385        let indexset: IndexSet<_> = "0123456789".chars().collect();
386
387        assert_eq!(Pool(indexset), "0123456789".to_owned().parse().unwrap())
388    }
389
390    #[test]
391    fn pool_from_str() {
392        let indexset: IndexSet<_> = "0123456789".chars().collect();
393
394        assert_eq!(Pool(indexset), "0123456789".parse().unwrap())
395    }
396
397    #[test]
398    fn generate_password_assert_len() {
399        let pool = "0123456789".chars().collect::<IndexSet<char>>();
400        let password = generate_password(&Pool(pool), 15);
401
402        assert_eq!(password.chars().count(), 15);
403    }
404
405    #[test]
406    #[should_panic(expected = "Pool contains no elements!")]
407    fn generate_password_passed_empty_pool() {
408        let pool = "".chars().collect::<IndexSet<char>>();
409
410        generate_password(&Pool(pool), 15);
411    }
412
413    #[test]
414    fn calculate_entropy_assert_true() {
415        let entropy = calculate_entropy(12, 64);
416
417        assert_eq!(entropy, 72_f64);
418    }
419
420    #[test]
421    fn calculate_entropy_passed_length_is_0() {
422        let entropy = calculate_entropy(0, 64);
423
424        assert_eq!(entropy, 0_f64)
425    }
426
427    #[test]
428    fn calculate_entropy_passed_pool_size_is_0() {
429        let entropy = calculate_entropy(12, 0);
430
431        assert_eq!(entropy, f64::NEG_INFINITY)
432    }
433
434    #[test]
435    fn calculate_entropy_passed_pool_size_is_1() {
436        let entropy = calculate_entropy(12, 1);
437
438        assert_eq!(entropy, 0_f64)
439    }
440
441    #[test]
442    fn calculate_length_assert_true() {
443        let length = calculate_length(128_f64, 64_f64);
444
445        assert_eq!(length, 22_f64);
446    }
447
448    #[test]
449    fn calculate_length_entropy_is_0() {
450        let length = calculate_length(0_f64, 64_f64);
451
452        assert_eq!(length, 0_f64);
453    }
454
455    #[test]
456    fn calculate_length_pool_size_is_0() {
457        let length = calculate_length(128_f64, 0_f64);
458
459        assert_eq!(length, 0_f64);
460    }
461
462    #[test]
463    fn calculate_length_entropy_and_pool_size_is_0() {
464        let length = calculate_length(0_f64, 0_f64);
465
466        assert_eq!(length, 0_f64);
467    }
468
469    #[test]
470    fn calculate_length_entropy_is_0_and_pool_size_is_1() {
471        let length = calculate_length(0_f64, 1_f64);
472
473        assert!(length.is_nan());
474    }
475
476    #[test]
477    fn calculate_length_entropy_is_1_and_pool_size_is_1() {
478        let length = calculate_length(1_f64, 1_f64);
479
480        assert_eq!(length, f64::INFINITY);
481    }
482}