Add DistributedCacheEventStreamStore (#1136)
Co-authored-by: Stephen Halter <halter73@gmail.com>
This commit is contained in:
parent
712068b161
commit
d5c6b0d7f9
1 changed files with 3045 additions and 511 deletions
|
|
@ -0,0 +1,117 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
// This is a shared source file included in both ModelContextProtocol and the test project.
|
||||
// Do not reference symbols internal to the core project, as they won't be available in tests.
|
||||
#if NET
|
||||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for formatting and parsing event IDs used by <see cref="DistributedCacheEventStreamStore"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Event IDs are formatted as "{base64(sessionId)}:{base64(streamId)}:{sequence}".
|
||||
/// </remarks>
|
||||
internal static class DistributedCacheEventIdFormatter
|
||||
{
|
||||
private const char Separator = ':';
|
||||
|
||||
/// <summary>
|
||||
/// Formats session ID, stream ID, and sequence number into an event ID string.
|
||||
/// </summary>
|
||||
public static string Format(string sessionId, string streamId, long sequence)
|
||||
{
|
||||
// Base64-encode session and stream IDs so the event ID can be parsed
|
||||
// even if the original IDs contain the ':' separator character
|
||||
var sessionBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(sessionId));
|
||||
var streamBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(streamId));
|
||||
return $"{sessionBase64}{Separator}{streamBase64}{Separator}{sequence}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse an event ID into its component parts.
|
||||
/// </summary>
|
||||
public static bool TryParse(string eventId, out string sessionId, out string streamId, out long sequence)
|
||||
{
|
||||
sessionId = string.Empty;
|
||||
streamId = string.Empty;
|
||||
sequence = 0;
|
||||
|
||||
#if NET
|
||||
ReadOnlySpan<char> eventIdSpan = eventId.AsSpan();
|
||||
Span<Range> partRanges = stackalloc Range[4];
|
||||
int rangeCount = eventIdSpan.Split(partRanges, Separator);
|
||||
if (rangeCount != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadOnlySpan<char> sessionBase64 = eventIdSpan[partRanges[0]];
|
||||
ReadOnlySpan<char> streamBase64 = eventIdSpan[partRanges[1]];
|
||||
ReadOnlySpan<char> sequenceSpan = eventIdSpan[partRanges[2]];
|
||||
|
||||
if (!TryDecodeBase64ToString(sessionBase64, out sessionId!) ||
|
||||
!TryDecodeBase64ToString(streamBase64, out streamId!))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return long.TryParse(sequenceSpan, out sequence);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
var parts = eventId.Split(Separator);
|
||||
if (parts.Length != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
sessionId = Encoding.UTF8.GetString(Convert.FromBase64String(parts[0]));
|
||||
streamId = Encoding.UTF8.GetString(Convert.FromBase64String(parts[1]));
|
||||
return long.TryParse(parts[2], out sequence);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET
|
||||
private static bool TryDecodeBase64ToString(ReadOnlySpan<char> base64Chars, [NotNullWhen(true)] out string? result)
|
||||
{
|
||||
// Use a single buffer: base64 chars are ASCII (1:1 with UTF8 bytes),
|
||||
// and decoded data is always smaller than encoded, so we can decode in-place.
|
||||
int bufferLength = base64Chars.Length;
|
||||
Span<byte> buffer = bufferLength <= 256
|
||||
? stackalloc byte[bufferLength]
|
||||
: new byte[bufferLength];
|
||||
|
||||
Encoding.UTF8.GetBytes(base64Chars, buffer);
|
||||
|
||||
OperationStatus status = Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten);
|
||||
if (status != OperationStatus.Done)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = Encoding.UTF8.GetString(buffer[..bytesWritten]);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Loading…
Reference in a new issue