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.
Golden frame captures — libplctag ⇄ ab_server
These .hex files are the exact EtherNet/IP frames libplctag exchanged with the ab_server
simulator. The native AB transport's codecs (plan Tasks 7 and 8) assert byte parity against
them.
⚠️ They prove parity with libplctag, not correctness against a controller. The independent reference is the real-press captures in
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/CipSymbolObjectRealControllerCaptureTests.csandCipTemplateObjectWireCaptureTests.cs.
Format
One frame per line: > = client→server, < = server→client, then the full encapsulation
frame (24-byte header + data) as uppercase hex with no spaces. # lines are comments; each file
opens with a comment block naming its client, server, profile, tag string and what the frames are.
CaptureFile.Load reads them; CaptureFileTests checks every frame's header length field
against its data length and that requests and replies alternate.
Values are little-endian on the wire and carry live per-session values — session handle, connection IDs, connection serial, PCCC TNS. A parity test must compare structure and the fields it builds, not those session-assigned values.
How they were taken
| Date | 2026-10-07 |
| Repo commit | a0897fda (fixture sources identical to the files on the docker host, checked by md5) |
| Client | libplctag 1.5.2 (libplctag.NET NuGet, Directory.Packages.props) → libplctag.NativeImport 1.0.41 → native core 2.6.0 (read via plc_tag_get_int_attribute(0,"version_*")) |
| Server image | otopcua-ab-server:libplctag-release (ab_server from libplctag v2.7.0 + vendored patches 0001 @tags listing and 0002 tag-not-found → CIP 0x04; both confirmed live — see logix-tags-listing.hex / logix-read-unknown-tag.hex) |
| AbCip compose | /opt/otopcua-abcip on 10.100.0.35 (= tests/.../AbCip.IntegrationTests/Docker/), host :44818 |
| AbLegacy compose | ~/otopcua-ablegacy on 10.100.0.35 (= tests/.../AbLegacy.IntegrationTests/Docker/), host :44819 slc500 / :44820 micrologix / :44821 plc5 |
| Capture point | tcpdump -i eth0 tcp port 44818 in a nicolaka/netshoot:v0.13 sidecar sharing the simulator container's network namespace; tshark 4.2.4 in the same image; segments re-joined into frames by the 24-byte header length field |
| Recipe | scripts/ab-capture/capture.sh run … then capture.sh tohex … |
The client was a ~60-line console program over libplctag 1.5.2 that mirrors how
LibplctagTagRuntime / LibplctagLegacyTagRuntime / LibplctagTagEnumerator build a tag
(Gateway, Path, PlcType, Protocol=ab_eip, Name, Timeout=5s, ElementCount when > 1;
no other attributes), then runs InitializeAsync + one ReadAsync or WriteAsync, disposes the
tag and calls plc_tag_shutdown so the Forward Close reaches the wire. One fresh session per
capture, one profile up at a time. Note that InitializeAsync itself performs a read, so a
"read" capture holds two read exchanges and a "write" capture holds a read then the write.
Per-file record
All files: libplctag 1.5.2 (native 2.6.0), image otopcua-ab-server:libplctag-release. The
tag string is the libplctag attribute set the wrapper was given.
| File | Profile | Tag string | Frames |
|---|---|---|---|
logix-register-session.hex |
controllogix | protocol=ab_eip&gateway=10.100.0.35:44818&path=1,0&plc=controllogix&name=TestDINT |
RegisterSession (frames 1–2 of the logix-read-dint session) |
logix-forward-open.hex |
controllogix | same as above | Large Forward Open 0x5B (frames 3–4) |
logix-read-dint.hex |
controllogix | …&plc=controllogix&name=TestDINT |
2× connected Read Tag Fragmented 0x52 (value 123456) |
logix-read-dint-array16.hex |
controllogix | …&name=TestArray&elem_count=16 |
2× 0x52, 16 elements |
logix-read-string.hex |
controllogix | …&name=TestString |
2× 0x52, STRING "HelloAB" |
logix-read-bool.hex |
controllogix | …&name=TestBOOL |
2× 0x52, BOOL 0 |
logix-write-dint.hex |
controllogix | …&name=TestDINT |
0x52 read, then Write Tag 0x4D of 123456 |
logix-write-string.hex |
controllogix | …&name=TestString |
0x52 read, then 0x4D of "HelloAB" |
logix-read-unknown-tag.hex |
controllogix | …&name=NoSuchTag |
0x52 → general status 0x04 (patch 0002); libplctag raised ErrorNotFound, no retry |
logix-tags-listing.hex |
controllogix | …&name=@tags |
Get Instance Attribute List 0x55 on Symbol class 0x6B (patch 0001); all 9 tags in one reply, status 0x00 |
logix-forward-close.hex |
controllogix | …&name=TestDINT |
Forward Close 0x4E (last 2 frames of the logix-read-dint session) |
micro800-read-dint.hex |
micro800 | protocol=ab_eip&gateway=10.100.0.35:44818&plc=micro800&name=TestDINT |
Full session: RegisterSession, Large FO, 2× 0x52 (value 987654), Forward Close |
slc500-read-n7.hex |
slc500 | protocol=ab_eip&gateway=10.100.0.35:44819&path=1,0&plc=slc500&name=N7:0 |
Full session: RegisterSession, 2× Execute PCCC 0x4B / PCCC 0x0F-0xA2 (value 4321) |
slc500-write-n7.hex |
slc500 | same | Full session: 0x0F-0xA2 read, then 0x0F-0xAA write of 4321 |
slc500-read-f8.hex |
slc500 | …:44819&path=1,0&plc=slc500&name=F8:0 |
Full session: 2× 0x0F-0xA2 (value 3.5) |
micrologix-read-n7.hex |
micrologix | protocol=ab_eip&gateway=10.100.0.35:44820&plc=micrologix&name=N7:0 |
Full session: 2× 0x0F-0xA2 (value 2468) |
plc5-read-n7.hex |
plc5 | protocol=ab_eip&gateway=10.100.0.35:44821&path=1,0&plc=plc5&name=N7:0 |
Full session: 2× PCCC 0x0F-0x01 PLC-5 typed read (value 1357) |
Logix files other than register-session/forward-open/forward-close hold only the
operation's exchanges; the handshake frames of their sessions are byte-for-byte the same shape
as those three files. The Micro800 and PCCC files hold the whole session so their routing is
visible in one place. No session in any family sent UnregisterSession (0x66).
Routing observed
What libplctag 2.6.0 actually put on the wire. Tasks 9, 12 and 14 take their Routing choice
from this table.
| Family | ForwardOpen | CIP request wrapped in Unconnected Send (0x52) | Route bytes | Requestor-id bytes (PCCC) |
|---|---|---|---|---|
controllogix (path=1,0) |
yes — Large FO 0x5B only (0x54 never sent; the 0x5B succeeded). FO itself is sent direct to the Connection Manager 20 06 24 01, not wrapped. Connection path 01 00 20 02 24 01 (3 words), net params 0x42000FA2, RPI 1 000 000 µs, transport 0xA3 |
no — requests ride the connection (SendUnitData, item 0xB1) | 01 00 (port 1, slot 0), carried only inside the FO connection path |
n/a |
| micro800 (no path) | yes — Large FO 0x5B, connection path 20 02 24 01 (2 words, no port segment). ⚠️ Not UCMM; see concerns |
no — connected | none | n/a |
slc500 (path=1,0) |
no | no — Execute PCCC 0x4B sent unconnected (SendRRData, item 0xB2) straight to 20 67 24 01 (PCCC object) |
none — the 1,0 path was given but never appeared on the wire |
07 3D F3 45 43 50 21 (length 7, vendor 0xF33D, serial 0x21504345) |
| micrologix (no path) | no | no — as slc500 | none | 07 3D F3 45 43 50 21 |
plc5 (path=1,0) |
no | no — as slc500 | none — 1,0 not emitted |
07 3D F3 45 43 50 21 |
logixpccc (path=1,0, against the controllogix profile) |
no | yes — Unconnected Send 0x52 to 20 06 24 01, priority/tick 0A 05, embedded 0x4B (len 0x1C) |
01 00 (path size 1 word, reserved, then 01 00) |
07 3D F3 45 43 50 21 |
logixpccc is not reachable: ab_server's ControlLogix mode has no PCCC object and answers
CB 00 08 00 (service not supported). Its request shape above is read from the client side of
that rejected exchange; no .hex fixture was committed for it, since a parity fixture whose
reply is an error would teach nothing about the success path.