osrand/
lib.rs

1#![warn(clippy::all, clippy::pedantic)]
2#![doc = include_str!("../README.md")]
3use std::{
4    error, fmt,
5    fs::File,
6    io::{self, BufReader, Read},
7    num::TryFromIntError,
8};
9
10#[cfg(not(feature = "urandom"))]
11static RAND_DEV: &str = "/dev/random";
12#[cfg(feature = "urandom")]
13static RAND_DEV: &str = "/dev/urandom";
14
15static ALPHA_LOWER: [char; 26] = [
16    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
17    't', 'u', 'v', 'w', 'x', 'y', 'z',
18];
19
20static ALPHA_UPPER: [char; 26] = [
21    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
22    'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
23];
24
25static NUMERIC: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
26
27static SYMBOLS: [char; 20] = [
28    '~', '!', '@', '#', '$', '%', '^', '&', '*', '-', '_', '=', '+', ':', ';', '<', '>', ',', '.',
29    '?',
30];
31
32#[derive(Debug)]
33pub enum Error {
34    Io(io::Error),
35    TryFromInt,
36}
37
38impl fmt::Display for Error {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            Self::Io(e) => write!(f, "{e}"),
42            Self::TryFromInt => write!(f, "TryFromIntError"),
43        }
44    }
45}
46
47impl error::Error for Error {
48    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
49        match self {
50            Self::Io(e) => Some(e),
51            Self::TryFromInt => None,
52        }
53    }
54}
55
56impl From<io::Error> for Error {
57    fn from(value: io::Error) -> Self {
58        Self::Io(value)
59    }
60}
61
62impl From<TryFromIntError> for Error {
63    fn from(_value: TryFromIntError) -> Self {
64        Self::TryFromInt
65    }
66}
67
68/// Pulls random integers from the OS RNG device.
69/// This object can be reused to create multiple random
70/// numbers and uses an internal `BufReader` around the
71/// rng device file in order to keep from doing multiple
72/// small reads.
73pub struct BufRng {
74    reader: BufReader<File>,
75}
76
77impl BufRng {
78    /// Creates a new instance
79    /// # Errors
80    /// Returns an io error if there is a problem opening the RNG device
81    pub fn new() -> Result<Self, io::Error> {
82        let fd = File::open(RAND_DEV)?;
83        Ok(Self {
84            reader: BufReader::new(fd),
85        })
86    }
87
88    /// Gets a `u16`
89    /// # Errors
90    /// Returns an io error if there is a problem reading from the RNG device
91    pub fn get_u16(&mut self) -> Result<u16, io::Error> {
92        let mut buf = [0; 2];
93        self.reader.read_exact(&mut buf)?;
94        Ok(u16::from_ne_bytes(buf))
95    }
96
97    /// Gets a `u32`
98    /// # Errors
99    /// Returns an io error if there is a problem reading from the RNG device
100    pub fn get_u32(&mut self) -> Result<u32, io::Error> {
101        let mut buf = [0; 4];
102        self.reader.read_exact(&mut buf)?;
103        Ok(u32::from_ne_bytes(buf))
104    }
105
106    /// Gets a `u64`
107    /// # Errors
108    /// Returns an io error if there is a problem reading from the RNG device
109    pub fn get_u64(&mut self) -> Result<u64, io::Error> {
110        let mut buf = [0; 8];
111        self.reader.read_exact(&mut buf)?;
112        Ok(u64::from_ne_bytes(buf))
113    }
114}
115
116#[repr(u8)]
117#[derive(Clone, Copy)]
118/// These flags specify the contents of the dictionary used to
119/// create random strings.
120pub enum Flags {
121    Lowercase = 0o1,
122    Uppercase = 0o2,
123    Numeric = 0o4,
124    Special = 0o10,
125}
126
127impl Flags {
128    #[must_use]
129    /// Creates a dictionary using all of the available characters
130    pub fn all() -> Vec<char> {
131        let mut dict = Vec::with_capacity(82);
132        dict.extend_from_slice(&ALPHA_LOWER);
133        dict.extend_from_slice(&ALPHA_UPPER);
134        dict.extend_from_slice(&NUMERIC);
135        dict.extend_from_slice(&SYMBOLS);
136        dict
137    }
138
139    #[must_use]
140    /// Creates a dictionary using only alphanumeric characters
141    pub fn alphanumeric() -> Vec<char> {
142        let mut dict = Vec::with_capacity(62);
143        dict.extend_from_slice(&ALPHA_LOWER);
144        dict.extend_from_slice(&ALPHA_UPPER);
145        dict.extend_from_slice(&NUMERIC);
146        dict
147    }
148
149    #[must_use]
150    /// Creates a dictionary using only alphabet characters
151    pub fn alphabetical() -> Vec<char> {
152        let mut dict = Vec::with_capacity(52);
153        dict.extend_from_slice(&ALPHA_LOWER);
154        dict.extend_from_slice(&ALPHA_UPPER);
155        dict
156    }
157}
158
159/// Gets a `u16` from the RNG device. Do not use this function if
160/// you require multiple random numbers, as each use will be a single
161/// read.
162/// # Errors
163/// Returns an io error if there is a problem reading from the RNG device
164pub fn random_u16() -> Result<u16, io::Error> {
165    let mut buf = [0; 2];
166    let mut fd = File::open(RAND_DEV)?;
167    fd.read_exact(&mut buf)?;
168    Ok(u16::from_ne_bytes(buf))
169}
170
171/// Gets a `u32` from the RNG device. Do not use this function if
172/// you require multiple random numbers, as each use will be a single
173/// read.
174/// # Errors
175/// Returns an io error if there is a problem reading from the RNG device
176pub fn random_u32() -> Result<u32, io::Error> {
177    let mut buf = [0; 4];
178    let mut fd = File::open(RAND_DEV)?;
179    fd.read_exact(&mut buf)?;
180    Ok(u32::from_ne_bytes(buf))
181}
182
183/// Gets a `u64` from the RNG device. Do not use this function if
184/// you require multiple random numbers, as each use will be a single
185/// read.
186/// # Errors
187/// Returns an io error if there is a problem reading from the RNG device
188pub fn random_u64() -> Result<u64, io::Error> {
189    let mut buf = [0; 8];
190    let mut fd = File::open(RAND_DEV)?;
191    fd.read_exact(&mut buf)?;
192    Ok(u64::from_ne_bytes(buf))
193}
194
195/// A random string generator which gets it's entropy from an internal
196/// `BufReader` wrapping the OS RNG device. This generator may be re-used
197/// as many times as required.
198pub struct RandomString {
199    dictionary: Vec<char>,
200    rng: BufRng,
201}
202
203impl From<RandomString> for BufRng {
204    fn from(value: RandomString) -> Self {
205        value.rng
206    }
207}
208
209impl From<BufRng> for RandomString {
210    fn from(value: BufRng) -> Self {
211        Self { dictionary: Flags::all(), rng: value }
212    }
213}
214
215impl RandomString {
216    /// Creates a new random string generator, which gets it's randomness
217    /// from an internal `BufReader` around the OS RNG device. If `flags`
218    /// is empty, the full dictionary will be used.
219    /// # Errors
220    /// Returns an io error if there is a problem reading from the RNG device
221    pub fn new(flags: &[Flags]) -> Result<Self, io::Error> {
222        let dictionary = if flags.is_empty() {
223            Flags::all()
224        } else {
225            let mut dict = vec![];
226            flags.iter().for_each(|f| match f {
227                Flags::Lowercase => dict.extend_from_slice(&ALPHA_LOWER),
228                Flags::Uppercase => dict.extend_from_slice(&ALPHA_UPPER),
229                Flags::Numeric => dict.extend_from_slice(&NUMERIC),
230                Flags::Special => dict.extend_from_slice(&SYMBOLS),
231            });
232            dict
233        };
234        Ok(Self {
235            dictionary,
236            rng: BufRng::new()?,
237        })
238    }
239
240    /// Creates a new random string generator with the given dictionary, which
241    /// gets it's randomness from an internal `BufReader` around the OS RNG device.
242    /// If `dict` is empty, the full dictionary will be used.
243    /// # Errors
244    /// Returns an io error if there is a problem reading from the RNG device
245    pub fn with_dict(dict: Vec<char>) -> Result<Self, io::Error> {
246        let dictionary = if dict.is_empty() {
247            Flags::all()
248        } else {
249            dict
250        };
251        Ok(Self {
252            dictionary,
253            rng: BufRng::new()?,
254        })
255    }
256
257    /// Creates a new random string generator from the provided `BufRng` rng`
258    /// and the `Dictionary` dict.
259    /// # Errors
260    /// Returns an io error if there is a problem reading from the RNG device
261    pub fn from_parts(rng: BufRng, dict: Vec<char>) -> Self {
262        let dictionary = if dict.is_empty() {
263            Flags::all()
264        } else {
265            dict
266        };
267        Self {
268            dictionary,
269            rng,
270        }
271    }
272
273    #[must_use]
274    /// Gets the dictionary being used by the generator
275    pub fn get_dictionary(&self) -> &[char] {
276        &self.dictionary
277    }
278
279    /// Sets the dictionary to be used for new random strings
280    pub fn set_dictionary(&mut self, dict: Vec<char>) {
281        self.dictionary = dict;
282    }
283
284    /// Generates a random string of the given size
285    /// # Errors
286    /// Returns an io error if there is a problem reading from the RNG device
287    pub fn gen(&mut self, len: usize) -> Result<String, Error> {
288        let mut s = String::with_capacity(len);
289        for _i in 0..len {
290            let n = self.rng.get_u32()?;
291            let idx = usize::try_from(n)? % self.dictionary.len();
292            if let Some(c) = self.dictionary.get(idx) {
293                s.push(*c);
294            }
295        }
296        Ok(s)
297    }
298
299    /// Appends `len` random characters  to string `s` and returns the result
300    /// # Errors
301    /// Returns an io error if there is a problem reading from the RNG device
302    pub fn append(&mut self, mut s: String, len: usize) -> Result<String, Error> {
303        for _i in 0..len {
304            let n = self.rng.get_u32()?;
305            let idx = usize::try_from(n)? % self.dictionary.len();
306            if let Some(c) = self.dictionary.get(idx) {
307                s.push(*c);
308            }
309        }
310        Ok(s)
311    }
312}
313
314#[test]
315fn random_string() {
316    let mut rs = RandomString::new(&[
317        Flags::Lowercase,
318        Flags::Numeric,
319        Flags::Uppercase,
320        Flags::Special,
321    ])
322    .unwrap();
323    let out = rs.gen(8).unwrap();
324    assert_eq!(out.len(), 8);
325}