diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22e3d34a..4a2be3c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,9 @@ name: Continuous integration +env: + VERSION_FEATURES: "v1 v3 v4 v5 v6 v7" + STABLE_DEP_FEATURES: "serde arbitrary" + on: pull_request: push: @@ -34,13 +38,12 @@ jobs: - nightly os: - macos-10.15 - - windows-2019 - ubuntu-20.04 - rust_target: + rust_target: - x86_64-gnu - x86_64-msvc - x86_64-apple-darwin - + steps: - name: Checkout repository uses: actions/checkout@v2 @@ -61,8 +64,11 @@ jobs: - name: Examples run: cargo test --all-features --examples - - name: Powerset - run: cargo hack test --feature-powerset --lib --optional-deps "serde arbitrary" --depth 3 + - name: Each version feature + run: cargo hack test --lib --each-feature --optional-deps $STABLE_DEP_FEATURES + + - name: All version features + run: cargo hack test --lib --each-feature --features "$VERSION_FEATURES" --optional-deps "$STABLE_DEP_FEATURES" msrv: name: "Tests / MSRV / OS: ${{ matrix.os }}" @@ -71,7 +77,6 @@ jobs: matrix: os: - macos-10.15 - - windows-2019 - ubuntu-20.04 steps: @@ -86,7 +91,7 @@ jobs: override: true - name: Version features - run: cargo test --features "v1 v3 v4 v5 serde" + run: cargo test --features "$VERSION_FEATURES $STABLE_DEP_FEATURES" wasm: name: Tests / WebAssembly @@ -102,18 +107,17 @@ jobs: run: wasm-pack test --node - name: Version features - run: wasm-pack test --node -- --features "js v1 v3 v4 v5" - + run: wasm-pack test --node -- --features "$VERION_FEATURES $STABLE_DEP_FEATURES js" + - name: Fast RNG run: wasm-pack test --node -- --features "js v4 fast-rng" - + mips: name: Tests / MIPS (Big Endian) runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: @@ -147,12 +151,11 @@ jobs: with: command: build args: -Z avoid-dev-deps --target thumbv6m-none-eabi --no-default-features - - name: Version features uses: actions-rs/cargo@v1 with: command: build - args: -Z avoid-dev-deps --target thumbv6m-none-eabi --no-default-features --features "v1 v3 v5 serde" + args: -Z avoid-dev-deps --target thumbv6m-none-eabi --no-default-features --features "v1 v3 v5 v6 serde" nodeps: name: Build / No deps @@ -174,4 +177,63 @@ jobs: run: cargo install cargo-hack - name: Powerset - run: cargo hack check --feature-powerset -Z avoid-dev-deps + run: cargo hack check --each-feature -Z avoid-dev-deps + win_tests: + name: "Tests / OS: Windows 2019 - ${{ matrix.channel }}-${{ matrix.rust_target }}" + runs-on: windows-2019 + env: + RUSTFLAGS: "--cfg uuid_unstable" + RUSTDOCFLAGS: "--cfg uuid_unstable" + strategy: + matrix: + channel: + - stable + - beta + - nightly + os: + - windows-2019 + rust_target: + - x86_64-gnu + - x86_64-msvc + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Rust Toolchain + uses: actions-rs/toolchain@v1 + with: + override: true + profile: minimal + toolchain: ${{ matrix.channel }}-${{ matrix.rust_target }} + + - name: Install cargo-hack + run: cargo install cargo-hack + + - name: Docs + run: cargo test --all-features --doc + + - name: Examples + run: cargo test --all-features --examples + + - name: Each version feature + run: cargo hack test --lib --each-feature --optional-deps $env:STABLE_DEP_FEATURES + + - name: All version features + run: cargo hack test --lib --each-feature --features "$env:VERSION_FEATURES" --optional-deps "$env:STABLE_DEP_FEATURES" + + win-msrv: + name: "Tests / MSRV / OS: Windows 2019" + runs-on: windows-2019 + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.57.0 + override: true + + - name: Version features + run: cargo test --features "$env:VERSION_FEATURES $env:STABLE_DEP_FEATURES" diff --git a/Cargo.toml b/Cargo.toml index 6b80bd45..de7ccb69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,12 +51,15 @@ repository = "uuid-rs/uuid" 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"] v3 = ["md5"] v4 = ["rng"] v5 = ["sha1"] - +v6 = ["private_atomic"] +v7 = ["rng"] +v8 = [] js = ["private_getrandom", "private_getrandom/js"] rng = ["private_getrandom"] @@ -79,12 +82,12 @@ version = "2" # Public: Used in trait impls on `Uuid` [dependencies.arbitrary] optional = true -version = "1" +version = "=1.1.3" # Public (unstable): Used in `zerocopy` derive # Unstable: also need RUSTFLAGS="--cfg uuid_unstable" to work # This feature may break between releases, or be removed entirely before -# stabilization. +# stabilization. # See: https://github.com/uuid-rs/uuid/issues/588 [dependencies.zerocopy] optional = true @@ -133,8 +136,6 @@ version = "1.1.2" path = "macros" optional = true -# Private -# Don't depend on this optional feature directly: it may change at any time [dependencies.private_atomic] package = "atomic" default-features = false diff --git a/src/builder.rs b/src/builder.rs index 6b2e05c5..bc628db7 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -67,6 +67,30 @@ impl Uuid { Uuid::from_bytes([0; 16]) } + /// The 'max UUID'. + /// + /// 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]. + /// + /// [Draft RFC]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#page-12 + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use uuid::Uuid; + /// let uuid = Uuid::max(); + /// + /// assert_eq!( + /// "ffffffff-ffff-ffff-ffff-ffffffffffff", + /// uuid.hyphenated().to_string(), + /// ); + /// ``` + pub const fn max() -> Self { + Uuid::from_bytes([0xFF; 16]) + } + /// Creates a UUID from four field values. /// /// # Examples @@ -283,7 +307,7 @@ impl Uuid { /// Basic usage: /// /// ``` - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// # use uuid::Uuid; /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, @@ -324,7 +348,7 @@ impl Uuid { /// Basic usage: /// /// ``` - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// # use uuid::Uuid; /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, @@ -359,7 +383,7 @@ impl Uuid { /// Basic usage: /// /// ``` - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// # use uuid::Uuid; /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, @@ -390,7 +414,7 @@ impl Uuid { /// Basic usage: /// /// ``` - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// # use uuid::Uuid; /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, @@ -422,7 +446,7 @@ impl Uuid { /// Basic usage: /// /// ``` - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// # use uuid::Uuid; /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, @@ -491,7 +515,7 @@ impl Builder { /// Basic usage: /// /// ``` - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// # use uuid::{Builder, Uuid}; /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, @@ -565,7 +589,7 @@ impl Builder { /// /// ``` /// # use uuid::Builder; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, /// 0xb1, 0xb2, @@ -600,7 +624,7 @@ impl Builder { /// /// ``` /// # use uuid::Builder; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let bytes = [ /// 0xa1, 0xa2, 0xa3, 0xa4, /// 0xb1, 0xb2, diff --git a/src/lib.rs b/src/lib.rs index 7aee4f78..940e842e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,7 +157,7 @@ //! //! ``` //! # use uuid::Uuid; -//! # fn main() -> Result<(), Box> { +//! # fn main() -> Result<(), uuid::Error> { //! let my_uuid = Uuid::parse_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8")?; //! //! println!("{}", my_uuid.urn()); @@ -220,9 +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 mod fmt; +pub use timestamp::{ClockSequence, Timestamp}; + +#[cfg(any(feature = "v1", feature = "v6"))] +pub use timestamp::context::Context; #[cfg(feature = "v1")] pub mod v1; @@ -232,6 +238,12 @@ mod v3; mod v4; #[cfg(feature = "v5")] mod v5; +#[cfg(feature = "v6")] +mod v6; +#[cfg(feature = "v7")] +pub mod v7; +#[cfg(feature = "v8")] +mod v8; #[cfg(feature = "md5")] mod md5; @@ -280,6 +292,12 @@ pub enum Version { Random, /// Version 5: SHA-1 hash. Sha1, + /// Version 6: Sortable MAC/Node-ID + SortMac, + /// Version 7: Timestamp + Random + SortRand, + /// Version 8: Custom + Custom, } /// The reserved variants of UUIDs. @@ -308,7 +326,7 @@ pub enum Variant { /// /// ``` /// # use uuid::Uuid; -/// # fn main() -> Result<(), Box> { +/// # fn main() -> Result<(), uuid::Error> { /// let my_uuid = Uuid::parse_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8")?; /// /// println!("{}", my_uuid.urn()); @@ -345,7 +363,7 @@ pub enum Variant { /// /// ``` /// # use uuid::Uuid; -/// # fn main() -> Result<(), Box> { +/// # fn main() -> Result<(), uuid::Error> { /// let my_uuid = Uuid::parse_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8")?; /// /// assert_eq!( @@ -360,7 +378,7 @@ pub enum Variant { /// /// ``` /// # use uuid::Uuid; -/// # fn main() -> Result<(), Box> { +/// # fn main() -> Result<(), uuid::Error> { /// let my_uuid = Uuid::parse_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8")?; /// /// assert_eq!( @@ -436,7 +454,7 @@ impl Uuid { /// /// ``` /// # use uuid::{Uuid, Variant}; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let my_uuid = Uuid::parse_str("02f09a3f-1624-3b1d-8409-44eff7708208")?; /// /// assert_eq!(Variant::RFC4122, my_uuid.get_variant()); @@ -471,7 +489,7 @@ impl Uuid { /// /// ``` /// # use uuid::Uuid; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let my_uuid = Uuid::parse_str("02f09a3f-1624-3b1d-8409-44eff7708208")?; /// /// assert_eq!(3, my_uuid.get_version_num()); @@ -501,7 +519,7 @@ impl Uuid { /// /// ``` /// # use uuid::{Uuid, Version}; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let my_uuid = Uuid::parse_str("02f09a3f-1624-3b1d-8409-44eff7708208")?; /// /// assert_eq!(Some(Version::Md5), my_uuid.get_version()); @@ -520,6 +538,9 @@ impl Uuid { 3 => Some(Version::Md5), 4 => Some(Version::Random), 5 => Some(Version::Sha1), + 6 => Some(Version::SortMac), + 7 => Some(Version::SortRand), + 8 => Some(Version::Custom), _ => None, } } @@ -548,7 +569,7 @@ impl Uuid { /// /// ``` /// # use uuid::Uuid; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::nil(); /// /// assert_eq!(uuid.as_fields(), (0, 0, 0, &[0u8; 8])); @@ -595,7 +616,7 @@ impl Uuid { /// ``` /// use uuid::Uuid; /// - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8")?; /// /// assert_eq!( @@ -632,7 +653,7 @@ impl Uuid { /// /// ``` /// # use uuid::Uuid; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8")?; /// /// assert_eq!( @@ -676,7 +697,7 @@ impl Uuid { /// /// ``` /// # use uuid::Uuid; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8")?; /// /// assert_eq!( @@ -715,7 +736,7 @@ impl Uuid { /// /// ``` /// # use uuid::Uuid; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8")?; /// assert_eq!( /// uuid.as_u64_pair(), @@ -789,7 +810,7 @@ impl Uuid { /// ``` /// use uuid::Uuid; /// - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8")?; /// /// assert_eq!( @@ -843,6 +864,78 @@ impl Uuid { pub const fn encode_buffer() -> [u8; fmt::Urn::LENGTH] { [0; fmt::Urn::LENGTH] } + + /// If the UUID is the correct version (v1, v6, or v7) this will return + /// the timestamp and counter portion parsed from a V1 UUID. + /// + /// Returns `None` if the supplied UUID is not V1. + /// + /// The V1 timestamp format defined in RFC4122 specifies a 60-bit + /// integer representing the number of 100-nanosecond intervals + /// since 00:00:00.00, 15 Oct 1582. + /// + /// [`Timestamp`] offers several options for converting the raw RFC4122 + /// value into more commonly-used formats, such as a unix timestamp. + /// + /// [`Timestamp`]: v1/struct.Timestamp.html + 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)) + } + 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)) + } + 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 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 }) + } + } + _ => None, + } + } } impl Default for Uuid { diff --git a/src/parser.rs b/src/parser.rs index ea21d780..bc5f8263 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -51,7 +51,7 @@ impl Uuid { /// /// ``` /// # use uuid::{Uuid, Version, Variant}; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?; /// /// assert_eq!(Some(Version::Random), uuid.get_version()); @@ -84,7 +84,7 @@ impl Uuid { /// /// ``` /// # use uuid::{Uuid, Version, Variant}; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000")?; /// /// assert_eq!(Some(Version::Random), uuid.get_version()); @@ -112,7 +112,7 @@ impl Uuid { /// /// ``` /// # use uuid::{Uuid, Version, Variant}; - /// # fn main() -> Result<(), Box> { + /// # fn main() -> Result<(), uuid::Error> { /// let uuid = Uuid::try_parse_ascii(b"550e8400-e29b-41d4-a716-446655440000")?; /// /// assert_eq!(Some(Version::Random), uuid.get_version()); diff --git a/src/rng.rs b/src/rng.rs index fa21b626..21a4af32 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "v4")] +#[cfg(any(feature = "v4", feature = "v7"))] pub(crate) fn bytes() -> [u8; 16] { #[cfg(not(feature = "fast-rng"))] { @@ -18,7 +18,7 @@ pub(crate) fn bytes() -> [u8; 16] { } } -#[cfg(feature = "v1")] +#[cfg(any(feature = "v1", feature = "v6", feature = "v7"))] pub(crate) fn u16() -> u16 { #[cfg(not(feature = "fast-rng"))] { diff --git a/src/timestamp.rs b/src/timestamp.rs new file mode 100644 index 00000000..609d7d68 --- /dev/null +++ b/src/timestamp.rs @@ -0,0 +1,233 @@ +/// Contains the `Timestamp` struct and `ClockSequence` traits +/// as well as an implementation of ClockSequence for Context (only for features v1 and v6) + +/// 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)] +pub struct Timestamp { + pub(crate) seconds: u64, + pub(crate) nanos: u32, + #[cfg(any(feature = "v1", feature = "v6"))] + pub(crate) counter: u16, +} + +impl Timestamp { + /// Construct a `Timestamp` from its raw component values: an RFC4122 + /// timestamp and counter. + /// + /// 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 + /// + /// 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, + } + } + #[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(), + } + } + + /// 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"); + Timestamp { + seconds: dur.as_secs(), + nanos: dur.subsec_nanos(), + 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. + #[cfg(any(feature = "v1", feature = "v6"))] + pub const fn to_rfc4122(&self) -> (u64, u16) { + ( + Self::unix_to_rfc4122_ticks(self.seconds, self.nanos), + self.counter, + ) + } + + /// Returns the timestamp converted to the seconds and fractional + /// nanoseconds since Jan 1 1970. + 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 + const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 { + let ticks = UUID_TICKS_BETWEEN_EPOCHS + + seconds * 10_000_000 + + nanos as u64 / 100; + + ticks + } + + /// convert rfc4122 ticks into unix-timestamp + const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) { + ( + (ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000, + ((ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100, + ) + } +} + +/// A trait that abstracts over generation of UUID v1 "Clock Sequence" values. +/// +/// # 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 + 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; +} + +impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { + type Output = T::Output; + fn generate_sequence( + &self, + seconds: u64, + subsec_nanos: u32, + ) -> Self::Output { + (**self).generate_sequence(seconds, subsec_nanos) + } +} + +/// For features v1 and v1, constructs a `Context` struct which implements the `ClockSequence` trait +#[cfg(any(feature = "v1", feature = "v6"))] +pub mod context { + use private_atomic::{Atomic, Ordering}; + /// A thread-safe, stateful context for the v1 generator to help ensure + /// process-wide uniqueness. + #[derive(Debug)] + pub struct Context { + count: Atomic, + } + + impl Context { + /// Creates a thread-safe, internally mutable context to help ensure + /// uniqueness. + /// + /// 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. + 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. + #[cfg(feature = "rng")] + pub fn new_random() -> Self { + Self { + count: Atomic::::new(crate::rng::u16()), + } + } + } + + impl super::ClockSequence for Context { + type Output = u16; + fn generate_sequence( + &self, + _seconds: u64, + _nanos: u32, + ) -> Self::Output { + // RFC4122 reserves 2 bits of the clock sequence so the actual + // maximum value is smaller than `u16::MAX`. Since we unconditionally + // increment the clock sequence we want to wrap once it becomes larger + // than what we can represent in a "u14". Otherwise there'd be patches + // where the clock sequence doesn't change regardless of the timestamp + self.count.fetch_add(1, Ordering::AcqRel) % (u16::MAX >> 2) + } + } +} diff --git a/src/v1.rs b/src/v1.rs index 94dd4c71..b166eecf 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -3,134 +3,15 @@ //! Note that you need to enable the `v1` Cargo feature //! in order to use this module. -use crate::{Uuid, Version}; - -use private_atomic::{Atomic, Ordering}; +use crate::timestamp::Timestamp; +use crate::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; -/// A thread-safe, stateful context for the v1 generator to help ensure -/// process-wide uniqueness. -#[derive(Debug)] -pub struct Context { - count: Atomic, -} - -/// Stores the number of nanoseconds from an epoch and a counter for ensuring -/// V1 ids generated on the same host are unique. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Timestamp { - ticks: u64, - counter: u16, -} - -impl Timestamp { - /// Construct a `Timestamp` from its raw component values: an RFC4122 - /// timestamp and counter. - /// - /// 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." - /// - /// The counter value is used to differentiate between ids generated by - /// the same host computer in rapid succession (i.e. with the same observed - /// time). See the [`ClockSequence`] trait for a generic interface to any - /// counter generators that might be used. - /// - /// Internally, the timestamp is stored as a `u64`. For this reason, dates - /// prior to October 1582 are not supported. - /// - /// [`ClockSequence`]: trait.ClockSequence.html - pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { - Timestamp { ticks, counter } - } - - /// Construct a `Timestamp` from a unix timestamp and sequence-generating - /// `context`. - /// - /// 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. - /// - /// This constructs a `Timestamp` from the seconds and fractional - /// nanoseconds of a unix timestamp, converting the duration since 1970 - /// into the number of 100-nanosecond intervals since 00:00:00.00, 15 - /// Oct 1582 specified by RFC4122 and used internally by `Timestamp`. - /// - /// The function is not guaranteed to produce monotonically increasing - /// values however. There is a slight possibility that two successive - /// equal time values could be supplied and the sequence counter wraps back - /// over to 0. - /// - /// If uniqueness and monotonicity is required, the user is responsible for - /// ensuring that the time value always increases between calls (including - /// between restarts of the process and device). - pub fn from_unix(context: impl ClockSequence, seconds: u64, subsec_nanos: u32) -> Self { - let counter = context.generate_sequence(seconds, subsec_nanos); - let ticks = - UUID_TICKS_BETWEEN_EPOCHS + seconds * 10_000_000 + u64::from(subsec_nanos) / 100; - - Timestamp { ticks, counter } - } - - /// Returns the raw RFC4122 timestamp and counter values stored by the - /// `Timestamp`. - /// - /// The timestamp (the first, `u64` element in the tuple) represents the - /// number of 100-nanosecond intervals since 00:00:00.00, 15 Oct 1582. - /// The counter is used to differentiate between ids generated on the - /// same host computer with the same observed time. - pub const fn to_rfc4122(&self) -> (u64, u16) { - (self.ticks, self.counter) - } - - /// Returns the timestamp converted to the seconds and fractional - /// nanoseconds since Jan 1 1970. - /// - /// Internally, the time is stored in 100-nanosecond intervals, - /// thus the maximum precision represented by the fractional nanoseconds - /// value is less than its unit size (100 ns vs. 1 ns). - pub const fn to_unix(&self) -> (u64, u32) { - ( - (self.ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000, - ((self.ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100, - ) - } - - /// Returns the timestamp converted into nanoseconds elapsed since Jan 1 - /// 1970. Internally, the time is stored in 100-nanosecond intervals, - /// thus the maximum precision represented is less than the units it is - /// measured in (100 ns vs. 1 ns). The value returned represents the - /// same duration as [`Timestamp::to_unix`]; this provides it in nanosecond - /// units for convenience. - pub const fn to_unix_nanos(&self) -> u64 { - (self.ticks - UUID_TICKS_BETWEEN_EPOCHS) * 100 - } -} - -/// A trait that abstracts over generation of UUID v1 "Clock Sequence" values. -/// -/// # References -/// -/// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) -pub trait ClockSequence { - /// Return a 16-bit 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) -> u16; -} - -impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { - fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> u16 { - (**self).generate_sequence(seconds, subsec_nanos) - } -} +/// 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 + @@ -160,7 +41,7 @@ impl Uuid { /// is seeded with a random value: /// /// ```rust - /// # use uuid::v1::{Timestamp, Context}; + /// # use uuid::{Timestamp, Context}; /// # use uuid::Uuid; /// # fn random_seed() -> u16 { 42 } /// let context = Context::new(random_seed()); @@ -177,31 +58,42 @@ impl Uuid { /// The timestamp can also be created manually as per RFC4122: /// /// ``` - /// # use uuid::v1::{Timestamp, Context}; - /// # use uuid::Uuid; + /// # use uuid::{Uuid, Timestamp, Context, ClockSequence}; /// let context = Context::new(42); - /// let ts = Timestamp::from_rfc4122(1497624119, 0); + /// let ts = Timestamp::from_rfc4122(14976234442241191232, context.generate_sequence(0, 0)); /// /// let uuid = Uuid::new_v1(ts, &[1, 2, 3, 4, 5, 6]); /// /// assert_eq!( /// uuid.hyphenated().to_string(), - /// "5943ee37-0000-1000-8000-010203040506" + /// "b2c1ad40-45e0-1fd6-802a-010203040506" /// ); /// ``` /// + /// The timestamp can also just use the current SystemTime + /// + /// ```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]); + /// ``` + /// /// [`Timestamp`]: v1/struct.Timestamp.html /// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`Context`]: v1/struct.Context.html - pub const fn new_v1(ts: Timestamp, node_id: &[u8; 6]) -> Self { - let time_low = (ts.ticks & 0xFFFF_FFFF) as u32; - let time_mid = ((ts.ticks >> 32) & 0xFFFF) as u16; - let time_high_and_version = (((ts.ticks >> 48) & 0x0FFF) as u16) | (1 << 12); + 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] = (((ts.counter & 0x3F00) >> 8) as u8) | 0x80; - d4[1] = (ts.counter & 0xFF) as u8; + 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]; @@ -211,96 +103,16 @@ impl Uuid { Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4) } - - /// Returns an optional [`Timestamp`] storing the timestamp and - /// counter portion parsed from a V1 UUID. - /// - /// Returns `None` if the supplied UUID is not V1. - /// - /// The V1 timestamp format defined in RFC4122 specifies a 60-bit - /// integer representing the number of 100-nanosecond intervals - /// since 00:00:00.00, 15 Oct 1582. - /// - /// [`Timestamp`] offers several options for converting the raw RFC4122 - /// value into more commonly-used formats, such as a unix timestamp. - /// - /// [`Timestamp`]: v1/struct.Timestamp.html - pub const fn get_timestamp(&self) -> Option { - match self.get_version() { - Some(Version::Mac) => { - let ticks: u64 = ((self.as_bytes()[6] & 0x0F) as u64) << 56 - | ((self.as_bytes()[7]) as u64) << 48 - | ((self.as_bytes()[4]) as u64) << 40 - | ((self.as_bytes()[5]) as u64) << 32 - | ((self.as_bytes()[0]) as u64) << 24 - | ((self.as_bytes()[1]) as u64) << 16 - | ((self.as_bytes()[2]) as u64) << 8 - | (self.as_bytes()[3] as u64); - - let counter: u16 = - ((self.as_bytes()[8] & 0x3F) as u16) << 8 | (self.as_bytes()[9] as u16); - - Some(Timestamp::from_rfc4122(ticks, counter)) - } - _ => None, - } - } -} - -impl Context { - /// Creates a thread-safe, internally mutable context to help ensure - /// uniqueness. - /// - /// 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. - 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. - #[cfg(feature = "rng")] - pub fn new_random() -> Self { - Self { - count: Atomic::new(crate::rng::u16()), - } - } -} - -impl ClockSequence for Context { - fn generate_sequence(&self, _: u64, _: u32) -> u16 { - // RFC4122 reserves 2 bits of the clock sequence so the actual - // maximum value is smaller than `u16::MAX`. Since we unconditionally - // increment the clock sequence we want to wrap once it becomes larger - // than what we can represent in a "u14". Otherwise there'd be patches - // where the clock sequence doesn't change regardless of the timestamp - self.count.fetch_add(1, Ordering::AcqRel) % (u16::MAX >> 2) - } } #[cfg(test)] mod tests { use super::*; + use crate::{std::string::ToString, Variant, Version}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - use crate::{std::string::ToString, Variant}; - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_new_v1() { diff --git a/src/v4.rs b/src/v4.rs index 093bc0e7..7aafbfc8 100644 --- a/src/v4.rs +++ b/src/v4.rs @@ -34,8 +34,8 @@ impl Uuid { #[cfg(test)] 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 new file mode 100644 index 00000000..e4b56271 --- /dev/null +++ b/src/v6.rs @@ -0,0 +1,167 @@ +//! The implementation for Version 1 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; + +impl Uuid { + /// 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. + /// + /// 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 + /// 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 `v6` feature of this crate + /// to be enabled. + /// + /// # Examples + /// + /// A UUID can be created from a unix [`Timestamp`] with a + /// [`ClockSequence`]. RFC4122 requires the clock sequence + /// is seeded with a random value: + /// + /// ```rust + /// # use uuid::{Uuid, Timestamp, Context}; + /// # fn random_seed() -> u16 { 42 } + /// let context = Context::new(random_seed()); + /// let ts = Timestamp::from_unix(context, 1497624119, 1234); + /// + /// let uuid = Uuid::new_v6(ts, &[1, 2, 3, 4, 5, 6]); + /// + /// assert_eq!( + /// uuid.hyphenated().to_string(), + /// "1e752a1f-3b49-658c-802a-010203040506" + /// ); + /// ``` + /// + /// The timestamp can also be created manually as per RFC4122: + /// + /// ``` + /// # use uuid::{Uuid, Timestamp, Context, ClockSequence}; + /// # fn random_seed() -> u16 { 42 } + /// let context = Context::new(random_seed()); + /// let ts = Timestamp::from_rfc4122(14976241191231231313, context.generate_sequence(0, 0) ); + /// + /// let uuid = Uuid::new_v6(ts, &[1, 2, 3, 4, 5, 6]); + /// + /// assert_eq!( + /// uuid.hyphenated().to_string(), + /// "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); + /// + /// let uuid = Uuid::new_v6(ts, &context, &[1, 2, 3, 4, 5, 6]); + /// + /// [`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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Variant, Version}; + use std::string::ToString; + + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_new_v6() { + let time: u64 = 1_496_854_535; + let time_fraction: u32 = 812_946_000; + let node = [1, 2, 3, 4, 5, 6]; + let context = Context::new(0); + + let uuid = Uuid::new_v6(Timestamp::from_unix(context, time, time_fraction), &node); + + assert_eq!(uuid.get_version(), Some(Version::SortMac)); + assert_eq!(uuid.get_variant(), Variant::RFC4122); + assert_eq!( + uuid.hyphenated().to_string(), + "1e74ba22-0616-6934-8000-010203040506" + ); + + let ts = uuid.get_timestamp().unwrap().to_rfc4122(); + + assert_eq!(ts.0 - 0x01B2_1DD2_1381_4000, 14_968_545_358_129_460); + + // Ensure parsing the same UUID produces the same timestamp + let parsed = Uuid::parse_str("1e74ba22-0616-6934-8000-010203040506").unwrap(); + + assert_eq!( + uuid.get_timestamp().unwrap(), + parsed.get_timestamp().unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_new_v6_context() { + let time: u64 = 1_496_854_535; + let time_fraction: u32 = 812_946_000; + let node = [1, 2, 3, 4, 5, 6]; + + // This context will wrap + let context = Context::new((u16::MAX >> 2) - 1); + + let uuid1 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); + + let time: u64 = 1_496_854_536; + + let uuid2 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); + + assert_eq!(uuid1.get_timestamp().unwrap().to_rfc4122().1, 16382); + assert_eq!(uuid2.get_timestamp().unwrap().to_rfc4122().1, 0); + + let time = 1_496_854_535; + + let uuid3 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); + let uuid4 = Uuid::new_v6(Timestamp::from_unix(&context, time, time_fraction), &node); + + assert_eq!(uuid3.get_timestamp().unwrap().counter, 1); + assert_eq!(uuid4.get_timestamp().unwrap().counter, 2); + } +} diff --git a/src/v7.rs b/src/v7.rs new file mode 100644 index 00000000..6f96fef8 --- /dev/null +++ b/src/v7.rs @@ -0,0 +1,91 @@ +//! The implementation for Version 1 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 core::convert::TryInto; + +impl Uuid { + /// 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. + /// + /// # 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); + /// + /// let uuid = Uuid::new_v7(ts); + /// + /// assert!( + /// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7") + /// ); + /// ``` + /// + /// 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) + } +} + +/// 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::*; + use crate::{Variant, Version}; + use std::string::ToString; + #[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 uuid = Uuid::new_v7(Timestamp::from_unix(NullSequence {}, time, time_fraction)); + 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")); + + // Ensure parsing the same UUID produces the same timestamp + let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); + + assert_eq!(uuid, parsed,); + } +} diff --git a/src/v8.rs b/src/v8.rs new file mode 100644 index 00000000..f6a0005a --- /dev/null +++ b/src/v8.rs @@ -0,0 +1,53 @@ +use crate::{Builder, Uuid, Variant, Version}; + +impl Uuid { + /// Creates a custom UUID comprised almost entirely of user-supplied bytes + /// + /// This will inject the UUID Version at 4 bits starting at the 48th bit + /// and the Variant into 2 bits 64th bit. + /// So if there are bits are supplied in the input buffer, they will not be + /// visible in the result + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use uuid::{Uuid, Version}; + /// let buf: [u8; 16] = *b"abcdefghijklmnop"; + /// let uuid = Uuid::new_v8(buf); + /// + /// 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() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::string::ToString; + + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; + + #[test] + #[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, + ]; + let uuid = Uuid::new_v8(buf); + assert_eq!(uuid.get_version(), Some(Version::Custom)); + assert_eq!(uuid.get_variant(), Variant::RFC4122); + assert_eq!(uuid.get_version_num(), 8); + assert_eq!( + uuid.hyphenated().to_string(), + "0f0e0d0c-0b0a-8908-8706-050403020100" + ); + } +}