diff --git a/Cargo.toml b/Cargo.toml index 1dd214f9..82ecc6e4 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"] @@ -55,8 +56,9 @@ v3 = ["md5"] v4 = ["rng"] v5 = ["sha1"] v6 = ["private_atomic"] -v7 = ["rng"] +v7 = ["private_atomic", "rng"] v8 = [] + js = ["private_getrandom", "private_getrandom/js"] rng = ["private_getrandom"] diff --git a/examples/sortable_uuid.rs b/examples/sortable_uuid.rs new file mode 100644 index 00000000..dc128125 --- /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::now_v7(); + + assert_eq!(Some(uuid::Version::SortRand), uuid.get_version()); +} + +fn main() {} diff --git a/src/builder.rs b/src/builder.rs index bc628db7..315512fa 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; @@ -537,11 +543,22 @@ 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 { + Builder(timestamp::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(md5_bytes: Bytes) -> Self { + Builder(Uuid::from_bytes(md5_bytes)) + .with_variant(Variant::RFC4122) + .with_version(Version::Md5) + } + + /// 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 - /// version and variant fields. + /// This method assumes the bytes are already sufficiently random, it will only + /// set the appropriate bits for the UUID version and variant. /// /// # Examples /// @@ -557,24 +574,77 @@ 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) } - /// Creates a `Builder` using the supplied MD5 hashed bytes. - pub const fn from_md5_bytes(b: Bytes) -> Self { - Builder(Uuid::from_bytes(b)) + /// Creates a `Builder` for a version 5 UUID using the supplied SHA-1 hashed bytes. + /// + /// 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)) .with_variant(Variant::RFC4122) - .with_version(Version::Md5) + .with_version(Version::Sha1) } - /// Creates a `Builder` 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 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( + ticks: u64, + counter: u16, + node_id: &[u8; 6], + ) -> Self { + Builder(timestamp::encode_sorted_rfc4122_timestamp( + ticks, counter, node_id, + )) + } + + /// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp and random bytes. + /// + /// 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}; + /// # let rng = || [ + /// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13 + /// # ]; + /// let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; + /// + /// let random_bytes = rng(); + /// + /// 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()); + /// # Ok(()) + /// # } + /// ``` + pub const fn from_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Self { + Builder(timestamp::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(custom_bytes: Bytes) -> Self { + Builder::from_bytes(custom_bytes) .with_variant(Variant::RFC4122) - .with_version(Version::Sha1) + .with_version(Version::Custom) } /// Creates a `Builder` using the supplied bytes. 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 940e842e..1bea67ee 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://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04). +//! //! # Getting started //! //! Add the following to your `Cargo.toml`: @@ -65,21 +68,32 @@ //! const ID: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); //! ``` //! -//! # Dependencies +//! # Working with different UUID versions +//! +//! 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. Depending on the kind of UUID you'd like to work with, there +//! are Cargo features that enable generating them: +//! +//! * `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. //! -//! By default, this crate depends on nothing but `std` and can parse and format -//! UUIDs, but cannot generate them. You need to enable the following Cargo -//! features to enable various pieces of functionality: +//! ## Which UUID version should I use? //! -//! * `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. -//! * `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. +//! 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: //! @@ -88,11 +102,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: @@ -114,8 +128,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] @@ -126,9 +139,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 @@ -142,10 +152,10 @@ //! //! Some additional features are supported in no-std environments: //! -//! * `v1`, `v3`, and `v5` -//! * `serde` +//! * `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`] @@ -153,7 +163,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; @@ -182,17 +192,11 @@ //! # 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) +//! * [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 -//! [`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] @@ -220,17 +224,20 @@ use zerocopy::{AsBytes, FromBytes, Unaligned}; mod builder; mod error; -pub mod fmt; mod parser; -/// contains the `Timestamp` struct and `ClockSequence` trait + +pub mod fmt; pub mod timestamp; -pub use timestamp::{ClockSequence, Timestamp}; +pub use timestamp::{context::NoContext, ClockSequence, Timestamp}; #[cfg(any(feature = "v1", feature = "v6"))] pub use timestamp::context::Context; #[cfg(feature = "v1")] +#[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; @@ -241,7 +248,7 @@ mod v5; #[cfg(feature = "v6")] mod v6; #[cfg(feature = "v7")] -pub mod v7; +mod v7; #[cfg(feature = "v8")] mod v8; @@ -257,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; @@ -280,9 +288,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, @@ -292,11 +300,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, } @@ -877,61 +885,37 @@ 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 { + 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) = timestamp::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) = timestamp::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 = timestamp::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, } @@ -1162,7 +1146,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/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 609d7d68..532ba8ee 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -1,11 +1,42 @@ -/// 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. +//! +//! 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. +//! +//! # 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) +//! * [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) -/// 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`. +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`). 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 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) +/// * [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 { pub(crate) seconds: u64, @@ -15,93 +46,72 @@ 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." - 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, - } - } - #[cfg(not(any(feature = "v1", feature = "v6")))] - { - Timestamp { seconds, nanos } - } - } - - /// Construct a `Timestamp` from a unix timestamp + /// This method defers to the standard library's `SystemTime` type. /// - /// 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 { - #[cfg(any(feature = "v1", feature = "v6"))] - { - let counter = _context.generate_sequence(seconds, nanos); - Timestamp { - seconds, - nanos, - counter, - } - } + /// # 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")))] { - Timestamp { seconds, nanos } + let _ = context; } - } - /// 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."); + .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 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"); + /// Construct a `Timestamp` from an RFC4122 timestamp and counter, as used + /// in versions 1 and 6 UUIDs. + pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { + #[cfg(not(any(feature = "v1", feature = "v6")))] + { + let _ = counter; + } + + let (seconds, nanos) = Self::rfc4122_to_unix(ticks); + Timestamp { - seconds: dur.as_secs(), - nanos: dur.subsec_nanos(), - counter: context - .generate_sequence(dur.as_secs(), dur.subsec_nanos()), + seconds, + nanos, + #[cfg(any(feature = "v1", feature = "v6"))] + counter, } } - /// 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. + /// 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")))] + { + let _ = context; + + Timestamp { seconds, nanos } + } + #[cfg(any(feature = "v1", feature = "v6"))] + { + let counter = context.generate_sequence(seconds, nanos); + + Timestamp { + seconds, + nanos, + counter, + } + } + } + + /// Get the value of the timestamp as an RFC4122 timestamp and counter, + /// as used in versions 1 and 6 UUIDs. #[cfg(any(feature = "v1", feature = "v6"))] pub const fn to_rfc4122(&self) -> (u64, u16) { ( @@ -110,103 +120,242 @@ 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, as used in version 7 UUIDs. pub const fn to_unix(&self) -> (u64, u32) { (self.seconds, self.nanos) } - /// Returns the timestamp converted into nanoseconds elapsed since Jan 1 - /// 1970. - pub const fn to_unix_nanos(&self) -> u32 { - 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 + } +} + +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 trait that abstracts over generation of UUID v1 "Clock Sequence" values. +/// 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 v1, constructs a `Context` struct which implements the `ClockSequence` trait -#[cfg(any(feature = "v1", feature = "v6"))] +/// Default implementations for the [`ClockSequence`] trait. pub mod context { + use super::ClockSequence; + + #[cfg(any(feature = "v1", feature = "v6"))] use private_atomic::{Atomic, Ordering}; - /// A thread-safe, stateful context for the v1 generator to help ensure - /// process-wide uniqueness. + + /// 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; + + impl ClockSequence for NoContext { + type Output = u16; + + fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { + 0 + } + } + + #[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 { count: Atomic, } + #[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 { @@ -215,13 +364,11 @@ pub mod context { } } - impl super::ClockSequence for Context { + #[cfg(any(feature = "v1", feature = "v6"))] + 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 b166eecf..3b05fd0c 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -1,36 +1,40 @@ //! 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::Timestamp; -use crate::Uuid; +use crate::{Builder, Timestamp, 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; - -/// The Context implementation is specific to Uuids v1 and v6 pub use crate::timestamp::context::Context; impl Uuid { - /// Create a new UUID (version 1) using a time value + sequence + - /// *NodeId*. + /// 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(crate::timestamp::context::shared_context()); + + Self::new_v1(ts, 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 /// 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. /// @@ -40,7 +44,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 } @@ -70,38 +74,17 @@ impl Uuid { /// ); /// ``` /// - /// The timestamp can also just use the current SystemTime + /// # References /// - /// ```rust - /// # 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]); - /// ``` + /// * [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 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() } } @@ -115,7 +98,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]; @@ -145,7 +128,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/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 7aafbfc8..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 { @@ -35,7 +39,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/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 e4b56271..4689a925 100644 --- a/src/v6.rs +++ b/src/v6.rs @@ -1,18 +1,27 @@ -//! 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. -use crate::timestamp::{ClockSequence, Timestamp}; -use crate::Uuid; - -/// The Context implementation is specific to Uuids v1 and v6 -pub use crate::timestamp::context::Context; +use crate::{Builder, Timestamp, Uuid}; impl Uuid { - /// Create a new UUID (version 6) using a time value + sequence + + /// 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(crate::timestamp::context::shared_context()); + + Self::new_v6(ts, node_id) + } + + /// 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 @@ -66,42 +75,25 @@ 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 /// [`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() } } #[cfg(test)] mod tests { use super::*; - use crate::{Variant, Version}; + use crate::{Context, Variant, Version}; use std::string::ToString; #[cfg(target_arch = "wasm32")] diff --git a/src/v7.rs b/src/v7.rs index 6f96fef8..947a350a 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -1,28 +1,39 @@ -//! 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::{rng, timestamp::Timestamp, Builder, NoContext, Uuid}; use core::convert::TryInto; impl Uuid { - /// Create a new UUID (version 7) using a time value + random number + /// 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. + #[cfg(feature = "std")] + pub fn now_v7() -> Self { + Self::new_v7(Timestamp::now(NoContext)) + } + + /// 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 /// 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); /// @@ -31,61 +42,58 @@ impl Uuid { /// ); /// ``` /// - /// The timestamp can also be created automatically from the current SystemTime - /// - /// let ts = Timestamp::now(); + /// # References /// - /// let uuid = Uuid::new_v7(ts); - /// - /// [`Timestamp`]: v1/struct.Timestamp.html + /// * [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 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) - } -} - -/// Dummy struct and ClockSequence implementation to ease the construction of v7 -/// using a Timestamp -#[derive(Debug)] -pub struct NullSequence {} + let (secs, nanos) = ts.to_unix(); + let millis = (secs * 1000).saturating_add(nanos as u64 / 1_000_000); -impl super::ClockSequence for NullSequence { - type Output = u16; - fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { - 0 + Builder::from_unix_timestamp_millis(millis, &rng::bytes()[..10].try_into().unwrap()) + .into_uuid() } } #[cfg(test)] mod tests { use super::*; - use crate::{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(NullSequence {}, 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(); - 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_000_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 f6a0005a..d3c64bdb 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 @@ -18,17 +18,19 @@ 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_bytes(buf) - .with_variant(Variant::RFC4122) - .with_version(Version::Custom) - .into_uuid() + Builder::from_custom_bytes(buf).into_uuid() } } #[cfg(test)] mod tests { use super::*; + use crate::{Variant, Version}; use std::string::ToString; #[cfg(target_arch = "wasm32")] @@ -38,8 +40,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));