aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-22 16:51:37 -0400
committerFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-22 18:21:32 -0400
commite9b882be058f08f014ea0e598c18b5df05a86a8a (patch)
treede81e329c56202e9410d476c9b57b14d481ad658
parent859cbf7d72ffc080f6e0224980bbed7458f1d07e (diff)
Add vendor-specific version 8 UUID generation (hashing)
-rw-r--r--core/encoding/uuid/generation.odin116
-rw-r--r--tests/core/encoding/uuid/test_core_uuid.odin14
2 files changed, 130 insertions, 0 deletions
diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin
index 427d5243e..cf29df378 100644
--- a/core/encoding/uuid/generation.odin
+++ b/core/encoding/uuid/generation.odin
@@ -1,6 +1,7 @@
package uuid
import "base:runtime"
+import "core:crypto/hash"
import "core:math/rand"
import "core:time"
@@ -215,3 +216,118 @@ generate_v7 :: proc {
generate_v7_basic,
generate_v7_with_counter,
}
+
+/*
+Generate a version 8 UUID using a specific hashing algorithm.
+
+This UUID is generated by hashing a name with a namespace.
+
+Note that all version 8 UUIDs are for experimental or vendor-specific use
+cases, per the specification. This use case in particular is for offering a
+non-legacy alternative to UUID versions 3 and 5.
+
+Inputs:
+- namespace: An `Identifier` that is used to represent the underlying namespace.
+ This can be any one of the `Namespace_*` values provided in this package.
+- name: The byte slice which will be hashed with the namespace.
+- algorithm: A hashing algorithm from `core:crypto/hash`.
+
+Returns:
+- result: The generated UUID.
+
+Example:
+ import "core:crypto/hash"
+ import "core:encoding/uuid"
+ import "core:fmt"
+
+ main :: proc() {
+ my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
+ my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
+ fmt.println(my_uuid_string)
+ }
+
+Output:
+
+ 3730f688-4bff-8dce-9cbf-74a3960c5703
+
+*/
+generate_v8_hash_bytes :: proc(
+ namespace: Identifier,
+ name: []byte,
+ algorithm: hash.Algorithm,
+) -> (
+ result: Identifier,
+) {
+ // 128 bytes should be enough for the foreseeable future.
+ digest: [128]byte
+
+ assert(hash.DIGEST_SIZES[algorithm] >= 16, "Per RFC 9562, the hashing algorithm used must generate a digest of 128 bits or larger.")
+ assert(hash.DIGEST_SIZES[algorithm] < len(digest), "Digest size is too small for this algorithm. The buffer must be increased.")
+
+ hash_context: hash.Context
+ hash.init(&hash_context, algorithm)
+
+ mutable_namespace := namespace
+ hash.update(&hash_context, mutable_namespace[:])
+ hash.update(&hash_context, name[:])
+ hash.final(&hash_context, digest[:])
+
+ runtime.mem_copy_non_overlapping(&result, &digest, 16)
+
+ result[VERSION_BYTE_INDEX] &= 0x0F
+ result[VERSION_BYTE_INDEX] |= 0x80
+
+ result[VARIANT_BYTE_INDEX] &= 0x3F
+ result[VARIANT_BYTE_INDEX] |= 0x80
+
+ return
+}
+
+/*
+Generate a version 8 UUID using a specific hashing algorithm.
+
+This UUID is generated by hashing a name with a namespace.
+
+Note that all version 8 UUIDs are for experimental or vendor-specific use
+cases, per the specification. This use case in particular is for offering a
+non-legacy alternative to UUID versions 3 and 5.
+
+Inputs:
+- namespace: An `Identifier` that is used to represent the underlying namespace.
+ This can be any one of the `Namespace_*` values provided in this package.
+- name: The string which will be hashed with the namespace.
+- algorithm: A hashing algorithm from `core:crypto/hash`.
+
+Returns:
+- result: The generated UUID.
+
+Example:
+ import "core:crypto/hash"
+ import "core:encoding/uuid"
+ import "core:fmt"
+
+ main :: proc() {
+ my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
+ my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
+ fmt.println(my_uuid_string)
+ }
+
+Output:
+
+ 3730f688-4bff-8dce-9cbf-74a3960c5703
+
+*/
+generate_v8_hash_string :: proc(
+ namespace: Identifier,
+ name: string,
+ algorithm: hash.Algorithm,
+) -> (
+ result: Identifier,
+) {
+ return generate_v8_hash_bytes(namespace, transmute([]byte)name, algorithm)
+}
+
+generate_v8_hash :: proc {
+ generate_v8_hash_bytes,
+ generate_v8_hash_string,
+}
diff --git a/tests/core/encoding/uuid/test_core_uuid.odin b/tests/core/encoding/uuid/test_core_uuid.odin
index 3a0d621ca..aeb73c841 100644
--- a/tests/core/encoding/uuid/test_core_uuid.odin
+++ b/tests/core/encoding/uuid/test_core_uuid.odin
@@ -18,6 +18,7 @@ test_version_and_variant :: proc(t: ^testing.T) {
v5 := uuid_legacy.generate_v5(uuid.Namespace_DNS, "")
v6 := uuid.generate_v6()
v7 := uuid.generate_v7()
+ v8 := uuid.generate_v8_hash(uuid.Namespace_DNS, "", .SHA512)
testing.expect_value(t, uuid.version(v1), 1)
testing.expect_value(t, uuid.variant(v1), uuid.Variant_Type.RFC_4122)
@@ -31,6 +32,8 @@ test_version_and_variant :: proc(t: ^testing.T) {
testing.expect_value(t, uuid.variant(v6), uuid.Variant_Type.RFC_4122)
testing.expect_value(t, uuid.version(v7), 7)
testing.expect_value(t, uuid.variant(v7), uuid.Variant_Type.RFC_4122)
+ testing.expect_value(t, uuid.version(v8), 8)
+ testing.expect_value(t, uuid.variant(v8), uuid.Variant_Type.RFC_4122)
}
@(test)
@@ -82,6 +85,17 @@ test_timestamps :: proc(t: ^testing.T) {
}
@(test)
+test_v8_hash_implementation :: proc(t: ^testing.T) {
+ // This example and its results are derived from RFC 9562.
+ // https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv8-value-n
+
+ id := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.example.com", .SHA256)
+ id_str := uuid.to_string(id)
+ defer delete(id_str)
+ testing.expect_value(t, id_str, "5c146b14-3c52-8afd-938a-375d0df1fbf6")
+}
+
+@(test)
test_legacy_namespaced_uuids :: proc(t: ^testing.T) {
TEST_NAME :: "0123456789ABCDEF0123456789ABCDEF"