/* ======================================================================== * Copyright (c) 2005-2013 The OPC Foundation, Inc. All rights reserved. * * OPC Reciprocal Community License ("RCL") Version 1.00 * * Unless explicitly acquired and licensed from Licensor under another * license, the contents of this file are subject to the Reciprocal * Community License ("RCL") Version 1.00, or subsequent versions * as allowed by the RCL, and You may not copy or use this file in either * source code or executable form, except in compliance with the terms and * conditions of the RCL. * * All software distributed under the RCL is provided strictly on an * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, * AND LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RCL for specific * language governing rights and limitations under the RCL. * * The complete license agreement can be found here: * http://opcfoundation.org/License/RCL/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Xml; using System.ServiceModel; using System.Runtime.Serialization; using Opc.Ua.Bindings; using System.ServiceModel.Description; using System.ServiceModel.Channels; using System.Security.Cryptography.X509Certificates; using System.ServiceModel.Security; using System.Net; namespace Opc.Ua { /// /// A base class for WCF channel objects used access UA interfaces /// public abstract class WcfChannelBase : IChannelBase, ITransportChannel { #region Constructors /// /// Initializes the object with the specified binding and endpoint address. /// public WcfChannelBase() { m_messageContext = null; m_settings = null; m_wcfBypassChannel = null; } #endregion #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) { // nothing to do. } #endregion #region IChannelBase Members /// /// Returns true if the channel uses the UA Binary encoding. /// public bool UseBinaryEncoding { get { if (m_settings != null && m_settings.Configuration != null) { return m_settings.Configuration.UseBinaryEncoding; } return false; } } /// /// Gets the binary encoding support. /// public BinaryEncodingSupport BinaryEncodingSupport { get { if (m_settings != null && m_settings.Configuration != null) { if (m_settings != null && m_settings.Configuration.UseBinaryEncoding) { return BinaryEncodingSupport.Required; } return BinaryEncodingSupport.None; } return BinaryEncodingSupport.Optional; } } /// /// Opens the channel with the server. /// public void OpenChannel() { ICommunicationObject channel = m_channel as ICommunicationObject; if (channel != null && channel.State == CommunicationState.Closed) { channel.Open(); } } /// /// Closes the channel with the server. /// public void CloseChannel() { ICommunicationObject channel = m_channel as ICommunicationObject; if (channel != null && channel.State == CommunicationState.Opened) { channel.Abort(); } } /// /// Schedules an outgoing request. /// /// The request. public void ScheduleOutgoingRequest(IChannelOutgoingRequest request) { #if MANAGE_CHANNEL_THREADS System.Threading.Thread thread = new System.Threading.Thread(OnSendRequest); thread.Start(request); #else throw new NotImplementedException(); #endif } #endregion #region ITransportChannel Members /// /// A masking indicating which features are implemented. /// public TransportChannelFeatures SupportedFeatures { get { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.SupportedFeatures; } return TransportChannelFeatures.Reconnect | TransportChannelFeatures.BeginSendRequest | TransportChannelFeatures.BeginClose; } } /// /// Gets the description for the endpoint used by the channel. /// public EndpointDescription EndpointDescription { get { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.EndpointDescription; } if (m_settings != null) { return m_settings.Description; } return null; } } /// /// Gets the configuration for the channel. /// public EndpointConfiguration EndpointConfiguration { get { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.EndpointConfiguration; } if (m_settings != null) { return m_settings.Configuration; } return null; } } /// /// Gets the context used when serializing messages exchanged via the channel. /// public ServiceMessageContext MessageContext { get { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.MessageContext; } return m_messageContext; } } /// /// Gets or sets the default timeout for requests send via the channel. /// public int OperationTimeout { get { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.OperationTimeout; } return m_operationTimeout; } set { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.OperationTimeout = value; return; } m_operationTimeout = value; } } /// /// Initializes a secure channel with the endpoint identified by the URL. /// /// The URL for the endpoint. /// The settings to use when creating the channel. /// Thrown if any communication error occurs. public void Initialize( Uri url, TransportChannelSettings settings) { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.Initialize(url, settings); return; } throw new NotSupportedException("WCF channels must be configured when they are constructed."); } /// /// Opens a secure channel with the endpoint identified by the URL. /// public void Open() { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.Open(); return; } } /// /// Begins an asynchronous operation to open a secure channel with the endpoint identified by the URL. /// public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData) { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.BeginOpen(callback, callbackData); } throw new NotSupportedException("WCF channels must be configured when they are constructed."); } /// /// Completes an asynchronous operation to open a communication object. /// public void EndOpen(IAsyncResult result) { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.EndOpen(result); return; } throw new NotSupportedException("WCF channels must be configured when they are constructed."); } /// /// Closes any existing secure channel and opens a new one. /// /// Thrown if any communication error occurs. /// /// Calling this method will cause outstanding requests over the current secure channel to fail. /// public abstract void Reconnect(); /// /// Begins an asynchronous operation to close the existing secure channel and open a new one. /// public IAsyncResult BeginReconnect(AsyncCallback callback, object callbackData) { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.BeginReconnect(callback, callbackData); } throw new NotSupportedException("WCF channels cannot be reconnected."); } /// /// Completes an asynchronous operation to close the existing secure channel and open a new one. /// public void EndReconnect(IAsyncResult result) { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.EndReconnect(result); return; } throw new NotSupportedException("WCF channels cannot be reconnected."); } /// /// Closes any existing secure channel. /// public void Close() { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.Close(); return; } CloseChannel(); } /// /// Begins an asynchronous operation to close the secure channel. /// public IAsyncResult BeginClose(AsyncCallback callback, object callbackData) { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.BeginClose(callback, callbackData); } AsyncResultBase result = new AsyncResultBase(callback, callbackData, 0); result.OperationCompleted(); return result; } /// /// Completes an asynchronous operation to close a communication object. /// public void EndClose(IAsyncResult result) { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.EndClose(result); return; } AsyncResultBase.WaitForComplete(result); CloseChannel(); } /// /// Sends a request over the secure channel. /// public IServiceResponse SendRequest(IServiceRequest request) { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.SendRequest(request); } byte[] requestMessage = BinaryEncoder.EncodeMessage(request, m_messageContext); InvokeServiceResponseMessage responseMessage = InvokeService(new InvokeServiceMessage(requestMessage)); return (IServiceResponse)BinaryDecoder.DecodeMessage(responseMessage.InvokeServiceResponse, null, m_messageContext); } /// /// Begins an asynchronous operation to send a request over the secure channel. /// public IAsyncResult BeginSendRequest(IServiceRequest request, AsyncCallback callback, object callbackData) { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.BeginSendRequest(request, callback, callbackData); } #if MANAGE_CHANNEL_THREADS SendRequestAsyncResult asyncResult = new SendRequestAsyncResult(this, callback, callbackData, 0); asyncResult.BeginSendRequest(SendRequest, request); return asyncResult; #else byte[] requestMessage = BinaryEncoder.EncodeMessage(request, m_messageContext); return BeginInvokeService(new InvokeServiceMessage(requestMessage), callback, callbackData); #endif } /// /// Completes an asynchronous operation to send a request over the secure channel. /// public IServiceResponse EndSendRequest(IAsyncResult result) { if (m_wcfBypassChannel != null) { return m_wcfBypassChannel.EndSendRequest(result); } #if MANAGE_CHANNEL_THREADS return SendRequestAsyncResult.WaitForComplete(result); #else InvokeServiceResponseMessage responseMessage = EndInvokeService(result); return (IServiceResponse)BinaryDecoder.DecodeMessage(responseMessage.InvokeServiceResponse, null, m_messageContext); #endif } /// /// The client side implementation of the InvokeService service contract. /// public abstract InvokeServiceResponseMessage InvokeService(InvokeServiceMessage request); /// /// The client side implementation of the BeginInvokeService service contract. /// public abstract IAsyncResult BeginInvokeService(InvokeServiceMessage request, AsyncCallback callback, object asyncState); /// /// The client side implementation of the EndInvokeService service contract. /// public abstract InvokeServiceResponseMessage EndInvokeService(IAsyncResult result); #endregion #if MANAGE_CHANNEL_THREADS #region SendRequestAsyncResult Class /// /// An AsyncResult object when handling an asynchronous request. /// protected class SendRequestAsyncResult : AsyncResultBase, IChannelOutgoingRequest { #region Constructors /// /// Initializes a new instance of the class. /// /// The channel being used. /// The callback to use when the operation completes. /// The callback data. /// The timeout in milliseconds public SendRequestAsyncResult( IChannelBase channel, AsyncCallback callback, object callbackData, int timeout) : base(callback, callbackData, timeout) { m_channel = channel; } #endregion #region IChannelOutgoingRequest Members /// /// Gets the request. /// /// The request. public IServiceRequest Request { get { return m_request; } } /// /// Gets the handler used to send the request. /// /// The send request handler. public ChannelSendRequestEventHandler Handler { get { return m_handler; } } /// /// Used to call the default synchronous handler. /// /// /// This method may block the current thread so the caller must not call in the /// thread that calls IServerBase.ScheduleIncomingRequest(). /// This method always traps any exceptions and reports them to the client as a fault. /// public void CallSynchronously() { OnSendRequest(null); } /// /// Used to indicate that the asynchronous operation has completed. /// /// The response. May be null if an error is provided. /// public void OperationCompleted(IServiceResponse response, ServiceResult error) { // save response and/or error. m_error = null; m_response = response; if (ServiceResult.IsBad(error)) { m_error = new ServiceResultException(error); m_response = null; } // operation completed. OperationCompleted(); } #endregion #region Public Members /// /// Begins processing an incoming request. /// /// The method which sends the request. /// The request. /// The result object that is used to call the EndSendRequest method. public IAsyncResult BeginSendRequest( ChannelSendRequestEventHandler handler, IServiceRequest request) { m_handler = handler; m_request = request; try { // queue request. m_channel.ScheduleOutgoingRequest(this); } catch (Exception e) { m_error = e; m_response = null; // operation completed. OperationCompleted(); } return this; } /// /// Checks for a valid IAsyncResult object and waits for the operation to complete. /// /// The IAsyncResult object for the operation. /// The response. public static new IServiceResponse WaitForComplete(IAsyncResult ar) { SendRequestAsyncResult result = ar as SendRequestAsyncResult; if (result == null) { throw new ArgumentException("End called with an invalid IAsyncResult object.", "ar"); } if (result.m_response == null && result.m_error == null) { if (!result.WaitForComplete()) { throw new TimeoutException(); } } if (result.m_error != null) { throw new ServiceResultException(result.m_error, StatusCodes.BadInternalError); } return result.m_response; } /// /// Checks for a valid IAsyncResult object and returns the original request object. /// /// The IAsyncResult object for the operation. /// The request object if available; otherwise null. public static IServiceRequest GetRequest(IAsyncResult ar) { SendRequestAsyncResult result = ar as SendRequestAsyncResult; if (result != null) { return result.m_request; } return null; } #endregion #region Private Members /// /// Processes the request. /// private void OnSendRequest(object state) { try { // call the service. m_response = m_handler(m_request); } catch (Exception e) { // save any error. m_error = e; m_response = null; } // report completion. OperationCompleted(); } #endregion #region Private Fields private IChannelBase m_channel; private ChannelSendRequestEventHandler m_handler; private IServiceRequest m_request; private IServiceResponse m_response; private Exception m_error; #endregion } #endregion /// /// Processes the request. /// /// IChannelOutgoingRequest object passed to the ScheduleOutgoingRequest method. protected virtual void OnSendRequest(object state) { try { IChannelOutgoingRequest request = (IChannelOutgoingRequest)state; request.CallSynchronously(); } catch (Exception e) { Utils.Trace(e, "Unexpected error sending outgoing request."); } } #endif #region Protected Methods /// /// Creates a new UA-binary transport channel if requested. Null otherwise. /// /// The application configuration. /// The description for the endpoint. /// The configuration to use with the endpoint. /// The client certificate. /// The message context to use when serializing the messages. /// public static ITransportChannel CreateUaBinaryChannel( ApplicationConfiguration configuration, EndpointDescription description, EndpointConfiguration endpointConfiguration, X509Certificate2 clientCertificate, ServiceMessageContext messageContext) { // check if the server if configured to use the ANSI C stack. bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp); bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps); #if !SILVERLIGHT bool useAnsiCStack = false; #else useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttp); #endif switch (description.TransportProfileUri) { case Profiles.UaTcpTransport: { useUaTcp = true; #if !SILVERLIGHT if (configuration != null) { useAnsiCStack = configuration.UseNativeStack; } #endif break; } case Profiles.HttpsXmlTransport: case Profiles.HttpsBinaryTransport: case Profiles.HttpsXmlOrBinaryTransport: { useHttps = true; break; } } #if !SILVERLIGHT // check for a WCF channel. if (!useUaTcp && !useHttps) { // binary channels only need the base class. if (endpointConfiguration.UseBinaryEncoding) { Uri endpointUrl = new Uri(description.EndpointUrl); BindingFactory bindingFactory = BindingFactory.Create(configuration, messageContext); Binding binding = bindingFactory.Create(endpointUrl.Scheme, description, endpointConfiguration); WcfChannelBase wcfChannel = new WcfChannelBase(); // create regular binding. if (configuration != null) { wcfChannel.Initialize( configuration, description, endpointConfiguration, binding, clientCertificate, null); } // create no-security discovery binding. else { wcfChannel.Initialize( description, endpointConfiguration, binding, null); } return wcfChannel; } return null; } #endif // initialize the channel which will be created with the server. ITransportChannel channel = null; // create a UA-TCP channel. TransportChannelSettings settings = new TransportChannelSettings(); settings.Description = description; settings.Configuration = endpointConfiguration; settings.ClientCertificate = clientCertificate; if (description.ServerCertificate != null && description.ServerCertificate.Length > 0) { settings.ServerCertificate = Utils.ParseCertificateBlob(description.ServerCertificate); } #if !SILVERLIGHT if (configuration != null) { settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator(); } #endif settings.NamespaceUris = messageContext.NamespaceUris; settings.Factory = messageContext.Factory; if (useUaTcp) { #if !SILVERLIGHT Type type = null; if (useAnsiCStack) { type = Type.GetType("Opc.Ua.NativeStack.NativeStackChannel,Opc.Ua.NativeStackWrapper"); } if (useAnsiCStack && type != null) { channel = (ITransportChannel)Activator.CreateInstance(type); } else { channel = new Opc.Ua.Bindings.TcpTransportChannel(); } #endif } else if (useHttps) { channel = new Opc.Ua.Bindings.HttpsTransportChannel(); } channel.Initialize(new Uri(description.EndpointUrl), settings); channel.Open(); return channel; } /// /// Creates a new UA-binary transport channel if requested. Null otherwise. /// /// The configuration. /// The description. /// The endpoint configuration. /// The client certificates. /// The message context. /// /*public static ITransportChannel CreateUaBinaryChannel( ApplicationConfiguration configuration, EndpointDescription description, EndpointConfiguration endpointConfiguration, X509Certificate2Collection clientCertificates, ServiceMessageContext messageContext) { // check if the server if configured to use the ANSI C stack. bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp); bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps); #if !SILVERLIGHT bool useAnsiCStack = false; #else useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttp); #endif switch (description.TransportProfileUri) { case Profiles.UaTcpTransport: { useUaTcp = true; #if !SILVERLIGHT if (configuration != null) { useAnsiCStack = configuration.UseNativeStack; } #endif break; } case Profiles.HttpsXmlTransport: case Profiles.HttpsBinaryTransport: case Profiles.HttpsXmlOrBinaryTransport: { useHttps = true; break; } } #if !SILVERLIGHT // check for a WCF channel. if (!useUaTcp && !useHttps) { // binary channels only need the base class. if (endpointConfiguration.UseBinaryEncoding) { Uri endpointUrl = new Uri(description.EndpointUrl); BindingFactory bindingFactory = BindingFactory.Create(configuration, messageContext); Binding binding = bindingFactory.Create(endpointUrl.Scheme, description, endpointConfiguration); WcfChannelBase wcfChannel = new WcfChannelBase(); // create regular binding. if (configuration != null) { wcfChannel.Initialize( configuration, description, endpointConfiguration, binding, clientCertificates, null); } // create no-security discovery binding. else { wcfChannel.Initialize( description, endpointConfiguration, binding, null); } return wcfChannel; } return null; } #endif // initialize the channel which will be created with the server. ITransportChannel channel = null; // create a UA-TCP channel. TransportChannelSettings settings = new TransportChannelSettings(); settings.Description = description; settings.Configuration = endpointConfiguration; if (clientCertificates != null && clientCertificates.Count > 0) { settings.ClientCertificate = clientCertificates[0]; //settings.ClientCertificateChain = clientCertificates; } if (description.ServerCertificate != null && description.ServerCertificate.Length > 0) { settings.ServerCertificate = Utils.ParseCertificateBlob(description.ServerCertificate); } #if !SILVERLIGHT if (configuration != null) { settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator(); } #endif settings.NamespaceUris = messageContext.NamespaceUris; settings.Factory = messageContext.Factory; if (useUaTcp) { #if !SILVERLIGHT Type type = null; if (useAnsiCStack) { type = Type.GetType("Opc.Ua.NativeStack.NativeStackChannel,Opc.Ua.NativeStackWrapper"); } if (useAnsiCStack && type != null) { channel = (ITransportChannel)Activator.CreateInstance(type); } else { channel = new Opc.Ua.Bindings.TcpTransportChannel(); } #endif } else if (useHttps) { channel = new Opc.Ua.Bindings.HttpsTransportChannel(); } channel.Initialize(new Uri(description.EndpointUrl), settings); channel.Open(); return channel; } */ /// /// Handles the Opened event of the InnerChannel control. /// /// The source of the event. /// The instance containing the event data. internal void InnerChannel_Opened(object sender, EventArgs e) { #if !SILVERLIGHT Uri endpointUrl = this.m_channelFactory.Endpoint.Address.Uri; if (endpointUrl.Scheme == Utils.UriSchemeHttp || endpointUrl.Scheme == Utils.UriSchemeHttps) { ServicePoint sp = System.Net.ServicePointManager.FindServicePoint(endpointUrl); sp.ConnectionLimit = 1000; } X509Certificate2 clientCertificate = null; X509Certificate2 serverCertificate = null; ClientCredentials credentials = m_channelFactory.Endpoint.Behaviors.Find(); if (credentials != null) { clientCertificate = credentials.ClientCertificate.Certificate; serverCertificate = credentials.ServiceCertificate.DefaultCertificate; } Opc.Ua.Security.Audit.SecureChannelCreated( g_ImplementationString, m_channelFactory.Endpoint.Address.Uri.ToString(), null, EndpointDescription, clientCertificate, serverCertificate, BinaryEncodingSupport.Optional); #endif } /// /// Converts a FaultException into a ServiceResultException. /// public ServiceResultException HandleSoapFault(System.ServiceModel.FaultException exception) { if (exception == null || exception.Detail == null || exception.Detail.ResponseHeader == null) { return ServiceResultException.Create(StatusCodes.BadUnexpectedError, exception, "SOAP fault did not contain any details."); } ResponseHeader header = exception.Detail.ResponseHeader; return new ServiceResultException(new ServiceResult( header.ServiceResult, header.ServiceDiagnostics, header.StringTable)); } #endregion #region Private Fields internal TransportChannelSettings m_settings; internal ServiceMessageContext m_messageContext; internal ITransportChannel m_wcfBypassChannel; internal int m_operationTimeout; internal ChannelFactory m_channelFactory; internal IChannelBase m_channel; internal const string g_ImplementationString = "Opc.Ua.ChannelBase WCF Client " + AssemblyVersionInfo.CurrentVersion; #endregion } /// /// A base class for WCF channel objects used access UA interfaces /// public class WcfChannelBase : WcfChannelBase where TChannel : class, IChannelBase { #region Constructors /// /// Initializes the object with the specified binding and endpoint address. /// public WcfChannelBase() { } #if !SILVERLIGHT /// /// Initializes the channel without security. /// /// The endpoint. /// The endpoint configuration. /// The binding. /// Name of the configuration in the app.config file. public void Initialize( EndpointDescription endpoint, EndpointConfiguration endpointConfiguration, Binding binding, string configurationName) { if (endpoint == null) throw new ArgumentNullException("endpoint"); if (endpointConfiguration == null) throw new ArgumentNullException("endpointConfiguration"); if (binding == null) throw new ArgumentNullException("binding"); Uri endpointUrl = new Uri(endpoint.EndpointUrl); // create the configuration. if (endpointConfiguration == null) { endpointConfiguration = EndpointConfiguration.Create(); } ServiceMessageContext messageContext = null; // extract the message context from the binding. BaseBinding baseBinding = binding as BaseBinding; if (baseBinding != null) { messageContext = baseBinding.MessageContext; } // create a new message context. else { messageContext = new ServiceMessageContext(); messageContext.MaxArrayLength = endpointConfiguration.MaxArrayLength; messageContext.MaxByteStringLength = endpointConfiguration.MaxByteStringLength; messageContext.MaxMessageSize = endpointConfiguration.MaxMessageSize; messageContext.MaxStringLength = endpointConfiguration.MaxStringLength; messageContext.NamespaceUris = new NamespaceTable(); messageContext.ServerUris = new StringTable(); messageContext.Factory = EncodeableFactory.GlobalFactory; } // a work around designed to preserve the behavoir of the object when the WCF UA-TCP implementation was supported. if (endpointUrl.Scheme == Utils.UriSchemeOpcTcp) { m_wcfBypassChannel = CreateUaBinaryChannel( null, endpoint, endpointConfiguration, (X509Certificate2)null, messageContext); return; } // update the channel timeout. TimeSpan timeout = new TimeSpan(endpointConfiguration.OperationTimeout * TimeSpan.TicksPerMillisecond); binding.OpenTimeout = timeout; binding.CloseTimeout = timeout; binding.SendTimeout = timeout; binding.ReceiveTimeout = timeout; // create the address. EndpointAddress address = new EndpointAddress(endpointUrl); // create the factory. ChannelFactory channelFactory = null; if (binding == null) { channelFactory = new System.ServiceModel.ChannelFactory(configurationName, address); } else { channelFactory = new System.ServiceModel.ChannelFactory(binding, address); } // check if XML encoding is used. if (!endpointConfiguration.UseBinaryEncoding) { ServiceMessageContextMessageInspector inspector = new ServiceMessageContextMessageInspector(binding); channelFactory.Endpoint.Behaviors.Add(inspector); // update the max items in graph (set to an low value by default). foreach (OperationDescription operation in channelFactory.Endpoint.Contract.Operations) { operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue; } } // create the settings. TransportChannelSettings settings = new TransportChannelSettings(); settings.Description = endpoint; settings.Configuration = endpointConfiguration; settings.ClientCertificate = null; settings.ServerCertificate = null; settings.CertificateValidator = null; settings.NamespaceUris = messageContext.NamespaceUris; settings.Factory = messageContext.Factory; // save the parameters. m_settings = settings; m_messageContext = messageContext; m_operationTimeout = endpointConfiguration.OperationTimeout; m_channelFactory = channelFactory; // create the channel. base.m_channel = m_channel = channelFactory.CreateChannel(); ICommunicationObject communicationObject = m_channel as ICommunicationObject; if (communicationObject != null) { communicationObject.Opened += new EventHandler(InnerChannel_Opened); } } /// /// Initializes the channel with security. /// /// The application configuration. /// The description. /// The endpoint configuration. /// The binding. /// The client certificate. /// Name of the configuration in the app.config file. public void Initialize( ApplicationConfiguration configuration, EndpointDescription description, EndpointConfiguration endpointConfiguration, Binding binding, X509Certificate2 clientCertificate, string configurationName) { if (configuration == null) throw new ArgumentNullException("configuration"); if (description == null) throw new ArgumentNullException("description"); if (binding == null) throw new ArgumentNullException("binding"); // validate the url. Uri uri = new Uri(description.EndpointUrl); // create the configuration. if (endpointConfiguration == null) { endpointConfiguration = EndpointConfiguration.Create(configuration); } ServiceMessageContext messageContext = null; // extract the message context from the binding. BaseBinding baseBinding = binding as BaseBinding; if (baseBinding != null) { messageContext = baseBinding.MessageContext; } // create a new message context. else { messageContext = configuration.CreateMessageContext(); messageContext.MaxArrayLength = endpointConfiguration.MaxArrayLength; messageContext.MaxByteStringLength = endpointConfiguration.MaxByteStringLength; messageContext.MaxMessageSize = endpointConfiguration.MaxMessageSize; messageContext.MaxStringLength = endpointConfiguration.MaxStringLength; messageContext.NamespaceUris = new NamespaceTable(); messageContext.ServerUris = new StringTable(); messageContext.Factory = EncodeableFactory.GlobalFactory; } // a work around designed to preserve the behavoir of the object when the WCF UA-TCP implementation was supported. if (uri.Scheme == Utils.UriSchemeOpcTcp) { m_wcfBypassChannel = CreateUaBinaryChannel( configuration, description, endpointConfiguration, clientCertificate, messageContext); return; } TimeSpan timeout = new TimeSpan(endpointConfiguration.OperationTimeout * TimeSpan.TicksPerMillisecond); binding.OpenTimeout = timeout; binding.CloseTimeout = timeout; binding.SendTimeout = timeout; binding.ReceiveTimeout = timeout; X509Certificate2 serverCertificate = null; EndpointIdentity identity = null; if (description.SecurityPolicyUri != SecurityPolicies.None) { // create the DNS identity for the server from the certificate. if (description.ServerCertificate == null) { throw ServiceResultException.Create( StatusCodes.BadCertificateInvalid, "EndpointDescription for {0} does not have a ServerCertificate specified.", description.EndpointUrl ); } serverCertificate = CertificateFactory.Create( description.ServerCertificate, true ); identity = EndpointIdentity.CreateX509CertificateIdentity( serverCertificate ); } // create the adderss. EndpointAddress address = new EndpointAddress(uri, identity); // create the factory. ChannelFactory channelFactory = null; if (binding == null) { channelFactory = new System.ServiceModel.ChannelFactory(configurationName, address); } else { channelFactory = new System.ServiceModel.ChannelFactory(binding, address); } // check if XML encoding is used. if (!endpointConfiguration.UseBinaryEncoding) { // add behavoir required to provide the MessageContext to the WCF encoders. ServiceMessageContextMessageInspector inspector = new ServiceMessageContextMessageInspector(binding); channelFactory.Endpoint.Behaviors.Add(inspector); // update the max items in graph (set to an low value by default). foreach (OperationDescription operation in channelFactory.Endpoint.Contract.Operations) { operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue; } } // add the service certificate into the behaviors. if (serverCertificate != null) { ClientCredentials credentials = (ClientCredentials)channelFactory.Endpoint.Behaviors[typeof(ClientCredentials)]; credentials.ServiceCertificate.DefaultCertificate = serverCertificate; credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom; credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck; credentials.ServiceCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine; credentials.ServiceCertificate.Authentication.CustomCertificateValidator = configuration.CertificateValidator.GetChannelValidator(); if (clientCertificate != null) { credentials.ClientCertificate.Certificate = clientCertificate; } } // set the protection level on the contract. if (description != null) { if (description.SecurityMode == MessageSecurityMode.Sign) { channelFactory.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign; } } // create the settings. TransportChannelSettings settings = new TransportChannelSettings(); settings.Description = description; settings.Configuration = endpointConfiguration; settings.ClientCertificate = clientCertificate; settings.ServerCertificate = serverCertificate; settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator(); settings.NamespaceUris = messageContext.NamespaceUris; settings.Factory = messageContext.Factory; // save the parameters. m_settings = settings; m_messageContext = messageContext; m_operationTimeout = endpointConfiguration.OperationTimeout; m_channelFactory = channelFactory; // create the channel. base.m_channel = m_channel = channelFactory.CreateChannel(); ICommunicationObject communicationObject = m_channel as ICommunicationObject; if (communicationObject != null) { communicationObject.Opened += new EventHandler(InnerChannel_Opened); } } /// /// Initializes the channel with security. /// /// The configuration. /// The description. /// The endpoint configuration. /// The binding. /// The client certificates. /// Name of the configuration. /*public void Initialize( ApplicationConfiguration configuration, EndpointDescription description, EndpointConfiguration endpointConfiguration, Binding binding, X509Certificate2Collection clientCertificates, string configurationName) { if (configuration == null) throw new ArgumentNullException("configuration"); if (description == null) throw new ArgumentNullException("description"); if (binding == null) throw new ArgumentNullException("binding"); // validate the url. Uri uri = new Uri(description.EndpointUrl); // create the configuration. if (endpointConfiguration == null) { endpointConfiguration = EndpointConfiguration.Create(configuration); } ServiceMessageContext messageContext = null; // extract the message context from the binding. BaseBinding baseBinding = binding as BaseBinding; if (baseBinding != null) { messageContext = baseBinding.MessageContext; } // create a new message context. else { messageContext = configuration.CreateMessageContext(); messageContext.MaxArrayLength = endpointConfiguration.MaxArrayLength; messageContext.MaxByteStringLength = endpointConfiguration.MaxByteStringLength; messageContext.MaxMessageSize = endpointConfiguration.MaxMessageSize; messageContext.MaxStringLength = endpointConfiguration.MaxStringLength; messageContext.NamespaceUris = new NamespaceTable(); messageContext.ServerUris = new StringTable(); messageContext.Factory = EncodeableFactory.GlobalFactory; } // a work around designed to preserve the behavoir of the object when the WCF UA-TCP implementation was supported. if (uri.Scheme == Utils.UriSchemeOpcTcp) { m_wcfBypassChannel = CreateUaBinaryChannel( configuration, description, endpointConfiguration, clientCertificates, messageContext); return; } TimeSpan timeout = new TimeSpan(endpointConfiguration.OperationTimeout * TimeSpan.TicksPerMillisecond); binding.OpenTimeout = timeout; binding.CloseTimeout = timeout; binding.SendTimeout = timeout; binding.ReceiveTimeout = timeout; X509Certificate2 serverCertificate = null; EndpointIdentity identity = null; if (description.SecurityPolicyUri != SecurityPolicies.None) { // create the DNS identity for the server from the certificate. if (description.ServerCertificate == null) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "EndpointDescription for {0} does not have a ServerCertificate specified.", description.EndpointUrl); } serverCertificate = CertificateFactory.Create(description.ServerCertificate, true); identity = EndpointIdentity.CreateX509CertificateIdentity(serverCertificate); } // create the adderss. EndpointAddress address = new EndpointAddress(uri, identity); // create the factory. ChannelFactory channelFactory = null; if (binding == null) { channelFactory = new System.ServiceModel.ChannelFactory(configurationName, address); } else { channelFactory = new System.ServiceModel.ChannelFactory(binding, address); } // check if XML encoding is used. if (!endpointConfiguration.UseBinaryEncoding) { // add behavoir required to provide the MessageContext to the WCF encoders. ServiceMessageContextMessageInspector inspector = new ServiceMessageContextMessageInspector(binding); channelFactory.Endpoint.Behaviors.Add(inspector); // update the max items in graph (set to an low value by default). foreach (OperationDescription operation in channelFactory.Endpoint.Contract.Operations) { operation.Behaviors.Find().MaxItemsInObjectGraph = Int32.MaxValue; } } // add the service certificate into the behaviors. if (serverCertificate != null) { ClientCredentials credentials = (ClientCredentials)channelFactory.Endpoint.Behaviors[typeof(ClientCredentials)]; credentials.ServiceCertificate.DefaultCertificate = serverCertificate; credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom; credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck; credentials.ServiceCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine; credentials.ServiceCertificate.Authentication.CustomCertificateValidator = configuration.CertificateValidator.GetChannelValidator(); if (clientCertificates != null && clientCertificates.Count > 0 && clientCertificates[0] != null) { credentials.ClientCertificate.Certificate = clientCertificates[0]; } } // set the protection level on the contract. if (description != null) { if (description.SecurityMode == MessageSecurityMode.Sign) { channelFactory.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign; } } // create the settings. TransportChannelSettings settings = new TransportChannelSettings(); settings.Description = description; settings.Configuration = endpointConfiguration; if (clientCertificates != null && clientCertificates.Count > 0) { settings.ClientCertificate = clientCertificates[0]; //settings.ClientCertificateChain = clientCertificates; } settings.ServerCertificate = serverCertificate; settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator(); settings.NamespaceUris = messageContext.NamespaceUris; settings.Factory = messageContext.Factory; // save the parameters. m_settings = settings; m_messageContext = messageContext; m_operationTimeout = endpointConfiguration.OperationTimeout; m_channelFactory = channelFactory; // create the channel. base.m_channel = m_channel = channelFactory.CreateChannel(); ICommunicationObject communicationObject = m_channel as ICommunicationObject; if (communicationObject != null) { communicationObject.Opened += new EventHandler(InnerChannel_Opened); } } */ #endif #endregion #region IDisposable Members /// /// An overrideable version of the Dispose. /// protected override void Dispose(bool disposing) { if (disposing) { try { var communicationObject = m_channel as ICommunicationObject; if (communicationObject != null) { communicationObject.Opened -= new EventHandler(InnerChannel_Opened); communicationObject.Close(); } } catch (Exception) { // ignore errors. } Utils.SilentDispose(m_channel); m_channel = null; Utils.SilentDispose(m_channelFactory); m_channelFactory = null; } base.Dispose(disposing); } #endregion #region IChannelBase Members /// /// The client side implementation of the InvokeService service contract. /// public override InvokeServiceResponseMessage InvokeService(InvokeServiceMessage request) { IAsyncResult result = null; lock (this.Channel) { result = this.Channel.BeginInvokeService(request, null, null); } return this.Channel.EndInvokeService(result); } /// /// The client side implementation of the BeginInvokeService service contract. /// public override IAsyncResult BeginInvokeService(InvokeServiceMessage request, AsyncCallback callback, object asyncState) { WcfChannelAsyncResult asyncResult = new WcfChannelAsyncResult(m_channel, callback, asyncState); lock (asyncResult.Lock) { asyncResult.InnerResult = asyncResult.Channel.BeginInvokeService(request, asyncResult.OnOperationCompleted, null); } return asyncResult; } /// /// The client side implementation of the EndInvokeService service contract. /// public override InvokeServiceResponseMessage EndInvokeService(IAsyncResult result) { WcfChannelAsyncResult asyncResult = WcfChannelAsyncResult.WaitForComplete(result); return asyncResult.Channel.EndInvokeService(asyncResult.InnerResult); } #endregion #region ITransportChannel Members /// /// Closes any existing secure channel and opens a new one. /// public override void Reconnect() { if (m_wcfBypassChannel != null) { m_wcfBypassChannel.Reconnect(); return; } Utils.Trace("RECONNECT: Reconnecting to {0}.", m_settings.Description.EndpointUrl); // grap the existing channel. TChannel channel = m_channel; ChannelFactory channelFactory = m_channelFactory as ChannelFactory; // create the new channel. base.m_channel = m_channel = channelFactory.CreateChannel(); ICommunicationObject communicationObject = null; if (channel != null) { try { communicationObject = channel as ICommunicationObject; if (communicationObject != null) { communicationObject.Opened -= new EventHandler(InnerChannel_Opened); communicationObject.Close(); } } catch (Exception) { // ignore errors. } Utils.SilentDispose(channel); } // register callback with new channel. communicationObject = m_channel as ICommunicationObject; if (communicationObject != null) { communicationObject.Opened += new EventHandler(InnerChannel_Opened); } } #endregion #region WcfChannelAsyncResult Class /// /// An async result object that wraps the WCF channel. /// protected class WcfChannelAsyncResult : AsyncResultBase { /// /// Initializes a new instance of the class. /// /// The channel. /// The callback. /// The callback data. public WcfChannelAsyncResult( TChannel channel, AsyncCallback callback, object callbackData) : base(callback, callbackData, 0) { m_channel = channel; } /// /// Gets the wrapped channel. /// /// The wrapped channel. public TChannel Channel { get { return m_channel; } } /// /// Called when asynchronous operation completes. /// /// The asynchronous result object. public void OnOperationCompleted(IAsyncResult ar) { try { // check if the begin operation has had a chance to complete. lock (Lock) { if (InnerResult == null) { InnerResult = ar; } } // signal that the operation is complete. OperationCompleted(); } catch (Exception e) { Utils.Trace(e, "Unexpected exception invoking WcfChannelAsyncResult callback function."); } } /// /// Checks for a valid IAsyncResult object and waits for the operation to complete. /// /// The IAsyncResult object for the operation. /// The oject that public static new WcfChannelAsyncResult WaitForComplete(IAsyncResult ar) { WcfChannelAsyncResult asyncResult = ar as WcfChannelAsyncResult; if (asyncResult == null) { throw new ArgumentException("End called with an invalid IAsyncResult object.", "ar"); } if (!asyncResult.WaitForComplete()) { throw new ServiceResultException(StatusCodes.BadTimeout); } return asyncResult; } private TChannel m_channel; } #endregion #region Protected Methods /// /// Gets the inner channel. /// /// The channel. protected TChannel Channel { get { return m_channel; } } #endregion #region Private Fields private new TChannel m_channel; #endregion } }