1use oxicrypto_core::{CryptoError, Rng};
7use rand_chacha::ChaCha20Rng;
8use rand_core::{SeedableRng, TryRng};
9
10pub struct OxiRng {
21 pub(crate) inner: ChaCha20Rng,
22 #[cfg(unix)]
23 pub(crate) last_pid: u32,
24}
25
26impl OxiRng {
27 #[must_use = "the RNG must be stored and used; discarding it serves no purpose"]
31 pub fn new() -> Result<Self, CryptoError> {
32 let mut seed = [0u8; 32];
33 getrandom::fill(&mut seed).map_err(|_| CryptoError::Internal("getrandom failed"))?;
34 Ok(Self {
35 inner: ChaCha20Rng::from_seed(seed),
36 #[cfg(unix)]
37 last_pid: std::process::id(),
38 })
39 }
40
41 pub fn reseed(&mut self) -> Result<(), CryptoError> {
49 crate::helpers::reseed(self)
50 }
51
52 pub fn fill_exact<const N: usize>(&mut self, dst: &mut [u8; N]) -> Result<(), CryptoError> {
54 self.fill(dst.as_mut_slice())
55 }
56
57 #[cfg(unix)]
61 pub(crate) fn check_fork(&mut self) -> Result<(), CryptoError> {
62 let current_pid = std::process::id();
63 if current_pid != self.last_pid {
64 let mut seed = [0u8; 32];
66 getrandom::fill(&mut seed).map_err(|_| CryptoError::Rng)?;
67 self.inner = ChaCha20Rng::from_seed(seed);
68 self.last_pid = current_pid;
69 }
70 Ok(())
71 }
72}
73
74impl Rng for OxiRng {
75 fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
76 #[cfg(unix)]
77 self.check_fork()?;
78 self.inner
79 .try_fill_bytes(dst)
80 .map_err(|_| CryptoError::Rng)?;
81 Ok(())
82 }
83}
84
85impl core::fmt::Debug for OxiRng {
86 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87 f.write_str("OxiRng { [state redacted] }")
88 }
89}
90
91impl core::fmt::Display for OxiRng {
92 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93 f.write_str("OxiRng(ChaCha20)")
94 }
95}
96
97impl rand_core::TryRng for OxiRng {
104 type Error = CryptoError;
105
106 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
107 self.inner.try_next_u32().map_err(|_| CryptoError::Rng)
108 }
109
110 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
111 self.inner.try_next_u64().map_err(|_| CryptoError::Rng)
112 }
113
114 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
115 #[cfg(unix)]
116 self.check_fork()?;
117 self.inner
118 .try_fill_bytes(dest)
119 .map_err(|_| CryptoError::Rng)
120 }
121}
122
123impl rand_core::TryCryptoRng for OxiRng {}
124
125pub struct OxiRng8 {
135 inner: rand_chacha::ChaCha8Rng,
136 #[cfg(unix)]
137 last_pid: u32,
138}
139
140impl OxiRng8 {
141 pub fn new() -> Result<Self, CryptoError> {
143 let mut seed = [0u8; 32];
144 getrandom::fill(&mut seed).map_err(|_| CryptoError::Internal("getrandom failed"))?;
145 Ok(Self {
146 inner: rand_chacha::ChaCha8Rng::from_seed(seed),
147 #[cfg(unix)]
148 last_pid: std::process::id(),
149 })
150 }
151
152 pub fn reseed(&mut self) -> Result<(), CryptoError> {
154 let mut seed = [0u8; 32];
155 getrandom::fill(&mut seed).map_err(|_| CryptoError::Rng)?;
156 self.inner = rand_chacha::ChaCha8Rng::from_seed(seed);
157 #[cfg(unix)]
158 {
159 self.last_pid = std::process::id();
160 }
161 Ok(())
162 }
163
164 #[cfg(unix)]
165 fn check_fork(&mut self) -> Result<(), CryptoError> {
166 let current_pid = std::process::id();
167 if current_pid != self.last_pid {
168 let mut seed = [0u8; 32];
169 getrandom::fill(&mut seed).map_err(|_| CryptoError::Rng)?;
170 self.inner = rand_chacha::ChaCha8Rng::from_seed(seed);
171 self.last_pid = current_pid;
172 }
173 Ok(())
174 }
175}
176
177impl Rng for OxiRng8 {
178 fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
179 #[cfg(unix)]
180 self.check_fork()?;
181 self.inner.try_fill_bytes(dst).map_err(|_| CryptoError::Rng)
182 }
183}
184
185impl rand_core::TryRng for OxiRng8 {
186 type Error = CryptoError;
187
188 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
189 self.inner.try_next_u32().map_err(|_| CryptoError::Rng)
190 }
191
192 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
193 self.inner.try_next_u64().map_err(|_| CryptoError::Rng)
194 }
195
196 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
197 #[cfg(unix)]
198 self.check_fork()?;
199 self.inner
200 .try_fill_bytes(dest)
201 .map_err(|_| CryptoError::Rng)
202 }
203}
204
205impl rand_core::TryCryptoRng for OxiRng8 {}
206
207impl core::fmt::Debug for OxiRng8 {
208 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209 f.debug_struct("OxiRng8").finish_non_exhaustive()
210 }
211}
212
213pub struct OxiRng12 {
223 inner: rand_chacha::ChaCha12Rng,
224 #[cfg(unix)]
225 last_pid: u32,
226}
227
228impl OxiRng12 {
229 pub fn new() -> Result<Self, CryptoError> {
231 let mut seed = [0u8; 32];
232 getrandom::fill(&mut seed).map_err(|_| CryptoError::Internal("getrandom failed"))?;
233 Ok(Self {
234 inner: rand_chacha::ChaCha12Rng::from_seed(seed),
235 #[cfg(unix)]
236 last_pid: std::process::id(),
237 })
238 }
239
240 pub fn reseed(&mut self) -> Result<(), CryptoError> {
242 let mut seed = [0u8; 32];
243 getrandom::fill(&mut seed).map_err(|_| CryptoError::Rng)?;
244 self.inner = rand_chacha::ChaCha12Rng::from_seed(seed);
245 #[cfg(unix)]
246 {
247 self.last_pid = std::process::id();
248 }
249 Ok(())
250 }
251
252 #[cfg(unix)]
253 fn check_fork(&mut self) -> Result<(), CryptoError> {
254 let current_pid = std::process::id();
255 if current_pid != self.last_pid {
256 let mut seed = [0u8; 32];
257 getrandom::fill(&mut seed).map_err(|_| CryptoError::Rng)?;
258 self.inner = rand_chacha::ChaCha12Rng::from_seed(seed);
259 self.last_pid = current_pid;
260 }
261 Ok(())
262 }
263}
264
265impl Rng for OxiRng12 {
266 fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
267 #[cfg(unix)]
268 self.check_fork()?;
269 self.inner.try_fill_bytes(dst).map_err(|_| CryptoError::Rng)
270 }
271}
272
273impl rand_core::TryRng for OxiRng12 {
274 type Error = CryptoError;
275
276 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
277 self.inner.try_next_u32().map_err(|_| CryptoError::Rng)
278 }
279
280 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
281 self.inner.try_next_u64().map_err(|_| CryptoError::Rng)
282 }
283
284 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
285 #[cfg(unix)]
286 self.check_fork()?;
287 self.inner
288 .try_fill_bytes(dest)
289 .map_err(|_| CryptoError::Rng)
290 }
291}
292
293impl rand_core::TryCryptoRng for OxiRng12 {}
294
295impl core::fmt::Debug for OxiRng12 {
296 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
297 f.debug_struct("OxiRng12").finish_non_exhaustive()
298 }
299}
300
301#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn oxi_rng_creates_ok() {
309 let rng = OxiRng::new();
310 assert!(rng.is_ok(), "OxiRng::new() should succeed on this platform");
311 }
312
313 #[test]
314 fn oxi_rng_fills_buffer() {
315 let mut rng = OxiRng::new().expect("OxiRng::new failed");
316 let mut buf = [0u8; 64];
317 rng.fill(&mut buf).expect("fill should succeed");
318 assert_ne!(buf, [0u8; 64], "Random bytes should not all be zero");
319 }
320
321 #[test]
322 fn oxi_rng_two_outputs_differ() {
323 let mut rng = OxiRng::new().expect("OxiRng::new failed");
324 let mut buf1 = [0u8; 32];
325 let mut buf2 = [0u8; 32];
326 rng.fill(&mut buf1).expect("fill 1 failed");
327 rng.fill(&mut buf2).expect("fill 2 failed");
328 assert_ne!(buf1, buf2, "Consecutive RNG outputs should differ");
329 }
330
331 #[test]
332 fn oxi_rng_reseed_method_changes_output() {
333 let mut rng = OxiRng::new().expect("new failed");
334 let mut buf1 = [0u8; 32];
335 rng.fill(&mut buf1).expect("fill 1 failed");
336 rng.reseed().expect("OxiRng::reseed() failed");
337 let mut buf2 = [0u8; 32];
338 rng.fill(&mut buf2).expect("fill 2 failed");
339 assert_ne!(buf1, buf2, "Output after reseed() should differ");
340 }
341
342 #[cfg(unix)]
343 #[test]
344 fn fork_safe_pid_simulation() {
345 let mut rng = OxiRng::new().expect("OxiRng::new failed");
346 let mut before = [0u8; 32];
347 rng.fill(&mut before).expect("fill before failed");
348 rng.last_pid = 0; let mut after = [0u8; 32];
351 rng.fill(&mut after)
352 .expect("fill after fork-simulation failed");
353 assert_ne!(
354 before, after,
355 "After fork simulation, RNG should have reseeded"
356 );
357 assert_eq!(rng.last_pid, std::process::id());
358 }
359
360 #[test]
361 fn oxi_rng_implements_try_crypto_rng() {
362 fn requires_try_crypto_rng<R: rand_core::TryCryptoRng>(_rng: &mut R) {}
363 let mut rng = OxiRng::new().expect("new failed");
364 requires_try_crypto_rng(&mut rng);
365 }
366
367 #[test]
368 fn oxi_rng_debug_does_not_leak_state() {
369 let rng = OxiRng::new().expect("OxiRng::new failed");
370 let dbg = std::format!("{rng:?}");
371 assert!(dbg.contains("OxiRng"), "Debug must include type name");
372 assert!(
373 dbg.contains("redacted"),
374 "Debug must not expose internal state"
375 );
376 }
377
378 #[test]
379 fn oxi_rng_display_shows_algorithm() {
380 let rng = OxiRng::new().expect("OxiRng::new failed");
381 let display = std::format!("{rng}");
382 assert_eq!(
383 display, "OxiRng(ChaCha20)",
384 "Display must identify the algorithm"
385 );
386 }
387
388 #[test]
389 fn fill_exact_works() {
390 let mut rng = OxiRng::new().expect("OxiRng::new failed");
391 let mut arr = [0u8; 16];
392 rng.fill_exact(&mut arr).expect("fill_exact failed");
393 assert_ne!(
394 arr, [0u8; 16],
395 "fill_exact must not produce all-zero output"
396 );
397 }
398
399 #[test]
400 fn oxi_rng8_fills_buffer() {
401 let mut rng = OxiRng8::new().expect("OxiRng8::new failed");
402 let mut buf = [0u8; 64];
403 rng.fill(&mut buf).expect("OxiRng8::fill failed");
404 assert_ne!(buf, [0u8; 64], "OxiRng8 output should not be all zeros");
405 }
406
407 #[test]
408 fn oxi_rng12_fills_buffer() {
409 let mut rng = OxiRng12::new().expect("OxiRng12::new failed");
410 let mut buf = [0u8; 64];
411 rng.fill(&mut buf).expect("OxiRng12::fill failed");
412 assert_ne!(buf, [0u8; 64], "OxiRng12 output should not be all zeros");
413 }
414
415 #[test]
416 fn oxi_rng8_two_instances_differ() {
417 let mut rng1 = OxiRng8::new().expect("OxiRng8::new 1 failed");
418 let mut rng2 = OxiRng8::new().expect("OxiRng8::new 2 failed");
419 let mut buf1 = [0u8; 32];
420 let mut buf2 = [0u8; 32];
421 rng1.fill(&mut buf1).expect("fill 1 failed");
422 rng2.fill(&mut buf2).expect("fill 2 failed");
423 assert_ne!(
424 buf1, buf2,
425 "Two independently seeded OxiRng8 instances should differ"
426 );
427 }
428
429 #[test]
430 fn oxi_rng12_two_instances_differ() {
431 let mut rng1 = OxiRng12::new().expect("OxiRng12::new 1 failed");
432 let mut rng2 = OxiRng12::new().expect("OxiRng12::new 2 failed");
433 let mut buf1 = [0u8; 32];
434 let mut buf2 = [0u8; 32];
435 rng1.fill(&mut buf1).expect("fill 1 failed");
436 rng2.fill(&mut buf2).expect("fill 2 failed");
437 assert_ne!(
438 buf1, buf2,
439 "Two independently seeded OxiRng12 instances should differ"
440 );
441 }
442
443 #[test]
444 fn oxi_rng8_reseed_changes_output() {
445 let mut rng = OxiRng8::new().expect("OxiRng8::new failed");
446 let mut buf1 = [0u8; 32];
447 rng.fill(&mut buf1).expect("fill 1 failed");
448 rng.reseed().expect("OxiRng8::reseed failed");
449 let mut buf2 = [0u8; 32];
450 rng.fill(&mut buf2).expect("fill 2 failed");
451 assert_ne!(buf1, buf2, "Output after OxiRng8::reseed should differ");
452 }
453
454 #[test]
455 fn oxi_rng12_reseed_changes_output() {
456 let mut rng = OxiRng12::new().expect("OxiRng12::new failed");
457 let mut buf1 = [0u8; 32];
458 rng.fill(&mut buf1).expect("fill 1 failed");
459 rng.reseed().expect("OxiRng12::reseed failed");
460 let mut buf2 = [0u8; 32];
461 rng.fill(&mut buf2).expect("fill 2 failed");
462 assert_ne!(buf1, buf2, "Output after OxiRng12::reseed should differ");
463 }
464
465 #[test]
466 fn oxi_rng8_implements_try_crypto_rng() {
467 fn requires_try_crypto_rng<R: rand_core::TryCryptoRng>(_rng: &mut R) {}
468 let mut rng = OxiRng8::new().expect("OxiRng8::new failed");
469 requires_try_crypto_rng(&mut rng);
470 }
471
472 #[test]
473 fn oxi_rng12_implements_try_crypto_rng() {
474 fn requires_try_crypto_rng<R: rand_core::TryCryptoRng>(_rng: &mut R) {}
475 let mut rng = OxiRng12::new().expect("OxiRng12::new failed");
476 requires_try_crypto_rng(&mut rng);
477 }
478
479 #[test]
480 fn fill_various_sizes() {
481 let mut rng = OxiRng::new().expect("OxiRng::new failed");
482 for size in [0usize, 1, 31, 32, 33, 1024] {
483 let mut buf = std::vec![0u8; size];
484 rng.fill(&mut buf)
485 .expect("fill should succeed for all sizes");
486 assert_eq!(buf.len(), size, "fill must not change buffer length");
487 }
488 }
489
490 #[test]
491 fn fill_one_byte_not_stuck_at_zero() {
492 let mut rng = OxiRng::new().expect("OxiRng::new failed");
493 let mut saw_nonzero = false;
494 for _ in 0..256 {
495 let mut buf = [0u8; 1];
496 rng.fill(&mut buf).expect("fill 1 byte");
497 if buf[0] != 0 {
498 saw_nonzero = true;
499 break;
500 }
501 }
502 assert!(
503 saw_nonzero,
504 "At least one single-byte fill should be non-zero"
505 );
506 }
507}