Skip to content

Commit c236f6f

Browse files
committed
Improve JitterRng documentation
1 parent 2def04e commit c236f6f

File tree

1 file changed

+77
-48
lines changed

1 file changed

+77
-48
lines changed

src/jitter.rs

+77-48
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
//! Non-physical true random number generator based on timing jitter.
1818
19+
// Note: the C implementation of `Jitterentropy` relies on being compiled
20+
// without optimizations. This implementation goes through lengths to make the
21+
// compiler not optimise out what is technically dead code, but that does
22+
// influence timing jitter.
23+
1924
use rand_core::{RngCore, CryptoRng, Error, ErrorKind, impls};
2025

2126
use core::{fmt, mem, ptr};
@@ -31,7 +36,7 @@ const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE;
3136
///
3237
/// This is a true random number generator, as opposed to pseudo-random
3338
/// generators. Random numbers generated by `JitterRng` can be seen as fresh
34-
/// entropy. A consequence is that is orders of magnitude slower than `OsRng`
39+
/// entropy. A consequence is that is orders of magnitude slower than [`OsRng`]
3540
/// and PRNGs (about 10<sup>3</sup>..10<sup>6</sup> slower).
3641
///
3742
/// There are very few situations where using this RNG is appropriate. Only very
@@ -40,21 +45,19 @@ const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE;
4045
/// predict.
4146
///
4247
/// Use of `JitterRng` is recommended for initializing cryptographic PRNGs when
43-
/// `OsRng` is not available.
48+
/// [`OsRng`] is not available.
4449
///
4550
/// This implementation is based on
4651
/// [Jitterentropy](http://www.chronox.de/jent.html) version 2.1.0.
47-
//
48-
// Note: the C implementation relies on being compiled without optimizations.
49-
// This implementation goes through lengths to make the compiler not optimise
50-
// out what is technically dead code, but that does influence timing jitter.
52+
///
53+
/// [`OsRng`]: os/struct.OsRng.html
5154
pub struct JitterRng {
5255
data: u64, // Actual random number
5356
// Number of rounds to run the entropy collector per 64 bits
5457
rounds: u8,
5558
// Timer used by `measure_jitter`
5659
timer: fn() -> u64,
57-
// Memory for the Memory Access noise source FIXME
60+
// Memory for the Memory Access noise source
5861
mem_prev_index: u16,
5962
// Make `next_u32` not waste 32 bits
6063
data_half_used: bool,
@@ -100,7 +103,9 @@ impl fmt::Debug for JitterRng {
100103
}
101104
}
102105

