Skip to main content

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}