Skip to content

Commit

Permalink
Merge pull request #235 from pitdicker/entropy_source
Browse files Browse the repository at this point in the history
Add EntropySource wrapper
  • Loading branch information
dhardy authored Feb 5, 2018
2 parents 0994d09 + 8c500a4 commit fc187b3
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 70 deletions.
7 changes: 6 additions & 1 deletion src/jitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,12 @@ impl Rng for JitterRng {
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
impls::fill_bytes_via_u64(self, dest)
// Fill using `next_u32`. This is faster for filling small slices (four
// bytes or less), while the overhead is negligible.
//
// This is done especially for wrappers that implement `next_u32`
// themselves via `fill_bytes`.
impls::fill_bytes_via_u32(self, dest)
}
}

Expand Down
160 changes: 129 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ use prng::Isaac64Rng as IsaacWordRng;

use distributions::{Range, IndependentSample};
use distributions::range::SampleRange;
#[cfg(feature="std")] use reseeding::{ReseedingRng, ReseedWithNew};
#[cfg(feature="std")] use reseeding::ReseedingRng;

// public modules
pub mod distributions;
Expand Down Expand Up @@ -844,29 +844,7 @@ pub trait NewRng: SeedableRng {
#[cfg(feature="std")]
impl<R: SeedableRng> NewRng for R {
fn new() -> Result<Self, Error> {
// Note: error handling would be easier with try/catch blocks
fn new_os<T: SeedableRng>() -> Result<T, Error> {
let mut r = OsRng::new()?;
T::from_rng(&mut r)
}

fn new_jitter<T: SeedableRng>() -> Result<T, Error> {
let mut r = JitterRng::new()?;
T::from_rng(&mut r)
}

trace!("Seeding new RNG");
new_os().or_else(|e1| {
warn!("OsRng failed [falling back to JitterRng]: {:?}", e1);
new_jitter().map_err(|_e2| {
warn!("JitterRng failed: {:?}", _e2);
// TODO: can we somehow return both error sources?
Error::with_cause(
ErrorKind::Unavailable,
"seeding a new RNG failed: both OS and Jitter entropy sources failed",
e1)
})
})
R::from_rng(EntropyRng::new())
}
}

Expand Down Expand Up @@ -964,20 +942,19 @@ pub fn weak_rng() -> XorShiftRng {
#[cfg(feature="std")]
#[derive(Clone, Debug)]
pub struct ThreadRng {
rng: Rc<RefCell<ReseedingRng<StdRng, ReseedWithNew>>>,
rng: Rc<RefCell<ReseedingRng<StdRng, EntropyRng>>>,
}

#[cfg(feature="std")]
thread_local!(
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<StdRng, ReseedWithNew>>> = {
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<StdRng, EntropyRng>>> = {
const THREAD_RNG_RESEED_THRESHOLD: u64 = 32_768;
let r = match StdRng::new() {
Ok(r) => r,
Err(e) => panic!("could not initialize thread_rng: {:?}", e)
};
let mut entropy_source = EntropyRng::new();
let r = StdRng::from_rng(&mut entropy_source)
.expect("could not initialize thread_rng");
let rng = ReseedingRng::new(r,
THREAD_RNG_RESEED_THRESHOLD,
ReseedWithNew);
entropy_source);
Rc::new(RefCell::new(rng))
}
);
Expand Down Expand Up @@ -1018,6 +995,127 @@ impl Rng for ThreadRng {
}
}

/// An RNG provided specifically for seeding PRNG's.
///
/// `EntropyRng` uses the interface for random numbers provided by the operating
/// system ([`OsRng`]). If that returns an error, it will fall back to the
/// [`JitterRng`] entropy collector. Every time it will then check if `OsRng`
/// is still not available, and switch back if possible.
///
/// [`OsRng`]: os/struct.OsRng.html
/// [`JitterRng`]: jitter/struct.JitterRng.html
#[cfg(feature="std")]
#[derive(Debug)]
pub struct EntropyRng {
rng: EntropySource,
}

#[cfg(feature="std")]
#[derive(Debug)]
enum EntropySource {
Os(OsRng),
Jitter(JitterRng),
None,
}

#[cfg(feature="std")]
impl EntropyRng {
/// Create a new `EntropyRng`.
///
/// This method will do no system calls or other initialization routines,
/// those are done on first use. This is done to make `new` infallible,
/// and `try_fill_bytes` the only place to report errors.
pub fn new() -> Self {
EntropyRng { rng: EntropySource::None }
}
}

#[cfg(feature="std")]
impl Rng for EntropyRng {
fn next_u32(&mut self) -> u32 {
impls::next_u32_via_fill(self)
}

fn next_u64(&mut self) -> u64 {
impls::next_u64_via_fill(self)
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
self.try_fill_bytes(dest).unwrap();
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
fn try_os_new(dest: &mut [u8]) -> Result<OsRng, Error>
{
let mut rng = OsRng::new()?;
rng.try_fill_bytes(dest)?;
Ok(rng)
}

fn try_jitter_new(dest: &mut [u8]) -> Result<JitterRng, Error>
{
let mut rng = JitterRng::new()?;
rng.try_fill_bytes(dest)?;
Ok(rng)
}

let mut switch_rng = None;
match self.rng {
EntropySource::None => {
let os_rng_result = try_os_new(dest);
match os_rng_result {
Ok(os_rng) => {
switch_rng = Some(EntropySource::Os(os_rng));
}
Err(os_rng_error) => {
warn!("EntropyRng: OsRng failed [falling back to JitterRng]: {}",
os_rng_error);
match try_jitter_new(dest) {
Ok(jitter_rng) => {
switch_rng = Some(EntropySource::Jitter(jitter_rng));
}
Err(_jitter_error) => {
warn!("EntropyRng: JitterRng failed: {}",
_jitter_error);
return Err(os_rng_error);
}
}
}
}
}
EntropySource::Os(ref mut rng) => {
let os_rng_result = rng.try_fill_bytes(dest);
if let Err(os_rng_error) = os_rng_result {
warn!("EntropyRng: OsRng failed [falling back to JitterRng]: {}",
os_rng_error);
match try_jitter_new(dest) {
Ok(jitter_rng) => {
switch_rng = Some(EntropySource::Jitter(jitter_rng));
}
Err(_jitter_error) => {
warn!("EntropyRng: JitterRng failed: {}",
_jitter_error);
return Err(os_rng_error);
}
}
}
}
EntropySource::Jitter(ref mut rng) => {
if let Ok(os_rng) = try_os_new(dest) {
info!("EntropyRng: OsRng available [switching back from JitterRng]");
switch_rng = Some(EntropySource::Os(os_rng));
} else {
return rng.try_fill_bytes(dest); // use JitterRng
}
}
}
if let Some(rng) = switch_rng {
self.rng = rng;
}
Ok(())
}
}

/// Generates a random value using the thread-local random number generator.
///
/// `random()` can generate various types of random things, and so may require
Expand Down
52 changes: 14 additions & 38 deletions src/reseeding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
//! A wrapper around another RNG that reseeds it after it
//! generates a certain number of random bytes.

use {Rng, Error};
#[cfg(feature="std")]
use NewRng;
use {Rng, SeedableRng, Error};

/// A wrapper around any RNG which reseeds the underlying RNG after it
/// has generated a certain number of random bytes.
Expand All @@ -23,10 +21,10 @@ pub struct ReseedingRng<R, Rsdr> {
generation_threshold: u64,
bytes_generated: u64,
/// Controls the behaviour when reseeding the RNG.
pub reseeder: Rsdr,
reseeder: Rsdr,
}

impl<R: Rng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> {
impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
/// Create a new `ReseedingRng` with the given parameters.
///
/// # Arguments
Expand All @@ -48,14 +46,14 @@ impl<R: Rng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> {
pub fn reseed_if_necessary(&mut self) {
if self.bytes_generated >= self.generation_threshold {
trace!("Reseeding RNG after {} bytes", self.bytes_generated);
self.reseeder.reseed(&mut self.rng).unwrap();
R::from_rng(&mut self.reseeder).map(|result| self.rng = result).unwrap();
self.bytes_generated = 0;
}
}
}


impl<R: Rng, Rsdr: Reseeder<R>> Rng for ReseedingRng<R, Rsdr> {
impl<R: Rng+SeedableRng, Rsdr: Rng> Rng for ReseedingRng<R, Rsdr> {
fn next_u32(&mut self) -> u32 {
self.reseed_if_necessary();
self.bytes_generated += 4;
Expand All @@ -81,35 +79,10 @@ impl<R: Rng, Rsdr: Reseeder<R>> Rng for ReseedingRng<R, Rsdr> {
}
}

/// Something that can be used to reseed an RNG via `ReseedingRng`.
///
/// Note that implementations should support `Clone` only if reseeding is
/// deterministic (no external entropy source). This is so that a `ReseedingRng`
/// only supports `Clone` if fully deterministic.
pub trait Reseeder<R: ?Sized> {
/// Reseed the given RNG.
///
/// On error, this should just forward the source error; errors are handled
/// by the caller.
fn reseed(&mut self, rng: &mut R) -> Result<(), Error>;
}

/// Reseed an RNG using `NewRng` to replace the current instance.
#[cfg(feature="std")]
#[derive(Debug)]
pub struct ReseedWithNew;

#[cfg(feature="std")]
impl<R: Rng + NewRng> Reseeder<R> for ReseedWithNew {
fn reseed(&mut self, rng: &mut R) -> Result<(), Error> {
R::new().map(|result| *rng = result)
}
}

#[cfg(test)]
mod test {
use {impls, le};
use super::{ReseedingRng, Reseeder};
use super::{ReseedingRng};
use {SeedableRng, Rng, Error};

struct Counter {
Expand Down Expand Up @@ -140,17 +113,20 @@ mod test {
}

#[derive(Debug, Clone)]
struct ReseedCounter;
impl Reseeder<Counter> for ReseedCounter {
fn reseed(&mut self, rng: &mut Counter) -> Result<(), Error> {
*rng = Counter { i: 0 };
struct ResetCounter;
impl Rng for ResetCounter {
fn next_u32(&mut self) -> u32 { unimplemented!() }
fn next_u64(&mut self) -> u64 { unimplemented!() }
fn fill_bytes(&mut self, _dest: &mut [u8]) { unimplemented!() }
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
for i in dest.iter_mut() { *i = 0; }
Ok(())
}
}

#[test]
fn test_reseeding() {
let mut rs = ReseedingRng::new(Counter {i:0}, 400, ReseedCounter);
let mut rs = ReseedingRng::new(Counter {i:0}, 400, ResetCounter);

let mut i = 0;
for _ in 0..1000 {
Expand Down

0 comments on commit fc187b3

Please sign in to comment.