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
5.4 KiB
WCF event-read spike — live result (2026-06-25/26): transport+auth viable, row-retrieval server-gated
Settles the open question behind C2 ("event reads over gRPC are gated; the only listed unblock is
route event reads via WCF"). The gRPC event-read path is a proven server-side dead-end
(grpc-event-query-capture.md: auth fully solved, every client-controllable layer byte-matched to the
stock client, yet the server scopes 0 rows to our connection). This spike resolved the WCF leg.
Correction to an earlier draft of this doc. A first pass concluded "the 2023 R2 historian does not serve the legacy WCF transport (connection reset at framing)." That was a test error, not a server fact. It connected to the historian's real WCF port
32568directly and used the Windows-integrated transport. In this environment the historian is reached through a reverse SSH tunnel (local42568→ historian32568), and integrated/Kerberos auth does not work through that tunnel. The socket-RST was the tunnel/transport mismatch, not an absent listener. Corrected below.
What was run
A Windows-only-by-default, env-gated diagnostic (tests/AVEVA.Historian.Client.Tests/WcfEventReadSpikeTests.cs)
drives HistorianWcfEventOrchestrator.ReadEventsAsync directly. The decisive run was cross-platform,
direct (no tunnel): from the VPN-holding host straight to the historian's real WCF endpoint
net.tcp://<historian>:32568/HistCert, using the certificate transport (RemoteTcpCertificate,
TLS, AllowUntrustedServerCertificate) and NegotiateAuthentication (cross-platform, explicit domain
credentials). The SDK's interface-version gate was bypassed (VerifyServerInterfaceVersion=false) —
the 2023 R2 WCF History interface reports version 13 (this SDK's serializers target 11/12).
Result — transport+auth viable; row-retrieval server-gated (sanitized)
Progression of the live errors as the addressing/transport was corrected:
| attempt | error |
|---|---|
direct :32568, integrated |
SocketException "forcibly closed" (wrong port + transport for the tunnel) |
tunnel :42568, integrated |
ProtocolException at the security UpgradeResponse (integrated can't negotiate through the tunnel) |
tunnel :42568, certificate |
reached the WCF dispatcher → AddressFilter mismatch (tunnel rewrites the port) |
direct :32568, certificate, cross-platform |
past auth → ProtocolEvidenceMissingException: History interface version 13 |
+ VerifyServerInterfaceVersion=false |
full chain runs; query returns a 10-byte 0-row header, then GetNext long-polls |
Connection-mode experiment (certificate transport, direct, version-bypassed, a 1-day window that holds events), comparing the native OpenConnection mode used for the event-read chain:
| connMode | RegisterTags (RTag2) | EnsureTags (EnsT2) | result buffer | events |
|---|---|---|---|---|
0x501 (event) |
0 — success | 1 (benign-false, as in the 2020 flow) | 10 bytes (0-row header) | 0 |
0x401 (write) |
1 (fail) | 1 | 10 bytes | 0 |
0x402 (read-only, default) |
1 (fail) | 1 | 10 bytes | 0 |
Conclusion
- WCF transport + auth ARE viable on 2023 R2. The certificate (TLS) transport negotiates and the
NegotiateAuthenticationapp-level handshake authenticates — cross-platform (proven from a non-Windows VPN host). The earlier "WCF not served" conclusion was wrong. (Integrated/Windows transport security is not usable through the reverse tunnel —net.tcpKerberos does not tunnel.) - The event-read chain needs the
0x501event connection mode. With it, CM_EVENTRegisterTagssucceeds (it fails on0x402/0x401).EnsureTagsreturns false, but that is documented as benign in the 2020 flow that did return rows. - Row retrieval is server-gated — same as gRPC. Even with auth solved and
RegisterTagssucceeding, over a window that holds events,StartEventQuerysucceeds butGetNextEventQueryResultBufferreturns a 0-row header (10 bytes) and long-polls. Registration and window are ruled out as the cause; the server simply does not scope event rows to a managed connection. This is the identical server-side per-connection retrieval working-set gate proven for gRPC ingrpc-event-query-capture.md.
Therefore event reads do not return rows on the 2023 R2 historian over either transport — gRPC (retrieval-server-gated) and WCF (transport+auth work, but the same server-side row gate). The only remaining theoretical unblock is server-side (AVEVA exposing event-row retrieval to a managed connection) — not client-fixable. C2 stays closed won't-fix, for this (corrected) reason.
SDK additions from this investigation (retained, build-clean, golden where applicable)
HistorianClientOptions.ConnectViaAddress— WCFVia(connect to a tunnel/proxy while addressing the SOAPTothe real endpoint), so a port-forward whose local port differs from the server's real port satisfies the server-side WCF AddressFilter.HistorianClientOptions.EventReadConnectionModeOverride— diagnostic override of the event-read OpenConnection mode (the0x501finding above).- The C2 spike is now transport-selectable (integrated|certificate), cross-platform for the cert transport, bounded (per-call timeout + overall budget with a phase-diagnostic dump), and version-gate bypassable. Output stays sanitized (counts, native return codes, buffer lengths, sha256).