use core::fmt;
use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Uuid([u8; 16]);
impl Uuid {
pub const fn nil() -> Self {
Self([0u8; 16])
}
pub const fn max() -> Self {
Self([0xff; 16])
}
pub fn v4() -> Self {
let mut bytes = random_bytes_16();
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
Self(bytes)
}
pub fn v7() -> Self {
let ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
let mut bytes = random_bytes_16();
let ms_bytes = ms.to_be_bytes();
bytes[0..6].copy_from_slice(&ms_bytes[2..8]);
bytes[6] = (bytes[6] & 0x0f) | 0x70;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
Self(bytes)
}
pub const fn from_bytes(bytes: &[u8; 16]) -> Self {
Self(*bytes)
}
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
pub const fn version(&self) -> u8 {
self.0[6] >> 4
}
pub fn parse_str(input: &str) -> Result<Self, ParseError> {
let bytes = input.as_bytes();
if bytes.len() != 36 {
return Err(ParseError::InvalidLength(bytes.len()));
}
let hyphen_positions = [8usize, 13, 18, 23];
for &p in &hyphen_positions {
if bytes[p] != b'-' {
return Err(ParseError::InvalidGroup(p));
}
}
let mut out = [0u8; 16];
let mut hex_idx = 0;
let mut byte_idx = 0;
while hex_idx < 36 {
if hyphen_positions.contains(&hex_idx) {
hex_idx += 1;
continue;
}
let hi = hex_value(bytes[hex_idx]).ok_or(ParseError::InvalidChar(hex_idx))?;
let lo = hex_value(bytes[hex_idx + 1]).ok_or(ParseError::InvalidChar(hex_idx + 1))?;
out[byte_idx] = (hi << 4) | lo;
byte_idx += 1;
hex_idx += 2;
}
Ok(Self(out))
}
}
impl Default for Uuid {
fn default() -> Self {
Self::nil()
}
}
impl fmt::Display for Uuid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let b = &self.0;
write!(
f,
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
)
}
}
impl core::str::FromStr for Uuid {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError {
InvalidLength(usize),
InvalidGroup(usize),
InvalidChar(usize),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(n) => write!(f, "expected 36 characters, got {n}"),
Self::InvalidGroup(p) => write!(f, "expected hyphen at position {p}"),
Self::InvalidChar(p) => write!(f, "invalid hex digit at position {p}"),
}
}
}
impl std::error::Error for ParseError {}
#[inline]
const fn hex_value(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
thread_local! {
static RNG: RefCell<Xoshiro256SS> = RefCell::new(Xoshiro256SS::from_entropy());
}
fn random_bytes_16() -> [u8; 16] {
RNG.with(|cell| {
let mut r = cell.borrow_mut();
let a = r.next_u64();
let b = r.next_u64();
let mut out = [0u8; 16];
out[0..8].copy_from_slice(&a.to_be_bytes());
out[8..16].copy_from_slice(&b.to_be_bytes());
out
})
}
struct Xoshiro256SS {
s: [u64; 4],
}
impl Xoshiro256SS {
fn from_entropy() -> Self {
static SEED_COUNTER: AtomicU64 = AtomicU64::new(0);
let pid = std::process::id() as u64;
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let counter = SEED_COUNTER.fetch_add(1, Ordering::Relaxed);
let seed = pid
.wrapping_mul(0x9E37_79B9_7F4A_7C15)
.wrapping_add(nanos)
.wrapping_add(counter.wrapping_mul(0xBF58_476D_1CE4_E5B9));
Self::from_seed(seed)
}
fn from_seed(mut seed: u64) -> Self {
let mut s = [0u64; 4];
for slot in &mut s {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
*slot = z ^ (z >> 31);
}
if s == [0; 4] {
s[0] = 1;
}
Self { s }
}
#[inline]
fn next_u64(&mut self) -> u64 {
let result = self.s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
let t = self.s[1] << 17;
self.s[2] ^= self.s[0];
self.s[3] ^= self.s[1];
self.s[1] ^= self.s[2];
self.s[0] ^= self.s[3];
self.s[2] ^= t;
self.s[3] = self.s[3].rotate_left(45);
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v4_version_and_variant() {
let id = Uuid::v4();
assert_eq!(id.version(), 4);
assert_eq!(id.0[8] & 0xc0, 0x80);
}
#[test]
fn v7_version_and_variant() {
let id = Uuid::v7();
assert_eq!(id.version(), 7);
assert_eq!(id.0[8] & 0xc0, 0x80);
}
#[test]
fn display_format_canonical() {
let id = Uuid::v4();
let s = id.to_string();
assert_eq!(s.len(), 36);
let hyphen_positions: Vec<usize> = s
.char_indices()
.filter_map(|(i, c)| if c == '-' { Some(i) } else { None })
.collect();
assert_eq!(hyphen_positions, vec![8, 13, 18, 23]);
}
#[test]
fn v4_pair_differs() {
assert_ne!(Uuid::v4(), Uuid::v4());
}
#[test]
fn v7_pair_differs() {
assert_ne!(Uuid::v7(), Uuid::v7());
}
#[test]
fn v7_time_ordered_across_ms() {
let a = Uuid::v7();
std::thread::sleep(std::time::Duration::from_millis(2));
let b = Uuid::v7();
assert!(b.as_bytes() > a.as_bytes());
}
#[test]
fn nil_and_max() {
assert_eq!(Uuid::nil().as_bytes(), &[0u8; 16]);
assert_eq!(Uuid::max().as_bytes(), &[0xffu8; 16]);
assert_eq!(
Uuid::nil().to_string(),
"00000000-0000-0000-0000-000000000000"
);
assert_eq!(
Uuid::max().to_string(),
"ffffffff-ffff-ffff-ffff-ffffffffffff"
);
}
#[test]
fn default_is_nil() {
assert_eq!(Uuid::default(), Uuid::nil());
}
#[test]
fn from_bytes_roundtrip() {
let id = Uuid::v4();
assert_eq!(Uuid::from_bytes(id.as_bytes()), id);
}
#[test]
fn parse_rfc9562_v4_example() {
let s = "919108f7-52d1-4320-9bac-f847db4148a8";
let id = Uuid::parse_str(s).unwrap();
assert_eq!(id.version(), 4);
assert_eq!(id.0[8] & 0xc0, 0x80);
assert_eq!(id.to_string(), s);
}
#[test]
fn parse_rfc9562_v7_example() {
let s = "017f22e2-79b0-7cc3-98c4-dc0c0c07398f";
let id = Uuid::parse_str(s).unwrap();
assert_eq!(id.version(), 7);
assert_eq!(id.0[8] & 0xc0, 0x80);
assert_eq!(id.to_string(), s);
}
#[test]
fn parse_uppercase() {
let id = Uuid::parse_str("F47AC10B-58CC-4372-A567-0E02B2C3D479").unwrap();
assert_eq!(id.to_string(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
}
#[test]
fn parse_nil() {
let id = Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap();
assert_eq!(id, Uuid::nil());
}
#[test]
fn parse_rejects_short() {
assert!(matches!(
Uuid::parse_str("abc"),
Err(ParseError::InvalidLength(3))
));
}
#[test]
fn parse_rejects_missing_hyphen() {
assert!(matches!(
Uuid::parse_str("f47ac10b_58cc-4372-a567-0e02b2c3d479"),
Err(ParseError::InvalidGroup(8))
));
}
#[test]
fn parse_rejects_bad_hex() {
assert!(matches!(
Uuid::parse_str("g47ac10b-58cc-4372-a567-0e02b2c3d479"),
Err(ParseError::InvalidChar(0))
));
}
#[test]
fn from_str_works() {
let id: Uuid = "f47ac10b-58cc-4372-a567-0e02b2c3d479".parse().unwrap();
assert_eq!(id.to_string(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
}
#[test]
fn xoshiro_seeded_is_nonzero_state() {
let mut r = Xoshiro256SS::from_seed(0);
let a = r.next_u64();
let b = r.next_u64();
assert_ne!(a, 0);
assert_ne!(a, b);
}
#[test]
fn many_v4_unique() {
use std::collections::HashSet;
let mut set = HashSet::new();
for _ in 0..10_000 {
assert!(set.insert(Uuid::v4()));
}
}
}