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}