From 5efa7677217bd1a189c092f9a01d32fcc450d2a3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 5 May 2026 03:07:13 -0400 Subject: [PATCH] HistorianWcfTagClient: respect options.Transport for cert variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browse/metadata previously hardcoded the Windows transport binding + /Hist-Integrated endpoint regardless of options.Transport. That meant RemoteTcpCertificate clients (notably anything from Linux, where the Windows transport security isn't available in WCF) couldn't use these ops even when other reads worked. Made the binding+endpoint selection conditional: - LocalPipe / RemoteTcpIntegrated: keep existing /Hist-Integrated + Windows transport binding (the legacy Open2-V1 buffer carries its own auth blob and only validates against this transport). - RemoteTcpCertificate: switch to /HistCert + cert binding, propagating options.ServerDnsIdentity. Cert-binding callers also opt out of setting Windows credentials on the factory. Cert-validation override (HistorianWcfClientCredentialsHelper.Configure) now applies on both the history and retrieval factories. Confirmed locally: 178/178 tests pass on Windows. Linux verification still pending — gated tests don't exercise the path. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Wcf/HistorianWcfTagClient.cs | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/AVEVA.Historian.Client/Wcf/HistorianWcfTagClient.cs b/src/AVEVA.Historian.Client/Wcf/HistorianWcfTagClient.cs index e126298..118bb3f 100644 --- a/src/AVEVA.Historian.Client/Wcf/HistorianWcfTagClient.cs +++ b/src/AVEVA.Historian.Client/Wcf/HistorianWcfTagClient.cs @@ -1,6 +1,7 @@ using System.Net; using System.Runtime.CompilerServices; using System.ServiceModel; +using System.ServiceModel.Channels; using AVEVA.Historian.Client.Models; using AVEVA.Historian.Client.Wcf.Contracts; @@ -281,17 +282,38 @@ internal static class HistorianWcfTagClient { ValidateSupportedAuth(options); + // The browse/metadata code uses the legacy Open2-V1 buffer, which carries + // its own auth blob. That buffer is only valid against the WCF transport that + // negotiates Windows security at the channel level (`/Hist-Integrated`) or + // against the cert binding (which trusts the channel-level cert identity). + // For LocalPipe and RemoteTcpIntegrated the original behaviour stays — + // hit the Integrated endpoint with the Windows transport binding. Only + // RemoteTcpCertificate gets the cert binding here, so browse/metadata + // works from a Linux client over the cert transport. + (Binding historyBinding, EndpointAddress historyEndpoint) = options.Transport switch + { + HistorianTransport.RemoteTcpCertificate => ( + HistorianWcfBindingFactory.CreateMdasNetTcpCertificateBinding(options.RequestTimeout), + HistorianWcfBindingFactory.CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.HistoryCertificate, options.ServerDnsIdentity)), + _ => ( + HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(options.RequestTimeout), + HistorianWcfBindingFactory.CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.HistoryIntegrated)), + }; + ChannelFactory? historyFactory = null; IHistoryServiceContract2? historyChannel = null; - ChannelFactory? retrievalFactory = null; - IRetrievalServiceContract2? retrievalChannel = null; + ChannelFactory? retrievalFactory = null; + IRetrievalServiceContract2? retrievalChannel = null; try { - historyFactory = new ChannelFactory( - HistorianWcfBindingFactory.CreateMdasNetTcpWindowsBinding(options.RequestTimeout), - HistorianWcfBindingFactory.CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.HistoryIntegrated)); - historyFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation; - ApplyWindowsCredential(historyFactory, options); + historyFactory = new ChannelFactory(historyBinding, historyEndpoint); + HistorianWcfClientCredentialsHelper.Configure(historyFactory, options); + if (options.Transport != HistorianTransport.RemoteTcpCertificate) + { + // Windows transport-security only applies to the integrated-auth binding. + historyFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation; + ApplyWindowsCredential(historyFactory, options); + } historyFactory.Open(); historyChannel = historyFactory.CreateChannel(); @@ -310,6 +332,7 @@ internal static class HistorianWcfTagClient retrievalFactory = new ChannelFactory( HistorianWcfBindingFactory.CreateMdasNetTcpBinding(options.RequestTimeout), HistorianWcfBindingFactory.CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.Retrieval)); + HistorianWcfClientCredentialsHelper.Configure(retrievalFactory, options); retrievalFactory.Open(); retrievalChannel = retrievalFactory.CreateChannel();