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