feat: port session 07 — Protocol Parser, Auth extras (TPM/certidp/certstore), Internal utilities & data structures
Session 07 scope (5 features, 17 tests, ~1165 Go LOC): - Protocol/ParserTypes.cs: ParserState enum (79 states), PublishArgument, ParseContext - Protocol/IProtocolHandler.cs: handler interface decoupling parser from client - Protocol/ProtocolParser.cs: Parse(), ProtoSnippet(), OverMaxControlLineLimit(), ProcessPub/HeaderPub/RoutedMsgArgs/RoutedHeaderMsgArgs, ClonePubArg(), GetHeader() - tests/Protocol/ProtocolParserTests.cs: 17 tests via TestProtocolHandler stub Auth extras from session 06 (committed separately): - Auth/TpmKeyProvider.cs, Auth/CertificateIdentityProvider/, Auth/CertificateStore/ Internal utilities & data structures (session 06 overflow): - Internal/AccessTimeService.cs, ElasticPointer.cs, SystemMemory.cs, ProcessStatsProvider.cs - Internal/DataStructures/GenericSublist.cs, HashWheel.cs - Internal/DataStructures/SubjectTree.cs, SubjectTreeNode.cs, SubjectTreeParts.cs All 461 tests pass (460 unit + 1 integration). DB updated for features 2588-2592 and tests 2598-2614.
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
// Copyright 2025 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.Internal.DataStructures;
|
||||
|
||||
/// <summary>
|
||||
/// Ports all 21 tests from Go's gsl/gsl_test.go.
|
||||
/// </summary>
|
||||
public sealed class GenericSublistTests
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers (mirror Go's require_* functions)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Counts how many values the sublist matches for <paramref name="subject"/>
|
||||
/// and asserts that count equals <paramref name="expected"/>.
|
||||
/// Mirrors Go's <c>require_Matches</c>.
|
||||
/// </summary>
|
||||
private static void RequireMatches<T>(GenericSublist<T> s, string subject, int expected)
|
||||
where T : notnull
|
||||
{
|
||||
var matches = 0;
|
||||
s.Match(subject, _ => matches++);
|
||||
matches.ShouldBe(expected);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistInit
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistInit()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Count.ShouldBe(0u);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistInsertCount
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistInsertCount()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("foo", EmptyStruct.Value);
|
||||
s.Insert("bar", EmptyStruct.Value);
|
||||
s.Insert("foo.bar", EmptyStruct.Value);
|
||||
s.Count.ShouldBe(3u);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistSimple
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistSimple()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("foo", EmptyStruct.Value);
|
||||
RequireMatches(s, "foo", 1);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistSimpleMultiTokens
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistSimpleMultiTokens()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("foo.bar.baz", EmptyStruct.Value);
|
||||
RequireMatches(s, "foo.bar.baz", 1);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistPartialWildcard
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistPartialWildcard()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("a.b.c", EmptyStruct.Value);
|
||||
s.Insert("a.*.c", EmptyStruct.Value);
|
||||
RequireMatches(s, "a.b.c", 2);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistPartialWildcardAtEnd
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistPartialWildcardAtEnd()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("a.b.c", EmptyStruct.Value);
|
||||
s.Insert("a.b.*", EmptyStruct.Value);
|
||||
RequireMatches(s, "a.b.c", 2);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistFullWildcard
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistFullWildcard()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("a.b.c", EmptyStruct.Value);
|
||||
s.Insert("a.>", EmptyStruct.Value);
|
||||
RequireMatches(s, "a.b.c", 2);
|
||||
RequireMatches(s, "a.>", 1);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistRemove
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistRemove()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
|
||||
s.Insert("a.b.c.d", EmptyStruct.Value);
|
||||
s.Count.ShouldBe(1u);
|
||||
RequireMatches(s, "a.b.c.d", 1);
|
||||
|
||||
s.Remove("a.b.c.d", EmptyStruct.Value);
|
||||
s.Count.ShouldBe(0u);
|
||||
RequireMatches(s, "a.b.c.d", 0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistRemoveWildcard
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistRemoveWildcard()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
|
||||
s.Insert("a.b.c.d", 11);
|
||||
s.Insert("a.b.*.d", 22);
|
||||
s.Insert("a.b.>", 33);
|
||||
s.Count.ShouldBe(3u);
|
||||
RequireMatches(s, "a.b.c.d", 3);
|
||||
|
||||
s.Remove("a.b.*.d", 22);
|
||||
s.Count.ShouldBe(2u);
|
||||
RequireMatches(s, "a.b.c.d", 2);
|
||||
|
||||
s.Remove("a.b.>", 33);
|
||||
s.Count.ShouldBe(1u);
|
||||
RequireMatches(s, "a.b.c.d", 1);
|
||||
|
||||
s.Remove("a.b.c.d", 11);
|
||||
s.Count.ShouldBe(0u);
|
||||
RequireMatches(s, "a.b.c.d", 0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistRemoveCleanup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistRemoveCleanup()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.NumLevels().ShouldBe(0);
|
||||
s.Insert("a.b.c.d.e.f", EmptyStruct.Value);
|
||||
s.NumLevels().ShouldBe(6);
|
||||
s.Remove("a.b.c.d.e.f", EmptyStruct.Value);
|
||||
s.NumLevels().ShouldBe(0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistRemoveCleanupWildcards
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistRemoveCleanupWildcards()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.NumLevels().ShouldBe(0);
|
||||
s.Insert("a.b.*.d.e.>", EmptyStruct.Value);
|
||||
s.NumLevels().ShouldBe(6);
|
||||
s.Remove("a.b.*.d.e.>", EmptyStruct.Value);
|
||||
s.NumLevels().ShouldBe(0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistInvalidSubjectsInsert
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistInvalidSubjectsInsert()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
|
||||
// Insert, or subscriptions, can have wildcards, but not empty tokens,
|
||||
// and can not have a FWC that is not the terminal token.
|
||||
Should.Throw<ArgumentException>(() => s.Insert(".foo", EmptyStruct.Value));
|
||||
Should.Throw<ArgumentException>(() => s.Insert("foo.", EmptyStruct.Value));
|
||||
Should.Throw<ArgumentException>(() => s.Insert("foo..bar", EmptyStruct.Value));
|
||||
Should.Throw<ArgumentException>(() => s.Insert("foo.bar..baz", EmptyStruct.Value));
|
||||
Should.Throw<ArgumentException>(() => s.Insert("foo.>.baz", EmptyStruct.Value));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistBadSubjectOnRemove
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistBadSubjectOnRemove()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
Should.Throw<ArgumentException>(() => s.Insert("a.b..d", EmptyStruct.Value));
|
||||
Should.Throw<ArgumentException>(() => s.Remove("a.b..d", EmptyStruct.Value));
|
||||
Should.Throw<ArgumentException>(() => s.Remove("a.>.b", EmptyStruct.Value));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistTwoTokenPubMatchSingleTokenSub
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistTwoTokenPubMatchSingleTokenSub()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert("foo", EmptyStruct.Value);
|
||||
RequireMatches(s, "foo", 1);
|
||||
RequireMatches(s, "foo.bar", 0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistInsertWithWildcardsAsLiterals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistInsertWithWildcardsAsLiterals()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
var subjects = new[] { "foo.*-", "foo.>-" };
|
||||
for (var i = 0; i < subjects.Length; i++)
|
||||
{
|
||||
var subject = subjects[i];
|
||||
s.Insert(subject, i);
|
||||
RequireMatches(s, "foo.bar", 0);
|
||||
RequireMatches(s, subject, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistRemoveWithWildcardsAsLiterals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistRemoveWithWildcardsAsLiterals()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
var subjects = new[] { "foo.*-", "foo.>-" };
|
||||
for (var i = 0; i < subjects.Length; i++)
|
||||
{
|
||||
var subject = subjects[i];
|
||||
s.Insert(subject, i);
|
||||
RequireMatches(s, "foo.bar", 0);
|
||||
RequireMatches(s, subject, 1);
|
||||
Should.Throw<KeyNotFoundException>(() => s.Remove("foo.bar", i));
|
||||
s.Count.ShouldBe(1u);
|
||||
s.Remove(subject, i);
|
||||
s.Count.ShouldBe(0u);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistMatchWithEmptyTokens
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistMatchWithEmptyTokens()
|
||||
{
|
||||
var s = GenericSublist<EmptyStruct>.NewSublist();
|
||||
s.Insert(">", EmptyStruct.Value);
|
||||
|
||||
var subjects = new[]
|
||||
{
|
||||
".foo", "..foo", "foo..", "foo.", "foo..bar", "foo...bar"
|
||||
};
|
||||
|
||||
foreach (var subject in subjects)
|
||||
{
|
||||
RequireMatches(s, subject, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistHasInterest
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistHasInterest()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
s.Insert("foo", 11);
|
||||
|
||||
// Expect to find that "foo" matches but "bar" doesn't.
|
||||
s.HasInterest("foo").ShouldBeTrue();
|
||||
s.HasInterest("bar").ShouldBeFalse();
|
||||
|
||||
// Call Match on a subject we know there is no match.
|
||||
RequireMatches(s, "bar", 0);
|
||||
s.HasInterest("bar").ShouldBeFalse();
|
||||
|
||||
// Remove fooSub and check interest again.
|
||||
s.Remove("foo", 11);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
|
||||
// Try with some wildcards.
|
||||
s.Insert("foo.*", 22);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar").ShouldBeTrue();
|
||||
s.HasInterest("foo.bar.baz").ShouldBeFalse();
|
||||
|
||||
// Remove sub, there should be no interest.
|
||||
s.Remove("foo.*", 22);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar.baz").ShouldBeFalse();
|
||||
|
||||
s.Insert("foo.>", 33);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar").ShouldBeTrue();
|
||||
s.HasInterest("foo.bar.baz").ShouldBeTrue();
|
||||
|
||||
s.Remove("foo.>", 33);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar.baz").ShouldBeFalse();
|
||||
|
||||
s.Insert("*.>", 44);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar").ShouldBeTrue();
|
||||
s.HasInterest("foo.baz").ShouldBeTrue();
|
||||
s.Remove("*.>", 44);
|
||||
|
||||
s.Insert("*.bar", 55);
|
||||
s.HasInterest("foo").ShouldBeFalse();
|
||||
s.HasInterest("foo.bar").ShouldBeTrue();
|
||||
s.HasInterest("foo.baz").ShouldBeFalse();
|
||||
s.Remove("*.bar", 55);
|
||||
|
||||
s.Insert("*", 66);
|
||||
s.HasInterest("foo").ShouldBeTrue();
|
||||
s.HasInterest("foo.bar").ShouldBeFalse();
|
||||
s.Remove("*", 66);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistHasInterestOverlapping
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistHasInterestOverlapping()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
s.Insert("stream.A.child", 11);
|
||||
s.Insert("stream.*", 11);
|
||||
s.HasInterest("stream.A.child").ShouldBeTrue();
|
||||
s.HasInterest("stream.A").ShouldBeTrue();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistHasInterestStartingInRace
|
||||
// Tests that HasInterestStartingIn is safe to call concurrently with
|
||||
// modifications to the sublist. Mirrors Go's goroutine test using Tasks.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public async Task TestGenericSublistHasInterestStartingInRace()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
|
||||
// Pre-populate with some patterns.
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
s.Insert("foo.bar.baz", i);
|
||||
s.Insert("foo.*.baz", i + 10);
|
||||
s.Insert("foo.>", i + 20);
|
||||
}
|
||||
|
||||
const int iterations = 1000;
|
||||
|
||||
// Task 1: repeatedly call HasInterestStartingIn.
|
||||
var task1 = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
s.HasInterestStartingIn("foo");
|
||||
s.HasInterestStartingIn("foo.bar");
|
||||
s.HasInterestStartingIn("foo.bar.baz");
|
||||
s.HasInterestStartingIn("other.subject");
|
||||
}
|
||||
});
|
||||
|
||||
// Task 2: repeatedly modify the sublist.
|
||||
var task2 = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
var val = 1000 + i;
|
||||
var dynSubject = "test.subject." + (char)('a' + i % 26);
|
||||
s.Insert(dynSubject, val);
|
||||
s.Insert("foo.*.test", val);
|
||||
// Remove may fail if not found (concurrent), so swallow KeyNotFoundException.
|
||||
try { s.Remove(dynSubject, val); } catch (KeyNotFoundException) { }
|
||||
try { s.Remove("foo.*.test", val); } catch (KeyNotFoundException) { }
|
||||
}
|
||||
});
|
||||
|
||||
// Task 3: also call HasInterest (which does lock).
|
||||
var task3 = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < iterations; i++)
|
||||
{
|
||||
s.HasInterest("foo.bar.baz");
|
||||
s.HasInterest("foo.something.baz");
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(task1, task2, task3);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TestGenericSublistNumInterest
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TestGenericSublistNumInterest()
|
||||
{
|
||||
var s = GenericSublist<int>.NewSublist();
|
||||
s.Insert("foo", 11);
|
||||
|
||||
void RequireNumInterest(string subj, int expected)
|
||||
{
|
||||
RequireMatches(s, subj, expected);
|
||||
s.NumInterest(subj).ShouldBe(expected);
|
||||
}
|
||||
|
||||
// Expect to find that "foo" matches but "bar" doesn't.
|
||||
RequireNumInterest("foo", 1);
|
||||
RequireNumInterest("bar", 0);
|
||||
|
||||
// Remove fooSub and check interest again.
|
||||
s.Remove("foo", 11);
|
||||
RequireNumInterest("foo", 0);
|
||||
|
||||
// Try with some wildcards.
|
||||
s.Insert("foo.*", 22);
|
||||
RequireNumInterest("foo", 0);
|
||||
RequireNumInterest("foo.bar", 1);
|
||||
RequireNumInterest("foo.bar.baz", 0);
|
||||
|
||||
// Remove sub, there should be no interest.
|
||||
s.Remove("foo.*", 22);
|
||||
RequireNumInterest("foo", 0);
|
||||
RequireNumInterest("foo.bar", 0);
|
||||
RequireNumInterest("foo.bar.baz", 0);
|
||||
|
||||
s.Insert("foo.>", 33);
|
||||
RequireNumInterest("foo", 0);
|
||||
RequireNumInterest("foo.bar", 1);
|
||||
RequireNumInterest("foo.bar.baz", 1);
|
||||
|
||||
s.Remove("foo.>", 33);
|
||||
RequireNumInterest("foo", 0);
|
||||
RequireNumInterest("foo.bar", 0);
|
||||
RequireNumInterest("foo.bar.baz", 0);
|
||||
|
||||
s.Insert("*.>", 44);
|
||||
RequireNumInterest("foo", 0);
|
||||
RequireNumInterest("foo.bar", 1);
|
||||
RequireNumInterest("foo.bar.baz", 1);
|
||||
s.Remove("*.>", 44);
|
||||
|
||||
s.Insert("*.bar", 55);
|
||||
RequireNumInterest("foo", 0);
|
||||
RequireNumInterest("foo.bar", 1);
|
||||
RequireNumInterest("foo.bar.baz", 0);
|
||||
s.Remove("*.bar", 55);
|
||||
|
||||
s.Insert("*", 66);
|
||||
RequireNumInterest("foo", 1);
|
||||
RequireNumInterest("foo.bar", 0);
|
||||
s.Remove("*", 66);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user