/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved. The source code in this file is covered under a dual-license scenario: - RCL: for OPC Foundation members in good-standing - GPL V2: everybody else RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ GNU General Public License as published by the Free Software Foundation; version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2 This source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; namespace Opc.Ua.Bindings { /// /// Creates a new CustomTransportChannel with ITransportChannel interface. /// public class CustomTransportChannelFactory : ITransportChannelFactory { /// /// The method creates a new instance of a custom transport channel /// /// the transport channel public ITransportChannel Create() { return new CustomTransportChannel(); } } /// /// Wraps the CustomTransportChannel and provides an ITransportChannel implementation. /// public class CustomTransportChannel : ITransportChannel { #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) { if (disposing) { Utils.SilentDispose(m_channel); m_channel = null; } } #endregion #region ITransportChannel Members /// /// A masking indicating which features are implemented. /// public TransportChannelFeatures SupportedFeatures { get { return TransportChannelFeatures.Open | TransportChannelFeatures.BeginOpen | TransportChannelFeatures.Reconnect | TransportChannelFeatures.BeginSendRequest; } } /// /// Gets the description for the endpoint used by the channel. /// public EndpointDescription EndpointDescription { get { return m_settings.Description; } } /// /// Gets the configuration for the channel. /// public EndpointConfiguration EndpointConfiguration { get { return m_settings.Configuration; } } /// /// Gets the context used when serializing messages exchanged via the channel. /// public ServiceMessageContext MessageContext { get { return m_quotas.MessageContext; } } /// /// Gets or sets the default timeout for requests send via the channel. /// public int OperationTimeout { get { return m_operationTimeout; } set { 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) { SaveSettings(url, settings); } /// /// Opens a secure channel with the endpoint identified by the URL. /// /// Thrown if any communication error occurs. public void Open() { // opens when the first request is called to preserve previous behavoir. } /// /// Begins an asynchronous operation to open a secure channel with the endpoint identified by the URL. /// /// The callback to call when the operation completes. /// The callback data to return with the callback. /// /// The result which must be passed to the EndOpen method. /// /// Thrown if any communication error occurs. /// public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData) { lock (m_lock) { // create the channel. m_channel = new TcpClientChannel( Guid.NewGuid().ToString(), m_bufferManager, m_quotas, m_settings.ClientCertificate, m_settings.ServerCertificate, m_settings.Description); // begin connect operation. return m_channel.BeginConnect(this.m_url, m_operationTimeout, callback, callbackData); } } /// /// Completes an asynchronous operation to open a secure channel. /// /// The result returned from the BeginOpen call. /// Thrown if any communication error occurs. /// public void EndOpen(IAsyncResult result) { m_channel.EndConnect(result); } /// /// 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 void Reconnect() { Utils.Trace("CustomTransportChannel RECONNECT: Reconnecting to {0}.", m_url); lock (m_lock) { // the new channel must be created first because WinSock will reuse sockets and this // can result in messages sent over the old socket arriving as messages on the new socket. // if this happens the new channel is shutdown because of a security violation. TcpClientChannel channel = m_channel; m_channel = null; // reconnect. OpenOnDemand(); // begin connect operation. IAsyncResult result = m_channel.BeginConnect(m_url, m_operationTimeout, null, null); m_channel.EndConnect(result); // close existing channel. if (channel != null) { try { channel.Close(1000); } catch (Exception) { // do nothing. } finally { channel.Dispose(); } } } } /// /// Begins an asynchronous operation to close the existing secure channel and open a new one. /// /// The callback to call when the operation completes. /// The callback data to return with the callback. /// /// The result which must be passed to the EndReconnect method. /// /// Thrown if any communication error occurs. /// public IAsyncResult BeginReconnect(AsyncCallback callback, object callbackData) { throw new NotImplementedException(); } /// /// Completes an asynchronous operation to close the existing secure channel and open a new one. /// /// The result returned from the BeginReconnect call. /// Thrown if any communication error occurs. /// public void EndReconnect(IAsyncResult result) { throw new NotImplementedException(); } /// /// Closes the secure channel. /// /// Thrown if any communication error occurs. public void Close() { if (m_channel != null) { lock (m_lock) { if (m_channel != null) { m_channel.Close(1000); m_channel = null; } } } } /// /// Begins an asynchronous operation to close the secure channel. /// /// The callback to call when the operation completes. /// The callback data to return with the callback. /// /// The result which must be passed to the EndClose method. /// /// Thrown if any communication error occurs. /// public IAsyncResult BeginClose(AsyncCallback callback, object callbackData) { throw new NotImplementedException(); } /// /// Completes an asynchronous operation to close the secure channel. /// /// The result returned from the BeginClose call. /// Thrown if any communication error occurs. /// public void EndClose(IAsyncResult result) { throw new NotImplementedException(); } /// /// Sends a request over the secure channel. /// /// The request to send. /// The response returned by the server. /// Thrown if any communication error occurs. public IServiceResponse SendRequest(IServiceRequest request) { IAsyncResult result = BeginSendRequest(request, null, null); return EndSendRequest(result); } /// /// Begins an asynchronous operation to send a request over the secure channel. /// /// The request to send. /// The callback to call when the operation completes. /// The callback data to return with the callback. /// /// The result which must be passed to the EndSendRequest method. /// /// Thrown if any communication error occurs. /// public IAsyncResult BeginSendRequest(IServiceRequest request, AsyncCallback callback, object callbackData) { TcpClientChannel channel = m_channel; if (channel == null) { lock (m_lock) { if (m_channel == null) { OpenOnDemand(); } channel = m_channel; } } return channel.BeginSendRequest(request, m_operationTimeout, callback, callbackData); } /// /// Completes an asynchronous operation to send a request over the secure channel. /// /// The result returned from the BeginSendRequest call. /// /// Thrown if any communication error occurs. /// public IServiceResponse EndSendRequest(IAsyncResult result) { TcpClientChannel channel = m_channel; if (channel == null) { throw ServiceResultException.Create(StatusCodes.BadSecureChannelClosed, "Channel has been closed."); } return channel.EndSendRequest(result); } /// /// Saves the settings so the channel can be opened later. /// /// The URL. /// The settings. private void SaveSettings(Uri url, TransportChannelSettings settings) { // save the settings. m_url = url; m_settings = settings; m_operationTimeout = settings.Configuration.OperationTimeout; // initialize the quotas. m_quotas = new TcpChannelQuotas(); m_quotas.MaxBufferSize = m_settings.Configuration.MaxBufferSize; m_quotas.MaxMessageSize = m_settings.Configuration.MaxMessageSize; m_quotas.ChannelLifetime = m_settings.Configuration.ChannelLifetime; m_quotas.SecurityTokenLifetime = m_settings.Configuration.SecurityTokenLifetime; m_quotas.MessageContext = new ServiceMessageContext(); m_quotas.MessageContext.MaxArrayLength = m_settings.Configuration.MaxArrayLength; m_quotas.MessageContext.MaxByteStringLength = m_settings.Configuration.MaxByteStringLength; m_quotas.MessageContext.MaxMessageSize = m_settings.Configuration.MaxMessageSize; m_quotas.MessageContext.MaxStringLength = m_settings.Configuration.MaxStringLength; m_quotas.MessageContext.NamespaceUris = m_settings.NamespaceUris; m_quotas.MessageContext.ServerUris = new StringTable(); m_quotas.MessageContext.Factory = m_settings.Factory; m_quotas.CertificateValidator = settings.CertificateValidator; // create the buffer manager. m_bufferManager = new BufferManager("Client", (int)Int32.MaxValue, settings.Configuration.MaxBufferSize); } /// /// Opens the channel before sending the request. /// private void OpenOnDemand() { // create the channel. m_channel = new TcpClientChannel( Guid.NewGuid().ToString(), m_bufferManager, m_quotas, m_settings.ClientCertificate, m_settings.ServerCertificate, m_settings.Description); // begin connect operation. // IAsyncResult result = m_channel.BeginConnect(m_url, m_operationTimeout, null, null); // m_channel.EndConnect(result); } #endregion #region Private Fields private object m_lock = new object(); private Uri m_url; private int m_operationTimeout; private TransportChannelSettings m_settings; private TcpChannelQuotas m_quotas; private BufferManager m_bufferManager; private TcpClientChannel m_channel; #endregion } }