# AB CIP integration-test fixture — `ab_server` (Docker) [libplctag](https://github.com/libplctag/libplctag)'s `ab_server` — a MIT-licensed C program that emulates a ControlLogix / CompactLogix CIP endpoint over EtherNet/IP. Docker is the only supported launch path; `ab_server` ships as a source-only tool under libplctag's `src/tools/ab_server/` so the Dockerfile's multi-stage build is the only reproducible way to get a working binary across developer boxes. A fresh clone needs Docker Desktop and nothing else. | File | Purpose | |---|---| | [`Dockerfile`](Dockerfile) | Multi-stage: build from libplctag at pinned tag → copy binary into `debian:bookworm-slim` runtime image | | [`docker-compose.yml`](docker-compose.yml) | One service per family (`controllogix` / `compactlogix` / `micro800` / `guardlogix`); all bind `:44818` | | [`patches/`](patches/) | Applied to the libplctag checkout before the build — see [Local patches](#local-patches) | ## Local patches `patches/0001-ab_server-serve-cip-symbol-object-listing.patch` teaches `ab_server` to answer **CIP service 0x55 (Get Instance Attribute List) on class 0x6B**, the Symbol Object listing a client reads through the `@tags` pseudo-tag to enumerate a controller's symbol table. Upstream answers `unsupported`, which is what left the AbCip controller browse unverifiable (Gitea #468) — the browse binding was proven alive, but no backend ever returned a tag. Two things worth knowing before touching it: - **The reply layout is fixed to the attribute set libplctag asks for** — `{0x02 symbol type, 0x07 element size, 0x08 array dimensions, 0x01 symbol name}`. A request for a different set is refused rather than answered with a differently-shaped reply the client would misparse. - **Entries are packed with no padding.** The next entry begins on the byte after the last name byte, even on an odd offset. This is not a detail of the fixture: it is what a controller does, and assuming otherwise is exactly the decoder defect the gate found. Correctness is checked against **libplctag's own `list_tags_logix`** client, not against our decoder — our decoder is the thing under test, so agreeing with it would prove nothing. `list_tags_logix` is the reference implementation exercised against real Logix hardware: ```bash git clone --depth 1 --branch v2.7.0 https://github.com/libplctag/libplctag.git cd libplctag && cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel ./build/bin_dist/list_tags_logix 10.100.0.35 1,0 # expect all nine tags, "SUCCESS!" ``` Partial-transfer paging is exercised the same way by starting `ab_server` with a few hundred tags: all of them should come back, with no duplicates and no gaps at the page boundaries. **Bumping `LIBPLCTAG_TAG` means re-checking the patch against the new tag.** It is a concrete tag rather than the `release` branch precisely so that this stays a deliberate act — the branch moves, and the Dockerfile previously called it "pinned" while tracking it. ## Run From the repo root: ```powershell # ControlLogix — widest-coverage profile docker compose -f tests\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests\Docker\docker-compose.yml --profile controllogix up # Per-family docker compose -f tests\...\Docker\docker-compose.yml --profile compactlogix up docker compose -f tests\...\Docker\docker-compose.yml --profile micro800 up docker compose -f tests\...\Docker\docker-compose.yml --profile guardlogix up ``` Detached + stop: ```powershell docker compose -f tests\...\Docker\docker-compose.yml --profile controllogix up -d docker compose -f tests\...\Docker\docker-compose.yml --profile controllogix down ``` First run builds the image (~3-5 minutes — clones libplctag + compiles `ab_server` + its dependencies). Subsequent runs are fast because the multi-stage build layer-caches the checkout + compile. ## Endpoint - Default: `localhost:44818` (EtherNet/IP standard; non-privileged) - Override with `AB_SERVER_ENDPOINT=host:port` to point at a real PLC. ## Run the integration tests In a separate shell with a container up: ```powershell cd C:\Users\dohertj2\Desktop\lmxopcua dotnet test tests\Drivers\ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests ``` `AbServerFixture` TCP-probes `localhost:44818` at collection init + records a skip reason when unreachable, so tests stay green on a fresh clone without the container running. Tests use `[AbServerFact]` / `[AbServerTheory]` which check the same probe. ## What each family seeds Tag sets match `AbServerProfile.cs` exactly — changing seeds in one place means updating both. | Family | Seeded tags | Notes | |---|---|---| | ControlLogix | `TestDINT` `TestREAL` `TestBOOL` `TestSINT` `TestString` `TestArray` `Pump_1_Speed` `Line1_Count` `Motor_Run` | Widest-coverage; PR 9 baseline. UDT emulation missing from ab_server. The last three serve the controller-browse gate and are additive — the read/write suites key off the `Test*` tags. `Motor_Run` (9), `Line1_Count` (11) and `TestArray` (9) have **odd-length names on purpose**: entries are unpadded, so an odd name puts every following entry on an odd offset, and a decoder that assumes even alignment loses everything behind it | | CompactLogix | `TestDINT` `TestREAL` `TestBOOL` | Narrow ConnectionSize cap enforced driver-side; ab_server accepts any size | | Micro800 | `TestDINT` `TestREAL` | ab_server has no `micro800` mode; falls back to `controllogix` emulation | | GuardLogix | `TestDINT` `SafetyDINT_S` | ab_server has no safety subsystem; `_S` suffix triggers driver-side classification only | ## Known limitations - **No UDT / CIP Template Object emulation** — `ab_server` covers atomic types only. UDT reads + task #194 whole-UDT optimization verify via unit tests with golden byte buffers. The Symbol Object patch above lists symbols, including their types; it does not add Template Object support, so a browsed UDT still cannot be expanded into members here. - **No `@raw_cpu_type`** — the driver's Test Connect probe falls back to that pseudo-tag when a device has no authored tags, and `ab_server` answers `ErrorBadParam`. So **Test Connect reports failure against this fixture even when the device is fine**, and browse works regardless. Author one tag and the probe uses it instead. - **Controller-scope listing only** — a program-scope listing arrives with a leading symbolic segment naming the program, and `ab_server` has no notion of programs. `Program:`-scoped tags cannot be browsed here. - **Family-specific quirks trust driver-side code** — ab_server emulates a generic Logix CPU; the ConnectionSize cap, empty-path unconnected mode, and safety-partition write rejection all need lab rigs for wire-level proof. See [`docs/drivers/AbServer-Test-Fixture.md`](../../../docs/drivers/AbServer-Test-Fixture.md) for the full coverage map. ## References - [libplctag on GitHub](https://github.com/libplctag/libplctag) - [`docs/drivers/AbServer-Test-Fixture.md`](../../../docs/drivers/AbServer-Test-Fixture.md) — coverage map - [`docs/v2/dev-environment.md`](../../../docs/v2/dev-environment.md) §Docker fixtures