Skip to content

Commit

Permalink
Update to version 14 of the RFC draft.
Browse files Browse the repository at this point in the history
Hash Space IDs are no longer used for UUIDv8.
  • Loading branch information
bgrainger committed Feb 6, 2024
1 parent a9a6449 commit c4004fd
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 54 deletions.
73 changes: 24 additions & 49 deletions src/NGuid/GuidHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public static Guid CreateFromName(Guid namespaceId, ReadOnlySpan<byte> name, int
/// Creates a new Version 6 UUID based on the current time and a random node ID.
/// </summary>
/// <returns>A new Version 6 UUID based on the current time and a random node ID.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
public static Guid CreateVersion6() =>
CreateVersion6(DateTimeOffset.UtcNow);

Expand All @@ -152,7 +152,7 @@ public static Guid CreateVersion6() =>
/// </summary>
/// <param name="timeProvider">A <see cref="TimeProvider"/> that can provide the current UTC time.</param>
/// <returns>A new Version 6 UUID based on the specified time and a random node ID.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
public static Guid CreateVersion6(TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(timeProvider);
Expand All @@ -165,7 +165,7 @@ public static Guid CreateVersion6(TimeProvider timeProvider)
/// </summary>
/// <param name="timestamp">The timestamp to be used to fill the <c>time_high</c>, <c>time_mid</c>, and <c>time_low</c> fields of the UUID.</param>
/// <returns>A new time-based Version 6 UUID.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
#if NET6_0_OR_GREATER
[SkipLocalsInit]
#endif
Expand Down Expand Up @@ -200,7 +200,7 @@ private static Guid CreateVersion6(DateTimeOffset timestamp)
/// </summary>
/// <param name="guid">The Version 1 UUID to convert.</param>
/// <returns>A UUID in Version 6 format, with the timestamp in MSB order.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-6">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
#if NET6_0_OR_GREATER
[SkipLocalsInit]
#endif
Expand Down Expand Up @@ -245,7 +245,7 @@ public static Guid CreateVersion6FromVersion1(Guid guid)
/// Creates a new Version 7 UUID based on the current time combined with random data.
/// </summary>
/// <returns>A new Version 7 UUID based on the current time and random data.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-7">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-7">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
public static Guid CreateVersion7() =>
CreateVersion7(DateTimeOffset.UtcNow);

Expand All @@ -255,7 +255,7 @@ public static Guid CreateVersion7() =>
/// </summary>
/// <param name="timeProvider">A <see cref="TimeProvider"/> that can provide the current UTC time.</param>
/// <returns>A new Version 7 UUID based on the specified time and random data.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-7">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-7">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
public static Guid CreateVersion7(TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(timeProvider);
Expand All @@ -268,7 +268,7 @@ public static Guid CreateVersion7(TimeProvider timeProvider)
/// </summary>
/// <param name="timestamp">The timestamp to be used to fill the <c>unix_ts_ms</c> field of the UUID.</param>
/// <returns>A new time-based Version 7 UUID.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-7">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-7">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
#if NET6_0_OR_GREATER
[SkipLocalsInit]
#endif
Expand Down Expand Up @@ -311,7 +311,7 @@ private static Guid CreateVersion7(DateTimeOffset timestamp)
/// This is the opposite of how the <see cref="Guid(byte[])"/> constructor treats its argument, and
/// will cause <see cref="Guid.ToByteArray()"/> to return a byte array whose bytes values are
/// "reversed" compared to the input values in <paramref name="bytes"/>.
/// This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-8">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-8">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
public static Guid CreateVersion8(byte[] bytes)
{
#if NET6_0_OR_GREATER
Expand Down Expand Up @@ -351,7 +351,7 @@ public static Guid CreateVersion8(byte[] bytes)
/// This is the opposite of how the <see cref="Guid(byte[])"/> constructor treats its argument, and
/// will cause <see cref="Guid.ToByteArray()"/> to return a byte array whose bytes values are
/// "reversed" compared to the input values in <paramref name="bytes"/>.
/// This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-uuid-version-8">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-uuid-version-8">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
[SkipLocalsInit]
public static Guid CreateVersion8(ReadOnlySpan<byte> bytes)
{
Expand All @@ -375,26 +375,20 @@ public static Guid CreateVersion8(ReadOnlySpan<byte> bytes)

/// <summary>
/// Creates a Version 8 UUID from a name in the specified namespace using the specified hash algorithm, according to the algorithm
/// in <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-07, section 6.5</a>.
/// in <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-14, section 6.5</a>.
/// </summary>
/// <param name="hashAlgorithmName">The name of the hash algorithm to use. Supported values are <c>SHA256</c>, <c>SHA384</c>, and <c>SHA512</c>.</param>
/// <param name="namespaceId">The namespace ID.</param>
/// <param name="name">The name within that namespace ID.</param>
/// <returns>A version 8 UUID formed by hashing the hash space ID, namespace ID, and name.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
public static Guid CreateVersion8FromName(HashAlgorithmName hashAlgorithmName, Guid namespaceId, byte[] name)
{
#if NET6_0_OR_GREATER
return CreateVersion8FromName(hashAlgorithmName, namespaceId, name.AsSpan());
#else
var (hashSpaceId, algorithm) = GetHashSpaceAndAlgorithm(hashAlgorithmName);
using (algorithm)
using (var algorithm = GetHashAlgorithm(hashAlgorithmName))
{
// add the hash space bytes (in network order) to the hash
var hashSpaceBytes = hashSpaceId.ToByteArray();
SwapByteOrder(hashSpaceBytes);
algorithm.TransformBlock(hashSpaceBytes, 0, hashSpaceBytes.Length, null, 0);

// add the namespace bytes (in network order) to the hash
var namespaceBytes = namespaceId.ToByteArray();
SwapByteOrder(namespaceBytes);
Expand All @@ -421,32 +415,28 @@ public static Guid CreateVersion8FromName(HashAlgorithmName hashAlgorithmName, G
#if NET6_0_OR_GREATER
/// <summary>
/// Creates a Version 8 UUID from a name in the specified namespace using the specified hash algorithm, according to the algorithm
/// in <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-07, section 6.5</a>.
/// in <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-14, section 6.5</a>.
/// </summary>
/// <param name="hashAlgorithmName">The name of the hash algorithm to use. Supported values are <c>SHA256</c>, <c>SHA384</c>, and <c>SHA512</c>.</param>
/// <param name="namespaceId">The namespace ID.</param>
/// <param name="name">The name within that namespace ID.</param>
/// <returns>A version 8 UUID formed by hashing the hash space ID, namespace ID, and name.</returns>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-07</a> and is subject to change.</remarks>
/// <remarks>This method is based on <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-name-based-uuid-generation">draft-ietf-uuidrev-rfc4122bis-14</a> and is subject to change.</remarks>
[SkipLocalsInit]
public static Guid CreateVersion8FromName(HashAlgorithmName hashAlgorithmName, Guid namespaceId, ReadOnlySpan<byte> name)
{
var (hashSpaceId, algorithm) = GetHashSpaceAndAlgorithm(hashAlgorithmName);
using (algorithm)
using (var algorithm = GetHashAlgorithm(hashAlgorithmName))
{
Span<byte> buffer = name.Length < 500 ? stackalloc byte[16 + 16 + name.Length] : new byte[16 + 16 + name.Length];
Span<byte> buffer = name.Length < 500 ? stackalloc byte[16 + name.Length] : new byte[16 + name.Length];
Span<byte> hashOutput = stackalloc byte[algorithm.HashSize / 8];

// convert the hash space and namespace UUIDs to network order
if (!hashSpaceId.TryWriteBytes(buffer))
throw new InvalidOperationException("Failed to write hash space ID bytes to buffer");
SwapByteOrder(buffer);
if (!namespaceId.TryWriteBytes(buffer[16..]))
if (!namespaceId.TryWriteBytes(buffer))
throw new InvalidOperationException("Failed to write namespace ID bytes to buffer");
SwapByteOrder(buffer[16..]);
SwapByteOrder(buffer);

// compute the hash of [ hash space ID, namespace ID, name ]
name.CopyTo(buffer[32..]);
// compute the hash of [ namespace ID, name ]
name.CopyTo(buffer[16..]);
var success = algorithm.TryComputeHash(buffer, hashOutput, out var bytesWritten);
if (!success || bytesWritten != hashOutput.Length)
throw new InvalidOperationException("Failed to hash data");
Expand All @@ -465,12 +455,12 @@ public static Guid CreateVersion8FromName(HashAlgorithmName hashAlgorithmName, G
}
#endif

private static (Guid NamespaceId, HashAlgorithm Algorithm) GetHashSpaceAndAlgorithm(HashAlgorithmName hashAlgorithmName) =>
private static HashAlgorithm GetHashAlgorithm(HashAlgorithmName hashAlgorithmName) =>
hashAlgorithmName.Name switch
{
"SHA256" => (Sha256HashSpaceId, (HashAlgorithm) SHA256.Create()),
"SHA384" => (Sha384HashSpaceId, SHA384.Create()),
"SHA512" => (Sha512HashSpaceId, SHA512.Create()),
"SHA256" => SHA256.Create(),
"SHA384" => SHA384.Create(),
"SHA512" => SHA512.Create(),
_ => throw new ArgumentException($"Unsupported hash algorithm name: {hashAlgorithmName.Name}", nameof(hashAlgorithmName)),
};

Expand All @@ -489,21 +479,6 @@ private static (Guid NamespaceId, HashAlgorithm Algorithm) GetHashSpaceAndAlgori
/// </summary>
public static readonly Guid IsoOidNamespace = new("6ba7b812-9dad-11d1-80b4-00c04fd430c8");

/// <summary>
/// The hash space ID for SHA-256 hashed names (from <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#appendix-B">draft-ietf-uuidrev-rfc4122bis-07, Appendix B</a>).
/// </summary>
public static readonly Guid Sha256HashSpaceId = new("3fb32780-953c-4464-9cfd-e85dbbe9843d");

/// <summary>
/// The hash space ID for SHA-384 hashed names (from <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#appendix-B">draft-ietf-uuidrev-rfc4122bis-07, Appendix B</a>).
/// </summary>
public static readonly Guid Sha384HashSpaceId = new("e6800581-f333-484b-8778-601ff2b58da8");

/// <summary>
/// The hash space ID for SHA-512 hashed names (from <a href="https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#appendix-B">draft-ietf-uuidrev-rfc4122bis-07, Appendix B</a>).
/// </summary>
public static readonly Guid Sha512HashSpaceId = new("0fde22f2-e7ba-4fd1-9753-9c2ea88fa3f9");

// Converts a GUID (expressed as a byte array) to/from network order (MSB-first).
internal static void SwapByteOrder(Span<byte> guid)
{
Expand Down
10 changes: 5 additions & 5 deletions tests/NGuid.Tests/GuidHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public void CreateGuidFromDnsName(string name, int version, string expected) =>
[Theory]
[InlineData("www.terraform.io", 5, "a5008fae-b28c-5ba5-96cd-82b4c53552d6")] // https://developer.hashicorp.com/terraform/language/functions/uuidv5
[InlineData("www.example.org", 5, "74738ff5-5367-5958-9aee-98fffdcd1876")] // https://stackoverflow.com/a/5541986/23633
[InlineData("www.example.com", 3, "5df41881-3aed-3515-88a7-2f4a814cf09e")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-example-of-a-uuidv3-value
[InlineData("www.example.com", 5, "2ed6657d-e927-568b-95e1-2665a8aea6a2")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-example-of-a-uuidv5-value
[InlineData("www.example.com", 3, "5df41881-3aed-3515-88a7-2f4a814cf09e")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-example-of-a-uuidv3-value
[InlineData("www.example.com", 5, "2ed6657d-e927-568b-95e1-2665a8aea6a2")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-example-of-a-uuidv5-value
public void CreateGuidFromAsciiDnsName(string name, int version, string expected) =>
Assert.Equal(new Guid(expected), GuidHelpers.CreateFromName(GuidHelpers.DnsNamespace, Encoding.ASCII.GetBytes(name), version));

Expand Down Expand Up @@ -80,7 +80,7 @@ public void CreateV6()
#if NET8_0_OR_GREATER
[Theory]
[InlineData("1998-02-04T22:13:53.151183Z", "1d19dad6-ba7b-6816")] // timestamp from the RFC 4122 example; see the date on this draft: https://datatracker.ietf.org/doc/html/draft-leach-uuids-guids-01
[InlineData("2022-02-22T14:22:22-05:00", "1ec9414c-232a-6b00")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-example-of-a-uuidv6-value
[InlineData("2022-02-22T14:22:22-05:00", "1ec9414c-232a-6b00")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-example-of-a-uuidv6-value
public void CreateV6FromTimeProvider(string timestamp, string expectedPrefix)
{
var timeProvider = new FixedTimeProvider(DateTimeOffset.Parse(timestamp, CultureInfo.InvariantCulture));
Expand All @@ -105,7 +105,7 @@ public void CreateV7()

#if NET8_0_OR_GREATER
[Theory]
[InlineData("2022-02-22T14:22:22-05:00", "017f22e2-79b0-7")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-example-of-a-uuidv7-value
[InlineData("2022-02-22T14:22:22-05:00", "017f22e2-79b0-7")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-example-of-a-uuidv7-value
public void CreateV7FromTimeProvider(string timestamp, string expectedPrefix)
{
var timeProvider = new FixedTimeProvider(DateTimeOffset.Parse(timestamp, CultureInfo.InvariantCulture));
Expand Down Expand Up @@ -175,7 +175,7 @@ public void CreateV8FromNewSpan() =>
#endif

[Theory]
[InlineData("SHA256", "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "www.example.com", "401835fd-a627-870a-873f-ed73f2bc5b2c")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-07#name-example-of-a-uuidv8-value-n
[InlineData("SHA256", "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "www.example.com", "5c146b14-3c52-8afd-938a-375d0df1fbf6")] // https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-14#name-example-of-a-uuidv8-value-n
public void CreateV8FromName(string algorithmName, string namespaceId, string name, string expected) =>
Assert.Equal(new Guid(expected), GuidHelpers.CreateVersion8FromName(new(algorithmName), new(namespaceId), Encoding.ASCII.GetBytes(name)));

Expand Down

0 comments on commit c4004fd

Please sign in to comment.