Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EntropySource wrapper #235

Merged
merged 8 commits into from
Feb 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -287,7 +287,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 @@ -843,29 +843,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 @@ -963,20 +941,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 @@ -1017,6 +994,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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unwrap has to go — but sure, this is a quick implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was even thinking of just using unimplemented!() here. The situations where both OsRng and JitterRng fail should be extremely rare, so I didn't care much about the unwrap. What do suggest to replace it with?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay for now; revisit in #249

}

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