feat(wcf): C2 spike + ConnectViaAddress/connmode — WCF transport viable, rows server-gated #1
@@ -34,13 +34,16 @@ namespace AVEVA.Historian.Client.Grpc;
|
||||
/// (the read path proved the front-door session is sufficient over gRPC).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Live status (2026-06-22):</b> the chain runs end-to-end and <c>StartEventQuery</c> succeeds, but
|
||||
/// <c>GetNextEventQueryResultBuffer</c> <b>long-polls</b> when the query has no rows — it blocks to the
|
||||
/// call deadline instead of returning the synchronous 5-byte code-85 terminal the 2020 WCF op returns.
|
||||
/// A poll-deadline expiry is therefore treated as the no-data terminal (see the loop). The idle dev box
|
||||
/// holds no events, so <b>row-level retrieval is not yet live-verified</b>; verifying parsed rows over
|
||||
/// gRPC awaits an event-bearing 2023 R2 server. This is tooled + completes cleanly, NOT proven to
|
||||
/// return rows.
|
||||
/// <b>Live status — server-gated (settled 2026-06-25):</b> the chain runs end-to-end and
|
||||
/// <c>StartEventQuery</c> succeeds, but <c>GetNextEventQueryResultBuffer</c> <b>long-polls</b> to the
|
||||
/// no-data terminal (instead of the synchronous 5-byte code-85 terminal the 2020 WCF op returns); a
|
||||
/// poll-deadline expiry is treated as that terminal (see the loop). This is <b>not</b> an empty-box
|
||||
/// artifact: the live 2023 R2 server holds tens of thousands of events yet scopes <b>0 rows</b> to a
|
||||
/// managed connection. Every client-controllable layer was byte-matched to the stock client that returns
|
||||
/// rows (see <c>docs/reverse-engineering/grpc-event-query-capture.md</c>) — the gate is a server-internal
|
||||
/// per-connection retrieval working-set, <b>not client-fixable</b>. The legacy WCF transport is not a
|
||||
/// fallback on 2023 R2 (<c>docs/reverse-engineering/wcf-event-read-spike-results.md</c>). Tooled +
|
||||
/// completes cleanly, but proven NOT to return rows over a managed connection.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed class HistorianGrpcEventOrchestrator
|
||||
@@ -101,9 +104,11 @@ internal sealed class HistorianGrpcEventOrchestrator
|
||||
{
|
||||
throw new ProtocolEvidenceMissingException(
|
||||
$"ReadEvents over gRPC did not return rows within {OverallBudget.TotalSeconds:0}s: StartEventQuery " +
|
||||
"succeeds but the CM_EVENT registration replay stalls and GetNextEventQueryResultBuffer long-polls " +
|
||||
"(no synchronous code-85 terminal over gRPC). Row-level retrieval is not yet verified over gRPC " +
|
||||
"(the dev box holds no events) — use the WCF transport for event reads.");
|
||||
"succeeds but GetNextEventQueryResultBuffer long-polls to the no-data terminal. Event-row retrieval " +
|
||||
"over gRPC is auth-solved but server-gated — the 2023 R2 server scopes 0 rows to a managed connection " +
|
||||
"(see docs/reverse-engineering/grpc-event-query-capture.md). The legacy WCF transport is NOT a fallback " +
|
||||
"on 2023 R2 (live-disproven 2026-06-25: net.tcp is reset at the framing layer — see " +
|
||||
"docs/reverse-engineering/wcf-event-read-spike-results.md), so there is no event-read path on a 2023 R2 historian.");
|
||||
}
|
||||
|
||||
foreach (HistorianEvent evt in events)
|
||||
@@ -169,16 +174,19 @@ internal sealed class HistorianGrpcEventOrchestrator
|
||||
// reaches the no-data terminal with ZERO rows (the gRPC server long-polls GetNext rather than
|
||||
// returning the WCF code-85 terminal), we cannot distinguish "genuinely no events in range"
|
||||
// from "the CM_EVENT registration replay didn't fully land over gRPC" — so we refuse to return
|
||||
// a possibly-false empty list and surface the unverified state instead. An event-bearing 2023 R2
|
||||
// server will return rows here and exercise the parse path; flip this once that is captured.
|
||||
// a possibly-false empty list and surface the gated state instead. Proven server-gated: the live
|
||||
// 2023 R2 server holds tens of thousands of events yet scopes 0 to a managed gRPC connection
|
||||
// (grpc-event-query-capture.md); WCF is not a 2023 R2 fallback (wcf-event-read-spike-results.md).
|
||||
if (events.Count == 0)
|
||||
{
|
||||
throw new ProtocolEvidenceMissingException(
|
||||
"ReadEvents over gRPC: the chain completes and StartEventQuery succeeds, but " +
|
||||
"GetNextEventQueryResultBuffer returns no rows (it long-polls to the no-data terminal " +
|
||||
$"after the CM_EVENT registration replay; last={LastErrorBufferDescription}). Row-level " +
|
||||
"retrieval is not yet verified over gRPC (the dev box holds no events) — use the WCF " +
|
||||
"transport for event reads until a capture against an event-bearing 2023 R2 server confirms it.");
|
||||
$"after the CM_EVENT registration replay; last={LastErrorBufferDescription}). Event-row retrieval " +
|
||||
"over gRPC is auth-solved but server-gated — the 2023 R2 server scopes 0 rows to a managed connection " +
|
||||
"(see docs/reverse-engineering/grpc-event-query-capture.md). The legacy WCF transport is NOT a fallback " +
|
||||
"on 2023 R2 (live-disproven 2026-06-25: net.tcp is reset at the framing layer — see " +
|
||||
"docs/reverse-engineering/wcf-event-read-spike-results.md).");
|
||||
}
|
||||
|
||||
return events;
|
||||
|
||||
@@ -9,9 +9,13 @@ namespace AVEVA.Historian.Client.Wcf;
|
||||
|
||||
/// <remarks>
|
||||
/// Mirrors HistorianWcfReadOrchestrator but targets IRetrievalServiceContract4 for the event flow.
|
||||
/// Event row buffer layout is undecoded as of this pass — when StartEventQuery succeeds, this
|
||||
/// orchestrator returns an empty enumeration but logs the row-buffer length via the
|
||||
/// <see cref="LastResultBufferLength"/> diagnostic so a follow-up capture can decode the wire shape.
|
||||
/// Applies to <b>legacy 2020-era WCF (net.tcp) historians only</b>. The event row-buffer layout is now
|
||||
/// decoded (<see cref="HistorianEventRowProtocol"/>; verified against real captured rows). Note: a
|
||||
/// <b>2023 R2</b> historian does NOT serve this WCF transport at all — net.tcp is reset at the framing
|
||||
/// layer before any auth (live-disproven 2026-06-25; see
|
||||
/// <c>docs/reverse-engineering/wcf-event-read-spike-results.md</c>), so this orchestrator is not a
|
||||
/// fallback for 2023 R2 deployments. The native return codes 76/85 noted below were 2020-historian
|
||||
/// observations.
|
||||
/// </remarks>
|
||||
internal sealed class HistorianWcfEventOrchestrator
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user