///|
/// Errors that can occur while constructing or generating UUIDv7 values.
pub(all) enum V7Error {
EntropyUnavailable
InvalidEntropyLength(Int)
TimestampOutOfRange(UInt64)
RandAOutOfRange(UInt64)
RandBOutOfRange(UInt64)
MonotonicOverflow
} derive(Eq)
///|
pub extend V7Error with Eq::{not_equal, equal}
///|
/// Stateful UUIDv7 generator for strict monotonic ordering on one node/process.
///
/// For a new millisecond, fresh random fields are generated. For the same
/// millisecond, or when the supplied clock moves backwards, the previous
/// timestamp is reused and the 74-bit random payload is incremented.
pub struct V7Generator {
mut initialized : Bool
mut last_ms : UInt64
mut rand_a : UInt64
mut rand_b : UInt64
}
///|
/// Construct an empty monotonic UUIDv7 generator.
pub fn V7Generator::new() -> V7Generator {
{ initialized: false, last_ms: 0UL, rand_a: 0UL, rand_b: 0UL, }
}
///|
/// Return the 48-bit Unix-millisecond timestamp embedded in a UUIDv7.
///
/// Returns None for UUIDs that are not RFC-variant version 7 values.
pub fn unix_ts_ms(uuid : Uuid) -> UInt64? {
if version(uuid) == Some(7) {
Some((uuid.high >> 16) & 0x0000FFFFFFFFFFFFUL)
} else {
None
}
}
///|
/// Construct UUIDv7 from its RFC 9562 fields.
///
/// `unix_ms` must fit in 48 bits, `rand_a` in 12 bits and `rand_b` in 62 bits.
pub fn v7_from_parts(
unix_ms : UInt64,
rand_a : UInt64,
rand_b : UInt64,
) -> Result[Uuid, V7Error] {
if unix_ms > 0x0000FFFFFFFFFFFFUL {
return Err(TimestampOutOfRange(unix_ms))
}
if rand_a > 0xFFFUL {
return Err(RandAOutOfRange(rand_a))
}
if rand_b > 0x3FFFFFFFFFFFFFFFUL {
return Err(RandBOutOfRange(rand_b))
}
let high = (unix_ms << 16) | 0x7000UL | rand_a
let low = 0x8000000000000000UL | rand_b
Ok({ high, low, })
}
///|
fn v7_entropy_fields(entropy : Bytes) -> Result[(UInt64, UInt64), V7Error] {
if entropy.length() != 10 {
return Err(InvalidEntropyLength(entropy.length()))
}
let rand_a = (
(entropy[0].to_int().to_uint64() << 8) | entropy[1].to_int().to_uint64()
) &
0xFFFUL
let mut rand_b = 0UL
for i = 2; i < 10; i = i + 1 {
rand_b = (rand_b << 8) | entropy[i].to_int().to_uint64()
}
rand_b = rand_b & 0x3FFFFFFFFFFFFFFFUL
Ok((rand_a, rand_b))
}
///|
/// Construct UUIDv7 from a Unix-millisecond timestamp and 10 entropy bytes.
///
/// The first 12 usable bits seed `rand_a`; the following 62 usable bits seed
/// `rand_b`. Excess high bits are masked away to preserve the RFC layout.
pub fn v7_from_entropy(
unix_ms : UInt64,
entropy : Bytes,
) -> Result[Uuid, V7Error] {
match v7_entropy_fields(entropy) {
Err(error) => Err(error)
Ok((rand_a, rand_b)) => v7_from_parts(unix_ms, rand_a, rand_b)
}
}
///|
/// Generate UUIDv7 using injected clock and entropy providers.
///
/// The clock returns Unix time in milliseconds. The entropy provider is asked
/// for exactly 10 bytes.
pub fn v7_with(
clock : () -> UInt64,
entropy : (Int) -> Bytes?,
) -> Result[Uuid, V7Error] {
let unix_ms = clock()
match entropy(10) {
None => Err(EntropyUnavailable)
Some(bytes) => v7_from_entropy(unix_ms, bytes)
}
}
///|
/// Generate UUIDv7 using MoonBit's wall clock and secure platform entropy.
///
/// Returns EntropyUnavailable when the runtime cannot supply secure randomness.
pub fn v7() -> Result[Uuid, V7Error] {
v7_with(@env.now, @env.rand)
}
///|
fn V7Generator::seed(
self : V7Generator,
unix_ms : UInt64,
entropy : (Int) -> Bytes?,
) -> Result[Uuid, V7Error] {
if unix_ms > 0x0000FFFFFFFFFFFFUL {
return Err(TimestampOutOfRange(unix_ms))
}
match entropy(10) {
None => Err(EntropyUnavailable)
Some(bytes) =>
match v7_entropy_fields(bytes) {
Err(error) => Err(error)
Ok((rand_a, rand_b)) => {
self.initialized = true
self.last_ms = unix_ms
self.rand_a = rand_a
self.rand_b = rand_b
v7_from_parts(self.last_ms, self.rand_a, self.rand_b)
}
}
}
}
///|
/// Generate the next strictly monotonic UUIDv7 with injected providers.
///
/// RFC 9562 section 6.2 guidance is followed:
/// - a newer millisecond reseeds the random fields;
/// - the same millisecond increments the prior random payload;
/// - clock rollback reuses the previous timestamp and increments the payload;
/// - full 74-bit payload rollover returns MonotonicOverflow.
pub fn V7Generator::next_with(
self : V7Generator,
clock : () -> UInt64,
entropy : (Int) -> Bytes?,
) -> Result[Uuid, V7Error] {
let now = clock()
if !self.initialized || now > self.last_ms {
return self.seed(now, entropy)
}
if self.rand_b < 0x3FFFFFFFFFFFFFFFUL {
self.rand_b = self.rand_b + 1UL
} else if self.rand_a < 0xFFFUL {
self.rand_a = self.rand_a + 1UL
self.rand_b = 0UL
} else {
return Err(MonotonicOverflow)
}
v7_from_parts(self.last_ms, self.rand_a, self.rand_b)
}
///|
/// Generate the next strictly monotonic UUIDv7 using MoonBit platform providers.
pub fn V7Generator::next(self : V7Generator) -> Result[Uuid, V7Error] {
self.next_with(@env.now, @env.rand)
}