|
1 | 1 | module rand |
2 | 2 |
|
3 | | -const clock_seq_hi_and_reserved_valid_values = [`8`, `9`, `a`, `b`]! |
| 3 | +import time |
4 | 4 |
|
5 | 5 | // uuid_v4 generates a random (v4) UUID |
6 | 6 | // See https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) |
| 7 | +// See https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-4 |
7 | 8 | pub fn uuid_v4() string { |
8 | | - return internal_uuid_v4(mut default_rng) |
| 9 | + rand_1 := default_rng.u64() |
| 10 | + rand_2 := default_rng.u64() |
| 11 | + return internal_uuid(4, rand_1, rand_2) |
9 | 12 | } |
10 | 13 |
|
11 | | -@[direct_array_access] |
12 | | -fn internal_uuid_v4(mut rng PRNG) string { |
13 | | - buflen := 36 |
| 14 | +@[direct_array_access; inline] |
| 15 | +fn internal_uuid(version u8, rand_1 u64, rand_2 u64) string { |
| 16 | + // 0 1 2 3 |
| 17 | + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| 18 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 19 | + // | rand_1 | |
| 20 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 21 | + // | rand_1 | ver | rand_1 | |
| 22 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 23 | + // |var| rand_2 | |
| 24 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 25 | + // | rand_2 | |
| 26 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 27 | + |
| 28 | + mut parts := [8]u16{} |
| 29 | + parts[0] = u16(rand_1 >> 48) |
| 30 | + parts[1] = u16(rand_1 >> 32) |
| 31 | + parts[2] = u16(rand_1 >> 16) |
| 32 | + parts[3] = u16(rand_1) |
| 33 | + parts[4] = u16(rand_2 >> 48) |
| 34 | + parts[5] = u16(rand_2 >> 32) |
| 35 | + parts[6] = u16(rand_2 >> 16) |
| 36 | + parts[7] = u16(rand_2) |
| 37 | + |
| 38 | + parts[3] = (parts[3] & 0x0FFF) | (u16(version) << 12) // set version |
| 39 | + parts[4] = (parts[4] & 0x3FFF) | 0x8000 // set variant = 0b10 |
| 40 | + |
14 | 41 | mut buf := unsafe { malloc_noscan(37) } |
15 | | - mut i_buf := 0 |
16 | | - mut x := u64(0) |
17 | | - mut d := u8(0) |
18 | | - for i_buf < buflen { |
19 | | - mut c := 0 |
20 | | - x = rng.u64() |
21 | | - // do most of the bit manipulation at once: |
22 | | - x &= 0x0F0F0F0F0F0F0F0F |
23 | | - x += 0x3030303030303030 |
24 | | - // write the ASCII codes to the buffer: |
25 | | - for c < 8 && i_buf < buflen { |
26 | | - d = u8(x) |
27 | | - unsafe { |
28 | | - buf[i_buf] = if d > 0x39 { d + 0x27 } else { d } |
| 42 | + mut start := 0 |
| 43 | + unsafe { |
| 44 | + for i in 0 .. 8 { |
| 45 | + val := parts[i] |
| 46 | + buf[start] = hex_chars[(val >> 12) & 0xF] |
| 47 | + buf[start + 1] = hex_chars[(val >> 8) & 0xF] |
| 48 | + buf[start + 2] = hex_chars[(val >> 4) & 0xF] |
| 49 | + buf[start + 3] = hex_chars[val & 0xF] |
| 50 | + start += 4 |
| 51 | + // insert `_` at specified locations |
| 52 | + if start in [8, 13, 18, 23]! { |
| 53 | + buf[start] = `_` |
| 54 | + start++ |
29 | 55 | } |
30 | | - i_buf++ |
31 | | - c++ |
32 | | - x = x >> 8 |
33 | 56 | } |
| 57 | + buf[36] = 0 |
| 58 | + return buf.vstring_with_len(36) |
34 | 59 | } |
35 | | - // there are still some random bits in x: |
36 | | - x = x >> 8 |
37 | | - d = u8(x) |
38 | | - unsafe { |
39 | | - // From https://www.ietf.org/rfc/rfc4122.txt : |
40 | | - // >> Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved |
41 | | - // >> to zero and one, respectively. |
42 | | - // all nibbles starting with 10 are: 1000, 1001, 1010, 1011 -> hex digits `8`, `9`, `a`, `b` |
43 | | - // these are stored in clock_seq_hi_and_reserved_valid_values, choose one of them at random: |
44 | | - buf[19] = clock_seq_hi_and_reserved_valid_values[d & 0x03] |
45 | | - // >> Set the four most significant bits (bits 12 through 15) of the |
46 | | - // >> time_hi_and_version field to the 4-bit version number from Section 4.1.3. |
47 | | - buf[14] = `4` |
48 | | - buf[8] = `-` |
49 | | - buf[13] = `-` |
50 | | - buf[18] = `-` |
51 | | - buf[23] = `-` |
52 | | - buf[buflen] = 0 // ensure the string will be 0 terminated, just in case |
53 | | - // for i in 0..37 { println('i: ${i:2} | ${buf[i].ascii_str()} | ${buf[i].hex()} | ${buf[i]:08b}') } |
54 | | - return buf.vstring_with_len(buflen) |
55 | | - } |
| 60 | +} |
| 61 | + |
| 62 | +// uuid_v7 generates a time-ordered (v7) UUID |
| 63 | +// See https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7 |
| 64 | +pub fn uuid_v7() string { |
| 65 | + timestamp_48 := u64(time.now().unix_milli()) << 16 |
| 66 | + rand_1 := timestamp_48 | default_rng.u16() |
| 67 | + rand_2 := default_rng.u64() |
| 68 | + return internal_uuid(7, rand_1, rand_2) |
| 69 | +} |
| 70 | + |
| 71 | +pub struct UUIDSession { |
| 72 | +mut: |
| 73 | + counter u8 // 6 bits session counter |
| 74 | +} |
| 75 | + |
| 76 | +// new_uuid_v7_session create a new session for generating uuid_v7. |
| 77 | +// The 12 bits `rand_a` in the RFC 9652, is replaced by 6 bits |
| 78 | +// sub-millisecond timestamp + 6 bits session counter. |
| 79 | +// See https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=78c5e141e9c139fc2ff36a220334e4aa25e1b0eb |
| 80 | +pub fn new_uuid_v7_session() UUIDSession { |
| 81 | + return UUIDSession{} |
| 82 | +} |
| 83 | + |
| 84 | +// next get a new uuid_v7 from current session. |
| 85 | +pub fn (mut u UUIDSession) next() string { |
| 86 | + timestamp := u64(time.now().unix_nano()) |
| 87 | + // make place for holding 4 bits `version` |
| 88 | + timestamp_shift_4bits := (timestamp & 0xFFFF_FFFF_FFFF_0000) | ((timestamp & 0x0000_0000_0000_FFFF) >> 4) |
| 89 | + rand_1 := (timestamp_shift_4bits & 0xFFFF_FFFF_FFFF_FFC0) | u64(u.counter & 0x3F) // 6 bits session counter |
| 90 | + rand_2 := default_rng.u64() |
| 91 | + |
| 92 | + u.counter++ |
| 93 | + |
| 94 | + return internal_uuid(7, rand_1, rand_2) |
56 | 95 | } |
57 | 96 |
|
58 | 97 | const ulid_encoding = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' |
|
0 commit comments