feat(batch2): verify parser remainder features

This commit is contained in:
Joseph Doherty
2026-02-28 07:06:09 -05:00
parent f17d6c6bfb
commit 3ace39d606
3 changed files with 159 additions and 1 deletions

View File

@@ -1158,6 +1158,58 @@ public sealed partial class ClientConnection
TraceInOp("PRE", pre);
}
// =========================================================================
// Parser compatibility wrappers (features 2588, 2590, 2591)
// =========================================================================
/// <summary>
/// Parses protocol bytes using the shared parser state for this connection.
/// Mirrors Go <c>client.parse</c>.
/// </summary>
internal Exception? Parse(byte[] buf, IProtocolHandler handler)
{
ArgumentNullException.ThrowIfNull(buf);
ArgumentNullException.ThrowIfNull(handler);
ParseCtx.Kind = Kind;
ParseCtx.HasHeaders = Headers;
if (_mcl > 0)
ParseCtx.MaxControlLine = _mcl;
if (_mpay != 0)
ParseCtx.MaxPayload = _mpay;
return ProtocolParser.Parse(ParseCtx, handler, buf);
}
/// <summary>
/// Checks max control line enforcement for the current connection kind.
/// Mirrors Go <c>client.overMaxControlLineLimit</c>.
/// </summary>
internal Exception? OverMaxControlLineLimit(byte[] arg, int mcl, IProtocolHandler handler)
{
ArgumentNullException.ThrowIfNull(arg);
ArgumentNullException.ThrowIfNull(handler);
ParseCtx.Kind = Kind;
return ProtocolParser.OverMaxControlLineLimit(ParseCtx, handler, arg, mcl);
}
/// <summary>
/// Re-processes stored pub args for split-buffer message payload handling.
/// Mirrors Go <c>client.clonePubArg</c>.
/// </summary>
internal Exception? ClonePubArg(IProtocolHandler handler, bool lmsg)
{
ArgumentNullException.ThrowIfNull(handler);
ParseCtx.Kind = Kind;
ParseCtx.HasHeaders = Headers;
if (_mpay != 0)
ParseCtx.MaxPayload = _mpay;
return ProtocolParser.ClonePubArg(ParseCtx, handler, lmsg);
}
/// <summary>
/// Generates the INFO JSON bytes sent to the client on connect.
/// Stub — full implementation in session 09.

View File

