summary refs log tree commit diff
path: root/rand_ish/src/lib.rs
blob: 80022b7ed86cb16581269ba213c2372a4184baab (plain) (blame)
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
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::fs::File;
use std::io::{self, Read};

/// A simple prng based on a Linear congruential generator
/// https://en.wikipedia.org/wiki/Linear_congruential_generator
pub struct SimpleRng {
    seed: u64,
}

impl SimpleRng {
    /// Create a new SimpleRng
    pub fn new(seed: u64) -> SimpleRng {
        SimpleRng { seed }
    }

    /// Generate random u64
    pub fn rng(&mut self) -> u64 {
        // a simple Linear congruential generator
        let a: u64 = 6364136223846793005;
        let c: u64 = 1442695040888963407;
        self.seed = a.wrapping_mul(self.seed).wrapping_add(c);
        self.seed
    }
}

// Uniformly samples the ASCII alphanumeric characters given a random variable. If an `Err` is
// passed in, the error is returned as `Some(Err(...))`. If the the random variable can not be used
// to uniformly sample, `None` is returned.
fn uniform_sample_ascii_alphanumeric(
    b: Result<u8, std::io::Error>,
) -> Option<Result<char, std::io::Error>> {
    const ASCII_CHARS: &'static [u8] = b"\
          ABCDEFGHIJKLMNOPQRSTUVWXYZ\
          abcdefghijklmnopqrstuvwxyz\
          0123456789";
    let char_index = match b {
        Ok(c) => c as usize,
        Err(e) => return Some(Err(e)),
    };
    // Throw away numbers that would cause sampling bias.
    if char_index >= ASCII_CHARS.len() * 4 {
        None
    } else {
        Some(Ok(ASCII_CHARS[char_index % ASCII_CHARS.len()] as char))
    }
}

/// Samples `/dev/urandom` and generates a random ASCII string of length `len`
pub fn urandom_str(len: usize) -> io::Result<String> {
    File::open("/dev/urandom")?
        .bytes()
        .filter_map(uniform_sample_ascii_alphanumeric)
        .take(len)
        .collect::<io::Result<String>>()
}

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

    #[test]
    fn check_100() {
        for i in 0..100 {
            let s = urandom_str(i).unwrap();
            assert_eq!(s.len(), i);
            assert!(s.is_ascii());
            assert!(!s.contains(' '));
            assert!(!s.contains(|c: char| !c.is_ascii_alphanumeric()));
        }
    }
}