mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-03 05:08:14 +00:00
Add version 7 UUID generation
This commit is contained in:
@@ -8,6 +8,11 @@ EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4
|
|||||||
VERSION_BYTE_INDEX :: 6
|
VERSION_BYTE_INDEX :: 6
|
||||||
VARIANT_BYTE_INDEX :: 8
|
VARIANT_BYTE_INDEX :: 8
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
Read_Error :: enum {
|
Read_Error :: enum {
|
||||||
None,
|
None,
|
||||||
Invalid_Length,
|
Invalid_Length,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import "core:crypto/legacy/md5"
|
|||||||
import "core:crypto/legacy/sha1"
|
import "core:crypto/legacy/sha1"
|
||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Generate a version 3 UUID.
|
Generate a version 3 UUID.
|
||||||
@@ -158,3 +159,86 @@ generate_v5 :: proc {
|
|||||||
generate_v5_bytes,
|
generate_v5_bytes,
|
||||||
generate_v5_string,
|
generate_v5_string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v7 :: proc() -> (result: Identifier) {
|
||||||
|
unix_time_in_milliseconds := time.to_unix_nanoseconds(time.now()) / 1e6
|
||||||
|
|
||||||
|
temporary := 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 |= transmute(Identifier)temporary
|
||||||
|
|
||||||
|
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 with 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, incremented each time a UUID is generated in a batch.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The generated UUID.
|
||||||
|
*/
|
||||||
|
generate_v7_counter :: proc(counter: u16) -> (result: Identifier) {
|
||||||
|
assert(counter <= 0x0fff, "This implementation of the version 7 UUID does not support counters in excess of 12 bits (4,095).")
|
||||||
|
unix_time_in_milliseconds := time.to_unix_nanoseconds(time.now()) / 1e6
|
||||||
|
|
||||||
|
temporary := cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT
|
||||||
|
temporary |= 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 |= transmute(Identifier)temporary
|
||||||
|
|
||||||
|
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||||
|
result[VERSION_BYTE_INDEX] |= 0x70
|
||||||
|
|
||||||
|
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||||
|
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,3 +95,34 @@ variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bo
|
|||||||
return .Unknown
|
return .Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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: u64) {
|
||||||
|
time_bits := transmute(u128be)id & VERSION_7_TIME_MASK
|
||||||
|
return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package test_core_uuid
|
package test_core_uuid
|
||||||
|
|
||||||
import "core:testing"
|
|
||||||
import "core:encoding/uuid"
|
import "core:encoding/uuid"
|
||||||
|
import "core:log"
|
||||||
|
import "core:testing"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_version_and_variant :: proc(t: ^testing.T) {
|
test_version_and_variant :: proc(t: ^testing.T) {
|
||||||
v3 := uuid.generate_v3(uuid.Namespace_DNS, "")
|
v3 := uuid.generate_v3(uuid.Namespace_DNS, "")
|
||||||
v4 := uuid.generate_v4()
|
v4 := uuid.generate_v4()
|
||||||
v5 := uuid.generate_v5(uuid.Namespace_DNS, "")
|
v5 := uuid.generate_v5(uuid.Namespace_DNS, "")
|
||||||
|
v7 := uuid.generate_v7()
|
||||||
|
|
||||||
testing.expect_value(t, uuid.version(v3), 3)
|
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.variant(v3), uuid.Variant_Type.RFC_4122)
|
||||||
@@ -15,6 +18,8 @@ test_version_and_variant :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, uuid.variant(v4), uuid.Variant_Type.RFC_4122)
|
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.version(v5), 5)
|
||||||
testing.expect_value(t, uuid.variant(v5), uuid.Variant_Type.RFC_4122)
|
testing.expect_value(t, uuid.variant(v5), 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -48,6 +53,31 @@ test_namespaced_uuids :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_v7 :: proc(t: ^testing.T) {
|
||||||
|
v7_a := uuid.generate_v7()
|
||||||
|
time.sleep(10 * time.Millisecond)
|
||||||
|
v7_b := uuid.generate_v7()
|
||||||
|
time.sleep(10 * time.Millisecond)
|
||||||
|
v7_c := uuid.generate_v7()
|
||||||
|
|
||||||
|
time_bits_a := uuid.time_v7(v7_a)
|
||||||
|
time_bits_b := uuid.time_v7(v7_b)
|
||||||
|
time_bits_c := uuid.time_v7(v7_c)
|
||||||
|
|
||||||
|
log.debugf("A: %02x, %i", v7_a, time_bits_a)
|
||||||
|
log.debugf("B: %02x, %i", v7_b, time_bits_b)
|
||||||
|
log.debugf("C: %02x, %i", v7_c, time_bits_c)
|
||||||
|
|
||||||
|
testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.")
|
||||||
|
testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.")
|
||||||
|
testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.")
|
||||||
|
|
||||||
|
v7_with_counter := uuid.generate_v7_counter(0x555)
|
||||||
|
log.debugf("D: %02x", v7_with_counter)
|
||||||
|
testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_writing :: proc(t: ^testing.T) {
|
test_writing :: proc(t: ^testing.T) {
|
||||||
id: uuid.Identifier
|
id: uuid.Identifier
|
||||||
|
|||||||
Reference in New Issue
Block a user