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

Implement BlockRngCore ISAAC and ISAAC-64 #325

Merged
merged 9 commits into from
Apr 4, 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
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ matrix:
install:
script:
- cargo test --all --tests --no-default-features
- cargo test --features serde-1,log
- cargo test --features serde1,log
- rust: stable
os: osx
install:
script:
- cargo test --all --tests --no-default-features
- cargo test --features serde-1,log
- cargo test --features serde1,log
- rust: beta
install:
script:
- cargo test --all --tests --no-default-features
- cargo test --tests --no-default-features --features=serde-1
- cargo test --tests --no-default-features --features=serde1
- rust: nightly
install:
before_script:
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
script:
- cargo test --all --tests --no-default-features --features=alloc
- cargo test --all --features=alloc
- cargo test --features serde-1,log,nightly
- cargo test --features serde1,log,nightly
- cargo test --benches
- cargo doc --no-deps --all --all-features
- cargo --list | egrep "^\s*deadlinks$" -q || cargo install cargo-deadlinks
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You may also find the [Update Guide](UPDATING.md) useful.
- Create a seperate `rand_core` crate. (#288)
- Deprecate `rand_derive`. (#256)
- Add `log` feature. Logging is now available in `JitterRng`, `OsRng`, `EntropyRng` and `ReseedingRng`. (#246)
- Add `serde-1` feature for some PRNGs. (#189)
- Add `serde1` feature for some PRNGs. (#189)
- `stdweb` feature for `OsRng` support on WASM via stdweb. (#272, #336)

### `Rng` trait
Expand Down Expand Up @@ -55,7 +55,7 @@ You may also find the [Update Guide](UPDATING.md) useful.
- Change `thread_rng` reseeding threshold to 32 MiB. (#277)
- PRNGs no longer implement `Copy`. (#209)
- `Debug` implementations no longer show internals. (#209)
- Implement serialisation for `XorShiftRng`, `IsaacRng` and `Isaac64Rng` under the `serde-1` feature. (#189)
- Implement serialization for `XorShiftRng`, `IsaacRng` and `Isaac64Rng` under the `serde1` feature. (#189)
- Implement `BlockRngCore` for `ChaChaCore` and `Hc128Core`. (#281)
- All PRNGs are now portable across big- and little-endian architectures. (#209)
- `Isaac64Rng::next_u32` no longer throws away half the results. (#209)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ nightly = ["i128_support"] # enables all features requiring nightly rust
std = ["rand_core/std", "alloc", "libc", "winapi", "cloudabi", "fuchsia-zircon"]
alloc = ["rand_core/alloc"] # enables Vec and Box support (without std)
i128_support = [] # enables i128 and u128 support
serde-1 = ["serde", "serde_derive"] # enables serialisation for PRNGs
serde1 = ["serde", "serde_derive", "rand_core/serde1"] # enables serialization for PRNGs

[workspace]
members = ["rand_core"]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ optional features are available:
- `i128_support` enables support for generating `u128` and `i128` values
- `log` enables some logging via the `log` crate
- `nightly` enables all unstable features (`i128_support`)
- `serde-1` enables serialisation for some types, via Serde version 1
- `serde1` enables serialization for some types, via Serde version 1
- `stdweb` enables support for `OsRng` on WASM via stdweb.
- `std` enabled by default; by setting "default-features = false" `no_std`
mode is activated; this removes features depending on `std` functionality:
Expand Down Expand Up @@ -132,7 +132,7 @@ cargo test --tests --no-default-features
cargo test --tests --no-default-features --features alloc

# Test log and serde support
cargo test --features serde-1,log
cargo test --features serde1,log

# Test 128-bit support (requires nightly)
cargo test --all --features nightly
Expand Down
2 changes: 1 addition & 1 deletion UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ deprecation of `Rand`.
Several new Cargo feature flags have been added:

- `alloc`, used without `std`, allows use of `Box` and `Vec`
- `serde-1` adds serialisation support to some PRNGs
- `serde1` adds serialization support to some PRNGs
- `log` adds logging in a few places (primarily to `OsRng` and `JitterRng`)

### `Rng` and friends (core traits)
Expand Down
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ build: false
test_script:
- cargo test --benches
- cargo test --all
- cargo test --features serde-1,log,nightly
- cargo test --features serde1,log,nightly
- cargo test --all --tests --no-default-features --features=alloc
- cargo test --tests --no-default-features --features=serde-1
- cargo test --tests --no-default-features --features=serde1
5 changes: 5 additions & 0 deletions rand_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ appveyor = { repository = "alexcrichton/rand" }
# default = ["std"]
std = ["alloc"] # use std library; should be default but for above bug
alloc = [] # enables Vec and Box support without std
serde1 = ["serde", "serde_derive"] # enables serde for BlockRng wrapper

[dependencies]
serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true }
228 changes: 216 additions & 12 deletions rand_core/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use core::cmp::min;
use core::mem::size_of;
use {RngCore, BlockRngCore, CryptoRng, SeedableRng, Error};

#[cfg(feature="serde1")] use serde::{Serialize, Deserialize};

/// Implement `next_u64` via `next_u32`, little-endian order.
pub fn next_u64_via_u32<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
// Use LE; we explicitly generate one value before the next.
Expand Down Expand Up @@ -184,10 +186,14 @@ pub fn next_u64_via_fill<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
/// [`RngCore`]: ../RngCore.t.html
/// [`SeedableRng`]: ../SeedableRng.t.html
#[derive(Clone)]
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
pub struct BlockRng<R: BlockRngCore + ?Sized> {
pub results: R::Results,
pub index: usize,
pub core: R,
#[cfg_attr(feature="serde1", serde(bound(
serialize = "R::Results: Serialize",
deserialize = "R::Results: Deserialize<'de>")))]
results: R::Results,
index: usize,
core: R,
}

// Custom Debug implementation that does not expose the contents of `results`.
Expand All @@ -201,6 +207,35 @@ impl<R: BlockRngCore + fmt::Debug> fmt::Debug for BlockRng<R> {
}
}

impl<R: BlockRngCore> BlockRng<R> {
/// Create a new `BlockRng` from an existing RNG implementing
/// `BlockRngCore`. Results will be generated on first use.
pub fn new(core: R) -> BlockRng<R>{
let results_empty = R::Results::default();
BlockRng {
core,
index: results_empty.as_ref().len(),
results: results_empty,
}
}

/// Return a reference the wrapped `BlockRngCore`.
pub fn inner(&self) -> &R {
&self.core
}

/// Return a mutable reference the wrapped `BlockRngCore`.
pub fn inner_mut(&mut self) -> &mut R {
&mut self.core
}

// Reset the number of available results.
// This will force a new set of results to be generated on next use.
pub fn reset(&mut self) {
self.index = self.results.as_ref().len();
}
}

impl<R: BlockRngCore<Item=u32>> RngCore for BlockRng<R>
where <R as BlockRngCore>::Results: AsRef<[u32]>
{
Expand Down Expand Up @@ -317,21 +352,190 @@ impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng<R> {
type Seed = R::Seed;

fn from_seed(seed: Self::Seed) -> Self {
Self::new(R::from_seed(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
Ok(Self::new(R::from_rng(rng)?))
}
}



/// Wrapper around PRNGs that implement [`BlockRngCore`] to keep a results
/// buffer and offer the methods from [`RngCore`].
///
/// This is similar to [`BlockRng`], but specialized for algorithms that operate
/// on `u64` values.
///
/// [`BlockRngCore`]: ../BlockRngCore.t.html
/// [`RngCore`]: ../RngCore.t.html
/// [`BlockRng`]: struct.BlockRng.html
#[derive(Clone)]
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
pub struct BlockRng64<R: BlockRngCore + ?Sized> {
#[cfg_attr(feature="serde1", serde(bound(
serialize = "R::Results: Serialize",
deserialize = "R::Results: Deserialize<'de>")))]
results: R::Results,
index: usize,
half_used: bool, // true if only half of the previous result is used
core: R,
}

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

impl<R: BlockRngCore> BlockRng64<R> {
/// Create a new `BlockRng` from an existing RNG implementing
/// `BlockRngCore`. Results will be generated on first use.
pub fn new(core: R) -> BlockRng64<R>{
let results_empty = R::Results::default();
Self {
core: R::from_seed(seed),
index: results_empty.as_ref().len(), // generate on first use
BlockRng64 {
core,
index: results_empty.as_ref().len(),
half_used: false,
results: results_empty,
}
}

/// Return a mutable reference the wrapped `BlockRngCore`.
pub fn inner(&mut self) -> &mut R {
&mut self.core
}

// Reset the number of available results.
// This will force a new set of results to be generated on next use.
pub fn reset(&mut self) {
self.index = self.results.as_ref().len();
}
}

impl<R: BlockRngCore<Item=u64>> RngCore for BlockRng64<R>
where <R as BlockRngCore>::Results: AsRef<[u64]>
{
#[inline(always)]
fn next_u32(&mut self) -> u32 {
let mut index = self.index * 2 - self.half_used as usize;
if index >= self.results.as_ref().len() * 2 {
self.core.generate(&mut self.results);
self.index = 0;
// `self.half_used` is by definition `false`
self.half_used = false;
index = 0;
}

self.half_used = !self.half_used;
self.index += self.half_used as usize;

// Index as if this is a u32 slice.
unsafe {
let results =
&*(self.results.as_ref() as *const [u64] as *const [u32]);
if cfg!(target_endian = "little") {
*results.get_unchecked(index)
} else {
*results.get_unchecked(index ^ 1)
}
}
}

#[inline(always)]
fn next_u64(&mut self) -> u64 {
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;
self.half_used = false;
value
}

// 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;
self.half_used = false;

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

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

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

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

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

self.index = consumed_u64;
}
}

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

let (consumed_u64, filled_u8) =
fill_via_u64_chunks(&self.results.as_ref()[self.index as usize..],
&mut dest[read_len..]);

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

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

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

fn from_seed(seed: Self::Seed) -> Self {
Self::new(R::from_seed(seed))
}

fn from_rng<S: RngCore>(rng: S) -> 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,
})
Ok(Self::new(R::from_rng(rng)?))
}
}

Expand Down
2 changes: 2 additions & 0 deletions rand_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@

#[cfg(feature="std")] extern crate core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
#[cfg(feature="serde1")] extern crate serde;
#[cfg(feature="serde1")] #[macro_use] extern crate serde_derive;


use core::default::Default;
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@
#[cfg(feature="std")] extern crate std as core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;

#[cfg(test)] #[cfg(feature="serde-1")] extern crate bincode;
#[cfg(feature="serde-1")] extern crate serde;
#[cfg(feature="serde-1")] #[macro_use] extern crate serde_derive;
#[cfg(test)] #[cfg(feature="serde1")] extern crate bincode;
#[cfg(feature="serde1")] extern crate serde;
#[cfg(feature="serde1")] #[macro_use] extern crate serde_derive;

#[cfg(all(target_arch = "wasm32", feature = "stdweb"))]
#[macro_use]
Expand Down
Loading