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:
@@ -45,7 +45,8 @@ internal static class HistorianWcfAuthChainHelper
|
||||
ICommunicationObject historyChannelCo = (ICommunicationObject)historyChannel;
|
||||
try
|
||||
{
|
||||
historyChannel.GetInterfaceVersion(out _);
|
||||
historyChannel.GetInterfaceVersion(out uint historyVersion);
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.History, historyVersion, options);
|
||||
RunValClRounds(historyChannel, contextKey, options, cancellationToken);
|
||||
|
||||
byte[] open2Request = HistorianNativeHandshake.BuildOpenConnection3Request(options.Host, contextKey, connectionMode);
|
||||
|
||||
@@ -187,7 +187,8 @@ internal sealed class HistorianWcfReadOrchestrator
|
||||
ICommunicationObject retrievalChannelCo = (ICommunicationObject)retrievalChannel;
|
||||
try
|
||||
{
|
||||
retrievalChannel.GetInterfaceVersion(out _);
|
||||
retrievalChannel.GetInterfaceVersion(out uint retrievalVersion);
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.Retrieval, retrievalVersion, _options);
|
||||
|
||||
uint isAllowedReturn = retrievalChannel.IsOriginalAllowed(clientHandle, out bool isAllowed);
|
||||
if (isAllowedReturn != 0 || !isAllowed)
|
||||
@@ -289,7 +290,8 @@ internal sealed class HistorianWcfReadOrchestrator
|
||||
ICommunicationObject retrievalChannelCo = (ICommunicationObject)retrievalChannel;
|
||||
try
|
||||
{
|
||||
retrievalChannel.GetInterfaceVersion(out _);
|
||||
retrievalChannel.GetInterfaceVersion(out uint retrievalVersion);
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.Retrieval, retrievalVersion, _options);
|
||||
|
||||
uint isAllowedReturn = retrievalChannel.IsOriginalAllowed(clientHandle, out bool isAllowed);
|
||||
if (isAllowedReturn != 0 || !isAllowed)
|
||||
|
||||
Reference in New Issue
Block a user