Skip to main content

nros_platform_api/
xorshift32.rs

1//! Xorshift32 PRNG helpers shared by RTOS platform crates that lack a
2//! hardware RNG.
3//!
4//! The state is owned by the caller (typically a `static mut` inside the
5//! platform crate), so multiple platforms can share these helpers without
6//! the API crate carrying mutable state.
7//!
8//! Quality is sufficient for zenoh session-ID seeding — not for
9//! cryptography. Pair with hardware entropy at boot (clock, MAC address,
10//! ADC noise) via `seed()`.
11
12/// Default seed used when entropy is unavailable. Xorshift cannot escape
13/// an all-zero state, so callers that pass `0` to [`seed`] fall back to
14/// this value.
15pub const DEFAULT_SEED: u32 = 0x12345678;
16
17/// One xorshift32 step. Pure function — caller manages the state cell.
18#[inline]
19pub const fn step(state: u32) -> u32 {
20    let mut x = state;
21    x ^= x << 13;
22    x ^= x >> 17;
23    x ^= x << 5;
24    x
25}
26
27/// Advance `state` by one step and return the new value.
28///
29/// # Safety
30/// `state` must be a valid, exclusively-owned `*mut u32`. Single-threaded
31/// RTOS init code typically guarantees this; SMP callers must wrap in a
32/// critical section.
33#[inline]
34pub unsafe fn next(state: *mut u32) -> u32 {
35    let next = step(unsafe { *state });
36    unsafe { *state = next };
37    next
38}
39
40/// Replace `state` with `value`, falling back to [`DEFAULT_SEED`] if
41/// `value == 0`.
42///
43/// # Safety
44/// `state` must be a valid, exclusively-owned `*mut u32`.
45#[inline]
46pub unsafe fn seed(state: *mut u32, value: u32) {
47    unsafe { *state = if value == 0 { DEFAULT_SEED } else { value } };
48}
49
50/// Fill `buf[..len]` with bytes derived from xorshift32 output. No-op if
51/// `buf` is null.
52///
53/// # Safety
54/// `buf` must be valid for `len` writes. `state` must be exclusively
55/// owned.
56pub unsafe fn random_fill(state: *mut u32, buf: *mut u8, len: usize) {
57    if buf.is_null() {
58        return;
59    }
60    let mut offset = 0;
61    let mut remaining = len;
62    while remaining >= 4 {
63        let bytes = unsafe { next(state) }.to_ne_bytes();
64        unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf.add(offset), 4) };
65        offset += 4;
66        remaining -= 4;
67    }
68    if remaining > 0 {
69        let bytes = unsafe { next(state) }.to_ne_bytes();
70        unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf.add(offset), remaining) };
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn step_is_deterministic() {
80        assert_eq!(step(DEFAULT_SEED), step(DEFAULT_SEED));
81    }
82
83    #[test]
84    fn step_changes_state() {
85        assert_ne!(step(DEFAULT_SEED), DEFAULT_SEED);
86    }
87
88    #[test]
89    fn seed_zero_falls_back_to_default() {
90        let mut s: u32 = 1;
91        unsafe { seed(&raw mut s, 0) };
92        assert_eq!(s, DEFAULT_SEED);
93    }
94
95    #[test]
96    fn seed_nonzero_takes_value() {
97        let mut s: u32 = 1;
98        unsafe { seed(&raw mut s, 0xDEADBEEF) };
99        assert_eq!(s, 0xDEADBEEF);
100    }
101
102    #[test]
103    fn next_advances_state() {
104        let mut s: u32 = DEFAULT_SEED;
105        let v = unsafe { next(&raw mut s) };
106        assert_eq!(v, step(DEFAULT_SEED));
107        assert_eq!(s, v);
108    }
109
110    #[test]
111    fn random_fill_null_buf_is_noop() {
112        let mut s: u32 = DEFAULT_SEED;
113        unsafe { random_fill(&raw mut s, core::ptr::null_mut(), 16) };
114        assert_eq!(s, DEFAULT_SEED);
115    }
116
117    #[test]
118    fn random_fill_writes_requested_length() {
119        let mut s: u32 = DEFAULT_SEED;
120        let mut buf = [0u8; 13];
121        unsafe { random_fill(&raw mut s, buf.as_mut_ptr(), buf.len()) };
122        assert!(buf.iter().any(|&b| b != 0));
123    }
124}