Merge pull request #3792 from Feoramund/core-uuid

Add `core:encoding/uuid`
This commit is contained in:
gingerBill
2024-06-28 09:49:23 +01:00
committed by GitHub
11 changed files with 1526 additions and 0 deletions

View 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.

View 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,
}

View 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

View 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,
}

View 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,
}

View 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)
}

View 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,
}

View 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,
}

View File

@@ -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

View 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)
}
}

View File

@@ -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"