diff --git a/Applications/ConsoleReferenceServer/IUAServer.cs b/Applications/ConsoleReferenceServer/IUAServer.cs new file mode 100644 index 0000000000..a2fc8d14bf --- /dev/null +++ b/Applications/ConsoleReferenceServer/IUAServer.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Opc.Ua; +using Opc.Ua.Configuration; +using Opc.Ua.Server; + +namespace Quickstarts +{ + public interface IUAServer where T : IStandardServer + { + IApplicationInstance Application { get; } + + ApplicationConfiguration Configuration { get; } + + bool AutoAccept { get; set; } + + string Password { get; set; } + + ExitCode ExitCode { get; } + + T Server { get; } + + /// + /// Load the application configuration. + /// + Task LoadAsync(string applicationName, string configSectionName); + + /// + /// Load the application configuration. + /// + Task CheckCertificateAsync(bool renewCertificate); + + /// + /// Create server instance and add node managers. + /// + void Create(IList nodeManagerFactories); + + /// + /// Start the server. + /// + Task StartAsync(); + + /// + /// Stops the server. + /// + Task StopAsync(); + } +} diff --git a/Applications/ConsoleReferenceServer/Program.cs b/Applications/ConsoleReferenceServer/Program.cs index 2f11acbd2b..7a72561883 100644 --- a/Applications/ConsoleReferenceServer/Program.cs +++ b/Applications/ConsoleReferenceServer/Program.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -32,8 +32,11 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Opc.Ua; +using Opc.Ua.Configuration; +using Opc.Ua.Server; namespace Quickstarts.ReferenceServer { @@ -81,6 +84,15 @@ public static async Task Main(string[] args) { "ctt", "CTT mode, use to preset alarms for CTT testing.", c => cttMode = c != null }, }; + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped() + .AddScoped(sp => new LogWriter()) + .AddScoped, UAServer>(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + try { // parse command line and set options @@ -88,14 +100,13 @@ public static async Task Main(string[] args) if (logConsole && appLog) { - output = new LogWriter(); + output = serviceProvider.GetRequiredService(); } // create the UA server - var server = new UAServer(output) { - AutoAccept = autoAccept, - Password = password - }; + IUAServer server = serviceProvider.GetRequiredService>(); + server.AutoAccept = autoAccept; + server.Password = password; // load the server configuration, validate certificates output.WriteLine("Loading configuration from {0}.", configSectionName); diff --git a/Applications/ConsoleReferenceServer/UAServer.cs b/Applications/ConsoleReferenceServer/UAServer.cs index 9f87609894..a2c8e20964 100644 --- a/Applications/ConsoleReferenceServer/UAServer.cs +++ b/Applications/ConsoleReferenceServer/UAServer.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -39,9 +39,25 @@ namespace Quickstarts { - public class UAServer where T : StandardServer, new() + public class UAServer : IUAServer where T : IStandardServer { - public ApplicationInstance Application => m_application; + /// + /// Constructor of the server. + /// + /// Application instance. + /// Server of the specified generic type. + /// The text output. + public UAServer( + IApplicationInstance applicationInstance, + T server, + LogWriter writer) + { + m_application = applicationInstance; + m_server = server; + m_output = writer; + } + + public IApplicationInstance Application => m_application; public ApplicationConfiguration Configuration => m_application.ApplicationConfiguration; public bool AutoAccept { get; set; } @@ -50,15 +66,6 @@ namespace Quickstarts public ExitCode ExitCode { get; private set; } public T Server => m_server; - /// - /// Ctor of the server. - /// - /// The text output. - public UAServer(TextWriter writer) - { - m_output = writer; - } - /// /// Load the application configuration. /// @@ -69,13 +76,12 @@ public async Task LoadAsync(string applicationName, string configSectionName) ExitCode = ExitCode.ErrorNotStarted; ApplicationInstance.MessageDlg = new ApplicationMessageDlg(m_output); - CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(Password); - m_application = new ApplicationInstance { - ApplicationName = applicationName, - ApplicationType = ApplicationType.Server, - ConfigSectionName = configSectionName, - CertificatePasswordProvider = PasswordProvider - }; + var passwordProvider = new CertificatePasswordProvider(Password); + + m_application.ApplicationName = applicationName; + m_application.ApplicationType = ApplicationType.Server; + m_application.ConfigSectionName = configSectionName; + m_application.CertificatePasswordProvider = passwordProvider; // load the application configuration. await m_application.LoadApplicationConfiguration(false).ConfigureAwait(false); @@ -125,8 +131,6 @@ public void Create(IList nodeManagerFactories) { try { - // create the server. - m_server = new T(); if (nodeManagerFactories != null) { foreach (var factory in nodeManagerFactories) @@ -148,9 +152,6 @@ public async Task StartAsync() { try { - // create the server. - m_server = m_server ?? new T(); - // start the server await m_application.Start(m_server).ConfigureAwait(false); @@ -190,7 +191,6 @@ public async Task StopAsync() using (T server = m_server) { // Stop status thread - m_server = null; await m_status.ConfigureAwait(false); // Stop server and dispose @@ -281,8 +281,8 @@ private async Task StatusThreadAsync() #region Private Members private readonly TextWriter m_output; - private ApplicationInstance m_application; - private T m_server; + private readonly T m_server; + private readonly IApplicationInstance m_application; private Task m_status; private DateTime m_lastEventTime; #endregion diff --git a/Applications/Quickstarts.Servers/ReferenceServer/IReferenceServer.cs b/Applications/Quickstarts.Servers/ReferenceServer/IReferenceServer.cs new file mode 100644 index 0000000000..841c9df9da --- /dev/null +++ b/Applications/Quickstarts.Servers/ReferenceServer/IReferenceServer.cs @@ -0,0 +1,44 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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 Opc.Ua; +using Opc.Ua.Server; + +namespace Quickstarts.ReferenceServer +{ + public interface IReferenceServer : IReverseConnectServer + { + ITokenValidator TokenValidator { get; set; } + + /// + /// Creates an instance of the service host. + /// + ServiceHost CreateServiceHost(ServerBase server, params Uri[] addresses); + } +} diff --git a/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs index 6779c4eff5..74365cb1dd 100644 --- a/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs +++ b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -32,6 +32,7 @@ using System.Linq; using System.Security.Cryptography.X509Certificates; using Opc.Ua; +using Opc.Ua.Configuration; using Opc.Ua.Server; namespace Quickstarts.ReferenceServer @@ -43,12 +44,23 @@ namespace Quickstarts.ReferenceServer /// Each server instance must have one instance of a StandardServer object which is /// responsible for reading the configuration file, creating the endpoints and dispatching /// incoming requests to the appropriate handler. - /// + /// /// This sub-class specifies non-configurable metadata such as Product Name and initializes /// the EmptyNodeManager which provides access to the data exposed by the Server. /// - public partial class ReferenceServer : ReverseConnectServer + public partial class ReferenceServer : ReverseConnectServer, IReferenceServer { + public ReferenceServer( + IApplicationInstance applicationInstance, + IServerInternal serverInternal, + IMainNodeManagerFactory mainNodeManagerFactory) + : base(applicationInstance, serverInternal, mainNodeManagerFactory) + { + m_applicationInstance = applicationInstance; + m_serverInternal = serverInternal; + m_mainNodeManagerFactory = mainNodeManagerFactory; + } + #region Properties public ITokenValidator TokenValidator { get; set; } @@ -63,7 +75,9 @@ public partial class ReferenceServer : ReverseConnectServer /// always creates a CoreNodeManager which handles the built-in nodes defined by the specification. /// Any additional NodeManagers are expected to handle application specific nodes. /// - protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration) + protected override IMasterNodeManager CreateMasterNodeManager( + IServerInternal server, + ApplicationConfiguration configuration) { Utils.LogInfo(Utils.TraceMasks.StartStop, "Creating the Reference Server Node Manager."); @@ -78,7 +92,7 @@ protected override MasterNodeManager CreateMasterNodeManager(IServerInternal ser } // create master node manager. - return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray()); + return m_mainNodeManagerFactory.CreateMasterNodeManager(null, nodeManagers.ToArray()); } protected override IMonitoredItemQueueFactory CreateMonitoredItemQueueFactory(IServerInternal server, ApplicationConfiguration configuration) @@ -136,7 +150,7 @@ protected override ResourceManager CreateResourceManager(IServerInternal server, /// Initializes the server before it starts up. /// /// - /// This method is called before any startup processing occurs. The sub-class may update the + /// This method is called before any startup processing occurs. The sub-class may update the /// configuration object or do any other application specific startup tasks. /// protected override void OnServerStarting(ApplicationConfiguration configuration) @@ -162,11 +176,10 @@ protected override void OnServerStarted(IServerInternal server) try { - lock (ServerInternal.Status.Lock) - { - // allow a faster sampling interval for CurrentTime node. - ServerInternal.Status.Variable.CurrentTime.MinimumSamplingInterval = 250; - } + ServerInternal.UpdateServerStatus( + (serverStatus) => { + serverStatus.Variable.CurrentTime.MinimumSamplingInterval = 250; + }); } catch { } @@ -416,7 +429,7 @@ private IUserIdentity VerifyIssuedToken(IssuedIdentityToken issuedToken) info = new TranslationInfo("IssuedTokenInvalid", "en-US", "token is an invalid issued token."); result = StatusCodes.BadIdentityTokenInvalid; } - else // Rejected + else // Rejected { // construct translation object with default text. info = new TranslationInfo("IssuedTokenRejected", "en-US", "token is rejected."); @@ -434,6 +447,10 @@ private IUserIdentity VerifyIssuedToken(IssuedIdentityToken issuedToken) #region Private Fields private ICertificateValidator m_userCertificateValidator; + private readonly IMainNodeManagerFactory m_mainNodeManagerFactory; + private readonly IServerInternal m_serverInternal; + private readonly IApplicationInstance m_applicationInstance; + #endregion } } diff --git a/Applications/Quickstarts.Servers/Utils.cs b/Applications/Quickstarts.Servers/Utils.cs index a96ae8c133..09a512c974 100644 --- a/Applications/Quickstarts.Servers/Utils.cs +++ b/Applications/Quickstarts.Servers/Utils.cs @@ -47,7 +47,7 @@ public static class Utils /// Applies custom settings to quickstart servers for CTT run. /// /// - public static void ApplyCTTMode(TextWriter output, StandardServer server) + public static void ApplyCTTMode(TextWriter output, IStandardServer server) { var methodsToCall = new CallMethodRequestCollection(); var index = server.CurrentInstance.NamespaceUris.GetIndex(Alarms.Namespaces.Alarms); diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index ccbf372c30..cdb59f0a7d 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -44,7 +44,7 @@ namespace Opc.Ua.Configuration /// /// A class that install, configures and runs a UA application. /// - public class ApplicationInstance + public class ApplicationInstance : IApplicationInstance { #region Ctors /// @@ -67,56 +67,39 @@ public ApplicationInstance(ApplicationConfiguration applicationConfiguration) #endregion #region Public Properties - /// - /// Gets or sets the name of the application. - /// - /// The name of the application. + + /// public string ApplicationName { get { return m_applicationName; } set { m_applicationName = value; } } - /// - /// Gets or sets the type of the application. - /// - /// The type of the application. + /// public ApplicationType ApplicationType { get { return m_applicationType; } set { m_applicationType = value; } } - /// - /// Gets or sets the name of the config section containing the path to the application configuration file. - /// - /// The name of the config section. + /// public string ConfigSectionName { get { return m_configSectionName; } set { m_configSectionName = value; } } - /// - /// Gets or sets the type of configuration file. - /// - /// The type of configuration file. + /// public Type ConfigurationType { get { return m_configurationType; } set { m_configurationType = value; } } - /// - /// Gets the server. - /// - /// The server. - public ServerBase Server => m_server; + /// + public IServerBase Server => m_server; - /// - /// Gets the application configuration used when the Start() method was called. - /// - /// The application configuration. + /// public ApplicationConfiguration ApplicationConfiguration { get { return m_applicationConfiguration; } @@ -128,52 +111,30 @@ public ApplicationConfiguration ApplicationConfiguration /// public static IApplicationMessageDlg MessageDlg { get; set; } - /// - /// Get or set the certificate password provider. - /// + /// public ICertificatePasswordProvider CertificatePasswordProvider { get; set; } - /// - /// Get or set bool which indicates if the auto creation - /// of a new application certificate during startup is disabled. - /// Default is enabled./> - /// - /// - /// Prevents auto self signed cert creation in use cases - /// where an expired certificate should not be automatically - /// renewed or where it is required to only use certificates - /// provided by the user. - /// + /// public bool DisableCertificateAutoCreation { get; set; } #endregion #region Public Methods - /// - /// Processes the command line. - /// - /// - /// True if the arguments were processed; False otherwise. - /// + + /// public bool ProcessCommandLine() { // ignore processing of command line return false; } - /// - /// Starts the UA server as a Windows Service. - /// - /// The server. - public void StartAsService(ServerBase server) + /// + public void StartAsService(IServerBase server) { throw new NotImplementedException(".NetStandard Opc.Ua libraries do not support to start as a windows service"); } - /// - /// Starts the UA server. - /// - /// The server. - public async Task Start(ServerBase server) + /// + public async Task Start(IServerBase server) { m_server = server; @@ -185,17 +146,13 @@ public async Task Start(ServerBase server) server.Start(m_applicationConfiguration); } - /// - /// Stops the UA server. - /// + /// public void Stop() { m_server.Stop(); } - /// - /// Loads the configuration. - /// + /// public async Task LoadAppConfig( bool silent, string filePath, @@ -244,9 +201,7 @@ public async Task LoadAppConfig( } } - /// - /// Loads the configuration. - /// + /// public async Task LoadAppConfig( bool silent, Stream stream, @@ -295,9 +250,7 @@ public async Task LoadAppConfig( } } - /// - /// Loads the application configuration. - /// + /// public async Task LoadApplicationConfiguration(Stream stream, bool silent) { ApplicationConfiguration configuration = null; @@ -322,9 +275,7 @@ public async Task LoadApplicationConfiguration(Stream return configuration; } - /// - /// Loads the application configuration. - /// + /// public async Task LoadApplicationConfiguration(string filePath, bool silent) { ApplicationConfiguration configuration = null; @@ -349,9 +300,7 @@ public async Task LoadApplicationConfiguration(string return configuration; } - /// - /// Loads the application configuration. - /// + /// public async Task LoadApplicationConfiguration(bool silent) { string filePath = ApplicationConfiguration.GetFilePathFromAppConfig(ConfigSectionName); @@ -380,9 +329,7 @@ public static ApplicationConfiguration FixupAppConfig( return configuration; } - /// - /// Create a builder for a UA application configuration. - /// + /// public IApplicationConfigurationBuilderTypes Build( string applicationUri, string productUri @@ -406,11 +353,7 @@ string productUri return new ApplicationConfigurationBuilder(this); } - /// - /// Checks for a valid application instance certificate. - /// - /// if set to true no dialogs will be displayed. - /// Minimum size of the key. + /// [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] public Task CheckApplicationInstanceCertificate( bool silent, @@ -419,36 +362,14 @@ public Task CheckApplicationInstanceCertificate( return CheckApplicationInstanceCertificate(silent, minimumKeySize, CertificateFactory.DefaultLifeTime); } - /// - /// Checks for a valid application instance certificate. - /// - /// if set to true no dialogs will be displayed. + /// public async Task CheckApplicationInstanceCertificates( bool silent) { return await CheckApplicationInstanceCertificates(silent, CertificateFactory.DefaultLifeTime).ConfigureAwait(false); } - /// - /// Deletes all application certificates. - /// - public async Task DeleteApplicationInstanceCertificate(string[] profileIds = null, CancellationToken ct = default) - { - // TODO: delete only selected profiles - if (m_applicationConfiguration == null) throw new ArgumentException("Missing configuration."); - foreach (var id in m_applicationConfiguration.SecurityConfiguration.ApplicationCertificates) - { - await DeleteApplicationInstanceCertificateAsync(m_applicationConfiguration, id, ct).ConfigureAwait(false); - } - } - - /// - /// Checks for a valid application instance certificate. - /// - /// if set to true no dialogs will be displayed. - /// Minimum size of the key. - /// The lifetime in months. - /// + /// [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] public async Task CheckApplicationInstanceCertificate( bool silent, @@ -459,12 +380,7 @@ public async Task CheckApplicationInstanceCertificate( return await CheckApplicationInstanceCertificates(silent, lifeTimeInMonths, ct).ConfigureAwait(false); } - /// - /// Checks for a valid application instance certificate. - /// - /// if set to true no dialogs will be displayed. - /// The lifetime in months. - /// + /// public async Task CheckApplicationInstanceCertificates( bool silent, ushort lifeTimeInMonths, @@ -495,6 +411,24 @@ public async Task CheckApplicationInstanceCertificates( return result; } + + /// + public async Task DeleteApplicationInstanceCertificate(string[] profileIds = null, CancellationToken ct = default) + { + // TODO: delete only selected profiles + if (m_applicationConfiguration == null) throw new ArgumentException("Missing configuration."); + foreach (var id in m_applicationConfiguration.SecurityConfiguration.ApplicationCertificates) + { + await DeleteApplicationInstanceCertificateAsync(m_applicationConfiguration, id, ct).ConfigureAwait(false); + } + } + + /// + public async Task AddOwnCertificateToTrustedStoreAsync(X509Certificate2 certificate, CancellationToken ct) + { + await AddToTrustedStoreAsync(m_applicationConfiguration, certificate, ct).ConfigureAwait(false); + } + #endregion #region Private Methods @@ -631,17 +565,6 @@ private async Task CheckCertificateTypeAsync( return true; } - /// - /// Adds a Certificate to the Trusted Store of the Application, needed e.g. for the GDS to trust it´s own CA - /// - /// The certificate to add to the store - /// The cancellation token - /// - public async Task AddOwnCertificateToTrustedStoreAsync(X509Certificate2 certificate, CancellationToken ct) - { - await AddToTrustedStoreAsync(m_applicationConfiguration, certificate, ct).ConfigureAwait(false); - } - /// /// Helper to suppress errors which are allowed for the application certificate validation. /// @@ -1144,7 +1067,7 @@ private static async Task ApproveMessageAsync(string message, bool silent) private ApplicationType m_applicationType; private string m_configSectionName; private Type m_configurationType; - private ServerBase m_server; + private IServerBase m_server; private ApplicationConfiguration m_applicationConfiguration; #endregion } diff --git a/Libraries/Opc.Ua.Configuration/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/Libraries/Opc.Ua.Configuration/Extensions/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..01d3728cfc --- /dev/null +++ b/Libraries/Opc.Ua.Configuration/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,50 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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 Microsoft.Extensions.DependencyInjection; + +namespace Opc.Ua.Configuration +{ + /// + /// Class that provides service collection extensions to setup dependency injection + /// for server objects. + /// + public static class ServiceCollectionExtensions + { + /// + /// Add all the necessary server services to dependency injection. + /// + /// The service collection of the application. + /// The updated service collection. + public static IServiceCollection AddConfigurationServices(this IServiceCollection services) + { + return services.AddScoped(); + } + } +} diff --git a/Libraries/Opc.Ua.Configuration/IApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/IApplicationInstance.cs new file mode 100644 index 0000000000..e2fe3b4b14 --- /dev/null +++ b/Libraries/Opc.Ua.Configuration/IApplicationInstance.cs @@ -0,0 +1,222 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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.IO; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; + +namespace Opc.Ua.Configuration +{ + /// + /// Interface of the application instance. + /// + public interface IApplicationInstance + { + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + string ApplicationName { get; set; } + + /// + /// Gets or sets the type of the application. + /// + /// The type of the application. + ApplicationType ApplicationType { get; set; } + + /// + /// Gets or sets the name of the config section containing the path to the application configuration file. + /// + /// The name of the config section. + string ConfigSectionName { get; set; } + + /// + /// Gets or sets the type of configuration file. + /// + /// The type of configuration file. + Type ConfigurationType { get; set; } + + /// + /// Gets the server. + /// + /// The server. + IServerBase Server { get; } + + /// + /// Gets the application configuration used when the Start() method was called. + /// + /// The application configuration. + ApplicationConfiguration ApplicationConfiguration { get; set; } + + /// + /// Get or set the certificate password provider. + /// + ICertificatePasswordProvider CertificatePasswordProvider { get; set; } + + /// + /// Get or set bool which indicates if the auto creation + /// of a new application certificate during startup is disabled. + /// Default is enabled./> + /// + /// + /// Prevents auto self signed cert creation in use cases + /// where an expired certificate should not be automatically + /// renewed or where it is required to only use certificates + /// provided by the user. + /// + bool DisableCertificateAutoCreation { get; set; } + + /// + /// Processes the command line. + /// + /// + /// True if the arguments were processed; False otherwise. + /// + bool ProcessCommandLine(); + + /// + /// Starts the UA server as a Windows Service. + /// + /// The server. + void StartAsService(IServerBase server); + + /// + /// Starts the UA server. + /// + /// The server. + Task Start(IServerBase server); + + /// + /// Stops the UA server. + /// + void Stop(); + + /// + /// Loads the configuration. + /// + Task LoadAppConfig( + bool silent, + string filePath, + ApplicationType applicationType, + Type configurationType, + bool applyTraceSettings, + ICertificatePasswordProvider certificatePasswordProvider = null); + + /// + /// Loads the configuration. + /// + Task LoadAppConfig( + bool silent, + Stream stream, + ApplicationType applicationType, + Type configurationType, + bool applyTraceSettings, + ICertificatePasswordProvider certificatePasswordProvider = null); + + /// + /// Loads the application configuration. + /// + Task LoadApplicationConfiguration(Stream stream, bool silent); + + /// + /// Loads the application configuration. + /// + Task LoadApplicationConfiguration(string filePath, bool silent); + + /// + /// Loads the application configuration. + /// + Task LoadApplicationConfiguration(bool silent); + + /// + /// Create a builder for a UA application configuration. + /// + IApplicationConfigurationBuilderTypes Build( + string applicationUri, + string productUri + ); + + /// + /// Checks for a valid application instance certificate. + /// + /// if set to true no dialogs will be displayed. + /// Minimum size of the key. + [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] + Task CheckApplicationInstanceCertificate( + bool silent, + ushort minimumKeySize); + + /// + /// Checks for a valid application instance certificate. + /// + /// if set to true no dialogs will be displayed. + Task CheckApplicationInstanceCertificates( + bool silent); + + /// + /// Checks for a valid application instance certificate. + /// + /// if set to true no dialogs will be displayed. + /// Minimum size of the key. + /// The lifetime in months. + /// + [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] + Task CheckApplicationInstanceCertificate( + bool silent, + ushort minimumKeySize, + ushort lifeTimeInMonths, + CancellationToken ct = default); + + /// + /// Checks for a valid application instance certificate. + /// + /// if set to true no dialogs will be displayed. + /// The lifetime in months. + /// + Task CheckApplicationInstanceCertificates( + bool silent, + ushort lifeTimeInMonths, + CancellationToken ct = default); + + /// + /// Deletes all application certificates. + /// + Task DeleteApplicationInstanceCertificate(string[] profileIds = null, CancellationToken ct = default); + + /// + /// Adds a Certificate to the Trusted Store of the Application, needed e.g. for the GDS to trust it´s own CA + /// + /// The certificate to add to the store + /// The cancellation token + /// + Task AddOwnCertificateToTrustedStoreAsync(X509Certificate2 certificate, CancellationToken ct); + } +} diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index 2cd5060eb8..0379ce4bbf 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -27,6 +27,19 @@ $(PackageId).Debug + + + + + + + + + + + + + @@ -34,7 +47,7 @@ $(DefineConstants);SIGNASSEMBLY - + diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index a0ed22b90b..f522b5ab88 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -29,12 +29,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Cryptography.X509Certificates; -using Opc.Ua.Server; +using Opc.Ua.Configuration; using Opc.Ua.Gds.Server.Database; -using Opc.Ua.Server.UserDatabase; -using System.Linq; using Opc.Ua.Security.Certificates; +using Opc.Ua.Server; +using Opc.Ua.Server.UserDatabase; namespace Opc.Ua.Gds.Server { @@ -45,14 +46,17 @@ namespace Opc.Ua.Gds.Server /// Each server instance must have one instance of a StandardServer object which is /// responsible for reading the configuration file, creating the endpoints and dispatching /// incoming requests to the appropriate handler. - /// + /// /// This sub-class specifies non-configurable metadata such as Product Name and initializes /// the ApplicationNodeManager which provides access to the data exposed by the Global Discovery Server. - /// + /// /// - public class GlobalDiscoverySampleServer : StandardServer + public class GlobalDiscoverySampleServer : StandardServer, IGlobalDiscoverySampleServer { public GlobalDiscoverySampleServer( + IApplicationInstance applicationInstance, + IServerInternal serverInternal, + IMainNodeManagerFactory mainNodeManagerFactory, IApplicationsDatabase database, ICertificateRequest request, ICertificateGroup certificateGroup, @@ -60,7 +64,9 @@ public GlobalDiscoverySampleServer( bool autoApprove = true, bool createStandardUsers = true ) + : base (applicationInstance, serverInternal, mainNodeManagerFactory) { + m_mainNodeManagerFactory = mainNodeManagerFactory; m_database = database; m_request = request; m_certificateGroup = certificateGroup; @@ -95,7 +101,9 @@ protected override void OnServerStarted(IServerInternal server) /// always creates a CoreNodeManager which handles the built-in nodes defined by the specification. /// Any additional NodeManagers are expected to handle application specific nodes. /// - protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration) + protected override IMasterNodeManager CreateMasterNodeManager( + IServerInternal server, + ApplicationConfiguration configuration) { Utils.LogInfo("Creating the Node Managers."); @@ -106,7 +114,8 @@ protected override MasterNodeManager CreateMasterNodeManager(IServerInternal ser }; // create master node manager. - return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray()); + + return m_mainNodeManagerFactory.CreateMasterNodeManager(null, nodeManagers.ToArray()); } /// @@ -213,7 +222,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg { VerifyUserTokenCertificate(x509Token.Certificate); - // todo: is cert listed in admin list? then + // todo: is cert listed in admin list? then // role = GdsRole.ApplicationAdmin; Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, Role.AuthenticatedUser); @@ -375,6 +384,8 @@ private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEvent private IUserDatabase m_userDatabase = null; private bool m_autoApprove; private bool m_createStandardUsers; - #endregion + private readonly IMainNodeManagerFactory m_mainNodeManagerFactory; + + #endregion } } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/IGlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/IGlobalDiscoverySampleServer.cs new file mode 100644 index 0000000000..5454079e7e --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/IGlobalDiscoverySampleServer.cs @@ -0,0 +1,37 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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 Opc.Ua.Server; + +namespace Opc.Ua.Gds.Server +{ + public interface IGlobalDiscoverySampleServer : IStandardServer + { + } +} diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 6adacd7f6b..dc8ca2a21d 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -59,7 +59,7 @@ public SystemConfigurationIdentity(IUserIdentity identity) /// /// The Server Configuration Node Manager. /// - public class ConfigurationNodeManager : DiagnosticsNodeManager + public class ConfigurationNodeManager : DiagnosticsNodeManager, IConfigurationNodeManager { #region Constructors /// @@ -604,7 +604,7 @@ private ServiceResult UpdateCertificate( { // report the failure of UpdateCertificate via an audit event Server.ReportCertificateUpdatedAuditEvent(context, objectId, method, inputArguments, certificateGroupId, certificateTypeId, e); - // Raise audit certificate event + // Raise audit certificate event Server.ReportAuditCertificateEvent(newCert, e); throw; } diff --git a/Libraries/Opc.Ua.Server/Configuration/IConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/IConfigurationNodeManager.cs new file mode 100644 index 0000000000..87c404533c --- /dev/null +++ b/Libraries/Opc.Ua.Server/Configuration/IConfigurationNodeManager.cs @@ -0,0 +1,75 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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/ + * ======================================================================*/ + +namespace Opc.Ua.Server +{ + /// + /// Interface of the configuration node manager. + /// + public interface IConfigurationNodeManager : IDiagnosticsNodeManager + { + /// + /// Creates the configuration node for the server. + /// + void CreateServerConfiguration( + ServerSystemContext systemContext, + ApplicationConfiguration configuration); + + /// + /// Gets and returns the node associated with the specified NamespaceUri + /// + /// + /// + NamespaceMetadataState GetNamespaceMetadataState(string namespaceUri); + + /// + /// Gets or creates the node for the specified NamespaceUri. + /// + /// + /// + NamespaceMetadataState CreateNamespaceMetadataState(string namespaceUri); + + /// + /// Determine if the impersonated user has admin access. + /// + /// + /// + /// + void HasApplicationSecureAdminAccess(ISystemContext context); + + /// + /// Determine if the impersonated user has admin access. + /// + /// + /// + /// + /// + void HasApplicationSecureAdminAccess(ISystemContext context, CertificateStoreIdentifier _); + } +} diff --git a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs index cf4d2fccb2..d35c8b47b8 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs @@ -1902,7 +1902,7 @@ public virtual void Write( if (Server?.Auditing == true) { - //current server supports auditing + //current server supports auditing oldValue = new DataValue(); // read the old value for the purpose of auditing handle.Node.ReadAttribute(systemContext, nodeToWrite.AttributeId, nodeToWrite.ParsedIndexRange, null, oldValue); @@ -1915,7 +1915,7 @@ public virtual void Write( nodeToWrite.ParsedIndexRange, nodeToWrite.Value); - // report the write value audit event + // report the write value audit event Server.ReportAuditWriteUpdateEvent(systemContext, nodeToWrite, oldValue?.Value, errors[ii]?.StatusCode ?? StatusCodes.Good); if (!ServiceResult.IsGood(errors[ii])) diff --git a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs index fa19338c80..6aa61dea5b 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -39,7 +39,7 @@ namespace Opc.Ua.Server /// /// A node manager the diagnostic information exposed by the server. /// - public class DiagnosticsNodeManager : CustomNodeManager2 + public class DiagnosticsNodeManager : CustomNodeManager2, IDiagnosticsNodeManager { #region Constructors /// @@ -93,12 +93,8 @@ protected override void Dispose(bool disposing) #endregion #region INodeIdFactory Members - /// - /// Creates the NodeId for the specified node. - /// - /// The context. - /// The node. - /// The new NodeId. + + /// public override NodeId New(ISystemContext context, NodeState node) { uint id = Utils.IncrementIdentifier(ref m_lastUsedId); @@ -113,7 +109,7 @@ public override NodeId New(ISystemContext context, NodeState node) /// /// The externalReferences is an out parameter that allows the node manager to link to nodes /// in other node managers. For example, the 'Objects' node is managed by the CoreNodeManager and - /// should have a reference to the root folder node(s) exposed by this node manager. + /// should have a reference to the root folder node(s) exposed by this node manager. /// public override void CreateAddressSpace(IDictionary> externalReferences) { @@ -219,10 +215,7 @@ public override void CreateAddressSpace(IDictionary> e } } - /// - /// Called when a client sets a subscription as durable. - /// - + /// public ServiceResult OnSetSubscriptionDurable( ISystemContext context, MethodState method, @@ -234,9 +227,7 @@ public ServiceResult OnSetSubscriptionDurable( return Server.SubscriptionManager.SetSubscriptionDurable(context, subscriptionId, lifetimeInHours, out revisedLifetimeInHours); } - /// - /// Called when a client gets the monitored items of a subscription. - /// + /// public ServiceResult OnGetMonitoredItems( ISystemContext context, MethodState method, @@ -280,9 +271,7 @@ public ServiceResult OnGetMonitoredItems( return StatusCodes.BadSubscriptionIdInvalid; } - /// - /// Called when a client initiates resending of all data monitored items in a Subscription. - /// + /// public ServiceResult OnResendData( ISystemContext context, MethodState method, @@ -320,9 +309,7 @@ public ServiceResult OnResendData( return StatusCodes.BadSubscriptionIdInvalid; } - /// - /// Called when a client locks the server. - /// + /// public ServiceResult OnLockServer( ISystemContext context, MethodState method, @@ -344,9 +331,7 @@ public ServiceResult OnLockServer( return ServiceResult.Good; } - /// - /// Called when a client locks the server. - /// + /// public ServiceResult OnUnlockServer( ISystemContext context, MethodState method, @@ -612,22 +597,16 @@ private bool IsDiagnosticsStructureNode(NodeState node) return false; } - /// - /// Force out of band diagnostics update after a change of diagnostics variables. - /// + /// public void ForceDiagnosticsScan() { m_lastDiagnosticsScanTime = DateTime.MinValue; } - /// - /// True if diagnostics are currently enabled. - /// + /// public bool DiagnosticsEnabled => m_diagnosticsEnabled; - /// - /// Sets the flag controlling whether diagnostics is enabled for the server. - /// + /// public void SetDiagnosticsEnabled(ServerSystemContext context, bool enabled) { List nodesToDelete = new List(); @@ -727,9 +706,7 @@ public void SetDiagnosticsEnabled(ServerSystemContext context, bool enabled) } } - /// - /// Creates the diagnostics node for the server. - /// + /// public void CreateServerDiagnostics( ServerSystemContext systemContext, ServerDiagnosticsSummaryDataType diagnostics, @@ -800,9 +777,7 @@ public void CreateServerDiagnostics( } } - /// - /// Creates the diagnostics node for a subscription. - /// + /// public NodeId CreateSessionDiagnostics( ServerSystemContext systemContext, SessionDiagnosticsDataType diagnostics, @@ -903,9 +878,7 @@ public NodeId CreateSessionDiagnostics( return nodeId; } - /// - /// Delete the diagnostics node for a session. - /// + /// public void DeleteSessionDiagnostics( ServerSystemContext systemContext, NodeId nodeId) @@ -933,9 +906,7 @@ public void DeleteSessionDiagnostics( DeleteNode(systemContext, nodeId); } - /// - /// Creates the diagnostics node for a subscription. - /// + /// public NodeId CreateSubscriptionDiagnostics( ServerSystemContext systemContext, SubscriptionDiagnosticsDataType diagnostics, @@ -1019,9 +990,7 @@ public NodeId CreateSubscriptionDiagnostics( return nodeId; } - /// - /// Delete the diagnostics node for a subscription. - /// + /// public void DeleteSubscriptionDiagnostics( ServerSystemContext systemContext, NodeId nodeId) @@ -1043,9 +1012,7 @@ public void DeleteSubscriptionDiagnostics( DeleteNode(systemContext, nodeId); } - /// - /// Gets the default history capabilities object. - /// + /// public HistoryServerCapabilitiesState GetDefaultHistoryCapabilities() { lock (Lock) @@ -1103,9 +1070,7 @@ public HistoryServerCapabilitiesState GetDefaultHistoryCapabilities() } } - /// - /// Adds an aggregate function to the server capabilities object. - /// + /// public void AddAggregateFunction(NodeId aggregateId, string aggregateName, bool isHistorical) { lock (Lock) @@ -1837,7 +1802,7 @@ protected override void OnMonitoringModeChanged( /// Returns an index for the NamespaceURI (Adds it to the server namespace table if it does not already exist). /// /// - /// Returns the server's default index (1) if the namespaceUri is empty or null. + /// Returns the server's default index (1) if the namespaceUri is empty or null. /// public ushort GetNamespaceIndex(string namespaceUri) { @@ -1860,7 +1825,7 @@ public NodeId FindTargetId(NodeId sourceId, NodeId referenceTypeId, bool isInver { return null; } - + public ILocalNode GetLocalNode(NodeId nodeId) { return null; @@ -1896,7 +1861,7 @@ public void ReplaceNode(ILocalNode existingNode, ILocalNode newNode) public void DeleteNode(NodeId nodeId, bool deleteChildren, bool silent) { - } + } public ILocalNode ReferenceSharedNode( ILocalNode source, @@ -1915,7 +1880,7 @@ public ILocalNode UnreferenceSharedNode( { return null; } - + public NodeId CreateUniqueNodeId() { return null; @@ -1940,7 +1905,7 @@ public NodeId CreateObjectType( { return null; } - + public NodeId CreateVariable( NodeId parentId, NodeId referenceTypeId, @@ -2059,7 +2024,7 @@ private void DeleteSampledItem(MonitoredItem monitoredItem) } /// - /// Polls each monitored item which requires sample. + /// Polls each monitored item which requires sample. /// private void DoSample(object state) { diff --git a/Libraries/Opc.Ua.Server/Diagnostics/IDiagnosticsNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/IDiagnosticsNodeManager.cs new file mode 100644 index 0000000000..5ab6708e54 --- /dev/null +++ b/Libraries/Opc.Ua.Server/Diagnostics/IDiagnosticsNodeManager.cs @@ -0,0 +1,255 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.Reflection; + +namespace Opc.Ua.Server +{ + /// + /// Interface of the diagnostic node manager. + /// + public interface IDiagnosticsNodeManager : INodeManager2, INodeIdFactory, IDisposable + { + /// + /// Called when a client sets a subscription as durable. + /// + ServiceResult OnSetSubscriptionDurable( + ISystemContext context, + MethodState method, + NodeId objectId, + uint subscriptionId, + uint lifetimeInHours, + ref uint revisedLifetimeInHours); + + /// + /// Called when a client gets the monitored items of a subscription. + /// + ServiceResult OnGetMonitoredItems( + ISystemContext context, + MethodState method, + IList inputArguments, + IList outputArguments); + + /// + /// Called when a client initiates resending of all data monitored items in a Subscription. + /// + ServiceResult OnResendData( + ISystemContext context, + MethodState method, + IList inputArguments, + IList outputArguments); + + /// + /// Called when a client locks the server. + /// + ServiceResult OnLockServer( + ISystemContext context, + MethodState method, + IList inputArguments, + IList outputArguments); + + /// + /// Called when a client locks the server. + /// + ServiceResult OnUnlockServer( + ISystemContext context, + MethodState method, + IList inputArguments, + IList outputArguments); + + /// + /// Loads a node set from a file or resource and adds them to the set of predefined nodes. + /// + void LoadPredefinedNodes( + ISystemContext context, + Assembly assembly, + string resourcePath, + IDictionary> externalReferences); + + /// + /// Force out of band diagnostics update after a change of diagnostics variables. + /// + void ForceDiagnosticsScan(); + + /// + /// True if diagnostics are currently enabled. + /// + bool DiagnosticsEnabled { get; } + + /// + /// Acquires the lock on the node manager. + /// + object Lock { get; } + + /// + /// Gets the server that the node manager belongs to. + /// + IServerInternal Server { get; } + + /// + /// The default context to use. + /// + ServerSystemContext SystemContext { get; } + + /// + /// Gets the default index for the node manager's namespace. + /// + ushort NamespaceIndex { get; } + + /// + /// Gets the namespace indexes owned by the node manager. + /// + /// The namespace indexes. + IReadOnlyList NamespaceIndexes { get; } + + /// + /// Gets or sets the maximum size of a monitored item queue. + /// + /// The maximum size of a monitored item queue. + uint MaxQueueSize { get; set; } + + /// + /// The root for the alias assigned to the node manager. + /// + string AliasRoot { get; set; } + + /// + /// Sets the flag controlling whether diagnostics is enabled for the server. + /// + void SetDiagnosticsEnabled(ServerSystemContext context, bool enabled); + + /// + /// Creates the diagnostics node for the server. + /// + void CreateServerDiagnostics( + ServerSystemContext systemContext, + ServerDiagnosticsSummaryDataType diagnostics, + NodeValueSimpleEventHandler updateCallback); + + /// + /// Creates the diagnostics node for a subscription. + /// + NodeId CreateSessionDiagnostics( + ServerSystemContext systemContext, + SessionDiagnosticsDataType diagnostics, + NodeValueSimpleEventHandler updateCallback, + SessionSecurityDiagnosticsDataType securityDiagnostics, + NodeValueSimpleEventHandler updateSecurityCallback); + + /// + /// Delete the diagnostics node for a session. + /// + void DeleteSessionDiagnostics( + ServerSystemContext systemContext, + NodeId nodeId); + + /// + /// Creates the diagnostics node for a subscription. + /// + NodeId CreateSubscriptionDiagnostics( + ServerSystemContext systemContext, + SubscriptionDiagnosticsDataType diagnostics, + NodeValueSimpleEventHandler updateCallback); + + /// + /// Delete the diagnostics node for a subscription. + /// + void DeleteSubscriptionDiagnostics( + ServerSystemContext systemContext, + NodeId nodeId); + + /// + /// Gets the default history capabilities object. + /// + HistoryServerCapabilitiesState GetDefaultHistoryCapabilities(); + + /// + /// Adds an aggregate function to the server capabilities object. + /// + void AddAggregateFunction(NodeId aggregateId, string aggregateName, bool isHistorical); + + /// + /// Returns the state object for the specified node if it exists. + /// + NodeState Find(NodeId nodeId); + + /// + /// Creates a new instance and assigns unique identifiers to all children. + /// + /// The operation context. + /// An optional parent identifier. + /// The reference type from the parent. + /// The browse name. + /// The instance to create. + /// The new node id. + NodeId CreateNode( + ServerSystemContext context, + NodeId parentId, + NodeId referenceTypeId, + QualifiedName browseName, + BaseInstanceState instance); + + /// + /// Deletes a node and all of its children. + /// + bool DeleteNode( + ServerSystemContext context, + NodeId nodeId); + + /// + /// Searches the node id in all node managers + /// + /// + /// + NodeState FindNodeInAddressSpace(NodeId nodeId); + + /// + /// Finds the specified and checks if it is of the expected type. + /// + /// Returns null if not found or not of the correct type. + NodeState FindPredefinedNode(NodeId nodeId, Type expectedType); + + /// + /// Validates Role permissions for the specified NodeId + /// + /// + /// + /// + /// + ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission); + + /// + /// Validates if the specified event monitored item has enough permissions to receive the specified event + /// + /// + ServiceResult ValidateEventRolePermissions(IEventMonitoredItem monitoredItem, IFilterTarget filterTarget); + } +} diff --git a/Libraries/Opc.Ua.Server/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/Libraries/Opc.Ua.Server/Extensions/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..dd7b13b800 --- /dev/null +++ b/Libraries/Opc.Ua.Server/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,52 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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 Microsoft.Extensions.DependencyInjection; + +namespace Opc.Ua.Server +{ + /// + /// Class that provides service collection extensions to setup dependency injection + /// for server objects. + /// + public static class ServiceCollectionExtensions + { + /// + /// Add all the necessary server services to dependency injection. + /// + /// The service collection of the application. + /// The updated service collection. + public static IServiceCollection AddServerServices(this IServiceCollection services) + { + return services + .AddScoped() + .AddScoped(); + } + } +} diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs index fec5b963fb..75ba4df389 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2022 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 @@ -11,7 +11,7 @@ * 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, @@ -40,12 +40,12 @@ namespace Opc.Ua.Server /// The default node manager for the server. /// /// - /// Every Server has one instance of this NodeManager. + /// Every Server has one instance of this NodeManager. /// It stores objects that implement ILocalNode and indexes them by NodeId. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] - public partial class CoreNodeManager : INodeManager, IDisposable - { + public partial class CoreNodeManager : ICoreNodeManager + { #region Constructors /// /// Initializes the object with default values. @@ -57,7 +57,7 @@ public CoreNodeManager( { if (server == null) throw new ArgumentNullException(nameof(server)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - + m_server = server; m_nodes = new NodeTable(server.NamespaceUris, server.ServerUris, server.TypeTree); m_monitoredItems = new Dictionary(); @@ -72,20 +72,20 @@ public CoreNodeManager( } m_samplingGroupManager = new SamplingGroupManager( - server, + server, this, (uint)configuration.ServerConfiguration.MaxNotificationQueueSize, (uint)configuration.ServerConfiguration.MaxDurableNotificationQueueSize, configuration.ServerConfiguration.AvailableSamplingRates); } #endregion - - #region IDisposable Members + + #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() - { + { Dispose(true); GC.SuppressFinalize(this); } @@ -94,10 +94,10 @@ public void Dispose() /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) - { + { if (disposing) { - List nodes = null; + List nodes = null; lock(m_lock) { @@ -127,18 +127,14 @@ public object DataLock } #endregion - /// - /// Imports the nodes from a dictionary of NodeState objects. - /// + /// public void ImportNodes(ISystemContext context, IEnumerable predefinedNodes) { ImportNodes(context, predefinedNodes, false); } - /// - /// Imports the nodes from a dictionary of NodeState objects. - /// - internal void ImportNodes(ISystemContext context, IEnumerable predefinedNodes, bool isInternal) + /// + public void ImportNodes(ISystemContext context, IEnumerable predefinedNodes, bool isInternal) { NodeTable nodesToExport = new NodeTable(Server.NamespaceUris, Server.ServerUris, Server.TypeTree); @@ -147,15 +143,15 @@ internal void ImportNodes(ISystemContext context, IEnumerable predefi node.Export(context, nodesToExport); } - lock (Server.CoreNodeManager.DataLock) + lock (m_lock) { foreach (ILocalNode nodeToExport in nodesToExport) { - Server.CoreNodeManager.AttachNode(nodeToExport, isInternal); + AttachNode(nodeToExport, isInternal); } } } - + #region INodeManager Members /// public IEnumerable NamespaceUris @@ -174,7 +170,7 @@ public void CreateAddressSpace(IDictionary> externalRef { // TBD } - + /// /// /// Disposes all of the nodes. @@ -196,7 +192,7 @@ public void DeleteAddressSpace() } } - m_nodes.Clear(); + m_nodes.Clear(); } // dispose of the nodes. @@ -212,7 +208,7 @@ public void DeleteAddressSpace() } } } - + /// public object GetManagerHandle(NodeId nodeId) { @@ -226,12 +222,12 @@ public object GetManagerHandle(NodeId nodeId) return GetLocalNode(nodeId); } } - + /// public void TranslateBrowsePath( OperationContext context, - object sourceHandle, - RelativePathElement relativePath, + object sourceHandle, + RelativePathElement relativePath, IList targetIds, IList unresolvedTargetIds) { @@ -247,14 +243,14 @@ public void TranslateBrowsePath( { return; } - + lock(m_lock) { // find the references that meet the filter criteria. IList references = source.References.Find( - relativePath.ReferenceTypeId, - relativePath.IsInverse, - relativePath.IncludeSubtypes, + relativePath.ReferenceTypeId, + relativePath.IsInverse, + relativePath.IncludeSubtypes, m_server.TypeTree); // nothing more to do. @@ -297,11 +293,11 @@ public void Browse( OperationContext context, ref ContinuationPoint continuationPoint, IList references) - { + { if (context == null) throw new ArgumentNullException(nameof(context)); if (continuationPoint == null) throw new ArgumentNullException(nameof(continuationPoint)); if (references == null) throw new ArgumentNullException(nameof(references)); - + // check for valid handle. ILocalNode source = continuationPoint.NodeToBrowse as ILocalNode; @@ -309,7 +305,7 @@ public void Browse( { throw new ServiceResultException(StatusCodes.BadNodeIdUnknown); } - + // check for view. if (!ViewDescription.IsDefault(continuationPoint.View)) { @@ -323,7 +319,7 @@ public void Browse( // get previous enumerator. IEnumerator enumerator = continuationPoint.Data as IEnumerator; - + // fetch a snapshot all references for node. if (enumerator == null) { @@ -350,15 +346,15 @@ public void Browse( continuationPoint.IncludeSubtypes); if (include) - { + { ReferenceDescription description = new ReferenceDescription(); - + description.NodeId = reference.TargetId; description.SetReferenceType(continuationPoint.ResultMask, reference.ReferenceTypeId, !reference.IsInverse); // only fetch the metadata if it is requested. if (continuationPoint.TargetAttributesRequired) - { + { // get the metadata for the node. NodeMetadata metadata = GetNodeMetadata(context, GetManagerHandle(reference.TargetId), continuationPoint.ResultMask); @@ -391,7 +387,7 @@ public void Browse( // construct continuation point if max results reached. if (maxResultsToReturn > 0 && references.Count >= maxResultsToReturn) - { + { continuationPoint.Index = 0; continuationPoint.Data = enumerator; enumerator.MoveNext(); @@ -400,13 +396,13 @@ public void Browse( } } while (enumerator.MoveNext()); - + // nothing more to browse if it exits from the loop normally. continuationPoint.Dispose(); continuationPoint = null; } } - + /// /// Returns true if the target meets the filter criteria. /// @@ -444,16 +440,16 @@ private bool ApplyBrowseFilters( return true; } } - + return false; } } - + // include reference for now. return true; } #endregion - + /// public NodeMetadata GetNodeMetadata( OperationContext context, @@ -461,7 +457,7 @@ public NodeMetadata GetNodeMetadata( BrowseResultMask resultMask) { if (context == null) throw new ArgumentNullException(nameof(context)); - + // find target. ILocalNode target = targetHandle as ILocalNode; @@ -474,7 +470,7 @@ public NodeMetadata GetNodeMetadata( { // copy the default metadata. NodeMetadata metadata = new NodeMetadata(target, target.NodeId); - + // copy target attributes. if ((resultMask & BrowseResultMask.NodeClass) != 0) { @@ -485,7 +481,7 @@ public NodeMetadata GetNodeMetadata( { metadata.BrowseName = target.BrowseName; } - + if ((resultMask & BrowseResultMask.DisplayName) != 0) { metadata.DisplayName = target.DisplayName; @@ -496,7 +492,7 @@ public NodeMetadata GetNodeMetadata( metadata.DisplayName = Server.ResourceManager.Translate(context.PreferredLocales, metadata.DisplayName); } } - + metadata.WriteMask = target.WriteMask; if (metadata.WriteMask != AttributeWriteMask.None) @@ -517,7 +513,7 @@ public NodeMetadata GetNodeMetadata( metadata.EventNotifier = EventNotifiers.None; metadata.AccessLevel = AccessLevels.None; metadata.Executable = false; - + switch (target.NodeClass) { case NodeClass.Object: @@ -537,7 +533,7 @@ public NodeMetadata GetNodeMetadata( IVariable variable = (IVariable)target; metadata.DataType = variable.DataType; metadata.ValueRank = variable.ValueRank; - metadata.ArrayDimensions = variable.ArrayDimensions; + metadata.ArrayDimensions = variable.ArrayDimensions; metadata.AccessLevel = variable.AccessLevel; DataValue value = new DataValue(variable.UserAccessLevel); @@ -548,7 +544,7 @@ public NodeMetadata GetNodeMetadata( metadata.AccessLevel = 0; break; } - + metadata.AccessLevel = (byte)(metadata.AccessLevel & (byte)value.Value); break; } @@ -568,14 +564,14 @@ public NodeMetadata GetNodeMetadata( metadata.Executable = false; break; } - + metadata.Executable = (bool)value.Value; } break; } } - + // look up type definition. if ((resultMask & BrowseResultMask.TypeDefinition) != 0) { @@ -587,7 +583,7 @@ public NodeMetadata GetNodeMetadata( // Set AccessRestrictions and RolePermissions Node node = (Node)target; - metadata.AccessRestrictions = (AccessRestrictionType)Enum.Parse(typeof(AccessRestrictionType), node.AccessRestrictions.ToString(CultureInfo.InvariantCulture)); + metadata.AccessRestrictions = (AccessRestrictionType)Enum.Parse(typeof(AccessRestrictionType), node.AccessRestrictions.ToString(CultureInfo.InvariantCulture)); metadata.RolePermissions = node.RolePermissions; metadata.UserRolePermissions = node.UserRolePermissions; @@ -598,7 +594,7 @@ public NodeMetadata GetNodeMetadata( { metadata.DefaultAccessRestrictions = (AccessRestrictionType)Enum.ToObject(typeof(AccessRestrictionType), namespaceMetadataState.DefaultAccessRestrictions.Value); - + metadata.DefaultRolePermissions = namespaceMetadataState.DefaultRolePermissions.Value; metadata.DefaultUserRolePermissions = namespaceMetadataState.DefaultUserRolePermissions.Value; } @@ -610,7 +606,7 @@ public NodeMetadata GetNodeMetadata( /// /// - /// This method must not be called without first acquiring + /// This method must not be called without first acquiring /// public void AddReferences(IDictionary> references) { @@ -630,7 +626,7 @@ public void AddReferences(IDictionary> references) { AddReference(actualNode, reference.ReferenceTypeId, reference.IsInverse, reference.TargetId); } - } + } } } } @@ -659,7 +655,7 @@ public void Read( { continue; } - + // look up the node. ILocalNode node = GetLocalNode(nodeToRead.NodeId) as ILocalNode; @@ -669,7 +665,7 @@ public void Read( } DataValue value = values[ii] = new DataValue(); - + value.Value = null; value.ServerTimestamp = DateTime.UtcNow; value.SourceTimestamp = DateTime.MinValue; @@ -677,15 +673,15 @@ public void Read( // owned by this node manager. nodeToRead.Processed = true; - - // read the default value (also verifies that the attribute id is valid for the node). + + // read the default value (also verifies that the attribute id is valid for the node). ServiceResult error = node.Read(context, nodeToRead.AttributeId, value); if (ServiceResult.IsBad(error)) { errors[ii] = error; continue; - } + } // always use default value for base attributes. bool useDefault = false; @@ -706,26 +702,26 @@ public void Read( errors[ii] = error; continue; } - + // apply index range to value attributes. if (nodeToRead.AttributeId == Attributes.Value) { object defaultValue = value.Value; error = nodeToRead.ParsedIndexRange.ApplyRange(ref defaultValue); - + if (ServiceResult.IsBad(error)) { value.Value = null; errors[ii] = error; continue; } - + // apply data encoding. if (!QualifiedName.IsNull(nodeToRead.DataEncoding)) { error = EncodeableObject.ApplyDataEncoding(Server.MessageContext, nodeToRead.DataEncoding, ref defaultValue); - + if (ServiceResult.IsBad(error)) { value.Value = null; @@ -733,29 +729,29 @@ public void Read( continue; } } - - value.Value = defaultValue; - - // don't replace timestamp if it was set in the NodeSource - if (value.SourceTimestamp == DateTime.MinValue) - { - value.SourceTimestamp = DateTime.UtcNow; - } + + value.Value = defaultValue; + + // don't replace timestamp if it was set in the NodeSource + if (value.SourceTimestamp == DateTime.MinValue) + { + value.SourceTimestamp = DateTime.UtcNow; + } } } - } - + } + } - + /// public void HistoryRead( OperationContext context, - HistoryReadDetails details, - TimestampsToReturn timestampsToReturn, - bool releaseContinuationPoints, - IList nodesToRead, - IList results, - IList errors) + HistoryReadDetails details, + TimestampsToReturn timestampsToReturn, + bool releaseContinuationPoints, + IList nodesToRead, + IList results, + IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (details == null) throw new ArgumentNullException(nameof(details)); @@ -779,7 +775,7 @@ public void HistoryRead( { continue; } - + // look up the node. ILocalNode node = GetLocalNode(nodeToRead.NodeId) as ILocalNode; @@ -787,22 +783,22 @@ public void HistoryRead( { continue; } - + // owned by this node manager. nodeToRead.Processed = true; errors[ii] = StatusCodes.BadNotReadable; } - + } - + } /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public void Write( OperationContext context, - IList nodesToWrite, + IList nodesToWrite, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); @@ -820,7 +816,7 @@ public void Write( { continue; } - + // look up the node. ILocalNode node = GetLocalNode(nodeToWrite.NodeId) as ILocalNode; @@ -828,7 +824,7 @@ public void Write( { continue; } - + // owned by this node manager. nodeToWrite.Processed = true; @@ -837,10 +833,10 @@ public void Write( errors[ii] = StatusCodes.BadAttributeIdInvalid; continue; } - + // fetch the node metadata. NodeMetadata metadata = GetNodeMetadata(context, node, BrowseResultMask.All); - + // check access. bool writeable = true; ServiceResult error = null; @@ -883,7 +879,7 @@ public void Write( // determine expected datatype and value rank. NodeId expectedDatatypeId = metadata.DataType; int expectedValueRank = metadata.ValueRank; - + if (nodeToWrite.AttributeId != Attributes.Value) { expectedDatatypeId = Attributes.GetDataTypeId(nodeToWrite.AttributeId); @@ -922,14 +918,14 @@ public void Write( // check index range. if (nodeToWrite.ParsedIndexRange.Count > 0) - { + { // check index range for scalars. if (typeInfo.ValueRank < 0) { errors[ii] = StatusCodes.BadIndexRangeInvalid; continue; } - + // check index range for arrays. else { @@ -942,7 +938,7 @@ public void Write( } } } - + // write the default value. error = node.Write(nodeToWrite.AttributeId, nodeToWrite.Value); @@ -954,15 +950,15 @@ public void Write( } } } - + /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public void HistoryUpdate( OperationContext context, Type detailsType, - IList nodesToUpdate, - IList results, - IList errors) + IList nodesToUpdate, + IList results, + IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (nodesToUpdate == null) throw new ArgumentNullException(nameof(nodesToUpdate)); @@ -980,7 +976,7 @@ public void HistoryUpdate( { continue; } - + // look up the node. ILocalNode node = GetLocalNode(nodeToUpdate.NodeId) as ILocalNode; @@ -988,14 +984,14 @@ public void HistoryUpdate( { continue; } - + // owned by this node manager. nodeToUpdate.Processed = true; - + errors[ii] = StatusCodes.BadNotWritable; } } - + } /// @@ -1022,7 +1018,7 @@ public void Call( { continue; } - + // look up the node. ILocalNode node = GetLocalNode(methodToCall.ObjectId) as ILocalNode; @@ -1030,9 +1026,9 @@ public void Call( { continue; } - - methodToCall.Processed = true; - + + methodToCall.Processed = true; + // look up the method. ILocalNode method = GetLocalNode(methodToCall.MethodId) as ILocalNode; @@ -1047,14 +1043,14 @@ public void Call( { errors[ii] = ServiceResult.Create(StatusCodes.BadMethodInvalid, "Method is not a component of the Object."); continue; - } - + } + errors[ii] = StatusCodes.BadNotImplemented; } } - + } - + /// public ServiceResult SubscribeToEvents( OperationContext context, @@ -1088,7 +1084,7 @@ public ServiceResult SubscribeToEvents( { return StatusCodes.BadNotSupported; } - + return ServiceResult.Good; } } @@ -1099,24 +1095,24 @@ public ServiceResult SubscribeToAllEvents( uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe) - { + { if (context == null) throw new ArgumentNullException(nameof(context)); if (monitoredItem == null) throw new ArgumentNullException(nameof(monitoredItem)); - + return ServiceResult.Good; } /// - public ServiceResult ConditionRefresh( + public ServiceResult ConditionRefresh( OperationContext context, IList monitoredItems) - { + { if (context == null) throw new ArgumentNullException(nameof(context)); - + return ServiceResult.Good; } - + /// /// Creates a set of monitored items. @@ -1149,7 +1145,7 @@ public void CreateMonitoredItems( { continue; } - + // look up the node. ILocalNode node = this.GetLocalNode(itemToCreate.ItemToMonitor.NodeId) as ILocalNode; @@ -1183,7 +1179,7 @@ public void CreateMonitoredItems( if (itemToCreate.ItemToMonitor.ParsedIndexRange != NumericRange.Empty) { int valueRank = metadata.ValueRank; - + if (itemToCreate.ItemToMonitor.AttributeId != Attributes.Value) { valueRank = Attributes.GetValueRank(itemToCreate.ItemToMonitor.AttributeId); @@ -1197,7 +1193,7 @@ public void CreateMonitoredItems( } bool rangeRequired = false; - + // validate the filter against the node/attribute being monitored. errors[ii] = ValidateFilter( metadata, @@ -1216,13 +1212,13 @@ public void CreateMonitoredItems( if (rangeRequired) { errors[ii] = ReadEURange(context, node, out range); - + if (ServiceResult.IsBad(errors[ii])) { continue; } } - + // create a globally unique identifier. uint monitoredItemId = Utils.IncrementIdentifier(ref globalIdCounter); @@ -1233,7 +1229,7 @@ public void CreateMonitoredItems( { // use the MinimumSamplingInterval attribute to limit the sampling rate for value attributes. IVariable variableNode = node as IVariable; - + if (variableNode != null) { minimumSamplingInterval = variableNode.MinimumSamplingInterval; @@ -1245,7 +1241,7 @@ public void CreateMonitoredItems( } } } - + // create monitored item. MonitoredItem monitoredItem = m_samplingGroupManager.CreateMonitoredItem( context, @@ -1282,7 +1278,7 @@ public void CreateMonitoredItems( errors[ii] = StatusCodes.Good; } } - + // update all groups with any new items. m_samplingGroupManager.ApplyChanges(); } @@ -1328,7 +1324,7 @@ public void ModifyMonitoredItems( IList itemsToModify, IList errors, IList filterErrors) - { + { if (context == null) throw new ArgumentNullException(nameof(context)); if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); if (itemsToModify == null) throw new ArgumentNullException(nameof(itemsToModify)); @@ -1345,16 +1341,16 @@ public void ModifyMonitoredItems( { continue; } - - // check if the node manager created the item. + + // check if the node manager created the item. if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager)) { continue; } - + // owned by this node manager. itemToModify.Processed = true; - + // validate monitored item. MonitoredItem monitoredItem = null; @@ -1369,7 +1365,7 @@ public void ModifyMonitoredItems( errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } - + // find the node being monitored. ILocalNode node = monitoredItem.ManagerHandle as ILocalNode; @@ -1381,7 +1377,7 @@ public void ModifyMonitoredItems( // fetch the metadata for the node. NodeMetadata metadata = GetNodeMetadata(context, monitoredItem.ManagerHandle, BrowseResultMask.All); - + bool rangeRequired = false; // validate the filter against the node/attribute being monitored. @@ -1395,7 +1391,7 @@ public void ModifyMonitoredItems( { continue; } - + // lookup EU range if required. Range range = null; @@ -1403,7 +1399,7 @@ public void ModifyMonitoredItems( { // look up EU range. errors[ii] = ReadEURange(context, node, out range); - + if (ServiceResult.IsBad(errors[ii])) { continue; @@ -1417,14 +1413,14 @@ public void ModifyMonitoredItems( monitoredItem, itemToModify, range); - + // state of item did not change if an error returned here. if (ServiceResult.IsBad(errors[ii])) { continue; } - // item has been modified successfully. + // item has been modified successfully. // errors updating the sampling groups will be reported in notifications. errors[ii] = StatusCodes.Good; } @@ -1439,7 +1435,7 @@ public void ModifyMonitoredItems( /// public void DeleteMonitoredItems( OperationContext context, - IList monitoredItems, + IList monitoredItems, IList processedItems, IList errors) { @@ -1456,8 +1452,8 @@ public void DeleteMonitoredItems( { continue; } - - // check if the node manager created the item. + + // check if the node manager created the item. if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager)) { continue; @@ -1465,7 +1461,7 @@ public void DeleteMonitoredItems( // owned by this node manager. processedItems[ii] = true; - + // validate monitored item. MonitoredItem monitoredItem = null; @@ -1491,7 +1487,7 @@ public void DeleteMonitoredItems( errors[ii] = StatusCodes.Good; } } - + // remove all items from groups. m_samplingGroupManager.ApplyChanges(); } @@ -1560,7 +1556,7 @@ public virtual void TransferMonitoredItems( public void SetMonitoringMode( OperationContext context, MonitoringMode monitoringMode, - IList monitoredItems, + IList monitoredItems, IList processedItems, IList errors) { @@ -1572,14 +1568,14 @@ public void SetMonitoringMode( lock (m_lock) { for (int ii = 0; ii < errors.Count; ii++) - { + { // skip items that have already been processed. if (processedItems[ii] || monitoredItems[ii] == null) { continue; } - - // check if the node manager created the item. + + // check if the node manager created the item. if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager)) { continue; @@ -1587,7 +1583,7 @@ public void SetMonitoringMode( // owned by this node manager. processedItems[ii] = true; - + // validate monitored item. MonitoredItem monitoredItem = null; @@ -1613,10 +1609,10 @@ public void SetMonitoringMode( initialValue.ServerTimestamp = DateTime.UtcNow; initialValue.StatusCode = StatusCodes.BadWaitingForInitialData; - + // read the initial value. Node node = monitoredItem.ManagerHandle as Node; - + if (node != null) { ServiceResult error = node.Read(context, monitoredItem.AttributeId, initialValue); @@ -1627,24 +1623,24 @@ public void SetMonitoringMode( initialValue.StatusCode = error.StatusCode; } } - + monitoredItem.QueueValue(initialValue, null); } - - // modify the item attributes. + + // modify the item attributes. m_samplingGroupManager.ModifyMonitoring(context, monitoredItem); - - // item has been modified successfully. + + // item has been modified successfully. // errors updating the sampling groups will be reported in notifications. errors[ii] = StatusCodes.Good; } } - + // update all sampling groups. m_samplingGroupManager.ApplyChanges(); } #endregion - + #region Static Members /// /// Returns true if the node class matches the node class mask. @@ -1669,13 +1665,13 @@ protected IServerInternal Server get { return m_server; } } #endregion - + #region Browsing/Searching /// /// Returns an index for the NamespaceURI (Adds it to the server namespace table if it does not already exist). /// /// - /// Returns the server's default index (1) if the namespaceUri is empty or null. + /// Returns the server's default index (1) if the namespaceUri is empty or null. /// public ushort GetNamespaceIndex(string namespaceUri) { @@ -1693,7 +1689,7 @@ public ushort GetNamespaceIndex(string namespaceUri) return (ushort)namespaceIndex; } - + /// /// Returns all targets of the specified reference. /// @@ -1712,7 +1708,7 @@ public NodeIdCollection FindLocalNodes(NodeId sourceId, NodeId referenceTypeId, } NodeIdCollection targets = new NodeIdCollection(); - + foreach (IReference reference in source.References) { if (reference.IsInverse != isInverse || !m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId)) @@ -1729,7 +1725,7 @@ public NodeIdCollection FindLocalNodes(NodeId sourceId, NodeId referenceTypeId, targets.Add((NodeId)targetId); } - + return targets; } } @@ -1750,7 +1746,7 @@ public NodeId FindTargetId(NodeId sourceId, NodeId referenceTypeId, bool isInver { return null; } - + foreach (ReferenceNode reference in source.References) { if (reference.IsInverse != isInverse || !m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId)) @@ -1771,16 +1767,16 @@ public NodeId FindTargetId(NodeId sourceId, NodeId referenceTypeId, bool isInver { continue; } - + if (QualifiedName.IsNull(browseName) || target.BrowseName == browseName) { return (NodeId)targetId; } } - + return null; } - } + } /// /// Returns the first target that matches the browse path. @@ -1801,39 +1797,39 @@ public NodeId Find(NodeId sourceId, string browsePath) /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( - OperationContext context, - NodeId sourceId, + OperationContext context, + NodeId sourceId, string browsePath) { return TranslateBrowsePath(context, sourceId, RelativePath.Parse(browsePath, m_server.TypeTree)); } - + /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( - NodeId sourceId, + NodeId sourceId, string browsePath) { return TranslateBrowsePath(null, sourceId, RelativePath.Parse(browsePath, m_server.TypeTree)); } - + /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( - NodeId sourceId, + NodeId sourceId, RelativePath relativePath) { return TranslateBrowsePath(null, sourceId, relativePath); } - + /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( - OperationContext context, - NodeId sourceId, + OperationContext context, + NodeId sourceId, RelativePath relativePath) { List targets = new List(); @@ -1873,7 +1869,7 @@ public void RegisterSource(NodeId nodeId, object source, object handle, bool isE { if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); if (source == null) throw new ArgumentNullException(nameof(source)); - } + } /// /// Called when the source is no longer used. @@ -1889,16 +1885,16 @@ public void UnregisterSource(object source) #endregion #region Adding/Removing Nodes - + #region Apply Modelling Rules /// /// Applys the modelling rules to any existing instance. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public void ApplyModellingRules( - ILocalNode instance, - ILocalNode typeDefinition, - ILocalNode templateDeclaration, + ILocalNode instance, + ILocalNode typeDefinition, + ILocalNode templateDeclaration, ushort namespaceIndex) { if (instance == null) throw new ArgumentNullException(nameof(instance)); @@ -1910,7 +1906,7 @@ public void ApplyModellingRules( // create list of declarations for the type definition (recursively collects definitions from supertypes). List declarations = new List(); BuildDeclarationList(typeDefinition, declarations); - + // add instance declaration if provided. if (templateDeclaration != null) { @@ -1928,13 +1924,13 @@ public void ApplyModellingRules( List typeDefinitions = new List(); SortedDictionary instanceDeclarations = new SortedDictionary(); SortedDictionary possibleTargets = new SortedDictionary(); - + // create instances from declarations. // subtypes appear in list last so traversing the list backwards find the overridden nodes first. for (int ii = declarations.Count-1; ii >= 0; ii--) { DeclarationNode declaration = declarations[ii]; - + // update type definition list. if (String.IsNullOrEmpty(declaration.BrowsePath)) { @@ -1948,21 +1944,21 @@ public void ApplyModellingRules( { continue; } - + // update instance declaration list. instanceDeclarations[declaration.BrowsePath] = declaration.Node; - + // save the node as a possible target of references. - possibleTargets[declaration.Node.NodeId] = declaration.Node; + possibleTargets[declaration.Node.NodeId] = declaration.Node; } - + // build list of instances that already exist. SortedDictionary existingInstances = new SortedDictionary(); BuildInstanceList(instance, String.Empty, existingInstances); // maps the instance declaration onto an instance node. - Dictionary instancesToCreate = new Dictionary(); - + Dictionary instancesToCreate = new Dictionary(); + // apply modelling rules to instance declarations. foreach (KeyValuePair current in instanceDeclarations) { @@ -1971,12 +1967,12 @@ public void ApplyModellingRules( // check if the same instance has multiple browse paths to it. ILocalNode newInstance = null; - + if (instancesToCreate.TryGetValue(instanceDeclaration.NodeId, out newInstance)) - { + { continue; } - + // check for an existing instance. if (existingInstances.TryGetValue(browsePath, out newInstance)) { @@ -1985,10 +1981,10 @@ public void ApplyModellingRules( // apply modelling rule to determine whether to create a new instance. NodeId modellingRule = instanceDeclaration.ModellingRule; - + // always create a new instance if one does not already exist. if (modellingRule == Objects.ModellingRule_Mandatory) - { + { if (newInstance == null) { newInstance = instanceDeclaration.CreateCopy(CreateUniqueNodeId()); @@ -1998,7 +1994,7 @@ public void ApplyModellingRules( // ignore optional instances unless one has been specified in the existing tree. else if (modellingRule == Objects.ModellingRule_Optional) - { + { if (newInstance == null) { continue; @@ -2014,7 +2010,7 @@ public void ApplyModellingRules( // save the mapping between the instance declaration and the new instance. instancesToCreate[instanceDeclaration.NodeId] = newInstance; } - + // add references from type definitions to top level. foreach (ILocalNode type in typeDefinitions) { @@ -2025,11 +2021,11 @@ public void ApplyModellingRules( { continue; } - + // ignore subtype references. if (m_nodes.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype)) { - continue; + continue; } // ignore targets that are not in the instance tree. @@ -2042,9 +2038,9 @@ public void ApplyModellingRules( // add forward and backward reference. AddReference(instance, reference.ReferenceTypeId, reference.IsInverse, target, true); - } + } } - + // add references between instance declarations. foreach (ILocalNode instanceDeclaration in instanceDeclarations.Values) { @@ -2055,7 +2051,7 @@ public void ApplyModellingRules( { continue; } - + // check if the source is a shared node. bool sharedNode = Object.ReferenceEquals(instanceDeclaration, source); @@ -2080,18 +2076,18 @@ public void ApplyModellingRules( AddReference(source, reference.ReferenceTypeId, false, reference.TargetId); } - continue; + continue; } // check for type definition. if (reference.ReferenceTypeId == ReferenceTypeIds.HasTypeDefinition) { if (!sharedNode) - { + { UpdateTypeDefinition(source, instanceDeclaration.TypeDefinitionId); } - continue; + continue; } // add targets that are not in the instance tree. @@ -2121,18 +2117,18 @@ public void ApplyModellingRules( source.References.Add(reference.ReferenceTypeId, reference.IsInverse, reference.TargetId); continue; } - + // add forward and backward reference. AddReference(source, reference.ReferenceTypeId, reference.IsInverse, target, true); } } - } + } /// /// Returns true if a one-way reference to external nodes is permitted. /// private bool IsExternalReferenceAllowed(NodeId referenceTypeId) - { + { // always exclude hierarchial references. if (m_nodes.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HierarchicalReferences)) { @@ -2187,9 +2183,9 @@ private class DeclarationNode public ILocalNode Node; public string BrowsePath; } - + /// - /// Builds the list of declaration nodes for a type definition. + /// Builds the list of declaration nodes for a type definition. /// private void BuildDeclarationList(ILocalNode typeDefinition, List declarations) { @@ -2227,14 +2223,14 @@ private void BuildDeclarationList(ILocalNode typeDefinition, List /// Builds a list of declarations from the nodes aggregated by a parent. /// private void BuildDeclarationList(DeclarationNode parent, List declarations) - { + { if (parent == null) throw new ArgumentNullException(nameof(parent)); if (declarations == null) throw new ArgumentNullException(nameof(declarations)); @@ -2274,22 +2270,22 @@ private void BuildDeclarationList(DeclarationNode parent, List } } } - + /// /// Builds a table of instances indexed by browse path from the nodes aggregated by a parent /// private void BuildInstanceList(ILocalNode parent, string browsePath, IDictionary instances) - { + { if (parent == null) throw new ArgumentNullException(nameof(parent)); if (instances == null) throw new ArgumentNullException(nameof(instances)); - + // guard against loops. if (instances.ContainsKey(browsePath)) { return; } - // index parent by browse path. + // index parent by browse path. instances[browsePath] = parent; // get list of children. @@ -2304,7 +2300,7 @@ private void BuildInstanceList(ILocalNode parent, string browsePath, IDictionary { continue; } - + // recursively include aggregated children. BuildInstanceList(child, Utils.Format("{0}.{1}", browsePath, child.BrowseName), instances); } @@ -2358,12 +2354,12 @@ public void ExportNode(ILocalNode node, NodeSet nodeSet, bool instance) { export = false; } - + if (export) { nodeSet.AddReference(nodeToExport, reference, m_nodes.NamespaceUris, m_nodes.ServerUris); } - + if (reference.IsInverse || m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype)) { nodeSet.AddReference(nodeToExport, reference, m_nodes.NamespaceUris, m_nodes.ServerUris); @@ -2375,7 +2371,7 @@ public void ExportNode(ILocalNode node, NodeSet nodeSet, bool instance) { continue; } - + ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode; if (child != null) @@ -2396,7 +2392,7 @@ public void ExportNode(ILocalNode node, NodeSet nodeSet, bool instance) } } } - + #if XXX /// /// Changes the type definition for an instance. @@ -2409,7 +2405,7 @@ public void ChangeTypeDefinition( { m_lock.Enter(); - // find the instance. + // find the instance. ILocalNode instance = GetLocalNode(instanceId) as ILocalNode; if (instance == null) @@ -2461,7 +2457,7 @@ public void ChangeTypeDefinition( } } #endif - + /// /// Updates the attributes for the node. /// @@ -2471,7 +2467,7 @@ private static void UpdateAttributes(ILocalNode node, NodeAttributes attributes) if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.DisplayName) != 0) { node.DisplayName = attributes.DisplayName; - + if (node.DisplayName == null) { node.DisplayName = new LocalizedText(node.BrowseName.Name); @@ -2481,13 +2477,13 @@ private static void UpdateAttributes(ILocalNode node, NodeAttributes attributes) { node.DisplayName = new LocalizedText(node.BrowseName.Name); } - + // Description if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.Description) != 0) { node.Description = attributes.Description; } - + // WriteMask if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.WriteMask) != 0) { @@ -2497,8 +2493,8 @@ private static void UpdateAttributes(ILocalNode node, NodeAttributes attributes) { node.WriteMask = AttributeWriteMask.None; } - - // WriteMask + + // WriteMask if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.UserWriteMask) != 0) { node.UserWriteMask = (AttributeWriteMask)attributes.UserWriteMask; @@ -2508,14 +2504,14 @@ private static void UpdateAttributes(ILocalNode node, NodeAttributes attributes) node.UserWriteMask = AttributeWriteMask.None; } } - + /// /// Deletes a node from the address sapce. /// public void DeleteNode(NodeId nodeId, bool deleteChildren, bool silent) { if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); - + // find the node to delete. ILocalNode node = GetManagerHandle(nodeId) as ILocalNode; @@ -2532,7 +2528,7 @@ public void DeleteNode(NodeId nodeId, bool deleteChildren, bool silent) bool instance = (node.NodeClass & (NodeClass.Object | NodeClass.Variable)) != 0; Dictionary> referencesToDelete = new Dictionary>(); - + if (silent) { try @@ -2556,7 +2552,7 @@ public void DeleteNode(NodeId nodeId, bool deleteChildren, bool silent) OnDeleteReferences(referencesToDelete); }); } - } + } /// /// Deletes a node from the address sapce. @@ -2572,10 +2568,10 @@ private void DeleteNode(ILocalNode node, bool deleteChildren, bool instance, Dic { // remove the node. m_nodes.Remove(node.NodeId); - + // check need to connect subtypes to the supertype if they are being deleted. ExpandedNodeId supertypeId = m_server.TypeTree.FindSuperType(node.NodeId); - + if (!NodeId.IsNull(supertypeId)) { m_server.TypeTree.Remove(node.NodeId); @@ -2598,7 +2594,7 @@ private void DeleteNode(ILocalNode node, bool deleteChildren, bool instance, Dic referencesForNode.Add(reference); continue; } - + // delete the backward reference. target.References.Remove(reference.ReferenceTypeId, !reference.IsInverse, node.NodeId); @@ -2623,20 +2619,20 @@ private void DeleteNode(ILocalNode node, bool deleteChildren, bool instance, Dic { DeleteNode(nodeToDelete, deleteChildren, instance, referencesToDelete); } - } + } /// /// Deletes the external references to a node in a background thread. /// private void OnDeleteReferences(object state) - { + { Dictionary> referencesToDelete = state as Dictionary>; if (state == null) { return; } - + foreach (KeyValuePair> current in referencesToDelete) { try @@ -2647,7 +2643,7 @@ private void OnDeleteReferences(object state) { Utils.LogError(e, "Error deleting references for node: {0}", current.Key); } - } + } } #region Add/Remove Node Support Functions @@ -2689,7 +2685,7 @@ private void ValidateReference( { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Target node cannot be used with HasComponent references."); } - + if (targetNodeClass == NodeClass.Variable) { if ((targetNodeClass & (NodeClass.Variable | NodeClass.VariableType)) == 0) @@ -2706,14 +2702,14 @@ private void ValidateReference( } } } - + // check HasProperty references. if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypes.HasProperty)) { if (targetNodeClass != NodeClass.Variable) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Targets of HasProperty references must be Variables."); - } + } } // check HasSubtype references. @@ -2728,13 +2724,13 @@ private void ValidateReference( { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "The source and target cannot be connected by a HasSubtype reference."); } - } + } // TBD - check rules for other reference types. } #endregion #endregion - + #region Adding/Removing References /// /// Adds a reference between two existing nodes. @@ -2755,10 +2751,10 @@ public ServiceResult AddReference( { return StatusCodes.BadParentNodeIdInvalid; } - - // add reference from target to source. + + // add reference from target to source. if (bidirectional) - { + { // find target. ILocalNode target = GetManagerHandle(targetId) as ILocalNode; @@ -2766,15 +2762,15 @@ public ServiceResult AddReference( { return StatusCodes.BadNodeIdUnknown; } - + // ensure the reference is valid. ValidateReference(source, referenceTypeId, isInverse, target.NodeClass); // add reference from target to source. AddReferenceToLocalNode(target, referenceTypeId, !isInverse, sourceId, false); } - - // add reference from source to target. + + // add reference from source to target. AddReferenceToLocalNode(source, referenceTypeId, isInverse, targetId, false); return null; @@ -2856,27 +2852,27 @@ public void CreateReference( /// Adds a reference to the address space. /// private void AddReference( - ILocalNode source, - NodeId referenceTypeId, - bool isInverse, - ILocalNode target, + ILocalNode source, + NodeId referenceTypeId, + bool isInverse, + ILocalNode target, bool bidirectional) { AddReferenceToLocalNode(source, referenceTypeId, isInverse, target.NodeId, false); - + if (bidirectional) { AddReferenceToLocalNode(target, referenceTypeId, !isInverse, source.NodeId, false); - } - } + } + } /// /// Adds a reference to the address space. /// private void AddReference( - ILocalNode source, - NodeId referenceTypeId, - bool isInverse, + ILocalNode source, + NodeId referenceTypeId, + bool isInverse, ExpandedNodeId targetId) { AddReferenceToLocalNode(source, referenceTypeId, isInverse, targetId, false); @@ -2886,10 +2882,10 @@ private void AddReference( /// Deletes a reference. /// public ServiceResult DeleteReference( - object sourceHandle, + object sourceHandle, NodeId referenceTypeId, - bool isInverse, - ExpandedNodeId targetId, + bool isInverse, + ExpandedNodeId targetId, bool deleteBidirectional) { if (sourceHandle == null) throw new ArgumentNullException(nameof(sourceHandle)); @@ -2916,26 +2912,26 @@ public ServiceResult DeleteReference( target.References.Remove(referenceTypeId, !isInverse, source.NodeId); } } - + return ServiceResult.Good; } } - + /// /// Deletes a reference. /// public void DeleteReference( - NodeId sourceId, - NodeId referenceTypeId, - bool isInverse, - ExpandedNodeId targetId, + NodeId sourceId, + NodeId referenceTypeId, + bool isInverse, + ExpandedNodeId targetId, bool deleteBidirectional) - { + { ServiceResult result = DeleteReference( - GetManagerHandle(sourceId) as ILocalNode, - referenceTypeId, - isInverse, - targetId, + GetManagerHandle(sourceId) as ILocalNode, + referenceTypeId, + isInverse, + targetId, deleteBidirectional); if (ServiceResult.IsBad(result)) @@ -2948,11 +2944,11 @@ public void DeleteReference( /// Adds a node to the address space. /// private void AddNode(ILocalNode node) - { + { m_nodes.Attach(node); } #endregion - + /// /// Returns a node managed by the manager with the specified node id. /// @@ -2970,9 +2966,9 @@ public ILocalNode GetLocalNode(ExpandedNodeId nodeId) { return null; } - + int namespaceIndex = this.Server.NamespaceUris.GetIndex(nodeId.NamespaceUri); - + if (namespaceIndex < 0 || nodeId.NamespaceIndex >= this.Server.NamespaceUris.Count) { return null; @@ -2983,7 +2979,7 @@ public ILocalNode GetLocalNode(ExpandedNodeId nodeId) return GetLocalNode((NodeId)nodeId); } - + /// /// Returns a node managed by the manager with the specified node id. /// @@ -3015,9 +3011,9 @@ public ILocalNode GetLocalNode(NodeId nodeId) /// Returns a list of nodes which are targets of the specified references. /// public IList GetLocalNodes( - NodeId sourceId, + NodeId sourceId, NodeId referenceTypeId, - bool isInverse, + bool isInverse, bool includeSubtypes) { lock (m_lock) @@ -3032,7 +3028,7 @@ public IList GetLocalNodes( } foreach (IReference reference in source.References.Find(referenceTypeId, isInverse, true, m_nodes.TypeTree)) - { + { ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode; if (target != null) @@ -3044,15 +3040,15 @@ public IList GetLocalNodes( return targets; } } - + /// /// Returns a node managed by the manager that has the specified browse name. /// public ILocalNode GetTargetNode( - NodeId sourceId, + NodeId sourceId, NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, + bool isInverse, + bool includeSubtypes, QualifiedName browseName) { lock (m_lock) @@ -3067,15 +3063,15 @@ public ILocalNode GetTargetNode( return GetTargetNode(source, referenceTypeId, isInverse, includeSubtypes, browseName); } } - + /// /// Returns a node managed by the manager that has the specified browse name. /// private ILocalNode GetTargetNode( - ILocalNode source, + ILocalNode source, NodeId referenceTypeId, - bool isInverse, - bool includeSubtypes, + bool isInverse, + bool includeSubtypes, QualifiedName browseName) { foreach (IReference reference in source.References.Find(referenceTypeId, isInverse, includeSubtypes, m_server.TypeTree)) @@ -3116,7 +3112,7 @@ private void AttachNode(ILocalNode node, bool isInternal) if (m_nodes.Exists(node.NodeId)) { throw ServiceResultException.Create( - StatusCodes.BadNodeIdExists, + StatusCodes.BadNodeIdExists, "A node with the same node id already exists: {0}", node.NodeId); } @@ -3136,14 +3132,14 @@ private void AttachNode(ILocalNode node, bool isInternal) if (target != null) { AddReferenceToLocalNode(target, reference.ReferenceTypeId, !reference.IsInverse, node.NodeId, isInternal); - } + } } - + // must generate a model change event. AddNode(node); } } - + /// /// Creates a unique node identifier. /// @@ -3151,7 +3147,7 @@ public NodeId CreateUniqueNodeId() { return CreateUniqueNodeId(m_dynamicNamespaceIndex); } - + #region Private Methods /// private object GetManagerHandle(ExpandedNodeId nodeId) @@ -3182,7 +3178,7 @@ private ServiceResult ReadEURange(OperationContext context, ILocalNode node, out } range = target.Value as Range; - + if (range == null) { return StatusCodes.BadTypeMismatch; @@ -3190,14 +3186,14 @@ private ServiceResult ReadEURange(OperationContext context, ILocalNode node, out return ServiceResult.Good; } - + /// /// Validates a filter for a monitored item. /// private ServiceResult ValidateFilter( - NodeMetadata metadata, - uint attributeId, - ExtensionObject filter, + NodeMetadata metadata, + uint attributeId, + ExtensionObject filter, out bool rangeRequired) { rangeRequired = false; @@ -3209,12 +3205,12 @@ private ServiceResult ValidateFilter( { datachangeFilter = filter.Body as DataChangeFilter; } - + if (datachangeFilter != null) { - // get the datatype of the node. + // get the datatype of the node. NodeId datatypeId = metadata.DataType; - + // check that filter is valid. ServiceResult error = datachangeFilter.Validate(); @@ -3222,7 +3218,7 @@ private ServiceResult ValidateFilter( { return error; } - + // check datatype of the variable. if (!m_server.TypeTree.IsTypeOf(datatypeId, DataTypes.Number)) { @@ -3238,7 +3234,7 @@ private ServiceResult ValidateFilter( { return StatusCodes.BadDeadbandFilterInvalid; } - + // percent deadbands only allowed for analog data items. if (!m_server.TypeTree.IsTypeOf(typeDefinitionId, VariableTypes.AnalogItemType)) { @@ -3249,7 +3245,7 @@ private ServiceResult ValidateFilter( rangeRequired = true; } } - + // filter is valid return ServiceResult.Good; } @@ -3262,7 +3258,7 @@ private NodeId CreateUniqueNodeId(ushort namespaceIndex) return new NodeId(Utils.IncrementIdentifier(ref m_lastId), namespaceIndex); } #endregion - + #region Private Fields private readonly object m_lock = new object(); private IServerInternal m_server; @@ -3273,7 +3269,7 @@ private NodeId CreateUniqueNodeId(ushort namespaceIndex) private double m_defaultMinimumSamplingInterval; private List m_namespaceUris; private ushort m_dynamicNamespaceIndex; - #endregion - } - + #endregion + } + } diff --git a/Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs new file mode 100644 index 0000000000..d88565f988 --- /dev/null +++ b/Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs @@ -0,0 +1,50 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.Server +{ + /// + /// Interface of the core node manager. + /// + public interface ICoreNodeManager : INodeManager, IDisposable + { + /// + /// Imports the nodes from a dictionary of NodeState objects. + /// + void ImportNodes(ISystemContext context, IEnumerable predefinedNodes); + + /// + /// Imports the nodes from a dictionary of NodeState objects. + /// + void ImportNodes(ISystemContext context, IEnumerable predefinedNodes, bool isInternal); + } +} diff --git a/Libraries/Opc.Ua.Server/NodeManager/IMainNodeManagerFactory.cs b/Libraries/Opc.Ua.Server/NodeManager/IMainNodeManagerFactory.cs new file mode 100644 index 0000000000..a300aff360 --- /dev/null +++ b/Libraries/Opc.Ua.Server/NodeManager/IMainNodeManagerFactory.cs @@ -0,0 +1,59 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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/ + * ======================================================================*/ + +namespace Opc.Ua.Server +{ + /// + /// Interface of the core node manager factory which helps creating main + /// node managers used by the server. + /// + public interface IMainNodeManagerFactory + { + /// + /// Creates the master node manager. + /// + /// The URI of the dynamic namespace. + /// Additional node managers provided by the application. + /// The master node manager. + IMasterNodeManager CreateMasterNodeManager(string dynamicNamespaceUri, params INodeManager[] additionalManagers); + + /// + /// Creates the configuration node manager. + /// + /// The configuration node manager. + IConfigurationNodeManager CreateConfigurationNodeManager(); + + /// + /// Creates the core node manager. + /// + /// The namespace index of the dynamic namespace. + /// + ICoreNodeManager CreateCoreNodeManager(ushort dynamicNamespaceIndex); + } +} diff --git a/Libraries/Opc.Ua.Server/NodeManager/IMasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/IMasterNodeManager.cs new file mode 100644 index 0000000000..470576a13d --- /dev/null +++ b/Libraries/Opc.Ua.Server/NodeManager/IMasterNodeManager.cs @@ -0,0 +1,274 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.Server +{ + /// + /// Interface of the master node manager. + /// + public interface IMasterNodeManager : IDisposable + { + /// + /// Returns the core node manager. + /// + ICoreNodeManager CoreNodeManager { get; } + + /// + /// Returns the diagnostics node manager. + /// + IDiagnosticsNodeManager DiagnosticsNodeManager { get; } + + /// + /// Returns the configuration node manager. + /// + IConfigurationNodeManager ConfigurationNodeManager { get; } + + /// + /// The node managers being managed. + /// + IList NodeManagers { get; } + + /// + /// Creates the node managers and start them + /// + void Startup(); + + /// + /// Signals that a session is closing. + /// + void SessionClosing(OperationContext context, NodeId sessionId, bool deleteSubscriptions); + + /// + /// Shuts down the node managers. + /// + void Shutdown(); + + /// + /// Registers the node manager as the node manager for Nodes in the specified namespace. + /// + /// The URI of the namespace. + /// The NodeManager which owns node in the namespace. + /// + /// Multiple NodeManagers may register interest in a Namespace. + /// The order in which this method is called determines the precedence if multiple NodeManagers exist. + /// This method adds the namespaceUri to the Server's Namespace table if it does not already exist. + /// + /// This method is thread safe and can be called at anytime. + /// + /// This method does not have to be called for any namespaces that were in the NodeManager's + /// NamespaceUri property when the MasterNodeManager was created. + /// + /// Throw if the namespaceUri or the nodeManager are null. + void RegisterNamespaceManager(string namespaceUri, INodeManager nodeManager); + + /// + /// Unregisters the node manager as the node manager for Nodes in the specified namespace. + /// + /// The URI of the namespace. + /// The NodeManager which no longer owns nodes in the namespace. + /// A value indicating whether the node manager was successfully unregistered. + /// Throw if the namespaceUri or the nodeManager are null. + bool UnregisterNamespaceManager(string namespaceUri, INodeManager nodeManager); + + /// + /// Returns node handle and its node manager. + /// + object GetManagerHandle(NodeId nodeId, out INodeManager nodeManager); + + /// + /// Adds the references to the target. + /// + void AddReferences(NodeId sourceId, IList references); + + /// + /// Deletes the references to the target. + /// + void DeleteReferences(NodeId targetId, IList references); + + /// + /// Deletes the specified references. + /// + void RemoveReferences(List referencesToRemove); + + /// + /// Registers a set of node ids. + /// + void RegisterNodes( + OperationContext context, + NodeIdCollection nodesToRegister, + out NodeIdCollection registeredNodeIds); + + /// + /// Unregisters a set of node ids. + /// + void UnregisterNodes( + OperationContext context, + NodeIdCollection nodesToUnregister); + + /// + /// Translates a start node id plus a relative paths into a node id. + /// + void TranslateBrowsePathsToNodeIds( + OperationContext context, + BrowsePathCollection browsePaths, + out BrowsePathResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Returns the set of references that meet the filter criteria. + /// + void Browse( + OperationContext context, + ViewDescription view, + uint maxReferencesPerNode, + BrowseDescriptionCollection nodesToBrowse, + out BrowseResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Continues a browse operation that was previously halted. + /// + void BrowseNext( + OperationContext context, + bool releaseContinuationPoints, + ByteStringCollection continuationPoints, + out BrowseResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Reads a set of nodes. + /// + void Read( + OperationContext context, + double maxAge, + TimestampsToReturn timestampsToReturn, + ReadValueIdCollection nodesToRead, + out DataValueCollection values, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Reads the history of a set of items. + /// + void HistoryRead( + OperationContext context, + ExtensionObject historyReadDetails, + TimestampsToReturn timestampsToReturn, + bool releaseContinuationPoints, + HistoryReadValueIdCollection nodesToRead, + out HistoryReadResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Writes a set of values. + /// + void Write( + OperationContext context, + WriteValueCollection nodesToWrite, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Updates the history for a set of nodes. + /// + void HistoryUpdate( + OperationContext context, + ExtensionObjectCollection historyUpdateDetails, + out HistoryUpdateResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Calls a method defined on an object. + /// + void Call( + OperationContext context, + CallMethodRequestCollection methodsToCall, + out CallMethodResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Handles condition refresh request. + /// + void ConditionRefresh(OperationContext context, IList monitoredItems); + + /// + /// Creates a set of monitored items. + /// + void CreateMonitoredItems( + OperationContext context, + uint subscriptionId, + double publishingInterval, + TimestampsToReturn timestampsToReturn, + IList itemsToCreate, + IList errors, + IList filterResults, + IList monitoredItems, + bool createDurable); + + /// + /// Modifies a set of monitored items. + /// + void ModifyMonitoredItems( + OperationContext context, + TimestampsToReturn timestampsToReturn, + IList monitoredItems, + IList itemsToModify, + IList errors, + IList filterResults); + + /// + /// Transfers a set of monitored items. + /// + void TransferMonitoredItems( + OperationContext context, + bool sendInitialValues, + IList monitoredItems, + IList errors); + + /// + /// Deletes a set of monitored items. + /// + void DeleteMonitoredItems( + OperationContext context, + uint subscriptionId, + IList itemsToDelete, + IList errors); + + /// + /// Changes the monitoring mode for a set of items. + /// + void SetMonitoringMode( + OperationContext context, + MonitoringMode monitoringMode, + IList itemsToModify, + IList errors); + } +} diff --git a/Libraries/Opc.Ua.Server/NodeManager/MainNodeManagerFactory.cs b/Libraries/Opc.Ua.Server/NodeManager/MainNodeManagerFactory.cs new file mode 100644 index 0000000000..b095e10698 --- /dev/null +++ b/Libraries/Opc.Ua.Server/NodeManager/MainNodeManagerFactory.cs @@ -0,0 +1,75 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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 Opc.Ua.Configuration; + +namespace Opc.Ua.Server +{ + /// + /// The factory that creates the main node managers of the server. The main + /// node managers are the one always present when creating a server. + /// + public class MainNodeManagerFactory : IMainNodeManagerFactory + { + /// + /// Initializes the object with default values. + /// + public MainNodeManagerFactory( + IApplicationInstance applicationInstance, + IServerInternal server) + { + m_applicationInstance = applicationInstance; + m_server = server; + } + + /// + public IMasterNodeManager CreateMasterNodeManager(string dynamicNamespaceUri, params INodeManager[] additionalManagers) + { + return new MasterNodeManager(m_server, m_applicationInstance.ApplicationConfiguration, this, dynamicNamespaceUri, additionalManagers); + } + + /// + public IConfigurationNodeManager CreateConfigurationNodeManager() + { + return new ConfigurationNodeManager(m_server, m_applicationInstance.ApplicationConfiguration); + } + + /// + + public ICoreNodeManager CreateCoreNodeManager(ushort dynamicNamespaceIndex) + { + return new CoreNodeManager(m_server, m_applicationInstance.ApplicationConfiguration, dynamicNamespaceIndex); + } + + #region Private Fields + private readonly IApplicationInstance m_applicationInstance; + private readonly IServerInternal m_server; + #endregion + } +} diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs index 711472c8d9..59c97a6e55 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -38,7 +38,7 @@ namespace Opc.Ua.Server /// The master node manager for the server. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] - public class MasterNodeManager : IDisposable + public class MasterNodeManager : IMasterNodeManager { #region Constructors /// @@ -47,20 +47,22 @@ public class MasterNodeManager : IDisposable public MasterNodeManager( IServerInternal server, ApplicationConfiguration configuration, + IMainNodeManagerFactory mainNodeManagerFactory, string dynamicNamespaceUri, params INodeManager[] additionalManagers) { if (server == null) throw new ArgumentNullException(nameof(server)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + if (mainNodeManagerFactory == null) throw new ArgumentNullException(nameof(mainNodeManagerFactory)); m_server = server; - m_nodeManagers = new List(); - m_maxContinuationPointsPerBrowse = (uint)configuration.ServerConfiguration.MaxBrowseContinuationPoints; + m_configuration = configuration; + m_mainNodeManagerFactory = mainNodeManagerFactory; // ensure the dynamic namespace uris. int dynamicNamespaceIndex = 1; - if (!String.IsNullOrEmpty(dynamicNamespaceUri)) + if (!string.IsNullOrEmpty(dynamicNamespaceUri)) { dynamicNamespaceIndex = server.NamespaceUris.GetIndex(dynamicNamespaceUri); @@ -71,19 +73,20 @@ public MasterNodeManager( } // need to build a table of NamespaceIndexes and their NodeManagers. - List registeredManagers = null; - Dictionary> namespaceManagers = new Dictionary>(); - - namespaceManagers[0] = registeredManagers = new List(); - namespaceManagers[1] = registeredManagers = new List(); + var registeredManagers = new List(); + var namespaceManagers = new Dictionary> + { + [0] = registeredManagers, + [1] = registeredManagers + }; // always add the diagnostics and configuration node manager to the start of the list. - ConfigurationNodeManager configurationAndDiagnosticsManager = new ConfigurationNodeManager(server, configuration); + IConfigurationNodeManager configurationAndDiagnosticsManager = m_mainNodeManagerFactory.CreateConfigurationNodeManager(); RegisterNodeManager(configurationAndDiagnosticsManager, registeredManagers, namespaceManagers); // add the core node manager second because the diagnostics node manager takes priority. // always add the core node manager to the second of the list. - m_nodeManagers.Add(new CoreNodeManager(m_server, configuration, (ushort)dynamicNamespaceIndex)); + m_nodeManagers.Add(m_mainNodeManagerFactory.CreateCoreNodeManager((ushort)dynamicNamespaceIndex)); // register core node manager for default UA namespace. namespaceManagers[0].Add(m_nodeManagers[1]); @@ -244,7 +247,7 @@ protected static PermissionType DetermineHistoryAccessPermission(HistoryUpdateDe } /// - /// Determine the History PermissionType depending on PerformUpdateType + /// Determine the History PermissionType depending on PerformUpdateType /// /// /// The corresponding PermissionType @@ -263,42 +266,41 @@ protected static PermissionType GetHistoryPermissionType(PerformUpdateType updat #endregion #region Public Interface - /// - /// Returns the core node manager. - /// - public CoreNodeManager CoreNodeManager + + /// + public ICoreNodeManager CoreNodeManager { get { - return m_nodeManagers[1] as CoreNodeManager; + return m_nodeManagers[1] as ICoreNodeManager; } } - /// - /// Returns the diagnostics node manager. - /// - public DiagnosticsNodeManager DiagnosticsNodeManager + /// + public IDiagnosticsNodeManager DiagnosticsNodeManager { get { - return m_nodeManagers[0] as DiagnosticsNodeManager; + return m_nodeManagers[0] as IDiagnosticsNodeManager; } } - /// - /// Returns the configuration node manager. - /// - public ConfigurationNodeManager ConfigurationNodeManager + /// + public IConfigurationNodeManager ConfigurationNodeManager { get { - return m_nodeManagers[0] as ConfigurationNodeManager; + return m_nodeManagers[0] as IConfigurationNodeManager; } } - /// - /// Creates the node managers and start them - /// + /// + public IList NodeManagers + { + get { return m_nodeManagers; } + } + + /// public virtual void Startup() { lock (m_lock) @@ -344,9 +346,7 @@ public virtual void Startup() } } - /// - /// Signals that a session is closing. - /// + /// public virtual void SessionClosing(OperationContext context, NodeId sessionId, bool deleteSubscriptions) { lock (m_lock) @@ -370,9 +370,7 @@ public virtual void SessionClosing(OperationContext context, NodeId sessionId, b } } - /// - /// Shuts down the node managers. - /// + /// public virtual void Shutdown() { lock (m_lock) @@ -389,22 +387,7 @@ public virtual void Shutdown() } } - /// - /// Registers the node manager as the node manager for Nodes in the specified namespace. - /// - /// The URI of the namespace. - /// The NodeManager which owns node in the namespace. - /// - /// Multiple NodeManagers may register interest in a Namespace. - /// The order in which this method is called determines the precedence if multiple NodeManagers exist. - /// This method adds the namespaceUri to the Server's Namespace table if it does not already exist. - /// - /// This method is thread safe and can be called at anytime. - /// - /// This method does not have to be called for any namespaces that were in the NodeManager's - /// NamespaceUri property when the MasterNodeManager was created. - /// - /// Throw if the namespaceUri or the nodeManager are null. + /// public void RegisterNamespaceManager(string namespaceUri, INodeManager nodeManager) { if (String.IsNullOrEmpty(namespaceUri)) throw new ArgumentNullException(nameof(namespaceUri)); @@ -460,13 +443,7 @@ public void RegisterNamespaceManager(string namespaceUri, INodeManager nodeManag } } - /// - /// Unregisters the node manager as the node manager for Nodes in the specified namespace. - /// - /// The URI of the namespace. - /// The NodeManager which no longer owns nodes in the namespace. - /// A value indicating whether the node manager was successfully unregistered. - /// Throw if the namespaceUri or the nodeManager are null. + /// public bool UnregisterNamespaceManager(string namespaceUri, INodeManager nodeManager) { if (String.IsNullOrEmpty(namespaceUri)) throw new ArgumentNullException(nameof(namespaceUri)); @@ -504,7 +481,7 @@ public bool UnregisterNamespaceManager(string namespaceUri, INodeManager nodeMan // allocate a new smaller array to support element removal for the index being updated. INodeManager[] registeredManagers = new INodeManager[namespaceManagers[namespaceIndex].Length - 1]; - // begin by populating the new array with existing elements up to the target index. + // begin by populating the new array with existing elements up to the target index. if (nodeManagerIndex > 0) { Array.Copy( @@ -540,9 +517,7 @@ public bool UnregisterNamespaceManager(string namespaceUri, INodeManager nodeMan } } - /// - /// Returns node handle and its node manager. - /// + /// public virtual object GetManagerHandle(NodeId nodeId, out INodeManager nodeManager) { nodeManager = null; @@ -597,9 +572,7 @@ public virtual object GetManagerHandle(NodeId nodeId, out INodeManager nodeManag return null; } - /// - /// Adds the references to the target. - /// + /// public virtual void AddReferences(NodeId sourceId, IList references) { foreach (IReference reference in references) @@ -621,9 +594,7 @@ public virtual void AddReferences(NodeId sourceId, IList references) } } - /// - /// Deletes the references to the target. - /// + /// public virtual void DeleteReferences(NodeId targetId, IList references) { foreach (ReferenceNode reference in references) @@ -644,9 +615,7 @@ public virtual void DeleteReferences(NodeId targetId, IList referenc } } - /// - /// Deletes the specified references. - /// + /// public void RemoveReferences(List referencesToRemove) { for (int ii = 0; ii < referencesToRemove.Count; ii++) @@ -667,9 +636,8 @@ public void RemoveReferences(List referencesToRemove) } #region Register/Unregister Nodes - /// - /// Registers a set of node ids. - /// + + /// public virtual void RegisterNodes( OperationContext context, NodeIdCollection nodesToRegister, @@ -705,9 +673,7 @@ public virtual void RegisterNodes( */ } - /// - /// Unregisters a set of node ids. - /// + /// public virtual void UnregisterNodes( OperationContext context, NodeIdCollection nodesToUnregister) @@ -735,9 +701,8 @@ public virtual void UnregisterNodes( #endregion #region TranslateBrowsePathsToNodeIds - /// - /// Translates a start node id plus a relative paths into a node id. - /// + + /// public virtual void TranslateBrowsePathsToNodeIds( OperationContext context, BrowsePathCollection browsePaths, @@ -1048,7 +1013,7 @@ private void TranslateBrowsePath( continue; } - // check for valid start node. + // check for valid start node. sourceHandle = GetManagerHandle((NodeId)targetId, out nodeManager); if (sourceHandle == null) @@ -1069,9 +1034,8 @@ private void TranslateBrowsePath( #endregion #region Browse - /// - /// Returns the set of references that meet the filter criteria. - /// + + /// public virtual void Browse( OperationContext context, ViewDescription view, @@ -1149,7 +1113,7 @@ public virtual void Browse( context, view, maxReferencesPerNode, - continuationPointsAssigned < m_maxContinuationPointsPerBrowse, + continuationPointsAssigned < (uint)m_configuration.ServerConfiguration.MaxBrowseContinuationPoints, nodeToBrowse, result); } @@ -1164,7 +1128,7 @@ public virtual void Browse( continuationPointsAssigned++; } - // check for error. + // check for error. result.StatusCode = error.StatusCode; if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) @@ -1226,9 +1190,7 @@ private void PrepareValidationCache(List nodesCollection, } } - /// - /// Continues a browse operation that was previously halted. - /// + /// public virtual void BrowseNext( OperationContext context, bool releaseContinuationPoints, @@ -1284,7 +1246,7 @@ public virtual void BrowseNext( } } - // initialize result. + // initialize result. BrowseResult result = new BrowseResult(); result.StatusCode = StatusCodes.Good; results.Add(result); @@ -1318,7 +1280,7 @@ public virtual void BrowseNext( error = FetchReferences( context, - continuationPointsAssigned < m_maxContinuationPointsPerBrowse, + continuationPointsAssigned < (uint)m_configuration.ServerConfiguration.MaxBrowseContinuationPoints, ref cp, ref references); @@ -1575,9 +1537,7 @@ private bool UpdateReferenceDescription( return true; } - /// - /// Reads a set of nodes. - /// + /// public virtual void Read( OperationContext context, double maxAge, @@ -1709,9 +1669,7 @@ public virtual void Read( UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } - /// - /// Reads the history of a set of items. - /// + /// public virtual void HistoryRead( OperationContext context, ExtensionObject historyReadDetails, @@ -1750,7 +1708,7 @@ public virtual void HistoryRead( for (int ii = 0; ii < nodesToRead.Count; ii++) { - // Limit permission restrictions to Client initiated service call + // Limit permission restrictions to Client initiated service call HistoryReadResult result = null; DiagnosticInfo diagnosticInfo = null; @@ -1834,9 +1792,7 @@ public virtual void HistoryRead( UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } - /// - /// Writes a set of values. - /// + /// public virtual void Write( OperationContext context, WriteValueCollection nodesToWrite, @@ -1922,16 +1878,14 @@ public virtual void Write( } ServerUtils.ReportWriteValue(nodesToWrite[ii].NodeId, nodesToWrite[ii].Value, results[ii]); - } + } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } - /// - /// Updates the history for a set of nodes. - /// + /// public virtual void HistoryUpdate( OperationContext context, ExtensionObjectCollection historyUpdateDetails, @@ -2063,9 +2017,7 @@ public virtual void HistoryUpdate( UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } - /// - /// Calls a method defined on an object. - /// + /// public virtual void Call( OperationContext context, CallMethodRequestCollection methodsToCall, @@ -2160,9 +2112,7 @@ public virtual void Call( UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } - /// - /// Handles condition refresh request. - /// + /// public virtual void ConditionRefresh(OperationContext context, IList monitoredItems) { foreach (INodeManager nodeManager in m_nodeManagers) @@ -2178,9 +2128,7 @@ public virtual void ConditionRefresh(OperationContext context, IList - /// Creates a set of monitored items. - /// + /// public virtual void CreateMonitoredItems( OperationContext context, uint subscriptionId, @@ -2403,9 +2351,7 @@ private void CreateMonitoredItemsForEvents( } } - /// - /// Modifies a set of monitored items. - /// + /// public virtual void ModifyMonitoredItems( OperationContext context, TimestampsToReturn timestampsToReturn, @@ -2571,9 +2517,7 @@ private void ModifyMonitoredItemsForEvents( } } - /// - /// Transfers a set of monitored items. - /// + /// public virtual void TransferMonitoredItems( OperationContext context, bool sendInitialValues, @@ -2605,9 +2549,7 @@ public virtual void TransferMonitoredItems( } } - /// - /// Deletes a set of monitored items. - /// + /// public virtual void DeleteMonitoredItems( OperationContext context, uint subscriptionId, @@ -2698,9 +2640,7 @@ private void DeleteMonitoredItemsForEvents( } } - /// - /// Changes the monitoring mode for a set of items. - /// + /// public virtual void SetMonitoringMode( OperationContext context, MonitoringMode monitoringMode, @@ -2787,14 +2727,6 @@ protected IServerInternal Server get { return m_server; } } - /// - /// The node managers being managed. - /// - public IList NodeManagers - { - get { return m_nodeManagers; } - } - /// /// The namespace managers being managed /// @@ -2932,7 +2864,7 @@ protected static ServiceResult ValidateMonitoredItemModifyRequest(MonitoredItemM return error; } - // validate monitoring filter. + // validate monitoring filter. error = ValidateMonitoringFilter(attributes.Filter); if (ServiceResult.IsBad(error)) @@ -3091,7 +3023,7 @@ protected ServiceResult ValidateHistoryUpdateRequest(OperationContext operationC #region Validate Permissions Methods /// - /// Check if the Base NodeClass attributes and NameSpace meta-data attributes + /// Check if the Base NodeClass attributes and NameSpace meta-data attributes /// are valid for the given operation context of the specified node. /// /// The Operation Context @@ -3099,7 +3031,7 @@ protected ServiceResult ValidateHistoryUpdateRequest(OperationContext operationC /// The requested permission /// The cache holding the values of the attributes neeeded to be used in subsequent calls /// Only the AccessRestrictions and RolePermission attributes are read. Should be false if uniqueNodesServiceAttributes is not null - /// StatusCode Good if permission is granted, BadUserAccessDenied if not granted + /// StatusCode Good if permission is granted, BadUserAccessDenied if not granted /// or a bad status code describing the validation process failure protected ServiceResult ValidatePermissions( OperationContext context, @@ -3120,7 +3052,7 @@ protected ServiceResult ValidatePermissions( } /// - /// Check if the Base NodeClass attributes and NameSpace meta-data attributes + /// Check if the Base NodeClass attributes and NameSpace meta-data attributes /// are valid for the given operation context of the specified node. /// /// The Operation Context @@ -3129,7 +3061,7 @@ protected ServiceResult ValidatePermissions( /// The requested permission /// The cache holding the values of the attributes neeeded to be used in subsequent calls /// Only the AccessRestrictions and RolePermission attributes are read. Should be false if uniqueNodesServiceAttributes is not null - /// StatusCode Good if permission is granted, BadUserAccessDenied if not granted + /// StatusCode Good if permission is granted, BadUserAccessDenied if not granted /// or a bad status code describing the validation process failure protected ServiceResult ValidatePermissions( OperationContext context, @@ -3163,7 +3095,7 @@ protected ServiceResult ValidatePermissions( if (nodeMetadata != null) { - // check RolePermissions + // check RolePermissions serviceResult = ValidateRolePermissions(context, nodeMetadata, requestedPermision); if (ServiceResult.IsGood(serviceResult)) @@ -3227,7 +3159,7 @@ protected static ServiceResult ValidateAccessRestrictions(OperationContext conte } /// - /// Validates the role permissions + /// Validates the role permissions /// /// /// @@ -3267,7 +3199,7 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext return StatusCodes.Good; } - // group all permissions defined in rolePermissions by RoleId + // group all permissions defined in rolePermissions by RoleId Dictionary roleIdPermissions = new Dictionary(); if (rolePermissions != null && rolePermissions.Count > 0) { @@ -3284,7 +3216,7 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext } } - // group all permissions defined in userRolePermissions by RoleId + // group all permissions defined in userRolePermissions by RoleId Dictionary roleIdPermissionsDefinedForUser = new Dictionary(); if (userRolePermissions != null && userRolePermissions.Count > 0) { @@ -3348,12 +3280,14 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext #region Private Fields private readonly object m_lock = new object(); - private IServerInternal m_server; - private List m_nodeManagers; + private readonly ApplicationConfiguration m_configuration; + private readonly IServerInternal m_server; + private readonly List m_nodeManagers = new List(); private long m_lastMonitoredItemId; private INodeManager[][] m_namespaceManagers; - private uint m_maxContinuationPointsPerBrowse; private ReaderWriterLockSlim m_readWriterLockSlim = new ReaderWriterLockSlim(); + private readonly IMainNodeManagerFactory m_mainNodeManagerFactory; + #endregion } diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index 6eb988d840..eca79e59a8 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -32,11 +32,24 @@ + + + + + + + + + + + + + - + diff --git a/Libraries/Opc.Ua.Server/Server/IReverseConnectServer.cs b/Libraries/Opc.Ua.Server/Server/IReverseConnectServer.cs new file mode 100644 index 0000000000..1d92f07c0e --- /dev/null +++ b/Libraries/Opc.Ua.Server/Server/IReverseConnectServer.cs @@ -0,0 +1,56 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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.ObjectModel; + +namespace Opc.Ua.Server +{ + /// + /// Interface of the reverse connect server. + /// + public interface IReverseConnectServer : IStandardServer + { + /// + /// Add a reverse connection url. + /// + void AddReverseConnection(Uri url, int timeout = 0, int maxSessionCount = 0, bool enabled = true); + + /// + /// Remove a reverse connection url. + /// + /// true if the reverse connection is found and removed + bool RemoveReverseConnection(Uri url); + + /// + /// Return a dictionary of configured reverse connection Urls. + /// + ReadOnlyDictionary GetReverseConnections(); + } +} diff --git a/Libraries/Opc.Ua.Server/Server/IServerInternal.cs b/Libraries/Opc.Ua.Server/Server/IServerInternal.cs index d34fd9b0d9..e2242582da 100644 --- a/Libraries/Opc.Ua.Server/Server/IServerInternal.cs +++ b/Libraries/Opc.Ua.Server/Server/IServerInternal.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -37,7 +37,7 @@ namespace Opc.Ua.Server /// /// The interface that a server exposes to objects that it contains. /// - public interface IServerInternal : IAuditEventServer + public interface IServerInternal : IAuditEventServer, IDisposable { /// /// The endpoint addresses used by the server. @@ -81,7 +81,7 @@ public interface IServerInternal : IAuditEventServer /// The type tree. /// /// The type tree table is a global object that all components of a server have access to. - /// Node managers must populate this table with all types that they define. + /// Node managers must populate this table with all types that they define. /// This object is thread safe. /// TypeTable TypeTree { get; } @@ -90,19 +90,19 @@ public interface IServerInternal : IAuditEventServer /// The master node manager for the server. /// /// The node manager. - MasterNodeManager NodeManager { get; } + IMasterNodeManager NodeManager { get; } /// /// The internal node manager for the servers. /// /// The core node manager. - CoreNodeManager CoreNodeManager { get; } + ICoreNodeManager CoreNodeManager { get; } /// /// Returns the node manager that managers the server diagnostics. /// /// The diagnostics node manager. - DiagnosticsNodeManager DiagnosticsNodeManager { get; } + IDiagnosticsNodeManager DiagnosticsNodeManager { get; } /// /// The manager for events that all components use to queue events that occur. @@ -122,12 +122,6 @@ public interface IServerInternal : IAuditEventServer /// The request manager. RequestManager RequestManager { get; } - /// - /// A manager for aggregate calculators supported by the server. - /// - /// The aggregate manager. - AggregateManager AggregateManager { get; } - /// /// The manager for active sessions. /// @@ -139,11 +133,6 @@ public interface IServerInternal : IAuditEventServer /// ISubscriptionManager SubscriptionManager { get; } - /// - /// The factory for (durable) monitored item queues - /// - IMonitoredItemQueueFactory MonitoredItemQueueFactory { get; } - /// /// Whether the server is currently running. /// @@ -199,6 +188,34 @@ public interface IServerInternal : IAuditEventServer /// true if diagnostics is enabled; otherwise, false. bool DiagnosticsEnabled { get; } + /// + /// Indicates that the server internal data object to be ready for usage (true) or not (false). + /// + bool Initialized { get; } + + /// + /// A manager for aggregate calculators supported by the server. + /// + /// The aggregate manager. + AggregateManager AggregateManager { get; set; } + + /// + /// The factory for durable monitored item queues + /// + IMonitoredItemQueueFactory MonitoredItemQueueFactory { get; } + + /// + /// Initializes the server internal data object to be ready for usage. + /// + /// Server description. + /// Server message context. + void Initialize(ServerProperties serverDescription, IServiceMessageContext messageContext); + + /// + /// Resets the server internal data, cannot be used until it is initialized again. + /// + void Reset(); + /// /// Closes the specified session. /// @@ -240,5 +257,51 @@ public interface IServerInternal : IAuditEventServer /// The subscription identifier. /// The monitored item identifier. void ConditionRefresh2(OperationContext context, uint subscriptionId, uint monitoredItemId); + + /// + /// Sets the server current state to shutdown with the given reason. + /// + /// Reason of the shutdown. + void SetServerCurrentShutdown(LocalizedText shutdownReason); + + /// + /// Sets the duration in seconds until shutdown. + /// + /// Time until shutdown in seconds. + void SetServerSecondsTillShutdown(uint timeTillShutdown); + + /// + /// Sets the EventManager, the ResourceManager, the RequestManager and the AggregateManager. + /// + /// The event manager. + /// The resource manager. + /// The request manager. + public void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager); + + /// + /// Stores the MasterNodeManager and the CoreNodeManager + /// + /// The node manager. + public void SetNodeManager(IMasterNodeManager nodeManager); + + /// + /// Stores the SessionManager, the SubscriptionManager in the datastore. + /// + /// The session manager. + /// The subscription manager. + public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager); + + /// + /// Updates the server status safely. + /// + /// Action to perform on the server status object. + void UpdateServerStatus(Action action); + + /// + /// Stores the MonitoredItemQueueFactory in the datastore. + /// + /// The MonitoredItemQueueFactory. + void SetMonitoredItemQueueFactory( + IMonitoredItemQueueFactory monitoredItemQueueFactory); } } diff --git a/Libraries/Opc.Ua.Server/Server/IStandardServer.cs b/Libraries/Opc.Ua.Server/Server/IStandardServer.cs new file mode 100644 index 0000000000..5e2db864ff --- /dev/null +++ b/Libraries/Opc.Ua.Server/Server/IStandardServer.cs @@ -0,0 +1,140 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 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 Opc.Ua.Security.Certificates; + +namespace Opc.Ua.Server +{ + /// + /// Interface of the standard server. + /// + public interface IStandardServer : ISessionServer + { + /// + /// Begins an asynchronous publish operation. + /// + /// The request. + void BeginPublish(IEndpointIncomingRequest request); + + /// + /// Completes an asynchronous publish operation. + /// + /// The request. + void CompletePublish(IEndpointIncomingRequest request); + + /// + /// The state object associated with the server. + /// It provides the shared components for the Server. + /// + /// The current instance. + IServerInternal CurrentInstance { get; } + + /// + /// Gets the current server state. + /// + ServerState CurrentState { get; } + + /// + /// The node manager factories that are used on startup of the server. + /// + IEnumerable NodeManagerFactories { get; } + + /// + /// The object used to verify client certificates + /// + /// The identifier for an X509 certificate. + CertificateValidator CertificateValidator { get; } + + /// + /// The server's application instance certificate types provider. + /// + /// The provider for the X.509 certificates. + CertificateTypesProvider InstanceCertificateTypesProvider { get; } + + /// + /// Registers the server with the discovery server. + /// + /// Boolean value. + bool RegisterWithDiscoveryServer(); + + /// + /// Add a node manager factory which is used on server start + /// to instantiate the node manager in the server. + /// + /// The node manager factory used to create the NodeManager. + void AddNodeManager(INodeManagerFactory nodeManagerFactory); + + /// + /// Remove a node manager factory from the list of node managers. + /// Does not remove a NodeManager from a running server, + /// only removes the factory before the server starts. + /// + /// The node manager factory to remove. + void RemoveNodeManager(INodeManagerFactory nodeManagerFactory); + + /// + /// Raised when the status of a monitored connection changes. + /// + event EventHandler ConnectionStatusChanged; + + /// + /// Creates a new connection with a client. + /// + void CreateConnection(Uri url, int timeout); + + /// + /// Create the transport listener for the service host endpoint. + /// + /// The endpoint Uri. + /// The description of the endpoints. + /// The configuration of the endpoints. + /// The transport listener. + /// The certificate validator for the transport. + void CreateServiceHostEndpoint( + Uri endpointUri, + EndpointDescriptionCollection endpoints, + EndpointConfiguration endpointConfiguration, + ITransportListener listener, + ICertificateValidator certificateValidator + ); + + /// + /// Returns the UserTokenPolicies supported by the server. + /// + /// The configuration. + /// The description. + /// + /// Returns a collection of UserTokenPolicy objects, + /// the return type is . + /// + UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description); + } +} diff --git a/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs b/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs index 08acee92cf..26404c8731 100644 --- a/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs +++ b/Libraries/Opc.Ua.Server/Server/ReverseConnectServer.cs @@ -32,6 +32,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading; +using Opc.Ua.Configuration; namespace Opc.Ua.Server { @@ -137,7 +138,7 @@ public ReverseConnectProperty( /// /// The standard implementation of a UA server with reverse connect. /// - public class ReverseConnectServer : StandardServer + public class ReverseConnectServer : StandardServer, IReverseConnectServer { /// /// The default reverse connect interval. @@ -157,7 +158,11 @@ public class ReverseConnectServer : StandardServer /// /// Creates a reverse connect server based on a StandardServer. /// - public ReverseConnectServer() + public ReverseConnectServer( + IApplicationInstance applicationInstance, + IServerInternal serverInternal, + IMainNodeManagerFactory mainNodeManagerFactory) + : base (applicationInstance, serverInternal, mainNodeManagerFactory) { m_connectInterval = DefaultReverseConnectInterval; m_connectTimeout = DefaultReverseConnectTimeout; diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs index cdc712c123..7b8f10231f 100644 --- a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs +++ b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2022 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 @@ -11,7 +11,7 @@ * 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, @@ -30,8 +30,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Security.Cryptography.X509Certificates; -using Opc.Ua.Security.Certificates; +using Opc.Ua.Configuration; #pragma warning disable 0618 @@ -44,63 +43,28 @@ namespace Opc.Ua.Server /// This is a readonly class that is initialized when the server starts up. It provides /// access to global objects and data that different parts of the server may require. /// It also defines some global methods. - /// + /// /// This object is constructed is three steps: /// - the configuration is provided. /// - the node managers et. al. are provided. /// - the session/subscription managers are provided. - /// + /// /// The server is not running until all three steps are complete. - /// - /// The references returned from this object do not change after all three states are complete. + /// + /// The references returned from this object do not change after all three states are complete. /// This ensures the object is thread safe even though it does not use a lock. /// Objects returned from this object can be assumed to be threadsafe unless otherwise stated. /// - public class ServerInternalData : IServerInternal, IDisposable + public class ServerInternalData : IServerInternal { #region Constructors /// /// Initializes the datastore with the server configuration. /// - /// The server description. - /// The configuration. - /// The message context. - /// The certificate validator. - /// The certificate type provider. - public ServerInternalData( - ServerProperties serverDescription, - ApplicationConfiguration configuration, - IServiceMessageContext messageContext, - CertificateValidator certificateValidator, - CertificateTypesProvider instanceCertificateProvider) - { - m_serverDescription = serverDescription; - m_configuration = configuration; - m_messageContext = messageContext; - - m_endpointAddresses = new List(); - - foreach (string baseAddresses in m_configuration.ServerConfiguration.BaseAddresses) - { - Uri url = Utils.ParseUri(baseAddresses); - - if (url != null) - { - m_endpointAddresses.Add(url); - } - } - - m_namespaceUris = m_messageContext.NamespaceUris; - m_factory = m_messageContext.Factory; - - m_serverUris = new StringTable(); - m_typeTree = new TypeTable(m_namespaceUris); - - // add the server uri to the server table. - m_serverUris.Append(m_configuration.ApplicationUri); - - // create the default system context. - m_defaultSystemContext = new ServerSystemContext(this); + /// The application instance. + public ServerInternalData(IApplicationInstance applicationInstance) + { + m_applicationInstance = applicationInstance; } #endregion @@ -122,6 +86,8 @@ protected virtual void Dispose(bool disposing) { if (disposing) { + Initialized = false; + Utils.SilentDispose(m_resourceManager); Utils.SilentDispose(m_requestManager); Utils.SilentDispose(m_aggregateManager); @@ -129,6 +95,13 @@ protected virtual void Dispose(bool disposing) Utils.SilentDispose(m_sessionManager); Utils.SilentDispose(m_subscriptionManager); Utils.SilentDispose(m_monitoredItemQueueFactory); + + m_endpointAddresses?.Clear(); + m_endpointAddresses = null; + m_typeTree?.Clear(); + m_typeTree = null; + m_serverUris = null; + m_defaultSystemContext = null; } } #endregion @@ -138,7 +111,7 @@ protected virtual void Dispose(bool disposing) /// The session manager to use with the server. /// /// The session manager. - public SessionManager SessionManager + public ISessionManager SessionManager { get { return m_sessionManager; } } @@ -147,7 +120,7 @@ public SessionManager SessionManager /// The subscription manager to use with the server. /// /// The subscription manager. - public SubscriptionManager SubscriptionManager + public ISubscriptionManager SubscriptionManager { get { return m_subscriptionManager; } } @@ -156,7 +129,7 @@ public SubscriptionManager SubscriptionManager /// Stores the MasterNodeManager and the CoreNodeManager /// /// The node manager. - public void SetNodeManager(MasterNodeManager nodeManager) + public void SetNodeManager(IMasterNodeManager nodeManager) { m_nodeManager = nodeManager; m_diagnosticsNodeManager = nodeManager.DiagnosticsNodeManager; @@ -281,7 +254,7 @@ public TypeTable TypeTree /// The master node manager for the server. /// /// The node manager. - public MasterNodeManager NodeManager + public IMasterNodeManager NodeManager { get { return m_nodeManager; } } @@ -290,7 +263,7 @@ public MasterNodeManager NodeManager /// The internal node manager for the servers. /// /// The core node manager. - public CoreNodeManager CoreNodeManager + public ICoreNodeManager CoreNodeManager { get { return m_coreNodeManager; } } @@ -299,7 +272,7 @@ public CoreNodeManager CoreNodeManager /// Returns the node manager that managers the server diagnostics. /// /// The diagnostics node manager. - public DiagnosticsNodeManager DiagnosticsNodeManager + public IDiagnosticsNodeManager DiagnosticsNodeManager { get { return m_diagnosticsNodeManager; } } @@ -372,6 +345,7 @@ public IMonitoredItemQueueFactory MonitoredItemQueueFactory /// Returns the status object for the server. /// /// The status. + [Obsolete("No longer thread safe. Must not use.")] public ServerStatusValue Status { get { return m_serverStatus; } @@ -571,6 +545,11 @@ public void ConditionRefresh2(OperationContext context, uint subscriptionId, uin /// public ISystemContext DefaultAuditContext => DefaultSystemContext.Copy(); + /// + /// Indicates that the server internal data object to be ready for usage (true) or not (false). + /// + public bool Initialized { get; private set; } + /// public void ReportAuditEvent(ISystemContext context, AuditEventState e) { @@ -597,21 +576,23 @@ private void CreateServerObject() ObjectIds.Server, typeof(ServerObjectState)); + ApplicationConfiguration configuration = m_applicationInstance.ApplicationConfiguration; + // update server capabilities. serverObject.ServiceLevel.Value = 255; serverObject.ServerCapabilities.LocaleIdArray.Value = m_resourceManager.GetAvailableLocales(); - serverObject.ServerCapabilities.ServerProfileArray.Value = m_configuration.ServerConfiguration.ServerProfileArray.ToArray(); + serverObject.ServerCapabilities.ServerProfileArray.Value = configuration.ServerConfiguration.ServerProfileArray.ToArray(); serverObject.ServerCapabilities.MinSupportedSampleRate.Value = 0; - serverObject.ServerCapabilities.MaxBrowseContinuationPoints.Value = (ushort)m_configuration.ServerConfiguration.MaxBrowseContinuationPoints; - serverObject.ServerCapabilities.MaxQueryContinuationPoints.Value = (ushort)m_configuration.ServerConfiguration.MaxQueryContinuationPoints; - serverObject.ServerCapabilities.MaxHistoryContinuationPoints.Value = (ushort)m_configuration.ServerConfiguration.MaxHistoryContinuationPoints; - serverObject.ServerCapabilities.MaxArrayLength.Value = (uint)m_configuration.TransportQuotas.MaxArrayLength; - serverObject.ServerCapabilities.MaxStringLength.Value = (uint)m_configuration.TransportQuotas.MaxStringLength; - serverObject.ServerCapabilities.MaxByteStringLength.Value = (uint)m_configuration.TransportQuotas.MaxByteStringLength; + serverObject.ServerCapabilities.MaxBrowseContinuationPoints.Value = (ushort)configuration.ServerConfiguration.MaxBrowseContinuationPoints; + serverObject.ServerCapabilities.MaxQueryContinuationPoints.Value = (ushort)configuration.ServerConfiguration.MaxQueryContinuationPoints; + serverObject.ServerCapabilities.MaxHistoryContinuationPoints.Value = (ushort)configuration.ServerConfiguration.MaxHistoryContinuationPoints; + serverObject.ServerCapabilities.MaxArrayLength.Value = (uint)configuration.TransportQuotas.MaxArrayLength; + serverObject.ServerCapabilities.MaxStringLength.Value = (uint)configuration.TransportQuotas.MaxStringLength; + serverObject.ServerCapabilities.MaxByteStringLength.Value = (uint)configuration.TransportQuotas.MaxByteStringLength; // Any operational limits Property that is provided shall have a non zero value. var operationLimits = serverObject.ServerCapabilities.OperationLimits; - var configOperationLimits = m_configuration.ServerConfiguration.OperationLimits; + var configOperationLimits = configuration.ServerConfiguration.OperationLimits; if (configOperationLimits != null) { operationLimits.MaxNodesPerRead = SetPropertyValue(operationLimits.MaxNodesPerRead, configOperationLimits.MaxNodesPerRead); @@ -725,14 +706,14 @@ private void CreateServerObject() // set the diagnostics enabled state. m_diagnosticsNodeManager.SetDiagnosticsEnabled( m_defaultSystemContext, - m_configuration.ServerConfiguration.DiagnosticsEnabled); + configuration.ServerConfiguration.DiagnosticsEnabled); ConfigurationNodeManager configurationNodeManager = m_diagnosticsNodeManager as ConfigurationNodeManager; configurationNodeManager?.CreateServerConfiguration( m_defaultSystemContext, - m_configuration); + configuration); - m_auditing = m_configuration.ServerConfiguration.AuditingEnabled; + m_auditing = configuration.ServerConfiguration.AuditingEnabled; PropertyState auditing = serverObject.Auditing; auditing.OnSimpleWriteValue += OnWriteAuditing; auditing.OnSimpleReadValue += OnReadAuditing; @@ -883,11 +864,134 @@ private PropertyState SetPropertyValue(PropertyState property, uint } return property; } + + /// + /// Initializes the server internal data object to be ready for usage. + /// + /// Server description. + /// Server message context. + public void Initialize(ServerProperties serverDescription, IServiceMessageContext messageContext) + { + if (serverDescription == null) throw new ArgumentNullException(nameof(serverDescription)); + if (messageContext == null) throw new ArgumentNullException(nameof(messageContext)); + + lock (m_dataLock) + { + if (Initialized) + { + throw new InvalidOperationException("Server internal data already initialized."); + } + + m_serverDescription = serverDescription; + m_messageContext = messageContext; + + m_endpointAddresses = new List(); + + ApplicationConfiguration configuration = m_applicationInstance.ApplicationConfiguration; + + foreach (string baseAddresses in configuration.ServerConfiguration.BaseAddresses) + { + Uri url = Utils.ParseUri(baseAddresses); + + if (url != null) + { + m_endpointAddresses.Add(url); + } + } + + m_namespaceUris = m_messageContext.NamespaceUris; + m_factory = m_messageContext.Factory; + + m_typeTree = new TypeTable(m_namespaceUris); + + // add the server uri to the server table. + m_serverUris = new StringTable(); + m_serverUris.Append(configuration.ApplicationUri); + + // create the default system context. + m_defaultSystemContext = new ServerSystemContext(this); + + Initialized = true; + } + } + + /// + /// Resets the server internal data, cannot be used until it is initialized again. + /// + public void Reset() + { + lock (m_dataLock) + { + if (!Initialized) + { + return; + } + + Initialized = false; + } + + Dispose(true); + + m_serverUris = null; + m_endpointAddresses = null; + + m_serverDescription = null; + m_messageContext = null; + m_namespaceUris = null; + m_factory = null; + m_typeTree = null; + m_defaultSystemContext = null; + } + + /// + /// Sets the server current state to shutdown with the given reason. + /// + /// Reason of the shutdown. + public void SetServerCurrentShutdown(LocalizedText shutdownReason) + { + lock (m_dataLock) + { + m_serverStatus.Value.ShutdownReason = shutdownReason; + m_serverStatus.Variable.ShutdownReason.Value = shutdownReason; + m_serverStatus.Value.State = ServerState.Shutdown; + m_serverStatus.Variable.State.Value = ServerState.Shutdown; + m_serverStatus.Variable.ClearChangeMasks(DefaultSystemContext, true); + } + } + + /// + /// Sets the duration in seconds until shutdown. + /// + /// Time until shutdown in seconds. + public void SetServerSecondsTillShutdown(uint timeTillShutdown) + { + lock (m_dataLock) + { + m_serverStatus.Value.SecondsTillShutdown = timeTillShutdown; + m_serverStatus.Variable.SecondsTillShutdown.Value = timeTillShutdown; + m_serverStatus.Variable.ClearChangeMasks(DefaultSystemContext, true); + } + } + + /// + /// Updates the server status safely. + /// + /// Action to perform on the server status object. + public void UpdateServerStatus(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + lock (m_dataLock) + { + action.Invoke(m_serverStatus); + } + } + #endregion #region Private Fields + private readonly IApplicationInstance m_applicationInstance; private ServerProperties m_serverDescription; - private ApplicationConfiguration m_configuration; private List m_endpointAddresses; private IServiceMessageContext m_messageContext; private ServerSystemContext m_defaultSystemContext; @@ -898,9 +1002,9 @@ private PropertyState SetPropertyValue(PropertyState property, uint private ResourceManager m_resourceManager; private RequestManager m_requestManager; private AggregateManager m_aggregateManager; - private MasterNodeManager m_nodeManager; - private CoreNodeManager m_coreNodeManager; - private DiagnosticsNodeManager m_diagnosticsNodeManager; + private IMasterNodeManager m_nodeManager; + private ICoreNodeManager m_coreNodeManager; + private IDiagnosticsNodeManager m_diagnosticsNodeManager; private EventManager m_eventManager; private SessionManager m_sessionManager; private SubscriptionManager m_subscriptionManager; diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 66e72a5678..3200158fd6 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -36,6 +36,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using Opc.Ua.Bindings; +using Opc.Ua.Configuration; using static Opc.Ua.Utils; namespace Opc.Ua.Server @@ -44,15 +45,20 @@ namespace Opc.Ua.Server /// The standard implementation of a UA server. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] - public partial class StandardServer : SessionServerBase + public partial class StandardServer : SessionServerBase, IStandardServer { #region Constructors /// /// Initializes the object with default values. /// - public StandardServer() + public StandardServer( + IApplicationInstance applicationInstance, + IServerInternal serverInternal, + IMainNodeManagerFactory mainNodeManagerFactory) { - m_nodeManagerFactories = new List(); + m_applicationInstance = applicationInstance ?? throw new ArgumentNullException(nameof(applicationInstance)); + m_serverInternal = serverInternal ?? throw new ArgumentNullException(nameof(serverInternal)); + m_mainNodeManagerFactory = mainNodeManagerFactory ?? throw new ArgumentNullException(nameof(mainNodeManagerFactory)); ; } #endregion @@ -82,12 +88,7 @@ protected override void Dispose(bool disposing) m_configurationWatcher = null; } - // close the server. - if (m_serverInternal != null) - { - Utils.SilentDispose(m_serverInternal); - m_serverInternal = null; - } + Utils.SilentDispose(m_serverInternal); } base.Dispose(disposing); @@ -472,7 +473,7 @@ public override ResponseHeader CreateSession( } } -#if ECC_SUPPORT +#if ECC_SUPPORT var parameters = ExtensionObject.ToEncodeable(requestHeader.AdditionalHeader) as AdditionalParametersType; if (parameters != null) @@ -526,7 +527,7 @@ public override ResponseHeader CreateSession( ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good); -#if ECC_SUPPORT +#if ECC_SUPPORT if (parameters != null) { responseHeader.AdditionalHeader = new ExtensionObject(parameters); @@ -606,7 +607,7 @@ protected virtual AdditionalParametersType CreateSessionProcessAdditionalParamet } /// - /// Process additional parameters during ECC session activation + /// Process additional parameters during ECC session activation /// /// The session /// The additional parameters for the session @@ -869,7 +870,7 @@ public override ResponseHeader CloseSession(RequestHeader requestHeader, bool de ServerInternal.CloseSession(context, context.Session.Id, deleteSubscriptions); - // report the audit event for close session + // report the audit event for close session ServerInternal.ReportAuditCloseSessionEvent(context.AuditEntryId, session, "Session/CloseSession"); return CreateResponse(requestHeader, context.StringTable); @@ -1657,7 +1658,7 @@ public override ResponseHeader Publish( /* if (notificationMessage != null) { - Utils.LogTrace(m_eventId, + Utils.LogTrace(m_eventId, "PublishResponse: SubId={0} SeqNo={1}, PublishTime={2:mm:ss.fff}, Time={3:mm:ss.fff}", subscriptionId, notificationMessage.SequenceNumber, @@ -1688,10 +1689,7 @@ public override ResponseHeader Publish( } } - /// - /// Begins an asynchronous publish operation. - /// - /// The request. + /// public virtual void BeginPublish(IEndpointIncomingRequest request) { PublishRequest input = (PublishRequest)request.Request; @@ -1753,10 +1751,7 @@ public virtual void BeginPublish(IEndpointIncomingRequest request) } } - /// - /// Completes an asynchronous publish operation. - /// - /// The request. + /// public virtual void CompletePublish(IEndpointIncomingRequest request) { AsyncPublishOperation operation = (AsyncPublishOperation)request.Calldata; @@ -2303,21 +2298,18 @@ public override ResponseHeader Call( OnRequestComplete(context); } } -#endregion + #endregion -#region Public Methods used by the Host Process - /// - /// The state object associated with the server. - /// It provides the shared components for the Server. - /// - /// The current instance. + #region Public Methods used by the Host Process + + /// public IServerInternal CurrentInstance { get { lock (m_lock) { - if (m_serverInternal == null) + if (!m_serverInternal.Initialized) { throw new ServiceResultException(StatusCodes.BadServerHalted); } @@ -2331,11 +2323,12 @@ public IServerInternal CurrentInstance /// Returns the current status of the server. /// /// Returns a ServerStatusDataType object + [Obsolete("No longer thread safe. Must not use.")] public ServerStatusDataType GetStatus() { lock (m_lock) { - if (m_serverInternal == null) + if (!m_serverInternal.Initialized) { throw new ServiceResultException(StatusCodes.BadServerHalted); } @@ -2344,10 +2337,10 @@ public ServerStatusDataType GetStatus() } } - /// - /// Registers the server with the discovery server. - /// - /// Boolean value. + /// + public ServerState CurrentState => m_serverInternal.CurrentState; + + /// public bool RegisterWithDiscoveryServer() { ApplicationConfiguration configuration = new ApplicationConfiguration(base.Configuration); @@ -2568,18 +2561,16 @@ private void OnRegisterServer(object state) /// The state object associated with the server. /// /// The server internal data. - protected ServerInternalData ServerInternal + protected IServerInternal ServerInternal { get { - ServerInternalData serverInternal = m_serverInternal; - - if (serverInternal == null) + if (!m_serverInternal.Initialized) { throw new ServiceResultException(StatusCodes.BadServerHalted); } - return serverInternal; + return m_serverInternal; } } @@ -2598,9 +2589,7 @@ protected override void ValidateRequest(RequestHeader requestHeader) } // check server state. - ServerInternalData serverInternal = m_serverInternal; - - if (serverInternal == null || !serverInternal.IsRunning) + if (!m_serverInternal.Initialized || !m_serverInternal.IsRunning) { throw new ServiceResultException(StatusCodes.BadServerHalted); } @@ -2621,7 +2610,7 @@ protected virtual void SetServerState(ServerState state) throw new ServiceResultException(ServerError); } - if (m_serverInternal == null) + if (!m_serverInternal.Initialized) { throw new ServiceResultException(StatusCodes.BadServerHalted); } @@ -2806,7 +2795,7 @@ protected virtual void OnRequestComplete(OperationContext context) { lock (m_lock) { - if (m_serverInternal == null) + if (!m_serverInternal.Initialized) { throw new ServiceResultException(StatusCodes.BadServerHalted); } @@ -2816,6 +2805,7 @@ protected virtual void OnRequestComplete(OperationContext context) } #endregion + #region Protected Members used for Initialization /// /// Raised when the configuration changes. @@ -2920,9 +2910,8 @@ protected override IList InitializeServiceHosts( // ensure at least one user token policy exists. if (configuration.ServerConfiguration.UserTokenPolicies.Count == 0) { - UserTokenPolicy userTokenPolicy = new UserTokenPolicy(); + var userTokenPolicy = new UserTokenPolicy { TokenType = UserTokenType.Anonymous }; - userTokenPolicy.TokenType = UserTokenType.Anonymous; userTokenPolicy.PolicyId = userTokenPolicy.TokenType.ToString(); configuration.ServerConfiguration.UserTokenPolicies.Add(userTokenPolicy); @@ -3006,14 +2995,9 @@ protected override void StartApplication(ApplicationConfiguration configuration) Nonce.SetMinNonceValue((uint)configuration.SecurityConfiguration.NonceLength); // create the datastore for the instance. - m_serverInternal = new ServerInternalData( - ServerProperties, - configuration, - MessageContext, - new CertificateValidator(), - InstanceCertificateTypesProvider); + m_serverInternal.Initialize(ServerProperties, MessageContext); - // create the manager responsible for providing localized string resources. + // create the manager responsible for providing localized string resources. Utils.LogInfo(TraceMasks.StartStop, "Server - CreateResourceManager."); ResourceManager resourceManager = CreateResourceManager(m_serverInternal, configuration); @@ -3023,7 +3007,7 @@ protected override void StartApplication(ApplicationConfiguration configuration) // create the master node manager. Utils.LogInfo(TraceMasks.StartStop, "Server - CreateMasterNodeManager."); - MasterNodeManager masterNodeManager = CreateMasterNodeManager(m_serverInternal, configuration); + IMasterNodeManager masterNodeManager = CreateMasterNodeManager(m_serverInternal, configuration); // add the node manager to the datastore. m_serverInternal.SetNodeManager(masterNodeManager); @@ -3159,7 +3143,7 @@ protected override void StartApplication(ApplicationConfiguration configuration) { var message = "Unexpected error starting application"; Utils.LogCritical(TraceMasks.StartStop, e, message); - m_serverInternal = null; + m_serverInternal.Reset(); ServiceResult error = ServiceResult.Create(e, StatusCodes.BadInternalError, message); ServerError = error; throw new ServiceResultException(error); @@ -3214,12 +3198,7 @@ protected override void OnServerStopping() } finally { - // ensure that everything is cleaned up. - if (m_serverInternal != null) - { - Utils.SilentDispose(m_serverInternal); - m_serverInternal = null; - } + Utils.SilentDispose(m_serverInternal); } } @@ -3236,11 +3215,7 @@ protected void ShutDownDelay() 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); + ServerInternal.SetServerCurrentShutdown(new LocalizedText("en-US", "Application closed.")); foreach (Session session in currentessions) { @@ -3250,9 +3225,7 @@ protected void ShutDownDelay() for (int timeTillShutdown = Configuration.ServerConfiguration.ShutdownDelay; timeTillShutdown > 0; timeTillShutdown--) { - ServerInternal.Status.Value.SecondsTillShutdown = (uint)timeTillShutdown; - ServerInternal.Status.Variable.SecondsTillShutdown.Value = (uint)timeTillShutdown; - ServerInternal.Status.Variable.ClearChangeMasks(ServerInternal.DefaultSystemContext, true); + ServerInternal.SetServerSecondsTillShutdown((uint)timeTillShutdown); // exit if all client connections are closed. var sessions = ServerInternal.SessionManager.GetSessions().Count; @@ -3364,7 +3337,9 @@ protected virtual ResourceManager CreateResourceManager(IServerInternal server, /// The server. /// The configuration. /// Returns the master node manager for the server, the return type is . - protected virtual MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration) + protected virtual IMasterNodeManager CreateMasterNodeManager( + IServerInternal server, + ApplicationConfiguration configuration) { var nodeManagers = new List(); @@ -3373,7 +3348,7 @@ protected virtual MasterNodeManager CreateMasterNodeManager(IServerInternal serv nodeManagers.Add(nodeManagerFactory.Create(server, configuration)); } - return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray()); + return m_mainNodeManagerFactory.CreateMasterNodeManager(null, nodeManagers.ToArray()); } /// @@ -3440,27 +3415,16 @@ protected virtual void OnServerStarted(IServerInternal server) // may be overridden by the subclass. } - /// - /// The node manager factories that are used on startup of the server. - /// + /// public IEnumerable NodeManagerFactories => m_nodeManagerFactories; - /// - /// Add a node manager factory which is used on server start - /// to instantiate the node manager in the server. - /// - /// The node manager factory used to create the NodeManager. + /// public virtual void AddNodeManager(INodeManagerFactory nodeManagerFactory) { m_nodeManagerFactories.Add(nodeManagerFactory); } - /// - /// Remove a node manager factory from the list of node managers. - /// Does not remove a NodeManager from a running server, - /// only removes the factory before the server starts. - /// - /// The node manager factory to remove. + /// public virtual void RemoveNodeManager(INodeManagerFactory nodeManagerFactory) { m_nodeManagerFactories.Remove(nodeManagerFactory); @@ -3487,12 +3451,15 @@ private void SessionChannelKeepAliveEvent(Session session, SessionEventReason re #region Private Properties private OperationLimitsState OperationLimits => ServerInternal.ServerObject.ServerCapabilities.OperationLimits; -#endregion + #endregion -#region Private Fields - private readonly object m_lock = new object(); - private readonly object m_registrationLock = new object(); - private ServerInternalData m_serverInternal; + #region Private Fields + private readonly IApplicationInstance m_applicationInstance; + private readonly IServerInternal m_serverInternal; + private readonly IMainNodeManagerFactory m_mainNodeManagerFactory; + private readonly object m_lock = new(); + private readonly object m_registrationLock = new(); + private readonly List m_nodeManagerFactories = new(); private ConfigurationWatcher m_configurationWatcher; private ConfiguredEndpointCollection m_registrationEndpoints; private RegisteredServer m_registrationInfo; @@ -3503,7 +3470,6 @@ private void SessionChannelKeepAliveEvent(Session session, SessionEventReason re private bool m_registeredWithDiscoveryServer; private int m_minNonceLength; private bool m_useRegisterServer2; - private List m_nodeManagerFactories; #endregion } } diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index 9a27309e95..8ee5fa70c1 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -70,7 +70,7 @@ public SessionManager( } #endregion - #region IDisposable Members + #region IDisposable Members /// /// Frees any unmanaged resources. /// @@ -479,7 +479,7 @@ public virtual void CloseSession(NodeId sessionId) /// /// /// This method verifies that the session id is valid and that it uses secure channel id - /// associated with current thread. It also verifies that the timestamp is not too + /// associated with current thread. It also verifies that the timestamp is not too /// and that the sequence number is not out of order (update requests only). /// public virtual OperationContext ValidateRequest(RequestHeader requestHeader, RequestType requestType) @@ -888,10 +888,65 @@ public interface ISessionManager /// /// The requested session. Session GetSession(NodeId authenticationToken); + + /// + /// Stops the session manager and closes all sessions. + /// + void Shutdown(); + + /// + /// Creates a new session. + /// + Session CreateSession( + OperationContext context, + X509Certificate2 serverCertificate, + string sessionName, + byte[] clientNonce, + ApplicationDescription clientDescription, + string endpointUrl, + X509Certificate2 clientCertificate, + X509Certificate2Collection clientCertificateChain, + double requestedSessionTimeout, + uint maxResponseMessageSize, + out NodeId sessionId, + out NodeId authenticationToken, + out byte[] serverNonce, + out double revisedSessionTimeout); + + /// + /// Activates an existing session + /// + bool ActivateSession( + OperationContext context, + NodeId authenticationToken, + SignatureData clientSignature, + List clientSoftwareCertificates, + ExtensionObject userIdentityToken, + SignatureData userTokenSignature, + StringCollection localeIds, + out byte[] serverNonce); + + /// + /// Closes the specified session. + /// + /// + /// This method should not throw an exception if the session no longer exists. + /// + void CloseSession(NodeId sessionId); + + /// + /// Validates request header and returns a request context. + /// + /// + /// This method verifies that the session id is valid and that it uses secure channel id + /// associated with current thread. It also verifies that the timestamp is not too + /// and that the sequence number is not out of order (update requests only). + /// + OperationContext ValidateRequest(RequestHeader requestHeader, RequestType requestType); } /// - /// The possible reasons for a session related event. + /// The possible reasons for a session related event. /// public enum SessionEventReason { @@ -990,7 +1045,7 @@ public ServiceResult IdentityValidationError } /// - /// Get the EndpointDescription + /// Get the EndpointDescription /// public EndpointDescription EndpointDescription { diff --git a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs index c0614b668b..e48d9030ec 100644 --- a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs +++ b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs @@ -38,7 +38,7 @@ namespace Opc.Ua.Server /// /// A generic session manager object for a server. /// - public class SubscriptionManager : IDisposable, ISubscriptionManager + public class SubscriptionManager : ISubscriptionManager { #region Constructors /// @@ -2175,7 +2175,7 @@ public override int GetHashCode() /// /// Sinks that receive these events must not block the thread. /// - public interface ISubscriptionManager + public interface ISubscriptionManager : IDisposable { /// /// Raised after a new subscription is created. @@ -2201,6 +2201,161 @@ ServiceResult SetSubscriptionDurable( uint subscriptionId, uint lifetimeInHours, out uint revisedLifetimeInHours); + + /// + /// Creates a new subscription. + /// + void CreateSubscription( + OperationContext context, + double requestedPublishingInterval, + uint requestedLifetimeCount, + uint requestedMaxKeepAliveCount, + uint maxNotificationsPerPublish, + bool publishingEnabled, + byte priority, + out uint subscriptionId, + out double revisedPublishingInterval, + out uint revisedLifetimeCount, + out uint revisedMaxKeepAliveCount); + + /// + /// Closes all subscriptions and rejects any new requests. + /// + void Shutdown(); + + /// + /// Deletes group of subscriptions. + /// + void DeleteSubscriptions( + OperationContext context, + UInt32Collection subscriptionIds, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Publishes a subscription. + /// + NotificationMessage Publish( + OperationContext context, + SubscriptionAcknowledgementCollection subscriptionAcknowledgements, + AsyncPublishOperation operation, + out uint subscriptionId, + out UInt32Collection availableSequenceNumbers, + out bool moreNotifications, + out StatusCodeCollection acknowledgeResults, + out DiagnosticInfoCollection acknowledgeDiagnosticInfos); + + /// + /// Completes the publish. + /// + /// The context. + /// The asynchronous operation. + /// + /// True if successful. False if the request has been requeued. + /// + bool CompletePublish( + OperationContext context, + AsyncPublishOperation operation); + + /// + /// Modifies an existing subscription. + /// + void ModifySubscription( + OperationContext context, + uint subscriptionId, + double requestedPublishingInterval, + uint requestedLifetimeCount, + uint requestedMaxKeepAliveCount, + uint maxNotificationsPerPublish, + byte priority, + out double revisedPublishingInterval, + out uint revisedLifetimeCount, + out uint revisedMaxKeepAliveCount); + + /// + /// Sets the publishing mode for a set of subscriptions. + /// + void SetPublishingMode( + OperationContext context, + bool publishingEnabled, + UInt32Collection subscriptionIds, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Attaches a groups of subscriptions to a different session. + /// + void TransferSubscriptions( + OperationContext context, + UInt32Collection subscriptionIds, + bool sendInitialValues, + out TransferResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Republishes a previously published notification message. + /// + NotificationMessage Republish( + OperationContext context, + uint subscriptionId, + uint retransmitSequenceNumber); + + /// + /// Updates the triggers for the monitored item. + /// + void SetTriggering( + OperationContext context, + uint subscriptionId, + uint triggeringItemId, + UInt32Collection linksToAdd, + UInt32Collection linksToRemove, + out StatusCodeCollection addResults, + out DiagnosticInfoCollection addDiagnosticInfos, + out StatusCodeCollection removeResults, + out DiagnosticInfoCollection removeDiagnosticInfos); + + /// + /// Adds monitored items to a subscription. + /// + void CreateMonitoredItems( + OperationContext context, + uint subscriptionId, + TimestampsToReturn timestampsToReturn, + MonitoredItemCreateRequestCollection itemsToCreate, + out MonitoredItemCreateResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Modifies monitored items in a subscription. + /// + void ModifyMonitoredItems( + OperationContext context, + uint subscriptionId, + TimestampsToReturn timestampsToReturn, + MonitoredItemModifyRequestCollection itemsToModify, + out MonitoredItemModifyResultCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Deletes the monitored items in a subscription. + /// + void DeleteMonitoredItems( + OperationContext context, + uint subscriptionId, + UInt32Collection monitoredItemIds, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); + + /// + /// Changes the monitoring mode for a set of items. + /// + void SetMonitoringMode( + OperationContext context, + uint subscriptionId, + MonitoringMode monitoringMode, + UInt32Collection monitoredItemIds, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos); } /// /// The delegate for functions used to receive subscription related events. diff --git a/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs index 4226d7c7c0..cfadf58673 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/IServerBase.cs @@ -11,16 +11,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; -using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; -using Opc.Ua.Bindings; namespace Opc.Ua { /// /// An interface to a service response message. /// - public interface IServerBase : IAuditEventCallback + public interface IServerBase : IAuditEventCallback, IDisposable { /// /// The message context to use with the service. @@ -47,6 +44,28 @@ public interface IServerBase : IAuditEventCallback /// /// The request. void ScheduleIncomingRequest(IEndpointIncomingRequest request); + + /// + /// Stops the server and releases all resources. + /// + void Stop(); + + /// + /// Starts the server. + /// + /// The object that stores the configurable configuration information + /// for a UA application + /// The array of Uri elements which contains base addresses. + /// Returns a host for a UA service. + ServiceHost Start(ApplicationConfiguration configuration, params Uri[] baseAddresses); + + /// + /// Starts the server (called from a dedicated host process). + /// + /// The object that stores the configurable configuration + /// information for a UA application. + /// + void Start(ApplicationConfiguration configuration); } /// @@ -77,7 +96,7 @@ public interface IEndpointIncomingRequest /// /// /// This method may block the current thread so the caller must not call in the - /// thread that calls IServerBase.ScheduleIncomingRequest(). + /// thread that calls IServerBase.ScheduleIncomingRequest(). /// This method always traps any exceptions and reports them to the client as a fault. /// void CallSynchronously(); diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index 47540dfe5f..865e0538e0 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -16,14 +16,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections.Generic; using System.Globalization; -using System.Net; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; -using Microsoft.Extensions.Logging; using Opc.Ua.Bindings; -using System.Net.Sockets; using Opc.Ua.Security.Certificates; namespace Opc.Ua @@ -31,7 +30,7 @@ namespace Opc.Ua /// /// A base class for a UA server implementation. /// - public partial class ServerBase : IServerBase, IDisposable + public partial class ServerBase : IServerBase { #region Constructors /// @@ -97,7 +96,7 @@ protected virtual void Dispose(bool disposing) /// /// The message context to use with the service. /// - /// The message context that stores context information associated with a UA + /// The message context that stores context information associated with a UA /// server that is used during message processing. /// public IServiceMessageContext MessageContext @@ -164,7 +163,7 @@ public virtual void ReportAuditOpenSecureChannelEvent( X509Certificate2 clientCertificate, Exception exception) { - // raise an audit open secure channel event. + // raise an audit open secure channel event. } /// @@ -172,7 +171,7 @@ public virtual void ReportAuditCloseSecureChannelEvent( string globalChannelId, Exception exception) { - // raise an audit close secure channel event. + // raise an audit close secure channel event. } /// @@ -230,7 +229,7 @@ public void CreateConnection(Uri url, int timeout) /// /// Starts the server. /// - /// The object that stores the configurable configuration information + /// The object that stores the configurable configuration information /// for a UA application /// The array of Uri elements which contains base addresses. /// Returns a host for a UA service. @@ -294,8 +293,8 @@ public ServiceHost Start(ApplicationConfiguration configuration, params Uri[] ba /// /// Starts the server (called from a dedicated host process). /// - /// The object that stores the configurable configuration - /// information for a UA application. + /// The object that stores the configurable configuration + /// information for a UA application. /// public void Start(ApplicationConfiguration configuration) { @@ -1396,7 +1395,7 @@ protected virtual void OnServerStarting(ApplicationConfiguration configuration) defaultInstanceCertificate = instanceCertificate; } - // preload chain + // preload chain InstanceCertificateTypesProvider.LoadCertificateChainAsync(instanceCertificate).GetAwaiter().GetResult(); } diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs index d77cdb0c5e..40baa23ac5 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs @@ -35,8 +35,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Opc.Ua.Client.Tests; +using Opc.Ua.Configuration; +using Opc.Ua.Server; using Opc.Ua.Server.Tests; using Quickstarts; using Quickstarts.ReferenceServer; @@ -57,9 +60,9 @@ public class TypeSystemClientTest : IUAClient const int kMaxReferences = 100; const int kMaxTimeout = 10000; - ServerFixture m_serverFixture; + ServerFixture m_serverFixture; ClientFixture m_clientFixture; - ReferenceServer m_server; + IReferenceServer m_server; ISession m_session; string m_uriScheme; string m_pkiRoot; @@ -101,8 +104,17 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null) // pki directory root for test runs. m_pkiRoot = Path.GetTempPath() + Path.GetRandomFileName(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start Ref server - m_serverFixture = new ServerFixture { + m_serverFixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()) { UriScheme = m_uriScheme, SecurityNone = true, AutoAccept = true, diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs index 9728b44853..b8dc6f9148 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs @@ -32,11 +32,12 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Opc.Ua.Configuration; +using Opc.Ua.Server; using Opc.Ua.Server.Tests; using Quickstarts.ReferenceServer; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -63,9 +64,9 @@ public class ClientTestFramework public bool SingleSession { get; set; } = true; public int MaxChannelCount { get; set; } = 100; public bool SupportsExternalServerUrl { get; set; } = false; - public ServerFixture ServerFixture { get; set; } + public ServerFixture ServerFixture { get; set; } public ClientFixture ClientFixture { get; set; } - public ReferenceServer ReferenceServer { get; set; } + public IReferenceServer ReferenceServer { get; set; } public EndpointDescriptionCollection Endpoints { get; set; } public ReferenceDescriptionCollection ReferenceDescriptions { get; set; } public ISession Session { get; protected set; } @@ -167,7 +168,7 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null, } ServerUrl = new Uri(url); - + } if (SingleSession) @@ -192,8 +193,20 @@ virtual public async Task CreateReferenceServerFixture( TextWriter writer) { { + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start Ref server - ServerFixture = new ServerFixture(enableTracing, disableActivityLogging) { + ServerFixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + enableTracing, + disableActivityLogging) + { UriScheme = UriScheme, SecurityNone = securityNone, AutoAccept = true, diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestServerQuotas.cs b/Tests/Opc.Ua.Client.Tests/ClientTestServerQuotas.cs index 1aada494ee..ca8bbbf011 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestServerQuotas.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestServerQuotas.cs @@ -29,19 +29,14 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; -using Moq; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; -using Opc.Ua.Bindings; using Opc.Ua.Configuration; +using Opc.Ua.Server; using Opc.Ua.Server.Tests; using Quickstarts.ReferenceServer; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -92,8 +87,20 @@ public ClientTestServerQuotas(string uriScheme = Utils.UriSchemeOpcTcp) : public override async Task CreateReferenceServerFixture(bool enableTracing, bool disableActivityLogging, bool securityNone, TextWriter writer) { { + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start Ref server - ServerFixture = new ServerFixture(enableTracing, disableActivityLogging) { + ServerFixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + enableTracing, + disableActivityLogging) + { UriScheme = UriScheme, SecurityNone = securityNone, AutoAccept = true, diff --git a/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs b/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs index 78c745178d..179d422a3c 100644 --- a/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs @@ -31,16 +31,15 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; -using NUnit.Framework.Internal; +using Opc.Ua.Configuration; using Opc.Ua.Server; using Opc.Ua.Server.Tests; -using Quickstarts.ReferenceServer; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -170,9 +169,20 @@ TextWriter writer { TextWriter localWriter = enableTracing ? writer : null; { - - // start Ref server - ServerFixtureWithLimits = new ServerFixture(enableTracing, disableActivityLogging) { + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + // start Ref server + ServerFixtureWithLimits = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + enableTracing, + disableActivityLogging) + { UriScheme = UriScheme, SecurityNone = securityNone, AutoAccept = true, diff --git a/Tests/Opc.Ua.Client.Tests/DurableSubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/DurableSubscriptionTest.cs index e924727939..18659d7631 100644 --- a/Tests/Opc.Ua.Client.Tests/DurableSubscriptionTest.cs +++ b/Tests/Opc.Ua.Client.Tests/DurableSubscriptionTest.cs @@ -28,19 +28,16 @@ * ======================================================================*/ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.InteropServices; -using System.Text; -using System.Threading; using System.Threading.Tasks; -using BenchmarkDotNet.Configs; -using CommandLine; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Opc.Ua.Configuration; +using Opc.Ua.Server; using Opc.Ua.Server.Tests; using Quickstarts.ReferenceServer; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -80,8 +77,20 @@ override public async Task CreateReferenceServerFixture( TextWriter writer) { { + + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start Ref server - ServerFixture = new ServerFixture(enableTracing, disableActivityLogging) { + ServerFixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + enableTracing, + disableActivityLogging) { UriScheme = UriScheme, SecurityNone = securityNone, AutoAccept = true, diff --git a/Tests/Opc.Ua.Client.Tests/ReferenceServerWithLimits.cs b/Tests/Opc.Ua.Client.Tests/ReferenceServerWithLimits.cs index 9d3c0b1ebf..13f72892bb 100644 --- a/Tests/Opc.Ua.Client.Tests/ReferenceServerWithLimits.cs +++ b/Tests/Opc.Ua.Client.Tests/ReferenceServerWithLimits.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; +using Opc.Ua.Configuration; using Opc.Ua.Server; using Quickstarts.ReferenceServer; @@ -50,6 +51,15 @@ namespace Opc.Ua.Client.Tests /// public class ReferenceServerWithLimits : ReferenceServer { + public ReferenceServerWithLimits( + IApplicationInstance applicationInstance, + IServerInternal serverInternal, + IMainNodeManagerFactory mainNodeManagerFactory) + : base(applicationInstance, serverInternal, mainNodeManagerFactory) + { + m_mainNodeManagerFactory = mainNodeManagerFactory; + } + public uint Test_MaxBrowseReferencesPerNode { get; set; } = 10u; private MasterNodeManager MasterNodeManagerReference { get; set; } private SessionManagerWithLimits SessionManagerForTest { get; set; } @@ -89,7 +99,7 @@ public void SetMaxNumberOfContinuationPoints(uint maxNumberOfContinuationPoints) } - protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration) + protected override IMasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration) { Utils.LogInfo(Utils.TraceMasks.StartStop, "Creating the Reference Server Node Manager."); @@ -103,7 +113,12 @@ protected override MasterNodeManager CreateMasterNodeManager(IServerInternal ser nodeManagers.Add(nodeManagerFactory.Create(server, configuration)); } //this.MasterNodeManagerReference = new MasterNodeManager(server, configuration, null, nodeManagers.ToArray()); - this.MasterNodeManagerReference = new MasterNodeManagerWithLimits(server, configuration, null, nodeManagers.ToArray()); + this.MasterNodeManagerReference = new MasterNodeManagerWithLimits( + server, + configuration, + m_mainNodeManagerFactory, + null, + nodeManagers.ToArray()); // create master node manager. return this.MasterNodeManagerReference; } @@ -113,6 +128,8 @@ protected override SessionManager CreateSessionManager(IServerInternal server, A this.SessionManagerForTest = new SessionManagerWithLimits(server, configuration); return this.SessionManagerForTest; } + + private readonly IMainNodeManagerFactory m_mainNodeManagerFactory; } /// @@ -228,7 +245,13 @@ protected override Opc.Ua.Server.Session CreateSession( /// public class MasterNodeManagerWithLimits : MasterNodeManager { - public MasterNodeManagerWithLimits(IServerInternal server, ApplicationConfiguration configuration, string dynamicNamespaceUri, params INodeManager[] additionalManagers) : base(server, configuration, dynamicNamespaceUri, additionalManagers) + public MasterNodeManagerWithLimits( + IServerInternal server, + ApplicationConfiguration configuration, + IMainNodeManagerFactory mainNodeManagerFactory, + string dynamicNamespaceUri, + params INodeManager[] additionalManagers) + : base(server, configuration, mainNodeManagerFactory, dynamicNamespaceUri, additionalManagers) { } @@ -331,7 +354,7 @@ public override void Browse( continuationPointsAssigned++; } - // check for error. + // check for error. result.StatusCode = error.StatusCode; if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) @@ -351,8 +374,5 @@ public override void Browse( // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } - - } - } diff --git a/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs b/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs index e0ca3ec332..1bd41901ed 100644 --- a/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ReverseConnectTest.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -33,7 +33,10 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Opc.Ua.Configuration; +using Opc.Ua.Server; using Opc.Ua.Server.Tests; using Quickstarts.ReferenceServer; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -69,11 +72,21 @@ public async Task OneTimeSetUpAsync() Assert.Ignore("Reverse connect fails on mac OS."); } - // pki directory root for test runs. + // pki directory root for test runs. PkiRoot = Path.GetTempPath() + Path.GetRandomFileName(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start ref server with reverse connect - ServerFixture = new ServerFixture { + ServerFixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()) + { AutoAccept = true, SecurityNone = true, ReverseConnectTimeout = MaxTimeout, diff --git a/Tests/Opc.Ua.Gds.Tests/Common.cs b/Tests/Opc.Ua.Gds.Tests/Common.cs index 5ef1938473..3d21724461 100644 --- a/Tests/Opc.Ua.Gds.Tests/Common.cs +++ b/Tests/Opc.Ua.Gds.Tests/Common.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -32,10 +32,11 @@ using System.Globalization; using System.IO; using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Opc.Ua.Configuration; using Opc.Ua.Gds.Client; +using Opc.Ua.Server; using Opc.Ua.Server.Tests; using Opc.Ua.Test; @@ -364,7 +365,17 @@ public static async Task StartGDS(bool clean, string { try { - server = new GlobalDiscoveryTestServer(true); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + server = new GlobalDiscoveryTestServer( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + true); await server.StartServer(clean, testPort, storeType).ConfigureAwait(false); } catch (ServiceResultException sre) diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index 14c298e84a..1715af79c0 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -34,6 +34,7 @@ using Opc.Ua.Configuration; using Opc.Ua.Gds.Server; using Opc.Ua.Gds.Server.Database.Linq; +using Opc.Ua.Server; using Opc.Ua.Server.UserDatabase; namespace Opc.Ua.Gds.Tests @@ -41,12 +42,20 @@ namespace Opc.Ua.Gds.Tests public class GlobalDiscoveryTestServer { public GlobalDiscoverySampleServer Server => m_server; - public ApplicationInstance Application { get; private set; } + public IApplicationInstance Application { get; private set; } public ApplicationConfiguration Config { get; private set; } public int BasePort { get; private set; } - public GlobalDiscoveryTestServer(bool autoAccept) + public GlobalDiscoveryTestServer( + IApplicationInstance applicationInstance, + IServerInternal serverInternal, + IMainNodeManagerFactory mainNodeManagerFactory, + bool autoAccept) { + m_applicationInstance = applicationInstance; + Application = m_applicationInstance; + m_serverInternal = serverInternal; + m_mainNodeManagerFactory = mainNodeManagerFactory; s_autoAccept = autoAccept; } @@ -63,14 +72,13 @@ public async Task StartServer(bool clean, int basePort = -1, string storeType = } configSectionName = "Opc.Ua.GlobalDiscoveryTestServerX509Stores"; } - Application = new ApplicationInstance { - ApplicationName = "Global Discovery Server", - ApplicationType = ApplicationType.Server, - ConfigSectionName = configSectionName - }; + + m_applicationInstance.ApplicationName = "Global Discovery Server"; + m_applicationInstance.ApplicationType = ApplicationType.Server; + m_applicationInstance.ConfigSectionName = configSectionName; BasePort = basePort; - Config = await Load(Application, basePort).ConfigureAwait(false); + Config = await Load(m_applicationInstance, basePort).ConfigureAwait(false); if (clean) { @@ -135,14 +143,16 @@ public async Task StartServer(bool clean, int basePort = -1, string storeType = // start the server. m_server = new GlobalDiscoverySampleServer( + m_applicationInstance, + m_serverInternal, + m_mainNodeManagerFactory, applicationsDatabase, applicationsDatabase, new CertificateGroup(), usersDatabase); await Application.Start(m_server).ConfigureAwait(false); - ServerState serverState = Server.GetStatus().State; - if (serverState != ServerState.Running) + if (Server.CurrentState != ServerState.Running) { throw new ServiceResultException("Server failed to start"); } @@ -200,7 +210,7 @@ private static void CertificateValidator_CertificateValidation(CertificateValida } } - private static async Task Load(ApplicationInstance application, int basePort) + private static async Task Load(IApplicationInstance application, int basePort) { #if !USE_FILE_CONFIG // load the application configuration. @@ -272,6 +282,9 @@ private static async Task Load(ApplicationInstance app return config; } + private readonly IApplicationInstance m_applicationInstance; + private readonly IMainNodeManagerFactory m_mainNodeManagerFactory; + private readonly IServerInternal m_serverInternal; private GlobalDiscoverySampleServer m_server; private static bool s_autoAccept = false; } diff --git a/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs b/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs index 6371f779af..a670ff58ee 100644 --- a/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs +++ b/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs @@ -2,7 +2,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Opc.Ua.Configuration; namespace Opc.Ua.Server.Tests { @@ -21,7 +23,16 @@ public class CustomNodeManagerTests [Test] public async Task TestComponentCacheAsync() { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -56,7 +67,16 @@ public async Task TestComponentCacheAsync() [Test] public async Task TestPredefinedNodes() { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -121,15 +141,15 @@ private static async Task UsePredefinedNodesAsync(TestableCustomNodeManger2 node { nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); - NodeState nodeState = nodeManager.Find(nodeId); + nodeManager.Find(nodeId); - NodeHandle handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; + _ = nodeManager.GetManagerHandle(nodeId) as NodeHandle; nodeManager.DeleteNode(nodeManager.SystemContext, nodeId); - nodeState = nodeManager.Find(nodeId); + nodeManager.Find(nodeId); - handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; + _ = nodeManager.GetManagerHandle(nodeId) as NodeHandle; nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); await Task.CompletedTask.ConfigureAwait(false); @@ -168,23 +188,23 @@ private static async Task UseComponentCacheAsync(TestableCustomNodeManger2 nodeM var cancellationTokenSource = new CancellationTokenSource(); Exception error = null; int tasksCompletedCount = 0; - var result = Parallel.For(0, iterations, new ParallelOptions(), - async index => { - try - { - await task().ConfigureAwait(false); - } - catch (Exception ex) - { - error = ex; - cancellationTokenSource.Cancel(); - } - finally - { - tasksCompletedCount++; - } - - }); + Parallel.For(0, iterations, new ParallelOptions(), + async index => { + try + { + await task().ConfigureAwait(false); + } + catch (Exception ex) + { + error = ex; + cancellationTokenSource.Cancel(); + } + finally + { + tasksCompletedCount++; + } + + }); int spinWaitCount = 0; int maxSpinWaitCount = 100; diff --git a/Tests/Opc.Ua.Server.Tests/MasterNodeManagerTests.cs b/Tests/Opc.Ua.Server.Tests/MasterNodeManagerTests.cs index 0ec01d4c82..1b79d0034d 100644 --- a/Tests/Opc.Ua.Server.Tests/MasterNodeManagerTests.cs +++ b/Tests/Opc.Ua.Server.Tests/MasterNodeManagerTests.cs @@ -27,10 +27,13 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; +using Opc.Ua.Configuration; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Opc.Ua.Server.Tests @@ -51,7 +54,16 @@ public class MasterNodeManagerTests [Test] public async Task RegisterNamespaceManagerNewNamespace() { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -62,17 +74,18 @@ public async Task RegisterNamespaceManagerNewNamespace() nodeManager.Setup(x => x.NamespaceUris).Returns(new List()); //-- Act - var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + IStandardServer server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); var sut = new MasterNodeManager( server.CurrentInstance, fixture.Config, + serviceProvider.GetRequiredService(), null, nodeManager.Object); sut.RegisterNamespaceManager(ns, nodeManager.Object); //-- Assert Assert.Contains(ns, server.CurrentInstance.NamespaceUris.ToArray()); - var registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; + INodeManager[] registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; Assert.AreEqual(1, registeredManagers.Length); Assert.Contains(nodeManager.Object, registeredManagers); } @@ -89,7 +102,16 @@ public async Task RegisterNamespaceManagerNewNamespace() [Test] public async Task RegisterNamespaceManagerExistingNamespace() { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -104,17 +126,18 @@ public async Task RegisterNamespaceManagerExistingNamespace() newNodeManager.Setup(x => x.NamespaceUris).Returns(namespaceUris); //-- Act - var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + IStandardServer server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); var sut = new MasterNodeManager( server.CurrentInstance, fixture.Config, + serviceProvider.GetRequiredService(), null, originalNodeManager.Object); sut.RegisterNamespaceManager(ns, newNodeManager.Object); //-- Assert Assert.Contains(ns, server.CurrentInstance.NamespaceUris.ToArray()); - var registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; + INodeManager[] registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; Assert.AreEqual(2, registeredManagers.Length); Assert.Contains(originalNodeManager.Object, registeredManagers); Assert.Contains(newNodeManager.Object, registeredManagers); @@ -135,7 +158,16 @@ public async Task RegisterNamespaceManagerExistingNamespace() [TestCase(3, 2)] public async Task UnregisterNamespaceManagerInCollection(int totalManagers, int indexToRemove) { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -152,23 +184,24 @@ public async Task UnregisterNamespaceManagerInCollection(int totalManagers, int additionalManagers[ii] = nodeManager.Object; } - var nodeManagerToRemove = additionalManagers[indexToRemove]; + INodeManager nodeManagerToRemove = additionalManagers[indexToRemove]; //-- Act - var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + IStandardServer server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); var sut = new MasterNodeManager( server.CurrentInstance, fixture.Config, + serviceProvider.GetRequiredService(), null, additionalManagers); - var result = sut.UnregisterNamespaceManager(ns, nodeManagerToRemove); + bool result = sut.UnregisterNamespaceManager(ns, nodeManagerToRemove); //-- Assert Assert.IsTrue(result); Assert.Contains(ns, server.CurrentInstance.NamespaceUris.ToArray()); - var registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; + INodeManager[] registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; Assert.AreEqual(totalManagers - 1, registeredManagers.Length); - Assert.That(registeredManagers, Has.No.Member(nodeManagerToRemove)); + NUnit.Framework.Assert.That(registeredManagers, Has.No.Member(nodeManagerToRemove)); } finally { @@ -183,7 +216,16 @@ public async Task UnregisterNamespaceManagerInCollection(int totalManagers, int [Test] public async Task UnregisterNamespaceManagerNotInCollection() { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -201,20 +243,21 @@ public async Task UnregisterNamespaceManagerNotInCollection() thirdNodeManager.Setup(x => x.NamespaceUris).Returns(namespaceUris); //-- Act - var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + IStandardServer server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); var sut = new MasterNodeManager( server.CurrentInstance, fixture.Config, + serviceProvider.GetRequiredService(), null, firstNodeManager.Object, // Do not add the secondNodeManager to additionalManagers thirdNodeManager.Object); - var result = sut.UnregisterNamespaceManager(ns, secondNodeManager.Object); + bool result = sut.UnregisterNamespaceManager(ns, secondNodeManager.Object); //-- Assert Assert.IsFalse(result); Assert.Contains(ns, server.CurrentInstance.NamespaceUris.ToArray()); - var registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; + INodeManager[] registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(ns)]; Assert.AreEqual(2, registeredManagers.Length); Assert.Contains(firstNodeManager.Object, registeredManagers); Assert.Contains(thirdNodeManager.Object, registeredManagers); @@ -233,7 +276,16 @@ public async Task UnregisterNamespaceManagerNotInCollection() [Test] public async Task UnregisterNamespaceManagerUnknownNamespace() { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); try { @@ -248,20 +300,21 @@ public async Task UnregisterNamespaceManagerUnknownNamespace() newNodeManager.Setup(x => x.NamespaceUris).Returns(new List { originalNs, newNs }); //-- Act - var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + IStandardServer server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); var sut = new MasterNodeManager( server.CurrentInstance, fixture.Config, + serviceProvider.GetRequiredService(), null, originalNodeManager.Object); - var result = sut.UnregisterNamespaceManager(newNs, newNodeManager.Object); + bool result = sut.UnregisterNamespaceManager(newNs, newNodeManager.Object); //-- Assert Assert.IsFalse(result); - Assert.That(server.CurrentInstance.NamespaceUris.ToArray(), Has.No.Member(newNs)); + NUnit.Framework.Assert.That(server.CurrentInstance.NamespaceUris.ToArray(), Has.No.Member(newNs)); Assert.Contains(originalNs, server.CurrentInstance.NamespaceUris.ToArray()); - var registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(originalNs)]; + INodeManager[] registeredManagers = sut.NamespaceManagers[server.CurrentInstance.NamespaceUris.GetIndex(originalNs)]; Assert.AreEqual(1, registeredManagers.Length); Assert.Contains(originalNodeManager.Object, registeredManagers); } diff --git a/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs b/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs index e19375401e..f750f16053 100644 --- a/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs +++ b/Tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs @@ -32,7 +32,9 @@ using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Opc.Ua.Configuration; using Opc.Ua.Test; using Quickstarts.ReferenceServer; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -53,8 +55,8 @@ public class ReferenceServerTests const uint kTimeoutHint = 10000; const uint kQueueSize = 5; - ServerFixture m_fixture; - ReferenceServer m_server; + ServerFixture m_fixture; + IReferenceServer m_server; RequestHeader m_requestHeader; OperationLimits m_operationLimits; ReferenceDescriptionCollection m_referenceDescriptions; @@ -70,8 +72,17 @@ public class ReferenceServerTests [OneTimeSetUp] public async Task OneTimeSetUp() { + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start Ref server - m_fixture = new ServerFixture() { + m_fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()) { AllNodeManagers = true, OperationLimits = true, DurableSubscriptionsEnabled = false, @@ -125,8 +136,20 @@ public void TearDown() [GlobalSetup] public void GlobalSetup() { + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddScoped(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // start Ref server - m_fixture = new ServerFixture() { AllNodeManagers = true }; + m_fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()) + { + AllNodeManagers = true + }; m_server = m_fixture.StartAsync(null).GetAwaiter().GetResult(); m_requestHeader = m_server.CreateAndActivateSession("Bench"); } @@ -367,11 +390,10 @@ public void TransferSubscriptionSessionClosed(bool sendInitialData, bool useSecu try { RequestHeader transferRequestHeader = m_server.CreateAndActivateSession("ClosedSession", useSecurity); - var transferSecurityContext = SecureChannelContext.Current; var namespaceUris = m_server.CurrentInstance.NamespaceUris; NodeId[] testSet = CommonTestWorkers.NodeIdTestSetStatic.Select(n => ExpandedNodeId.ToNodeId(n, namespaceUris)).ToArray(); transferRequestHeader.Timestamp = DateTime.UtcNow; - var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, transferRequestHeader, testSet, kQueueSize, -1); + var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, transferRequestHeader, testSet, kQueueSize); transferRequestHeader.Timestamp = DateTime.UtcNow; m_server.CloseSession(transferRequestHeader, false); @@ -383,7 +405,7 @@ public void TransferSubscriptionSessionClosed(bool sendInitialData, bool useSecu if (useSecurity) { // subscription was deleted, expect 'BadNoSubscription' - var sre = Assert.Throws(() => { + var sre = NUnit.Framework.Assert.Throws(() => { m_requestHeader.Timestamp = DateTime.UtcNow; CommonTestWorkers.VerifySubscriptionTransferred(serverTestServices, m_requestHeader, subscriptionIds, true); }); @@ -412,7 +434,7 @@ public void TransferSubscription(bool sendInitialData, bool useSecurity) { var namespaceUris = m_server.CurrentInstance.NamespaceUris; NodeId[] testSet = CommonTestWorkers.NodeIdTestSetStatic.Select(n => ExpandedNodeId.ToNodeId(n, namespaceUris)).ToArray(); - var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, m_requestHeader, testSet, kQueueSize, -1); + var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, m_requestHeader, testSet, kQueueSize); RequestHeader transferRequestHeader = m_server.CreateAndActivateSession("TransferSession", useSecurity); var transferSecurityContext = SecureChannelContext.Current; @@ -472,15 +494,15 @@ public void ResendData(bool updateValues, uint queueSize) Thread.Sleep(1000); - // Make sure publish queue becomes empty by consuming it + // Make sure publish queue becomes empty by consuming it Assert.AreEqual(1, subscriptionIds.Count); // Issue a Publish request m_requestHeader.Timestamp = DateTime.UtcNow; var acknowledgements = new SubscriptionAcknowledgementCollection(); var response = serverTestServices.Publish(m_requestHeader, acknowledgements, - out uint publishedId, out UInt32Collection availableSequenceNumbers, - out bool moreNotifications, out NotificationMessage notificationMessage, + out uint publishedId, out UInt32Collection _, + out bool _, out NotificationMessage notificationMessage, out StatusCodeCollection _, out DiagnosticInfoCollection diagnosticInfos); Assert.AreEqual((StatusCode)StatusCodes.Good, response.ServiceResult); @@ -495,8 +517,8 @@ public void ResendData(bool updateValues, uint queueSize) { m_requestHeader.Timestamp = DateTime.UtcNow; response = serverTestServices.Publish(m_requestHeader, acknowledgements, - out publishedId, out availableSequenceNumbers, - out moreNotifications, out notificationMessage, + out publishedId, out UInt32Collection _, + out bool _, out notificationMessage, out StatusCodeCollection _, out diagnosticInfos); Assert.AreEqual((StatusCode)StatusCodes.Good, response.ServiceResult); @@ -525,8 +547,8 @@ public void ResendData(bool updateValues, uint queueSize) // Still nothing to publish since previous ResendData call did not execute m_requestHeader.Timestamp = DateTime.UtcNow; response = serverTestServices.Publish(m_requestHeader, acknowledgements, - out publishedId, out availableSequenceNumbers, - out moreNotifications, out notificationMessage, + out publishedId, out UInt32Collection _, + out bool _, out notificationMessage, out StatusCodeCollection _, out diagnosticInfos); Assert.AreEqual((StatusCode)StatusCodes.Good, response.ServiceResult); @@ -552,8 +574,8 @@ public void ResendData(bool updateValues, uint queueSize) // Data should be available for publishing now m_requestHeader.Timestamp = DateTime.UtcNow; response = serverTestServices.Publish(m_requestHeader, acknowledgements, - out publishedId, out availableSequenceNumbers, - out moreNotifications, out notificationMessage, + out publishedId, out UInt32Collection _, + out bool _, out notificationMessage, out StatusCodeCollection _, out diagnosticInfos); Assert.AreEqual((StatusCode)StatusCodes.Good, response.ServiceResult); @@ -562,8 +584,8 @@ public void ResendData(bool updateValues, uint queueSize) Assert.AreEqual(subscriptionIds[0], publishedId); Assert.AreEqual(1, notificationMessage.NotificationData.Count); var items = notificationMessage.NotificationData.FirstOrDefault(); - Assert.IsTrue(items.Body is Opc.Ua.DataChangeNotification); - var monitoredItemsCollection = ((Opc.Ua.DataChangeNotification)items.Body).MonitoredItems; + Assert.IsTrue(items.Body is DataChangeNotification); + var monitoredItemsCollection = ((DataChangeNotification)items.Body).MonitoredItems; Assert.AreEqual(testSet.Length, monitoredItemsCollection.Count); Thread.Sleep(1000); @@ -573,8 +595,8 @@ public void ResendData(bool updateValues, uint queueSize) // remaining queue Data should be sent in this publish m_requestHeader.Timestamp = DateTime.UtcNow; response = serverTestServices.Publish(m_requestHeader, acknowledgements, - out publishedId, out availableSequenceNumbers, - out moreNotifications, out notificationMessage, + out publishedId, out UInt32Collection _, + out bool _, out notificationMessage, out StatusCodeCollection _, out diagnosticInfos); Assert.AreEqual((StatusCode)StatusCodes.Good, response.ServiceResult); @@ -583,8 +605,8 @@ public void ResendData(bool updateValues, uint queueSize) Assert.AreEqual(subscriptionIds[0], publishedId); Assert.AreEqual(1, notificationMessage.NotificationData.Count); items = notificationMessage.NotificationData.FirstOrDefault(); - Assert.IsTrue(items.Body is Opc.Ua.DataChangeNotification); - monitoredItemsCollection = ((Opc.Ua.DataChangeNotification)items.Body).MonitoredItems; + Assert.IsTrue(items.Body is DataChangeNotification); + monitoredItemsCollection = ((DataChangeNotification)items.Body).MonitoredItems; Assert.AreEqual(testSet.Length * (queueSize - 1), monitoredItemsCollection.Count, testSet.Length); } @@ -594,8 +616,8 @@ public void ResendData(bool updateValues, uint queueSize) // Nothing to publish since previous ResendData call did not execute m_requestHeader.Timestamp = DateTime.UtcNow; response = serverTestServices.Publish(m_requestHeader, acknowledgements, - out publishedId, out availableSequenceNumbers, - out moreNotifications, out notificationMessage, + out publishedId, out UInt32Collection _, + out bool _, out notificationMessage, out StatusCodeCollection _, out diagnosticInfos); Assert.AreEqual((StatusCode)StatusCodes.Good, response.ServiceResult); diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index 2aacb06fb7..1bb21735ed 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -30,7 +30,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Opc.Ua.Configuration; @@ -41,12 +40,12 @@ namespace Opc.Ua.Server.Tests /// Server fixture for testing. /// /// A server class T used for testing. - public class ServerFixture where T : ServerBase, new() + public class ServerFixture where T : IServerBase { private NUnitTestLogger m_traceLogger; - public ApplicationInstance Application { get; private set; } + public IApplicationInstance Application { get; private set; } public ApplicationConfiguration Config { get; private set; } - public T Server { get; private set; } + public T Server => m_server; public bool LogConsole { get; set; } public bool AutoAccept { get; set; } public bool OperationLimits { get; set; } @@ -61,8 +60,14 @@ namespace Opc.Ua.Server.Tests public bool DurableSubscriptionsEnabled { get; set; } = false; public ActivityListener ActivityListener { get; private set; } - public ServerFixture(bool useTracing, bool disableActivityLogging) + public ServerFixture( + T server, + IApplicationInstance applicationInstance, + bool useTracing, + bool disableActivityLogging) + : this(server, applicationInstance) { + Application = applicationInstance; UseTracing = useTracing; if (UseTracing) { @@ -70,17 +75,16 @@ public ServerFixture(bool useTracing, bool disableActivityLogging) } } - public ServerFixture() + public ServerFixture(T server, IApplicationInstance applicationInstance) { - + Application = applicationInstance; + m_server = server; } public async Task LoadConfiguration(string pkiRoot = null) { - Application = new ApplicationInstance { - ApplicationName = typeof(T).Name, - ApplicationType = ApplicationType.Server - }; + Application.ApplicationName = typeof(T).Name; + Application.ApplicationType = ApplicationType.Server; // create the application configuration. Use temp path for cert stores. pkiRoot = pkiRoot ?? Path.GetTempPath() + Path.GetRandomFileName(); @@ -92,7 +96,7 @@ public async Task LoadConfiguration(string pkiRoot = null) .SetMaxArrayLength(1024 * 1024) .SetChannelLifetime(30000) .AsServer( - new string[] { + new[] { endpointUrl }); @@ -148,7 +152,7 @@ public async Task LoadConfiguration(string pkiRoot = null) RejectTimeout = ReverseConnectTimeout / 4 }); } - + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( "CN=" + typeof(T).Name + ", C=US, S=Arizona, O=OPC Foundation, DC=localhost", CertificateStoreType.Directory, @@ -187,7 +191,7 @@ public async Task StartAsync(TextWriter writer, string pkiRoot, int port = 0) int testPort = port; int serverStartRetries = 1; - if (Application == null) + if (Application.ApplicationConfiguration == null) { await LoadConfiguration(pkiRoot).ConfigureAwait(false); } @@ -244,13 +248,11 @@ private async Task InternalStartServerAsync(TextWriter writer, int port) } // start the server. - T server = new T(); - if (AllNodeManagers && server is StandardServer standardServer) + if (AllNodeManagers && m_server is StandardServer standardServer) { Quickstarts.Servers.Utils.AddDefaultNodeManagers(standardServer); } - await Application.Start(server).ConfigureAwait(false); - Server = server; + await Application.Start(m_server).ConfigureAwait(false); Port = port; } @@ -283,7 +285,7 @@ public void StartActivityListenerInternal(bool disableActivityLogging = false) // Create an instance of ActivityListener without logging ActivityListener = new ActivityListener() { ShouldListenTo = (source) => (source.Name == EndpointBase.ActivitySourceName), - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = _ => { }, ActivityStopped = _ => { } }; @@ -293,7 +295,7 @@ public void StartActivityListenerInternal(bool disableActivityLogging = false) // Create an instance of ActivityListener and configure its properties with logging ActivityListener = new ActivityListener() { ShouldListenTo = (source) => (source.Name == EndpointBase.ActivitySourceName), - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = activity => Utils.LogInfo("Server Started: {0,-15} - TraceId: {1,-32} SpanId: {2,-16} ParentId: {3,-32}", activity.OperationName, activity.TraceId, activity.SpanId, activity.ParentId), ActivityStopped = activity => Utils.LogInfo("Server Stopped: {0,-15} - TraceId: {1,-32} SpanId: {2,-16} ParentId: {3,-32} Duration: {4}", @@ -309,12 +311,13 @@ public void StartActivityListenerInternal(bool disableActivityLogging = false) /// public Task StopAsync() { - Server?.Stop(); - Server?.Dispose(); - Server = null; + Server.Stop(); + Server.Dispose(); ActivityListener?.Dispose(); ActivityListener = null; return Task.Delay(100); } + + private readonly T m_server; } } diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs b/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs index 395855dc8d..2841c15aa1 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -59,7 +59,7 @@ public static class ServerFixtureUtils /// A session name. /// The request header for the session. public static RequestHeader CreateAndActivateSession( - this SessionServerBase server, + this ISessionServer server, string sessionName, bool useSecurity = false, UserIdentityToken identityToken = null, @@ -125,7 +125,7 @@ public static RequestHeader CreateAndActivateSession( /// /// The server where the session is active. /// The request header of the session. - public static void CloseSession(this SessionServerBase server, RequestHeader requestHeader) + public static void CloseSession(this ISessionServer server, RequestHeader requestHeader) { // close session var response = server.CloseSession(requestHeader, true); diff --git a/Tests/Opc.Ua.Server.Tests/ServerStartupTests.cs b/Tests/Opc.Ua.Server.Tests/ServerStartupTests.cs index 57a5df6ada..8dc733ec43 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerStartupTests.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerStartupTests.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2020 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 @@ -11,7 +11,7 @@ * 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, @@ -27,8 +27,11 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using System; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Opc.Ua.Configuration; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Opc.Ua.Server.Tests @@ -56,7 +59,16 @@ public async Task StartServerAsync( string uriScheme ) { - var fixture = new ServerFixture(); + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); Assert.NotNull(fixture); fixture.UriScheme = uriScheme; var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); @@ -65,6 +77,55 @@ string uriScheme await Task.Delay(1000).ConfigureAwait(false); await fixture.StopAsync().ConfigureAwait(false); } + + /// + /// Start a server fixture, stop it and restart it. + /// + [Test] + public async Task StartAndRestartServerAsync() + { + IServiceCollection services = new ServiceCollection() + .AddConfigurationServices() + .AddServerServices() + .AddSingleton(); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + var fixture = new ServerFixture( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); + Assert.NotNull(fixture); + fixture.UriScheme = Utils.UriSchemeOpcTcp; + + IStandardServer server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + fixture.SetTraceOutput(TestContext.Out); + Assert.NotNull(server); + + Assert.AreEqual(server.CurrentInstance.Initialized, true); + Assert.AreEqual(server.CurrentState, ServerState.Running); + + await Task.Delay(1000).ConfigureAwait(false); + await fixture.StopAsync().ConfigureAwait(false); + + uint? exceptionStatusCode1 = NUnit.Framework.Assert.Throws(delegate { bool result = server.CurrentInstance.Initialized; })?.StatusCode; + Assert.AreEqual(exceptionStatusCode1, StatusCodes.BadServerHalted); + + await Task.Delay(1000).ConfigureAwait(false); + + IStandardServer server2 = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + Assert.NotNull(server2); + + Assert.AreEqual(server.CurrentInstance.Initialized, true); + Assert.AreEqual(server.CurrentState, ServerState.Running); + + await Task.Delay(1000).ConfigureAwait(false); + await fixture.StopAsync().ConfigureAwait(false); + + await Task.Delay(1000).ConfigureAwait(false); + + uint? exceptionStatusCode2 = NUnit.Framework.Assert.Throws(delegate { bool result = server.CurrentInstance.Initialized; })?.StatusCode; + Assert.AreEqual(exceptionStatusCode2, StatusCodes.BadServerHalted); + } #endregion } }