1use std::cell::Cell;
2use std::fmt;
3
4use crate::timestamp::Timestamp;
5use crate::{de::Deserialize, impl_st, ser::Serialize, AlgebraicType, AlgebraicValue};
6use uuid::{Builder, Uuid as UUID, Variant};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9#[repr(u8)]
10pub enum Version {
11 Nil = 0u8,
13 V4 = 4,
15 V7 = 7,
17 Max = 0xff,
19}
20
21#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
25#[sats(crate = crate)]
26pub struct Uuid {
27 __uuid__: u128,
28}
29
30impl Uuid {
31 pub const NIL: Self = Self {
45 __uuid__: UUID::nil().as_u128(),
46 };
47
48 pub const MAX: Self = Self {
61 __uuid__: UUID::max().as_u128(),
62 };
63
64 pub fn from_random_bytes_v4(counter_random_bytes: [u8; 16]) -> Self {
83 Self {
84 __uuid__: Builder::from_random_bytes(counter_random_bytes).into_uuid().as_u128(),
85 }
86 }
87
88 pub fn from_counter_v7(counter: &Cell<u32>, now: Timestamp, random_bytes: &[u8; 4]) -> anyhow::Result<Self> {
127 let counter_val = counter.get();
129 counter.set(counter_val.wrapping_add(1) & 0x7FFF_FFFF);
130
131 let ts_ms = now
132 .to_duration_since_unix_epoch()
133 .expect("timestamp before unix epoch")
134 .as_millis() as i64
135 & 0xFFFFFFFFFFFF;
136
137 let mut bytes = [0u8; 16];
138
139 bytes[0] = (ts_ms >> 40) as u8;
141 bytes[1] = (ts_ms >> 32) as u8;
142 bytes[2] = (ts_ms >> 24) as u8;
143 bytes[3] = (ts_ms >> 16) as u8;
144 bytes[4] = (ts_ms >> 8) as u8;
145 bytes[5] = ts_ms as u8;
146
147 bytes[7] = ((counter_val >> 23) & 0xFF) as u8;
153 bytes[9] = ((counter_val >> 15) & 0xFF) as u8;
154 bytes[10] = ((counter_val >> 7) & 0xFF) as u8;
155 bytes[11] = ((counter_val & 0x7F) << 1) as u8;
156
157 bytes[12] |= random_bytes[0] & 0x7F;
159 bytes[13] = random_bytes[1];
160 bytes[14] = random_bytes[2];
161 bytes[15] = random_bytes[3];
162
163 let uuid = Builder::from_bytes(bytes)
164 .with_variant(Variant::RFC4122)
165 .with_version(uuid::Version::SortRand)
166 .into_uuid();
167
168 Ok(Self {
169 __uuid__: uuid.as_u128(),
170 })
171 }
172
173 #[cfg(test)]
175 fn get_counter(&self) -> i32 {
176 let bytes: [u8; 16] = self.__uuid__.to_be_bytes();
177
178 let high = bytes[7] as u32; let mid1 = bytes[9] as u32; let mid2 = bytes[10] as u32; let low = (bytes[11] as u32) >> 1; ((high << 23) | (mid1 << 15) | (mid2 << 7) | low) as i32
185 }
186
187 pub fn parse_str(s: &str) -> Result<Self, uuid::Error> {
204 Ok(Self {
205 __uuid__: UUID::parse_str(s)?.as_u128(),
206 })
207 }
208
209 pub fn get_version(&self) -> Option<Version> {
215 match self.to_uuid().get_version() {
216 Some(uuid::Version::Nil) => Some(Version::Nil),
217 Some(uuid::Version::Random) => Some(Version::V4),
218 Some(uuid::Version::SortRand) => Some(Version::V7),
219 Some(uuid::Version::Max) => Some(Version::Max),
220 _ => None,
221 }
222 }
223
224 #[cfg(test)]
225 fn get_variant(&self) -> Variant {
226 self.to_uuid().get_variant()
227 }
228
229 pub fn to_uuid(self) -> UUID {
231 UUID::from_u128(self.__uuid__)
232 }
233
234 pub fn from_u128(u: u128) -> Self {
235 Self { __uuid__: u }
236 }
237
238 pub fn as_u128(&self) -> u128 {
239 self.__uuid__
240 }
241}
242
243impl_st!([] Uuid, AlgebraicType::uuid());
244
245impl fmt::Display for Uuid {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "{}", self.to_uuid())
248 }
249}
250
251impl From<Uuid> for AlgebraicValue {
252 fn from(value: Uuid) -> Self {
253 AlgebraicValue::product([value.as_u128().into()])
254 }
255}
256
257#[cfg(test)]
258mod test {
259 use super::*;
260 use crate::timestamp::Timestamp;
261 use crate::GroundSpacetimeType;
262 use rand::RngCore;
263
264 #[test]
265 fn uuid_type_matches() {
266 assert_eq!(AlgebraicType::uuid(), Uuid::get_type());
267 assert!(Uuid::get_type().is_uuid());
268 assert!(Uuid::get_type().is_special());
269 }
270
271 #[test]
272 fn round_trip() {
273 let u1 = Uuid::NIL;
274 let s = u1.to_string();
275 let u2 = Uuid::parse_str(&s).unwrap();
276 assert_eq!(u1, u2);
277 assert_eq!(u1.as_u128(), u2.as_u128());
278 assert_eq!(u1.to_uuid(), u2.to_uuid());
279 assert_eq!(s, u2.to_string());
280 }
281
282 #[test]
283 fn to_string() {
284 for u in [
285 Uuid::NIL,
286 Uuid::from_u128(0x0102_0304_0506_0708_090a_0b0c_0d0e_0f10_u128),
287 Uuid::MAX,
288 ] {
289 let s = u.to_string();
290 let u2 = Uuid::parse_str(&s).unwrap();
291 assert_eq!(u, u2);
292 }
293 }
294
295 #[test]
296 fn version() {
297 let u_nil = Uuid::NIL;
298 assert_eq!(u_nil.get_version(), Some(Version::Nil));
299
300 let u_max = Uuid::MAX;
301 assert_eq!(u_max.get_version(), Some(Version::Max));
302
303 assert_eq!(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, u128::MAX);
304
305 let u_v4 = Uuid::from_random_bytes_v4([0u8; 16]);
306 assert_eq!(u_v4.get_version(), Some(Version::V4));
307
308 let counter = Cell::new(0);
309 let ts = Timestamp::from_micros_since_unix_epoch(1_686_000_000_000);
310 let u_v7 = Uuid::from_counter_v7(&counter, ts, &[0u8; 4]).unwrap();
311 assert_eq!(u_v7.get_version(), Some(Version::V7));
312 }
313
314 #[test]
315 fn wrap_around() {
316 let counter = Cell::new(u32::MAX);
318 let ts = Timestamp::now();
319 let _u1 = Uuid::from_counter_v7(&counter, ts, &[0u8; 4]).unwrap();
320 assert_eq!(0, counter.get());
321 }
322
323 #[test]
324 #[should_panic(expected = "timestamp before unix epoch")]
325 fn negative_timestamp_panics() {
326 let counter = Cell::new(0);
327 let ts = Timestamp::from_micros_since_unix_epoch(-1);
328 let _u = Uuid::from_counter_v7(&counter, ts, &[0u8; 4]).unwrap();
329 }
330
331 #[test]
332 fn ordered() {
333 let u1 = Uuid::from_u128(1);
334 let u2 = Uuid::from_u128(2);
335 assert!(u1 < u2);
336 assert!(u2 > u1);
337 assert_eq!(u1, u1);
338 assert_ne!(u1, u2);
339 let counter = Cell::new(0);
341 let ts = Timestamp::now();
342 let u_start = Uuid::from_counter_v7(&counter, ts, &[0u8; 4]).unwrap();
343 assert_eq!(u_start.get_counter(), 0);
344 let total = 10_000_000;
346 let counter = Cell::new(u32::MAX - total);
347 let ts = Timestamp::now();
348 let uuids = (0..total)
349 .map(|_| {
350 let mut bytes = [0u8; 4];
351 rand::rng().fill_bytes(&mut bytes);
352 Uuid::from_counter_v7(&counter, ts, &bytes).unwrap()
353 })
354 .collect::<Vec<Uuid>>();
355
356 for (pos, pair) in uuids.windows(2).enumerate() {
357 assert_eq!(pair[0].get_version(), Some(Version::V7));
358 assert_eq!(pair[0].get_variant(), Variant::RFC4122);
359 assert!(
360 pair[0] < pair[1],
361 "UUIDs are not ordered at {pos}: {} !< {}",
362 pair[0],
363 pair[1]
364 );
365
366 assert!(
367 pair[0].get_counter() < pair[1].get_counter(),
368 "UUID counters are not ordered at {pos}: {} !< {}",
369 pair[0].get_counter(),
370 pair[1].get_counter()
371 );
372 }
373 }
374}