1use crate::error::{Error, Result};
12
13pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
17 let mut diff = (a.len() ^ b.len()) as u8;
18 for i in 0..a.len().max(b.len()) {
19 diff |= a.get(i).copied().unwrap_or(0) ^ b.get(i).copied().unwrap_or(0);
20 }
21 std::hint::black_box(diff) == 0
22}
23
24pub fn random_bytes(len: usize) -> Result<Vec<u8>> {
26 let mut buf = vec![0u8; len];
27 getrandom::fill(&mut buf)
28 .map_err(|e| Error::internal(format!("Random number generator unavailable: {}", e)))?;
29 Ok(buf)
30}
31
32pub fn random_token(bytes: usize) -> Result<String> {
35 let mut out = String::with_capacity(bytes * 2);
36 for b in random_bytes(bytes)? {
37 out.push(char::from_digit(u32::from(b >> 4), 16).unwrap_or('0'));
38 out.push(char::from_digit(u32::from(b & 0xf), 16).unwrap_or('0'));
39 }
40 Ok(out)
41}
42
43pub fn random_alphanumeric(len: usize) -> Result<String> {
47 const ALPHABET: &[u8; 62] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
48 let mut out = String::with_capacity(len);
49 while out.len() < len {
50 for b in random_bytes(len.max(8))? {
51 if b < 248 && out.len() < len {
53 out.push(ALPHABET[usize::from(b) % 62] as char);
54 }
55 }
56 }
57 Ok(out)
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use std::collections::HashSet;
64
65 #[test]
66 fn constant_time_eq_semantics() {
67 assert!(constant_time_eq(b"", b""));
68 assert!(constant_time_eq(b"secret", b"secret"));
69 assert!(!constant_time_eq(b"secret", b"secreT"));
70 assert!(!constant_time_eq(b"secret", b"secre"));
71 assert!(!constant_time_eq(b"", b"x"));
72 assert!(!constant_time_eq(b"abc", b"abc\0"));
74 }
75
76 #[test]
77 fn token_shape_and_uniqueness() {
78 let tokens: HashSet<String> = (0..200).map(|_| random_token(16).unwrap()).collect();
79 assert_eq!(tokens.len(), 200);
80 assert!(tokens.iter().all(|t| t.len() == 32
81 && t.bytes()
82 .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())));
83 assert_eq!(random_token(0).unwrap(), "");
84 }
85
86 #[test]
87 fn alphanumeric_shape_and_coverage() {
88 let s = random_alphanumeric(2000).unwrap();
89 assert_eq!(s.len(), 2000);
90 assert!(s.bytes().all(|b| b.is_ascii_alphanumeric()));
91 let distinct: HashSet<u8> = s.bytes().collect();
93 assert_eq!(distinct.len(), 62);
94 assert_eq!(random_alphanumeric(0).unwrap(), "");
95 }
96}