Skip to main content

bitwarden_random/
seeded.rs

1//! Deterministic, seeded RNG support for tests/benches.
2
3use rand::SeedableRng;
4
5thread_local! {
6    /// When `Some`, [`crate::rng`] returns a handle that advances this single stream across all
7    /// `rng()` calls on the thread (keeping IVs/nonces unique while reproducible).
8    static SEEDED_STREAM: core::cell::RefCell<Option<rand_chacha::ChaCha8Rng>> =
9        const { core::cell::RefCell::new(None) };
10}
11
12/// Sets up a deterministic, seeded ChaCha8 stream for the current thread.
13/// Where possible, the higher level [`rng_set_scene`] should be used instead.
14pub fn set_seed_raw(seed: [u8; 32]) {
15    SEEDED_STREAM.with(|c| *c.borrow_mut() = Some(rand_chacha::ChaCha8Rng::from_seed(seed)));
16}
17
18/// A named, reproducible RNG scene for tests. Each variant maps to a fixed seed.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum Scene {
21    /// Deterministic scene A.
22    SceneA,
23    /// Deterministic scene B.
24    SceneB,
25    /// Deterministic scene C.
26    SceneC,
27}
28
29/// Seeds the current thread's RNG with the deterministic seed for the given [`Scene`].
30///
31/// The scene is mapped to a number which is used as the seed (via [`set_seed_raw`]).
32pub fn rng_set_scene(scene: Scene) {
33    set_seed_raw([scene as u8; 32]);
34}
35
36/// Removes any thread-local seed, reverting [`crate::rng`] to the OS entropy source on this thread.
37pub fn clear_seed() {
38    SEEDED_STREAM.with(|c| *c.borrow_mut() = None);
39}
40
41pub(crate) fn is_seeded() -> bool {
42    SEEDED_STREAM.with(|c| c.borrow().is_some())
43}
44
45pub(crate) fn next_u32() -> u32 {
46    SEEDED_STREAM
47        .with(|c| rand::Rng::next_u32(c.borrow_mut().as_mut().expect("seed set for this thread")))
48}
49
50pub(crate) fn next_u64() -> u64 {
51    SEEDED_STREAM
52        .with(|c| rand::Rng::next_u64(c.borrow_mut().as_mut().expect("seed set for this thread")))
53}
54
55pub(crate) fn fill_bytes(dst: &mut [u8]) {
56    SEEDED_STREAM.with(|c| {
57        rand::Rng::fill_bytes(
58            c.borrow_mut().as_mut().expect("seed set for this thread"),
59            dst,
60        )
61    });
62}
63
64#[cfg(test)]
65mod tests {
66    use rand::RngExt;
67
68    use super::*;
69    use crate::rng;
70
71    #[test]
72    fn set_seed_makes_rng_calls_deterministic_and_advancing() {
73        // Two separate `rng()` calls draw from the SAME advancing stream — distinct values...
74        set_seed_raw([7u8; 32]);
75        let a: [u8; 16] = rng().random();
76        let b: [u8; 16] = rng().random();
77        assert_ne!(a, b, "successive rng() must not repeat (no IV/nonce reuse)");
78
79        // ...and re-seeding reproduces the exact sequence.
80        set_seed_raw([7u8; 32]);
81        let a2: [u8; 16] = rng().random();
82        let b2: [u8; 16] = rng().random();
83        assert_eq!((a, b), (a2, b2));
84
85        clear_seed();
86    }
87
88    #[test]
89    fn rng_set_scene_is_deterministic_and_distinct_per_scene() {
90        rng_set_scene(Scene::SceneA);
91        let a1: [u8; 16] = rng().random();
92        rng_set_scene(Scene::SceneA);
93        let a2: [u8; 16] = rng().random();
94        assert_eq!(a1, a2, "same scene must reproduce the same stream");
95
96        rng_set_scene(Scene::SceneB);
97        let b: [u8; 16] = rng().random();
98        assert_ne!(a1, b, "different scenes must use different seeds");
99
100        clear_seed();
101    }
102}