From 677e1fb159be7a9ab1e2f8aa98c7978b5733c4cb Mon Sep 17 00:00:00 2001 From: KodrAus Date: Fri, 23 Sep 2022 12:00:17 +1000 Subject: [PATCH 01/10] some work on the new version features --- Cargo.toml | 2 +- examples/sortable_uuid.rs | 15 ++++++ src/lib.rs | 12 ++--- src/timestamp.rs | 102 ++++++++++++++++++++++---------------- src/v1.rs | 2 +- src/v6.rs | 5 +- src/v7.rs | 18 ++----- 7 files changed, 85 insertions(+), 71 deletions(-) create mode 100644 examples/sortable_uuid.rs diff --git a/Cargo.toml b/Cargo.toml index 8747f611..06c09e7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ v3 = ["md5"] v4 = ["rng"] v5 = ["sha1"] v6 = ["private_atomic"] -v7 = ["rng"] +v7 = ["private_atomic", "rng"] v8 = [] js = ["private_getrandom", "private_getrandom/js"] diff --git a/examples/sortable_uuid.rs b/examples/sortable_uuid.rs new file mode 100644 index 00000000..af57a6c7 --- /dev/null +++ b/examples/sortable_uuid.rs @@ -0,0 +1,15 @@ +//! Generating a sortable UUID. +//! +//! If you enable the `v7` feature you can generate sortable UUIDs. + +#[test] +#[cfg(feature = "v7")] +fn generate_sortable_uuid() { + use uuid::Uuid; + + let uuid = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)); + + assert_eq!(Some(uuid::Version::SortRand), uuid.get_version()); +} + +fn main() {} diff --git a/src/lib.rs b/src/lib.rs index 940e842e..8bf340bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,17 +220,15 @@ use zerocopy::{AsBytes, FromBytes, Unaligned}; mod builder; mod error; -pub mod fmt; mod parser; -/// contains the `Timestamp` struct and `ClockSequence` trait -pub mod timestamp; -pub use timestamp::{ClockSequence, Timestamp}; +pub mod fmt; +pub mod timestamp; -#[cfg(any(feature = "v1", feature = "v6"))] -pub use timestamp::context::Context; +pub use timestamp::{ClockSequence, Timestamp, context::{Context, NoContext}}; #[cfg(feature = "v1")] +#[doc(hidden)] pub mod v1; #[cfg(feature = "v3")] mod v3; @@ -241,7 +239,7 @@ mod v5; #[cfg(feature = "v6")] mod v6; #[cfg(feature = "v7")] -pub mod v7; +mod v7; #[cfg(feature = "v8")] mod v8; diff --git a/src/timestamp.rs b/src/timestamp.rs index 609d7d68..3a938cbd 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -1,9 +1,9 @@ -/// Contains the `Timestamp` struct and `ClockSequence` traits -/// as well as an implementation of ClockSequence for Context (only for features v1 and v6) +//! Generating UUIDs from timestamps. /// 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`. 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 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -22,20 +22,19 @@ impl Timestamp { /// 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." - pub const fn from_rfc4122(ticks: u64, _counter: u16) -> Self { - let (seconds, nanos) = Self::rfc4122_to_unix(ticks); - - #[cfg(any(feature = "v1", feature = "v6"))] - { - Timestamp { - seconds, - nanos, - counter: _counter, - } - } + pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { #[cfg(not(any(feature = "v1", feature = "v6")))] { - Timestamp { seconds, nanos } + let _ = counter; + } + + let (seconds, nanos) = Self::rfc4122_to_unix(ticks); + + Timestamp { + seconds, + nanos, + #[cfg(any(feature = "v1", feature = "v6"))] + counter, } } @@ -48,50 +47,45 @@ impl Timestamp { /// nanoseconds elapsed since the timestamp's second began, /// respectively. pub fn from_unix( - _context: impl ClockSequence, + context: impl ClockSequence, seconds: u64, nanos: u32, ) -> Self { + #[cfg(not(any(feature = "v1", feature = "v6")))] + { + let _ = context; + + Timestamp { seconds, nanos } + } #[cfg(any(feature = "v1", feature = "v6"))] { - let counter = _context.generate_sequence(seconds, nanos); + let counter = context.generate_sequence(seconds, nanos); + Timestamp { seconds, nanos, counter, } } - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - Timestamp { seconds, nanos } - } } /// Construct a `Timestamp` from the current time of day - /// according to Rust's SystemTime - /// NOTE: This function will panic if the current system time is earlier than - /// the Unix Epoch of 1970-01-01 00:00:00 So please use caution when time travelling. - #[cfg(all(feature = "std", not(any(feature = "v1", feature = "v6"))))] - pub fn now() -> Self { - let dur = std::time::SystemTime::UNIX_EPOCH - .elapsed() - .expect("Getting elapsed time since UNIX_EPOCH failed. This is due to a system time that is somehow earlier than Unix Epoch."); - Timestamp { - seconds: dur.as_secs(), - nanos: dur.subsec_nanos(), + /// according to Rust's SystemTime. + #[cfg(feature = "std")] + pub fn now(context: impl ClockSequence) -> Self { + #[cfg(not(any(feature = "v1", feature = "v6")))] + { + let _ = context; } - } - /// Construct a `Timestamp` from the current time of day - /// according to Rust's SystemTime - #[cfg(all(feature = "std", any(feature = "v1", feature = "v6")))] - pub fn now(context: impl ClockSequence) -> Self { let dur = std::time::SystemTime::UNIX_EPOCH .elapsed() - .expect("Getting elapsed time since UNIX_EPOCH. If this fails, we've somehow violated causality"); + .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()), } @@ -116,14 +110,21 @@ impl Timestamp { (self.seconds, self.nanos) } - /// Returns the timestamp converted into nanoseconds elapsed since Jan 1 - /// 1970. + #[deprecated(note = "use `to_unix` instead")] + #[doc(hidden)] 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 + // part of the timestamp. + // + // We can't fix the behavior because the return type is too small to fit + // 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 + /// 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 @@ -170,10 +171,24 @@ impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { } } -/// For features v1 and v1, constructs a `Context` struct which implements the `ClockSequence` trait -#[cfg(any(feature = "v1", feature = "v6"))] +/// For features v1 and v6, constructs a `Context` struct which implements the `ClockSequence` trait. pub mod context { + use super::ClockSequence; + use private_atomic::{Atomic, Ordering}; + + /// A clock sequence that never produces a counter value to deduplicate equal timestamps with. + #[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 { + 0 + } + } + /// A thread-safe, stateful context for the v1 generator to help ensure /// process-wide uniqueness. #[derive(Debug)] @@ -215,8 +230,9 @@ pub mod context { } } - impl super::ClockSequence for Context { + impl ClockSequence for Context { type Output = u16; + fn generate_sequence( &self, _seconds: u64, diff --git a/src/v1.rs b/src/v1.rs index b166eecf..e2778d58 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -10,7 +10,7 @@ use crate::Uuid; /// `1582-10-15 00:00:00` and the Unix epoch `1970-01-01 00:00:00`. const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; -/// The Context implementation is specific to Uuids v1 and v6 +#[deprecated(note = "use `Context` from the crate root")] pub use crate::timestamp::context::Context; impl Uuid { diff --git a/src/v6.rs b/src/v6.rs index e4b56271..469ce8bc 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -1,4 +1,4 @@ -//! The implementation for Version 1 UUIDs. +//! The implementation for Version 6 UUIDs. //! //! Note that you need to enable the `v6` Cargo feature //! in order to use this module. @@ -6,9 +6,6 @@ use crate::timestamp::{ClockSequence, Timestamp}; use crate::Uuid; -/// The Context implementation is specific to Uuids v1 and v6 -pub use crate::timestamp::context::Context; - impl Uuid { /// Create a new UUID (version 6) using a time value + sequence + /// *NodeId*. diff --git a/src/v7.rs b/src/v7.rs index 6f96fef8..5270eefe 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -1,11 +1,11 @@ -//! The implementation for Version 1 UUIDs. +//! The implementation for Version 7 UUIDs. //! //! Note that you need to enable the `v7` Cargo feature //! in order to use this module. use crate::rng::{bytes, u16}; use crate::timestamp::Timestamp; -use crate::{Uuid, Version}; +use crate::Uuid; use core::convert::TryInto; impl Uuid { @@ -22,7 +22,7 @@ impl Uuid { /// ```rust /// # use uuid::{Uuid, Timestamp}; /// # use uuid::v7::NullSequence; - /// let ts = Timestamp::from_unix(NullSequence {}, 1497624119, 1234); + /// let ts = Timestamp::from_unix(NullSequence, 1497624119, 1234); /// /// let uuid = Uuid::new_v7(ts); /// @@ -50,18 +50,6 @@ impl Uuid { } } -/// Dummy struct and ClockSequence implementation to ease the construction of v7 -/// using a Timestamp -#[derive(Debug)] -pub struct NullSequence {} - -impl super::ClockSequence for NullSequence { - type Output = u16; - fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { - 0 - } -} - #[cfg(test)] mod tests { use super::*; From 6c3cf9ad66f983ac7bf1fa4d7e301fc75bb4a98f Mon Sep 17 00:00:00 2001 From: KodrAus Date: Sat, 24 Sep 2022 16:15:57 +1000 Subject: [PATCH 02/10] add notes for v6-v8 UUIDs --- Cargo.toml | 2 ++ src/lib.rs | 26 ++++++++++++++------------ src/timestamp.rs | 6 +++++- src/v6.rs | 4 ++-- src/v7.rs | 9 ++++----- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06c09e7d..7e8ec4fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ status = "actively-developed" default = ["std"] std = [] macro-diagnostics = ["private_uuid-macro-internal"] + # NOTE: When adding new features, check the `ci.yml` workflow .. # and include them where necessary (you can follow along with existing features) v1 = ["private_atomic"] @@ -57,6 +58,7 @@ v5 = ["sha1"] v6 = ["private_atomic"] v7 = ["private_atomic", "rng"] v8 = [] + js = ["private_getrandom", "private_getrandom/js"] rng = ["private_getrandom"] diff --git a/src/lib.rs b/src/lib.rs index 8bf340bd..7e7b1a20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,14 +72,19 @@ //! features to enable various pieces of functionality: //! //! * `v1` - adds the [`Uuid::new_v1`] function and the ability to create a V1 -//! UUID using an implementation of [`v1::ClockSequence`] (usually -//! [`v1::Context`]) and a UNIX timestamp. +//! UUID using a timestamp and monotonic counter. //! * `v3` - adds the [`Uuid::new_v3`] function and the ability to create a V3 //! UUID based on the MD5 hash of some data. //! * `v4` - adds the [`Uuid::new_v4`] function and the ability to randomly //! generate a UUID. //! * `v5` - adds the [`Uuid::new_v5`] function and the ability to create a V5 //! UUID based on the SHA1 hash of some data. +//! * `v6` - adds the [`Uuid::new_v6`] function and the ability to create a V6 +//! UUID using a timestamp and monotonic counter. +//! * `v7` - adds the [`Uuid::new_v7`] function and the ability to create a V7 +//! UUID using a timestamp. +//! * `v8` - adds the [`Uuid::new_v8`] function and the ability to create a V8 +//! UUID using user-defined data. //! //! Other crate features can also be useful beyond the version support: //! @@ -142,8 +147,8 @@ //! //! Some additional features are supported in no-std environments: //! -//! * `v1`, `v3`, and `v5` -//! * `serde` +//! * `v1`, `v3`, `v5`, `v6`, `v7`, and `v8`. +//! * `serde`. //! //! If you need to use `v4` in a no-std environment, you'll need to //! follow [`getrandom`'s docs] on configuring a source of randomness @@ -186,13 +191,6 @@ //! //! [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen //! [`cargo-web`]: https://crates.io/crates/cargo-web -//! [`Uuid`]: struct.Uuid.html -//! [`Uuid::new_v1`]: struct.Uuid.html#method.new_v1 -//! [`Uuid::new_v3`]: struct.Uuid.html#method.new_v3 -//! [`Uuid::new_v4`]: struct.Uuid.html#method.new_v4 -//! [`Uuid::new_v5`]: struct.Uuid.html#method.new_v5 -//! [`v1::ClockSequence`]: v1/trait.ClockSequence.html -//! [`v1::Context`]: v1/struct.Context.html //! [`getrandom`'s docs]: https://docs.rs/getrandom #![no_std] @@ -225,9 +223,13 @@ mod parser; pub mod fmt; pub mod timestamp; -pub use timestamp::{ClockSequence, Timestamp, context::{Context, NoContext}}; +pub use timestamp::{ClockSequence, Timestamp, context::NoContext}; + +#[cfg(any(feature = "v1", feature = "v6"))] +pub use timestamp::Context; #[cfg(feature = "v1")] +#[deprecated(note = "use items directly from the crate root")] #[doc(hidden)] pub mod v1; #[cfg(feature = "v3")] diff --git a/src/timestamp.rs b/src/timestamp.rs index 3a938cbd..1d3e7157 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -142,7 +142,7 @@ impl Timestamp { } } -/// A trait that abstracts over generation of UUID v1 "Clock Sequence" values. +/// A trait that abstracts over generation of counter values used in UUID timestamps. /// /// # References /// @@ -175,6 +175,7 @@ impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { 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. @@ -192,10 +193,12 @@ pub mod context { /// A thread-safe, stateful context for the v1 generator to help ensure /// process-wide uniqueness. #[derive(Debug)] + #[cfg(any(feature = "v1", feature = "v6"))] pub struct Context { count: Atomic, } + #[cfg(any(feature = "v1", feature = "v6"))] impl Context { /// Creates a thread-safe, internally mutable context to help ensure /// uniqueness. @@ -230,6 +233,7 @@ pub mod context { } } + #[cfg(any(feature = "v1", feature = "v6"))] impl ClockSequence for Context { type Output = u16; diff --git a/src/v6.rs b/src/v6.rs index 469ce8bc..e898cb4e 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -3,7 +3,7 @@ //! Note that you need to enable the `v6` Cargo feature //! in order to use this module. -use crate::timestamp::{ClockSequence, Timestamp}; +use crate::timestamp::Timestamp; use crate::Uuid; impl Uuid { @@ -98,7 +98,7 @@ impl Uuid { #[cfg(test)] mod tests { use super::*; - use crate::{Variant, Version}; + use crate::{Variant, Version, Context}; use std::string::ToString; #[cfg(target_arch = "wasm32")] diff --git a/src/v7.rs b/src/v7.rs index 5270eefe..05eb89cd 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -20,9 +20,8 @@ impl Uuid { /// random number. When supplied as such, the data will be /// /// ```rust - /// # use uuid::{Uuid, Timestamp}; - /// # use uuid::v7::NullSequence; - /// let ts = Timestamp::from_unix(NullSequence, 1497624119, 1234); + /// # use uuid::{Uuid, Timestamp, NoContext}; + /// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234); /// /// let uuid = Uuid::new_v7(ts); /// @@ -53,7 +52,7 @@ impl Uuid { #[cfg(test)] mod tests { use super::*; - use crate::{Variant, Version}; + use crate::{Variant, Version, NoContext}; use std::string::ToString; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -64,7 +63,7 @@ mod tests { let time: u64 = 1_496_854_535; let time_fraction: u32 = 812_946_000; - let uuid = Uuid::new_v7(Timestamp::from_unix(NullSequence {}, time, time_fraction)); + let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, time, time_fraction)); let uustr = uuid.hyphenated().to_string(); assert_eq!(uuid.get_version(), Some(Version::SortRand)); From 017899f84136beb4d41db5d52b7ad0f8e5b21958 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Mon, 26 Sep 2022 09:04:09 +0200 Subject: [PATCH 03/10] some more docs --- src/lib.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7e7b1a20..a26c1937 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,9 +65,13 @@ //! const ID: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); //! ``` //! -//! # Dependencies +//! # Working with different UUID versions //! -//! By default, this crate depends on nothing but `std` and can parse and format +//! There are a number of approaches to generating and interpreting UUIDs that are specified +//! as _versions_ in [RFC4122](http://tools.ietf.org/html/rfc4122). This library supports all of them, +//! in addition to some newer versions in [a draft RFC](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04). +//! +//! By default, this crate depends on nothing but the Rust standard library and can parse and format //! UUIDs, but cannot generate them. You need to enable the following Cargo //! features to enable various pieces of functionality: //! @@ -86,6 +90,8 @@ //! * `v8` - adds the [`Uuid::new_v8`] function and the ability to create a V8 //! UUID using user-defined data. //! +//! # Other features +//! //! Other crate features can also be useful beyond the version support: //! //! * `macro-diagnostics` - enhances the diagnostics of `uuid!` macro. @@ -93,11 +99,11 @@ //! `serde`. //! * `arbitrary` - adds an `Arbitrary` trait implementation to `Uuid` for //! fuzzing. -//! * `fast-rng` - when combined with `v4` uses a faster algorithm for -//! generating random UUIDs. This feature requires more dependencies to -//! compile, but is just as suitable for UUIDs as the default algorithm. +//! * `fast-rng` - uses a faster algorithm for generating random UUIDs. +//! This feature requires more dependencies to compile, but is just as suitable for +//! UUIDs as the default algorithm. //! -//! ## Unstable features +//! # Unstable features //! //! Some features are unstable. They may be incomplete or depend on other //! unstable libraries. These include: @@ -119,8 +125,7 @@ //! //! ## WebAssembly //! -//! For WebAssembly, enable the `js` feature along with `v4` for a -//! source of randomness: +//! For WebAssembly, enable the `js` feature: //! //! ```toml //! [dependencies.uuid] @@ -131,9 +136,6 @@ //! ] //! ``` //! -//! You don't need the `js` feature to use `uuid` in WebAssembly if you're -//! not also enabling `v4`. -//! //! ## Embedded //! //! For embedded targets without the standard library, you'll need to @@ -147,10 +149,10 @@ //! //! Some additional features are supported in no-std environments: //! -//! * `v1`, `v3`, `v5`, `v6`, `v7`, and `v8`. +//! * `v1`, `v3`, `v5`, `v6`, and `v8`. //! * `serde`. //! -//! If you need to use `v4` in a no-std environment, you'll need to +//! If you need to use `v4` or `v7` in a no-std environment, you'll need to //! follow [`getrandom`'s docs] on configuring a source of randomness //! on currently unsupported targets. Alternatively, you can produce //! random bytes yourself and then pass them to [`Builder::from_random_bytes`] @@ -158,7 +160,7 @@ //! //! # Examples //! -//! To parse a UUID given in the simple format and print it as a urn: +//! To parse a UUID given in the simple format and print it as a URN: //! //! ``` //! # use uuid::Uuid; @@ -187,7 +189,7 @@ //! # References //! //! * [Wikipedia: Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) -//! * [RFC4122: A Universally Unique IDentifier (UUID) URN Namespace](http://tools.ietf.org/html/rfc4122) +//! * [RFC4122: A Universally Unique Identifier (UUID) URN Namespace](http://tools.ietf.org/html/rfc4122) //! //! [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen //! [`cargo-web`]: https://crates.io/crates/cargo-web From bd3f3a3d500d8f75cf7a0d0c922d0f6985a45d10 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 27 Sep 2022 08:49:00 +0200 Subject: [PATCH 04/10] move more uuid version impls into Builder --- src/builder.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- src/v1.rs | 21 +++----------- src/v6.rs | 20 ++----------- src/v7.rs | 24 ++++++++-------- src/v8.rs | 5 +--- 6 files changed, 94 insertions(+), 54 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index bc628db7..e2ffb480 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -13,6 +13,8 @@ //! //! [`Uuid`]: ../struct.Uuid.html +use core::convert::TryInto; +use core::time::Duration; use crate::{error::*, Bytes, Uuid, Variant, Version}; /// A builder struct for creating a UUID. @@ -537,7 +539,75 @@ impl Builder { Builder(Uuid::from_bytes_le(b)) } - /// Creates a `Builder` using the supplied random bytes. + /// Creates a `Builder` for a version 1 UUID using the supplied timestamp and node id. + pub const fn from_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Self { + 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) + } + + /// Creates a `Builder` for a version 6 UUID using the supplied timestamp and node id. + pub const fn from_sorted_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Self { + 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) + } + + /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp in millisecond precision and random data. + pub const fn from_timestamp_millis(since_unix_epoch: Duration, random_bytes: &[u8; 11]) -> Self { + let millis = since_unix_epoch.as_millis() as u64; + let ms_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; + let ms_low = (millis & 0xFFFF) as u16; + + let rng_ver = random_bytes[0] as u16 | ((random_bytes[1] as u16) << 8) | (0x7 << 12); + + let mut rng_rest = [0; 8]; + rng_rest[0] = (random_bytes[2] & 0x3F) | 0x80; + rng_rest[1] = random_bytes[3]; + rng_rest[2] = random_bytes[4]; + rng_rest[3] = random_bytes[5]; + rng_rest[4] = random_bytes[6]; + rng_rest[5] = random_bytes[7]; + rng_rest[6] = random_bytes[8]; + rng_rest[7] = random_bytes[9]; + + Self::from_fields(ms_high, ms_low, rng_ver, &rng_rest) + } + + /// Creates a `Builder` for a version 8 UUID using the supplied user-defined bytes. + pub const fn from_custom_bytes(b: Bytes) -> Self { + Builder::from_bytes(b) + .with_variant(Variant::RFC4122) + .with_version(Version::Custom) + } + + /// Creates a `Builder` for a version 4 UUID using the supplied random bytes. /// /// This method can be useful in environments where the `v4` feature isn't /// available. This method will take care of setting the appropriate @@ -563,14 +633,14 @@ impl Builder { .with_version(Version::Random) } - /// Creates a `Builder` using the supplied MD5 hashed bytes. + /// 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)) .with_variant(Variant::RFC4122) .with_version(Version::Md5) } - /// Creates a `Builder` using the supplied SHA1 hashed bytes. + /// Creates a `Builder` for a version 5 UUID using the supplied SHA1 hashed bytes. pub const fn from_sha1_bytes(b: Bytes) -> Self { Builder(Uuid::from_bytes(b)) .with_variant(Variant::RFC4122) diff --git a/src/lib.rs b/src/lib.rs index a26c1937..2914f322 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,7 +228,7 @@ pub mod timestamp; pub use timestamp::{ClockSequence, Timestamp, context::NoContext}; #[cfg(any(feature = "v1", feature = "v6"))] -pub use timestamp::Context; +pub use timestamp::context::Context; #[cfg(feature = "v1")] #[deprecated(note = "use items directly from the crate root")] diff --git a/src/v1.rs b/src/v1.rs index e2778d58..403d2241 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -4,7 +4,7 @@ //! in order to use this module. use crate::timestamp::Timestamp; -use crate::Uuid; +use crate::{Builder, Uuid}; /// 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`. @@ -86,26 +86,13 @@ impl Uuid { /// [`Context`]: v1/struct.Context.html pub fn new_v1(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]; - - Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4) + + Builder::from_rfc4122_timestamp(ticks, counter, node_id).into_uuid() } } #[cfg(test)] +#[allow(deprecated)] mod tests { use super::*; diff --git a/src/v6.rs b/src/v6.rs index e898cb4e..bd5321e6 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -4,7 +4,7 @@ //! in order to use this module. use crate::timestamp::Timestamp; -use crate::Uuid; +use crate::{Builder, Uuid}; impl Uuid { /// Create a new UUID (version 6) using a time value + sequence + @@ -76,22 +76,8 @@ impl Uuid { /// [`Context`]: v1/struct.Context.html pub fn new_v6(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]; - - Uuid::from_fields(time_high, time_mid, time_low_and_version, &d4) + + Builder::from_sorted_rfc4122_timestamp(ticks, counter, node_id).into_uuid() } } diff --git a/src/v7.rs b/src/v7.rs index 05eb89cd..9d5d729b 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -3,17 +3,21 @@ //! Note that you need to enable the `v7` Cargo feature //! in order to use this module. -use crate::rng::{bytes, u16}; +use crate::{Builder, rng}; use crate::timestamp::Timestamp; use crate::Uuid; use core::convert::TryInto; +use core::time::Duration; impl Uuid { - /// Create a new UUID (version 7) using a time value + random number + /// Create a new UUID (version 7) using a time value + random number. /// /// 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 @@ -32,20 +36,16 @@ impl Uuid { /// /// The timestamp can also be created automatically from the current SystemTime /// + /// ``` /// let ts = Timestamp::now(); /// /// let uuid = Uuid::new_v7(ts); - /// - /// [`Timestamp`]: v1/struct.Timestamp.html + /// ``` pub fn new_v7(ts: Timestamp) -> Self { - let millis = ts.seconds * 1000 + (ts.nanos as u64) / 1_000_000; - let ms_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; - let ms_low = (millis & 0xFFFF) as u16; - let ver_rand = u16() & 0xFFF | (0x7 << 12); - let mut rnd = bytes(); - rnd[0] = (rnd[0] & 0x3F) | 0x80; - let buf: [u8; 8] = (&rnd[0..8]).try_into().unwrap(); - Uuid::from_fields(ms_high, ms_low, ver_rand, &buf) + let duration = Duration::new(ts.seconds, ts.nanos); + let buf: &[u8] = &rng::bytes()[0..11]; + + Builder::from_timestamp_millis(duration, buf.try_into().unwrap()).into_uuid() } } diff --git a/src/v8.rs b/src/v8.rs index f6a0005a..d2d4b368 100644 --- a/src/v8.rs +++ b/src/v8.rs @@ -19,10 +19,7 @@ impl Uuid { /// assert_eq!(Some(Version::Custom), uuid.get_version()); /// ``` pub fn new_v8(buf: [u8; 16]) -> Uuid { - Builder::from_bytes(buf) - .with_variant(Variant::RFC4122) - .with_version(Version::Custom) - .into_uuid() + Builder::from_custom_bytes(buf).into_uuid() } } From 9d445c7c94a7ca61e09a58706ea153024b4cd179 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 27 Sep 2022 13:40:30 +0200 Subject: [PATCH 05/10] work on docs and deprecations --- src/builder.rs | 146 ++++++++++++++++++++++++++++++------------------- src/lib.rs | 8 ++- src/v1.rs | 14 +---- src/v4.rs | 1 - src/v6.rs | 4 +- src/v7.rs | 24 +++++--- src/v8.rs | 3 +- 7 files changed, 119 insertions(+), 81 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index e2ffb480..5bce22da 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -13,9 +13,8 @@ //! //! [`Uuid`]: ../struct.Uuid.html -use core::convert::TryInto; use core::time::Duration; -use crate::{error::*, Bytes, Uuid, Variant, Version}; +use crate::{error::*, Bytes, Uuid, Variant, Version, Timestamp}; /// A builder struct for creating a UUID. /// @@ -540,7 +539,9 @@ impl Builder { } /// Creates a `Builder` for a version 1 UUID using the supplied timestamp and node id. - pub const fn from_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Self { + 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); @@ -559,8 +560,54 @@ impl Builder { Self::from_fields(time_low, time_mid, time_high_and_version, &d4) } + /// 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)) + .with_variant(Variant::RFC4122) + .with_version(Version::Md5) + } + + /// Creates a `Builder` for a version 4 UUID using the supplied random bytes. + /// + /// This method assumes the bytes are already sufficiently random, it will only + /// set the appropriate bits for the UUID version and variant. + /// + /// # Examples + /// + /// ``` + /// # use uuid::{Builder, Variant, Version}; + /// # let rng = || [ + /// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204, 195, 90, + /// # 145, 63, 62, + /// # ]; + /// let random_bytes = rng(); + /// let uuid = Builder::from_random_bytes(random_bytes).into_uuid(); + /// + /// 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)) + .with_variant(Variant::RFC4122) + .with_version(Version::Random) + } + + /// Creates a `Builder` for a version 5 UUID using the supplied SHA1 hashed bytes. + /// + /// 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)) + .with_variant(Variant::RFC4122) + .with_version(Version::Sha1) + } + /// Creates a `Builder` for a version 6 UUID using the supplied timestamp and node id. - pub const fn from_sorted_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Self { + /// + /// 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); @@ -579,72 +626,61 @@ impl Builder { Self::from_fields(time_high, time_mid, time_low_and_version, &d4) } - /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp in millisecond precision and random data. - pub const fn from_timestamp_millis(since_unix_epoch: Duration, random_bytes: &[u8; 11]) -> Self { - let millis = since_unix_epoch.as_millis() as u64; - let ms_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; - let ms_low = (millis & 0xFFFF) as u16; - - let rng_ver = random_bytes[0] as u16 | ((random_bytes[1] as u16) << 8) | (0x7 << 12); - - let mut rng_rest = [0; 8]; - rng_rest[0] = (random_bytes[2] & 0x3F) | 0x80; - rng_rest[1] = random_bytes[3]; - rng_rest[2] = random_bytes[4]; - rng_rest[3] = random_bytes[5]; - rng_rest[4] = random_bytes[6]; - rng_rest[5] = random_bytes[7]; - rng_rest[6] = random_bytes[8]; - rng_rest[7] = random_bytes[9]; - - Self::from_fields(ms_high, ms_low, rng_ver, &rng_rest) - } - - /// Creates a `Builder` for a version 8 UUID using the supplied user-defined bytes. - pub const fn from_custom_bytes(b: Bytes) -> Self { - Builder::from_bytes(b) - .with_variant(Variant::RFC4122) - .with_version(Version::Custom) - } - - /// Creates a `Builder` for a version 4 UUID using the supplied random bytes. + /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp. /// - /// This method can be useful in environments where the `v4` feature isn't - /// available. This method will take care of setting the appropriate - /// version and variant fields. + /// This method will encode the duration since the Unix epoch at millisecond precision, filling + /// the rest with data from the given random bytes. This method assumes the bytes are already + /// sufficiently random. /// /// # Examples /// + /// Creating a UUID using the current system timestamp: + /// /// ``` - /// # use uuid::{Builder, Variant, Version}; + /// # 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, 195, 90, - /// # 145, 63, 62, + /// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204, /// # ]; + /// let ts = Timestamp::now(NoContext); /// let random_bytes = rng(); - /// let uuid = Builder::from_random_bytes(random_bytes).into_uuid(); /// - /// assert_eq!(Some(Version::Random), uuid.get_version()); + /// let uuid = Builder::from_timestamp_millis(ts, &random_bytes).into_uuid(); + /// + /// assert_eq!(Some(Version::SortRand), uuid.get_version()); /// assert_eq!(Variant::RFC4122, uuid.get_variant()); + /// # Ok(()) + /// # } /// ``` - pub const fn from_random_bytes(b: Bytes) -> Self { - Builder(Uuid::from_bytes(b)) - .with_variant(Variant::RFC4122) - .with_version(Version::Random) - } + 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; - /// 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)) - .with_variant(Variant::RFC4122) - .with_version(Version::Md5) + 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) } - /// Creates a `Builder` for a version 5 UUID using the supplied SHA1 hashed bytes. - pub const fn from_sha1_bytes(b: Bytes) -> Self { - Builder(Uuid::from_bytes(b)) + /// 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) .with_variant(Variant::RFC4122) - .with_version(Version::Sha1) + .with_version(Version::Custom) } /// Creates a `Builder` using the supplied bytes. diff --git a/src/lib.rs b/src/lib.rs index 2914f322..f3daa286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ //! * `v3` - adds the [`Uuid::new_v3`] function and the ability to create a V3 //! UUID based on the MD5 hash of some data. //! * `v4` - adds the [`Uuid::new_v4`] function and the ability to randomly -//! generate a UUID. +//! generate a V4 UUID. //! * `v5` - adds the [`Uuid::new_v5`] function and the ability to create a V5 //! UUID based on the SHA1 hash of some data. //! * `v6` - adds the [`Uuid::new_v6`] function and the ability to create a V6 @@ -90,6 +90,9 @@ //! * `v8` - adds the [`Uuid::new_v8`] function and the ability to create a V8 //! UUID using user-defined data. //! +//! This library also includes a [`Builder`] type that can be used to help construct UUIDs of any +//! version without any additional dependencies or features. +//! //! # Other features //! //! Other crate features can also be useful beyond the version support: @@ -231,8 +234,9 @@ pub use timestamp::{ClockSequence, Timestamp, context::NoContext}; pub use timestamp::context::Context; #[cfg(feature = "v1")] -#[deprecated(note = "use items directly from the crate root")] #[doc(hidden)] +// Soft-deprecated (Rust doesn't support deprecating re-exports) +// Use `Context` from the crate root instead pub mod v1; #[cfg(feature = "v3")] mod v3; diff --git a/src/v1.rs b/src/v1.rs index 403d2241..7cba0fca 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -6,11 +6,6 @@ use crate::timestamp::Timestamp; use crate::{Builder, Uuid}; -/// 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`. -const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; - -#[deprecated(note = "use `Context` from the crate root")] pub use crate::timestamp::context::Context; impl Uuid { @@ -85,14 +80,11 @@ impl Uuid { /// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`Context`]: v1/struct.Context.html pub fn new_v1(ts: Timestamp, node_id: &[u8; 6]) -> Self { - let (ticks, counter) = ts.to_rfc4122(); - - Builder::from_rfc4122_timestamp(ticks, counter, node_id).into_uuid() + Builder::from_rfc4122_timestamp(ts, node_id).into_uuid() } } #[cfg(test)] -#[allow(deprecated)] mod tests { use super::*; @@ -102,7 +94,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_new_v1() { + fn test_new() { let time: u64 = 1_496_854_535; let time_fraction: u32 = 812_946_000; let node = [1, 2, 3, 4, 5, 6]; @@ -132,7 +124,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_new_v1_context() { + 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]; diff --git a/src/v4.rs b/src/v4.rs index 7aafbfc8..9c7029f6 100644 --- a/src/v4.rs +++ b/src/v4.rs @@ -35,7 +35,6 @@ impl Uuid { mod tests { use super::*; use crate::{Variant, Version}; - use std::string::ToString; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/src/v6.rs b/src/v6.rs index bd5321e6..056d9318 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -75,9 +75,7 @@ impl Uuid { /// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`Context`]: v1/struct.Context.html pub fn new_v6(ts: Timestamp, node_id: &[u8; 6]) -> Self { - let (ticks, counter) = ts.to_rfc4122(); - - Builder::from_sorted_rfc4122_timestamp(ticks, counter, node_id).into_uuid() + Builder::from_sorted_rfc4122_timestamp(ts, node_id).into_uuid() } } diff --git a/src/v7.rs b/src/v7.rs index 9d5d729b..92aeedf9 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -3,14 +3,22 @@ //! Note that you need to enable the `v7` Cargo feature //! in order to use this module. -use crate::{Builder, rng}; -use crate::timestamp::Timestamp; -use crate::Uuid; +use crate::{Builder, NoContext, rng, timestamp::Timestamp, Uuid}; use core::convert::TryInto; -use core::time::Duration; impl Uuid { - /// Create a new UUID (version 7) using a time value + random number. + /// Create a new UUID (version 7) using the current time value and random bytes. + /// + /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time + /// as the source timestamp. + #[cfg(feature = "std")] + pub fn now_v7() -> Self { + Self::new_v7(Timestamp::now(NoContext)) + } + + /// Create a new UUID (version 7) using a time value and random bytes. + /// + /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`]. /// /// Note that usage of this method requires the `v7` feature of this crate /// to be enabled. @@ -37,15 +45,15 @@ impl Uuid { /// The timestamp can also be created automatically from the current SystemTime /// /// ``` - /// let ts = Timestamp::now(); + /// # use uuid::{Uuid, Timestamp, NoContext}; + /// let ts = Timestamp::now(NoContext); /// /// let uuid = Uuid::new_v7(ts); /// ``` pub fn new_v7(ts: Timestamp) -> Self { - let duration = Duration::new(ts.seconds, ts.nanos); let buf: &[u8] = &rng::bytes()[0..11]; - Builder::from_timestamp_millis(duration, buf.try_into().unwrap()).into_uuid() + Builder::from_timestamp_millis(ts, buf.try_into().unwrap()).into_uuid() } } diff --git a/src/v8.rs b/src/v8.rs index d2d4b368..647a6f3f 100644 --- a/src/v8.rs +++ b/src/v8.rs @@ -1,4 +1,4 @@ -use crate::{Builder, Uuid, Variant, Version}; +use crate::{Builder, Uuid}; impl Uuid { /// Creates a custom UUID comprised almost entirely of user-supplied bytes @@ -26,6 +26,7 @@ impl Uuid { #[cfg(test)] mod tests { use super::*; + use crate::{Version, Variant}; use std::string::ToString; #[cfg(target_arch = "wasm32")] From fbf463fdd1f24b4897bbbafdd8014df922b038d2 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Wed, 5 Oct 2022 17:51:45 +0200 Subject: [PATCH 06/10] reorganize time code a bit --- src/builder.rs | 99 +++++++-------------- src/lib.rs | 66 ++++---------- src/timestamp.rs | 218 +++++++++++++++++++++++------------------------ src/v1.rs | 68 ++++++++++++--- src/v6.rs | 57 ++++++++++++- src/v7.rs | 73 ++++++++++++---- src/v8.rs | 5 +- 7 files changed, 322 insertions(+), 264 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 5bce22da..4d24d7d4 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 f3daa286..c36d2b0b 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 counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); - - Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter)) + let (ticks, counter) = v1::decode_rfc4122_timestamp(self); + + 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 counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); - - Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter)) + let (ticks, counter) = v6::decode_sorted_rfc4122_timestamp(self); + + 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 1d3e7157..4660bca7 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -1,11 +1,20 @@ //! Generating UUIDs from timestamps. - -/// 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`. +//! +//! 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 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,81 +94,77 @@ 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) } - #[deprecated(note = "use `to_unix` instead")] - #[doc(hidden)] - 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 - // part of the timestamp. - // - // We can't fix the behavior because the return type is too small to fit - // 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; + 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, ) } + + #[deprecated(note = "use `to_unix` instead")] + /// 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 + // part of the timestamp. + // + // We can't fix the behavior because the return type is too small to fit + // a useful value for nanoseconds since the epoch. + self.nanos + } } -/// 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 7cba0fca..08db8ee0 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 056d9318..639e4db0 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 92aeedf9..60ffb631 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 647a6f3f..a67dfcb9 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)); From c31ddafb29775b23a1f78c7f87793e792536276e Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Thu, 6 Oct 2022 02:40:40 +1000 Subject: [PATCH 07/10] more work on docs --- src/{external/mod.rs => external.rs} | 0 src/lib.rs | 55 ++++++++++++++++------------ src/timestamp.rs | 35 ++++++++++++++---- src/v1.rs | 8 ++-- src/v3.rs | 4 ++ src/v4.rs | 4 ++ src/v5.rs | 4 ++ src/v6.rs | 11 ++---- src/v7.rs | 4 ++ src/v8.rs | 4 ++ 10 files changed, 87 insertions(+), 42 deletions(-) rename src/{external/mod.rs => external.rs} (100%) diff --git a/src/external/mod.rs b/src/external.rs similarity index 100% rename from src/external/mod.rs rename to src/external.rs diff --git a/src/lib.rs b/src/lib.rs index c36d2b0b..3f88b5b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,9 @@ //! practical purposes, it can be assumed that an unintentional collision would //! be extremely unlikely. //! +//! UUIDs have a number of standardized encodings that are specified in [RFC4122](http://tools.ietf.org/html/rfc4122), +//! with recent additions [in draft](https://github.com/uuid6/uuid6-ietf-draft). +//! //! # Getting started //! //! Add the following to your `Cargo.toml`: @@ -67,32 +70,29 @@ //! //! # Working with different UUID versions //! -//! There are a number of approaches to generating and interpreting UUIDs that are specified -//! as _versions_ in [RFC4122](http://tools.ietf.org/html/rfc4122). This library supports all of them, -//! in addition to some newer versions in [a draft RFC](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04). +//! This library supports all standardized methods for generating UUIDs through individual Cargo features. //! //! By default, this crate depends on nothing but the Rust standard library and can parse and format -//! UUIDs, but cannot generate them. You need to enable the following Cargo -//! features to enable various pieces of functionality: +//! UUIDs, but cannot generate them. Depending on the kind of UUID you'd like to work with, there +//! are Cargo features that enable generating them: //! -//! * `v1` - adds the [`Uuid::new_v1`] function and the ability to create a V1 -//! UUID using a timestamp and monotonic counter. -//! * `v3` - adds the [`Uuid::new_v3`] function and the ability to create a V3 -//! UUID based on the MD5 hash of some data. -//! * `v4` - adds the [`Uuid::new_v4`] function and the ability to randomly -//! generate a V4 UUID. -//! * `v5` - adds the [`Uuid::new_v5`] function and the ability to create a V5 -//! UUID based on the SHA1 hash of some data. -//! * `v6` - adds the [`Uuid::new_v6`] function and the ability to create a V6 -//! UUID using a timestamp and monotonic counter. -//! * `v7` - adds the [`Uuid::new_v7`] function and the ability to create a V7 -//! UUID using a timestamp. -//! * `v8` - adds the [`Uuid::new_v8`] function and the ability to create a V8 -//! UUID using user-defined data. +//! * `v1` - Version 1 UUIDs using a timestamp and monotonic counter. +//! * `v3` - Version 3 UUIDs based on the MD5 hash of some data. +//! * `v4` - Version 4 UUIDs with random data. +//! * `v5` - Version 5 UUIDs based on the SHA1 hash of some data. +//! * `v6` - Version 6 UUIDs using a timestamp and monotonic counter. +//! * `v7` - Version 7 UUIDs using a Unix timestamp. +//! * `v8` - Version 8 UUIDs using user-defined data. //! //! This library also includes a [`Builder`] type that can be used to help construct UUIDs of any //! version without any additional dependencies or features. //! +//! ## Which UUID version should I use? +//! +//! If you just want to generate unique identifiers then consider version 4 (`v4`) UUIDs. If you want +//! to use UUIDs as database keys or need to sort them then consider version 7 (`v7`) UUIDs. +//! Other versions should generally be avoided unless there's an existing need for them. +//! //! # Other features //! //! Other crate features can also be useful beyond the version support: @@ -193,6 +193,7 @@ //! //! * [Wikipedia: Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) //! * [RFC4122: A Universally Unique Identifier (UUID) URN Namespace](http://tools.ietf.org/html/rfc4122) +//! * [Draft RFC: New UUID Formats](https://github.com/uuid6/uuid6-ietf-draft) //! //! [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen //! [`cargo-web`]: https://crates.io/crates/cargo-web @@ -286,9 +287,9 @@ pub type Bytes = [u8; 16]; #[derive(Clone, Copy, Debug, PartialEq)] #[non_exhaustive] pub enum Version { - /// Special case for `nil` UUID. + /// The _nil_ (all zeros) UUID. Nil = 0, - /// Version 1: MAC address. + /// Version 1: Timestamp and node ID. Mac, /// Version 2: DCE Security. Dce, @@ -298,11 +299,11 @@ pub enum Version { Random, /// Version 5: SHA-1 hash. Sha1, - /// Version 6: Sortable MAC/Node-ID + /// Version 6: Sortable Timestamp and node ID. SortMac, - /// Version 7: Timestamp + Random + /// Version 7: Timestamp and random. SortRand, - /// Version 8: Custom + /// Version 8: Custom. Custom, } @@ -883,6 +884,12 @@ impl Uuid { /// [`Timestamp`] offers several options for converting the raw RFC4122 /// value into more commonly-used formats, such as a unix timestamp. /// + /// # Roundtripping + /// + /// This method is unlikely to roundtrip a timestamp in a UUID due to the way + /// UUIDs encode timestamps. The timestamp returned from this method will be truncated to + /// 100ns precision for version 1 and 6 UUIDs, and to millisecond precision for version 7 UUIDs. + /// /// [`Timestamp`]: v1/struct.Timestamp.html pub const fn get_timestamp(&self) -> Option { match self.get_version() { diff --git a/src/timestamp.rs b/src/timestamp.rs index 4660bca7..f2f6fc86 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -5,6 +5,19 @@ //! 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. +//! +//! # Timestamp representations in UUIDs +//! +//! Versions 1 and 6 UUIDs use a bespoke timestamp that consists of the +//! number of 100ns ticks since `1582-10-15 00:00:00`, along with +//! a counter value to avoid duplicates. +//! +//! Version 7 UUIDs use a more standard timestamp that consists of the +//! number of millisecond ticks since the Unix epoch (`1970-01-01 00:00:00`). +//! +//! # References +//! +//! * [Timestamp in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4) /// 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`). @@ -12,9 +25,14 @@ pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; /// 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 +/// This type abstracts the specific encoding, so versions 1, 6, and 7 +/// UUIDs can both be supported through the same type, even /// though they have a different representation of a timestamp. +/// +/// # References +/// +/// * [Timestamp in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4) +/// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Timestamp { pub(crate) seconds: u64, @@ -27,6 +45,10 @@ impl Timestamp { /// Get a timestamp representing the current system time. /// /// This method defers to the standard library's `SystemTime` type. + /// + /// # Panics + /// + /// 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")))] @@ -47,7 +69,7 @@ impl Timestamp { } /// Construct a `Timestamp` from an RFC4122 timestamp and counter, as used - /// in version 1 and version 6 UUIDs. + /// in versions 1 and 6 UUIDs. pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { #[cfg(not(any(feature = "v1", feature = "v6")))] { @@ -64,7 +86,7 @@ impl Timestamp { } } - /// Construct a `Timestamp` from a Unix timestamp. + /// Construct a `Timestamp` from a Unix timestamp, as used in version 7 UUIDs. pub fn from_unix(context: impl ClockSequence, seconds: u64, nanos: u32) -> Self { #[cfg(not(any(feature = "v1", feature = "v6")))] { @@ -85,7 +107,7 @@ impl Timestamp { } /// Get the value of the timestamp as an RFC4122 timestamp and counter, - /// as used in version 1 and version 6 UUIDs. + /// as used in versions 1 and 6 UUIDs. #[cfg(any(feature = "v1", feature = "v6"))] pub const fn to_rfc4122(&self) -> (u64, u16) { ( @@ -94,8 +116,7 @@ impl Timestamp { ) } - /// Get the value of the timestamp as a Unix timestamp, consisting of the - /// number of whole and fractional seconds. + /// Get the value of the timestamp as a Unix timestamp, as used in version 7 UUIDs. pub const fn to_unix(&self) -> (u64, u32) { (self.seconds, self.nanos) } diff --git a/src/v1.rs b/src/v1.rs index 08db8ee0..6729aeb8 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -3,9 +3,7 @@ //! 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}; +use crate::{timestamp::context::shared_context, Builder, Timestamp, Uuid}; pub use crate::timestamp::context::Context; @@ -84,6 +82,10 @@ impl Uuid { /// let _uuid = Uuid::new_v1(ts, &[1, 2, 3, 4, 5, 6]); /// ``` /// + /// # References + /// + /// * [Version 1 UUIDs in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.2) + /// /// [`Timestamp`]: v1/struct.Timestamp.html /// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`Context`]: v1/struct.Context.html diff --git a/src/v3.rs b/src/v3.rs index bfe9d597..cc7cb9ac 100644 --- a/src/v3.rs +++ b/src/v3.rs @@ -25,6 +25,10 @@ impl Uuid { /// assert_eq!(Some(Version::Md5), uuid.get_version()); /// ``` /// + /// # References + /// + /// * [Version 3 and 5 UUIDs in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.3) + /// /// [`NAMESPACE_DNS`]: #associatedconstant.NAMESPACE_DNS /// [`NAMESPACE_OID`]: #associatedconstant.NAMESPACE_OID /// [`NAMESPACE_URL`]: #associatedconstant.NAMESPACE_URL diff --git a/src/v4.rs b/src/v4.rs index 9c7029f6..e78fb1e6 100644 --- a/src/v4.rs +++ b/src/v4.rs @@ -24,6 +24,10 @@ impl Uuid { /// assert_eq!(Some(Version::Random), uuid.get_version()); /// ``` /// + /// # References + /// + /// * [Version 4 UUIDs in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.4) + /// /// [`getrandom`]: https://crates.io/crates/getrandom /// [from_random_bytes]: struct.Builder.html#method.from_random_bytes pub fn new_v4() -> Uuid { diff --git a/src/v5.rs b/src/v5.rs index 0edbc685..c3dd4479 100644 --- a/src/v5.rs +++ b/src/v5.rs @@ -24,6 +24,10 @@ impl Uuid { /// assert_eq!(Some(Version::Sha1), uuid.get_version()); /// ``` /// + /// # References + /// + /// * [Version 3 and 5 UUIDs in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.3) + /// /// [`NAMESPACE_DNS`]: struct.Uuid.html#associatedconst.NAMESPACE_DNS /// [`NAMESPACE_OID`]: struct.Uuid.html#associatedconst.NAMESPACE_OID /// [`NAMESPACE_URL`]: struct.Uuid.html#associatedconst.NAMESPACE_URL diff --git a/src/v6.rs b/src/v6.rs index 639e4db0..ba610609 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -3,9 +3,7 @@ //! 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}; +use crate::{timestamp::context::shared_context, Builder, Timestamp, Uuid}; impl Uuid { /// Create a new UUID (version 6) using the current time value and a node id. @@ -74,13 +72,10 @@ impl Uuid { /// "fd64c041-1e91-6551-802a-010203040506" /// ); /// ``` - /// The timestamp can also be created automatically from the current SystemTime /// - /// # use uuid::{Uuid, Timestamp, Context}; - /// let context = Context::new(42); - /// let ts = Timestamp::from_rfc4122(14976241191231231313); + /// # References /// - /// let uuid = Uuid::new_v6(ts, &context, &[1, 2, 3, 4, 5, 6]); + /// * [Version 6 UUIDs in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.1) /// /// [`Timestamp`]: v1/struct.Timestamp.html /// [`ClockSequence`]: v1/trait.ClockSequence.html diff --git a/src/v7.rs b/src/v7.rs index 60ffb631..7feba546 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -38,6 +38,10 @@ impl Uuid { /// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7") /// ); /// ``` + /// + /// # References + /// + /// * [Version 7 UUIDs in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2) pub fn new_v7(ts: Timestamp) -> Self { let (secs, nanos) = ts.to_unix(); let millis = secs.saturating_add(nanos as u64 / 1_000_000); diff --git a/src/v8.rs b/src/v8.rs index a67dfcb9..d3c64bdb 100644 --- a/src/v8.rs +++ b/src/v8.rs @@ -18,6 +18,10 @@ impl Uuid { /// /// assert_eq!(Some(Version::Custom), uuid.get_version()); /// ``` + /// + /// # References + /// + /// * [Version 8 UUIDs in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.3) pub fn new_v8(buf: [u8; 16]) -> Uuid { Builder::from_custom_bytes(buf).into_uuid() } From c60dd9404a5b9d80aea83aaa1cccbd3913d5c1c7 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Thu, 6 Oct 2022 03:13:11 +1000 Subject: [PATCH 08/10] shift some more code around --- examples/sortable_uuid.rs | 2 +- src/builder.rs | 39 +++++++------ src/lib.rs | 11 ++-- src/rng.rs | 2 +- src/timestamp.rs | 114 ++++++++++++++++++++++++++++++++++++++ src/v1.rs | 54 ++---------------- src/v6.rs | 49 ++-------------- src/v7.rs | 58 +++++-------------- 8 files changed, 169 insertions(+), 160 deletions(-) diff --git a/examples/sortable_uuid.rs b/examples/sortable_uuid.rs index af57a6c7..dc128125 100644 --- a/examples/sortable_uuid.rs +++ b/examples/sortable_uuid.rs @@ -7,7 +7,7 @@ fn generate_sortable_uuid() { use uuid::Uuid; - let uuid = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)); + let uuid = Uuid::now_v7(); assert_eq!(Some(uuid::Version::SortRand), uuid.get_version()); } diff --git a/src/builder.rs b/src/builder.rs index 4d24d7d4..53e4c3fa 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -13,7 +13,7 @@ //! //! [`Uuid`]: ../struct.Uuid.html -use crate::{error::*, Bytes, Uuid, Variant, Version}; +use crate::{error::*, timestamp, Bytes, Uuid, Variant, Version}; /// A builder struct for creating a UUID. /// @@ -43,12 +43,14 @@ use crate::{error::*, Bytes, Uuid, Variant, Version}; pub struct Builder(Uuid); impl Uuid { - /// The 'nil UUID'. + /// The 'nil UUID' (all zeros). /// /// The nil UUID is a special form of UUID that is specified to have all - /// 128 bits set to zero, as defined in [IETF RFC 4122 Section 4.1.7][RFC]. + /// 128 bits set to zero. /// - /// [RFC]: https://tools.ietf.org/html/rfc4122.html#section-4.1.7 + /// # References + /// + /// * [Nil UUID in RFC4122]: https://tools.ietf.org/html/rfc4122.html#section-4.1.7 /// /// # Examples /// @@ -67,12 +69,14 @@ impl Uuid { Uuid::from_bytes([0; 16]) } - /// The 'max UUID'. + /// The 'max UUID' (all ones). /// /// The max UUID is a special form of UUID that is specified to have all - /// 128 bits set to one, as defined in [IETF RFC 4122 Update Section 5.4][Draft RFC]. + /// 128 bits set to one. + /// + /// # References /// - /// [Draft RFC]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#page-12 + /// * [Max UUID in Draft RFC: New UUID Formats, Version 4]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.4 /// /// # Examples /// @@ -141,6 +145,8 @@ impl Uuid { /// /// # Examples /// + /// Basic usage: + /// /// ``` /// # use uuid::Uuid; /// let d1 = 0xa1a2a3a4; @@ -539,7 +545,7 @@ impl Builder { /// Creates a `Builder` for a version 1 UUID using the supplied timestamp and node id. 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)) + Builder(timestamp::encode_rfc4122_timestamp(ticks, counter, node_id)) } /// Creates a `Builder` for a version 3 UUID using the supplied MD5 hashed bytes. @@ -574,9 +580,9 @@ impl Builder { .with_version(Version::Random) } - /// Creates a `Builder` for a version 5 UUID using the supplied SHA1 hashed bytes. + /// Creates a `Builder` for a version 5 UUID using the supplied SHA-1 hashed bytes. /// - /// This method assumes the bytes are already a SHA1 hash, it will only set the appropriate + /// This method assumes the bytes are already a SHA-1 hash, it will only set the appropriate /// bits for the UUID version and variant. pub const fn from_sha1_bytes(sha1_bytes: Bytes) -> Self { Builder(Uuid::from_bytes(sha1_bytes)) @@ -592,22 +598,21 @@ impl Builder { counter: u16, node_id: &[u8; 6], ) -> Self { - Builder(crate::v6::encode_sorted_rfc4122_timestamp( + Builder(timestamp::encode_sorted_rfc4122_timestamp( ticks, counter, node_id, )) } - /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp. + /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp and random bytes. /// - /// This method will encode the duration since the Unix epoch at millisecond precision, filling - /// the rest with data from the given random bytes. This method assumes the bytes are already - /// sufficiently random. + /// This method assumes the bytes are already sufficiently random. /// /// # Examples /// /// Creating a UUID using the current system timestamp: /// /// ``` + /// # use std::convert::TryInto; /// use std::time::{Duration, SystemTime}; /// # fn main() -> Result<(), Box> { /// # use uuid::{Builder, Uuid, Variant, Version, Timestamp, NoContext}; @@ -618,7 +623,7 @@ impl Builder { /// /// let random_bytes = rng(); /// - /// let uuid = Builder::from_unix_timestamp(ts.as_secs(), ts.subsec_millis(), &random_bytes).into_uuid(); + /// let uuid = Builder::from_unix_timestamp(ts.as_millis().try_into()?, &random_bytes).into_uuid(); /// /// assert_eq!(Some(Version::SortRand), uuid.get_version()); /// assert_eq!(Variant::RFC4122, uuid.get_variant()); @@ -626,7 +631,7 @@ impl Builder { /// # } /// ``` pub const fn from_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Self { - Builder(crate::v7::encode_unix_timestamp_millis( + Builder(timestamp::encode_unix_timestamp_millis( millis, random_bytes, )) diff --git a/src/lib.rs b/src/lib.rs index 3f88b5b3..1bea67ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! be extremely unlikely. //! //! UUIDs have a number of standardized encodings that are specified in [RFC4122](http://tools.ietf.org/html/rfc4122), -//! with recent additions [in draft](https://github.com/uuid6/uuid6-ietf-draft). +//! with recent additions [in draft](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04). //! //! # Getting started //! @@ -193,7 +193,7 @@ //! //! * [Wikipedia: Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) //! * [RFC4122: A Universally Unique Identifier (UUID) URN Namespace](http://tools.ietf.org/html/rfc4122) -//! * [Draft RFC: New UUID Formats](https://github.com/uuid6/uuid6-ietf-draft) +//! * [Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04) //! //! [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen //! [`cargo-web`]: https://crates.io/crates/cargo-web @@ -264,6 +264,7 @@ mod external; #[macro_use] mod macros; +extern crate alloc; #[doc(hidden)] #[cfg(feature = "macro-diagnostics")] pub extern crate private_uuid_macro_internal; @@ -894,17 +895,17 @@ impl Uuid { pub const fn get_timestamp(&self) -> Option { match self.get_version() { Some(Version::Mac) => { - let (ticks, counter) = v1::decode_rfc4122_timestamp(self); + let (ticks, counter) = timestamp::decode_rfc4122_timestamp(self); Some(Timestamp::from_rfc4122(ticks, counter)) } Some(Version::SortMac) => { - let (ticks, counter) = v6::decode_sorted_rfc4122_timestamp(self); + let (ticks, counter) = timestamp::decode_sorted_rfc4122_timestamp(self); Some(Timestamp::from_rfc4122(ticks, counter)) } Some(Version::SortRand) => { - let millis = v7::decode_unix_timestamp_millis(self); + let millis = timestamp::decode_unix_timestamp_millis(self); let seconds = millis / 1000; let nanos = ((millis % 1000) * 1_000_000) as u32; diff --git a/src/rng.rs b/src/rng.rs index 21a4af32..2df10307 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -18,7 +18,7 @@ pub(crate) fn bytes() -> [u8; 16] { } } -#[cfg(any(feature = "v1", feature = "v6", feature = "v7"))] +#[cfg(any(feature = "v1", feature = "v6"))] pub(crate) fn u16() -> u16 { #[cfg(not(feature = "fast-rng"))] { diff --git a/src/timestamp.rs b/src/timestamp.rs index f2f6fc86..532ba8ee 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -18,6 +18,9 @@ //! # References //! //! * [Timestamp in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4) +//! * [Timestamp in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-6.1) + +use crate::Uuid; /// 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`). @@ -32,6 +35,7 @@ pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; /// # References /// /// * [Timestamp in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4) +/// * [Timestamp in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-6.1) /// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Timestamp { @@ -152,6 +156,116 @@ impl Timestamp { } } +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) +} + +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) +} + +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 +} + /// A counter that can be used by version 1 and version 6 UUIDs to support /// the uniqueness of timestamps. /// diff --git a/src/v1.rs b/src/v1.rs index 6729aeb8..52f51116 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -8,9 +8,12 @@ use crate::{timestamp::context::shared_context, Builder, Timestamp, Uuid}; pub use crate::timestamp::context::Context; impl Uuid { - /// Create a new UUID (version 1) using the current system time and a node id. + /// Create a new version 1 UUID using the current system time and a node id. /// /// This method is only available if both the `std` and `rng` features are enabled. + /// + /// This method is a convenient alternative to [`Uuid::new_v1`] that uses the current system time + /// as the source timestamp. #[cfg(all(feature = "std", feature = "rng"))] pub fn now_v1(node_id: &[u8; 6]) -> Self { let ts = Timestamp::now(shared_context()); @@ -18,7 +21,7 @@ impl Uuid { Self::new_v1(ts, node_id) } - /// Create a new UUID (version 1) using the given timestamp and node id. + /// Create a new version 1 UUID 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 @@ -71,17 +74,6 @@ impl Uuid { /// ); /// ``` /// - /// The timestamp can also just use the current SystemTime - /// - /// ``` - /// # use uuid::{Timestamp, Context}; - /// # use uuid::Uuid; - /// let context = Context::new(42); - /// let ts = Timestamp::now(&context); - /// - /// let _uuid = Uuid::new_v1(ts, &[1, 2, 3, 4, 5, 6]); - /// ``` - /// /// # References /// /// * [Version 1 UUIDs in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.2) @@ -96,42 +88,6 @@ impl 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 ba610609..d2122f47 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -6,9 +6,12 @@ use crate::{timestamp::context::shared_context, Builder, Timestamp, Uuid}; impl Uuid { - /// Create a new UUID (version 6) using the current time value and a node id. + /// Create a new version 6 UUID using the current time value and a node id. /// /// This method is only available if the `std` feature is enabled. + /// + /// This method is a convenient alternative to [`Uuid::new_v6`] that uses the current system time + /// as the source timestamp. #[cfg(all(feature = "std", feature = "rng"))] pub fn now_v6(node_id: &[u8; 6]) -> Self { let ts = Timestamp::now(shared_context()); @@ -16,9 +19,9 @@ impl Uuid { Self::new_v6(ts, node_id) } - /// Create a new UUID (version 6) using a time value + sequence + + /// Create a new version 6 UUID using a time value + sequence + /// *NodeId*. - /// This is similar to UUIDv1, except that it is lexographically sortable by timestamp. + /// This is similar to UUIDv1, except that it is lexicographically sortable by timestamp. /// /// When generating [`Timestamp`]s using a [`ClockSequence`], this function /// is only guaranteed to produce unique values if the following conditions @@ -87,46 +90,6 @@ impl 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::*; diff --git a/src/v7.rs b/src/v7.rs index 7feba546..947a350a 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -7,7 +7,7 @@ use crate::{rng, timestamp::Timestamp, Builder, NoContext, Uuid}; use core::convert::TryInto; impl Uuid { - /// Create a new UUID (version 7) using the current time value and random bytes. + /// Create a new version 7 UUID using the current time value and random bytes. /// /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time /// as the source timestamp. @@ -16,13 +16,16 @@ impl Uuid { Self::new_v7(Timestamp::now(NoContext)) } - /// Create a new UUID (version 7) using a time value and random bytes. + /// Create a new version 7 UUID using a time value and random bytes. /// /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`]. /// /// Note that usage of this method requires the `v7` feature of this crate /// to be enabled. /// + /// Also see [`Uuid::now_v7`] for a convenient way to generate version 7 + /// UUIDs using the current system time. + /// /// # Examples /// /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit @@ -44,67 +47,34 @@ impl Uuid { /// * [Version 7 UUIDs in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2) pub fn new_v7(ts: Timestamp) -> Self { let (secs, nanos) = ts.to_unix(); - let millis = secs.saturating_add(nanos as u64 / 1_000_000); + 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() } } -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::{NoContext, Variant, Version}; - use std::string::ToString; + use crate::{std::string::ToString, NoContext, Variant, Version}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new_v7() { - let time: u64 = 1_496_854_535; - let time_fraction: u32 = 812_946_000; + let ts: u64 = 1645557742000; + + let seconds = ts / 1000; + let nanos = ((ts % 1000) * 1_000_000) as u32; - let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, time, time_fraction)); + let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos)); let uustr = uuid.hyphenated().to_string(); assert_eq!(uuid.get_version(), Some(Version::SortRand)); assert_eq!(uuid.get_variant(), Variant::RFC4122); - assert!(uustr.starts_with("015c837b-9e84-7")); + assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7")); // Ensure parsing the same UUID produces the same timestamp let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); @@ -116,7 +86,7 @@ mod tests { #[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 time_fraction: u32 = 812_000_000; let ts = Timestamp::from_unix(NoContext, time, time_fraction); From fba5975cefab56a20889c75f6201f001e7ddb51e Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Thu, 6 Oct 2022 04:05:38 +1000 Subject: [PATCH 09/10] fix up a doc test --- src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index 53e4c3fa..315512fa 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -623,7 +623,7 @@ impl Builder { /// /// let random_bytes = rng(); /// - /// let uuid = Builder::from_unix_timestamp(ts.as_millis().try_into()?, &random_bytes).into_uuid(); + /// let uuid = Builder::from_unix_timestamp_millis(ts.as_millis().try_into()?, &random_bytes).into_uuid(); /// /// assert_eq!(Some(Version::SortRand), uuid.get_version()); /// assert_eq!(Variant::RFC4122, uuid.get_variant()); From 56e589c653497e8fbb754325df0d918247f8c798 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Wed, 5 Oct 2022 20:14:59 +0200 Subject: [PATCH 10/10] fix up some imports --- src/v1.rs | 4 ++-- src/v6.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v1.rs b/src/v1.rs index 52f51116..3b05fd0c 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -3,7 +3,7 @@ //! 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, Builder, Timestamp, Uuid}; +use crate::{Builder, Timestamp, Uuid}; pub use crate::timestamp::context::Context; @@ -16,7 +16,7 @@ impl Uuid { /// as the source timestamp. #[cfg(all(feature = "std", feature = "rng"))] pub fn now_v1(node_id: &[u8; 6]) -> Self { - let ts = Timestamp::now(shared_context()); + let ts = Timestamp::now(crate::timestamp::context::shared_context()); Self::new_v1(ts, node_id) } diff --git a/src/v6.rs b/src/v6.rs index d2122f47..4689a925 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -3,7 +3,7 @@ //! Note that you need to enable the `v6` Cargo feature //! in order to use this module. -use crate::{timestamp::context::shared_context, Builder, Timestamp, Uuid}; +use crate::{Builder, Timestamp, Uuid}; impl Uuid { /// Create a new version 6 UUID using the current time value and a node id. @@ -14,7 +14,7 @@ impl Uuid { /// as the source timestamp. #[cfg(all(feature = "std", feature = "rng"))] pub fn now_v6(node_id: &[u8; 6]) -> Self { - let ts = Timestamp::now(shared_context()); + let ts = Timestamp::now(crate::timestamp::context::shared_context()); Self::new_v6(ts, node_id) }