fix(ablegacy): PCCC requestor id is libplctag's captured identity (found by Task 17 slc500)

ab_server answers every Execute PCCC with a fixed requestor id (pccc.c
PCCC_RESP_PREFIX: 07 3D F3 45 43 50 21) instead of echoing the request's, so
the R14-3 echo check rejected every native reply sent under the 0x1337 CIP
originator identity as BadReply. The pooled runtime now sends libplctag's
identity (PcccServices.RequestorVendor/RequestorSerial), which passes the echo
check against ab_server and against a real controller that echoes. Pinned by a
FakeEipServer round trip that answers with ab_server's fixed prefix.
This commit is contained in:
Joseph Doherty
2026-10-07 17:31:45 -04:00
parent 82d011c7a8
commit 2171fc8ce3
4 changed files with 46 additions and 3 deletions
@@ -475,4 +475,32 @@ public sealed class NativeAbLegacyTagRuntimeTests
b.Dispose();
await Task.Yield();
}
[Fact]
public async Task A_pooled_runtime_sends_libplctags_requestor_id_and_accepts_ab_servers_fixed_echo()
{
// ab_server answers every PCCC request with the fixed prefix CB 00 00 00 07 3D F3 45 43 50 21
// (pccc.c PCCC_RESP_PREFIX) and a zeroed sender context, whatever requestor id was sent. With the
// 0x1337 CIP originator identity every native read was BadReply. Found by Task 17 slc500.
byte[] fixedId = [0x07, 0x3D, 0xF3, 0x45, 0x43, 0x50, 0x21];
await using var server = new FakeEipServer { ZeroReplyContext = true };
server.Handler = req =>
{
if (FakeEipServer.Command(req) != Ab.Transport.Eip.EncapCommand.SendRRData) return null;
var cip = FakeEipServer.RequestCip(req);
cip.AsSpan(6, 7).ToArray().ShouldBe(fixedId);
byte[] reply = [0xCB, 0x00, 0x00, 0x00, .. fixedId, 0x4F, 0x00, cip[15], cip[16], 0xE1, 0x10];
return FakeEipServer.RRReply(req, reply);
};
var pool = new DeviceSessionPool();
var p = new AbLegacyTagCreateParams("127.0.0.1", server.Port, "1,0", "slc500", "N7:0", TimeSpan.FromSeconds(5));
using var runtime = new NativeAbLegacyTagRuntime(p, pool);
await runtime.InitializeAsync(TestContext.Current.CancellationToken);
await runtime.ReadAsync(TestContext.Current.CancellationToken);
runtime.GetStatus().ShouldBe(0);
runtime.DecodeValue(AbLegacyDataType.Int, null).ShouldBe(4321);
server.HandlerErrors.ShouldBeEmpty();
}
}
@@ -27,6 +27,7 @@
<ItemGroup>
<!-- Shared transport test fakes + golden captures (same sources as the Ab.Transport tests). -->
<Compile Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests\Fakes\FakeCipChannel.cs" Link="Fakes\FakeCipChannel.cs"/>
<Compile Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests\Fakes\FakeEipServer.cs" Link="Fakes\FakeEipServer.cs"/>
<Compile Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests\Captures\CaptureFile.cs" Link="Captures\CaptureFile.cs"/>
<None Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests\Captures\*.hex" Link="Captures\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>