/rust/registry/src/index.crates.io-1949cf8c6b5b557f/uuid-1.23.1/src/v7.rs
Line | Count | Source |
1 | | //! The implementation for Version 7 UUIDs. |
2 | | //! |
3 | | //! Note that you need to enable the `v7` Cargo feature |
4 | | //! in order to use this module. |
5 | | |
6 | | use core::cmp; |
7 | | |
8 | | use crate::{rng, timestamp::Timestamp, Builder, Uuid}; |
9 | | |
10 | | impl Uuid { |
11 | | /// Create a new version 7 UUID using the current time value. |
12 | | /// |
13 | | /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time |
14 | | /// as the source timestamp. All UUIDs generated through this method by the same process are |
15 | | /// guaranteed to be ordered by their creation. |
16 | | #[cfg(feature = "std")] |
17 | 35.4k | pub fn now_v7() -> Self { |
18 | 35.4k | Self::new_v7(Timestamp::now( |
19 | 35.4k | crate::timestamp::context::shared_context_v7(), |
20 | | )) |
21 | 35.4k | } |
22 | | |
23 | | /// Create a new version 7 UUID using a time value and random bytes. |
24 | | /// |
25 | | /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`]. |
26 | | /// |
27 | | /// Note that usage of this method requires the `v7` feature of this crate |
28 | | /// to be enabled. |
29 | | /// |
30 | | /// Also see [`Uuid::now_v7`] for a convenient way to generate version 7 |
31 | | /// UUIDs using the current system time. |
32 | | /// |
33 | | /// # Counter treatment |
34 | | /// |
35 | | /// This method accepts a [`Timestamp`] which may include a counter value. |
36 | | /// Only up to 74 bits of the counter value will be used when constructing |
37 | | /// the UUID. Any unused counter bits will be filled with random data. |
38 | | /// |
39 | | /// # Examples |
40 | | /// |
41 | | /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit |
42 | | /// random number. When supplied as such, the data will be combined |
43 | | /// to ensure uniqueness and sortability at millisecond granularity. |
44 | | /// |
45 | | /// ```rust |
46 | | /// # use uuid::{Uuid, Timestamp, NoContext}; |
47 | | /// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234); |
48 | | /// |
49 | | /// let uuid = Uuid::new_v7(ts); |
50 | | /// |
51 | | /// assert!( |
52 | | /// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7") |
53 | | /// ); |
54 | | /// ``` |
55 | | /// |
56 | | /// A v7 UUID can also be created with a counter to ensure batches of |
57 | | /// UUIDs created together remain sortable: |
58 | | /// |
59 | | /// ```rust |
60 | | /// # use uuid::{Uuid, Timestamp, ContextV7}; |
61 | | /// let context = ContextV7::new(); |
62 | | /// let uuid1 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234)); |
63 | | /// let uuid2 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234)); |
64 | | /// |
65 | | /// assert!(uuid1 < uuid2); |
66 | | /// ``` |
67 | | /// |
68 | | /// # References |
69 | | /// |
70 | | /// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7) |
71 | 35.4k | pub fn new_v7(ts: Timestamp) -> Self { |
72 | 35.4k | let (secs, nanos) = ts.to_unix(); |
73 | 35.4k | let millis = secs |
74 | 35.4k | .saturating_mul(1000) |
75 | 35.4k | .saturating_add(nanos as u64 / 1_000_000); |
76 | | |
77 | 35.4k | let (counter, counter_bits) = ts.counter(); |
78 | | |
79 | | // If the counter intersects the variant field then shift around it. |
80 | | // This ensures that any bits set in the counter that would intersect |
81 | | // the variant are still preserved |
82 | 35.4k | let shift_counter_over_variant = |mut counter: u128, mut counter_bits: u32| { |
83 | 35.4k | let mask = u128::MAX << (cmp::min(128, counter_bits) - 12); |
84 | 35.4k | counter = (counter & !mask) | ((counter & mask) << 2); |
85 | 35.4k | counter_bits += 2; |
86 | | |
87 | 35.4k | (counter, counter_bits) |
88 | 35.4k | }; |
89 | | |
90 | | // Mask `counter_bits` of the `counter` into `dst` |
91 | 35.4k | let mask_counter_into_random = |mut dst: u128, counter: u128, counter_bits: u32| { |
92 | 35.4k | dst &= u128::MAX >> counter_bits; |
93 | 35.4k | dst |= counter << (128 - counter_bits); |
94 | | |
95 | 35.4k | dst |
96 | 35.4k | }; |
97 | | |
98 | 35.4k | let counter_and_random = match counter_bits { |
99 | | // The counter doesn't contribute any bits |
100 | 0 | 0 => rng::u128(), |
101 | | // The counter doesn't intersect the variant field |
102 | | // It needs to be merged with random data |
103 | 35.4k | ..12 => { |
104 | 0 | let counter_bits = counter_bits as u32; |
105 | | |
106 | 0 | mask_counter_into_random(rng::u128(), counter, counter_bits) |
107 | | } |
108 | | // `rand_a` (12 bits) + `rand_b` (62 bits) + `var` (2 bits) |
109 | | // The counter needs to be shifted around the variant and merged with random data |
110 | 35.4k | ..74 => { |
111 | 35.4k | let (counter, counter_bits) = |
112 | 35.4k | shift_counter_over_variant(counter, counter_bits as u32); |
113 | | |
114 | 35.4k | mask_counter_into_random(rng::u128(), counter, counter_bits) |
115 | | } |
116 | | // The counter overrides all bits |
117 | 0 | 74.. => { |
118 | 0 | let (counter, _) = shift_counter_over_variant(counter, counter_bits as u32); |
119 | | |
120 | 0 | counter |
121 | | } |
122 | | }; |
123 | | |
124 | 35.4k | Builder::from_unix_timestamp_millis( |
125 | 35.4k | millis, |
126 | 35.4k | &counter_and_random.to_be_bytes()[..10].try_into().unwrap(), |
127 | | ) |
128 | 35.4k | .into_uuid() |
129 | 35.4k | } |
130 | | } |
131 | | |
132 | | #[cfg(test)] |
133 | | mod tests { |
134 | | use super::*; |
135 | | |
136 | | use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version}; |
137 | | |
138 | | #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] |
139 | | use wasm_bindgen_test::*; |
140 | | |
141 | | #[test] |
142 | | #[cfg_attr( |
143 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
144 | | wasm_bindgen_test |
145 | | )] |
146 | | fn test_new() { |
147 | | let ts: u64 = 1645557742000; |
148 | | |
149 | | let seconds = ts / 1000; |
150 | | let nanos = ((ts % 1000) * 1_000_000) as u32; |
151 | | |
152 | | let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos)); |
153 | | let uustr = uuid.hyphenated().to_string(); |
154 | | |
155 | | assert_eq!(uuid.get_version(), Some(Version::SortRand)); |
156 | | assert_eq!(uuid.get_variant(), Variant::RFC4122); |
157 | | assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7")); |
158 | | |
159 | | // Ensure parsing the same UUID produces the same timestamp |
160 | | let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); |
161 | | |
162 | | assert_eq!(uuid, parsed); |
163 | | } |
164 | | |
165 | | #[test] |
166 | | #[cfg_attr( |
167 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
168 | | wasm_bindgen_test |
169 | | )] |
170 | | #[cfg(feature = "std")] |
171 | | fn test_now() { |
172 | | let uuid = Uuid::now_v7(); |
173 | | |
174 | | assert_eq!(uuid.get_version(), Some(Version::SortRand)); |
175 | | assert_eq!(uuid.get_variant(), Variant::RFC4122); |
176 | | } |
177 | | |
178 | | #[test] |
179 | | #[cfg_attr( |
180 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
181 | | wasm_bindgen_test |
182 | | )] |
183 | | fn test_sorting() { |
184 | | let time1: u64 = 1_496_854_535; |
185 | | let time_fraction1: u32 = 812_000_000; |
186 | | |
187 | | let time2 = time1 + 4000; |
188 | | let time_fraction2 = time_fraction1; |
189 | | |
190 | | let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1)); |
191 | | let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2)); |
192 | | |
193 | | assert!(uuid1.as_bytes() < uuid2.as_bytes()); |
194 | | assert!(uuid1.to_string() < uuid2.to_string()); |
195 | | } |
196 | | |
197 | | #[test] |
198 | | #[cfg_attr( |
199 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
200 | | wasm_bindgen_test |
201 | | )] |
202 | | fn test_new_timestamp_roundtrip() { |
203 | | let time: u64 = 1_496_854_535; |
204 | | let time_fraction: u32 = 812_000_000; |
205 | | |
206 | | let ts = Timestamp::from_unix(NoContext, time, time_fraction); |
207 | | |
208 | | let uuid = Uuid::new_v7(ts); |
209 | | |
210 | | let decoded_ts = uuid.get_timestamp().unwrap(); |
211 | | |
212 | | assert_eq!(ts.to_unix(), decoded_ts.to_unix()); |
213 | | } |
214 | | |
215 | | #[test] |
216 | | #[cfg_attr( |
217 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
218 | | wasm_bindgen_test |
219 | | )] |
220 | | fn test_new_max_context() { |
221 | | struct MaxContext; |
222 | | |
223 | | impl ClockSequence for MaxContext { |
224 | | type Output = u128; |
225 | | |
226 | | fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { |
227 | | u128::MAX |
228 | | } |
229 | | |
230 | | fn usable_bits(&self) -> usize { |
231 | | 128 |
232 | | } |
233 | | } |
234 | | |
235 | | let time: u64 = 1_496_854_535; |
236 | | let time_fraction: u32 = 812_000_000; |
237 | | |
238 | | // Ensure we don't overflow here |
239 | | let ts = Timestamp::from_unix(MaxContext, time, time_fraction); |
240 | | |
241 | | let uuid = Uuid::new_v7(ts); |
242 | | |
243 | | assert_eq!(uuid.get_version(), Some(Version::SortRand)); |
244 | | assert_eq!(uuid.get_variant(), Variant::RFC4122); |
245 | | |
246 | | let decoded_ts = uuid.get_timestamp().unwrap(); |
247 | | |
248 | | assert_eq!(ts.to_unix(), decoded_ts.to_unix()); |
249 | | } |
250 | | |
251 | | #[test] |
252 | | #[cfg_attr( |
253 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
254 | | wasm_bindgen_test |
255 | | )] |
256 | | fn test_new_counter_range() { |
257 | | for (width, eq) in [ |
258 | | (0, false), |
259 | | (3, false), |
260 | | (43, false), |
261 | | (74, true), |
262 | | (u8::MAX, true), |
263 | | ] { |
264 | | for counter in [0u128, u128::MAX] { |
265 | | let ts = Timestamp::from_unix_time(1_700_000_000, 0, counter, width); |
266 | | |
267 | | let a = Uuid::new_v7(ts); |
268 | | let b = Uuid::new_v7(ts); |
269 | | |
270 | | assert_eq!((1_700_000_000, 0), a.get_timestamp().unwrap().to_unix()); |
271 | | assert_eq!((1_700_000_000, 0), b.get_timestamp().unwrap().to_unix()); |
272 | | |
273 | | assert_eq!( |
274 | | eq, |
275 | | a == b, |
276 | | "{:>032x} = {:>032x} with counter {counter:x} should be {eq:?}", |
277 | | a.as_u128(), |
278 | | b.as_u128() |
279 | | ); |
280 | | } |
281 | | } |
282 | | } |
283 | | |
284 | | #[test] |
285 | | #[cfg_attr( |
286 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
287 | | wasm_bindgen_test |
288 | | )] |
289 | | fn test_new_max() { |
290 | | let ts = Timestamp::from_unix_time(u64::MAX, 0, 0, 0); |
291 | | let uuid = Uuid::new_v7(ts); |
292 | | |
293 | | let decoded_ts = uuid.get_timestamp().unwrap(); |
294 | | |
295 | | assert_eq!((281474976710, 655000000), decoded_ts.to_unix()); |
296 | | } |
297 | | } |