Commit Graph

36 Commits

Author SHA1 Message Date
Joseph Doherty 1f983d610f test(abcip): hw5 hardware gate — #566 idle close drops TCP; native STRING write on Z-35162 (#592)
A. #566 on a real CompactLogix (Z-35162, read-only): a class-3 connection idled 60 s past its
16 s timeout is closed at the TCP level, not with CIP 0x0107/0x0203; the next request fails on
its write (Broken pipe -> BadConnection) in 9 ms and the one after reopens. The transport already
handles this, so no production behaviour changed; the shape is pinned offline by
CipConnectionTests.A_target_that_resets_an_idle_session_... (FakeEipServer.ResetAllConnections),
proven red with a reconnect-skipping mutation. The diagnostics-only exchange tap now also sees
Forward Open replies (granted RPIs).

B. Native STRING write through the driver on Program:ES03_TemperatureControl._StrTmp: write,
read back, restored byte-exact (user-approved). Array write NOT run: the census's unreferenced
DINT[32] _SEQ_EP arrays are aliased sequencer step registers (raw L5X grep), so none qualifies.
#567 still impossible (a 128 B array / 88 B STRING never fragments).

New env-gated harness: AbCip.IntegrationTests/HardwareGate (IdleConnectionEvidenceTests,
StringWriteGateTests). Docs: gate results (hw5 section + raw output), AbCip.md, CLAUDE.md.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 06:16:16 -04:00
Joseph Doherty 21267750c7 test(ab-transport): blocked-write timeout fault path via a stream seam (#565, #593)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:50:40 -04:00
Joseph Doherty fe94a3bdb8 test(ab): replace fixed-delay absence assertions with positive controls / processed counts (#583)
Ab.Transport.Tests: predecessor-wait counter instead of 300 ms (DeviceSessionPoolTests x2), throttle
queue/interval-park hooks instead of 50/100 ms (ForwardOpenThrottleTests x2), UnobservedTaskProbe with a
must-report control fault instead of N GC cycles (EncapsulationClientTests), re-armed keep-alive timer as
the control for the never-opened keep-alive test.
AbCip.Tests: still-polling sibling / control driver read counts instead of 300-500 ms (subscriptions x4,
alarm projection x5), structural ProbeTask==null + enabled-probe control (host probe x2), predecessor
wait (NativeAbCipLeaseReleaseTests), processed waits in the shutdown/logging tests.
AbLegacy.Tests: sibling-subscription control (Unsubscribe_halts_polling), ProbeCts==null + control.
AbCip.IntegrationTests differential safety: a post-phase control open through the same throttle instead
of 1.5-2.5 s sleeps (via a native tag init, keeping the harness read-only guard green).
Fake tag ReadCount made interlocked; CapturingLogger entries snapshotted under a lock.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 23:28:33 -04:00
Joseph Doherty 3edff3304b fix(ab-transport): per-pool diagnostics + per-device open-attempt limit, CIP-level close recycle, CipConnection/pool test gaps (#566, #581)
- Process-wide CipConnection statics (ForwardOpenCount, Forward Close counts, RequestsSent, exchange
  stats, OpenAttempts, OpenAttemptBudget) move to an instance ConnectionDiagnostics owned by each
  DeviceSessionPool (and by a stand-alone CipConnection). The open-attempt budget becomes a disposable
  per-DeviceKey limit (LimitOpenAttempts); the differential harness scopes one to the press under test.
- LibplctagOrphanRelease.ForceRootingForTest is gone: ReleaseIfOrphaned(tag, canReadHandles) overload.
- A connected reply with general 0x01 / extended 0x0107 or 0x0203 (target no longer holds the class-3
  connection, TCP still up) retires the session at once; the next send reopens through the throttle.
- New tests: dispose during RegisterSession, abandoned-open observer (falsified), scheduler-dispose-throws
  guard (DeviceSessionPool.CloseAsync), release-in-background with a shared open in flight, per-device
  limit scoping, CIP-close recycle (request + keep-alive + negative control).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 23:21:02 -04:00
Joseph Doherty 501f6ea4e5 Merge #570/#579: native AbCip per-tag config errors, no mistype churn, STRING 82, LWORD type checks; non-blocking lease release (AbCip + AbLegacy)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe

# Conflicts:
#	src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport/Cip/CipPath.cs
2026-10-07 23:06:52 -04:00
Joseph Doherty 4e6eed7454 fix(abcip): native runtime STRING capacity, mistype churn, malformed-name config error, LWORD type check; non-blocking lease release (#570, #579)
#570
- STRING: the built-in 88-byte Logix STRING holds 82 characters (LEN + DATA[82] + 2 pad), not 84; the
  write still sends the whole 88-byte structure and a read never decodes the pad as characters.
- Mistype churn: a type refusal (CIP 0xFF/0x2107) on the existence read no longer throws from
  InitializeAsync, so the runtime is not disposed/re-created (and re-Forward-Opened) every poll; the driver
  also keeps a runtime that throws TypeMismatch from init and records the device as reachable.
- Malformed tag name: a TagPath that does not parse, or a name the CIP symbolic encoder refuses (now also
  non-ASCII, empty member, >255-char member, unterminated subscript), is AbCipTagConfigurationException ->
  per-tag BadConfigurationError (named const in AbCipStatusMapper), warned once, never feeding the device
  backoff or health; siblings keep reading. A malformed probe tag no longer feeds the backoff either.
- Micro800 BYTE/WORD/LWORD (0xD1/0xD2/0xD4) are type-checked as integers of their width: REAL/LREAL or a
  narrower integer over LWORD is TypeMismatch, not decoded garbage.

#579
- ConnectionLease.ReleaseInBackground: one non-blocking release shape for both native AB runtimes. The
  AbCip runtime's Dispose no longer waits up to 2 s under the per-tag lock; the pool still registers the
  close synchronously so a re-create cannot overlap it. Offline soak tests (25 cycles, AbCip + AbLegacy)
  prove no leaked connection; FakeEipServer gains OpenConnections.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 23:05:49 -04:00
Joseph Doherty 4ba6cbab3a Merge #572/#575: native browse paging/lease/pacing, differential equal-refusal + budget isolation
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe

# Conflicts:
#	docs/drivers/AbCip.md
2026-10-07 23:01:26 -04:00
Joseph Doherty be421a4cb0 fix(abcip): native browse paging/pacing/template lease (#572); differential equal-refusal + budget isolation (#575)
#572: listing continues at max(instance in page)+1 (no ascending-order assumption, no-progress guard kept);
browsePageDelayMs (default 5 ms) paces pages; DiscoverAsync reads templates through one batch reader
(IAbCipTemplateReaderFactory.CreateBatch) holding one lease.
#575: differential harness reports a same-status template refusal as an equal refusal; offline test pins the
open-attempt budget without relying on the abort ending the phase.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:59:42 -04:00
Joseph Doherty 29cd25dec8 Merge #568/#569/#580/#585: shared AB status map + orphan release, confinement guard tokeniser, adapter + DefaultTransport tests
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:59:30 -04:00
Joseph Doherty 341417f193 fix(ab): share status map + libplctag orphan release, harden confinement guard, pin width-mismatch + DefaultTransport wiring (#568 #569 #580 #585)
- Ab.Transport: AbOpcUaStatusMap is the single AbStatus->OPC UA table; AbCip/AbLegacy mappers delegate.
- Remove dead MapCipGeneralStatus / MapPcccStatus / AbLegacy MapException (+ tests).
- LibplctagOrphanRelease is one source file (Ab.LibplctagShared) compiled into both drivers.
- LibplctagConfinementGuardTests: tokenising comment/literal stripper, block comments, csproj PackageReference guard.
- LibplctagStatusTranslationTests: real LibPlcTagException round trip, WrapSync/Wrap OCE pass-through, adapter routes decode via WrapSync.
- AbCipTypeMismatchTests: libplctag width-mismatch path (BadTypeMismatch, handle kept, no Degraded, warn once).
- Host: AddAbCipDriverDefaults extension + Program.cs source pin for Drivers:AbCip:DefaultTransport.
- docs/drivers/AbCip.md: layout rows + benign Degraded-after-outage note.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:59:06 -04:00
Joseph Doherty 13a6727033 Merge #564/#565: CIP codec hardening, EncapsulationClient gate/timeout bounds
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:55:02 -04:00
Joseph Doherty 7892eb13a8 fix(abcip): harden Ab.Transport CIP codec and EncapsulationClient (#564, #565)
CIP codec (#564): PatchU16 bounds guard; CipReply content equality; CipPath
port/length/ASCII/odd/oversize validation with ArgumentException for malformed
tag names and routes; CipPath.MessageRouter/ConnectionManager are now
ReadOnlySpan<byte>; refused Forward Open surfaces ForwardOpenRefusedException
with structured general/extended status; test CaptureFile uses BinaryPrimitives.

EncapsulationClient (#565): frame write bounded by the exchange timeout, which
faults (never cancels) the connection; finite positive timeouts enforced at
every entry point; gate wait bounded by the timeout; deterministic reorder of
the 300 ms test; new dispose-during-drain, gate-wait and RegisterSession
zero-context tests.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:54:32 -04:00
Joseph Doherty 9641cefe2e fix(abcip): budget expected reply size in RequestScheduler.TakeBatch (#582); private lock + struct-handle check across read fragments (#567)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:53:10 -04:00
Joseph Doherty 1d6501f427 feat(ab-transport): process-wide open-attempt budget so the differential never retries an open (Task 18 review r1) 2026-10-07 18:10:25 -04:00
Joseph Doherty 82d011c7a8 fix(ab-transport): RegisterSession carries a zero sender context (found by Task 17 controllogix)
ab_server's RegisterSession sanity check rejects any non-zero sender context
with encapsulation status 1 and a header-only reply, so every native session
against the simulator failed with BadCommunicationError. libplctag sends zero
(logix-register-session.hex); the codec test now matches that capture byte for
byte instead of excluding the context, and a client test pins the zero on the wire.
2026-10-07 17:20:06 -04:00
Joseph Doherty 1211cd9a83 test(abcip): Task 13 review r2 - non-vacuous exclusive-packing tests
Scheduler tests (exclusive mid-queue and at head) and a template-reader test that
genuinely queues a Read Template behind a small read; bounded QueuedCount wait
replaces the fixed delay. Falsified: removing exclusive from the reader turns the
reader test red; removing the Exclusive handling in RequestScheduler turns all three red.
2026-10-07 17:10:35 -04:00
Joseph Doherty 33dbd342e5 fix(ab-transport): trim-only CIP path canonicalisation, observe abandoned shared opens, no Forward Open after dispose during connect 2026-10-07 16:49:19 -04:00
Joseph Doherty a251954bf7 fix(ab-transport): harden Task 9 pool and connection (close chaining, uncancellable shared open, monotonic throttle, canonical CIP path, internal ctors, shared-pool logger) 2026-10-07 16:45:26 -04:00
Joseph Doherty b82fffd269 feat(ab-transport): per-device connection with routing modes, shared scheduler, keep-alive and Forward Open throttle 2026-10-07 16:35:11 -04:00
Joseph Doherty ba73f48f9c merge: Task 10 — RequestScheduler (MSP coalescing) + LogixTagClient fragment loops 2026-10-07 16:23:08 -04:00
Joseph Doherty 5211975067 fix(ab-transport): harden Task 10 scheduler and tag client (dispose faults, pump guard, read cap, write validation, 8-byte wide-struct alignment) 2026-10-07 16:22:40 -04:00
Joseph Doherty 1c7fd5d5de feat(ab-transport): MSP-coalescing request scheduler and fragmented Logix read/write 2026-10-07 16:15:05 -04:00
Joseph Doherty 2f9c7eff58 merge: Task 5 — EtherNet/IP encapsulation codec + single-flight client 2026-10-07 16:14:02 -04:00
Joseph Doherty 4c1c362b0e feat(ab-transport): EtherNet/IP encapsulation codec and single-flight session client 2026-10-07 16:09:06 -04:00
Joseph Doherty 7c81844be5 merge: Task 8 — PCCC codec (Execute PCCC, SLC typed, PLC-5 range read) 2026-10-07 16:07:32 -04:00
Joseph Doherty 5eb018937b fix(ab-transport): PCCC review r1 - UA bit, bit limits, stricter ParseReply, non-const verified flags 2026-10-07 16:07:13 -04:00
Joseph Doherty 6d16765804 fix(ab-transport): harden Task 7 codecs (read status, 32-bit start instance, empty-More, size range) 2026-10-07 16:06:49 -04:00
Joseph Doherty 13d631d856 feat(ab-transport): PCCC Execute-PCCC codec for SLC/MicroLogix typed logical and PLC-5 range read 2026-10-07 16:03:43 -04:00
Joseph Doherty 5f86b370f7 feat(ab-transport): Connection Manager, MSP, Logix read/write, symbol and template service codecs 2026-10-07 16:03:41 -04:00
Joseph Doherty ec1a39ff6e merge: Task 4 — golden libplctag<->ab_server frame captures 2026-10-07 15:59:39 -04:00
Joseph Doherty b085360454 test(ab-transport): non-zero array/BOOL captures, committed capture client, sim quirks
Review round 1 on the golden frames:
- Re-captured logix-read-dint-array16 (element i = ((i+1)<<24)|(i+1)) and
  logix-read-bool (= 1) so neither decode oracle is all zeros. ab_server rejects
  libplctag's own BOOL write with CIP 0x03 (two data bytes for a one-byte BOOL),
  so the BOOL is seeded by a raw UCMM Write Tag (client seed-bool).
- Committed the capture client as scripts/ab-capture/client (outside the slnx,
  CPM off, libplctag 1.5.2 pinned); capture.sh gains a `client` subcommand.
- README documents simulator-only behaviour: zeroed reply sender context, STRING
  typed D0 00 instead of A0 02 + handle, and the BOOL write refusal.
- capture.sh no longer claims UnregisterSession reaches the wire (session ends in RST).
- CaptureFileTests also check the CPF walk ends at the frame end and that every
  reply service is its request service | 0x80.
2026-10-07 15:58:05 -04:00
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
Joseph Doherty de14d354e6 fix(ab-transport): widen libplctag guard regex to catch aliases and static usings 2026-10-07 15:49:24 -04:00
Joseph Doherty 29f456c3ef test(ab-transport): guard libplctag confinement to its adapters (falsified by adding using libplctag to AbCipDriver.cs) 2026-10-07 15:47:52 -04:00
Joseph Doherty f2f1ec2585 feat(ab-transport): CIP route/symbolic path encoders and Logix type codes 2026-10-07 15:38:04 -04:00
Joseph Doherty a0897fda96 feat(ab-transport): project skeleton with AbStatus, CIP reply parsing and the ICipChannel seam 2026-10-07 15:34:34 -04:00