Skip to content

Commit ef14220

Browse files
authored
feat: adding RFC 9562 (#40)
* feat: adding V2 * more extensive tests * adding legal notifce from rfc9562 * fix: adding support for upper and lowercase for parsing --------- Signed-off-by: GitHub <[email protected]>
1 parent 394a588 commit ef14220

File tree

27 files changed

+485
-95
lines changed

27 files changed

+485
-95
lines changed

.devcontainer/devcontainer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"image": "mcr.microsoft.com/devcontainers/universal:2"
3+
}

.github/workflows/publish.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ jobs:
6868
uses: actions/setup-dotnet@v4
6969
with:
7070
dotnet-version: |
71-
7.0.x
7271
8.0.x
7372
9.0.x
7473

Benchmark/Benchmark.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
5+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<IsPublishable>False</IsPublishable>

Benchmark/Dictionary/Class.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
namespace Benchmark.Dictionary;
22

3+
#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.
34
public class DictonaryTestClass {
45
public String Data1 { get; set; }
56
public String Data2 { get; set; }
67
public String Data3 { get; set; }
78
}
9+
10+
#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.

LICENSE.rfc9562.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.
2+
3+
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.

Library/Classes/UUIDJsonConverter/UUIDJsonConverter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ public partial class UUIDJsonConverter : JsonConverter<UUID> {
1212
public override UUID Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
1313
ReadOnlySpan<Byte> data = reader.ValueSpan;
1414

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

20-
throw new JsonException("Unknown UUID format");
19+
Vector128<Byte> d = Format.Parse(data);
20+
return new UUID(d);
2121
}
2222

2323
/// <inheritdoc/>

Library/DaanV2.UUID.Net.csproj

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
4+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<AssemblyName>DaanV2.UUID.Net</AssemblyName>
@@ -17,13 +17,13 @@
1717
<SignAssembly>False</SignAssembly>
1818
<Description>A library that provides a way to handle and generate UUIDs. Convert them to and from strings, GUIDs, and the like.
1919
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.
20-
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>
20+
Complies with the RFC 4122 / RFC 9562 standard. And has version 1-8 UUIDs implemented.</Description>
2121
<PackageProjectUrl>https://github.com/DaanV2/DaanV2.UUID.Net</PackageProjectUrl>
2222
<PackageIcon>icon.png</PackageIcon>
2323
<PackageReadmeFile>README.md</PackageReadmeFile>
2424
<RepositoryUrl>https://github.com/DaanV2/DaanV2.UUID.Net</RepositoryUrl>
2525
<RepositoryType>git</RepositoryType>
26-
<PackageTags>UUID;RFC-4122;V1;V3;V4;V5;V6;V7;V8</PackageTags>
26+
<PackageTags>UUID;RFC-4122;RFC-9562;V1;V2;V3;V4;V5;V6;V7;V8</PackageTags>
2727
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
2828
<IncludeSymbols>True</IncludeSymbols>
2929
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
@@ -58,9 +58,13 @@ Complies with the RFC 4122 standard. And has version 1-8 UUIDs implemented. exce
5858
<Pack>True</Pack>
5959
<PackagePath>\</PackagePath>
6060
</None>
61+
<None Include="..\LICENSE.rfc9562.txt">
62+
<Pack>True</Pack>
63+
<PackagePath>\</PackagePath>
64+
</None>
6165
<None Include="..\README.md">
62-
<Pack>True</Pack>
63-
<PackagePath>\</PackagePath>
66+
<Pack>True</Pack>
67+
<PackagePath>\</PackagePath>
6468
</None>
6569
<None Include="..\Resources\icon.png">
6670
<Pack>True</Pack>

Library/Static Classes/Format/Format - Extract.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static (UInt64 bits48, UInt16 bits12, UInt64 bits62) Extract(UUID uuid) {
1919

2020
// Remove top 2 bits, so we have 62 bits of data
2121
const UInt64 mask = 0xC000000000000000u;
22-
dataC = (dataC & ~mask);
22+
dataC &= ~mask;
2323

2424
return (dataA, dataB, dataC);
2525
}

Library/Static Classes/Format/Format - String.cs

Lines changed: 29 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -122,40 +122,10 @@ public static Vector128<Byte> Parse(ReadOnlySpan<Char> chars) {
122122
throw new ArgumentException($"The length of the string is not {chars}", nameof(chars));
123123
}
124124

