uuid/v7.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
//! The implementation for Version 7 UUIDs.
//!
//! Note that you need to enable the `v7` Cargo feature
//! in order to use this module.
use crate::{rng, std::convert::TryInto, timestamp::Timestamp, Builder, Uuid};
impl Uuid {
/// Create a new version 7 UUID using the current time value.
///
/// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time
/// as the source timestamp. All UUIDs generated through this method by the same process are
/// guaranteed to be ordered by their creation.
#[cfg(feature = "std")]
pub fn now_v7() -> Self {
Self::new_v7(Timestamp::now(
crate::timestamp::context::shared_context_v7(),
))
}
/// Create a new version 7 UUID using a time value and random bytes.
///
/// When the `std` feature is enabled, you can also use [`Uuid::now_v7`].
///
/// Note that usage of this method requires the `v7` feature of this crate
/// to be enabled.
///
/// Also see [`Uuid::now_v7`] for a convenient way to generate version 7
/// UUIDs using the current system time.
///
/// # Examples
///
/// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit
/// random number. When supplied as such, the data will be
///
/// ```rust
/// # use uuid::{Uuid, Timestamp, NoContext};
/// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234);
///
/// let uuid = Uuid::new_v7(ts);
///
/// assert!(
/// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7")
/// );
/// ```
///
/// A v7 UUID can also be created with a counter to ensure batches of
/// UUIDs created together remain sortable:
///
/// ```rust
/// # use uuid::{Uuid, Timestamp, ContextV7};
/// let context = ContextV7::new();
/// let uuid1 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234));
/// let uuid2 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234));
///
/// assert!(uuid1 < uuid2);
/// ```
///
/// # References
///
/// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7)
pub fn new_v7(ts: Timestamp) -> Self {
let (secs, nanos) = ts.to_unix();
let millis = (secs * 1000).saturating_add(nanos as u64 / 1_000_000);
let mut counter_and_random = rng::u128();
let (mut counter, counter_bits) = ts.counter();
debug_assert!(counter_bits <= 128);
let mut counter_bits = counter_bits as u32;
// If the counter intersects the variant field then shift around it.
// This ensures that any bits set in the counter that would intersect
// the variant are still preserved
if counter_bits > 12 {
let mask = u128::MAX << (counter_bits - 12);
counter = (counter & !mask) | ((counter & mask) << 2);
counter_bits += 2;
}
counter_and_random &= u128::MAX.overflowing_shr(counter_bits).0;
counter_and_random |= counter
.overflowing_shl(128u32.saturating_sub(counter_bits))
.0;
Builder::from_unix_timestamp_millis(
millis,
&counter_and_random.to_be_bytes()[..10].try_into().unwrap(),
)
.into_uuid()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version};
#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
use wasm_bindgen_test::*;
#[test]
#[cfg_attr(
all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
),
wasm_bindgen_test
)]
fn test_new() {
let ts: u64 = 1645557742000;
let seconds = ts / 1000;
let nanos = ((ts % 1000) * 1_000_000) as u32;
let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos));
let uustr = uuid.hyphenated().to_string();
assert_eq!(uuid.get_version(), Some(Version::SortRand));
assert_eq!(uuid.get_variant(), Variant::RFC4122);
assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7"));
// Ensure parsing the same UUID produces the same timestamp
let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
assert_eq!(uuid, parsed);
}
#[test]
#[cfg_attr(
all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
),
wasm_bindgen_test
)]
#[cfg(feature = "std")]
fn test_now() {
let uuid = Uuid::now_v7();
assert_eq!(uuid.get_version(), Some(Version::SortRand));
assert_eq!(uuid.get_variant(), Variant::RFC4122);
}
#[test]
#[cfg_attr(
all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
),
wasm_bindgen_test
)]
fn test_sorting() {
let time1: u64 = 1_496_854_535;
let time_fraction1: u32 = 812_000_000;
let time2 = time1 + 4000;
let time_fraction2 = time_fraction1;
let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1));
let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2));
assert!(uuid1.as_bytes() < uuid2.as_bytes());
assert!(uuid1.to_string() < uuid2.to_string());
}
#[test]
#[cfg_attr(
all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
),
wasm_bindgen_test
)]
fn test_new_timestamp_roundtrip() {
let time: u64 = 1_496_854_535;
let time_fraction: u32 = 812_000_000;
let ts = Timestamp::from_unix(NoContext, time, time_fraction);
let uuid = Uuid::new_v7(ts);
let decoded_ts = uuid.get_timestamp().unwrap();
assert_eq!(ts.to_unix(), decoded_ts.to_unix());
}
#[test]
#[cfg_attr(
all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
),
wasm_bindgen_test
)]
fn test_new_max_context() {
struct MaxContext;
#[cfg(test)]
impl ClockSequence for MaxContext {
type Output = u128;
fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
u128::MAX
}
fn usable_bits(&self) -> usize {
128
}
}
let time: u64 = 1_496_854_535;
let time_fraction: u32 = 812_000_000;
// Ensure we don't overflow here
let ts = Timestamp::from_unix(MaxContext, time, time_fraction);
let uuid = Uuid::new_v7(ts);
assert_eq!(uuid.get_version(), Some(Version::SortRand));
assert_eq!(uuid.get_variant(), Variant::RFC4122);
let decoded_ts = uuid.get_timestamp().unwrap();
assert_eq!(ts.to_unix(), decoded_ts.to_unix());
}
}