1use core::fmt;
20
21use crate::hash::{md5, sha1};
22
23#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct Uuid([u8; 16]);
26
27#[inline]
31fn with_namespaced_input<R>(namespace: Uuid, name: &[u8], f: impl FnOnce(&[u8]) -> R) -> R {
32 const INLINE: usize = 256;
33 let total = 16 + name.len();
34 if total <= INLINE {
35 let mut buf = [0u8; INLINE];
36 buf[..16].copy_from_slice(&namespace.0);
37 buf[16..total].copy_from_slice(name);
38 f(&buf[..total])
39 } else {
40 let mut input = Vec::with_capacity(total);
41 input.extend_from_slice(&namespace.0);
42 input.extend_from_slice(name);
43 f(&input)
44 }
45}
46
47#[derive(Clone, Copy, PartialEq, Eq, Debug)]
49pub enum Variant {
50 Ncs,
52 Rfc4122,
54 Microsoft,
56 Future,
58}
59
60impl Uuid {
61 pub const NIL: Uuid = Uuid([0u8; 16]);
63 pub const MAX: Uuid = Uuid([0xFFu8; 16]);
65
66 pub const NAMESPACE_DNS: Uuid = Uuid([
68 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
69 ]);
70 pub const NAMESPACE_URL: Uuid = Uuid([
72 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
73 ]);
74 pub const NAMESPACE_OID: Uuid = Uuid([
76 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
77 ]);
78 pub const NAMESPACE_X500: Uuid = Uuid([
80 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
81 ]);
82
83 pub const fn from_bytes(bytes: [u8; 16]) -> Uuid {
85 Uuid(bytes)
86 }
87
88 pub const fn to_bytes(self) -> [u8; 16] {
90 self.0
91 }
92
93 pub const fn as_bytes(&self) -> &[u8; 16] {
95 &self.0
96 }
97
98 pub fn is_nil(&self) -> bool {
100 self.0 == [0u8; 16]
101 }
102
103 pub fn is_max(&self) -> bool {
105 self.0 == [0xFFu8; 16]
106 }
107
108 pub fn version(&self) -> u8 {
111 self.0[6] >> 4
112 }
113
114 pub fn variant(&self) -> Variant {
116 let b = self.0[8];
117 if b & 0x80 == 0x00 {
118 Variant::Ncs
119 } else if b & 0xC0 == 0x80 {
120 Variant::Rfc4122
121 } else if b & 0xE0 == 0xC0 {
122 Variant::Microsoft
123 } else {
124 Variant::Future
125 }
126 }
127
128 #[inline]
132 fn stamp(bytes: [u8; 16], version: u8) -> Uuid {
133 let mut v = u128::from_be_bytes(bytes);
134 v = (v & !(0xFu128 << 76)) | ((version as u128) << 76);
135 v = (v & !(0b11u128 << 62)) | (0b10u128 << 62);
136 Uuid(v.to_be_bytes())
137 }
138
139 pub fn new_v1(timestamp_100ns: u64, clock_seq: u16, node: [u8; 6]) -> Uuid {
143 let ts = timestamp_100ns & 0x0FFF_FFFF_FFFF_FFFF;
144 let time_low = (ts & 0xFFFF_FFFF) as u32;
145 let time_mid = ((ts >> 32) & 0xFFFF) as u16;
146 let time_hi = ((ts >> 48) & 0x0FFF) as u16;
147 let mut b = [0u8; 16];
148 b[0..4].copy_from_slice(&time_low.to_be_bytes());
149 b[4..6].copy_from_slice(&time_mid.to_be_bytes());
150 b[6..8].copy_from_slice(&time_hi.to_be_bytes());
151 b[8] = (clock_seq >> 8) as u8;
152 b[9] = (clock_seq & 0xFF) as u8;
153 b[10..16].copy_from_slice(&node);
154 Uuid::stamp(b, 1)
155 }
156
157 pub fn new_v6(timestamp_100ns: u64, clock_seq: u16, node: [u8; 6]) -> Uuid {
160 let ts = timestamp_100ns & 0x0FFF_FFFF_FFFF_FFFF;
161 let time_high = ((ts >> 28) & 0xFFFF_FFFF) as u32;
162 let time_mid = ((ts >> 12) & 0xFFFF) as u16;
163 let time_low = (ts & 0x0FFF) as u16;
164 let mut b = [0u8; 16];
165 b[0..4].copy_from_slice(&time_high.to_be_bytes());
166 b[4..6].copy_from_slice(&time_mid.to_be_bytes());
167 b[6..8].copy_from_slice(&time_low.to_be_bytes());
168 b[8] = (clock_seq >> 8) as u8;
169 b[9] = (clock_seq & 0xFF) as u8;
170 b[10..16].copy_from_slice(&node);
171 Uuid::stamp(b, 6)
172 }
173
174 pub fn new_v3(namespace: Uuid, name: &[u8]) -> Uuid {
179 with_namespaced_input(namespace, name, |input| Uuid::stamp(md5(input), 3))
180 }
181
182 pub fn new_v5(namespace: Uuid, name: &[u8]) -> Uuid {
186 with_namespaced_input(namespace, name, |input| {
187 let digest = sha1(input);
188 let mut b = [0u8; 16];
189 b.copy_from_slice(&digest[..16]);
190 Uuid::stamp(b, 5)
191 })
192 }
193
194 #[inline]
197 pub fn new_v4(random: [u8; 16]) -> Uuid {
198 Uuid::stamp(random, 4)
199 }
200
201 #[inline]
204 pub fn new_v7(unix_ms: u64, random: [u8; 10]) -> Uuid {
205 let mut b = [0u8; 16];
206 let ms = unix_ms & 0xFFFF_FFFF_FFFF;
207 b[0..6].copy_from_slice(&ms.to_be_bytes()[2..8]);
208 b[6..16].copy_from_slice(&random);
209 Uuid::stamp(b, 7)
210 }
211
212 pub fn new_v8(bytes: [u8; 16]) -> Uuid {
215 Uuid::stamp(bytes, 8)
216 }
217
218 pub fn parse(input: &str) -> Option<Uuid> {
222 let s = input.trim();
223 if s.len() == 36 {
226 return Self::parse_canonical(s.as_bytes().try_into().unwrap());
228 }
229 let s = s.strip_prefix("urn:uuid:").or_else(|| s.strip_prefix("URN:UUID:")).unwrap_or(s);
231 let s = s.strip_prefix('{').and_then(|t| t.strip_suffix('}')).unwrap_or(s);
232
233 let bytes = s.as_bytes();
236 let mut hex = [0u8; 32];
237 let mut n = 0usize;
238 match bytes.len() {
239 36 => {
240 for (i, &c) in bytes.iter().enumerate() {
242 if matches!(i, 8 | 13 | 18 | 23) {
243 if c != b'-' {
244 return None;
245 }
246 } else {
247 if n >= 32 {
248 return None;
249 }
250 hex[n] = c;
251 n += 1;
252 }
253 }
254 }
255 32 => hex.copy_from_slice(bytes),
256 _ => return None,
257 }
258 if n != 0 && n != 32 {
259 return None;
260 }
261
262 let mut out = [0u8; 16];
263 for i in 0..16 {
264 let hi = hex_val(hex[2 * i])?;
265 let lo = hex_val(hex[2 * i + 1])?;
266 out[i] = (hi << 4) | lo;
267 }
268 Some(Uuid(out))
269 }
270
271 #[inline]
275 fn parse_canonical(arr: &[u8; 36]) -> Option<Uuid> {
276 if arr[8] != b'-' || arr[13] != b'-' || arr[18] != b'-' || arr[23] != b'-' {
277 return None;
278 }
279 #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
280 {
281 return unsafe { x86_hex::parse_hyphenated(arr) }.map(Uuid);
282 }
283 #[cfg(all(target_arch = "x86_64", not(target_feature = "ssse3")))]
284 {
285 if x86_hex::available() {
286 return unsafe { x86_hex::parse_hyphenated(arr) }.map(Uuid);
287 }
288 }
289 #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
290 {
291 let mut out = [0u8; 16];
292 let mut bad = 0u8;
293 const PAIRS: [usize; 16] = [0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34];
295 for (i, &p) in PAIRS.iter().enumerate() {
296 let hi = HEX_DECODE[arr[p] as usize];
297 let lo = HEX_DECODE[arr[p + 1] as usize];
298 bad |= hi | lo;
299 out[i] = (hi << 4) | (lo & 0x0f);
300 }
301 if bad < 16 {
302 Some(Uuid(out))
303 } else {
304 None
305 }
306 }
307 }
308
309 pub fn parse_many(packed: &[u8]) -> Option<Vec<Uuid>> {
314 if packed.is_empty() || packed.len() % 36 != 0 {
315 return None;
316 }
317 #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
318 {
319 return unsafe { x86_hex::parse_batch(packed) };
320 }
321 #[cfg(all(target_arch = "x86_64", not(target_feature = "ssse3")))]
322 {
323 if x86_hex::available() {
324 return unsafe { x86_hex::parse_batch(packed) };
325 }
326 }
327 #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
328 {
329 let n = packed.len() / 36;
330 let mut out = Vec::with_capacity(n);
331 for chunk in packed.chunks_exact(36) {
332 let rec: &[u8; 36] = chunk.try_into().unwrap();
333 out.push(Self::parse_canonical(rec)?);
334 }
335 Some(out)
336 }
337 }
338}
339
340const HEX_DECODE: [u8; 256] = {
343 let mut t = [0xFFu8; 256];
344 let mut i = 0u8;
345 while i < 10 {
346 t[(b'0' + i) as usize] = i;
347 i += 1;
348 }
349 let mut i = 0u8;
350 while i < 6 {
351 t[(b'a' + i) as usize] = 10 + i;
352 t[(b'A' + i) as usize] = 10 + i;
353 i += 1;
354 }
355 t
356};
357
358fn hex_val(c: u8) -> Option<u8> {
359 match c {
360 b'0'..=b'9' => Some(c - b'0'),
361 b'a'..=b'f' => Some(c - b'a' + 10),
362 b'A'..=b'F' => Some(c - b'A' + 10),
363 _ => None,
364 }
365}
366
367const HEX_PAIRS: [[u8; 2]; 256] = {
370 const HEX: &[u8; 16] = b"0123456789abcdef";
371 let mut t = [[0u8; 2]; 256];
372 let mut i = 0;
373 while i < 256 {
374 t[i] = [HEX[i >> 4], HEX[i & 0xf]];
375 i += 1;
376 }
377 t
378};
379
380#[inline]
384pub fn encode_canonical(bytes: &[u8; 16], buf: &mut [u8; 36]) {
385 #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
387 {
388 return unsafe { x86_hex::encode_canonical(bytes, buf) };
390 }
391 #[cfg(all(target_arch = "x86_64", not(target_feature = "ssse3")))]
392 {
393 if x86_hex::available() {
394 unsafe { x86_hex::encode_canonical(bytes, buf) };
396 return;
397 }
398 }
399 #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
400 encode_canonical_scalar(bytes, buf);
401}
402
403#[inline]
405fn encode_canonical_scalar(bytes: &[u8; 16], buf: &mut [u8; 36]) {
406 let mut j = 0;
407 let mut write = |buf: &mut [u8; 36], range: std::ops::Range<usize>, j: &mut usize| {
408 for &byte in &bytes[range] {
409 let pair = HEX_PAIRS[byte as usize];
410 buf[*j] = pair[0];
411 buf[*j + 1] = pair[1];
412 *j += 2;
413 }
414 };
415 write(buf, 0..4, &mut j);
416 buf[j] = b'-';
417 j += 1;
418 write(buf, 4..6, &mut j);
419 buf[j] = b'-';
420 j += 1;
421 write(buf, 6..8, &mut j);
422 buf[j] = b'-';
423 j += 1;
424 write(buf, 8..10, &mut j);
425 buf[j] = b'-';
426 j += 1;
427 write(buf, 10..16, &mut j);
428}
429
430#[cfg(target_arch = "x86_64")]
433mod x86_hex {
434 #[inline]
436 pub fn available() -> bool {
437 std::is_x86_feature_detected!("ssse3")
438 }
439
440 #[target_feature(enable = "ssse3,sse2")]
441 pub unsafe fn encode_canonical(bytes: &[u8; 16], buf: &mut [u8; 36]) {
442 use core::arch::x86_64::*;
443 let v = _mm_loadu_si128(bytes.as_ptr() as *const __m128i);
444 let mask0f = _mm_set1_epi8(0x0f);
445 let hi = _mm_and_si128(_mm_srli_epi16(v, 4), mask0f);
447 let lo = _mm_and_si128(v, mask0f);
448 let lut = _mm_setr_epi8(
449 b'0' as i8, b'1' as i8, b'2' as i8, b'3' as i8, b'4' as i8, b'5' as i8, b'6' as i8,
450 b'7' as i8, b'8' as i8, b'9' as i8, b'a' as i8, b'b' as i8, b'c' as i8, b'd' as i8,
451 b'e' as i8, b'f' as i8,
452 );
453 let hi_hex = _mm_shuffle_epi8(lut, hi);
454 let lo_hex = _mm_shuffle_epi8(lut, lo);
455 let h0 = _mm_unpacklo_epi8(hi_hex, lo_hex);
457 let h1 = _mm_unpackhi_epi8(hi_hex, lo_hex);
458 let mask_lo = _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, -128, 8, 9, 10, 11, -128, 12, 13);
462 let dash_lo = _mm_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0x2d, 0, 0, 0, 0, 0x2d, 0, 0);
463 let out_lo = _mm_or_si128(_mm_shuffle_epi8(h0, mask_lo), dash_lo);
464 let mid = _mm_alignr_epi8(h1, h0, 14); let mask_mid = _mm_setr_epi8(0, 1, -128, 2, 3, 4, 5, -128, 6, 7, 8, 9, 10, 11, 12, 13);
466 let dash_mid = _mm_setr_epi8(0, 0, 0x2d, 0, 0, 0, 0, 0x2d, 0, 0, 0, 0, 0, 0, 0, 0);
467 let out_mid = _mm_or_si128(_mm_shuffle_epi8(mid, mask_mid), dash_mid);
468 _mm_storeu_si128(buf.as_mut_ptr() as *mut __m128i, out_lo);
469 _mm_storeu_si128(buf.as_mut_ptr().add(16) as *mut __m128i, out_mid);
470 let mut t = [0u8; 16];
471 _mm_storeu_si128(t.as_mut_ptr() as *mut __m128i, h1);
472 buf[32..36].copy_from_slice(&t[12..16]); }
474
475 #[inline]
477 #[target_feature(enable = "ssse3,sse2")]
478 unsafe fn nibbles(chars: core::arch::x86_64::__m128i) -> core::arch::x86_64::__m128i {
479 use core::arch::x86_64::*;
480 let lo = _mm_and_si128(chars, _mm_set1_epi8(0x0f));
481 let hi = _mm_srli_epi16(_mm_and_si128(chars, _mm_set1_epi8(0x40)), 6); let hi8 = _mm_slli_epi16(hi, 3); _mm_add_epi8(_mm_add_epi8(lo, hi8), hi) }
485
486 #[inline]
490 #[target_feature(enable = "ssse3,sse2")]
491 unsafe fn hex_ok(chars: core::arch::x86_64::__m128i) -> core::arch::x86_64::__m128i {
492 use core::arch::x86_64::*;
493 let cf = _mm_or_si128(chars, _mm_set1_epi8(0x20)); let clamp = |lo: i8, hi: i8| {
495 _mm_cmpeq_epi8(_mm_min_epu8(_mm_max_epu8(cf, _mm_set1_epi8(lo)), _mm_set1_epi8(hi)), cf)
496 };
497 _mm_or_si128(clamp(0x30, 0x39), clamp(0x61, 0x66))
498 }
499
500 #[inline]
506 #[target_feature(enable = "ssse3,sse2")]
507 unsafe fn decode_core(b: &[u8; 36], out: &mut [u8; 16]) -> core::arch::x86_64::__m128i {
508 use core::arch::x86_64::*;
509 let l0 = _mm_loadu_si128(b.as_ptr() as *const __m128i); let l1 = _mm_loadu_si128(b.as_ptr().add(16) as *const __m128i); let mask_a = _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 0, 0);
514 let mask_b = _mm_setr_epi8(0, 1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0);
515 let ra = _mm_shuffle_epi8(l0, mask_a);
516 let rb = _mm_shuffle_epi8(l1, mask_b);
517 let weights = _mm_set1_epi16(0x0110);
519 let ba = _mm_packus_epi16(_mm_maddubs_epi16(nibbles(ra), weights), _mm_setzero_si128());
520 let bb = _mm_packus_epi16(_mm_maddubs_epi16(nibbles(rb), weights), _mm_setzero_si128());
521 _mm_storel_epi64(out.as_mut_ptr() as *mut __m128i, ba); _mm_storel_epi64(out.as_mut_ptr().add(7) as *mut __m128i, bb); let nib = |c: u8| ((c & 0x0f) + 9 * (c >> 6)) & 0x0f;
527 out[14] = (nib(b[32]) << 4) | nib(b[33]);
528 out[15] = (nib(b[34]) << 4) | nib(b[35]);
529 _mm_and_si128(hex_ok(ra), hex_ok(rb))
530 }
531
532 #[target_feature(enable = "ssse3,sse2")]
536 pub unsafe fn parse_hyphenated(b: &[u8; 36]) -> Option<[u8; 16]> {
537 use core::arch::x86_64::*;
538 let mut out = [0u8; 16];
539 let mask = decode_core(b, &mut out);
540 let tail = super::HEX_DECODE[b[32] as usize]
541 | super::HEX_DECODE[b[33] as usize]
542 | super::HEX_DECODE[b[34] as usize]
543 | super::HEX_DECODE[b[35] as usize];
544 if _mm_movemask_epi8(mask) == 0xffff && tail < 16 {
545 Some(out)
546 } else {
547 None
548 }
549 }
550
551 #[target_feature(enable = "ssse3,sse2")]
560 pub unsafe fn parse_batch(packed: &[u8]) -> Option<Vec<super::Uuid>> {
561 use core::arch::x86_64::*;
562 let n = packed.len() / 36;
563 let mut out: Vec<super::Uuid> = Vec::with_capacity(n);
564 let mut acc = _mm_set1_epi8(-1i8); let mut tail_bad = 0u8;
566 let mut dash_bad = 0u8;
567 for chunk in packed.chunks_exact(36) {
568 let b: &[u8; 36] = chunk.try_into().unwrap();
569 dash_bad |= (b[8] ^ b'-') | (b[13] ^ b'-') | (b[18] ^ b'-') | (b[23] ^ b'-');
570 tail_bad |= super::HEX_DECODE[b[32] as usize]
571 | super::HEX_DECODE[b[33] as usize]
572 | super::HEX_DECODE[b[34] as usize]
573 | super::HEX_DECODE[b[35] as usize];
574 let mut rec = [0u8; 16];
575 acc = _mm_and_si128(acc, decode_core(b, &mut rec));
576 out.push(super::Uuid(rec));
577 }
578 if dash_bad == 0 && tail_bad < 16 && _mm_movemask_epi8(acc) == 0xffff {
579 Some(out)
580 } else {
581 None
582 }
583 }
584}
585
586pub fn encode_many(ids: &[Uuid]) -> Vec<u8> {
589 let mut out = vec![0u8; ids.len() * 36];
594 for (i, id) in ids.iter().enumerate() {
595 let slot: &mut [u8; 36] = (&mut out[i * 36..i * 36 + 36]).try_into().unwrap();
596 encode_canonical(&id.0, slot);
597 }
598 out
599}
600
601impl fmt::Display for Uuid {
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604 let mut buf = [0u8; 36];
605 encode_canonical(&self.0, &mut buf);
606 f.write_str(unsafe { core::str::from_utf8_unchecked(&buf) })
608 }
609}
610
611impl fmt::Debug for Uuid {
612 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613 write!(f, "Uuid({self})")
614 }
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620
621 #[test]
622 fn nil_and_max_render_canonically() {
623 assert_eq!(Uuid::NIL.to_string(), "00000000-0000-0000-0000-000000000000");
624 assert_eq!(Uuid::MAX.to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
625 assert!(Uuid::NIL.is_nil());
626 assert!(Uuid::MAX.is_max());
627 assert_eq!(Uuid::NIL.version(), 0);
628 assert_eq!(Uuid::MAX.version(), 0xF);
629 }
630
631 #[test]
632 fn parse_round_trips_every_accepted_form() {
633 let canonical = "550e8400-e29b-41d4-a716-446655440000";
634 let u = Uuid::parse(canonical).unwrap();
635 assert_eq!(u.to_string(), canonical);
636 assert_eq!(Uuid::parse("550e8400e29b41d4a716446655440000").unwrap(), u);
638 assert_eq!(Uuid::parse("{550e8400-e29b-41d4-a716-446655440000}").unwrap(), u);
639 assert_eq!(Uuid::parse("urn:uuid:550e8400-e29b-41d4-a716-446655440000").unwrap(), u);
640 assert_eq!(Uuid::parse("550E8400-E29B-41D4-A716-446655440000").unwrap(), u);
641 assert_eq!(Uuid::parse(" 550e8400-e29b-41d4-a716-446655440000 ").unwrap(), u);
642 }
643
644 #[test]
645 fn parse_rejects_malformed_input() {
646 assert_eq!(Uuid::parse(""), None);
647 assert_eq!(Uuid::parse("not-a-uuid"), None);
648 assert_eq!(Uuid::parse("550e8400-e29b-41d4-a716-44665544000"), None); assert_eq!(Uuid::parse("550e8400-e29b-41d4-a716-4466554400000"), None); assert_eq!(Uuid::parse("550e8400xe29b-41d4-a716-446655440000"), None); assert_eq!(Uuid::parse("ZZZe8400-e29b-41d4-a716-446655440000"), None); }
653
654 #[test]
655 fn parse_validates_every_hex_position_and_case() {
656 assert_eq!(Uuid::parse("ffffffff-ffff-ffff-ffff-ffffffffffff").unwrap(), Uuid::MAX);
658 assert_eq!(Uuid::parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF").unwrap(), Uuid::MAX);
659 assert_eq!(Uuid::parse("00000000-0000-0000-0000-000000000000").unwrap(), Uuid::NIL);
660 let base = "550e8400-e29b-41d4-a716-446655440000";
661 assert_eq!(
662 Uuid::parse("550E8400-e29b-41D4-A716-446655440000").unwrap(),
663 Uuid::parse(base).unwrap(),
664 "mixed-case canonical must equal lowercase",
665 );
666 for i in 0..36 {
670 if matches!(i, 8 | 13 | 18 | 23) {
671 continue; }
673 for c in [b'g', b'G', b':', b'/', b'@', b'`', b' ', 0x00u8, 0xff] {
674 let mut bad = base.as_bytes().to_vec();
675 bad[i] = c;
676 if let Ok(s) = std::str::from_utf8(&bad) {
677 assert_eq!(Uuid::parse(s), None, "must reject byte {c:#x} at position {i}");
678 }
679 }
680 }
681 }
682
683 #[test]
684 fn parse_many_round_trips_a_packed_column() {
685 let mut ids = Vec::new();
687 let mut st = 0x1234_5678_9abc_def0u64;
688 for _ in 0..257 {
689 let mut b = [0u8; 16];
690 for x in b.iter_mut() {
691 st = st.wrapping_mul(6364136223846793005).wrapping_add(1);
692 *x = (st >> 56) as u8;
693 }
694 ids.push(Uuid::from_bytes(b));
695 }
696 let packed = encode_many(&ids);
697 assert_eq!(Uuid::parse_many(&packed).unwrap(), ids);
698 assert_eq!(Uuid::parse_many(&[]), None);
700 assert_eq!(Uuid::parse_many(&packed[..packed.len() - 1]), None);
701 let n = ids.len();
707 for &rec in &[0usize, 3, n - 1] {
708 for &pos in &[0usize, 5, 8, 12, 18, 23, 30, 32, 35] {
709 let mut bad = packed.clone();
710 let byte = 36 * rec + pos;
711 bad[byte] = if pos == 8 || pos == 13 || pos == 18 || pos == 23 {
712 b'f' } else {
714 b'z' };
716 assert_eq!(
717 Uuid::parse_many(&bad),
718 None,
719 "record {rec} byte {pos} corruption not rejected"
720 );
721 }
722 }
723 }
724
725 #[test]
726 fn name_based_versions_match_the_rfc_worked_examples() {
727 let v3 = Uuid::new_v3(Uuid::NAMESPACE_DNS, b"www.example.com");
729 assert_eq!(v3.version(), 3);
730 assert_eq!(v3.variant(), Variant::Rfc4122);
731 assert_eq!(v3.to_string(), "5df41881-3aed-3515-88a7-2f4a814cf09e");
732
733 let v5 = Uuid::new_v5(Uuid::NAMESPACE_DNS, b"www.example.com");
734 assert_eq!(v5.version(), 5);
735 assert_eq!(v5.variant(), Variant::Rfc4122);
736 assert_eq!(v5.to_string(), "2ed6657d-e927-568b-95e1-2665a8aea6a2");
737 }
738
739 #[test]
740 fn version_and_variant_bits_are_correct_for_every_generated_version() {
741 let node = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
742 let cases: [(Uuid, u8); 7] = [
743 (Uuid::new_v1(0x1234_5678_9ABC, 0x0102, node), 1),
744 (Uuid::new_v3(Uuid::NAMESPACE_URL, b"a"), 3),
745 (Uuid::new_v4([0xAB; 16]), 4),
746 (Uuid::new_v5(Uuid::NAMESPACE_URL, b"a"), 5),
747 (Uuid::new_v6(0x1234_5678_9ABC, 0x0102, node), 6),
748 (Uuid::new_v7(0x0190_0000_0000, [0x11; 10]), 7),
749 (Uuid::new_v8([0x77; 16]), 8),
750 ];
751 for (u, want) in cases {
752 assert_eq!(u.version(), want, "version nibble for v{want}");
753 assert_eq!(u.variant(), Variant::Rfc4122, "variant for v{want}");
754 assert_eq!(Uuid::parse(&u.to_string()).unwrap(), u, "round trip v{want}");
756 }
757 }
758
759 #[test]
760 fn v7_is_time_ordered_by_bytes() {
761 let earlier = Uuid::new_v7(1000, [0xFF; 10]);
763 let later = Uuid::new_v7(1001, [0x00; 10]);
764 assert!(earlier < later, "v7 must sort by timestamp: {earlier} !< {later}");
765 }
766
767 #[test]
768 fn differential_against_the_uuid_crate() {
769 for name in ["", "a", "www.example.com", "the quick brown fox", "🦀"] {
771 let ours = Uuid::new_v3(Uuid::NAMESPACE_DNS, name.as_bytes());
772 let theirs = uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, name.as_bytes());
773 assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v3 differs for {name:?}");
774
775 let ours = Uuid::new_v5(Uuid::NAMESPACE_URL, name.as_bytes());
776 let theirs = uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, name.as_bytes());
777 assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v5 differs for {name:?}");
778 }
779
780 let rand16 = [
782 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
783 ];
784 let ours = Uuid::new_v4(rand16);
785 let theirs = uuid::Builder::from_random_bytes(rand16).into_uuid();
786 assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v4 differs");
787
788 let rand10 = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc];
789 let ms: u64 = 0x0190_2233_4455;
790 let ours = Uuid::new_v7(ms, rand10);
791 let theirs = uuid::Builder::from_unix_timestamp_millis(ms, &rand10).into_uuid();
792 assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v7 differs");
793
794 let mut state: u64 = 0xDEAD_BEEF_CAFE_F00D;
796 for _ in 0..2000 {
797 let mut bytes = [0u8; 16];
798 for byte in bytes.iter_mut() {
799 state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
800 *byte = (state >> 56) as u8;
801 }
802 let ours = Uuid::from_bytes(bytes);
803 let theirs = uuid::Uuid::from_bytes(bytes);
804 assert_eq!(ours.to_string(), theirs.to_string(), "display differs");
805 assert_eq!(Uuid::parse(&theirs.to_string()).unwrap(), ours, "parse differs");
806 assert_eq!(ours.version(), theirs.get_version_num() as u8, "version differs");
807 }
808 }
809}