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

View File

@ -228,7 +228,7 @@ mod parser;
pub mod fmt; pub mod fmt;
pub mod timestamp; pub mod timestamp;
pub use timestamp::{ClockSequence, Timestamp, context::NoContext}; pub use timestamp::{context::NoContext, ClockSequence, Timestamp};
#[cfg(any(feature = "v1", feature = "v6"))] #[cfg(any(feature = "v1", feature = "v6"))]
pub use timestamp::context::Context; pub use timestamp::context::Context;
@ -884,61 +884,31 @@ impl Uuid {
/// value into more commonly-used formats, such as a unix timestamp. /// value into more commonly-used formats, such as a unix timestamp.
/// ///
/// [`Timestamp`]: v1/struct.Timestamp.html /// [`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() { match self.get_version() {
Some(Version::Mac) => { Some(Version::Mac) => {
let bytes = self.as_bytes(); let (ticks, counter) = v1::decode_rfc4122_timestamp(self);
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(Timestamp::from_rfc4122(ticks, counter))
Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter))
} }
Some(Version::SortMac) => { Some(Version::SortMac) => {
let bytes = self.as_bytes(); let (ticks, counter) = v6::decode_sorted_rfc4122_timestamp(self);
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(Timestamp::from_rfc4122(ticks, counter))
Some(crate::timestamp::Timestamp::from_rfc4122(ticks, counter))
} }
Some(Version::SortRand) => { Some(Version::SortRand) => {
let bytes = self.as_bytes(); let millis = v7::decode_unix_timestamp_millis(self);
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 seconds = millis / 1000;
let nanos = ((millis % 1000) * 1_000_000) as u32; let nanos = ((millis % 1000) * 1_000_000) as u32;
#[cfg(any(feature = "v1", feature = "v6"))]
{
Some(Timestamp { Some(Timestamp {
seconds, seconds,
nanos, nanos,
#[cfg(any(feature = "v1", feature = "v6"))]
counter: 0, counter: 0,
}) })
} }
#[cfg(not(any(feature = "v1", feature = "v6")))]
{
Some(Timestamp { seconds, nanos })
}
}
_ => None, _ => None,
} }
} }
@ -1168,7 +1138,7 @@ mod tests {
let uuid1 = new(); let uuid1 = new();
let s = uuid1.hyphenated().to_string(); 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 == '-')); assert!(s.chars().all(|c| c.is_digit(16) || c == '-'));
} }

View File

