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&section=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() {