/rust/registry/src/index.crates.io-1949cf8c6b5b557f/uuid-1.8.0/src/timestamp.rs
Line | Count | Source |
1 | | //! Generating UUIDs from timestamps. |
2 | | //! |
3 | | //! Timestamps are used in a few UUID versions as a source of decentralized |
4 | | //! uniqueness (as in versions 1 and 6), and as a way to enable sorting (as |
5 | | //! in versions 6 and 7). Timestamps aren't encoded the same way by all UUID |
6 | | //! versions so this module provides a single [`Timestamp`] type that can |
7 | | //! convert between them. |
8 | | //! |
9 | | //! # Timestamp representations in UUIDs |
10 | | //! |
11 | | //! Versions 1 and 6 UUIDs use a bespoke timestamp that consists of the |
12 | | //! number of 100ns ticks since `1582-10-15 00:00:00`, along with |
13 | | //! a counter value to avoid duplicates. |
14 | | //! |
15 | | //! Version 7 UUIDs use a more standard timestamp that consists of the |
16 | | //! number of millisecond ticks since the Unix epoch (`1970-01-01 00:00:00`). |
17 | | //! |
18 | | //! # References |
19 | | //! |
20 | | //! * [Timestamp in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4) |
21 | | //! * [Timestamp in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-6.1) |
22 | | |
23 | | use crate::Uuid; |
24 | | |
25 | | /// The number of 100 nanosecond ticks between the RFC4122 epoch |
26 | | /// (`1582-10-15 00:00:00`) and the Unix epoch (`1970-01-01 00:00:00`). |
27 | | pub const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; |
28 | | |
29 | | /// A timestamp that can be encoded into a UUID. |
30 | | /// |
31 | | /// This type abstracts the specific encoding, so versions 1, 6, and 7 |
32 | | /// UUIDs can both be supported through the same type, even |
33 | | /// though they have a different representation of a timestamp. |
34 | | /// |
35 | | /// # References |
36 | | /// |
37 | | /// * [Timestamp in RFC4122](https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4) |
38 | | /// * [Timestamp in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-6.1) |
39 | | /// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) |
40 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
41 | | pub struct Timestamp { |
42 | | pub(crate) seconds: u64, |
43 | | pub(crate) nanos: u32, |
44 | | #[cfg(any(feature = "v1", feature = "v6"))] |
45 | | pub(crate) counter: u16, |
46 | | } |
47 | | |
48 | | impl Timestamp { |
49 | | /// Get a timestamp representing the current system time. |
50 | | /// |
51 | | /// This method defers to the standard library's `SystemTime` type. |
52 | | /// |
53 | | /// # Panics |
54 | | /// |
55 | | /// This method will panic if calculating the elapsed time since the Unix epoch fails. |
56 | | #[cfg(feature = "std")] |
57 | 0 | pub fn now(context: impl ClockSequence<Output = u16>) -> Self { |
58 | | #[cfg(not(any(feature = "v1", feature = "v6")))] |
59 | 0 | { |
60 | 0 | let _ = context; |
61 | 0 | } |
62 | | |
63 | 0 | let (seconds, nanos) = now(); |
64 | | |
65 | 0 | Timestamp { |
66 | 0 | seconds, |
67 | 0 | nanos, |
68 | 0 | #[cfg(any(feature = "v1", feature = "v6"))] |
69 | 0 | counter: context.generate_sequence(seconds, nanos), |
70 | 0 | } |
71 | 0 | } |
72 | | |
73 | | /// Construct a `Timestamp` from an RFC4122 timestamp and counter, as used |
74 | | /// in versions 1 and 6 UUIDs. |
75 | | /// |
76 | | /// # Overflow |
77 | | /// |
78 | | /// If conversion from RFC4122 ticks to the internal timestamp format would overflow |
79 | | /// it will wrap. |
80 | 0 | pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { |
81 | | #[cfg(not(any(feature = "v1", feature = "v6")))] |
82 | 0 | { |
83 | 0 | let _ = counter; |
84 | 0 | } |
85 | | |
86 | 0 | let (seconds, nanos) = Self::rfc4122_to_unix(ticks); |
87 | | |
88 | 0 | Timestamp { |
89 | 0 | seconds, |
90 | 0 | nanos, |
91 | 0 | #[cfg(any(feature = "v1", feature = "v6"))] |
92 | 0 | counter, |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | | /// Construct a `Timestamp` from a Unix timestamp, as used in version 7 UUIDs. |
97 | | /// |
98 | | /// # Overflow |
99 | | /// |
100 | | /// If conversion from RFC4122 ticks to the internal timestamp format would overflow |
101 | | /// it will wrap. |
102 | 0 | pub fn from_unix(context: impl ClockSequence<Output = u16>, seconds: u64, nanos: u32) -> Self { |
103 | | #[cfg(not(any(feature = "v1", feature = "v6")))] |
104 | | { |
105 | 0 | let _ = context; |
106 | | |
107 | 0 | Timestamp { seconds, nanos } |
108 | | } |
109 | | #[cfg(any(feature = "v1", feature = "v6"))] |
110 | | { |
111 | | let counter = context.generate_sequence(seconds, nanos); |
112 | | |
113 | | Timestamp { |
114 | | seconds, |
115 | | nanos, |
116 | | counter, |
117 | | } |
118 | | } |
119 | 0 | } |
120 | | |
121 | | /// Get the value of the timestamp as an RFC4122 timestamp and counter, |
122 | | /// as used in versions 1 and 6 UUIDs. |
123 | | /// |
124 | | /// # Overflow |
125 | | /// |
126 | | /// If conversion from RFC4122 ticks to the internal timestamp format would overflow |
127 | | /// it will wrap. |
128 | | #[cfg(any(feature = "v1", feature = "v6"))] |
129 | | pub const fn to_rfc4122(&self) -> (u64, u16) { |
130 | | ( |
131 | | Self::unix_to_rfc4122_ticks(self.seconds, self.nanos), |
132 | | self.counter, |
133 | | ) |
134 | | } |
135 | | |
136 | | /// Get the value of the timestamp as a Unix timestamp, as used in version 7 UUIDs. |
137 | | /// |
138 | | /// # Overflow |
139 | | /// |
140 | | /// If conversion from RFC4122 ticks to the internal timestamp format would overflow |
141 | | /// it will wrap. |
142 | 0 | pub const fn to_unix(&self) -> (u64, u32) { |
143 | 0 | (self.seconds, self.nanos) |
144 | 0 | } |
145 | | |
146 | | #[cfg(any(feature = "v1", feature = "v6"))] |
147 | | const fn unix_to_rfc4122_ticks(seconds: u64, nanos: u32) -> u64 { |
148 | | UUID_TICKS_BETWEEN_EPOCHS |
149 | | .wrapping_add(seconds.wrapping_mul(10_000_000)) |
150 | | .wrapping_add(nanos as u64 / 100) |
151 | | } |
152 | | |
153 | 0 | const fn rfc4122_to_unix(ticks: u64) -> (u64, u32) { |
154 | 0 | ( |
155 | 0 | ticks.wrapping_sub(UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000, |
156 | 0 | (ticks.wrapping_sub(UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 * 100, |
157 | 0 | ) |
158 | 0 | } |
159 | | |
160 | | #[deprecated(note = "use `to_unix` instead; this method will be removed in a future release")] |
161 | | /// Get the number of fractional nanoseconds in the Unix timestamp. |
162 | | /// |
163 | | /// This method is deprecated and probably doesn't do what you're expecting it to. |
164 | | /// It doesn't return the timestamp as nanoseconds since the Unix epoch, it returns |
165 | | /// the fractional seconds of the timestamp. |
166 | 0 | pub const fn to_unix_nanos(&self) -> u32 { |
167 | 0 | panic!("`Timestamp::to_unix_nanos` is deprecated and will be removed: use `Timestamp::to_unix` instead") |
168 | | } |
169 | | } |
170 | | |
171 | 0 | pub(crate) const fn encode_rfc4122_timestamp(ticks: u64, counter: u16, node_id: &[u8; 6]) -> Uuid { |
172 | 0 | let time_low = (ticks & 0xFFFF_FFFF) as u32; |
173 | 0 | let time_mid = ((ticks >> 32) & 0xFFFF) as u16; |
174 | 0 | let time_high_and_version = (((ticks >> 48) & 0x0FFF) as u16) | (1 << 12); |
175 | | |
176 | 0 | let mut d4 = [0; 8]; |
177 | | |
178 | 0 | d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80; |
179 | 0 | d4[1] = (counter & 0xFF) as u8; |
180 | 0 | d4[2] = node_id[0]; |
181 | 0 | d4[3] = node_id[1]; |
182 | 0 | d4[4] = node_id[2]; |
183 | 0 | d4[5] = node_id[3]; |
184 | 0 | d4[6] = node_id[4]; |
185 | 0 | d4[7] = node_id[5]; |
186 | | |
187 | 0 | Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4) |
188 | 0 | } |
189 | | |
190 | 0 | pub(crate) const fn decode_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) { |
191 | 0 | let bytes = uuid.as_bytes(); |
192 | | |
193 | 0 | let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56 |
194 | 0 | | (bytes[7] as u64) << 48 |
195 | 0 | | (bytes[4] as u64) << 40 |
196 | 0 | | (bytes[5] as u64) << 32 |
197 | 0 | | (bytes[0] as u64) << 24 |
198 | 0 | | (bytes[1] as u64) << 16 |
199 | 0 | | (bytes[2] as u64) << 8 |
200 | 0 | | (bytes[3] as u64); |
201 | | |
202 | 0 | let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); |
203 | | |
204 | 0 | (ticks, counter) |
205 | 0 | } |
206 | | |
207 | 0 | pub(crate) const fn encode_sorted_rfc4122_timestamp( |
208 | 0 | ticks: u64, |
209 | 0 | counter: u16, |
210 | 0 | node_id: &[u8; 6], |
211 | 0 | ) -> Uuid { |
212 | 0 | let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32; |
213 | 0 | let time_mid = ((ticks >> 12) & 0xFFFF) as u16; |
214 | 0 | let time_low_and_version = ((ticks & 0x0FFF) as u16) | (0x6 << 12); |
215 | | |
216 | 0 | let mut d4 = [0; 8]; |
217 | | |
218 | 0 | d4[0] = (((counter & 0x3F00) >> 8) as u8) | 0x80; |
219 | 0 | d4[1] = (counter & 0xFF) as u8; |
220 | 0 | d4[2] = node_id[0]; |
221 | 0 | d4[3] = node_id[1]; |
222 | 0 | d4[4] = node_id[2]; |
223 | 0 | d4[5] = node_id[3]; |
224 | 0 | d4[6] = node_id[4]; |
225 | 0 | d4[7] = node_id[5]; |
226 | | |
227 | 0 | Uuid::from_fields(time_high, time_mid, time_low_and_version, &d4) |
228 | 0 | } |
229 | | |
230 | 0 | pub(crate) const fn decode_sorted_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) { |
231 | 0 | let bytes = uuid.as_bytes(); |
232 | | |
233 | 0 | let ticks: u64 = ((bytes[0]) as u64) << 52 |
234 | 0 | | (bytes[1] as u64) << 44 |
235 | 0 | | (bytes[2] as u64) << 36 |
236 | 0 | | (bytes[3] as u64) << 28 |
237 | 0 | | (bytes[4] as u64) << 20 |
238 | 0 | | (bytes[5] as u64) << 12 |
239 | 0 | | ((bytes[6] & 0xF) as u64) << 8 |
240 | 0 | | (bytes[7] as u64); |
241 | | |
242 | 0 | let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16); |
243 | | |
244 | 0 | (ticks, counter) |
245 | 0 | } |
246 | | |
247 | 0 | pub(crate) const fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Uuid { |
248 | 0 | let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; |
249 | 0 | let millis_low = (millis & 0xFFFF) as u16; |
250 | | |
251 | 0 | let random_and_version = |
252 | 0 | (random_bytes[1] as u16 | ((random_bytes[0] as u16) << 8) & 0x0FFF) | (0x7 << 12); |
253 | | |
254 | 0 | let mut d4 = [0; 8]; |
255 | | |
256 | 0 | d4[0] = (random_bytes[2] & 0x3F) | 0x80; |
257 | 0 | d4[1] = random_bytes[3]; |
258 | 0 | d4[2] = random_bytes[4]; |
259 | 0 | d4[3] = random_bytes[5]; |
260 | 0 | d4[4] = random_bytes[6]; |
261 | 0 | d4[5] = random_bytes[7]; |
262 | 0 | d4[6] = random_bytes[8]; |
263 | 0 | d4[7] = random_bytes[9]; |
264 | | |
265 | 0 | Uuid::from_fields(millis_high, millis_low, random_and_version, &d4) |
266 | 0 | } |
267 | | |
268 | 0 | pub(crate) const fn decode_unix_timestamp_millis(uuid: &Uuid) -> u64 { |
269 | 0 | let bytes = uuid.as_bytes(); |
270 | | |
271 | 0 | let millis: u64 = (bytes[0] as u64) << 40 |
272 | 0 | | (bytes[1] as u64) << 32 |
273 | 0 | | (bytes[2] as u64) << 24 |
274 | 0 | | (bytes[3] as u64) << 16 |
275 | 0 | | (bytes[4] as u64) << 8 |
276 | 0 | | (bytes[5] as u64); |
277 | | |
278 | 0 | millis |
279 | 0 | } |
280 | | |
281 | | #[cfg(all( |
282 | | feature = "std", |
283 | | feature = "js", |
284 | | all( |
285 | | target_arch = "wasm32", |
286 | | target_vendor = "unknown", |
287 | | target_os = "unknown" |
288 | | ) |
289 | | ))] |
290 | | fn now() -> (u64, u32) { |
291 | | use wasm_bindgen::prelude::*; |
292 | | |
293 | | #[wasm_bindgen] |
294 | | extern "C" { |
295 | | // NOTE: This signature works around https://bugzilla.mozilla.org/show_bug.cgi?id=1787770 |
296 | | #[wasm_bindgen(js_namespace = Date, catch)] |
297 | | fn now() -> Result<f64, JsValue>; |
298 | | } |
299 | | |
300 | | let now = now().unwrap_throw(); |
301 | | |
302 | | let secs = (now / 1_000.0) as u64; |
303 | | let nanos = ((now % 1_000.0) * 1_000_000.0) as u32; |
304 | | |
305 | | (secs, nanos) |
306 | | } |
307 | | |
308 | | #[cfg(all( |
309 | | feature = "std", |
310 | | any( |
311 | | not(feature = "js"), |
312 | | not(all( |
313 | | target_arch = "wasm32", |
314 | | target_vendor = "unknown", |
315 | | target_os = "unknown" |
316 | | )) |
317 | | ) |
318 | | ))] |
319 | 0 | fn now() -> (u64, u32) { |
320 | 0 | let dur = std::time::SystemTime::UNIX_EPOCH.elapsed().expect( |
321 | 0 | "Getting elapsed time since UNIX_EPOCH. If this fails, we've somehow violated causality", |
322 | | ); |
323 | | |
324 | 0 | (dur.as_secs(), dur.subsec_nanos()) |
325 | 0 | } |
326 | | |
327 | | /// A counter that can be used by version 1 and version 6 UUIDs to support |
328 | | /// the uniqueness of timestamps. |
329 | | /// |
330 | | /// # References |
331 | | /// |
332 | | /// * [Clock Sequence in RFC4122](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.5) |
333 | | pub trait ClockSequence { |
334 | | /// The type of sequence returned by this counter. |
335 | | type Output; |
336 | | |
337 | | /// Get the next value in the sequence to feed into a timestamp. |
338 | | /// |
339 | | /// This method will be called each time a [`Timestamp`] is constructed. |
340 | | fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output; |
341 | | } |
342 | | |
343 | | impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { |
344 | | type Output = T::Output; |
345 | 0 | fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> Self::Output { |
346 | 0 | (**self).generate_sequence(seconds, subsec_nanos) |
347 | 0 | } |
348 | | } |
349 | | |
350 | | /// Default implementations for the [`ClockSequence`] trait. |
351 | | pub mod context { |
352 | | use super::ClockSequence; |
353 | | |
354 | | #[cfg(any(feature = "v1", feature = "v6"))] |
355 | | use atomic::{Atomic, Ordering}; |
356 | | |
357 | | /// An empty counter that will always return the value `0`. |
358 | | /// |
359 | | /// This type should be used when constructing timestamps for version 7 UUIDs, |
360 | | /// since they don't need a counter for uniqueness. |
361 | | #[derive(Debug, Clone, Copy, Default)] |
362 | | pub struct NoContext; |
363 | | |
364 | | impl ClockSequence for NoContext { |
365 | | type Output = u16; |
366 | | |
367 | 0 | fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { |
368 | 0 | 0 |
369 | 0 | } |
370 | | } |
371 | | |
372 | | #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))] |
373 | | static CONTEXT: Context = Context { |
374 | | count: Atomic::new(0), |
375 | | }; |
376 | | |
377 | | #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))] |
378 | | static CONTEXT_INITIALIZED: Atomic<bool> = Atomic::new(false); |
379 | | |
380 | | #[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))] |
381 | | pub(crate) fn shared_context() -> &'static Context { |
382 | | // If the context is in its initial state then assign it to a random value |
383 | | // It doesn't matter if multiple threads observe `false` here and initialize the context |
384 | | if CONTEXT_INITIALIZED |
385 | | .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) |
386 | | .is_ok() |
387 | | { |
388 | | CONTEXT.count.store(crate::rng::u16(), Ordering::Release); |
389 | | } |
390 | | |
391 | | &CONTEXT |
392 | | } |
393 | | |
394 | | /// A thread-safe, wrapping counter that produces 14-bit numbers. |
395 | | /// |
396 | | /// This type should be used when constructing version 1 and version 6 UUIDs. |
397 | | #[derive(Debug)] |
398 | | #[cfg(any(feature = "v1", feature = "v6"))] |
399 | | pub struct Context { |
400 | | count: Atomic<u16>, |
401 | | } |
402 | | |
403 | | #[cfg(any(feature = "v1", feature = "v6"))] |
404 | | impl Context { |
405 | | /// Construct a new context that's initialized with the given value. |
406 | | /// |
407 | | /// The starting value should be a random number, so that UUIDs from |
408 | | /// different systems with the same timestamps are less likely to collide. |
409 | | /// When the `rng` feature is enabled, prefer the [`Context::new_random`] method. |
410 | | pub const fn new(count: u16) -> Self { |
411 | | Self { |
412 | | count: Atomic::<u16>::new(count), |
413 | | } |
414 | | } |
415 | | |
416 | | /// Construct a new context that's initialized with a random value. |
417 | | #[cfg(feature = "rng")] |
418 | | pub fn new_random() -> Self { |
419 | | Self { |
420 | | count: Atomic::<u16>::new(crate::rng::u16()), |
421 | | } |
422 | | } |
423 | | } |
424 | | |
425 | | #[cfg(any(feature = "v1", feature = "v6"))] |
426 | | impl ClockSequence for Context { |
427 | | type Output = u16; |
428 | | |
429 | | fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { |
430 | | // RFC4122 reserves 2 bits of the clock sequence so the actual |
431 | | // maximum value is smaller than `u16::MAX`. Since we unconditionally |
432 | | // increment the clock sequence we want to wrap once it becomes larger |
433 | | // than what we can represent in a "u14". Otherwise there'd be patches |
434 | | // where the clock sequence doesn't change regardless of the timestamp |
435 | | self.count.fetch_add(1, Ordering::AcqRel) & (u16::MAX >> 2) |
436 | | } |
437 | | } |
438 | | } |
439 | | |
440 | | #[cfg(all(test, any(feature = "v1", feature = "v6")))] |
441 | | mod tests { |
442 | | use super::*; |
443 | | |
444 | | #[cfg(all( |
445 | | target_arch = "wasm32", |
446 | | target_vendor = "unknown", |
447 | | target_os = "unknown" |
448 | | ))] |
449 | | use wasm_bindgen_test::*; |
450 | | |
451 | | #[test] |
452 | | #[cfg_attr( |
453 | | all( |
454 | | target_arch = "wasm32", |
455 | | target_vendor = "unknown", |
456 | | target_os = "unknown" |
457 | | ), |
458 | | wasm_bindgen_test |
459 | | )] |
460 | | fn rfc4122_unix_does_not_panic() { |
461 | | // Ensure timestamp conversions never panic |
462 | | Timestamp::unix_to_rfc4122_ticks(u64::MAX, 0); |
463 | | Timestamp::unix_to_rfc4122_ticks(0, u32::MAX); |
464 | | Timestamp::unix_to_rfc4122_ticks(u64::MAX, u32::MAX); |
465 | | |
466 | | Timestamp::rfc4122_to_unix(u64::MAX); |
467 | | } |
468 | | } |