From a0527f9b5a6d177bbbb0de50f034947fffccf929 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 19 Jun 2026 06:56:44 -0400 Subject: [PATCH] fix(sphistorianclient): gRPC auth handshake uses StorageService.ValidateClientCredential MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RemoteGrpc orchestrator drove the SSPI/NTLM token loop through HistoryService.ExchangeKey, which the 2023 R2 contract analysis shows is a separate key-exchange/cert op — not the credential handshake. The server rejected the NTLM Type-1 token at round 0. The Negotiate loop belongs on StorageService.ValidateClientCredential (Handle/InBuff -> Status/OutBuff; field names match the 2020 native contract). Live-verified end-to-end against a 2023 R2 Historian (wonder-sql-vd03): SysTimeSec raw read returns correct timestamped values. --- .../Grpc/HistorianGrpcReadOrchestrator.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/Grpc/HistorianGrpcReadOrchestrator.cs b/ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/Grpc/HistorianGrpcReadOrchestrator.cs index 8fe3f72..d040949 100644 --- a/ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/Grpc/HistorianGrpcReadOrchestrator.cs +++ b/ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/Grpc/HistorianGrpcReadOrchestrator.cs @@ -5,6 +5,7 @@ using ZB.MOM.WW.SPHistorianClient.Models; using ZB.MOM.WW.SPHistorianClient.Wcf; using GrpcHistory = ArchestrA.Grpc.Contract.History; using GrpcRetrieval = ArchestrA.Grpc.Contract.Retrieval; +using GrpcStorage = ArchestrA.Grpc.Contract.Storage; namespace ZB.MOM.WW.SPHistorianClient.Grpc; @@ -16,17 +17,19 @@ namespace ZB.MOM.WW.SPHistorianClient.Grpc; /// /// Operation mapping (2020 WCF → 2023 R2 gRPC): /// Hist.GetInterfaceVersion → HistoryService.GetInterfaceVersion -/// Hist.ValidateClientCredential (loop) → HistoryService.ExchangeKey (loop) +/// Hist.ValidateClientCredential (loop) → StorageService.ValidateClientCredential (loop) /// Hist.OpenConnection2 → HistoryService.OpenConnection /// Retr.StartQuery2 → RetrievalService.StartQuery /// Retr.GetNextQueryResultBuffer2 (loop) → RetrievalService.GetNextQueryResultBuffer (loop) /// Retr.EndQuery2 → RetrievalService.EndQuery /// -/// NOTE: not yet live-verified against a 2023 R2 server. The auth handshake uses -/// HistoryService.ExchangeKey because the gRPC HistoryService dropped ValidateClientCredential -/// (it now lives only on StorageService) and gained ExchangeKey with the identical -/// handle+token→token shape. If a live server rejects this, the handshake op is the first thing -/// to revisit — everything else is the proven 2020 byte protocol. +/// AUTH: the SSPI/Negotiate token loop runs through StorageService.ValidateClientCredential +/// (Handle + InBuff → Status + OutBuff) — per the 2023 R2 contract analysis, that op carries the +/// NTLM/SSPI tokens (the field names inBuff/outBuff match the 2020 native contract), whereas +/// HistoryService.ExchangeKey is a separate key-exchange/cert op (NOT the credential handshake). +/// OpenConnection and the retrieval chain stay on their original services; the server correlates +/// the validated context by the handshake GUID handle. Live-verified 2026-06-19 against a 2023 R2 +/// server (wonder-sql-vd03) — earlier ExchangeKey wiring was rejected at token round 0. /// internal sealed class HistorianGrpcReadOrchestrator { @@ -162,18 +165,19 @@ internal sealed class HistorianGrpcReadOrchestrator { Guid contextKey = Guid.NewGuid(); var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel); + var storageClient = new GrpcStorage.StorageService.StorageServiceClient(connection.Channel); historyClient.GetInterfaceVersion(new GrpcHistory.GetInterfaceVersionRequest(), connection.Metadata, Deadline(), cancellationToken); HistorianNativeHandshake.RunTokenRounds( (handle, wrapped, _) => { - GrpcHistory.ExchangeKeyResponse response = historyClient.ExchangeKey( - new GrpcHistory.ExchangeKeyRequest { StrHandle = handle, BtInput = ByteString.CopyFrom(wrapped) }, + GrpcStorage.ValidateClientCredentialResponse response = storageClient.ValidateClientCredential( + new GrpcStorage.ValidateClientCredentialRequest { Handle = handle, InBuff = ByteString.CopyFrom(wrapped) }, connection.Metadata, Deadline(), cancellationToken); - byte[] serverOutput = response.BtOutput?.ToByteArray() ?? []; + byte[] serverOutput = response.OutBuff?.ToByteArray() ?? []; byte[] error = response.Status?.BtError?.ToByteArray() ?? []; bool success = response.Status?.BSuccess ?? false; return new HistorianNativeHandshake.TokenExchangeResult(success, serverOutput, error);