Skip to content

Commit 13f4af4

Browse files
authored
feat: adding V2
1 parent 394a588 commit 13f4af4

File tree

8 files changed

+225
-19
lines changed

8 files changed

+225
-19
lines changed

Library/DaanV2.UUID.Net.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
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>

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 | (1 << 12)); // Set version 1
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) | 0x80); // Set variant (10xxxxxx)
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
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace DaanV2.UUID;
4+
5+
public static partial class V2 {
6+
/// <inheritdoc cref="Batch(Int32, DateTime, ReadOnlySpan{Byte})"/>
7+
8+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
9+
public static UUID[] Batch(Int32 amount, byte domain = 0) {
10+
return Batch(amount, DateTime.UtcNow, V1.GetMacAddressBytes(), domain);
11+
}
12+
13+
/// <inheritdoc cref="Batch(Int32, DateTime, ReadOnlySpan{Byte})"/>
14+
15+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
16+
public static UUID[] Batch(Int32 amount, DateTime startTimestamp, byte domain = 0) {
17+
return Batch(amount, startTimestamp, V1.GetMacAddressBytes(), domain);
18+
}
19+
20+
/// <inheritdoc cref="Batch(Int32, DateTime, ReadOnlySpan{Byte})"/>
21+
22+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
23+
public static UUID[] Batch(Int32 amount, ReadOnlySpan<Byte> macAddress, byte domain = 0) {
24+
return Batch(amount, DateTime.UtcNow, macAddress, domain);
25+
}
26+
27+
/// <summary>Creates a batch of <see cref="UUID"/>, Note: This function use <see cref="DateTime.Now"/> To grab a starting point,
28+
/// but will increment the timestamp to create a new <see cref="UUID"/></summary>
29+
/// <param name="amount">The amount of <see cref="UUID"/> to create</param>
30+
/// <param name="startTimestamp">The time to start at, will increment for each following <see cref="UUID"/></param>
31+
/// <param name="macAddress">The macAddress to use, excepts to be 6 bytes</param>
32+
/// <returns>A collection of <see cref="UUID"/></returns>
33+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
34+
public static UUID[] Batch(Int32 amount, DateTime startTimestamp, ReadOnlySpan<Byte> macAddress, byte domain = 0) {
35+
var uuids = new UUID[amount];
36+
DateTime timestamp = startTimestamp;
37+
for (Int32 i = 0; i < amount; i++) {
38+
// Use i as the local identifier (UID/GID) for demonstration; domain is passed in
39+
uuids[i] = Generate(timestamp, (uint)i, macAddress, domain);
40+
// Optionally increment timestamp if you want unique timestamps per UUID
41+
if (i % UInt16.MaxValue == 0) {
42+
timestamp = timestamp.AddTicks(1);
43+
}
44+
}
45+
return uuids;
46+
}
47+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Runtime.Intrinsics;
2+
using System.Security.Cryptography;
3+
4+
namespace DaanV2.UUID;
5+
6+
public static partial class V2 {
7+
/// <inheritdoc cref="V2.Version"/>
8+
public const Version Version = DaanV2.UUID.Version.V2;
9+
/// <inheritdoc cref="V2.Variant"/>
10+
public const Variant Variant = DaanV2.UUID.Variant.V1;
11+
/// <summary>The prefered minimum data length for chunking bytes array</summary>
12+
public const Int32 MinimumDataLength = MD5.HashSizeInBytes;
13+
14+
private static readonly Vector128<Byte> _VersionMask = Format.VersionVariantMaskNot(V2.Version, V2.Variant);
15+
private static readonly Vector128<Byte> _VersionOverlay = Format.VersionVariantOverlayer(V2.Version, V2.Variant);
16+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Buffers.Binary;
2+
using System.Runtime.Intrinsics;
3+
4+
namespace DaanV2.UUID;
5+
6+
public static partial class V2 {
7+
/// <summary>A record of what information can be returned by a <see cref="UUID"/> of version 2 (DCE Security)</summary>
8+
public record Information {
9+
/// <summary>The timestamp it was made at (may be imprecise due to overlays)</summary>
10+
public DateTime Timestamp { get; init; }
11+
/// <summary>The local identifier (UID/GID) embedded in the UUID</summary>
12+
public UInt16 LocalIdentifier { get; init; }
13+
/// <summary>The domain (person, group, org) embedded in the UUID</summary>
14+
public Byte Domain { get; init; }
15+
/// <summary>The mac address of the machine it was made on</summary>
16+
public required Byte[] MacAddress { get; init; }
17+
}
18+
19+
/// <summary>Extract from the given <see cref="UUID"/> its information record, if made with <see cref="V2"/></summary>
20+
/// <param name="uuid">The <see cref="UUID"/> to extract from</param>
21+
/// <returns>A record of <see cref="Information"/></returns>
22+
public static Information Extract(UUID uuid) {
23+
Span<Byte> data = stackalloc Byte[Format.UUID_BYTE_LENGTH];
24+
uuid._Data.CopyTo(data);
25+
26+
// Extract time_low (first 32 bits)
27+
UInt32 timeLow = BinaryPrimitives.ReadUInt32BigEndian(data);
28+
UInt64 timestampTicks = (UInt64)timeLow;
29+
30+
// Extract time_mid (next 16 bits)
31+
UInt16 timeMid = BinaryPrimitives.ReadUInt16BigEndian(data[4..]);
32+
timestampTicks |= (UInt64)timeMid << 32;
33+
34+
// Extract time_hi_version (next 16 bits)
35+
UInt16 timeHiVersion = BinaryPrimitives.ReadUInt16BigEndian(data[6..]);
36+
timestampTicks |= (UInt64)(timeHiVersion & 0x0FFF) << 48; // Clear version bits
37+
38+
// Convert timestampTicks to DateTime (uses V1 epoch, as V2 overlays V1 layout)
39+
Int64 timestampFileTime = (Int64)(timestampTicks - V1.Epoch);
40+
var timestamp = DateTime.FromFileTimeUtc(timestampFileTime);
41+
42+
// Extract domain (high 6 bits of data[8])
43+
Byte domain = (Byte)(data[8] & 0x3F);
44+
// Extract local identifier (UID/GID): low 2 bits of data[8] and all of data[9]
45+
UInt16 localIdentifier = (UInt16)(((data[8] & 0xC0) << 2) | data[9]);
46+
47+
// Extract mac address
48+
Byte[] macAddress = data.Slice(10, 6).ToArray();
49+
50+
return new Information() {
51+
MacAddress = macAddress,
52+
LocalIdentifier = localIdentifier,
53+
Domain = domain,
54+
Timestamp = timestamp
55+
};
56+
}
57+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.Buffers.Binary;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.Intrinsics;
4+
using System.Runtime.InteropServices;
5+
using System;
6+
7+
namespace DaanV2.UUID;
8+
9+
public static partial class V2 {
10+
/// <summary>
11+
/// Generates a Version 2 (DCE Security) UUID using the current time, UID, and MAC address.
12+
/// </summary>
13+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
14+
public static UUID Generate() {
15+
// Use current time, current UID, and MAC address
16+
return Generate(DateTime.UtcNow, GetCurrentUid(), V1.GetMacAddressBytes());
17+
}
18+
19+
/// <summary>
20+
/// Generates a Version 2 UUID with custom timestamp, UID, and MAC address.
21+
/// </summary>
22+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
23+
public static UUID Generate(DateTime timestamp, uint localIdentifier, ReadOnlySpan<byte> macAddress, byte domain = 0) {
24+
ulong uuidTimestamp = V1.TimeStamp(timestamp);
25+
26+
Span<byte> data = stackalloc byte[16];
27+
28+
// Write node (MAC address)
29+
macAddress.CopyTo(data[10..]);
30+
31+
// Split timestamp
32+
uint timeLow = (uint)(uuidTimestamp & 0xFFFFFFFF);
33+
ushort timeMid = (ushort)((uuidTimestamp >> 32) & 0xFFFF);
34+
ushort timeHi = (ushort)((uuidTimestamp >> 48) & 0x0FFF); // 12 bits for time_hi
35+
ushort timeHiAndVersion = (ushort)(timeHi | (_Version << 12)); // Set version 2
36+
37+
BinaryPrimitives.WriteUInt32BigEndian(data, timeLow);
38+
BinaryPrimitives.WriteUInt16BigEndian(data[4..], timeMid);
39+
BinaryPrimitives.WriteUInt16BigEndian(data[6..], timeHiAndVersion);
40+
41+
// Per RFC 4122 DCE Security UUID:
42+
// data[8]: high 2 bits = variant, low 6 bits = high 6 bits of local identifier
43+
// data[9]: domain (per spec), or low 8 bits of local identifier if domain not used
44+
data[8] = (byte)(_Variant | ((localIdentifier >> 8) & 0x3F));
45+
data[9] = domain; // Store domain in data[9]
46+
47+
var uuid = Vector128.Create<Byte>(data);
48+
return new UUID(uuid);
49+
}
50+
51+
/// <summary>
52+
/// Gets the current user's UID (Linux only). Returns 0 if not available.
53+
/// </summary>
54+
private static uint GetCurrentUid() {
55+
try {
56+
return (uint)System.Convert.ToInt32(Environment.GetEnvironmentVariable("UID") ?? "0");
57+
} catch {
58+
return 0;
59+
}
60+
}
61+
62+
/// <summary>
63+
/// Generates a Version 2 UUID from a byte source (not standard, but for API compatibility).
64+
/// </summary>
65+
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
66+
public static UUID Generate(ReadOnlySpan<Byte> source) {
67+
// Not standard for V2, but for API compatibility: hash and overlay version/variant
68+
Span<byte> data = stackalloc byte[16];
69+
System.Security.Cryptography.MD5.HashData(source, data);
70+
// Set version and variant
71+
data[6] = (byte)((data[6] & 0x0F) | (_Version << 4));
72+
data[8] = (byte)((data[8] & 0x3F) | _Variant);
73+
74+
var uuid = Vector128.Create<Byte>(data);
75+
return new UUID(uuid);
76+
}
77+
}

Library/Static Classes/V2/V2.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace DaanV2.UUID;
2+
3+
/// <summary>The <see cref="UUID"/> tjat os nased pm DCE Security, with embedded POSIX UIDs.</summary>
4+
public static partial class V2 {
5+
6+
}

README.md

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

88
A library that provides a way to handle and generate UUIDs. Convert them to and from strings, GUIDs, and the like.
99
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.
10-
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.
10+
Complies with the RFC 4122 / RFC 9562 standard. And has version 1-8 UUIDs implemented.
1111

1212
## Usage Example
1313
Below are two examples of generating UUIDs and usage

0 commit comments

Comments
 (0)