Merge pull request #495 from vks/pareto
Implement Pareto distribution
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1aa6398..c9d235c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,19 @@
You may also find the [Update Guide](UPDATING.md) useful.
+## [0.5.1] - Unreleased
+
+### Platform support and `OsRng`
+- Remove blanket Unix implementation. (#484)
+- Remove Wasm unimplemented stub. (#484)
+- Dragonfly BSD: read from `/dev/random`. (#484)
+- Bitrig: use `getentropy` like OpenBSD. (#484)
+- Solaris: (untested) use `getrandom` if available, otherwise `/dev/random`. (#484)
+- Emscripten, `stdweb`: split the read up in chunks. (#484)
+- Emscripten, Haiku: don't do an extra blocking read from `/dev/random`. (#484)
+- Linux, NetBSD, Solaris: read in blocking mode on first use in `fill_bytes`. (#484)
+- Fuchsia, CloudABI: fix compilation (broken in Rand 0.5). (#484)
+
## [0.5.0] - 2018-05-21
### Crate features and organisation
diff --git a/README.md b/README.md
index d897387..75950ed 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@
// type, range, distribution, etc.
let mut rng = thread_rng();
if rng.gen() { // random bool
- let x: f64 = rng.gen(); // random number in range (0, 1)
+ let x: f64 = rng.gen(); // random number in range [0, 1)
println!("x is: {}", x);
let ch = rng.gen::<char>(); // Sometimes you need type annotation
println!("char is: {}", ch);
@@ -108,7 +108,7 @@
## Crate Features
-Rand is built with only the `std` feature anabled by default. The following
+Rand is built with only the `std` feature enabled by default. The following
optional features are available:
- `alloc` can be used instead of `std` to provide `Vec` and `Box`.
@@ -116,7 +116,8 @@
- `log` enables some logging via the `log` crate.
- `nightly` enables all unstable features (`i128_support`).
- `serde1` enables serialization for some types, via Serde version 1.
-- `stdweb` enables support for `OsRng` on WASM via stdweb.
+- `stdweb` enables support for `OsRng` on `wasm-unknown-unknown` via `stdweb`
+ combined with `cargo-web`.
`no_std` mode is activated by setting `default-features = false`; this removes
functionality depending on `std`:
diff --git a/benches/misc.rs b/benches/misc.rs
index d3c60c3..a1822a5 100644
--- a/benches/misc.rs
+++ b/benches/misc.rs
@@ -172,6 +172,17 @@
}
#[bench]
+fn gen_1k_gen_array(b: &mut Bencher) {
+ let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
+ b.iter(|| {
+ // max supported array length is 32!
+ let v: [[u64; 32]; 4] = rng.gen();
+ v
+ });
+ b.bytes = 1024;
+}
+
+#[bench]
fn gen_1k_fill(b: &mut Bencher) {
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut buf = [0u64; 128];
diff --git a/src/lib.rs b/src/lib.rs
index 3723754..eb23f86 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -111,7 +111,7 @@
//! let mut rng = thread_rng();
//!
//! if rng.gen() { // random bool
-//! let x: f64 = rng.gen(); // random number in range (0, 1)
+//! let x: f64 = rng.gen(); // random number in range [0, 1)
//! println!("x is: {}", x);
//! let ch = rng.gen::<char>(); // using type annotation
//! println!("char is: {}", ch);
diff --git a/src/rngs/os.rs b/src/rngs/os.rs
index 2239d45..01fdfb0 100644
--- a/src/rngs/os.rs
+++ b/src/rngs/os.rs
@@ -24,60 +24,96 @@
/// not entirely theoretical, for `OsRng` to fail. In such cases [`EntropyRng`]
/// falls back on a good alternative entropy source.
///
-/// `OsRng` usually does not block. On some systems, and notably virtual
-/// machines, it may block very early in the init process, when the OS CSPRNG
-/// has not yet been seeded.
-///
/// `OsRng::new()` is guaranteed to be very cheap (after the first successful
/// call), and will never consume more than one file handle per process.
///
/// # Platform sources
///
-/// - Linux, Android: reads from the `getrandom(2)` system call if available,
-/// otherwise from `/dev/urandom`.
-/// - macOS, iOS: calls `SecRandomCopyBytes`.
-/// - Windows: calls `RtlGenRandom`.
-/// - WASM (with `stdweb` feature): calls `window.crypto.getRandomValues` in
-/// browsers, and in Node.js `require("crypto").randomBytes`.
-/// - Emscripten: reads from emulated `/dev/urandom`, which maps to the same
-/// interfaces as `stdweb`, but falls back to the insecure `Math.random()` if
-/// unavailable.
-/// - OpenBSD: calls `getentropy(2)`.
-/// - FreeBSD: uses the `kern.arandom` `sysctl(2)` mib.
-/// - Fuchsia: calls `cprng_draw`.
-/// - Redox: reads from `rand:` device.
-/// - CloudABI: calls `random_get`.
-/// - Other Unix-like systems: reads directly from `/dev/urandom`.
+/// | OS | interface
+/// |------------------|---------------------------------------------------------
+/// | Linux, Android | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after reading from `/dev/random` once
+/// | Windows | [`RtlGenRandom`][3]
+/// | macOS, iOS | [`SecRandomCopyBytes`][4]
+/// | FreeBSD | [`kern.arandom`][5]
+/// | OpenBSD, Bitrig | [`getentropy`][6]
+/// | NetBSD | [`/dev/urandom`][7] after reading from `/dev/random` once
+/// | Dragonfly BSD | [`/dev/random`][8]
+/// | Solaris, illumos | [`getrandom`][9] system call if available, otherwise [`/dev/random`][10]
+/// | Fuchsia OS | [`cprng_draw`][11]
+/// | Redox | [`rand:`][12]
+/// | CloudABI | [`random_get`][13]
+/// | Haiku | `/dev/random` (identical to `/dev/urandom`)
+/// | Web browsers | [`Crypto.getRandomValues`][14] (see [Support for WebAssembly and ams.js][14])
+/// | Node.js | [`crypto.randomBytes`][15] (see [Support for WebAssembly and ams.js][16])
///
-/// ## Notes on Unix `/dev/urandom`
+/// Rand doesn't have a blanket implementation for all Unix-like operating
+/// systems that reads from `/dev/urandom`. This ensures all supported operating
+/// systems are using the recommended interface and respect maximum buffer
+/// sizes.
///
-/// Many Unix systems provide `/dev/random` as well as `/dev/urandom`. On all
-/// modern systems these two interfaces offer identical quality, with the
-/// difference that on some systems `/dev/random` may block. This is a dated
-/// design, and `/dev/urandom` is preferred by cryptography experts.
-/// See [Myths about urandom](https://www.2uo.de/myths-about-urandom/).
+/// ## Support for WebAssembly and ams.js
///
-/// On some systems reading from `/dev/urandom` “may return data prior to the
-/// entropy pool being initialized”. I.e., early in the boot process, and
-/// especially on virtual machines, `/dev/urandom` may return data that is less
-/// random. As a countermeasure we try to do a single read from `/dev/random` in
-/// non-blocking mode. If the OS RNG is not yet properly seeded, we will get an
-/// error. Because we keep one file descriptor to `/dev/urandom` open when
-/// succesful, this is only a small one-time cost.
+/// The three Emscripten targets `asmjs-unknown-emscripten`,
+/// `wasm32-unknown-emscripten` and `wasm32-experimental-emscripten` use
+/// Emscripten's emulation of `/dev/random` on web browsers and Node.js.
+/// Unfortunately it falls back to the insecure `Math.random()` if a browser
+/// doesn't support [`Crypto.getRandomValues`][12].
+///
+/// The bare Wasm target `wasm32-unknown-unknown` tries to call the javascript
+/// methods directly, using `stdweb` in combination with `cargo-web`.
+/// `wasm-bindgen` is not yet supported.
+///
+/// ## Early boot
+///
+/// It is possible that early in the boot process the OS hasn't had enough time
+/// yet to collect entropy to securely seed its RNG, especially on virtual
+/// machines.
+///
+/// Some operating systems always block the thread until the RNG is securely
+/// seeded. This can take anywhere from a few seconds to more than a minute.
+/// Others make a best effort to use a seed from before the shutdown and don't
+/// document much.
+///
+/// A few, Linux, NetBSD and Solaris, offer a choice between blocking, and
+/// getting an error. With `try_fill_bytes` we choose to get the error
+/// ([`ErrorKind::NotReady`]), while the other methods use a blocking interface.
+///
+/// On Linux (when the `genrandom` system call is not available) and on NetBSD
+/// reading from `/dev/urandom` never blocks, even when the OS hasn't collected
+/// enough entropy yet. As a countermeasure we try to do a single read from
+/// `/dev/random` until we know the OS RNG is initialized (and store this in a
+/// global static).
///
/// # Panics
///
-/// `OsRng` is extremely unlikely to fail if `OsRng::new()` was succesfull. But
-/// in case it does fail, only [`try_fill_bytes`] is able to report the cause.
-/// Depending on the error the other [`RngCore`] methods will retry several
-/// times, and panic in case the error remains.
+/// `OsRng` is extremely unlikely to fail if `OsRng::new()`, and one read from
+/// it, where succesfull. But in case it does fail, only [`try_fill_bytes`] is
+/// able to report the cause. Depending on the error the other [`RngCore`]
+/// methods will retry several times, and panic in case the error remains.
///
/// [`EntropyRng`]: struct.EntropyRng.html
/// [`RngCore`]: ../trait.RngCore.html
/// [`try_fill_bytes`]: ../trait.RngCore.html#method.tymethod.try_fill_bytes
+/// [`ErrorKind::NotReady`]: ../enum.ErrorKind.html#variant.NotReady
+///
+/// [1]: http://man7.org/linux/man-pages/man2/getrandom.2.html
+/// [2]: http://man7.org/linux/man-pages/man4/urandom.4.html
+/// [3]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx
+/// [4]: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc
+/// [5]: https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4
+/// [6]: https://man.openbsd.org/getentropy.2
+/// [7]: http://netbsd.gw.com/cgi-bin/man-cgi?random+4+NetBSD-current
+/// [8]: https://leaf.dragonflybsd.org/cgi/web-man?command=random§ion=4
+/// [9]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html
+/// [10]: https://docs.oracle.com/cd/E86824_01/html/E54777/random-7d.html
+/// [11]: https://fuchsia.googlesource.com/zircon/+/HEAD/docs/syscalls/cprng_draw.md
+/// [12]: https://github.com/redox-os/randd/blob/master/src/main.rs
+/// [13]: https://github.com/NuxiNL/cloudabi/blob/v0.20/cloudabi.txt#L1826
+/// [14]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
+/// [15]: https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback
+/// [16]: #support-for-webassembly-and-amsjs
-#[allow(unused)] // not used by all targets
#[derive(Clone)]
pub struct OsRng(imp::OsRng);
@@ -117,6 +153,11 @@
let mut err_count = 0;
let mut error_logged = false;
+ // Maybe block until the OS RNG is initialized
+ let mut read = 0;
+ if let Ok(n) = self.0.test_initialized(dest, true) { read = n };
+ let dest = &mut dest[read..];
+
loop {
if let Err(e) = self.try_fill_bytes(dest) {
if err_count >= RETRY_LIMIT {
@@ -153,29 +194,146 @@
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- self.0.try_fill_bytes(dest)
+ // Some systems do not support reading 0 random bytes.
+ // (And why waste a system call?)
+ if dest.len() == 0 { return Ok(()); }
+
+ let read = self.0.test_initialized(dest, false)?;
+ let dest = &mut dest[read..];
+
+ let max = self.0.max_chunk_size();
+ if dest.len() <= max {
+ trace!("OsRng: reading {} bytes via {}",
+ dest.len(), self.0.method_str());
+ } else {
+ trace!("OsRng: reading {} bytes via {} in {} chunks of {} bytes",
+ dest.len(), self.0.method_str(), (dest.len() + max) / max, max);
+ }
+ for slice in dest.chunks_mut(max) {
+ self.0.fill_chunk(slice)?;
+ }
+ Ok(())
}
}
-#[cfg(all(unix,
- not(target_os = "cloudabi"),
- not(target_os = "freebsd"),
- not(target_os = "fuchsia"),
- not(target_os = "ios"),
- not(target_os = "macos"),
- not(target_os = "openbsd"),
- not(target_os = "redox")))]
-mod imp {
- extern crate libc;
+trait OsRngImpl where Self: Sized {
+ // Create a new `OsRng` platform interface.
+ fn new() -> Result<Self, Error>;
+
+ // Fill a chunk with random bytes.
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error>;
+
+ // Test whether the OS RNG is initialized. This method may not be possible
+ // to support cheaply (or at all) on all operating systems.
+ //
+ // If `blocking` is set, this will cause the OS the block execution until
+ // its RNG is initialized.
+ //
+ // Random values that are read while this are stored in `dest`, the amount
+ // of read bytes is returned.
+ fn test_initialized(&mut self, _dest: &mut [u8], _blocking: bool)
+ -> Result<usize, Error> { Ok(0) }
+
+ // Maximum chunk size supported.
+ fn max_chunk_size(&self) -> usize { ::core::usize::MAX }
+
+ // Name of the OS interface (used for logging).
+ fn method_str(&self) -> &'static str;
+}
+
+
+
+
+// Helper functions to read from a random device such as `/dev/urandom`.
+//
+// All instances use a single internal file handle, to prevent possible
+// exhaustion of file descriptors.
+#[cfg(any(target_os = "linux", target_os = "android",
+ target_os = "netbsd", target_os = "dragonfly",
+ target_os = "solaris", target_os = "redox",
+ target_os = "haiku", target_os = "emscripten"))]
+mod random_device {
use {Error, ErrorKind};
- use std::fs::{OpenOptions, File};
- use std::os::unix::fs::OpenOptionsExt;
+ use std::fs::File;
use std::io;
use std::io::Read;
use std::sync::{Once, Mutex, ONCE_INIT};
+ // TODO: remove outer Option when `Mutex::new(None)` is a constant expression
+ static mut READ_RNG_FILE: Option<Mutex<Option<File>>> = None;
+ static READ_RNG_ONCE: Once = ONCE_INIT;
+
+ #[allow(unused)]
+ pub fn open<F>(path: &'static str, open_fn: F) -> Result<(), Error>
+ where F: Fn(&'static str) -> Result<File, io::Error>
+ {
+ READ_RNG_ONCE.call_once(|| {
+ unsafe { READ_RNG_FILE = Some(Mutex::new(None)) }
+ });
+
+ // We try opening the file outside the `call_once` fn because we cannot
+ // clone the error, thus we must retry on failure.
+
+ let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() };
+ let mut guard = mutex.lock().unwrap();
+ if (*guard).is_none() {
+ info!("OsRng: opening random device {}", path);
+ let file = open_fn(path).map_err(map_err)?;
+ *guard = Some(file);
+ };
+ Ok(())
+ }
+
+ pub fn read(dest: &mut [u8]) -> Result<(), Error> {
+ // We expect this function only to be used after `random_device::open`
+ // was succesful. Therefore we can assume that our memory was set with a
+ // valid object.
+ let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() };
+ let mut guard = mutex.lock().unwrap();
+ let file = (*guard).as_mut().unwrap();
+
+ // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`.
+ file.read_exact(dest).map_err(|err| {
+ Error::with_cause(ErrorKind::Unavailable,
+ "error reading random device", err)
+ })
+
+ }
+
+ pub fn map_err(err: io::Error) -> Error {
+ match err.kind() {
+ io::ErrorKind::Interrupted =>
+ Error::new(ErrorKind::Transient, "interrupted"),
+ io::ErrorKind::WouldBlock =>
+ Error::with_cause(ErrorKind::NotReady,
+ "OS RNG not yet seeded", err),
+ _ => Error::with_cause(ErrorKind::Unavailable,
+ "error while opening random device", err)
+ }
+ }
+}
+
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+mod imp {
+ extern crate libc;
+
+ use {Error, ErrorKind};
+ use super::random_device;
+ use super::OsRngImpl;
+
+ use std::io;
+ use std::io::Read;
+ use std::fs::{File, OpenOptions};
+ use std::os::unix::fs::OpenOptionsExt;
+ use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
+ use std::sync::{Once, ONCE_INIT};
+
#[derive(Clone, Debug)]
- pub struct OsRng(OsRngMethod);
+ pub struct OsRng {
+ method: OsRngMethod,
+ initialized: bool,
+ }
#[derive(Clone, Debug)]
enum OsRngMethod {
@@ -183,80 +341,107 @@
RandomDevice,
}
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> {
if is_getrandom_available() {
- return Ok(OsRng(OsRngMethod::GetRandom));
+ return Ok(OsRng { method: OsRngMethod::GetRandom,
+ initialized: false });
}
-
- open_dev_random()?;
- Ok(OsRng(OsRngMethod::RandomDevice))
+ random_device::open("/dev/urandom", &|p| File::open(p))?;
+ Ok(OsRng { method: OsRngMethod::RandomDevice, initialized: false })
}
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- match self.0 {
- OsRngMethod::GetRandom => getrandom_try_fill(dest),
- OsRngMethod::RandomDevice => dev_random_try_fill(dest),
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ match self.method {
+ OsRngMethod::GetRandom => getrandom_try_fill(dest, false),
+ OsRngMethod::RandomDevice => random_device::read(dest),
+ }
+ }
+
+ fn test_initialized(&mut self, dest: &mut [u8], blocking: bool)
+ -> Result<usize, Error>
+ {
+ static OS_RNG_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT;
+ if !self.initialized {
+ self.initialized = OS_RNG_INITIALIZED.load(Ordering::Relaxed);
+ }
+ if self.initialized { return Ok(0); }
+
+ let result = match self.method {
+ OsRngMethod::GetRandom => {
+ getrandom_try_fill(dest, blocking)?;
+ Ok(dest.len())
+ }
+ OsRngMethod::RandomDevice => {
+ info!("OsRng: testing random device /dev/random");
+ let mut file = OpenOptions::new()
+ .read(true)
+ .custom_flags(if blocking { 0 } else { libc::O_NONBLOCK })
+ .open("/dev/random")
+ .map_err(random_device::map_err)?;
+ file.read(&mut dest[..1]).map_err(random_device::map_err)?;
+ Ok(1)
+ }
+ };
+ OS_RNG_INITIALIZED.store(true, Ordering::Relaxed);
+ self.initialized = true;
+ result
+ }
+
+ fn method_str(&self) -> &'static str {
+ match self.method {
+ OsRngMethod::GetRandom => "getrandom",
+ OsRngMethod::RandomDevice => "/dev/urandom",
}
}
}
- #[cfg(all(any(target_os = "linux", target_os = "android"),
- any(target_arch = "x86_64", target_arch = "x86",
+ #[cfg(target_arch = "x86_64")]
+ const NR_GETRANDOM: libc::c_long = 318;
+ #[cfg(target_arch = "x86")]
+ const NR_GETRANDOM: libc::c_long = 355;
+ #[cfg(target_arch = "arm")]
+ const NR_GETRANDOM: libc::c_long = 384;
+ #[cfg(target_arch = "aarch64")]
+ const NR_GETRANDOM: libc::c_long = 278;
+ #[cfg(target_arch = "s390x")]
+ const NR_GETRANDOM: libc::c_long = 349;
+ #[cfg(target_arch = "powerpc")]
+ const NR_GETRANDOM: libc::c_long = 359;
+ #[cfg(target_arch = "mips")] // old ABI
+ const NR_GETRANDOM: libc::c_long = 4353;
+ #[cfg(target_arch = "mips64")]
+ const NR_GETRANDOM: libc::c_long = 5313;
+ #[cfg(not(any(target_arch = "x86_64", target_arch = "x86",
target_arch = "arm", target_arch = "aarch64",
target_arch = "s390x", target_arch = "powerpc",
target_arch = "mips", target_arch = "mips64")))]
- fn getrandom(buf: &mut [u8]) -> libc::c_long {
+ const NR_GETRANDOM: libc::c_long = 0;
+
+ fn getrandom(buf: &mut [u8], blocking: bool) -> libc::c_long {
extern "C" {
fn syscall(number: libc::c_long, ...) -> libc::c_long;
}
-
- #[cfg(target_arch = "x86_64")]
- const NR_GETRANDOM: libc::c_long = 318;
- #[cfg(target_arch = "x86")]
- const NR_GETRANDOM: libc::c_long = 355;
- #[cfg(target_arch = "arm")]
- const NR_GETRANDOM: libc::c_long = 384;
- #[cfg(target_arch = "aarch64")]
- const NR_GETRANDOM: libc::c_long = 278;
- #[cfg(target_arch = "s390x")]
- const NR_GETRANDOM: libc::c_long = 349;
- #[cfg(target_arch = "powerpc")]
- const NR_GETRANDOM: libc::c_long = 359;
- #[cfg(target_arch = "mips")] // old ABI
- const NR_GETRANDOM: libc::c_long = 4353;
- #[cfg(target_arch = "mips64")]
- const NR_GETRANDOM: libc::c_long = 5313;
-
const GRND_NONBLOCK: libc::c_uint = 0x0001;
+ if NR_GETRANDOM == 0 { return -1 };
+
unsafe {
- syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)
+ syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(),
+ if blocking { 0 } else { GRND_NONBLOCK })
}
}
- #[cfg(not(all(any(target_os = "linux", target_os = "android"),
- any(target_arch = "x86_64", target_arch = "x86",
- target_arch = "arm", target_arch = "aarch64",
- target_arch = "s390x", target_arch = "powerpc",
- target_arch = "mips", target_arch = "mips64"))))]
- fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
-
- fn getrandom_try_fill(dest: &mut [u8]) -> Result<(), Error> {
- trace!("OsRng: reading {} bytes via getrandom", dest.len());
+ fn getrandom_try_fill(dest: &mut [u8], blocking: bool) -> Result<(), Error> {
let mut read = 0;
- let len = dest.len();
- while read < len {
- let result = getrandom(&mut dest[read..]);
+ while read < dest.len() {
+ let result = getrandom(&mut dest[read..], blocking);
if result == -1 {
let err = io::Error::last_os_error();
let kind = err.kind();
if kind == io::ErrorKind::Interrupted {
continue;
} else if kind == io::ErrorKind::WouldBlock {
- // Potentially this would waste bytes, but since we use
- // /dev/urandom blocking only happens if not initialised.
- // Also, wasting the bytes in dest doesn't matter very much.
return Err(Error::with_cause(
ErrorKind::NotReady,
"getrandom not ready",
@@ -276,22 +461,16 @@
Ok(())
}
- #[cfg(all(any(target_os = "linux", target_os = "android"),
- any(target_arch = "x86_64", target_arch = "x86",
- target_arch = "arm", target_arch = "aarch64",
- target_arch = "s390x", target_arch = "powerpc",
- target_arch = "mips", target_arch = "mips64")))]
fn is_getrandom_available() -> bool {
- use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
- use std::sync::{Once, ONCE_INIT};
-
static CHECKER: Once = ONCE_INIT;
static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT;
+ if NR_GETRANDOM == 0 { return false };
+
CHECKER.call_once(|| {
debug!("OsRng: testing getrandom");
let mut buf: [u8; 0] = [];
- let result = getrandom(&mut buf);
+ let result = getrandom(&mut buf, false);
let available = if result == -1 {
let err = io::Error::last_os_error().raw_os_error();
err != Some(libc::ENOSYS)
@@ -304,105 +483,278 @@
AVAILABLE.load(Ordering::Relaxed)
}
+}
- #[cfg(not(all(any(target_os = "linux", target_os = "android"),
- any(target_arch = "x86_64", target_arch = "x86",
- target_arch = "arm", target_arch = "aarch64",
- target_arch = "s390x", target_arch = "powerpc",
- target_arch = "mips", target_arch = "mips64"))))]
- fn is_getrandom_available() -> bool { false }
- // TODO: remove outer Option when `Mutex::new(None)` is a constant expression
- static mut READ_RNG_FILE: Option<Mutex<Option<File>>> = None;
- static READ_RNG_ONCE: Once = ONCE_INIT;
+#[cfg(target_os = "netbsd")]
+mod imp {
+ use Error;
+ use super::random_device;
+ use super::OsRngImpl;
- // Note: all instances use a single internal file handle, to prevent
- // possible exhaustion of file descriptors.
- //
- // We do a single read from `/dev/random` in non-blocking mode. If the OS
- // RNG is not yet properly seeded, we will get an error, instead of silently
- // getting less random bytes, as `/dev/urandom` can return. Because we keep
- // `/dev/urandom` open when succesful, this is only a small one-time cost.
- fn open_dev_random() -> Result<(), Error> {
- fn map_err(err: io::Error) -> Error {
- match err.kind() {
- io::ErrorKind::Interrupted =>
- Error::new(ErrorKind::Transient, "interrupted"),
- io::ErrorKind::WouldBlock =>
- Error::with_cause(ErrorKind::NotReady,
- "OS RNG not yet seeded", err),
- _ => Error::with_cause(ErrorKind::Unavailable,
- "error while opening random device", err)
+ use std::fs::File;
+ use std::io::Read;
+ use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
+
+ #[derive(Clone, Debug)]
+ pub struct OsRng { initialized: bool }
+
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> {
+ random_device::open("/dev/urandom", &|p| File::open(p))?;
+ Ok(OsRng { initialized: false })
+ }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ random_device::read(dest)
+ }
+
+ // Read a single byte from `/dev/random` to determine if the OS RNG is
+ // already seeded. NetBSD always blocks if not yet ready.
+ fn test_initialized(&mut self, dest: &mut [u8], _blocking: bool)
+ -> Result<usize, Error>
+ {
+ static OS_RNG_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT;
+ if !self.initialized {
+ self.initialized = OS_RNG_INITIALIZED.load(Ordering::Relaxed);
+ }
+ if self.initialized { return Ok(0); }
+
+ info!("OsRng: testing random device /dev/random");
+ let mut file =
+ File::open("/dev/random").map_err(random_device::map_err)?;
+ file.read(&mut dest[..1]).map_err(random_device::map_err)?;
+
+ OS_RNG_INITIALIZED.store(true, Ordering::Relaxed);
+ self.initialized = true;
+ Ok(1)
+ }
+
+ fn method_str(&self) -> &'static str { "/dev/urandom" }
+ }
+}
+
+
+#[cfg(any(target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "emscripten"))]
+mod imp {
+ use Error;
+ use super::random_device;
+ use super::OsRngImpl;
+ use std::fs::File;
+
+ #[derive(Clone, Debug)]
+ pub struct OsRng();
+
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> {
+ random_device::open("/dev/random", &|p| File::open(p))?;
+ Ok(OsRng())
+ }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ random_device::read(dest)
+ }
+
+ #[cfg(target_os = "emscripten")]
+ fn max_chunk_size(&self) -> usize {
+ // `Crypto.getRandomValues` documents `dest` should be at most 65536
+ // bytes. `crypto.randomBytes` documents: "To minimize threadpool
+ // task length variation, partition large randomBytes requests when
+ // doing so as part of fulfilling a client request.
+ 65536
+ }
+
+ fn method_str(&self) -> &'static str { "/dev/random" }
+ }
+}
+
+
+// Read from `/dev/random`, with chunks of limited size (1040 bytes).
+// `/dev/random` uses the Hash_DRBG with SHA512 algorithm from NIST SP 800-90A.
+// `/dev/urandom` uses the FIPS 186-2 algorithm, which is considered less
+// secure. We choose to read from `/dev/random`.
+//
+// Since Solaris 11.3 the `getrandom` syscall is available. To make sure we can
+// compile on both Solaris and on OpenSolaris derivatives, that do not have the
+// function, we do a direct syscall instead of calling a library function.
+//
+// We have no way to differentiate between Solaris, illumos, SmartOS, etc.
+#[cfg(target_os = "solaris")]
+mod imp {
+ extern crate libc;
+
+ use {Error, ErrorKind};
+ use super::random_device;
+ use super::OsRngImpl;
+
+ use std::io;
+ use std::io::Read;
+ use std::fs::{File, OpenOptions};
+ use std::os::unix::fs::OpenOptionsExt;
+ use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
+
+ #[derive(Clone, Debug)]
+ pub struct OsRng {
+ method: OsRngMethod,
+ initialized: bool,
+ }
+
+ #[derive(Clone, Debug)]
+ enum OsRngMethod {
+ GetRandom,
+ RandomDevice,
+ }
+
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> {
+ if is_getrandom_available() {
+ return Ok(OsRng { method: OsRngMethod::GetRandom,
+ initialized: false });
+ }
+ let open = |p| OpenOptions::new()
+ .read(true)
+ .custom_flags(libc::O_NONBLOCK)
+ .open(p);
+ random_device::open("/dev/random", &open)?;
+ Ok(OsRng { method: OsRngMethod::RandomDevice, initialized: false })
+ }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ match self.method {
+ OsRngMethod::GetRandom => getrandom_try_fill(dest, false),
+ OsRngMethod::RandomDevice => random_device::read(dest),
}
}
- READ_RNG_ONCE.call_once(|| {
- unsafe { READ_RNG_FILE = Some(Mutex::new(None)) }
- });
-
- // We try opening the file outside the `call_once` fn because we cannot
- // clone the error, thus we must retry on failure.
-
- let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() };
- let mut guard = mutex.lock().unwrap();
- if (*guard).is_none() {
- {
- info!("OsRng: opening random device /dev/random");
- let mut file = OpenOptions::new()
- .read(true)
- .custom_flags(libc::O_NONBLOCK)
- .open("/dev/random")
- .map_err(map_err)?;
- let mut buf = [0u8; 1];
- file.read_exact(&mut buf).map_err(map_err)?;
+ fn test_initialized(&mut self, dest: &mut [u8], blocking: bool)
+ -> Result<usize, Error>
+ {
+ static OS_RNG_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT;
+ if !self.initialized {
+ self.initialized = OS_RNG_INITIALIZED.load(Ordering::Relaxed);
}
+ if self.initialized { return Ok(0); }
- info!("OsRng: opening random device /dev/urandom");
- let file = File::open("/dev/urandom").map_err(map_err)?;
- *guard = Some(file);
- };
+ let chunk_len = ::core::cmp::min(1024, dest.len());
+ let dest = &mut dest[..chunk_len];
+
+ match self.method {
+ OsRngMethod::GetRandom => getrandom_try_fill(dest, blocking)?,
+ OsRngMethod::RandomDevice => {
+ if blocking {
+ info!("OsRng: testing random device /dev/random");
+ // We already have a non-blocking handle, but now need a
+ // blocking one. Not much choice except opening it twice
+ let mut file = File::open("/dev/random")
+ .map_err(random_device::map_err)?;
+ file.read(dest).map_err(random_device::map_err)?;
+ } else {
+ self.fill_chunk(dest)?;
+ }
+ }
+ };
+ OS_RNG_INITIALIZED.store(true, Ordering::Relaxed);
+ self.initialized = true;
+ Ok(chunk_len)
+ }
+
+ fn max_chunk_size(&self) -> usize {
+ // The documentation says 1024 is the maximum for getrandom, but
+ // 1040 for /dev/random.
+ 1024
+ }
+
+ fn method_str(&self) -> &'static str {
+ match self.method {
+ OsRngMethod::GetRandom => "getrandom",
+ OsRngMethod::RandomDevice => "/dev/random",
+ }
+ }
+ }
+
+ fn getrandom(buf: &mut [u8], blocking: bool) -> libc::c_long {
+ extern "C" {
+ fn syscall(number: libc::c_long, ...) -> libc::c_long;
+ }
+
+ const SYS_GETRANDOM: libc::c_long = 143;
+ const GRND_NONBLOCK: libc::c_uint = 0x0001;
+ const GRND_RANDOM: libc::c_uint = 0x0002;
+
+ unsafe {
+ syscall(SYS_GETRANDOM, buf.as_mut_ptr(), buf.len(),
+ if blocking { 0 } else { GRND_NONBLOCK } | GRND_RANDOM)
+ }
+ }
+
+ fn getrandom_try_fill(dest: &mut [u8], blocking: bool) -> Result<(), Error> {
+ let result = getrandom(dest, blocking);
+ if result == -1 || result == 0 {
+ let err = io::Error::last_os_error();
+ let kind = err.kind();
+ if kind == io::ErrorKind::WouldBlock {
+ return Err(Error::with_cause(
+ ErrorKind::NotReady,
+ "getrandom not ready",
+ err,
+ ));
+ } else {
+ return Err(Error::with_cause(
+ ErrorKind::Unavailable,
+ "unexpected getrandom error",
+ err,
+ ));
+ }
+ } else if result != dest.len() as i64 {
+ return Err(Error::new(ErrorKind::Unavailable,
+ "unexpected getrandom error"));
+ }
Ok(())
}
- fn dev_random_try_fill(dest: &mut [u8]) -> Result<(), Error> {
- if dest.len() == 0 { return Ok(()); }
- trace!("OsRng: reading {} bytes from random device", dest.len());
+ fn is_getrandom_available() -> bool {
+ use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
+ use std::sync::{Once, ONCE_INIT};
- // We expect this function only to be used after `open_dev_random` was
- // succesful. Therefore we can assume that our memory was set with a
- // valid object.
- let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() };
- let mut guard = mutex.lock().unwrap();
- let file = (*guard).as_mut().unwrap();
- // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`.
- file.read_exact(dest).map_err(|err| {
- match err.kind() {
- ::std::io::ErrorKind::WouldBlock => Error::with_cause(
- ErrorKind::NotReady,
- "reading from random device would block", err),
- _ => Error::with_cause(ErrorKind::Unavailable,
- "error reading random device", err)
- }
- })
+ static CHECKER: Once = ONCE_INIT;
+ static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT;
+
+ CHECKER.call_once(|| {
+ debug!("OsRng: testing getrandom");
+ let mut buf: [u8; 0] = [];
+ let result = getrandom(&mut buf, false);
+ let available = if result == -1 {
+ let err = io::Error::last_os_error().raw_os_error();
+ err != Some(libc::ENOSYS)
+ } else {
+ true
+ };
+ AVAILABLE.store(available, Ordering::Relaxed);
+ info!("OsRng: using {}", if available { "getrandom" } else { "/dev/random" });
+ });
+
+ AVAILABLE.load(Ordering::Relaxed)
}
}
+
#[cfg(target_os = "cloudabi")]
mod imp {
extern crate cloudabi;
+ use std::io;
use {Error, ErrorKind};
+ use super::OsRngImpl;
#[derive(Clone, Debug)]
pub struct OsRng;
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Ok(OsRng)
- }
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> { Ok(OsRng) }
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- trace!("OsRng: reading {} bytes via cloadabi::random_get", dest.len());
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
let errno = unsafe { cloudabi::random_get(dest) };
if errno == cloudabi::errno::SUCCESS {
Ok(())
@@ -412,19 +764,23 @@
Err(Error::with_cause(
ErrorKind::Unavailable,
"random_get() system call failed",
- io::Error::from_raw_os_error(errno),
+ io::Error::from_raw_os_error(errno as i32),
))
}
}
+
+ fn method_str(&self) -> &'static str { "cloudabi::random_get" }
}
}
+
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod imp {
extern crate libc;
use {Error, ErrorKind};
-
+ use super::OsRngImpl;
+
use std::io;
use self::libc::{c_int, size_t};
@@ -442,14 +798,14 @@
count: size_t, bytes: *mut u8) -> c_int;
}
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Ok(OsRng)
- }
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- trace!("OsRng: reading {} bytes via SecRandomCopyBytes", dest.len());
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> { Ok(OsRng) }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
let ret = unsafe {
- SecRandomCopyBytes(kSecRandomDefault, dest.len() as size_t, dest.as_mut_ptr())
+ SecRandomCopyBytes(kSecRandomDefault,
+ dest.len() as size_t,
+ dest.as_mut_ptr())
};
if ret == -1 {
Err(Error::with_cause(
@@ -460,184 +816,157 @@
Ok(())
}
}
+
+ fn method_str(&self) -> &'static str { "SecRandomCopyBytes" }
}
}
+
#[cfg(target_os = "freebsd")]
mod imp {
extern crate libc;
use {Error, ErrorKind};
-
+ use super::OsRngImpl;
+
use std::ptr;
use std::io;
#[derive(Clone, Debug)]
pub struct OsRng;
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Ok(OsRng)
- }
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> { Ok(OsRng) }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
let mib = [libc::CTL_KERN, libc::KERN_ARND];
- trace!("OsRng: reading {} bytes via kern.arandom", dest.len());
- // kern.arandom permits a maximum buffer size of 256 bytes
- for s in dest.chunks_mut(256) {
- let mut s_len = s.len();
- let ret = unsafe {
- libc::sysctl(mib.as_ptr(), mib.len() as libc::c_uint,
- s.as_mut_ptr() as *mut _, &mut s_len,
- ptr::null(), 0)
- };
- if ret == -1 || s_len != s.len() {
- return Err(Error::with_cause(
- ErrorKind::Unavailable,
- "kern.arandom sysctl failed",
- io::Error::last_os_error()));
- }
+ let mut len = dest.len();
+ let ret = unsafe {
+ libc::sysctl(mib.as_ptr(), mib.len() as libc::c_uint,
+ dest.as_mut_ptr() as *mut _, &mut len,
+ ptr::null(), 0)
+ };
+ if ret == -1 || len != dest.len() {
+ return Err(Error::with_cause(
+ ErrorKind::Unavailable,
+ "kern.arandom sysctl failed",
+ io::Error::last_os_error()));
}
Ok(())
}
+
+ fn max_chunk_size(&self) -> usize { 256 }
+
+ fn method_str(&self) -> &'static str { "kern.arandom" }
}
}
-#[cfg(target_os = "openbsd")]
+
+#[cfg(any(target_os = "openbsd", target_os = "bitrig"))]
mod imp {
extern crate libc;
use {Error, ErrorKind};
-
+ use super::OsRngImpl;
+
use std::io;
#[derive(Clone, Debug)]
pub struct OsRng;
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Ok(OsRng)
- }
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- // getentropy(2) permits a maximum buffer size of 256 bytes
- for s in dest.chunks_mut(256) {
- trace!("OsRng: reading {} bytes via getentropy", s.len());
- let ret = unsafe {
- libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len())
- };
- if ret == -1 {
- return Err(Error::with_cause(
- ErrorKind::Unavailable,
- "getentropy failed",
- io::Error::last_os_error()));
- }
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> { Ok(OsRng) }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ let ret = unsafe {
+ libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len())
+ };
+ if ret == -1 {
+ return Err(Error::with_cause(
+ ErrorKind::Unavailable,
+ "getentropy failed",
+ io::Error::last_os_error()));
}
Ok(())
}
+
+ fn max_chunk_size(&self) -> usize { 256 }
+
+ fn method_str(&self) -> &'static str { "getentropy" }
}
}
+
#[cfg(target_os = "redox")]
mod imp {
- use {Error, ErrorKind};
+ use Error;
+ use super::random_device;
+ use super::OsRngImpl;
use std::fs::File;
- use std::io::Read;
- use std::io::ErrorKind::*;
- use std::sync::{Once, Mutex, ONCE_INIT};
#[derive(Clone, Debug)]
pub struct OsRng();
- // TODO: remove outer Option when `Mutex::new(None)` is a constant expression
- static mut READ_RNG_FILE: Option<Mutex<Option<File>>> = None;
- static READ_RNG_ONCE: Once = ONCE_INIT;
-
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- READ_RNG_ONCE.call_once(|| {
- unsafe { READ_RNG_FILE = Some(Mutex::new(None)) }
- });
-
- // We try opening the file outside the `call_once` fn because we cannot
- // clone the error, thus we must retry on failure.
-
- let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() };
- let mut guard = mutex.lock().unwrap();
- if (*guard).is_none() {
- info!("OsRng: opening random device 'rand:'");
- let file = File::open("rand:").map_err(|err| {
- match err.kind() {
- Interrupted => Error::new(ErrorKind::Transient, "interrupted"),
- WouldBlock => Error::with_cause(ErrorKind::NotReady,
- "opening random device would block", err),
- _ => Error::with_cause(ErrorKind::Unavailable,
- "error while opening random device", err)
- }
- })?;
- *guard = Some(file);
- };
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> {
+ random_device::open("rand:", &|p| File::open(p))?;
Ok(OsRng())
}
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- if dest.len() == 0 { return Ok(()); }
- trace!("OsRng: reading {} bytes from random device", dest.len());
-
- // Since we have an instance of Self, we can assume that our memory was
- // set with a valid object.
- let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() };
- let mut guard = mutex.lock().unwrap();
- let file = (*guard).as_mut().unwrap();
- // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`.
- file.read_exact(dest).map_err(|err| {
- Error::with_cause(ErrorKind::Unavailable,
- "error reading random device", err)
- })
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ random_device::read(dest)
}
+
+ fn method_str(&self) -> &'static str { "'rand:'" }
}
}
+
#[cfg(target_os = "fuchsia")]
mod imp {
extern crate fuchsia_zircon;
use {Error, ErrorKind};
-
- use std::io;
+ use super::OsRngImpl;
#[derive(Clone, Debug)]
pub struct OsRng;
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Ok(OsRng)
- }
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- for s in dest.chunks_mut(fuchsia_zircon::sys::ZX_CPRNG_DRAW_MAX_LEN) {
- trace!("OsRng: reading {} bytes via cprng_draw", s.len());
- let mut filled = 0;
- while filled < s.len() {
- match fuchsia_zircon::cprng_draw(&mut s[filled..]) {
- Ok(actual) => filled += actual,
- Err(e) => {
- return Err(Error::with_cause(
- ErrorKind::Unavailable,
- "cprng_draw failed",
- e));
- }
- };
- }
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> { Ok(OsRng) }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ let mut read = 0;
+ while read < dest.len() {
+ match fuchsia_zircon::cprng_draw(&mut dest[read..]) {
+ Ok(actual) => read += actual,
+ Err(e) => {
+ return Err(Error::with_cause(
+ ErrorKind::Unavailable,
+ "cprng_draw failed",
+ e.into_io_error()));
+ }
+ };
}
Ok(())
}
+
+ fn max_chunk_size(&self) -> usize {
+ fuchsia_zircon::sys::ZX_CPRNG_DRAW_MAX_LEN
+ }
+
+ fn method_str(&self) -> &'static str { "cprng_draw" }
}
}
+
#[cfg(windows)]
mod imp {
extern crate winapi;
use {Error, ErrorKind};
-
+ use super::OsRngImpl;
+
use std::io;
use self::winapi::shared::minwindef::ULONG;
@@ -647,51 +976,28 @@
#[derive(Clone, Debug)]
pub struct OsRng;
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Ok(OsRng)
- }
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
- // RtlGenRandom takes an ULONG (u32) for the length so we need to
- // split up the buffer.
- for slice in dest.chunks_mut(<ULONG>::max_value() as usize) {
- trace!("OsRng: reading {} bytes via RtlGenRandom", slice.len());
- let ret = unsafe {
- RtlGenRandom(slice.as_mut_ptr() as PVOID, slice.len() as ULONG)
- };
- if ret == 0 {
- return Err(Error::with_cause(
- ErrorKind::Unavailable,
- "couldn't generate random bytes",
- io::Error::last_os_error()));
- }
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> { Ok(OsRng) }
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ let ret = unsafe {
+ RtlGenRandom(dest.as_mut_ptr() as PVOID, dest.len() as ULONG)
+ };
+ if ret == 0 {
+ return Err(Error::with_cause(
+ ErrorKind::Unavailable,
+ "couldn't generate random bytes",
+ io::Error::last_os_error()));
}
Ok(())
}
+
+ fn max_chunk_size(&self) -> usize { <ULONG>::max_value() as usize }
+
+ fn method_str(&self) -> &'static str { "RtlGenRandom" }
}
}
-#[cfg(all(target_arch = "wasm32",
- not(target_os = "emscripten"),
- not(feature = "stdweb")))]
-mod imp {
- use {Error, ErrorKind};
-
- #[derive(Clone, Debug)]
- pub struct OsRng;
-
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
- Err(Error::new(ErrorKind::Unavailable,
- "not supported on WASM without stdweb"))
- }
-
- pub fn try_fill_bytes(&mut self, _v: &mut [u8]) -> Result<(), Error> {
- Err(Error::new(ErrorKind::Unavailable,
- "not supported on WASM without stdweb"))
- }
- }
-}
#[cfg(all(target_arch = "wasm32",
not(target_os = "emscripten"),
@@ -701,18 +1007,19 @@
use stdweb::unstable::TryInto;
use stdweb::web::error::Error as WebError;
use {Error, ErrorKind};
+ use super::OsRngImpl;
#[derive(Clone, Debug)]
- enum OsRngInner {
+ enum OsRngMethod {
Browser,
Node
}
#[derive(Clone, Debug)]
- pub struct OsRng(OsRngInner);
+ pub struct OsRng(OsRngMethod);
- impl OsRng {
- pub fn new() -> Result<OsRng, Error> {
+ impl OsRngImpl for OsRng {
+ fn new() -> Result<OsRng, Error> {
let result = js! {
try {
if (
@@ -736,8 +1043,8 @@
if js!{ return @{ result.as_ref() }.success } == true {
let ty = js!{ return @{ result }.ty };
- if ty == 1 { Ok(OsRng(OsRngInner::Browser)) }
- else if ty == 2 { Ok(OsRng(OsRngInner::Node)) }
+ if ty == 1 { Ok(OsRng(OsRngMethod::Browser)) }
+ else if ty == 2 { Ok(OsRng(OsRngMethod::Node)) }
else { unreachable!() }
} else {
let err: WebError = js!{ return @{ result }.error }.try_into().unwrap();
@@ -745,14 +1052,15 @@
}
}
- pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+
+ fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
assert_eq!(mem::size_of::<usize>(), 4);
let len = dest.len() as u32;
let ptr = dest.as_mut_ptr() as i32;
let result = match self.0 {
- OsRngInner::Browser => js! {
+ OsRngMethod::Browser => js! {
try {
let array = new Uint8Array(@{ len });
window.crypto.getRandomValues(array);
@@ -763,7 +1071,7 @@
return { success: false, error: err };
}
},
- OsRngInner::Node => js! {
+ OsRngMethod::Node => js! {
try {
let bytes = require("crypto").randomBytes(@{ len });
HEAPU8.set(new Uint8Array(bytes), @{ ptr });
@@ -782,9 +1090,19 @@
Err(Error::with_cause(ErrorKind::Unexpected, "WASM Error", err))
}
}
+
+ fn max_chunk_size(&self) -> usize { 65536 }
+
+ fn method_str(&self) -> &'static str {
+ match self.0 {
+ OsRngMethod::Browser => "Crypto.getRandomValues",
+ OsRngMethod::Node => "crypto.randomBytes",
+ }
+ }
}
}
+
#[cfg(test)]
mod test {
use RngCore;
@@ -812,6 +1130,22 @@
assert!(n_diff_bits >= v1.len() as u32);
}
+ #[test]
+ fn test_os_rng_empty() {
+ let mut r = OsRng::new().unwrap();
+
+ let mut empty = [0u8; 0];
+ r.fill_bytes(&mut empty);
+ }
+
+ #[test]
+ fn test_os_rng_huge() {
+ let mut r = OsRng::new().unwrap();
+
+ let mut huge = [0u8; 100_000];
+ r.fill_bytes(&mut huge);
+ }
+
#[cfg(not(any(target_arch = "wasm32", target_arch = "asmjs")))]
#[test]
fn test_os_rng_tasks() {