Coverage Report

Created: 2025-07-11 06:41

/rust/registry/src/index.crates.io-6f17d22bba15001f/rand-0.9.1/src/rngs/reseeding.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 Developers of the Rand project.
2
// Copyright 2013 The Rust Project Developers.
3
//
4
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7
// option. This file may not be copied, modified, or distributed
8
// except according to those terms.
9
10
//! A wrapper around another PRNG that reseeds it after it
11
//! generates a certain number of random bytes.
12
13
use core::mem::size_of_val;
14
15
use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng};
16
use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore};
17
18
/// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the
19
/// ability to reseed it.
20
///
21
/// `ReseedingRng` reseeds the underlying PRNG in the following cases:
22
///
23
/// - On a manual call to [`reseed()`].
24
/// - After `clone()`, the clone will be reseeded on first use.
25
/// - After the PRNG has generated a configurable number of random bytes.
26
///
27
/// # When should reseeding after a fixed number of generated bytes be used?
28
///
29
/// Reseeding after a fixed number of generated bytes is never strictly
30
/// *necessary*. Cryptographic PRNGs don't have a limited number of bytes they
31
/// can output, or at least not a limit reachable in any practical way. There is
32
/// no such thing as 'running out of entropy'.
33
///
34
/// Occasionally reseeding can be seen as some form of 'security in depth'. Even
35
/// if in the future a cryptographic weakness is found in the CSPRNG being used,
36
/// or a flaw in the implementation, occasionally reseeding should make
37
/// exploiting it much more difficult or even impossible.
38
///
39
/// Use [`ReseedingRng::new`] with a `threshold` of `0` to disable reseeding
40
/// after a fixed number of generated bytes.
41
///
42
/// # Error handling
43
///
44
/// Although unlikely, reseeding the wrapped PRNG can fail. `ReseedingRng` will
45
/// never panic but try to handle the error intelligently through some
46
/// combination of retrying and delaying reseeding until later.
47
/// If handling the source error fails `ReseedingRng` will continue generating
48
/// data from the wrapped PRNG without reseeding.
49
///
50
/// Manually calling [`reseed()`] will not have this retry or delay logic, but
51
/// reports the error.
52
///
53
/// # Example
54
///
55
/// ```
56
/// use rand::prelude::*;
57
/// use rand_chacha::ChaCha20Core; // Internal part of ChaChaRng that
58
///                              // implements BlockRngCore
59
/// use rand::rngs::OsRng;
60
/// use rand::rngs::ReseedingRng;
61
///
62
/// let mut reseeding_rng = ReseedingRng::<ChaCha20Core, _>::new(0, OsRng).unwrap();
63
///
64
/// println!("{}", reseeding_rng.random::<u64>());
65
///
66
/// let mut cloned_rng = reseeding_rng.clone();
67
/// assert!(reseeding_rng.random::<u64>() != cloned_rng.random::<u64>());
68
/// ```
69
///
70
/// [`BlockRngCore`]: rand_core::block::BlockRngCore
71
/// [`ReseedingRng::new`]: ReseedingRng::new
72
/// [`reseed()`]: ReseedingRng::reseed
73
#[derive(Debug)]
74
pub struct ReseedingRng<R, Rsdr>(BlockRng<ReseedingCore<R, Rsdr>>)
75
where
76
    R: BlockRngCore + SeedableRng,
77
    Rsdr: TryRngCore;
78
79
impl<R, Rsdr> ReseedingRng<R, Rsdr>
80
where
81
    R: BlockRngCore + SeedableRng,
82
    Rsdr: TryRngCore,
83
{
84
    /// Create a new `ReseedingRng` from an existing PRNG, combined with a RNG
85
    /// to use as reseeder.
86
    ///
87
    /// `threshold` sets the number of generated bytes after which to reseed the
88
    /// PRNG. Set it to zero to never reseed based on the number of generated
89
    /// values.
90
0
    pub fn new(threshold: u64, reseeder: Rsdr) -> Result<Self, Rsdr::Error> {
91
0
        Ok(ReseedingRng(BlockRng::new(ReseedingCore::new(
92
0
            threshold, reseeder,
93
0
        )?)))
94
0
    }
95
96
    /// Immediately reseed the generator
97
    ///
98
    /// This discards any remaining random data in the cache.
99
0
    pub fn reseed(&mut self) -> Result<(), Rsdr::Error> {
100
0
        self.0.reset();
101
0
        self.0.core.reseed()
102
0
    }
103
}
104
105
// TODO: this should be implemented for any type where the inner type
106
// implements RngCore, but we can't specify that because ReseedingCore is private
107
impl<R, Rsdr> RngCore for ReseedingRng<R, Rsdr>
108
where
109
    R: BlockRngCore<Item = u32> + SeedableRng,
