use std::time::Instant;
use crate::SequenceExhausted;
use crate::prng::WyRand;
use crate::types::{Uuid, UuidCompact};
const SEQUENCE_MAX: u16 = 0xFFF;
#[derive(Debug, Clone)]
pub struct UuidV7 {
epoch: Instant,
unix_base_ms: u64,
rng: WyRand,
last_ts_ms: u64,
sequence: u16,
}
impl UuidV7 {
#[inline]
pub fn new(epoch: Instant, unix_base_ms: u64, seed: u64) -> Self {
Self {
epoch,
unix_base_ms,
rng: WyRand::new(seed),
last_ts_ms: u64::MAX, sequence: 0,
}
}
pub fn from_entropy(epoch: Instant, unix_base_ms: u64) -> Self {
Self {
epoch,
unix_base_ms,
rng: WyRand::from_entropy(),
last_ts_ms: u64::MAX,
sequence: 0,
}
}
#[inline]
pub fn next_raw(&mut self, now: Instant) -> Result<(u64, u64), SequenceExhausted> {
let offset_ms = now.duration_since(self.epoch).as_millis() as u64;
let ts_ms = self.unix_base_ms.wrapping_add(offset_ms);
if ts_ms == self.last_ts_ms {
self.sequence = self.sequence.wrapping_add(1);
if self.sequence > SEQUENCE_MAX {
return Err(SequenceExhausted {
tick: ts_ms,
max_sequence: SEQUENCE_MAX as u64,
});
}
} else {
self.last_ts_ms = ts_ms;
self.sequence = 0;
}
let rand_b = self.rng.next_u64();
let hi = (ts_ms << 16) | (0x7 << 12) | (self.sequence as u64);
let lo = (0b10 << 62) | (rand_b & 0x3FFF_FFFF_FFFF_FFFF);
Ok((hi, lo))
}
#[inline]
pub fn next(&mut self, now: Instant) -> Result<Uuid, SequenceExhausted> {
let (hi, lo) = self.next_raw(now)?;
Ok(Uuid::from_raw(hi, lo))
}
#[inline]
pub fn next_compact(&mut self, now: Instant) -> Result<UuidCompact, SequenceExhausted> {
let (hi, lo) = self.next_raw(now)?;
Ok(UuidCompact::from_raw(hi, lo))
}
#[inline]
pub fn epoch(&self) -> Instant {
self.epoch
}
#[inline]
pub const fn unix_base_ms(&self) -> u64 {
self.unix_base_ms
}
#[inline]
pub const fn sequence(&self) -> u16 {
self.sequence
}
#[inline]
pub const fn last_timestamp(&self) -> u64 {
self.last_ts_ms
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn test_generator() -> UuidV7 {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64; UuidV7::new(epoch, unix_base, 42)
}
#[test]
fn basic_generation() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UuidV7::new(epoch, unix_base, 42);
let uuid = generator.next(epoch).unwrap();
assert_eq!(uuid.len(), 36);
}
#[test]
fn deterministic_with_seed() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut gen1 = UuidV7::new(epoch, unix_base, 42);
let mut gen2 = UuidV7::new(epoch, unix_base, 42);
for _ in 0..10 {
assert_eq!(
gen1.next(epoch).unwrap().as_str(),
gen2.next(epoch).unwrap().as_str()
);
}
}
#[test]
fn version_is_7() {
let mut generator = test_generator();
let epoch = generator.epoch();
for _ in 0..100 {
let (hi, _lo) = generator.next_raw(epoch).unwrap();
let version = (hi >> 12) & 0xF;
assert_eq!(version, 7);
}
}
#[test]
fn variant_is_rfc() {
let mut generator = test_generator();
let epoch = generator.epoch();
for _ in 0..100 {
let (_hi, lo) = generator.next_raw(epoch).unwrap();
let variant = (lo >> 62) & 0b11;
assert_eq!(variant, 0b10);
}
}
#[test]
fn sequence_increments_same_ms() {
let mut generator = test_generator();
let epoch = generator.epoch();
let uuid1 = generator.next(epoch).unwrap();
let uuid2 = generator.next(epoch).unwrap();
let uuid3 = generator.next(epoch).unwrap();
assert_ne!(uuid1.as_str(), uuid2.as_str());
assert_ne!(uuid2.as_str(), uuid3.as_str());
assert_eq!(generator.sequence(), 2);
}
#[test]
fn sequence_resets_new_ms() {
let mut generator = test_generator();
let epoch = generator.epoch();
let _ = generator.next(epoch).unwrap();
let _ = generator.next(epoch).unwrap();
assert_eq!(generator.sequence(), 1);
let later = epoch + Duration::from_millis(1);
let _ = generator.next(later).unwrap();
assert_eq!(generator.sequence(), 0);
}
#[test]
fn sequence_exhaustion() {
let mut generator = test_generator();
let epoch = generator.epoch();
for _ in 0..=SEQUENCE_MAX {
generator.next(epoch).unwrap();
}
let result = generator.next(epoch);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.max_sequence, SEQUENCE_MAX as u64);
}
#[test]
fn timestamp_embedded() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UuidV7::new(epoch, unix_base, 42);
let (hi, _lo) = generator.next_raw(epoch).unwrap();
let extracted_ts = hi >> 16;
assert_eq!(extracted_ts, unix_base);
let later = epoch + Duration::from_millis(100);
let mut generator2 = UuidV7::new(epoch, unix_base, 42);
let (hi2, _lo2) = generator2.next_raw(later).unwrap();
let extracted_ts2 = hi2 >> 16;
assert_eq!(extracted_ts2, unix_base + 100);
}
#[test]
fn format_is_correct() {
let mut generator = test_generator();
let uuid = generator.next(generator.epoch()).unwrap();
assert_eq!(uuid.len(), 36);
let s = uuid.as_str();
assert_eq!(s.as_bytes()[8], b'-');
assert_eq!(s.as_bytes()[13], b'-');
assert_eq!(s.as_bytes()[18], b'-');
assert_eq!(s.as_bytes()[23], b'-');
assert_eq!(s.as_bytes()[14], b'7');
let variant_char = s.as_bytes()[19];
assert!(
variant_char == b'8'
|| variant_char == b'9'
|| variant_char == b'a'
|| variant_char == b'b'
);
}
#[test]
fn time_ordering() {
let mut generator = test_generator();
let epoch = generator.epoch();
let mut uuids = Vec::new();
for i in 0..100 {
let now = epoch + Duration::from_millis(i);
uuids.push(generator.next(now).unwrap());
}
for i in 1..uuids.len() {
assert!(uuids[i].as_str() > uuids[i - 1].as_str());
}
}
#[test]
fn from_entropy_works() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UuidV7::from_entropy(epoch, unix_base);
let uuid = generator.next(epoch).unwrap();
assert_eq!(uuid.len(), 36);
}
}