808e0482c3
Add OsRandom::try_clone for fallible duplication of the underlying /dev/urandom file handle, and implement Clone as the infallible convenience form. This gives independent call sites their own OsRandom values without adding interior mutability or shared buffering to the core type. The fallible method is documented as the preferred API when callers need to handle a failed file-handle duplication. Clone mirrors Default by panicking only for the convenience path. Document both cloning forms with doctested examples and add a runnable clone example that uses independent handles for IDs and string helpers. Test Plan: - cargo test - cargo clippy - cargo clippy --benches - cargo clippy --tests - cargo +nightly fmt Refs: IDEAS.md ergonomics backlog
631 lines
20 KiB
Rust
631 lines
20 KiB
Rust
//! Easy access to operating-system randomness via `/dev/urandom`.
|
||
//!
|
||
//! The crate exposes [`OsRandom`], a small wrapper around an open handle to
|
||
//! `/dev/urandom` with helpers for reading primitive integers, sampling
|
||
//! uniformly without modulo bias, and generating random ASCII strings from a
|
||
//! caller-supplied alphabet. Common alphabets live in [`charset`].
|
||
//!
|
||
//! # Project layout
|
||
//!
|
||
//! - [`OsRandom`] owns an open `/dev/urandom` handle.
|
||
//! - Constructors open or duplicate that handle.
|
||
//! - Primitive readers turn raw bytes into integer values.
|
||
//! - Convenience byte readers fill caller-provided buffers.
|
||
//! - Sampling helpers build unbiased higher-level choices from those primitive
|
||
//! readers.
|
||
//! - Range helpers sample integer spans.
|
||
//! - Slice helpers choose caller-owned values by reference.
|
||
//! - [`charset`] contains reusable ASCII alphabets for string generation.
|
||
//! - String helpers compose those alphabets into common token formats.
|
||
|
||
use std::{
|
||
fs::File,
|
||
io::{self, Read},
|
||
mem::size_of,
|
||
ops::{Bound, RangeBounds},
|
||
};
|
||
|
||
use ::paste::paste;
|
||
|
||
const DEV_URANDOM: &str = "/dev/urandom";
|
||
|
||
/// Common character sets for use with [`OsRandom::pick`] and
|
||
/// [`OsRandom::string_from`].
|
||
pub mod charset {
|
||
const fn concat<const N: usize>(parts: &[&[u8]]) -> [u8; N] {
|
||
let mut out = [0u8; N];
|
||
let mut i = 0;
|
||
let mut p = 0;
|
||
while p < parts.len() {
|
||
let part = parts[p];
|
||
let mut j = 0;
|
||
while j < part.len() {
|
||
out[i] = part[j];
|
||
i += 1;
|
||
j += 1;
|
||
}
|
||
p += 1;
|
||
}
|
||
out
|
||
}
|
||
|
||
/// ASCII decimal digits `0`–`9`.
|
||
pub const DIGITS: &[u8] = b"0123456789";
|
||
/// ASCII lowercase letters `a`–`z`.
|
||
pub const LOWERCASE: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
|
||
|
||
const UPPERCASE_ARR: [u8; 26] = {
|
||
let mut out = [0u8; 26];
|
||
let mut i = 0;
|
||
while i < 26 {
|
||
out[i] = LOWERCASE[i].to_ascii_uppercase();
|
||
i += 1;
|
||
}
|
||
out
|
||
};
|
||
/// ASCII uppercase letters `A`–`Z`.
|
||
pub const UPPERCASE: &[u8] = &UPPERCASE_ARR;
|
||
|
||
const ALPHABETIC_ARR: [u8; 52] = concat(&[LOWERCASE, UPPERCASE]);
|
||
/// ASCII letters: [`LOWERCASE`] followed by [`UPPERCASE`].
|
||
pub const ALPHABETIC: &[u8] = &ALPHABETIC_ARR;
|
||
|
||
const ALPHANUMERIC_ARR: [u8; 62] = concat(&[LOWERCASE, UPPERCASE, DIGITS]);
|
||
/// ASCII letters and digits: [`LOWERCASE`], [`UPPERCASE`], then [`DIGITS`].
|
||
pub const ALPHANUMERIC: &[u8] = &ALPHANUMERIC_ARR;
|
||
|
||
const HEX_LOWER_ARR: [u8; 16] = concat(&[DIGITS, LOWERCASE.split_at(6).0]);
|
||
/// Lowercase hexadecimal digits `0`–`9`, `a`–`f`.
|
||
pub const HEX_LOWER: &[u8] = &HEX_LOWER_ARR;
|
||
|
||
const HEX_UPPER_ARR: [u8; 16] = concat(&[DIGITS, UPPERCASE.split_at(6).0]);
|
||
/// Uppercase hexadecimal digits `0`–`9`, `A`–`F`.
|
||
pub const HEX_UPPER: &[u8] = &HEX_UPPER_ARR;
|
||
}
|
||
|
||
/// Handle to the OS randomness source (`/dev/urandom`).
|
||
///
|
||
/// Construct with [`OsRandom::try_new`], then call the typed `get_*` readers,
|
||
/// the uniform-sampling helpers, or use the [`Read`] impl to fill an arbitrary
|
||
/// buffer.
|
||
pub struct OsRandom {
|
||
devurandom: File,
|
||
}
|
||
|
||
macro_rules! os_random_get_integer_impls {
|
||
($($t:ty),*) => {
|
||
$(
|
||
paste! {
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
pub fn [<get_ $t>](&mut self) -> std::io::Result<$t> {
|
||
let mut buf = [0u8; size_of::<$t>()];
|
||
self.devurandom.read_exact(&mut buf)?;
|
||
Ok($t::from_ne_bytes(buf))
|
||
}
|
||
}
|
||
)*
|
||
};
|
||
}
|
||
|
||
macro_rules! os_random_unsigned_range_impls {
|
||
($(($t:ty, $method:ident, $range_method:ident, $below_method:ident, $get_method:ident)),*) => {
|
||
$(
|
||
/// Returns a uniformly distributed integer in `0..n`.
|
||
///
|
||
/// # Panics
|
||
/// Panics if `n == 0`.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
#[doc = concat!("let value = rng.", stringify!($method), "(10)?;")]
|
||
/// assert!(value < 10);
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn $method(&mut self, n: $t) -> io::Result<$t> {
|
||
assert!(n > 0, "n must be greater than zero");
|
||
self.$below_method(n)
|
||
}
|
||
|
||
/// Returns a uniformly distributed integer from `range`.
|
||
///
|
||
/// The range may use any [`RangeBounds`] form, including `a..b`,
|
||
/// `a..=b`, `..b`, `..=b`, `a..`, and `..`.
|
||
///
|
||
/// # Panics
|
||
/// Panics if the range is empty or if an excluded bound overflows
|
||
/// the integer type.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
#[doc = concat!("let value = rng.", stringify!($range_method), "(4..=9)?;")]
|
||
/// assert!((4..=9).contains(&value));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn $range_method<R>(&mut self, range: R) -> io::Result<$t>
|
||
where
|
||
R: RangeBounds<$t>,
|
||
{
|
||
let start = match range.start_bound() {
|
||
Bound::Included(&start) => start,
|
||
Bound::Excluded(&start) => start
|
||
.checked_add(1)
|
||
.expect("excluded range start must not overflow"),
|
||
Bound::Unbounded => <$t>::MIN,
|
||
};
|
||
let end = match range.end_bound() {
|
||
Bound::Included(&end) => end,
|
||
Bound::Excluded(&end) => end
|
||
.checked_sub(1)
|
||
.expect("excluded range end must not overflow"),
|
||
Bound::Unbounded => <$t>::MAX,
|
||
};
|
||
assert!(start <= end, "range must not be empty");
|
||
|
||
let span = end.wrapping_sub(start).wrapping_add(1);
|
||
if span == 0 {
|
||
return self.$get_method();
|
||
}
|
||
|
||
Ok(start.wrapping_add(self.$below_method(span)?))
|
||
}
|
||
|
||
fn $below_method(&mut self, n: $t) -> io::Result<$t> {
|
||
let threshold = (0 as $t).wrapping_sub(n) % n;
|
||
loop {
|
||
let value = self.$get_method()?;
|
||
if value >= threshold {
|
||
return Ok(value % n);
|
||
}
|
||
}
|
||
}
|
||
)*
|
||
};
|
||
}
|
||
|
||
macro_rules! os_random_signed_range_impls {
|
||
($(($t:ty, $u:ty, $method:ident, $range_method:ident, $below_method:ident, $get_method:ident)),*) => {
|
||
$(
|
||
/// Returns a uniformly distributed integer in `0..n`.
|
||
///
|
||
/// # Panics
|
||
/// Panics if `n <= 0`.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
#[doc = concat!("let value = rng.", stringify!($method), "(10)?;")]
|
||
/// assert!((0..10).contains(&value));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn $method(&mut self, n: $t) -> io::Result<$t> {
|
||
assert!(n > 0, "n must be greater than zero");
|
||
self.$range_method(0..n)
|
||
}
|
||
|
||
/// Returns a uniformly distributed integer from `range`.
|
||
///
|
||
/// The range may use any [`RangeBounds`] form, including `a..b`,
|
||
/// `a..=b`, `..b`, `..=b`, `a..`, and `..`.
|
||
///
|
||
/// # Panics
|
||
/// Panics if the range is empty or if an excluded bound overflows
|
||
/// the integer type.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
#[doc = concat!("let value = rng.", stringify!($range_method), "(-4..=4)?;")]
|
||
/// assert!((-4..=4).contains(&value));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn $range_method<R>(&mut self, range: R) -> io::Result<$t>
|
||
where
|
||
R: RangeBounds<$t>,
|
||
{
|
||
const SIGN_BIT: $u = (<$u>::MAX >> 1) + 1;
|
||
|
||
let start = match range.start_bound() {
|
||
Bound::Included(&start) => start,
|
||
Bound::Excluded(&start) => start
|
||
.checked_add(1)
|
||
.expect("excluded range start must not overflow"),
|
||
Bound::Unbounded => <$t>::MIN,
|
||
};
|
||
let end = match range.end_bound() {
|
||
Bound::Included(&end) => end,
|
||
Bound::Excluded(&end) => end
|
||
.checked_sub(1)
|
||
.expect("excluded range end must not overflow"),
|
||
Bound::Unbounded => <$t>::MAX,
|
||
};
|
||
assert!(start <= end, "range must not be empty");
|
||
|
||
let start = start.cast_unsigned() ^ SIGN_BIT;
|
||
let end = end.cast_unsigned() ^ SIGN_BIT;
|
||
let span = end.wrapping_sub(start).wrapping_add(1);
|
||
if span == 0 {
|
||
return self.$get_method();
|
||
}
|
||
|
||
let value = start.wrapping_add(self.$below_method(span)?) ^ SIGN_BIT;
|
||
Ok(value.cast_signed())
|
||
}
|
||
)*
|
||
};
|
||
}
|
||
|
||
/// Constructor and primitive integer readers.
|
||
///
|
||
/// Each `get_<int>` method reads exactly `size_of::<T>()` bytes from
|
||
/// `/dev/urandom` and interprets them in native-endian order, so every bit
|
||
/// pattern is equally likely.
|
||
impl OsRandom {
|
||
/// # Errors
|
||
/// Returns any I/O error produced while opening `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let value = rng.get_u64()?;
|
||
/// # let _ = value;
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn try_new() -> Result<Self, io::Error> {
|
||
let devurandom = File::open(DEV_URANDOM)?;
|
||
|
||
Ok(Self { devurandom })
|
||
}
|
||
|
||
/// Fills `buf` with bytes from `/dev/urandom`.
|
||
///
|
||
/// This is equivalent to calling [`Read::read_exact`] on `OsRandom`, but it
|
||
/// keeps the common "fill this byte slice" use case available without
|
||
/// importing the [`Read`] trait.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let mut bytes = [0u8; 32];
|
||
/// rng.fill_bytes(&mut bytes)?;
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn fill_bytes(&mut self, buf: &mut [u8]) -> io::Result<()> {
|
||
self.devurandom.read_exact(buf)
|
||
}
|
||
|
||
/// Duplicates this handle to `/dev/urandom`.
|
||
///
|
||
/// The cloned value has its own [`File`] handle, so separate call sites can
|
||
/// own independent `OsRandom` values while reading from the same
|
||
/// operating-system randomness source.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while duplicating the underlying file
|
||
/// handle.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut first = ez_urandom::OsRandom::try_new()?;
|
||
/// let mut second = first.try_clone()?;
|
||
///
|
||
/// let a = first.get_u64()?;
|
||
/// let b = second.get_u64()?;
|
||
/// # let _ = (a, b);
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn try_clone(&self) -> io::Result<Self> {
|
||
Ok(Self {
|
||
devurandom: self.devurandom.try_clone()?,
|
||
})
|
||
}
|
||
|
||
os_random_get_integer_impls!(
|
||
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
|
||
);
|
||
}
|
||
|
||
/// Opens `/dev/urandom` and panics if the operating-system source is
|
||
/// unavailable.
|
||
///
|
||
/// Prefer [`OsRandom::try_new`] when the caller should decide how to handle an
|
||
/// I/O failure.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// let mut rng = ez_urandom::OsRandom::default();
|
||
/// let mut bytes = [0u8; 16];
|
||
/// rng.fill_bytes(&mut bytes).expect("/dev/urandom read failed");
|
||
/// ```
|
||
impl Default for OsRandom {
|
||
fn default() -> Self {
|
||
Self::try_new().expect("/dev/urandom should be available")
|
||
}
|
||
}
|
||
|
||
/// Duplicates the underlying `/dev/urandom` handle.
|
||
///
|
||
/// Prefer [`OsRandom::try_clone`] when the caller should decide how to handle an
|
||
/// I/O failure while duplicating the file handle.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// let rng = ez_urandom::OsRandom::default();
|
||
/// let mut cloned = rng.clone();
|
||
/// let value = cloned.get_u32().expect("/dev/urandom read failed");
|
||
/// # let _ = value;
|
||
/// ```
|
||
impl Clone for OsRandom {
|
||
fn clone(&self) -> Self {
|
||
self.try_clone()
|
||
.expect("/dev/urandom handle should be cloneable")
|
||
}
|
||
}
|
||
|
||
/// Uniform sampling helpers.
|
||
///
|
||
/// Range methods use rejection sampling on top of the matching primitive
|
||
/// integer reader, so bounded results are exactly uniform — no modulo bias.
|
||
impl OsRandom {
|
||
os_random_unsigned_range_impls!(
|
||
(u8, gen_range_u8, gen_range_u8_in, gen_below_u8, get_u8),
|
||
(u16, gen_range_u16, gen_range_u16_in, gen_below_u16, get_u16),
|
||
(u32, gen_range_u32, gen_range_u32_in, gen_below_u32, get_u32),
|
||
(u64, gen_range_u64, gen_range_u64_in, gen_below_u64, get_u64),
|
||
(
|
||
u128,
|
||
gen_range_u128,
|
||
gen_range_u128_in,
|
||
gen_below_u128,
|
||
get_u128
|
||
),
|
||
(
|
||
usize,
|
||
gen_range_usize,
|
||
gen_range_usize_in,
|
||
gen_below_usize,
|
||
get_usize
|
||
)
|
||
);
|
||
|
||
os_random_signed_range_impls!(
|
||
(i8, u8, gen_range_i8, gen_range_i8_in, gen_below_u8, get_i8),
|
||
(
|
||
i16,
|
||
u16,
|
||
gen_range_i16,
|
||
gen_range_i16_in,
|
||
gen_below_u16,
|
||
get_i16
|
||
),
|
||
(
|
||
i32,
|
||
u32,
|
||
gen_range_i32,
|
||
gen_range_i32_in,
|
||
gen_below_u32,
|
||
get_i32
|
||
),
|
||
(
|
||
i64,
|
||
u64,
|
||
gen_range_i64,
|
||
gen_range_i64_in,
|
||
gen_below_u64,
|
||
get_i64
|
||
),
|
||
(
|
||
i128,
|
||
u128,
|
||
gen_range_i128,
|
||
gen_range_i128_in,
|
||
gen_below_u128,
|
||
get_i128
|
||
),
|
||
(
|
||
isize,
|
||
usize,
|
||
gen_range_isize,
|
||
gen_range_isize_in,
|
||
gen_below_usize,
|
||
get_isize
|
||
)
|
||
);
|
||
|
||
/// Returns a uniformly chosen byte from `set`.
|
||
///
|
||
/// # Panics
|
||
/// Panics if `set` is empty.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let digit = rng.pick(ez_urandom::charset::DIGITS)?;
|
||
/// assert!(digit.is_ascii_digit());
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn pick(&mut self, set: &[u8]) -> io::Result<u8> {
|
||
Ok(*self.choose(set)?)
|
||
}
|
||
|
||
/// Returns a uniformly chosen item from `items`.
|
||
///
|
||
/// The returned reference points into the caller-provided slice; no item is
|
||
/// cloned or moved.
|
||
///
|
||
/// # Panics
|
||
/// Panics if `items` is empty.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let colors = ["red", "green", "blue"];
|
||
/// let color = rng.choose(&colors)?;
|
||
/// assert!(colors.contains(color));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn choose<'a, T>(&mut self, items: &'a [T]) -> io::Result<&'a T> {
|
||
assert!(!items.is_empty(), "items must not be empty");
|
||
let i = self.gen_range_usize(items.len())?;
|
||
Ok(&items[i])
|
||
}
|
||
|
||
/// Returns a `String` of `len` characters drawn uniformly from `set`.
|
||
///
|
||
/// # Panics
|
||
/// Panics if `set` is empty, longer than `u32::MAX`, or contains
|
||
/// non-ASCII bytes.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let token = rng.string_from(ez_urandom::charset::ALPHANUMERIC, 24)?;
|
||
/// assert_eq!(token.len(), 24);
|
||
/// assert!(token.is_ascii());
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn string_from(&mut self, set: &[u8], len: usize) -> io::Result<String> {
|
||
assert!(set.is_ascii(), "set must contain only ASCII bytes");
|
||
let mut buf = vec![0u8; len];
|
||
for byte in &mut buf {
|
||
*byte = self.pick(set)?;
|
||
}
|
||
Ok(String::from_utf8(buf).expect("ASCII bytes are valid UTF-8"))
|
||
}
|
||
|
||
/// Returns an alphanumeric ASCII token of `len` characters.
|
||
///
|
||
/// Characters are drawn uniformly from [`charset::ALPHANUMERIC`].
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let token = rng.token(32)?;
|
||
/// assert_eq!(token.len(), 32);
|
||
/// assert!(token.bytes().all(|byte| byte.is_ascii_alphanumeric()));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn token(&mut self, len: usize) -> io::Result<String> {
|
||
self.string_from(charset::ALPHANUMERIC, len)
|
||
}
|
||
|
||
/// Returns a lowercase hexadecimal string of `len` characters.
|
||
///
|
||
/// Characters are drawn uniformly from [`charset::HEX_LOWER`]. The `len`
|
||
/// argument is the number of hex characters, not the number of source
|
||
/// bytes.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let hex = rng.hex(16)?;
|
||
/// assert_eq!(hex.len(), 16);
|
||
/// assert!(hex.bytes().all(|byte| byte.is_ascii_hexdigit()));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn hex(&mut self, len: usize) -> io::Result<String> {
|
||
self.string_from(charset::HEX_LOWER, len)
|
||
}
|
||
|
||
/// Returns a decimal digit string of `len` characters.
|
||
///
|
||
/// This is useful for PINs, short human-entered codes, and other cases
|
||
/// where only ASCII digits are accepted.
|
||
///
|
||
/// # Errors
|
||
/// Returns any I/O error produced while reading from `/dev/urandom`.
|
||
///
|
||
/// # Examples
|
||
///
|
||
/// ```
|
||
/// # fn main() -> std::io::Result<()> {
|
||
/// let mut rng = ez_urandom::OsRandom::try_new()?;
|
||
/// let pin = rng.pin(6)?;
|
||
/// assert_eq!(pin.len(), 6);
|
||
/// assert!(pin.bytes().all(|byte| byte.is_ascii_digit()));
|
||
/// # Ok(())
|
||
/// # }
|
||
/// ```
|
||
pub fn pin(&mut self, len: usize) -> io::Result<String> {
|
||
self.string_from(charset::DIGITS, len)
|
||
}
|
||
}
|
||
|
||
/// Forwards reads directly to the underlying `/dev/urandom` handle so an
|
||
/// `OsRandom` can be used anywhere an [`io::Read`] source is expected.
|
||
impl Read for OsRandom {
|
||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||
self.devurandom.read(buf)
|
||
}
|
||
}
|