From f00207ce3cbf87267f167366b46254a3f350dc43 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 15:24:43 +0000
Subject: [PATCH 1/5] Initial plan
From f5ec81ac52c6ea17451a667c8b957dac39db68c7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 15:34:21 +0000
Subject: [PATCH 2/5] Create CoreNodeManager2 based on CustomNodeManager2 and
mark old CoreNodeManager as obsolete
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
---
.../NodeManager/CoreNodeManager.cs | 6 +-
.../NodeManager/CoreNodeManager2.cs | 181 ++++++++++++++++++
.../NodeManager/MasterNodeManager.cs | 4 +-
.../Opc.Ua.Server/Server/IServerInternal.cs | 2 +-
.../Server/ServerInternalData.cs | 2 +-
5 files changed, 189 insertions(+), 6 deletions(-)
create mode 100644 Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
index 0679ffda4..4b5670adf 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
@@ -42,7 +42,9 @@ namespace Opc.Ua.Server
///
/// Every Server has one instance of this NodeManager.
/// It stores objects that implement ILocalNode and indexes them by NodeId.
+ /// This class is deprecated. Use instead.
///
+ [Obsolete("Use CoreNodeManager2 instead. This class will be removed in a future version.")]
public class CoreNodeManager : INodeManager, IDisposable
{
///
@@ -148,11 +150,11 @@ internal void ImportNodes(
node.Export(context, nodesToExport);
}
- lock (Server.CoreNodeManager.DataLock)
+ lock (DataLock)
{
foreach (ILocalNode nodeToExport in nodesToExport.OfType())
{
- Server.CoreNodeManager.AttachNode(nodeToExport, isInternal);
+ AttachNode(nodeToExport, isInternal);
}
}
}
diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
new file mode 100644
index 000000000..99da03f51
--- /dev/null
+++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
@@ -0,0 +1,181 @@
+/* ========================================================================
+ * 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 Microsoft.Extensions.Logging;
+
+namespace Opc.Ua.Server
+{
+ ///
+ /// The core node manager for the server based on CustomNodeManager2.
+ ///
+ ///
+ /// Every Server has one instance of this NodeManager.
+ /// It manages the built-in OPC UA nodes and provides core functionality.
+ /// This is a refactored version of CoreNodeManager that inherits from CustomNodeManager2
+ /// to consolidate the NodeManager implementations in the server library.
+ ///
+ public class CoreNodeManager2 : CustomNodeManager2
+ {
+ ///
+ /// Initializes the node manager with default values.
+ ///
+ public CoreNodeManager2(
+ IServerInternal server,
+ ApplicationConfiguration configuration,
+ ushort dynamicNamespaceIndex)
+ : base(
+ server,
+ configuration,
+ true, // Enable SamplingGroups
+ server.Telemetry.CreateLogger(),
+ Array.Empty()) // CoreNodeManager manages namespaces 0 and 1 by default
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ // Store the dynamic namespace index (typically namespace index 1)
+ m_dynamicNamespaceIndex = dynamicNamespaceIndex;
+
+ // Use namespace 1 if out of range
+ if (m_dynamicNamespaceIndex == 0 ||
+ m_dynamicNamespaceIndex >= server.NamespaceUris.Count)
+ {
+ m_dynamicNamespaceIndex = 1;
+ }
+
+ // Set up namespaces - CoreNodeManager handles namespace 0 (UA) and 1 (server namespace)
+ SetNamespaceIndexes([0, m_dynamicNamespaceIndex]);
+ }
+
+ ///
+ /// Acquires the lock on the node manager.
+ ///
+ ///
+ /// This property provides compatibility with the old CoreNodeManager API.
+ /// It maps to the Lock property from CustomNodeManager2.
+ ///
+ public object DataLock => Lock;
+
+ ///
+ /// Returns an opaque handle identifying the node to the node manager.
+ ///
+ public override object GetManagerHandle(NodeId nodeId)
+ {
+ lock (Lock)
+ {
+ if (NodeId.IsNull(nodeId))
+ {
+ return null;
+ }
+
+ // Check if it's in namespace 0 (UA standard namespace) or the dynamic namespace
+ if (nodeId.NamespaceIndex != 0 && nodeId.NamespaceIndex != m_dynamicNamespaceIndex)
+ {
+ return null;
+ }
+
+ // Try to find the node in predefined nodes
+ NodeState node = Find(nodeId);
+ if (node != null)
+ {
+ return new NodeHandle(nodeId, node);
+ }
+
+ // Return null if not found (will be handled by other node managers)
+ return null;
+ }
+ }
+
+ ///
+ /// Creates a unique node identifier.
+ ///
+ public NodeId CreateUniqueNodeId()
+ {
+ return CreateUniqueNodeId(m_dynamicNamespaceIndex);
+ }
+
+ ///
+ /// Creates a new unique identifier for a node in the specified namespace.
+ ///
+ private NodeId CreateUniqueNodeId(ushort namespaceIndex)
+ {
+ return new NodeId(Utils.IncrementIdentifier(ref m_lastId), namespaceIndex);
+ }
+
+ ///
+ /// 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)
+ {
+ lock (Lock)
+ {
+ foreach (NodeState node in predefinedNodes)
+ {
+ // Add the node to the predefined nodes dictionary
+ AddPredefinedNode(context, node);
+ }
+ }
+ }
+
+ ///
+ /// Attaches a node to the address space.
+ ///
+ ///
+ /// This method is provided for compatibility with the old CoreNodeManager.
+ /// It maps to AddPredefinedNode from CustomNodeManager2.
+ ///
+ internal void AttachNode(NodeState node, bool isInternal)
+ {
+ AddPredefinedNode(SystemContext, node);
+ }
+
+ ///
+ /// Returns the namespace index used for dynamically created nodes.
+ ///
+ public ushort DynamicNamespaceIndex => m_dynamicNamespaceIndex;
+
+ private uint m_lastId;
+ private readonly ushort m_dynamicNamespaceIndex;
+ }
+}
diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
index 38ebb8fee..af95b6e79 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
@@ -121,7 +121,7 @@ public MasterNodeManager(
// 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.
- var coreNodeManager = new CoreNodeManager(Server, configuration, (ushort)dynamicNamespaceIndex);
+ var coreNodeManager = new CoreNodeManager2(Server, configuration, (ushort)dynamicNamespaceIndex);
m_nodeManagers.Add(coreNodeManager.ToAsyncNodeManager());
// register core node manager for default UA namespace.
@@ -306,7 +306,7 @@ protected static PermissionType GetHistoryPermissionType(PerformUpdateType updat
///
/// Returns the core node manager.
///
- public CoreNodeManager CoreNodeManager => m_nodeManagers[1].SyncNodeManager as CoreNodeManager;
+ public CoreNodeManager2 CoreNodeManager => m_nodeManagers[1].SyncNodeManager as CoreNodeManager2;
///
/// Returns the diagnostics node manager.
diff --git a/Libraries/Opc.Ua.Server/Server/IServerInternal.cs b/Libraries/Opc.Ua.Server/Server/IServerInternal.cs
index dee2635d4..18994445a 100644
--- a/Libraries/Opc.Ua.Server/Server/IServerInternal.cs
+++ b/Libraries/Opc.Ua.Server/Server/IServerInternal.cs
@@ -96,7 +96,7 @@ public interface IServerInternal : IAuditEventServer, IDisposable
/// The internal node manager for the servers.
///
/// The core node manager.
- CoreNodeManager CoreNodeManager { get; }
+ CoreNodeManager2 CoreNodeManager { get; }
///
/// Returns the node manager that managers the server diagnostics.
diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
index 6032cb8b0..906142d2e 100644
--- a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
+++ b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
@@ -281,7 +281,7 @@ public void SetModellingRulesManager(ModellingRulesManager modellingRulesManager
/// The internal node manager for the servers.
///
/// The core node manager.
- public CoreNodeManager CoreNodeManager { get; private set; }
+ public CoreNodeManager2 CoreNodeManager { get; private set; }
///
/// Returns the node manager that managers the server diagnostics.
From fe6a058f8ce4ca5c4b10f34b7f8ee96a30b6c9c0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 15:40:32 +0000
Subject: [PATCH 3/5] Add comprehensive tests for CoreNodeManager2
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
---
.../CoreNodeManager2Tests.cs | 241 ++++++++++++++++++
1 file changed, 241 insertions(+)
create mode 100644 Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
diff --git a/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs b/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
new file mode 100644
index 000000000..5ddb0381a
--- /dev/null
+++ b/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
@@ -0,0 +1,241 @@
+/* ========================================================================
+ * 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.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Opc.Ua.Tests;
+
+namespace Opc.Ua.Server.Tests
+{
+ ///
+ /// Test
+ ///
+ [TestFixture]
+ [Category("CoreNodeManager2")]
+ [SetCulture("en-us")]
+ [SetUICulture("en-us")]
+ [Parallelizable]
+ public class CoreNodeManager2Tests
+ {
+ ///
+ /// Tests that CoreNodeManager2 is properly instantiated and accessible.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2Instantiation()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange & Act
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+
+ // Assert
+ Assert.That(server.CurrentInstance.CoreNodeManager, Is.Not.Null);
+ Assert.That(server.CurrentInstance.CoreNodeManager, Is.InstanceOf());
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Tests that CoreNodeManager2 inherits from CustomNodeManager2.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2InheritsFromCustomNodeManager2()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange & Act
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+
+ // Assert
+ Assert.That(server.CurrentInstance.CoreNodeManager, Is.InstanceOf());
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Tests that CoreNodeManager2 has DataLock property for compatibility.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2HasDataLockProperty()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange & Act
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+
+ // Assert
+ Assert.That(coreNodeManager.DataLock, Is.Not.Null);
+ Assert.That(coreNodeManager.DataLock, Is.EqualTo(coreNodeManager.Lock));
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Tests that CoreNodeManager2 can import nodes.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2ImportNodes()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+
+ var testNode = new DataItemState(null)
+ {
+ NodeId = new NodeId(Guid.NewGuid(), coreNodeManager.DynamicNamespaceIndex),
+ BrowseName = new QualifiedName("TestNode", coreNodeManager.DynamicNamespaceIndex),
+ DisplayName = "Test Node"
+ };
+
+ // Act
+ coreNodeManager.ImportNodes(coreNodeManager.SystemContext, [testNode]);
+
+ // Assert
+ NodeState foundNode = coreNodeManager.Find(testNode.NodeId);
+ Assert.That(foundNode, Is.Not.Null);
+ Assert.That(foundNode.NodeId, Is.EqualTo(testNode.NodeId));
+ Assert.That(foundNode.BrowseName, Is.EqualTo(testNode.BrowseName));
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Tests that CoreNodeManager2 can create unique node IDs.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2CreateUniqueNodeId()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+
+ // Act
+ NodeId nodeId1 = coreNodeManager.CreateUniqueNodeId();
+ NodeId nodeId2 = coreNodeManager.CreateUniqueNodeId();
+
+ // Assert
+ Assert.That(nodeId1, Is.Not.Null);
+ Assert.That(nodeId2, Is.Not.Null);
+ Assert.That(nodeId1, Is.Not.EqualTo(nodeId2));
+ Assert.That(nodeId1.NamespaceIndex, Is.EqualTo(coreNodeManager.DynamicNamespaceIndex));
+ Assert.That(nodeId2.NamespaceIndex, Is.EqualTo(coreNodeManager.DynamicNamespaceIndex));
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Tests that CoreNodeManager2 manages namespace 0 and 1.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2ManagesCorrectNamespaces()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange & Act
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+
+ // Assert
+ Assert.That(coreNodeManager.NamespaceIndexes, Is.Not.Null);
+ Assert.That(coreNodeManager.NamespaceIndexes, Does.Contain((ushort)0)); // UA namespace
+ Assert.That(coreNodeManager.NamespaceIndexes.Count, Is.EqualTo(2));
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Tests that CoreNodeManager2 uses SamplingGroups.
+ ///
+ [Test]
+ public async Task TestCoreNodeManager2UsesSamplingGroups()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create();
+ var fixture = new ServerFixture();
+
+ try
+ {
+ // Arrange & Act
+ StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+
+ // Assert that the node manager is properly initialized
+ // The SamplingGroups support is enabled in the constructor
+ Assert.That(coreNodeManager, Is.Not.Null);
+ Assert.That(coreNodeManager.Server, Is.Not.Null);
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+ }
+}
From dacf81c1fda6003a87582dafce227ac6ecf75ddb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 15:44:05 +0000
Subject: [PATCH 4/5] Address code review feedback - remove unused variables
and use explicit array syntax
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
---
.../Opc.Ua.Server/NodeManager/CoreNodeManager2.cs | 2 +-
Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs | 10 +---------
2 files changed, 2 insertions(+), 10 deletions(-)
diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
index 99da03f51..00f943e99 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
@@ -74,7 +74,7 @@ public CoreNodeManager2(
}
// Set up namespaces - CoreNodeManager handles namespace 0 (UA) and 1 (server namespace)
- SetNamespaceIndexes([0, m_dynamicNamespaceIndex]);
+ SetNamespaceIndexes(new ushort[] { 0, m_dynamicNamespaceIndex });
}
///
diff --git a/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs b/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
index 5ddb0381a..83afa4e3a 100644
--- a/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
+++ b/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
@@ -31,7 +31,6 @@
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
-using Opc.Ua.Tests;
namespace Opc.Ua.Server.Tests
{
@@ -51,7 +50,6 @@ public class CoreNodeManager2Tests
[Test]
public async Task TestCoreNodeManager2Instantiation()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
@@ -75,7 +73,6 @@ public async Task TestCoreNodeManager2Instantiation()
[Test]
public async Task TestCoreNodeManager2InheritsFromCustomNodeManager2()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
@@ -98,7 +95,6 @@ public async Task TestCoreNodeManager2InheritsFromCustomNodeManager2()
[Test]
public async Task TestCoreNodeManager2HasDataLockProperty()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
@@ -123,7 +119,6 @@ public async Task TestCoreNodeManager2HasDataLockProperty()
[Test]
public async Task TestCoreNodeManager2ImportNodes()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
@@ -140,7 +135,7 @@ public async Task TestCoreNodeManager2ImportNodes()
};
// Act
- coreNodeManager.ImportNodes(coreNodeManager.SystemContext, [testNode]);
+ coreNodeManager.ImportNodes(coreNodeManager.SystemContext, new NodeState[] { testNode });
// Assert
NodeState foundNode = coreNodeManager.Find(testNode.NodeId);
@@ -160,7 +155,6 @@ public async Task TestCoreNodeManager2ImportNodes()
[Test]
public async Task TestCoreNodeManager2CreateUniqueNodeId()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
@@ -192,7 +186,6 @@ public async Task TestCoreNodeManager2CreateUniqueNodeId()
[Test]
public async Task TestCoreNodeManager2ManagesCorrectNamespaces()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
@@ -218,7 +211,6 @@ public async Task TestCoreNodeManager2ManagesCorrectNamespaces()
[Test]
public async Task TestCoreNodeManager2UsesSamplingGroups()
{
- ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture();
try
From 4fc1afaf58dbbf38601bda598288eda231f17a16 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 16:27:49 +0000
Subject: [PATCH 5/5] Add ICoreNodeManager interface and update server to
depend on interface instead of concrete type
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
---
.../Diagnostics/DiagnosticsNodeManager.cs | 4 +-
.../NodeManager/CoreNodeManager.cs | 38 ++++++++-
.../NodeManager/CoreNodeManager2.cs | 2 +-
.../NodeManager/ICoreNodeManager.cs | 79 +++++++++++++++++++
.../NodeManager/MasterNodeManager.cs | 2 +-
.../Opc.Ua.Server/Server/IServerInternal.cs | 2 +-
.../Server/ServerInternalData.cs | 2 +-
.../CoreNodeManager2Tests.cs | 10 +--
8 files changed, 127 insertions(+), 12 deletions(-)
create mode 100644 Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs
diff --git a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs
index 0ed69b096..10fbb1f8e 100644
--- a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs
@@ -155,8 +155,8 @@ public override void CreateAddressSpace(
// The nodes are now loaded by the DiagnosticsNodeManager from the file
// output by the ModelDesigner V2. These nodes are added to the CoreNodeManager
- // via the AttachNode() method when the DiagnosticsNodeManager starts.
- Server.CoreNodeManager.ImportNodes(SystemContext, PredefinedNodes.Values, true);
+ // via ImportNodes() method when the DiagnosticsNodeManager starts.
+ Server.CoreNodeManager.ImportNodes(SystemContext, PredefinedNodes.Values);
// hook up the server GetMonitoredItems method.
var getMonitoredItems = (GetMonitoredItemsMethodState)FindPredefinedNode(
diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
index 4b5670adf..f0605499a 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
@@ -45,7 +45,7 @@ namespace Opc.Ua.Server
/// This class is deprecated. Use instead.
///
[Obsolete("Use CoreNodeManager2 instead. This class will be removed in a future version.")]
- public class CoreNodeManager : INodeManager, IDisposable
+ public class CoreNodeManager : INodeManager, ICoreNodeManager, IDisposable
{
///
/// Initializes the object with default values.
@@ -3573,6 +3573,11 @@ public NodeId CreateUniqueNodeId()
return CreateUniqueNodeId(m_dynamicNamespaceIndex);
}
+ ///
+ /// Returns the namespace index used for dynamically created nodes.
+ ///
+ public ushort DynamicNamespaceIndex => m_dynamicNamespaceIndex;
+
///
private object GetManagerHandle(ExpandedNodeId nodeId)
{
@@ -3684,6 +3689,37 @@ private NodeId CreateUniqueNodeId(ushort namespaceIndex)
return new NodeId(Utils.IncrementIdentifier(ref m_lastId), namespaceIndex);
}
+ ///
+ /// Called when the session is closed.
+ ///
+ public void SessionClosing(OperationContext context, NodeId sessionId, bool deleteSubscriptions)
+ {
+ // No special handling needed for session closing in the core node manager
+ }
+
+ ///
+ /// Returns true if the node is in the view.
+ ///
+ public bool IsNodeInView(OperationContext context, NodeId viewId, object nodeHandle)
+ {
+ // Core node manager supports all views
+ return true;
+ }
+
+ ///
+ /// Returns the metadata needed for validating permissions, associated with the node.
+ ///
+ public NodeMetadata GetPermissionMetadata(
+ OperationContext context,
+ object targetHandle,
+ BrowseResultMask resultMask,
+ Dictionary> uniqueNodesServiceAttributesCache,
+ bool permissionsOnly)
+ {
+ // Delegate to the standard GetNodeMetadata method
+ return GetNodeMetadata(context, targetHandle, resultMask);
+ }
+
private readonly NodeTable m_nodes;
private uint m_lastId;
private readonly SamplingGroupManager m_samplingGroupManager;
diff --git a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
index 00f943e99..351843201 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager2.cs
@@ -42,7 +42,7 @@ namespace Opc.Ua.Server
/// This is a refactored version of CoreNodeManager that inherits from CustomNodeManager2
/// to consolidate the NodeManager implementations in the server library.
///
- public class CoreNodeManager2 : CustomNodeManager2
+ public class CoreNodeManager2 : CustomNodeManager2, ICoreNodeManager
{
///
/// Initializes the node manager with default values.
diff --git a/Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs
new file mode 100644
index 000000000..20fc54515
--- /dev/null
+++ b/Libraries/Opc.Ua.Server/NodeManager/ICoreNodeManager.cs
@@ -0,0 +1,79 @@
+/* ========================================================================
+ * 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.Collections.Generic;
+
+namespace Opc.Ua.Server
+{
+ ///
+ /// The interface for the core node manager.
+ ///
+ ///
+ /// This interface defines the contract for the core node manager that handles
+ /// the built-in OPC UA nodes (namespace 0) and the server's dynamic namespace.
+ /// It extends INodeManager2 with additional methods specific to the core node manager.
+ ///
+ public interface ICoreNodeManager : INodeManager2
+ {
+ ///
+ /// Acquires the lock on the node manager.
+ ///
+ ///
+ /// This lock should be used when accessing or modifying the node manager's internal state.
+ ///
+ object DataLock { get; }
+
+ ///
+ /// Imports the nodes from a collection of NodeState objects.
+ ///
+ /// The system context.
+ /// The predefined nodes to import.
+ ///
+ /// This method is used to add nodes to the core node manager's address space.
+ /// It is typically called during initialization or when loading predefined nodes.
+ ///
+ void ImportNodes(ISystemContext context, IEnumerable predefinedNodes);
+
+ ///
+ /// Creates a unique node identifier.
+ ///
+ /// A new unique NodeId in the dynamic namespace.
+ ///
+ /// This method generates unique node identifiers for dynamically created nodes.
+ /// The NodeIds are created in the server's dynamic namespace.
+ ///
+ NodeId CreateUniqueNodeId();
+
+ ///
+ /// Returns the namespace index used for dynamically created nodes.
+ ///
+ /// The dynamic namespace index.
+ ushort DynamicNamespaceIndex { get; }
+ }
+}
diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
index af95b6e79..558443a10 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
@@ -306,7 +306,7 @@ protected static PermissionType GetHistoryPermissionType(PerformUpdateType updat
///
/// Returns the core node manager.
///
- public CoreNodeManager2 CoreNodeManager => m_nodeManagers[1].SyncNodeManager as CoreNodeManager2;
+ public ICoreNodeManager CoreNodeManager => m_nodeManagers[1].SyncNodeManager as ICoreNodeManager;
///
/// Returns the diagnostics node manager.
diff --git a/Libraries/Opc.Ua.Server/Server/IServerInternal.cs b/Libraries/Opc.Ua.Server/Server/IServerInternal.cs
index 18994445a..08e7171e4 100644
--- a/Libraries/Opc.Ua.Server/Server/IServerInternal.cs
+++ b/Libraries/Opc.Ua.Server/Server/IServerInternal.cs
@@ -96,7 +96,7 @@ public interface IServerInternal : IAuditEventServer, IDisposable
/// The internal node manager for the servers.
///
/// The core node manager.
- CoreNodeManager2 CoreNodeManager { get; }
+ ICoreNodeManager CoreNodeManager { get; }
///
/// Returns the node manager that managers the server diagnostics.
diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
index 906142d2e..18f514827 100644
--- a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
+++ b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
@@ -281,7 +281,7 @@ public void SetModellingRulesManager(ModellingRulesManager modellingRulesManager
/// The internal node manager for the servers.
///
/// The core node manager.
- public CoreNodeManager2 CoreNodeManager { get; private set; }
+ public ICoreNodeManager CoreNodeManager { get; private set; }
///
/// Returns the node manager that managers the server diagnostics.
diff --git a/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs b/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
index 83afa4e3a..a90ad2fe0 100644
--- a/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
+++ b/Tests/Opc.Ua.Server.Tests/CoreNodeManager2Tests.cs
@@ -101,7 +101,7 @@ public async Task TestCoreNodeManager2HasDataLockProperty()
{
// Arrange & Act
StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
- CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager as CoreNodeManager2;
// Assert
Assert.That(coreNodeManager.DataLock, Is.Not.Null);
@@ -125,7 +125,7 @@ public async Task TestCoreNodeManager2ImportNodes()
{
// Arrange
StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
- CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager as CoreNodeManager2;
var testNode = new DataItemState(null)
{
@@ -161,7 +161,7 @@ public async Task TestCoreNodeManager2CreateUniqueNodeId()
{
// Arrange
StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
- CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager as CoreNodeManager2;
// Act
NodeId nodeId1 = coreNodeManager.CreateUniqueNodeId();
@@ -192,7 +192,7 @@ public async Task TestCoreNodeManager2ManagesCorrectNamespaces()
{
// Arrange & Act
StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
- CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager as CoreNodeManager2;
// Assert
Assert.That(coreNodeManager.NamespaceIndexes, Is.Not.Null);
@@ -217,7 +217,7 @@ public async Task TestCoreNodeManager2UsesSamplingGroups()
{
// Arrange & Act
StandardServer server = await fixture.StartAsync().ConfigureAwait(false);
- CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager;
+ CoreNodeManager2 coreNodeManager = server.CurrentInstance.CoreNodeManager as CoreNodeManager2;
// Assert that the node manager is properly initialized
// The SamplingGroups support is enabled in the constructor