Skip to main content

mod_rand/
tier3.rs

1//! # Tier 3 — OS-backed cryptographic random
2//!
3//! Random values pulled directly from the operating system's secure
4//! random source. Suitable for tokens, API keys, password salts,
5//! session IDs, nonces — anything an attacker would benefit from
6//! predicting.
7//!
8//! ## Source per platform
9//!
10//! | Platform | Source                                      |
11//! |----------|---------------------------------------------|
12//! | Linux    | `getrandom(2)` syscall (via libc symbol)    |
13//! | macOS    | `getentropy(3)` from libSystem              |
14//! | Windows  | `BCryptGenRandom` from `bcrypt.dll`         |
15//! | Other Unix | `/dev/urandom` read                       |
16//!
17//! On Linux, `getrandom` returns `ENOSYS` in two different cases: the
18//! kernel is older than 3.17 and does not have the syscall, or a
19//! sandbox (a seccomp filter, some container runtimes) blocks it and
20//! reports `ENOSYS`. Since 1.1.0 the two are told apart by the kernel
21//! version from `uname(2)`:
22//!
23//! - kernel older than 3.17: the syscall does not exist, and the
24//!   implementation reads `/dev/urandom` instead for the rest of the
25//!   process. On these kernels `/dev/urandom` does not wait for the
26//!   entropy pool to be initialised at early boot.
27//! - kernel 3.17 or newer, or a version that cannot be read: the
28//!   syscall exists and was blocked, so the call returns the `ENOSYS`
29//!   error. It does not switch to another source behind the sandbox's
30//!   back.
31//!
32//! It is never used as a fallback from a *failed* syscall, only from
33//! an *unavailable* one.
34//!
35//! On every platform, if the OS RNG cannot service the request, the
36//! call returns `io::Error`. This module will **never** fall back to
37//! a non-cryptographic source.
38//!
39//! ## Failure modes
40//!
41//! - On sandboxed processes (Linux seccomp, macOS sandbox) where the
42//!   syscall is filtered, you receive `io::Error`, whether the filter
43//!   reports `EPERM` or `ENOSYS`.
44//! - On Linux at very early boot before the entropy pool has been
45//!   seeded, `getrandom` blocks (does not fail). This is the desired
46//!   behaviour — predictable boot-time random is the classic
47//!   real-world weakness this tier prevents.
48//! - On Windows, BCryptGenRandom failures surface the NTSTATUS code
49//!   in the returned `io::Error`.
50//!
51//! ## Thread safety
52//!
53//! All functions in this module are thread-safe. The underlying
54//! syscalls (`getrandom`, `getentropy`, `BCryptGenRandom`) are
55//! documented thread-safe by their respective platforms, and the
56//! Rust-side wrappers hold no shared mutable state.
57//!
58//! ## Performance
59//!
60//! One syscall worth of overhead per call (typically 100 to 500 ns).
61//! The string functions and [`shuffle`] read up to 256 bytes per
62//! syscall and draw every character or swap from that, so a 32
63//! character token costs one syscall. Nothing is kept between calls:
64//! the buffer lives on the stack for one call only, so the module
65//! stays safe across `fork()` and VM snapshots.
66
67use core::ops::{Range, RangeInclusive};
68use std::io;
69
70mod sys {
71    //! Platform-specific entropy primitives.
72    //!
73    //! Each platform module exposes a single `fill(buf)` function that
74    //! either fills the buffer completely or returns `io::Error`.
75
76    use std::io;
77
78    /// Fill `buf` with cryptographically secure random bytes.
79    ///
80    /// Dispatches to the platform-specific implementation.
81    pub fn fill(buf: &mut [u8]) -> io::Result<()> {
82        #[cfg(test)]
83        super::test_counter::bump();
84        platform::fill(buf)
85    }
86
87    #[cfg(target_os = "linux")]
88    mod platform {
89        use std::io;
90        use std::sync::atomic::{AtomicU8, Ordering};
91
92        // getrandom(2) — present in glibc >= 2.25 and musl >= 1.1.20.
93        // The symbol is resolved at link time; on systems lacking it
94        // we'd fail to link, but every supported Rust target ships a
95        // libc new enough to provide it.
96        //
97        // EINTR retry is required because getrandom can be interrupted
98        // by signal delivery when buflen > 256.
99        extern "C" {
100            fn getrandom(buf: *mut u8, buflen: usize, flags: u32) -> isize;
101            fn __errno_location() -> *mut i32;
102            fn uname(buf: *mut UtsName) -> i32;
103        }
104
105        /// `struct utsname` as glibc and musl define it on Linux: six
106        /// NUL-terminated fields of 65 bytes each.
107        #[repr(C)]
108        struct UtsName {
109            sysname: [u8; 65],
110            nodename: [u8; 65],
111            release: [u8; 65],
112            version: [u8; 65],
113            machine: [u8; 65],
114            domainname: [u8; 65],
115        }
116
117        const EINTR: i32 = 4;
118        const ENOSYS: i32 = 38;
119
120        // 0 = unknown, 1 = available, 2 = unavailable (use /dev/urandom).
121        static STATE: AtomicU8 = AtomicU8::new(0);
122
123        fn errno() -> i32 {
124            // SAFETY: __errno_location returns a valid pointer into
125            // thread-local storage in every supported libc. The
126            // pointer remains valid for the lifetime of the thread.
127            unsafe { *__errno_location() }
128        }
129
130        pub fn fill(buf: &mut [u8]) -> io::Result<()> {
131            if STATE.load(Ordering::Relaxed) == 2 {
132                return urandom_fallback(buf);
133            }
134
135            let mut pos = 0;
136            while pos < buf.len() {
137                // SAFETY: buf is a valid &mut [u8]; the pointer and
138                // length describe the unfilled tail. flags = 0 selects
139                // blocking, /dev/urandom-style behaviour.
140                let r = unsafe { getrandom(buf.as_mut_ptr().add(pos), buf.len() - pos, 0) };
141                if r > 0 {
142                    pos += r as usize;
143                    continue;
144                }
145                let e = errno();
146                if e == EINTR {
147                    continue;
148                }
149                if e == ENOSYS {
150                    if !kernel_lacks_getrandom() {
151                        // The kernel has the syscall, so something
152                        // (a seccomp filter, a sandbox) blocked it.
153                        // Report that instead of switching sources.
154                        return Err(io::Error::from_raw_os_error(e));
155                    }
156                    STATE.store(2, Ordering::Relaxed);
157                    // Re-attempt the whole fill via /dev/urandom from
158                    // the start of the unfilled region.
159                    return urandom_fallback(&mut buf[pos..]);
160                }
161                return Err(io::Error::from_raw_os_error(e));
162            }
163            STATE.store(1, Ordering::Relaxed);
164            Ok(())
165        }
166
167        fn urandom_fallback(buf: &mut [u8]) -> io::Result<()> {
168            use std::fs::File;
169            use std::io::Read;
170            let mut f = File::open("/dev/urandom")?;
171            f.read_exact(buf)
172        }
173
174        /// True only when the running kernel is known to predate
175        /// `getrandom(2)` (added in Linux 3.17). An unreadable version
176        /// counts as "has it", so `ENOSYS` is then reported as an error.
177        fn kernel_lacks_getrandom() -> bool {
178            let mut uts = UtsName {
179                sysname: [0; 65],
180                nodename: [0; 65],
181                release: [0; 65],
182                version: [0; 65],
183                machine: [0; 65],
184                domainname: [0; 65],
185            };
186            // SAFETY: `uts` is a valid, writable `struct utsname` with
187            // the layout glibc and musl use on Linux.
188            if unsafe { uname(&mut uts) } != 0 {
189                return false;
190            }
191            match super::super::parse_kernel_version(&uts.release) {
192                Some(v) => v < (3, 17),
193                None => false,
194            }
195        }
196    }
197
198    #[cfg(target_os = "macos")]
199    mod platform {
200        use std::io;
201
202        // getentropy(3) — present in macOS 10.12+ via libSystem.
203        // Capped at 256 bytes per call.
204        extern "C" {
205            fn getentropy(buf: *mut u8, buflen: usize) -> i32;
206            fn __error() -> *mut i32;
207        }
208
209        const EINTR: i32 = 4;
210        const MAX_PER_CALL: usize = 256;
211
212        fn errno() -> i32 {
213            // SAFETY: __error returns a pointer into thread-local
214            // storage; valid for the thread's lifetime.
215            unsafe { *__error() }
216        }
217
218        pub fn fill(buf: &mut [u8]) -> io::Result<()> {
219            for chunk in buf.chunks_mut(MAX_PER_CALL) {
220                loop {
221                    // SAFETY: chunk is a valid &mut [u8] of length
222                    // <= MAX_PER_CALL, satisfying getentropy's
223                    // contract.
224                    let r = unsafe { getentropy(chunk.as_mut_ptr(), chunk.len()) };
225                    if r == 0 {
226                        break;
227                    }
228                    let e = errno();
229                    if e == EINTR {
230                        continue;
231                    }
232                    return Err(io::Error::from_raw_os_error(e));
233                }
234            }
235            Ok(())
236        }
237    }
238
239    #[cfg(target_os = "windows")]
240    mod platform {
241        use std::io;
242
243        // BCryptGenRandom from bcrypt.dll. With a NULL algorithm
244        // handle plus BCRYPT_USE_SYSTEM_PREFERRED_RNG, the call uses
245        // the system's preferred CSPRNG without any caller setup. The
246        // call is documented thread-safe and reentrant.
247        //
248        // BCRYPT_USE_SYSTEM_PREFERRED_RNG is the only flag that
249        // permits a NULL algorithm handle. Available since
250        // Windows Vista / Server 2008.
251        #[link(name = "bcrypt")]
252        extern "system" {
253            fn BCryptGenRandom(
254                hAlgorithm: *mut core::ffi::c_void,
255                pbBuffer: *mut u8,
256                cbBuffer: u32,
257                dwFlags: u32,
258            ) -> i32;
259        }
260
261        const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x0000_0002;
262        // STATUS_SUCCESS — every other NTSTATUS is an error in this API.
263        const STATUS_SUCCESS: i32 = 0;
264
265        pub fn fill(buf: &mut [u8]) -> io::Result<()> {
266            // cbBuffer is u32; if `buf` somehow exceeds u32::MAX, chunk it.
267            // In practice no caller approaches this limit; we handle it
268            // for safety rather than out of expected need.
269            for chunk in buf.chunks_mut(u32::MAX as usize) {
270                // SAFETY: chunk is a valid &mut [u8]; cbBuffer fits in
271                // u32 by construction.
272                let status = unsafe {
273                    BCryptGenRandom(
274                        core::ptr::null_mut(),
275                        chunk.as_mut_ptr(),
276                        chunk.len() as u32,
277                        BCRYPT_USE_SYSTEM_PREFERRED_RNG,
278                    )
279                };
280                if status != STATUS_SUCCESS {
281                    return Err(io::Error::other(format!(
282                        "BCryptGenRandom failed: NTSTATUS 0x{:08X}",
283                        status as u32
284                    )));
285                }
286            }
287            Ok(())
288        }
289    }
290
291    // Other Unix-like targets (FreeBSD, OpenBSD, NetBSD, illumos,
292    // etc.): /dev/urandom is universally available and is a
293    // cryptographic source on every one of these. Not a "fallback"
294    // from a stronger primitive — it IS the platform primitive here.
295    #[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
296    mod platform {
297        use std::fs::File;
298        use std::io::{self, Read};
299
300        pub fn fill(buf: &mut [u8]) -> io::Result<()> {
301            let mut f = File::open("/dev/urandom")?;
302            f.read_exact(buf)
303        }
304    }
305
306    #[cfg(not(any(unix, target_os = "windows")))]
307    mod platform {
308        use std::io;
309
310        pub fn fill(_buf: &mut [u8]) -> io::Result<()> {
311            Err(io::Error::new(
312                io::ErrorKind::Unsupported,
313                "mod-rand tier3 has no entropy source on this platform",
314            ))
315        }
316    }
317}
318
319/// Parse the `major.minor` prefix of a NUL-terminated kernel release
320/// string such as `5.15.0-91-generic`.
321#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
322fn parse_kernel_version(release: &[u8]) -> Option<(u32, u32)> {
323    let end = release
324        .iter()
325        .position(|&b| b == 0)
326        .unwrap_or(release.len());
327    let s = core::str::from_utf8(&release[..end]).ok()?;
328    let mut parts = s.split(|c: char| !c.is_ascii_digit());
329    let major = parts.next()?.parse().ok()?;
330    let minor = parts.next()?.parse().ok()?;
331    Some((major, minor))
332}
333
334/// Number of OS reads made on this thread, for tests of batching.
335#[cfg(test)]
336mod test_counter {
337    use std::cell::Cell;
338
339    thread_local! {
340        static CALLS: Cell<usize> = const { Cell::new(0) };
341    }
342
343    pub(super) fn bump() {
344        CALLS.with(|c| c.set(c.get() + 1));
345    }
346
347    pub(super) fn take() -> usize {
348        CALLS.with(|c| c.replace(0))
349    }
350}
351
352/// Random `u64` words read from the OS in batches, for one call.
353///
354/// Reads at most 32 words per syscall, and only as many as
355/// the caller said it needs (plus a refill on the rare rejection).
356/// Lives on the stack for one public call; nothing is kept afterwards.
357const WORDS_MAX: usize = 32;
358
359struct Words {
360    buf: [u8; WORDS_MAX * 8],
361    pos: usize,
362    len: usize,
363    remaining: usize,
364}
365
366impl Words {
367    /// A source for about `needed` words.
368    fn new(needed: usize) -> Self {
369        Self {
370            buf: [0; WORDS_MAX * 8],
371            pos: 0,
372            len: 0,
373            remaining: needed,
374        }
375    }
376
377    fn next(&mut self) -> io::Result<u64> {
378        if self.pos == self.len {
379            let words = self.remaining.clamp(1, WORDS_MAX);
380            let bytes = words * 8;
381            fill_bytes(&mut self.buf[..bytes])?;
382            self.pos = 0;
383            self.len = bytes;
384        }
385        let mut w = [0u8; 8];
386        w.copy_from_slice(&self.buf[self.pos..self.pos + 8]);
387        self.buf[self.pos..self.pos + 8].fill(0);
388        self.pos += 8;
389        self.remaining = self.remaining.saturating_sub(1);
390        Ok(u64::from_le_bytes(w))
391    }
392
393    /// Uniform value in `[0, n)` by Lemire's method. `n > 0`.
394    fn bounded(&mut self, n: u64) -> io::Result<u64> {
395        debug_assert!(n != 0, "Words::bounded requires n > 0");
396        let mut m: u128 = (self.next()? as u128).wrapping_mul(n as u128);
397        let mut l: u64 = m as u64;
398        if l < n {
399            let t: u64 = n.wrapping_neg() % n;
400            while l < t {
401                m = (self.next()? as u128).wrapping_mul(n as u128);
402                l = m as u64;
403            }
404        }
405        Ok((m >> 64) as u64)
406    }
407}
408
409/// Fill the given buffer with cryptographically secure random bytes.
410///
411/// Returns `Ok(())` only if the entire buffer was filled from the
412/// platform's secure random source. Returns `io::Error` if the OS
413/// random source is unavailable (sandbox, seccomp filter, missing
414/// device) — never falls back to a weaker source.
415///
416/// An empty buffer succeeds without making any syscall.
417///
418/// # Example
419///
420/// ```
421/// use mod_rand::tier3;
422///
423/// let mut buf = [0u8; 32];
424/// tier3::fill_bytes(&mut buf).unwrap();
425/// ```
426pub fn fill_bytes(buf: &mut [u8]) -> io::Result<()> {
427    if buf.is_empty() {
428        return Ok(());
429    }
430    sys::fill(buf)
431}
432
433/// Return a cryptographically secure random `u64`.
434///
435/// Convenience wrapper around [`fill_bytes`]. Uses little-endian byte
436/// order; consumers should not rely on this — for randomness, byte
437/// order is immaterial.
438///
439/// # Example
440///
441/// ```
442/// use mod_rand::tier3;
443///
444/// let n: u64 = tier3::random_u64().unwrap();
445/// # let _ = n;
446/// ```
447pub fn random_u64() -> io::Result<u64> {
448    let mut buf = [0u8; 8];
449    fill_bytes(&mut buf)?;
450    Ok(u64::from_le_bytes(buf))
451}
452
453/// Return a cryptographically secure random `u32`.
454///
455/// # Example
456///
457/// ```
458/// use mod_rand::tier3;
459///
460/// let n: u32 = tier3::random_u32().unwrap();
461/// # let _ = n;
462/// ```
463pub fn random_u32() -> io::Result<u32> {
464    let mut buf = [0u8; 4];
465    fill_bytes(&mut buf)?;
466    Ok(u32::from_le_bytes(buf))
467}
468
469/// Return a `Vec<u8>` of cryptographically secure random bytes.
470///
471/// Convenience for callers who don't already have a buffer.
472///
473/// # Example
474///
475/// ```
476/// use mod_rand::tier3;
477///
478/// let bytes = tier3::random_bytes(32).unwrap();
479/// assert_eq!(bytes.len(), 32);
480/// ```
481pub fn random_bytes(len: usize) -> io::Result<Vec<u8>> {
482    let mut v = vec![0u8; len];
483    fill_bytes(&mut v)?;
484    Ok(v)
485}
486
487/// Return a hex-encoded cryptographically secure random token.
488///
489/// `bytes` is the number of raw random bytes drawn; the resulting
490/// string is exactly `bytes * 2` lowercase hex characters long.
491///
492/// Common sizes:
493/// - 16 bytes (32 hex chars) — session tokens
494/// - 32 bytes (64 hex chars) — API keys, password reset tokens
495/// - 64 bytes (128 hex chars) — long-lived secrets
496///
497/// # Example
498///
499/// ```
500/// use mod_rand::tier3;
501///
502/// let token = tier3::random_hex(16).unwrap();
503/// assert_eq!(token.len(), 32);
504/// assert!(token.chars().all(|c| c.is_ascii_hexdigit()));
505/// ```
506pub fn random_hex(bytes: usize) -> io::Result<String> {
507    const HEX: &[u8; 16] = b"0123456789abcdef";
508    let mut buf = vec![0u8; bytes];
509    fill_bytes(&mut buf)?;
510    let mut out = String::with_capacity(bytes * 2);
511    for b in buf {
512        out.push(HEX[(b >> 4) as usize] as char);
513        out.push(HEX[(b & 0xF) as usize] as char);
514    }
515    Ok(out)
516}
517
518/// Return a Crockford base32-encoded cryptographically secure random
519/// token of exactly `chars` characters.
520///
521/// Each character contributes 5 bits of entropy; the function draws
522/// `ceil(chars * 5 / 8)` random bytes and encodes them. Suitable for
523/// case-insensitive secrets and filesystem-safe identifiers.
524///
525/// # Example
526///
527/// ```
528/// use mod_rand::tier3;
529///
530/// let s = tier3::random_base32(24).unwrap();
531/// assert_eq!(s.len(), 24);
532/// ```
533pub fn random_base32(chars: usize) -> io::Result<String> {
534    const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
535    let byte_count = (chars * 5).div_ceil(8);
536    let mut buf = vec![0u8; byte_count.max(1)];
537    fill_bytes(&mut buf)?;
538    let mut out = String::with_capacity(chars);
539    let mut acc: u64 = 0;
540    let mut bits: u32 = 0;
541    let mut idx = 0;
542    while out.len() < chars {
543        if bits < 5 {
544            acc |= (buf[idx] as u64) << bits;
545            bits += 8;
546            idx += 1;
547            if idx == buf.len() && bits < 5 && out.len() < chars {
548                // Should not happen with our byte_count math; guard
549                // defensively rather than panic.
550                let mut extra = [0u8; 8];
551                fill_bytes(&mut extra)?;
552                for &b in &extra {
553                    acc |= (b as u64) << bits;
554                    bits += 8;
555                    if bits >= 5 + 56 {
556                        break;
557                    }
558                }
559            }
560        }
561        out.push(ALPHABET[(acc & 0x1F) as usize] as char);
562        acc >>= 5;
563        bits -= 5;
564    }
565    Ok(out)
566}
567
568// ------------------------------------------------------------
569// Bounded-range API
570//
571// Every bounded function below pulls a fresh `u64` from the OS CSPRNG
572// and reduces it with Lemire's "Nearly Divisionless" rejection
573// sampling. The result is uniformly distributed over the requested
574// range with no modulo bias.
575//
576// Each rejected draw is a fresh syscall (or a fresh `/dev/urandom`
577// read on the Linux ENOSYS fallback path). The rejection rate is
578// approximately `n / 2^64` per draw, so for any range smaller than
579// half the u64 space, the expected syscall count per call is
580// effectively 1.
581//
582// Invalid ranges (empty / reversed) return
583// `io::Error` with `ErrorKind::InvalidInput` rather than panicking,
584// matching the rest of the Tier 3 fallible API.
585// ------------------------------------------------------------
586
587/// Produce a uniformly-distributed `u64` in `[0, n)`.
588///
589/// Internal helper. `n` MUST be greater than zero; the public wrappers
590/// enforce this. Returns `io::Error` only if the underlying
591/// `random_u64()` call fails.
592#[inline]
593fn bounded_u64(n: u64) -> io::Result<u64> {
594    debug_assert!(n != 0, "bounded_u64 requires n > 0");
595    let mut x = random_u64()?;
596    let mut m: u128 = (x as u128).wrapping_mul(n as u128);
597    let mut l: u64 = m as u64;
598    if l < n {
599        let t: u64 = n.wrapping_neg() % n;
600        while l < t {
601            x = random_u64()?;
602            m = (x as u128).wrapping_mul(n as u128);
603            l = m as u64;
604        }
605    }
606    Ok((m >> 64) as u64)
607}
608
609/// Helper: build an `InvalidInput` error for empty ranges.
610#[inline]
611fn empty_range_error(msg: &'static str) -> io::Error {
612    io::Error::new(io::ErrorKind::InvalidInput, msg)
613}
614
615/// Generate a cryptographically-secure uniformly-distributed `u64` in
616/// the half-open range `[range.start, range.end)`.
617///
618/// Uses Lemire's "Nearly Divisionless" rejection sampling so the
619/// output is genuinely uniform — there is no modulo bias.
620///
621/// # Errors
622///
623/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
624///   is empty (`range.start >= range.end`).
625/// - Returns `io::Error` if the OS CSPRNG is unavailable.
626///
627/// # Example
628///
629/// ```
630/// use mod_rand::tier3;
631///
632/// let n = tier3::random_range_u64(10..20)?;
633/// assert!((10..20).contains(&n));
634/// # Ok::<(), std::io::Error>(())
635/// ```
636pub fn random_range_u64(range: Range<u64>) -> io::Result<u64> {
637    let Range { start, end } = range;
638    if start >= end {
639        return Err(empty_range_error("random_range_u64: empty range"));
640    }
641    let span = end - start;
642    Ok(start + bounded_u64(span)?)
643}
644
645/// Generate a cryptographically-secure uniformly-distributed `u64` in
646/// the closed range `[range.start(), range.end()]`.
647///
648/// The full-width inclusive range `0..=u64::MAX` is supported and is
649/// equivalent to a raw `random_u64()` call.
650///
651/// # Errors
652///
653/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
654///   is empty (`*range.start() > *range.end()`).
655/// - Returns `io::Error` if the OS CSPRNG is unavailable.
656///
657/// # Example
658///
659/// ```
660/// use mod_rand::tier3;
661///
662/// let d = tier3::random_range_inclusive_u64(1..=6)?;
663/// assert!((1..=6).contains(&d));
664/// # Ok::<(), std::io::Error>(())
665/// ```
666pub fn random_range_inclusive_u64(range: RangeInclusive<u64>) -> io::Result<u64> {
667    let (start, end) = range.into_inner();
668    if start > end {
669        return Err(empty_range_error("random_range_inclusive_u64: empty range"));
670    }
671    if start == 0 && end == u64::MAX {
672        return random_u64();
673    }
674    let span = end - start + 1;
675    Ok(start + bounded_u64(span)?)
676}
677
678/// Generate a cryptographically-secure uniformly-distributed `u32` in
679/// the half-open range `[range.start, range.end)`.
680///
681/// # Errors
682///
683/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
684///   is empty.
685/// - Returns `io::Error` if the OS CSPRNG is unavailable.
686///
687/// # Example
688///
689/// ```
690/// use mod_rand::tier3;
691///
692/// let pct = tier3::random_range_u32(0..100)?;
693/// assert!(pct < 100);
694/// # Ok::<(), std::io::Error>(())
695/// ```
696pub fn random_range_u32(range: Range<u32>) -> io::Result<u32> {
697    let Range { start, end } = range;
698    if start >= end {
699        return Err(empty_range_error("random_range_u32: empty range"));
700    }
701    let span = (end - start) as u64;
702    Ok((start as u64 + bounded_u64(span)?) as u32)
703}
704
705/// Generate a cryptographically-secure uniformly-distributed `u32` in
706/// the closed range `[range.start(), range.end()]`.
707///
708/// The full-width inclusive range `0..=u32::MAX` is supported.
709///
710/// # Errors
711///
712/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
713///   is empty.
714/// - Returns `io::Error` if the OS CSPRNG is unavailable.
715pub fn random_range_inclusive_u32(range: RangeInclusive<u32>) -> io::Result<u32> {
716    let (start, end) = range.into_inner();
717    if start > end {
718        return Err(empty_range_error("random_range_inclusive_u32: empty range"));
719    }
720    let span = (end as u64) - (start as u64) + 1;
721    Ok((start as u64 + bounded_u64(span)?) as u32)
722}
723
724/// Generate a cryptographically-secure uniformly-distributed `i64` in
725/// the half-open range `[range.start, range.end)`.
726///
727/// Negative bounds and mixed-sign ranges are supported.
728///
729/// # Errors
730///
731/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
732///   is empty.
733/// - Returns `io::Error` if the OS CSPRNG is unavailable.
734///
735/// # Example
736///
737/// ```
738/// use mod_rand::tier3;
739///
740/// let n = tier3::random_range_i64(-50..50)?;
741/// assert!((-50..50).contains(&n));
742/// # Ok::<(), std::io::Error>(())
743/// ```
744pub fn random_range_i64(range: Range<i64>) -> io::Result<i64> {
745    let Range { start, end } = range;
746    if start >= end {
747        return Err(empty_range_error("random_range_i64: empty range"));
748    }
749    let span = (end as i128 - start as i128) as u64;
750    let offset = bounded_u64(span)?;
751    Ok(((start as i128) + (offset as i128)) as i64)
752}
753
754/// Generate a cryptographically-secure uniformly-distributed `i64` in
755/// the closed range `[range.start(), range.end()]`.
756///
757/// The full-width inclusive range `i64::MIN..=i64::MAX` is supported
758/// and is equivalent to reinterpreting a raw `random_u64()` draw as
759/// `i64`.
760///
761/// # Errors
762///
763/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
764///   is empty.
765/// - Returns `io::Error` if the OS CSPRNG is unavailable.
766pub fn random_range_inclusive_i64(range: RangeInclusive<i64>) -> io::Result<i64> {
767    let (start, end) = range.into_inner();
768    if start > end {
769        return Err(empty_range_error("random_range_inclusive_i64: empty range"));
770    }
771    if start == i64::MIN && end == i64::MAX {
772        return random_u64().map(|u| u as i64);
773    }
774    let span = ((end as i128) - (start as i128) + 1) as u64;
775    let offset = bounded_u64(span)?;
776    Ok(((start as i128) + (offset as i128)) as i64)
777}
778
779/// Generate a cryptographically-secure uniformly-distributed `i32` in
780/// the half-open range `[range.start, range.end)`.
781///
782/// # Errors
783///
784/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
785///   is empty.
786/// - Returns `io::Error` if the OS CSPRNG is unavailable.
787pub fn random_range_i32(range: Range<i32>) -> io::Result<i32> {
788    let Range { start, end } = range;
789    if start >= end {
790        return Err(empty_range_error("random_range_i32: empty range"));
791    }
792    let span = (end as i64 - start as i64) as u64;
793    let offset = bounded_u64(span)?;
794    Ok(((start as i64) + (offset as i64)) as i32)
795}
796
797/// Generate a cryptographically-secure uniformly-distributed `i32` in
798/// the closed range `[range.start(), range.end()]`.
799///
800/// The full-width inclusive range `i32::MIN..=i32::MAX` is supported.
801///
802/// # Errors
803///
804/// - Returns `io::Error` with `ErrorKind::InvalidInput` if the range
805///   is empty.
806/// - Returns `io::Error` if the OS CSPRNG is unavailable.
807pub fn random_range_inclusive_i32(range: RangeInclusive<i32>) -> io::Result<i32> {
808    let (start, end) = range.into_inner();
809    if start > end {
810        return Err(empty_range_error("random_range_inclusive_i32: empty range"));
811    }
812    let span = ((end as i64) - (start as i64) + 1) as u64;
813    let offset = bounded_u64(span)?;
814    Ok(((start as i64) + (offset as i64)) as i32)
815}
816
817// ------------------------------------------------------------
818// Bounded-range API — additional integer widths
819// ------------------------------------------------------------
820
821/// Produce a uniformly-distributed `u128` in `[0, n)`.
822///
823/// Internal helper. Lemire's "Nearly Divisionless" rejection sampling
824/// generalised to 128-bit width via a 256-bit intermediate. Two
825/// `random_u64` calls (two syscalls) per draw in the common case.
826#[inline]
827fn bounded_u128(n: u128) -> io::Result<u128> {
828    debug_assert!(n != 0, "bounded_u128 requires n > 0");
829    let mut x = draw_u128()?;
830    let (mut hi, mut lo) = mul_128_full(x, n);
831    if lo < n {
832        let t: u128 = n.wrapping_neg() % n;
833        while lo < t {
834            x = draw_u128()?;
835            let (h, l) = mul_128_full(x, n);
836            hi = h;
837            lo = l;
838        }
839    }
840    let _ = x;
841    Ok(hi)
842}
843
844#[inline]
845fn draw_u128() -> io::Result<u128> {
846    let mut buf = [0u8; 16];
847    fill_bytes(&mut buf)?;
848    Ok(u128::from_le_bytes(buf))
849}
850
851#[inline]
852fn mul_128_full(a: u128, b: u128) -> (u128, u128) {
853    let a_lo: u64 = a as u64;
854    let a_hi: u64 = (a >> 64) as u64;
855    let b_lo: u64 = b as u64;
856    let b_hi: u64 = (b >> 64) as u64;
857    let p00: u128 = (a_lo as u128) * (b_lo as u128);
858    let p01: u128 = (a_lo as u128) * (b_hi as u128);
859    let p10: u128 = (a_hi as u128) * (b_lo as u128);
860    let p11: u128 = (a_hi as u128) * (b_hi as u128);
861    let mask_lo: u128 = 0xFFFF_FFFF_FFFF_FFFF;
862    let mid: u128 = (p00 >> 64) + (p01 & mask_lo) + (p10 & mask_lo);
863    let low: u128 = (p00 & mask_lo) | (mid << 64);
864    let high: u128 = p11 + (p01 >> 64) + (p10 >> 64) + (mid >> 64);
865    (high, low)
866}
867
868/// Generate a cryptographically-secure uniformly-distributed `u8` in
869/// the half-open range `[range.start, range.end)`.
870///
871/// # Errors
872///
873/// - Returns `InvalidInput` if the range is empty.
874/// - Returns `io::Error` if the OS CSPRNG is unavailable.
875///
876/// # Example
877///
878/// ```
879/// use mod_rand::tier3;
880/// let n = tier3::random_range_u8(0..100)?;
881/// assert!(n < 100);
882/// # Ok::<(), std::io::Error>(())
883/// ```
884pub fn random_range_u8(range: Range<u8>) -> io::Result<u8> {
885    let Range { start, end } = range;
886    if start >= end {
887        return Err(empty_range_error("random_range_u8: empty range"));
888    }
889    let span = (end - start) as u64;
890    Ok((start as u64 + bounded_u64(span)?) as u8)
891}
892
893/// Generate a cryptographically-secure uniformly-distributed `u8` in
894/// the closed range `[range.start(), range.end()]`. The full-width
895/// `0..=u8::MAX` is supported.
896///
897/// # Errors
898///
899/// As for [`random_range_u8`].
900pub fn random_range_inclusive_u8(range: RangeInclusive<u8>) -> io::Result<u8> {
901    let (start, end) = range.into_inner();
902    if start > end {
903        return Err(empty_range_error("random_range_inclusive_u8: empty range"));
904    }
905    let span = (end as u64) - (start as u64) + 1;
906    Ok((start as u64 + bounded_u64(span)?) as u8)
907}
908
909/// Generate a cryptographically-secure uniformly-distributed `u16` in
910/// the half-open range `[range.start, range.end)`.
911///
912/// # Errors
913///
914/// As for [`random_range_u8`].
915pub fn random_range_u16(range: Range<u16>) -> io::Result<u16> {
916    let Range { start, end } = range;
917    if start >= end {
918        return Err(empty_range_error("random_range_u16: empty range"));
919    }
920    let span = (end - start) as u64;
921    Ok((start as u64 + bounded_u64(span)?) as u16)
922}
923
924/// Generate a cryptographically-secure uniformly-distributed `u16` in
925/// the closed range `[range.start(), range.end()]`. The full-width
926/// `0..=u16::MAX` is supported.
927///
928/// # Errors
929///
930/// As for [`random_range_u8`].
931pub fn random_range_inclusive_u16(range: RangeInclusive<u16>) -> io::Result<u16> {
932    let (start, end) = range.into_inner();
933    if start > end {
934        return Err(empty_range_error("random_range_inclusive_u16: empty range"));
935    }
936    let span = (end as u64) - (start as u64) + 1;
937    Ok((start as u64 + bounded_u64(span)?) as u16)
938}
939
940/// Generate a cryptographically-secure uniformly-distributed `u128` in
941/// the half-open range `[range.start, range.end)`.
942///
943/// Uses two `random_u64` calls per draw in the common case. Lemire
944/// rejection sampling at 128-bit width via a 256-bit intermediate.
945///
946/// # Errors
947///
948/// As for [`random_range_u8`].
949pub fn random_range_u128(range: Range<u128>) -> io::Result<u128> {
950    let Range { start, end } = range;
951    if start >= end {
952        return Err(empty_range_error("random_range_u128: empty range"));
953    }
954    let span = end - start;
955    Ok(start + bounded_u128(span)?)
956}
957
958/// Generate a cryptographically-secure uniformly-distributed `u128` in
959/// the closed range `[range.start(), range.end()]`. The full-width
960/// `0..=u128::MAX` is supported.
961///
962/// # Errors
963///
964/// As for [`random_range_u8`].
965pub fn random_range_inclusive_u128(range: RangeInclusive<u128>) -> io::Result<u128> {
966    let (start, end) = range.into_inner();
967    if start > end {
968        return Err(empty_range_error(
969            "random_range_inclusive_u128: empty range",
970        ));
971    }
972    if start == 0 && end == u128::MAX {
973        return draw_u128();
974    }
975    let span = end - start + 1;
976    Ok(start + bounded_u128(span)?)
977}
978
979/// Generate a cryptographically-secure uniformly-distributed `usize`
980/// in the half-open range `[range.start, range.end)`.
981///
982/// # Errors
983///
984/// As for [`random_range_u8`].
985pub fn random_range_usize(range: Range<usize>) -> io::Result<usize> {
986    let Range { start, end } = range;
987    if start >= end {
988        return Err(empty_range_error("random_range_usize: empty range"));
989    }
990    let span = (end - start) as u64;
991    Ok((start as u64 + bounded_u64(span)?) as usize)
992}
993
994/// Generate a cryptographically-secure uniformly-distributed `usize`
995/// in the closed range `[range.start(), range.end()]`. The full-width
996/// `0..=usize::MAX` is supported.
997///
998/// # Errors
999///
1000/// As for [`random_range_u8`].
1001pub fn random_range_inclusive_usize(range: RangeInclusive<usize>) -> io::Result<usize> {
1002    let (start, end) = range.into_inner();
1003    if start > end {
1004        return Err(empty_range_error(
1005            "random_range_inclusive_usize: empty range",
1006        ));
1007    }
1008    if start == 0 && end == usize::MAX {
1009        #[cfg(target_pointer_width = "64")]
1010        {
1011            return random_u64().map(|u| u as usize);
1012        }
1013        #[cfg(not(target_pointer_width = "64"))]
1014        {
1015            let span = (end as u64) - (start as u64) + 1;
1016            return bounded_u64(span).map(|o| (start as u64 + o) as usize);
1017        }
1018    }
1019    let span = (end - start) as u64 + 1;
1020    Ok((start as u64 + bounded_u64(span)?) as usize)
1021}
1022
1023/// Generate a cryptographically-secure uniformly-distributed `i8` in
1024/// the half-open range `[range.start, range.end)`.
1025///
1026/// # Errors
1027///
1028/// As for [`random_range_u8`].
1029pub fn random_range_i8(range: Range<i8>) -> io::Result<i8> {
1030    let Range { start, end } = range;
1031    if start >= end {
1032        return Err(empty_range_error("random_range_i8: empty range"));
1033    }
1034    let span = (end as i64 - start as i64) as u64;
1035    let offset = bounded_u64(span)?;
1036    Ok(((start as i64) + (offset as i64)) as i8)
1037}
1038
1039/// Generate a cryptographically-secure uniformly-distributed `i8` in
1040/// the closed range `[range.start(), range.end()]`. The full-width
1041/// `i8::MIN..=i8::MAX` is supported.
1042///
1043/// # Errors
1044///
1045/// As for [`random_range_u8`].
1046pub fn random_range_inclusive_i8(range: RangeInclusive<i8>) -> io::Result<i8> {
1047    let (start, end) = range.into_inner();
1048    if start > end {
1049        return Err(empty_range_error("random_range_inclusive_i8: empty range"));
1050    }
1051    let span = ((end as i64) - (start as i64) + 1) as u64;
1052    let offset = bounded_u64(span)?;
1053    Ok(((start as i64) + (offset as i64)) as i8)
1054}
1055
1056/// Generate a cryptographically-secure uniformly-distributed `i16` in
1057/// the half-open range `[range.start, range.end)`.
1058///
1059/// # Errors
1060///
1061/// As for [`random_range_u8`].
1062pub fn random_range_i16(range: Range<i16>) -> io::Result<i16> {
1063    let Range { start, end } = range;
1064    if start >= end {
1065        return Err(empty_range_error("random_range_i16: empty range"));
1066    }
1067    let span = (end as i64 - start as i64) as u64;
1068    let offset = bounded_u64(span)?;
1069    Ok(((start as i64) + (offset as i64)) as i16)
1070}
1071
1072/// Generate a cryptographically-secure uniformly-distributed `i16` in
1073/// the closed range `[range.start(), range.end()]`. The full-width
1074/// `i16::MIN..=i16::MAX` is supported.
1075///
1076/// # Errors
1077///
1078/// As for [`random_range_u8`].
1079pub fn random_range_inclusive_i16(range: RangeInclusive<i16>) -> io::Result<i16> {
1080    let (start, end) = range.into_inner();
1081    if start > end {
1082        return Err(empty_range_error("random_range_inclusive_i16: empty range"));
1083    }
1084    let span = ((end as i64) - (start as i64) + 1) as u64;
1085    let offset = bounded_u64(span)?;
1086    Ok(((start as i64) + (offset as i64)) as i16)
1087}
1088
1089/// Generate a cryptographically-secure uniformly-distributed `i128` in
1090/// the half-open range `[range.start, range.end)`.
1091///
1092/// # Errors
1093///
1094/// As for [`random_range_u8`].
1095pub fn random_range_i128(range: Range<i128>) -> io::Result<i128> {
1096    let Range { start, end } = range;
1097    if start >= end {
1098        return Err(empty_range_error("random_range_i128: empty range"));
1099    }
1100    let span = (end as u128).wrapping_sub(start as u128);
1101    let offset = bounded_u128(span)?;
1102    Ok((start as u128).wrapping_add(offset) as i128)
1103}
1104
1105/// Generate a cryptographically-secure uniformly-distributed `i128` in
1106/// the closed range `[range.start(), range.end()]`. The full-width
1107/// `i128::MIN..=i128::MAX` is supported.
1108///
1109/// # Errors
1110///
1111/// As for [`random_range_u8`].
1112pub fn random_range_inclusive_i128(range: RangeInclusive<i128>) -> io::Result<i128> {
1113    let (start, end) = range.into_inner();
1114    if start > end {
1115        return Err(empty_range_error(
1116            "random_range_inclusive_i128: empty range",
1117        ));
1118    }
1119    if start == i128::MIN && end == i128::MAX {
1120        return draw_u128().map(|u| u as i128);
1121    }
1122    let span = (end as u128).wrapping_sub(start as u128) + 1;
1123    let offset = bounded_u128(span)?;
1124    Ok((start as u128).wrapping_add(offset) as i128)
1125}
1126
1127/// Generate a cryptographically-secure uniformly-distributed `isize`
1128/// in the half-open range `[range.start, range.end)`.
1129///
1130/// # Errors
1131///
1132/// As for [`random_range_u8`].
1133pub fn random_range_isize(range: Range<isize>) -> io::Result<isize> {
1134    let Range { start, end } = range;
1135    if start >= end {
1136        return Err(empty_range_error("random_range_isize: empty range"));
1137    }
1138    let span = (end as i128 - start as i128) as u64;
1139    let offset = bounded_u64(span)?;
1140    Ok(((start as i128) + (offset as i128)) as isize)
1141}
1142
1143/// Generate a cryptographically-secure uniformly-distributed `isize`
1144/// in the closed range `[range.start(), range.end()]`. The full-width
1145/// `isize::MIN..=isize::MAX` is supported.
1146///
1147/// # Errors
1148///
1149/// As for [`random_range_u8`].
1150pub fn random_range_inclusive_isize(range: RangeInclusive<isize>) -> io::Result<isize> {
1151    let (start, end) = range.into_inner();
1152    if start > end {
1153        return Err(empty_range_error(
1154            "random_range_inclusive_isize: empty range",
1155        ));
1156    }
1157    if start == isize::MIN && end == isize::MAX {
1158        #[cfg(target_pointer_width = "64")]
1159        {
1160            return random_u64().map(|u| u as isize);
1161        }
1162        #[cfg(not(target_pointer_width = "64"))]
1163        {
1164            let span = ((end as i128) - (start as i128) + 1) as u64;
1165            let offset = bounded_u64(span)?;
1166            return Ok(((start as i128) + (offset as i128)) as isize);
1167        }
1168    }
1169    let span = ((end as i128) - (start as i128) + 1) as u64;
1170    let offset = bounded_u64(span)?;
1171    Ok(((start as i128) + (offset as i128)) as isize)
1172}
1173
1174// ------------------------------------------------------------
1175// String generation
1176// ------------------------------------------------------------
1177
1178/// Helper: build an `InvalidInput` error for charset-validation
1179/// failures.
1180#[inline]
1181fn invalid_charset_error(msg: &'static str) -> io::Error {
1182    io::Error::new(io::ErrorKind::InvalidInput, msg)
1183}
1184
1185/// Generate a cryptographically-secure random string of exactly `len`
1186/// characters drawn from `charset` (uniformly).
1187///
1188/// Selection uses Lemire rejection sampling against `charset.len()`,
1189/// so the per-character distribution is uniform — no modulo bias.
1190///
1191/// Random words are read from the OS up to 256 bytes per syscall, so
1192/// a string of up to 32 characters usually costs one syscall.
1193///
1194/// # Errors
1195///
1196/// - Returns `InvalidInput` if `charset` is empty or contains a
1197///   non-ASCII byte (byte >= 128).
1198/// - Returns `io::Error` if the OS CSPRNG is unavailable.
1199///
1200/// # Example
1201///
1202/// ```
1203/// use mod_rand::{tier3, charsets};
1204/// let s = tier3::random_string(16, charsets::ALPHANUMERIC)?;
1205/// assert_eq!(s.len(), 16);
1206/// # Ok::<(), std::io::Error>(())
1207/// ```
1208pub fn random_string(len: usize, charset: &[u8]) -> io::Result<String> {
1209    if charset.is_empty() {
1210        return Err(invalid_charset_error(
1211            "random_string: charset must be non-empty",
1212        ));
1213    }
1214    if !charset.iter().all(|&b| b < 128) {
1215        return Err(invalid_charset_error(
1216            "random_string: charset must be ASCII (every byte < 128)",
1217        ));
1218    }
1219    let n = charset.len() as u64;
1220    let mut out = String::with_capacity(len);
1221    let mut words = Words::new(len);
1222    for _ in 0..len {
1223        let idx = words.bounded(n)? as usize;
1224        out.push(charset[idx] as char);
1225    }
1226    Ok(out)
1227}
1228
1229/// Generate a cryptographically-secure ASCII alphanumeric string
1230/// (`A-Z`, `a-z`, `0-9`) of exactly `len` characters.
1231///
1232/// Equivalent to `random_string(len, charsets::ALPHANUMERIC)`.
1233///
1234/// # Example
1235///
1236/// ```
1237/// use mod_rand::tier3;
1238/// let token = tier3::random_alphanumeric(32)?;
1239/// assert_eq!(token.len(), 32);
1240/// # Ok::<(), std::io::Error>(())
1241/// ```
1242#[inline]
1243pub fn random_alphanumeric(len: usize) -> io::Result<String> {
1244    random_string(len, crate::charsets::ALPHANUMERIC)
1245}
1246
1247/// Generate a cryptographically-secure ASCII alphabetic string
1248/// (`A-Z`, `a-z`) of exactly `len` characters.
1249///
1250/// # Example
1251///
1252/// ```
1253/// use mod_rand::tier3;
1254/// let s = tier3::random_alpha(12)?;
1255/// assert_eq!(s.len(), 12);
1256/// # Ok::<(), std::io::Error>(())
1257/// ```
1258#[inline]
1259pub fn random_alpha(len: usize) -> io::Result<String> {
1260    random_string(len, crate::charsets::ALPHA)
1261}
1262
1263/// Generate a cryptographically-secure ASCII numeric string (`0-9`)
1264/// of exactly `len` characters. Leading zeros may appear.
1265///
1266/// # Example
1267///
1268/// ```
1269/// use mod_rand::tier3;
1270/// let s = tier3::random_numeric(6)?;
1271/// assert_eq!(s.len(), 6);
1272/// # Ok::<(), std::io::Error>(())
1273/// ```
1274#[inline]
1275pub fn random_numeric(len: usize) -> io::Result<String> {
1276    random_string(len, crate::charsets::NUMERIC)
1277}
1278
1279/// Generate a cryptographically-secure lowercase hex string (`0-9`,
1280/// `a-f`) of exactly `len` characters.
1281///
1282/// **Distinct from [`random_hex`]**: [`random_hex`] takes a byte
1283/// count and returns `bytes * 2` hex characters; this function takes
1284/// a character count directly. Both produce identical entropy per
1285/// character but accept different argument units.
1286///
1287/// # Example
1288///
1289/// ```
1290/// use mod_rand::tier3;
1291/// let s = tier3::random_hex_string(40)?;
1292/// assert_eq!(s.len(), 40);
1293/// # Ok::<(), std::io::Error>(())
1294/// ```
1295#[inline]
1296pub fn random_hex_string(len: usize) -> io::Result<String> {
1297    random_string(len, crate::charsets::HEX_LOWER)
1298}
1299
1300// ------------------------------------------------------------
1301// Collection operations — cryptographic shuffle
1302// ------------------------------------------------------------
1303
1304/// In-place cryptographically-secure uniform Fisher-Yates shuffle.
1305///
1306/// Every permutation of `slice` is equally likely under the OS
1307/// CSPRNG. O(n) time, no allocation. Each swap consumes one
1308/// `random_u64` worth of entropy from Tier 3; the words are read up to
1309/// 32 at a time, so a 52-card deck costs two syscalls.
1310///
1311/// # Errors
1312///
1313/// Returns `io::Error` if the OS CSPRNG is unavailable. If the call
1314/// fails mid-shuffle, the slice is left in a partially-shuffled state
1315/// but still contains exactly its original elements.
1316///
1317/// # Example
1318///
1319/// ```
1320/// use mod_rand::tier3;
1321/// let mut deck: Vec<u8> = (0..52).collect();
1322/// tier3::shuffle(&mut deck)?;
1323/// // deck is now a uniformly-random permutation.
1324/// # Ok::<(), std::io::Error>(())
1325/// ```
1326pub fn shuffle<T>(slice: &mut [T]) -> io::Result<()> {
1327    let n = slice.len();
1328    if n < 2 {
1329        return Ok(());
1330    }
1331    let mut words = Words::new(n - 1);
1332    for i in (1..n).rev() {
1333        // Pick j uniformly in [0, i]. i fits in u64 because n <= usize::MAX
1334        // and usize <= u64 on every supported target.
1335        let span = (i as u64) + 1;
1336        let j = words.bounded(span)? as usize;
1337        slice.swap(i, j);
1338    }
1339    Ok(())
1340}
1341
1342#[cfg(test)]
1343mod tests {
1344    use super::*;
1345    use std::collections::HashSet;
1346
1347    #[test]
1348    fn strings_and_shuffle_batch_os_reads() {
1349        let _ = test_counter::take();
1350        let t = random_alphanumeric(32).unwrap();
1351        assert_eq!(t.len(), 32);
1352        assert_eq!(test_counter::take(), 1, "32 characters, one OS read");
1353
1354        let t = random_string(100, crate::charsets::HEX_LOWER).unwrap();
1355        assert_eq!(t.len(), 100);
1356        assert_eq!(test_counter::take(), 4, "100 characters, four OS reads");
1357
1358        let mut deck: Vec<u8> = (0..52).collect();
1359        shuffle(&mut deck).unwrap();
1360        assert_eq!(test_counter::take(), 2, "51 swaps, two OS reads");
1361        let mut sorted = deck.clone();
1362        sorted.sort_unstable();
1363        assert_eq!(sorted, (0..52).collect::<Vec<u8>>());
1364    }
1365
1366    #[test]
1367    fn words_bounded_is_in_range_and_uniform_enough() {
1368        let mut w = Words::new(60_000);
1369        let mut counts = [0u32; 6];
1370        for _ in 0..60_000 {
1371            let v = w.bounded(6).unwrap();
1372            counts[v as usize] += 1;
1373        }
1374        for c in counts {
1375            assert!((9_000..=11_000).contains(&c), "count {c} out of band");
1376        }
1377    }
1378
1379    #[test]
1380    fn kernel_version_parsing() {
1381        assert_eq!(
1382            parse_kernel_version(b"5.15.0-91-generic\0\0"),
1383            Some((5, 15))
1384        );
1385        assert_eq!(
1386            parse_kernel_version(b"5.15.167.4-microsoft-standard-WSL2\0"),
1387            Some((5, 15))
1388        );
1389        assert_eq!(parse_kernel_version(b"3.16.0\0"), Some((3, 16)));
1390        assert_eq!(parse_kernel_version(b"3.17\0"), Some((3, 17)));
1391        assert_eq!(parse_kernel_version(b"6.1"), Some((6, 1)));
1392        assert_eq!(parse_kernel_version(b"\0"), None);
1393        assert_eq!(parse_kernel_version(b"linux\0"), None);
1394        assert!(parse_kernel_version(b"3.16.0\0").unwrap() < (3, 17));
1395        assert!(parse_kernel_version(b"4.4.0\0").unwrap() >= (3, 17));
1396    }
1397
1398    #[test]
1399    fn fill_bytes_produces_output() {
1400        let mut buf = [0u8; 32];
1401        fill_bytes(&mut buf).unwrap();
1402        assert!(buf.iter().any(|&b| b != 0));
1403    }
1404
1405    #[test]
1406    fn empty_buffer_succeeds_without_syscall() {
1407        let mut buf: [u8; 0] = [];
1408        fill_bytes(&mut buf).unwrap();
1409    }
1410
1411    #[test]
1412    fn random_u64_varies_within_small_sample() {
1413        let mut seen = HashSet::with_capacity(16);
1414        for _ in 0..16 {
1415            seen.insert(random_u64().unwrap());
1416        }
1417        assert!(seen.len() > 1, "u64 output appears constant in sample");
1418    }
1419
1420    #[test]
1421    fn random_u32_varies_within_small_sample() {
1422        let mut seen = HashSet::with_capacity(32);
1423        for _ in 0..32 {
1424            seen.insert(random_u32().unwrap());
1425        }
1426        assert!(seen.len() > 1, "u32 output appears constant in sample");
1427    }
1428
1429    #[test]
1430    fn random_hex_correct_length_and_alphabet() {
1431        let h = random_hex(16).unwrap();
1432        assert_eq!(h.len(), 32);
1433        assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
1434        assert!(h.chars().all(|c| !c.is_ascii_uppercase()));
1435    }
1436
1437    #[test]
1438    fn random_hex_zero_length() {
1439        let h = random_hex(0).unwrap();
1440        assert_eq!(h, "");
1441    }
1442
1443    #[test]
1444    fn random_base32_correct_length_and_alphabet() {
1445        const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1446        for len in [1, 5, 8, 16, 24, 32, 64] {
1447            let s = random_base32(len).unwrap();
1448            assert_eq!(s.len(), len, "length {len}");
1449            assert!(
1450                s.bytes().all(|b| ALPHABET.contains(&b)),
1451                "alphabet violation in {s}"
1452            );
1453        }
1454    }
1455
1456    #[test]
1457    fn random_bytes_is_correct_length() {
1458        let b = random_bytes(48).unwrap();
1459        assert_eq!(b.len(), 48);
1460    }
1461
1462    #[test]
1463    fn large_buffer_fill_succeeds() {
1464        // A buffer larger than macOS's 256-byte getentropy cap and
1465        // larger than typical syscall short-read thresholds. Verifies
1466        // the looping/chunking logic on every platform.
1467        let mut buf = vec![0u8; 4096];
1468        fill_bytes(&mut buf).unwrap();
1469        // The probability all 4096 bytes are zero is 2^-32768 — any
1470        // observation of that is a bug.
1471        assert!(buf.iter().any(|&b| b != 0));
1472    }
1473
1474    #[test]
1475    fn stress_many_small_calls() {
1476        // 1000 calls of 32 bytes each. Exercises syscall stability.
1477        for _ in 0..1000 {
1478            let mut buf = [0u8; 32];
1479            fill_bytes(&mut buf).unwrap();
1480        }
1481    }
1482
1483    #[test]
1484    fn byte_frequency_chi_squared() {
1485        // 1 048 576 bytes — 256 buckets, expected ~4096 per bucket.
1486        // Chi-squared critical value (255 d.f., alpha = 0.001) is
1487        // about 330. We use 500 to keep flake rate negligible.
1488        let mut buf = vec![0u8; 1 << 20];
1489        fill_bytes(&mut buf).unwrap();
1490
1491        let mut counts = [0u32; 256];
1492        for &b in &buf {
1493            counts[b as usize] += 1;
1494        }
1495        let n = buf.len() as f64;
1496        let expected = n / 256.0;
1497        let chi: f64 = counts
1498            .iter()
1499            .map(|&c| {
1500                let diff = c as f64 - expected;
1501                diff * diff / expected
1502            })
1503            .sum();
1504        assert!(chi < 500.0, "byte-frequency chi-squared {chi} too high");
1505    }
1506
1507    // ------------------------------------------------------------
1508    // Bounded-range tests
1509    // ------------------------------------------------------------
1510
1511    #[test]
1512    fn random_range_u64_bounds() {
1513        for _ in 0..1000 {
1514            let n = random_range_u64(100..200).unwrap();
1515            assert!((100..200).contains(&n));
1516        }
1517    }
1518
1519    #[test]
1520    fn random_range_u64_single_value_window() {
1521        // [start, start+1) — every draw lands on start. No syscalls
1522        // wasted on rejection in this case (span=1, l=0 is never < n).
1523        for _ in 0..100 {
1524            assert_eq!(random_range_u64(7..8).unwrap(), 7);
1525        }
1526    }
1527
1528    #[test]
1529    fn random_range_inclusive_u64_die_roll_visits_all_faces() {
1530        // 1000 cryptographic die rolls — all six faces must appear.
1531        let mut faces = [0u32; 6];
1532        for _ in 0..1000 {
1533            let d = random_range_inclusive_u64(1..=6).unwrap();
1534            assert!((1..=6).contains(&d));
1535            faces[(d - 1) as usize] += 1;
1536        }
1537        for (i, &c) in faces.iter().enumerate() {
1538            assert!(c > 0, "face {} never appeared in 1000 rolls", i + 1);
1539        }
1540    }
1541
1542    #[test]
1543    fn random_range_inclusive_u64_single_value() {
1544        for _ in 0..100 {
1545            assert_eq!(random_range_inclusive_u64(42..=42).unwrap(), 42);
1546        }
1547    }
1548
1549    #[test]
1550    fn random_range_inclusive_u64_full_width() {
1551        // 0..=u64::MAX — full-width path. Two draws differ.
1552        let a = random_range_inclusive_u64(0..=u64::MAX).unwrap();
1553        let b = random_range_inclusive_u64(0..=u64::MAX).unwrap();
1554        assert_ne!(a, b);
1555    }
1556
1557    #[test]
1558    fn random_range_u32_bounds() {
1559        for _ in 0..1000 {
1560            let n = random_range_u32(0..256).unwrap();
1561            assert!(n < 256);
1562        }
1563    }
1564
1565    #[test]
1566    fn random_range_inclusive_u32_full_width() {
1567        for _ in 0..100 {
1568            let _ = random_range_inclusive_u32(0..=u32::MAX).unwrap();
1569        }
1570    }
1571
1572    #[test]
1573    fn random_range_i64_negative() {
1574        for _ in 0..1000 {
1575            let n = random_range_i64(-100..-50).unwrap();
1576            assert!((-100..-50).contains(&n));
1577        }
1578    }
1579
1580    #[test]
1581    fn random_range_i64_mixed_sign() {
1582        let mut saw_neg = false;
1583        let mut saw_pos = false;
1584        for _ in 0..1000 {
1585            let n = random_range_i64(-100..100).unwrap();
1586            assert!((-100..100).contains(&n));
1587            if n < 0 {
1588                saw_neg = true;
1589            }
1590            if n >= 0 {
1591                saw_pos = true;
1592            }
1593        }
1594        assert!(saw_neg && saw_pos);
1595    }
1596
1597    #[test]
1598    fn random_range_inclusive_i64_full_width() {
1599        let _ = random_range_inclusive_i64(i64::MIN..=i64::MAX).unwrap();
1600    }
1601
1602    #[test]
1603    fn random_range_i32_bounds() {
1604        for _ in 0..1000 {
1605            let n = random_range_i32(-1000..1000).unwrap();
1606            assert!((-1000..1000).contains(&n));
1607        }
1608    }
1609
1610    #[test]
1611    fn random_range_inclusive_i32_full_width() {
1612        for _ in 0..100 {
1613            let _ = random_range_inclusive_i32(i32::MIN..=i32::MAX).unwrap();
1614        }
1615    }
1616
1617    #[test]
1618    fn random_range_u64_empty_returns_invalid_input() {
1619        let err = random_range_u64(10..10).unwrap_err();
1620        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1621    }
1622
1623    #[test]
1624    #[allow(clippy::reversed_empty_ranges)]
1625    fn random_range_u64_reverse_returns_invalid_input() {
1626        let err = random_range_u64(10..5).unwrap_err();
1627        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1628    }
1629
1630    #[test]
1631    #[allow(clippy::reversed_empty_ranges)]
1632    fn random_range_inclusive_u64_reverse_returns_invalid_input() {
1633        let err = random_range_inclusive_u64(10..=5).unwrap_err();
1634        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1635    }
1636
1637    #[test]
1638    #[allow(clippy::reversed_empty_ranges)]
1639    fn random_range_i64_reverse_returns_invalid_input() {
1640        let err = random_range_i64(5..-5).unwrap_err();
1641        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1642    }
1643
1644    #[test]
1645    fn random_range_uniformity_chi_squared() {
1646        // 50 000 cryptographic draws over 50 buckets. Expected count
1647        // per bucket: 1000. Chi-squared critical value (49 d.f.,
1648        // alpha=0.001) is ~85; we use 200 to keep flake rate
1649        // negligible. This is fewer draws than tier1/tier2 because
1650        // each draw is a syscall.
1651        let mut counts = [0u32; 50];
1652        for _ in 0..50_000 {
1653            let v = random_range_u32(0..50).unwrap();
1654            counts[v as usize] += 1;
1655        }
1656        let expected = 50_000.0 / 50.0;
1657        let chi: f64 = counts
1658            .iter()
1659            .map(|&c| {
1660                let diff = c as f64 - expected;
1661                diff * diff / expected
1662            })
1663            .sum();
1664        assert!(
1665            chi < 200.0,
1666            "tier3 chi-squared {chi} too high — bounded-range output is biased"
1667        );
1668    }
1669}