oxicrypto_rand/
reseeding.rs1use oxicrypto_core::{CryptoError, Rng};
5
6use crate::OxiRng;
7
8const DEFAULT_RESEED_THRESHOLD: u64 = 1 << 20;
10
11pub struct ReseedingRng {
22 inner: OxiRng,
23 bytes_generated: u64,
24 reseed_threshold: u64,
25}
26
27impl ReseedingRng {
28 pub fn new() -> Result<Self, CryptoError> {
30 Self::with_threshold(DEFAULT_RESEED_THRESHOLD)
31 }
32
33 pub fn with_threshold(threshold: u64) -> Result<Self, CryptoError> {
38 Ok(Self {
39 inner: OxiRng::new()?,
40 bytes_generated: 0,
41 reseed_threshold: threshold,
42 })
43 }
44
45 pub fn bytes_generated(&self) -> u64 {
47 self.bytes_generated
48 }
49
50 pub fn reseed_threshold(&self) -> u64 {
52 self.reseed_threshold
53 }
54
55 pub fn reseed(&mut self) -> Result<(), CryptoError> {
57 self.inner.reseed()?;
58 self.bytes_generated = 0;
59 Ok(())
60 }
61
62 fn maybe_reseed(&mut self) -> Result<(), CryptoError> {
64 if self.bytes_generated >= self.reseed_threshold {
65 self.inner.reseed()?;
66 self.bytes_generated = 0;
67 }
68 Ok(())
69 }
70}
71
72impl Rng for ReseedingRng {
73 fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
74 self.maybe_reseed()?;
75 self.inner.fill(dst)?;
76 self.bytes_generated = self.bytes_generated.saturating_add(dst.len() as u64);
77 Ok(())
78 }
79}
80
81impl rand_core::TryRng for ReseedingRng {
82 type Error = CryptoError;
83
84 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
85 self.maybe_reseed()?;
86 let v = self.inner.try_next_u32()?;
87 self.bytes_generated = self.bytes_generated.saturating_add(4);
88 Ok(v)
89 }
90
91 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
92 self.maybe_reseed()?;
93 let v = self.inner.try_next_u64()?;
94 self.bytes_generated = self.bytes_generated.saturating_add(8);
95 Ok(v)
96 }
97
98 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
99 self.maybe_reseed()?;
100 self.inner.try_fill_bytes(dest)?;
101 self.bytes_generated = self.bytes_generated.saturating_add(dest.len() as u64);
102 Ok(())
103 }
104}
105
106impl rand_core::TryCryptoRng for ReseedingRng {}
107
108impl core::fmt::Debug for ReseedingRng {
109 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110 f.debug_struct("ReseedingRng")
111 .field("bytes_generated", &self.bytes_generated)
112 .field("reseed_threshold", &self.reseed_threshold)
113 .finish()
114 }
115}
116
117#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn reseeding_rng_new_works() {
125 let mut rng = ReseedingRng::new().expect("ReseedingRng::new failed");
126 let mut buf = [0u8; 32];
127 rng.fill(&mut buf).expect("ReseedingRng::fill failed");
128 assert_ne!(buf, [0u8; 32], "Output should not be all zeros");
129 }
130
131 #[test]
132 fn reseeding_rng_threshold_triggers_reseed() {
133 let mut rng =
134 ReseedingRng::with_threshold(16).expect("ReseedingRng::with_threshold failed");
135 assert_eq!(rng.bytes_generated(), 0);
136 let mut buf = [0u8; 20];
137 rng.fill(&mut buf).expect("first fill failed");
138 assert_eq!(rng.bytes_generated(), 20, "20 bytes should be tracked");
139 let mut buf2 = [0u8; 20];
140 rng.fill(&mut buf2).expect("second fill failed");
141 assert_eq!(
142 rng.bytes_generated(),
143 20,
144 "counter should reset after reseed"
145 );
146 }
147
148 #[test]
149 fn reseeding_rng_debug_does_not_leak_state() {
150 let rng = ReseedingRng::new().expect("ReseedingRng::new failed");
151 let dbg = format!("{rng:?}");
152 assert!(
153 dbg.contains("ReseedingRng"),
154 "Debug format should include type name"
155 );
156 }
157}