diff --git a/src/NATS.Server/Auth/PermissionLruCache.cs b/src/NATS.Server/Auth/PermissionLruCache.cs index dfe8f56..e0e5856 100644 --- a/src/NATS.Server/Auth/PermissionLruCache.cs +++ b/src/NATS.Server/Auth/PermissionLruCache.cs @@ -35,6 +35,17 @@ public sealed class PermissionLruCache } } + public int Count + { + get + { + lock (_lock) + { + return _map.Count; + } + } + } + public void Set(string key, bool value) { lock (_lock) diff --git a/src/NATS.Server/Protocol/NatsHeaderParser.cs b/src/NATS.Server/Protocol/NatsHeaderParser.cs index 669be0c..5f21080 100644 --- a/src/NATS.Server/Protocol/NatsHeaderParser.cs +++ b/src/NATS.Server/Protocol/NatsHeaderParser.cs @@ -1,20 +1,26 @@ +using System.Collections.ObjectModel; using System.Text; namespace NATS.Server.Protocol; -public readonly struct NatsHeaders +public readonly struct NatsHeaders() { public int Status { get; init; } - public string Description { get; init; } - public Dictionary Headers { get; init; } + public string Description { get; init; } = string.Empty; + public IReadOnlyDictionary Headers { get; init; } = ReadOnlyDictionary.Empty; - public static readonly NatsHeaders Invalid = new() { Status = -1, Description = string.Empty, Headers = new() }; + public static readonly NatsHeaders Invalid = new() + { + Status = -1, + Description = string.Empty, + Headers = ReadOnlyDictionary.Empty, + }; } public static class NatsHeaderParser { - private static readonly byte[] CrLf = "\r\n"u8.ToArray(); - private static readonly byte[] Prefix = "NATS/1.0"u8.ToArray(); + private static ReadOnlySpan CrLf => "\r\n"u8; + private static ReadOnlySpan Prefix => "NATS/1.0"u8; public static NatsHeaders Parse(ReadOnlySpan data) { @@ -46,9 +52,10 @@ public static class NatsHeaderParser while (si < statusLine.Length && statusLine[si] >= (byte)'0' && statusLine[si] <= (byte)'9') si++; - if (si > numStart) + if (si > numStart && si - numStart <= 5) // max 5 digits to avoid overflow { - status = int.Parse(Encoding.ASCII.GetString(statusLine[numStart..si])); + for (int idx = numStart; idx < si; idx++) + status = status * 10 + (statusLine[idx] - '0'); while (si < statusLine.Length && statusLine[si] == (byte)' ') si++; diff --git a/src/NATS.Server/Subscriptions/SubjectMatch.cs b/src/NATS.Server/Subscriptions/SubjectMatch.cs index 55310f2..40d8b5c 100644 --- a/src/NATS.Server/Subscriptions/SubjectMatch.cs +++ b/src/NATS.Server/Subscriptions/SubjectMatch.cs @@ -172,11 +172,13 @@ public static class SubjectMatch if (lit2 && !lit1) return MatchLiteral(subj2, subj1); - // Both have wildcards - int n1 = NumTokens(subj1); - int n2 = NumTokens(subj2); - bool hasFwc1 = subj1.Contains('>'); - bool hasFwc2 = subj2.Contains('>'); + // Both have wildcards — split once to avoid O(n²) TokenAt calls + var tokens1 = subj1.Split(Sep); + var tokens2 = subj2.Split(Sep); + int n1 = tokens1.Length; + int n2 = tokens2.Length; + bool hasFwc1 = tokens1[^1] == ">"; + bool hasFwc2 = tokens2[^1] == ">"; if (!hasFwc1 && !hasFwc2 && n1 != n2) return false; @@ -188,9 +190,7 @@ public static class SubjectMatch int stop = Math.Min(n1, n2); for (int i = 0; i < stop; i++) { - var t1 = TokenAt(subj1, i); - var t2 = TokenAt(subj2, i); - if (!TokensCanMatch(t1, t2)) + if (!TokensCanMatch(tokens1[i], tokens2[i])) return false; } return true;