1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4use core::{fmt, str::FromStr};
54
55#[cfg(feature = "std")]
56use rand::Rng as _;
57
58mod hex;
59
60#[cfg(feature = "serde")]
61mod serde;
62
63#[cfg(feature = "std")]
64thread_local! {
65 static V7_STATE: std::cell::RefCell<(u64, u32)> = const { std::cell::RefCell::new((0, 0)) };
71}
72
73#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
75pub struct Uuid([u8; 16]);
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub enum Version {
82 Nil,
84 V1,
86 V2,
88 V3,
90 V4,
92 V5,
94 V6,
96 V7,
98 V8,
100 Max,
102 Unknown,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
108pub enum Error {
109 InvalidUuid,
112}
113
114impl fmt::Display for Error {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 Self::InvalidUuid => f.write_str("invalid uuid"),
118 }
119 }
120}
121
122impl core::error::Error for Error {}
123
124#[cfg(feature = "std")]
125impl Uuid {
126 #[inline]
144 pub fn new_v4() -> Uuid {
145 let mut bytes = [0u8; 16];
146 rand::rng().fill_bytes(&mut bytes);
147
148 bytes[6] = (bytes[6] & 0x0f) | 0x40;
150
151 bytes[8] = (bytes[8] & 0x3f) | 0x80;
153
154 Uuid(bytes)
155 }
156
157 pub fn new_v7() -> Uuid {
213 new_v7_at(v7_now_ms())
214 }
215}
216
217#[cfg(feature = "std")]
222fn new_v7_at(now_ms: u64) -> Uuid {
223 let (timestamp, counter) = V7_STATE.with(|cell| {
224 let mut state = cell.borrow_mut();
225 let (mut ts, mut ctr) = *state;
226
227 if now_ms > ts {
228 ts = now_ms;
230 ctr = v7_random_counter();
231 } else if ctr == u32::MAX {
232 ts += 1;
236 ctr = v7_random_counter();
237 } else {
238 ctr += 1;
241 }
242
243 *state = (ts, ctr);
244 (ts, ctr)
245 });
246
247 let mut bytes = [0u8; 16];
248 rand::rng().fill_bytes(&mut bytes);
249
250 bytes[0..6].copy_from_slice(×tamp.to_be_bytes()[2..8]);
252
253 bytes[6] = 0x70 | ((counter >> 28) & 0x0F) as u8;
255 bytes[7] = (counter >> 20) as u8;
257 bytes[8] = 0x80 | ((counter >> 14) & 0x3F) as u8;
259 bytes[9] = (counter >> 6) as u8;
261 bytes[10] = (((counter & 0x3F) << 2) as u8) | (bytes[10] & 0x03);
264
265 Uuid(bytes)
266}
267
268impl Uuid {
269 pub fn parse(input: impl AsRef<[u8]>) -> Result<Uuid, Error> {
287 let bytes = input.as_ref();
288 if bytes.len() != 36 {
289 return Err(Error::InvalidUuid);
290 }
291
292 if bytes[8] != b'-' || bytes[13] != b'-' || bytes[18] != b'-' || bytes[23] != b'-' {
293 return Err(Error::InvalidUuid);
294 }
295
296 let positions: [usize; 8] = [0, 4, 9, 14, 19, 24, 28, 32];
297 let mut buf = [0u8; 16];
298
299 for (j, &pos) in positions.iter().enumerate() {
300 let b0 = hex::decode_pair(bytes[pos], bytes[pos + 1]).ok_or(Error::InvalidUuid)?;
301 let b1 = hex::decode_pair(bytes[pos + 2], bytes[pos + 3]).ok_or(Error::InvalidUuid)?;
302 buf[j * 2] = b0;
303 buf[j * 2 + 1] = b1;
304 }
305
306 Ok(Uuid(buf))
307 }
308
309 #[inline]
329 pub fn to_heapless_string(&self) -> heapless::String<36> {
330 let buf = self.encode_lower();
331
332 let text = unsafe { core::str::from_utf8_unchecked(&buf) };
335
336 let mut out = heapless::String::new();
337 out.push_str(text).expect("36 bytes fit in heapless::String<36>");
340 out
341 }
342
343 #[inline]
345 const fn encode_lower(&self) -> [u8; 36] {
346 let bytes = &self.0;
347 let mut buf = [0u8; 36];
348
349 let groups: [(usize, usize); 5] = [(0, 8), (9, 13), (14, 18), (19, 23), (24, 36)];
350
351 let mut src = 0;
352 let mut gi = 0;
353 while gi < groups.len() {
354 let (start, end) = groups[gi];
355 let mut j = start;
356 while j < end {
357 let b = bytes[src];
358 src += 1;
359 buf[j] = hex::encode_nibble(b >> 4);
360 buf[j + 1] = hex::encode_nibble(b & 0x0f);
361 j += 2;
362 }
363 if gi < 4 {
364 buf[end] = b'-';
365 }
366 gi += 1;
367 }
368
369 buf
370 }
371
372 #[inline]
383 pub const fn from_bytes(bytes: [u8; 16]) -> Uuid {
384 Uuid(bytes)
385 }
386
387 #[inline]
402 pub fn from_slice(bytes: &[u8]) -> Result<Uuid, Error> {
403 <[u8; 16]>::try_from(bytes).map(Uuid).map_err(|_| Error::InvalidUuid)
404 }
405
406 #[inline]
417 pub const fn as_bytes(&self) -> [u8; 16] {
418 self.0
419 }
420
421 #[inline]
432 pub const fn from_u128(v: u128) -> Uuid {
433 Uuid([
434 (v >> 120) as u8,
435 (v >> 112) as u8,
436 (v >> 104) as u8,
437 (v >> 96) as u8,
438 (v >> 88) as u8,
439 (v >> 80) as u8,
440 (v >> 72) as u8,
441 (v >> 64) as u8,
442 (v >> 56) as u8,
443 (v >> 48) as u8,
444 (v >> 40) as u8,
445 (v >> 32) as u8,
446 (v >> 24) as u8,
447 (v >> 16) as u8,
448 (v >> 8) as u8,
449 v as u8,
450 ])
451 }
452
453 #[inline]
464 pub const fn as_u128(&self) -> u128 {
465 u128::from_be_bytes(self.0)
466 }
467
468 #[inline]
478 pub const fn nil() -> Uuid {
479 Uuid([0; 16])
480 }
481
482 #[inline]
492 pub const fn max() -> Uuid {
493 Uuid([0xff; 16])
494 }
495
496 pub const fn version(&self) -> Version {
507 match self.0[6] >> 4 {
508 1 => return Version::V1,
509 2 => return Version::V2,
510 3 => return Version::V3,
511 4 => return Version::V4,
512 5 => return Version::V5,
513 6 => return Version::V6,
514 7 => return Version::V7,
515 8 => return Version::V8,
516 _ => {}
517 }
518
519 let mut all_zero = true;
520 let mut all_max = true;
521
522 let mut i = 0;
523 while i < 16 {
524 if self.0[i] != 0 {
525 all_zero = false;
526 }
527 if self.0[i] != 0xff {
528 all_max = false;
529 }
530 i += 1;
531 }
532
533 if all_zero {
534 return Version::Nil;
535 }
536 if all_max {
537 return Version::Max;
538 }
539
540 Version::Unknown
541 }
542
543 #[inline]
560 pub fn timestamp(&self) -> Option<u64> {
561 if self.version() != Version::V7 {
562 return None;
563 }
564 Some(
565 (self.0[0] as u64) << 40
566 | (self.0[1] as u64) << 32
567 | (self.0[2] as u64) << 24
568 | (self.0[3] as u64) << 16
569 | (self.0[4] as u64) << 8
570 | self.0[5] as u64,
571 )
572 }
573}
574
575impl fmt::Display for Uuid {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585 let buf = self.encode_lower();
586
587 let text = unsafe { core::str::from_utf8_unchecked(&buf) };
590 f.write_str(text)
591 }
592}
593
594impl fmt::Debug for Uuid {
595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596 fmt::Display::fmt(self, f)
597 }
598}
599
600impl FromStr for Uuid {
601 type Err = Error;
602
603 fn from_str(s: &str) -> Result<Uuid, Error> {
604 Uuid::parse(s)
605 }
606}
607
608impl TryFrom<&str> for Uuid {
609 type Error = Error;
610
611 #[inline]
612 fn try_from(value: &str) -> Result<Uuid, Error> {
613 Uuid::parse(value)
614 }
615}
616
617impl TryFrom<&[u8]> for Uuid {
618 type Error = Error;
619
620 #[inline]
621 fn try_from(value: &[u8]) -> Result<Uuid, Error> {
622 Uuid::parse(value)
623 }
624}
625
626#[cfg(feature = "std")]
627#[inline]
628fn v7_now_ms() -> u64 {
629 std::time::SystemTime::UNIX_EPOCH
630 .elapsed()
631 .expect("SystemTime::UNIX_EPOCH elapsed should not fail")
632 .as_millis() as u64
633}
634
635#[cfg(feature = "std")]
636#[inline]
637fn v7_random_counter() -> u32 {
638 let mut seed = [0u8; 4];
642 rand::rng().fill_bytes(&mut seed);
643 u32::from_be_bytes(seed) & 0x7FFF_FFFF
644}
645
646#[cfg(all(test, feature = "std"))]
648fn v7_set_state(timestamp_ms: u64, counter: u32) {
649 V7_STATE.with(|cell| *cell.borrow_mut() = (timestamp_ms, counter));
650}
651
652#[cfg(all(test, feature = "std"))]
654fn v7_get_state() -> (u64, u32) {
655 V7_STATE.with(|cell| *cell.borrow())
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn nil() {
664 let uuid = Uuid::nil();
665 assert_eq!(uuid.version(), Version::Nil);
666 assert_eq!(uuid.to_string(), "00000000-0000-0000-0000-000000000000");
667 assert_eq!(Uuid::from_bytes(uuid.as_bytes()), uuid);
668 assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
669 }
670
671 #[test]
672 fn max() {
673 let uuid = Uuid::max();
674 assert_eq!(uuid.version(), Version::Max);
675 assert_eq!(uuid.to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
676 assert_eq!(Uuid::from_bytes(uuid.as_bytes()), uuid);
677 assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
678 }
679
680 #[test]
681 fn parse_v4() {
682 let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
683 assert_eq!(uuid.version(), Version::V4);
684 }
685
686 #[test]
687 fn parse_v4_uppercase() {
688 let uuid = Uuid::parse("F47AC10B-58CC-4372-A567-0E02B2C3D479").unwrap();
689 assert_eq!(uuid.version(), Version::V4);
690 }
691
692 #[test]
693 fn parse_v4_mixed_case() {
694 let uuid = Uuid::parse("F47ac10B-58cC-4372-A567-0e02B2c3d479").unwrap();
695 assert_eq!(uuid.version(), Version::V4);
696 }
697
698 #[test]
699 fn parse_invalid_short() {
700 assert_eq!(Uuid::parse("too-short"), Err(Error::InvalidUuid));
701 }
702
703 #[test]
704 fn parse_invalid_long() {
705 assert_eq!(
706 Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479-extra"),
707 Err(Error::InvalidUuid)
708 );
709 }
710
711 #[test]
712 fn parse_invalid_hyphen() {
713 assert_eq!(Uuid::parse("f47ac10b-58cc-4372-a567x0e02b2c3d479"), Err(Error::InvalidUuid));
714 }
715
716 #[test]
717 fn parse_invalid_hex() {
718 assert_eq!(Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d47g"), Err(Error::InvalidUuid));
719 }
720
721 #[test]
722 fn from_bytes_roundtrip() {
723 let bytes = [
724 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
725 ];
726 let uuid = Uuid::from_bytes(bytes);
727 assert_eq!(uuid.to_string(), "01020304-0506-0708-090a-0b0c0d0e0f10");
728 }
729
730 #[test]
731 fn parse_then_to_string() {
732 let s = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
733 let uuid = Uuid::parse(s).unwrap();
734 assert_eq!(uuid.to_string(), s);
735 }
736
737 #[test]
738 fn eq_and_clone() {
739 let a = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
740 let b = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
741 assert_eq!(a, b);
742 assert_eq!(a.clone(), b);
743 }
744
745 #[test]
746 fn copy_works() {
747 let a = Uuid::nil();
748 let b = a;
749 assert_eq!(a, b);
750 }
751
752 #[test]
753 fn from_str_valid() {
754 let uuid: Uuid = "f47ac10b-58cc-4372-a567-0e02b2c3d479".parse().unwrap();
755 assert_eq!(uuid.version(), Version::V4);
756 }
757
758 #[test]
759 fn from_str_invalid() {
760 let result: Result<Uuid, _> = "not-a-uuid".parse();
761 assert_eq!(result, Err(Error::InvalidUuid));
762 }
763
764 #[test]
765 fn unknown_version() {
766 let bytes = [
768 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
769 ];
770 let uuid = Uuid::from_bytes(bytes);
771 assert_eq!(uuid.version(), Version::Unknown);
772 }
773
774 #[test]
775 fn version_unknown_when_not_recognized() {
776 let bytes = [
778 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
779 ];
780 let uuid = Uuid::from_bytes(bytes);
781 assert_eq!(uuid.version(), Version::Unknown);
782 }
783
784 #[cfg(feature = "std")]
785 #[test]
786 fn new_v4_has_correct_version() {
787 let uuid = Uuid::new_v4();
788 assert_eq!(uuid.version(), Version::V4);
789 }
790
791 #[cfg(feature = "std")]
792 #[test]
793 fn new_v4_has_correct_variant() {
794 let uuid = Uuid::new_v4();
795 assert_eq!(uuid.0[8] & 0xc0, 0x80);
797 }
798
799 #[cfg(feature = "std")]
800 #[test]
801 fn new_v7_has_correct_version() {
802 let uuid = Uuid::new_v7();
803 assert_eq!(uuid.version(), Version::V7);
804 }
805
806 #[cfg(feature = "std")]
807 #[test]
808 fn new_v7_has_correct_variant() {
809 let uuid = Uuid::new_v7();
810 assert_eq!(uuid.0[8] & 0xc0, 0x80);
811 }
812
813 #[cfg(feature = "std")]
814 #[test]
815 fn new_v7_has_recent_timestamp() {
816 for _ in 0..100 {
817 let uuid = Uuid::new_v7();
818 let ts = uuid.timestamp().expect("v7 UUID should have a timestamp");
819
820 assert!(ts > 1_577_836_800_000, "timestamp should be recent: {}", ts);
821 assert!(ts < 4_102_444_800_000, "timestamp should not be in distant future: {}", ts);
822
823 let now_ms = std::time::SystemTime::UNIX_EPOCH
824 .elapsed()
825 .expect("SystemTime::UNIX_EPOCH elapsed should not fail")
826 .as_millis() as u64;
827
828 let diff = now_ms.saturating_sub(ts);
829 assert!(
830 diff < 10_000,
831 "v7 timestamp should be within 10s of system time: ts={ts}, now={now_ms}, diff={diff}",
832 );
833 }
834 }
835
836 #[cfg(feature = "std")]
837 #[test]
838 fn new_v7_has_monotonic_counter() {
839 let uuids: Vec<Uuid> = (0..50).map(|_| Uuid::new_v7()).collect();
842
843 for w in uuids.windows(2) {
844 let (a, b) = (w[0], w[1]);
845 let ts_a = a.timestamp().unwrap();
846 let ts_b = b.timestamp().unwrap();
847 let ctr_a = extract_v7_counter(&a);
848 let ctr_b = extract_v7_counter(&b);
849
850 assert!(
851 (ts_a, ctr_a) < (ts_b, ctr_b),
852 "UUIDs not monotonic: ts=({ts_a}, {ctr_a}), ts=({ts_b}, {ctr_b})",
853 );
854 }
855 }
856
857 #[cfg(feature = "std")]
859 fn extract_v7_counter(uuid: &Uuid) -> u32 {
860 ((uuid.0[6] & 0x0F) as u32) << 28
862 | (uuid.0[7] as u32) << 20
864 | ((uuid.0[8] & 0x3F) as u32) << 14
866 | (uuid.0[9] as u32) << 6
868 | ((uuid.0[10] >> 2) & 0x3F) as u32
870 }
871
872 fn xorshift64(state: &mut u64) -> u64 {
874 let mut x = *state;
875 x ^= x << 13;
876 x ^= x >> 7;
877 x ^= x << 17;
878 *state = x;
879 x
880 }
881
882 #[test]
885 fn to_heapless_string_matches_to_string() {
886 let samples = [
887 Uuid::nil(),
888 Uuid::max(),
889 Uuid::from_u128(0x0102_0304_0506_0708_090a_0b0c_0d0e_0f10),
890 Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap(),
891 ];
892 for uuid in samples {
893 let text = uuid.to_heapless_string();
894 assert_eq!(text.as_str(), uuid.to_string());
895 assert_eq!(text.len(), 36);
896 assert_eq!(text.as_bytes(), uuid.to_string().as_bytes());
897 assert_eq!(format!("{text}"), uuid.to_string());
898 assert_eq!(format!("{text:?}"), format!("{:?}", uuid.to_string()));
899 assert_eq!(text, uuid.to_string().as_str());
901 assert_eq!(AsRef::<str>::as_ref(&text), uuid.to_string());
902 }
903 }
904
905 #[test]
906 fn to_heapless_string_allocates_nothing() {
907 let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
908 let (text, c) = alloc_counter::measure(|| uuid.to_heapless_string());
909 assert_eq!(c.calls, 0, "to_heapless_string must not allocate");
910 assert_eq!(text.len(), 36);
911 assert_eq!(text.as_str(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
912 }
913
914 #[test]
915 fn parse_allocates_nothing() {
916 let (_, c) = alloc_counter::measure(|| Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap());
917 assert_eq!(c.calls, 0, "parse must not allocate");
918 }
919
920 #[test]
923 fn hash_ord_default() {
924 use std::collections::{BTreeMap, HashMap};
925
926 let a = Uuid::from_u128(1);
927 let b = Uuid::from_u128(2);
928
929 let mut map = HashMap::new();
931 map.insert(a, "a");
932 map.insert(b, "b");
933 assert_eq!(map.get(&a), Some(&"a"));
934
935 let mut v = [b, a];
937 v.sort();
938 assert_eq!(v, [a, b]);
939 let mut tree = BTreeMap::new();
940 tree.insert(b, ());
941 tree.insert(a, ());
942 assert_eq!(tree.keys().next(), Some(&a));
943
944 assert_eq!(Uuid::default(), Uuid::nil());
946 }
947
948 #[test]
949 fn try_from_impls() {
950 let s = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
951 let expected = Uuid::parse(s).unwrap();
952 assert_eq!(Uuid::try_from(s).unwrap(), expected);
953 assert_eq!(Uuid::try_from(s.as_bytes()).unwrap(), expected);
954 assert!(Uuid::try_from("nope").is_err());
955 assert!(Uuid::try_from(&b"nope"[..]).is_err());
956 }
957
958 #[test]
961 fn parse_strict_rejections() {
962 let rejected: &[&[u8]] = &[
965 b"",
966 b"0",
967 b"0-0-0-0-0",
968 b"1-1-1-1-1",
969 b"00112233-4455-6677-8899-aabbccddee", b"00112233-4455-6677-8899-aabbccddeeff0", b"00112233445566778899aabbccddeeff", b"urn:uuid:00112233-4455-6677-8899-aabbccddeeff",
973 b"{00112233-4455-6677-8899-aabbccddeeff}",
974 b" 00112233-4455-6677-8899-aabbccddeeff",
975 b"00112233-4455-6677-8899-aabbccddeeff ",
976 b"\t00112233-4455-6677-8899-aabbccddeeff",
977 b"00112233-4455-6677-8899-aabbccddeeff\n",
978 b"00112233-4455-6677-8899-aabbccddeefg",
979 b"00112233-4455-6677-8899-aabbccddeefG",
980 b"00112233+4455-6677-8899-aabbccddeeff",
981 b"00112233-4455-6677-8899-aabbccddee f",
982 b"00112233-4455-6677-8899-aabbccddee\x00",
983 b"00112233-4455-6677-8899-aabbccddef\xff",
984 b"00112233-4455-6677-8899-aabbccdde\xc3\xa9f", b"00112233-4455-6677-8899-aabbccddeeff--",
986 b"--00112233-4455-6677-8899-aabbccddee",
987 b"00112233-4455-6677-8899-aabbccddee fffffffff",
988 ];
989 for input in rejected {
990 assert_eq!(Uuid::parse(*input), Err(Error::InvalidUuid), "should reject {input:?}");
991 if let Ok(s) = std::str::from_utf8(input) {
992 assert_eq!(s.parse::<Uuid>(), Err(Error::InvalidUuid), "FromStr should reject {s:?}");
993 }
994 }
995 }
996
997 #[test]
998 fn parse_accepts_both_cases() {
999 let lower = Uuid::parse("00112233-4455-6677-8899-aabbccddeeff").unwrap();
1000 let upper = Uuid::parse("00112233-4455-6677-8899-AABBCCDDEEFF").unwrap();
1001 let mixed = Uuid::parse("00112233-4455-6677-8899-AabbCCddEEff").unwrap();
1002 assert_eq!(lower, upper);
1003 assert_eq!(lower, mixed);
1004 assert_eq!(lower.to_string(), "00112233-4455-6677-8899-aabbccddeeff");
1005 }
1006
1007 #[test]
1008 fn parse_mutation_sweep() {
1009 let good: [u8; 36] = *b"f47ac10b-58cc-4372-a567-0e02b2c3d479";
1012 for pos in 0..36 {
1013 for byte in 0..=255u8 {
1014 let mut buf = good;
1015 buf[pos] = byte;
1016 if let Ok(uuid) = Uuid::parse(&buf[..]) {
1017 assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
1018 }
1019 }
1020 }
1021 }
1022
1023 #[test]
1024 fn parse_random_garbage_never_panics() {
1025 let mut state = 0xdead_beef_cafe_babeu64;
1026 for _ in 0..100_000 {
1027 let len = (xorshift64(&mut state) % 65) as usize;
1028 let mut buf = [0u8; 64];
1029 for b in buf.iter_mut().take(len) {
1030 *b = xorshift64(&mut state) as u8;
1031 }
1032 let _ = Uuid::parse(&buf[..len]);
1034 }
1035 }
1036
1037 #[test]
1038 fn roundtrip_pseudo_random() {
1039 let mut state = 0x1234_5678_9abc_def0u64;
1040 for _ in 0..100_000 {
1041 let v = ((xorshift64(&mut state) as u128) << 64) | xorshift64(&mut state) as u128;
1042 let uuid = Uuid::from_u128(v);
1043 assert_eq!(uuid.as_u128(), v);
1044 assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
1045 assert_eq!(Uuid::parse(uuid.to_heapless_string().as_str()).unwrap(), uuid);
1046 }
1047 }
1048
1049 #[test]
1052 fn version_edge_cases() {
1053 let unknown = Uuid::from_bytes([0x00, 0, 0, 0, 0, 0, 0x00, 0x80, 0x80, 0, 0, 0, 0, 0, 0, 0x01]);
1055 assert_eq!(unknown.version(), Version::Unknown);
1056
1057 let almost_max = Uuid::from_bytes([0xff; 16]).as_u128() & !(1u128 << 64);
1060 assert_eq!(Uuid::from_u128(almost_max).version(), Version::Unknown);
1061
1062 assert_eq!(Uuid::max().version(), Version::Max);
1064 assert_eq!(Uuid::nil().version(), Version::Nil);
1065
1066 for (nibble, expected) in [
1068 (1u8, Version::V1),
1069 (2, Version::V2),
1070 (3, Version::V3),
1071 (4, Version::V4),
1072 (5, Version::V5),
1073 (6, Version::V6),
1074 (7, Version::V7),
1075 (8, Version::V8),
1076 ] {
1077 let mut bytes = [0u8; 16];
1078 bytes[6] = nibble << 4;
1079 bytes[8] = 0x80;
1080 assert_eq!(Uuid::from_bytes(bytes).version(), expected);
1081 }
1082 }
1083
1084 #[test]
1085 fn timestamp_only_for_v7() {
1086 assert_eq!(Uuid::nil().timestamp(), None);
1087 assert_eq!(Uuid::max().timestamp(), None);
1088 let v1 = Uuid::from_u128(0x1234_5678_9abc_1def_8000_0000_0000_0001);
1090 assert_eq!(v1.version(), Version::V1);
1091 assert_eq!(v1.timestamp(), None);
1092
1093 let v7 = Uuid::from_bytes([
1094 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0x70, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1095 ]);
1096 assert_eq!(v7.version(), Version::V7);
1097 assert_eq!(v7.timestamp(), Some(0x0190_1234));
1098 }
1099
1100 #[cfg(feature = "std")]
1103 #[test]
1104 fn v7_same_ms_values_strictly_increase() {
1105 v7_set_state(1_000, 0);
1109 let mut previous: Option<Uuid> = None;
1110 for _ in 0..10_000 {
1111 let uuid = new_v7_at(1_000);
1112 if let Some(prev) = previous {
1113 assert!(prev.as_u128() < uuid.as_u128(), "not increasing: {prev} -> {uuid}");
1114 }
1115 previous = Some(uuid);
1116 }
1117 }
1118
1119 #[cfg(feature = "std")]
1120 #[test]
1121 fn v7_clock_rollback_keeps_logical_timestamp() {
1122 v7_set_state(1_000, 5);
1123 let uuid = new_v7_at(500);
1126 assert_eq!(uuid.timestamp(), Some(1_000));
1127 assert_eq!(extract_v7_counter(&uuid), 6);
1128 assert_eq!(v7_get_state(), (1_000, 6));
1129
1130 let uuid = new_v7_at(500);
1131 assert_eq!(uuid.timestamp(), Some(1_000));
1132 assert_eq!(extract_v7_counter(&uuid), 7);
1133 }
1134
1135 #[cfg(feature = "std")]
1136 #[test]
1137 fn v7_counter_overflow_advances_logical_clock() {
1138 v7_set_state(100, u32::MAX);
1139 let uuid = new_v7_at(100);
1140 assert_eq!(uuid.timestamp(), Some(101));
1144 let counter = extract_v7_counter(&uuid);
1145 assert!(counter <= 0x7FFF_FFFF, "counter should be re-seeded: {counter}");
1146 assert_eq!(v7_get_state(), (101, counter));
1147 }
1148
1149 #[cfg(feature = "std")]
1150 #[test]
1151 fn v7_advancing_clock_reseeds_counter() {
1152 v7_set_state(100, 5);
1153 let uuid = new_v7_at(200);
1154 assert_eq!(uuid.timestamp(), Some(200));
1155 assert!(extract_v7_counter(&uuid) <= 0x7FFF_FFFF);
1156 }
1157
1158 #[cfg(feature = "std")]
1159 #[test]
1160 fn v7_real_calls_are_strictly_increasing() {
1161 let mut previous = Uuid::new_v7();
1162 for _ in 0..5_000 {
1163 let uuid = Uuid::new_v7();
1164 assert!(
1165 previous.as_u128() < uuid.as_u128(),
1166 "consecutive v7 UUIDs must be strictly increasing: {previous} -> {uuid}"
1167 );
1168 previous = uuid;
1169 }
1170 }
1171
1172 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1173 #[test]
1174 fn v7_multithread_unique() {
1175 use std::{
1176 collections::HashSet,
1177 sync::{Arc, Mutex},
1178 };
1179
1180 const THREADS: usize = 4;
1181 const PER_THREAD: usize = 20_000;
1182
1183 let seen = Arc::new(Mutex::new(HashSet::with_capacity(THREADS * PER_THREAD)));
1184 let handles: Vec<_> = (0..THREADS)
1185 .map(|_| {
1186 let seen = Arc::clone(&seen);
1187 std::thread::spawn(move || {
1188 let local: Vec<Uuid> = (0..PER_THREAD).map(|_| Uuid::new_v7()).collect();
1189 let mut seen = seen.lock().unwrap();
1190 for uuid in local {
1191 assert!(seen.insert(uuid), "duplicate v7 UUID across threads: {uuid}");
1192 }
1193 })
1194 })
1195 .collect();
1196 for handle in handles {
1197 handle.join().unwrap();
1198 }
1199 assert_eq!(seen.lock().unwrap().len(), THREADS * PER_THREAD);
1200 }
1201
1202 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1203 #[test]
1204 fn v4_multithread_unique() {
1205 use std::{
1206 collections::HashSet,
1207 sync::{Arc, Mutex},
1208 };
1209
1210 const THREADS: usize = 4;
1211 const PER_THREAD: usize = 20_000;
1212
1213 let seen = Arc::new(Mutex::new(HashSet::with_capacity(THREADS * PER_THREAD)));
1214 let handles: Vec<_> = (0..THREADS)
1215 .map(|_| {
1216 let seen = Arc::clone(&seen);
1217 std::thread::spawn(move || {
1218 let local: Vec<Uuid> = (0..PER_THREAD).map(|_| Uuid::new_v4()).collect();
1219 let mut seen = seen.lock().unwrap();
1220 for uuid in local {
1221 assert!(seen.insert(uuid), "duplicate v4 UUID across threads: {uuid}");
1222 }
1223 })
1224 })
1225 .collect();
1226 for handle in handles {
1227 handle.join().unwrap();
1228 }
1229 assert_eq!(seen.lock().unwrap().len(), THREADS * PER_THREAD);
1230 }
1231}