spinoso_securerandom/lib.rs
1#![warn(clippy::all, clippy::pedantic, clippy::undocumented_unsafe_blocks)]
2#![allow(
3 clippy::let_underscore_untyped,
4 reason = "https://github.com/rust-lang/rust-clippy/pull/10442#issuecomment-1516570154"
5)]
6#![allow(
7 clippy::question_mark,
8 reason = "https://github.com/rust-lang/rust-clippy/issues/8281"
9)]
10#![allow(clippy::manual_let_else, reason = "manual_let_else was very buggy on release")]
11#![allow(clippy::missing_errors_doc, reason = "A lot of existing code fails this lint")]
12#![allow(
13 clippy::unnecessary_lazy_evaluations,
14 reason = "https://github.com/rust-lang/rust-clippy/issues/8109"
15)]
16#![cfg_attr(
17 test,
18 allow(clippy::non_ascii_literal, reason = "tests sometimes require UTF-8 string content")
19)]
20#![allow(unknown_lints)]
21#![warn(
22 missing_copy_implementations,
23 missing_debug_implementations,
24 missing_docs,
25 rust_2024_compatibility,
26 trivial_casts,
27 trivial_numeric_casts,
28 unused_qualifications,
29 variant_size_differences
30)]
31#![forbid(unsafe_code)]
32// Enable feature callouts in generated documentation:
33// https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html
34//
35// This approach is borrowed from tokio.
36#![cfg_attr(docsrs, feature(doc_cfg))]
37#![cfg_attr(docsrs, feature(doc_alias))]
38
39//! Secure random number generator interface.
40//!
41//! This module implements the [`SecureRandom`] package from the Ruby Standard
42//! Library. It is an interface to secure random number generators which are
43//! suitable for generating session keys in HTTP cookies, etc.
44//!
45//! This implementation of `SecureRandom` supports the system RNG via the
46//! [`getrandom`] crate. This implementation does not depend on OpenSSL.
47//!
48//! # Examples
49//!
50//! Generate cryptographically secure random bytes:
51//!
52//! ```rust
53//! # fn example() -> Result<(), spinoso_securerandom::Error> {
54//! let bytes = spinoso_securerandom::random_bytes(Some(1024))?;
55//! assert_eq!(bytes.len(), 1024);
56//! # Ok(())
57//! # }
58//! # example().unwrap()
59//! ```
60//!
61//! Generate base64-encoded random data:
62//!
63//! ```rust
64//! # fn example() -> Result<(), spinoso_securerandom::Error> {
65//! let bytes = spinoso_securerandom::base64(Some(1024))?;
66//! assert_eq!(bytes.len(), 1368);
67//! assert!(bytes.is_ascii());
68//! # Ok(())
69//! # }
70//! # example().unwrap()
71//! ```
72//!
73//! Generate random floats and integers in a range bounded from zero to a
74//! maximum:
75//!
76//! ```rust
77//! # use spinoso_securerandom::{Error, Max, Rand};
78//! # fn example() -> Result<(), Error> {
79//! let rand = spinoso_securerandom::random_number(Max::None)?;
80//! assert!(matches!(rand, Rand::Float(_)));
81//!
82//! let rand = spinoso_securerandom::random_number(Max::Integer(57))?;
83//! assert!(matches!(rand, Rand::Integer(_)));
84//!
85//! let rand = spinoso_securerandom::random_number(Max::Float(57.0))?;
86//! assert!(matches!(rand, Rand::Float(_)));
87//! # Ok(())
88//! # }
89//! # example().unwrap()
90//! ```
91//!
92//! Generate version 4 random UUIDs:
93//!
94//! ```rust
95//! # fn example() -> Result<(), spinoso_securerandom::Error> {
96//! let uuid = spinoso_securerandom::uuid()?;
97//! assert_eq!(uuid.len(), 36);
98//! assert!(uuid.chars().all(|ch| ch == '-' || ch.is_ascii_hexdigit()));
99//! # Ok(())
100//! # }
101//! # example().unwrap()
102//! ```
103//!
104//! [`SecureRandom`]: https://ruby-doc.org/stdlib-3.1.2/libdoc/securerandom/rdoc/SecureRandom.html
105//! [`getrandom`]: https://crates.io/crates/getrandom
106
107// Ensure code blocks in `README.md` compile
108#[cfg(doctest)]
109#[doc = include_str!("../README.md")]
110mod readme {}
111
112use core::fmt;
113use std::collections::TryReserveError;
114use std::error;
115
116use rand::distr::Alphanumeric;
117use rand::rngs::StdRng;
118use rand::{CryptoRng, Rng, SeedableRng};
119use scolapasta_hex as hex;
120
121mod uuid;
122
123const DEFAULT_REQUESTED_BYTES: usize = 16;
124
125/// Sum type of all errors possibly returned from [`random_bytes`].
126///
127/// `random_bytes` can return errors under several conditions:
128///
129/// - The given byte length is not a valid [`usize`].
130/// - The underlying source of randomness returns an error when generating the
131/// requested random bytes.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub enum Error {
134 /// Error that indicates an argument parsing or value logic error occurred.
135 ///
136 /// See [`ArgumentError`].
137 Argument(ArgumentError),
138 /// Error that indicates an invalid range was given.
139 ///
140 /// See [`DomainError`].
141 Domain(DomainError),
142 /// Error that indicates the underlying source of randomness failed to
143 /// generate the requested random bytes.
144 ///
145 /// See [`RandomBytesError`].
146 RandomBytes(RandomBytesError),
147 /// Error that indicates an error was received from the memory allocator.
148 ///
149 /// This may mean that too many random bytes were requested or the system is
150 /// out of memory.
151 ///
152 /// See [`TryReserveError`] and [`TryReserveErrorKind`] for more information.
153 ///
154 /// [`TryReserveErrorKind`]: std::collections::TryReserveErrorKind
155 Memory(TryReserveError),
156}
157
158impl From<ArgumentError> for Error {
159 #[inline]
160 fn from(err: ArgumentError) -> Self {
161 Self::Argument(err)
162 }
163}
164
165impl From<DomainError> for Error {
166 #[inline]
167 fn from(err: DomainError) -> Self {
168 Self::Domain(err)
169 }
170}
171
172impl From<RandomBytesError> for Error {
173 #[inline]
174 fn from(err: RandomBytesError) -> Self {
175 Self::RandomBytes(err)
176 }
177}
178
179impl From<TryReserveError> for Error {
180 fn from(err: TryReserveError) -> Self {
181 Self::Memory(err)
182 }
183}
184
185impl From<getrandom::Error> for Error {
186 #[inline]
187 fn from(_: getrandom::Error) -> Self {
188 Self::RandomBytes(RandomBytesError::new())
189 }
190}
191
192impl fmt::Display for Error {
193 #[inline]
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 f.write_str("SecureRandom error")
196 }
197}
198
199impl error::Error for Error {
200 #[inline]
201 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
202 match self {
203 Self::Argument(err) => Some(err),
204 Self::Domain(err) => Some(err),
205 Self::RandomBytes(err) => Some(err),
206 Self::Memory(err) => Some(err),
207 }
208 }
209}
210
211/// Error that indicates an argument parsing or value logic error occurred.
212///
213/// Argument errors have an associated message.
214///
215/// This error corresponds to the [Ruby `ArgumentError` Exception class].
216///
217/// # Examples
218///
219/// ```
220/// # use spinoso_securerandom::ArgumentError;
221/// let err = ArgumentError::new();
222/// assert_eq!(err.message(), "ArgumentError");
223///
224/// let err = ArgumentError::with_message("negative string size (or size too big)");
225/// assert_eq!(err.message(), "negative string size (or size too big)");
226/// ```
227///
228/// [Ruby `ArgumentError` Exception class]: https://ruby-doc.org/core-3.1.2/ArgumentError.html
229#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
230pub struct ArgumentError(&'static str);
231
232impl From<&'static str> for ArgumentError {
233 #[inline]
234 fn from(message: &'static str) -> Self {
235 Self::with_message(message)
236 }
237}
238
239impl Default for ArgumentError {
240 #[inline]
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246impl fmt::Display for ArgumentError {
247 #[inline]
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 f.write_str(self.message())
250 }
251}
252
253impl error::Error for ArgumentError {}
254
255impl ArgumentError {
256 /// Construct a new, default argument error.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// # use spinoso_securerandom::ArgumentError;
262 /// const ERR: ArgumentError = ArgumentError::new();
263 /// assert_eq!(ERR.message(), "ArgumentError");
264 /// ```
265 #[inline]
266 #[must_use]
267 pub const fn new() -> Self {
268 Self("ArgumentError")
269 }
270
271 /// Construct a new argument error with a message.
272 ///
273 /// # Examples
274 ///
275 /// ```
276 /// # use spinoso_securerandom::ArgumentError;
277 /// const ERR: ArgumentError =
278 /// ArgumentError::with_message("negative string size (or size too big)");
279 /// assert_eq!(ERR.message(), "negative string size (or size too big)");
280 /// ```
281 #[inline]
282 #[must_use]
283 pub const fn with_message(message: &'static str) -> Self {
284 Self(message)
285 }
286
287 /// Retrieve the exception message associated with this argument error.
288 ///
289 /// # Examples
290 ///
291 /// ```
292 /// # use spinoso_securerandom::ArgumentError;
293 /// let err = ArgumentError::new();
294 /// assert_eq!(err.message(), "ArgumentError");
295 ///
296 /// let err = ArgumentError::with_message("negative string size (or size too big)");
297 /// assert_eq!(err.message(), "negative string size (or size too big)");
298 /// ```
299 #[inline]
300 #[must_use]
301 pub const fn message(self) -> &'static str {
302 self.0
303 }
304}
305
306/// Error that indicates the underlying source of randomness failed to generate
307/// the requested random bytes.
308///
309/// This error is typically returned by the operating system.
310#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
311pub struct RandomBytesError {
312 _private: (),
313}
314
315impl fmt::Display for RandomBytesError {
316 #[inline]
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 f.write_str(self.message())
319 }
320}
321
322impl From<getrandom::Error> for RandomBytesError {
323 #[inline]
324 fn from(_: getrandom::Error) -> Self {
325 Self::new()
326 }
327}
328
329impl error::Error for RandomBytesError {}
330
331impl RandomBytesError {
332 /// Construct a new, default random bytes error.
333 ///
334 /// # Examples
335 ///
336 /// ```
337 /// # use spinoso_securerandom::RandomBytesError;
338 /// const ERR: RandomBytesError = RandomBytesError::new();
339 /// assert_eq!(ERR.message(), "OS Error: Failed to generate random bytes");
340 /// ```
341 #[inline]
342 #[must_use]
343 pub const fn new() -> Self {
344 Self { _private: () }
345 }
346
347 /// Retrieve the exception message associated with this random bytes error.
348 ///
349 /// # Examples
350 ///
351 /// ```
352 /// # use spinoso_securerandom::RandomBytesError;
353 /// let err = RandomBytesError::new();
354 /// assert_eq!(err.message(), "OS Error: Failed to generate random bytes");
355 /// ```
356 #[inline]
357 #[must_use]
358 pub const fn message(self) -> &'static str {
359 "OS Error: Failed to generate random bytes"
360 }
361}
362
363/// Error that indicates the given maximum value is not finite and cannot be
364/// used to bound a domain for generating random numbers.
365///
366/// This error is returned by [`random_number`].
367#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
368pub struct DomainError {
369 _private: (),
370}
371
372impl fmt::Display for DomainError {
373 #[inline]
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 f.write_str(self.message())
376 }
377}
378
379impl error::Error for DomainError {}
380
381impl DomainError {
382 /// Construct a new, default domain error.
383 ///
384 /// # Examples
385 ///
386 /// ```
387 /// # use spinoso_securerandom::DomainError;
388 /// const ERR: DomainError = DomainError::new();
389 /// assert_eq!(ERR.message(), "Numerical argument out of domain");
390 /// ```
391 #[inline]
392 #[must_use]
393 pub const fn new() -> Self {
394 Self { _private: () }
395 }
396
397 /// Retrieve the exception message associated with this domain error.
398 ///
399 /// # Examples
400 ///
401 /// ```
402 /// # use spinoso_securerandom::DomainError;
403 /// let err = DomainError::new();
404 /// assert_eq!(err.message(), "Numerical argument out of domain");
405 /// ```
406 #[inline]
407 #[must_use]
408 pub const fn message(self) -> &'static str {
409 "Numerical argument out of domain"
410 }
411}
412
413/// A handle to the underlying secure random number generator.
414///
415/// This is a copy zero-sized type with no associated methods. This type exists
416/// so a Ruby VM can attempt to unbox this type and statically dispatch to
417/// functions defined in this crate.
418///
419/// # Examples
420///
421/// ```
422/// # use spinoso_securerandom::SecureRandom;
423/// const RANDOM: SecureRandom = SecureRandom::new();
424/// ```
425#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
426pub struct SecureRandom {
427 _private: (),
428}
429
430impl SecureRandom {
431 /// Constructs a new, default `SecureRandom`.
432 ///
433 /// # Examples
434 ///
435 /// ```
436 /// # use spinoso_securerandom::SecureRandom;
437 /// const RANDOM: SecureRandom = SecureRandom::new();
438 /// ```
439 #[inline]
440 #[must_use]
441 pub const fn new() -> Self {
442 Self { _private: () }
443 }
444}
445
446/// Generate a vector of random bytes.
447///
448/// If `len` is [`Some`] and non-negative, generate a vector of `len` random
449/// bytes. If `len` is [`None`], generate 16 random bytes.
450///
451/// # Examples
452///
453/// ```rust
454/// # fn example() -> Result<(), spinoso_securerandom::Error> {
455/// let bytes = spinoso_securerandom::random_bytes(Some(1024))?;
456/// assert_eq!(bytes.len(), 1024);
457/// # Ok(())
458/// # }
459/// # example().unwrap()
460/// ```
461///
462/// # Errors
463///
464/// If the given length is negative, return an [`ArgumentError`].
465///
466/// If the underlying source of randomness returns an error, return a
467/// [`RandomBytesError`].
468///
469/// If an allocation error occurs, an error is returned.
470#[inline]
471pub fn random_bytes(len: Option<i64>) -> Result<Vec<u8>, Error> {
472 let len = match len.map(usize::try_from) {
473 Some(Ok(0)) => return Ok(Vec::new()),
474 Some(Ok(len)) => len,
475 Some(Err(_)) => {
476 let err = ArgumentError::with_message("negative string size (or size too big)");
477 return Err(Error::Argument(err));
478 }
479 None => DEFAULT_REQUESTED_BYTES,
480 };
481
482 let mut bytes = Vec::new();
483 bytes.try_reserve(len)?;
484 bytes.resize(len, 0);
485 getrandom::fill(&mut bytes)?;
486 Ok(bytes)
487}
488
489/// Max value when generating a random number from a range.
490///
491/// In Ruby, the `rand` family of functions generate random numbers form within
492/// a range. This range is always anchored on the left by zero. The `Max` enum
493/// allows callers to specify the upper bound of the range. If the `None`
494/// variant is given, the default is set to generate floats in the range of
495/// `[0.0, 1.0)`.
496#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
497pub enum Max {
498 /// Generate floats in the range `[0, max)`.
499 ///
500 /// If `max` is less than or equal to zero, the range defaults to floats
501 /// in `[0.0, 1.0]`.
502 ///
503 /// If `max` is [`NaN`](f64::NAN), an error is returned.
504 Float(f64),
505 /// Generate signed integers in the range `[0, max)`.
506 ///
507 /// If `max` is less than or equal to zero, the range defaults to floats
508 /// in `[0.0, 1.0]`.
509 Integer(i64),
510 /// Generate floats in the range `[0.0, 1.0]`.
511 None,
512}
513
514/// Random numeric value generated from the secure random number generator.
515///
516/// In Ruby, the `rand` family of functions generate random numbers that are
517/// either floats or signed integers.
518///
519/// The numeric contents of this enum will never be negative and will always be
520/// finite.
521#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
522pub enum Rand {
523 /// A random float that is greater than or equal to zero.
524 Float(f64),
525 /// A random signed integer that is greater than or equal to zero.
526 Integer(i64),
527}
528
529/// Generate a single random number, either a float or an integer.
530///
531/// In Ruby, the `rand` family of functions generate random numbers that are
532/// either floats or signed integers.
533///
534/// The random numbers returned by this function will never be negative and will
535/// always be finite.
536///
537/// In Ruby, the `rand` family of functions generate random numbers form within
538/// a range. This range is always anchored on the left by zero. See the [`Max`]
539/// enum documentation for how to bound the random numbers returned by this
540/// function.
541///
542/// # Examples
543///
544/// ```rust
545/// # use spinoso_securerandom::{Max, Rand};
546/// # fn example() -> Result<(), spinoso_securerandom::Error> {
547/// let rand = spinoso_securerandom::random_number(Max::None)?;
548/// assert!(matches!(rand, Rand::Float(_)));
549///
550/// let rand = spinoso_securerandom::random_number(Max::Integer(57))?;
551/// assert!(matches!(rand, Rand::Integer(_)));
552///
553/// let rand = spinoso_securerandom::random_number(Max::Integer(-20))?;
554/// assert!(matches!(rand, Rand::Float(_)));
555///
556/// let rand = spinoso_securerandom::random_number(Max::Integer(0))?;
557/// assert!(matches!(rand, Rand::Float(_)));
558///
559/// let rand = spinoso_securerandom::random_number(Max::Float(57.0))?;
560/// assert!(matches!(rand, Rand::Float(_)));
561///
562/// let rand = spinoso_securerandom::random_number(Max::Float(-20.0))?;
563/// assert!(matches!(rand, Rand::Float(_)));
564///
565/// let rand = spinoso_securerandom::random_number(Max::Float(0.0))?;
566/// assert!(matches!(rand, Rand::Float(_)));
567/// # Ok(())
568/// # }
569/// # example().unwrap()
570/// ```
571///
572/// # Errors
573///
574/// If the float given in a [`Max::Float`] variant is [`NaN`](f64::NAN) or
575/// infinite, a [`DomainError`] is returned.
576#[inline]
577pub fn random_number(max: Max) -> Result<Rand, Error> {
578 fn get_random_number<T: Rng + CryptoRng>(mut rng: T, max: Max) -> Result<Rand, DomainError> {
579 match max {
580 Max::Float(max) if !max.is_finite() => {
581 // NOTE: MRI returns `Errno::EDOM` exception class.
582 Err(DomainError::new())
583 }
584 Max::Float(max) if max <= 0.0 => {
585 let number = rng.random_range(0.0..1.0);
586 Ok(Rand::Float(number))
587 }
588 Max::Float(max) => {
589 let number = rng.random_range(0.0..max);
590 Ok(Rand::Float(number))
591 }
592 Max::Integer(max) if !max.is_positive() => {
593 let number = rng.random_range(0.0..1.0);
594 Ok(Rand::Float(number))
595 }
596 Max::Integer(max) => {
597 let number = rng.random_range(0..max);
598 Ok(Rand::Integer(number))
599 }
600 Max::None => {
601 let number = rng.random_range(0.0..1.0);
602 Ok(Rand::Float(number))
603 }
604 }
605 }
606
607 let rng = StdRng::try_from_os_rng()?;
608 let num = get_random_number(rng, max)?;
609 Ok(num)
610}
611
612/// Generate a hex-encoded [`String`] of random bytes.
613///
614/// If `len` is [`Some`] and non-negative, generate a vector of `len` random
615/// bytes. If `len` is [`None`], generate 16 random bytes. Take the resulting
616/// bytes and hexadecimal encode them.
617///
618/// # Examples
619///
620/// ```rust
621/// # fn example() -> Result<(), spinoso_securerandom::Error> {
622/// let bytes = spinoso_securerandom::hex(Some(1024))?;
623/// assert_eq!(bytes.len(), 2048);
624/// assert!(bytes.is_ascii());
625/// # Ok(())
626/// # }
627/// # example().unwrap()
628/// ```
629///
630/// # Errors
631///
632/// If the given length is negative, return an [`ArgumentError`].
633///
634/// If the underlying source of randomness returns an error, return a
635/// [`RandomBytesError`].
636#[inline]
637pub fn hex(len: Option<i64>) -> Result<String, Error> {
638 let bytes = random_bytes(len)?;
639 let s = hex::try_encode(bytes)?;
640 Ok(s)
641}
642
643/// Generate a base64-encoded [`String`] of random bytes.
644///
645/// If `len` is [`Some`] and non-negative, generate a vector of `len` random
646/// bytes. If `len` is [`None`], generate 16 random bytes. Take the resulting
647/// bytes and base64 encode them.
648///
649/// # Examples
650///
651/// ```rust
652/// # fn example() -> Result<(), spinoso_securerandom::Error> {
653/// let bytes = spinoso_securerandom::base64(Some(1024))?;
654/// assert_eq!(bytes.len(), 1368);
655/// assert!(bytes.is_ascii());
656/// # Ok(())
657/// # }
658/// # example().unwrap()
659/// ```
660///
661/// # Errors
662///
663/// If the given length is negative, return an [`ArgumentError`].
664///
665/// If the underlying source of randomness returns an error, return a
666/// [`RandomBytesError`].
667#[inline]
668pub fn base64(len: Option<i64>) -> Result<String, Error> {
669 // A `GeneralPurpose` engine using the `alphabet::STANDARD` base64 alphabet
670 // and PAD config.
671 use base64::engine::Engine as _;
672 use base64::engine::general_purpose::STANDARD;
673
674 let bytes = random_bytes(len)?;
675 Ok(STANDARD.encode(bytes))
676}
677
678/// Generate a URL-safe base64-encoded [`String`] of random bytes.
679///
680/// If `len` is [`Some`] and non-negative, generate a vector of `len` random
681/// bytes. If `len` is [`None`], generate 16 random bytes. Take the resulting
682/// bytes and base64 encode them.
683///
684/// # Examples
685///
686/// ```rust
687/// # fn example() -> Result<(), spinoso_securerandom::Error> {
688/// let bytes = spinoso_securerandom::urlsafe_base64(Some(1024), false)?;
689/// assert_eq!(bytes.len(), 1366);
690/// assert!(bytes.is_ascii());
691/// # Ok(())
692/// # }
693/// # example().unwrap()
694/// ```
695///
696/// # Errors
697///
698/// If the given length is negative, return an [`ArgumentError`].
699///
700/// If the underlying source of randomness returns an error, return a
701/// [`RandomBytesError`].
702#[inline]
703pub fn urlsafe_base64(len: Option<i64>, padding: bool) -> Result<String, Error> {
704 use base64::engine::Engine as _;
705 use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD};
706
707 let bytes = random_bytes(len)?;
708 let engine = if padding { URL_SAFE } else { URL_SAFE_NO_PAD };
709 Ok(engine.encode(bytes))
710}
711
712/// Generate a random sequence of ASCII alphanumeric bytes.
713///
714/// If `len` is [`Some`] and non-negative, generate a [`String`] of `len`
715/// random ASCII alphanumeric bytes. If `len` is [`None`], generate 16 random
716/// alphanumeric bytes.
717///
718/// The returned [`Vec<u8>`](Vec) is guaranteed to contain only ASCII bytes.
719///
720/// # Examples
721///
722/// ```rust
723/// # use std::error::Error;
724/// # fn example() -> Result<(), Box<dyn Error>> {
725/// let bytes = spinoso_securerandom::alphanumeric(Some(1024))?;
726/// let bytes = String::from_utf8(bytes)?;
727/// assert_eq!(bytes.len(), 1024);
728/// assert!(bytes.is_ascii());
729/// assert!(bytes.find(|ch: char| !ch.is_ascii_alphanumeric()).is_none());
730/// # Ok(())
731/// # }
732/// # example().unwrap()
733/// ```
734///
735/// # Errors
736///
737/// If the given length is negative, return an [`ArgumentError`].
738///
739/// If an allocation error occurs, an error is returned.
740#[inline]
741pub fn alphanumeric(len: Option<i64>) -> Result<Vec<u8>, Error> {
742 fn get_alphanumeric<T: Rng + CryptoRng>(rng: T, len: usize) -> Result<Vec<u8>, TryReserveError> {
743 let mut buf = Vec::new();
744 buf.try_reserve(len)?;
745 for ch in rng.sample_iter(Alphanumeric).take(len) {
746 buf.push(ch);
747 }
748 Ok(buf)
749 }
750
751 let len = match len.map(usize::try_from) {
752 Some(Ok(0)) => return Ok(Vec::new()),
753 Some(Ok(len)) => len,
754 Some(Err(_)) => {
755 let err = ArgumentError::with_message("negative string size (or size too big)");
756 return Err(err.into());
757 }
758 None => DEFAULT_REQUESTED_BYTES,
759 };
760
761 let rng = StdRng::try_from_os_rng()?;
762 let string = get_alphanumeric(rng, len)?;
763 Ok(string)
764}
765
766/// Generate a Version 4 (random) UUID and return a [`String`].
767///
768/// A Version 4 UUID is randomly generated. See [RFC4122] for details.
769///
770/// # Examples
771///
772/// ```rust
773/// # fn example() -> Result<(), spinoso_securerandom::Error> {
774/// let uuid = spinoso_securerandom::uuid()?;
775/// assert_eq!(uuid.len(), 36);
776/// assert!(uuid.chars().all(|ch| ch == '-' || ch.is_ascii_hexdigit()));
777/// # Ok(())
778/// # }
779/// # example().unwrap()
780/// ```
781///
782/// # Errors
783///
784/// If the underlying source of randomness returns an error, an error is
785/// returned.
786///
787/// If an allocation error occurs, an error is returned.
788///
789/// [RFC4122]: https://tools.ietf.org/html/rfc4122#section-4.4
790#[inline]
791pub fn uuid() -> Result<String, Error> {
792 uuid::v4()
793}
794
795#[cfg(test)]
796mod tests {
797 use core::ops::Not;
798
799 use super::{DomainError, Error, Max, Rand, alphanumeric, base64, hex, random_bytes, random_number, uuid};
800
801 #[test]
802 fn random_bytes_default_bytes() {
803 // <https://github.com/ruby/ruby/blob/v2_6_3/lib/securerandom.rb#L135>
804 assert_eq!(super::DEFAULT_REQUESTED_BYTES, 16);
805 let default_requested_bytes = random_bytes(None).unwrap();
806 assert_eq!(default_requested_bytes.len(), 16);
807 }
808
809 #[test]
810 fn random_bytes_len_must_be_positive() {
811 assert!(matches!(random_bytes(Some(-1)), Err(Error::Argument(_))));
812 assert!(matches!(base64(Some(-1)), Err(Error::Argument(_))));
813 assert!(matches!(hex(Some(-1)), Err(Error::Argument(_))));
814 assert!(alphanumeric(Some(-1)).is_err());
815 }
816
817 #[test]
818 fn random_bytes_zero_len_gives_empty_result() {
819 assert!(random_bytes(Some(0)).unwrap().is_empty());
820 assert!(base64(Some(0)).unwrap().is_empty());
821 assert!(hex(Some(0)).unwrap().is_empty());
822 assert!(alphanumeric(Some(0)).unwrap().is_empty());
823 }
824
825 #[test]
826 fn random_bytes_nonzero_len_gives_len_result() {
827 assert_eq!(random_bytes(Some(32)).unwrap().len(), 32);
828 assert_eq!(base64(Some(32)).unwrap().len(), 44);
829 assert_eq!(hex(Some(32)).unwrap().len(), 64);
830 assert_eq!(alphanumeric(Some(32)).unwrap().len(), 32);
831
832 // for a length that is not a power of two
833 assert_eq!(random_bytes(Some(57)).unwrap().len(), 57);
834 assert_eq!(base64(Some(57)).unwrap().len(), 76);
835 assert_eq!(hex(Some(57)).unwrap().len(), 114);
836 assert_eq!(alphanumeric(Some(57)).unwrap().len(), 57);
837 }
838
839 #[test]
840 fn random_bytes_none_len_gives_len_16_result() {
841 assert_eq!(random_bytes(None).unwrap().len(), 16);
842 assert_eq!(base64(None).unwrap().len(), 24);
843 assert_eq!(hex(None).unwrap().len(), 32);
844 assert_eq!(alphanumeric(None).unwrap().len(), 16);
845 }
846
847 #[test]
848 fn random_number_domain_error() {
849 assert_eq!(
850 random_number(Max::Float(f64::NAN)),
851 Err(Error::Domain(DomainError::new()))
852 );
853 assert_eq!(
854 random_number(Max::Float(f64::INFINITY)),
855 Err(Error::Domain(DomainError::new()))
856 );
857 assert_eq!(
858 random_number(Max::Float(f64::NEG_INFINITY)),
859 Err(Error::Domain(DomainError::new()))
860 );
861 }
862
863 #[test]
864 fn random_number_in_float_out_float() {
865 assert!(matches!(random_number(Max::None), Ok(Rand::Float(_))));
866 assert!(matches!(random_number(Max::Float(0.5)), Ok(Rand::Float(_))));
867 assert!(matches!(random_number(Max::Float(1.0)), Ok(Rand::Float(_))));
868 assert!(matches!(random_number(Max::Float(9000.63)), Ok(Rand::Float(_))));
869 assert!(matches!(random_number(Max::Float(0.0)), Ok(Rand::Float(_))));
870 assert!(matches!(random_number(Max::Float(-0.0)), Ok(Rand::Float(_))));
871 assert!(matches!(random_number(Max::Float(-1.0)), Ok(Rand::Float(_))));
872 }
873
874 #[test]
875 fn random_number_in_neg_integer_out_float() {
876 assert!(matches!(random_number(Max::Integer(-1)), Ok(Rand::Float(_))));
877 }
878
879 #[test]
880 fn random_number_in_zero_integer_out_float() {
881 assert!(matches!(random_number(Max::Integer(0)), Ok(Rand::Float(_))));
882 }
883
884 #[test]
885 fn random_number_in_pos_integer_out_integer() {
886 assert!(matches!(random_number(Max::Integer(1)), Ok(Rand::Integer(_))));
887 assert!(matches!(random_number(Max::Integer(9000)), Ok(Rand::Integer(_))));
888 assert!(matches!(random_number(Max::Integer(i64::MAX)), Ok(Rand::Integer(_))));
889 }
890
891 #[test]
892 fn uuid_format() {
893 let id = uuid().unwrap();
894 assert_eq!(id.len(), 36);
895 assert!(id.chars().all(|ch| ch == '-' || ch.is_ascii_hexdigit()));
896 assert!(id.chars().any(char::is_uppercase).not());
897 assert_eq!(&id[14..15], "4");
898 }
899
900 #[test]
901 fn alphanumeric_format() {
902 let random = alphanumeric(Some(1024)).unwrap();
903 assert!(random.iter().all(|&byte| byte.is_ascii_alphanumeric()));
904 }
905}