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:
@@ -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;
|
||||
}
|
||||
|
||||
+28
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user