oxidite_security/
random.rs1use rand::Rng;
4use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
5use crate::{Result, SecurityError};
6
7#[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#[must_use]
18pub fn random_hex(length: usize) -> String {
19 hex::encode(random_bytes(length))
20}
21
22#[must_use]
24pub fn secure_token(bytes: usize) -> String {
25 URL_SAFE_NO_PAD.encode(random_bytes(bytes))
26}
27
28#[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#[must_use]
44pub fn random_range(min: u64, max: u64) -> u64 {
45 try_random_range(min, max).unwrap_or(min)
46}
47
48pub 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); }
75
76 #[test]
77 fn test_secure_token() {
78 let token = secure_token(32);
79 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}