// *********************************************************************** // Assembly : FCS.Lib.Utility // Author : fhdk // Created : 2022 12 17 13:33 // // Last Modified By: fhdk // Last Modified On : 2023 03 14 09:16 // *********************************************************************** // // Copyright (C) 2022-2023 FCS Frede's Computer Services. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see [https://www.gnu.org/licenses] // // // *********************************************************************** using System; using System.Text; namespace FCS.Lib.Utility; /// /// Used for generating UUID based on RFC 4122. /// /// RFC 4122 - A Universally Unique IDentifier (UUID) URN Namespace public static class GuidGenerator { /// /// number of bytes in guid /// public const int ByteArraySize = 16; /// /// multiplex variant info - variant byte /// public const int VariantByte = 8; /// /// multiplex variant info - variant byte mask /// public const int VariantByteMask = 0x3f; /// /// multiplex variant info - variant byte shift /// public const int VariantByteShift = 0x80; /// /// multiplex version info - version byte /// public const int VersionByte = 7; /// /// multiplex version info - version byte mask /// public const int VersionByteMask = 0x0f; /// /// multiplex version info version byte shift /// public const int VersionByteShift = 4; // indexes within the uuid array for certain boundaries private const byte TimestampByte = 0; private const byte GuidClockSequenceByte = 8; private const byte NodeByte = 10; // 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); static GuidGenerator() { DefaultClockSequence = new byte[2]; DefaultNode = new byte[6]; var random = new Random(); random.NextBytes(DefaultClockSequence); random.NextBytes(DefaultNode); } /// /// Default clock sequence /// public static byte[] DefaultClockSequence { get; set; } /// /// Default node /// public static byte[] DefaultNode { get; set; } /// /// Set default node /// /// public static void SetDefaultNode(string nodeName) { var x = nodeName.GetHashCode(); var node = $"{x:X}"; DefaultNode = Encoding.UTF8.GetBytes(node.ToCharArray(), 0, 6); } /// /// Get version /// /// /// public static GuidVersion GetVersion(this Guid guid) { var bytes = guid.ToByteArray(); return (GuidVersion)((bytes[VersionByte] & 0xFF) >> VersionByteShift); } /// /// Get date time offset from guid /// /// /// public static DateTimeOffset GetDateTimeOffset(Guid guid) { var bytes = guid.ToByteArray(); // reverse the version bytes[VersionByte] &= VersionByteMask; bytes[VersionByte] |= (byte)GuidVersion.TimeBased >> VersionByteShift; var timestampBytes = new byte[8]; Array.Copy(bytes, TimestampByte, timestampBytes, 0, 8); var timestamp = BitConverter.ToInt64(timestampBytes, 0); var ticks = timestamp + GregorianCalendarStart.Ticks; return new DateTimeOffset(ticks, TimeSpan.Zero); } /// /// get date time from guid /// /// /// public static DateTime GetDateTime(Guid guid) { return GetDateTimeOffset(guid).DateTime; } /// /// get local date time from guid /// /// /// public static DateTime GetLocalDateTime(Guid guid) { return GetDateTimeOffset(guid).LocalDateTime; } /// /// get utc date time from guid /// /// /// public static DateTime GetUtcDateTime(Guid guid) { return GetDateTimeOffset(guid).UtcDateTime; } /// /// Generate time based guid /// /// public static Guid GenerateTimeBasedGuid() { return GenerateTimeBasedGuid(DateTimeOffset.UtcNow, DefaultClockSequence, DefaultNode); } /// /// Generate time based guid providing a NodeName string /// /// /// public static Guid GenerateTimeBasedGuid(string nodeName) { var x = nodeName.GetHashCode(); var node = $"{x:X}"; var defaultNode = Encoding.UTF8.GetBytes(node.ToCharArray(), 0, 6); return GenerateTimeBasedGuid(DateTimeOffset.UtcNow, DefaultClockSequence, defaultNode); } /// /// Generate time based guid providing a valid DateTime object /// /// /// public static Guid GenerateTimeBasedGuid(DateTime dateTime) { return GenerateTimeBasedGuid(dateTime, DefaultClockSequence, DefaultNode); } /// /// Generate time base guid providing a valid DateTimeOffset object /// /// /// public static Guid GenerateTimeBasedGuid(DateTimeOffset dateTime) { return GenerateTimeBasedGuid(dateTime, DefaultClockSequence, DefaultNode); } /// /// Generate time based guid providing a date time, byte array for clock sequence and node /// /// /// /// /// public static Guid GenerateTimeBasedGuid(DateTime dateTime, byte[] clockSequence, byte[] node) { return GenerateTimeBasedGuid(new DateTimeOffset(dateTime), clockSequence, node); } /// /// Generate time based guid providing a valid DateTimeOffset Object and byte arrays for clock sequence and node /// /// /// /// /// /// /// public static Guid GenerateTimeBasedGuid(DateTimeOffset dateTime, byte[] clockSequence, byte[] node) { if (clockSequence == null) throw new ArgumentNullException(nameof(clockSequence)); if (node == null) throw new ArgumentNullException(nameof(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[ByteArraySize]; var timestamp = BitConverter.GetBytes(ticks); // copy node Array.Copy(node, 0, guid, NodeByte, Math.Min(6, node.Length)); // copy clock sequence Array.Copy(clockSequence, 0, guid, GuidClockSequenceByte, Math.Min(2, clockSequence.Length)); // copy timestamp Array.Copy(timestamp, 0, guid, TimestampByte, Math.Min(8, timestamp.Length)); // set the variant guid[VariantByte] &= VariantByteMask; guid[VariantByte] |= VariantByteShift; // set the version guid[VersionByte] &= VersionByteMask; guid[VersionByte] |= (byte)GuidVersion.TimeBased << VersionByteShift; return new Guid(guid); } }