use std::fmt::Write;
use aws_lc_rs::rand::{SecureRandom, SystemRandom};
#[inline]
pub fn parse_uuid(uuid_str: &str) -> std::io::Result<Vec<u8>> {
let mut bytes = Vec::with_capacity(16);
let mut first_nibble: Option<u8> = None;
for &c in uuid_str.as_bytes() {
let hex = match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
b'-' => continue,
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid uuid: {uuid_str}"),
));
}
};
if let Some(first) = first_nibble.take() {
bytes.push((first << 4) | hex);
} else {
first_nibble = Some(hex);
}
}
if first_nibble.is_some() || bytes.len() != 16 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid uuid: {uuid_str}"),
));
}
if (bytes[6] >> 4) != 4 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("UUID is not version 4: {uuid_str}"),
));
}
if (bytes[8] >> 6) != 2 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("UUID does not have RFC 4122 variant: {uuid_str}"),
));
}
Ok(bytes)
}
#[inline]
pub fn generate_uuid() -> String {
let rng = SystemRandom::new();
let mut bytes = [0u8; 16];
rng.fill(&mut bytes).expect("RNG failed");
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
let mut s = String::with_capacity(36);
for (i, &b) in bytes.iter().enumerate() {
if i == 4 || i == 6 || i == 8 || i == 10 {
s.push('-');
}
write!(s, "{:02x}", b).unwrap();
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_uuid_format() {
let uuid = generate_uuid();
assert_eq!(uuid.len(), 36);
assert_eq!(uuid.chars().nth(8), Some('-'));
assert_eq!(uuid.chars().nth(13), Some('-'));
assert_eq!(uuid.chars().nth(18), Some('-'));
assert_eq!(uuid.chars().nth(23), Some('-'));
assert_eq!(uuid.chars().nth(14), Some('4'));
let variant_char = uuid.chars().nth(19).unwrap();
assert!(
variant_char == '8'
|| variant_char == '9'
|| variant_char == 'a'
|| variant_char == 'b'
);
}
#[test]
fn test_generate_uuid_roundtrip() {
let uuid = generate_uuid();
let bytes = parse_uuid(&uuid).unwrap();
assert_eq!(bytes.len(), 16);
assert_eq!(bytes[6] >> 4, 4);
assert_eq!(bytes[8] >> 6, 2);
}
#[test]
fn test_parse_uuid_with_dashes() {
let uuid = "550e8400-e29b-41d4-a716-446655440000";
let bytes = parse_uuid(uuid).unwrap();
assert_eq!(bytes.len(), 16);
assert_eq!(bytes[0], 0x55);
assert_eq!(bytes[1], 0x0e);
}
#[test]
fn test_parse_uuid_without_dashes() {
let uuid = "550e8400e29b41d4a716446655440000";
let bytes = parse_uuid(uuid).unwrap();
assert_eq!(bytes.len(), 16);
assert_eq!(bytes[0], 0x55);
assert_eq!(bytes[1], 0x0e);
}
#[test]
fn test_parse_uuid_rejects_non_v4() {
let uuid = "550e8400-e29b-11d4-a716-446655440000";
assert!(parse_uuid(uuid).is_err());
}
#[test]
fn test_parse_uuid_rejects_wrong_variant() {
let uuid = "550e8400-e29b-41d4-c716-446655440000";
assert!(parse_uuid(uuid).is_err());
}
}