perf(auth): allocation-free token parsing; single partition-key build per RPC

This commit is contained in:
Joseph Doherty
2026-08-15 12:23:25 -04:00
parent 3ff073d1ea
commit 88d38bb900
5 changed files with 221 additions and 25 deletions
@@ -213,7 +213,10 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid
// DashboardApiKeyManagementService.ValidateKeyId each restrict a key id to
// char.IsAsciiLetterOrDigit || '.' || '-'. Key ids are never library-generated, so no path can
// mint one containing '_'.
private static string? TryParseKeyId(string? authorizationHeader)
//
// Internal rather than private so the parse rules can be pinned directly by test: the guard's
// correctness depends on this returning the full key id.
internal static string? TryParseKeyId(string? authorizationHeader)
{
if (string.IsNullOrEmpty(authorizationHeader))
{
@@ -226,15 +229,29 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid
? header[bearer.Length..].Trim()
: header;
string[] parts = token.ToString().Split('_');
if (parts.Length < 3
|| !string.Equals(parts[0], TokenPrefix, StringComparison.Ordinal)
|| parts[1].Length == 0)
// Scanned rather than split, for the same reason as the interceptor's copy: Split would
// allocate a token copy, an array and a string per segment on every cache miss to produce
// one key id. Two IndexOf scans allocate only that key id.
//
// The '_' checked immediately after the prefix is what makes "mxgw" the whole first segment
// (so "mxgwabc_..." is still rejected), and the second separator must exist because the
// split form required three segments — a token with no secret delimiter is not a key token.
if (!token.StartsWith(TokenPrefix, StringComparison.Ordinal)
|| token.Length <= TokenPrefix.Length
|| token[TokenPrefix.Length] != '_')
{
return null;
}
return parts[1];
ReadOnlySpan<char> afterPrefix = token[(TokenPrefix.Length + 1)..];
int separator = afterPrefix.IndexOf('_');
if (separator <= 0)
{
// -1 is a token with no second separator; 0 is an empty key id.
return null;
}
return new string(afterPrefix[..separator]);
}
private void IndexCacheKey(string keyId, string cacheKey)