mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-18 12:30:28 +00:00
Add vendor-specific version 8 UUID generation (hashing)
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -81,6 +84,17 @@ test_timestamps :: proc(t: ^testing.T) {
|
||||
max_time_ms_resolution, v7_counter_time)
|
||||
}
|
||||
|
||||
@(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"
|
||||
|
||||
Reference in New Issue
Block a user