Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/CaptureFileTests.cs
T
Joseph Doherty 1201d5fcfb test(ab-transport): golden libplctag<->ab_server frame captures for Logix, Micro800 and PCCC
17 .hex fixtures captured from libplctag 1.5.2 (native 2.6.0) against
otopcua-ab-server:libplctag-release, plus the CaptureFile loader, its
length/alternation tests, and the re-runnable scripts/ab-capture/capture.sh.

Observed and recorded in Captures/README.md "Routing observed":
- Micro800 is NOT UCMM under libplctag 2.6.0: it opens a Large Forward Open
  (path 20 02 24 01) and reads connected; use_connected_msg=0 is ignored.
- SLC500/MicroLogix/PLC-5 send Execute PCCC 0x4B unconnected and unwrapped;
  a configured path=1,0 never reaches the wire.
- Only Large FO 0x5B was observed; no 0x54 fallback occurred.
- LogixPccc is unreachable against ab_server (CIP 0x08); no fixture committed.
2026-10-07 15:50:00 -04:00

75 lines
2.7 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests.Captures;
public sealed class CaptureFileTests
{
private static readonly string[] Required =
[
"logix-register-session.hex", "logix-forward-open.hex",
"logix-read-dint.hex", "logix-read-dint-array16.hex", "logix-read-string.hex", "logix-read-bool.hex",
"logix-write-dint.hex", "logix-write-string.hex", "logix-read-unknown-tag.hex",
"logix-tags-listing.hex", "logix-forward-close.hex", "micro800-read-dint.hex",
"slc500-read-n7.hex", "slc500-write-n7.hex", "slc500-read-f8.hex",
"micrologix-read-n7.hex", "plc5-read-n7.hex",
];
public static TheoryData<string> AllCaptures()
{
var data = new TheoryData<string>();
foreach (var f in Directory.GetFiles(CaptureFile.Dir, "*.hex").Order()) data.Add(Path.GetFileName(f));
return data;
}
[Fact]
public void Every_required_capture_is_present() =>
Required.Where(n => !File.Exists(Path.Combine(CaptureFile.Dir, n))).ShouldBeEmpty();
[Theory]
[MemberData(nameof(AllCaptures))]
public void Every_frame_length_field_matches_its_data_length(string name)
{
var frames = CaptureFile.Load(name);
frames.ShouldNotBeEmpty();
foreach (var f in frames)
{
f.Bytes.Length.ShouldBeGreaterThanOrEqualTo(24, name);
BitConverter.ToUInt16(f.Bytes, 2).ShouldBe((ushort)(f.Bytes.Length - 24), name);
}
}
[Theory]
[MemberData(nameof(AllCaptures))]
public void Frames_alternate_request_then_reply(string name)
{
var frames = CaptureFile.Load(name);
frames.Count.ShouldBe(frames.Count / 2 * 2, name);
for (var i = 0; i < frames.Count; i++) frames[i].FromClient.ShouldBe(i % 2 == 0, $"{name} frame {i}");
}
[Fact]
public void CipPayload_strips_the_connected_sequence_count()
{
// Connected Read Tag Fragmented of TestDINT: service 0x52, path size 5 words, symbol 0x91 len 8.
var request = CaptureFile.Load("logix-read-dint.hex")[0];
CaptureFile.CipPayload(request)[..4].ShouldBe(new byte[] { 0x52, 0x05, 0x91, 0x08 });
}
[Fact]
public void CipPayload_returns_the_unconnected_item_for_SendRRData()
{
// Large Forward Open request goes out in an unconnected data item (0xB2): service 0x5B.
var request = CaptureFile.Load("logix-forward-open.hex")[0];
CaptureFile.CipPayload(request)[0].ShouldBe((byte)0x5B);
}
[Fact]
public void CipPayload_is_empty_for_RegisterSession() =>
CaptureFile.CipPayload(CaptureFile.Load("logix-register-session.hex")[0]).ShouldBeEmpty();
}