From fbf463fdd1f24b4897bbbafdd8014df922b038d2 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Wed, 5 Oct 2022 17:51:45 +0200 Subject: [PATCH] reorganize time code a bit --- src/builder.rs | 99 +++++++-------------- src/lib.rs | 62 ++++--------- src/timestamp.rs | 220 +++++++++++++++++++++++------------------------ src/v1.rs | 68 ++++++++++++--- src/v6.rs | 57 +++++++++++- src/v7.rs | 73 ++++++++++++---- src/v8.rs | 5 +- 7 files changed, 321 insertions(+), 263 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 5bce22d..4d24d7d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -13,8 +13,7 @@ //! //! [`Uuid`]: ../struct.Uuid.html -use core::time::Duration; -use crate::{error::*, Bytes, Uuid, Variant, Version, Timestamp}; +use crate::{error::*, Bytes, Uuid, Variant, Version}; /// A builder struct for creating a UUID. /// @@ -539,30 +538,13 @@ impl Builder { } /// Creates a `Builder` for a version 1 UUID using the supplied timestamp and node id. - pub const fn from_rfc4122_timestamp(ts: Timestamp, node_id: &[u8; 6]) -> Self { - let (ticks, counter) = ts.to_rfc4122(); - - let time_low = (ticks & 0xFFFF_FFFF) as u32; - let time_mid = ((ticks >> 32) & 0xFFFF) as u16; - let time_high_and_version = (((ticks >> 48) & 0x0FFF) as u16) | (1 << 12); - - let mut d4 = [0; 8]; - - d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80; - d4[1] = (counter & 0xFF) as u8; - d4[2] = node_id[0]; - d4[3] = node_id[1]; - d4[4] = node_id[2]; - d4[5] = node_id[3]; - d4[6] = node_id[4]; - d4[7] = node_id[5]; - - Self::from_fields(time_low, time_mid, time_high_and_version, &d4) + pub const fn from_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Self { + Builder(crate::v1::encode_rfc4122_timestamp(ticks, counter, node_id)) } /// Creates a `Builder` for a version 3 UUID using the supplied MD5 hashed bytes. - pub const fn from_md5_bytes(b: Bytes) -> Self { - Builder(Uuid::from_bytes(b)) + pub const fn from_md5_bytes(md5_bytes: Bytes) -> Self { + Builder(Uuid::from_bytes(md5_bytes)) .with_variant(Variant::RFC4122) .with_version(Version::Md5) } @@ -586,8 +568,8 @@ impl Builder { /// assert_eq!(Some(Version::Random), uuid.get_version()); /// assert_eq!(Variant::RFC4122, uuid.get_variant()); /// ``` - pub const fn from_random_bytes(b: Bytes) -> Self { - Builder(Uuid::from_bytes(b)) + pub const fn from_random_bytes(random_bytes: Bytes) -> Self { + Builder(Uuid::from_bytes(random_bytes)) .with_variant(Variant::RFC4122) .with_version(Version::Random) } @@ -596,8 +578,8 @@ impl Builder { /// /// This method assumes the bytes are already a SHA1 hash, it will only set the appropriate /// bits for the UUID version and variant. - pub const fn from_sha1_bytes(b: Bytes) -> Self { - Builder(Uuid::from_bytes(b)) + pub const fn from_sha1_bytes(sha1_bytes: Bytes) -> Self { + Builder(Uuid::from_bytes(sha1_bytes)) .with_variant(Variant::RFC4122) .with_version(Version::Sha1) } @@ -605,25 +587,14 @@ impl Builder { /// Creates a `Builder` for a version 6 UUID using the supplied timestamp and node id. /// /// This method will encode the ticks, counter, and node id in a sortable UUID. - pub const fn from_sorted_rfc4122_timestamp(ts: Timestamp, node_id: &[u8; 6]) -> Self { - let (ticks, counter) = ts.to_rfc4122(); - - let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32; - let time_mid = ((ticks >> 12) & 0xFFFF) as u16; - let time_low_and_version = ((ticks & 0x0FFF) as u16) | (0x6 << 12); - - let mut d4 = [0; 8]; - - d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80; - d4[1] = (counter & 0xFF) as u8; - d4[2] = node_id[0]; - d4[3] = node_id[1]; - d4[4] = node_id[2]; - d4[5] = node_id[3]; - d4[6] = node_id[4]; - d4[7] = node_id[5]; - - Self::from_fields(time_high, time_mid, time_low_and_version, &d4) + pub const fn from_sorted_rfc4122_timestamp( + ticks: u64, + counter: u16, + node_id: &[u8; 6], + ) -> Self { + Builder(crate::v6::encode_sorted_rfc4122_timestamp( + ticks, counter, node_id, + )) } /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp. @@ -637,48 +608,36 @@ impl Builder { /// Creating a UUID using the current system timestamp: /// /// ``` + /// use std::time::{Duration, SystemTime}; /// # fn main() -> Result<(), Box> { /// # use uuid::{Builder, Uuid, Variant, Version, Timestamp, NoContext}; /// # let rng = || [ - /// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204, + /// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13 /// # ]; - /// let ts = Timestamp::now(NoContext); + /// let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; + /// /// let random_bytes = rng(); /// - /// let uuid = Builder::from_timestamp_millis(ts, &random_bytes).into_uuid(); + /// let uuid = Builder::from_unix_timestamp(ts.as_secs(), ts.subsec_millis(), &random_bytes).into_uuid(); /// /// assert_eq!(Some(Version::SortRand), uuid.get_version()); /// assert_eq!(Variant::RFC4122, uuid.get_variant()); /// # Ok(()) /// # } /// ``` - pub const fn from_timestamp_millis(ts: Timestamp, random_bytes: &[u8; 11]) -> Self { - let millis = Duration::new(ts.seconds, ts.nanos).as_millis() as u64; - let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; - let millis_low = (millis & 0xFFFF) as u16; - - let random_and_version = (random_bytes[0] as u16 | ((random_bytes[1] 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]; - - Self::from_fields(millis_high, millis_low, random_and_version, &d4) + pub const fn from_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Self { + Builder(crate::v7::encode_unix_timestamp_millis( + millis, + random_bytes, + )) } /// Creates a `Builder` for a version 8 UUID using the supplied user-defined bytes. /// /// This method won't interpret the given bytes in any way, except to set the appropriate /// bits for the UUID version and variant. - pub const fn from_custom_bytes(b: Bytes) -> Self { - Builder::from_bytes(b) + pub const fn from_custom_bytes(custom_bytes: Bytes) -> Self { + Builder::from_bytes(custom_bytes) .with_variant(Variant::RFC4122) .with_version(Version::Custom) } diff --git a/src/lib.rs b/src/lib.rs index f3daa28..c36d2b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,7 +228,7 @@ mod parser; pub mod fmt; pub mod timestamp; -pub use timestamp::{ClockSequence, Timestamp, context::NoContext}; +pub use timestamp::{context::NoContext, ClockSequence, Timestamp}; #[cfg(any(feature = "v1", feature = "v6"))] pub use timestamp::context::Context; @@ -884,60 +884,30 @@ impl Uuid { /// value into more commonly-used formats, such as a unix timestamp. /// /// [`Timestamp`]: v1/struct.Timestamp.html - pub const fn get_timestamp(&self) -> Option { + pub const fn get_timestamp(&self) -> Option { match self.get_version() { Some(Version::Mac) => { - let bytes = self.as_bytes(); - let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56 - | (bytes[7] as u64) << 48 - | (bytes[4] as u64) << 40 - | (bytes[5] as u64) << 32 - | (bytes[0] as u64) << 24 - | (bytes[1] as u64) << 16 - | (bytes[2] as u64) << 8 - | (bytes[3] as u64); + let (ticks, counter) = v1::decode_rfc4122_timestamp(self); - let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); - - Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter)) + Some(Timestamp::from_rfc4122(ticks, counter)) } Some(Version::SortMac) => { - let bytes = self.as_bytes(); - let ticks: u64 = ((self.as_bytes()[0]) as u64) << 52 - | (bytes[1] as u64) << 44 - | (bytes[2] as u64) << 36 - | (bytes[3] as u64) << 28 - | (bytes[4] as u64) << 20 - | (bytes[5] as u64) << 12 - | ((bytes[6] & 0xF) as u64) << 8 - | (bytes[7] as u64); + let (ticks, counter) = v6::decode_sorted_rfc4122_timestamp(self); - let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); - - Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter)) + Some(Timestamp::from_rfc4122(ticks, counter)) } Some(Version::SortRand) => { - let bytes = self.as_bytes(); - let millis: u64 = (bytes[0] as u64) << 40 - | (bytes[1] as u64) << 32 - | (bytes[2] as u64) << 24 - | (bytes[3] as u64) << 16 - | (bytes[4] as u64) << 8 - | (bytes[5] as u64); + let millis = v7::decode_unix_timestamp_millis(self); + let seconds = millis / 1000; let nanos = ((millis % 1000) * 1_000_000) as u32; - #[cfg(any(feature = "v1", feature = "v6"))] - { - Some(Timestamp { - seconds, - nanos, - counter: 0, - }) - } - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - Some(Timestamp { seconds, nanos }) - } + + Some(Timestamp { + seconds, + nanos, + #[cfg(any(feature = "v1", feature = "v6"))] + counter: 0, + }) } _ => None, } @@ -1168,7 +1138,7 @@ mod tests { let uuid1 = new(); let s = uuid1.hyphenated().to_string(); - assert!(s.len() == 36); + assert_eq!(36, s.len()); assert!(s.chars().all(|c| c.is_digit(16) || c == '-')); } diff --git a/src/timestamp.rs b/src/timestamp.rs index 1d3e715..4660bca 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -1,11 +1,20 @@ //! Generating UUIDs from timestamps. +//! +//! Timestamps are used in a few UUID versions as a source of decentralized +//! uniqueness (as in versions 1 and 6), and as a way to enable sorting (as +//! in versions 6 and 7). Timestamps aren't encoded the same way by all UUID +//! versions so this module provides a single [`Timestamp`] type that can +//! convert between them. -/// The number of 100 ns ticks between the UUID epoch -/// `1582-10-15 00:00:00` and the Unix epoch `1970-01-01 00:00:00`. +/// The number of 100 nanosecond ticks between the RFC4122 epoch +/// (`1582-10-15 00:00:00`) and the Unix epoch (`1970-01-01 00:00:00`). pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; -/// Stores the number of seconds since epoch, -/// as well as the fractional nanoseconds of that second +/// A timestamp that can be encoded into a UUID. +/// +/// This type abstracts the specific encoding, so a UUID version 1 or +/// a UUID version 7 can both be supported through the same type, even +/// though they have a different representation of a timestamp. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Timestamp { pub(crate) seconds: u64, @@ -15,13 +24,30 @@ pub struct Timestamp { } impl Timestamp { - /// Construct a `Timestamp` from its raw component values: an RFC4122 - /// timestamp and counter. + /// Get a timestamp representing the current system time. /// - /// RFC4122, which defines the V1 UUID, specifies a 60-byte timestamp format - /// as the number of 100-nanosecond intervals elapsed since 00:00:00.00, - /// 15 Oct 1582, "the date of the Gregorian reform of the Christian - /// calendar." + /// This method defers to the standard library's `SystemTime` type. + #[cfg(feature = "std")] + pub fn now(context: impl ClockSequence) -> Self { + #[cfg(not(any(feature = "v1", feature = "v6")))] + { + let _ = context; + } + + let dur = std::time::SystemTime::UNIX_EPOCH + .elapsed() + .expect("Getting elapsed time since UNIX_EPOCH. If this fails, we've somehow violated causality"); + + Timestamp { + seconds: dur.as_secs(), + nanos: dur.subsec_nanos(), + #[cfg(any(feature = "v1", feature = "v6"))] + counter: context.generate_sequence(dur.as_secs(), dur.subsec_nanos()), + } + } + + /// Construct a `Timestamp` from an RFC4122 timestamp and counter, as used + /// in version 1 and version 6 UUIDs. pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { #[cfg(not(any(feature = "v1", feature = "v6")))] { @@ -38,19 +64,8 @@ impl Timestamp { } } - /// Construct a `Timestamp` from a unix timestamp - /// - /// A unix timestamp represents the elapsed time since Jan 1 1970. Libc's - /// `clock_gettime` and other popular implementations traditionally - /// represent this duration as a `timespec`: a struct with `u64` and - /// `u32` fields representing the seconds, and "subsecond" or fractional - /// nanoseconds elapsed since the timestamp's second began, - /// respectively. - pub fn from_unix( - context: impl ClockSequence, - seconds: u64, - nanos: u32, - ) -> Self { + /// Construct a `Timestamp` from a Unix timestamp. + pub fn from_unix(context: impl ClockSequence, seconds: u64, nanos: u32) -> Self { #[cfg(not(any(feature = "v1", feature = "v6")))] { let _ = context; @@ -69,33 +84,8 @@ impl Timestamp { } } - /// Construct a `Timestamp` from the current time of day - /// according to Rust's SystemTime. - #[cfg(feature = "std")] - pub fn now(context: impl ClockSequence) -> Self { - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - let _ = context; - } - - let dur = std::time::SystemTime::UNIX_EPOCH - .elapsed() - .expect("Getting elapsed time since UNIX_EPOCH. If this fails, we've somehow violated causality"); - - Timestamp { - seconds: dur.as_secs(), - nanos: dur.subsec_nanos(), - #[cfg(any(feature = "v1", feature = "v6"))] - counter: context - .generate_sequence(dur.as_secs(), dur.subsec_nanos()), - } - } - - /// Returns the raw RFC4122 timestamp "tick" values stored by the - /// `Timestamp`. - /// - /// The ticks represent the number of 100-nanosecond intervals - /// since 00:00:00.00, 15 Oct 1582. + /// Get the value of the timestamp as an RFC4122 timestamp and counter, + /// as used in version 1 and version 6 UUIDs. #[cfg(any(feature = "v1", feature = "v6"))] pub const fn to_rfc4122(&self) -> (u64, u16) { ( @@ -104,14 +94,32 @@ impl Timestamp { ) } - /// Returns the timestamp converted to the seconds and fractional - /// nanoseconds since Jan 1 1970. + /// Get the value of the timestamp as a Unix timestamp, consisting of the + /// number of whole and fractional seconds. pub const fn to_unix(&self) -> (u64, u32) { (self.seconds, self.nanos) } + #[cfg(any(feature = "v1", feature = "v6"))] + const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 { + let ticks = UUID_TICKS_BETWEEN_EPOCHS + seconds * 10_000_000 + nanos as u64 / 100; + + ticks + } + + const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) { + ( + (ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000, + ((ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100, + ) + } + #[deprecated(note = "use `to_unix` instead")] - #[doc(hidden)] + /// Get the number of fractional nanoseconds in the Unix timestamp. + /// + /// This method is deprecated and probably doesn't do what you're expecting it to. + /// It doesn't return the timestamp as nanoseconds since the Unix epoch, it returns + /// the fractional seconds of the timestamp. pub const fn to_unix_nanos(&self) -> u32 { // NOTE: This method never did what it said on the tin: instead of // converting the timestamp into nanos it simply returned the nanoseconds @@ -121,64 +129,42 @@ impl Timestamp { // a useful value for nanoseconds since the epoch. self.nanos } - - /// internal utility functions for converting between Unix and Uuid-epoch - /// convert unix-timestamp into rfc4122 ticks. - #[cfg(any(feature = "v1", feature = "v6"))] - const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 { - let ticks = UUID_TICKS_BETWEEN_EPOCHS - + seconds * 10_000_000 - + nanos as u64 / 100; - - ticks - } - - /// convert rfc4122 ticks into unix-timestamp - const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) { - ( - (ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000, - ((ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100, - ) - } } -/// A trait that abstracts over generation of counter values used in UUID timestamps. +/// A counter that can be used by version 1 and version 6 UUIDs to support +/// the uniqueness of timestamps. /// /// # References /// /// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) pub trait ClockSequence { - /// The primitive type you wish out output + /// The type of sequence returned by this counter. type Output; - /// Return an arbitrary width number that will be used as the "clock sequence" in - /// the UUID. The number must be different if the time has changed since - /// the last time a clock sequence was requested. - fn generate_sequence( - &self, - seconds: u64, - subsec_nanos: u32, - ) -> Self::Output; + + /// Get the next value in the sequence to feed into a timestamp. + /// + /// This method will be called each time a [`Timestamp`] is constructed. + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output; } impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { type Output = T::Output; - fn generate_sequence( - &self, - seconds: u64, - subsec_nanos: u32, - ) -> Self::Output { + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { (**self).generate_sequence(seconds, subsec_nanos) } } -/// For features v1 and v6, constructs a `Context` struct which implements the `ClockSequence` trait. +/// Default implementations for the [`ClockSequence`] trait. pub mod context { use super::ClockSequence; #[cfg(any(feature = "v1", feature = "v6"))] use private_atomic::{Atomic, Ordering}; - /// A clock sequence that never produces a counter value to deduplicate equal timestamps with. + /// 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; @@ -190,8 +176,31 @@ pub mod context { } } - /// A thread-safe, stateful context for the v1 generator to help ensure - /// process-wide uniqueness. + #[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); + } + + &CONTEXT + } + + /// 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 { @@ -200,31 +209,18 @@ pub mod context { #[cfg(any(feature = "v1", feature = "v6"))] impl Context { - /// Creates a thread-safe, internally mutable context to help ensure - /// uniqueness. + /// Construct a new context that's initialized with the given value. /// - /// This is a context which can be shared across threads. It maintains an - /// internal counter that is incremented at every request, the value ends - /// up in the clock_seq portion of the UUID (the fourth group). This - /// will improve the probability that the UUID is unique across the - /// process. + /// 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), } } - /// Creates a thread-safe, internally mutable context that's seeded with a - /// random value. - /// - /// This method requires either the `rng` or `fast-rng` feature to also be - /// enabled. - /// - /// This is a context which can be shared across threads. It maintains an - /// internal counter that is incremented at every request, the value ends - /// up in the clock_seq portion of the UUID (the fourth group). This - /// will improve the probability that the UUID is unique across the - /// process. + /// Construct a new context that's initialized with a random value. #[cfg(feature = "rng")] pub fn new_random() -> Self { Self { @@ -237,11 +233,7 @@ pub mod context { impl ClockSequence for Context { type Output = u16; - fn generate_sequence( - &self, - _seconds: u64, - _nanos: u32, - ) -> Self::Output { + 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 diff --git a/src/v1.rs b/src/v1.rs index 7cba0fc..08db8ee 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -1,31 +1,39 @@ //! The implementation for Version 1 UUIDs. //! -//! Note that you need to enable the `v1` Cargo feature -//! in order to use this module. +//! This module is soft-deprecated. Instead of using the `Context` type re-exported here, +//! use the one from the crate root. +use crate::timestamp::context::shared_context; use crate::timestamp::Timestamp; use crate::{Builder, Uuid}; pub use crate::timestamp::context::Context; impl Uuid { - /// Create a new UUID (version 1) using a time value + sequence + - /// *NodeId*. + /// Create a new UUID (version 1) using the current system time and a node id. + /// + /// This method is only available if both the `std` and `rng` features are enabled. + #[cfg(all(feature = "std", feature = "rng"))] + pub fn now_v1(node_id: &[u8; 6]) -> Self { + let ts = Timestamp::now(shared_context()); + + Self::new_v1(ts, node_id) + } + + /// Create a new UUID (version 1) using the given timestamp and node id. /// /// When generating [`Timestamp`]s using a [`ClockSequence`], this function /// is only guaranteed to produce unique values if the following conditions /// hold: /// - /// 1. The *NodeId* is unique for this process, - /// 2. The *Context* is shared across all threads which are generating v1 + /// 1. The *node id* is unique for this process, + /// 2. The *context* is shared across all threads which are generating version 1 /// UUIDs, /// 3. The [`ClockSequence`] implementation reliably returns unique /// clock sequences (this crate provides [`Context`] for this /// purpose. However you can create your own [`ClockSequence`] /// implementation, if [`Context`] does not meet your needs). /// - /// The NodeID must be exactly 6 bytes long. - /// /// Note that usage of this method requires the `v1` feature of this crate /// to be enabled. /// @@ -35,7 +43,7 @@ impl Uuid { /// [`ClockSequence`]. RFC4122 requires the clock sequence /// is seeded with a random value: /// - /// ```rust + /// ``` /// # use uuid::{Timestamp, Context}; /// # use uuid::Uuid; /// # fn random_seed() -> u16 { 42 } @@ -67,7 +75,7 @@ impl Uuid { /// /// The timestamp can also just use the current SystemTime /// - /// ```rust + /// ``` /// # use uuid::{Timestamp, Context}; /// # use uuid::Uuid; /// let context = Context::new(42); @@ -80,10 +88,48 @@ impl Uuid { /// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`Context`]: v1/struct.Context.html pub fn new_v1(ts: Timestamp, node_id: &[u8; 6]) -> Self { - Builder::from_rfc4122_timestamp(ts, node_id).into_uuid() + let (ticks, counter) = ts.to_rfc4122(); + + Builder::from_rfc4122_timestamp(ticks, counter, node_id).into_uuid() } } +pub(crate) const fn encode_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Uuid { + let time_low = (ticks & 0xFFFF_FFFF) as u32; + let time_mid = ((ticks >> 32) & 0xFFFF) as u16; + let time_high_and_version = (((ticks >> 48) & 0x0FFF) as u16) | (1 << 12); + + let mut d4 = [0; 8]; + + d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80; + d4[1] = (counter & 0xFF) as u8; + d4[2] = node_id[0]; + d4[3] = node_id[1]; + d4[4] = node_id[2]; + d4[5] = node_id[3]; + d4[6] = node_id[4]; + d4[7] = node_id[5]; + + Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4) +} + +pub(crate) const fn decode_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) { + let bytes = uuid.as_bytes(); + + let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56 + | (bytes[7] as u64) << 48 + | (bytes[4] as u64) << 40 + | (bytes[5] as u64) << 32 + | (bytes[0] as u64) << 24 + | (bytes[1] as u64) << 16 + | (bytes[2] as u64) << 8 + | (bytes[3] as u64); + + let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); + + (ticks, counter) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/v6.rs b/src/v6.rs index 056d931..639e4db 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -3,10 +3,21 @@ //! Note that you need to enable the `v6` Cargo feature //! in order to use this module. +use crate::timestamp::context::shared_context; use crate::timestamp::Timestamp; use crate::{Builder, Uuid}; impl Uuid { + /// Create a new UUID (version 6) using the current time value and a node id. + /// + /// This method is only available if the `std` feature is enabled. + #[cfg(all(feature = "std", feature = "rng"))] + pub fn now_v6(node_id: &[u8; 6]) -> Self { + let ts = Timestamp::now(shared_context()); + + Self::new_v6(ts, node_id) + } + /// Create a new UUID (version 6) using a time value + sequence + /// *NodeId*. /// This is similar to UUIDv1, except that it is lexographically sortable by timestamp. @@ -75,14 +86,56 @@ impl Uuid { /// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`Context`]: v1/struct.Context.html pub fn new_v6(ts: Timestamp, node_id: &[u8; 6]) -> Self { - Builder::from_sorted_rfc4122_timestamp(ts, node_id).into_uuid() + let (ticks, counter) = ts.to_rfc4122(); + + Builder::from_sorted_rfc4122_timestamp(ticks, counter, node_id).into_uuid() } } +pub(crate) const fn encode_sorted_rfc4122_timestamp( + ticks: u64, + counter: u16, + node_id: &[u8; 6], +) -> Uuid { + let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32; + let time_mid = ((ticks >> 12) & 0xFFFF) as u16; + let time_low_and_version = ((ticks & 0x0FFF) as u16) | (0x6 << 12); + + let mut d4 = [0; 8]; + + d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80; + d4[1] = (counter & 0xFF) as u8; + d4[2] = node_id[0]; + d4[3] = node_id[1]; + d4[4] = node_id[2]; + d4[5] = node_id[3]; + d4[6] = node_id[4]; + d4[7] = node_id[5]; + + Uuid::from_fields(time_high, time_mid, time_low_and_version, &d4) +} + +pub(crate) const fn decode_sorted_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) { + let bytes = uuid.as_bytes(); + + let ticks: u64 = ((bytes[0]) as u64) << 52 + | (bytes[1] as u64) << 44 + | (bytes[2] as u64) << 36 + | (bytes[3] as u64) << 28 + | (bytes[4] as u64) << 20 + | (bytes[5] as u64) << 12 + | ((bytes[6] & 0xF) as u64) << 8 + | (bytes[7] as u64); + + let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); + + (ticks, counter) +} + #[cfg(test)] mod tests { use super::*; - use crate::{Variant, Version, Context}; + use crate::{Context, Variant, Version}; use std::string::ToString; #[cfg(target_arch = "wasm32")] diff --git a/src/v7.rs b/src/v7.rs index 92aeedf..60ffb63 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -3,7 +3,7 @@ //! Note that you need to enable the `v7` Cargo feature //! in order to use this module. -use crate::{Builder, NoContext, rng, timestamp::Timestamp, Uuid}; +use crate::{rng, timestamp::Timestamp, Builder, NoContext, Uuid}; use core::convert::TryInto; impl Uuid { @@ -23,9 +23,6 @@ impl Uuid { /// Note that usage of this method requires the `v7` feature of this crate /// to be enabled. /// - /// This method will use millisecond precision for the timestamp and fill the - /// rest with random data. - /// /// # Examples /// /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit @@ -41,26 +38,53 @@ impl Uuid { /// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7") /// ); /// ``` - /// - /// The timestamp can also be created automatically from the current SystemTime - /// - /// ``` - /// # use uuid::{Uuid, Timestamp, NoContext}; - /// let ts = Timestamp::now(NoContext); - /// - /// let uuid = Uuid::new_v7(ts); - /// ``` pub fn new_v7(ts: Timestamp) -> Self { - let buf: &[u8] = &rng::bytes()[0..11]; + let (secs, nanos) = ts.to_unix(); + let millis = secs.saturating_add(nanos as u64 / 1_000_000); - Builder::from_timestamp_millis(ts, buf.try_into().unwrap()).into_uuid() + Builder::from_unix_timestamp_millis(millis, &rng::bytes()[..10].try_into().unwrap()) + .into_uuid() } } +pub(crate) const fn encode_unix_timestamp_millis(millis: u64, 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[0] as u16 | ((random_bytes[1] 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]; + + Uuid::from_fields(millis_high, millis_low, random_and_version, &d4) +} + +pub(crate) const fn decode_unix_timestamp_millis(uuid: &Uuid) -> u64 { + let bytes = uuid.as_bytes(); + + let millis: u64 = (bytes[0] as u64) << 40 + | (bytes[1] as u64) << 32 + | (bytes[2] as u64) << 24 + | (bytes[3] as u64) << 16 + | (bytes[4] as u64) << 8 + | (bytes[5] as u64); + + millis +} + #[cfg(test)] mod tests { use super::*; - use crate::{Variant, Version, NoContext}; + use crate::{NoContext, Variant, Version}; use std::string::ToString; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -81,6 +105,21 @@ mod tests { // Ensure parsing the same UUID produces the same timestamp let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); - assert_eq!(uuid, parsed,); + assert_eq!(uuid, parsed); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_new_v7_timestamp_roundtrip() { + let time: u64 = 1_496_854_535; + let time_fraction: u32 = 812_946_000; + + let ts = Timestamp::from_unix(NoContext, time, time_fraction); + + let uuid = Uuid::new_v7(ts); + + let decoded_ts = uuid.get_timestamp().unwrap(); + + assert_eq!(ts.to_unix(), decoded_ts.to_unix()); } } diff --git a/src/v8.rs b/src/v8.rs index 647a6f3..a67dfcb 100644 --- a/src/v8.rs +++ b/src/v8.rs @@ -26,7 +26,7 @@ impl Uuid { #[cfg(test)] mod tests { use super::*; - use crate::{Version, Variant}; + use crate::{Variant, Version}; use std::string::ToString; #[cfg(target_arch = "wasm32")] @@ -36,8 +36,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new() { let buf: [u8; 16] = [ - 0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, - 0x2, 0x1, 0x0, + 0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, ]; let uuid = Uuid::new_v8(buf); assert_eq!(uuid.get_version(), Some(Version::Custom));