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]