mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-19 21:10:30 +00:00
Merge pull request #3792 from Feoramund/core-uuid
Add `core:encoding/uuid`
This commit is contained in:
28
core/encoding/uuid/LICENSE
Normal file
28
core/encoding/uuid/LICENSE
Normal file
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
67
core/encoding/uuid/definitions.odin
Normal file
67
core/encoding/uuid/definitions.odin
Normal file
@@ -0,0 +1,67 @@
|
||||
package uuid
|
||||
|
||||
// A RFC 4122 Universally Unique Identifier
|
||||
Identifier :: distinct [16]u8
|
||||
|
||||
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
|
||||
VERSION_7_COUNTER_SHIFT :: 64
|
||||
|
||||
@(private)
|
||||
NO_CSPRNG_ERROR :: "The context random generator is not cryptographic. See the documentation for an example of how to set one up."
|
||||
@(private)
|
||||
BIG_CLOCK_ERROR :: "The clock sequence can only hold 14 bits of data, therefore no number greater than 16,383 (0x3FFF)."
|
||||
@(private)
|
||||
VERSION_7_BIG_COUNTER_ERROR :: "This implementation of the version 7 UUID counter can only hold 12 bits of data, therefore no number greater than 4,095 (0xFFF)."
|
||||
|
||||
Read_Error :: enum {
|
||||
None,
|
||||
Invalid_Length,
|
||||
Invalid_Hexadecimal,
|
||||
Invalid_Separator,
|
||||
}
|
||||
|
||||
Variant_Type :: enum {
|
||||
Unknown,
|
||||
Reserved_Apollo_NCS, // 0b0xx
|
||||
RFC_4122, // 0b10x
|
||||
Reserved_Microsoft_COM, // 0b110
|
||||
Reserved_Future, // 0b111
|
||||
}
|
||||
|
||||
// Name string is a fully-qualified domain name.
|
||||
@(rodata)
|
||||
Namespace_DNS := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is a URL.
|
||||
@(rodata)
|
||||
Namespace_URL := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is an ISO OID.
|
||||
@(rodata)
|
||||
Namespace_OID := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is an X.500 DN (in DER or a text output format).
|
||||
@(rodata)
|
||||
Namespace_X500 := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
46
core/encoding/uuid/doc.odin
Normal file
46
core/encoding/uuid/doc.odin
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
package uuid implements Universally Unique Identifiers according to the
|
||||
standard originally outlined in RFC 4122 with additions from RFC 9562.
|
||||
|
||||
The UUIDs are textually represented and read in the following string format:
|
||||
`00000000-0000-v000-V000-000000000000`
|
||||
|
||||
`v` is where the version bits reside, and `V` is where the variant bits reside.
|
||||
The meaning of the other bits is version-dependent.
|
||||
|
||||
Outside of string representations, UUIDs are represented in memory by a 128-bit
|
||||
structure organized as an array of 16 bytes.
|
||||
|
||||
|
||||
Of the UUID versions which may make use of random number generation, a
|
||||
requirement is placed upon them that the underlying generator be
|
||||
cryptographically-secure, per RFC 9562's suggestion.
|
||||
|
||||
- Version 1 without a node argument.
|
||||
- Version 4 in all cases.
|
||||
- Version 6 without either a clock or node argument.
|
||||
- Version 7 in all cases.
|
||||
|
||||
Here's an example of how to set up one:
|
||||
|
||||
import "core:crypto"
|
||||
import "core:encoding/uuid"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid: uuid.Identifier
|
||||
|
||||
{
|
||||
// This scope will have a CSPRNG.
|
||||
context.random_generator = crypto.random_generator()
|
||||
my_uuid = uuid.generate_v7()
|
||||
}
|
||||
|
||||
// Back to the default random number generator.
|
||||
}
|
||||
|
||||
|
||||
For more information on the specifications, see here:
|
||||
- https://www.rfc-editor.org/rfc/rfc4122.html
|
||||
- https://www.rfc-editor.org/rfc/rfc9562.html
|
||||
*/
|
||||
package uuid
|
||||
333
core/encoding/uuid/generation.odin
Normal file
333
core/encoding/uuid/generation.odin
Normal file
@@ -0,0 +1,333 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/hash"
|
||||
import "core:math/rand"
|
||||
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.
|
||||
- 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, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||
|
||||
uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||
uuid_timestamp_octets := transmute([8]u8)uuid_timestamp
|
||||
|
||||
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
|
||||
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
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 4 UUID.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant bits.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v4 :: proc() -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[:])
|
||||
assert(bytes_generated == 16, "RNG failed to generate 16 bytes for UUID v4.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x40
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- clock_seq: The clock sequence from version 1, now made optional.
|
||||
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, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||
|
||||
uuid_timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||
|
||||
result = transmute(Identifier)(
|
||||
uuid_timestamp & 0x0FFFFFFF_FFFFF000 << 68 |
|
||||
uuid_timestamp & 0x00000000_00000FFF << 64
|
||||
)
|
||||
|
||||
if realized_clock_seq, ok := clock_seq.?; ok {
|
||||
assert(realized_clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||
result[8] |= cast(u8)(realized_clock_seq & 0x3F00 >> 8)
|
||||
result[9] = cast(u8)realized_clock_seq
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
temporary: [2]u8
|
||||
bytes_generated := rand.read(temporary[:])
|
||||
assert(bytes_generated == 2, "RNG failed to generate 2 bytes for UUID v1.")
|
||||
result[8] |= cast(u8)temporary[0] & 0x3F
|
||||
result[9] = cast(u8)temporary[1]
|
||||
}
|
||||
|
||||
if realized_node, ok := node.?; ok {
|
||||
mutable_node := realized_node
|
||||
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[10:])
|
||||
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||
}
|
||||
|
||||
result[VERSION_BYTE_INDEX] |= 0x60
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 7 UUID.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||
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_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(timestamp.? or_else time.now()) / 1e6
|
||||
|
||||
result = transmute(Identifier)(cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT)
|
||||
|
||||
bytes_generated := rand.read(result[6:])
|
||||
assert(bytes_generated == 10, "RNG failed to generate 10 bytes for UUID v7.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x70
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
This procedure is preferable if you are generating hundreds or thousands of
|
||||
UUIDs as a batch within the span of a millisecond. Do note that the counter
|
||||
only has 12 bits of state, thus `counter` cannot exceed the number 4,095.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:uuid"
|
||||
|
||||
// Create a batch of UUIDs all at once.
|
||||
batch: [dynamic]uuid.Identifier
|
||||
|
||||
for i: u16 = 0; i < 1000; i += 1 {
|
||||
my_uuid := uuid.generate_v7_counter(i)
|
||||
append(&batch, my_uuid)
|
||||
}
|
||||
|
||||
Inputs:
|
||||
- counter: A 12-bit value which should be 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_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, VERSION_7_BIG_COUNTER_ERROR)
|
||||
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||
|
||||
result = transmute(Identifier)(
|
||||
cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT |
|
||||
cast(u128be)counter << VERSION_7_COUNTER_SHIFT
|
||||
)
|
||||
|
||||
bytes_generated := rand.read(result[8:])
|
||||
assert(bytes_generated == 8, "RNG failed to generate 8 bytes for UUID v7.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x70
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
146
core/encoding/uuid/legacy/legacy.odin
Normal file
146
core/encoding/uuid/legacy/legacy.odin
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
package uuid/legacy implements versions 3 and 5 of UUID generation, both of
|
||||
which are using hashing algorithms (MD5 and SHA1, respectively) that are known
|
||||
these days to no longer be secure.
|
||||
*/
|
||||
package uuid_legacy
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/legacy/md5"
|
||||
import "core:crypto/legacy/sha1"
|
||||
import "core:encoding/uuid"
|
||||
|
||||
Identifier :: uuid.Identifier
|
||||
VERSION_BYTE_INDEX :: uuid.VERSION_BYTE_INDEX
|
||||
VARIANT_BYTE_INDEX :: uuid.VARIANT_BYTE_INDEX
|
||||
|
||||
|
||||
/*
|
||||
Generate a version 3 UUID.
|
||||
|
||||
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v3_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
namespace := namespace
|
||||
|
||||
ctx: md5.Context
|
||||
md5.init(&ctx)
|
||||
md5.update(&ctx, namespace[:])
|
||||
md5.update(&ctx, name)
|
||||
md5.final(&ctx, result[:])
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x30
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 3 UUID.
|
||||
|
||||
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v3_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v3_bytes(namespace, transmute([]byte)name)
|
||||
}
|
||||
|
||||
generate_v3 :: proc {
|
||||
generate_v3_bytes,
|
||||
generate_v3_string,
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 5 UUID.
|
||||
|
||||
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v5_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
namespace := namespace
|
||||
digest: [sha1.DIGEST_SIZE]byte
|
||||
|
||||
ctx: sha1.Context
|
||||
sha1.init(&ctx)
|
||||
sha1.update(&ctx, namespace[:])
|
||||
sha1.update(&ctx, name)
|
||||
sha1.final(&ctx, digest[:])
|
||||
|
||||
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x50
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 5 UUID.
|
||||
|
||||
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v5_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v5_bytes(namespace, transmute([]byte)name)
|
||||
}
|
||||
|
||||
generate_v5 :: proc {
|
||||
generate_v5_bytes,
|
||||
generate_v5_string,
|
||||
}
|
||||
|
||||
242
core/encoding/uuid/reading.odin
Normal file
242
core/encoding/uuid/reading.odin
Normal file
@@ -0,0 +1,242 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
Convert a string to a UUID.
|
||||
|
||||
Inputs:
|
||||
- str: A string in the 8-4-4-4-12 format.
|
||||
|
||||
Returns:
|
||||
- id: The converted identifier, or `nil` if there is an error.
|
||||
- error: A description of the error, or `nil` if successful.
|
||||
*/
|
||||
read :: proc "contextless" (str: string) -> (id: Identifier, error: Read_Error) #no_bounds_check {
|
||||
// Only exact-length strings are acceptable.
|
||||
if len(str) != EXPECTED_LENGTH {
|
||||
return {}, .Invalid_Length
|
||||
}
|
||||
|
||||
// Check ahead to see if the separators are in the right places.
|
||||
if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' {
|
||||
return {}, .Invalid_Separator
|
||||
}
|
||||
|
||||
read_nibble :: proc "contextless" (nibble: u8) -> u8 {
|
||||
switch nibble {
|
||||
case '0' ..= '9':
|
||||
return nibble - '0'
|
||||
case 'A' ..= 'F':
|
||||
return nibble - 'A' + 10
|
||||
case 'a' ..= 'f':
|
||||
return nibble - 'a' + 10
|
||||
case:
|
||||
// Return an error value.
|
||||
return 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
index := 0
|
||||
octet_index := 0
|
||||
|
||||
CHUNKS :: [5]int{8, 4, 4, 4, 12}
|
||||
|
||||
for chunk in CHUNKS {
|
||||
for i := index; i < index + chunk; i += 2 {
|
||||
high := read_nibble(str[i])
|
||||
low := read_nibble(str[i + 1])
|
||||
|
||||
if high | low > 0xF {
|
||||
return {}, .Invalid_Hexadecimal
|
||||
}
|
||||
|
||||
id[octet_index] = low | high << 4
|
||||
octet_index += 1
|
||||
}
|
||||
|
||||
index += chunk + 1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Get the version of a UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- number: The version number.
|
||||
*/
|
||||
version :: proc "contextless" (id: Identifier) -> (number: int) #no_bounds_check {
|
||||
return cast(int)(id[VERSION_BYTE_INDEX] & 0xF0 >> 4)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the variant of a UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- variant: The variant type.
|
||||
*/
|
||||
variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bounds_check {
|
||||
switch {
|
||||
case id[VARIANT_BYTE_INDEX] & 0x80 == 0:
|
||||
return .Reserved_Apollo_NCS
|
||||
case id[VARIANT_BYTE_INDEX] & 0xC0 == 0x80:
|
||||
return .RFC_4122
|
||||
case id[VARIANT_BYTE_INDEX] & 0xE0 == 0xC0:
|
||||
return .Reserved_Microsoft_COM
|
||||
case id[VARIANT_BYTE_INDEX] & 0xF0 == 0xE0:
|
||||
return .Reserved_Future
|
||||
case:
|
||||
return .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get the clock sequence of a version 1 or version 6 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 or version 6 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 raw timestamp of a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
raw_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 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp of the UUID.
|
||||
*/
|
||||
time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 7 UUID.
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 7 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||
*/
|
||||
time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)raw_time_v7(id) * 1e6)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the 12-bit counter value of a version 7 UUID.
|
||||
|
||||
The UUID must have been generated with a counter, otherwise this procedure will
|
||||
return random bits.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- counter: The 12-bit counter value.
|
||||
*/
|
||||
counter_v7 :: proc "contextless" (id: Identifier) -> (counter: u16) {
|
||||
counter_bits := transmute(u128be)id & VERSION_7_COUNTER_MASK
|
||||
return cast(u16)(counter_bits >> VERSION_7_COUNTER_SHIFT)
|
||||
}
|
||||
89
core/encoding/uuid/stamping.odin
Normal file
89
core/encoding/uuid/stamping.odin
Normal file
@@ -0,0 +1,89 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
Stamp a 128-bit integer as being a valid version 8 UUID.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- integer: Any integer type.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_int :: proc(#any_int integer: u128) -> (result: Identifier) {
|
||||
result = transmute(Identifier)cast(u128be)integer
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Stamp an array of 16 bytes as being a valid version 8 UUID.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- array: An array of 16 bytes.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_array :: proc(array: [16]u8) -> (result: Identifier) {
|
||||
result = transmute(Identifier)array
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Stamp a slice of bytes as being a valid version 8 UUID.
|
||||
|
||||
If the slice is less than 16 bytes long, the data available will be used.
|
||||
If it is longer than 16 bytes, only the first 16 will be used.
|
||||
|
||||
This procedure does not modify the underlying slice.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- slice: A slice of bytes.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_slice :: proc(slice: []u8) -> (result: Identifier) {
|
||||
runtime.mem_copy_non_overlapping(&result, &slice[0], min(16, len(slice)))
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stamp_v8 :: proc {
|
||||
stamp_v8_int,
|
||||
stamp_v8_array,
|
||||
stamp_v8_slice,
|
||||
}
|
||||
131
core/encoding/uuid/writing.odin
Normal file
131
core/encoding/uuid/writing.odin
Normal file
@@ -0,0 +1,131 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Write a UUID in the 8-4-4-4-12 format.
|
||||
|
||||
This procedure performs error checking with every byte written.
|
||||
|
||||
If you can guarantee beforehand that your stream has enough space to hold the
|
||||
UUID (32 bytes), then it is better to use `unsafe_write` instead as that will
|
||||
be faster.
|
||||
|
||||
Inputs:
|
||||
- w: A writable stream.
|
||||
- id: The identifier to convert.
|
||||
|
||||
Returns:
|
||||
- error: An `io` error, if one occurred, otherwise `nil`.
|
||||
*/
|
||||
write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check {
|
||||
write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check {
|
||||
high_nibble := octet >> 4
|
||||
low_nibble := octet & 0xF
|
||||
|
||||
io.write_byte(w, strconv.digits[high_nibble]) or_return
|
||||
io.write_byte(w, strconv.digits[low_nibble]) or_return
|
||||
return nil
|
||||
}
|
||||
|
||||
for index in 0 ..< 4 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 4 ..< 6 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 6 ..< 8 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 8 ..< 10 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 10 ..< 16 { write_octet(w, id[index]) or_return }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Write a UUID in the 8-4-4-4-12 format.
|
||||
|
||||
This procedure performs no error checking on the underlying stream.
|
||||
|
||||
Inputs:
|
||||
- w: A writable stream.
|
||||
- id: The identifier to convert.
|
||||
*/
|
||||
unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check {
|
||||
write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check {
|
||||
high_nibble := octet >> 4
|
||||
low_nibble := octet & 0xF
|
||||
|
||||
io.write_byte(w, strconv.digits[high_nibble])
|
||||
io.write_byte(w, strconv.digits[low_nibble])
|
||||
}
|
||||
|
||||
for index in 0 ..< 4 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 4 ..< 6 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 6 ..< 8 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 8 ..< 10 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 10 ..< 16 { write_octet(w, id[index]) }
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- id: The identifier to convert.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- str: The allocated and converted string.
|
||||
- error: An optional allocator error if one occured, `nil` otherwise.
|
||||
*/
|
||||
to_string_allocated :: proc(
|
||||
id: Identifier,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
str: string,
|
||||
error: runtime.Allocator_Error,
|
||||
) #optional_allocator_error {
|
||||
buf := make([]byte, EXPECTED_LENGTH, allocator, loc) or_return
|
||||
builder := strings.builder_from_bytes(buf[:])
|
||||
unsafe_write(strings.to_writer(&builder), id)
|
||||
return strings.to_string(builder), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier to convert.
|
||||
- buffer: A byte buffer to store the result. Must be at least 32 bytes large.
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- str: The converted string which will be stored in `buffer`.
|
||||
*/
|
||||
to_string_buffer :: proc(
|
||||
id: Identifier,
|
||||
buffer: []byte,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
str: string,
|
||||
) {
|
||||
assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc)
|
||||
builder := strings.builder_from_bytes(buffer)
|
||||
unsafe_write(strings.to_writer(&builder), id)
|
||||
return strings.to_string(builder)
|
||||
}
|
||||
|
||||
to_string :: proc {
|
||||
to_string_allocated,
|
||||
to_string_buffer,
|
||||
}
|
||||
@@ -62,6 +62,8 @@ import varint "core:encoding/varint"
|
||||
import xml "core:encoding/xml"
|
||||
import endian "core:encoding/endian"
|
||||
import cbor "core:encoding/cbor"
|
||||
import uuid "core:encoding/uuid"
|
||||
import uuid_legacy "core:encoding/uuid/legacy"
|
||||
|
||||
import fmt "core:fmt"
|
||||
import hash "core:hash"
|
||||
@@ -237,6 +239,8 @@ _ :: datetime
|
||||
_ :: flags
|
||||
_ :: sysinfo
|
||||
_ :: unicode
|
||||
_ :: uuid
|
||||
_ :: uuid_legacy
|
||||
_ :: utf8
|
||||
_ :: utf8string
|
||||
_ :: utf16
|
||||
|
||||
439
tests/core/encoding/uuid/test_core_uuid.odin
Normal file
439
tests/core/encoding/uuid/test_core_uuid.odin
Normal file
@@ -0,0 +1,439 @@
|
||||
package test_core_uuid
|
||||
|
||||
import "core:crypto"
|
||||
import "core:encoding/uuid"
|
||||
import uuid_legacy "core:encoding/uuid/legacy"
|
||||
import "core:log"
|
||||
import "core:slice"
|
||||
import "core:testing"
|
||||
import "core:time"
|
||||
|
||||
@(test)
|
||||
test_version_and_variant :: proc(t: ^testing.T) {
|
||||
context.random_generator = crypto.random_generator()
|
||||
|
||||
v1 := uuid.generate_v1(0)
|
||||
v3 := uuid_legacy.generate_v3(uuid.Namespace_DNS, "")
|
||||
v4 := uuid.generate_v4()
|
||||
v5 := uuid_legacy.generate_v5(uuid.Namespace_DNS, "")
|
||||
v6 := uuid.generate_v6()
|
||||
v7 := uuid.generate_v7()
|
||||
|
||||
_v8_array: [16]u8 = 0xff
|
||||
v8_int := uuid.stamp_v8(max(u128))
|
||||
v8_array := uuid.stamp_v8(_v8_array)
|
||||
v8_slice := uuid.stamp_v8(_v8_array[:])
|
||||
|
||||
v8_hash := 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)
|
||||
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)
|
||||
testing.expect_value(t, uuid.variant(v4), uuid.Variant_Type.RFC_4122)
|
||||
testing.expect_value(t, uuid.version(v5), 5)
|
||||
testing.expect_value(t, uuid.variant(v5), uuid.Variant_Type.RFC_4122)
|
||||
testing.expect_value(t, uuid.version(v6), 6)
|
||||
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_int), 8)
|
||||
testing.expect_value(t, uuid.variant(v8_int), uuid.Variant_Type.RFC_4122)
|
||||
testing.expect_value(t, uuid.version(v8_array), 8)
|
||||
testing.expect_value(t, uuid.variant(v8_array), uuid.Variant_Type.RFC_4122)
|
||||
testing.expect_value(t, uuid.version(v8_slice), 8)
|
||||
testing.expect_value(t, uuid.variant(v8_slice), uuid.Variant_Type.RFC_4122)
|
||||
|
||||
testing.expect_value(t, uuid.version(v8_hash), 8)
|
||||
testing.expect_value(t, uuid.variant(v8_hash), uuid.Variant_Type.RFC_4122)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_timestamps :: proc(t: ^testing.T) {
|
||||
// This test makes sure that timestamps are recoverable and have not been
|
||||
// overwritten by neighboring bits, taking into account precision loss.
|
||||
context.random_generator = crypto.random_generator()
|
||||
|
||||
N :: max(i64)
|
||||
|
||||
max_time := time.Time { N }
|
||||
|
||||
mac: [6]byte
|
||||
v1 := uuid.generate_v1(0, mac, max_time)
|
||||
v6 := uuid.generate_v6(0, mac, max_time)
|
||||
v7 := uuid.generate_v7(max_time)
|
||||
// The counter version keeps its time in the same place as the basic version,
|
||||
// this is just for the sake of completeness.
|
||||
v7_counter := uuid.generate_v7(0, max_time)
|
||||
|
||||
v1_time := uuid.time_v1(v1)
|
||||
v6_time := uuid.time_v6(v6)
|
||||
v7_time := uuid.time_v7(v7)
|
||||
v7_counter_time := uuid.time_v7(v7_counter)
|
||||
|
||||
// I hope the compiler doesn't ever optimize this out.
|
||||
max_time_hns_resolution := time.Time { N / 100 * 100 }
|
||||
max_time_ms_resolution := time.Time { N / 1e6 * 1e6 }
|
||||
|
||||
testing.expectf(t,
|
||||
time.diff(max_time_hns_resolution, v1_time) == 0,
|
||||
"v1 UUID timestamp is invalid, expected %x, got %x",
|
||||
max_time_hns_resolution, v1_time)
|
||||
|
||||
testing.expectf(t,
|
||||
time.diff(max_time_hns_resolution, v6_time) == 0,
|
||||
"v6 UUID timestamp is invalid, expected %x, got %x",
|
||||
max_time_hns_resolution, v6_time)
|
||||
|
||||
testing.expectf(t,
|
||||
time.diff(max_time_ms_resolution, v7_time) == 0,
|
||||
"v7 UUID timestamp is invalid, expected %x, got %x",
|
||||
max_time_ms_resolution, v7_time)
|
||||
|
||||
testing.expectf(t,
|
||||
time.diff(max_time_ms_resolution, v7_counter_time) == 0,
|
||||
"v7 UUID (with counter) timestamp is invalid, expected %x, got %x",
|
||||
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"
|
||||
|
||||
Expected_Result :: struct {
|
||||
namespace: uuid.Identifier,
|
||||
v3, v5: string,
|
||||
}
|
||||
|
||||
Expected_Results := [?]Expected_Result {
|
||||
{ uuid.Namespace_DNS, "80147f37-36db-3b82-b78f-810c3c6504ba", "18394c41-13a2-593f-abf2-a63e163c2860" },
|
||||
{ uuid.Namespace_URL, "8136789b-8e16-3fbd-800b-1587e2f22521", "07337422-eb77-5fd3-99af-c7f59e641e13" },
|
||||
{ uuid.Namespace_OID, "adbb95bc-ea50-3226-9a75-20c34a6030f8", "24db9b0f-70b8-53c4-a301-f695ce17276d" },
|
||||
{ uuid.Namespace_X500, "a8965ad1-0e54-3d65-b933-8b7cca8e8313", "3012bf2d-fac4-5187-9825-493e6636b936" },
|
||||
}
|
||||
|
||||
for exp in Expected_Results {
|
||||
v3 := uuid_legacy.generate_v3(exp.namespace, TEST_NAME)
|
||||
v5 := uuid_legacy.generate_v5(exp.namespace, TEST_NAME)
|
||||
|
||||
v3_str := uuid.to_string(v3)
|
||||
defer delete(v3_str)
|
||||
|
||||
v5_str := uuid.to_string(v5)
|
||||
defer delete(v5_str)
|
||||
|
||||
testing.expect_value(t, v3_str, exp.v3)
|
||||
testing.expect_value(t, v5_str, exp.v5)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_v1 :: proc(t: ^testing.T) {
|
||||
context.random_generator = crypto.random_generator()
|
||||
|
||||
point_a := time.unix(1, 0)
|
||||
point_b := time.unix(3, 0)
|
||||
point_c := time.unix(5, 0)
|
||||
|
||||
CLOCK :: 0x3A1A
|
||||
mac := [6]u8{0xFF, 0x10, 0xAA, 0x55, 0x01, 0xFF}
|
||||
|
||||
v1_a := uuid.generate_v1(CLOCK, mac, point_a)
|
||||
v1_b := uuid.generate_v1(CLOCK, mac, point_b)
|
||||
v1_c := uuid.generate_v1(CLOCK, mac, point_c)
|
||||
|
||||
testing.expect_value(t, uuid.clock_seq(v1_a), CLOCK)
|
||||
|
||||
extracted_mac := uuid.node(v1_a)
|
||||
for i in 0 ..< len(mac) {
|
||||
testing.expect(t, mac[i] == extracted_mac[i])
|
||||
}
|
||||
|
||||
time_a := uuid.time_v1(v1_a)
|
||||
time_b := uuid.time_v1(v1_b)
|
||||
time_c := uuid.time_v1(v1_c)
|
||||
|
||||
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)
|
||||
|
||||
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.unix(1, 0)
|
||||
point_b := time.unix(3, 0)
|
||||
point_c := time.unix(5, 0)
|
||||
|
||||
CLOCK :: 0x3A1A
|
||||
mac := [6]u8{0xFF, 0x10, 0xAA, 0x55, 0x01, 0xFF}
|
||||
|
||||
v6_a := uuid.generate_v6(CLOCK, mac, point_a)
|
||||
v6_b := uuid.generate_v6(CLOCK, mac, point_b)
|
||||
v6_c := uuid.generate_v6(CLOCK, mac, point_c)
|
||||
|
||||
testing.expect_value(t, uuid.clock_seq(v6_a), CLOCK)
|
||||
|
||||
extracted_mac := uuid.node(v6_a)
|
||||
for i in 0 ..< len(mac) {
|
||||
testing.expect(t, mac[i] == extracted_mac[i])
|
||||
}
|
||||
|
||||
time_a := uuid.time_v6(v6_a)
|
||||
time_b := uuid.time_v6(v6_b)
|
||||
time_c := uuid.time_v6(v6_c)
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
point_a := time.unix(1, 0)
|
||||
point_b := time.unix(3, 0)
|
||||
point_c := time.unix(5, 0)
|
||||
|
||||
v7_a := uuid.generate_v7(point_a)
|
||||
v7_b := uuid.generate_v7(point_b)
|
||||
v7_c := uuid.generate_v7(point_c)
|
||||
|
||||
time_a := uuid.time_v7(v7_a)
|
||||
time_b := uuid.time_v7(v7_b)
|
||||
time_c := uuid.time_v7(v7_c)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
test_sorting_v1 :: proc(t: ^testing.T) {
|
||||
// This test is to make sure that the v1 UUIDs do _not_ sort.
|
||||
// They are incapable of sorting properly by the nature their time bit ordering.
|
||||
//
|
||||
// Something is very strange if they do sort correctly.
|
||||
point_a := time.unix(1, 0)
|
||||
point_b := time.unix(3, 0)
|
||||
point_c := time.unix(5, 0)
|
||||
point_d := time.unix(7, 0)
|
||||
point_e := time.unix(11, 0)
|
||||
|
||||
mac: [6]byte
|
||||
v1_a := uuid.generate_v1(0, mac, point_a)
|
||||
v1_b := uuid.generate_v1(0, mac, point_b)
|
||||
v1_c := uuid.generate_v1(0, mac, point_c)
|
||||
v1_d := uuid.generate_v1(0, mac, point_d)
|
||||
v1_e := uuid.generate_v1(0, mac, point_e)
|
||||
|
||||
sort_test := [5]u128be {
|
||||
transmute(u128be)v1_e,
|
||||
transmute(u128be)v1_a,
|
||||
transmute(u128be)v1_d,
|
||||
transmute(u128be)v1_b,
|
||||
transmute(u128be)v1_c,
|
||||
}
|
||||
|
||||
log.debugf("Before: %x", sort_test)
|
||||
slice.sort(sort_test[:])
|
||||
log.debugf("After: %x", sort_test)
|
||||
|
||||
ERROR :: "v1 UUIDs are sorting by time, despite this not being possible."
|
||||
|
||||
testing.expect(t, sort_test[0] != transmute(u128be)v1_a, ERROR)
|
||||
testing.expect(t, sort_test[1] != transmute(u128be)v1_b, ERROR)
|
||||
testing.expect(t, sort_test[2] != transmute(u128be)v1_c, ERROR)
|
||||
testing.expect(t, sort_test[3] != transmute(u128be)v1_d, ERROR)
|
||||
testing.expect(t, sort_test[4] != transmute(u128be)v1_e, ERROR)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_sorting_v6 :: proc(t: ^testing.T) {
|
||||
context.random_generator = crypto.random_generator()
|
||||
|
||||
point_a := time.unix(1, 0)
|
||||
point_b := time.unix(3, 0)
|
||||
point_c := time.unix(5, 0)
|
||||
point_d := time.unix(7, 0)
|
||||
point_e := time.unix(11, 0)
|
||||
|
||||
mac: [6]byte
|
||||
v6_a := uuid.generate_v6(0, mac, point_a)
|
||||
v6_b := uuid.generate_v6(0, mac, point_b)
|
||||
v6_c := uuid.generate_v6(0, mac, point_c)
|
||||
v6_d := uuid.generate_v6(0, mac, point_d)
|
||||
v6_e := uuid.generate_v6(0, mac, point_e)
|
||||
|
||||
sort_test := [5]u128be {
|
||||
transmute(u128be)v6_e,
|
||||
transmute(u128be)v6_a,
|
||||
transmute(u128be)v6_d,
|
||||
transmute(u128be)v6_b,
|
||||
transmute(u128be)v6_c,
|
||||
}
|
||||
|
||||
log.debugf("Before: %x", sort_test)
|
||||
slice.sort(sort_test[:])
|
||||
log.debugf("After: %x", sort_test)
|
||||
|
||||
ERROR :: "v6 UUIDs are failing to sort properly."
|
||||
|
||||
testing.expect(t, sort_test[0] < sort_test[1], ERROR)
|
||||
testing.expect(t, sort_test[1] < sort_test[2], ERROR)
|
||||
testing.expect(t, sort_test[2] < sort_test[3], ERROR)
|
||||
testing.expect(t, sort_test[3] < sort_test[4], ERROR)
|
||||
|
||||
testing.expect(t, sort_test[0] == transmute(u128be)v6_a, ERROR)
|
||||
testing.expect(t, sort_test[1] == transmute(u128be)v6_b, ERROR)
|
||||
testing.expect(t, sort_test[2] == transmute(u128be)v6_c, ERROR)
|
||||
testing.expect(t, sort_test[3] == transmute(u128be)v6_d, ERROR)
|
||||
testing.expect(t, sort_test[4] == transmute(u128be)v6_e, ERROR)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_sorting_v7 :: proc(t: ^testing.T) {
|
||||
context.random_generator = crypto.random_generator()
|
||||
|
||||
point_a := time.unix(1, 0)
|
||||
point_b := time.unix(3, 0)
|
||||
point_c := time.unix(5, 0)
|
||||
point_d := time.unix(7, 0)
|
||||
point_e := time.unix(11, 0)
|
||||
|
||||
v7_a := uuid.generate_v7(point_a)
|
||||
v7_b := uuid.generate_v7(point_b)
|
||||
v7_c := uuid.generate_v7(point_c)
|
||||
v7_d := uuid.generate_v7(point_d)
|
||||
v7_e := uuid.generate_v7(point_e)
|
||||
|
||||
sort_test := [5]u128be {
|
||||
transmute(u128be)v7_e,
|
||||
transmute(u128be)v7_a,
|
||||
transmute(u128be)v7_d,
|
||||
transmute(u128be)v7_b,
|
||||
transmute(u128be)v7_c,
|
||||
}
|
||||
|
||||
log.debugf("Before: %x", sort_test)
|
||||
slice.sort(sort_test[:])
|
||||
log.debugf("After: %x", sort_test)
|
||||
|
||||
ERROR :: "v7 UUIDs are failing to sort properly."
|
||||
|
||||
testing.expect(t, sort_test[0] < sort_test[1], ERROR)
|
||||
testing.expect(t, sort_test[1] < sort_test[2], ERROR)
|
||||
testing.expect(t, sort_test[2] < sort_test[3], ERROR)
|
||||
testing.expect(t, sort_test[3] < sort_test[4], ERROR)
|
||||
|
||||
testing.expect(t, sort_test[0] == transmute(u128be)v7_a, ERROR)
|
||||
testing.expect(t, sort_test[1] == transmute(u128be)v7_b, ERROR)
|
||||
testing.expect(t, sort_test[2] == transmute(u128be)v7_c, ERROR)
|
||||
testing.expect(t, sort_test[3] == transmute(u128be)v7_d, ERROR)
|
||||
testing.expect(t, sort_test[4] == transmute(u128be)v7_e, ERROR)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_writing :: proc(t: ^testing.T) {
|
||||
id: uuid.Identifier
|
||||
|
||||
for &b, i in id {
|
||||
b = u8(i)
|
||||
}
|
||||
|
||||
buf: [uuid.EXPECTED_LENGTH]u8
|
||||
|
||||
s_alloc := uuid.to_string(id)
|
||||
defer delete(s_alloc)
|
||||
|
||||
s_buf := uuid.to_string(id, buf[:])
|
||||
|
||||
testing.expect_value(t, s_alloc, "00010203-0405-0607-0809-0a0b0c0d0e0f")
|
||||
testing.expect_value(t, s_buf, "00010203-0405-0607-0809-0a0b0c0d0e0f")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_reading :: proc(t: ^testing.T) {
|
||||
id, err := uuid.read("00010203-0405-0607-0809-0a0b0c0d0e0f")
|
||||
testing.expect_value(t, err, nil)
|
||||
|
||||
for b, i in id {
|
||||
testing.expect_value(t, b, u8(i))
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_reading_errors :: proc(t: ^testing.T) {
|
||||
{
|
||||
BAD_STRING :: "|.......@....@....@....@............"
|
||||
_, err := uuid.read(BAD_STRING)
|
||||
testing.expect_value(t, err, uuid.Read_Error.Invalid_Separator)
|
||||
}
|
||||
|
||||
{
|
||||
BAD_STRING :: "|.......-....-....-....-............"
|
||||
_, err := uuid.read(BAD_STRING)
|
||||
testing.expect_value(t, err, uuid.Read_Error.Invalid_Hexadecimal)
|
||||
}
|
||||
|
||||
{
|
||||
BAD_STRING :: ".......-....-....-....-............"
|
||||
_, err := uuid.read(BAD_STRING)
|
||||
testing.expect_value(t, err, uuid.Read_Error.Invalid_Length)
|
||||
}
|
||||
|
||||
{
|
||||
BAD_STRING :: "|.......-....-....-....-............|"
|
||||
_, err := uuid.read(BAD_STRING)
|
||||
testing.expect_value(t, err, uuid.Read_Error.Invalid_Length)
|
||||
}
|
||||
|
||||
{
|
||||
BAD_STRING :: "00000000-0000-0000-0000-0000000000001"
|
||||
_, err := uuid.read(BAD_STRING)
|
||||
testing.expect_value(t, err, uuid.Read_Error.Invalid_Length)
|
||||
}
|
||||
|
||||
{
|
||||
BAD_STRING :: "00000000000000000000000000000000"
|
||||
_, err := uuid.read(BAD_STRING)
|
||||
testing.expect_value(t, err, uuid.Read_Error.Invalid_Length)
|
||||
}
|
||||
|
||||
{
|
||||
OK_STRING :: "00000000-0000-0000-0000-000000000000"
|
||||
_, err := uuid.read(OK_STRING)
|
||||
testing.expect_value(t, err, nil)
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ download_assets :: proc() {
|
||||
@(require) import "encoding/hex"
|
||||
@(require) import "encoding/hxa"
|
||||
@(require) import "encoding/json"
|
||||
@(require) import "encoding/uuid"
|
||||
@(require) import "encoding/varint"
|
||||
@(require) import "encoding/xml"
|
||||
@(require) import "flags"
|
||||
|
||||
Reference in New Issue
Block a user