diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d483c9b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,3 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2" +} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 57a20a9..26c3878 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -68,7 +68,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 7.0.x 8.0.x 9.0.x diff --git a/Benchmark/Benchmark.csproj b/Benchmark/Benchmark.csproj index 61d075e..510d1f6 100644 --- a/Benchmark/Benchmark.csproj +++ b/Benchmark/Benchmark.csproj @@ -2,7 +2,7 @@ Exe - net7.0;net8.0;net9.0 + net8.0;net9.0 enable enable False diff --git a/Benchmark/Dictionary/Class.cs b/Benchmark/Dictionary/Class.cs index 4fc160c..2ebc7d9 100644 --- a/Benchmark/Dictionary/Class.cs +++ b/Benchmark/Dictionary/Class.cs @@ -1,7 +1,10 @@ namespace Benchmark.Dictionary; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public class DictonaryTestClass { public String Data1 { get; set; } public String Data2 { get; set; } public String Data3 { get; set; } } + +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. \ No newline at end of file diff --git a/LICENSE.rfc9562.txt b/LICENSE.rfc9562.txt new file mode 100644 index 0000000..cc1d89f --- /dev/null +++ b/LICENSE.rfc9562.txt @@ -0,0 +1,3 @@ +Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved. + +This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. \ No newline at end of file diff --git a/Library/Classes/UUIDJsonConverter/UUIDJsonConverter.cs b/Library/Classes/UUIDJsonConverter/UUIDJsonConverter.cs index f548c31..3921698 100644 --- a/Library/Classes/UUIDJsonConverter/UUIDJsonConverter.cs +++ b/Library/Classes/UUIDJsonConverter/UUIDJsonConverter.cs @@ -12,12 +12,12 @@ public partial class UUIDJsonConverter : JsonConverter { public override UUID Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { ReadOnlySpan data = reader.ValueSpan; - if (data != null && data.Length == Format.UUID_STRING_LENGTH) { - Vector128 d = Format.Parse(data); - return new UUID(d); + if (data == Span.Empty || data.Length != Format.UUID_STRING_LENGTH) { + throw new JsonException("Unknown UUID format"); } - throw new JsonException("Unknown UUID format"); + Vector128 d = Format.Parse(data); + return new UUID(d); } /// diff --git a/Library/DaanV2.UUID.Net.csproj b/Library/DaanV2.UUID.Net.csproj index 2be08d5..f93e001 100644 --- a/Library/DaanV2.UUID.Net.csproj +++ b/Library/DaanV2.UUID.Net.csproj @@ -1,7 +1,7 @@ - + - net7.0;net8.0;net9.0 + net8.0;net9.0 enable enable DaanV2.UUID.Net @@ -17,13 +17,13 @@ False A library that provides a way to handle and generate UUIDs. Convert them to and from strings, GUIDs, and the like. The library is written to be fast and efficient when comparing, generating, or handling operations. Provides ways to generate UUIDs from different data, like a string, a byte array, or cutting up a byte array into UUIDs. -Complies with the RFC 4122 standard. And has version 1-8 UUIDs implemented. except 2. Which I haven't been able to figure out how to implement. +Complies with the RFC 4122 / RFC 9562 standard. And has version 1-8 UUIDs implemented. https://github.com/DaanV2/DaanV2.UUID.Net icon.png README.md https://github.com/DaanV2/DaanV2.UUID.Net git - UUID;RFC-4122;V1;V3;V4;V5;V6;V7;V8 + UUID;RFC-4122;RFC-9562;V1;V2;V3;V4;V5;V6;V7;V8 LICENSE.txt True snupkg @@ -58,9 +58,13 @@ Complies with the RFC 4122 standard. And has version 1-8 UUIDs implemented. exce True \ + + True + \ + - True - \ + True + \ True diff --git a/Library/Static Classes/Format/Format - Extract.cs b/Library/Static Classes/Format/Format - Extract.cs index a094919..cd75fc3 100644 --- a/Library/Static Classes/Format/Format - Extract.cs +++ b/Library/Static Classes/Format/Format - Extract.cs @@ -19,7 +19,7 @@ public static (UInt64 bits48, UInt16 bits12, UInt64 bits62) Extract(UUID uuid) { // Remove top 2 bits, so we have 62 bits of data const UInt64 mask = 0xC000000000000000u; - dataC = (dataC & ~mask); + dataC &= ~mask; return (dataA, dataB, dataC); } diff --git a/Library/Static Classes/Format/Format - String.cs b/Library/Static Classes/Format/Format - String.cs index e9c4448..b1df457 100644 --- a/Library/Static Classes/Format/Format - String.cs +++ b/Library/Static Classes/Format/Format - String.cs @@ -122,40 +122,10 @@ public static Vector128 Parse(ReadOnlySpan chars) { throw new ArgumentException($"The length of the string is not {chars}", nameof(chars)); } - //Upper 4 bits goes first in the string and lower 4 bits goes second in the string - //We create two vectors, one for the upper 4 bits and one for the lower 4 bits - // - //No need to init, all values will be overwritten - Unsafe.SkipInit(out Vector128 upper); - Unsafe.SkipInit(out Vector128 lower); - - static Vector128 LowerNine(Vector128 data) { - //Each element becomes 0xff if it is greater than 9 - var elementsGreatherThan = Vector128.GreaterThan(data, _9Vector); - var toAdd = Vector128.BitwiseAnd(elementsGreatherThan, _9AOffsetVector); - return Vector128.Subtract(data, toAdd); - } - - Int32 J = 0; - for (Int32 I = 0; I < UUID_STRING_LENGTH;) { - if (I is 8 or 13 or 18 or 23) { - I++; - continue; - } - upper = Vector128.WithElement(upper, J, (Byte)chars[I++]); - lower = Vector128.WithElement(lower, J, (Byte)chars[I++]); - J++; - } - - upper = LowerNine(upper); - upper = Vector128.Subtract(upper, _AddOffset); + Span data = stackalloc Byte[UUID_STRING_LENGTH]; + for (Int32 i = 0; i < UUID_STRING_LENGTH; i++) data[i] = (Byte)chars[i]; - lower = LowerNine(lower); - lower = Vector128.Subtract(lower, _AddOffset); - - upper = Vector128.ShiftLeft(upper, 4); - - return Vector128.BitwiseOr(upper, lower); + return Parse(data); } /// @@ -164,37 +134,44 @@ internal static Vector128 Parse(ReadOnlySpan chars) { throw new ArgumentException($"The length of the string is not {chars.Length}", nameof(chars)); } - //Upper 4 bits goes first in the string and lower 4 bits goes second in the string - //We create two vectors, one for the upper 4 bits and one for the lower 4 bits - // - //No need to init, all values will be overwritten + Unsafe.SkipInit(out Vector128 upper); Unsafe.SkipInit(out Vector128 lower); static Vector128 LowerNine(Vector128 data) { - //Each element becomes 0xff if it is greater than 9 - var elementsGreatherThan = Vector128.GreaterThan(data, _9Vector); - var toAdd = Vector128.BitwiseAnd(elementsGreatherThan, _9AOffsetVector); - return Vector128.Subtract(data, toAdd); + // Bring 'a' - 'z' down to 'A' - 'Z' + // Bring 'A' - 'Z' down to values above '9' + // Bring '0' - '9' to values of 0 to 9 + + // If we have values still large then 9 we must have A-Z and a-z + // Bring a-z to A-Z + var elementsGreatherThan = Vector128.GreaterThan(data, Vector128.Create((Byte)'Z')); + var toSubstract = Vector128.BitwiseAnd(elementsGreatherThan, Vector128.Create('a' - 'A')); + data = Vector128.Subtract(data, toSubstract); + + elementsGreatherThan = Vector128.GreaterThan(data, Vector128.Create((Byte)'9')); + toSubstract = Vector128.BitwiseAnd(elementsGreatherThan, Vector128.Create('A' - ('9' + 1))); + data = Vector128.Subtract(data, toSubstract); + + data = Vector128.Subtract(data, Vector128.Create((Byte)'0')); + + return data; } - Int32 J = 0; - for (Int32 I = 0; I < UUID_STRING_LENGTH;) { - if (I is 8 or 13 or 18 or 23) { - I++; + Int32 j = 0; + for (Int32 i = 0; i < UUID_STRING_LENGTH;) { + if (i is 8 or 13 or 18 or 23) { + i++; continue; } - upper = Vector128.WithElement(upper, J, chars[I++]); - lower = Vector128.WithElement(lower, J, chars[I++]); - J++; + upper = Vector128.WithElement(upper, j, chars[i]); + lower = Vector128.WithElement(lower, j, chars[i + 1]); + i += 2; + j++; } upper = LowerNine(upper); - upper = Vector128.Subtract(upper, _AddOffset); - lower = LowerNine(lower); - lower = Vector128.Subtract(lower, _AddOffset); - upper = Vector128.ShiftLeft(upper, 4); return Vector128.BitwiseOr(upper, lower); diff --git a/Library/Static Classes/V1/V1 - Generate.cs b/Library/Static Classes/V1/V1 - Generate.cs index c19a725..612b5d8 100644 --- a/Library/Static Classes/V1/V1 - Generate.cs +++ b/Library/Static Classes/V1/V1 - Generate.cs @@ -58,32 +58,35 @@ public static UUID Generate(DateTime timestamp, UInt16 nanoSeconds, ReadOnlySpan /// A new [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal static UUID Generate(UInt64 timestamp, UInt16 nanoSeconds, ReadOnlySpan macAddress) { - //First 32 bites = time_low - //Next 16 bites = time_mid - //Next 16 bites = time_hi_version = time_high | version - //next 8 bites = clock_seq_hi_variant - //next 8 bites = clock_seq_low | node - //last 48 bites mac address + // RFC 4122 layout: + // 0-3: time_low + // 4-5: time_mid + // 6-7: time_hi_and_version + // 8: clock_seq_hi_and_reserved (variant in high bits) + // 9: clock_seq_low + // 10-15: node (MAC address) Span data = stackalloc Byte[Format.UUID_BYTE_LENGTH]; - //mac is 6 bytes, transfer to 8 bytes and use BinaryPrimitives to convert to UInt64 + // Write node (MAC address) macAddress.CopyTo(data[10..]); - var read = Vector64.Create(timestamp); - //Setting timelow to be the 32 least significant bits of the timestamp - UInt32 timeLow = read.AsUInt32().GetElement(0); + // Split timestamp + UInt32 timeLow = (UInt32)(timestamp & 0xFFFFFFFF); + UInt16 timeMid = (UInt16)((timestamp >> 32) & 0xFFFF); + UInt16 timeHi = (UInt16)((timestamp >> 48) & 0x0FFF); // 12 bits for time_hi + UInt16 timeHiAndVersion = (UInt16)(timeHi | ((UInt16)V1.Version << 8)); // Set version 1 using constant + BinaryPrimitives.WriteUInt32BigEndian(data, timeLow); - //Next 16 bites = time_mid - UInt16 timeMid = read.AsUInt16().GetElement(2); BinaryPrimitives.WriteUInt16BigEndian(data[4..], timeMid); - //Next 16 bites = time_hi_version = time_high | version - UInt16 timeHiVersion = read.AsUInt16().GetElement(3); - BinaryPrimitives.WriteUInt16BigEndian(data[6..], timeHiVersion); - BinaryPrimitives.WriteUInt16BigEndian(data[8..], nanoSeconds); + BinaryPrimitives.WriteUInt16BigEndian(data[6..], timeHiAndVersion); + + // Split nanoSeconds (clock sequence) into 14 bits + UInt16 clockSeq = (UInt16)(nanoSeconds & 0x3FFF); + data[8] = (Byte)(((clockSeq >> 8) & 0x3F) | (Byte)V1.Variant); // Set variant using constant + data[9] = (Byte)(clockSeq & 0xFF); var uuid = Vector128.Create(data); - uuid = Format.StampVersion(V1._VersionMask, V1._VersionOverlay, uuid); return new UUID(uuid); } } diff --git a/Library/Static Classes/V2/V2 - Batch.cs b/Library/Static Classes/V2/V2 - Batch.cs new file mode 100644 index 0000000..dd21810 --- /dev/null +++ b/Library/Static Classes/V2/V2 - Batch.cs @@ -0,0 +1,48 @@ +using System.Runtime.CompilerServices; + +namespace DaanV2.UUID; + +public static partial class V2 { + /// + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static UUID[] Batch(Int32 amount, Byte domain = 0) { + return Batch(amount, DateTime.UtcNow, V1.GetMacAddressBytes(), domain); + } + + /// + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static UUID[] Batch(Int32 amount, DateTime startTimestamp, Byte domain = 0) { + return Batch(amount, startTimestamp, V1.GetMacAddressBytes(), domain); + } + + /// + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static UUID[] Batch(Int32 amount, ReadOnlySpan macAddress, Byte domain = 0) { + return Batch(amount, DateTime.UtcNow, macAddress, domain); + } + + /// Creates a batch of , Note: This function use To grab a starting point, + /// but will increment the timestamp to create a new + /// The amount of to create + /// The time to start at, will increment for each following + /// The macAddress to use, excepts to be 6 bytes + /// An byte to indicate the domain + /// A collection of + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static UUID[] Batch(Int32 amount, DateTime startTimestamp, ReadOnlySpan macAddress, Byte domain = 0) { + var uuids = new UUID[amount]; + DateTime timestamp = startTimestamp; + for (Int32 i = 0; i < amount; i++) { + // Use i as the local identifier (UID/GID) for demonstration; domain is passed in + uuids[i] = Generate(timestamp, (UInt32)(i << 8), macAddress, domain); + // Optionally increment timestamp if you want unique timestamps per UUID + if (i % UInt16.MaxValue == 0) { + timestamp = timestamp.AddTicks(1); + } + } + return uuids; + } +} diff --git a/Library/Static Classes/V2/V2 - Const.cs b/Library/Static Classes/V2/V2 - Const.cs new file mode 100644 index 0000000..eebf93c --- /dev/null +++ b/Library/Static Classes/V2/V2 - Const.cs @@ -0,0 +1,16 @@ +using System.Runtime.Intrinsics; +using System.Security.Cryptography; + +namespace DaanV2.UUID; + +public static partial class V2 { + /// + public const Version Version = DaanV2.UUID.Version.V2; + /// + public const Variant Variant = DaanV2.UUID.Variant.V1; + /// The prefered minimum data length for chunking bytes array + public const Int32 MinimumDataLength = MD5.HashSizeInBytes; + + private static readonly Vector128 _VersionMask = Format.VersionVariantMaskNot(V2.Version, V2.Variant); + private static readonly Vector128 _VersionOverlay = Format.VersionVariantOverlayer(V2.Version, V2.Variant); +} diff --git a/Library/Static Classes/V2/V2 - Extract.cs b/Library/Static Classes/V2/V2 - Extract.cs new file mode 100644 index 0000000..a527621 --- /dev/null +++ b/Library/Static Classes/V2/V2 - Extract.cs @@ -0,0 +1,57 @@ +using System.Buffers.Binary; +using System.Runtime.Intrinsics; + +namespace DaanV2.UUID; + +public static partial class V2 { + /// A record of what information can be returned by a of version 2 (DCE Security) + public record Information { + /// The timestamp it was made at (may be imprecise due to overlays) + public DateTime Timestamp { get; init; } + /// The local identifier (UID/GID) embedded in the UUID + public UInt16 LocalIdentifier { get; init; } + /// The domain (person, group, org) embedded in the UUID + public Byte Domain { get; init; } + /// The mac address of the machine it was made on + public required Byte[] MacAddress { get; init; } + } + + /// Extract from the given its information record, if made with + /// The to extract from + /// A record of + public static Information Extract(UUID uuid) { + Span data = stackalloc Byte[Format.UUID_BYTE_LENGTH]; + uuid._Data.CopyTo(data); + + // Extract time_low (first 32 bits) + UInt32 timeLow = BinaryPrimitives.ReadUInt32BigEndian(data); + UInt64 timestampTicks = (UInt64)timeLow; + + // Extract time_mid (next 16 bits) + UInt16 timeMid = BinaryPrimitives.ReadUInt16BigEndian(data[4..]); + timestampTicks |= (UInt64)timeMid << 32; + + // Extract time_hi_version (next 16 bits) + UInt16 timeHiVersion = BinaryPrimitives.ReadUInt16BigEndian(data[6..]); + timestampTicks |= (UInt64)(timeHiVersion & 0x0FFF) << 48; // Clear version bits + + // Convert timestampTicks to DateTime (uses V1 epoch, as V2 overlays V1 layout) + Int64 timestampFileTime = (Int64)(timestampTicks - V1.Epoch); + var timestamp = DateTime.FromFileTimeUtc(timestampFileTime); + + // Extract domain (high 6 bits of data[8]) + Byte domain = (Byte)(data[8] & 0x3F); + // Extract local identifier (UID/GID) + UInt16 localIdentifier = (UInt16)((data[8] & 0x3F) << 8); + + // Extract mac address + Byte[] macAddress = data.Slice(10, 6).ToArray(); + + return new Information() { + MacAddress = macAddress, + LocalIdentifier = localIdentifier, + Domain = domain, + Timestamp = timestamp + }; + } +} diff --git a/Library/Static Classes/V2/V2 - Generate.cs b/Library/Static Classes/V2/V2 - Generate.cs new file mode 100644 index 0000000..10c832c --- /dev/null +++ b/Library/Static Classes/V2/V2 - Generate.cs @@ -0,0 +1,75 @@ +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace DaanV2.UUID; + +public static partial class V2 { + /// + /// Generates a Version 2 (DCE Security) UUID using the current time, UID, and MAC address. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static UUID Generate() { + // Use current time, current UID, and MAC address + return Generate(DateTime.UtcNow, GetCurrentUid(), V1.GetMacAddressBytes()); + } + + /// + /// Generates a Version 2 UUID with custom timestamp, UID, and MAC address. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static UUID Generate(DateTime timestamp, UInt32 localIdentifier, ReadOnlySpan macAddress, Byte domain = 0) { + UInt64 uuidTimestamp = V1.TimeStamp(timestamp); + + Span data = stackalloc Byte[16]; + + // Write node (MAC address) + macAddress.CopyTo(data[10..]); + + // Split timestamp + UInt32 timeLow = (UInt32)(uuidTimestamp & 0xFFFFFFFF); + UInt16 timeMid = (UInt16)((uuidTimestamp >> 32) & 0xFFFF); + UInt16 timeHi = (UInt16)((uuidTimestamp >> 48) & 0x0FFF); // 12 bits for time_hi + + BinaryPrimitives.WriteUInt32BigEndian(data, timeLow); + BinaryPrimitives.WriteUInt16BigEndian(data[4..], timeMid); + BinaryPrimitives.WriteUInt16BigEndian(data[6..], timeHi); + + // Per RFC 4122 DCE Security UUID: + // data[8]: high 2 bits = variant, low 6 bits = high 6 bits of local identifier + // data[9]: domain (per spec), or low 8 bits of local identifier if domain not used + data[8] = (Byte)((localIdentifier >> 8) & 0x3F); + data[9] = domain; // Store domain in data[9] + Vector128 uuid = Format.StampVersion(_VersionMask, _VersionOverlay, data); + + return new UUID(uuid); + } + + /// + /// Gets the current user's UID (Linux only). Returns 0 if not available. + /// + private static UInt32 GetCurrentUid() { + try { + return (UInt32)System.Convert.ToInt32(Environment.GetEnvironmentVariable("UID") ?? "0"); + } + catch { + return 0; + } + } + + /// + /// Generates a Version 2 UUID from a byte source (not standard, but for API compatibility). + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static UUID Generate(ReadOnlySpan source) { + // Not standard for V2, but for API compatibility: hash and overlay version/variant + Span data = stackalloc Byte[16]; + System.Security.Cryptography.MD5.HashData(source, data); + // Set version and variant using V2.Version and V2.Variant constants + data[6] = (Byte)((data[6] & 0x0F) | (Byte)Version.V2); + data[8] = (Byte)((data[8] & 0x3F) | (Byte)Variant.V1); + + var uuid = Vector128.Create(data); + return new UUID(uuid); + } +} diff --git a/Library/Static Classes/V2/V2 - Utillity.cs b/Library/Static Classes/V2/V2 - Utillity.cs new file mode 100644 index 0000000..feff206 --- /dev/null +++ b/Library/Static Classes/V2/V2 - Utillity.cs @@ -0,0 +1,16 @@ +using System.Net.NetworkInformation; + +namespace DaanV2.UUID; + +public static partial class V2 { + + /// + public static PhysicalAddress? GetMacAddress() { + return V1.GetMacAddress(); + } + + /// + public static Byte[] GetMacAddressBytes() { + return V1.GetMacAddressBytes(); + } +} \ No newline at end of file diff --git a/Library/Static Classes/V2/V2.cs b/Library/Static Classes/V2/V2.cs new file mode 100644 index 0000000..e3d2d28 --- /dev/null +++ b/Library/Static Classes/V2/V2.cs @@ -0,0 +1,6 @@ +namespace DaanV2.UUID; + +/// The tjat os nased pm DCE Security, with embedded POSIX UIDs. +public static partial class V2 { + +} \ No newline at end of file diff --git a/Library/Static Classes/V4/V4 - Generate.cs b/Library/Static Classes/V4/V4 - Generate.cs index 9517ad4..a94e417 100644 --- a/Library/Static Classes/V4/V4 - Generate.cs +++ b/Library/Static Classes/V4/V4 - Generate.cs @@ -19,7 +19,7 @@ public static UUID Generate(Random rnd) { /// public static UUID Generate(Stream stream) { Span bytes = stackalloc Byte[Format.UUID_BYTE_LENGTH]; - stream.Read(bytes); + stream.ReadExactly(bytes); return Generate(bytes); } diff --git a/Library/Static Classes/V6/V6.cs b/Library/Static Classes/V6/V6.cs index ff1de09..5131e62 100644 --- a/Library/Static Classes/V6/V6.cs +++ b/Library/Static Classes/V6/V6.cs @@ -1,4 +1,11 @@ -using System.Net.NetworkInformation; + +// Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved. +// +// This code is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of RFC 9562. +// Code Components extracted from RFC 9562 must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. +// +// This code was derived from IETF RFC 9562. Please reproduce this note if possible. +using System.Net.NetworkInformation; namespace DaanV2.UUID; diff --git a/Library/Static Classes/V7/V7.cs b/Library/Static Classes/V7/V7.cs index abaf8b2..234aa5f 100644 --- a/Library/Static Classes/V7/V7.cs +++ b/Library/Static Classes/V7/V7.cs @@ -1,4 +1,10 @@ -using System; +// Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved. +// +// This code is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of RFC 9562. +// Code Components extracted from RFC 9562 must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. +// +// This code was derived from IETF RFC 9562. Please reproduce this note if possible. +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Library/Static Classes/V8/V8.cs b/Library/Static Classes/V8/V8.cs index fd84814..d3b759a 100644 --- a/Library/Static Classes/V8/V8.cs +++ b/Library/Static Classes/V8/V8.cs @@ -1,4 +1,10 @@ -using System; +// Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved. +// +// This code is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of RFC 9562. +// Code Components extracted from RFC 9562 must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. +// +// This code was derived from IETF RFC 9562. Please reproduce this note if possible. +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Library/Static Classes/VectorExtension/VectorExtension - Reverse.cs b/Library/Static Classes/VectorExtension/VectorExtension - Reverse.cs index e1dbe1d..62f5caf 100644 --- a/Library/Static Classes/VectorExtension/VectorExtension - Reverse.cs +++ b/Library/Static Classes/VectorExtension/VectorExtension - Reverse.cs @@ -1,14 +1,18 @@ using System.Runtime.Intrinsics; namespace DaanV2.UUID; -internal static partial class VectorExtension { + +internal static partial class VectorExtension +{ /// The reverse indexes private static readonly Vector128 _ReverseIndexes = Vector128.Create((Byte)15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); /// Reverses the order of the bytes /// The value to reverse /// A - public static Vector128 Reverse(this Vector128 value) { + public static Vector128 Reverse(this Vector128 value) + { return Vector128.Shuffle(value, _ReverseIndexes); } + } diff --git a/README.md b/README.md index d8d3365..d67a24e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A library that provides a way to handle and generate UUIDs. Convert them to and from strings, GUIDs, and the like. The library is written to be fast and efficient when comparing, generating, or handling operations. Provides ways to generate UUIDs from different data, like a string, a byte array, or cutting up a byte array into UUIDs. -Complies with the RFC 4122 standard. And has version 1-8 UUIDs implemented. except 2. Which I haven't been able to figure out how to implement. +Complies with the RFC 4122 / RFC 9562 standard. And has version 1-8 UUIDs implemented. ## Usage Example Below are two examples of generating UUIDs and usage diff --git a/Tests/FormatTest/String & Parse.cs b/Tests/FormatTest/String & Parse.cs index c2e4bef..a6c6e35 100644 --- a/Tests/FormatTest/String & Parse.cs +++ b/Tests/FormatTest/String & Parse.cs @@ -29,4 +29,41 @@ public void TestParse() { Assert.Equal(uuid.GetHashCode(), Temp2.GetHashCode()); } + [Theory()] + [InlineData("c232ab00-9414-11ec-b3c8-9F6BDECED846")] + [InlineData("c232ab00-9414-11ec-b3c8-9f6bdeced846")] + [InlineData("C232AB00-9414-11EC-B3C8-9F6BDECED846")] + public void TestSpecific(String data) { + var tmp = UUID.Parse(data); + String back = tmp.ToString(); + Assert.Equal(back, data.ToLower()); + + Utility.ValidateUUID(tmp); + } + + [Theory()] + [MemberData(nameof(Characters))] + public void TestSpecificCharacters(Char value) { + String data = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".Replace('x', value); + var tmp = UUID.Parse(data); + String back = tmp.ToString(); + Assert.Equal(back, data.ToLower()); + + Utility.ValidateUUID(tmp); + } + + public static IEnumerable Characters { + get { + String chars = "abcdefABCDEF0123456789"; + var result = new List(); + + foreach (Char c in chars) { + result.Add(new Object[] { c }); + } + + return result; + } + + } + } \ No newline at end of file diff --git a/Tests/Generation/V2Tests.cs b/Tests/Generation/V2Tests.cs new file mode 100644 index 0000000..3ff6888 --- /dev/null +++ b/Tests/Generation/V2Tests.cs @@ -0,0 +1,42 @@ +using DaanV2.UUID; + +namespace Tests.Generation; +public sealed partial class V2Tests { + [Fact(DisplayName = "When generating with specific time returns expected result")] + public void TestSpecific1() { + var dateTime = new DateTime(2019, 1, 2, 3, 4, 5, DateTimeKind.Utc); + UUID uuid = V2.Generate(dateTime, 0, V2.GetMacAddressBytes()); + + Utility.ValidateUUID(uuid, V2.Version, V2.Variant); + Byte[] macAddress = V2.GetMacAddressBytes(); + String machex = BitConverter.ToString(macAddress).Replace("-", String.Empty).ToLower(); + + String str = uuid.ToString(); + + // V2 string format may differ, so only check length and mac ending + Assert.EndsWith($"-{machex}", str); + Assert.Equal(36, str.Length); + + V2.Information data = V2.Extract(uuid); + Assert.Equal(dateTime, data.Timestamp); + Assert.Equal(0u, data.LocalIdentifier); + Assert.Equal(V2.GetMacAddressBytes(), data.MacAddress); + } + + [Theory(DisplayName = "When batch generating, will always return unique UUIDs")] + [InlineData(10)] + public void TestBatchUnique(Int32 Amount) { + UUID[] UUIDs = V2.Batch(Amount); + Assert.Equal(Amount, UUIDs.Length); + + for (Int32 i = 0; i < UUIDs.Length; i++) { + for (Int32 j = 0; j < UUIDs.Length; j++) { + if (i == j) { + continue; + } + + Assert.NotEqual(UUIDs[i], UUIDs[j]); + } + } + } +} diff --git a/Tests/RFC/RFC4122.cs b/Tests/RFC/RFC4122.cs new file mode 100644 index 0000000..d19bc03 --- /dev/null +++ b/Tests/RFC/RFC4122.cs @@ -0,0 +1,15 @@ +using DaanV2.UUID; + +namespace Tests.RFC; + +public sealed partial class RFC4122Tests { + + [Fact(DisplayName = "RFC4122 Appendix B v3 (MD5, DNS, www.example.com)")] + public void RFC4122_AppendixB_V3_MD5_DNS_ExampleCom() { + // Per RFC4122 Appendix B, corrected by EID 3476 + var uuid = new UUID("5df41881-3aed-3515-88a7-2f4a814cf09e"); + Assert.Equal("5df41881-3aed-3515-88a7-2f4a814cf09e", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V3.Version, V3.Variant); + } + +} diff --git a/Tests/RFC/RFC9562.cs b/Tests/RFC/RFC9562.cs new file mode 100644 index 0000000..34a717d --- /dev/null +++ b/Tests/RFC/RFC9562.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved. +// +// This code is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of RFC 9562. +// Code Components extracted from RFC 9562 must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. +// +// This code was derived from IETF RFC 9562. Please reproduce this note if possible. + +using DaanV2.UUID; + +namespace Tests.RFC; + +public sealed partial class RFC9562Tests { + [Fact(DisplayName = "UUIDv1 RFC test vector")] + public void UUIDv1_RFC_TestVector() { + var uuid = new UUID("C232AB00-9414-11EC-B3C8-9F6BDECED846"); + Assert.Equal("c232ab00-9414-11ec-b3c8-9f6bdeced846", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V1.Version, V1.Variant); + } + + [Fact(DisplayName = "UUIDv3 RFC test vector")] + public void UUIDv3_RFC_TestVector() { + var uuid = new UUID("5df41881-3aed-3515-88a7-2f4a814cf09e"); + Assert.Equal("5df41881-3aed-3515-88a7-2f4a814cf09e", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V3.Version, V3.Variant); + } + + [Fact(DisplayName = "UUIDv4 RFC test vector")] + public void UUIDv4_RFC_TestVector() { + var uuid = new UUID("919108f7-52d1-4320-9bac-f847db4148a8"); + Assert.Equal("919108f7-52d1-4320-9bac-f847db4148a8", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V4.Version, V4.Variant); + } + + [Fact(DisplayName = "UUIDv5 RFC test vector")] + public void UUIDv5_RFC_TestVector() { + var uuid = new UUID("2ed6657d-e927-568b-95e1-2665a8aea6a2"); + Assert.Equal("2ed6657d-e927-568b-95e1-2665a8aea6a2", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V5.Version, V5.Variant); + } + + [Fact(DisplayName = "UUIDv6 RFC test vector")] + public void UUIDv6_RFC_TestVector() { + var uuid = new UUID("1ec9414c-232a-6b00-b3c8-9f6bdeced846"); + Assert.Equal("1ec9414c-232a-6b00-b3c8-9f6bdeced846", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V6.Version, V6.Variant); + } + + [Fact(DisplayName = "UUIDv7 RFC test vector")] + public void UUIDv7_RFC_TestVector() { + var uuid = new UUID("017f22e2-79b0-7cc3-98c4-dc0c0c07398f"); + Assert.Equal("017f22e2-79b0-7cc3-98c4-dc0c0c07398f", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V7.Version, V7.Variant); + } + + [Fact(DisplayName = "UUIDv8 RFC test vector (time-based)")] + public void UUIDv8_RFC_TestVector_TimeBased() { + var uuid = new UUID("2489e9ad-2ee2-8e00-8ec9-32d5f69181c0"); + Assert.Equal("2489e9ad-2ee2-8e00-8ec9-32d5f69181c0", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V8.Version, V8.Variant); + } + + [Fact(DisplayName = "UUIDv8 RFC test vector (name-based SHA-256)")] + public void UUIDv8_RFC_TestVector_NameBased() { + var uuid = new UUID("5c146b14-3c52-8afd-938a-375d0df1fbf6"); + Assert.Equal("5c146b14-3c52-8afd-938a-375d0df1fbf6", uuid.ToString().ToLower()); + Utility.ValidateUUID(uuid, V8.Version, V8.Variant); + } +} diff --git a/Tests/Units Tests.csproj b/Tests/Units Tests.csproj index 7f07405..c9a665d 100644 --- a/Tests/Units Tests.csproj +++ b/Tests/Units Tests.csproj @@ -1,18 +1,13 @@ - net7.0;net8.0;net9.0 + net8.0;net9.0 enable enable - false - $(ProjectName) - Tests - AnyCPU;x64;x86 - False