aboutsummaryrefslogtreecommitdiff
path: root/core/encoding
diff options
context:
space:
mode:
authorFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-21 12:04:45 -0400
committerFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-22 18:21:31 -0400
commit4cfbd83b10ff17b6c7ca4b01dac249bfbea0da84 (patch)
treef18dc3b825e1317ef4dcdfd5c6e2a190b2dda589 /core/encoding
parentfee81985b4b7ade3cddc7038360efcd32555d879 (diff)
Add version 7 UUID generation
Diffstat (limited to 'core/encoding')
-rw-r--r--core/encoding/uuid/definitions.odin5
-rw-r--r--core/encoding/uuid/generation.odin84
-rw-r--r--core/encoding/uuid/reading.odin31
3 files changed, 120 insertions, 0 deletions
diff --git a/core/encoding/uuid/definitions.odin b/core/encoding/uuid/definitions.odin
index 1208e26f4..b54965e23 100644
--- a/core/encoding/uuid/definitions.odin
+++ b/core/encoding/uuid/definitions.odin
@@ -8,6 +8,11 @@ EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4
VERSION_BYTE_INDEX :: 6
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 {
None,
Invalid_Length,
diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin
index 9b790714c..7fe0bbd13 100644
--- a/core/encoding/uuid/generation.odin
+++ b/core/encoding/uuid/generation.odin
@@ -4,6 +4,7 @@ import "core:crypto/legacy/md5"
import "core:crypto/legacy/sha1"
import "core:math/rand"
import "core:mem"
+import "core:time"
/*
Generate a version 3 UUID.
@@ -158,3 +159,86 @@ generate_v5 :: proc {
generate_v5_bytes,
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
+}
diff --git a/core/encoding/uuid/reading.odin b/core/encoding/uuid/reading.odin
index 0c0274e53..c72f5791e 100644
--- a/core/encoding/uuid/reading.odin
+++ b/core/encoding/uuid/reading.odin
@@ -95,3 +95,34 @@ variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bo
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)
+}