浏览代码

Add version 1 UUID generation

Feoramund 1 年之前
父节点
当前提交
525bfca4ef

+ 3 - 0
core/encoding/uuid/definitions.odin

@@ -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

+ 44 - 0
core/encoding/uuid/generation.odin

@@ -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.
 

+ 55 - 0
core/encoding/uuid/reading.odin

@@ -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.
 

+ 31 - 0
tests/core/encoding/uuid/test_core_uuid.odin

@@ -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()