R0.6: fail-closed server interface-version gate at connect
Turns the previously-discarded GetInterfaceVersion result into a connect-time version pin. The native buffers carried in the WCF/MDAS body (and in the 2023 R2 gRPC bytes fields) are framed per native interface version; parsing them against an unexpected version risks silent misinterpretation, so we throw rather than best-effort parse. - HistorianServerVersionGate + HistorianServiceInterface: evidence-based supported versions discovered from a live Historian 2020 server (product 20.0.000) via the wcf-probe command — History=11, Retrieval=4, Transaction=2. Status' GetInterfaceVersion returns 0, so Status is reachability-only. - HistorianClientOptions.VerifyServerInterfaceVersion (default true) — bypass knob for bringing up a server whose reported integers aren't yet captured (e.g. a 2023 R2 gRPC endpoint carrying the same proven 2020 buffers). - Wired into both transports' connect paths: WCF history (auth-chain helper) + retrieval (read orchestrator), and gRPC history + retrieval. - Mismatch throws ProtocolEvidenceMissingException naming reported/expected version and the bypass knob. 10 new unit tests (198 total green). Verified the gate does not regress the proven WCF read path: a live read against the local 2020 server reaches past the gate (Retr=4 matches) — the only live failures are a pre-existing environmental read timeout (OperationCanceledException), identical with and without this change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -163,7 +163,9 @@ internal sealed class HistorianGrpcReadOrchestrator
|
||||
Guid contextKey = Guid.NewGuid();
|
||||
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
||||
|
||||
historyClient.GetInterfaceVersion(new GrpcHistory.GetInterfaceVersionRequest(), connection.Metadata, Deadline(), cancellationToken);
|
||||
GrpcHistory.GetInterfaceVersionResponse historyVersion = historyClient.GetInterfaceVersion(
|
||||
new GrpcHistory.GetInterfaceVersionRequest(), connection.Metadata, Deadline(), cancellationToken);
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.History, historyVersion.UiVersion, _options);
|
||||
|
||||
HistorianNativeHandshake.RunTokenRounds(
|
||||
(handle, wrapped, _) =>
|
||||
@@ -210,7 +212,9 @@ internal sealed class HistorianGrpcReadOrchestrator
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
|
||||
retrievalClient.GetRetrievalInterfaceVersion(new GrpcRetrieval.GetRetrievalInterfaceVersionRequest(), null, Deadline(), cancellationToken);
|
||||
GrpcRetrieval.GetRetrievalInterfaceVersionResponse retrievalVersion = retrievalClient.GetRetrievalInterfaceVersion(
|
||||
new GrpcRetrieval.GetRetrievalInterfaceVersionRequest(), null, Deadline(), cancellationToken);
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.Retrieval, retrievalVersion.UiVersion, _options);
|
||||
|
||||
byte[] requestBuffer = HistorianDataQueryProtocol.SerializeFullHistoryRequest(request);
|
||||
uint queryHandle = StartQuery(retrievalClient, clientHandle, requestBuffer, "raw", cancellationToken);
|
||||
@@ -260,7 +264,9 @@ internal sealed class HistorianGrpcReadOrchestrator
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
|
||||
retrievalClient.GetRetrievalInterfaceVersion(new GrpcRetrieval.GetRetrievalInterfaceVersionRequest(), null, Deadline(), cancellationToken);
|
||||
GrpcRetrieval.GetRetrievalInterfaceVersionResponse retrievalVersion = retrievalClient.GetRetrievalInterfaceVersion(
|
||||
new GrpcRetrieval.GetRetrievalInterfaceVersionRequest(), null, Deadline(), cancellationToken);
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.Retrieval, retrievalVersion.UiVersion, _options);
|
||||
|
||||
HistorianDataQueryRequest request = HistorianWcfReadOrchestrator.BuildAggregateQueryRequest(tag, startUtc, endUtc, mode, interval);
|
||||
byte[] requestBuffer = HistorianDataQueryProtocol.SerializeFullHistoryRequest(request);
|
||||
|
||||
Reference in New Issue
Block a user