125-
//Upper 4 bits goes first in the string and lower 4 bits goes second in the string
126-
//We create two vectors, one for the upper 4 bits and one for the lower 4 bits
127-
//
128-
//No need to init, all values will be overwritten
129-
Unsafe.SkipInit(out Vector128<Byte> upper);
130-
Unsafe.SkipInit(out Vector128<Byte> lower);
131-
132-
static Vector128<Byte> LowerNine(Vector128<Byte> data) {
133-
//Each element becomes 0xff if it is greater than 9
134-
var elementsGreatherThan = Vector128.GreaterThan(data, _9Vector);
135-
var toAdd = Vector128.BitwiseAnd(elementsGreatherThan, _9AOffsetVector);
136-
return Vector128.Subtract(data, toAdd);
137-
}
138-
139-
Int32 J = 0;
140-
for (Int32 I = 0; I < UUID_STRING_LENGTH;) {
141-
if (I is 8 or 13 or 18 or 23) {
142-
I++;
143-
continue;
144-
}
145-
upper = Vector128.WithElement(upper, J, (Byte)chars[I++]);
146-
lower = Vector128.WithElement(lower, J, (Byte)chars[I++]);
147-
J++;
148-
}
149-
150-
upper = LowerNine(upper);
151-
upper = Vector128.Subtract(upper, _AddOffset);
125+
Span<Byte> data = stackalloc Byte[UUID_STRING_LENGTH];
126+
for (Int32 i = 0; i < UUID_STRING_LENGTH; i++) data[i] = (Byte)chars[i];
152127

153-
lower = LowerNine(lower);
154-
lower = Vector128.Subtract(lower, _AddOffset);
155-
156-
upper = Vector128.ShiftLeft(upper, 4);
157-
158-
return Vector128.BitwiseOr(upper, lower);
128+
return Parse(data);
159129
}
160130

161131
/// <inheritdoc cref="Parse(ReadOnlySpan{Char})"/>
@@ -164,37 +134,44 @@ internal static Vector128<Byte> Parse(ReadOnlySpan<Byte> chars) {
164134
throw new ArgumentException($"The length of the string is not {chars.Length}", nameof(chars));
165135
}
166136

167-
//Upper 4 bits goes first in the string and lower 4 bits goes second in the string
168-
//We create two vectors, one for the upper 4 bits and one for the lower 4 bits
169-
//
170-
//No need to init, all values will be overwritten
137+
171138
Unsafe.SkipInit(out Vector128<Byte> upper);
172139
Unsafe.SkipInit(out Vector128<Byte> lower);
173140

174141
static Vector128<Byte> LowerNine(Vector128<Byte> data) {
175-
//Each element becomes 0xff if it is greater than 9
176-
var elementsGreatherThan = Vector128.GreaterThan(data, _9Vector);
177-
var toAdd = Vector128.BitwiseAnd(elementsGreatherThan, _9AOffsetVector);
178-
return Vector128.Subtract(data, toAdd);
142+
// Bring 'a' - 'z' down to 'A' - 'Z'
143+
// Bring 'A' - 'Z' down to values above '9'
144+
// Bring '0' - '9' to values of 0 to 9
145+
146+
// If we have values still large then 9 we must have A-Z and a-z
147+
// Bring a-z to A-Z
148+
var elementsGreatherThan = Vector128.GreaterThan(data, Vector128.Create<Byte>((Byte)'Z'));
149+
var toSubstract = Vector128.BitwiseAnd(elementsGreatherThan, Vector128.Create<Byte>('a' - 'A'));
150+
data = Vector128.Subtract<Byte>(data, toSubstract);
151+
152+
elementsGreatherThan = Vector128.GreaterThan(data, Vector128.Create<Byte>((Byte)'9'));
153+
toSubstract = Vector128.BitwiseAnd(elementsGreatherThan, Vector128.Create<Byte>('A' - ('9' + 1)));
154+
data = Vector128.Subtract<Byte>(data, toSubstract);
155+
156+
data = Vector128.Subtract<Byte>(data, Vector128.Create((Byte)'0'));
157+
158+
return data;
179159
}
180160

