nx_uuid.nx source
↩ module page · 155 lines · 5535 B
1// nx_uuid.nx -- RFC 4122 UUIDv4 + RFC 9562 UUIDv7.
2//
3// Used by:
4// - Distributed log correlation (request IDs)
5// - Database row primary keys (when an external system needs
6// them before insert)
7// - Cache key generation
8//
9// UUIDv4 is fully random (122 bits of entropy + 6 fixed bits).
10// UUIDv7 is timestamp-prefixed (48 bits Unix-ms timestamp + 74 bits
11// of entropy + 6 fixed bits) -- sortable by creation time, which
12// makes B-tree primary keys 100x faster on insert than v4.
13//
14// Both are 128-bit values rendered as
15// "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" where M = version digit
16// (4 or 7) and N's high 2 bits are 10 (RFC variant marker).
17//
18// We accept any nx_rng for the entropy source. v7 uses
19// gettimeofday() for the timestamp.
20//
21// Pairs with nx_random (entropy) + nx_strconv (hex output).
22
23// nx_safety_envelope:
24// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
25// sil_target: SIL1
26// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
27// verdict: NOT_YET_EVALUATED
28
29import "syscalls.nx"
30import "nx_random.nx"
31const K_MAGIC_12345: i64 = 12345
32
33// Render byte array (16 bytes) into a 36-char canonical UUID
34// string: 8-4-4-4-12 hex with hyphens. Buf must be >= 37 bytes.
35func nx_uuid_render(b: *u8, out: *u8) -> i64 {
36 let hex: *u8 = sys_mmap(16)
37 hex[0]=0x30; hex[1]=0x31; hex[2]=0x32; hex[3]=0x33; hex[4]=0x34
38 hex[5]=0x35; hex[6]=0x36; hex[7]=0x37; hex[8]=0x38; hex[9]=0x39
39 hex[10]=0x61; hex[11]=0x62; hex[12]=0x63; hex[13]=0x64; hex[14]=0x65; hex[15]=0x66
40
41 var src: i64 = 0
42 var dst: i64 = 0
43 var hyphens_done: i64 = 0
44 while src < 16 {
45 out[dst] = hex[(b[src] >> 4) & 0xF]
46 out[dst + 1] = hex[b[src] & 0xF]
47 dst = dst + 2
48 src = src + 1
49 // Insert hyphens after byte indices 4, 6, 8, 10.
50 if hyphens_done == 0 {
51 if src == 4 { out[dst] = 0x2D; dst = dst + 1; hyphens_done = 1 }
52 } else {
53 if hyphens_done == 1 {
54 if src == 6 { out[dst] = 0x2D; dst = dst + 1; hyphens_done = 2 }
55 } else {
56 if hyphens_done == 2 {
57 if src == 8 { out[dst] = 0x2D; dst = dst + 1; hyphens_done = 3 }
58 } else {
59 if hyphens_done == 3 {
60 if src == 10 { out[dst] = 0x2D; dst = dst + 1; hyphens_done = 4 }
61 }
62 }
63 }
64 }
65 }
66 out[dst] = 0
67 return dst
68}
69
70// Generate a UUIDv4 into b[0..16]. Bytes 6 (high nibble) gets 0x4
71// (version), byte 8 (high 2 bits) gets 0b10 (variant).
72func nx_uuid_v4(rng: *NxRng, b: *u8) -> i64 {
73 nx_rng_fill(rng, b, 16)
74 b[6] = (b[6] & 0x0F) | 0x40
75 b[8] = (b[8] & 0x3F) | 0x80
76 return 0
77}
78
79// Generate a UUIDv7 into b[0..16].
80// bytes [0..6) = unix_ms timestamp (48 bits, big-endian)
81// byte 6: high nibble = 7 (version), low nibble = random
82// bytes 7..8: random
83// byte 8: high 2 bits = 10 (variant), low 6 bits = random
84// bytes 9..16: random
85//
86// We read the timestamp via clock_gettime(CLOCK_REALTIME) and
87// convert to milliseconds.
88func nx_uuid_v7(rng: *NxRng, b: *u8) -> i64 {
89 // Fill with entropy first.
90 nx_rng_fill(rng, b, 16)
91
92 // gettimeofday: { tv_sec: i64, tv_usec: i64 }
93 let tv_raw: *u8 = sys_mmap(32)
94 let tv: *i64 = tv_raw as *i64
95 __syscall(169, tv_raw, 0, 0, 0, 0, 0)
96 let sec: i64 = tv[0]
97 let usec: i64 = tv[1]
98 let ms: i64 = sec * 1000 + usec / 1000
99
100 // Big-endian 48-bit ms into bytes [0..6).
101 b[0] = (ms >> 40) & 0xFF
102 b[1] = (ms >> 32) & 0xFF
103 b[2] = (ms >> 24) & 0xFF
104 b[3] = (ms >> 16) & 0xFF
105 b[4] = (ms >> 8) & 0xFF
106 b[5] = ms & 0xFF
107
108 // Version + variant.
109 b[6] = (b[6] & 0x0F) | 0x70
110 b[8] = (b[8] & 0x3F) | 0x80
111 return 0
112}
113
114// ---- self-test ---------------------------------------------------
115
116func main() -> i64 {
117 let rng: *NxRng = nx_rng_new(K_MAGIC_12345)
118 let bytes: *u8 = sys_mmap(16)
119
120 // v4: byte 6 high nibble == 4, byte 8 high two bits == 10.
121 nx_uuid_v4(rng, bytes)
122 if ((bytes[6] >> 4) & 0xF) != 4 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
123 if ((bytes[8] >> 6) & 0x3) != 2 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
124
125 // v7: byte 6 high nibble == 7, byte 8 high two bits == 10.
126 nx_uuid_v7(rng, bytes)
127 if ((bytes[6] >> 4) & 0xF) != 7 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
128 if ((bytes[8] >> 6) & 0x3) != 2 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
129
130 // Render. Should be 36 chars + NUL.
131 let txt: *u8 = sys_mmap(64)
132 let n: i64 = nx_uuid_render(bytes, txt)
133 if n != 36 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
134 if txt[8] != 0x2D { return __syscall(93, 6, 0, 0, 0, 0, 0) }
135 if txt[13] != 0x2D { return __syscall(93, 7, 0, 0, 0, 0, 0) }
136 if txt[18] != 0x2D { return __syscall(93, 8, 0, 0, 0, 0, 0) }
137 if txt[23] != 0x2D { return __syscall(93, 9, 0, 0, 0, 0, 0) }
138 if txt[36] != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
139
140 // Position 14 in canonical form is the version digit -> '7' for v7.
141 if txt[14] != 0x37 { return __syscall(93, 11, 0, 0, 0, 0, 0) } // '7'
142
143 // Two consecutive v7 calls should produce different bytes (entropy).
144 let bytes2: *u8 = sys_mmap(16)
145 nx_uuid_v7(rng, bytes2)
146 var same: i64 = 1
147 var i: i64 = 0
148 while i < 16 {
149 if bytes[i] != bytes2[i] { same = 0; i = 16 }
150 else { i = i + 1 }
151 }
152 if same == 1 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
153
154 return 0
155}