fix(abcip): #611 review round 1 — native keeps BOOL type distinction, WriteWidthRefusal tests, doc caveats

- L2: WriteWidthRefusal leaves a BOOL over a wider value to a runtime that sees the CIP
  type (native): mistyped integer member -> BadTypeMismatch (as its read), 0xD3 BOOL-array
  DWORD -> BadNotSupported; libplctag keeps BadNotSupported for both (size only).
- L4: direct theory over WriteWidthRefusal (1/2/4/8 widths incl LINT/ULINT/LREAL, String/Dt,
  unknown size) + array/bit-selector exemptions with a non-vacuous control.
- L1: CLAUDE.md member/BIT-member writes are ab_server-proven only.
- L3: patch 0003 blind spots documented (Docker README, AbServer-Test-Fixture.md); nits.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 04:31:45 -04:00
parent 5018627b35
commit f85fc8fbeb
7 changed files with 146 additions and 9 deletions
+6 -3
View File
@@ -405,8 +405,10 @@ when it is browse-committed in `/raw`, and that tag writes like any other atomic
with the bytes the controller holds at the path (the runtime's last read) and refuses a mismatch without
writing: a narrower/wider atomic type is `BadTypeMismatch` (libplctag would otherwise patch part of its read
buffer and send the whole buffer back — e.g. an `Int` over a DINT member wrote four bytes with a stale high
word); a `Bool` over a wider value (a packed BOOL-array DWORD, a mistyped integer) is `BadNotSupported`,
because writing it would rewrite every neighbouring bit. Native additionally refuses a same-width
word); a `Bool` over a wider value is never written, because writing it would rewrite every neighbouring
bit. On **native** the runtime judges it from the CIP type, exactly as its read does: a packed BOOL-array
DWORD (`0xD3`) is `BadNotSupported`, a BOOL authored over a mistyped integer member is `BadTypeMismatch`.
**libplctag** exposes only the size, cannot tell the two apart, and refuses both `BadNotSupported`. Native additionally refuses a same-width
mismatch (REAL vs DINT) from the CIP type code — see [Native transport behaviour](#native-transport-behaviour).
A refusal keeps the handle, warns once per tag and **does not touch driver health**.
- **Not verified on a real controller.** Production presses are read-only, so member writes are proven on
@@ -414,7 +416,8 @@ when it is browse-committed in `/raw`, and that tag writes like any other atomic
Z-35162 controller tag `_EmptyTemperatureSegment` (`UDT_TemperatureProfile_Segment`), which no logic
references — its members (`ID`, `Enable`, `RampTime`, …) are all zero and resolve as described above.
Pre-declared `Members` with `Writable: true` keep working exactly as before (same write path).
Pre-declared `Members` with `Writable: true` keep working as before — same write path, now width-checked
(a mistyped declared member is refused where libplctag used to write it).
### Whole-UDT writes — refused, by decision
+3 -1
View File
@@ -55,7 +55,9 @@ smoke test passes on both transports. Evidence:
- ab_server serves no UDTs, so template parity is vacuous there. Docker patch 0003 serves a UDT **member
path** from a flat tag named by that dotted path (`UdtSeg.ID`, `UdtSeg.Sub.Code`, `UdtSeg.Points[2]`) — the
wire of a member read/write, not a structure — and accepts the pad byte libplctag adds to a 1-byte write,
which stock ab_server refused (`0x03`) for every libplctag BOOL/SINT write. Native writes remain
which stock ab_server refused (`0x03`) for every libplctag BOOL/SINT write. Two blind spots follow (see
the Docker README §0003): a +1-byte over-write of an odd-sized tag is no longer caught, and a malformed
single segment `"UdtSeg.ID"` resolves where a real Logix would refuse it. Native writes remain
simulator-only.
- **Golden-frame capture:** `scripts/ab-capture/capture.sh` (capture client in `scripts/ab-capture/client`);
fixtures live in `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/` with a README.