namespace Nop.Plugin.Misc.Zettle.Services; /// /// Used for generating UUID based on RFC 4122. /// /// Source: https://github.com/fluentcassandra/fluentcassandra/blob/master/src/GuidGenerator.cs /// RFC 4122 - A Universally Unique IDentifier (UUID) URN Namespace public static class GuidGenerator { #region Constants // number of bytes in uuid private const int BYTE_ARRAY_SIZE = 16; // multiplex variant info private const int VARIANT_BYTE = 8; private const int VARIANT_BYTE_MASK = 0x3f; private const int VARIANT_BYTE_SHIFT = 0x80; // multiplex version info private const int VERSION_BYTE = 7; private const int VERSION_BYTE_MASK = 0x0f; private const int VERSION_BYTE_SHIFT = 4; // indexes within the uuid array for certain boundaries private const byte TIMESTAMP_BYTE = 0; private const byte GUID_CLOCK_SEQUENCE_BYTE = 8; private const byte NODE_BYTE = 10; #endregion #region Fields // offset to move from 1/1/0001, which is 0-time for .NET, to gregorian 0-time of 10/15/1582 private static readonly DateTimeOffset _gregorianCalendarStart = new(1582, 10, 15, 0, 0, 0, TimeSpan.Zero); private static readonly Random _random; private static readonly byte[] _nodeBytes; private static readonly byte[] _clockSequenceBytes; #endregion #region Ctor static GuidGenerator() { _random = new Random(); _nodeBytes = GenerateNodeBytes(); _clockSequenceBytes = GenerateClockSequenceBytes(); } #endregion #region Utilities /// /// Generates a random value for the node. /// /// private static byte[] GenerateNodeBytes() { var node = new byte[6]; _random.NextBytes(node); return node; } /// /// Generates a random clock sequence. /// private static byte[] GenerateClockSequenceBytes() { var bytes = new byte[2]; _random.NextBytes(bytes); return bytes; } /// /// In order to maintain a constant value we need to get a two byte hash from the DateTime. /// private static byte[] GenerateClockSequenceBytes(DateTime dt) { var utc = dt.ToUniversalTime(); var bytes = BitConverter.GetBytes(utc.Ticks); if (bytes.Length == 0) return [0x0, 0x0]; if (bytes.Length == 1) return [0x0, bytes[0]]; return [bytes[0], bytes[1]]; } private static Guid GenerateTimeBasedGuid(DateTimeOffset dateTime, byte[] clockSequence, byte[] node) { ArgumentNullException.ThrowIfNull(clockSequence); ArgumentNullException.ThrowIfNull(node); if (clockSequence.Length != 2) throw new ArgumentOutOfRangeException(nameof(clockSequence), "The clockSequence must be 2 bytes."); if (node.Length != 6) throw new ArgumentOutOfRangeException(nameof(node), "The node must be 6 bytes."); var ticks = (dateTime - _gregorianCalendarStart).Ticks; var guid = new byte[BYTE_ARRAY_SIZE]; var timestamp = BitConverter.GetBytes(ticks); // copy node Array.Copy(node, 0, guid, NODE_BYTE, Math.Min(6, node.Length)); // copy clock sequence Array.Copy(clockSequence, 0, guid, GUID_CLOCK_SEQUENCE_BYTE, Math.Min(2, clockSequence.Length)); // copy timestamp Array.Copy(timestamp, 0, guid, TIMESTAMP_BYTE, Math.Min(8, timestamp.Length)); // set the variant guid[VARIANT_BYTE] &= (byte)VARIANT_BYTE_MASK; guid[VARIANT_BYTE] |= (byte)VARIANT_BYTE_SHIFT; // set the version guid[VERSION_BYTE] &= (byte)VERSION_BYTE_MASK; guid[VERSION_BYTE] |= (byte)((byte)GuidVersion.TimeBased << VERSION_BYTE_SHIFT); return new Guid(guid); } #endregion #region Methods public static GuidVersion GetUuidVersion(this Guid guid) { var bytes = guid.ToByteArray(); return (GuidVersion)((bytes[VERSION_BYTE] & 0xFF) >> VERSION_BYTE_SHIFT); } public static Guid GenerateTimeBasedGuid(DateTime? dateTime = null) { return GenerateTimeBasedGuid(dateTime ?? DateTime.UtcNow, dateTime.HasValue ? GenerateClockSequenceBytes(dateTime.Value) : _clockSequenceBytes, _nodeBytes); } #endregion #region Nested class public enum GuidVersion { TimeBased = 0x01, Reserved = 0x02, NameBased = 0x03, Random = 0x04 } #endregion }