feat(wcf): C2 spike + ConnectViaAddress/connmode — WCF transport viable, rows server-gated #1

Merged
dohertj2 merged 16 commits from feat/c2-wcf-event-spike into main 2026-06-26 06:48:03 -04:00
Owner

Summary

The C2 (event-read) investigation: a cross-platform WCF event-read diagnostic spike + the evidence and SDK additions it produced.

Result (live, cross-platform direct to the 2023 R2 historian):

  • The certificate (TLS) transport + NegotiateAuthentication auth reach the 2023 R2 historian cross-platform (integrated/Windows transport security does not tunnel). The 0x501 event connection mode makes CM_EVENT RegisterTags succeed (it fails on 0x402/0x401).
  • Yet StartEventQuery returns a 0-row buffer and long-polls over a window that holds events — registration and window ruled out → the same server-side per-connection row gate as gRPC. Not client-fixable.

Corrects an earlier draft on this branch that said "WCF not served on 2023 R2 (reset at framing)" — a test error vs the reverse SSH tunnel (wrong port + integrated transport). Evidence doc + gating messages rewritten.

Changes

  • tests/.../WcfEventReadSpikeTests.cs — env-gated diagnostic; transport-selectable (integrated|certificate), cross-platform for the cert transport, bounded (per-call timeout + overall budget with a phase-diagnostic dump), version-gate bypassable. Sanitized output only.
  • docs/reverse-engineering/wcf-event-read-spike-results.md — the corrected, sanitized live result.
  • HistorianClientOptions.ConnectViaAddress — WCF Via (connect to a tunnel/proxy while addressing the SOAP To the real endpoint), via ChannelFactory.CreateChannel(address, via) at the event/read sites.
  • HistorianClientOptions.EventReadConnectionModeOverride — diagnostic override of the event-read OpenConnection mode (the 0x501 finding).
  • Grpc/HistorianGrpcEventOrchestrator.cs + Wcf/HistorianWcfEventOrchestrator.cs — corrected gating messages (server-gated over both transports). String/comment + the two new options; no default-behavior change.

Test plan

  • Offline suite green modulo the 2 pre-existing macOS WCF NetNamedPipe fails (CreateMdasNetNamedPipeBinding_* — not in scope): 352 passed / 0 new fails with the historian env unset.
  • 0 warnings.
  • Live spike run over VPN; no secrets/host/identity in committed artifacts.

Pairs with HistorianGateway PR feat/c2-wcf-event-spike.

https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii

