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
@@ -16,6 +16,18 @@ public static class PcccServices
/// <summary>False: no PLC-5 write capture exists, so no PLC-5 write builder ships.</summary>
public static bool Plc5WriteVerified { get; } = false;
/// <summary>Requestor vendor id the native transport puts in Execute PCCC: libplctag's, from the captures
/// (<c>07 3D F3 45 43 50 21</c> in <c>slc500-read-n7.hex</c>).</summary>
/// <remarks>Not the CIP originator vendor (<see cref="ConnectionOptions.VendorId"/>). ab_server answers every
/// PCCC request with this fixed requestor id (<c>PCCC_RESP_PREFIX</c> in its <c>pccc.c</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 <see cref="ParseReply"/> pass against both (found by
/// Task 17 slc500: with the 0x1337 originator identity every native PCCC reply was BadReply).</remarks>
public const ushort RequestorVendor = 0xF33D;
/// <summary>Requestor serial number paired with <see cref="RequestorVendor"/> (libplctag's, from the captures).</summary>
public const uint RequestorSerial = 0x21504345;
/// <summary>Wraps a PCCC command in Execute PCCC: <c>4B 02 20 67 24 01</c> + requestor id (7, vendor, serial) + PCCC.</summary>
public static byte[] ExecutePccc(ushort requestorVendor, uint requestorSerial, ReadOnlySpan<byte> pccc) =>
new CipWriter().U8(0x4B).U8(2).U8(0x20).U8(0x67).U8(0x24).U8(0x01)
@@ -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;
}