diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationClient.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationClient.cs index f5c732ad..2b1be8b1 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationClient.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationClient.cs @@ -82,7 +82,7 @@ public sealed class EncapsulationClient : IAsyncDisposable try { var (header, _) = await client.ExchangeAsync(connected: false, - (ctx, _) => EncapsulationCodec.RegisterSession(ctx), timeout, ct).ConfigureAwait(false); + (_, _) => EncapsulationCodec.RegisterSession(), timeout, ct).ConfigureAwait(false); client.SessionHandle = header.SessionHandle; return client; } diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationCodec.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationCodec.cs index 60234a05..8b7d4f10 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationCodec.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Eip/EncapsulationCodec.cs @@ -30,9 +30,13 @@ public static class EncapsulationCodec return w.ToArray(); } - /// RegisterSession: protocol version 1, option flags 0. - public static byte[] RegisterSession(ulong ctx) => - Finish(Header(EncapCommand.RegisterSession, 0, ctx).U16(1).U16(0)); + /// RegisterSession: protocol version 1, option flags 0, and a zero sender context. + /// The sender context is always zero, as libplctag sends it (logix-register-session.hex): + /// ab_server's RegisterSession sanity check rejects any non-zero context with encapsulation status 1 + /// and a header-only reply, so a per-request context here failed every native session against the + /// simulator. The reply is still matched — unconnected matching tolerates a zero context. + public static byte[] RegisterSession() => + Finish(Header(EncapCommand.RegisterSession, 0, 0).U16(1).U16(0)); /// UnRegisterSession: header only, no data. public static byte[] UnRegisterSession(uint session, ulong ctx) => diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationClientTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationClientTests.cs index 0d69e38d..3392ff68 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationClientTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationClientTests.cs @@ -32,6 +32,16 @@ public sealed class EncapsulationClientTests FakeEipServer.Command(server.Received[0]).ShouldBe(EncapCommand.RegisterSession); } + [Fact] + public async Task RegisterSession_goes_on_the_wire_with_a_zero_sender_context() + { + // ab_server refuses RegisterSession with any non-zero sender context (status 1, header-only + // reply), so the client must not stamp its per-request context on it. Found by Task 17. + await using var server = new FakeEipServer(); + await using var client = await Connect(server); + EncapsulationCodec.ReadHeader(server.Received[0]).SenderContext.ShouldBe(0UL); + } + [Fact] public async Task SendRRData_returns_cip_payload() { diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationCodecTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationCodecTests.cs index a9c3888b..174b99e7 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationCodecTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Eip/EncapsulationCodecTests.cs @@ -10,8 +10,8 @@ public sealed class EncapsulationCodecTests [Fact] public void RegisterSession_frame_is_byte_exact() => - Convert.ToHexString(EncapsulationCodec.RegisterSession(Ctx)).ShouldBe( - "6500" + "0400" + "00000000" + "00000000" + "0102030405060708" + "00000000" + "0100" + "0000"); + Convert.ToHexString(EncapsulationCodec.RegisterSession()).ShouldBe( + "6500" + "0400" + "00000000" + "00000000" + "0000000000000000" + "00000000" + "0100" + "0000"); [Fact] public void UnRegisterSession_frame_is_header_only() => @@ -43,13 +43,12 @@ public sealed class EncapsulationCodecTests .Status.ShouldBe(AbStatus.TooLarge); [Fact] - public void RegisterSession_matches_libplctag_capture_except_context() + public void RegisterSession_matches_libplctag_capture_byte_for_byte() { + // Including the sender context: ab_server rejects a non-zero RegisterSession context with + // encapsulation status 1 (found by Task 17 controllogix — every native session failed). var captured = CaptureFile.Load("logix-register-session.hex").First(f => f.FromClient).Bytes; - var ours = EncapsulationCodec.RegisterSession(Ctx); - ours.Length.ShouldBe(captured.Length); - ours.AsSpan(0, 12).ToArray().ShouldBe(captured.AsSpan(0, 12).ToArray()); // cmd,len,session,status - ours.AsSpan(20).ToArray().ShouldBe(captured.AsSpan(20).ToArray()); // options + data + Convert.ToHexString(EncapsulationCodec.RegisterSession()).ShouldBe(Convert.ToHexString(captured)); } [Fact]