std::rand: Add OSRng, ReaderRng wrappers around the OS RNG & generic Readers respectively.

The former reads from e.g. /dev/urandom, the latter just wraps any
std::rt::io::Reader into an interface that implements Rng.

This also adds Rng.fill_bytes for efficient implementations of the above
(reading 8 bytes at a time is inefficient when you can read 1000), and
removes the dependence on src/rt (i.e. rand_gen_seed) although this last
one requires implementing hand-seeding of the XorShiftRng used in the
scheduler on Linux/unixes, since OSRng relies on a scheduler existing to
be able to read from /dev/urandom.
This commit is contained in:
Huon Wilson 2013-09-22 20:51:57 +10:00
parent a2b509656a
commit 39a69d323d
4 changed files with 435 additions and 30 deletions

@ -44,24 +44,24 @@ fn main () {
*/
use cast;
use cmp;
use container::Container;
use int;
use iter::{Iterator, range, range_step};
use iter::{Iterator, range};
use local_data;
use prelude::*;
use str;
use sys;
use u32;
use u64;
use uint;
use vec;
use libc::size_t;
pub use self::isaac::{IsaacRng, Isaac64Rng};
pub use self::os::OSRng;
pub mod distributions;
pub mod isaac;
pub mod os;
pub mod reader;
/// A type that can be randomly generated using an Rng
pub trait Rand {
@ -233,15 +233,6 @@ impl<T: Rand + 'static> Rand for @T {
fn rand<R: Rng>(rng: &mut R) -> @T { @rng.gen() }
}
#[abi = "cdecl"]
pub mod rustrt {
use libc::size_t;
extern {
pub fn rand_gen_seed(buf: *mut u8, sz: size_t);
}
}
/// A value with a particular weight compared to other values
pub struct Weighted<T> {
/// The numerical weight of this item
@ -252,7 +243,8 @@ pub struct Weighted<T> {
/// A random number generator
pub trait Rng {
/// Return the next random u32.
/// Return the next random u32. This rarely needs to be called
/// directly, prefer `r.gen()` to `r.next_u32()`.
///
/// By default this is implemented in terms of `next_u64`. An
/// implementation of this trait must provide at least one of
@ -261,7 +253,8 @@ pub trait Rng {
self.next_u64() as u32
}
/// Return the next random u64.
/// Return the next random u64. This rarely needs to be called
/// directly, prefer `r.gen()` to `r.next_u64()`.
///
/// By default this is implemented in terms of `next_u32`. An
/// implementation of this trait must provide at least one of
@ -270,6 +263,76 @@ pub trait Rng {
(self.next_u32() as u64 << 32) | (self.next_u32() as u64)
}
/// Fill `dest` with random data.
///
/// This has a default implementation in terms of `next_u64` and
/// `next_u32`, but should be overriden by implementations that
/// offer a more efficient solution than just calling those
/// methods repeatedly.
///
/// This method does *not* have a requirement to bear any fixed
/// relationship to the other methods, for example, it does *not*
/// have to result in the same output as progressively filling
/// `dest` with `self.gen::<u8>()`, and any such behaviour should
/// not be relied upon.
///
/// This method should guarantee that `dest` is entirely filled
/// with new data, and may fail if this is impossible
/// (e.g. reading past the end of a file that is being used as the
/// source of randomness).
///
/// # Example
///
/// ~~~{.rust}
/// use std::rand::{task_rng, Rng};
///
/// fn main() {
/// let mut v = [0u8, .. 13579];
/// task_rng().fill_bytes(v);
/// printfln!(v);
/// }
/// ~~~
fn fill_bytes(&mut self, mut dest: &mut [u8]) {
// this relies on the lengths being transferred correctly when
// transmuting between vectors like this.
let as_u64: &mut &mut [u64] = unsafe { cast::transmute(&mut dest) };
for dest in as_u64.mut_iter() {
*dest = self.next_u64();
}
// the above will have filled up the vector as much as
// possible in multiples of 8 bytes.
let mut remaining = dest.len() % 8;
// space for a u32
if remaining >= 4 {
let as_u32: &mut &mut [u32] = unsafe { cast::transmute(&mut dest) };
as_u32[as_u32.len() - 1] = self.next_u32();
remaining -= 4;
}
// exactly filled
if remaining == 0 { return }
// now we know we've either got 1, 2 or 3 spots to go,
// i.e. exactly one u32 is enough.
let rand = self.next_u32();
let remaining_index = dest.len() - remaining;
match dest.mut_slice_from(remaining_index) {
[ref mut a] => {
*a = rand as u8;
}
[ref mut a, ref mut b] => {
*a = rand as u8;
*b = (rand >> 8) as u8;
}
[ref mut a, ref mut b, ref mut c] => {
*a = rand as u8;
*b = (rand >> 8) as u8;
*c = (rand >> 16) as u8;
}
_ => fail2!("Rng.fill_bytes: the impossible occurred: remaining != 1, 2 or 3")
}
}
/// Return a random value of a Rand type.
///
@ -630,11 +693,9 @@ impl XorShiftRng {
// specific size, so we can just use a fixed buffer.
let mut s = [0u8, ..16];
loop {
do s.as_mut_buf |p, sz| {
unsafe {
rustrt::rand_gen_seed(p, sz as size_t);
}
}
let mut r = OSRng::new();
r.fill_bytes(s);
if !s.iter().all(|x| *x == 0) {
break;
}
@ -660,15 +721,10 @@ impl XorShiftRng {
/// Create a new random seed of length `n`.
pub fn seed(n: uint) -> ~[u8] {
#[fixed_stack_segment]; #[inline(never)];
unsafe {
let mut s = vec::from_elem(n as uint, 0_u8);
do s.as_mut_buf |p, sz| {
rustrt::rand_gen_seed(p, sz as size_t)
}
s
}
let mut s = vec::from_elem(n as uint, 0_u8);
let mut r = OSRng::new();
r.fill_bytes(s);
s
}
// used to make space in TLS for a random number generator
@ -719,6 +775,14 @@ mod test {
use option::{Option, Some};
use super::*;
#[test]
fn test_fill_bytes_default() {
let mut r = weak_rng();
let mut v = [0u8, .. 100];
r.fill_bytes(v);
}
#[test]
fn test_gen_integer_range() {
let mut r = rng();

193
src/libstd/rand/os.rs Normal file

@ -0,0 +1,193 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Interfaces to the operating system provided random number
//! generators.
use rand::Rng;
use ops::Drop;
#[cfg(unix)]
use rand::reader::ReaderRng;
#[cfg(unix)]
use rt::io::{file, Open, Read};
#[cfg(windows)]
use ptr;
#[cfg(windows)]
use cast;
#[cfg(windows)]
use libc::{GetLastError, FALSE};
/// A random number generator that retrieves randomness straight from
/// the operating system. On Unix-like systems this reads from
/// `/dev/urandom`, on Windows this uses `CryptGenRandom`.
///
/// This does not block.
#[cfg(unix)]
pub struct OSRng {
priv inner: ReaderRng<file::FileStream>
}
/// A random number generator that retrieves randomness straight from
/// the operating system. On Unix-like systems this reads from
/// `/dev/urandom`, on Windows this uses `CryptGenRandom`.
///
/// This does not block.
///
/// XXX: it is unlikely that this is threadsafe with the use of
/// GetLastError.
#[cfg(windows)]
pub struct OSRng {
priv hcryptprov: raw::HCRYPTPROV
}
impl OSRng {
/// Create a new `OSRng`.
#[cfg(unix)]
pub fn new() -> OSRng {
let reader = file::open(& &"/dev/urandom", Open, Read).expect("Error opening /dev/urandom");
let reader_rng = ReaderRng::new(reader);
OSRng { inner: reader_rng }
}
/// Create a new `OSRng`.
#[cfg(windows)]
pub fn new() -> OSRng {
let hcp = ptr::mut_null();
// TODO these two 0 constants are incorrect!
if unsafe { raw::CryptAcquireContext(hcp, ptr::null(), ptr::null(), 0, 0); } == FALSE {
fail!("CryptAcquireContext failed with error %u", unsafe {GetLastError()})
}
OSRng { hcryptprov: hcp }
}
}
#[cfg(unix)]
impl Rng for OSRng {
fn next_u32(&mut self) -> u32 {
self.inner.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.inner.next_u64()
}
fn fill_bytes(&mut self, v: &mut [u8]) {
self.inner.fill_bytes(v)
}
}
#[cfg(windows)]
impl Rng for OSRng {
fn next_u32(&mut self) -> u32 {
let mut v = [0u8, .. 4];
self.fill_bytes(v);
unsafe { cast::transmute(v) }
}
fn next_u64(&mut self) -> u64 {
let mut v = [0u8, .. 8];
self.fill_bytes(v);
unsafe { cast::transmute(v) }
}
fn fill_bytes(&mut self, v: &mut [u8]) {
if unsafe { raw::CryptGenRandom(self.hcryptprov, v.len(), v.unsafe_mut_ref(0)) } == FALSE {
fail!("CryptGenRandom failed with error %u", unsafe {GetLastError()})
}
}
}
impl Drop for OSRng {
#[cfg(unix)]
fn drop(&mut self) {
// ensure that OSRng is not implicitly copyable on all
// platforms, for consistency.
}
#[cfg(windows)]
fn drop(&mut self) {
// TODO this 0 means?
if unsafe { raw::CryptReleaseContext(self.hcryptprov, 0)} == FALSE {
fail!("CryptReleaseContext failed with error %u", unsafe {GetLastError()})
}
}
}
#[abi = "cdecl"]
#[cfg(windows)]
mod raw {
use libc::{LPCTSTR, DWORD, BOOL, BYTE};
enum HCRYPTPROV_opaque {}
pub type HCRYPTPROV = *CRYPTPROV;
extern {
pub fn CryptAcquireContext(phProv: *mut HCRYPTPROV,
pszContainer: LPCTSTR, pszProvider: LPCTSTR,
dwProvType: DWORD, dwFlags: DWORD) -> BOOL;
pub fn CryptGenRandom(hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: *mut BYTE) -> BOOL;
pub fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) -> BOOL;
}
}
#[cfg(test)]
mod test {
use super::*;
use rand::Rng;
#[test]
fn test_os_rng() {
let mut r = OSRng::new();
r.next_u32();
r.next_u64();
let mut v = [0u8, .. 1000];
r.fill_bytes(v);
}
#[test]
fn test_os_rng_tasks() {
use task;
use comm;
use comm::{GenericChan, GenericPort};
use option::{None, Some};
use iter::{Iterator, range};
use vec::{ImmutableVector, OwnedVector};
let mut chans = ~[];
for _ in range(0, 20) {
let (p, c) = comm::stream();
chans.push(c);
do task::spawn_with(p) |p| {
// wait until all the tasks are ready to go.
p.recv();
// deschedule to attempt to interleave things as much
// as possible (XXX: is this a good test?)
let mut r = OSRng::new();
task::deschedule();
let mut v = [0u8, .. 1000];
for _ in range(0, 100) {
r.next_u32();
task::deschedule();
r.next_u64();
task::deschedule();
r.fill_bytes(v);
task::deschedule();
}
}
}
// start all the tasks
for c in chans.iter() {
c.send(())
}
}
}

94
src/libstd/rand/reader.rs Normal file

@ -0,0 +1,94 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use rt::io::Reader;
use rt::io::ReaderByteConversions;
use rand::Rng;
/// An RNG that reads random bytes straight from a `Reader`. This will
/// work best with an infinite reader, but this is not required. The
/// semantics of reading past the end of the reader are the same as
/// those of the `read` method of the inner `Reader`.
pub struct ReaderRng<R> {
priv reader: R
}
impl<R: Reader> ReaderRng<R> {
/// Create a new `ReaderRng` from a `Reader`.
pub fn new(r: R) -> ReaderRng<R> {
ReaderRng {
reader: r
}
}
}
impl<R: Reader> Rng for ReaderRng<R> {
fn next_u32(&mut self) -> u32 {
// XXX which is better: consistency between big/little-endian
// platforms, or speed.
if cfg!(target_endian="little") {
self.reader.read_le_u32_()
} else {
self.reader.read_be_u32_()
}
}
fn next_u64(&mut self) -> u64 {
if cfg!(target_endian="little") {
self.reader.read_le_u64_()
} else {
self.reader.read_be_u64_()
}
}
fn fill_bytes(&mut self, v: &mut [u8]) {
// XXX: check that we filled `v``
let _n = self.reader.read(v);
}
}
#[cfg(test)]
mod test {
use super::*;
use rt::io::mem::MemReader;
use cast;
#[test]
fn test_reader_rng_u64() {
// transmute from the target to avoid endianness concerns.
let v = ~[1u64, 2u64, 3u64];
let bytes: ~[u8] = unsafe {cast::transmute(v)};
let mut rng = ReaderRng::new(MemReader::new(bytes));
assert_eq!(rng.next_u64(), 1);
assert_eq!(rng.next_u64(), 2);
assert_eq!(rng.next_u64(), 3);
}
#[test]
fn test_reader_rng_u32() {
// transmute from the target to avoid endianness concerns.
let v = ~[1u32, 2u32, 3u32];
let bytes: ~[u8] = unsafe {cast::transmute(v)};
let mut rng = ReaderRng::new(MemReader::new(bytes));
assert_eq!(rng.next_u32(), 1);
assert_eq!(rng.next_u32(), 2);
assert_eq!(rng.next_u32(), 3);
}
#[test]
fn test_reader_rng_fill_bytes() {
let v = [1u8, 2, 3, 4, 5, 6, 7, 8];
let mut w = [0u8, .. 8];
let mut rng = ReaderRng::new(MemReader::new(v.to_owned()));
rng.fill_bytes(w);
assert_eq!(v, w);
}
}

@ -140,7 +140,7 @@ impl Scheduler {
cleanup_job: None,
run_anything: run_anything,
friend_handle: friend,
rng: XorShiftRng::new(),
rng: new_sched_rng(),
idle_callback: None,
yield_check_count: 0,
steal_for_yield: false
@ -844,6 +844,60 @@ impl ClosureConverter for UnsafeTaskReceiver {
fn to_fn(self) -> &fn(&mut Scheduler, ~Task) { unsafe { transmute(self) } }
}
// On unix, we read randomness straight from /dev/urandom, but the
// default constructor of an XorShiftRng does this via io::file, which
// relies on the scheduler existing, so we have to manually load
// randomness. Windows has its own C API for this, so we don't need to
// worry there.
#[cfg(windows)]
fn new_sched_rng() -> XorShiftRng {
XorShiftRng::new()
}
#[cfg(unix)]
#[fixed_stack_segment] #[inline(never)]
fn new_sched_rng() -> XorShiftRng {
use libc;
use sys;
use c_str::ToCStr;
use ptr::RawPtr;
use vec::MutableVector;
use iter::Iterator;
// XXX: this could use io::native::file, when it works.
let file = do "/dev/urandom".with_c_str |name| {
do "r".with_c_str |mode| {
unsafe { libc::fopen(name, mode) }
}
};
if file.is_null() {
rtabort!("could not open /dev/urandom for reading.")
}
let mut seeds = [0u32, .. 4];
loop {
let nbytes = do seeds.as_mut_buf |buf, len| {
unsafe {
libc::fread(buf as *mut libc::c_void,
sys::size_of::<u32>() as libc::size_t,
len as libc::size_t,
file)
}
};
rtassert!(nbytes == seeds.len() as libc::size_t);
if !seeds.iter().all(|x| *x == 0) {
break;
}
}
// XXX: do we need to guarantee that this is closed with a finally
// block (is that even possible without a scheduler?), or do we
// know that the only way that we can fail here is `abort`ing?
unsafe {libc::fclose(file);}
XorShiftRng::new_seeded(seeds[0], seeds[1], seeds[2], seeds[3])
}
#[cfg(test)]
mod test {
extern mod extra;