use super::hash::Sha1;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
#[must_use]
pub fn random_uuid() -> [u8; 16] {
let mut bytes = [0u8; 16];
if !fill_from_os(&mut bytes) {
bytes = mixed_entropy();
}
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
bytes
}
#[cfg(unix)]
fn fill_from_os(buf: &mut [u8; 16]) -> bool {
use std::io::Read;
std::fs::File::open("/dev/urandom")
.and_then(|mut file| file.read_exact(buf))
.is_ok()
}
#[cfg(not(unix))]
fn fill_from_os(_buf: &mut [u8; 16]) -> bool {
false
}
fn mixed_entropy() -> [u8; 16] {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let mut sha = Sha1::new();
for _ in 0..2 {
let mut hasher = RandomState::new().build_hasher();
hasher.write_u64(COUNTER.fetch_add(1, Ordering::Relaxed));
sha.update(&hasher.finish().to_le_bytes());
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_nanos());
sha.update(&now.to_le_bytes());
sha.update(&std::process::id().to_le_bytes());
sha.update(format!("{:?}", std::thread::current().id()).as_bytes());
let stack_marker = 0u8;
sha.update(&(std::ptr::addr_of!(stack_marker) as usize).to_le_bytes());
let digest = sha.finalize();
let mut out = [0u8; 16];
for (dst, src) in out.iter_mut().zip(digest) {
*dst = src;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuids_are_version_4_and_differ() {
let a = random_uuid();
let b = random_uuid();
assert_ne!(a, b);
for uuid in [a, b] {
assert_eq!(uuid[6] >> 4, 4);
assert_eq!(uuid[8] >> 6, 0b10);
}
}
#[test]
fn fallback_differs_between_calls() {
assert_ne!(mixed_entropy(), mixed_entropy());
}
}