mirror of
https://github.com/uuid-rs/uuid.git
synced 2025-09-29 22:10:50 +00:00
reorganize time code a bit
This commit is contained in:
parent
9d445c7c94
commit
fbf463fdd1
@ -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)
|
||||
}
|
||||
|
62
src/lib.rs
62
src/lib.rs
@ -228,7 +228,7 @@ mod parser;
|
||||
pub mod fmt;
|
||||
pub mod timestamp;
|
||||
|
||||
pub use timestamp::{ClockSequence, Timestamp, context::NoContext};
|
||||
pub use timestamp::{context::NoContext, ClockSequence, Timestamp};
|
||||
|
||||
#[cfg(any(feature = "v1", feature = "v6"))]
|
||||
pub use timestamp::context::Context;
|
||||
@ -884,60 +884,30 @@ impl Uuid {
|
||||
/// value into more commonly-used formats, such as a unix timestamp.
|
||||
///
|
||||
/// [`Timestamp`]: v1/struct.Timestamp.html
|
||||
pub const fn get_timestamp(&self) -> Option<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 == '-'));
|
||||
}
|
||||
|
||||
|
220
src/timestamp.rs
220
src/timestamp.rs
@ -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
|
||||
|
68
src/v1.rs
68
src/v1.rs
@ -1,31 +1,39 @@
|
||||
//! The implementation for Version 1 UUIDs.
|
||||
//!
|
||||
//! Note that you need to enable the `v1` Cargo feature
|
||||
//! in order to use this module.
|
||||
//! This module is soft-deprecated. Instead of using the `Context` type re-exported here,
|
||||
//! use the one from the crate root.
|
||||
|
||||
use crate::timestamp::context::shared_context;
|
||||
use crate::timestamp::Timestamp;
|
||||
use crate::{Builder, Uuid};
|
||||
|
||||
pub use crate::timestamp::context::Context;
|
||||
|
||||
impl Uuid {
|
||||
/// Create a new UUID (version 1) using a time value + sequence +
|
||||
/// *NodeId*.
|
||||
/// Create a new UUID (version 1) using the current system time and a node id.
|
||||
///
|
||||
/// This method is only available if both the `std` and `rng` features are enabled.
|
||||
#[cfg(all(feature = "std", feature = "rng"))]
|
||||
pub fn now_v1(node_id: &[u8; 6]) -> Self {
|
||||
let ts = Timestamp::now(shared_context());
|
||||
|
||||
Self::new_v1(ts, node_id)
|
||||
}
|
||||
|
||||
/// Create a new UUID (version 1) using the given timestamp and node id.
|
||||
///
|
||||
/// When generating [`Timestamp`]s using a [`ClockSequence`], this function
|
||||
/// is only guaranteed to produce unique values if the following conditions
|
||||
/// hold:
|
||||
///
|
||||
/// 1. The *NodeId* is unique for this process,
|
||||
/// 2. The *Context* is shared across all threads which are generating v1
|
||||
/// 1. The *node id* is unique for this process,
|
||||
/// 2. The *context* is shared across all threads which are generating version 1
|
||||
/// UUIDs,
|
||||
/// 3. The [`ClockSequence`] implementation reliably returns unique
|
||||
/// clock sequences (this crate provides [`Context`] for this
|
||||
/// purpose. However you can create your own [`ClockSequence`]
|
||||
/// implementation, if [`Context`] does not meet your needs).
|
||||
///
|
||||
/// The NodeID must be exactly 6 bytes long.
|
||||
///
|
||||
/// Note that usage of this method requires the `v1` feature of this crate
|
||||
/// to be enabled.
|
||||
///
|
||||
@ -35,7 +43,7 @@ impl Uuid {
|
||||
/// [`ClockSequence`]. RFC4122 requires the clock sequence
|
||||
/// is seeded with a random value:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```
|
||||
/// # use uuid::{Timestamp, Context};
|
||||
/// # use uuid::Uuid;
|
||||
/// # fn random_seed() -> u16 { 42 }
|
||||
@ -67,7 +75,7 @@ impl Uuid {
|
||||
///
|
||||
/// The timestamp can also just use the current SystemTime
|
||||
///
|
||||
/// ```rust
|
||||
/// ```
|
||||
/// # use uuid::{Timestamp, Context};
|
||||
/// # use uuid::Uuid;
|
||||
/// let context = Context::new(42);
|
||||
@ -80,10 +88,48 @@ impl Uuid {
|
||||
/// [`ClockSequence`]: v1/trait.ClockSequence.html
|
||||
/// [`Context`]: v1/struct.Context.html
|
||||
pub fn new_v1(ts: Timestamp, node_id: &[u8; 6]) -> Self {
|
||||
Builder::from_rfc4122_timestamp(ts, node_id).into_uuid()
|
||||
let (ticks, counter) = ts.to_rfc4122();
|
||||
|
||||
Builder::from_rfc4122_timestamp(ticks, counter, node_id).into_uuid()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn encode_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Uuid {
|
||||
let time_low = (ticks & 0xFFFF_FFFF) as u32;
|
||||
let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
|
||||
let time_high_and_version = (((ticks >> 48) & 0x0FFF) as u16) | (1 << 12);
|
||||
|
||||
let mut d4 = [0; 8];
|
||||
|
||||
d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80;
|
||||
d4[1] = (counter & 0xFF) as u8;
|
||||
d4[2] = node_id[0];
|
||||
d4[3] = node_id[1];
|
||||
d4[4] = node_id[2];
|
||||
d4[5] = node_id[3];
|
||||
d4[6] = node_id[4];
|
||||
d4[7] = node_id[5];
|
||||
|
||||
Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4)
|
||||
}
|
||||
|
||||
pub(crate) const fn decode_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) {
|
||||
let bytes = uuid.as_bytes();
|
||||
|
||||
let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56
|
||||
| (bytes[7] as u64) << 48
|
||||
| (bytes[4] as u64) << 40
|
||||
| (bytes[5] as u64) << 32
|
||||
| (bytes[0] as u64) << 24
|
||||
| (bytes[1] as u64) << 16
|
||||
| (bytes[2] as u64) << 8
|
||||
| (bytes[3] as u64);
|
||||
|
||||
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
|
||||
|
||||
(ticks, counter)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
57
src/v6.rs
57
src/v6.rs
@ -3,10 +3,21 @@
|
||||
//! Note that you need to enable the `v6` Cargo feature
|
||||
//! in order to use this module.
|
||||
|
||||
use crate::timestamp::context::shared_context;
|
||||
use crate::timestamp::Timestamp;
|
||||
use crate::{Builder, Uuid};
|
||||
|
||||
impl Uuid {
|
||||
/// Create a new UUID (version 6) using the current time value and a node id.
|
||||
///
|
||||
/// This method is only available if the `std` feature is enabled.
|
||||
#[cfg(all(feature = "std", feature = "rng"))]
|
||||
pub fn now_v6(node_id: &[u8; 6]) -> Self {
|
||||
let ts = Timestamp::now(shared_context());
|
||||
|
||||
Self::new_v6(ts, node_id)
|
||||
}
|
||||
|
||||
/// Create a new UUID (version 6) using a time value + sequence +
|
||||
/// *NodeId*.
|
||||
/// This is similar to UUIDv1, except that it is lexographically sortable by timestamp.
|
||||
@ -75,14 +86,56 @@ impl Uuid {
|
||||
/// [`ClockSequence`]: v1/trait.ClockSequence.html
|
||||
/// [`Context`]: v1/struct.Context.html
|
||||
pub fn new_v6(ts: Timestamp, node_id: &[u8; 6]) -> Self {
|
||||
Builder::from_sorted_rfc4122_timestamp(ts, node_id).into_uuid()
|
||||
let (ticks, counter) = ts.to_rfc4122();
|
||||
|
||||
Builder::from_sorted_rfc4122_timestamp(ticks, counter, node_id).into_uuid()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn encode_sorted_rfc4122_timestamp(
|
||||
ticks: u64,
|
||||
counter: u16,
|
||||
node_id: &[u8; 6],
|
||||
) -> Uuid {
|
||||
let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32;
|
||||
let time_mid = ((ticks >> 12) & 0xFFFF) as u16;
|
||||
let time_low_and_version = ((ticks & 0x0FFF) as u16) | (0x6 << 12);
|
||||
|
||||
let mut d4 = [0; 8];
|
||||
|
||||
d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80;
|
||||
d4[1] = (counter & 0xFF) as u8;
|
||||
d4[2] = node_id[0];
|
||||
d4[3] = node_id[1];
|
||||
d4[4] = node_id[2];
|
||||
d4[5] = node_id[3];
|
||||
d4[6] = node_id[4];
|
||||
d4[7] = node_id[5];
|
||||
|
||||
Uuid::from_fields(time_high, time_mid, time_low_and_version, &d4)
|
||||
}
|
||||
|
||||
pub(crate) const fn decode_sorted_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) {
|
||||
let bytes = uuid.as_bytes();
|
||||
|
||||
let ticks: u64 = ((bytes[0]) as u64) << 52
|
||||
| (bytes[1] as u64) << 44
|
||||
| (bytes[2] as u64) << 36
|
||||
| (bytes[3] as u64) << 28
|
||||
| (bytes[4] as u64) << 20
|
||||
| (bytes[5] as u64) << 12
|
||||
| ((bytes[6] & 0xF) as u64) << 8
|
||||
| (bytes[7] as u64);
|
||||
|
||||
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
|
||||
|
||||
(ticks, counter)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Variant, Version, Context};
|
||||
use crate::{Context, Variant, Version};
|
||||
use std::string::ToString;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
73
src/v7.rs
73
src/v7.rs
@ -3,7 +3,7 @@
|
||||
//! Note that you need to enable the `v7` Cargo feature
|
||||
//! in order to use this module.
|
||||
|
||||
use crate::{Builder, NoContext, rng, timestamp::Timestamp, Uuid};
|
||||
use crate::{rng, timestamp::Timestamp, Builder, NoContext, Uuid};
|
||||
use core::convert::TryInto;
|
||||
|
||||
impl Uuid {
|
||||
@ -23,9 +23,6 @@ impl Uuid {
|
||||
/// Note that usage of this method requires the `v7` feature of this crate
|
||||
/// to be enabled.
|
||||
///
|
||||
/// This method will use millisecond precision for the timestamp and fill the
|
||||
/// rest with random data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit
|
||||
@ -41,26 +38,53 @@ impl Uuid {
|
||||
/// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7")
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// The timestamp can also be created automatically from the current SystemTime
|
||||
///
|
||||
/// ```
|
||||
/// # use uuid::{Uuid, Timestamp, NoContext};
|
||||
/// let ts = Timestamp::now(NoContext);
|
||||
///
|
||||
/// let uuid = Uuid::new_v7(ts);
|
||||
/// ```
|
||||
pub fn new_v7(ts: Timestamp) -> Self {
|
||||
let buf: &[u8] = &rng::bytes()[0..11];
|
||||
let (secs, nanos) = ts.to_unix();
|
||||
let millis = secs.saturating_add(nanos as u64 / 1_000_000);
|
||||
|
||||
Builder::from_timestamp_millis(ts, buf.try_into().unwrap()).into_uuid()
|
||||
Builder::from_unix_timestamp_millis(millis, &rng::bytes()[..10].try_into().unwrap())
|
||||
.into_uuid()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Uuid {
|
||||
let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32;
|
||||
let millis_low = (millis & 0xFFFF) as u16;
|
||||
|
||||
let random_and_version =
|
||||
(random_bytes[0] as u16 | ((random_bytes[1] as u16) << 8) & 0x0FFF) | (0x7 << 12);
|
||||
|
||||
let mut d4 = [0; 8];
|
||||
|
||||
d4[0] = (random_bytes[2] & 0x3F) | 0x80;
|
||||
d4[1] = random_bytes[3];
|
||||
d4[2] = random_bytes[4];
|
||||
d4[3] = random_bytes[5];
|
||||
d4[4] = random_bytes[6];
|
||||
d4[5] = random_bytes[7];
|
||||
d4[6] = random_bytes[8];
|
||||
d4[7] = random_bytes[9];
|
||||
|
||||
Uuid::from_fields(millis_high, millis_low, random_and_version, &d4)
|
||||
}
|
||||
|
||||
pub(crate) const fn decode_unix_timestamp_millis(uuid: &Uuid) -> u64 {
|
||||
let bytes = uuid.as_bytes();
|
||||
|
||||
let millis: u64 = (bytes[0] as u64) << 40
|
||||
| (bytes[1] as u64) << 32
|
||||
| (bytes[2] as u64) << 24
|
||||
| (bytes[3] as u64) << 16
|
||||
| (bytes[4] as u64) << 8
|
||||
| (bytes[5] as u64);
|
||||
|
||||
millis
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Variant, Version, NoContext};
|
||||
use crate::{NoContext, Variant, Version};
|
||||
use std::string::ToString;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen_test::*;
|
||||
@ -81,6 +105,21 @@ mod tests {
|
||||
// Ensure parsing the same UUID produces the same timestamp
|
||||
let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
|
||||
|
||||
assert_eq!(uuid, parsed,);
|
||||
assert_eq!(uuid, parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
|
||||
fn test_new_v7_timestamp_roundtrip() {
|
||||
let time: u64 = 1_496_854_535;
|
||||
let time_fraction: u32 = 812_946_000;
|
||||
|
||||
let ts = Timestamp::from_unix(NoContext, time, time_fraction);
|
||||
|
||||
let uuid = Uuid::new_v7(ts);
|
||||
|
||||
let decoded_ts = uuid.get_timestamp().unwrap();
|
||||
|
||||
assert_eq!(ts.to_unix(), decoded_ts.to_unix());
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
x
Reference in New Issue
Block a user