181-
Int32 J = 0;
182-
for (Int32 I = 0; I < UUID_STRING_LENGTH;) {
183-
if (I is 8 or 13 or 18 or 23) {
184-
I++;
161+
Int32 j = 0;
162+
for (Int32 i = 0; i < UUID_STRING_LENGTH;) {
163+
if (i is 8 or 13 or 18 or 23) {
164+
i++;
185165
continue;
186166
}
187-
upper = Vector128.WithElement(upper, J, chars[I++]);
188-
lower = Vector128.WithElement(lower, J, chars[I++]);
189-
J++;
167+
upper = Vector128.WithElement(upper, j, chars[i]);
168+
lower = Vector128.WithElement(lower, j, chars[i + 1]);
169+
i += 2;
170+
j++;
190171
}
191172

192173
upper = LowerNine(upper);
193-
upper = Vector128.Subtract(upper, _AddOffset);
194-
195174
lower = LowerNine(lower);
196-
lower = Vector128.Subtract(lower, _AddOffset);
197-
198175
upper = Vector128.ShiftLeft(upper, 4);
199176

200177
return Vector128.BitwiseOr(upper, lower);

Library/Static Classes/V1/V1 - Generate.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,35 @@ public static UUID Generate(DateTime timestamp, UInt16 nanoSeconds, ReadOnlySpan
5858
/// <returns>A new <see cref="UUID"/></returns>
5959
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
6060
internal static UUID Generate(UInt64 timestamp, UInt16 nanoSeconds, ReadOnlySpan<Byte> macAddress) {
61-
//First 32 bites = time_low
62-
//Next 16 bites = time_mid
63-
//Next 16 bites = time_hi_version = time_high | version
64-
//next 8 bites = clock_seq_hi_variant
65-
//next 8 bites = clock_seq_low | node
66-
//last 48 bites mac address
61+
// RFC 4122 layout:
62+
// 0-3: time_low
63+
// 4-5: time_mid
64+
// 6-7: time_hi_and_version
65+
// 8: clock_seq_hi_and_reserved (variant in high bits)
66+
// 9: clock_seq_low
67+
// 10-15: node (MAC address)
6768

6869
Span<Byte> data = stackalloc Byte[Format.UUID_BYTE_LENGTH];
6970

70-
//mac is 6 bytes, transfer to 8 bytes and use BinaryPrimitives to convert to UInt64
71+
// Write node (MAC address)
7172
macAddress.CopyTo(data[10..]);
72-
var read = Vector64.Create<UInt64>(timestamp);
7373

74-
//Setting timelow to be the 32 least significant bits of the timestamp
75-
UInt32 timeLow = read.AsUInt32().GetElement(0);
74+
// Split timestamp
75+
UInt32 timeLow = (UInt32)(timestamp & 0xFFFFFFFF);
76+
UInt16 timeMid = (UInt16)((timestamp >> 32) & 0xFFFF);
77+
UInt16 timeHi = (UInt16)((timestamp >> 48) & 0x0FFF); // 12 bits for time_hi
78+
UInt16 timeHiAndVersion = (UInt16)(timeHi | ((UInt16)V1.Version << 8)); // Set version 1 using constant
79+
7680
BinaryPrimitives.WriteUInt32BigEndian(data, timeLow);
77-
//Next 16 bites = time_mid
78-
UInt16 timeMid = read.AsUInt16().GetElement(2);
7981
BinaryPrimitives.WriteUInt16BigEndian(data[4..], timeMid);
80-
//Next 16 bites = time_hi_version = time_high | version
81-
UInt16 timeHiVersion = read.AsUInt16().GetElement(3);
82-
BinaryPrimitives.WriteUInt16BigEndian(data[6..], timeHiVersion);
83-
BinaryPrimitives.WriteUInt16BigEndian(data[8..], nanoSeconds);
82+
BinaryPrimitives.WriteUInt16BigEndian(data[6..], timeHiAndVersion);
83+
84+
// Split nanoSeconds (clock sequence) into 14 bits
85+
UInt16 clockSeq = (UInt16)(nanoSeconds & 0x3FFF);
86+
data[8] = (Byte)(((clockSeq >> 8) & 0x3F) | (Byte)V1.Variant); // Set variant using constant
87+
data[9] = (Byte)(clockSeq & 0xFF);
8488

8589
var uuid = Vector128.Create<Byte>(data);
86-
uuid = Format.StampVersion(V1._VersionMask, V1._VersionOverlay, uuid);
8790
return new UUID(uuid);
8891
}
8992
}

0 commit comments

Comments
 (0)