reorganize time code a bit

This commit is contained in:
KodrAus 2022-10-05 17:51:45 +02:00
parent 9d445c7c94
commit fbf463fdd1
7 changed files with 321 additions and 263 deletions

View File

@ -13,8 +13,7 @@
//!
//! [`Uuid`]: ../struct.Uuid.html
use core::time::Duration;
use crate::{error::*, Bytes, Uuid, Variant, Version, Timestamp};
use crate::{error::*, Bytes, Uuid, Variant, Version};
/// A builder struct for creating a UUID.
///
@ -539,30 +538,13 @@ impl Builder {
}
/// Creates a `Builder` for a version 1 UUID using the supplied timestamp and node id.
pub const fn from_rfc4122_timestamp(ts: Timestamp, node_id: &[u8; 6]) -> Self {
let (ticks, counter) = ts.to_rfc4122();
let time_low = (ticks & 0xFFFF_FFFF) as u32;
let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
let time_high_and_version = (((ticks >> 48) & 0x0FFF) as u16) | (1 << 12);
let mut d4 = [0; 8];
d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80;
d4[1] = (counter & 0xFF) as u8;
d4[2] = node_id[0];
d4[3] = node_id[1];
d4[4] = node_id[2];
d4[5] = node_id[3];
d4[6] = node_id[4];
d4[7] = node_id[5];
Self::from_fields(time_low, time_mid, time_high_and_version, &d4)
pub const fn from_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Self {
Builder(crate::v1::encode_rfc4122_timestamp(ticks, counter, node_id))
}
/// Creates a `Builder` for a version 3 UUID using the supplied MD5 hashed bytes.
pub const fn from_md5_bytes(b: Bytes) -> Self {
Builder(Uuid::from_bytes(b))
pub const fn from_md5_bytes(md5_bytes: Bytes) -> Self {
Builder(Uuid::from_bytes(md5_bytes))
.with_variant(Variant::RFC4122)
.with_version(Version::Md5)
}
@ -586,8 +568,8 @@ impl Builder {
/// assert_eq!(Some(Version::Random), uuid.get_version());
/// assert_eq!(Variant::RFC4122, uuid.get_variant());
/// ```
pub const fn from_random_bytes(b: Bytes) -> Self {
Builder(Uuid::from_bytes(b))
pub const fn from_random_bytes(random_bytes: Bytes) -> Self {
Builder(Uuid::from_bytes(random_bytes))
.with_variant(Variant::RFC4122)
.with_version(Version::Random)
}
@ -596,8 +578,8 @@ impl Builder {
///
/// This method assumes the bytes are already a SHA1 hash, it will only set the appropriate
/// bits for the UUID version and variant.
pub const fn from_sha1_bytes(b: Bytes) -> Self {
Builder(Uuid::from_bytes(b))
pub const fn from_sha1_bytes(sha1_bytes: Bytes) -> Self {
Builder(Uuid::from_bytes(sha1_bytes))
.with_variant(Variant::RFC4122)
.with_version(Version::Sha1)
}
@ -605,25 +587,14 @@ impl Builder {
/// Creates a `Builder` for a version 6 UUID using the supplied timestamp and node id.
///
/// This method will encode the ticks, counter, and node id in a sortable UUID.
pub const fn from_sorted_rfc4122_timestamp(ts: Timestamp, node_id: &[u8; 6]) -> Self {
let (ticks, counter) = ts.to_rfc4122();
let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32;
let time_mid = ((ticks >> 12) & 0xFFFF) as u16;
let time_low_and_version = ((ticks & 0x0FFF) as u16) | (0x6 << 12);
let mut d4 = [0; 8];
d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80;
d4[1] = (counter & 0xFF) as u8;
d4[2] = node_id[0];
d4[3] = node_id[1];
d4[4] = node_id[2];
d4[5] = node_id[3];
d4[6] = node_id[4];
d4[7] = node_id[5];
Self::from_fields(time_high, time_mid, time_low_and_version, &d4)
pub const fn from_sorted_rfc4122_timestamp(
ticks: u64,
counter: u16,
node_id: &[u8; 6],
) -> Self {
Builder(crate::v6::encode_sorted_rfc4122_timestamp(
ticks, counter, node_id,
))
}
/// Creates a `Builder` for a version 7 UUID using the supplied Unix timestamp.
@ -637,48 +608,36 @@ impl Builder {
/// Creating a UUID using the current system timestamp:
///
/// ```
/// use std::time::{Duration, SystemTime};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use uuid::{Builder, Uuid, Variant, Version, Timestamp, NoContext};
/// # let rng = || [
/// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204,
/// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13
/// # ];
/// let ts = Timestamp::now(NoContext);
/// let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
///
/// let random_bytes = rng();
///
/// let uuid = Builder::from_timestamp_millis(ts, &random_bytes).into_uuid();
/// let uuid = Builder::from_unix_timestamp(ts.as_secs(), ts.subsec_millis(), &random_bytes).into_uuid();
///
/// assert_eq!(Some(Version::SortRand), uuid.get_version());
/// assert_eq!(Variant::RFC4122, uuid.get_variant());
/// # Ok(())
/// # }
/// ```
pub const fn from_timestamp_millis(ts: Timestamp, random_bytes: &[u8; 11]) -> Self {
let millis = Duration::new(ts.seconds, ts.nanos).as_millis() as u64;
let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32;
let millis_low = (millis & 0xFFFF) as u16;
let random_and_version = (random_bytes[0] as u16 | ((random_bytes[1] as u16) << 8) & 0x0FFF) | (0x7 << 12);
let mut d4 = [0; 8];
d4[0] = (random_bytes[2] & 0x3F) | 0x80;
d4[1] = random_bytes[3];
d4[2] = random_bytes[4];
d4[3] = random_bytes[5];
d4[4] = random_bytes[6];
d4[5] = random_bytes[7];
d4[6] = random_bytes[8];
d4[7] = random_bytes[9];
Self::from_fields(millis_high, millis_low, random_and_version, &d4)
pub const fn from_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Self {
Builder(crate::v7::encode_unix_timestamp_millis(
millis,
random_bytes,
))
}
/// Creates a `Builder` for a version 8 UUID using the supplied user-defined bytes.
///
/// This method won't interpret the given bytes in any way, except to set the appropriate
/// bits for the UUID version and variant.
pub const fn from_custom_bytes(b: Bytes) -> Self {
Builder::from_bytes(b)
pub const fn from_custom_bytes(custom_bytes: Bytes) -> Self {
Builder::from_bytes(custom_bytes)
.with_variant(Variant::RFC4122)
.with_version(Version::Custom)
}

View File

@ -228,7 +228,7 @@ mod parser;
pub mod fmt;
pub mod timestamp;
pub use timestamp::{ClockSequence, Timestamp, context::NoContext};
pub use timestamp::{context::NoContext, ClockSequence, Timestamp};
#[cfg(any(feature = "v1", feature = "v6"))]
pub use timestamp::context::Context;
@ -884,60 +884,30 @@ impl Uuid {
/// value into more commonly-used formats, such as a unix timestamp.
///
/// [`Timestamp`]: v1/struct.Timestamp.html
pub const fn get_timestamp(&self) -> Option<crate::timestamp::Timestamp> {
pub const fn get_timestamp(&self) -> Option<Timestamp> {
match self.get_version() {
Some(Version::Mac) => {
let bytes = self.as_bytes();
let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56
| (bytes[7] as u64) << 48
| (bytes[4] as u64) << 40
| (bytes[5] as u64) << 32
| (bytes[0] as u64) << 24
| (bytes[1] as u64) << 16
| (bytes[2] as u64) << 8
| (bytes[3] as u64);
let (ticks, counter) = v1::decode_rfc4122_timestamp(self);
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter))
Some(Timestamp::from_rfc4122(ticks, counter))
}
Some(Version::SortMac) => {
let bytes = self.as_bytes();
let ticks: u64 = ((self.as_bytes()[0]) as u64) << 52
| (bytes[1] as u64) << 44
| (bytes[2] as u64) << 36
| (bytes[3] as u64) << 28
| (bytes[4] as u64) << 20
| (bytes[5] as u64) << 12
| ((bytes[6] & 0xF) as u64) << 8
| (bytes[7] as u64);
let (ticks, counter) = v6::decode_sorted_rfc4122_timestamp(self);
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter))
Some(Timestamp::from_rfc4122(ticks, counter))
}
Some(Version::SortRand) => {
let bytes = self.as_bytes();
let millis: u64 = (bytes[0] as u64) << 40
| (bytes[1] as u64) << 32
| (bytes[2] as u64) << 24
| (bytes[3] as u64) << 16
| (bytes[4] as u64) << 8
| (bytes[5] as u64);
let millis = v7::decode_unix_timestamp_millis(self);
let seconds = millis / 1000;
let nanos = ((millis % 1000) * 1_000_000) as u32;
#[cfg(any(feature = "v1", feature = "v6"))]
{
Some(Timestamp {
seconds,
nanos,
counter: 0,
})
}
#[cfg(not(any(feature = "v1", feature = "v6")))]
{
Some(Timestamp { seconds, nanos })
}
Some(Timestamp {
seconds,
nanos,
#[cfg(any(feature = "v1", feature = "v6"))]
counter: 0,
})
}
_ => None,
}
@ -1168,7 +1138,7 @@ mod tests {
let uuid1 = new();
let s = uuid1.hyphenated().to_string();
assert!(s.len() == 36);
assert_eq!(36, s.len());
assert!(s.chars().all(|c| c.is_digit(16) || c == '-'));
}

View File

@ -1,11 +1,20 @@
//! Generating UUIDs from timestamps.
//!
//! Timestamps are used in a few UUID versions as a source of decentralized
//! uniqueness (as in versions 1 and 6), and as a way to enable sorting (as
//! in versions 6 and 7). Timestamps aren't encoded the same way by all UUID
//! versions so this module provides a single [`Timestamp`] type that can
//! convert between them.
/// The number of 100 ns ticks between the UUID epoch
/// `1582-10-15 00:00:00` and the Unix epoch `1970-01-01 00:00:00`.
/// The number of 100 nanosecond ticks between the RFC4122 epoch
/// (`1582-10-15 00:00:00`) and the Unix epoch (`1970-01-01 00:00:00`).
pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000;
/// Stores the number of seconds since epoch,
/// as well as the fractional nanoseconds of that second
/// A timestamp that can be encoded into a UUID.
///
/// This type abstracts the specific encoding, so a UUID version 1 or
/// a UUID version 7 can both be supported through the same type, even
/// though they have a different representation of a timestamp.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Timestamp {
pub(crate) seconds: u64,
@ -15,13 +24,30 @@ pub struct Timestamp {
}
impl Timestamp {
/// Construct a `Timestamp` from its raw component values: an RFC4122
/// timestamp and counter.
/// Get a timestamp representing the current system time.
///
/// RFC4122, which defines the V1 UUID, specifies a 60-byte timestamp format
/// as the number of 100-nanosecond intervals elapsed since 00:00:00.00,
/// 15 Oct 1582, "the date of the Gregorian reform of the Christian
/// calendar."
/// This method defers to the standard library's `SystemTime` type.
#[cfg(feature = "std")]
pub fn now(context: impl ClockSequence<Output = u16>) -> Self {
#[cfg(not(any(feature = "v1", feature = "v6")))]
{
let _ = context;
}
let dur = std::time::SystemTime::UNIX_EPOCH
.elapsed()
.expect("Getting elapsed time since UNIX_EPOCH. If this fails, we've somehow violated causality");
Timestamp {
seconds: dur.as_secs(),
nanos: dur.subsec_nanos(),
#[cfg(any(feature = "v1", feature = "v6"))]
counter: context.generate_sequence(dur.as_secs(), dur.subsec_nanos()),
}
}
/// Construct a `Timestamp` from an RFC4122 timestamp and counter, as used
/// in version 1 and version 6 UUIDs.
pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self {
#[cfg(not(any(feature = "v1", feature = "v6")))]
{
@ -38,19 +64,8 @@ impl Timestamp {
}
}
/// Construct a `Timestamp` from a unix timestamp
///
/// A unix timestamp represents the elapsed time since Jan 1 1970. Libc's
/// `clock_gettime` and other popular implementations traditionally
/// represent this duration as a `timespec`: a struct with `u64` and
/// `u32` fields representing the seconds, and "subsecond" or fractional
/// nanoseconds elapsed since the timestamp's second began,
/// respectively.
pub fn from_unix(
context: impl ClockSequence<Output = u16>,
seconds: u64,
nanos: u32,
) -> Self {
/// Construct a `Timestamp` from a Unix timestamp.
pub fn from_unix(context: impl ClockSequence<Output = u16>, seconds: u64, nanos: u32) -> Self {
#[cfg(not(any(feature = "v1", feature = "v6")))]
{
let _ = context;
@ -69,33 +84,8 @@ impl Timestamp {
}
}
/// Construct a `Timestamp` from the current time of day
/// according to Rust's SystemTime.
#[cfg(feature = "std")]
pub fn now(context: impl ClockSequence<Output = u16>) -> Self {
#[cfg(not(any(feature = "v1", feature = "v6")))]
{
let _ = context;
}
let dur = std::time::SystemTime::UNIX_EPOCH
.elapsed()
.expect("Getting elapsed time since UNIX_EPOCH. If this fails, we've somehow violated causality");
Timestamp {
seconds: dur.as_secs(),
nanos: dur.subsec_nanos(),
#[cfg(any(feature = "v1", feature = "v6"))]
counter: context
.generate_sequence(dur.as_secs(), dur.subsec_nanos()),
}
}
/// Returns the raw RFC4122 timestamp "tick" values stored by the
/// `Timestamp`.
///
/// The ticks represent the number of 100-nanosecond intervals
/// since 00:00:00.00, 15 Oct 1582.
/// Get the value of the timestamp as an RFC4122 timestamp and counter,
/// as used in version 1 and version 6 UUIDs.
#[cfg(any(feature = "v1", feature = "v6"))]
pub const fn to_rfc4122(&self) -> (u64, u16) {
(
@ -104,14 +94,32 @@ impl Timestamp {
)
}
/// Returns the timestamp converted to the seconds and fractional
/// nanoseconds since Jan 1 1970.
/// Get the value of the timestamp as a Unix timestamp, consisting of the
/// number of whole and fractional seconds.
pub const fn to_unix(&self) -> (u64, u32) {
(self.seconds, self.nanos)
}
#[cfg(any(feature = "v1", feature = "v6"))]
const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 {
let ticks = UUID_TICKS_BETWEEN_EPOCHS + seconds * 10_000_000 + nanos as u64 / 100;
ticks
}
const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) {
(
(ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000,
((ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100,
)
}
#[deprecated(note = "use `to_unix` instead")]
#[doc(hidden)]
/// Get the number of fractional nanoseconds in the Unix timestamp.
///
/// This method is deprecated and probably doesn't do what you're expecting it to.
/// It doesn't return the timestamp as nanoseconds since the Unix epoch, it returns
/// the fractional seconds of the timestamp.
pub const fn to_unix_nanos(&self) -> u32 {
// NOTE: This method never did what it said on the tin: instead of
// converting the timestamp into nanos it simply returned the nanoseconds
@ -121,64 +129,42 @@ impl Timestamp {
// a useful value for nanoseconds since the epoch.
self.nanos
}
/// internal utility functions for converting between Unix and Uuid-epoch
/// convert unix-timestamp into rfc4122 ticks.
#[cfg(any(feature = "v1", feature = "v6"))]
const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 {
let ticks = UUID_TICKS_BETWEEN_EPOCHS
+ seconds * 10_000_000
+ nanos as u64 / 100;
ticks
}
/// convert rfc4122 ticks into unix-timestamp
const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) {
(
(ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000,
((ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100,
)
}
}
/// A trait that abstracts over generation of counter values used in UUID timestamps.
/// A counter that can be used by version 1 and version 6 UUIDs to support
/// the uniqueness of timestamps.
///
/// # References
///
/// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5)
pub trait ClockSequence {
/// The primitive type you wish out output
/// The type of sequence returned by this counter.
type Output;
/// Return an arbitrary width number that will be used as the "clock sequence" in
/// the UUID. The number must be different if the time has changed since
/// the last time a clock sequence was requested.
fn generate_sequence(
&self,
seconds: u64,
subsec_nanos: u32,
) -> Self::Output;
/// Get the next value in the sequence to feed into a timestamp.
///
/// This method will be called each time a [`Timestamp`] is constructed.
fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output;
}
impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T {
type Output = T::Output;
fn generate_sequence(
&self,
seconds: u64,
subsec_nanos: u32,
) -> Self::Output {
fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output {
(**self).generate_sequence(seconds, subsec_nanos)
}
}
/// For features v1 and v6, constructs a `Context` struct which implements the `ClockSequence` trait.
/// Default implementations for the [`ClockSequence`] trait.
pub mod context {
use super::ClockSequence;
#[cfg(any(feature = "v1", feature = "v6"))]
use private_atomic::{Atomic, Ordering};
/// A clock sequence that never produces a counter value to deduplicate equal timestamps with.
/// An empty counter that will always return the value `0`.
///
/// This type should be used when constructing timestamps for version 7 UUIDs,
/// since they don't need a counter for uniqueness.
#[derive(Debug, Clone, Copy, Default)]
pub struct NoContext;
@ -190,8 +176,31 @@ pub mod context {
}
}
/// A thread-safe, stateful context for the v1 generator to help ensure
/// process-wide uniqueness.
#[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
static CONTEXT: Context = Context {
count: Atomic::new(0),
};
#[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
static CONTEXT_INITIALIZED: Atomic<bool> = Atomic::new(false);
#[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
pub(crate) fn shared_context() -> &'static Context {
// If the context is in its initial state then assign it to a random value
// It doesn't matter if multiple threads observe `false` here and initialize the context
if CONTEXT_INITIALIZED
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
CONTEXT.count.store(crate::rng::u16(), Ordering::Release);
}
&CONTEXT
}
/// A thread-safe, wrapping counter that produces 14-bit numbers.
///
/// This type should be used when constructing version 1 and version 6 UUIDs.
#[derive(Debug)]
#[cfg(any(feature = "v1", feature = "v6"))]
pub struct Context {
@ -200,31 +209,18 @@ pub mod context {
#[cfg(any(feature = "v1", feature = "v6"))]
impl Context {
/// Creates a thread-safe, internally mutable context to help ensure
/// uniqueness.
/// Construct a new context that's initialized with the given value.
///
/// This is a context which can be shared across threads. It maintains an
/// internal counter that is incremented at every request, the value ends
/// up in the clock_seq portion of the UUID (the fourth group). This
/// will improve the probability that the UUID is unique across the
/// process.
/// The starting value should be a random number, so that UUIDs from
/// different systems with the same timestamps are less likely to collide.
/// When the `rng` feature is enabled, prefer the [`Context::new_random`] method.
pub const fn new(count: u16) -> Self {
Self {
count: Atomic::<u16>::new(count),
}
}
/// Creates a thread-safe, internally mutable context that's seeded with a
/// random value.
///
/// This method requires either the `rng` or `fast-rng` feature to also be
/// enabled.
///
/// This is a context which can be shared across threads. It maintains an
/// internal counter that is incremented at every request, the value ends
/// up in the clock_seq portion of the UUID (the fourth group). This
/// will improve the probability that the UUID is unique across the
/// process.
/// Construct a new context that's initialized with a random value.
#[cfg(feature = "rng")]
pub fn new_random() -> Self {
Self {
@ -237,11 +233,7 @@ pub mod context {
impl ClockSequence for Context {
type Output = u16;
fn generate_sequence(
&self,
_seconds: u64,
_nanos: u32,
) -> Self::Output {
fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
// RFC4122 reserves 2 bits of the clock sequence so the actual
// maximum value is smaller than `u16::MAX`. Since we unconditionally
// increment the clock sequence we want to wrap once it becomes larger

View File

@ -1,31 +1,39 @@
//! The implementation for Version 1 UUIDs.
//!
//! Note that you need to enable the `v1` Cargo feature
//! in order to use this module.
//! This module is soft-deprecated. Instead of using the `Context` type re-exported here,
//! use the one from the crate root.
use crate::timestamp::context::shared_context;
use crate::timestamp::Timestamp;
use crate::{Builder, Uuid};
pub use crate::timestamp::context::Context;
impl Uuid {
/// Create a new UUID (version 1) using a time value + sequence +
/// *NodeId*.
/// Create a new UUID (version 1) using the current system time and a node id.
///
/// This method is only available if both the `std` and `rng` features are enabled.
#[cfg(all(feature = "std", feature = "rng"))]
pub fn now_v1(node_id: &[u8; 6]) -> Self {
let ts = Timestamp::now(shared_context());
Self::new_v1(ts, node_id)
}
/// Create a new UUID (version 1) using the given timestamp and node id.
///
/// When generating [`Timestamp`]s using a [`ClockSequence`], this function
/// is only guaranteed to produce unique values if the following conditions
/// hold:
///
/// 1. The *NodeId* is unique for this process,
/// 2. The *Context* is shared across all threads which are generating v1
/// 1. The *node id* is unique for this process,
/// 2. The *context* is shared across all threads which are generating version 1
/// UUIDs,
/// 3. The [`ClockSequence`] implementation reliably returns unique
/// clock sequences (this crate provides [`Context`] for this
/// purpose. However you can create your own [`ClockSequence`]
/// implementation, if [`Context`] does not meet your needs).
///
/// The NodeID must be exactly 6 bytes long.
///
/// Note that usage of this method requires the `v1` feature of this crate
/// to be enabled.
///
@ -35,7 +43,7 @@ impl Uuid {
/// [`ClockSequence`]. RFC4122 requires the clock sequence
/// is seeded with a random value:
///
/// ```rust
/// ```
/// # use uuid::{Timestamp, Context};
/// # use uuid::Uuid;
/// # fn random_seed() -> u16 { 42 }
@ -67,7 +75,7 @@ impl Uuid {
///
/// The timestamp can also just use the current SystemTime
///
/// ```rust
/// ```
/// # use uuid::{Timestamp, Context};
/// # use uuid::Uuid;
/// let context = Context::new(42);
@ -80,10 +88,48 @@ impl Uuid {
/// [`ClockSequence`]: v1/trait.ClockSequence.html
/// [`Context`]: v1/struct.Context.html
pub fn new_v1(ts: Timestamp, node_id: &[u8; 6]) -> Self {
Builder::from_rfc4122_timestamp(ts, node_id).into_uuid()
let (ticks, counter) = ts.to_rfc4122();
Builder::from_rfc4122_timestamp(ticks, counter, node_id).into_uuid()
}
}
pub(crate) const fn encode_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Uuid {
let time_low = (ticks & 0xFFFF_FFFF) as u32;
let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
let time_high_and_version = (((ticks >> 48) & 0x0FFF) as u16) | (1 << 12);
let mut d4 = [0; 8];
d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80;
d4[1] = (counter & 0xFF) as u8;
d4[2] = node_id[0];
d4[3] = node_id[1];
d4[4] = node_id[2];
d4[5] = node_id[3];
d4[6] = node_id[4];
d4[7] = node_id[5];
Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4)
}
pub(crate) const fn decode_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) {
let bytes = uuid.as_bytes();
let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56
| (bytes[7] as u64) << 48
| (bytes[4] as u64) << 40
| (bytes[5] as u64) << 32
| (bytes[0] as u64) << 24
| (bytes[1] as u64) << 16
| (bytes[2] as u64) << 8
| (bytes[3] as u64);
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
(ticks, counter)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -3,10 +3,21 @@
//! Note that you need to enable the `v6` Cargo feature
//! in order to use this module.
use crate::timestamp::context::shared_context;
use crate::timestamp::Timestamp;
use crate::{Builder, Uuid};
impl Uuid {
/// Create a new UUID (version 6) using the current time value and a node id.
///
/// This method is only available if the `std` feature is enabled.
#[cfg(all(feature = "std", feature = "rng"))]
pub fn now_v6(node_id: &[u8; 6]) -> Self {
let ts = Timestamp::now(shared_context());
Self::new_v6(ts, node_id)
}
/// Create a new UUID (version 6) using a time value + sequence +
/// *NodeId*.
/// This is similar to UUIDv1, except that it is lexographically sortable by timestamp.
@ -75,14 +86,56 @@ impl Uuid {
/// [`ClockSequence`]: v1/trait.ClockSequence.html
/// [`Context`]: v1/struct.Context.html
pub fn new_v6(ts: Timestamp, node_id: &[u8; 6]) -> Self {
Builder::from_sorted_rfc4122_timestamp(ts, node_id).into_uuid()
let (ticks, counter) = ts.to_rfc4122();
Builder::from_sorted_rfc4122_timestamp(ticks, counter, node_id).into_uuid()
}
}
pub(crate) const fn encode_sorted_rfc4122_timestamp(
ticks: u64,
counter: u16,
node_id: &[u8; 6],
) -> Uuid {
let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32;
let time_mid = ((ticks >> 12) & 0xFFFF) as u16;
let time_low_and_version = ((ticks & 0x0FFF) as u16) | (0x6 << 12);
let mut d4 = [0; 8];
d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80;
d4[1] = (counter & 0xFF) as u8;
d4[2] = node_id[0];
d4[3] = node_id[1];
d4[4] = node_id[2];
d4[5] = node_id[3];
d4[6] = node_id[4];
d4[7] = node_id[5];
Uuid::from_fields(time_high, time_mid, time_low_and_version, &d4)
}
pub(crate) const fn decode_sorted_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) {
let bytes = uuid.as_bytes();
let ticks: u64 = ((bytes[0]) as u64) << 52
| (bytes[1] as u64) << 44
| (bytes[2] as u64) << 36
| (bytes[3] as u64) << 28
| (bytes[4] as u64) << 20
| (bytes[5] as u64) << 12
| ((bytes[6] & 0xF) as u64) << 8
| (bytes[7] as u64);
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
(ticks, counter)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Variant, Version, Context};
use crate::{Context, Variant, Version};
use std::string::ToString;
#[cfg(target_arch = "wasm32")]

View File

@ -3,7 +3,7 @@
//! Note that you need to enable the `v7` Cargo feature
//! in order to use this module.
use crate::{Builder, NoContext, rng, timestamp::Timestamp, Uuid};
use crate::{rng, timestamp::Timestamp, Builder, NoContext, Uuid};
use core::convert::TryInto;
impl Uuid {
@ -23,9 +23,6 @@ impl Uuid {
/// Note that usage of this method requires the `v7` feature of this crate
/// to be enabled.
///
/// This method will use millisecond precision for the timestamp and fill the
/// rest with random data.
///
/// # Examples
///
/// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit
@ -41,26 +38,53 @@ impl Uuid {
/// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7")
/// );
/// ```
///
/// The timestamp can also be created automatically from the current SystemTime
///
/// ```
/// # use uuid::{Uuid, Timestamp, NoContext};
/// let ts = Timestamp::now(NoContext);
///
/// let uuid = Uuid::new_v7(ts);
/// ```
pub fn new_v7(ts: Timestamp) -> Self {
let buf: &[u8] = &rng::bytes()[0..11];
let (secs, nanos) = ts.to_unix();
let millis = secs.saturating_add(nanos as u64 / 1_000_000);
Builder::from_timestamp_millis(ts, buf.try_into().unwrap()).into_uuid()
Builder::from_unix_timestamp_millis(millis, &rng::bytes()[..10].try_into().unwrap())
.into_uuid()
}
}
pub(crate) const fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Uuid {
let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32;
let millis_low = (millis & 0xFFFF) as u16;
let random_and_version =
(random_bytes[0] as u16 | ((random_bytes[1] as u16) << 8) & 0x0FFF) | (0x7 << 12);
let mut d4 = [0; 8];
d4[0] = (random_bytes[2] & 0x3F) | 0x80;
d4[1] = random_bytes[3];
d4[2] = random_bytes[4];
d4[3] = random_bytes[5];
d4[4] = random_bytes[6];
d4[5] = random_bytes[7];
d4[6] = random_bytes[8];
d4[7] = random_bytes[9];
Uuid::from_fields(millis_high, millis_low, random_and_version, &d4)
}
pub(crate) const fn decode_unix_timestamp_millis(uuid: &Uuid) -> u64 {
let bytes = uuid.as_bytes();
let millis: u64 = (bytes[0] as u64) << 40
| (bytes[1] as u64) << 32
| (bytes[2] as u64) << 24
| (bytes[3] as u64) << 16
| (bytes[4] as u64) << 8
| (bytes[5] as u64);
millis
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Variant, Version, NoContext};
use crate::{NoContext, Variant, Version};
use std::string::ToString;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
@ -81,6 +105,21 @@ mod tests {
// Ensure parsing the same UUID produces the same timestamp
let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
assert_eq!(uuid, parsed,);
assert_eq!(uuid, parsed);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_new_v7_timestamp_roundtrip() {
let time: u64 = 1_496_854_535;
let time_fraction: u32 = 812_946_000;
let ts = Timestamp::from_unix(NoContext, time, time_fraction);
let uuid = Uuid::new_v7(ts);
let decoded_ts = uuid.get_timestamp().unwrap();
assert_eq!(ts.to_unix(), decoded_ts.to_unix());
}
}

View File

@ -26,7 +26,7 @@ impl Uuid {
#[cfg(test)]
mod tests {
use super::*;
use crate::{Version, Variant};
use crate::{Variant, Version};
use std::string::ToString;
#[cfg(target_arch = "wasm32")]
@ -36,8 +36,7 @@ mod tests {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_new() {
let buf: [u8; 16] = [
0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3,
0x2, 0x1, 0x0,
0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0,
];
let uuid = Uuid::new_v8(buf);
assert_eq!(uuid.get_version(), Some(Version::Custom));