Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2"
}
1 change: 0 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
7.0.x
8.0.x
9.0.x

Expand Down
2 changes: 1 addition & 1 deletion Benchmark/Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPublishable>False</IsPublishable>
Expand Down
3 changes: 3 additions & 0 deletions Benchmark/Dictionary/Class.cs
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions LICENSE.rfc9562.txt
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions Library/Classes/UUIDJsonConverter/UUIDJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public partial class UUIDJsonConverter : JsonConverter<UUID> {
public override UUID Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
ReadOnlySpan<Byte> data = reader.ValueSpan;

if (data != null && data.Length == Format.UUID_STRING_LENGTH) {
Vector128<Byte> d = Format.Parse(data);
return new UUID(d);
if (data == Span<Byte>.Empty || data.Length != Format.UUID_STRING_LENGTH) {
throw new JsonException("Unknown UUID format");
}

throw new JsonException("Unknown UUID format");
Vector128<Byte> d = Format.Parse(data);
return new UUID(d);
}

/// <inheritdoc/>
Expand Down
16 changes: 10 additions & 6 deletions Library/DaanV2.UUID.Net.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>DaanV2.UUID.Net</AssemblyName>
Expand All @@ -17,13 +17,13 @@
<SignAssembly>False</SignAssembly>
<Description>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.</Description>
Complies with the RFC 4122 / RFC 9562 standard. And has version 1-8 UUIDs implemented.</Description>
<PackageProjectUrl>https://github.com/DaanV2/DaanV2.UUID.Net</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/DaanV2/DaanV2.UUID.Net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>UUID;RFC-4122;V1;V3;V4;V5;V6;V7;V8</PackageTags>
<PackageTags>UUID;RFC-4122;RFC-9562;V1;V2;V3;V4;V5;V6;V7;V8</PackageTags>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
Expand Down Expand Up @@ -58,9 +58,13 @@ Complies with the RFC 4122 standard. And has version 1-8 UUIDs implemented. exce
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\LICENSE.rfc9562.txt">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\Resources\icon.png">
<Pack>True</Pack>
Expand Down
2 changes: 1 addition & 1 deletion Library/Static Classes/Format/Format - Extract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
81 changes: 29 additions & 52 deletions Library/Static Classes/Format/Format - String.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,40 +122,10 @@ public static Vector128<Byte> Parse(ReadOnlySpan<Char> 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<Byte> upper);
Unsafe.SkipInit(out Vector128<Byte> lower);

static Vector128<Byte> LowerNine(Vector128<Byte> 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<Byte> 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);
}

/// <inheritdoc cref="Parse(ReadOnlySpan{Char})"/>
Expand All @@ -164,37 +134,44 @@ internal static Vector128<Byte> Parse(ReadOnlySpan<Byte> 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<Byte> upper);
Unsafe.SkipInit(out Vector128<Byte> lower);

static Vector128<Byte> LowerNine(Vector128<Byte> 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>((Byte)'Z'));
var toSubstract = Vector128.BitwiseAnd(elementsGreatherThan, Vector128.Create<Byte>('a' - 'A'));
data = Vector128.Subtract<Byte>(data, toSubstract);

elementsGreatherThan = Vector128.GreaterThan(data, Vector128.Create<Byte>((Byte)'9'));
toSubstract = Vector128.BitwiseAnd(elementsGreatherThan, Vector128.Create<Byte>('A' - ('9' + 1)));
data = Vector128.Subtract<Byte>(data, toSubstract);

data = Vector128.Subtract<Byte>(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);
Expand Down
37 changes: 20 additions & 17 deletions Library/Static Classes/V1/V1 - Generate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,35 @@ public static UUID Generate(DateTime timestamp, UInt16 nanoSeconds, ReadOnlySpan
/// <returns>A new <see cref="UUID"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static UUID Generate(UInt64 timestamp, UInt16 nanoSeconds, ReadOnlySpan<Byte> 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<Byte> 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<UInt64>(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<Byte>(data);
uuid = Format.StampVersion(V1._VersionMask, V1._VersionOverlay, uuid);
return new UUID(uuid);
}
}
48 changes: 48 additions & 0 deletions Library/Static Classes/V2/V2 - Batch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Runtime.CompilerServices;