@@ -13,6 +13,7 @@
using System.Text;
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Internal;
using ZB.MOM.NatsNet.Server.Protocol;
@@ -754,6 +755,110 @@ public class ProtocolParserTests
}
}
[Fact]
public void ParsePub_SplitPayload_ClonesPubArgAndCompletesMessage()
{
var c = DummyClient();
var h = DummyHandler();
var first = "PUB foo 5\r\nhe"u8.ToArray();
ProtocolParser.Parse(c, h, first).ShouldBeNull();
c.State.ShouldBe(ParserState.MsgPayload);
c.ArgBuf.ShouldNotBeNull();
Encoding.ASCII.GetString(c.ArgBuf!).ShouldBe("foo 5");
c.MsgBuf.ShouldNotBeNull();
Encoding.ASCII.GetString(c.MsgBuf!).ShouldBe("he");
h.InboundMessages.Count.ShouldBe(0);
var second = "llo\r\n"u8.ToArray();
ProtocolParser.Parse(c, h, second).ShouldBeNull();
c.State.ShouldBe(ParserState.OpStart);
h.InboundMessages.Count.ShouldBe(1);
Encoding.ASCII.GetString(h.InboundMessages[0]).ShouldBe("hello\r\n");
}
[Fact]
public void ClonePubArg_ClientBranch_ReprocessesPubArgs()
{
var c = DummyClient();
var h = DummyHandler();
c.Pa.Arg = "foo 5"u8.ToArray();
c.Pa.HeaderSize = -1;
var err = ProtocolParser.ClonePubArg(c, h, lmsg: false);
err.ShouldBeNull();
c.ArgBuf.ShouldNotBeNull();
Encoding.ASCII.GetString(c.ArgBuf!).ShouldBe("foo 5");
Encoding.ASCII.GetString(c.Pa.Subject!).ShouldBe("foo");
c.Pa.Size.ShouldBe(5);
}
[Fact]
public void ClonePubArg_RouterLmsgBranch_ReprocessesRoutedOriginArgs()
{
var c = DummyRouteClient();
var h = DummyHandler();
c.Pa.Arg = "$G foo 5"u8.ToArray();
c.Pa.HeaderSize = -1;
var err = ProtocolParser.ClonePubArg(c, h, lmsg: true);
err.ShouldBeNull();
c.ArgBuf.ShouldNotBeNull();
Encoding.ASCII.GetString(c.ArgBuf!).ShouldBe("$G foo 5");
Encoding.ASCII.GetString(c.Pa.Account!).ShouldBe("$G");
Encoding.ASCII.GetString(c.Pa.Subject!).ShouldBe("foo");
c.Pa.Size.ShouldBe(5);
}
[Fact]
public void ClonePubArg_LeafHeaderBranch_ReprocessesLeafHeaderArgs()
{
var c = new ParseContext { Kind = ClientKind.Leaf, MaxPayload = -1 };
var h = DummyHandler();
c.Pa.Arg = "$G foo 2 5"u8.ToArray();
c.Pa.HeaderSize = 2;
var err = ProtocolParser.ClonePubArg(c, h, lmsg: false);
err.ShouldBeNull();
c.ArgBuf.ShouldNotBeNull();
Encoding.ASCII.GetString(c.ArgBuf!).ShouldBe("$G foo 2 5");
Encoding.ASCII.GetString(c.Pa.Account!).ShouldBe("$G");
Encoding.ASCII.GetString(c.Pa.Subject!).ShouldBe("foo");
c.Pa.HeaderSize.ShouldBe(2);
c.Pa.Size.ShouldBe(5);
}
[Fact]
public void ClientConnection_ExposesParserCompatibilityMethods()
{
var flags = System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Public;
var methods = typeof(ClientConnection).GetMethods(flags);
methods.Any(method =>
method.Name == "Parse" &&
method.GetParameters().Length == 2 &&
method.GetParameters()[0].ParameterType == typeof(byte[]) &&
method.GetParameters()[1].ParameterType == typeof(IProtocolHandler)).ShouldBeTrue();
methods.Any(method =>
method.Name == "OverMaxControlLineLimit" &&
method.GetParameters().Length == 3 &&
method.GetParameters()[0].ParameterType == typeof(byte[]) &&
method.GetParameters()[1].ParameterType == typeof(int) &&
method.GetParameters()[2].ParameterType == typeof(IProtocolHandler)).ShouldBeTrue();
methods.Any(method =>
method.Name == "ClonePubArg" &&
method.GetParameters().Length == 2 &&
method.GetParameters()[0].ParameterType == typeof(IProtocolHandler) &&
method.GetParameters()[1].ParameterType == typeof(bool)).ShouldBeTrue();
}
// =====================================================================
// TestProtocolHandler — stub handler for tests
// =====================================================================
@@ -770,6 +875,7 @@ public class ProtocolParserTests
public int PingCount { get; private set; }
public int PongCount { get; private set; }
public byte[]? ConnectArgs { get; private set; }
public List<byte[]> InboundMessages { get; } = [];
public Exception? ProcessConnect(byte[] arg) { ConnectArgs = arg; return null; }
public Exception? ProcessInfo(byte[] arg) => null;
@@ -786,7 +892,7 @@ public class ProtocolParserTests
public Exception? ProcessLeafUnsub(byte[] arg) => null;
public Exception? ProcessAccountSub(byte[] arg) => null;
public void ProcessAccountUnsub(byte[] arg) { }
public void ProcessInboundMsg(byte[] msg) { }
public void ProcessInboundMsg(byte[] msg) => InboundMessages.Add(msg);
public bool SelectMappedSubject() => false;
public void TraceInOp(string name, byte[]? arg) { }
public void TraceMsg(byte[] msg) { }

Binary file not shown.