diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin index 1afa810a2..bf41b4a8c 100644 --- a/core/encoding/uuid/generation.odin +++ b/core/encoding/uuid/generation.odin @@ -11,25 +11,27 @@ 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. +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. Returns: - result: The generated UUID. */ -generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil) -> (result: Identifier) { +generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = 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 + unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100 - 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] + uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals) + uuid_timestamp_octets := transmute([8]u8)uuid_timestamp - result[6] = timestamp_octets[6] >> 4 - result[7] = timestamp_octets[6] << 4 | timestamp_octets[7] + result[0] = uuid_timestamp_octets[0] + result[1] = uuid_timestamp_octets[1] + result[2] = uuid_timestamp_octets[2] + result[3] = uuid_timestamp_octets[3] + result[4] = uuid_timestamp_octets[4] + result[5] = uuid_timestamp_octets[5] + + result[6] = uuid_timestamp_octets[6] >> 4 + result[7] = uuid_timestamp_octets[6] << 4 | uuid_timestamp_octets[7] if realized_node, ok := node.?; ok { mutable_node := realized_node @@ -79,12 +81,13 @@ Inputs: If unspecified, it will be replaced with random bits. - 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. +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. Returns: - result: The generated UUID. */ -generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil) -> (result: Identifier) { - unix_time_in_hns_intervals := time.to_unix_nanoseconds(time.now()) / 100 +generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { + unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100 timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals) @@ -128,12 +131,15 @@ bits and a 48 bit timestamp. It is designed with time-based sorting in mind, such as for database usage, as the highest bits are allocated from the timestamp of when it is created. +Inputs: +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. + Returns: - result: The generated UUID. */ -generate_v7 :: proc() -> (result: Identifier) { +generate_v7_basic :: proc(timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) - unix_time_in_milliseconds := time.to_unix_nanoseconds(time.now()) / 1e6 + unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6 temporary := cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT @@ -152,7 +158,7 @@ generate_v7 :: proc() -> (result: Identifier) { } /* -Generate a version 7 UUID with an incremented counter. +Generate a version 7 UUID that has an incremented counter. This UUID will be pseudorandom, save for 6 pre-determined version and variant bits, a 48 bit timestamp, and 12 bits of counter state. @@ -178,14 +184,15 @@ Example: Inputs: - counter: A 12-bit value, incremented each time a UUID is generated in a batch. +- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time. Returns: - result: The generated UUID. */ -generate_v7_counter :: proc(counter: u16) -> (result: Identifier) { +generate_v7_with_counter :: proc(counter: u16, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) { assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR) assert(counter <= 0x0fff, "This implementation of the version 7 UUID does not support counters in excess of 12 bits (4,095).") - unix_time_in_milliseconds := time.to_unix_nanoseconds(time.now()) / 1e6 + unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6 temporary := cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT temporary |= cast(u128be)counter << VERSION_7_COUNTER_SHIFT @@ -203,3 +210,8 @@ generate_v7_counter :: proc(counter: u16) -> (result: Identifier) { return } + +generate_v7 :: proc { + generate_v7_basic, + generate_v7_with_counter, +} diff --git a/core/encoding/uuid/reading.odin b/core/encoding/uuid/reading.odin index 3b2c48011..c32f69eb7 100644 --- a/core/encoding/uuid/reading.odin +++ b/core/encoding/uuid/reading.odin @@ -1,6 +1,7 @@ package uuid import "base:runtime" +import "core:time" /* Convert a string to a UUID. @@ -127,7 +128,7 @@ node :: proc "contextless" (id: Identifier) -> (node: [6]u8) { } /* -Get the timestamp of a version 1 UUID. +Get the raw timestamp of a version 1 UUID. Inputs: - id: The identifier. @@ -135,7 +136,7 @@ Inputs: Returns: - timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15. */ -time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { +raw_time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { timestamp_octets: [8]u8 timestamp_octets[0] = id[0] @@ -151,6 +152,39 @@ time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { return cast(u64)transmute(u64le)timestamp_octets } + +/* +Get the timestamp of a version 1 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp of the UUID. +*/ +time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) { + delta := cast(time.Duration)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100 + return time.time_add({}, delta) +} + +/* +Get the raw timestamp of a version 6 UUID. + +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15. +*/ +raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { + temporary := transmute(u128be)id + + timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68) + timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64) + + return timestamp +} + /* Get the timestamp of a version 6 UUID. @@ -160,13 +194,23 @@ Inputs: Returns: - timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15. */ -time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { - temporary := transmute(u128be)id +time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) { + delta := cast(time.Duration)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100 + return time.time_add({}, delta) +} - timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68) - timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64) +/* +Get the raw timestamp of a version 7 UUID. - return timestamp +Inputs: +- id: The identifier. + +Returns: +- timestamp: The timestamp, in milliseconds since the UNIX epoch. +*/ +raw_time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { + time_bits := transmute(u128be)id & VERSION_7_TIME_MASK + return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT) } /* @@ -178,9 +222,8 @@ Inputs: Returns: - timestamp: The timestamp, in milliseconds since the UNIX epoch. */ -time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) { - time_bits := transmute(u128be)id & VERSION_7_TIME_MASK - return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT) +time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) { + return time.time_add({}, cast(time.Duration)(raw_time_v7(id) * 1e6)) } /* diff --git a/tests/core/encoding/uuid/test_core_uuid.odin b/tests/core/encoding/uuid/test_core_uuid.odin index a8f563e13..c7beb67d3 100644 --- a/tests/core/encoding/uuid/test_core_uuid.odin +++ b/tests/core/encoding/uuid/test_core_uuid.odin @@ -67,87 +67,86 @@ test_legacy_namespaced_uuids :: proc(t: ^testing.T) { test_v1 :: proc(t: ^testing.T) { context.random_generator = crypto.random_generator() + point_a := time.time_add({}, 1 * time.Second) + point_b := time.time_add({}, 3 * time.Second) + point_c := time.time_add({}, 5 * time.Second) + 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) + v1_a := uuid.generate_v1(CLOCK, nil, point_a) + v1_b := uuid.generate_v1(CLOCK, nil, point_b) + v1_c := uuid.generate_v1(CLOCK, nil, point_c) 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 := uuid.time_v1(v1_a) + time_b := uuid.time_v1(v1_b) + time_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, %v", v1_a, time_a) + log.debugf("B: %02x, %v", v1_b, time_b) + log.debugf("C: %02x, %v", v1_c, time_c) - 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.") + testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v1 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v1 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v1 UUID is earlier than its successor.") } @(test) test_v6 :: proc(t: ^testing.T) { context.random_generator = crypto.random_generator() + point_a := time.time_add({}, 1 * time.Second) + point_b := time.time_add({}, 3 * time.Second) + point_c := time.time_add({}, 5 * time.Second) + CLOCK :: 0x3A1A - v6_a := uuid.generate_v6(CLOCK) - time.sleep(10 * time.Millisecond) - v6_b := uuid.generate_v6(CLOCK) - time.sleep(10 * time.Millisecond) - v6_c := uuid.generate_v6(CLOCK) + v6_a := uuid.generate_v6(CLOCK, nil, point_a) + v6_b := uuid.generate_v6(CLOCK, nil, point_b) + v6_c := uuid.generate_v6(CLOCK, nil, point_c) testing.expect_value(t, uuid.clock_seq(v6_a), CLOCK) - time_bits_a := uuid.time_v6(v6_a) - time_bits_b := uuid.time_v6(v6_b) - time_bits_c := uuid.time_v6(v6_c) + time_a := uuid.time_v6(v6_a) + time_b := uuid.time_v6(v6_b) + time_c := uuid.time_v6(v6_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, %v", v6_a, time_a) + log.debugf("B: %02x, %v", v6_b, time_b) + log.debugf("C: %02x, %v", v6_c, time_c) - log.debugf("A: %02x, %i, %v", v6_a, time_bits_a, time_a) - log.debugf("B: %02x, %i, %v", v6_b, time_bits_b, time_b) - log.debugf("C: %02x, %i, %v", v6_c, time_bits_c, time_c) - - testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v6 UUID are lesser than the earlier UUID.") - testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v6 UUID are lesser than the earlier UUID.") - testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v6 UUID are lesser than the earlier UUID.") + testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v6 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v6 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v6 UUID is earlier than its successor.") } @(test) test_v7 :: proc(t: ^testing.T) { context.random_generator = crypto.random_generator() - v7_a := uuid.generate_v7() - time.sleep(10 * time.Millisecond) - v7_b := uuid.generate_v7() - time.sleep(10 * time.Millisecond) - v7_c := uuid.generate_v7() + point_a := time.time_add({}, 1 * time.Second) + point_b := time.time_add({}, 3 * time.Second) + point_c := time.time_add({}, 5 * time.Second) - time_bits_a := uuid.time_v7(v7_a) - time_bits_b := uuid.time_v7(v7_b) - time_bits_c := uuid.time_v7(v7_c) + v7_a := uuid.generate_v7(point_a) + v7_b := uuid.generate_v7(point_b) + v7_c := uuid.generate_v7(point_c) - log.debugf("A: %02x, %i", v7_a, time_bits_a) - log.debugf("B: %02x, %i", v7_b, time_bits_b) - log.debugf("C: %02x, %i", v7_c, time_bits_c) + time_a := uuid.time_v7(v7_a) + time_b := uuid.time_v7(v7_b) + time_c := uuid.time_v7(v7_c) - testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.") - testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.") - testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.") + log.debugf("A: %02x, %v", v7_a, time_a) + log.debugf("B: %02x, %v", v7_b, time_b) + log.debugf("C: %02x, %v", v7_c, time_c) - v7_with_counter := uuid.generate_v7_counter(0x555) + testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v7 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v7 UUID is earlier than its successor.") + testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v7 UUID is earlier than its successor.") + + v7_with_counter := uuid.generate_v7(0x555) log.debugf("D: %02x", v7_with_counter) testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555) + } @(test)