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}