Skip to main content

oxidite_security/
random.rs

1//! Secure random generation
2
3use rand::Rng;
4use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
5use crate::{Result, SecurityError};
6
7/// Generate random bytes
8#[must_use]
9pub fn random_bytes(length: usize) -> Vec<u8> {
10    let mut rng = rand::rng();
11    let mut bytes = vec![0u8; length];
12    rng.fill(&mut bytes[..]);
13    bytes
14}
15
16/// Generate a random hex string
17#[must_use]
18pub fn random_hex(length: usize) -> String {
19    hex::encode(random_bytes(length))
20}
21
22/// Generate a secure token (URL-safe base64)
23#[must_use]
24pub fn secure_token(bytes: usize) -> String {
25    URL_SAFE_NO_PAD.encode(random_bytes(bytes))
26}
27
28/// Generate a random alphanumeric string
29#[must_use]
30pub fn random_alphanumeric(length: usize) -> String {
31    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
32    let mut rng = rand::rng();
33    
34    (0..length)
35        .map(|_| {
36            let idx = rng.random_range(0..CHARSET.len());
37            CHARSET[idx] as char
38        })
39        .collect()
40}
41
42/// Generate a random number in range
43#[must_use]
44pub fn random_range(min: u64, max: u64) -> u64 {
45    try_random_range(min, max).unwrap_or(min)
46}
47
48/// Generate a random number in half-open range (`min..max`).
49pub fn try_random_range(min: u64, max: u64) -> Result<u64> {
50    if min > max {
51        return Err(SecurityError::InvalidRange { min, max });
52    }
53    if min == max {
54        return Ok(min);
55    }
56    let mut rng = rand::rng();
57    Ok(rng.random_range(min..max))
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_random_bytes() {
66        let bytes = random_bytes(32);
67        assert_eq!(bytes.len(), 32);
68    }
69
70    #[test]
71    fn test_random_hex() {
72        let hex_str = random_hex(16);
73        assert_eq!(hex_str.len(), 32); // 16 bytes = 32 hex chars
74    }
75
76    #[test]
77    fn test_secure_token() {
78        let token = secure_token(32);
79        // Base64 encoded 32 bytes is about 43 chars
80        assert!(token.len() > 40);
81    }
82
83    #[test]
84    fn test_random_range() {
85        for _ in 0..100 {
86            let n = random_range(10, 20);
87            assert!(n >= 10 && n < 20);
88        }
89    }
90
91    #[test]
92    fn test_try_random_range_validation() {
93        assert!(try_random_range(5, 5).is_ok());
94        assert!(try_random_range(6, 5).is_err());
95    }
96}