Add version 1 UUID generation

This commit is contained in:
Feoramund
2024-06-21 17:55:27 -04:00
parent 4cfbd83b10
commit 525bfca4ef
4 changed files with 133 additions and 0 deletions

View File

@@ -8,6 +8,9 @@ EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4
VERSION_BYTE_INDEX :: 6
VARIANT_BYTE_INDEX :: 8
// The number of 100-nanosecond intervals between 1582-10-15 and 1970-01-01.
HNS_INTERVALS_BETWEEN_GREG_AND_UNIX :: 141427 * 24 * 60 * 60 * 1000 * 1000 * 10
VERSION_7_TIME_MASK :: 0xffffffff_ffff0000_00000000_00000000
VERSION_7_TIME_SHIFT :: 80
VERSION_7_COUNTER_MASK :: 0x00000000_00000fff_00000000_00000000

View File

@@ -6,6 +6,50 @@ import "core:math/rand"
import "core:mem"
import "core:time"
/*
Generate a version 1 UUID.
Inputs:
- clock_seq: The clock sequence, a number which must be initialized to a random number once in the lifetime of a system.
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
If one is not provided or available, 48 bits of random state will take its place.
Returns:
- result: The generated UUID.
*/
generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil) -> (result: Identifier) {
assert(clock_seq <= 0x3FFF, "The clock sequence can only hold 14 bits of data; no number greater than 16,383.")
unix_time_in_hns_intervals := time.to_unix_nanoseconds(time.now()) / 100
timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
timestamp_octets := transmute([8]u8)timestamp
result[0] = timestamp_octets[0]
result[1] = timestamp_octets[1]
result[2] = timestamp_octets[2]
result[3] = timestamp_octets[3]
result[4] = timestamp_octets[4]
result[5] = timestamp_octets[5]
result[6] = timestamp_octets[6] >> 4
result[7] = timestamp_octets[6] << 4 | timestamp_octets[7]
if realized_node, ok := node.?; ok {
mutable_node := realized_node
mem.copy_non_overlapping(&result[10], &mutable_node[0], 6)
} else {
bytes_generated := rand.read(result[10:])
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
}
result[VERSION_BYTE_INDEX] |= 0x10
result[VARIANT_BYTE_INDEX] |= 0x80
result[8] |= cast(u8)(clock_seq & 0x3F00 >> 8)
result[9] = cast(u8)clock_seq
return
}
/*
Generate a version 3 UUID.

View File

@@ -1,5 +1,7 @@
package uuid
import "base:runtime"
/*
Convert a string to a UUID.
@@ -96,6 +98,59 @@ variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bo
}
}
/*
Get the clock sequence of a version 1 UUID.
Inputs:
- id: The identifier.
Returns:
- clock_seq: The 14-bit clock sequence field.
*/
clock_seq :: proc "contextless" (id: Identifier) -> (clock_seq: u16) {
return cast(u16)id[9] | cast(u16)id[8] & 0x3F << 8
}
/*
Get the node of a version 1 UUID.
Inputs:
- id: The identifier.
Returns:
- node: The 48-bit spatially unique identifier.
*/
node :: proc "contextless" (id: Identifier) -> (node: [6]u8) {
mutable_id := id
runtime.mem_copy_non_overlapping(&node, &mutable_id[10], 6)
return
}
/*
Get the timestamp of a version 1 UUID.
Inputs:
- id: The identifier.
Returns:
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
*/
time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
timestamp_octets: [8]u8
timestamp_octets[0] = id[0]
timestamp_octets[1] = id[1]
timestamp_octets[2] = id[2]
timestamp_octets[3] = id[3]
timestamp_octets[4] = id[4]
timestamp_octets[5] = id[5]
timestamp_octets[6] = id[6] << 4 | id[7] >> 4
timestamp_octets[7] = id[7] & 0xF
return cast(u64)transmute(u64le)timestamp_octets
}
/*
Get the timestamp of a version 7 UUID.

View File

@@ -7,11 +7,14 @@ import "core:time"
@(test)
test_version_and_variant :: proc(t: ^testing.T) {
v1 := uuid.generate_v1(0)
v3 := uuid.generate_v3(uuid.Namespace_DNS, "")
v4 := uuid.generate_v4()
v5 := uuid.generate_v5(uuid.Namespace_DNS, "")
v7 := uuid.generate_v7()
testing.expect_value(t, uuid.version(v1), 1)
testing.expect_value(t, uuid.variant(v1), uuid.Variant_Type.RFC_4122)
testing.expect_value(t, uuid.version(v3), 3)
testing.expect_value(t, uuid.variant(v3), uuid.Variant_Type.RFC_4122)
testing.expect_value(t, uuid.version(v4), 4)
@@ -53,6 +56,34 @@ test_namespaced_uuids :: proc(t: ^testing.T) {
}
}
@(test)
test_v1 :: proc(t: ^testing.T) {
CLOCK :: 0x3A1A
v1_a := uuid.generate_v1(CLOCK)
time.sleep(10 * time.Millisecond)
v1_b := uuid.generate_v1(CLOCK)
time.sleep(10 * time.Millisecond)
v1_c := uuid.generate_v1(CLOCK)
testing.expect_value(t, uuid.clock_seq(v1_a), CLOCK)
time_bits_a := uuid.time_v1(v1_a)
time_bits_b := uuid.time_v1(v1_b)
time_bits_c := uuid.time_v1(v1_c)
time_a := time.Time { _nsec = cast(i64)((time_bits_a - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
time_b := time.Time { _nsec = cast(i64)((time_bits_b - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
time_c := time.Time { _nsec = cast(i64)((time_bits_c - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
log.debugf("A: %02x, %i, %v", v1_a, time_bits_a, time_a)
log.debugf("B: %02x, %i, %v", v1_b, time_bits_b, time_b)
log.debugf("C: %02x, %i, %v", v1_c, time_bits_c, time_c)
testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v1 UUID are lesser than the earlier UUID.")
testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v1 UUID are lesser than the earlier UUID.")
testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v1 UUID are lesser than the earlier UUID.")
}
@(test)
test_v7 :: proc(t: ^testing.T) {
v7_a := uuid.generate_v7()