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
68pub struct BufRng {
74 reader: BufReader<File>,
75}
76
77impl BufRng {
78 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 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 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 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)]
118pub enum Flags {
121 Lowercase = 0o1,
122 Uppercase = 0o2,
123 Numeric = 0o4,
124 Special = 0o10,
125}
126
127impl Flags {
128 #[must_use]
129 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 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 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
159pub 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
171pub 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
183pub 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
195pub 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 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 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 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 pub fn get_dictionary(&self) -> &[char] {
276 &self.dictionary
277 }
278
279 pub fn set_dictionary(&mut self, dict: Vec<char>) {
281 self.dictionary = dict;
282 }
283
284 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 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}