tiny_std/unix/
random.rs

1use crate::error::Result;
2use crate::fs::File;
3use crate::io::Read;
4use rusl::error::Errno;
5use rusl::string::unix_str::UnixStr;
6
7const DEV_RANDOM: &UnixStr = UnixStr::from_str_checked("/dev/random\0");
8
9/// Fills the provided buffer with random bytes from /dev/random.
10/// # Errors
11/// File permissions failure
12pub fn system_random(buf: &mut [u8]) -> Result<()> {
13    let mut file = File::open(DEV_RANDOM)?;
14    let mut offset = 0;
15    while offset < buf.len() {
16        match file.read(&mut buf[offset..]) {
17            Ok(read) => {
18                offset += read;
19            }
20            Err(e) => {
21                if e.matches_errno(Errno::EINTR) {
22                    continue;
23                }
24                return Err(e);
25            }
26        }
27    }
28    Ok(())
29}
30
31/// A small, basic [PRNG](https://en.wikipedia.org/wiki/Pseudorandom_number_generator)
32/// implemented as an [LCG](https://en.wikipedia.org/wiki/Linear_congruential_generator).
33/// Should never be used for security-critical things, but can be used as a high-performance
34/// source of pseudo-randomness.
35/// For secure randoms [`system_random`] is more appropriate.
36pub struct Prng {
37    seed: u64,
38}
39
40impl Prng {
41    /// Choosing same constants as glibc here.
42    /// [LCG](https://en.wikipedia.org/wiki/Linear_congruential_generator)
43    /// Yanked my implementation from [tiny-bench, license here](https://github.com/EmbarkStudios/tiny-bench/blob/main/LICENSE-MIT)
44    const MOD: u128 = 2u128.pow(48);
45    const A: u128 = 25_214_903_917;
46    const C: u128 = 11;
47
48    /// Create a new Prng-instance seeding with a [`crate::time::MonotonicInstant`], this is fine because
49    /// you shouldn't need a secure seed anyway, because you should not use this for
50    /// security-purposes at all.
51    #[must_use]
52    #[expect(clippy::cast_sign_loss)]
53    pub fn new_time_seeded() -> Self {
54        let time = crate::time::MonotonicInstant::now();
55        let time_nanos_in_u64 = (time.0.seconds() as u64)
56            .overflowing_add(time.0.nanoseconds() as u64)
57            .0;
58        Prng {
59            seed: time_nanos_in_u64,
60        }
61    }
62
63    /// Create a new prng-instance with the provided seed
64    #[inline]
65    #[must_use]
66    pub fn new(seed: u64) -> Self {
67        Prng {
68            // And maybe check for overflows. Note: No we're good until about year 2554
69            seed,
70        }
71    }
72
73    pub fn next_u64(&mut self) -> u64 {
74        self.seed = ((Self::A * u128::from(self.seed) + Self::C) % Self::MOD) as u64;
75        self.seed
76    }
77}
78
79impl Iterator for Prng {
80    type Item = u64;
81
82    #[inline]
83    fn next(&mut self) -> Option<Self::Item> {
84        Some(self.next_u64())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn gets_random() {
94        let mut buf = [0u8; 4096];
95        system_random(&mut buf).unwrap();
96        // Likely outcome is 16 around zeroes
97        let mut count_zero = 0;
98        for i in buf {
99            if i == 0 {
100                count_zero += 1;
101            }
102        }
103        // Could calculate the actual probability for this.
104        assert!(count_zero < 32, "After filling a buf with random bytes {count_zero} zeroes were found, should be around 16.");
105    }
106
107    #[test]
108    fn gets_pseudo_random() {
109        let mut count_zero = 0;
110        let prng = Prng::new(55);
111        for val in prng.take(4096) {
112            if val == 0 {
113                count_zero += val;
114            }
115        }
116        // Sweet determinism
117        assert_eq!(0, count_zero);
118    }
119
120    #[test]
121    fn prng_seeded_not_same() {
122        let time_seeded1 = Prng::new_time_seeded();
123        let time_seeded2 = Prng::new_time_seeded();
124        assert_ne!(time_seeded1.seed, time_seeded2.seed);
125    }
126}