@ -1,11 +1,20 @@
//! Generating UUIDs from timestamps. //! 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 /// 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`. /// (`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; pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000;
/// Stores the number of seconds since epoch, /// A timestamp that can be encoded into a UUID.
/// as well as the fractional nanoseconds of that second ///
/// 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Timestamp { pub struct Timestamp {
pub(crate) seconds: u64, pub(crate) seconds: u64,
@ -15,13 +24,30 @@ pub struct Timestamp {
} }
impl Timestamp { impl Timestamp {
/// Construct a `Timestamp` from its raw component values: an RFC4122 /// Get a timestamp representing the current system time.
/// timestamp and counter.
/// ///
/// RFC4122, which defines the V1 UUID, specifies a 60-byte timestamp format /// This method defers to the standard library's `SystemTime` type.
/// as the number of 100-nanosecond intervals elapsed since 00:00:00.00, #[cfg(feature = "std")]
/// 15 Oct 1582, "the date of the Gregorian reform of the Christian pub fn now(context: impl ClockSequence<Output = u16>) -> Self {
/// calendar." #[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 { pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self {
#[cfg(not(any(feature = "v1", feature = "v6")))] #[cfg(not(any(feature = "v1", feature = "v6")))]
{ {
@ -38,19 +64,8 @@ impl Timestamp {
} }
} }
/// Construct a `Timestamp` from a unix timestamp /// Construct a `Timestamp` from a Unix timestamp.
/// pub fn from_unix(context: impl ClockSequence<Output = u16>, seconds: u64, nanos: u32) -> Self {
/// 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 {
#[cfg(not(any(feature = "v1", feature = "v6")))] #[cfg(not(any(feature = "v1", feature = "v6")))]
{ {
let _ = context; let _ = context;
@ -69,33 +84,8 @@ impl Timestamp {
} }
} }
/// Construct a `Timestamp` from the current time of day /// Get the value of the timestamp as an RFC4122 timestamp and counter,
/// according to Rust's SystemTime. /// as used in version 1 and version 6 UUIDs.
#[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.
#[cfg(any(feature = "v1", feature = "v6"))] #[cfg(any(feature = "v1", feature = "v6"))]
pub const fn to_rfc4122(&self) -> (u64, u16) { pub const fn to_rfc4122(&self) -> (u64, u16) {
( (
@ -104,14 +94,32 @@ impl Timestamp {
) )
} }
/// Returns the timestamp converted to the seconds and fractional /// Get the value of the timestamp as a Unix timestamp, consisting of the
/// nanoseconds since Jan 1 1970. /// number of whole and fractional seconds.
pub const fn to_unix(&self) -> (u64, u32) { pub const fn to_unix(&self) -> (u64, u32) {
(self.seconds, self.nanos) (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")] #[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 { pub const fn to_unix_nanos(&self) -> u32 {
// NOTE: This method never did what it said on the tin: instead of // NOTE: This method never did what it said on the tin: instead of
// converting the timestamp into nanos it simply returned the nanoseconds // converting the timestamp into nanos it simply returned the nanoseconds
@ -121,64 +129,42 @@ impl Timestamp {
// a useful value for nanoseconds since the epoch. // a useful value for nanoseconds since the epoch.
self.nanos 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 /// A counter that can be used by version 1 and version 6 UUIDs to support
const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) { /// the uniqueness of timestamps.
(
(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.
/// ///
/// # References /// # References
/// ///
/// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) /// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5)
pub trait ClockSequence { pub trait ClockSequence {
/// The primitive type you wish out output /// The type of sequence returned by this counter.
type 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 /// Get the next value in the sequence to feed into a timestamp.
/// the last time a clock sequence was requested. ///
fn generate_sequence( /// This method will be called each time a [`Timestamp`] is constructed.
&self, fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output;
seconds: u64,
subsec_nanos: u32,
) -> Self::Output;
} }
impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T {
type Output = T::Output; type Output = T::Output;
fn generate_sequence( fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output {
&self,
seconds: u64,
subsec_nanos: u32,
) -> Self::Output {
(**self).generate_sequence(seconds, subsec_nanos) (**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 { pub mod context {
use super::ClockSequence; use super::ClockSequence;
#[cfg(any(feature = "v1", feature = "v6"))] #[cfg(any(feature = "v1", feature = "v6"))]
use private_atomic::{Atomic, Ordering}; 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)] #[derive(Debug, Clone, Copy, Default)]
pub struct NoContext; pub struct NoContext;
@ -190,8 +176,31 @@ pub mod context {
} }
} }
/// A thread-safe, stateful context for the v1 generator to help ensure #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
/// process-wide uniqueness. 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)] #[derive(Debug)]
#[cfg(any(feature = "v1", feature = "v6"))] #[cfg(any(feature = "v1", feature = "v6"))]
pub struct Context { pub struct Context {
@ -200,31 +209,18 @@ pub mod context {
#[cfg(any(feature = "v1", feature = "v6"))] #[cfg(any(feature = "v1", feature = "v6"))]
impl Context { impl Context {
/// Creates a thread-safe, internally mutable context to help ensure /// Construct a new context that's initialized with the given value.
/// uniqueness.
/// ///
/// This is a context which can be shared across threads. It maintains an /// The starting value should be a random number, so that UUIDs from
/// internal counter that is incremented at every request, the value ends /// different systems with the same timestamps are less likely to collide.
/// up in the clock_seq portion of the UUID (the fourth group). This /// When the `rng` feature is enabled, prefer the [`Context::new_random`] method.
/// will improve the probability that the UUID is unique across the
/// process.
pub const fn new(count: u16) -> Self { pub const fn new(count: u16) -> Self {
Self { Self {
count: Atomic::<u16>::new(count), count: Atomic::<u16>::new(count),
} }
} }
/// Creates a thread-safe, internally mutable context that's seeded with a /// Construct a new context that's initialized with a random value.
/// 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")] #[cfg(feature = "rng")]
pub fn new_random() -> Self { pub fn new_random() -> Self {
Self { Self {
@ -237,11 +233,7 @@ pub mod context {
impl ClockSequence for Context { impl ClockSequence for Context {
type Output = u16; type Output = u16;
fn generate_sequence( fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
&self,
_seconds: u64,
_nanos: u32,
) -> Self::Output {
// RFC4122 reserves 2 bits of the clock sequence so the actual // RFC4122 reserves 2 bits of the clock sequence so the actual
// maximum value is smaller than `u16::MAX`. Since we unconditionally // maximum value is smaller than `u16::MAX`. Since we unconditionally
// increment the clock sequence we want to wrap once it becomes larger // increment the clock sequence we want to wrap once it becomes larger

View File

@ -1,31 +1,39 @@
//! The implementation for Version 1 UUIDs. //! The implementation for Version 1 UUIDs.
//! //!
//! Note that you need to enable the `v1` Cargo feature //! This module is soft-deprecated. Instead of using the `Context` type re-exported here,
//! in order to use this module. //! use the one from the crate root.
use crate::timestamp::context::shared_context;
use crate::timestamp::Timestamp; use crate::timestamp::Timestamp;
use crate::{Builder, Uuid}; use crate::{Builder, Uuid};
pub use crate::timestamp::context::Context; pub use crate::timestamp::context::Context;
impl Uuid { impl Uuid {
/// Create a new UUID (version 1) using a time value + sequence + /// Create a new UUID (version 1) using the current system time and a node id.
/// *NodeId*. ///
/// 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 /// When generating [`Timestamp`]s using a [`ClockSequence`], this function
/// is only guaranteed to produce unique values if the following conditions /// is only guaranteed to produce unique values if the following conditions
/// hold: /// hold:
/// ///
/// 1. The *NodeId* is unique for this process, /// 1. The *node id* is unique for this process,
/// 2. The *Context* is shared across all threads which are generating v1 /// 2. The *context* is shared across all threads which are generating version 1
/// UUIDs, /// UUIDs,
/// 3. The [`ClockSequence`] implementation reliably returns unique /// 3. The [`ClockSequence`] implementation reliably returns unique
/// clock sequences (this crate provides [`Context`] for this /// clock sequences (this crate provides [`Context`] for this
/// purpose. However you can create your own [`ClockSequence`] /// purpose. However you can create your own [`ClockSequence`]
/// implementation, if [`Context`] does not meet your needs). /// 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 /// Note that usage of this method requires the `v1` feature of this crate
/// to be enabled. /// to be enabled.
/// ///
@ -35,7 +43,7 @@ impl Uuid {
/// [`ClockSequence`]. RFC4122 requires the clock sequence /// [`ClockSequence`]. RFC4122 requires the clock sequence
/// is seeded with a random value: /// is seeded with a random value:
/// ///
/// ```rust /// ```
/// # use uuid::{Timestamp, Context}; /// # use uuid::{Timestamp, Context};
/// # use uuid::Uuid; /// # use uuid::Uuid;
/// # fn random_seed() -> u16 { 42 } /// # fn random_seed() -> u16 { 42 }
@ -67,7 +75,7 @@ impl Uuid {
/// ///
/// The timestamp can also just use the current SystemTime /// The timestamp can also just use the current SystemTime
/// ///
/// ```rust /// ```
/// # use uuid::{Timestamp, Context}; /// # use uuid::{Timestamp, Context};
/// # use uuid::Uuid; /// # use uuid::Uuid;
/// let context = Context::new(42); /// let context = Context::new(42);
@ -80,10 +88,48 @@ impl Uuid {
/// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`ClockSequence`]: v1/trait.ClockSequence.html
/// [`Context`]: v1/struct.Context.html /// [`Context`]: v1/struct.Context.html
pub fn new_v1(ts: Timestamp, node_id: &[u8; 6]) -> Self { 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -3,10 +3,21 @@
//! Note that you need to enable the `v6` Cargo feature //! Note that you need to enable the `v6` Cargo feature
//! in order to use this module. //! in order to use this module.
use crate::timestamp::context::shared_context;
use crate::timestamp::Timestamp; use crate::timestamp::Timestamp;
use crate::{Builder, Uuid}; use crate::{Builder, Uuid};
impl 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 + /// Create a new UUID (version 6) using a time value + sequence +
/// *NodeId*. /// *NodeId*.
/// This is similar to UUIDv1, except that it is lexographically sortable by timestamp. /// This is similar to UUIDv1, except that it is lexographically sortable by timestamp.
@ -75,14 +86,56 @@ impl Uuid {
/// [`ClockSequence`]: v1/trait.ClockSequence.html /// [`ClockSequence`]: v1/trait.ClockSequence.html
/// [`Context`]: v1/struct.Context.html /// [`Context`]: v1/struct.Context.html
pub fn new_v6(ts: Timestamp, node_id: &[u8; 6]) -> Self { 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{Variant, Version, Context}; use crate::{Context, Variant, Version};
use std::string::ToString; use std::string::ToString;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -3,7 +3,7 @@
//! Note that you need to enable the `v7` Cargo feature //! Note that you need to enable the `v7` Cargo feature
//! in order to use this module. //! 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; use core::convert::TryInto;
impl Uuid { impl Uuid {
@ -23,9 +23,6 @@ impl Uuid {
/// Note that usage of this method requires the `v7` feature of this crate /// Note that usage of this method requires the `v7` feature of this crate
/// to be enabled. /// to be enabled.
/// ///
/// This method will use millisecond precision for the timestamp and fill the
/// rest with random data.
///
/// # Examples /// # Examples
/// ///
/// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit /// 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") /// 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 { 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{Variant, Version, NoContext}; use crate::{NoContext, Variant, Version};
use std::string::ToString; use std::string::ToString;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
@ -81,6 +105,21 @@ mod tests {
// Ensure parsing the same UUID produces the same timestamp // Ensure parsing the same UUID produces the same timestamp
let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{Version, Variant}; use crate::{Variant, Version};
use std::string::ToString; use std::string::ToString;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -36,8 +36,7 @@ mod tests {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_new() { fn test_new() {
let buf: [u8; 16] = [ let buf: [u8; 16] = [
0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0,
0x2, 0x1, 0x0,
]; ];
let uuid = Uuid::new_v8(buf); let uuid = Uuid::new_v8(buf);
assert_eq!(uuid.get_version(), Some(Version::Custom)); assert_eq!(uuid.get_version(), Some(Version::Custom));