namespace Hutopy.Web.Common; /// /// Adapted from https://raw.githubusercontent.com/uuidjs/uuid/main/src/v7.ts. /// to match the uuid v7 generated on the client /// public static class GuidHelper { private class V7State { public long Msecs { get; set; } = long.MinValue; public int Seq { get; set; } } private static readonly V7State State = new(); private static readonly Random Random = new(); public static Guid GenerateUuidV7() { byte[] randomValues = new byte[16]; Random.NextBytes(randomValues); UpdateV7State( State, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), randomValues); var values = V7Bytes(randomValues, State.Msecs, State.Seq); return new Guid(values); } private static void UpdateV7State(V7State state, long now, byte[] randomBytes) { if (now > state.Msecs) { state.Seq = (randomBytes[6] << 23) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9]; state.Msecs = now; } else { state.Seq = (state.Seq + 1) | 0; if (state.Seq == 0) { state.Msecs++; } } } private static byte[] V7Bytes(byte[] randomBytes, long? msecs = null, int? seq = null, byte[]? buf = null, int offset = 0) { if (buf == null) { buf = new byte[16]; offset = 0; } // Defaults msecs ??= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); seq ??= ((randomBytes[6] & 0x7f) << 24) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9]; // byte 0-5: timestamp (48 bits) buf[offset++] = (byte)((msecs.Value / 0x10000000000) & 0xff); buf[offset++] = (byte)((msecs.Value / 0x100000000) & 0xff); buf[offset++] = (byte)((msecs.Value / 0x1000000) & 0xff); buf[offset++] = (byte)((msecs.Value / 0x10000) & 0xff); buf[offset++] = (byte)((msecs.Value / 0x100) & 0xff); buf[offset++] = (byte)(msecs.Value & 0xff); // byte 6: `version` (4 bits) | sequence bits 28-31 (4 bits) buf[offset++] = (byte)(0x70 | ((seq.Value >> 28) & 0x0f)); // byte 7: sequence bits 20-27 (8 bits) buf[offset++] = (byte)((seq.Value >> 20) & 0xff); // byte 8: `variant` (2 bits) | sequence bits 14-19 (6 bits) buf[offset++] = (byte)(0x80 | ((seq.Value >> 14) & 0x3f)); // byte 9: sequence bits 6-13 (8 bits) buf[offset++] = (byte)((seq.Value >> 6) & 0xff); // byte 10: sequence bits 0-5 (6 bits) | random (2 bits) buf[offset++] = (byte)(((seq.Value << 2) & 0xff) | (randomBytes[10] & 0x03)); // bytes 11-15: random (40 bits) buf[offset++] = randomBytes[11]; buf[offset++] = randomBytes[12]; buf[offset++] = randomBytes[13]; buf[offset++] = randomBytes[14]; buf[offset] = randomBytes[15]; return buf; } }