diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Pccc/PcccServices.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Pccc/PcccServices.cs
index 7e1febc9..bfcd9065 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Pccc/PcccServices.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Pccc/PcccServices.cs
@@ -16,6 +16,18 @@ public static class PcccServices
/// False: no PLC-5 write capture exists, so no PLC-5 write builder ships.
public static bool Plc5WriteVerified { get; } = false;
+ /// Requestor vendor id the native transport puts in Execute PCCC: libplctag's, from the captures
+ /// (07 3D F3 45 43 50 21 in slc500-read-n7.hex).
+ /// Not the CIP originator vendor (). ab_server answers every
+ /// PCCC request with this fixed requestor id (PCCC_RESP_PREFIX in its pccc.c) instead of
+ /// echoing the request's, while real controllers echo whatever was sent. Sending libplctag's identity is
+ /// the only choice that lets the echo check in pass against both (found by
+ /// Task 17 slc500: with the 0x1337 originator identity every native PCCC reply was BadReply).
+ public const ushort RequestorVendor = 0xF33D;
+
+ /// Requestor serial number paired with (libplctag's, from the captures).
+ public const uint RequestorSerial = 0x21504345;
+
/// Wraps a PCCC command in Execute PCCC: 4B 02 20 67 24 01 + requestor id (7, vendor, serial) + PCCC.
public static byte[] ExecutePccc(ushort requestorVendor, uint requestorSerial, ReadOnlySpan pccc) =>
new CipWriter().U8(0x4B).U8(2).U8(0x20).U8(0x67).U8(0x24).U8(0x01)
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/NativeAbLegacyTagRuntime.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/NativeAbLegacyTagRuntime.cs
index a3078527..b11d3a82 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/NativeAbLegacyTagRuntime.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/NativeAbLegacyTagRuntime.cs
@@ -40,12 +40,14 @@ internal sealed class NativeAbLegacyTagRuntime : IAbLegacyTagRuntime
}
private NativeAbLegacyTagRuntime(AbLegacyTagCreateParams p, DeviceSessionPool pool, ConnectionOptions options)
- : this(p, pool.Acquire(KeyFor(p), options), options)
+ : this(p, pool.Acquire(KeyFor(p), options))
{
}
- private NativeAbLegacyTagRuntime(AbLegacyTagCreateParams p, ConnectionLease lease, ConnectionOptions options)
- : this(p, lease.Scheduler, options.VendorId, options.OriginatorSerial)
+ // The PCCC requestor id is libplctag's captured identity, not the connection's CIP originator: ab_server
+ // replies with that fixed id whatever was sent, and real controllers echo it (see PcccServices.RequestorVendor).
+ private NativeAbLegacyTagRuntime(AbLegacyTagCreateParams p, ConnectionLease lease)
+ : this(p, lease.Scheduler, PcccServices.RequestorVendor, PcccServices.RequestorSerial)
{
_lease = lease;
}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/NativeAbLegacyTagRuntimeTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/NativeAbLegacyTagRuntimeTests.cs
index 266fb44e..a9b479f7 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/NativeAbLegacyTagRuntimeTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/NativeAbLegacyTagRuntimeTests.cs
@@ -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();
+ }
}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests.csproj b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests.csproj
index 0dd8f992..a2b220f3 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests.csproj
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests.csproj
@@ -27,6 +27,7 @@
+