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 BlockRng abstraction #281

Merged
merged 11 commits into from
Mar 16, 2018
32 changes: 28 additions & 4 deletions benches/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use test::{black_box, Bencher};
use rand::{RngCore, Rng, SeedableRng, NewRng, StdRng, OsRng, JitterRng, EntropyRng};
use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng};
use rand::reseeding::ReseedingRng;
use rand::prng::hc128::Hc128Core;
use rand::thread_rng;

macro_rules! gen_bytes {
($fnn:ident, $gen:ident) => {
Expand Down Expand Up @@ -150,10 +152,13 @@ chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12);
chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20);


const RESEEDING_THRESHOLD: u64 = 1024*1024*1024; // something high enough to get
// deterministic measurements

#[bench]
fn reseeding_hc128_bytes(b: &mut Bencher) {
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
128*1024*1024,
let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(),
RESEEDING_THRESHOLD,
EntropyRng::new());
let mut buf = [0u8; BYTES_LEN];
b.iter(|| {
Expand All @@ -169,8 +174,8 @@ macro_rules! reseeding_uint {
($fnn:ident, $ty:ty) => {
#[bench]
fn $fnn(b: &mut Bencher) {
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
128*1024*1024,
let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(),
RESEEDING_THRESHOLD,
EntropyRng::new());
b.iter(|| {
for _ in 0..RAND_BENCH_N {
Expand All @@ -184,3 +189,22 @@ macro_rules! reseeding_uint {

reseeding_uint!(reseeding_hc128_u32, u32);
reseeding_uint!(reseeding_hc128_u64, u64);


macro_rules! threadrng_uint {
($fnn:ident, $ty:ty) => {
#[bench]
fn $fnn(b: &mut Bencher) {
let mut rng = thread_rng();
b.iter(|| {
for _ in 0..RAND_BENCH_N {
black_box(rng.gen::<$ty>());
}
});
b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N;
}
}
}

threadrng_uint!(thread_rng_u32, u32);
threadrng_uint!(thread_rng_u64, u64);
172 changes: 170 additions & 2 deletions rand-core/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

use core::intrinsics::transmute;
use core::ptr::copy_nonoverlapping;
use core::slice;
use core::{fmt, slice};
use core::cmp::min;
use core::mem::size_of;
use RngCore;
use {RngCore, BlockRngCore, CryptoRng, SeedableRng, Error};

/// Implement `next_u64` via `next_u32`, little-endian order.
pub fn next_u64_via_u32<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
Expand Down Expand Up @@ -164,4 +164,172 @@ pub fn next_u64_via_fill<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
impl_uint_from_fill!(rng, u64, 8)
}

/// Wrapper around PRNGs that implement [`BlockRngCore`] to keep a results
/// buffer and offer the methods from [`RngCore`].
///
/// `BlockRng` has optimized methods to read from the output array that the
/// algorithm of many cryptograpic RNGs generates natively. Also they handle the
/// bookkeeping when to generate a new batch of values.
///
/// `next_u32` simply indexes the array. `next_u64` tries to read two `u32`
/// values at a time if possible, and handles edge cases like when only one
/// value is left. `try_fill_bytes` is optimized use the [`BlockRngCore`]
/// implementation to write the results directly to the destination slice.
/// No generated values are ever thown away.
///
/// For easy initialization `BlockRng` also implements [`SeedableRng`].
///
/// [`BlockRngCore`]: ../BlockRngCore.t.html
/// [`RngCore`]: ../RngCore.t.html
/// [`SeedableRng`]: ../SeedableRng.t.html
#[derive(Clone)]
pub struct BlockRng<R: BlockRngCore<u32>> {
pub core: R,
pub results: R::Results,
pub index: usize,
}

// Custom Debug implementation that does not expose the contents of `results`.
impl<R: BlockRngCore<u32>+fmt::Debug> fmt::Debug for BlockRng<R> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("BlockRng")
.field("core", &self.core)
.field("result_len", &self.results.as_ref().len())
.field("index", &self.index)
.finish()
}
}

impl<R: BlockRngCore<u32>> RngCore for BlockRng<R> {
#[inline(always)]
fn next_u32(&mut self) -> u32 {
if self.index >= self.results.as_ref().len() {
self.core.generate(&mut self.results);
self.index = 0;
}

let value = self.results.as_ref()[self.index];
self.index += 1;
value
}

#[inline(always)]
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure these functions should always be inlined? They're relatively large.

fn next_u64(&mut self) -> u64 {
let read_u64 = |results: &[u32], index| {
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
// requires little-endian CPU supporting unaligned reads:
unsafe { *(&results[index] as *const u32 as *const u64) }
} else {
let x = results[index] as u64;
let y = results[index + 1] as u64;
(y << 32) | x
}
};

let len = self.results.as_ref().len();

let index = self.index;
if index < len-1 {
self.index += 2;
// Read an u64 from the current index
read_u64(self.results.as_ref(), index)
} else if index >= len {
self.core.generate(&mut self.results);
self.index = 2;
read_u64(self.results.as_ref(), 0)
} else {
let x = self.results.as_ref()[len-1] as u64;
self.core.generate(&mut self.results);
self.index = 1;
let y = self.results.as_ref()[0] as u64;
(y << 32) | x
}
}

// As an optimization we try to write directly into the output buffer.
// This is only enabled for little-endian platforms where unaligned writes
// are known to be safe and fast.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut filled = 0;

// Continue filling from the current set of results
if self.index < self.results.as_ref().len() {
let (consumed_u32, filled_u8) =
fill_via_u32_chunks(&self.results.as_ref()[self.index..],
dest);

self.index += consumed_u32;
filled += filled_u8;
}

let len_remainder =
(dest.len() - filled) % (self.results.as_ref().len() * 4);
let end_direct = dest.len() - len_remainder;

while filled < end_direct {
let dest_u32: &mut R::Results = unsafe {
::core::mem::transmute(dest[filled..].as_mut_ptr())
};
self.core.generate(dest_u32);
filled += self.results.as_ref().len() * 4;
}
self.index = self.results.as_ref().len();

if len_remainder > 0 {
self.core.generate(&mut self.results);
let (consumed_u32, _) =
fill_via_u32_chunks(&mut self.results.as_ref(),
&mut dest[filled..]);

self.index = consumed_u32;
}
}

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut read_len = 0;
while read_len < dest.len() {
if self.index >= self.results.as_ref().len() {
self.core.generate(&mut self.results);
self.index = 0;
}
let (consumed_u32, filled_u8) =
fill_via_u32_chunks(&self.results.as_ref()[self.index..],
&mut dest[read_len..]);

self.index += consumed_u32;
read_len += filled_u8;
}
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
Ok(self.fill_bytes(dest))
}
}

impl<R: BlockRngCore<u32> + SeedableRng> SeedableRng for BlockRng<R> {
type Seed = R::Seed;

fn from_seed(seed: Self::Seed) -> Self {
let results_empty = R::Results::default();
Self {
core: R::from_seed(seed),
index: results_empty.as_ref().len(), // generate on first use
results: results_empty,
}
}

fn from_rng<RNG: RngCore>(rng: &mut RNG) -> Result<Self, Error> {
let results_empty = R::Results::default();
Ok(Self {
core: R::from_rng(rng)?,
index: results_empty.as_ref().len(), // generate on first use
results: results_empty,
})
}
}

impl<R: BlockRngCore<u32>+CryptoRng> CryptoRng for BlockRng<R> {}

// TODO: implement tests for the above
30 changes: 19 additions & 11 deletions rand-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ pub trait RngCore {
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error>;
}

/// Trait for RNGs that do not generate random numbers one at a time, but in
/// blocks. Especially for cryptographic RNG's it is common to generate 16 or
/// more results at a time.
pub trait BlockRngCore<T>: Sized {
/// Results type. This is the 'block' an RNG implementing `BlockRngCore`
/// generates, which will usually be an array like `[u32; 16]`.
type Results: AsRef<[T]> + Default;

/// Generate a new block of results.
fn generate(&mut self, results: &mut Self::Results);
}

/// A marker trait for an `Rng` which may be considered for use in
/// cryptography.
///
Expand All @@ -182,7 +194,7 @@ pub trait RngCore {
///
/// Note also that use of a `CryptoRng` does not protect against other
/// weaknesses such as seeding from a weak entropy source or leaking state.
pub trait CryptoRng: RngCore {}
pub trait CryptoRng {}

/// A random number generator that can be explicitly seeded.
///
Expand Down Expand Up @@ -263,45 +275,41 @@ pub trait SeedableRng: Sized {


impl<'a, R: RngCore + ?Sized> RngCore for &'a mut R {
#[inline]
#[inline(always)]
fn next_u32(&mut self) -> u32 {
(**self).next_u32()
}

#[inline]
#[inline(always)]
fn next_u64(&mut self) -> u64 {
(**self).next_u64()
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
(**self).fill_bytes(dest)
}

#[inline]

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
(**self).try_fill_bytes(dest)
}
}

#[cfg(any(feature="std", feature="alloc"))]
impl<R: RngCore + ?Sized> RngCore for Box<R> {
#[inline]
#[inline(always)]
fn next_u32(&mut self) -> u32 {
(**self).next_u32()
}

#[inline]
#[inline(always)]
fn next_u64(&mut self) -> u64 {
(**self).next_u64()
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
(**self).fill_bytes(dest)
}

#[inline]

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
(**self).try_fill_bytes(dest)
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ pub use jitter::JitterRng;
#[cfg(feature="std")] pub use os::OsRng;

// pseudo rngs
pub mod prng;
pub use isaac::{IsaacRng, Isaac64Rng};
pub use chacha::ChaChaRng;
pub use prng::XorShiftRng;
Expand Down Expand Up @@ -312,7 +313,6 @@ pub mod isaac {

// private modules
#[cfg(feature="std")] mod entropy_rng;
mod prng;
#[cfg(feature="std")] mod thread_rng;


Expand Down Expand Up @@ -796,10 +796,12 @@ impl<R: SeedableRng> NewRng for R {
pub struct StdRng(Hc128Rng);

impl RngCore for StdRng {
#[inline(always)]
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}

#[inline(always)]
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
Expand Down
Loading