using System.Net.Security; using System.Runtime.Versioning; using System.ServiceModel; using System.ServiceModel.Channels; namespace AVEVA.Historian.Client.Wcf; internal static class HistorianWcfBindingFactory { public const string Scheme = "net.tcp"; public const int DefaultPort = 32568; public static Binding CreateMdasNetTcpBinding(TimeSpan timeout, long maxReceivedMessageSize = 64 * 1024 * 1024) { var encoding = new MdasMessageEncodingBindingElement( new BinaryMessageEncodingBindingElement { MessageVersion = MessageVersion.Soap12WSAddressing10 }); var transport = new TcpTransportBindingElement { MaxReceivedMessageSize = maxReceivedMessageSize, TransferMode = TransferMode.Buffered }; return new CustomBinding(encoding, transport) { CloseTimeout = timeout, OpenTimeout = timeout, ReceiveTimeout = timeout, SendTimeout = timeout }; } public static Binding CreateMdasNetTcpWindowsBinding(TimeSpan timeout, long maxReceivedMessageSize = 64 * 1024 * 1024) { NetTcpBinding nativeShape = new(SecurityMode.Transport) { MaxReceivedMessageSize = maxReceivedMessageSize, MaxBufferSize = checked((int)Math.Min(maxReceivedMessageSize, int.MaxValue)) }; nativeShape.ReaderQuotas.MaxArrayLength = nativeShape.MaxBufferSize; nativeShape.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; nativeShape.Security.Transport.ProtectionLevel = ProtectionLevel.None; BindingElementCollection elements = nativeShape.CreateBindingElements(); for (int i = 0; i < elements.Count; i++) { if (elements[i] is MessageEncodingBindingElement encoding) { elements[i] = new MdasMessageEncodingBindingElement(encoding); break; } } return new CustomBinding(elements) { CloseTimeout = timeout, OpenTimeout = timeout, ReceiveTimeout = timeout, SendTimeout = timeout }; } public static Binding CreateMdasNetTcpCertificateBinding(TimeSpan timeout, long maxReceivedMessageSize = 64 * 1024 * 1024) { NetTcpBinding nativeShape = new(SecurityMode.Transport) { MaxReceivedMessageSize = maxReceivedMessageSize, MaxBufferSize = checked((int)Math.Min(maxReceivedMessageSize, int.MaxValue)) }; nativeShape.ReaderQuotas.MaxArrayLength = nativeShape.MaxBufferSize; nativeShape.Security.Transport.ClientCredentialType = TcpClientCredentialType.None; BindingElementCollection elements = nativeShape.CreateBindingElements(); for (int i = 0; i < elements.Count; i++) { if (elements[i] is MessageEncodingBindingElement encoding) { elements[i] = new MdasMessageEncodingBindingElement(encoding); break; } } return new CustomBinding(elements) { CloseTimeout = timeout, OpenTimeout = timeout, ReceiveTimeout = timeout, SendTimeout = timeout }; } [SupportedOSPlatform("windows")] public static Binding CreateMdasNetNamedPipeBinding(TimeSpan timeout, int maxBufferSize = 64 * 1024 * 1024) { NetNamedPipeBinding nativeShape = new() { MaxBufferSize = maxBufferSize, MaxReceivedMessageSize = maxBufferSize }; nativeShape.Security.Mode = NetNamedPipeSecurityMode.None; nativeShape.ReaderQuotas.MaxArrayLength = maxBufferSize; BindingElementCollection elements = nativeShape.CreateBindingElements(); for (int i = 0; i < elements.Count; i++) { if (elements[i] is MessageEncodingBindingElement encoding) { elements[i] = new MdasMessageEncodingBindingElement(encoding); break; } } return new CustomBinding(elements) { CloseTimeout = timeout, OpenTimeout = timeout, ReceiveTimeout = timeout, SendTimeout = timeout }; } [SupportedOSPlatform("windows")] public static (Binding HistoryBinding, EndpointAddress HistoryEndpoint, Binding RetrievalBinding, EndpointAddress RetrievalEndpoint) CreateBindingPair( HistorianClientOptions options) { TimeSpan timeout = options.RequestTimeout; return options.Transport switch { HistorianTransport.LocalPipe => ( CreateMdasNetNamedPipeBinding(timeout), CreatePipeEndpointAddress(options.Host, HistorianWcfServiceNames.History), CreateMdasNetNamedPipeBinding(timeout), CreatePipeEndpointAddress(options.Host, HistorianWcfServiceNames.Retrieval)), HistorianTransport.RemoteTcpIntegrated => ( CreateMdasNetTcpWindowsBinding(timeout), CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.HistoryIntegrated), CreateMdasNetTcpBinding(timeout), CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.Retrieval)), HistorianTransport.RemoteTcpCertificate => ( CreateMdasNetTcpCertificateBinding(timeout), CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.HistoryCertificate), CreateMdasNetTcpBinding(timeout), CreateEndpointAddress(options.Host, options.Port, HistorianWcfServiceNames.Retrieval)), _ => throw new NotSupportedException($"Transport {options.Transport} is not supported.") }; } public static EndpointAddress CreateEndpointAddress(string host, int port, string serviceName) { ArgumentException.ThrowIfNullOrWhiteSpace(host); ArgumentException.ThrowIfNullOrWhiteSpace(serviceName); return new EndpointAddress($"{Scheme}://{host}:{port}/{serviceName}"); } public static EndpointAddress CreatePipeEndpointAddress(string host, string serviceName) { ArgumentException.ThrowIfNullOrWhiteSpace(host); ArgumentException.ThrowIfNullOrWhiteSpace(serviceName); return new EndpointAddress($"net.pipe://{host}/{serviceName}"); } /// /// Returns the appropriate endpoint address for an auxiliary service (Stat, Trx, etc.) /// based on the transport — net.pipe for LocalPipe, net.tcp for the remote variants. /// Use this rather than directly when the calling /// code may run under any transport. /// public static EndpointAddress CreateAuxiliaryEndpointAddress(HistorianClientOptions options, string serviceName) { ArgumentNullException.ThrowIfNull(options); ArgumentException.ThrowIfNullOrWhiteSpace(serviceName); return options.Transport == HistorianTransport.LocalPipe ? CreatePipeEndpointAddress(options.Host, serviceName) : CreateEndpointAddress(options.Host, options.Port, serviceName); } /// /// Returns the appropriate binding for an auxiliary service (Stat, Trx, etc.) given the /// transport. For LocalPipe, same NamedPipe binding as History. For remote TCP variants, /// plain — auxiliaries don't repeat the Windows- /// transport-security upgrade that the History service negotiates; the established session /// authenticates the client already. /// [SupportedOSPlatform("windows")] public static Binding CreateAuxiliaryBinding(HistorianClientOptions options) { ArgumentNullException.ThrowIfNull(options); TimeSpan timeout = options.RequestTimeout; return options.Transport switch { HistorianTransport.LocalPipe => CreateMdasNetNamedPipeBinding(timeout), HistorianTransport.RemoteTcpIntegrated => CreateMdasNetTcpBinding(timeout), HistorianTransport.RemoteTcpCertificate => CreateMdasNetTcpBinding(timeout), _ => throw new NotSupportedException($"Transport {options.Transport} is not supported.") }; } }