/* ======================================================================== * Copyright (c) 2005-2025 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.Diagnostics.CodeAnalysis; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Opc.Ua.Identity; namespace Opc.Ua.Server { /// /// A class that stores the globally accessible state of a server instance. /// /// /// /// 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. /// 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, AliasNames.IAliasNameStoreRegistryProvider, Historian.IHistorianRegistryProvider, ITransportListenerRegistryProvider, ITimeProviderProvider { /// /// Initializes the datastore with the server configuration. /// /// The server description. /// The configuration. /// The message context. public ServerInternalData( ServerProperties serverDescription, ApplicationConfiguration configuration, IServiceMessageContext messageContext) : this(serverDescription, configuration, messageContext, null) { } /// /// Initializes the datastore with the server configuration. /// /// The server description. /// The configuration. /// The message context. /// The time provider used for all /// time / duration calculations performed by the server, or /// null to use . public ServerInternalData( ServerProperties serverDescription, ApplicationConfiguration configuration, IServiceMessageContext messageContext, TimeProvider? timeProvider) { m_timeProvider = timeProvider ?? TimeProvider.System; m_serverDescription = serverDescription; m_configuration = configuration; MessageContext = messageContext; m_endpointAddresses = []; foreach (string baseAddresses in m_configuration.ServerConfiguration!.BaseAddresses) { Uri? url = Utils.ParseUri(baseAddresses); if (url != null) { m_endpointAddresses.Add(url); } } NamespaceUris = MessageContext.NamespaceUris; Factory = MessageContext.Factory; ServerUris = new StringTable(); TypeTree = new TypeTable(NamespaceUris); HistorianRegistry = new Historian.HistorianProviderRegistry(NamespaceUris); // add the server uri to the server table. ServerUris.Append(m_configuration.ApplicationUri!); // create the default system context. DefaultSystemContext = new ServerSystemContext(this); } /// /// Frees any unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// An overrideable version of the Dispose. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) { m_roleStateBinding?.Dispose(); m_roleStateBinding = null; (RoleManager as IDisposable)?.Dispose(); ResourceManager?.Dispose(); RequestManager?.Dispose(); AggregateManager?.Dispose(); ModellingRulesManager?.Dispose(); (NodeManager as IDisposable)?.Dispose(); SessionManager?.Dispose(); SubscriptionManager?.Dispose(); MonitoredItemQueueFactory?.Dispose(); (AliasNameStoreRegistry as IDisposable)?.Dispose(); (HistorianRegistry as IDisposable)?.Dispose(); } } /// /// The server-wide registry of OPC UA Part 17 alias-name stores. /// Surfaces through the optional /// /// interface so consumers can discover it without any change to /// ; never null. /// public AliasNames.IAliasNameStoreRegistry AliasNameStoreRegistry { get; } = new AliasNames.AliasNameStoreRegistry(); /// /// A snapshot of the transport listeners currently bound to the /// server. Populated by once /// listeners are opened so consumers such as /// can fan-out post- /// response channel cuts per OPC UA Part 12 §7.10.9. /// /// /// Returns an empty list before listeners are bound or after the /// server has shut them down. Surfaced through the optional /// interface so /// external/mocked implementations /// remain unaffected. /// public IReadOnlyList TransportListeners => m_transportListeners ?? (IReadOnlyList)Array.Empty(); /// /// Called by after listeners are /// bound (or torn down) to make them visible to downstream /// consumers via . /// /// /// The current listener registry. Passing null resets to /// an empty snapshot. /// public void SetTransportListenerRegistry(IReadOnlyList? listeners) { m_transportListeners = listeners; } /// /// The server-wide registry of Part 11 historian providers. /// Surfaces through the optional /// interface so /// consumers can discover it without any change to /// ; never null. /// public Historian.IHistorianProviderRegistry HistorianRegistry { get; } /// /// The time provider used for all time / duration calculations /// performed by the server. Surfaces through the optional /// interface so consumers can /// discover it without any change to ; /// never null. /// public TimeProvider TimeProvider => m_timeProvider; /// /// The session manager to use with the server. /// /// The session manager. public ISessionManager SessionManager { get; private set; } = null!; /// public IRoleManager RoleManager { get; private set; } = new RoleManager(); /// public IServerIdentityRegistry IdentityRegistry { get; private set; } = new ServerIdentityRegistry(); /// public UserManagement.IUserManagement? UserManagement { get; private set; } /// /// The subscription manager to use with the server. /// /// The subscription manager. public ISubscriptionManager SubscriptionManager { get; private set; } = null!; /// /// Stores the MasterNodeManager, the DiagnosticsNodeManager and the CoreNodeManager /// /// The node manager. [MemberNotNull( nameof(NodeManager), nameof(DiagnosticsNodeManager), nameof(ConfigurationNodeManager), nameof(CoreNodeManager))] public void SetNodeManager(IMasterNodeManager nodeManager) { NodeManager = nodeManager; DiagnosticsNodeManager = nodeManager.DiagnosticsNodeManager!; ConfigurationNodeManager = nodeManager.ConfigurationNodeManager!; CoreNodeManager = nodeManager.CoreNodeManager!; } /// /// Stores the MainNodeManagerFactory /// /// The main node manager factory. [MemberNotNull(nameof(MainNodeManagerFactory))] public void SetMainNodeManagerFactory(IMainNodeManagerFactory mainNodeManagerFactory) { MainNodeManagerFactory = mainNodeManagerFactory; } /// /// Sets the EventManager, the ResourceManager, the RequestManager and the AggregateManager. /// /// The event manager. /// The resource manager. /// The request manager. /// The cancellation token. [MemberNotNull( nameof(EventManager), nameof(ResourceManager), nameof(RequestManager), nameof(ServerObject), nameof(NonThreadSafeStatus), nameof(ServerDiagnostics))] public async ValueTask CreateServerObjectAsync( EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager, CancellationToken cancellationToken = default) { EventManager = eventManager; ResourceManager = resourceManager; RequestManager = requestManager; // create the server object. await CreateServerObjectAsync(cancellationToken).ConfigureAwait(false); } /// /// Stores the SessionManager, the SubscriptionManager in the datastore. /// /// The session manager. /// The subscription manager. [MemberNotNull(nameof(SessionManager), nameof(SubscriptionManager))] public void SetSessionManager( ISessionManager sessionManager, ISubscriptionManager subscriptionManager) { SessionManager = sessionManager; SubscriptionManager = subscriptionManager; } /// /// Stores the MonitoredItemQueueFactory in the datastore. /// /// The MonitoredItemQueueFactory. [MemberNotNull(nameof(MonitoredItemQueueFactory))] public void SetMonitoredItemQueueFactory( IMonitoredItemQueueFactory monitoredItemQueueFactory) { MonitoredItemQueueFactory = monitoredItemQueueFactory; } /// /// Stores the Subscriptionstore in the datastore. /// /// The subscriptionstore. [MemberNotNull(nameof(SubscriptionStore))] public void SetSubscriptionStore(ISubscriptionStore subscriptionStore) { SubscriptionStore = subscriptionStore; } /// public void SetRoleManager(IRoleManager roleManager) { RoleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); } /// public void SetIdentityRegistry(IServerIdentityRegistry registry) { IdentityRegistry = registry ?? throw new ArgumentNullException(nameof(registry)); } /// public void SetUserManagement(UserManagement.IUserManagement userManagement) { UserManagement = userManagement ?? throw new ArgumentNullException(nameof(userManagement)); } /// /// Stores the AggregateManager in the datastore. /// /// The AggregateManager. [MemberNotNull(nameof(AggregateManager))] public void SetAggregateManager(AggregateManager aggregateManager) { AggregateManager = aggregateManager; } /// /// Stores the ModellingRulesManager in the datastore. /// /// The ModellingRulesManager. [MemberNotNull(nameof(ModellingRulesManager))] public void SetModellingRulesManager(ModellingRulesManager modellingRulesManager) { ModellingRulesManager = modellingRulesManager; } /// /// The endpoint addresses used by the server. /// /// The endpoint addresses. public IEnumerable EndpointAddresses => m_endpointAddresses; /// /// The context to use when serializing/deserializing extension objects. /// /// The message context. public IServiceMessageContext MessageContext { get; } /// /// The default system context for the server. /// /// The default system context. public ServerSystemContext DefaultSystemContext { get; } /// /// The table of namespace uris known to the server. /// /// The namespace URIs. public NamespaceTable NamespaceUris { get; } /// /// The table of remote server uris known to the server. /// /// The server URIs. public StringTable ServerUris { get; } /// /// The factory used to create encodeable objects that the server understands. /// /// The factory. public IEncodeableFactory Factory { get; } /// /// The datatypes, object types and variable types known to the server. /// /// 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. /// This object is thread safe. /// public TypeTable TypeTree { get; } /// /// The master node manager for the server. /// /// The node manager. public IMasterNodeManager NodeManager { get; private set; } = null!; /// public IMainNodeManagerFactory MainNodeManagerFactory { get; private set; } = null!; /// /// The internal node manager for the servers. /// /// The core node manager. public ICoreNodeManager CoreNodeManager { get; private set; } = null!; /// /// Returns the node manager that managers the server diagnostics. /// /// The diagnostics node manager. public IDiagnosticsNodeManager DiagnosticsNodeManager { get; private set; } = null!; /// public IConfigurationNodeManager ConfigurationNodeManager { get; private set; } = null!; /// /// The manager for events that all components use to queue events that occur. /// /// The event manager. public EventManager EventManager { get; private set; } = null!; /// /// A manager for localized resources that components can use to localize text. /// /// The resource manager. public ResourceManager ResourceManager { get; private set; } = null!; /// /// A manager for outstanding requests that allows components to receive notifications if the timeout or are cancelled. /// /// The request manager. public RequestManager RequestManager { get; private set; } = null!; /// /// A manager for aggregate calculators supported by the server. /// /// The aggregate manager. public AggregateManager AggregateManager { get; private set; } = null!; /// /// A manager for modelling rules supported by the server. /// /// The modelling rules manager. public ModellingRulesManager ModellingRulesManager { get; private set; } = null!; /// /// The manager for active sessions. /// /// The session manager. ISessionManager IServerInternal.SessionManager => SessionManager; /// /// The manager for active subscriptions. /// ISubscriptionManager IServerInternal.SubscriptionManager => SubscriptionManager; /// /// The factory for durable monitored item queues /// public IMonitoredItemQueueFactory MonitoredItemQueueFactory { get; private set; } = null!; /// /// The store to persist and retrieve subscriptions /// public ISubscriptionStore SubscriptionStore { get; private set; } = null!; /// public ITelemetryContext Telemetry => MessageContext.Telemetry; /// /// Returns the status object for the server. /// /// The status. [Obsolete("No longer thread safe. To read the value use CurrentState, to write use UpdateServerStatus.")] public ServerStatusValue Status => NonThreadSafeStatus; /// /// Gets or sets the current state of the server. /// /// The state of the current. public ServerState CurrentState { get { lock (NonThreadSafeStatus.Lock) { return NonThreadSafeStatus.Value.State; } } set { lock (NonThreadSafeStatus.Lock) { NonThreadSafeStatus.Value.State = value; } } } /// /// Returns the Server object node /// /// The Server object node. public ServerObjectState ServerObject { get; private set; } = null!; /// /// Used to synchronize access to the server diagnostics. /// /// The diagnostics lock. public object DiagnosticsLock { get; } = new object(); /// /// Used to synchronize write access to /// the server diagnostics. /// /// The diagnostics lock. public object DiagnosticsWriteLock { get { // implicitly force diagnostics update DiagnosticsNodeManager?.ForceDiagnosticsScan(); return DiagnosticsLock; } } /// /// Returns the diagnostics structure for the server. /// /// The server diagnostics. public ServerDiagnosticsSummaryDataType ServerDiagnostics { get; private set; } = null!; /// /// Whether the server is currently running. /// /// /// true if this instance is running; otherwise, false. /// /// /// This flag is set to false when the server shuts down. Threads running should check this flag whenever /// they return from a blocking operation. If it is false the thread should clean up and terminate. /// public bool IsRunning { get { if (NonThreadSafeStatus == null) { return false; } lock (NonThreadSafeStatus.Lock) { if (NonThreadSafeStatus.Value.State == ServerState.Running) { return true; } if (NonThreadSafeStatus.Value.State == ServerState.Shutdown && NonThreadSafeStatus.Value.SecondsTillShutdown > 0) { return true; } return false; } } } /// /// Whether the server is collecting diagnostics. /// /// true if diagnostics are enabled; otherwise, false. public bool DiagnosticsEnabled { get { if (DiagnosticsNodeManager == null) { return false; } return DiagnosticsNodeManager.DiagnosticsEnabled; } } /// /// Status but non thread safe - internal so not part of public api /// internal ServerStatusValue NonThreadSafeStatus { get; private set; } = null!; /// /// Closes the specified session. /// /// The context. /// The session identifier. /// if set to true subscriptions are to be deleted. [Obsolete("Use CloseSessionAsync instead.")] public void CloseSession( OperationContext context, NodeId sessionId, bool deleteSubscriptions) { CloseSessionAsync(context, sessionId, deleteSubscriptions) .AsTask().GetAwaiter().GetResult(); } /// /// Closes the specified session. /// /// The context. /// The session identifier. /// if set to true subscriptions are to be deleted. /// The cancellationToken public async ValueTask CloseSessionAsync( OperationContext context, NodeId sessionId, bool deleteSubscriptions, CancellationToken cancellationToken = default) { await NodeManager.SessionClosingAsync(context, sessionId, deleteSubscriptions, cancellationToken) .ConfigureAwait(false); await SubscriptionManager.SessionClosingAsync(context, sessionId, deleteSubscriptions, cancellationToken) .ConfigureAwait(false); await SessionManager.CloseSessionAsync(sessionId, cancellationToken).ConfigureAwait(false); } /// /// Deletes the specified subscription. /// /// The subscription identifier. /// The cancellation token public async ValueTask DeleteSubscriptionAsync(uint subscriptionId, CancellationToken cancellationToken = default) { await SubscriptionManager.DeleteSubscriptionAsync(null!, subscriptionId, cancellationToken).ConfigureAwait(false); } /// /// Called by any component to report a global event. /// /// The event. public void ReportEvent(IFilterTarget e) { ReportEvent(DefaultSystemContext, e); } /// /// Called by any component to report a global event. /// /// The context. /// The event. public void ReportEvent(ISystemContext context, IFilterTarget e) { if ((!Auditing) && (e is AuditEventState)) { // do not report auditing events if server Auditing flag is false return; } ServerObject?.ReportEvent(context, e); } /// /// Refreshes the conditions for the specified subscription. /// /// The context. /// The subscription identifier. public void ConditionRefresh(OperationContext context, uint subscriptionId) { SubscriptionManager.ConditionRefresh(context, subscriptionId); } /// /// Refreshes the conditions for the specified subscription. /// /// The context. /// The subscription identifier. /// The monitored item identifier. public void ConditionRefresh2( OperationContext context, uint subscriptionId, uint monitoredItemId) { SubscriptionManager.ConditionRefresh2(context, subscriptionId, monitoredItemId); } /// /// Updates the server status safely. /// /// Action to perform on the server status object. /// is null. public void UpdateServerStatus(Action action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } lock (NonThreadSafeStatus.Lock) { action.Invoke(NonThreadSafeStatus); } } /// public bool Auditing { get; private set; } /// public ISystemContext DefaultAuditContext => DefaultSystemContext.Copy(); /// public void ReportAuditEvent(ISystemContext context, AuditEventState e) { if (!Auditing) { // do not report auditing events if server Auditing flag is false return; } ReportEvent(context, e); } /// /// Creates the ServerObject and attaches it to the NodeManager. /// [MemberNotNull( nameof(ServerObject), nameof(NonThreadSafeStatus), nameof(ServerDiagnostics))] private async ValueTask CreateServerObjectAsync(CancellationToken cancellationToken = default) { // get the server object. ServerObjectState serverObject = ServerObject = DiagnosticsNodeManager.FindPredefinedNode(ObjectIds.Server); // update server capabilities. ServerCapabilitiesState serverCapabilities = serverObject.ServerCapabilities!; serverObject.ServiceLevel!.Value = 255; serverCapabilities.LocaleIdArray!.Value = ResourceManager .GetAvailableLocales(); serverCapabilities.ServerProfileArray!.Value = [ .. m_configuration.ServerConfiguration!.ServerProfileArray ]; BaseVariableState conformanceUnits = DiagnosticsNodeManager.FindPredefinedNode( VariableIds.Server_ServerCapabilities_ConformanceUnits); if (conformanceUnits != null) { conformanceUnits.Value = Variant.From(Array.Empty().ToArrayOf()); } serverCapabilities.MinSupportedSampleRate!.Value = 0; serverCapabilities.MaxBrowseContinuationPoints!.Value = (ushort) m_configuration.ServerConfiguration.MaxBrowseContinuationPoints; serverCapabilities.MaxQueryContinuationPoints!.Value = (ushort) m_configuration.ServerConfiguration.MaxQueryContinuationPoints; serverCapabilities.MaxHistoryContinuationPoints!.Value = (ushort) m_configuration.ServerConfiguration.MaxHistoryContinuationPoints; serverCapabilities.MaxArrayLength!.Value = (uint) m_configuration.TransportQuotas!.MaxArrayLength; serverCapabilities.MaxStringLength!.Value = (uint) m_configuration.TransportQuotas.MaxStringLength; serverCapabilities.MaxByteStringLength!.Value = (uint) m_configuration.TransportQuotas.MaxByteStringLength; serverCapabilities.MaxSessions!.Value = (uint) m_configuration.ServerConfiguration.MaxSessionCount; serverCapabilities.MaxSubscriptions!.Value = (uint) m_configuration.ServerConfiguration.MaxSubscriptionCount; // Expose MaxSubscriptionsPerSession (optional property // on ServerCapabilitiesType per Part 5 §6.3) so clients that // enumerate per-session limits get a defined value instead of a // missing-attribute response. Use the configured global // MaxSubscriptionCount as the per-session ceiling — the SDK // doesn't track per-session limits separately at this layer. if (serverCapabilities.MaxSubscriptionsPerSession == null) { serverCapabilities.AddMaxSubscriptionsPerSession(DefaultSystemContext); } serverCapabilities.MaxSubscriptionsPerSession!.Value = (uint)Math.Max(1, m_configuration.ServerConfiguration.MaxSubscriptionCount); // Any operational limits Property that is provided shall have a non zero value. OperationLimitsState? operationLimits = serverCapabilities.OperationLimits; OperationLimits configOperationLimits = m_configuration.ServerConfiguration .OperationLimits; if (configOperationLimits != null) { operationLimits!.MaxNodesPerRead = SetPropertyValue( operationLimits.MaxNodesPerRead!, configOperationLimits.MaxNodesPerRead); operationLimits.MaxNodesPerHistoryReadData = SetPropertyValue( operationLimits.MaxNodesPerHistoryReadData!, configOperationLimits.MaxNodesPerHistoryReadData); operationLimits.MaxNodesPerHistoryReadEvents = SetPropertyValue( operationLimits.MaxNodesPerHistoryReadEvents!, configOperationLimits.MaxNodesPerHistoryReadEvents); operationLimits.MaxNodesPerWrite = SetPropertyValue( operationLimits.MaxNodesPerWrite!, configOperationLimits.MaxNodesPerWrite); operationLimits.MaxNodesPerHistoryUpdateData = SetPropertyValue( operationLimits.MaxNodesPerHistoryUpdateData!, configOperationLimits.MaxNodesPerHistoryUpdateData); operationLimits.MaxNodesPerHistoryUpdateEvents = SetPropertyValue( operationLimits.MaxNodesPerHistoryUpdateEvents!, configOperationLimits.MaxNodesPerHistoryUpdateEvents); operationLimits.MaxNodesPerMethodCall = SetPropertyValue( operationLimits.MaxNodesPerMethodCall!, configOperationLimits.MaxNodesPerMethodCall); operationLimits.MaxNodesPerBrowse = SetPropertyValue( operationLimits.MaxNodesPerBrowse!, configOperationLimits.MaxNodesPerBrowse); operationLimits.MaxNodesPerRegisterNodes = SetPropertyValue( operationLimits.MaxNodesPerRegisterNodes!, configOperationLimits.MaxNodesPerRegisterNodes); operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds = SetPropertyValue( operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds!, configOperationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds); operationLimits.MaxNodesPerNodeManagement = SetPropertyValue( operationLimits.MaxNodesPerNodeManagement!, configOperationLimits.MaxNodesPerNodeManagement); operationLimits.MaxMonitoredItemsPerCall = SetPropertyValue( operationLimits.MaxMonitoredItemsPerCall!, configOperationLimits.MaxMonitoredItemsPerCall); } else { operationLimits!.MaxNodesPerRead = operationLimits.MaxNodesPerHistoryReadData = operationLimits.MaxNodesPerHistoryReadEvents = operationLimits.MaxNodesPerWrite = operationLimits.MaxNodesPerHistoryUpdateData = operationLimits.MaxNodesPerHistoryUpdateEvents = operationLimits.MaxNodesPerMethodCall = operationLimits.MaxNodesPerBrowse = operationLimits.MaxNodesPerRegisterNodes = operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds = operationLimits.MaxNodesPerNodeManagement = operationLimits.MaxMonitoredItemsPerCall = null; } // setup PublishSubscribe Status State value const PubSubState pubSubState = PubSubState.Disabled; BaseVariableState default_PubSubState = DiagnosticsNodeManager.FindPredefinedNode( VariableIds.PublishSubscribe_Status_State); default_PubSubState.Value = Variant.From(pubSubState); // setup value for SupportedTransportProfiles BaseVariableState default_SupportedTransportProfiles = DiagnosticsNodeManager.FindPredefinedNode( VariableIds.PublishSubscribe_SupportedTransportProfiles); default_SupportedTransportProfiles.Value = "uadp"; // setup callbacks for dynamic values. serverObject.NamespaceArray!.OnSimpleReadValue = OnReadNamespaceArray; serverObject.NamespaceArray.MinimumSamplingInterval = 1000; serverObject.ServerArray!.OnSimpleReadValue = OnReadServerArray; serverObject.ServerArray.MinimumSamplingInterval = 1000; // dynamic change of enabledFlag is disabled to pass CTT serverObject.ServerDiagnostics!.EnabledFlag!.AccessLevel = AccessLevels.CurrentRead; serverObject.ServerDiagnostics.EnabledFlag.UserAccessLevel = AccessLevels .CurrentRead; serverObject.ServerDiagnostics.EnabledFlag.OnSimpleReadValue = OnReadDiagnosticsEnabledFlag; serverObject.ServerDiagnostics.EnabledFlag.OnSimpleWriteValue = OnWriteDiagnosticsEnabledFlag; serverObject.ServerDiagnostics.EnabledFlag.MinimumSamplingInterval = 1000; // initialize status. DateTime nowUtc = m_timeProvider.GetUtcNow().UtcDateTime; var serverStatus = new ServerStatusDataType { StartTime = nowUtc, CurrentTime = nowUtc, State = ServerState.Shutdown }; var buildInfo = new BuildInfo { ProductName = m_serverDescription.ProductName, ProductUri = m_serverDescription.ProductUri, ManufacturerName = m_serverDescription.ManufacturerName, SoftwareVersion = m_serverDescription.SoftwareVersion, BuildNumber = m_serverDescription.BuildNumber, BuildDate = m_serverDescription.BuildDate }; BuildInfoVariableState buildInfoVariableState = DiagnosticsNodeManager.FindPredefinedNode( VariableIds.Server_ServerStatus_BuildInfo); var buildInfoVariable = new BuildInfoVariableValue( buildInfoVariableState, buildInfo, null!); serverStatus.BuildInfo = buildInfoVariable.Value; serverObject.ServerStatus!.MinimumSamplingInterval = 1000; serverObject.ServerStatus.CurrentTime!.MinimumSamplingInterval = 1000; NonThreadSafeStatus = new ServerStatusValue( serverObject.ServerStatus, serverStatus, DiagnosticsLock) { Timestamp = nowUtc, OnBeforeRead = OnReadServerStatus }; // initialize diagnostics. ServerDiagnostics = new ServerDiagnosticsSummaryDataType { ServerViewCount = 0, CurrentSessionCount = 0, CumulatedSessionCount = 0, SecurityRejectedSessionCount = 0, RejectedSessionCount = 0, SessionTimeoutCount = 0, SessionAbortCount = 0, PublishingIntervalCount = 0, CurrentSubscriptionCount = 0, CumulatedSubscriptionCount = 0, SecurityRejectedRequestsCount = 0, RejectedRequestsCount = 0 }; await DiagnosticsNodeManager.CreateServerDiagnosticsAsync( DefaultSystemContext, ServerDiagnostics, OnUpdateDiagnostics, cancellationToken).ConfigureAwait(false); // set the diagnostics enabled state. await DiagnosticsNodeManager.SetDiagnosticsEnabledAsync( DefaultSystemContext, m_configuration.ServerConfiguration.DiagnosticsEnabled, cancellationToken).ConfigureAwait(false); var configurationNodeManager = DiagnosticsNodeManager as ConfigurationNodeManager; configurationNodeManager?.CreateServerConfiguration( DefaultSystemContext, m_configuration); // Initialize history capabilities and update Server EventNotifier accordingly await DiagnosticsNodeManager.UpdateServerEventNotifierAsync(cancellationToken) .ConfigureAwait(false); Auditing = m_configuration.ServerConfiguration.AuditingEnabled; PropertyState? auditing = serverObject.Auditing; auditing!.OnSimpleWriteValue += OnWriteAuditing; auditing.OnSimpleReadValue += OnReadAuditing; auditing.Value = Auditing; auditing.AccessLevel = AccessLevels.CurrentRead; auditing.UserAccessLevel = AccessLevels.CurrentReadOrWrite; auditing.MinimumSamplingInterval = 1000; // Wire RoleManager into the well-known role nodes per Part 18 §4. // The binding upgrades RoleSet to the typed RoleSetState proxy, // wires typed OnCallAsync delegates on each Role's method-state // children, applies the SecurityAdmin + SignAndEncrypt // authorization gate, and raises RoleMappingRuleChangedAuditEvent // on every successful mutation. if (DiagnosticsNodeManager is AsyncCustomNodeManager diagnosticsCustom) { m_roleStateBinding = RoleStateBinding.Bind(diagnosticsCustom, RoleManager, this); } } /// /// Updates the server status before a read. /// private void OnReadServerStatus( ISystemContext context, BaseVariableValue variable, NodeState component) { lock (NonThreadSafeStatus.Lock) { DateTime now = m_timeProvider.GetUtcNow().UtcDateTime; NonThreadSafeStatus.Timestamp = now; NonThreadSafeStatus.Value.CurrentTime = now; // update other timestamps in NodeState objects which are used to derive the source timestamp if (variable is ServerStatusValue serverStatusValue && serverStatusValue.Variable is ServerStatusState serverStatusState) { serverStatusState.Timestamp = now; serverStatusState.CurrentTime!.Timestamp = now; serverStatusState.State!.Timestamp = now; } } } /// /// Returns a copy of the namespace array. /// private ServiceResult OnReadNamespaceArray( ISystemContext context, NodeState node, ref Variant value) { value = NamespaceUris.ToArrayOf(); return ServiceResult.Good; } /// /// Returns a copy of the server array. /// private ServiceResult OnReadServerArray( ISystemContext context, NodeState node, ref Variant value) { value = ServerUris.ToArrayOf(); return ServiceResult.Good; } /// /// Returns Diagnostics.EnabledFlag /// private ServiceResult OnReadDiagnosticsEnabledFlag( ISystemContext context, NodeState node, ref Variant value) { value = DiagnosticsNodeManager.DiagnosticsEnabled; return ServiceResult.Good; } /// /// Sets the Diagnostics.EnabledFlag /// private ServiceResult OnWriteDiagnosticsEnabledFlag( ISystemContext context, NodeState node, ref Variant value) { bool enabled = (bool)value; DiagnosticsNodeManager.SetDiagnosticsEnabledAsync(DefaultSystemContext, enabled) .AsTask().GetAwaiter().GetResult(); return ServiceResult.Good; } /// /// Updates the Server.Auditing flag. /// private ServiceResult OnWriteAuditing( ISystemContext context, NodeState node, ref Variant value) { Auditing = Convert.ToBoolean(value, CultureInfo.InvariantCulture); return ServiceResult.Good; } /// /// Reads the Server.Auditing flag. /// private ServiceResult OnReadAuditing( ISystemContext context, NodeState node, ref Variant value) { value = Auditing; return ServiceResult.Good; } /// /// Returns a copy of the current diagnostics. /// private ServiceResult OnUpdateDiagnostics( ISystemContext context, NodeState node, ref Variant value) { lock (ServerDiagnostics) { value = Variant.FromStructure(ServerDiagnostics); } return ServiceResult.Good; } /// /// Set the property to null if the value is zero, /// to the value otherwise. /// private static PropertyState SetPropertyValue( PropertyState property, uint value) { if (value != 0) { property.Value = value; } else { property = null!; } return property; } private readonly ServerProperties m_serverDescription; private readonly ApplicationConfiguration m_configuration; private readonly List m_endpointAddresses; private readonly TimeProvider m_timeProvider; private RoleStateBinding? m_roleStateBinding; private volatile IReadOnlyList? m_transportListeners; } }