ps_uuid/methods/from_parts_v7.rs
1use crate::UUID;
2
3// ────────────────────────────────────────────────────────────────────────────
4// v7 constructor from wire-format fields
5// ────────────────────────────────────────────────────────────────────────────
6//
7// Layout (big-endian, RFC-4122-draft, 2022-07-07):
8//
9// 0‥47 unix_ts_ms 48-bit Unix-epoch time in *milliseconds*
10// 48‥51 version 0b0111
11// 52‥63 rand_a 12 random bits
12// 64‥65 variant 0b10 (inserted by `.with_version`)
13// 66‥127 rand_b 62 random bits
14//
15impl UUID {
16 /// Build a Version-7 (time-ordered) UUID from its constituent fields.
17 ///
18 /// Arguments
19 /// - `unix_ts_ms` – milliseconds since 1970-01-01 00:00:00 UTC\
20 /// (only the least-significant 48 bits are used)
21 /// - `rand_a` – 12 bits of random data
22 /// - `rand_b` – 62 additional random bits
23 ///
24 /// The function never fails; it masks super-fluous upper bits and then
25 /// calls `.with_version(7)` which patches in both the version nibble
26 /// **and** the RFC-4122 variant.
27 #[inline]
28 #[must_use]
29 pub fn from_parts_v7(unix_ts_ms: u64, rand_a: u16, rand_b: u64) -> Self {
30 let mut uuid = Self::nil();
31
32 // -----------------------------------------------------------------
33 // 1. 48-bit Unix-timestamp (bytes 0-5)
34 // -----------------------------------------------------------------
35 let ts = unix_ts_ms & 0xFFFF_FFFF_FFFF; // keep 48 bits
36 let ts_be = ts.to_be_bytes(); // 8-byte big-endian
37 uuid.bytes[0..6].copy_from_slice(&ts_be[2..8]);
38
39 // -----------------------------------------------------------------
40 // 2. 12 bits of random data (“rand_a”, bytes 6-7)
41 // -----------------------------------------------------------------
42 uuid.bytes[6..8].copy_from_slice(&rand_a.to_be_bytes());
43
44 // -----------------------------------------------------------------
45 // 3. 62 additional random bits (“rand_b”, bytes 8-15)
46 // -----------------------------------------------------------------
47 uuid.bytes[8..16].copy_from_slice(&rand_b.to_be_bytes());
48
49 // -----------------------------------------------------------------
50 // 4. Patch in version & variant bits
51 // -----------------------------------------------------------------
52 uuid.with_version(7)
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::Variant;
60
61 // Helper ---------------------------------------------------------------
62 const fn version(b: &[u8; 16]) -> u8 {
63 b[6] >> 4
64 }
65 const fn variant(b: &[u8; 16]) -> u8 {
66 b[8] >> 6
67 }
68
69 #[test]
70 fn static_example_is_encoded_correctly() {
71 // ts = 0x0123_4567_89AB (≈ 2023-03-18 01:32:21 UTC)
72 let ts = 0x0123_4567_89ABu64;
73 let ra = 0x0CDEu16; // 12 random bits
74 let rb = 0x0023_4567_89AB_CDEFu64; // 62 random bits
75
76 let uuid = UUID::from_parts_v7(ts, ra, rb);
77 let b = uuid.as_bytes();
78
79 // Timestamp (first 6 bytes)
80 assert_eq!(&b[0..6], &[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB]);
81
82 // rand_a
83 // high nibble patched later by version ⇒ 0x7C
84 assert_eq!(b[6] & 0x0F, 0x0C);
85 assert_eq!(b[7], 0xDE);
86
87 // rand_b (bytes 8-15) – only lower 62 bits should match
88 let mut rb_expected = [0u8; 8];
89 rb_expected.copy_from_slice(&rb.to_be_bytes());
90 // upper 2 bits will be replaced by variant “10”
91 assert_eq!(b[8] & 0x3F, rb_expected[0] & 0x3F);
92 assert_eq!(&b[9..16], &rb_expected[1..8]);
93
94 // Version / Variant
95 assert_eq!(uuid.get_version(), Some(7));
96 assert_eq!(uuid.get_variant(), Variant::OSF);
97 assert_eq!(version(b), 0b0111);
98 assert_eq!(variant(b), 0b10);
99 }
100
101 #[test]
102 fn nil_timestamp_and_zero_random_yields_valid_uuid() {
103 let uuid = UUID::from_parts_v7(0, 0, 0);
104 let mut expected = [0u8; 16];
105 expected[6] = 0x70; // version nibble
106 expected[8] = 0x80; // variant bits
107 assert_eq!(uuid.as_bytes(), &expected);
108 assert_eq!(uuid.get_version(), Some(7));
109 assert_eq!(uuid.get_variant(), Variant::OSF);
110 }
111
112 #[test]
113 fn byte_order_is_big_endian() {
114 let ts = 1u64; // 0x0000_0000_0001
115 let uuid = UUID::from_parts_v7(ts, 0, 0);
116 let b = uuid.as_bytes();
117 assert_eq!(
118 &b[0..6],
119 &[0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
120 "timestamp must be big-endian"
121 );
122 }
123}