103-
/// An error that can occur when `test_timer` fails.
106+
/// An error that can occur when [`JitterRng::test_timer`] fails.
107+
///
108+
/// [`JitterRng::test_timer`]: struct.JitterRng.html#method.test_timer
104109
#[derive(Debug, Clone, PartialEq, Eq)]
105110
pub enum TimerError {
106111
/// No timer available.
@@ -157,8 +162,9 @@ impl From<TimerError> for Error {
157162
static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT;
158163

159164
impl JitterRng {
160-
/// Create a new `JitterRng`.
161-
/// Makes use of `std::time` for a timer.
165+
/// Create a new `JitterRng`. Makes use of `std::time` for a timer, or a
166+
/// platform-specific function with higher accuracy if necessary and
167+
/// available.
162168
///
163169
/// During initialization CPU execution timing jitter is measured a few
164170
/// hundred times. If this does not pass basic quality tests, an error is
@@ -188,10 +194,44 @@ impl JitterRng {
188194
/// The timer must have nanosecond precision.
189195
///
190196
/// This method is more low-level than `new()`. It is the responsibility of
191-
/// the caller to run `test_timer` before using any numbers generated with
192-
/// `JitterRng`, and optionally call `set_rounds()`. Also it is important to
193-
/// call `gen_entropy` once before using the first result to initialize the
194-
/// entropy collection pool.
197+
/// the caller to run [`test_timer`] before using any numbers generated with
198+
/// `JitterRng`, and optionally call [`set_rounds`]. Also it is important to
199+
/// consume at least one `u64` before using the first result to initialize
200+
/// the entropy collection pool.
201+
///
202+
/// # Example
203+
///
204+
/// ```rust
205+
/// # use rand::{Rng, Error};
206+
/// use rand::JitterRng;
207+
///
208+
/// # fn try_inner() -> Result<(), Error> {
209+
/// fn get_nstime() -> u64 {
210+
/// use std::time::{SystemTime, UNIX_EPOCH};
211+
///
212+
/// let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
213+
/// // The correct way to calculate the current time is
214+
/// // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64`
215+
/// // But this is faster, and the difference in terms of entropy is
216+
/// // negligible (log2(10^9) == 29.9).
217+
/// dur.as_secs() << 30 | dur.subsec_nanos() as u64
218+
/// }
219+
///
220+
/// let mut rng = JitterRng::new_with_timer(get_nstime);
221+
/// let rounds = rng.test_timer()?;
222+
/// rng.set_rounds(rounds); // optional
223+
/// let _ = rng.gen::<u64>();
224+
///
225+
/// // Ready for use
226+
/// let v: u64 = rng.gen();
227+
/// # Ok(())
228+
/// # }
229+
///
230+
/// # try_inner().unwrap();
231+
/// ```
232+
///
233+
/// [`test_timer`]: struct.JitterRng.html#method.test_timer
234+
/// [`set_rounds`]: struct.JitterRng.html#method.set_rounds
195235
pub fn new_with_timer(timer: fn() -> u64) -> JitterRng {
196236
JitterRng {
197237
data: 0,
@@ -206,10 +246,12 @@ impl JitterRng {
206246
/// This must be greater than zero, and has a big impact on performance
207247
/// and output quality.
208248
///
209-
/// `new_with_timer` conservatively uses 64 rounds, but often less rounds
249+
/// [`new_with_timer`] conservatively uses 64 rounds, but often less rounds
210250
/// can be used. The `test_timer()` function returns the minimum number of
211251
/// rounds required for full strength (platform dependent), so one may use
212252
/// `rng.set_rounds(rng.test_timer()?);` or cache the value.
253+
///
254+
/// [`new_with_timer`]: struct.JitterRng.html#method.new_with_timer
213255
pub fn set_rounds(&mut self, rounds: u8) {
214256
assert!(rounds > 0);
215257
self.rounds = rounds;
@@ -447,17 +489,14 @@ impl JitterRng {
447489
self.data
448490
}
449491

450-
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
451-
pub fn test_timer(&mut self) -> Result<u8, TimerError> {
452-
return Err(TimerError::NoTimer);
453-
}
454-
455492
/// Basic quality tests on the timer, by measuring CPU timing jitter a few
456493
/// hundred times.
457494
///
458495
/// If succesful, this will return the estimated number of rounds necessary
459-
/// to collect 64 bits of entropy. Otherwise a `TimerError` with the cause
496+
/// to collect 64 bits of entropy. Otherwise a [`TimerError`] with the cause
460497
/// of the failure will be returned.
498+
///
499+
/// [`TimerError`]: jitter/enum.TimerError.html
461500
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
462501
pub fn test_timer(&mut self) -> Result<u8, TimerError> {
463502
debug!("JitterRng: testing timer ...");
@@ -595,6 +634,10 @@ impl JitterRng {
595634
Ok(log2_lookup[delta_average as usize])
596635
}
597636
}
637+
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
638+
pub fn test_timer(&mut self) -> Result<u8, TimerError> {
639+
return Err(TimerError::NoTimer);
640+
}
598641

599642
/// Statistical test: return the timer delta of one normal run of the
600643
/// `JitterEntropy` entropy collector.
@@ -640,33 +683,17 @@ impl JitterRng {
640683
///
641684
/// ```rust,no_run
642685
/// use rand::JitterRng;
643-
///
686+
/// #
644687
/// # use std::error::Error;
645688
/// # use std::fs::File;
646689
/// # use std::io::Write;
647690
/// #
648691
/// # fn try_main() -> Result<(), Box<Error>> {
649-
/// fn get_nstime() -> u64 {
650-
/// use std::time::{SystemTime, UNIX_EPOCH};
651-
///
652-
/// let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
653-
/// // The correct way to calculate the current time is
654-
/// // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64`
655-
/// // But this is faster, and the difference in terms of entropy is
656-
/// // negligible (log2(10^9) == 29.9).
657-
/// dur.as_secs() << 30 | dur.subsec_nanos() as u64
658-
/// }
659-
///
660-
/// // Do not initialize with `JitterRng::new`, but with `new_with_timer`.
661-
/// // 'new' always runst `test_timer`, and can therefore fail to
662-
/// // initialize. We want to be able to get the statistics even when the
663-
/// // timer test fails.
664-
/// let mut rng = JitterRng::new_with_timer(get_nstime);
692+
/// let mut rng = JitterRng::new()?;
665693
///
666694
/// // 1_000_000 results are required for the NIST SP 800-90B Entropy
667695
/// // Estimation Suite
668-
/// // FIXME: this number is smaller here, otherwise the Doc-test is too slow
669-
/// const ROUNDS: usize = 10_000;
696+
/// const ROUNDS: usize = 1_000_000;
670697
/// let mut deltas_variable: Vec<u8> = Vec::with_capacity(ROUNDS);
671698
/// let mut deltas_minimal: Vec<u8> = Vec::with_capacity(ROUNDS);
672699
///
@@ -687,6 +714,7 @@ impl JitterRng {
687714
/// # try_main().unwrap();
688715
/// # }
689716
/// ```
717+
///
690718
#[cfg(feature="std")]
691719
pub fn timer_stats(&mut self, var_rounds: bool) -> i64 {
692720
let mut mem = [0; MEMORY_SIZE];
@@ -701,27 +729,28 @@ impl JitterRng {
701729

702730
#[cfg(feature="std")]
703731
mod platform {
704-
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))]
732+
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows",
733+
all(target_arch = "wasm32", not(target_os = "emscripten")))))]
705734
pub fn get_nstime() -> u64 {
706735
use std::time::{SystemTime, UNIX_EPOCH};
707736

708737
let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
709738
// The correct way to calculate the current time is
710739
// `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64`
711-
// But this is faster, and the difference in terms of entropy is negligible
712-
// (log2(10^9) == 29.9).
740+
// But this is faster, and the difference in terms of entropy is
741+
// negligible (log2(10^9) == 29.9).
713742
dur.as_secs() << 30 | dur.subsec_nanos() as u64
714743
}
715744

716745
#[cfg(any(target_os = "macos", target_os = "ios"))]
717746
pub fn get_nstime() -> u64 {
718747
extern crate libc;
719748
// On Mac OS and iOS std::time::SystemTime only has 1000ns resolution.
720-
// We use `mach_absolute_time` instead. This provides a CPU dependent unit,
721-
// to get real nanoseconds the result should by multiplied by numer/denom
722-
// from `mach_timebase_info`.
723-
// But we are not interested in the exact nanoseconds, just entropy. So we
724-
// use the raw result.
749+
// We use `mach_absolute_time` instead. This provides a CPU dependent
750+
// unit, to get real nanoseconds the result should by multiplied by
751+
// numer/denom from `mach_timebase_info`.
752+
// But we are not interested in the exact nanoseconds, just entropy. So
753+
// we use the raw result.
725754
unsafe { libc::mach_absolute_time() }
726755
}
727756

0 commit comments

Comments
 (0)