diff --git a/core/encoding/uuid/definitions.odin b/core/encoding/uuid/definitions.odin index b54965e23..a63e72693 100644 --- a/core/encoding/uuid/definitions.odin +++ b/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 diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin index 7fe0bbd13..29944dcb5 100644 --- a/core/encoding/uuid/generation.odin +++ b/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. diff --git a/core/encoding/uuid/reading.odin b/core/encoding/uuid/reading.odin index c72f5791e..f31ae2bcd 100644 --- a/core/encoding/uuid/reading.odin +++ b/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. diff --git a/tests/core/encoding/uuid/test_core_uuid.odin b/tests/core/encoding/uuid/test_core_uuid.odin index b5e8e90cc..f934d13dc 100644 --- a/tests/core/encoding/uuid/test_core_uuid.odin +++ b/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()