1use rand::rngs::SmallRng;
7use rand::{Rng, SeedableRng};
8use std::cell::UnsafeCell;
9#[cfg(all(
10 any(target_arch = "x86", target_arch = "x86_64"),
11 not(target_feature = "ssse3")
12))]
13use std::sync::atomic::{AtomicU8, Ordering};
14
15const COUNTER_MAX: u32 = 0x3FFFF;
16const COUNTER_SEED_MASK: u32 = 0x0FFF;
17const HEX: &[u8; 16] = b"0123456789abcdef";
18#[cfg(not(target_arch = "aarch64"))]
19const HEX_PAIRS: [[u8; 2]; 256] = hex_pairs();
20#[cfg(all(
21 any(target_arch = "x86", target_arch = "x86_64"),
22 not(target_feature = "ssse3")
23))]
24static X86_FORMATTER: AtomicU8 = AtomicU8::new(0);
25
26#[cfg(not(target_arch = "aarch64"))]
27const fn hex_pairs() -> [[u8; 2]; 256] {
28 let mut pairs = [[0u8; 2]; 256];
29 let mut i = 0;
30
31 while i < 256 {
32 pairs[i][0] = HEX[i >> 4];
33 pairs[i][1] = HEX[i & 0x0f];
34 i += 1;
35 }
36
37 pairs
38}
39
40#[cfg(all(
41 any(target_arch = "x86", target_arch = "x86_64"),
42 not(target_feature = "ssse3")
43))]
44#[inline(always)]
45fn x86_has_ssse3() -> bool {
46 match X86_FORMATTER.load(Ordering::Relaxed) {
47 2 => true,
48 1 => false,
49 _ => {
50 let has_ssse3 = std::arch::is_x86_feature_detected!("ssse3");
51 X86_FORMATTER.store(if has_ssse3 { 2 } else { 1 }, Ordering::Relaxed);
52 has_ssse3
53 }
54 }
55}
56
57mod clock;
58mod sequential;
59
60pub use sequential::SequentialGenerator;
61
62struct ThreadState {
63 rng: SmallRng,
64 last_ms: u64,
65 last_nanos_within_ms: u32,
66 last_sampled_nanos_within_ms: u32,
67 counter: u32,
68 clock: clock::Clock,
69}
70
71impl ThreadState {
72 fn new() -> Self {
73 Self {
74 rng: SmallRng::from_rng(&mut rand::rng()),
75 last_ms: 0,
76 last_nanos_within_ms: 0,
77 last_sampled_nanos_within_ms: 0,
78 counter: 0,
79 clock: clock::Clock::new(),
80 }
81 }
82
83 #[inline(always)]
84 fn seed_counter(&mut self) -> u32 {
85 self.rng.next_u32() & COUNTER_SEED_MASK
89 }
90
91 #[inline(always)]
92 fn refresh_time(&mut self) -> bool {
93 let sample = self.clock.refresh_timestamp();
94 self.record_time_sample(sample, true)
95 }
96
97 #[inline(always)]
98 fn current_timestamp_sample(&self) -> clock::TimestampSample {
99 clock::TimestampSample {
100 ms: self.last_ms,
101 nanos_within_ms: self.last_nanos_within_ms,
102 }
103 }
104
105 #[inline(always)]
106 fn get_time(&mut self) -> u64 {
107 if self.last_ms == 0 || self.clock.should_refresh() {
108 self.refresh_time();
109 }
110 self.last_ms
111 }
112
113 #[inline(always)]
114 fn get_time_and_counter(&mut self) -> (u64, u32) {
115 if (self.last_ms == 0 || self.clock.should_refresh()) && self.refresh_time() {
116 (self.last_ms, self.counter)
117 } else {
118 let c = self.counter;
119 let mut current_timestamp = self.last_ms;
120
121 if c >= COUNTER_MAX {
123 current_timestamp += 1;
124 self.last_ms = current_timestamp;
125 self.last_nanos_within_ms = 0;
126 self.last_sampled_nanos_within_ms = 0;
127 self.counter = self.seed_counter();
128 (current_timestamp, self.counter)
129 } else {
130 let inc = c.wrapping_add(1);
131 self.counter = inc;
132 (current_timestamp, inc)
133 }
134 }
135 }
136
137 #[inline(always)]
138 fn record_time_sample(&mut self, sample: clock::TimestampSample, refreshed: bool) -> bool {
139 if sample.ms > self.last_ms {
140 if refreshed {
141 self.last_sampled_nanos_within_ms = sample.nanos_within_ms;
142 }
143 self.last_ms = sample.ms;
144 self.last_nanos_within_ms = sample.nanos_within_ms;
145 self.counter = self.seed_counter();
146 true
147 } else if sample.ms == self.last_ms {
148 if refreshed {
149 self.last_sampled_nanos_within_ms = sample.nanos_within_ms;
150 }
151 self.last_nanos_within_ms = self.last_nanos_within_ms.max(sample.nanos_within_ms);
152 false
153 } else {
154 false
155 }
156 }
157
158 #[inline(always)]
159 fn sample_time(&mut self) -> clock::TimestampSample {
160 let refreshed = self.last_ms == 0 || self.clock.should_refresh();
161 let sample = if refreshed {
162 self.clock.refresh_timestamp()
163 } else {
164 self.clock
165 .estimated_timestamp(self.last_ms, self.last_sampled_nanos_within_ms)
166 };
167 self.record_time_sample(sample, refreshed);
168 self.current_timestamp_sample()
169 }
170}
171
172thread_local! {
173 static STATE: UnsafeCell<ThreadState> = UnsafeCell::new(ThreadState::new());
174}
175
176#[inline(always)]
185fn with_state<R>(f: impl FnOnce(&mut ThreadState) -> R) -> R {
186 STATE.with(|state_cell| {
187 let state = unsafe { &mut *state_cell.get() };
190 f(state)
191 })
192}
193
194#[inline]
195fn compose_rand_a(random: u16, fraction: u16, bits: u8) -> u16 {
196 debug_assert!(bits <= 12);
197
198 let random_bits = 12 - bits;
199 let random_mask = if random_bits == 0 {
200 0
201 } else {
202 (1u16 << random_bits) - 1
203 };
204
205 ((fraction & ((1u16 << bits) - 1)) << random_bits) | (random & random_mask)
206}
207
208#[inline]
209fn uuid_v7_from_parts(timestamp_ms: u64, rand_a: u16, rand_b: u64) -> u128 {
210 let timestamp_part = (timestamp_ms as u128) << 80;
211 let version_part = 7u128 << 76;
212 let variant_part = 2u128 << 62;
213
214 timestamp_part
215 | version_part
216 | ((rand_a as u128) << 64)
217 | variant_part
218 | ((rand_b & 0x3FFF_FFFF_FFFF_FFFF) as u128)
219}
220
221#[inline]
237pub fn gen_id_u128() -> u128 {
238 with_state(|state| {
239 let timestamp = state.get_time();
240
241 let r1 = state.rng.next_u32();
243 let r2 = state.rng.next_u64();
244
245 let rand_a = (r1 & 0x0FFF) as u16;
247 let rand_b = r2 & 0x3FFF_FFFF_FFFF_FFFF;
249
250 uuid_v7_from_parts(timestamp, rand_a, rand_b)
251 })
252}
253
254#[inline]
256pub fn gen_id() -> u128 {
257 gen_id_u128()
258}
259
260#[inline]
272fn gen_id_with_sub_ms_bits(bits: u8) -> u128 {
273 debug_assert!(matches!(bits, 4 | 8 | 12));
274
275 with_state(|state| {
276 let sample = state.sample_time();
277
278 let r1 = state.rng.next_u32();
279 let r2 = state.rng.next_u64();
280
281 let fraction = sample.sub_ms_fraction(bits);
282 let rand_a = compose_rand_a((r1 & 0x0FFF) as u16, fraction, bits);
283 let rand_b = r2 & 0x3FFF_FFFF_FFFF_FFFF;
284
285 uuid_v7_from_parts(sample.ms, rand_a, rand_b)
286 })
287}
288
289#[inline]
291pub fn gen_id_with_sub_ms_4() -> u128 {
292 gen_id_with_sub_ms_bits(4)
293}
294
295#[inline]
297pub fn gen_id_with_sub_ms_8() -> u128 {
298 gen_id_with_sub_ms_bits(8)
299}
300
301#[inline]
303pub fn gen_id_with_sub_ms_12() -> u128 {
304 gen_id_with_sub_ms_bits(12)
305}
306
307pub fn gen_id_string() -> String {
318 gen_id_str().to_string()
319}
320
321pub fn gen_id_str() -> UuidString {
326 format_uuid(gen_id_u128())
327}
328
329pub fn format_uuid(id: u128) -> UuidString {
332 #[cfg(all(
333 any(target_arch = "x86", target_arch = "x86_64"),
334 target_feature = "ssse3"
335 ))]
336 {
337 return unsafe { format_uuid_simd(id) };
339 }
340
341 #[cfg(all(
342 any(target_arch = "x86", target_arch = "x86_64"),
343 not(target_feature = "ssse3")
344 ))]
345 {
346 if x86_has_ssse3() {
347 return unsafe { format_uuid_simd(id) };
350 }
351 }
352
353 #[cfg(target_arch = "aarch64")]
354 {
355 uuid_string_from_hex(unsafe { format_uuid_hex_neon(id) })
357 }
358
359 #[cfg(not(target_arch = "aarch64"))]
360 {
361 format_uuid_scalar(id)
362 }
363}
364
365pub fn format_uuid_hex(id: u128) -> UuidHex {
368 UuidHex(format_uuid_hex_bytes(id))
369}
370
371fn format_uuid_hex_bytes(id: u128) -> [u8; 32] {
372 #[cfg(all(
373 any(target_arch = "x86", target_arch = "x86_64"),
374 target_feature = "ssse3"
375 ))]
376 {
377 return unsafe { format_uuid_hex_simd(id) };
379 }
380
381 #[cfg(all(
382 any(target_arch = "x86", target_arch = "x86_64"),
383 not(target_feature = "ssse3")
384 ))]
385 {
386 if x86_has_ssse3() {
387 return unsafe { format_uuid_hex_simd(id) };
390 }
391 }
392
393 #[cfg(target_arch = "aarch64")]
394 {
395 unsafe { format_uuid_hex_neon(id) }
397 }
398
399 #[cfg(not(target_arch = "aarch64"))]
400 {
401 format_uuid_hex_scalar(id)
402 }
403}
404
405#[cfg(not(target_arch = "aarch64"))]
406#[inline(always)]
407fn format_uuid_scalar(id: u128) -> UuidString {
408 let mut out = UuidString([0; 36]);
409 let bytes = id.to_be_bytes();
410
411 unsafe {
412 let ptr = out.0.as_mut_ptr();
413
414 let pair = HEX_PAIRS[bytes[0] as usize];
416 *ptr.add(0) = pair[0];
417 *ptr.add(1) = pair[1];
418 let pair = HEX_PAIRS[bytes[1] as usize];
419 *ptr.add(2) = pair[0];
420 *ptr.add(3) = pair[1];
421 let pair = HEX_PAIRS[bytes[2] as usize];
422 *ptr.add(4) = pair[0];
423 *ptr.add(5) = pair[1];
424 let pair = HEX_PAIRS[bytes[3] as usize];
425 *ptr.add(6) = pair[0];
426 *ptr.add(7) = pair[1];
427 *ptr.add(8) = b'-';
428
429 let pair = HEX_PAIRS[bytes[4] as usize];
431 *ptr.add(9) = pair[0];
432 *ptr.add(10) = pair[1];
433 let pair = HEX_PAIRS[bytes[5] as usize];
434 *ptr.add(11) = pair[0];
435 *ptr.add(12) = pair[1];
436 *ptr.add(13) = b'-';
437
438 let pair = HEX_PAIRS[bytes[6] as usize];
440 *ptr.add(14) = pair[0];
441 *ptr.add(15) = pair[1];
442 let pair = HEX_PAIRS[bytes[7] as usize];
443 *ptr.add(16) = pair[0];
444 *ptr.add(17) = pair[1];
445 *ptr.add(18) = b'-';
446
447 let pair = HEX_PAIRS[bytes[8] as usize];
449 *ptr.add(19) = pair[0];
450 *ptr.add(20) = pair[1];
451 let pair = HEX_PAIRS[bytes[9] as usize];
452 *ptr.add(21) = pair[0];
453 *ptr.add(22) = pair[1];
454 *ptr.add(23) = b'-';
455
456 let pair = HEX_PAIRS[bytes[10] as usize];
458 *ptr.add(24) = pair[0];
459 *ptr.add(25) = pair[1];
460 let pair = HEX_PAIRS[bytes[11] as usize];
461 *ptr.add(26) = pair[0];
462 *ptr.add(27) = pair[1];
463 let pair = HEX_PAIRS[bytes[12] as usize];
464 *ptr.add(28) = pair[0];
465 *ptr.add(29) = pair[1];
466 let pair = HEX_PAIRS[bytes[13] as usize];
467 *ptr.add(30) = pair[0];
468 *ptr.add(31) = pair[1];
469 let pair = HEX_PAIRS[bytes[14] as usize];
470 *ptr.add(32) = pair[0];
471 *ptr.add(33) = pair[1];
472 let pair = HEX_PAIRS[bytes[15] as usize];
473 *ptr.add(34) = pair[0];
474 *ptr.add(35) = pair[1];
475 }
476 out
477}
478
479#[cfg(not(target_arch = "aarch64"))]
480#[inline(always)]
481fn format_uuid_hex_scalar(id: u128) -> [u8; 32] {
482 let bytes = id.to_be_bytes();
483 let mut out = [0u8; 32];
484
485 for (idx, byte) in bytes.iter().enumerate() {
486 let pair = HEX_PAIRS[*byte as usize];
487 out[idx * 2] = pair[0];
488 out[idx * 2 + 1] = pair[1];
489 }
490
491 out
492}
493
494#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
495#[target_feature(enable = "ssse3")]
496unsafe fn format_uuid_simd(id: u128) -> UuidString {
497 uuid_string_from_hex(unsafe { format_uuid_hex_simd(id) })
498}
499
500#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
501#[target_feature(enable = "ssse3")]
502unsafe fn format_uuid_hex_simd(id: u128) -> [u8; 32] {
503 #[cfg(target_arch = "x86")]
504 use std::arch::x86::{
505 __m128i, _mm_and_si128, _mm_loadu_si128, _mm_set1_epi8, _mm_shuffle_epi8, _mm_srli_epi16,
506 _mm_storeu_si128, _mm_unpackhi_epi8, _mm_unpacklo_epi8,
507 };
508 #[cfg(target_arch = "x86_64")]
509 use std::arch::x86_64::{
510 __m128i, _mm_and_si128, _mm_loadu_si128, _mm_set1_epi8, _mm_shuffle_epi8, _mm_srli_epi16,
511 _mm_storeu_si128, _mm_unpackhi_epi8, _mm_unpacklo_epi8,
512 };
513
514 let bytes = id.to_be_bytes();
515 let mut hex = [0u8; 32];
516
517 unsafe {
518 let raw = _mm_loadu_si128(bytes.as_ptr() as *const __m128i);
519 let mask = _mm_set1_epi8(0x0f);
520 let table = _mm_loadu_si128(HEX.as_ptr() as *const __m128i);
521
522 let lo = _mm_and_si128(raw, mask);
523 let hi = _mm_and_si128(_mm_srli_epi16(raw, 4), mask);
524 let nibbles_lo = _mm_unpacklo_epi8(hi, lo);
525 let nibbles_hi = _mm_unpackhi_epi8(hi, lo);
526
527 let hex_lo = _mm_shuffle_epi8(table, nibbles_lo);
528 let hex_hi = _mm_shuffle_epi8(table, nibbles_hi);
529
530 _mm_storeu_si128(hex.as_mut_ptr() as *mut __m128i, hex_lo);
531 _mm_storeu_si128(hex.as_mut_ptr().add(16) as *mut __m128i, hex_hi);
532 }
533
534 hex
535}
536
537#[cfg(target_arch = "aarch64")]
538#[inline(always)]
539unsafe fn format_uuid_hex_neon(id: u128) -> [u8; 32] {
540 use std::arch::aarch64::{
541 uint8x16_t, vandq_u8, vdupq_n_u8, vld1q_u8, vqtbl1q_u8, vshrq_n_u8, vst1q_u8, vzip1q_u8,
542 vzip2q_u8,
543 };
544
545 let bytes = id.to_be_bytes();
546 let mut hex = [0u8; 32];
547
548 unsafe {
549 let raw = vld1q_u8(bytes.as_ptr());
550 let table = vld1q_u8(HEX.as_ptr());
551 let mask = vdupq_n_u8(0x0f);
552
553 let lo = vandq_u8(raw, mask);
554 let hi = vshrq_n_u8::<4>(raw);
555 let nibbles_lo: uint8x16_t = vzip1q_u8(hi, lo);
556 let nibbles_hi: uint8x16_t = vzip2q_u8(hi, lo);
557
558 let hex_lo = vqtbl1q_u8(table, nibbles_lo);
559 let hex_hi = vqtbl1q_u8(table, nibbles_hi);
560
561 vst1q_u8(hex.as_mut_ptr(), hex_lo);
562 vst1q_u8(hex.as_mut_ptr().add(16), hex_hi);
563 }
564
565 hex
566}
567
568#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
569#[inline(always)]
570fn uuid_string_from_hex(hex: [u8; 32]) -> UuidString {
571 let mut out = UuidString([0; 36]);
572
573 out.0[0..8].copy_from_slice(&hex[0..8]);
574 out.0[8] = b'-';
575 out.0[9..13].copy_from_slice(&hex[8..12]);
576 out.0[13] = b'-';
577 out.0[14..18].copy_from_slice(&hex[12..16]);
578 out.0[18] = b'-';
579 out.0[19..23].copy_from_slice(&hex[16..20]);
580 out.0[23] = b'-';
581 out.0[24..36].copy_from_slice(&hex[20..32]);
582
583 out
584}
585
586#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
592pub struct UuidString([u8; 36]);
593
594#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
596pub struct UuidHex([u8; 32]);
597
598impl UuidString {
599 #[inline]
601 pub fn as_str(&self) -> &str {
602 unsafe { std::str::from_utf8_unchecked(&self.0) }
604 }
605
606 #[inline]
608 pub fn as_bytes(&self) -> &[u8; 36] {
609 &self.0
610 }
611}
612
613impl std::ops::Deref for UuidString {
614 type Target = str;
615
616 #[inline]
617 fn deref(&self) -> &str {
618 self.as_str()
619 }
620}
621
622impl AsRef<str> for UuidString {
623 #[inline]
624 fn as_ref(&self) -> &str {
625 self.as_str()
626 }
627}
628
629impl PartialEq<str> for UuidString {
630 #[inline]
631 fn eq(&self, other: &str) -> bool {
632 self.as_str() == other
633 }
634}
635
636impl PartialEq<&str> for UuidString {
637 #[inline]
638 fn eq(&self, other: &&str) -> bool {
639 self.as_str() == *other
640 }
641}
642
643impl std::fmt::Display for UuidString {
644 #[inline]
645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646 f.write_str(self.as_str())
647 }
648}
649
650impl UuidHex {
651 #[inline]
653 pub fn as_str(&self) -> &str {
654 unsafe { std::str::from_utf8_unchecked(&self.0) }
656 }
657
658 #[inline]
660 pub fn as_bytes(&self) -> &[u8; 32] {
661 &self.0
662 }
663}
664
665impl std::ops::Deref for UuidHex {
666 type Target = str;
667
668 #[inline]
669 fn deref(&self) -> &str {
670 self.as_str()
671 }
672}
673
674impl AsRef<str> for UuidHex {
675 #[inline]
676 fn as_ref(&self) -> &str {
677 self.as_str()
678 }
679}
680
681impl std::fmt::Display for UuidHex {
682 #[inline]
683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684 f.write_str(self.as_str())
685 }
686}
687
688#[inline]
693pub fn gen_id_with_count() -> u128 {
694 with_state(|state| {
695 let (timestamp, counter) = state.get_time_and_counter();
696
697 let rand_a = ((counter >> 6) & 0x0FFF) as u16;
699 let rand_b_high = counter & 0x3F;
700
701 let rand_nr = state.rng.next_u64();
702
703 let rand_b_low = rand_nr & 0x00FF_FFFF_FFFF_FFFF;
704 let random_part = ((rand_b_high as u64) << 56) | rand_b_low;
705
706 uuid_v7_from_parts(timestamp, rand_a, random_part)
707 })
708}
709
710#[inline]
711pub fn gen_id_with_count_str() -> UuidString {
712 format_uuid(gen_id_with_count())
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn test_deadline_is_reached_at_zero_or_after_deadline() {
721 assert!(clock::deadline_reached(10, 0));
722 assert!(clock::deadline_reached(10, 10));
723 assert!(clock::deadline_reached(11, 10));
724 assert!(!clock::deadline_reached(9, 10));
725 }
726
727 #[test]
728 fn test_deadline_handles_counter_wraparound() {
729 assert!(!clock::deadline_reached(u64::MAX - 1, 3));
730 assert!(clock::deadline_reached(3, u64::MAX - 1));
731 }
732
733 #[test]
734 fn test_ticks_until_next_refresh_caps_at_half_millisecond() {
735 assert_eq!(clock::ticks_until_next_refresh(1_000, 0), 500);
736 assert_eq!(clock::ticks_until_next_refresh(1_000, 250_000), 500);
737 assert_eq!(clock::ticks_until_next_refresh(1_000, 500_000), 500);
738 assert_eq!(clock::ticks_until_next_refresh(1_000, 750_000), 250);
739 assert_eq!(clock::ticks_until_next_refresh(1_000, 999_999), 1);
740 assert_eq!(clock::ticks_until_next_refresh(1_000, 1_000_000), 500);
741 }
742
743 #[test]
744 fn test_ticks_until_next_refresh_never_returns_zero() {
745 assert_eq!(clock::ticks_until_next_refresh(0, 0), 1);
746 assert_eq!(clock::ticks_until_next_refresh(1, 999_999), 1);
747 }
748
749 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
750 #[test]
751 fn test_clock_without_counter_backend_always_refreshes() {
752 let mut clock = clock::Clock::new();
753 assert!(clock.should_refresh());
754
755 clock.record_sample(0, 0);
756 assert!(clock.should_refresh());
757 }
758
759 #[test]
760 fn test_next_id_performance() {
762 let start = std::time::Instant::now();
763 for _ in 0..10_000_000 {
764 let _ = gen_id_u128();
765 }
766 println!("Generated 10,000,000 IDs in {:?}", start.elapsed());
767 }
768
769 #[test]
770 fn test_next_id_uniqueness() {
771 let mut set = std::collections::HashSet::with_capacity(1_000_000);
772 for _ in 0..1_000_000 {
773 let id = gen_id_u128();
774 assert!(set.insert(id), "Duplicate ID generated: {:032x}", id);
775 }
776 }
777
778 #[test]
779 fn test_next_id_ordering() {
782 let mut last_id = 0;
783 for _ in 0..1_000_000 {
784 let id = gen_id_with_count();
785 if last_id != 0 {
786 assert!(
787 id > last_id,
788 "IDs are not ordered: {:032x} <= {:032x}",
789 id,
790 last_id
791 );
792 }
793 last_id = id;
794 }
795 }
796
797 #[test]
798 fn test_next_id_string() {
799 let id_str = gen_id_string();
800 assert_eq!(id_str.len(), 36);
801 assert!(uuid::Uuid::parse_str(&id_str).is_ok());
802 }
803
804 #[test]
805 fn test_format_uuid_correctness() {
806 let id = gen_id_u128();
807 let formatted = format_uuid(id);
808 let uuid_crate_str = uuid::Uuid::from_u128(id).to_string();
809 assert_eq!(formatted.as_ref(), uuid_crate_str);
810 }
811
812 #[test]
813 fn test_format_uuid_hex_correctness() {
814 let id = gen_id_u128();
815 let formatted = format_uuid_hex(id);
816 let uuid_crate_str = uuid::Uuid::from_u128(id).simple().to_string();
817 assert_eq!(formatted.as_ref(), uuid_crate_str);
818 }
819
820 #[test]
821 fn test_uuid_string_str_accessors() {
822 fn accepts_str(value: &str) -> usize {
823 value.len()
824 }
825
826 let formatted = gen_id_str();
827 assert_eq!(accepts_str(&formatted), 36);
828 assert_eq!(accepts_str(formatted.as_str()), 36);
829 assert!(uuid::Uuid::parse_str(formatted.as_str()).is_ok());
830 }
831
832 #[test]
833 fn test_gen_id_structure() {
834 let id = gen_id();
835 let uuid = uuid::Uuid::from_u128(id);
836 assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
837 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
838 }
839
840 #[test]
841 fn test_gen_id_with_count_structure() {
842 let id = gen_id_with_count();
843 let uuid = uuid::Uuid::from_u128(id);
844 assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
845 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
846 }
847
848 #[test]
849 fn test_gen_id_with_sub_ms_4_has_uuid_v7_layout() {
850 let id = gen_id_with_sub_ms_4();
851 let uuid = uuid::Uuid::from_u128(id);
852 assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
853 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
854
855 let rand_a = ((id >> 64) & 0x0FFF) as u16;
856 assert!(rand_a <= 0x0FFF);
857 }
858
859 #[test]
860 fn test_sub_ms_fraction_placement_for_expected_bit_widths() {
861 let sample = clock::TimestampSample {
862 ms: 0,
863 nanos_within_ms: 654_321,
864 };
865 let random = 0b1010_0110_1101u16;
866
867 for bits in [4, 8, 12] {
868 let fraction = sample.sub_ms_fraction(bits);
869 let rand_a = compose_rand_a(random, fraction, bits);
870 let random_bits = 12 - bits;
871 let extracted_fraction = rand_a >> random_bits;
872
873 assert_eq!(extracted_fraction, fraction);
874 }
875 }
876
877 #[test]
878 fn test_remaining_rand_a_bits_stay_random() {
879 let sample = clock::TimestampSample {
880 ms: 0,
881 nanos_within_ms: 789_123,
882 };
883 let random = 0b1101_0011_1010u16;
884 let bits = 8;
885
886 let fraction = sample.sub_ms_fraction(bits);
887 let rand_a = compose_rand_a(random, fraction, bits);
888
889 assert_eq!(rand_a & 0x000F, random & 0x000F);
890 }
891
892 #[test]
893 fn test_exact_millisecond_boundary_sub_ms_fraction_stays_in_last_bucket() {
894 let sample = clock::TimestampSample {
895 ms: 0,
896 nanos_within_ms: 1_000_000,
897 };
898 let rand_a = compose_rand_a(0, sample.sub_ms_fraction(12), 12);
899
900 assert_eq!(rand_a, 0x0FFF);
901 }
902
903 #[test]
904 fn test_rand_b_remains_random() {
905 let timestamp = 1_748_000_000_000u64;
906 let rand_a = 0x0ABCu16;
907 let rand_b = 0x2ABC_DEF0_1234_5678u64;
908
909 let id = uuid_v7_from_parts(timestamp, rand_a, rand_b);
910
911 assert_eq!(id >> 80, timestamp as u128);
912 assert_eq!((id & 0x3FFF_FFFF_FFFF_FFFF) as u64, rand_b);
913 }
914
915 #[test]
916 fn test_public_sub_ms_variants_produce_distinct_layout_options() {
917 let id4 = gen_id_with_sub_ms_4();
918 let id8 = gen_id_with_sub_ms_8();
919 let id12 = gen_id_with_sub_ms_12();
920
921 for id in [id4, id8, id12] {
922 let uuid = uuid::Uuid::from_u128(id);
923 assert_eq!(uuid.get_version(), Some(uuid::Version::SortRand));
924 assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
925 }
926 }
927
928 fn extract_sub_ms_fraction(id: u128, bits: u8) -> (u64, u16) {
929 let ms = (id >> 80) as u64;
930 let rand_a = ((id >> 64) & 0x0FFF) as u16;
931 let fraction = rand_a >> (12 - bits);
932 (ms, fraction)
933 }
934
935 #[test]
936 fn test_sub_ms_variants_mostly_increase_within_same_millisecond() {
937 let cases: &[(u8, fn() -> u128)] = &[
938 (4, gen_id_with_sub_ms_4),
939 (8, gen_id_with_sub_ms_8),
940 (12, gen_id_with_sub_ms_12),
941 ];
942
943 for &(bits, gen_id) in cases {
944 let start = std::time::Instant::now();
945 let duration = std::time::Duration::from_millis(25);
946 let (mut last_ms, mut last_fraction) = extract_sub_ms_fraction(gen_id(), bits);
947 let mut comparisons = 0usize;
948 let mut nondecreasing = 0usize;
949
950 while start.elapsed() < duration && comparisons < 256 {
951 let (ms, fraction) = extract_sub_ms_fraction(gen_id(), bits);
952 if ms == last_ms {
953 comparisons += 1;
954 if fraction >= last_fraction {
955 nondecreasing += 1;
956 }
957 }
958
959 last_ms = ms;
960 last_fraction = fraction;
961 }
962
963 assert!(
964 comparisons >= 32,
965 "not enough same-millisecond samples were observed for the {bits}-bit variant"
966 );
967 assert!(
968 nondecreasing * 10 >= comparisons * 9,
969 "sub-ms fraction for the {bits}-bit variant only moved forward in {nondecreasing}/{comparisons} same-millisecond comparisons"
970 );
971 }
972 }
973
974 #[test]
975 fn test_timestamp_updates_continuously() {
976 let start = std::time::Instant::now();
977 let duration = std::time::Duration::from_millis(100);
978
979 let mut last_ts = gen_id() >> 80;
980 let start_ts = last_ts;
981 let mut distinct_timestamps = 0;
982
983 while start.elapsed() < duration {
984 let curr = gen_id();
985 let curr_ts = curr >> 80;
986 if curr_ts > last_ts {
987 distinct_timestamps += 1;
988 last_ts = curr_ts;
989 }
990 }
991
992 let elapsed_ts = last_ts - start_ts;
993 println!(
994 "Timestamp advanced: {} ms, Distinct timestamps observed: {}",
995 elapsed_ts, distinct_timestamps
996 );
997
998 assert!(
1001 elapsed_ts >= 98,
1002 "Timestamp should advance roughly 100ms, got {}ms",
1003 elapsed_ts
1004 );
1005
1006 let min_distinct_timestamps = elapsed_ts.saturating_mul(2) / 3;
1010 assert!(
1011 distinct_timestamps >= min_distinct_timestamps,
1012 "Should see frequent updates, got {} distinct timestamps over {}ms",
1013 distinct_timestamps,
1014 elapsed_ts
1015 );
1016 }
1017
1018 #[test]
1019 fn test_record_time_sample_ignores_older_millisecond_samples() {
1020 let mut state = ThreadState::new();
1021
1022 assert!(state.record_time_sample(
1023 clock::TimestampSample {
1024 ms: 1_000,
1025 nanos_within_ms: 800_000,
1026 },
1027 true
1028 ));
1029 assert!(!state.record_time_sample(
1030 clock::TimestampSample {
1031 ms: 999,
1032 nanos_within_ms: 100_000,
1033 },
1034 true
1035 ));
1036
1037 assert_eq!(state.last_ms, 1_000);
1038 assert_eq!(state.last_nanos_within_ms, 800_000);
1039 assert_eq!(state.last_sampled_nanos_within_ms, 800_000);
1040 }
1041
1042 #[test]
1043 fn test_record_time_sample_keeps_same_millisecond_fraction_monotonic() {
1044 let mut state = ThreadState::new();
1045
1046 assert!(state.record_time_sample(
1047 clock::TimestampSample {
1048 ms: 1_000,
1049 nanos_within_ms: 400_000,
1050 },
1051 true
1052 ));
1053 assert!(!state.record_time_sample(
1054 clock::TimestampSample {
1055 ms: 1_000,
1056 nanos_within_ms: 350_000,
1057 },
1058 false
1059 ));
1060 assert!(!state.record_time_sample(
1061 clock::TimestampSample {
1062 ms: 1_000,
1063 nanos_within_ms: 450_000,
1064 },
1065 true
1066 ));
1067
1068 assert_eq!(state.current_timestamp_sample().nanos_within_ms, 450_000);
1069 assert_eq!(state.last_sampled_nanos_within_ms, 450_000);
1070 }
1071
1072 #[test]
1073 fn test_counter_reset() {
1074 let mut state = ThreadState::new();
1075
1076 assert!(
1077 state.refresh_time(),
1078 "A fresh thread state should accept the first timestamp sample"
1079 );
1080 assert_eq!(
1081 state.counter & !COUNTER_SEED_MASK,
1082 0,
1083 "Seeded counter should start in the low 12 bits"
1084 );
1085 }
1086
1087 #[test]
1088 fn test_counter_rollover_resets_cached_sub_ms_state() {
1089 let mut state = ThreadState::new();
1090 assert!(state.refresh_time());
1091 let previous_ms = state.last_ms;
1092 state.last_nanos_within_ms = 900_000;
1093 state.last_sampled_nanos_within_ms = 900_000;
1094 state.counter = COUNTER_MAX;
1095
1096 let (timestamp, _) = state.get_time_and_counter();
1097
1098 assert_eq!(timestamp, previous_ms + 1);
1099 assert_eq!(state.current_timestamp_sample().nanos_within_ms, 0);
1100 assert_eq!(state.last_sampled_nanos_within_ms, 0);
1101 }
1102}