## Summary The C2 (event-read) investigation: a cross-platform WCF event-read diagnostic spike + the evidence and SDK additions it produced. **Result (live, cross-platform direct to the 2023 R2 historian):** - The **certificate (TLS)** transport + `NegotiateAuthentication` auth reach the 2023 R2 historian **cross-platform** (integrated/Windows transport security does not tunnel). The **`0x501` event connection mode** makes CM_EVENT `RegisterTags` succeed (it fails on `0x402`/`0x401`). - Yet `StartEventQuery` returns a **0-row** buffer and long-polls over a window that holds events — registration and window ruled out → the **same server-side per-connection row gate** as gRPC. Not client-fixable. > Corrects an earlier draft on this branch that said "WCF not served on 2023 R2 (reset at framing)" — a test error vs the reverse SSH tunnel (wrong port + integrated transport). Evidence doc + gating messages rewritten. ## Changes - `tests/.../WcfEventReadSpikeTests.cs` — env-gated diagnostic; transport-selectable (integrated|**certificate**), **cross-platform** for the cert transport, bounded (per-call timeout + overall budget with a phase-diagnostic dump), version-gate bypassable. Sanitized output only. - `docs/reverse-engineering/wcf-event-read-spike-results.md` — the corrected, sanitized live result. - **`HistorianClientOptions.ConnectViaAddress`** — WCF `Via` (connect to a tunnel/proxy while addressing the SOAP `To` the real endpoint), via `ChannelFactory.CreateChannel(address, via)` at the event/read sites. - **`HistorianClientOptions.EventReadConnectionModeOverride`** — diagnostic override of the event-read OpenConnection mode (the `0x501` finding). - `Grpc/HistorianGrpcEventOrchestrator.cs` + `Wcf/HistorianWcfEventOrchestrator.cs` — corrected gating messages (server-gated over both transports). String/comment + the two new options; no default-behavior change. ## Test plan - [x] Offline suite green modulo the 2 pre-existing macOS WCF NetNamedPipe fails (`CreateMdasNetNamedPipeBinding_*` — not in scope): 352 passed / 0 new fails with the historian env unset. - [x] 0 warnings. - [x] Live spike run over VPN; no secrets/host/identity in committed artifacts. Pairs with HistorianGateway PR `feat/c2-wcf-event-spike`. https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
dohertj2 added 16 commits 2026-06-26 04:47:07 -04:00
GetAnalogDataTypeCode gains UInt1=0x08/Int8=0x19/UInt8=0x39 (read-side
MapDataType already maps these); EncodeNativeValue gains 1-byte UInt1 +
8-byte LE Int8/UInt8 (mirrors the captured Double value layout). Value API
stays double; 2^53 exact-magnitude ceiling documented. Golden tests + live
round-trip follow.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
New InlineData rows derived from the captured Double baselines (type-code /
value bytes swapped). Negative-gate tests retained for still-gated types.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Env-gated live evidence test reads History/Retrieval/Transaction/Status
GetInterfaceVersion over gRPC; integers recorded in grpc-interface-versions.md.
Stale not-yet-captured comment fixed; gate XML-doc notes live confirmation.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Live evidence: EnsureTags(UInt1) returns success but the server stores a
degenerate tag (descriptor type byte 0x00, no GUID/name), so GetTagInfo
truncates and writes fail. Not client-fixable on the analog path. Int8/UInt8
stay GREEN (live-proven). UInt1 reverts to ProtocolEvidenceMissingException
(fail-closed); golden rows removed; negative-gate tests added. Removed the
one-off capture diagnostic.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
EnsureTagAsync + AddHistoricalValues now cover Int8/UInt8 (codes 0x19/0x39, native
LE int64/uint64 value). UInt1 documented as not-supported: the server stores a
degenerate analog tag (GetTagInfo stub, type byte 0x00).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Removes the stale UInt1 → UInt8(1) entry from the EncodeNativeValue layout
table (UInt1 is re-gated; the prose already said so). Int8/UInt8 layout note
updated from "pending" to live-proven.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Int8/UInt8 analog tag-create + historical-value write encoders un-gated and
live-proven; golden-pinned. UInt1 attempted but re-gated — the historian stores a
degenerate UInt1 analog tag. 2023 R2 gRPC interface-version integers captured (C3a).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Drives HistorianWcfEventOrchestrator over RemoteTcpIntegrated and dumps the
native chain (UpdC3/RTag2/EnsT2 return codes, result-buffer length, row count)
to settle whether WCF event reads return rows on an event-bearing 2023 R2 box.
Windows-only, gated by HISTORIAN_WCF_EVENT_HOST, never fails the suite, inert
off Windows. Sanitized: counts + return codes + buffer lengths + sha256 only.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
WCF net.tcp (RemoteTcpIntegrated) against the live 2023 R2 historian is reset
at the socket-write/framing layer before any auth — both the event spike and a
basic Probe/ReadRaw throw the identical CommunicationException/SocketException
("forcibly closed by the remote host"). The 2023 R2 box does not serve the
legacy WCF transport; C2's "route via WCF" unblock is moot on this server class.
Sanitized: counts + native return codes + buffer lengths only.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
The gRPC ReadEvents throws no longer advise "use the WCF transport for event
reads": that path is moot on 2023 R2 (net.tcp is reset at the framing layer,
live-disproven 2026-06-25). Messages now state event reads are auth-solved but
server-gated over gRPC and have no WCF fallback on 2023 R2, citing the two
evidence docs. WCF orchestrator remarks scoped to legacy 2020 historians; row
layout noted as decoded. String/comment only; throw behavior unchanged.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
The first live run used the wrong port (32568 direct vs the 42568 WCF tunnel) and
hardcoded RemoteTcpIntegrated; via the tunnel the error advanced from socket-RST
to ProtocolException (binding/security mismatch). Add HISTORIAN_WCF_EVENT_TRANSPORT
(certificate), _DNSID, _ALLOW_UNTRUSTED, and an opt-in _VERBOSE for live binding
diagnosis. Default output stays sanitized; still Windows-only, never fails the suite.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
When the historian is reached through a port-forward whose local port differs
from the server's real service port, WCF's server-side AddressFilter rejects the
message (To = tunnel port != server port). ConnectViaAddress lets the channel
connect to the tunnel while addressing the SOAP To the real Host/Port endpoint.
Applied in HistorianWcfClientCredentialsHelper.Configure (the critical event
factories already call it). The C2 spike reads HISTORIAN_WCF_EVENT_VIA.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
ClientViaBehavior is a .NET Framework type not present in the System.ServiceModel
client libraries. Use the portable ChannelFactory.CreateChannel(EndpointAddress, Uri)
overload instead, via a CreateChannel helper applied at the history-open and
retrieval-query sites (the critical event path). Fixes the build break in 954b9cc.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Live investigation (direct from a VPN host to the 2023 R2 historian's real WCF
port) showed the certificate transport + NegotiateAuthentication auth work
cross-platform, and that the event-read chain needs the 0x501 event connection
mode for CM_EVENT RegisterTags to succeed (0x402/0x401 fail). Even with
registration succeeding over a window that has events, StartEventQuery returns a
0-row header and long-polls — the same server-side per-connection row gate proven
for gRPC. Adds: EventReadConnectionModeOverride (diagnostic), and spike knobs —
cross-platform cert gate, version-check bypass, per-call timeout, overall budget
with phase-diagnostic dump, connection-mode override.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Overturns the earlier wrong "WCF not served on 2023 R2" conclusion (that was a
test error: wrong port/transport for the reverse tunnel). Corrected: the cert
(TLS) transport + NegotiateAuthentication auth reach the 2023 R2 historian
cross-platform; the 0x501 event connection mode makes CM_EVENT RegisterTags
succeed; yet StartEventQuery returns a 0-row buffer + long-polls over a window
that has events. Registration and window ruled out -> the same server-side
per-connection row gate as gRPC. Event reads stay server-gated over BOTH
transports; not client-fixable. Evidence doc rewritten; gRPC + WCF orchestrator
gating messages corrected.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
dohertj2 merged commit f0a1b04b34 into main 2026-06-26 06:48:03 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/histsdk#1