110
    Rsdr: TryRngCore,
111
{
112
    #[inline(always)]
113
0
    fn next_u32(&mut self) -> u32 {
114
0
        self.0.next_u32()
115
0
    }
Unexecuted instantiation: <rand::rngs::reseeding::ReseedingRng<rand_chacha::chacha::ChaCha12Core, rand_core::os::OsRng> as rand_core::RngCore>::next_u32
Unexecuted instantiation: <rand::rngs::reseeding::ReseedingRng<_, _> as rand_core::RngCore>::next_u32
116
117
    #[inline(always)]
118
0
    fn next_u64(&mut self) -> u64 {
119
0
        self.0.next_u64()
120
0
    }
121
122
0
    fn fill_bytes(&mut self, dest: &mut [u8]) {
123
0
        self.0.fill_bytes(dest)
124
0
    }
125
}
126
127
impl<R, Rsdr> Clone for ReseedingRng<R, Rsdr>
128
where
129
    R: BlockRngCore + SeedableRng + Clone,
130
    Rsdr: TryRngCore + Clone,
131
{
132
0
    fn clone(&self) -> ReseedingRng<R, Rsdr> {
133
0
        // Recreating `BlockRng` seems easier than cloning it and resetting
134
0
        // the index.
135
0
        ReseedingRng(BlockRng::new(self.0.core.clone()))
136
0
    }
137
}
138
139
impl<R, Rsdr> CryptoRng for ReseedingRng<R, Rsdr>
140
where
141
    R: BlockRngCore<Item = u32> + SeedableRng + CryptoBlockRng,
142
    Rsdr: TryCryptoRng,
143
{
144
}
145
146
#[derive(Debug)]
147
struct ReseedingCore<R, Rsdr> {
148
    inner: R,
149
    reseeder: Rsdr,
150
    threshold: i64,
151
    bytes_until_reseed: i64,
152
}
153
154
impl<R, Rsdr> BlockRngCore for ReseedingCore<R, Rsdr>
155
where
156
    R: BlockRngCore + SeedableRng,
157
    Rsdr: TryRngCore,
158
{
159
    type Item = <R as BlockRngCore>::Item;
160
    type Results = <R as BlockRngCore>::Results;
161
162
0
    fn generate(&mut self, results: &mut Self::Results) {
163
0
        if self.bytes_until_reseed <= 0 {
164
            // We get better performance by not calling only `reseed` here
165
            // and continuing with the rest of the function, but by directly
166
            // returning from a non-inlined function.
167
0
            return self.reseed_and_generate(results);
168
0
        }
169
0
        let num_bytes = size_of_val(results.as_ref());
170
0
        self.bytes_until_reseed -= num_bytes as i64;
171
0
        self.inner.generate(results);
172
0
    }
Unexecuted instantiation: <rand::rngs::reseeding::ReseedingCore<rand_chacha::chacha::ChaCha12Core, rand_core::os::OsRng> as rand_core::block::BlockRngCore>::generate
Unexecuted instantiation: <rand::rngs::reseeding::ReseedingCore<_, _> as rand_core::block::BlockRngCore>::generate
173
}
174
175
impl<R, Rsdr> ReseedingCore<R, Rsdr>
176
where
177
    R: BlockRngCore + SeedableRng,
178
    Rsdr: TryRngCore,
