util.uuid: Add UUIDv7

Allows sorting by id as a substitute for sorting by timestamp since it
has the timestamp in the encoded in the first part, and only things that
happen extremely close together may get out of order by such a sort,
which might not matter.

From draft-ietf-uuidrev-rfc4122bis formerly draft-peabody-dispatch-new-uuid-format
This commit is contained in:
Kim Alvefur 2021-08-15 14:44:21 +02:00
parent 99d2ebb731
commit 3619de6000
2 changed files with 39 additions and 0 deletions

View file

@ -20,6 +20,28 @@ describe("util.uuid", function()
for _ = 1, 100 do
assert.is_string(uuid.generate():match(pattern));
end
assert.truthy(uuid.generate() ~= uuid.generate(), "does not generate the same UUIDv4 twice")
end);
end);
describe("#v7", function()
it("should also follow the UUID pattern", function()
local pattern = "^" .. table.concat({
string.rep("%x", 8),
string.rep("%x", 4),
"7" .. -- version
string.rep("%x", 3),
"[89ab]" .. -- reserved bits of 1 and 0
string.rep("%x", 3),
string.rep("%x", 12),
}, "%-") .. "$";
local one = uuid.v7(); -- one before the loop to ensure some time passes
for _ = 1, 100 do
assert.is_string(uuid.v7():match(pattern));
end
-- one after the loop when some time should have passed
assert.truthy(one < uuid.v7(), "should be ordererd")
end);
end);
end);

View file

@ -8,8 +8,10 @@
local random = require "prosody.util.random";
local random_bytes = random.bytes;
local time = require "prosody.util.time";
local hex = require "prosody.util.hex".encode;
local m_ceil = math.ceil;
local m_floor = math.floor;
local function get_nibbles(n)
return hex(random_bytes(m_ceil(n/2))):sub(1, n);
@ -24,7 +26,22 @@ local function generate()
return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12);
end
local function generate_v7()
-- Sortable based on time and random
-- https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-01#section-4.4
local t = time.now();
local unixts = m_floor(t);
local unixts_a = m_floor(unixts / 16);
local unixts_b = m_floor(unixts % 16);
local subsec = t % 1;
local subsec_a = m_floor(subsec * 0x1000);
local subsec_b = m_floor(subsec * 0x1000000) % 0x1000;
return ("%08x-%x%03x-7%03x-%4s-%12s"):format(unixts_a, unixts_b, subsec_a, subsec_b, get_twobits() .. get_nibbles(3), get_nibbles(12));
end
return {
v4 = generate;
v7 = generate_v7;
get_nibbles=get_nibbles;
generate = generate ;
-- COMPAT