/* 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
}
}