179
{
180
    /// Create a new `ReseedingCore`.
181
    ///
182
    /// `threshold` is the maximum number of bytes produced by
183
    /// [`BlockRngCore::generate`] before attempting reseeding.
184
0
    fn new(threshold: u64, mut reseeder: Rsdr) -> Result<Self, Rsdr::Error> {
185
        // Because generating more values than `i64::MAX` takes centuries on
186
        // current hardware, we just clamp to that value.
187
        // Also we set a threshold of 0, which indicates no limit, to that
188
        // value.
189
0
        let threshold = if threshold == 0 {
190
0
            i64::MAX
191
0
        } else if threshold <= i64::MAX as u64 {
192
0
            threshold as i64
193
        } else {
194
0
            i64::MAX
195
        };
196
197
0
        let inner = R::try_from_rng(&mut reseeder)?;
198
199
0
        Ok(ReseedingCore {
200
0
            inner,
201
0
            reseeder,
202
0
            threshold,
203
0
            bytes_until_reseed: threshold,
204
0
        })
205
0
    }
206
207
    /// Reseed the internal PRNG.
208
0
    fn reseed(&mut self) -> Result<(), Rsdr::Error> {
209
0
        R::try_from_rng(&mut self.reseeder).map(|result| {
210
0
            self.bytes_until_reseed = self.threshold;
211
0
            self.inner = result
212
0
        })
213
0
    }
214
215
    #[inline(never)]
216
0
    fn reseed_and_generate(&mut self, results: &mut <Self as BlockRngCore>::Results) {
217
0
        trace!("Reseeding RNG (periodic reseed)");
218
0
219
0
        let num_bytes = size_of_val(results.as_ref());
220
221
0
        if let Err(e) = self.reseed() {
222
0
            warn!("Reseeding RNG failed: {}", e);
223
0
            let _ = e;
224
0
        }
225
226
0
        self.bytes_until_reseed = self.threshold - num_bytes as i64;
227
0
        self.inner.generate(results);
228
0
    }
Unexecuted instantiation: <rand::rngs::reseeding::ReseedingCore<rand_chacha::chacha::ChaCha12Core, rand_core::os::OsRng>>::reseed_and_generate
Unexecuted instantiation: <rand::rngs::reseeding::ReseedingCore<_, _>>::reseed_and_generate
229
}
230
231
impl<R, Rsdr> Clone for ReseedingCore<R, Rsdr>
232
where
233
    R: BlockRngCore + SeedableRng + Clone,
234
    Rsdr: TryRngCore + Clone,
235
{
236
0
    fn clone(&self) -> ReseedingCore<R, Rsdr> {
237
0
        ReseedingCore {
238
0
            inner: self.inner.clone(),
239
0
            reseeder: self.reseeder.clone(),
240
0
            threshold: self.threshold,
241
0
            bytes_until_reseed: 0, // reseed clone on first use
242
0
        }
243
0
    }
244
}
245
246
impl<R, Rsdr> CryptoBlockRng for ReseedingCore<R, Rsdr>
247
where
248
    R: BlockRngCore<Item = u32> + SeedableRng + CryptoBlockRng,
249
    Rsdr: TryCryptoRng,
250
{
251
}
252
253
#[cfg(feature = "std_rng")]
254
#[cfg(test)]
255
mod test {
256
    use crate::rngs::mock::StepRng;
257
    use crate::rngs::std::Core;
258
    use crate::Rng;
259
260
    use super::ReseedingRng;
261
262
    #[test]
263
    fn test_reseeding() {
264
        let zero = StepRng::new(0, 0);
265
        let thresh = 1; // reseed every time the buffer is exhausted
266
        let mut reseeding = ReseedingRng::<Core, _>::new(thresh, zero).unwrap();
267
268
        // RNG buffer size is [u32; 64]
269
        // Debug is only implemented up to length 32 so use two arrays
270
        let mut buf = ([0u32; 32], [0u32; 32]);
271
        reseeding.fill(&mut buf.0);
272
        reseeding.fill(&mut buf.1);
273
        let seq = buf;
274
        for _ in 0..10 {
275
            reseeding.fill(&mut buf.0);
276
            reseeding.fill(&mut buf.1);
277
            assert_eq!(buf, seq);
278
        }
279
    }
280
281
    #[test]
282
    #[allow(clippy::redundant_clone)]
283
    fn test_clone_reseeding() {
284
        let zero = StepRng::new(0, 0);
285
        let mut rng1 = ReseedingRng::<Core, _>::new(32 * 4, zero).unwrap();
286
287
        let first: u32 = rng1.random();
288
        for _ in 0..10 {
289
            let _ = rng1.random::<u32>();
290
        }
291
292
        let mut rng2 = rng1.clone();
293
        assert_eq!(first, rng2.random::<u32>());
294
    }
295
}