diff --git a/Cargo.toml b/Cargo.toml index 7d586259..702c0a39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,9 @@ features = ["serde", "arbitrary", "slog", "borsh", "v1", "v3", "v4", "v5", "v6", [package.metadata.playground] features = ["serde", "v1", "v3", "v4", "v5", "v6", "v7", "v8"] +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(uuid_unstable)'] } + [badges.is-it-maintained-issue-resolution] repository = "uuid-rs/uuid" diff --git a/src/builder.rs b/src/builder.rs index 5dfcaf6d..880327bb 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -609,9 +609,11 @@ impl Builder { )) } - /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp and random bytes. + /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp and counter bytes. /// - /// This method assumes the bytes are already sufficiently random. + /// This method will set the variant field within the counter bytes without attempting to shift + /// the data around it. Callers using the counter as a monotonic value should be careful not to + /// store significant data in the 2 least significant bits of the 3rd byte. /// /// # Examples /// @@ -636,10 +638,10 @@ impl Builder { /// # Ok(()) /// # } /// ``` - pub const fn from_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Self { + pub const fn from_unix_timestamp_millis(millis: u64, counter_random_bytes: &[u8; 10]) -> Self { Builder(timestamp::encode_unix_timestamp_millis( millis, - random_bytes, + counter_random_bytes, )) } diff --git a/src/lib.rs b/src/lib.rs index 42a0d80b..4c4ad703 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,6 +237,9 @@ pub use timestamp::{context::NoContext, ClockSequence, Timestamp}; #[cfg(any(feature = "v1", feature = "v6"))] pub use timestamp::context::Context; +#[cfg(feature = "v7")] +pub use timestamp::context::ContextV7; + #[cfg(feature = "v1")] #[doc(hidden)] // Soft-deprecated (Rust doesn't support deprecating re-exports) @@ -901,12 +904,7 @@ impl Uuid { let seconds = millis / 1000; let nanos = ((millis % 1000) * 1_000_000) as u32; - Some(Timestamp { - seconds, - nanos, - #[cfg(any(feature = "v1", feature = "v6"))] - counter: 0, - }) + Some(Timestamp::from_unix_time(seconds, nanos, 0)) } _ => None, } diff --git a/src/rng.rs b/src/rng.rs index dcfbb8d6..2ae23cdb 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -1,5 +1,5 @@ #[cfg(any(feature = "v4", feature = "v7"))] -pub(crate) fn bytes() -> [u8; 16] { +pub(crate) fn u128() -> u128 { #[cfg(not(feature = "fast-rng"))] { let mut bytes = [0u8; 16]; @@ -9,7 +9,7 @@ pub(crate) fn bytes() -> [u8; 16] { panic!("could not retrieve random bytes for uuid: {}", err) }); - bytes + u128::from_ne_bytes(bytes) } #[cfg(feature = "fast-rng")] @@ -29,7 +29,27 @@ pub(crate) fn u16() -> u16 { panic!("could not retrieve random bytes for uuid: {}", err) }); - ((bytes[0] as u16) << 8) | (bytes[1] as u16) + u16::from_ne_bytes(bytes) + } + + #[cfg(feature = "fast-rng")] + { + rand::random() + } +} + +#[cfg(feature = "v7")] +pub(crate) fn u64() -> u64 { + #[cfg(not(feature = "fast-rng"))] + { + let mut bytes = [0u8; 8]; + + getrandom::getrandom(&mut bytes).unwrap_or_else(|err| { + // NB: getrandom::Error has no source; this is adequate display + panic!("could not retrieve random bytes for uuid: {}", err) + }); + + u64::from_ne_bytes(bytes) } #[cfg(feature = "fast-rng")] diff --git a/src/timestamp.rs b/src/timestamp.rs index 46cd7ca9..b863639c 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -21,6 +21,8 @@ //! * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7) //! * [Timestamp Considerations in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-6.1) +use core::cmp; + use crate::Uuid; /// The number of 100 nanosecond ticks between the RFC 9562 epoch @@ -39,14 +41,14 @@ pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; /// * [UUID Generator States in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-6.3) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Timestamp { - pub(crate) seconds: u64, - pub(crate) nanos: u32, - #[cfg(any(feature = "v1", feature = "v6"))] - pub(crate) counter: u16, + seconds: u64, + subsec_nanos: u32, + counter: u128, + usable_counter_bits: u8, } impl Timestamp { - /// Get a timestamp representing the current system time. + /// Get a timestamp representing the current system time and up to a 16-bit counter. /// /// This method defers to the standard library's `SystemTime` type. /// @@ -55,22 +57,28 @@ impl Timestamp { /// This method will panic if calculating the elapsed time since the Unix epoch fails. #[cfg(feature = "std")] pub fn now(context: impl ClockSequence) -> Self { - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - let _ = context; - } + Self::now_128(context) + } + + /// Get a timestamp representing the current system time and up to a 128-bit counter. + #[cfg(feature = "std")] + pub fn now_128(context: impl ClockSequence>) -> Self { + let (seconds, subsec_nanos) = now(); - let (seconds, nanos) = now(); + let (counter, seconds, subsec_nanos) = + context.generate_timestamp_sequence(seconds, subsec_nanos); + let counter = counter.into(); + let usable_counter_bits = context.usable_bits() as u8; Timestamp { seconds, - nanos, - #[cfg(any(feature = "v1", feature = "v6"))] - counter: context.generate_sequence(seconds, nanos), + subsec_nanos, + counter, + usable_counter_bits, } } - /// Construct a `Timestamp` from an RFC 9562 timestamp and counter, as used + /// Construct a `Timestamp` from an RFC 9562 timestamp and 16-bit counter, as used /// in versions 1 and 6 UUIDs. /// /// # Overflow @@ -78,43 +86,61 @@ impl Timestamp { /// If conversion from RFC 9562 ticks to the internal timestamp format would overflow /// it will wrap. pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - let _ = counter; + let (seconds, subsec_nanos) = Self::rfc4122_to_unix(ticks); + + Timestamp { + seconds, + subsec_nanos, + counter: counter as u128, + usable_counter_bits: 16, } + } - let (seconds, nanos) = Self::rfc4122_to_unix(ticks); + /// Construct a `Timestamp` from a Unix timestamp and a 16-bit counter, as used in version 7 UUIDs. + pub const fn from_unix_time(seconds: u64, subsec_nanos: u32, counter: u16) -> Self { + Self::from_unix_time_128(seconds, subsec_nanos, counter as u128, 16) + } + /// Construct a `Timestamp` from a Unix timestamp and up to a 128-bit counter, as used in version 7 UUIDs. + pub const fn from_unix_time_128( + seconds: u64, + subsec_nanos: u32, + counter: u128, + usable_counter_bits: u8, + ) -> Self { Timestamp { seconds, - nanos, - #[cfg(any(feature = "v1", feature = "v6"))] + subsec_nanos, counter, + usable_counter_bits, } } - /// Construct a `Timestamp` from a Unix timestamp, as used in version 7 UUIDs. - /// - /// # Overflow - /// - /// If conversion from RFC 9562 ticks to the internal timestamp format would overflow - /// it will wrap. - pub fn from_unix(context: impl ClockSequence, seconds: u64, nanos: u32) -> Self { - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - let _ = context; + /// Construct a `Timestamp` from a Unix timestamp and up to a 16-bit counter, as used in version 7 UUIDs. + pub fn from_unix( + context: impl ClockSequence, + seconds: u64, + subsec_nanos: u32, + ) -> Self { + Self::from_unix_128(context, seconds, subsec_nanos) + } - Timestamp { seconds, nanos } - } - #[cfg(any(feature = "v1", feature = "v6"))] - { - let counter = context.generate_sequence(seconds, nanos); - - Timestamp { - seconds, - nanos, - counter, - } + /// Construct a `Timestamp` from a Unix timestamp and up to a 128-bit counter, as used in version 7 UUIDs. + pub fn from_unix_128( + context: impl ClockSequence>, + seconds: u64, + subsec_nanos: u32, + ) -> Self { + let (counter, seconds, subsec_nanos) = + context.generate_timestamp_sequence(seconds, subsec_nanos); + let counter = counter.into(); + let usable_counter_bits = context.usable_bits() as u8; + + Timestamp { + seconds, + subsec_nanos, + counter, + usable_counter_bits, } } @@ -125,25 +151,24 @@ impl Timestamp { /// /// If conversion from RFC 9562 ticks to the internal timestamp format would overflow /// it will wrap. - #[cfg(any(feature = "v1", feature = "v6"))] pub const fn to_rfc4122(&self) -> (u64, u16) { ( - Self::unix_to_rfc4122_ticks(self.seconds, self.nanos), - self.counter, + Self::unix_to_rfc4122_ticks(self.seconds, self.subsec_nanos), + self.counter as u16, ) } + // NOTE: This method is not public; the usable counter bits are lost in a version 7 UUID + // so can't be reliably recovered. + pub(crate) const fn counter(&self) -> (u128, u8) { + (self.counter, self.usable_counter_bits) + } + /// Get the value of the timestamp as a Unix timestamp, as used in version 7 UUIDs. - /// - /// # Overflow - /// - /// If conversion from RFC 9562 ticks to the internal timestamp format would overflow - /// it will wrap. pub const fn to_unix(&self) -> (u64, u32) { - (self.seconds, self.nanos) + (self.seconds, self.subsec_nanos) } - #[cfg(any(feature = "v1", feature = "v6"))] const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 { UUID_TICKS_BETWEEN_EPOCHS .wrapping_add(seconds.wrapping_mul(10_000_000)) @@ -244,25 +269,29 @@ pub(crate) const fn decode_sorted_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) { (ticks, counter) } -pub(crate) const fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Uuid { +pub(crate) const fn encode_unix_timestamp_millis( + millis: u64, + counter_random_bytes: &[u8; 10], +) -> Uuid { let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; let millis_low = (millis & 0xFFFF) as u16; - let random_and_version = - (random_bytes[1] as u16 | ((random_bytes[0] as u16) << 8) & 0x0FFF) | (0x7 << 12); + let counter_random_version = (counter_random_bytes[1] as u16 + | ((counter_random_bytes[0] as u16) << 8) & 0x0FFF) + | (0x7 << 12); let mut d4 = [0; 8]; - d4[0] = (random_bytes[2] & 0x3F) | 0x80; - d4[1] = random_bytes[3]; - d4[2] = random_bytes[4]; - d4[3] = random_bytes[5]; - d4[4] = random_bytes[6]; - d4[5] = random_bytes[7]; - d4[6] = random_bytes[8]; - d4[7] = random_bytes[9]; + d4[0] = (counter_random_bytes[2] & 0x3F) | 0x80; + d4[1] = counter_random_bytes[3]; + d4[2] = counter_random_bytes[4]; + d4[3] = counter_random_bytes[5]; + d4[4] = counter_random_bytes[6]; + d4[5] = counter_random_bytes[7]; + d4[6] = counter_random_bytes[8]; + d4[7] = counter_random_bytes[9]; - Uuid::from_fields(millis_high, millis_low, random_and_version, &d4) + Uuid::from_fields(millis_high, millis_low, counter_random_version, &d4) } pub(crate) const fn decode_unix_timestamp_millis(uuid: &Uuid) -> u64 { @@ -339,14 +368,63 @@ pub trait ClockSequence { /// Get the next value in the sequence to feed into a timestamp. /// /// This method will be called each time a [`Timestamp`] is constructed. + /// + /// Any bits beyond [`ClockSequence::usable_bits`] in the output must be unset. fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output; + + /// Get the next value in the sequence, potentially also adjusting the timestamp. + /// + /// This method should be preferred over `generate_sequence`. + /// + /// Any bits beyond [`ClockSequence::usable_bits`] in the output must be unset. + fn generate_timestamp_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> (Self::Output, u64, u32) { + ( + self.generate_sequence(seconds, subsec_nanos), + seconds, + subsec_nanos, + ) + } + + /// The number of usable bits from the least significant bit in the result of [`ClockSequence::generate_sequence`] + /// or [`ClockSequence::generate_timestamp_sequence`]. + /// + /// The number of usable bits must not exceed 128. + /// + /// The number of usable bits is not expected to change between calls. An implementation of `ClockSequence` should + /// always return the same value from this method. + fn usable_bits(&self) -> usize + where + Self::Output: Sized, + { + cmp::min(128, core::mem::size_of::()) + } } impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { type Output = T::Output; + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { (**self).generate_sequence(seconds, subsec_nanos) } + + fn generate_timestamp_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> (Self::Output, u64, u32) { + (**self).generate_timestamp_sequence(seconds, subsec_nanos) + } + + fn usable_bits(&self) -> usize + where + Self::Output: Sized, + { + (**self).usable_bits() + } } /// Default implementations for the [`ClockSequence`] trait. @@ -354,87 +432,458 @@ pub mod context { use super::ClockSequence; #[cfg(any(feature = "v1", feature = "v6"))] - use atomic::{Atomic, Ordering}; + mod v1_support { + use super::*; + + use atomic::{Atomic, Ordering}; + + #[cfg(all(feature = "std", feature = "rng"))] + static CONTEXT: Context = Context { + count: Atomic::new(0), + }; + + #[cfg(all(feature = "std", feature = "rng"))] + static CONTEXT_INITIALIZED: Atomic = Atomic::new(false); + + #[cfg(all(feature = "std", feature = "rng"))] + pub(crate) fn shared_context() -> &'static Context { + // If the context is in its initial state then assign it to a random value + // It doesn't matter if multiple threads observe `false` here and initialize the context + if CONTEXT_INITIALIZED + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + CONTEXT.count.store(crate::rng::u16(), Ordering::Release); + } - /// An empty counter that will always return the value `0`. - /// - /// This type should be used when constructing timestamps for version 7 UUIDs, - /// since they don't need a counter for uniqueness. - #[derive(Debug, Clone, Copy, Default)] - pub struct NoContext; + &CONTEXT + } - impl ClockSequence for NoContext { - type Output = u16; + /// A thread-safe, wrapping counter that produces 14-bit values. + /// + /// This type works by: + /// + /// 1. Atomically incrementing the counter value for each timestamp. + /// 2. Wrapping the counter back to zero if it overflows its 14-bit storage. + /// + /// This type should be used when constructing version 1 and version 6 UUIDs. + /// + /// This type should not be used when constructing version 7 UUIDs. When used to + /// construct a version 7 UUID, the 14-bit counter will be padded with random data. + /// Counter overflows are more likely with a 14-bit counter than they are with a + /// 42-bit counter when working at millisecond precision. This type doesn't attempt + /// to adjust the timestamp on overflow. + #[derive(Debug)] + pub struct Context { + count: Atomic, + } - fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { - 0 + impl Context { + /// Construct a new context that's initialized with the given value. + /// + /// The starting value should be a random number, so that UUIDs from + /// different systems with the same timestamps are less likely to collide. + /// When the `rng` feature is enabled, prefer the [`Context::new_random`] method. + pub const fn new(count: u16) -> Self { + Self { + count: Atomic::::new(count), + } + } + + /// Construct a new context that's initialized with a random value. + #[cfg(feature = "rng")] + pub fn new_random() -> Self { + Self { + count: Atomic::::new(crate::rng::u16()), + } + } } - } - #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))] - static CONTEXT: Context = Context { - count: Atomic::new(0), - }; - - #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))] - static CONTEXT_INITIALIZED: Atomic = Atomic::new(false); - - #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))] - pub(crate) fn shared_context() -> &'static Context { - // If the context is in its initial state then assign it to a random value - // It doesn't matter if multiple threads observe `false` here and initialize the context - if CONTEXT_INITIALIZED - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - CONTEXT.count.store(crate::rng::u16(), Ordering::Release); + impl ClockSequence for Context { + type Output = u16; + + fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { + // RFC4122 reserves 2 bits of the clock sequence so the actual + // maximum value is smaller than `u16::MAX`. Since we unconditionally + // increment the clock sequence we want to wrap once it becomes larger + // than what we can represent in a "u14". Otherwise there'd be patches + // where the clock sequence doesn't change regardless of the timestamp + self.count.fetch_add(1, Ordering::AcqRel) & (u16::MAX >> 2) + } + + fn usable_bits(&self) -> usize { + 14 + } } - &CONTEXT + #[cfg(test)] + mod tests { + use crate::Timestamp; + + use super::*; + + #[test] + fn context() { + let seconds = 1_496_854_535; + let subsec_nanos = 812_946_000; + + let context = Context::new(u16::MAX >> 2); + + let ts = Timestamp::from_unix(&context, seconds, subsec_nanos); + assert_eq!(16383, ts.counter); + assert_eq!(14, ts.usable_counter_bits); + + let seconds = 1_496_854_536; + + let ts = Timestamp::from_unix(&context, seconds, subsec_nanos); + assert_eq!(0, ts.counter); + + let seconds = 1_496_854_535; + + let ts = Timestamp::from_unix(&context, seconds, subsec_nanos); + assert_eq!(1, ts.counter); + } + } } - /// A thread-safe, wrapping counter that produces 14-bit numbers. - /// - /// This type should be used when constructing version 1 and version 6 UUIDs. - #[derive(Debug)] #[cfg(any(feature = "v1", feature = "v6"))] - pub struct Context { - count: Atomic, + pub use v1_support::*; + + #[cfg(feature = "std")] + mod std_support { + use super::*; + + use core::panic::{AssertUnwindSafe, RefUnwindSafe}; + use std::{sync::Mutex, thread::LocalKey}; + + /// A wrapper for a context that uses thread-local storage. + pub struct ThreadLocalContext(&'static LocalKey); + + impl std::fmt::Debug for ThreadLocalContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ThreadLocalContext").finish_non_exhaustive() + } + } + + impl ThreadLocalContext { + /// Wrap a thread-local container with a context. + pub const fn new(local_key: &'static LocalKey) -> Self { + ThreadLocalContext(local_key) + } + } + + impl ClockSequence for ThreadLocalContext { + type Output = C::Output; + + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { + self.0 + .with(|ctxt| ctxt.generate_sequence(seconds, subsec_nanos)) + } + + fn generate_timestamp_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> (Self::Output, u64, u32) { + self.0 + .with(|ctxt| ctxt.generate_timestamp_sequence(seconds, subsec_nanos)) + } + + fn usable_bits(&self) -> usize { + self.0.with(|ctxt| ctxt.usable_bits()) + } + } + + impl ClockSequence for AssertUnwindSafe { + type Output = C::Output; + + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { + self.0.generate_sequence(seconds, subsec_nanos) + } + + fn generate_timestamp_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> (Self::Output, u64, u32) { + self.0.generate_timestamp_sequence(seconds, subsec_nanos) + } + + fn usable_bits(&self) -> usize + where + Self::Output: Sized, + { + self.0.usable_bits() + } + } + + impl ClockSequence for Mutex { + type Output = C::Output; + + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { + self.lock() + .unwrap_or_else(|err| err.into_inner()) + .generate_sequence(seconds, subsec_nanos) + } + + fn generate_timestamp_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> (Self::Output, u64, u32) { + self.lock() + .unwrap_or_else(|err| err.into_inner()) + .generate_timestamp_sequence(seconds, subsec_nanos) + } + + fn usable_bits(&self) -> usize + where + Self::Output: Sized, + { + self.lock() + .unwrap_or_else(|err| err.into_inner()) + .usable_bits() + } + } } - #[cfg(any(feature = "v1", feature = "v6"))] - impl Context { - /// Construct a new context that's initialized with the given value. + #[cfg(feature = "std")] + pub use std_support::*; + + #[cfg(feature = "v7")] + mod v7_support { + use super::*; + + use core::{cell::Cell, panic::RefUnwindSafe}; + + #[cfg(feature = "std")] + static CONTEXT_V7: std::sync::Mutex = std::sync::Mutex::new(ContextV7::new()); + + #[cfg(feature = "std")] + pub(crate) fn shared_context_v7() -> &'static std::sync::Mutex { + &CONTEXT_V7 + } + + /// An unsynchronized, reseeding counter that produces 42-bit values. /// - /// The starting value should be a random number, so that UUIDs from - /// different systems with the same timestamps are less likely to collide. - /// When the `rng` feature is enabled, prefer the [`Context::new_random`] method. - pub const fn new(count: u16) -> Self { - Self { - count: Atomic::::new(count), + /// This type works by: + /// + /// 1. Reseeding the counter each millisecond with a random 41-bit value. The 42nd bit + /// is left unset so the counter can safely increment over the millisecond. + /// 2. Wrapping the counter back to zero if it overflows its 42-bit storage and adding a + /// millisecond to the timestamp. + /// + /// This type can be used when constructing version 7 UUIDs. When used to construct a + /// version 7 UUID, the 42-bit counter will be padded with random data. This type can + /// be used to maintain ordering of UUIDs within the same millisecond. + /// + /// This type should not be used when constructing version 1 or version 6 UUIDs. + /// When used to construct a version 1 or version 6 UUID, only the 14 least significant + /// bits of the counter will be used. + #[derive(Debug)] + pub struct ContextV7 { + last_reseed: Cell, + counter: Cell, + } + + #[derive(Debug, Default, Clone, Copy)] + struct LastReseed { + millis: u64, + ts_seconds: u64, + ts_subsec_nanos: u32, + } + + impl LastReseed { + fn from_millis(millis: u64) -> Self { + LastReseed { + millis, + ts_seconds: millis / 1_000, + ts_subsec_nanos: (millis % 1_000) as u32 * 1_000_000, + } + } + } + + impl RefUnwindSafe for ContextV7 {} + + impl ContextV7 { + /// Construct a new context that will reseed its counter on the first + /// non-zero timestamp it receives. + pub const fn new() -> Self { + ContextV7 { + last_reseed: Cell::new(LastReseed { + millis: 0, + ts_seconds: 0, + ts_subsec_nanos: 0, + }), + counter: Cell::new(0), + } + } + } + + impl ClockSequence for ContextV7 { + type Output = u64; + + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { + self.generate_timestamp_sequence(seconds, subsec_nanos).0 + } + + fn generate_timestamp_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> (Self::Output, u64, u32) { + // Leave the most significant bit unset + // This guarantees the counter has at least 2,199,023,255,552 + // values before it will overflow, which is exceptionally unlikely + // even in the worst case + const RESEED_MASK: u64 = u64::MAX >> 23; + const MAX_COUNTER: u64 = u64::MAX >> 22; + + let millis = (seconds * 1_000).saturating_add(subsec_nanos as u64 / 1_000_000); + + let last_reseed = self.last_reseed.get(); + + // If the observed system time has shifted forwards then regenerate the counter + if millis > last_reseed.millis { + let last_reseed = LastReseed::from_millis(millis); + self.last_reseed.set(last_reseed); + + let counter = crate::rng::u64() & RESEED_MASK; + self.counter.set(counter); + + (counter, last_reseed.ts_seconds, last_reseed.ts_subsec_nanos) + } + // If the observed system time has not shifted forwards then increment the counter + else { + // If the incoming timestamp is earlier than the last observed one then + // use it instead. This may happen if the system clock jitters, or if the counter + // has wrapped and the timestamp is artificially incremented + let millis = (); + let _ = millis; + + // Guaranteed to never overflow u64 + let counter = self.counter.get() + 1; + + // If the counter has not overflowed its 42-bit storage then return it + if counter <= MAX_COUNTER { + self.counter.set(counter); + + (counter, last_reseed.ts_seconds, last_reseed.ts_subsec_nanos) + } + // Unlikely: If the counter has overflowed its 42-bit storage then wrap it + // and increment the timestamp. Until the observed system time shifts past + // this incremented value, all timestamps will use it to maintain monotonicity + else { + // Increment the timestamp by 1 milli + let last_reseed = LastReseed::from_millis(last_reseed.millis + 1); + self.last_reseed.set(last_reseed); + + // Reseed the counter + let counter = crate::rng::u64() & RESEED_MASK; + self.counter.set(counter); + + (counter, last_reseed.ts_seconds, last_reseed.ts_subsec_nanos) + } + } + } + + fn usable_bits(&self) -> usize { + 42 } } - /// Construct a new context that's initialized with a random value. - #[cfg(feature = "rng")] - pub fn new_random() -> Self { - Self { - count: Atomic::::new(crate::rng::u16()), + #[cfg(test)] + mod tests { + use core::time::Duration; + + use super::*; + + use crate::Timestamp; + + #[test] + fn context() { + let seconds = 1_496_854_535; + let subsec_nanos = 812_946_000; + + let context = ContextV7::new(); + + let ts1 = Timestamp::from_unix_128(&context, seconds, subsec_nanos); + assert_eq!(42, ts1.usable_counter_bits); + + // Backwards second + let seconds = 1_496_854_534; + + let ts2 = Timestamp::from_unix_128(&context, seconds, subsec_nanos); + + // The backwards time should be ignored + // The counter should still increment + assert_eq!(ts1.seconds, ts2.seconds); + assert_eq!(ts1.subsec_nanos, ts2.subsec_nanos); + assert_eq!(ts1.counter + 1, ts2.counter); + + // Forwards second + let seconds = 1_496_854_536; + + let ts3 = Timestamp::from_unix_128(&context, seconds, subsec_nanos); + + // The counter should have reseeded + assert_ne!(ts2.counter + 1, ts3.counter); + assert_ne!(0, ts3.counter); + } + + #[test] + fn context_wrap() { + let seconds = 1_496_854_535u64; + let subsec_nanos = 812_946_000u32; + + let millis = (seconds * 1000).saturating_add(subsec_nanos as u64 / 1_000_000); + + // This context will wrap + let context = ContextV7 { + last_reseed: Cell::new(LastReseed::from_millis(millis)), + counter: Cell::new(u64::MAX >> 22), + }; + + let ts = Timestamp::from_unix_128(&context, seconds, subsec_nanos); + + // The timestamp should be incremented by 1ms + let expected_ts = Duration::new(seconds, subsec_nanos / 1_000_000 * 1_000_000) + + Duration::from_millis(1); + assert_eq!(expected_ts.as_secs(), ts.seconds); + assert_eq!(expected_ts.subsec_nanos(), ts.subsec_nanos); + + // The counter should have reseeded + assert!(ts.counter < (u64::MAX >> 22) as u128); + assert_ne!(0, ts.counter); } } } - #[cfg(any(feature = "v1", feature = "v6"))] - impl ClockSequence for Context { + #[cfg(feature = "v7")] + pub use v7_support::*; + + /// An empty counter that will always return the value `0`. + /// + /// This type can be used when constructing version 7 UUIDs. When used to + /// construct a version 7 UUID, the entire counter segment of the UUID will be + /// filled with a random value. This type does not maintain ordering of UUIDs + /// within a millisecond but is efficient. + /// + /// This type should not be used when constructing version 1 or version 6 UUIDs. + /// When used to construct a version 1 or version 6 UUID, the counter + /// segment will remain zero. + #[derive(Debug, Clone, Copy, Default)] + pub struct NoContext; + + impl ClockSequence for NoContext { type Output = u16; fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { - // RFC 9562 reserves 2 bits for Variant field, so the actual `clock_seq` - // maximum value is smaller than `u16::MAX`. Since we unconditionally - // increment the clock sequence we want to wrap once it becomes larger - // than what we can represent in a "u14". Otherwise there'd be patches - // where the clock sequence doesn't change regardless of the timestamp - self.count.fetch_add(1, Ordering::AcqRel) & (u16::MAX >> 2) + 0 + } + + fn usable_bits(&self) -> usize { + 0 } } } diff --git a/src/v1.rs b/src/v1.rs index cf5ba14d..dc8dd80f 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -166,39 +166,4 @@ mod tests { assert_eq!(uuid.get_version(), Some(Version::Mac)); assert_eq!(uuid.get_variant(), Variant::RFC4122); } - - #[test] - #[cfg_attr( - all( - target_arch = "wasm32", - target_vendor = "unknown", - target_os = "unknown" - ), - wasm_bindgen_test - )] - fn test_new_context() { - let time: u64 = 1_496_854_535; - let time_fraction: u32 = 812_946_000; - let node = [1, 2, 3, 4, 5, 6]; - - // This context will wrap - let context = Context::new(u16::MAX >> 2); - - let uuid1 = Uuid::new_v1(Timestamp::from_unix(&context, time, time_fraction), &node); - - let time: u64 = 1_496_854_536; - - let uuid2 = Uuid::new_v1(Timestamp::from_unix(&context, time, time_fraction), &node); - - assert_eq!(uuid1.get_timestamp().unwrap().to_rfc4122().1, 16383); - assert_eq!(uuid2.get_timestamp().unwrap().to_rfc4122().1, 0); - - let time = 1_496_854_535; - - let uuid3 = Uuid::new_v1(Timestamp::from_unix(&context, time, time_fraction), &node); - let uuid4 = Uuid::new_v1(Timestamp::from_unix(&context, time, time_fraction), &node); - - assert_eq!(uuid3.get_timestamp().unwrap().to_rfc4122().1, 1); - assert_eq!(uuid4.get_timestamp().unwrap().to_rfc4122().1, 2); - } } diff --git a/src/v4.rs b/src/v4.rs index 2760c881..14d755e2 100644 --- a/src/v4.rs +++ b/src/v4.rs @@ -31,7 +31,11 @@ impl Uuid { /// [`getrandom`]: https://crates.io/crates/getrandom /// [from_random_bytes]: struct.Builder.html#method.from_random_bytes pub fn new_v4() -> Uuid { - crate::Builder::from_random_bytes(crate::rng::bytes()).into_uuid() + // This is an optimized method for generating random UUIDs that just masks + // out the bits for the version and variant and sets them both together + Uuid::from_u128( + crate::rng::u128() & 0xFFFFFFFFFFFF4FFFBFFFFFFFFFFFFFFF | 0x40008000000000000000, + ) } } diff --git a/src/v6.rs b/src/v6.rs index 3485ef81..02db3e9e 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -168,39 +168,4 @@ mod tests { assert_eq!(uuid.get_version(), Some(Version::SortMac)); assert_eq!(uuid.get_variant(), Variant::RFC4122); } - - #[test] - #[cfg_attr( - all( - target_arch = "wasm32", - target_vendor = "unknown", - target_os = "unknown" - ), - wasm_bindgen_test - )] - fn test_new_context() { - let time: u64 = 1_496_854_535; - let time_fraction: u32 = 812_946_000; - let node = [1, 2, 3, 4, 5, 6]; - - // This context will wrap - let context = Context::new(u16::MAX >> 2); - - let uuid1 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); - - let time: u64 = 1_496_854_536; - - let uuid2 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); - - assert_eq!(uuid1.get_timestamp().unwrap().to_rfc4122().1, 16383); - assert_eq!(uuid2.get_timestamp().unwrap().to_rfc4122().1, 0); - - let time = 1_496_854_535; - - let uuid3 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); - let uuid4 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); - - assert_eq!(uuid3.get_timestamp().unwrap().counter, 1); - assert_eq!(uuid4.get_timestamp().unwrap().counter, 2); - } } diff --git a/src/v7.rs b/src/v7.rs index 70f8937b..5dbda3b9 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -6,13 +6,16 @@ use crate::{rng, std::convert::TryInto, timestamp::Timestamp, Builder, Uuid}; impl Uuid { - /// Create a new version 7 UUID using the current time value and random bytes. + /// Create a new version 7 UUID using the current time value. /// /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time - /// as the source timestamp. + /// as the source timestamp. All UUIDs generated through this method by the same process are + /// guaranteed to be ordered by their creation. #[cfg(feature = "std")] pub fn now_v7() -> Self { - Self::new_v7(Timestamp::now(crate::NoContext)) + Self::new_v7(Timestamp::now_128( + crate::timestamp::context::shared_context_v7(), + )) } /// Create a new version 7 UUID using a time value and random bytes. @@ -41,6 +44,18 @@ impl Uuid { /// ); /// ``` /// + /// A v7 UUID can also be created with a counter to ensure batches of + /// UUIDs created together remain sortable: + /// + /// ```rust + /// # use uuid::{Uuid, Timestamp, ContextV7}; + /// let context = ContextV7::new(); + /// let uuid1 = Uuid::new_v7(Timestamp::from_unix_128(&context, 1497624119, 1234)); + /// let uuid2 = Uuid::new_v7(Timestamp::from_unix_128(&context, 1497624119, 1234)); + /// + /// assert!(uuid1 < uuid2); + /// ``` + /// /// # References /// /// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7) @@ -48,8 +63,35 @@ impl Uuid { let (secs, nanos) = ts.to_unix(); let millis = (secs * 1000).saturating_add(nanos as u64 / 1_000_000); - Builder::from_unix_timestamp_millis(millis, &rng::bytes()[..10].try_into().unwrap()) - .into_uuid() + let mut counter_and_random = rng::u128(); + + let (mut counter, counter_bits) = ts.counter(); + + debug_assert!(counter_bits <= 128); + + let mut counter_bits = counter_bits as u32; + + // If the counter intersects the variant field then shift around it. + // This ensures that any bits set in the counter that would intersect + // the variant are still preserved + if counter_bits > 12 { + let mask = u128::MAX << (counter_bits - 12); + + counter = (counter & !mask) | ((counter & mask) << 2); + + counter_bits += 2; + } + + counter_and_random &= u128::MAX.overflowing_shr(counter_bits).0; + counter_and_random |= counter + .overflowing_shl(128u32.saturating_sub(counter_bits)) + .0; + + Builder::from_unix_timestamp_millis( + millis, + &counter_and_random.to_be_bytes()[..10].try_into().unwrap(), + ) + .into_uuid() } }