/* ========================================================================
* Copyright (c) 2005-2017 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Globalization;
using System.IO;
using System.Xml;
namespace Opc.Ua.Client
{
///
/// Manages a session with a server.
///
public class Session : SessionClient, IDisposable
{
#region Constructors
///
/// Constructs a new instance of the session.
///
/// The channel used to communicate with the server.
/// The configuration for the client application.
/// The endpoint use to initialize the channel.
public Session(
ISessionChannel channel,
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint)
:
this(channel as ITransportChannel, configuration, endpoint, null)
{
}
///
/// Constructs a new instance of the session.
///
/// The channel used to communicate with the server.
/// The configuration for the client application.
/// The endpoint use to initialize the channel.
/// The certificate to use for the client.
///
/// The application configuration is used to look up the certificate if none is provided.
/// The clientCertificate must have the private key. This will require that the certificate
/// be loaded from a certicate store. Converting a DER encoded blob to a X509Certificate2
/// will not include a private key.
///
public Session(
ITransportChannel channel,
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
X509Certificate2 clientCertificate)
:
base(channel)
{
Initialize(channel, configuration, endpoint, clientCertificate);
}
public Session(
ITransportChannel channel,
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
X509Certificate2 clientCertificate,
EndpointDescriptionCollection availableEndpoints)
:
base(channel)
{
Initialize(channel, configuration, endpoint, clientCertificate);
m_expectedServerEndpoints = availableEndpoints;
}
///
/// Initializes a new instance of the class.
///
/// The channel.
/// The template session.
/// if set to true the event handlers are copied.
public Session(ITransportChannel channel, Session template, bool copyEventHandlers)
:
base(channel)
{
Initialize(channel, template.m_configuration, template.m_endpoint, template.m_instanceCertificate);
m_defaultSubscription = template.m_defaultSubscription;
m_sessionTimeout = template.m_sessionTimeout;
m_maxRequestMessageSize = template.m_maxRequestMessageSize;
m_preferredLocales = template.m_preferredLocales;
m_sessionName = template.m_sessionName;
m_handle = template.m_handle;
m_identity = template.m_identity;
m_keepAliveInterval = template.m_keepAliveInterval;
if (copyEventHandlers)
{
m_KeepAlive = template.m_KeepAlive;
m_Publish = template.m_Publish;
m_PublishError = template.m_PublishError;
m_SubscriptionsChanged = template.m_SubscriptionsChanged;
m_SessionClosing = template.m_SessionClosing;
}
foreach (Subscription subscription in template.Subscriptions)
{
this.AddSubscription(new Subscription(subscription, copyEventHandlers));
}
}
///
/// Initializes the channel.
///
private void Initialize(
ITransportChannel channel,
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
X509Certificate2 clientCertificate)
{
Initialize();
// save configuration information.
m_configuration = configuration;
m_endpoint = endpoint;
// update the default subscription.
m_defaultSubscription.MinLifetimeInterval = (uint)configuration.ClientConfiguration.MinSubscriptionLifetime;
if (m_endpoint.Description.SecurityPolicyUri != SecurityPolicies.None)
{
// update client certificate.
m_instanceCertificate = clientCertificate;
if (clientCertificate == null)
{
// load the application instance certificate.
if (m_configuration.SecurityConfiguration.ApplicationCertificate == null)
{
throw new ServiceResultException(
StatusCodes.BadConfigurationError,
"The client configuration does not specify an application instance certificate.");
}
m_instanceCertificate = m_configuration.SecurityConfiguration.ApplicationCertificate.Find(true);
}
// check for valid certificate.
if (m_instanceCertificate == null)
{
throw ServiceResultException.Create(
StatusCodes.BadConfigurationError,
"Cannot find the application instance certificate. Store={0}, SubjectName={1}, Thumbprint={2}.",
m_configuration.SecurityConfiguration.ApplicationCertificate.StorePath,
m_configuration.SecurityConfiguration.ApplicationCertificate.SubjectName,
m_configuration.SecurityConfiguration.ApplicationCertificate.Thumbprint);
}
// check for private key.
if (!m_instanceCertificate.HasPrivateKey)
{
throw ServiceResultException.Create(
StatusCodes.BadConfigurationError,
"Do not have a privat key for the application instance certificate. Subject={0}, Thumbprint={1}.",
m_instanceCertificate.Subject,
m_instanceCertificate.Thumbprint);
}
//load certificate chain
/*m_instanceCertificateChain = new X509Certificate2Collection(m_instanceCertificate);
List issuers = new List();
configuration.CertificateValidator.GetIssuers(m_instanceCertificate, issuers);
for (int i = 0; i < issuers.Count; i++)
{
m_instanceCertificateChain.Add(issuers[i].Certificate);
}*/
}
// initialize the message context.
ServiceMessageContext messageContext = channel.MessageContext;
if (messageContext != null)
{
m_namespaceUris = messageContext.NamespaceUris;
m_serverUris = messageContext.ServerUris;
m_factory = messageContext.Factory;
}
else
{
m_namespaceUris = new NamespaceTable();
m_serverUris = new StringTable();
m_factory = ServiceMessageContext.GlobalContext.Factory;
}
// set the default preferred locales.
m_preferredLocales = new string[] { CultureInfo.CurrentCulture.Name };
// create a context to use.
m_systemContext = new SystemContext();
m_systemContext.SystemHandle = this;
m_systemContext.EncodeableFactory = m_factory;
m_systemContext.NamespaceUris = m_namespaceUris;
m_systemContext.ServerUris = m_serverUris;
m_systemContext.TypeTable = this.TypeTree;
m_systemContext.PreferredLocales = null;
m_systemContext.SessionId = null;
m_systemContext.UserIdentity = null;
}
///
/// Sets the object members to default values.
///
private void Initialize()
{
m_sessionTimeout = 0;
m_namespaceUris = new NamespaceTable();
m_serverUris = new StringTable();
m_factory = EncodeableFactory.GlobalFactory;
m_nodeCache = new NodeCache(this);
m_configuration = null;
m_instanceCertificate = null;
m_endpoint = null;
m_subscriptions = new List();
m_dictionaries = new Dictionary();
m_acknowledgementsToSend = new SubscriptionAcknowledgementCollection();
m_latestAcknowledgementsSent = new Dictionary();
m_identityHistory = new List();
m_outstandingRequests = new LinkedList();
m_keepAliveInterval = 5000;
m_defaultSubscription = new Subscription();
m_defaultSubscription.DisplayName = "Subscription";
m_defaultSubscription.PublishingInterval = 1000;
m_defaultSubscription.KeepAliveCount = 10;
m_defaultSubscription.LifetimeCount = 1000;
m_defaultSubscription.Priority = 255;
m_defaultSubscription.PublishingEnabled = true;
}
#endregion
#region IDisposable Members
///
/// Closes the session and the underlying channel.
///
protected override void Dispose(bool disposing)
{
if (disposing)
{
Utils.SilentDispose(m_keepAliveTimer);
m_keepAliveTimer = null;
Utils.SilentDispose(m_defaultSubscription);
m_defaultSubscription = null;
foreach (Subscription subscription in m_subscriptions)
{
Utils.SilentDispose(subscription);
}
m_subscriptions.Clear();
}
base.Dispose(disposing);
}
#endregion
#region Events
///
/// Raised when a keep alive arrives from the server or an error is detected.
///
///
/// Once a session is created a timer will periodically read the server state and current time.
/// If this read operation succeeds this event will be raised each time the keep alive period elapses.
/// If an error is detected (KeepAliveStopped == true) then this event will be raised as well.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event KeepAliveEventHandler KeepAlive
{
add
{
lock (m_eventLock)
{
m_KeepAlive += value;
}
}
remove
{
lock (m_eventLock)
{
m_KeepAlive -= value;
}
}
}
///
/// Raised when a notification message arrives in a publish response.
///
///
/// All publish requests are managed by the Session object. When a response arrives it is
/// validated and passed to the appropriate Subscription object and this event is raised.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event NotificationEventHandler Notification
{
add
{
lock (m_eventLock)
{
m_Publish += value;
}
}
remove
{
lock (m_eventLock)
{
m_Publish -= value;
}
}
}
///
/// Raised when an exception occurs while processing a publish response.
///
///
/// Exceptions in a publish response are not necessarily fatal and the Session will
/// attempt to recover by issuing Republish requests if missing messages are detected.
/// That said, timeout errors may be a symptom of a OperationTimeout that is too short
/// when compared to the shortest PublishingInterval/KeepAliveCount amount the current
/// Subscriptions. The OperationTimeout should be twice the minimum value for
/// PublishingInterval*KeepAliveCount.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event PublishErrorEventHandler PublishError
{
add
{
lock (m_eventLock)
{
m_PublishError += value;
}
}
remove
{
lock (m_eventLock)
{
m_PublishError -= value;
}
}
}
///
/// Raised when a subscription is added or removed
///
public event EventHandler SubscriptionsChanged
{
add
{
m_SubscriptionsChanged += value;
}
remove
{
m_SubscriptionsChanged -= value;
}
}
///
/// Raised to indicate the session is closing.
///
public event EventHandler SessionClosing
{
add
{
m_SessionClosing += value;
}
remove
{
m_SessionClosing -= value;
}
}
#endregion
#region Public Properties
///
/// Gets the endpoint used to connect to the server.
///
public ConfiguredEndpoint ConfiguredEndpoint
{
get
{
return m_endpoint;
}
}
///
/// Gets the name assigned to the session.
///
public string SessionName
{
get
{
return m_sessionName;
}
}
///
/// Gets the period for wich the server will maintain the session if there is no communication from the client.
///
public double SessionTimeout
{
get
{
return m_sessionTimeout;
}
}
///
/// Gets the local handle assigned to the session
///
public object Handle
{
get { return m_handle; }
set { m_handle = value; }
}
///
/// Gets the user identity currently used for the session.
///
public IUserIdentity Identity
{
get
{
return m_identity;
}
}
///
/// Gets a list of user identities that can be used to connect to the server.
///
public IEnumerable IdentityHistory
{
get { return m_identityHistory; }
}
///
/// Gets the table of namespace uris known to the server.
///
public NamespaceTable NamespaceUris
{
get { return m_namespaceUris; }
}
///
/// Gest the table of remote server uris known to the server.
///
public StringTable ServerUris
{
get { return m_serverUris; }
}
///
/// Gets the system context for use with the session.
///
public ISystemContext SystemContext
{
get { return m_systemContext; }
}
///
/// Gets the factory used to create encodeable objects that the server understands.
///
public EncodeableFactory Factory
{
get { return m_factory; }
}
///
/// Gets the cache of the server's type tree.
///
public ITypeTable TypeTree
{
get { return m_nodeCache.TypeTree; }
}
///
/// Gets the cache of nodes fetched from the server.
///
public NodeCache NodeCache
{
get { return m_nodeCache; }
}
///
/// Gets the context to use for filter operations.
///
public FilterContext FilterContext
{
get { return new FilterContext(m_namespaceUris, m_nodeCache.TypeTree, m_preferredLocales); }
}
///
/// Gets the locales that the server should use when returning localized text.
///
public StringCollection PreferredLocales
{
get { return m_preferredLocales; }
}
///
/// Gets the subscriptions owned by the session.
///
public IEnumerable Subscriptions
{
get
{
lock (SyncRoot)
{
return new ReadOnlyList(m_subscriptions);
}
}
}
///
/// Gets the number of subscriptions owned by the session.
///
public int SubscriptionCount
{
get
{
lock (SyncRoot)
{
return m_subscriptions.Count;
}
}
}
///
/// Gets or Sets the default subscription for the session.
///
public Subscription DefaultSubscription
{
get { return m_defaultSubscription; }
set { m_defaultSubscription = value; }
}
///
/// Gets or Sets how frequently the server is pinged to see if communication is still working.
///
///
/// This interval controls how much time elaspes before a communication error is detected.
/// If everything is ok the KeepAlive event will be raised each time this period elapses.
///
public int KeepAliveInterval
{
get
{
return m_keepAliveInterval;
}
set
{
m_keepAliveInterval = value;
StartKeepAliveTimer();
}
}
///
/// Returns true if the session is not receiving keep alives.
///
///
/// Set to true if the server does not respond for 2 times the KeepAliveInterval.
/// Set to false is communication recovers.
///
public bool KeepAliveStopped
{
get
{
lock (m_eventLock)
{
long delta = DateTime.UtcNow.Ticks - m_lastKeepAliveTime.Ticks;
// add a 1000ms guard band to allow for network lag.
return (m_keepAliveInterval*2)*TimeSpan.TicksPerMillisecond <= delta;
}
}
}
///
/// Gets the time of the last keep alive.
///
public DateTime LastKeepAliveTime
{
get { return m_lastKeepAliveTime; }
}
///
/// Gets the number of outstanding publish or keep alive requests.
///
public int OutstandingRequestCount
{
get
{
lock (m_outstandingRequests)
{
return m_outstandingRequests.Count;
}
}
}
///
/// Gets the number of outstanding publish or keep alive requests which appear to hung.
///
public int DefunctRequestCount
{
get
{
lock (m_outstandingRequests)
{
int count = 0;
for (LinkedListNode ii = m_outstandingRequests.First; ii != null; ii = ii.Next)
{
if (ii.Value.Defunct)
{
count++;
}
}
return count;
}
}
}
///
/// Gets the number of good outstanding publish requests.
///
public int GoodPublishRequestCount
{
get
{
lock (m_outstandingRequests)
{
int count = 0;
for (LinkedListNode ii = m_outstandingRequests.First; ii != null; ii = ii.Next)
{
if (!ii.Value.Defunct && ii.Value.RequestTypeId == DataTypes.PublishRequest)
{
count++;
}
}
return count;
}
}
}
#endregion
#region Public Methods
///
/// Creates a new communication session with a server by invoking the CreateSession service
///
/// The configuration for the client application.
/// The endpoint for the server.
/// If set to true the discovery endpoint is used to update the endpoint description before connecting.
/// The name to assign to the session.
/// The timeout period for the session.
/// The identity.
/// The user identity to associate with the session.
/// The new session object
public static Session Create(
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
bool updateBeforeConnect,
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList preferredLocales)
{
return Create(configuration, endpoint, updateBeforeConnect, false, sessionName, sessionTimeout, identity, preferredLocales);
}
///
/// Creates a new communication session with a server by invoking the CreateSession service
///
/// The configuration for the client application.
/// The endpoint for the server.
/// If set to true the discovery endpoint is used to update the endpoint description before connecting.
/// If set to true then the domain in the certificate must match the endpoint used.
/// The name to assign to the session.
/// The timeout period for the session.
/// The user identity to associate with the session.
/// The preferred locales.
/// The new session object.
public static Session Create(
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
bool updateBeforeConnect,
bool checkDomain,
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList preferredLocales)
{
endpoint.UpdateBeforeConnect = updateBeforeConnect;
EndpointDescription endpointDescription = endpoint.Description;
// create the endpoint configuration (use the application configuration to provide default values).
EndpointConfiguration endpointConfiguration = endpoint.Configuration;
if (endpointConfiguration == null)
{
endpoint.Configuration = endpointConfiguration = EndpointConfiguration.Create(configuration);
}
// create message context.
ServiceMessageContext messageContext = configuration.CreateMessageContext();
// update endpoint description using the discovery endpoint.
if (endpoint.UpdateBeforeConnect)
{
BindingFactory bindingFactory = BindingFactory.Create(configuration, messageContext);
endpoint.UpdateFromServer(bindingFactory);
endpointDescription = endpoint.Description;
endpointConfiguration = endpoint.Configuration;
}
// checks the domains in the certificate.
if (checkDomain && endpoint.Description.ServerCertificate != null && endpoint.Description.ServerCertificate.Length > 0)
{
CheckCertificateDomain(endpoint);
}
X509Certificate2 clientCertificate = null;
//X509Certificate2Collection clientCertificateChain = null;
if (endpointDescription.SecurityPolicyUri != SecurityPolicies.None)
{
if (configuration.SecurityConfiguration.ApplicationCertificate == null)
{
throw ServiceResultException.Create( StatusCodes.BadConfigurationError, "ApplicationCertificate must be specified." );
}
clientCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find( true );
if( clientCertificate == null )
{
throw ServiceResultException.Create( StatusCodes.BadConfigurationError, "ApplicationCertificate cannot be found." );
}
//load certificate chain
//clientCertificateChain = new X509Certificate2Collection(clientCertificate);
//List issuers = new List();
//configuration.CertificateValidator.GetIssuers(clientCertificate, issuers);
//for (int i = 0; i < issuers.Count; i++)
//{
// clientCertificateChain.Add(issuers[i].Certificate);
//}
}
// initialize the channel which will be created with the server.
ITransportChannel channel = SessionChannel.Create(
configuration,
endpointDescription,
endpointConfiguration,
//clientCertificateChain,
clientCertificate,
messageContext);
// create the session object.
Session session = new Session(channel, configuration, endpoint, null);
// create the session.
try
{
session.Open( sessionName, sessionTimeout, identity, preferredLocales, checkDomain );
}
catch
{
session.Dispose();
throw;
}
return session;
}
private static void CheckCertificateDomain(ConfiguredEndpoint endpoint)
{
bool domainFound = false;
X509Certificate2 serverCertificate = new X509Certificate2(endpoint.Description.ServerCertificate);
// check the certificate domains.
IList domains = Utils.GetDomainsFromCertficate(serverCertificate);
if (domains != null)
{
string hostname = endpoint.EndpointUrl.DnsSafeHost;
if (hostname == "localhost" || hostname == "127.0.0.1")
{
hostname = System.Net.Dns.GetHostName();
}
for (int ii = 0; ii < domains.Count; ii++)
{
if (String.Compare(hostname, domains[ii], StringComparison.InvariantCultureIgnoreCase) == 0)
{
domainFound = true;
break;
}
}
}
if (!domainFound)
{
throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid);
}
}
///
/// Recreates a session based on a specified template.
///
/// The Session object to use as template
/// The new session object.
public static Session Recreate(Session template)
{
// create the channel object used to connect to the server.
ITransportChannel channel = SessionChannel.Create(
template.m_configuration,
template.m_endpoint.Description,
template.m_endpoint.Configuration,
template.m_instanceCertificate,
template.m_configuration.CreateMessageContext());
// create the session object.
Session session = new Session(channel, template, true);
try
{
// open the session.
session.Open(
template.m_sessionName,
(uint)template.m_sessionTimeout,
template.m_identity,
template.m_preferredLocales);
// create the subscriptions.
foreach (Subscription subscription in session.Subscriptions)
{
subscription.Create();
}
}
catch (Exception e)
{
session.Dispose();
throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", template.m_sessionName);
}
return session;
}
///
/// Used to handle renews of user identity tokens before reconnect.
///
public delegate IUserIdentity RenewUserIdentityEventHandler(Session session, IUserIdentity identity);
///
/// Raised before a reconnect operation completes.
///
public event RenewUserIdentityEventHandler RenewUserIdentity
{
add { m_RenewUserIdentity += value; }
remove { m_RenewUserIdentity -= value; }
}
private event RenewUserIdentityEventHandler m_RenewUserIdentity;
///
/// Reconnects to the server after a network failure.
///
public void Reconnect()
{
try
{
lock (SyncRoot)
{
// check if already connecting.
if (m_reconnecting)
{
Utils.Trace("Session is already attempting to reconnect.");
throw ServiceResultException.Create(
StatusCodes.BadInvalidState,
"Session is already attempting to reconnect.");
}
Utils.Trace("Session RECONNECT starting.");
m_reconnecting = true;
// stop keep alives.
if (m_keepAliveTimer != null)
{
m_keepAliveTimer.Dispose();
m_keepAliveTimer = null;
}
}
EndpointDescription endpoint = m_endpoint.Description;
// create the client signature.
byte[] dataToSign = Utils.Append(endpoint.ServerCertificate, m_serverNonce);
SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, endpoint.SecurityPolicyUri, dataToSign);
// check that the user identity is supported by the endpoint.
UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(m_identity.TokenType, m_identity.IssuedTokenType);
if (identityPolicy == null)
{
Utils.Trace("Endpoint does not supported the user identity type provided.");
throw ServiceResultException.Create(
StatusCodes.BadUserAccessDenied,
"Endpoint does not supported the user identity type provided.");
}
// select the security policy for the user token.
string securityPolicyUri = identityPolicy.SecurityPolicyUri;
if (String.IsNullOrEmpty(securityPolicyUri))
{
securityPolicyUri = endpoint.SecurityPolicyUri;
}
// need to refresh the identity (reprompt for password, refresh token).
if (m_RenewUserIdentity != null)
{
m_identity = m_RenewUserIdentity(this, m_identity);
}
// sign data with user token.
UserIdentityToken identityToken = m_identity.GetIdentityToken();
identityToken.PolicyId = identityPolicy.PolicyId;
SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri);
// encrypt token.
identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri);
// send the software certificates assigned to the client.
SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates();
Utils.Trace("Session REPLACING channel.");
// check if the channel supports reconnect.
if ((TransportChannel.SupportedFeatures & TransportChannelFeatures.Reconnect) != 0)
{
TransportChannel.Reconnect();
}
else
{
// initialize the channel which will be created with the server.
ITransportChannel channel = SessionChannel.Create(
m_configuration,
m_endpoint.Description,
m_endpoint.Configuration,
m_instanceCertificate,
MessageContext);
// disposes the existing channel.
TransportChannel = channel;
}
// reactivate session.
byte[] serverNonce = null;
StatusCodeCollection certificateResults = null;
DiagnosticInfoCollection certificateDiagnosticInfos = null;
Utils.Trace("Session RE-ACTIVATING session.");
IAsyncResult result = BeginActivateSession(
null,
clientSignature,
null,
m_preferredLocales,
new ExtensionObject(identityToken),
userTokenSignature,
null,
null);
if (!result.AsyncWaitHandle.WaitOne(5000, false))
{
Utils.Trace("WARNING: ACTIVATE SESSION timed out. {1}/{0}", OutstandingRequestCount, GoodPublishRequestCount);
}
EndActivateSession(
result,
out serverNonce,
out certificateResults,
out certificateDiagnosticInfos);
int publishCount = 0;
lock (SyncRoot)
{
Utils.Trace("Session RECONNECT completed successfully.");
m_serverNonce = serverNonce;
m_reconnecting = false;
publishCount = m_subscriptions.Count;
}
// refill pipeline.
for (int ii = 0; ii < publishCount; ii++)
{
BeginPublish(OperationTimeout);
}
StartKeepAliveTimer();
}
finally
{
m_reconnecting = false;
}
}
///
/// Saves all the subscriptions of the session.
///
/// The file path.
public void Save(string filePath)
{
Save(filePath, Subscriptions);
}
///
/// Saves a set of subscriptions.
///
public void Save(string filePath, IEnumerable subscriptions)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.Encoding = Encoding.UTF8;
XmlWriter writer = XmlWriter.Create(filePath, settings);
SubscriptionCollection subscriptionList = new SubscriptionCollection(subscriptions);
try
{
DataContractSerializer serializer = new DataContractSerializer(typeof(SubscriptionCollection));
serializer.WriteObject(writer, subscriptionList);
}
finally
{
writer.Close();
}
}
///
/// Load the list of subscriptions saved in a file.
///
/// The file path.
/// The list of loaded subscriptions
public IEnumerable Load(string filePath)
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Document;
settings.CloseInput = true;
XmlReader reader = XmlReader.Create(filePath, settings);
try
{
DataContractSerializer serializer = new DataContractSerializer(typeof(SubscriptionCollection));
SubscriptionCollection subscriptions = (SubscriptionCollection)serializer.ReadObject(reader);
foreach (Subscription subscription in subscriptions)
{
AddSubscription(subscription);
}
return subscriptions;
}
finally
{
reader.Close();
}
}
///
/// Updates the local copy of the server's namespace uri and server uri tables.
///
public void FetchNamespaceTables()
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
// request namespace array.
ReadValueId valueId = new ReadValueId();
valueId.NodeId = Variables.Server_NamespaceArray;
valueId.AttributeId = Attributes.Value;
nodesToRead.Add(valueId);
// request server array.
valueId = new ReadValueId();
valueId.NodeId = Variables.Server_ServerArray;
valueId.AttributeId = Attributes.Value;
nodesToRead.Add(valueId);
// read from server.
DataValueCollection values = null;
DiagnosticInfoCollection diagnosticInfos = null;
ResponseHeader responseHeader = this.Read(
null,
0,
TimestampsToReturn.Both,
nodesToRead,
out values,
out diagnosticInfos);
ValidateResponse(values, nodesToRead);
ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
// validate namespace array.
ServiceResult result = ValidateDataValue(values[0], typeof(string[]), 0, diagnosticInfos, responseHeader);
if (ServiceResult.IsBad(result))
{
throw new ServiceResultException(result);
}
m_namespaceUris.Update((string[])values[0].Value);
// validate server array.
result = ValidateDataValue(values[1], typeof(string[]), 1, diagnosticInfos, responseHeader);
if (ServiceResult.IsBad(result))
{
throw new ServiceResultException(result);
}
m_serverUris.Update((string[])values[1].Value);
}
///
/// Updates the cache with the type and its subtypes.
///
///
/// This method can be used to ensure the TypeTree is populated.
///
public void FetchTypeTree(ExpandedNodeId typeId)
{
Node node = NodeCache.Find(typeId) as Node;
if (node != null)
{
foreach (IReference reference in node.Find(ReferenceTypeIds.HasSubtype, false))
{
FetchTypeTree(reference.TargetId);
}
}
}
///
/// Returns the available encodings for a node
///
/// The variable node.
///
public ReferenceDescriptionCollection ReadAvailableEncodings(NodeId variableId)
{
VariableNode variable = NodeCache.Find(variableId) as VariableNode;
if (variable == null)
{
throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "NodeId does not refer to a valid variable node.");
}
// no encodings available if there was a problem reading the data type for the node.
if (NodeId.IsNull(variable.DataType))
{
return new ReferenceDescriptionCollection();
}
// no encodings for non-structures.
if (!TypeTree.IsTypeOf(variable.DataType, DataTypes.Structure))
{
return new ReferenceDescriptionCollection();
}
// look for cached values.
IList encodings = NodeCache.Find(variableId, ReferenceTypeIds.HasEncoding, false, true);
if (encodings.Count > 0)
{
ReferenceDescriptionCollection references = new ReferenceDescriptionCollection();
foreach (INode encoding in encodings)
{
ReferenceDescription reference = new ReferenceDescription();
reference.ReferenceTypeId = ReferenceTypeIds.HasEncoding;
reference.IsForward = true;
reference.NodeId = encoding.NodeId;
reference.NodeClass = encoding.NodeClass;
reference.BrowseName = encoding.BrowseName;
reference.DisplayName = encoding.DisplayName;
reference.TypeDefinition = encoding.TypeDefinitionId;
references.Add(reference);
}
return references;
}
Browser browser = new Browser(this);
browser.BrowseDirection = BrowseDirection.Forward;
browser.ReferenceTypeId = ReferenceTypeIds.HasEncoding;
browser.IncludeSubtypes = false;
browser.NodeClassMask = 0;
return browser.Browse(variable.DataType);
}
///
/// Returns the data description for the encoding.
///
/// The encoding Id.
///
public ReferenceDescription FindDataDescription(NodeId encodingId)
{
Browser browser = new Browser(this);
browser.BrowseDirection = BrowseDirection.Forward;
browser.ReferenceTypeId = ReferenceTypeIds.HasDescription;
browser.IncludeSubtypes = false;
browser.NodeClassMask = 0;
ReferenceDescriptionCollection references = browser.Browse(encodingId);
if (references.Count == 0)
{
throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Encoding does not refer to a valid data description.");
}
return references[0];
}
///
/// Returns the data dictionary that constains the description.
///
/// The description id.
///
public DataDictionary FindDataDictionary(NodeId descriptionId)
{
// check if the dictionary has already been loaded.
foreach (DataDictionary dictionary in m_dictionaries.Values)
{
if (dictionary.Contains(descriptionId))
{
return dictionary;
}
}
// find the dictionary for the description.
Browser browser = new Browser(this);
browser.BrowseDirection = BrowseDirection.Inverse;
browser.ReferenceTypeId = ReferenceTypeIds.HasComponent;
browser.IncludeSubtypes = false;
browser.NodeClassMask = 0;
ReferenceDescriptionCollection references = browser.Browse(descriptionId);
if (references.Count == 0)
{
throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Description does not refer to a valid data dictionary.");
}
// load the dictionary.
NodeId dictionaryId = ExpandedNodeId.ToNodeId(references[0].NodeId, m_namespaceUris);
DataDictionary dictionaryToLoad = new DataDictionary(this);
dictionaryToLoad.Load(references[0]);
m_dictionaries[dictionaryId] = dictionaryToLoad;
return dictionaryToLoad;
}
///
/// Reads the values for the node attributes and returns a node object.
///
/// The nodeId.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public Node ReadNode(NodeId nodeId)
{
// build list of attributes.
SortedDictionary attributes = new SortedDictionary();
attributes.Add(Attributes.NodeId, null);
attributes.Add(Attributes.NodeClass, null);
attributes.Add(Attributes.BrowseName, null);
attributes.Add(Attributes.DisplayName, null);
attributes.Add(Attributes.Description, null);
attributes.Add(Attributes.WriteMask, null);
attributes.Add(Attributes.UserWriteMask, null);
attributes.Add(Attributes.DataType, null);
attributes.Add(Attributes.ValueRank, null);
attributes.Add(Attributes.ArrayDimensions, null);
attributes.Add(Attributes.AccessLevel, null);
attributes.Add(Attributes.UserAccessLevel, null);
attributes.Add(Attributes.Historizing, null);
attributes.Add(Attributes.MinimumSamplingInterval, null);
attributes.Add(Attributes.EventNotifier, null);
attributes.Add(Attributes.Executable, null);
attributes.Add(Attributes.UserExecutable, null);
attributes.Add(Attributes.IsAbstract, null);
attributes.Add(Attributes.InverseName, null);
attributes.Add(Attributes.Symmetric, null);
attributes.Add(Attributes.ContainsNoLoops, null);
// build list of values to read.
ReadValueIdCollection itemsToRead = new ReadValueIdCollection();
foreach (uint attributeId in attributes.Keys)
{
ReadValueId itemToRead = new ReadValueId();
itemToRead.NodeId = nodeId;
itemToRead.AttributeId = attributeId;
itemsToRead.Add(itemToRead);
}
// read from server.
DataValueCollection values = null;
DiagnosticInfoCollection diagnosticInfos = null;
ResponseHeader responseHeader = Read(
null,
0,
TimestampsToReturn.Neither,
itemsToRead,
out values,
out diagnosticInfos);
ClientBase.ValidateResponse(values, itemsToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead);
// process results.
int? nodeClass = null;
for (int ii = 0; ii < itemsToRead.Count; ii++)
{
uint attributeId = itemsToRead[ii].AttributeId;
// the node probably does not exist if the node class is not found.
if (attributeId == Attributes.NodeClass)
{
if (!DataValue.IsGood(values[ii]))
{
throw ServiceResultException.Create(values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
}
// check for valid node class.
nodeClass = values[ii].Value as int?;
if (nodeClass == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", values[ii].Value);
}
}
else
{
if (!DataValue.IsGood(values[ii]))
{
// check for unsupported attributes.
if (values[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
{
continue;
}
// all supported attributes must be readable.
if (attributeId != Attributes.Value)
{
throw ServiceResultException.Create(values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
}
}
}
attributes[attributeId] = values[ii];
}
Node node = null;
DataValue value = null;
switch ((NodeClass)nodeClass.Value)
{
default:
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", nodeClass.Value);
}
case NodeClass.Object:
{
ObjectNode objectNode = new ObjectNode();
value = attributes[Attributes.EventNotifier];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Object does not support the EventNotifier attribute.");
}
objectNode.EventNotifier = (byte)attributes[Attributes.EventNotifier].GetValue(typeof(byte));
node = objectNode;
break;
}
case NodeClass.ObjectType:
{
ObjectTypeNode objectTypeNode = new ObjectTypeNode();
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ObjectType does not support the IsAbstract attribute.");
}
objectTypeNode.IsAbstract = (bool)attributes[Attributes.IsAbstract].GetValue(typeof(bool));
node = objectTypeNode;
break;
}
case NodeClass.Variable:
{
VariableNode variableNode = new VariableNode();
// DataType Attribute
value = attributes[Attributes.DataType];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the DataType attribute.");
}
variableNode.DataType = (NodeId)attributes[Attributes.DataType].GetValue(typeof(NodeId));
// ValueRank Attribute
value = attributes[Attributes.ValueRank];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the ValueRank attribute.");
}
variableNode.ValueRank = (int)attributes[Attributes.ValueRank].GetValue(typeof(int));
// ArrayDimensions Attribute
value = attributes[Attributes.ArrayDimensions];
if (value != null)
{
if (value.Value == null)
{
variableNode.ArrayDimensions = new uint[0];
}
else
{
variableNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[]));
}
}
// AccessLevel Attribute
value = attributes[Attributes.AccessLevel];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the AccessLevel attribute.");
}
variableNode.AccessLevel = (byte)attributes[Attributes.AccessLevel].GetValue(typeof(byte));
// UserAccessLevel Attribute
value = attributes[Attributes.UserAccessLevel];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the UserAccessLevel attribute.");
}
variableNode.UserAccessLevel = (byte)attributes[Attributes.UserAccessLevel].GetValue(typeof(byte));
// Historizing Attribute
value = attributes[Attributes.Historizing];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the Historizing attribute.");
}
variableNode.Historizing = (bool)attributes[Attributes.Historizing].GetValue(typeof(bool));
// MinimumSamplingInterval Attribute
value = attributes[Attributes.MinimumSamplingInterval];
if (value != null)
{
variableNode.MinimumSamplingInterval = Convert.ToDouble(attributes[Attributes.MinimumSamplingInterval].Value);
}
node = variableNode;
break;
}
case NodeClass.VariableType:
{
VariableTypeNode variableTypeNode = new VariableTypeNode();
// IsAbstract Attribute
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the IsAbstract attribute.");
}
variableTypeNode.IsAbstract = (bool)attributes[Attributes.IsAbstract].GetValue(typeof(bool));
// DataType Attribute
value = attributes[Attributes.DataType];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the DataType attribute.");
}
variableTypeNode.DataType = (NodeId)attributes[Attributes.DataType].GetValue(typeof(NodeId));
// ValueRank Attribute
value = attributes[Attributes.ValueRank];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the ValueRank attribute.");
}
variableTypeNode.ValueRank = (int)attributes[Attributes.ValueRank].GetValue(typeof(int));
// ArrayDimensions Attribute
value = attributes[Attributes.ArrayDimensions];
if (value != null && value.Value != null)
{
variableTypeNode.ArrayDimensions = (uint[])attributes[Attributes.ArrayDimensions].GetValue(typeof(uint[]));
}
node = variableTypeNode;
break;
}
case NodeClass.Method:
{
MethodNode methodNode = new MethodNode();
// Executable Attribute
value = attributes[Attributes.Executable];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the Executable attribute.");
}
methodNode.Executable = (bool)attributes[Attributes.Executable].GetValue(typeof(bool));
// UserExecutable Attribute
value = attributes[Attributes.UserExecutable];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the UserExecutable attribute.");
}
methodNode.UserExecutable = (bool)attributes[Attributes.UserExecutable].GetValue(typeof(bool));
node = methodNode;
break;
}
case NodeClass.DataType:
{
DataTypeNode dataTypeNode = new DataTypeNode();
// IsAbstract Attribute
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "DataType does not support the IsAbstract attribute.");
}
dataTypeNode.IsAbstract = (bool)attributes[Attributes.IsAbstract].GetValue(typeof(bool));
node = dataTypeNode;
break;
}
case NodeClass.ReferenceType:
{
ReferenceTypeNode referenceTypeNode = new ReferenceTypeNode();
// IsAbstract Attribute
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the IsAbstract attribute.");
}
referenceTypeNode.IsAbstract = (bool)attributes[Attributes.IsAbstract].GetValue(typeof(bool));
// Symmetric Attribute
value = attributes[Attributes.Symmetric];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the Symmetric attribute.");
}
referenceTypeNode.Symmetric = (bool)attributes[Attributes.IsAbstract].GetValue(typeof(bool));
// InverseName Attribute
value = attributes[Attributes.InverseName];
if (value != null && value.Value != null)
{
referenceTypeNode.InverseName = (LocalizedText)attributes[Attributes.InverseName].GetValue(typeof(LocalizedText));
}
node = referenceTypeNode;
break;
}
case NodeClass.View:
{
ViewNode viewNode = new ViewNode();
// EventNotifier Attribute
value = attributes[Attributes.EventNotifier];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the EventNotifier attribute.");
}
viewNode.EventNotifier = (byte)attributes[Attributes.EventNotifier].GetValue(typeof(byte));
// ContainsNoLoops Attribute
value = attributes[Attributes.ContainsNoLoops];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the ContainsNoLoops attribute.");
}
viewNode.ContainsNoLoops = (bool)attributes[Attributes.ContainsNoLoops].GetValue(typeof(bool));
node = viewNode;
break;
}
}
// NodeId Attribute
value = attributes[Attributes.NodeId];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the NodeId attribute.");
}
node.NodeId = (NodeId)attributes[Attributes.NodeId].GetValue(typeof(NodeId));
node.NodeClass = (NodeClass)nodeClass.Value;
// BrowseName Attribute
value = attributes[Attributes.BrowseName];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the BrowseName attribute.");
}
node.BrowseName = (QualifiedName)attributes[Attributes.BrowseName].GetValue(typeof(QualifiedName));
// DisplayName Attribute
value = attributes[Attributes.DisplayName];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the DisplayName attribute.");
}
node.DisplayName = (LocalizedText)attributes[Attributes.DisplayName].GetValue(typeof(LocalizedText));
// Description Attribute
value = attributes[Attributes.Description];
if (value != null && value.Value != null)
{
node.Description = (LocalizedText)attributes[Attributes.Description].GetValue(typeof(LocalizedText));
}
// WriteMask Attribute
value = attributes[Attributes.WriteMask];
if (value != null)
{
node.WriteMask = (uint)attributes[Attributes.WriteMask].GetValue(typeof(uint));
}
// UserWriteMask Attribute
value = attributes[Attributes.UserWriteMask];
if (value != null)
{
node.WriteMask = (uint)attributes[Attributes.UserWriteMask].GetValue(typeof(uint));
}
return node;
}
///
/// Reads the value for a node.
///
/// The node Id.
///
public DataValue ReadValue(NodeId nodeId)
{
ReadValueId itemToRead = new ReadValueId();
itemToRead.NodeId = nodeId;
itemToRead.AttributeId = Attributes.Value;
ReadValueIdCollection itemsToRead = new ReadValueIdCollection();
itemsToRead.Add(itemToRead);
// read from server.
DataValueCollection values = null;
DiagnosticInfoCollection diagnosticInfos = null;
ResponseHeader responseHeader = Read(
null,
0,
TimestampsToReturn.Both,
itemsToRead,
out values,
out diagnosticInfos);
ClientBase.ValidateResponse(values, itemsToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead);
if (StatusCode.IsBad(values[0].StatusCode))
{
ServiceResult result = ClientBase.GetResult(values[0].StatusCode, 0, diagnosticInfos, responseHeader);
throw new ServiceResultException(result);
}
return values[0];
}
///
/// Reads the value for a node an checks that it is the specified type.
///
/// The node id.
/// The expected type.
///
public object ReadValue(NodeId nodeId, Type expectedType)
{
DataValue dataValue = ReadValue(nodeId);
object value = dataValue.Value;
if (expectedType != null)
{
ExtensionObject extension = value as ExtensionObject;
if (extension != null)
{
value = extension.Body;
}
if (!expectedType.IsInstanceOfType(value))
{
throw ServiceResultException.Create(
StatusCodes.BadTypeMismatch,
"Server returned value unexpected type: {0}",
(value != null)?value.GetType().Name:"(null)");
}
}
return value;
}
///
/// Fetches all references for the specified node.
///
/// The node id.
///
public ReferenceDescriptionCollection FetchReferences(NodeId nodeId)
{
// browse for all references.
byte[] continuationPoint;
ReferenceDescriptionCollection descriptions;
Browse(
null,
null,
nodeId,
0,
BrowseDirection.Both,
null,
true,
0,
out continuationPoint,
out descriptions);
// process any continuation point.
while (continuationPoint != null)
{
byte[] revisedContinuationPoint;
ReferenceDescriptionCollection additionalDescriptions;
BrowseNext(
null,
false,
continuationPoint,
out revisedContinuationPoint,
out additionalDescriptions);
continuationPoint = revisedContinuationPoint;
descriptions.AddRange(additionalDescriptions);
}
return descriptions;
}
///
/// Establishes a session with the server.
///
/// The name to assign to the session.
/// The user identity.
public void Open(
string sessionName,
IUserIdentity identity)
{
Open(sessionName, 0, identity, null);
}
///
/// Establishes a session with the server.
///
/// The name to assign to the session.
/// The session timeout.
/// The user identity.
/// The list of preferred locales.
public void Open(
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList preferredLocales)
{
Open(sessionName, sessionTimeout, identity, preferredLocales, true);
}
///
/// Establishes a session with the server.
///
/// The name to assign to the session.
/// The session timeout.
/// The user identity.
/// The list of preferred locales.
/// If set to true then the domain in the certificate must match the endpoint used.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public void Open(
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList preferredLocales,
bool checkDomain)
{
// check connection state.
lock (SyncRoot)
{
if (Connected)
{
throw new ServiceResultException(StatusCodes.BadInvalidState, "Already connected to server.");
}
}
string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
// get the identity token.
if (identity == null)
{
identity = new UserIdentity();
}
// get identity token.
UserIdentityToken identityToken = identity.GetIdentityToken();
// check that the user identity is supported by the endpoint.
UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId);
if (identityPolicy == null)
{
// try looking up by TokenType if the policy id was not found.
identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType);
if (identityPolicy == null)
{
throw ServiceResultException.Create(
StatusCodes.BadUserAccessDenied,
"Endpoint does not supported the user identity type provided.");
}
identityToken.PolicyId = identityPolicy.PolicyId;
}
bool requireEncryption = securityPolicyUri != SecurityPolicies.None;
if (!requireEncryption)
{
requireEncryption = identityPolicy.SecurityPolicyUri != SecurityPolicies.None &&
!String.IsNullOrEmpty(identityPolicy.SecurityPolicyUri);
}
// validate the server certificate.
X509Certificate2 serverCertificate = null;
byte[] certificateData = m_endpoint.Description.ServerCertificate;
if (certificateData != null && certificateData.Length > 0 && requireEncryption)
{
serverCertificate = Utils.ParseCertificateBlob(certificateData);
m_configuration.CertificateValidator.Validate(serverCertificate);
if(checkDomain)
{
CheckCertificateDomain(m_endpoint);
}
//X509Certificate2Collection certificateChain = Utils.ParseCertificateChainBlob(certificateData);
//if (certificateChain.Count > 0)
// serverCertificate = certificateChain[0];
//m_configuration.CertificateValidator.Validate(certificateChain);
}
// create a nonce.
byte[] clientNonce = new byte[m_configuration.SecurityConfiguration.NonceLength];
new RNGCryptoServiceProvider().GetBytes(clientNonce);
NodeId sessionId = null;
NodeId sessionCookie = null;
byte[] serverNonce = new byte[0];
byte[] serverCertificateData = new byte[0];
SignatureData serverSignature = null;
EndpointDescriptionCollection serverEndpoints = null;
SignedSoftwareCertificateCollection serverSoftwareCertificates = null;
// send the application instance certificate for the client.
byte[] clientCertificateData = m_instanceCertificate != null ? m_instanceCertificate.RawData : null;
//byte[] clientCertificateData = null;
//if (m_instanceCertificateChain != null)
//{
// List clientCertificateChainData = new List();
// for (int i = 0; i < m_instanceCertificateChain.Count; i++)
// {
// clientCertificateChainData.AddRange(m_instanceCertificateChain[i].RawData);
// }
// clientCertificateData = clientCertificateChainData.ToArray();
//}
ApplicationDescription clientDescription = new ApplicationDescription();
clientDescription.ApplicationUri = m_configuration.ApplicationUri;
clientDescription.ApplicationName = m_configuration.ApplicationName;
clientDescription.ApplicationType = ApplicationType.Client;
clientDescription.ProductUri = m_configuration.ProductUri;
if (sessionTimeout == 0)
{
sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout;
}
bool successCreateSession = false;
//if security none, first try to connect without certificate
if (m_endpoint.Description.SecurityPolicyUri == SecurityPolicies.None)
{
//first try to connect with client certificate NULL
try
{
CreateSession(
null,
clientDescription,
m_endpoint.Description.Server.ApplicationUri,
m_endpoint.EndpointUrl.ToString(),
sessionName,
clientNonce,
null,
sessionTimeout,
(uint)MessageContext.MaxMessageSize,
out sessionId,
out sessionCookie,
out m_sessionTimeout,
out serverNonce,
out serverCertificateData,
out serverEndpoints,
out serverSoftwareCertificates,
out serverSignature,
out m_maxRequestMessageSize);
successCreateSession = true;
}
catch(Exception ex)
{
Utils.Trace("Create session failed with client certificate NULL. " + ex.Message);
successCreateSession = false;
}
}
if (!successCreateSession)
{
CreateSession(
null,
clientDescription,
m_endpoint.Description.Server.ApplicationUri,
m_endpoint.EndpointUrl.ToString(),
sessionName,
clientNonce,
clientCertificateData,
sessionTimeout,
(uint)MessageContext.MaxMessageSize,
out sessionId,
out sessionCookie,
out m_sessionTimeout,
out serverNonce,
out serverCertificateData,
out serverEndpoints,
out serverSoftwareCertificates,
out serverSignature,
out m_maxRequestMessageSize);
}
// save session id.
lock (SyncRoot)
{
base.SessionCreated(sessionId, sessionCookie);
}
Utils.Trace("Revised session timeout value: {0}. ", m_sessionTimeout);
Utils.Trace("Max response message size value: {0}. Max request message size: {1} ", MessageContext.MaxMessageSize, m_maxRequestMessageSize);
//we need to call CloseSession if CreateSession was successful but some other exception is thrown
try
{
// verify that the server returned the same instance certificate.
if (serverCertificateData != null && !Utils.IsEqual(serverCertificateData, m_endpoint.Description.ServerCertificate))
{
throw ServiceResultException.Create(
StatusCodes.BadCertificateInvalid,
"Server did not return the certificate used to create the secure channel." );
}
if (serverSignature == null || serverSignature.Signature == null)
{
Utils.Trace("Server signature is null or empty.");
//throw ServiceResultException.Create(
// StatusCodes.BadSecurityChecksFailed,
// "Server signature is null or empty.");
}
if (m_expectedServerEndpoints != null && m_expectedServerEndpoints.Count > 0)
{
// verify that the list of endpoints returned by CreateSession matches the list returned at GetEndpoints.
if (m_expectedServerEndpoints.Count != serverEndpoints.Count)
{
throw ServiceResultException.Create(
StatusCodes.BadSecurityChecksFailed,
"Server did not return a number of ServerEndpoints that matches the one from GetEndpoints.");
}
for (int ii = 0; ii < serverEndpoints.Count; ii++)
{
EndpointDescription serverEndpoint = serverEndpoints[ii];
EndpointDescription expectedServerEndpoint = m_expectedServerEndpoints[ii];
if (serverEndpoint.SecurityMode != expectedServerEndpoint.SecurityMode ||
serverEndpoint.SecurityPolicyUri != expectedServerEndpoint.SecurityPolicyUri ||
serverEndpoint.TransportProfileUri != expectedServerEndpoint.TransportProfileUri ||
serverEndpoint.SecurityLevel != expectedServerEndpoint.SecurityLevel)
{
throw ServiceResultException.Create(
StatusCodes.BadSecurityChecksFailed,
"The list of ServerEndpoints returned at CreateSession does not match the list from GetEndpoints.");
}
if (serverEndpoint.UserIdentityTokens.Count != expectedServerEndpoint.UserIdentityTokens.Count)
{
throw ServiceResultException.Create(
StatusCodes.BadSecurityChecksFailed,
"The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints.");
}
for (int jj = 0; jj < serverEndpoint.UserIdentityTokens.Count; jj++)
{
if (!serverEndpoint.UserIdentityTokens[jj].IsEqual(expectedServerEndpoint.UserIdentityTokens[jj]))
{
throw ServiceResultException.Create(
StatusCodes.BadSecurityChecksFailed,
"The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints.");
}
}
}
}
// find the matching description (TBD - check domains against certificate).
bool found = false;
Uri expectedUrl = Utils.ParseUri( m_endpoint.Description.EndpointUrl );
if (expectedUrl != null)
{
for (int ii = 0; ii < serverEndpoints.Count; ii++)
{
EndpointDescription serverEndpoint = serverEndpoints[ii];
Uri actualUrl = Utils.ParseUri(serverEndpoint.EndpointUrl);
if (actualUrl != null && actualUrl.Scheme == expectedUrl.Scheme)
{
if (serverEndpoint.SecurityPolicyUri == m_endpoint.Description.SecurityPolicyUri)
{
if (serverEndpoint.SecurityMode == m_endpoint.Description.SecurityMode)
{
// ensure endpoint has up to date information.
m_endpoint.Description.Server.ApplicationName = serverEndpoint.Server.ApplicationName;
m_endpoint.Description.Server.ApplicationUri = serverEndpoint.Server.ApplicationUri;
m_endpoint.Description.Server.ApplicationType = serverEndpoint.Server.ApplicationType;
m_endpoint.Description.Server.ProductUri = serverEndpoint.Server.ProductUri;
m_endpoint.Description.TransportProfileUri = serverEndpoint.TransportProfileUri;
m_endpoint.Description.UserIdentityTokens = serverEndpoint.UserIdentityTokens;
found = true;
break;
}
}
}
}
}
// could be a security risk.
if( !found )
{
throw ServiceResultException.Create(
StatusCodes.BadSecurityChecksFailed,
"Server did not return an EndpointDescription that matched the one used to create the secure channel." );
}
// validate the server's signature.
byte[] dataToSign = Utils.Append( clientCertificateData, clientNonce );
if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature))
{
throw ServiceResultException.Create(
StatusCodes.BadApplicationSignatureInvalid,
"Server did not provide a correct signature for the nonce data provided by the client." );
}
// get a validator to check certificates provided by server.
CertificateValidator validator = m_configuration.CertificateValidator;
// validate software certificates.
List softwareCertificates = new List();
foreach( SignedSoftwareCertificate signedCertificate in serverSoftwareCertificates )
{
SoftwareCertificate softwareCertificate = null;
ServiceResult result = SoftwareCertificate.Validate(
validator,
signedCertificate.CertificateData,
out softwareCertificate );
if( ServiceResult.IsBad( result ) )
{
OnSoftwareCertificateError( signedCertificate, result );
}
softwareCertificates.Add( softwareCertificate );
}
// check if software certificates meet application requirements.
ValidateSoftwareCertificates( softwareCertificates );
// create the client signature.
dataToSign = Utils.Append( serverCertificateData, serverNonce );
SignatureData clientSignature = SecurityPolicies.Sign( m_instanceCertificate, securityPolicyUri, dataToSign );
// select the security policy for the user token.
securityPolicyUri = identityPolicy.SecurityPolicyUri;
if (String.IsNullOrEmpty(securityPolicyUri))
{
securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
}
// sign data with user token.
SignatureData userTokenSignature = identityToken.Sign( dataToSign, securityPolicyUri );
// encrypt token.
identityToken.Encrypt( serverCertificate, serverNonce, securityPolicyUri );
// send the software certificates assigned to the client.
SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates();
// copy the preferred locales if provided.
if( preferredLocales != null && preferredLocales.Count > 0 )
{
m_preferredLocales = new StringCollection( preferredLocales );
}
StatusCodeCollection certificateResults = null;
DiagnosticInfoCollection certificateDiagnosticInfos = null;
// activate session.
ActivateSession(
null,
clientSignature,
clientSoftwareCertificates,
m_preferredLocales,
new ExtensionObject( identityToken ),
userTokenSignature,
out serverNonce,
out certificateResults,
out certificateDiagnosticInfos );
if (certificateResults != null)
{
for (int i = 0; i < certificateResults.Count; i++)
{
Utils.Trace("ActivateSession result[{0}] = {1}", i, certificateResults[i]);
}
}
if (certificateResults == null || certificateResults.Count == 0)
{
Utils.Trace("Empty results were received for the ActivateSession call.");
}
// fetch namespaces.
FetchNamespaceTables();
lock( SyncRoot )
{
// save nonces.
m_sessionName = sessionName;
m_identity = identity;
m_serverNonce = serverNonce;
m_serverCertificate = serverCertificate;
// update system context.
m_systemContext.PreferredLocales = m_preferredLocales;
m_systemContext.SessionId = this.SessionId;
m_systemContext.UserIdentity = identity;
}
// start keep alive thread.
StartKeepAliveTimer();
}
catch
{
try
{
CloseSession( null, false );
CloseChannel();
}
catch(Exception e)
{
Utils.Trace("Cleanup: CloseSession() or CloseChannel() raised exception. " + e.Message);
}
finally
{
SessionCreated( null, null );
}
throw;
}
}
///
/// Updates the preferred locales used for the session.
///
/// The preferred locales.
public void ChangePreferredLocales(StringCollection preferredLocales)
{
UpdateSession(Identity, preferredLocales);
}
///
/// Updates the user identity and/or locales used for the session.
///
/// The user identity.
/// The preferred locales.
public void UpdateSession(IUserIdentity identity, StringCollection preferredLocales)
{
byte[] serverNonce = null;
lock (SyncRoot)
{
// check connection state.
if (!Connected)
{
throw new ServiceResultException(StatusCodes.BadInvalidState, "Not connected to server.");
}
// get current nonce.
serverNonce = m_serverNonce;
if (preferredLocales == null)
{
preferredLocales = m_preferredLocales;
}
}
// get the identity token.
UserIdentityToken identityToken = null;
SignatureData userTokenSignature = null;
string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
// create the client signature.
byte[] serverCertificateData = null;
if (m_serverCertificate != null)
{
serverCertificateData = m_serverCertificate.GetRawCertData();
}
// create the client signature.
byte[] dataToSign = Utils.Append(serverCertificateData, serverNonce);
SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign);
// choose a default token.
if (identity == null)
{
identity = new UserIdentity();
}
// check that the user identity is supported by the endpoint.
UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType);
if (identityPolicy == null)
{
throw ServiceResultException.Create(
StatusCodes.BadUserAccessDenied,
"Endpoint does not supported the user identity type provided.");
}
// select the security policy for the user token.
securityPolicyUri = identityPolicy.SecurityPolicyUri;
if (String.IsNullOrEmpty(securityPolicyUri))
{
securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
}
// sign data with user token.
identityToken = identity.GetIdentityToken();
identityToken.PolicyId = identityPolicy.PolicyId;
userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri);
// encrypt token.
identityToken.Encrypt(m_serverCertificate, serverNonce, securityPolicyUri);
// send the software certificates assigned to the client.
SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates();
StatusCodeCollection certificateResults = null;
DiagnosticInfoCollection certificateDiagnosticInfos = null;
// activate session.
ActivateSession(
null,
clientSignature,
clientSoftwareCertificates,
preferredLocales,
new ExtensionObject(identityToken),
userTokenSignature,
out serverNonce,
out certificateResults,
out certificateDiagnosticInfos);
// save nonce and new values.
lock (SyncRoot)
{
if (identity != null)
{
m_identity = identity;
}
m_serverNonce = serverNonce;
m_preferredLocales = preferredLocales;
// update system context.
m_systemContext.PreferredLocales = m_preferredLocales;
m_systemContext.SessionId = this.SessionId;
m_systemContext.UserIdentity = identity;
}
}
///
/// Finds the NodeIds for the components for an instance.
///
public void FindComponentIds(
NodeId instanceId,
IList componentPaths,
out NodeIdCollection componentIds,
out List errors)
{
componentIds = new NodeIdCollection();
errors = new List();
// build list of paths to translate.
BrowsePathCollection pathsToTranslate = new BrowsePathCollection();
for (int ii = 0; ii < componentPaths.Count; ii++)
{
BrowsePath pathToTranslate = new BrowsePath();
pathToTranslate.StartingNode = instanceId;
pathToTranslate.RelativePath = RelativePath.Parse(componentPaths[ii], TypeTree);
pathsToTranslate.Add(pathToTranslate);
}
// translate the paths.
BrowsePathResultCollection results = null;
DiagnosticInfoCollection diagnosticInfos = null;
ResponseHeader responseHeader = TranslateBrowsePathsToNodeIds(
null,
pathsToTranslate,
out results,
out diagnosticInfos);
// verify that the server returned the correct number of results.
ClientBase.ValidateResponse(results, pathsToTranslate);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, pathsToTranslate);
for (int ii = 0; ii < componentPaths.Count; ii++)
{
componentIds.Add(NodeId.Null);
errors.Add(ServiceResult.Good);
// process any diagnostics associated with any error.
if (StatusCode.IsBad(results[ii].StatusCode))
{
errors[ii] = new ServiceResult(results[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
continue;
}
// Expecting exact one NodeId for a local node.
// Report an error if the server returns anything other than that.
if (results[ii].Targets.Count == 0)
{
errors[ii] = ServiceResult.Create(
StatusCodes.BadTargetNodeIdInvalid,
"Could not find target for path: {0}.",
componentPaths[ii]);
continue;
}
if (results[ii].Targets.Count != 1)
{
errors[ii] = ServiceResult.Create(
StatusCodes.BadTooManyMatches,
"Too many matches found for path: {0}.",
componentPaths[ii]);
continue;
}
if (results[ii].Targets[0].RemainingPathIndex != UInt32.MaxValue)
{
errors[ii] = ServiceResult.Create(
StatusCodes.BadTargetNodeIdInvalid,
"Cannot follow path to external server: {0}.",
componentPaths[ii]);
continue;
}
if (NodeId.IsNull(results[ii].Targets[0].TargetId))
{
errors[ii] = ServiceResult.Create(
StatusCodes.BadUnexpectedError,
"Server returned a null NodeId for path: {0}.",
componentPaths[ii]);
continue;
}
if (results[ii].Targets[0].TargetId.IsAbsolute)
{
errors[ii] = ServiceResult.Create(
StatusCodes.BadUnexpectedError,
"Server returned a remote node for path: {0}.",
componentPaths[ii]);
continue;
}
// suitable target found.
componentIds[ii] = ExpandedNodeId.ToNodeId(results[ii].Targets[0].TargetId, m_namespaceUris);
}
}
///
/// Reads the values for a set of variables.
///
/// The variable ids.
/// The expected types.
/// The list of returned values.
/// The list of returned errors.
public void ReadValues(
IList variableIds,
IList expectedTypes,
out List