diff --git a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs
index d457a9e3ac..e9619c6cb0 100644
--- a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs
@@ -3849,7 +3849,7 @@ protected virtual void OnMonitoredItemCreated(
///
///
///
- public ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission)
+ public virtual ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission)
{
if (requestedPermission == PermissionType.None)
{
diff --git a/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs b/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs
index 53b4511078..8689c56970 100644
--- a/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs
+++ b/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using Opc.Ua;
using Opc.Ua.Server;
@@ -142,6 +143,10 @@ public void Remove(MonitoredItem datachangeItem)
if (Object.ReferenceEquals(DataChangeMonitoredItems[ii], datachangeItem))
{
DataChangeMonitoredItems.RemoveAt(ii);
+
+ // Remove the cached context for the monitored item
+ m_contextCache.TryRemove(datachangeItem.Id, out _);
+
break;
}
}
@@ -271,7 +276,7 @@ public void OnReportEvent(ISystemContext context, NodeState node, IFilterTarget
}
}
- }
+ }
///
/// Called when the state of a Node changes.
@@ -331,8 +336,15 @@ public void QueueValue(
value.SourceTimestamp = DateTime.MinValue;
value.StatusCode = StatusCodes.Good;
+ ISystemContext contextToUse = context;
+
+ if (context is ServerSystemContext systemContext)
+ {
+ contextToUse = GetOrCreateContext(systemContext, monitoredItem);
+ }
+
ServiceResult error = node.ReadAttribute(
- context,
+ contextToUse,
monitoredItem.AttributeId,
monitoredItem.IndexRange,
monitoredItem.DataEncoding,
@@ -347,11 +359,53 @@ public void QueueValue(
}
#endregion
+ #region Private Methods
+ ///
+ /// Gets or creates a cached context for the monitored item.
+ ///
+ /// The monitored item.
+ /// The system context.
+ /// The cached or newly created context.
+ private ServerSystemContext GetOrCreateContext(ServerSystemContext context, MonitoredItem monitoredItem)
+ {
+ uint monitoredItemId = monitoredItem.Id;
+ int currentTicks = HiResClock.TickCount;
+
+ // Check if the context already exists in the cache
+ if (m_contextCache.TryGetValue(monitoredItemId, out var cachedEntry))
+ {
+ // Check if the session or user identity has changed or the entry has expired
+ if (cachedEntry.Context.OperationContext.Session != monitoredItem.Session
+ || cachedEntry.Context.OperationContext.UserIdentity != monitoredItem.EffectiveIdentity
+ || (currentTicks - cachedEntry.CreatedAtTicks) > m_cacheLifetimeTicks)
+ {
+ var updatedContext = context.Copy(new OperationContext(monitoredItem));
+ m_contextCache[monitoredItemId] = (updatedContext, currentTicks);
+
+ return updatedContext;
+ }
+ // return cached entry
+ else
+ {
+ return cachedEntry.Context;
+ }
+ }
+
+ // Create a new context and add it to the cache
+ var newContext = context.Copy(new OperationContext(monitoredItem));
+ m_contextCache.TryAdd(monitoredItemId, (newContext, currentTicks));
+
+ return newContext;
+ }
+ #endregion
+
#region Private Fields
private CustomNodeManager2 m_nodeManager;
private NodeState m_node;
private List m_dataChangeMonitoredItems;
private List m_eventMonitoredItems;
+ private readonly ConcurrentDictionary m_contextCache = new();
+ private readonly int m_cacheLifetimeTicks = (int)TimeSpan.FromMinutes(5).TotalMilliseconds;
#endregion
}
}
diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
index 170183c15e..377ba1ece0 100644
--- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
@@ -3431,7 +3431,7 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext
}
}
- var currentRoleIds = context.UserIdentity.GrantedRoleIds;
+ var currentRoleIds = context?.UserIdentity?.GrantedRoleIds;
if (currentRoleIds == null || currentRoleIds.Count == 0)
{
return ServiceResult.Create(StatusCodes.BadUserAccessDenied, "Current user has no granted role.");