fix(sphistorianclient): gRPC auth handshake uses StorageService.ValidateClientCredential
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.
This commit is contained in:
+13
-9
@@ -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.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user