devela/sys/os/linux/fns/
rand.rs

1// devela::sys::os::linux::fns::rand
2//
3//!
4//
5// - https://www.man7.org/linux/man-pages/man2/getrandom.2.html
6// - https://www.gnu.org/software/libc/manual/html_node/Unpredictable-Bytes.html
7
8use crate::{
9    c_uint, iif, linux_print, linux_sys_exit, linux_sys_getrandom, paste, LINUX_ERRNO as ERRNO,
10};
11
12// from `sys/random.h`
13const GRND_NONBLOCK: c_uint = 0x0001;
14// const GRND_RANDOM: isize = 0x0002;
15const GRND_INSECURE: c_uint = 0x0004;
16const RAND_FLAGS: c_uint = GRND_NONBLOCK | GRND_INSECURE;
17
18const MAX_ATTEMPTS: usize = 15;
19
20// generates a rand function for each given integer primitive
21macro_rules! random_fns {
22    // $prim: the unsigned integer primitive
23    // $len: the length of the primitive in bytes
24    ($($prim:ident : $len:literal),+) => { paste! { $(
25        #[doc = "Generates a random `" $prim "` value that may not be criptographically secure."]
26        ///
27        /// It makes use of the `GRND_NONBLOCK` and `GRND_INSECURE` flags. So when the randomness
28        /// source is not ready, instead of blocking it may return less secure data in linux >= 5.6
29        /// or retry it a certain number of times, or even return 0 in some cases.
30        pub fn [<linux_random_ $prim>]() -> $prim {
31            let mut r = [0; $len];
32            let mut attempts = 0;
33            loop {
34                let n = unsafe { linux_sys_getrandom(r.as_mut_ptr(), $len, RAND_FLAGS) };
35                if n == $len {
36                    // hot path!
37                    break;
38                } else if n == -ERRNO::EAGAIN {
39                    iif![!getrandom_try_again(&mut attempts); break];
40                } else { // n < 0
41                    getrandom_failed();
42                }
43            }
44            $prim::from_ne_bytes(r)
45        }
46    )+ }};
47}
48random_fns![u8:1, u16:2, u32:4, u64:8, u128:16];
49
50/// Fills the given `buffer` with random bytes that may not be cryptographically secure.
51///
52/// It makes use of the `GRND_NONBLOCK` and `GRND_INSECURE` flags. So when the randomness
53/// source is not ready, instead of blocking it may return less secure data in linux >= 5.6
54/// or retry it a certain number of times, or even return 0 in some cases.
55///
56/// # Panics
57/// Panics in debug if `buffer.len() > `[`isize::MAX`]
58pub fn linux_random_bytes(buffer: &mut [u8]) {
59    debug_assert![buffer.len() <= isize::MAX as usize];
60    let mut attempts = 0;
61    let mut offset = 0;
62    while offset < buffer.len() {
63        let n = unsafe {
64            linux_sys_getrandom(buffer[offset..].as_mut_ptr(), buffer.len() - offset, RAND_FLAGS)
65        };
66        if n == -ERRNO::EAGAIN {
67            iif![!getrandom_try_again(&mut attempts); break];
68        } else if n < 0 {
69            getrandom_failed();
70        } else {
71            // hot path!
72            offset += n as usize;
73        }
74    }
75}
76
77// the cold path for trying again
78#[cold] #[must_use] #[rustfmt::skip]
79fn getrandom_try_again(attempts: &mut usize) -> bool {
80    // if *attempts >= MAX_ATTEMPTS { getrandom_failed(); }
81    *attempts += 1;
82    *attempts <= MAX_ATTEMPTS
83}
84
85// the cold path for some other error
86#[cold] #[rustfmt::skip]
87fn getrandom_failed() {
88    linux_print("getrandom failed");
89    unsafe { linux_sys_exit(12); }
90}