diff --git a/SampleApplications/SDK/Opc.Ua.Client/CoreClientUtils.cs b/SampleApplications/SDK/Opc.Ua.Client/CoreClientUtils.cs new file mode 100644 index 0000000000..c1fa3cce94 --- /dev/null +++ b/SampleApplications/SDK/Opc.Ua.Client/CoreClientUtils.cs @@ -0,0 +1,175 @@ +/* ======================================================================== + * Copyright (c) 2005-2013 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; + +namespace Opc.Ua.Client +{ + /// + /// Defines numerous re-useable utility functions for clients. + /// + public class CoreClientUtils + { + #region Discovery + /// + /// Discovers the servers on the local machine. + /// + /// The configuration. + /// A list of server urls. + public static IList DiscoverServers(ApplicationConfiguration configuration) + { + List serverUrls = new List(); + + // set a short timeout because this is happening in the drop down event. + EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration); + endpointConfiguration.OperationTimeout = 5000; + + // Connect to the local discovery server and find the available servers. + using (DiscoveryClient client = DiscoveryClient.Create(new Uri("opc.tcp://localhost:4840"), endpointConfiguration)) + { + ApplicationDescriptionCollection servers = client.FindServers(null); + + // populate the drop down list with the discovery URLs for the available servers. + for (int ii = 0; ii < servers.Count; ii++) + { + if (servers[ii].ApplicationType == ApplicationType.DiscoveryServer) + { + continue; + } + + for (int jj = 0; jj < servers[ii].DiscoveryUrls.Count; jj++) + { + string discoveryUrl = servers[ii].DiscoveryUrls[jj]; + + // Many servers will use the '/discovery' suffix for the discovery endpoint. + // The URL without this prefix should be the base URL for the server. + if (discoveryUrl.EndsWith("/discovery")) + { + discoveryUrl = discoveryUrl.Substring(0, discoveryUrl.Length - "/discovery".Length); + } + + // ensure duplicates do not get added. + if (!serverUrls.Contains(discoveryUrl)) + { + serverUrls.Add(discoveryUrl); + } + } + } + } + + return serverUrls; + } + + /// + /// Finds the endpoint that best matches the current settings. + /// + /// The discovery URL. + /// if set to true select an endpoint that uses security. + /// The best available endpoint. + public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity, int operationTimeout = -1) + { + // needs to add the '/discovery' back onto non-UA TCP URLs. + if (discoveryUrl.StartsWith(Utils.UriSchemeHttps)) + { + if (!discoveryUrl.EndsWith("/discovery")) + { + discoveryUrl += "/discovery"; + } + } + + // parse the selected URL. + Uri uri = new Uri(discoveryUrl); + + EndpointConfiguration configuration = EndpointConfiguration.Create(); + if (operationTimeout > 0) + { + configuration.OperationTimeout = operationTimeout; + } + + EndpointDescription selectedEndpoint = null; + + // Connect to the server's discovery endpoint and find the available configuration. + using (DiscoveryClient client = DiscoveryClient.Create(uri, configuration)) + { + EndpointDescriptionCollection endpoints = client.GetEndpoints(null); + + // select the best endpoint to use based on the selected URL and the UseSecurity checkbox. + for (int ii = 0; ii < endpoints.Count; ii++) + { + EndpointDescription endpoint = endpoints[ii]; + + // check for a match on the URL scheme. + if (endpoint.EndpointUrl.StartsWith(uri.Scheme)) + { + // check if security was requested. + if (useSecurity) + { + if (endpoint.SecurityMode == MessageSecurityMode.None) + { + continue; + } + } + else + { + if (endpoint.SecurityMode != MessageSecurityMode.None) + { + continue; + } + } + + // pick the first available endpoint by default. + if (selectedEndpoint == null) + { + selectedEndpoint = endpoint; + } + + // The security level is a relative measure assigned by the server to the + // endpoints that it returns. Clients should always pick the highest level + // unless they have a reason not too. + if (endpoint.SecurityLevel > selectedEndpoint.SecurityLevel) + { + selectedEndpoint = endpoint; + } + } + } + + // pick the first available endpoint by default. + if (selectedEndpoint == null && endpoints.Count > 0) + { + selectedEndpoint = endpoints[0]; + } + } + + // return the selected endpoint. + return selectedEndpoint; + } + #endregion + } +} diff --git a/SampleApplications/SDK/Opc.Ua.Client/Session.cs b/SampleApplications/SDK/Opc.Ua.Client/Session.cs index c58f9bc2f3..bcfeef10be 100644 --- a/SampleApplications/SDK/Opc.Ua.Client/Session.cs +++ b/SampleApplications/SDK/Opc.Ua.Client/Session.cs @@ -830,26 +830,29 @@ namespace Opc.Ua.Client if (domains != null) { - string hostname = endpoint.EndpointUrl.DnsSafeHost; + string [] hostnames = endpoint.EndpointUrl.DnsSafeHost.Split('.'); - if (hostname == "localhost" || hostname == "127.0.0.1") + if (String.Compare(hostnames[0], "localhost", StringComparison.CurrentCultureIgnoreCase)==0) { - hostname = Utils.GetHostName(); + hostnames[0] = Utils.GetHostName(); } for (int ii = 0; ii < domains.Count; ii++) { - if (String.Compare(hostname, domains[ii], StringComparison.CurrentCultureIgnoreCase) == 0) + foreach (string hostname in hostnames) { - domainFound = true; - break; + if (String.Compare(hostname, domains[ii], StringComparison.CurrentCultureIgnoreCase) == 0) + { + domainFound = true; + break; + } } } - } - if (!domainFound) - { - throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid); + if (!domainFound) + { + throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid); + } } } @@ -1994,7 +1997,7 @@ namespace Opc.Ua.Client serverCertificate = Utils.ParseCertificateBlob(certificateData); m_configuration.CertificateValidator.Validate(serverCertificate); - if(checkDomain) + if (checkDomain) { CheckCertificateDomain(m_endpoint); } diff --git a/SampleApplications/SDK/Opc.Ua.Configuration/ApplicationInstance.cs b/SampleApplications/SDK/Opc.Ua.Configuration/ApplicationInstance.cs index 3d4a94dfb7..e340fa6001 100644 --- a/SampleApplications/SDK/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/SampleApplications/SDK/Opc.Ua.Configuration/ApplicationInstance.cs @@ -707,19 +707,6 @@ namespace Opc.Ua.Configuration await AddToTrustedStore(configuration, certificate); } - // add to discovery server. - if (configuration.ApplicationType == ApplicationType.Server || configuration.ApplicationType == ApplicationType.ClientAndServer) - { - try - { - await AddToDiscoveryServerTrustList(certificate, null, null, configuration.SecurityConfiguration.TrustedPeerCertificates); - } - catch (Exception e) - { - Utils.Trace(e, "Could not add certificate to LDS trust list."); - } - } - // update configuration file. ConfigUtils.UpdateConfigurationLocation(InstallConfig.ExecutableFile, InstallConfig.ConfigurationFile); @@ -1012,19 +999,6 @@ namespace Opc.Ua.Configuration await AddToTrustedStore(configuration, certificate); } - // add to discovery server. - if (configuration.ApplicationType == ApplicationType.Server || configuration.ApplicationType == ApplicationType.ClientAndServer) - { - try - { - await AddToDiscoveryServerTrustList(certificate, null, null, configuration.SecurityConfiguration.TrustedPeerCertificates); - } - catch (Exception e) - { - Utils.Trace(e, "Could not add certificate to LDS trust list."); - } - } - return true; } #endregion @@ -1324,121 +1298,6 @@ namespace Opc.Ua.Configuration } } - /// - /// Adds the application certificate to the discovery server trust list. - /// - public static async Task AddToDiscoveryServerTrustList( - X509Certificate2 certificate, - string oldThumbprint, - IList issuers, - CertificateStoreIdentifier trustedCertificateStore) - { - Utils.Trace(Utils.TraceMasks.Information, "Adding certificate to discovery server trust list."); - - try - { - string configurationPath = Utils.GetAbsoluteFilePath(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "OPC Foundation" + Path.DirectorySeparatorChar + "Config" + Path.DirectorySeparatorChar + "Opc.Ua.DiscoveryServer.Config.xml", true, false, false); - if (configurationPath == null) - { - Utils.Trace("Could not find the discovery server configuration file. Please confirm that it is installed."); - } - else - { - Opc.Ua.Security.SecuredApplication ldsConfiguration = new Opc.Ua.Security.SecurityConfigurationManager().ReadConfiguration(configurationPath); - CertificateStoreIdentifier csid = Opc.Ua.Security.SecuredApplication.FromCertificateStoreIdentifier(ldsConfiguration.TrustedCertificateStore); - await AddApplicationCertificateToStore(csid, certificate, oldThumbprint); - - if (issuers != null && ldsConfiguration.IssuerCertificateStore != null) - { - csid = Opc.Ua.Security.SecuredApplication.FromCertificateStoreIdentifier(ldsConfiguration.IssuerCertificateStore); - AddIssuerCertificatesToStore(csid, issuers); - } - - CertificateIdentifier cid = Opc.Ua.Security.SecuredApplication.FromCertificateIdentifier(ldsConfiguration.ApplicationCertificate); - X509Certificate2 ldsCertificate = await cid.Find(false); - - // add LDS certificate to application trust list. - if (ldsCertificate != null && trustedCertificateStore != null) - { - await AddApplicationCertificateToStore(csid, ldsCertificate, null); - } - } - } - catch (Exception e) - { - Utils.Trace(e, "Could not add certificate to discovery server trust list."); - } - } - - /// - /// Adds an application certificate to a store. - /// - private static async Task AddApplicationCertificateToStore( - CertificateStoreIdentifier csid, - X509Certificate2 certificate, - string oldThumbprint) - { - ICertificateStore store = csid.OpenStore(); - - try - { - // delete the old certificate. - if (oldThumbprint != null) - { - await store.Delete(oldThumbprint); - } - - // delete certificates with the same application uri. - if (store.FindByThumbprint(certificate.Thumbprint) == null) - { - string applicationUri = Utils.GetApplicationUriFromCertificate(certificate); - - // delete any existing certificates. - X509Certificate2Collection collection = await store.Enumerate(); - foreach (X509Certificate2 target in collection) - { - if (Utils.CompareDistinguishedName(target.Subject, certificate.Subject)) - { - if (Utils.GetApplicationUriFromCertificate(target) == applicationUri) - { - await store.Delete(target.Thumbprint); - } - } - } - - // add new certificate. - await store.Add(new X509Certificate2(certificate.RawData)); - } - } - finally - { - store.Close(); - } - } - - /// - /// Adds an application certificate to a store. - /// - private static void AddIssuerCertificatesToStore(CertificateStoreIdentifier csid, IList issuers) - { - ICertificateStore store = csid.OpenStore(); - - try - { - foreach (X509Certificate2 issuer in issuers) - { - if (store.FindByThumbprint(issuer.Thumbprint) == null) - { - store.Add(issuer); - } - } - } - finally - { - store.Close(); - } - } - /// /// Adds the certificate to the Trusted Certificate Store /// diff --git a/SampleApplications/SDK/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs b/SampleApplications/SDK/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs index e1e7ae478a..388881fb6f 100644 --- a/SampleApplications/SDK/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs +++ b/SampleApplications/SDK/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs @@ -154,6 +154,25 @@ namespace Opc.Ua.Server getMonitoredItems.OnCallMethod = OnGetMonitoredItems; } + // set ArrayDimensions for GetMonitoredItems.OutputArguments.Value. + PropertyState getMonitoredItemsOutputArguments = (PropertyState)FindPredefinedNode( + VariableIds.Server_GetMonitoredItems_OutputArguments, + typeof(PropertyState)); + + if (getMonitoredItemsOutputArguments != null) + { + Argument[] outputArgumentsValue = (Argument[])getMonitoredItemsOutputArguments.Value; + + if (outputArgumentsValue != null) + { + foreach (Argument argument in outputArgumentsValue) + { + argument.ArrayDimensions = new UInt32Collection { 0 }; + } + + getMonitoredItemsOutputArguments.ClearChangeMasks(SystemContext, false); + } + } } } diff --git a/SampleApplications/Samples/ClientControls.Net4/ClientUtils.cs b/SampleApplications/Samples/ClientControls.Net4/ClientUtils.cs index af5dcf731b..7038a81a2c 100644 --- a/SampleApplications/Samples/ClientControls.Net4/ClientUtils.cs +++ b/SampleApplications/Samples/ClientControls.Net4/ClientUtils.cs @@ -284,154 +284,6 @@ namespace Opc.Ua.Client.Controls } #endregion - #region Discovery - /// - /// Discovers the servers on the local machine. - /// - /// The configuration. - /// A list of server urls. - public static IList DiscoverServers(ApplicationConfiguration configuration) - { - List serverUrls = new List(); - - // set a short timeout because this is happening in the drop down event. - EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration); - endpointConfiguration.OperationTimeout = 5000; - - // Connect to the local discovery server and find the available servers. - using (DiscoveryClient client = DiscoveryClient.Create(new Uri("opc.tcp://localhost:4840"), endpointConfiguration)) - { - ApplicationDescriptionCollection servers = client.FindServers(null); - - // populate the drop down list with the discovery URLs for the available servers. - for (int ii = 0; ii < servers.Count; ii++) - { - if (servers[ii].ApplicationType == ApplicationType.DiscoveryServer) - { - continue; - } - - for (int jj = 0; jj < servers[ii].DiscoveryUrls.Count; jj++) - { - string discoveryUrl = servers[ii].DiscoveryUrls[jj]; - - // Many servers will use the '/discovery' suffix for the discovery endpoint. - // The URL without this prefix should be the base URL for the server. - if (discoveryUrl.EndsWith("/discovery")) - { - discoveryUrl = discoveryUrl.Substring(0, discoveryUrl.Length - "/discovery".Length); - } - - // ensure duplicates do not get added. - if (!serverUrls.Contains(discoveryUrl)) - { - serverUrls.Add(discoveryUrl); - } - } - } - } - - return serverUrls; - } - - /// - /// Finds the endpoint that best matches the current settings. - /// - /// The discovery URL. - /// if set to true select an endpoint that uses security. - /// The best available endpoint. - public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity) - { - // needs to add the '/discovery' back onto non-UA TCP URLs. - if (!discoveryUrl.StartsWith(Utils.UriSchemeOpcTcp)) - { - if (!discoveryUrl.EndsWith("/discovery")) - { - discoveryUrl += "/discovery"; - } - } - - // parse the selected URL. - Uri uri = new Uri(discoveryUrl); - - // set a short timeout because this is happening in the drop down event. - EndpointConfiguration configuration = EndpointConfiguration.Create(); - configuration.OperationTimeout = 5000; - - EndpointDescription selectedEndpoint = null; - - // Connect to the server's discovery endpoint and find the available configuration. - using (DiscoveryClient client = DiscoveryClient.Create(uri, configuration)) - { - EndpointDescriptionCollection endpoints = client.GetEndpoints(null); - - // select the best endpoint to use based on the selected URL and the UseSecurity checkbox. - for (int ii = 0; ii < endpoints.Count; ii++) - { - EndpointDescription endpoint = endpoints[ii]; - - // check for a match on the URL scheme. - if (endpoint.EndpointUrl.StartsWith(uri.Scheme)) - { - // check if security was requested. - if (useSecurity) - { - if (endpoint.SecurityMode == MessageSecurityMode.None) - { - continue; - } - } - else - { - if (endpoint.SecurityMode != MessageSecurityMode.None) - { - continue; - } - } - - // pick the first available endpoint by default. - if (selectedEndpoint == null) - { - selectedEndpoint = endpoint; - } - - // The security level is a relative measure assigned by the server to the - // endpoints that it returns. Clients should always pick the highest level - // unless they have a reason not too. - if (endpoint.SecurityLevel > selectedEndpoint.SecurityLevel) - { - selectedEndpoint = endpoint; - } - } - } - - // pick the first available endpoint by default. - if (selectedEndpoint == null && endpoints.Count > 0) - { - selectedEndpoint = endpoints[0]; - } - } - - // if a server is behind a firewall it may return URLs that are not accessible to the client. - // This problem can be avoided by assuming that the domain in the URL used to call - // GetEndpoints can be used to access any of the endpoints. This code makes that conversion. - // Note that the conversion only makes sense if discovery uses the same protocol as the endpoint. - - Uri endpointUrl = Utils.ParseUri(selectedEndpoint.EndpointUrl); - - if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme) - { - UriBuilder builder = new UriBuilder(endpointUrl); - builder.Host = uri.DnsSafeHost; - builder.Port = uri.Port; - selectedEndpoint.EndpointUrl = builder.ToString(); - } - - // return the selected endpoint. - return selectedEndpoint; - } - #endregion - #region Browse /// /// Browses the address space and returns the references found. @@ -938,7 +790,6 @@ namespace Opc.Ua.Client.Controls } #endregion - #region Type Model Browsing /// /// Collects the instance declarations for a type. diff --git a/SampleApplications/Samples/ClientControls.Net4/Common/Client/ConnectServerCtrl.cs b/SampleApplications/Samples/ClientControls.Net4/Common/Client/ConnectServerCtrl.cs index f60a73c4d3..9d6818c98d 100644 --- a/SampleApplications/Samples/ClientControls.Net4/Common/Client/ConnectServerCtrl.cs +++ b/SampleApplications/Samples/ClientControls.Net4/Common/Client/ConnectServerCtrl.cs @@ -56,6 +56,7 @@ namespace Opc.Ua.Client.Controls private ApplicationConfiguration m_configuration; private Session m_session; private int m_reconnectPeriod = 10; + private int m_discoverTimeout = 5000; private SessionReconnectHandler m_reconnectHandler; private CertificateValidationEventHandler m_CertificateValidation; private EventHandler m_ReconnectComplete; @@ -285,7 +286,7 @@ namespace Opc.Ua.Client.Controls } // select the best endpoint. - EndpointDescription endpointDescription = ClientUtils.SelectEndpoint(serverUrl, UseSecurityCK.Checked); + EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, UseSecurityCK.Checked, m_discoverTimeout); EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); @@ -398,7 +399,7 @@ namespace Opc.Ua.Client.Controls } // return the selected endpoint. - return ClientUtils.SelectEndpoint(discoveryUrl, UseSecurityCK.Checked); + return CoreClientUtils.SelectEndpoint(discoveryUrl, UseSecurityCK.Checked, m_discoverTimeout); } finally { diff --git a/SampleApplications/Samples/NetCoreConsoleClient/Program.cs b/SampleApplications/Samples/NetCoreConsoleClient/Program.cs index 4c569be180..33d3ebf7d7 100644 --- a/SampleApplications/Samples/NetCoreConsoleClient/Program.cs +++ b/SampleApplications/Samples/NetCoreConsoleClient/Program.cs @@ -48,6 +48,7 @@ namespace NetCoreConsoleClient public static async Task ConsoleSampleClient(string endpointURL) { Console.WriteLine("1 - Create an Application Configuration."); + Utils.SetTraceOutput(Utils.TraceOutput.DebugAndFile); var config = new ApplicationConfiguration() { ApplicationName = "UA Core Sample Client", @@ -130,9 +131,7 @@ namespace NetCoreConsoleClient } Console.WriteLine("2 - Discover endpoints of {0}.", endpointURL); - Uri endpointURI = new Uri(endpointURL); - var endpointCollection = DiscoverEndpoints(config, endpointURI, 10); - var selectedEndpoint = SelectUaTcpEndpoint(endpointCollection, haveAppCertificate); + var selectedEndpoint = CoreClientUtils.SelectEndpoint(endpointURL, haveAppCertificate); Console.WriteLine(" Selected endpoint uses: {0}", selectedEndpoint.SecurityPolicyUri.Substring(selectedEndpoint.SecurityPolicyUri.LastIndexOf('#') + 1)); @@ -219,61 +218,5 @@ namespace NetCoreConsoleClient e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted); } - private static EndpointDescriptionCollection DiscoverEndpoints(ApplicationConfiguration config, Uri discoveryUrl, int timeout) - { - // use a short timeout. - EndpointConfiguration configuration = EndpointConfiguration.Create(config); - configuration.OperationTimeout = timeout; - - using (DiscoveryClient client = DiscoveryClient.Create( - discoveryUrl, - EndpointConfiguration.Create(config))) - { - try - { - EndpointDescriptionCollection endpoints = client.GetEndpoints(null); - ReplaceLocalHostWithRemoteHost(endpoints, discoveryUrl); - return endpoints; - } - catch (Exception e) - { - Console.WriteLine("Could not fetch endpoints from url: {0}", discoveryUrl); - Console.WriteLine("Reason = {0}", e.Message); - throw e; - } - } - } - - private static EndpointDescription SelectUaTcpEndpoint(EndpointDescriptionCollection endpointCollection, bool haveCert) - { - EndpointDescription bestEndpoint = null; - foreach (EndpointDescription endpoint in endpointCollection) - { - if (endpoint.TransportProfileUri == Profiles.UaTcpTransport) - { - if (bestEndpoint == null || - haveCert && (endpoint.SecurityLevel > bestEndpoint.SecurityLevel) || - !haveCert && (endpoint.SecurityLevel < bestEndpoint.SecurityLevel)) - { - bestEndpoint = endpoint; - } - } - } - return bestEndpoint; - } - - private static void ReplaceLocalHostWithRemoteHost(EndpointDescriptionCollection endpoints, Uri discoveryUrl) - { - foreach (EndpointDescription endpoint in endpoints) - { - endpoint.EndpointUrl = Utils.ReplaceLocalhost(endpoint.EndpointUrl, discoveryUrl.DnsSafeHost); - StringCollection updatedDiscoveryUrls = new StringCollection(); - foreach (string url in endpoint.Server.DiscoveryUrls) - { - updatedDiscoveryUrls.Add(Utils.ReplaceLocalhost(url, discoveryUrl.DnsSafeHost)); - } - endpoint.Server.DiscoveryUrls = updatedDiscoveryUrls; - } - } } } diff --git a/SampleApplications/Workshop/Reference/ConsoleReferenceServer/ConsoleReferenceServer.csproj b/SampleApplications/Workshop/Reference/ConsoleReferenceServer/ConsoleReferenceServer.csproj index dea189443f..b1b1352f1b 100644 --- a/SampleApplications/Workshop/Reference/ConsoleReferenceServer/ConsoleReferenceServer.csproj +++ b/SampleApplications/Workshop/Reference/ConsoleReferenceServer/ConsoleReferenceServer.csproj @@ -5,7 +5,6 @@ ConsoleReferenceServer Exe ConsoleReferenceServer - 1.0.4 false false false diff --git a/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Program.cs b/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Program.cs index e538d2ac51..e7766918df 100644 --- a/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Program.cs +++ b/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Program.cs @@ -103,6 +103,7 @@ namespace Quickstarts.ReferenceServer { Console.WriteLine("Server stopped. Waiting for exit..."); + server.Stop(); server.Dispose(); server = null; diff --git a/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index 5ca4fac7fb..f088750646 100644 --- a/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/SampleApplications/Workshop/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -192,7 +192,8 @@ - + + 5 diff --git a/SampleApplications/Workshop/Reference/Server/Quickstarts.ReferenceServer.Config.xml b/SampleApplications/Workshop/Reference/Server/Quickstarts.ReferenceServer.Config.xml index 2d9e01be59..dcb4b2b554 100644 --- a/SampleApplications/Workshop/Reference/Server/Quickstarts.ReferenceServer.Config.xml +++ b/SampleApplications/Workshop/Reference/Server/Quickstarts.ReferenceServer.Config.xml @@ -192,7 +192,8 @@ - + + 5 diff --git a/SampleApplications/Workshop/Reference/Server/ReferenceNodeManager.cs b/SampleApplications/Workshop/Reference/Server/ReferenceNodeManager.cs index e861d6ccbb..0c4e712a83 100644 --- a/SampleApplications/Workshop/Reference/Server/ReferenceNodeManager.cs +++ b/SampleApplications/Workshop/Reference/Server/ReferenceNodeManager.cs @@ -233,11 +233,8 @@ namespace Quickstarts.ReferenceServer #region Scalar_Static_Arrays FolderState arraysFolder = CreateFolder(staticFolder, "Scalar_Static_Arrays", "Arrays"); const string staticArrays = "Scalar_Static_Arrays_"; - // set ArrayDimensions for Boolean variable. - BaseDataVariableState booleanArrayVar = CreateVariable(arraysFolder, staticArrays + "Boolean", "Boolean", DataTypeIds.Boolean, ValueRanks.OneDimension); - booleanArrayVar.ArrayDimensions = new ReadOnlyList(new List { 0 }); - variables.Add(booleanArrayVar); + variables.Add(CreateVariable(arraysFolder, staticArrays + "Boolean", "Boolean", DataTypeIds.Boolean, ValueRanks.OneDimension)); variables.Add(CreateVariable(arraysFolder, staticArrays + "Byte", "Byte", DataTypeIds.Byte, ValueRanks.OneDimension)); variables.Add(CreateVariable(arraysFolder, staticArrays + "ByteString", "ByteString", DataTypeIds.ByteString, ValueRanks.OneDimension)); variables.Add(CreateVariable(arraysFolder, staticArrays + "DateTime", "DateTime", DataTypeIds.DateTime, ValueRanks.OneDimension)); @@ -2089,6 +2086,15 @@ namespace Quickstarts.ReferenceServer variable.StatusCode = StatusCodes.Good; variable.Timestamp = DateTime.UtcNow; + if (valueRank == ValueRanks.OneDimension) + { + variable.ArrayDimensions = new ReadOnlyList(new List { 0 }); + } + else if (valueRank == ValueRanks.TwoDimensions) + { + variable.ArrayDimensions = new ReadOnlyList(new List { 0, 0 }); + } + if (parent != null) { parent.AddChild(variable); diff --git a/SampleApplications/Workshop/Reference/Server/ReferenceServer.cs b/SampleApplications/Workshop/Reference/Server/ReferenceServer.cs index 79353e7c2b..6914b2abca 100644 --- a/SampleApplications/Workshop/Reference/Server/ReferenceServer.cs +++ b/SampleApplications/Workshop/Reference/Server/ReferenceServer.cs @@ -28,6 +28,7 @@ * ======================================================================*/ using System; +using System.Threading; using System.Collections.Generic; using System.Reflection; using System.Security.Cryptography.X509Certificates; @@ -67,6 +68,19 @@ namespace Quickstarts.ReferenceServer // create the custom node managers. nodeManagers.Add(new EmptyNodeManager(server, configuration)); + // get the ShutdownDelay configuration parameter. + ReferenceServerConfiguration referenceServerConfiguration = configuration.ParseExtension(); + + if (referenceServerConfiguration != null) + { + m_shutdownDelay = referenceServerConfiguration.ShutdownDelay; + } + else + { + // default value of 5 seconds. + m_shutdownDelay = 5; + } + // create master node manager. return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray()); } @@ -125,6 +139,46 @@ namespace Quickstarts.ReferenceServer // request notifications when the user identity is changed. all valid users are accepted by default. server.SessionManager.ImpersonateUser += new ImpersonateEventHandler(SessionManager_ImpersonateUser); } + + /// + /// Cleans up before the server shuts down. + /// + /// + /// This method is called before any shutdown processing occurs. + /// + protected override void OnServerStopping() + { + try + { + // check for connected clients + IList currentessions = this.ServerInternal.SessionManager.GetSessions(); + + if (currentessions.Count > 0) + { + // provide some time for the connected clients to detect the shutdown state. + ServerInternal.Status.Value.ShutdownReason = new LocalizedText("en-US", "Application closed."); + ServerInternal.Status.Variable.ShutdownReason.Value = new LocalizedText("en-US", "Application closed."); + ServerInternal.Status.Value.State = ServerState.Shutdown; + ServerInternal.Status.Variable.State.Value = ServerState.Shutdown; + ServerInternal.Status.Variable.ClearChangeMasks(ServerInternal.DefaultSystemContext, true); + + for (uint timeTillShutdown = m_shutdownDelay; timeTillShutdown > 0; timeTillShutdown--) + { + ServerInternal.Status.Value.SecondsTillShutdown = timeTillShutdown; + ServerInternal.Status.Variable.SecondsTillShutdown.Value = timeTillShutdown; + ServerInternal.Status.Variable.ClearChangeMasks(ServerInternal.DefaultSystemContext, true); + + Thread.Sleep(1000); + } + } + } + catch + { + // ignore error during shutdown procedure. + } + + base.OnServerStopping(); + } #endregion #region User Validation Functions /// @@ -243,6 +297,10 @@ namespace Quickstarts.ReferenceServer new LocalizedText(info))); } } + + #region Private Fields + private uint m_shutdownDelay = 0; + #endregion #endregion } } diff --git a/SampleApplications/Workshop/Reference/Server/ReferenceServerConfiguration.cs b/SampleApplications/Workshop/Reference/Server/ReferenceServerConfiguration.cs index 2e64fb32e8..5b39138324 100644 --- a/SampleApplications/Workshop/Reference/Server/ReferenceServerConfiguration.cs +++ b/SampleApplications/Workshop/Reference/Server/ReferenceServerConfiguration.cs @@ -68,9 +68,19 @@ namespace Quickstarts.ReferenceServer #endregion #region Public Properties + /// + /// The delay in seconds to allow a graceful shutdown. + /// + [DataMember(Order = 1)] + public uint ShutdownDelay + { + get { return m_shutdownDelay; } + set { m_shutdownDelay = value; } + } #endregion #region Private Members + private uint m_shutdownDelay; #endregion } } diff --git a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs index a02ddde00f..093af42df6 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs @@ -72,6 +72,34 @@ namespace Opc.Ua profileUris, out endpoints); + // if a server is behind a firewall, can only be accessed with a FQDN or IP address + // it may return URLs that are not accessible to the client. This problem can be avoided + // by assuming that the domain in the URL used to call GetEndpoints can be used to + // access any of the endpoints. This code patches the returned endpoints accordingly. + Uri endpointUrl = Utils.ParseUri(this.Endpoint.EndpointUrl); + if (endpointUrl != null) + { + // patch discovery Url to endpoint Url used for service call + foreach (EndpointDescription discoveryEndPoint in endpoints) + { + Uri discoveryEndPointUri = Utils.ParseUri(discoveryEndPoint.EndpointUrl); + if (endpointUrl.Scheme == discoveryEndPointUri.Scheme) + { + UriBuilder builder = new UriBuilder(discoveryEndPointUri); + builder.Host = endpointUrl.DnsSafeHost; + builder.Port = endpointUrl.Port; + discoveryEndPoint.EndpointUrl = builder.ToString(); + } + + if (discoveryEndPoint.Server != null && + discoveryEndPoint.Server.DiscoveryUrls != null) + { + discoveryEndPoint.Server.DiscoveryUrls.Clear(); + discoveryEndPoint.Server.DiscoveryUrls.Add(this.Endpoint.EndpointUrl.ToString()); + } + } + } + return endpoints; } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs index c8aedda962..c6ef48297e 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs @@ -1122,7 +1122,7 @@ namespace Opc.Ua // third pass - match based on security level. foreach (EndpointDescription description in matches) { - if (description.SecurityMode > match.SecurityMode) + if (description.SecurityLevel > match.SecurityLevel) { match = description; } diff --git a/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs b/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs index 153388ff72..1a3fcfc90a 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/DataGenerator.cs @@ -350,7 +350,7 @@ namespace Opc.Ua.Test actualDimensions[ii] = (int)arrayDimensions[ii]; } - if (actualDimensions[ii] == 0) + while (actualDimensions[ii] == 0) { actualDimensions[ii] = m_random.NextInt32(m_maxArrayLength); } diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index 27b59cf577..ea8ed0f7ac 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -271,7 +271,7 @@ namespace Opc.Ua truncated = true; } - using (StreamWriter writer = new StreamWriter(File.Open(file.FullName, FileMode.Append))) + using (StreamWriter writer = new StreamWriter(File.Open(file.FullName, FileMode.Append, FileAccess.Write, FileShare.Read))) { if (truncated) { @@ -1389,18 +1389,46 @@ namespace Opc.Ua return value; } - // copy arrays. + // copy arrays, any dimension. Array array = value as Array; if (array != null) { - Array clone = Array.CreateInstance(type.GetElementType(), array.Length); - - for (int ii = 0; ii < array.Length; ii++) + if (array.Rank == 1) { - clone.SetValue(Utils.Clone(array.GetValue(ii)), ii); + Array clone = Array.CreateInstance(type.GetElementType(), array.Length); + for (int ii = 0; ii < array.Length; ii++) + { + clone.SetValue(Utils.Clone(array.GetValue(ii)), ii); + } + return clone; } + else + { + int[] arrayRanks = new int[array.Rank]; + int[] arrayIndex = new int[array.Rank]; + for (int ii=0; ii