namespace DaanV2.UUID;

public static partial class V2 {
/// <inheritdoc cref="Batch(Int32, DateTime, ReadOnlySpan{Byte}, Byte)"/>

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static UUID[] Batch(Int32 amount, Byte domain = 0) {
return Batch(amount, DateTime.UtcNow, V1.GetMacAddressBytes(), domain);
}

/// <inheritdoc cref="Batch(Int32, DateTime, ReadOnlySpan{Byte}, Byte)"/>

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static UUID[] Batch(Int32 amount, DateTime startTimestamp, Byte domain = 0) {
return Batch(amount, startTimestamp, V1.GetMacAddressBytes(), domain);
}

/// <inheritdoc cref="Batch(Int32, DateTime, ReadOnlySpan{Byte}, Byte)"/>

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static UUID[] Batch(Int32 amount, ReadOnlySpan<Byte> macAddress, Byte domain = 0) {
return Batch(amount, DateTime.UtcNow, macAddress, domain);
}

/// <summary>Creates a batch of <see cref="UUID"/>, Note: This function use <see cref="DateTime.Now"/> To grab a starting point,
/// but will increment the timestamp to create a new <see cref="UUID"/></summary>
/// <param name="amount">The amount of <see cref="UUID"/> to create</param>
/// <param name="startTimestamp">The time to start at, will increment for each following <see cref="UUID"/></param>
/// <param name="macAddress">The macAddress to use, excepts to be 6 bytes</param>
/// <param name="domain">An byte to indicate the domain</param>
/// <returns>A collection of <see cref="UUID"/></returns>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static UUID[] Batch(Int32 amount, DateTime startTimestamp, ReadOnlySpan<Byte> 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;
}
}
16 changes: 16 additions & 0 deletions Library/Static Classes/V2/V2 - Const.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Runtime.Intrinsics;
using System.Security.Cryptography;

namespace DaanV2.UUID;

public static partial class V2 {
/// <inheritdoc cref="V2.Version"/>
public const Version Version = DaanV2.UUID.Version.V2;
/// <inheritdoc cref="V2.Variant"/>
public const Variant Variant = DaanV2.UUID.Variant.V1;
/// <summary>The prefered minimum data length for chunking bytes array</summary>
public const Int32 MinimumDataLength = MD5.HashSizeInBytes;

private static readonly Vector128<Byte> _VersionMask = Format.VersionVariantMaskNot(V2.Version, V2.Variant);
private static readonly Vector128<Byte> _VersionOverlay = Format.VersionVariantOverlayer(V2.Version, V2.Variant);
}
57 changes: 57 additions & 0 deletions Library/Static Classes/V2/V2 - Extract.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Buffers.Binary;
using System.Runtime.Intrinsics;

namespace DaanV2.UUID;

public static partial class V2 {
/// <summary>A record of what information can be returned by a <see cref="UUID"/> of version 2 (DCE Security)</summary>
public record Information {
/// <summary>The timestamp it was made at (may be imprecise due to overlays)</summary>
public DateTime Timestamp { get; init; }
/// <summary>The local identifier (UID/GID) embedded in the UUID</summary>
public UInt16 LocalIdentifier { get; init; }
/// <summary>The domain (person, group, org) embedded in the UUID</summary>
public Byte Domain { get; init; }
/// <summary>The mac address of the machine it was made on</summary>
public required Byte[] MacAddress { get; init; }
}

/// <summary>Extract from the given <see cref="UUID"/> its information record, if made with <see cref="V2"/></summary>
/// <param name="uuid">The <see cref="UUID"/> to extract from</param>
/// <returns>A record of <see cref="Information"/></returns>
public static Information Extract(UUID uuid) {
Span<Byte> 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
};
}
}
Loading
Loading