gRPC 2023 R2: fix auth handshake op routing + accept History v12
First live-verified gRPC read against a real 2023 R2 Historian. The handshake previously failed at round 0 (cred-independent) because the SSPI/Negotiate token loop was routed to HistoryService.ExchangeKey. ExchangeKey is a separate key-exchange/cert-path op, not the Negotiate loop — the token loop belongs on StorageService.ValidateClientCredential, which kept the 2020 inBuff/outBuff token framing the SDK's WrapValidateClientCredentialToken/TryRead helpers already build. Captured + diffed against the recovered 2023 R2 protobuf contract and the decompiled stock client; routing the loop to ValidateClientCredential completes the full chain (ValidateClientCredential x N -> OpenConnection -> StartQuery -> GetNextQueryResultBuffer) and returns rows. - HistorianGrpcReadOrchestrator: token loop now calls StorageService.ValidateClientCredential(Handle, InBuff); corrected the op-map doc comment (was asserting the wrong ExchangeKey mapping). - HistorianServerVersionGate: accept History interface version 12 alongside 11. Live server reports History=12, Retrieval=4, Storage=4; the buffers are byte-identical (a live read returns rows), so 12 is buffer-compatible. Retrieval stays pinned at 4 (matches). New AcceptedVersions() supports multi-version gates. - New HistorianGrpcHandshakeRoutingTests: IL-level structural guardrail that disassembles the orchestrator (incl. lambda closures) and asserts the handshake invokes ValidateClientCredential and never ExchangeKey — fails if the regression returns. - Updated gate tests + CLAUDE.md gRPC op-map. 240 unit tests pass on the full stack; 210 on this branch's base. The byte payloads remain the proven 2020 protocol. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -29,6 +29,20 @@ public sealed class HistorianServerVersionGateTests
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.Transaction, HistorianServerVersionGate.TransactionInterfaceVersion, Options());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_History_AcceptsBoth2020And2023R2Versions()
|
||||
{
|
||||
// History 11 (2020 WCF) and 12 (2023 R2 gRPC) are both buffer-compatible — a live gRPC
|
||||
// read against a real 2023 R2 server (interface version 12) returns rows. Both must pass.
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.History, 11u, Options());
|
||||
HistorianServerVersionGate.Validate(HistorianServiceInterface.History, HistorianServerVersionGate.HistoryInterfaceVersionGrpc2023R2, Options());
|
||||
Assert.Equal(12u, HistorianServerVersionGate.HistoryInterfaceVersionGrpc2023R2);
|
||||
Assert.Contains(11u, HistorianServerVersionGate.AcceptedVersions(HistorianServiceInterface.History));
|
||||
Assert.Contains(12u, HistorianServerVersionGate.AcceptedVersions(HistorianServiceInterface.History));
|
||||
// Retrieval reported 4 on the live 2023 R2 server — matches 2020, so it is NOT widened.
|
||||
Assert.DoesNotContain(5u, HistorianServerVersionGate.AcceptedVersions(HistorianServiceInterface.Retrieval));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MismatchedVersion_ThrowsProtocolEvidenceMissing()
|
||||
{
|
||||
@@ -36,7 +50,7 @@ public sealed class HistorianServerVersionGateTests
|
||||
(HistorianServiceInterface Service, uint Version)[] cases =
|
||||
[
|
||||
(HistorianServiceInterface.History, 10u),
|
||||
(HistorianServiceInterface.History, 12u),
|
||||
(HistorianServiceInterface.History, 13u),
|
||||
(HistorianServiceInterface.Retrieval, 3u),
|
||||
(HistorianServiceInterface.Retrieval, 5u),
|
||||
(HistorianServiceInterface.Transaction, 1u),
|
||||
|
||||
Reference in New Issue
Block a user