Skip to content

Commit fdd7e85

Browse files
Move UUID v7 polyfill to Common/Polyfills/System/GuidPolyfills.cs
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
1 parent 32dbb40 commit fdd7e85

2 files changed

Lines changed: 63 additions & 53 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#if !NET9_0_OR_GREATER
2+
namespace System;
3+
4+
/// <summary>
5+
/// Polyfill for Guid methods not available in older .NET versions.
6+
/// </summary>
7+
internal static class GuidPolyfills
8+
{
9+
/// <summary>
10+
/// Creates a new Guid according to RFC 9562, following the Version 7 format.
11+
/// This polyfill provides the functionality of <c>Guid.CreateVersion7()</c> for targets earlier than .NET 9.
12+
/// </summary>
13+
/// <returns>A new Guid with embedded timestamp for monotonic ordering.</returns>
14+
public static Guid CreateVersion7()
15+
{
16+
// UUID v7 format (RFC 9562):
17+
// - 48 bits: Unix timestamp in milliseconds (big-endian)
18+
// - 4 bits: version (0111 = 7)
19+
// - 12 bits: random
20+
// - 2 bits: variant (10)
21+
// - 62 bits: random
22+
23+
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
24+
byte[] bytes = new byte[16];
25+
26+
// Fill with random data first
27+
#if NETSTANDARD2_0
28+
using (var rng = Security.Cryptography.RandomNumberGenerator.Create())
29+
{
30+
rng.GetBytes(bytes);
31+
}
32+
#else
33+
Security.Cryptography.RandomNumberGenerator.Fill(bytes);
34+
#endif
35+
36+
// Set timestamp (48 bits, big-endian) in first 6 bytes
37+
bytes[0] = (byte)(timestamp >> 40);
38+
bytes[1] = (byte)(timestamp >> 32);
39+
bytes[2] = (byte)(timestamp >> 24);
40+
bytes[3] = (byte)(timestamp >> 16);
41+
bytes[4] = (byte)(timestamp >> 8);
42+
bytes[5] = (byte)timestamp;
43+
44+
// Set version 7 (0111) in high nibble of byte 6
45+
bytes[6] = (byte)((bytes[6] & 0x0F) | 0x70);
46+
47+
// Set variant (10) in high 2 bits of byte 8
48+
bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80);
49+
50+
// Convert from big-endian byte array to Guid
51+
// Guid constructor expects bytes in a specific order for the first 8 bytes
52+
// (little-endian for the first three components on Windows)
53+
// We need to swap bytes to match the Guid's internal layout
54+
return new Guid(
55+
(int)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]), // a (big-endian to int)
56+
(short)(bytes[4] << 8 | bytes[5]), // b (big-endian to short)
57+
(short)(bytes[6] << 8 | bytes[7]), // c (big-endian to short)
58+
bytes[8], bytes[9], bytes[10], bytes[11],
59+
bytes[12], bytes[13], bytes[14], bytes[15]);
60+
}
61+
}
62+
#endif

src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -451,59 +451,7 @@ public void Dispose()
451451
#if NET9_0_OR_GREATER
452452
private static string GenerateTaskId() => Guid.CreateVersion7().ToString("N");
453453
#else
454-
private static string GenerateTaskId() => CreateVersion7Guid().ToString("N");
455-
456-
/// <summary>
457-
/// Polyfill for Guid.CreateVersion7() on targets earlier than .NET 9.
458-
/// Generates a UUID v7 with embedded timestamp for monotonic ordering.
459-
/// </summary>
460-
private static Guid CreateVersion7Guid()
461-
{
462-
// UUID v7 format (RFC 9562):
463-
// - 48 bits: Unix timestamp in milliseconds (big-endian)
464-
// - 4 bits: version (0111 = 7)
465-
// - 12 bits: random
466-
// - 2 bits: variant (10)
467-
// - 62 bits: random
468-
469-
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
470-
byte[] bytes = new byte[16];
471-
472-
// Fill with random data first
473-
#if NETSTANDARD2_0
474-
using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
475-
{
476-
rng.GetBytes(bytes);
477-
}
478-
#else
479-
System.Security.Cryptography.RandomNumberGenerator.Fill(bytes);
480-
#endif
481-
482-
// Set timestamp (48 bits, big-endian) in first 6 bytes
483-
bytes[0] = (byte)(timestamp >> 40);
484-
bytes[1] = (byte)(timestamp >> 32);
485-
bytes[2] = (byte)(timestamp >> 24);
486-
bytes[3] = (byte)(timestamp >> 16);
487-
bytes[4] = (byte)(timestamp >> 8);
488-
bytes[5] = (byte)timestamp;
489-
490-
// Set version 7 (0111) in high nibble of byte 6
491-
bytes[6] = (byte)((bytes[6] & 0x0F) | 0x70);
492-
493-
// Set variant (10) in high 2 bits of byte 8
494-
bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80);
495-
496-
// Convert from big-endian byte array to Guid
497-
// Guid constructor expects bytes in a specific order for the first 8 bytes
498-
// (little-endian for the first three components on Windows)
499-
// We need to swap bytes to match the Guid's internal layout
500-
return new Guid(
501-
(int)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]), // a (big-endian to int)
502-
(short)(bytes[4] << 8 | bytes[5]), // b (big-endian to short)
503-
(short)(bytes[6] << 8 | bytes[7]), // c (big-endian to short)
504-
bytes[8], bytes[9], bytes[10], bytes[11],
505-
bytes[12], bytes[13], bytes[14], bytes[15]);
506-
}
454+
private static string GenerateTaskId() => GuidPolyfills.CreateVersion7().ToString("N");
507455
#endif
508456

509457
private static bool IsTerminalStatus(McpTaskStatus status) =>

0 commit comments

Comments
 (0)