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.");