/* ======================================================================== * 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 values, out List errors) { values = new List(); errors = new List(); // build list of values to read. ReadValueIdCollection valuesToRead = new ReadValueIdCollection(); for (int ii = 0; ii < variableIds.Count; ii++) { ReadValueId valueToRead = new ReadValueId(); valueToRead.NodeId = variableIds[ii]; valueToRead.AttributeId = Attributes.Value; valueToRead.IndexRange = null; valueToRead.DataEncoding = null; valuesToRead.Add(valueToRead); } // read the values. DataValueCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; ResponseHeader responseHeader = Read( null, Int32.MaxValue, TimestampsToReturn.Both, valuesToRead, out results, out diagnosticInfos); // verify that the server returned the correct number of results. ClientBase.ValidateResponse(results, valuesToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, valuesToRead); for (int ii = 0; ii < variableIds.Count; ii++) { values.Add(null); errors.Add(ServiceResult.Good); // process any diagnostics associated with bad or uncertain data. if (StatusCode.IsNotGood(results[ii].StatusCode)) { errors[ii] = new ServiceResult(results[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable); continue; } object value = results[ii].Value; // extract the body from extension objects. ExtensionObject extension = value as ExtensionObject; if (extension != null && extension.Body is IEncodeable) { value = extension.Body; } // check expected type. if (expectedTypes[ii] != null && !expectedTypes[ii].IsInstanceOfType(value)) { errors[ii] = ServiceResult.Create( StatusCodes.BadTypeMismatch, "Value {0} does not have expected type: {1}.", value, expectedTypes[ii].Name); continue; } // suitable value found. values[ii] = value; } } /// /// Reads the display name for a set of Nodes. /// public void ReadDisplayName( IList nodeIds, out List displayNames, out List errors) { displayNames = new List(); errors = new List(); // build list of values to read. ReadValueIdCollection valuesToRead = new ReadValueIdCollection(); for (int ii = 0; ii < nodeIds.Count; ii++) { ReadValueId valueToRead = new ReadValueId(); valueToRead.NodeId = nodeIds[ii]; valueToRead.AttributeId = Attributes.DisplayName; valueToRead.IndexRange = null; valueToRead.DataEncoding = null; valuesToRead.Add(valueToRead); } // read the values. DataValueCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; ResponseHeader responseHeader = Read( null, Int32.MaxValue, TimestampsToReturn.Both, valuesToRead, out results, out diagnosticInfos); // verify that the server returned the correct number of results. ClientBase.ValidateResponse(results, valuesToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, valuesToRead); for (int ii = 0; ii < nodeIds.Count; ii++) { displayNames.Add(String.Empty); errors.Add(ServiceResult.Good); // process any diagnostics associated with bad or uncertain data. if (StatusCode.IsNotGood(results[ii].StatusCode)) { errors[ii] = new ServiceResult(results[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable); continue; } // extract the name. LocalizedText displayName = results[ii].GetValue(null); if (!LocalizedText.IsNullOrEmpty(displayName)) { displayNames[ii] = displayName.Text; } } } /// /// Disconnects from the server and frees any network resources. /// public override StatusCode Close() { return Close(m_keepAliveInterval); } /// /// Disconnects from the server and frees any network resources with the specified timeout. /// public virtual StatusCode Close(int timeout) { // check if already called. if (Disposed) { return StatusCodes.Good; } StatusCode result = StatusCodes.Good; // stop the keep alive timer. if (m_keepAliveTimer != null) { m_keepAliveTimer.Dispose(); m_keepAliveTimer = null; } // check if currectly connected. bool connected = Connected; // halt all background threads. if (connected) { if (m_SessionClosing != null) { try { m_SessionClosing(this, null); } catch (Exception e) { Utils.Trace(e, "Session: Unexpected eror raising SessionClosing event."); } } } // close the session with the server. if (connected && !KeepAliveStopped) { int existingTimeout = this.OperationTimeout; try { // close the session and delete all subscriptions. this.OperationTimeout = timeout; CloseSession(null, true); this.OperationTimeout = existingTimeout; CloseChannel(); // raised notification indicating the session is closed. SessionCreated(null, null); } catch (Exception e) { // dont throw errors on disconnect, but return them // so the caller can log the error. if (e is ServiceResultException) { result = ((ServiceResultException)e).StatusCode; } else { result = StatusCodes.Bad; } Utils.Trace("Session close error: " + result); } } // clean up. Dispose(); return result; } /// /// Adds a subscription to the session. /// /// The subscription to add. /// public bool AddSubscription(Subscription subscription) { if (subscription == null) throw new ArgumentNullException("subscription"); lock (SyncRoot) { if (m_subscriptions.Contains(subscription)) { return false; } subscription.Session = this; m_subscriptions.Add(subscription); } if (m_SubscriptionsChanged != null) { m_SubscriptionsChanged(this, null); } return true; } /// /// Removes a subscription from the session. /// /// The subscription to remove. /// public bool RemoveSubscription(Subscription subscription) { if (subscription == null) throw new ArgumentNullException("subscription"); if (subscription.Created) { subscription.Delete(false); } lock (SyncRoot) { if (!m_subscriptions.Remove(subscription)) { return false; } subscription.Session = null; } if (m_SubscriptionsChanged != null) { m_SubscriptionsChanged(this, null); } return true; } /// /// Removes a list of subscriptions from the sessiont. /// /// The list of subscriptions to remove. /// public bool RemoveSubscriptions(IEnumerable subscriptions) { if (subscriptions == null) throw new ArgumentNullException("subscriptions"); bool removed = false; List subscriptionsToDelete = new List(); lock (SyncRoot) { foreach (Subscription subscription in subscriptions) { if (m_subscriptions.Remove(subscription)) { if (subscription.Created) { subscriptionsToDelete.Add(subscription); } removed = true; } } } foreach (Subscription subscription in subscriptionsToDelete) { subscription.Delete(true); } if (removed) { if (m_SubscriptionsChanged != null) { m_SubscriptionsChanged(this, null); } } return true; } #region Browse Methods /// /// Invokes the Browse service. /// /// The request header. /// The view to browse. /// The node to browse. /// The maximum number of returned values. /// The browse direction. /// The reference type id. /// If set to true the subtypes of the ReferenceType will be included in the browse. /// The node class mask. /// The continuation point. /// The list of node references. /// public virtual ResponseHeader Browse( RequestHeader requestHeader, ViewDescription view, NodeId nodeToBrowse, uint maxResultsToReturn, BrowseDirection browseDirection, NodeId referenceTypeId, bool includeSubtypes, uint nodeClassMask, out byte[] continuationPoint, out ReferenceDescriptionCollection references) { BrowseDescription description = new BrowseDescription(); description.NodeId = nodeToBrowse; description.BrowseDirection = browseDirection; description.ReferenceTypeId = referenceTypeId; description.IncludeSubtypes = includeSubtypes; description.NodeClassMask = nodeClassMask; description.ResultMask = (uint)BrowseResultMask.All; BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); nodesToBrowse.Add(description); BrowseResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = Browse( requestHeader, view, maxResultsToReturn, nodesToBrowse, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToBrowse); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(new ServiceResult(results[0].StatusCode, 0, diagnosticInfos, responseHeader.StringTable)); } continuationPoint = results[0].ContinuationPoint; references = results[0].References; return responseHeader; } /// /// Begins an asynchronous invocation of the Browse service. /// /// The request header. /// The view to browse. /// The node to browse. /// The maximum number of returned values.. /// The browse direction. /// The reference type id. /// If set to true the subtypes of the ReferenceType will be included in the browse. /// The node class mask. /// The callback. /// /// public IAsyncResult BeginBrowse( RequestHeader requestHeader, ViewDescription view, NodeId nodeToBrowse, uint maxResultsToReturn, BrowseDirection browseDirection, NodeId referenceTypeId, bool includeSubtypes, uint nodeClassMask, AsyncCallback callback, object asyncState) { BrowseDescription description = new BrowseDescription(); description.NodeId = nodeToBrowse; description.BrowseDirection = browseDirection; description.ReferenceTypeId = referenceTypeId; description.IncludeSubtypes = includeSubtypes; description.NodeClassMask = nodeClassMask; description.ResultMask = (uint)BrowseResultMask.All; BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); nodesToBrowse.Add(description); return BeginBrowse( requestHeader, view, maxResultsToReturn, nodesToBrowse, callback, asyncState); } /// /// Finishes an asynchronous invocation of the Browse service. /// /// The result. /// The continuation point. /// The list of node references. /// public ResponseHeader EndBrowse( IAsyncResult result, out byte[] continuationPoint, out ReferenceDescriptionCollection references) { BrowseResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = EndBrowse( result, out results, out diagnosticInfos); if (results == null || results.Count != 1) { throw new ServiceResultException(StatusCodes.BadUnknownResponse); } if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(new ServiceResult(results[0].StatusCode, 0, diagnosticInfos, responseHeader.StringTable)); } continuationPoint = results[0].ContinuationPoint; references = results[0].References; return responseHeader; } #endregion #region BrowseNext Methods /// /// Invokes the BrowseNext service. /// public virtual ResponseHeader BrowseNext( RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, out byte[] revisedContinuationPoint, out ReferenceDescriptionCollection references) { ByteStringCollection continuationPoints = new ByteStringCollection(); continuationPoints.Add(continuationPoint); BrowseResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = BrowseNext( requestHeader, releaseContinuationPoint, continuationPoints, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, continuationPoints); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(new ServiceResult(results[0].StatusCode, 0, diagnosticInfos, responseHeader.StringTable)); } revisedContinuationPoint = results[0].ContinuationPoint; references = results[0].References; return responseHeader; } /// /// Begins an asynchronous invocation of the BrowseNext service. /// public IAsyncResult BeginBrowseNext( RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, AsyncCallback callback, object asyncState) { ByteStringCollection continuationPoints = new ByteStringCollection(); continuationPoints.Add(continuationPoint); return BeginBrowseNext( requestHeader, releaseContinuationPoint, continuationPoints, callback, asyncState); } /// /// Finishes an asynchronous invocation of the BrowseNext service. /// public ResponseHeader EndBrowseNext( IAsyncResult result, out byte[] revisedContinuationPoint, out ReferenceDescriptionCollection references) { BrowseResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = EndBrowseNext( result, out results, out diagnosticInfos); if (results == null || results.Count != 1) { throw new ServiceResultException(StatusCodes.BadUnknownResponse); } if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(new ServiceResult(results[0].StatusCode, 0, diagnosticInfos, responseHeader.StringTable)); } revisedContinuationPoint = results[0].ContinuationPoint; references = results[0].References; return responseHeader; } #endregion /// /// Calls the specified method and returns the output arguments. /// /// The NodeId of the object that provides the method. /// The NodeId of the method to call. /// The input arguments. /// The list of output argument values. public IList Call(NodeId objectId, NodeId methodId, params object[] args) { VariantCollection inputArguments = new VariantCollection(); if (args != null) { for (int ii = 0; ii < args.Length; ii++) { inputArguments.Add(new Variant(args[ii])); } } CallMethodRequest request = new CallMethodRequest(); request.ObjectId = objectId; request.MethodId = methodId; request.InputArguments = inputArguments; CallMethodRequestCollection requests = new CallMethodRequestCollection(); requests.Add(request); CallMethodResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = Call( null, requests, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, requests); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, requests); if (StatusCode.IsBad(results[0].StatusCode)) { throw ServiceResultException.Create(results[0].StatusCode, 0, diagnosticInfos, responseHeader.StringTable); } List outputArguments = new List(); foreach (Variant arg in results[0].OutputArguments) { outputArguments.Add(arg.Value); } return outputArguments; } #endregion #region Protected Methods /// /// Returns the software certificates assigned to the application. /// protected virtual SignedSoftwareCertificateCollection GetSoftwareCertificates() { return new SignedSoftwareCertificateCollection(); } /// /// Handles an error when validating the application instance certificate provided by the server. /// protected virtual void OnApplicationCertificateError(byte[] serverCertificate, ServiceResult result) { throw new ServiceResultException(result); } /// /// Handles an error when validating software certificates provided by the server. /// protected virtual void OnSoftwareCertificateError(SignedSoftwareCertificate signedCertificate, ServiceResult result) { throw new ServiceResultException(result); } /// /// Inspects the software certificates provided by the server. /// protected virtual void ValidateSoftwareCertificates(List softwareCertificates) { // always accept valid certificates. } /// /// Starts a timer to check that the connection to the server is still available. /// private void StartKeepAliveTimer() { int keepAliveInterval = m_keepAliveInterval; lock (m_eventLock) { m_serverState = ServerState.Unknown; m_lastKeepAliveTime = DateTime.UtcNow; } ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); // read the server state. ReadValueId serverState = new ReadValueId(); serverState.NodeId = Variables.Server_ServerStatus_State; serverState.AttributeId = Attributes.Value; serverState.DataEncoding = null; serverState.IndexRange = null; nodesToRead.Add(serverState); // restart the publish timer. lock (SyncRoot) { if (m_keepAliveTimer != null) { m_keepAliveTimer.Dispose(); m_keepAliveTimer = null; } // start timer. m_keepAliveTimer = new Timer(OnKeepAlive, nodesToRead, keepAliveInterval, keepAliveInterval); } // send initial keep alive. OnKeepAlive(nodesToRead); } /// /// Removes a completed async request. /// private AsyncRequestState RemoveRequest(IAsyncResult result, uint requestId, uint typeId) { lock (m_outstandingRequests) { for (LinkedListNode ii = m_outstandingRequests.First; ii != null; ii = ii.Next) { if (Object.ReferenceEquals(result, ii.Value.Result) || (requestId == ii.Value.RequestId && typeId == ii.Value.RequestTypeId)) { AsyncRequestState state = ii.Value; m_outstandingRequests.Remove(ii); return state; } } return null; } } /// /// Adds a new async request. /// private void AsyncRequestStarted(IAsyncResult result, uint requestId, uint typeId) { lock (m_outstandingRequests) { // check if the request completed asynchronously. AsyncRequestState state = RemoveRequest(result, requestId, typeId); // add a new request. if (state == null) { state = new AsyncRequestState(); state.Defunct = false; state.RequestId = requestId; state.RequestTypeId = typeId; state.Result = result; state.Timestamp = DateTime.UtcNow; m_outstandingRequests.AddLast(state); } } } /// /// Removes a completed async request. /// private void AsyncRequestCompleted(IAsyncResult result, uint requestId, uint typeId) { lock (m_outstandingRequests) { // remove the request. AsyncRequestState state = RemoveRequest(result, requestId, typeId); if (state != null) { // mark any old requests as default (i.e. the should have returned before this request). DateTime maxAge = state.Timestamp.AddSeconds(-1); for (LinkedListNode ii = m_outstandingRequests.First; ii != null; ii = ii.Next) { if (ii.Value.RequestTypeId == typeId && ii.Value.Timestamp < maxAge) { ii.Value.Defunct = true; } } } // add a dummy placeholder since the begin request has not completed yet. if (state == null) { state = new AsyncRequestState(); state.Defunct = true; state.RequestId = requestId; state.RequestTypeId = typeId; state.Result = result; state.Timestamp = DateTime.UtcNow; m_outstandingRequests.AddLast(state); } } } /// /// Sends a keep alive by reading from the server. /// private void OnKeepAlive(object state) { ReadValueIdCollection nodesToRead = (ReadValueIdCollection)state; try { // check if session has been closed. if (!Connected) { return; } // raise error if keep alives are not coming back. if (KeepAliveStopped) { if (!OnKeepAliveError(ServiceResult.Create(StatusCodes.BadNoCommunication, "Server not responding to keep alive requests."))) { return; } } // limit the number of keep alives sent. if (OutstandingRequestCount > SubscriptionCount + 10) { return; } RequestHeader requestHeader = new RequestHeader(); requestHeader.RequestHandle = Utils.IncrementIdentifier(ref m_keepAliveCounter); requestHeader.TimeoutHint = (uint)(KeepAliveInterval*2); requestHeader.ReturnDiagnostics = 0; IAsyncResult result = BeginRead( requestHeader, 0, TimestampsToReturn.Neither, nodesToRead, OnKeepAliveComplete, nodesToRead); AsyncRequestStarted(result, requestHeader.RequestHandle, DataTypes.ReadRequest); } catch (Exception e) { Utils.Trace("Could not send keep alive request: {1} {0}", e.Message, e.GetType().FullName); } } /// /// Checks if a notification has arrived. Sends a publish if it has not. /// private void OnKeepAliveComplete(IAsyncResult result) { ReadValueIdCollection nodesToRead = (ReadValueIdCollection)result.AsyncState; AsyncRequestCompleted(result, 0, DataTypes.ReadRequest); try { // read the server status. DataValueCollection values = new DataValueCollection(); DiagnosticInfoCollection diagnosticInfos = new DiagnosticInfoCollection(); ResponseHeader responseHeader = EndRead( result, out values, out diagnosticInfos); ValidateResponse(values, nodesToRead); ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); // validate value returned. ServiceResult error = ValidateDataValue(values[0], typeof(int), 0, diagnosticInfos, responseHeader); if (ServiceResult.IsBad(error)) { throw new ServiceResultException(error); } // send notification that keep alive completed. OnKeepAlive((ServerState)(int)values[0].Value, responseHeader.Timestamp); } catch (Exception e) { Utils.Trace("Unexpected keep alive error occurred: {0}", e.Message); } } /// /// Called when the server returns a keep alive response. /// protected virtual void OnKeepAlive(ServerState currentState, DateTime currentTime) { // restart publishing if keep alives recovered. if (KeepAliveStopped) { // ignore if already reconnecting. if (m_reconnecting) { return; } int count = 0; lock (m_outstandingRequests) { for (LinkedListNode ii = m_outstandingRequests.First; ii != null; ii = ii.Next) { if (ii.Value.RequestTypeId == DataTypes.PublishRequest) { ii.Value.Defunct = true; } } } lock (SyncRoot) { count = m_subscriptions.Count; } while (count-- > 0) { BeginPublish(OperationTimeout); } } KeepAliveEventHandler callback = null; lock (m_eventLock) { callback = m_KeepAlive; // save server state. m_serverState = currentState; m_lastKeepAliveTime = DateTime.UtcNow; } if (callback != null) { try { callback(this, new KeepAliveEventArgs(null, currentState, currentTime)); } catch (Exception e) { Utils.Trace(e, "Session: Unexpected error invoking KeepAliveCallback."); } } } /// /// Called when a error occurs during a keep alive. /// protected virtual bool OnKeepAliveError(ServiceResult result) { long delta = 0; lock (m_eventLock) { delta = DateTime.UtcNow.Ticks - m_lastKeepAliveTime.Ticks; } Utils.Trace( "KEEP ALIVE LATE: {0}s, EndpointUrl={1}, RequestCount={3}/{2}", ((double)delta)/TimeSpan.TicksPerSecond, this.Endpoint.EndpointUrl, this.OutstandingRequestCount, this.GoodPublishRequestCount); KeepAliveEventHandler callback = null; lock (m_eventLock) { callback = m_KeepAlive; } if (callback != null) { try { KeepAliveEventArgs args = new KeepAliveEventArgs(result, ServerState.Unknown, DateTime.UtcNow); callback(this, args); return !args.CancelKeepAlive; } catch (Exception e) { Utils.Trace(e, "Session: Unexpected error invoking KeepAliveCallback."); } } return true; } #endregion #region Publish Methods /// /// Sends an additional publish request. /// public IAsyncResult BeginPublish(int timeout) { // do not publish if reconnecting. if (m_reconnecting) { Utils.Trace("Published skipped due to reconnect"); return null; } SubscriptionAcknowledgementCollection acknowledgementsToSend = null; // collect the current set if acknowledgements. lock (SyncRoot) { acknowledgementsToSend = m_acknowledgementsToSend; m_acknowledgementsToSend = new SubscriptionAcknowledgementCollection(); foreach (var toSend in acknowledgementsToSend) { if (m_latestAcknowledgementsSent.ContainsKey(toSend.SubscriptionId)) { m_latestAcknowledgementsSent[toSend.SubscriptionId] = toSend.SequenceNumber; } else { m_latestAcknowledgementsSent.Add(toSend.SubscriptionId, toSend.SequenceNumber); } } } // send publish request. RequestHeader requestHeader = new RequestHeader(); // ensure the publish request is discarded before the timeout occurs to ensure the channel is dropped. requestHeader.TimeoutHint = (uint)OperationTimeout/2; requestHeader.ReturnDiagnostics = (uint)(int)ReturnDiagnostics; requestHeader.RequestHandle = Utils.IncrementIdentifier(ref m_publishCounter); AsyncRequestState state = new AsyncRequestState(); state.RequestTypeId = DataTypes.PublishRequest; state.RequestId = requestHeader.RequestHandle; state.Timestamp = DateTime.UtcNow; try { IAsyncResult result = BeginPublish( requestHeader, acknowledgementsToSend, OnPublishComplete, new object[] { SessionId, acknowledgementsToSend, requestHeader }); AsyncRequestStarted(result, requestHeader.RequestHandle, DataTypes.PublishRequest); // Utils.Trace("PUBLISH #{0} SENT", requestHeader.RequestHandle); return result; } catch (Exception e) { Utils.Trace(e, "Unexpected error sending publish request."); return null; } } /// /// Completes an asynchronous publish operation. /// private void OnPublishComplete(IAsyncResult result) { // extract state information. object[] state = (object[])result.AsyncState; NodeId sessionId = (NodeId)state[0]; SubscriptionAcknowledgementCollection acknowledgementsToSend = (SubscriptionAcknowledgementCollection)state[1]; RequestHeader requestHeader = (RequestHeader)state[2]; bool moreNotifications; AsyncRequestCompleted(result, requestHeader.RequestHandle, DataTypes.PublishRequest); try { // Utils.Trace("PUBLISH #{0} RECEIVED", requestHeader.RequestHandle); // complete publish. uint subscriptionId; UInt32Collection availableSequenceNumbers; NotificationMessage notificationMessage; StatusCodeCollection acknowledgeResults; DiagnosticInfoCollection acknowledgeDiagnosticInfos; ResponseHeader responseHeader = EndPublish( result, out subscriptionId, out availableSequenceNumbers, out moreNotifications, out notificationMessage, out acknowledgeResults, out acknowledgeDiagnosticInfos); foreach (StatusCode code in acknowledgeResults) { if (StatusCode.IsBad(code)) { Utils.Trace("Error - Publish call finished. ResultCode={0}; SubscriptionId={1};", code.ToString(), subscriptionId); } } // nothing more to do if session changed. if (sessionId != SessionId) { Utils.Trace("Publish response discarded because session id changed: Old {0} != New {1}", sessionId, SessionId); return; } // Utils.Trace("NOTIFICATION RECEIVED: SubId={0}, SeqNo={1}", subscriptionId, notificationMessage.SequenceNumber); // process response. ProcessPublishResponse( responseHeader, subscriptionId, availableSequenceNumbers, moreNotifications, notificationMessage); // nothing more to do if reconnecting. if (m_reconnecting) { Utils.Trace("No new publish sent because of reconnect in progress."); return; } } catch (Exception e) { if (m_subscriptions.Count == 0) { // Publish responses with error should occur after deleting the last subscription. Utils.Trace("Publish #{0}, Subscription count = 0, Error: {1}", requestHeader.RequestHandle, e.Message); } else { Utils.Trace("Publish #{0}, Reconnecting={2}, Error: {1}", requestHeader.RequestHandle, e.Message, m_reconnecting); } moreNotifications = false; // ignore errors if reconnecting. if (m_reconnecting) { Utils.Trace("Publish abandoned after error due to reconnect: {0}", e.Message); return; } // nothing more to do if session changed. if (sessionId != SessionId) { Utils.Trace("Publish abandoned after error because session id changed: Old {0} != New {1}", sessionId, SessionId); return; } // try to acknowledge the notifications again in the next publish. if (acknowledgementsToSend != null) { lock (SyncRoot) { m_acknowledgementsToSend.AddRange(acknowledgementsToSend); } } // raise an error event. ServiceResult error = new ServiceResult(e); if (error.Code != StatusCodes.BadNoSubscription) { PublishErrorEventHandler callback = null; lock (m_eventLock) { callback = m_PublishError; } if (callback != null) { try { callback(this, new PublishErrorEventArgs(error)); } catch (Exception e2) { Utils.Trace(e2, "Session: Unexpected error invoking PublishErrorCallback."); } } } // don't send another publish for these errors. switch (error.Code) { case StatusCodes.BadNoSubscription: case StatusCodes.BadSessionClosed: case StatusCodes.BadTooManyPublishRequests: case StatusCodes.BadServerHalted: { return; } } Utils.Trace(e, "PUBLISH #{0} - Unhandled error during Publish.", requestHeader.RequestHandle); } int requestCount = GoodPublishRequestCount; if (requestCount < m_subscriptions.Count) { BeginPublish(OperationTimeout); } else { Utils.Trace("PUBLISH - Did not send another publish request. GoodPublishRequestCount={0}, Subscriptions={1}", requestCount, m_subscriptions.Count); } } /// /// Sends a republish request. /// public bool Republish(uint subscriptionId, uint sequenceNumber) { // send publish request. RequestHeader requestHeader = new RequestHeader(); requestHeader.TimeoutHint = (uint)OperationTimeout; requestHeader.ReturnDiagnostics = (uint)(int)ReturnDiagnostics; requestHeader.RequestHandle = Utils.IncrementIdentifier(ref m_publishCounter); try { Utils.Trace("Requesting Republish for {0}-{1}", subscriptionId, sequenceNumber); // request republish. NotificationMessage notificationMessage = null; ResponseHeader responseHeader = Republish( requestHeader, subscriptionId, sequenceNumber, out notificationMessage); Utils.Trace("Received Republish for {0}-{1}", subscriptionId, sequenceNumber); // process response. ProcessPublishResponse( responseHeader, subscriptionId, null, false, notificationMessage); return true; } catch (Exception e) { ServiceResult error = new ServiceResult(e); bool result = (error.StatusCode == StatusCodes.BadMessageNotAvailable); if (result) { Utils.Trace("Message {0}-{1} no longer available.", subscriptionId, sequenceNumber); } else { Utils.Trace(e, "Unexpected error sending republish request."); } PublishErrorEventHandler callback = null; lock (m_eventLock) { callback = m_PublishError; } // raise an error event. if (callback != null) { try { PublishErrorEventArgs args = new PublishErrorEventArgs( error, subscriptionId, sequenceNumber); callback(this, args); } catch (Exception e2) { Utils.Trace(e2, "Session: Unexpected error invoking PublishErrorCallback."); } } return result; } } /// /// Processes the response from a publish request. /// private void ProcessPublishResponse( ResponseHeader responseHeader, uint subscriptionId, UInt32Collection availableSequenceNumbers, bool moreNotifications, NotificationMessage notificationMessage) { Subscription subscription = null; // send notification that the server is alive. OnKeepAlive(m_serverState, responseHeader.Timestamp); // collect the current set if acknowledgements. lock (SyncRoot) { // clear out acknowledgements for messages that the server does not have any more. SubscriptionAcknowledgementCollection acknowledgementsToSend = new SubscriptionAcknowledgementCollection(); for (int ii = 0; ii < m_acknowledgementsToSend.Count; ii++) { SubscriptionAcknowledgement acknowledgement = m_acknowledgementsToSend[ii]; if (acknowledgement.SubscriptionId != subscriptionId) { acknowledgementsToSend.Add(acknowledgement); } else { if (availableSequenceNumbers == null || availableSequenceNumbers.Contains(acknowledgement.SequenceNumber)) { acknowledgementsToSend.Add(acknowledgement); } } } // create an acknowledgement to be sent back to the server. if (notificationMessage.NotificationData.Count > 0) { SubscriptionAcknowledgement acknowledgement = new SubscriptionAcknowledgement(); acknowledgement.SubscriptionId = subscriptionId; acknowledgement.SequenceNumber = notificationMessage.SequenceNumber; acknowledgementsToSend.Add(acknowledgement); } uint lastSentSequenceNumber = 0; if (availableSequenceNumbers != null) { foreach (uint availableSequenceNumber in availableSequenceNumbers) { if (m_latestAcknowledgementsSent.ContainsKey(subscriptionId)) { lastSentSequenceNumber = m_latestAcknowledgementsSent[subscriptionId]; // If the last sent sequence number is uint.Max do not display the warning; the counter rolled over // If the last sent sequence number is greater or equal to the available sequence number (returned by the publish), a warning must be logged. if (((lastSentSequenceNumber >= availableSequenceNumber) && (lastSentSequenceNumber != uint.MaxValue)) || (lastSentSequenceNumber == availableSequenceNumber) && (lastSentSequenceNumber == uint.MaxValue)) { Utils.Trace("Received sequence number which was already acknowledged={0}", availableSequenceNumber); } } } } if (m_latestAcknowledgementsSent.ContainsKey(subscriptionId)) { lastSentSequenceNumber = m_latestAcknowledgementsSent[subscriptionId]; // If the last sent sequence number is uint.Max do not display the warning; the counter rolled over // If the last sent sequence number is greater or equal to the notificationMessage's sequence number (returned by the publish), a warning must be logged. if (((lastSentSequenceNumber >= notificationMessage.SequenceNumber) && (lastSentSequenceNumber != uint.MaxValue)) || (lastSentSequenceNumber == notificationMessage.SequenceNumber) && (lastSentSequenceNumber == uint.MaxValue)) { Utils.Trace("Received sequence number which was already acknowledged={0}", notificationMessage.SequenceNumber); } } if (availableSequenceNumbers != null) { foreach (var acknowledgement in acknowledgementsToSend) { if (acknowledgement.SubscriptionId == subscriptionId && !availableSequenceNumbers.Contains(acknowledgement.SequenceNumber)) { Utils.Trace("Sequence number={0} was not received in the available sequence numbers.", acknowledgement.SequenceNumber); } } } m_acknowledgementsToSend = acknowledgementsToSend; if (notificationMessage.IsEmpty) { Utils.Trace("Empty notification message received for SessionId {0} with PublishTime {1}", SessionId, notificationMessage.PublishTime.ToLocalTime()); } // find the subscription. foreach (Subscription current in m_subscriptions) { if (current.Id == subscriptionId) { subscription = current; break; } } } // ignore messages with a subscription that has been deleted. if (subscription != null) { // Validate publish time and reject old values. if (notificationMessage.PublishTime.AddMilliseconds(subscription.CurrentPublishingInterval * subscription.CurrentLifetimeCount) < DateTime.UtcNow) { Utils.Trace("PublishTime {0} in publish response is too old for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); } // Validate publish time and reject old values. if (notificationMessage.PublishTime > DateTime.UtcNow.AddMilliseconds(subscription.CurrentPublishingInterval * subscription.CurrentLifetimeCount)) { Utils.Trace("PublishTime {0} in publish response is newer than actual time for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); } // update subscription cache. subscription.SaveMessageInCache( availableSequenceNumbers, notificationMessage, responseHeader.StringTable); // raise the notification. lock (m_eventLock) { NotificationEventArgs args = new NotificationEventArgs(subscription, notificationMessage, responseHeader.StringTable); if (m_Publish != null) { ThreadPool.QueueUserWorkItem(OnRaisePublishNotification, args); } } } else { Utils.Trace("Received Publish Response for Unknown SubscriptionId={0}", subscriptionId); } } /// /// Raises an event indicating that publish has returned a notification. /// private void OnRaisePublishNotification(object state) { try { NotificationEventArgs args = (NotificationEventArgs)state; NotificationEventHandler callback = m_Publish; if (callback != null && args.Subscription.Id != 0) { callback(this, args); } } catch (Exception e) { Utils.Trace(e, "Session: Unexpected rrror while raising Notification event."); } } #endregion #region Private Fields private SubscriptionAcknowledgementCollection m_acknowledgementsToSend; private Dictionary m_latestAcknowledgementsSent; private List m_subscriptions; private Dictionary m_dictionaries; private Subscription m_defaultSubscription; private double m_sessionTimeout; private uint m_maxRequestMessageSize; private StringCollection m_preferredLocales; private NamespaceTable m_namespaceUris; private StringTable m_serverUris; private EncodeableFactory m_factory; private SystemContext m_systemContext; private NodeCache m_nodeCache; private ApplicationConfiguration m_configuration; private ConfiguredEndpoint m_endpoint; private X509Certificate2 m_instanceCertificate; //private X509Certificate2Collection m_instanceCertificateChain; private List m_identityHistory; private string m_sessionName; private object m_handle; private IUserIdentity m_identity; private byte[] m_serverNonce; private X509Certificate2 m_serverCertificate; private long m_publishCounter; private DateTime m_lastKeepAliveTime; private ServerState m_serverState; private int m_keepAliveInterval; private Timer m_keepAliveTimer; private long m_keepAliveCounter; private bool m_reconnecting; private LinkedList m_outstandingRequests; private EndpointDescriptionCollection m_expectedServerEndpoints; private class AsyncRequestState { public uint RequestTypeId; public uint RequestId; public DateTime Timestamp; public IAsyncResult Result; public bool Defunct; } private object m_eventLock = new object(); private event KeepAliveEventHandler m_KeepAlive; private event NotificationEventHandler m_Publish; private event PublishErrorEventHandler m_PublishError; private event EventHandler m_SubscriptionsChanged; private event EventHandler m_SessionClosing; #endregion } #region KeepAliveEventArgs Class /// /// The event arguments provided when a keep alive response arrives. /// public class KeepAliveEventArgs : EventArgs { #region Constructors /// /// Creates a new instance. /// internal KeepAliveEventArgs( ServiceResult status, ServerState currentState, DateTime currentTime) { m_status = status; m_currentState = currentState; m_currentTime = currentTime; } #endregion #region Public Properties /// /// Gets the status associated with the keep alive operation. /// public ServiceResult Status { get { return m_status; } } /// /// Gets the current server state. /// public ServerState CurrentState { get { return m_currentState; } } /// /// Gets the current server time. /// public DateTime CurrentTime { get { return m_currentTime; } } /// /// Gets or sets a flag indicating whether the session should send another keep alive. /// public bool CancelKeepAlive { get { return m_cancelKeepAlive; } set { m_cancelKeepAlive = value; } } #endregion #region Private Fields private ServiceResult m_status; private ServerState m_currentState; private DateTime m_currentTime; private bool m_cancelKeepAlive; #endregion } /// /// The delegate used to receive keep alive notifications. /// public delegate void KeepAliveEventHandler(Session session, KeepAliveEventArgs e); #endregion #region NotificationEventArgs Class /// /// Represents the event arguments provided when a new notification message arrives. /// public class NotificationEventArgs : EventArgs { #region Constructors /// /// Creates a new instance. /// internal NotificationEventArgs( Subscription subscription, NotificationMessage notificationMessage, IList stringTable) { m_subscription = subscription; m_notificationMessage = notificationMessage; m_stringTable = stringTable; } #endregion #region Public Properties /// /// Gets the subscription that the notification applies to. /// public Subscription Subscription { get { return m_subscription; } } /// /// Gets the notification message. /// public NotificationMessage NotificationMessage { get { return m_notificationMessage; } } /// /// Gets the string table returned with the notification message. /// public IList StringTable { get { return m_stringTable; } } #endregion #region Private Fields private Subscription m_subscription; private NotificationMessage m_notificationMessage; private IList m_stringTable; #endregion } /// /// The delegate used to receive publish notifications. /// public delegate void NotificationEventHandler(Session session, NotificationEventArgs e); #endregion #region PublishErrorEventArgs Class /// /// Represents the event arguments provided when a publish error occurs. /// public class PublishErrorEventArgs : EventArgs { #region Constructors /// /// Creates a new instance. /// internal PublishErrorEventArgs(ServiceResult status) { m_status = status; } /// /// Creates a new instance. /// internal PublishErrorEventArgs(ServiceResult status, uint subscriptionId, uint sequenceNumber) { m_status = status; m_subscriptionId = subscriptionId; m_sequenceNumber = sequenceNumber; } #endregion #region Public Properties /// /// Gets the status associated with the keep alive operation. /// public ServiceResult Status { get { return m_status; } } /// /// Gets the subscription with the message that could not be republished. /// public uint SubscriptionId { get { return m_subscriptionId; } } /// /// Gets the sequence number for the message that could not be republished. /// public uint SequenceNumber { get { return m_sequenceNumber; } } #endregion #region Private Fields private uint m_subscriptionId; private uint m_sequenceNumber; private ServiceResult m_status; #endregion } /// /// The delegate used to receive pubish error notifications. /// public delegate void PublishErrorEventHandler(Session session, PublishErrorEventArgs e); #endregion }