1use core::fmt;
27use std::cell::RefCell;
28use std::sync::atomic::{AtomicU64, Ordering};
29use std::time::{SystemTime, UNIX_EPOCH};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
44pub struct Uuid([u8; 16]);
45
46impl Uuid {
47 pub const fn nil() -> Self {
57 Self([0u8; 16])
58 }
59
60 pub const fn max() -> Self {
70 Self([0xff; 16])
71 }
72
73 pub fn v4() -> Self {
87 let mut bytes = random_bytes_16();
88 bytes[6] = (bytes[6] & 0x0f) | 0x40;
89 bytes[8] = (bytes[8] & 0x3f) | 0x80;
90 Self(bytes)
91 }
92
93 pub fn v7() -> Self {
109 let ms = SystemTime::now()
110 .duration_since(UNIX_EPOCH)
111 .map(|d| d.as_millis() as u64)
112 .unwrap_or(0);
113 let mut bytes = random_bytes_16();
114 let ms_bytes = ms.to_be_bytes();
115 bytes[0..6].copy_from_slice(&ms_bytes[2..8]);
116 bytes[6] = (bytes[6] & 0x0f) | 0x70;
117 bytes[8] = (bytes[8] & 0x3f) | 0x80;
118 Self(bytes)
119 }
120
121 pub const fn from_bytes(bytes: &[u8; 16]) -> Self {
137 Self(*bytes)
138 }
139
140 pub const fn as_bytes(&self) -> &[u8; 16] {
142 &self.0
143 }
144
145 pub const fn version(&self) -> u8 {
149 self.0[6] >> 4
150 }
151
152 pub fn parse_str(input: &str) -> Result<Self, ParseError> {
168 let bytes = input.as_bytes();
169 if bytes.len() != 36 {
170 return Err(ParseError::InvalidLength(bytes.len()));
171 }
172 let hyphen_positions = [8usize, 13, 18, 23];
173 for &p in &hyphen_positions {
174 if bytes[p] != b'-' {
175 return Err(ParseError::InvalidGroup(p));
176 }
177 }
178 let mut out = [0u8; 16];
179 let mut hex_idx = 0;
180 let mut byte_idx = 0;
181 while hex_idx < 36 {
182 if hyphen_positions.contains(&hex_idx) {
183 hex_idx += 1;
184 continue;
185 }
186 let hi = hex_value(bytes[hex_idx]).ok_or(ParseError::InvalidChar(hex_idx))?;
187 let lo = hex_value(bytes[hex_idx + 1]).ok_or(ParseError::InvalidChar(hex_idx + 1))?;
188 out[byte_idx] = (hi << 4) | lo;
189 byte_idx += 1;
190 hex_idx += 2;
191 }
192 Ok(Self(out))
193 }
194}
195
196impl Default for Uuid {
197 fn default() -> Self {
198 Self::nil()
199 }
200}
201
202impl fmt::Display for Uuid {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 let b = &self.0;
205 write!(
206 f,
207 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
208 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
209 b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
210 )
211 }
212}
213
214impl core::str::FromStr for Uuid {
215 type Err = ParseError;
216 fn from_str(s: &str) -> Result<Self, Self::Err> {
217 Self::parse_str(s)
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum ParseError {
224 InvalidLength(usize),
226 InvalidGroup(usize),
228 InvalidChar(usize),
230}
231
232impl fmt::Display for ParseError {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 match self {
235 Self::InvalidLength(n) => write!(f, "expected 36 characters, got {n}"),
236 Self::InvalidGroup(p) => write!(f, "expected hyphen at position {p}"),
237 Self::InvalidChar(p) => write!(f, "invalid hex digit at position {p}"),
238 }
239 }
240}
241
242impl std::error::Error for ParseError {}
243
244#[inline]
245const fn hex_value(c: u8) -> Option<u8> {
246 match c {
247 b'0'..=b'9' => Some(c - b'0'),
248 b'a'..=b'f' => Some(c - b'a' + 10),
249 b'A'..=b'F' => Some(c - b'A' + 10),
250 _ => None,
251 }
252}
253
254thread_local! {
257 static RNG: RefCell<Xoshiro256SS> = RefCell::new(Xoshiro256SS::from_entropy());
258}
259
260fn random_bytes_16() -> [u8; 16] {
261 RNG.with(|cell| {
262 let mut r = cell.borrow_mut();
263 let a = r.next_u64();
264 let b = r.next_u64();
265 let mut out = [0u8; 16];
266 out[0..8].copy_from_slice(&a.to_be_bytes());
267 out[8..16].copy_from_slice(&b.to_be_bytes());
268 out
269 })
270}
271
272struct Xoshiro256SS {
273 s: [u64; 4],
274}
275
276impl Xoshiro256SS {
277 fn from_entropy() -> Self {
278 static SEED_COUNTER: AtomicU64 = AtomicU64::new(0);
279 let pid = std::process::id() as u64;
280 let nanos = SystemTime::now()
281 .duration_since(UNIX_EPOCH)
282 .map(|d| d.as_nanos() as u64)
283 .unwrap_or(0);
284 let counter = SEED_COUNTER.fetch_add(1, Ordering::Relaxed);
285 let seed = pid
286 .wrapping_mul(0x9E37_79B9_7F4A_7C15)
287 .wrapping_add(nanos)
288 .wrapping_add(counter.wrapping_mul(0xBF58_476D_1CE4_E5B9));
289 Self::from_seed(seed)
290 }
291
292 fn from_seed(mut seed: u64) -> Self {
293 let mut s = [0u64; 4];
294 for slot in &mut s {
295 seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
296 let mut z = seed;
297 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
298 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
299 *slot = z ^ (z >> 31);
300 }
301 if s == [0; 4] {
302 s[0] = 1;
303 }
304 Self { s }
305 }
306
307 #[inline]
308 fn next_u64(&mut self) -> u64 {
309 let result = self.s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
310 let t = self.s[1] << 17;
311 self.s[2] ^= self.s[0];
312 self.s[3] ^= self.s[1];
313 self.s[1] ^= self.s[2];
314 self.s[0] ^= self.s[3];
315 self.s[2] ^= t;
316 self.s[3] = self.s[3].rotate_left(45);
317 result
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn v4_version_and_variant() {
327 let id = Uuid::v4();
328 assert_eq!(id.version(), 4);
329 assert_eq!(id.0[8] & 0xc0, 0x80);
330 }
331
332 #[test]
333 fn v7_version_and_variant() {
334 let id = Uuid::v7();
335 assert_eq!(id.version(), 7);
336 assert_eq!(id.0[8] & 0xc0, 0x80);
337 }
338
339 #[test]
340 fn display_format_canonical() {
341 let id = Uuid::v4();
342 let s = id.to_string();
343 assert_eq!(s.len(), 36);
344 let hyphen_positions: Vec<usize> = s
345 .char_indices()
346 .filter_map(|(i, c)| if c == '-' { Some(i) } else { None })
347 .collect();
348 assert_eq!(hyphen_positions, vec![8, 13, 18, 23]);
349 }
350
351 #[test]
352 fn v4_pair_differs() {
353 assert_ne!(Uuid::v4(), Uuid::v4());
354 }
355
356 #[test]
357 fn v7_pair_differs() {
358 assert_ne!(Uuid::v7(), Uuid::v7());
359 }
360
361 #[test]
362 fn v7_time_ordered_across_ms() {
363 let a = Uuid::v7();
364 std::thread::sleep(std::time::Duration::from_millis(2));
365 let b = Uuid::v7();
366 assert!(b.as_bytes() > a.as_bytes());
367 }
368
369 #[test]
370 fn nil_and_max() {
371 assert_eq!(Uuid::nil().as_bytes(), &[0u8; 16]);
372 assert_eq!(Uuid::max().as_bytes(), &[0xffu8; 16]);
373 assert_eq!(
374 Uuid::nil().to_string(),
375 "00000000-0000-0000-0000-000000000000"
376 );
377 assert_eq!(
378 Uuid::max().to_string(),
379 "ffffffff-ffff-ffff-ffff-ffffffffffff"
380 );
381 }
382
383 #[test]
384 fn default_is_nil() {
385 assert_eq!(Uuid::default(), Uuid::nil());
386 }
387
388 #[test]
389 fn from_bytes_roundtrip() {
390 let id = Uuid::v4();
391 assert_eq!(Uuid::from_bytes(id.as_bytes()), id);
392 }
393
394 #[test]
396 fn parse_rfc9562_v4_example() {
397 let s = "919108f7-52d1-4320-9bac-f847db4148a8";
398 let id = Uuid::parse_str(s).unwrap();
399 assert_eq!(id.version(), 4);
400 assert_eq!(id.0[8] & 0xc0, 0x80);
401 assert_eq!(id.to_string(), s);
402 }
403
404 #[test]
406 fn parse_rfc9562_v7_example() {
407 let s = "017f22e2-79b0-7cc3-98c4-dc0c0c07398f";
408 let id = Uuid::parse_str(s).unwrap();
409 assert_eq!(id.version(), 7);
410 assert_eq!(id.0[8] & 0xc0, 0x80);
411 assert_eq!(id.to_string(), s);
412 }
413
414 #[test]
415 fn parse_uppercase() {
416 let id = Uuid::parse_str("F47AC10B-58CC-4372-A567-0E02B2C3D479").unwrap();
417 assert_eq!(id.to_string(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
418 }
419
420 #[test]
421 fn parse_nil() {
422 let id = Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap();
423 assert_eq!(id, Uuid::nil());
424 }
425
426 #[test]
427 fn parse_rejects_short() {
428 assert!(matches!(
429 Uuid::parse_str("abc"),
430 Err(ParseError::InvalidLength(3))
431 ));
432 }
433
434 #[test]
435 fn parse_rejects_missing_hyphen() {
436 assert!(matches!(
437 Uuid::parse_str("f47ac10b_58cc-4372-a567-0e02b2c3d479"),
438 Err(ParseError::InvalidGroup(8))
439 ));
440 }
441
442 #[test]
443 fn parse_rejects_bad_hex() {
444 assert!(matches!(
445 Uuid::parse_str("g47ac10b-58cc-4372-a567-0e02b2c3d479"),
446 Err(ParseError::InvalidChar(0))
447 ));
448 }
449
450 #[test]
451 fn from_str_works() {
452 let id: Uuid = "f47ac10b-58cc-4372-a567-0e02b2c3d479".parse().unwrap();
453 assert_eq!(id.to_string(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
454 }
455
456 #[test]
457 fn xoshiro_seeded_is_nonzero_state() {
458 let mut r = Xoshiro256SS::from_seed(0);
459 let a = r.next_u64();
460 let b = r.next_u64();
461 assert_ne!(a, 0);
462 assert_ne!(a, b);
463 }
464
465 #[test]
466 fn many_v4_unique() {
467 use std::collections::HashSet;
468 let mut set = HashSet::new();
469 for _ in 0..10_000 {
470 assert!(set.insert(Uuid::v4()));
471 }
472 }
473}