/* ======================================================================== * 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.Text; using System.Diagnostics; namespace Opc.Ua.Server { /// /// The master node manager for the server. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public class MasterNodeManager : IDisposable { #region Constructors /// /// Initializes the object with default values. /// public MasterNodeManager( IServerInternal server, ApplicationConfiguration configuration, string dynamicNamespaceUri, params INodeManager[] additionalManagers) { if (server == null) throw new ArgumentNullException(nameof(server)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); m_server = server; m_nodeManagers = new List(); m_maxContinuationPointsPerBrowse = (uint)configuration.ServerConfiguration.MaxBrowseContinuationPoints; // ensure the dynamic namespace uris. int dynamicNamespaceIndex = 1; if (!String.IsNullOrEmpty(dynamicNamespaceUri)) { dynamicNamespaceIndex = server.NamespaceUris.GetIndex(dynamicNamespaceUri); if (dynamicNamespaceIndex == -1) { dynamicNamespaceIndex = server.NamespaceUris.Append(dynamicNamespaceUri); } } // 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(); // always add the diagnostics and configuration node manager to the start of the list. ConfigurationNodeManager configurationAndDiagnosticsManager = new ConfigurationNodeManager(server, configuration); 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)); // register core node manager for default UA namespace. namespaceManagers[0].Add(m_nodeManagers[1]); // register core node manager for built-in server namespace. namespaceManagers[1].Add(m_nodeManagers[1]); // add the custom NodeManagers provided by the application. if (additionalManagers != null) { foreach (INodeManager nodeManager in additionalManagers) { RegisterNodeManager(nodeManager, registeredManagers, namespaceManagers); } // build table from dictionary. m_namespaceManagers = new INodeManager[m_server.NamespaceUris.Count][]; for (int ii = 0; ii < m_namespaceManagers.Length; ii++) { if (namespaceManagers.TryGetValue(ii, out registeredManagers)) { m_namespaceManagers[ii] = registeredManagers.ToArray(); } } } } /// /// Registers the node manager with the master node manager. /// private void RegisterNodeManager( INodeManager nodeManager, List registeredManagers, Dictionary> namespaceManagers) { m_nodeManagers.Add(nodeManager); // ensure the NamespaceUris supported by the NodeManager are in the Server's NamespaceTable. if (nodeManager.NamespaceUris != null) { foreach (string namespaceUri in nodeManager.NamespaceUris) { // look up the namespace uri. int index = m_server.NamespaceUris.GetIndex(namespaceUri); if (index == -1) { index = m_server.NamespaceUris.Append(namespaceUri); } // add manager to list for the namespace. if (!namespaceManagers.TryGetValue(index, out registeredManagers)) { namespaceManagers[index] = registeredManagers = new List(); } registeredManagers.Add(nodeManager); } } } #endregion #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) { if (disposing) { List nodeManagers = null; lock (m_lock) { nodeManagers = new List(m_nodeManagers); m_nodeManagers.Clear(); } foreach (INodeManager nodeManager in nodeManagers) { Utils.SilentDispose(nodeManager); } } } #endregion #region Static Methods /// /// Adds a reference to the table of external references. /// /// /// This is a convenience function used by custom NodeManagers. /// public static void CreateExternalReference( IDictionary> externalReferences, NodeId sourceId, NodeId referenceTypeId, bool isInverse, NodeId targetId) { ReferenceNode reference = new ReferenceNode(); reference.ReferenceTypeId = referenceTypeId; reference.IsInverse = isInverse; reference.TargetId = targetId; IList references = null; if (!externalReferences.TryGetValue(sourceId, out references)) { externalReferences[sourceId] = references = new List(); } references.Add(reference); } /// /// Determine the required history access permission depending on the HistoryUpdateDetails /// /// The HistoryUpdateDetails passed in /// The corresponding history access permission protected static PermissionType DetermineHistoryAccessPermission(HistoryUpdateDetails historyUpdateDetails) { Type detailsType = historyUpdateDetails.GetType(); if (detailsType == typeof(UpdateDataDetails)) { UpdateDataDetails updateDataDetails = (UpdateDataDetails)historyUpdateDetails; return GetHistoryPermissionType(updateDataDetails.PerformInsertReplace); } else if (detailsType == typeof(UpdateStructureDataDetails)) { UpdateStructureDataDetails updateStructureDataDetails = (UpdateStructureDataDetails)historyUpdateDetails; return GetHistoryPermissionType(updateStructureDataDetails.PerformInsertReplace); } else if (detailsType == typeof(UpdateEventDetails)) { UpdateEventDetails updateEventDetails = (UpdateEventDetails)historyUpdateDetails; return GetHistoryPermissionType(updateEventDetails.PerformInsertReplace); } else if (detailsType == typeof(DeleteRawModifiedDetails) || detailsType == typeof(DeleteAtTimeDetails) || detailsType == typeof(DeleteEventDetails)) { return PermissionType.DeleteHistory; } return PermissionType.ModifyHistory; } /// /// Determine the History PermissionType depending on PerformUpdateType /// /// /// The corresponding PermissionType protected static PermissionType GetHistoryPermissionType(PerformUpdateType updateType) { switch (updateType) { case PerformUpdateType.Insert: return PermissionType.InsertHistory; case PerformUpdateType.Update: return PermissionType.InsertHistory | PermissionType.ModifyHistory; default: // PerformUpdateType.Replace or PerformUpdateType.Remove return PermissionType.ModifyHistory; } } #endregion #region Public Interface /// /// Returns the core node manager. /// public CoreNodeManager CoreNodeManager { get { return m_nodeManagers[1] as CoreNodeManager; } } /// /// Returns the diagnostics node manager. /// public DiagnosticsNodeManager DiagnosticsNodeManager { get { return m_nodeManagers[0] as DiagnosticsNodeManager; } } /// /// Returns the configuration node manager. /// public ConfigurationNodeManager ConfigurationNodeManager { get { return m_nodeManagers[0] as ConfigurationNodeManager; } } /// /// Creates the node managers and start them /// public virtual void Startup() { lock (m_lock) { Utils.Trace( Utils.TraceMasks.StartStop, "MasterNodeManager.Startup - NodeManagers={0}", m_nodeManagers.Count); // create the address spaces. Dictionary> externalReferences = new Dictionary>(); for (int ii = 0; ii < m_nodeManagers.Count; ii++) { INodeManager nodeManager = m_nodeManagers[ii]; try { nodeManager.CreateAddressSpace(externalReferences); } catch (Exception e) { Utils.Trace(e, "Unexpected error creating address space for NodeManager #{0}.", ii); } } // update external references. for (int ii = 0; ii < m_nodeManagers.Count; ii++) { INodeManager nodeManager = m_nodeManagers[ii]; try { nodeManager.AddReferences(externalReferences); } catch (Exception e) { Utils.Trace(e, "Unexpected error adding references for NodeManager #{0}.", ii); } } } } /// /// Signals that a session is closing. /// public virtual void SessionClosing(OperationContext context, NodeId sessionId, bool deleteSubscriptions) { lock (m_lock) { for (int ii = 0; ii < m_nodeManagers.Count; ii++) { INodeManager2 nodeManager = m_nodeManagers[ii] as INodeManager2; if (nodeManager != null) { try { nodeManager.SessionClosing(context, sessionId, deleteSubscriptions); } catch (Exception e) { Utils.Trace(e, "Unexpected error closing session for NodeManager #{0}.", ii); } } } } } /// /// Shuts down the node managers a /// public virtual void Shutdown() { lock (m_lock) { Utils.Trace( Utils.TraceMasks.StartStop, "MasterNodeManager.Shutdown - NodeManagers={0}", m_nodeManagers.Count); foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.DeleteAddressSpace(); } } } /// /// 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)); if (nodeManager == null) throw new ArgumentNullException(nameof(nodeManager)); // look up the namespace uri. int index = m_server.NamespaceUris.GetIndex(namespaceUri); if (index < 0) { index = m_server.NamespaceUris.Append(namespaceUri); } // allocate a new table (using arrays instead of collections because lookup efficiency is critical). INodeManager[][] namespaceManagers = new INodeManager[m_server.NamespaceUris.Count][]; lock (m_namespaceManagers.SyncRoot) { // copy existing values. for (int ii = 0; ii < m_namespaceManagers.Length; ii++) { if (m_namespaceManagers.Length >= ii) { namespaceManagers[ii] = m_namespaceManagers[ii]; } } // allocate a new array for the index being updated. INodeManager[] registeredManagers = namespaceManagers[index]; if (registeredManagers == null) { registeredManagers = new INodeManager[1]; } else { registeredManagers = new INodeManager[registeredManagers.Length + 1]; Array.Copy(namespaceManagers[index], registeredManagers, namespaceManagers[index].Length); } // add new node manager to the end of the list. registeredManagers[registeredManagers.Length - 1] = nodeManager; namespaceManagers[index] = registeredManagers; // replace the table. m_namespaceManagers = namespaceManagers; } } /// /// Returns node handle and its node manager. /// public virtual object GetManagerHandle(NodeId nodeId, out INodeManager nodeManager) { nodeManager = null; object handle = null; // null node ids have no manager. if (NodeId.IsNull(nodeId)) { return null; } // use the namespace index to select the node manager. int index = nodeId.NamespaceIndex; lock (m_namespaceManagers.SyncRoot) { // check if node managers are registered - use the core node manager if unknown. if (index >= m_namespaceManagers.Length || m_namespaceManagers[index] == null) { handle = m_nodeManagers[1].GetManagerHandle(nodeId); if (handle != null) { nodeManager = m_nodeManagers[1]; return handle; } return null; } // check each of the registered node managers. INodeManager[] nodeManagers = m_namespaceManagers[index]; for (int ii = 0; ii < nodeManagers.Length; ii++) { handle = nodeManagers[ii].GetManagerHandle(nodeId); if (handle != null) { nodeManager = nodeManagers[ii]; return handle; } } } // node not recognized. return null; } /// /// Adds the references to the target. /// public virtual void AddReferences(NodeId sourceId, IList references) { foreach (IReference reference in references) { // find source node. INodeManager nodeManager = null; object sourceHandle = GetManagerHandle(sourceId, out nodeManager); if (sourceHandle == null) { continue; } // delete the reference. Dictionary> map = new Dictionary>(); map.Add(sourceId, references); nodeManager.AddReferences(map); } } /// /// Deletes the references to the target. /// public virtual void DeleteReferences(NodeId targetId, IList references) { foreach (ReferenceNode reference in references) { NodeId sourceId = ExpandedNodeId.ToNodeId(reference.TargetId, m_server.NamespaceUris); // find source node. INodeManager nodeManager = null; object sourceHandle = GetManagerHandle(sourceId, out nodeManager); if (sourceHandle == null) { continue; } // delete the reference. nodeManager.DeleteReference(sourceHandle, reference.ReferenceTypeId, !reference.IsInverse, targetId, false); } } /// /// Deletes the specified references. /// public void RemoveReferences(List referencesToRemove) { for (int ii = 0; ii < referencesToRemove.Count; ii++) { LocalReference reference = referencesToRemove[ii]; // find source node. INodeManager nodeManager = null; object sourceHandle = GetManagerHandle(reference.SourceId, out nodeManager); if (sourceHandle == null) { continue; } // delete the reference. nodeManager.DeleteReference(sourceHandle, reference.ReferenceTypeId, reference.IsInverse, reference.TargetId, false); } } #region Register/Unregister Nodes /// /// Registers a set of node ids. /// public virtual void RegisterNodes( OperationContext context, NodeIdCollection nodesToRegister, out NodeIdCollection registeredNodeIds) { if (nodesToRegister == null) throw new ArgumentNullException(nameof(nodesToRegister)); // return the node id provided. registeredNodeIds = new NodeIdCollection(nodesToRegister.Count); for (int ii = 0; ii < nodesToRegister.Count; ii++) { registeredNodeIds.Add(nodesToRegister[ii]); } Utils.Trace( (int)Utils.TraceMasks.ServiceDetail, "MasterNodeManager.RegisterNodes - Count={0}", nodesToRegister.Count); // it is up to the node managers to assign the handles. /* List processedNodes = new List(new bool[itemsToDelete.Count]); for (int ii = 0; ii < m_nodeManagers.Count; ii++) { m_nodeManagers[ii].RegisterNodes( context, nodesToRegister, registeredNodeIds, processedNodes); } */ } /// /// Unregisters a set of node ids. /// public virtual void UnregisterNodes( OperationContext context, NodeIdCollection nodesToUnregister) { if (nodesToUnregister == null) throw new ArgumentNullException(nameof(nodesToUnregister)); Utils.Trace( (int)Utils.TraceMasks.ServiceDetail, "MasterNodeManager.UnregisterNodes - Count={0}", nodesToUnregister.Count); // it is up to the node managers to assign the handles. /* List processedNodes = new List(new bool[itemsToDelete.Count]); for (int ii = 0; ii < m_nodeManagers.Count; ii++) { m_nodeManagers[ii].RegisterNodes( context, nodesToUnregister, processedNodes); } */ } #endregion #region TranslateBrowsePathsToNodeIds /// /// Translates a start node id plus a relative paths into a node id. /// public virtual void TranslateBrowsePathsToNodeIds( OperationContext context, BrowsePathCollection browsePaths, out BrowsePathResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { if (browsePaths == null) throw new ArgumentNullException(nameof(browsePaths)); bool diagnosticsExist = false; results = new BrowsePathResultCollection(browsePaths.Count); diagnosticInfos = new DiagnosticInfoCollection(browsePaths.Count); for (int ii = 0; ii < browsePaths.Count; ii++) { // check if request has timed out or been cancelled. if (StatusCode.IsBad(context.OperationStatus)) { throw new ServiceResultException(context.OperationStatus); } BrowsePath browsePath = browsePaths[ii]; BrowsePathResult result = new BrowsePathResult(); result.StatusCode = StatusCodes.Good; results.Add(result); ServiceResult error = null; // need to trap unexpected exceptions to handle bugs in the node managers. try { error = TranslateBrowsePath(context, browsePath, result); } catch (Exception e) { error = ServiceResult.Create(e, StatusCodes.BadUnexpectedError, "Unexpected error translating browse path."); } if (ServiceResult.IsGood(error)) { // check for no match. if (result.Targets.Count == 0) { error = StatusCodes.BadNoMatch; } // put a placeholder for diagnostics. else if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos.Add(null); } } // check for error. if (error != null && error.Code != StatusCodes.Good) { result.StatusCode = error.StatusCode; if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { DiagnosticInfo diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_server, context, error); diagnosticInfos.Add(diagnosticInfo); diagnosticsExist = true; } } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Updates the diagnostics return parameter. /// private void UpdateDiagnostics( OperationContext context, bool diagnosticsExist, ref DiagnosticInfoCollection diagnosticInfos) { if (diagnosticInfos == null) { return; } if (diagnosticsExist && context.StringTable.Count == 0) { diagnosticsExist = false; for (int ii = 0; !diagnosticsExist && ii < diagnosticInfos.Count; ii++) { DiagnosticInfo diagnosticInfo = diagnosticInfos[ii]; while (diagnosticInfo != null) { if (!String.IsNullOrEmpty(diagnosticInfo.AdditionalInfo)) { diagnosticsExist = true; break; } diagnosticInfo = diagnosticInfo.InnerDiagnosticInfo; } } } if (!diagnosticsExist) { diagnosticInfos = null; } } /// /// Translates a browse path. /// protected ServiceResult TranslateBrowsePath( OperationContext context, BrowsePath browsePath, BrowsePathResult result) { Debug.Assert(browsePath != null); Debug.Assert(result != null); // check for valid start node. INodeManager nodeManager = null; object sourceHandle = GetManagerHandle(browsePath.StartingNode, out nodeManager); if (sourceHandle == null) { return StatusCodes.BadNodeIdUnknown; } // check the relative path. RelativePath relativePath = browsePath.RelativePath; if (relativePath.Elements == null || relativePath.Elements.Count == 0) { return StatusCodes.BadNothingToDo; } for (int ii = 0; ii < relativePath.Elements.Count; ii++) { RelativePathElement element = relativePath.Elements[ii]; if (element == null || QualifiedName.IsNull(relativePath.Elements[ii].TargetName)) { return StatusCodes.BadBrowseNameInvalid; } if (NodeId.IsNull(element.ReferenceTypeId)) { element.ReferenceTypeId = ReferenceTypeIds.References; element.IncludeSubtypes = true; } } // validate access rights and role permissions ServiceResult serviceResult = ValidatePermissions(context, nodeManager, sourceHandle, PermissionType.Browse); if (ServiceResult.IsGood(serviceResult)) { // translate path only if validation is passing TranslateBrowsePath( context, nodeManager, sourceHandle, relativePath, result.Targets, 0); } return serviceResult; } /// /// Recursively processes the elements in the RelativePath starting at the specified index. /// private void TranslateBrowsePath( OperationContext context, INodeManager nodeManager, object sourceHandle, RelativePath relativePath, BrowsePathTargetCollection targets, int index) { Debug.Assert(nodeManager != null); Debug.Assert(sourceHandle != null); Debug.Assert(relativePath != null); Debug.Assert(targets != null); // check for end of list. if (index < 0 || index >= relativePath.Elements.Count) { return; } // follow the next hop. RelativePathElement element = relativePath.Elements[index]; // check for valid reference type. if (!element.IncludeSubtypes && NodeId.IsNull(element.ReferenceTypeId)) { return; } // check for valid target name. if (QualifiedName.IsNull(element.TargetName)) { throw new ServiceResultException(StatusCodes.BadBrowseNameInvalid); } List targetIds = new List(); List externalTargetIds = new List(); try { nodeManager.TranslateBrowsePath( context, sourceHandle, element, targetIds, externalTargetIds); } catch (Exception e) { Utils.Trace(e, "Unexpected error translating browse path."); return; } // must check the browse name on all external targets. for (int ii = 0; ii < externalTargetIds.Count; ii++) { // get the browse name from another node manager. ReferenceDescription description = new ReferenceDescription(); UpdateReferenceDescription( context, externalTargetIds[ii], NodeClass.Unspecified, BrowseResultMask.BrowseName, description); // add to list if target name matches. if (description.BrowseName == element.TargetName) { bool found = false; for (int jj = 0; jj < targetIds.Count; jj++) { if (targetIds[jj] == externalTargetIds[ii]) { found = true; break; } } if (!found) { targetIds.Add(externalTargetIds[ii]); } } } // check if done after a final hop. if (index == relativePath.Elements.Count - 1) { for (int ii = 0; ii < targetIds.Count; ii++) { // Check the role permissions for target nodes INodeManager targetNodeManager = null; object targetHandle = GetManagerHandle(ExpandedNodeId.ToNodeId(targetIds[ii], Server.NamespaceUris), out targetNodeManager); if (targetHandle != null && targetNodeManager != null) { NodeMetadata nodeMetadata = targetNodeManager.GetNodeMetadata(context, targetHandle, BrowseResultMask.All); ServiceResult serviceResult = ValidateRolePermissions(context, nodeMetadata, PermissionType.Browse); if (ServiceResult.IsBad(serviceResult)) { // Remove target node without role permissions. continue; } } BrowsePathTarget target = new BrowsePathTarget(); target.TargetId = targetIds[ii]; target.RemainingPathIndex = UInt32.MaxValue; targets.Add(target); } return; } // process next hops. for (int ii = 0; ii < targetIds.Count; ii++) { ExpandedNodeId targetId = targetIds[ii]; // check for external reference. if (targetId.IsAbsolute) { BrowsePathTarget target = new BrowsePathTarget(); target.TargetId = targetId; target.RemainingPathIndex = (uint)(index + 1); targets.Add(target); continue; } // check for valid start node. sourceHandle = GetManagerHandle((NodeId)targetId, out nodeManager); if (sourceHandle == null) { continue; } // recursively follow hops. TranslateBrowsePath( context, nodeManager, sourceHandle, relativePath, targets, index + 1); } } #endregion #region Browse /// /// Returns the set of references that meet the filter criteria. /// public virtual void Browse( OperationContext context, ViewDescription view, uint maxReferencesPerNode, BrowseDescriptionCollection nodesToBrowse, out BrowseResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { if (context == null) throw new ArgumentNullException(nameof(context)); if (nodesToBrowse == null) throw new ArgumentNullException(nameof(nodesToBrowse)); if (view != null && !NodeId.IsNull(view.ViewId)) { INodeManager viewManager = null; object viewHandle = GetManagerHandle(view.ViewId, out viewManager); if (viewHandle == null) { throw new ServiceResultException(StatusCodes.BadViewIdUnknown); } NodeMetadata metadata = viewManager.GetNodeMetadata(context, viewHandle, BrowseResultMask.NodeClass); if (metadata == null || metadata.NodeClass != NodeClass.View) { throw new ServiceResultException(StatusCodes.BadViewIdUnknown); } // validate access rights and role permissions ServiceResult validationResult = ValidatePermissions(context, viewManager, viewHandle, PermissionType.Browse); if (ServiceResult.IsBad(validationResult)) { throw new ServiceResultException(validationResult); } view.Handle = viewHandle; } bool diagnosticsExist = false; results = new BrowseResultCollection(nodesToBrowse.Count); diagnosticInfos = new DiagnosticInfoCollection(nodesToBrowse.Count); uint continuationPointsAssigned = 0; for (int ii = 0; ii < nodesToBrowse.Count; ii++) { // check if request has timed out or been cancelled. if (StatusCode.IsBad(context.OperationStatus)) { // release all allocated continuation points. foreach (BrowseResult current in results) { if (current != null && current.ContinuationPoint != null && current.ContinuationPoint.Length > 0) { ContinuationPoint cp = context.Session.RestoreContinuationPoint(current.ContinuationPoint); cp.Dispose(); } } throw new ServiceResultException(context.OperationStatus); } BrowseDescription nodeToBrowse = nodesToBrowse[ii]; // initialize result. BrowseResult result = new BrowseResult(); result.StatusCode = StatusCodes.Good; results.Add(result); ServiceResult error = null; // need to trap unexpected exceptions to handle bugs in the node managers. try { error = Browse( context, view, maxReferencesPerNode, continuationPointsAssigned < m_maxContinuationPointsPerBrowse, nodeToBrowse, result); } catch (Exception e) { error = ServiceResult.Create(e, StatusCodes.BadUnexpectedError, "Unexpected error browsing node."); } // check for continuation point. if (result.ContinuationPoint != null && result.ContinuationPoint.Length > 0) { continuationPointsAssigned++; } // check for error. result.StatusCode = error.StatusCode; if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { DiagnosticInfo diagnosticInfo = null; if (error != null && error.Code != StatusCodes.Good) { diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_server, context, error); diagnosticsExist = true; } diagnosticInfos.Add(diagnosticInfo); } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Continues a browse operation that was previously halted. /// public virtual void BrowseNext( OperationContext context, bool releaseContinuationPoints, ByteStringCollection continuationPoints, out BrowseResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { if (context == null) throw new ArgumentNullException(nameof(context)); if (continuationPoints == null) throw new ArgumentNullException(nameof(continuationPoints)); bool diagnosticsExist = false; results = new BrowseResultCollection(continuationPoints.Count); diagnosticInfos = new DiagnosticInfoCollection(continuationPoints.Count); uint continuationPointsAssigned = 0; for (int ii = 0; ii < continuationPoints.Count; ii++) { ContinuationPoint cp = null; // check if request has timed out or been canceled. if (StatusCode.IsBad(context.OperationStatus)) { // release all allocated continuation points. foreach (BrowseResult current in results) { if (current != null && current.ContinuationPoint != null && current.ContinuationPoint.Length > 0) { cp = context.Session.RestoreContinuationPoint(current.ContinuationPoint); cp.Dispose(); } } throw new ServiceResultException(context.OperationStatus); } // find the continuation point. cp = context.Session.RestoreContinuationPoint(continuationPoints[ii]); // validate access rights and role permissions if (cp != null) { ServiceResult validationResult = ValidatePermissions(context, cp.Manager, cp.NodeToBrowse, PermissionType.Browse); if (ServiceResult.IsBad(validationResult)) { BrowseResult badResult = new BrowseResult(); badResult.StatusCode = validationResult.Code; results.Add(badResult); // put placeholder for diagnostics diagnosticInfos.Add(null); continue; } } // initialize result. BrowseResult result = new BrowseResult(); result.StatusCode = StatusCodes.Good; results.Add(result); // check if simply releasing the continuation point. if (releaseContinuationPoints) { if (cp != null) { cp.Dispose(); cp = null; } continue; } ServiceResult error = null; // check if continuation point has expired. if (cp == null) { error = StatusCodes.BadContinuationPointInvalid; } if (cp != null) { // need to trap unexpected exceptions to handle bugs in the node managers. try { ReferenceDescriptionCollection references = result.References; error = FetchReferences( context, continuationPointsAssigned < m_maxContinuationPointsPerBrowse, ref cp, ref references); result.References = references; } catch (Exception e) { error = ServiceResult.Create(e, StatusCodes.BadUnexpectedError, "Unexpected error browsing node."); } // check for continuation point. if (result.ContinuationPoint != null && result.ContinuationPoint.Length > 0) { continuationPointsAssigned++; } } // check for error. result.StatusCode = error.StatusCode; if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { DiagnosticInfo diagnosticInfo = null; if (error != null && error.Code != StatusCodes.Good) { diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_server, context, error); diagnosticsExist = true; } diagnosticInfos.Add(diagnosticInfo); } // check for continuation point. if (cp != null) { result.StatusCode = StatusCodes.Good; result.ContinuationPoint = cp.Id.ToByteArray(); continue; } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Returns the set of references that meet the filter criteria. /// private ServiceResult Browse( OperationContext context, ViewDescription view, uint maxReferencesPerNode, bool assignContinuationPoint, BrowseDescription nodeToBrowse, BrowseResult result) { Debug.Assert(context != null); Debug.Assert(nodeToBrowse != null); Debug.Assert(result != null); // find node manager that owns the node. INodeManager nodeManager = null; object handle = GetManagerHandle(nodeToBrowse.NodeId, out nodeManager); if (handle == null) { return StatusCodes.BadNodeIdUnknown; } if (!NodeId.IsNull(nodeToBrowse.ReferenceTypeId) && !m_server.TypeTree.IsKnown(nodeToBrowse.ReferenceTypeId)) { return StatusCodes.BadReferenceTypeIdInvalid; } if (nodeToBrowse.BrowseDirection < BrowseDirection.Forward || nodeToBrowse.BrowseDirection > BrowseDirection.Both) { return StatusCodes.BadBrowseDirectionInvalid; } // validate access rights and role permissions ServiceResult validationResult = ValidatePermissions(context, nodeManager, handle, PermissionType.Browse); if (ServiceResult.IsBad(validationResult)) { return validationResult; } // create a continuation point. ContinuationPoint cp = new ContinuationPoint(); cp.Manager = nodeManager; cp.View = view; cp.NodeToBrowse = handle; cp.MaxResultsToReturn = maxReferencesPerNode; cp.BrowseDirection = nodeToBrowse.BrowseDirection; cp.ReferenceTypeId = nodeToBrowse.ReferenceTypeId; cp.IncludeSubtypes = nodeToBrowse.IncludeSubtypes; cp.NodeClassMask = nodeToBrowse.NodeClassMask; cp.ResultMask = (BrowseResultMask)nodeToBrowse.ResultMask; cp.Index = 0; cp.Data = null; // check if reference type left unspecified. if (NodeId.IsNull(cp.ReferenceTypeId)) { cp.ReferenceTypeId = ReferenceTypeIds.References; cp.IncludeSubtypes = true; } // loop until browse is complete or max results. ReferenceDescriptionCollection references = result.References; ServiceResult error = FetchReferences(context, assignContinuationPoint, ref cp, ref references); result.References = references; // save continuation point. if (cp != null) { result.StatusCode = StatusCodes.Good; result.ContinuationPoint = cp.Id.ToByteArray(); } // all is good. return error; } /// /// Loops until browse is complete for max results reached. /// protected ServiceResult FetchReferences( OperationContext context, bool assignContinuationPoint, ref ContinuationPoint cp, ref ReferenceDescriptionCollection references) { Debug.Assert(context != null); Debug.Assert(cp != null); Debug.Assert(references != null); INodeManager nodeManager = cp.Manager; NodeClass nodeClassMask = (NodeClass)cp.NodeClassMask; BrowseResultMask resultMask = cp.ResultMask; // loop until browse is complete or max results. while (cp != null) { // fetch next batch. nodeManager.Browse(context, ref cp, references); ReferenceDescriptionCollection referencesToKeep = new ReferenceDescriptionCollection(references.Count); // check for incomplete reference descriptions. for (int ii = 0; ii < references.Count; ii++) { ReferenceDescription reference = references[ii]; // check if filtering must be applied. if (reference.Unfiltered) { // ignore unknown external references. if (reference.NodeId.IsAbsolute) { continue; } // update the description. bool include = UpdateReferenceDescription( context, (NodeId)reference.NodeId, nodeClassMask, resultMask, reference); if (!include) { continue; } } // add to list. referencesToKeep.Add(reference); } // replace list. references = referencesToKeep; // check if browse limit reached. if (cp != null && references.Count >= cp.MaxResultsToReturn) { if (!assignContinuationPoint) { return StatusCodes.BadNoContinuationPoints; } cp.Id = Guid.NewGuid(); context.Session.SaveContinuationPoint(cp); break; } } // all is good. return ServiceResult.Good; } #endregion /// /// Updates the reference description with the node attributes. /// private bool UpdateReferenceDescription( OperationContext context, NodeId targetId, NodeClass nodeClassMask, BrowseResultMask resultMask, ReferenceDescription description) { if (targetId == null) throw new ArgumentNullException(nameof(targetId)); if (description == null) throw new ArgumentNullException(nameof(description)); // find node manager that owns the node. INodeManager nodeManager = null; object handle = GetManagerHandle(targetId, out nodeManager); // dangling reference - nothing more to do. if (handle == null) { return false; } // fetch the node attributes. NodeMetadata metadata = nodeManager.GetNodeMetadata(context, handle, resultMask); if (metadata == null) { return false; } // check nodeclass filter. if (nodeClassMask != NodeClass.Unspecified && (metadata.NodeClass & nodeClassMask) == 0) { return false; } // update attributes. description.NodeId = metadata.NodeId; description.SetTargetAttributes( resultMask, metadata.NodeClass, metadata.BrowseName, metadata.DisplayName, metadata.TypeDefinition); description.Unfiltered = false; return true; } /// /// Reads a set of nodes /// public virtual void Read( OperationContext context, double maxAge, TimestampsToReturn timestampsToReturn, ReadValueIdCollection nodesToRead, out DataValueCollection values, out DiagnosticInfoCollection diagnosticInfos) { if (nodesToRead == null) throw new ArgumentNullException(nameof(nodesToRead)); if (maxAge < 0) { throw new ServiceResultException(StatusCodes.BadMaxAgeInvalid); } if (timestampsToReturn < TimestampsToReturn.Source || timestampsToReturn > TimestampsToReturn.Neither) { throw new ServiceResultException(StatusCodes.BadTimestampsToReturnInvalid); } bool diagnosticsExist = false; values = new DataValueCollection(nodesToRead.Count); diagnosticInfos = new DiagnosticInfoCollection(nodesToRead.Count); // create empty list of errors. List errors = new List(values.Count); for (int ii = 0; ii < nodesToRead.Count; ii++) { errors.Add(null); } // add placeholder for each result. bool validItems = false; Utils.Trace( (int)Utils.TraceMasks.ServiceDetail, "MasterNodeManager.Read - Count={0}", nodesToRead.Count); for (int ii = 0; ii < nodesToRead.Count; ii++) { // add default value to values collection values.Add(null); // add placeholder for diagnostics diagnosticInfos.Add(null); // pre-validate and pre-parse parameter. errors[ii] = ValidateReadRequest(context, nodesToRead[ii]); // return error status. if (ServiceResult.IsBad(errors[ii])) { nodesToRead[ii].Processed = true; } // found at least one valid item. else { nodesToRead[ii].Processed = false; validItems = true; } } // call each node manager. if (validItems) { for (int ii = 0; ii < m_nodeManagers.Count; ii++) { Utils.Trace( (int)Utils.TraceMasks.ServiceDetail, "MasterNodeManager.Read - Calling NodeManager {0} of {1}", ii, m_nodeManagers.Count); m_nodeManagers[ii].Read( context, maxAge, nodesToRead, values, errors); } } // process results. for (int ii = 0; ii < nodesToRead.Count; ii++) { DataValue value = values[ii]; // set an error code for nodes that were not handled by any node manager. if (!nodesToRead[ii].Processed) { value = values[ii] = new DataValue(StatusCodes.BadNodeIdUnknown, DateTime.UtcNow); errors[ii] = new ServiceResult(values[ii].StatusCode); } // update the diagnostic info and ensure the status code in the data value is the same as the error code. if (errors[ii] != null && errors[ii].Code != StatusCodes.Good) { if (value == null) { value = values[ii] = new DataValue(errors[ii].Code, DateTime.UtcNow); } value.StatusCode = errors[ii].Code; if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos[ii] = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } } // apply the timestamp filters. if (timestampsToReturn != TimestampsToReturn.Server && timestampsToReturn != TimestampsToReturn.Both) { value.ServerTimestamp = DateTime.MinValue; } if (timestampsToReturn != TimestampsToReturn.Source && timestampsToReturn != TimestampsToReturn.Both) { value.SourceTimestamp = DateTime.MinValue; } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Reads the history of a set of items. /// public virtual void HistoryRead( OperationContext context, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueIdCollection nodesToRead, out HistoryReadResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { // validate history details parameter. if (ExtensionObject.IsNull(historyReadDetails)) { throw new ServiceResultException(StatusCodes.BadHistoryOperationInvalid); } HistoryReadDetails details = historyReadDetails.Body as HistoryReadDetails; if (details == null) { throw new ServiceResultException(StatusCodes.BadHistoryOperationUnsupported); } // create result lists. bool diagnosticsExist = false; results = new HistoryReadResultCollection(nodesToRead.Count); diagnosticInfos = new DiagnosticInfoCollection(nodesToRead.Count); // pre-validate items. bool validItems = false; // create empty list of errors. List errors = new List(results.Count); for (int ii = 0; ii < nodesToRead.Count; ii++) { errors.Add(null); } for (int ii = 0; ii < nodesToRead.Count; ii++) { // Limit permission restrictions to Client initiated service call HistoryReadResult result = null; DiagnosticInfo diagnosticInfo = null; // pre-validate and pre-parse parameter. errors[ii] = ValidateHistoryReadRequest(context, nodesToRead[ii]); // return error status. if (ServiceResult.IsBad(errors[ii])) { nodesToRead[ii].Processed = true; result = new HistoryReadResult(); result.StatusCode = errors[ii].Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } } // found at least one valid item. else { nodesToRead[ii].Processed = false; validItems = true; } results.Add(result); diagnosticInfos.Add(diagnosticInfo); } // call each node manager. if (validItems) { foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.HistoryRead( context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors); } for (int ii = 0; ii < nodesToRead.Count; ii++) { HistoryReadResult result = results[ii]; // set an error code for nodes that were not handled by any node manager. if (!nodesToRead[ii].Processed) { nodesToRead[ii].Processed = true; result = results[ii] = new HistoryReadResult(); result.StatusCode = StatusCodes.BadNodeIdUnknown; errors[ii] = results[ii].StatusCode; } // update the diagnostic info and ensure the status code in the result is the same as the error code. if (errors[ii] != null && errors[ii].Code != StatusCodes.Good) { if (result == null) { result = results[ii] = new HistoryReadResult(); } result.StatusCode = errors[ii].Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos[ii] = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } } } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Writes a set of values. /// public virtual void Write( OperationContext context, WriteValueCollection nodesToWrite, out StatusCodeCollection results, out DiagnosticInfoCollection diagnosticInfos) { if (context == null) throw new ArgumentNullException(nameof(context)); if (nodesToWrite == null) throw new ArgumentNullException(nameof(nodesToWrite)); int count = nodesToWrite.Count; bool diagnosticsExist = false; results = new StatusCodeCollection(count); diagnosticInfos = new DiagnosticInfoCollection(count); // add placeholder for each result. bool validItems = false; for (int ii = 0; ii < count; ii++) { StatusCode result = StatusCodes.Good; DiagnosticInfo diagnosticInfo = null; // pre-validate and pre-parse parameter. Validate also access rights and role permissions ServiceResult error = ValidateWriteRequest(context, nodesToWrite[ii]); // return error status. if (ServiceResult.IsBad(error)) { nodesToWrite[ii].Processed = true; result = error.Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_server, context, error); diagnosticsExist = true; } } // found at least one valid item. else { nodesToWrite[ii].Processed = false; validItems = true; } results.Add(result); diagnosticInfos.Add(diagnosticInfo); } // call each node manager. if (validItems) { List errors = new List(count); errors.AddRange(new ServiceResult[count]); foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.Write( context, nodesToWrite, errors); } for (int ii = 0; ii < nodesToWrite.Count; ii++) { if (!nodesToWrite[ii].Processed) { errors[ii] = StatusCodes.BadNodeIdUnknown; } if (errors[ii] != null && errors[ii].Code != StatusCodes.Good) { results[ii] = errors[ii].Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos[ii] = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } } 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, out HistoryUpdateResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { Type detailsType = null; List nodesToUpdate = new List(); // verify that all extension objects in the list have the same type. foreach (ExtensionObject details in historyUpdateDetails) { if (detailsType == null) { detailsType = details.Body.GetType(); } if (!ExtensionObject.IsNull(details)) { nodesToUpdate.Add(details.Body as HistoryUpdateDetails); } } // create result lists. bool diagnosticsExist = false; results = new HistoryUpdateResultCollection(nodesToUpdate.Count); diagnosticInfos = new DiagnosticInfoCollection(nodesToUpdate.Count); // pre-validate items. bool validItems = false; // create empty list of errors. List errors = new List(results.Count); for (int ii = 0; ii < nodesToUpdate.Count; ii++) { errors.Add(null); } for (int ii = 0; ii < nodesToUpdate.Count; ii++) { HistoryUpdateResult result = null; DiagnosticInfo diagnosticInfo = null; // check the type of details parameter. ServiceResult error = null; if (nodesToUpdate[ii].GetType() != detailsType) { error = StatusCodes.BadHistoryOperationInvalid; } // pre-validate and pre-parse parameter. else { error = ValidateHistoryUpdateRequest(context, nodesToUpdate[ii]); } // return error status. if (ServiceResult.IsBad(error)) { nodesToUpdate[ii].Processed = true; result = new HistoryUpdateResult(); result.StatusCode = error.Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfo = ServerUtils.CreateDiagnosticInfo(m_server, context, error); diagnosticsExist = true; } } // found at least one valid item. else { nodesToUpdate[ii].Processed = false; validItems = true; } results.Add(result); diagnosticInfos.Add(diagnosticInfo); } // call each node manager. if (validItems) { foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.HistoryUpdate( context, detailsType, nodesToUpdate, results, errors); } for (int ii = 0; ii < nodesToUpdate.Count; ii++) { HistoryUpdateResult result = results[ii]; // set an error code for nodes that were not handled by any node manager. if (!nodesToUpdate[ii].Processed) { nodesToUpdate[ii].Processed = true; result = results[ii] = new HistoryUpdateResult(); result.StatusCode = StatusCodes.BadNodeIdUnknown; errors[ii] = result.StatusCode; } // update the diagnostic info and ensure the status code in the result is the same as the error code. if (errors[ii] != null && errors[ii].Code != StatusCodes.Good) { if (result == null) { result = results[ii] = new HistoryUpdateResult(); } result.StatusCode = errors[ii].Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos[ii] = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } } } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Calls a method defined on a object. /// public virtual void Call( OperationContext context, CallMethodRequestCollection methodsToCall, out CallMethodResultCollection results, out DiagnosticInfoCollection diagnosticInfos) { if (context == null) throw new ArgumentNullException(nameof(context)); if (methodsToCall == null) throw new ArgumentNullException(nameof(methodsToCall)); bool diagnosticsExist = false; results = new CallMethodResultCollection(methodsToCall.Count); diagnosticInfos = new DiagnosticInfoCollection(methodsToCall.Count); List errors = new List(methodsToCall.Count); // add placeholder for each result. bool validItems = false; for (int ii = 0; ii < methodsToCall.Count; ii++) { results.Add(null); errors.Add(null); if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos.Add(null); } // validate request parameters. errors[ii] = ValidateCallRequestItem(context, methodsToCall[ii]); if (ServiceResult.IsBad(errors[ii])) { methodsToCall[ii].Processed = true; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos[ii] = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } continue; } // found at least one valid item. validItems = true; methodsToCall[ii].Processed = false; } // call each node manager. if (validItems) { foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.Call( context, methodsToCall, results, errors); } } for (int ii = 0; ii < methodsToCall.Count; ii++) { // set an error code for calls that were not handled by any node manager. if (!methodsToCall[ii].Processed) { results[ii] = new CallMethodResult(); errors[ii] = StatusCodes.BadNodeIdUnknown; } // update the diagnostic info and ensure the status code in the result is the same as the error code. if (errors[ii] != null && errors[ii].Code != StatusCodes.Good) { if (results[ii] == null) { results[ii] = new CallMethodResult(); } results[ii].StatusCode = errors[ii].Code; // add diagnostics if requested. if ((context.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { diagnosticInfos[ii] = ServerUtils.CreateDiagnosticInfo(m_server, context, errors[ii]); diagnosticsExist = true; } } } // clear the diagnostics array if no diagnostics requested or no errors occurred. UpdateDiagnostics(context, diagnosticsExist, ref diagnosticInfos); } /// /// Handles condition refresh request. /// public virtual void ConditionRefresh(OperationContext context, IList monitoredItems) { foreach (INodeManager nodeManager in m_nodeManagers) { try { nodeManager.ConditionRefresh(context, monitoredItems); } catch (Exception e) { Utils.Trace(e, "Error calling ConditionRefresh on NodeManager."); } } } /// /// Creates a set of monitored items. /// public virtual void CreateMonitoredItems( OperationContext context, uint subscriptionId, double publishingInterval, TimestampsToReturn timestampsToReturn, IList itemsToCreate, IList errors, IList filterResults, IList monitoredItems) { if (context == null) throw new ArgumentNullException(nameof(context)); if (itemsToCreate == null) throw new ArgumentNullException(nameof(itemsToCreate)); if (errors == null) throw new ArgumentNullException(nameof(errors)); if (filterResults == null) throw new ArgumentNullException(nameof(filterResults)); if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); if (publishingInterval < 0) throw new ArgumentOutOfRangeException(nameof(publishingInterval)); if (timestampsToReturn < TimestampsToReturn.Source || timestampsToReturn > TimestampsToReturn.Neither) { throw new ServiceResultException(StatusCodes.BadTimestampsToReturnInvalid); } // add placeholder for each result. bool validItems = false; for (int ii = 0; ii < itemsToCreate.Count; ii++) { // validate request parameters. errors[ii] = ValidateMonitoredItemCreateRequest(context, itemsToCreate[ii]); if (ServiceResult.IsBad(errors[ii])) { itemsToCreate[ii].Processed = true; continue; } // found at least one valid item. validItems = true; itemsToCreate[ii].Processed = false; } // call each node manager. if (validItems) { // create items for event filters. CreateMonitoredItemsForEvents( context, subscriptionId, publishingInterval, timestampsToReturn, itemsToCreate, errors, filterResults, monitoredItems, ref m_lastMonitoredItemId); // create items for data access. foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.CreateMonitoredItems( context, subscriptionId, publishingInterval, timestampsToReturn, itemsToCreate, errors, filterResults, monitoredItems, ref m_lastMonitoredItemId); } // fill results for unknown nodes. for (int ii = 0; ii < errors.Count; ii++) { if (!itemsToCreate[ii].Processed) { errors[ii] = new ServiceResult(StatusCodes.BadNodeIdUnknown); } } } } /// /// Create monitored items for event subscriptions. /// private void CreateMonitoredItemsForEvents( OperationContext context, uint subscriptionId, double publishingInterval, TimestampsToReturn timestampsToReturn, IList itemsToCreate, IList errors, IList filterResults, IList monitoredItems, ref long globalIdCounter) { for (int ii = 0; ii < itemsToCreate.Count; ii++) { MonitoredItemCreateRequest itemToCreate = itemsToCreate[ii]; if (!itemToCreate.Processed) { // must make sure the filter is not null before checking its type. if (ExtensionObject.IsNull(itemToCreate.RequestedParameters.Filter)) { continue; } // all event subscriptions required an event filter. EventFilter filter = itemToCreate.RequestedParameters.Filter.Body as EventFilter; if (filter == null) { continue; } itemToCreate.Processed = true; // only the value attribute may be used with an event subscription. if (itemToCreate.ItemToMonitor.AttributeId != Attributes.EventNotifier) { errors[ii] = StatusCodes.BadFilterNotAllowed; continue; } // the index range parameter has no meaning for event subscriptions. if (!String.IsNullOrEmpty(itemToCreate.ItemToMonitor.IndexRange)) { errors[ii] = StatusCodes.BadIndexRangeInvalid; continue; } // the data encoding has no meaning for event subscriptions. if (!QualifiedName.IsNull(itemToCreate.ItemToMonitor.DataEncoding)) { errors[ii] = StatusCodes.BadDataEncodingInvalid; continue; } // validate the event filter. EventFilter.Result result = filter.Validate(new FilterContext(m_server.NamespaceUris, m_server.TypeTree, context)); ; if (ServiceResult.IsBad(result.Status)) { errors[ii] = result.Status; filterResults[ii] = result.ToEventFilterResult(context.DiagnosticsMask, context.StringTable); continue; } // check if a valid node. INodeManager nodeManager = null; object handle = GetManagerHandle(itemToCreate.ItemToMonitor.NodeId, out nodeManager); if (handle == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; continue; } NodeMetadata nodeMetadata = nodeManager.GetNodeMetadata(context, handle, BrowseResultMask.All); errors[ii] = ValidateRolePermissions(context, nodeMetadata, PermissionType.ReceiveEvents); if (ServiceResult.IsBad(errors[ii])) { continue; } // create a globally unique identifier. uint monitoredItemId = Utils.IncrementIdentifier(ref globalIdCounter); MonitoredItem monitoredItem = m_server.EventManager.CreateMonitoredItem( context, nodeManager, handle, subscriptionId, monitoredItemId, timestampsToReturn, publishingInterval, itemToCreate, filter); // subscribe to all node managers. if (itemToCreate.ItemToMonitor.NodeId == Objects.Server) { foreach (INodeManager manager in m_nodeManagers) { try { manager.SubscribeToAllEvents(context, subscriptionId, monitoredItem, false); } catch (Exception e) { Utils.Trace(e, "NodeManager threw an exception subscribing to all events. NodeManager={0}", manager); } } } // only subscribe to the node manager that owns the node. else { ServiceResult error = nodeManager.SubscribeToEvents(context, handle, subscriptionId, monitoredItem, false); if (ServiceResult.IsBad(error)) { m_server.EventManager.DeleteMonitoredItem(monitoredItem.Id); errors[ii] = error; continue; } } monitoredItems[ii] = monitoredItem; errors[ii] = StatusCodes.Good; } } } /// /// Modifies a set of monitored items. /// public virtual void ModifyMonitoredItems( OperationContext context, TimestampsToReturn timestampsToReturn, IList monitoredItems, IList itemsToModify, IList errors, IList filterResults) { if (context == null) throw new ArgumentNullException(nameof(context)); if (itemsToModify == null) throw new ArgumentNullException(nameof(itemsToModify)); if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); if (errors == null) throw new ArgumentNullException(nameof(errors)); if (filterResults == null) throw new ArgumentNullException(nameof(filterResults)); if (timestampsToReturn < TimestampsToReturn.Source || timestampsToReturn > TimestampsToReturn.Neither) { throw new ServiceResultException(StatusCodes.BadTimestampsToReturnInvalid); } bool validItems = false; for (int ii = 0; ii < itemsToModify.Count; ii++) { // check for errors. if (ServiceResult.IsBad(errors[ii]) || monitoredItems[ii] == null) { itemsToModify[ii].Processed = true; continue; } // validate request parameters. errors[ii] = ValidateMonitoredItemModifyRequest(itemsToModify[ii]); if (ServiceResult.IsBad(errors[ii])) { itemsToModify[ii].Processed = true; continue; } // found at least one valid item. validItems = true; itemsToModify[ii].Processed = false; } // call each node manager. if (validItems) { // modify items for event filters. ModifyMonitoredItemsForEvents( context, timestampsToReturn, monitoredItems, itemsToModify, errors, filterResults); // let each node manager figure out which items it owns. foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.ModifyMonitoredItems( context, timestampsToReturn, monitoredItems, itemsToModify, errors, filterResults); } // update results. for (int ii = 0; ii < errors.Count; ii++) { if (!itemsToModify[ii].Processed) { errors[ii] = new ServiceResult(StatusCodes.BadMonitoredItemIdInvalid); } } } } /// /// Modify monitored items for event subscriptions. /// private void ModifyMonitoredItemsForEvents( OperationContext context, TimestampsToReturn timestampsToReturn, IList monitoredItems, IList itemsToModify, IList errors, IList filterResults) { for (int ii = 0; ii < itemsToModify.Count; ii++) { IEventMonitoredItem monitoredItem = monitoredItems[ii] as IEventMonitoredItem; // all event subscriptions are handled by the event manager. if (monitoredItem == null || (monitoredItem.MonitoredItemType & MonitoredItemTypeMask.Events) == 0) { continue; } MonitoredItemModifyRequest itemToModify = itemsToModify[ii]; itemToModify.Processed = true; // check for a valid filter. if (ExtensionObject.IsNull(itemToModify.RequestedParameters.Filter)) { errors[ii] = StatusCodes.BadEventFilterInvalid; continue; } // all event subscriptions required an event filter. EventFilter filter = itemToModify.RequestedParameters.Filter.Body as EventFilter; if (filter == null) { errors[ii] = StatusCodes.BadEventFilterInvalid; continue; } // validate the event filter. EventFilter.Result result = filter.Validate(new FilterContext(m_server.NamespaceUris, m_server.TypeTree, context)); if (ServiceResult.IsBad(result.Status)) { errors[ii] = result.Status; filterResults[ii] = result.ToEventFilterResult(context.DiagnosticsMask, context.StringTable); continue; } // modify the item. m_server.EventManager.ModifyMonitoredItem( context, monitoredItem, timestampsToReturn, itemToModify, filter); // subscribe to all node managers. if ((monitoredItem.MonitoredItemType & MonitoredItemTypeMask.AllEvents) != 0) { foreach (INodeManager manager in m_nodeManagers) { manager.SubscribeToAllEvents( context, monitoredItem.SubscriptionId, monitoredItem, false); } } // only subscribe to the node manager that owns the node. else { monitoredItem.NodeManager.SubscribeToEvents( context, monitoredItem.ManagerHandle, monitoredItem.SubscriptionId, monitoredItem, false); } errors[ii] = StatusCodes.Good; } } /// /// Deletes a set of monitored items. /// public virtual void DeleteMonitoredItems( OperationContext context, uint subscriptionId, IList itemsToDelete, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (itemsToDelete == null) throw new ArgumentNullException(nameof(itemsToDelete)); if (errors == null) throw new ArgumentNullException(nameof(errors)); List processedItems = new List(itemsToDelete.Count); for (int ii = 0; ii < itemsToDelete.Count; ii++) { processedItems.Add(ServiceResult.IsBad(errors[ii]) || itemsToDelete[ii] == null); } // delete items for event filters. DeleteMonitoredItemsForEvents( context, subscriptionId, itemsToDelete, processedItems, errors); // call each node manager. foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.DeleteMonitoredItems( context, itemsToDelete, processedItems, errors); } // fill results for unknown nodes. for (int ii = 0; ii < errors.Count; ii++) { if (!processedItems[ii]) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; } } } /// /// Delete monitored items for event subscriptions. /// private void DeleteMonitoredItemsForEvents( OperationContext context, uint subscriptionId, IList monitoredItems, IList processedItems, IList errors) { for (int ii = 0; ii < monitoredItems.Count; ii++) { IEventMonitoredItem monitoredItem = monitoredItems[ii] as IEventMonitoredItem; // all event subscriptions are handled by the event manager. if (monitoredItem == null || (monitoredItem.MonitoredItemType & MonitoredItemTypeMask.Events) == 0) { continue; } processedItems[ii] = true; // unsubscribe to all node managers. if ((monitoredItem.MonitoredItemType & MonitoredItemTypeMask.AllEvents) != 0) { foreach (INodeManager manager in m_nodeManagers) { manager.SubscribeToAllEvents(context, subscriptionId, monitoredItem, true); } } // only unsubscribe to the node manager that owns the node. else { monitoredItem.NodeManager.SubscribeToEvents(context, monitoredItem.ManagerHandle, subscriptionId, monitoredItem, true); } // delete the item. m_server.EventManager.DeleteMonitoredItem(monitoredItem.Id); // success. errors[ii] = StatusCodes.Good; } } /// /// Changes the monitoring mode for a set of items. /// public virtual void SetMonitoringMode( OperationContext context, MonitoringMode monitoringMode, IList itemsToModify, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (itemsToModify == null) throw new ArgumentNullException(nameof(itemsToModify)); if (errors == null) throw new ArgumentNullException(nameof(errors)); // call each node manager. List processedItems = new List(itemsToModify.Count); for (int ii = 0; ii < itemsToModify.Count; ii++) { processedItems.Add(ServiceResult.IsBad(errors[ii]) || itemsToModify[ii] == null); } // delete items for event filters. SetMonitoringModeForEvents( context, monitoringMode, itemsToModify, processedItems, errors); foreach (INodeManager nodeManager in m_nodeManagers) { nodeManager.SetMonitoringMode( context, monitoringMode, itemsToModify, processedItems, errors); } // fill results for unknown nodes. for (int ii = 0; ii < errors.Count; ii++) { if (!processedItems[ii]) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; } } } /// /// Delete monitored items for event subscriptions. /// private static void SetMonitoringModeForEvents( OperationContext context, MonitoringMode monitoringMode, IList monitoredItems, IList processedItems, IList errors) { for (int ii = 0; ii < monitoredItems.Count; ii++) { IEventMonitoredItem monitoredItem = monitoredItems[ii] as IEventMonitoredItem; // all event subscriptions are handled by the event manager. if (monitoredItem == null || (monitoredItem.MonitoredItemType & MonitoredItemTypeMask.Events) == 0) { continue; } processedItems[ii] = true; // set the monitoring mode. monitoredItem.SetMonitoringMode(monitoringMode); // success. errors[ii] = StatusCodes.Good; } } #endregion #region Protected Members /// /// The server that the node manager belongs to. /// protected IServerInternal Server { get { return m_server; } } /// /// The node managers being managed. /// public IList NodeManagers { get { return m_nodeManagers; } } /// /// Validates a monitoring attributes parameter. /// protected static ServiceResult ValidateMonitoringAttributes(MonitoringParameters attributes) { // check for null structure. if (attributes == null) { return new ServiceResult(StatusCodes.BadStructureMissing); } // check for known filter. if (!ExtensionObject.IsNull(attributes.Filter)) { MonitoringFilter filter = attributes.Filter.Body as MonitoringFilter; if (filter == null) { return new ServiceResult(StatusCodes.BadMonitoredItemFilterInvalid); } } // passed basic validation. return null; } /// /// Validates a monitoring filter. /// protected static ServiceResult ValidateMonitoringFilter(ExtensionObject filter) { ServiceResult error = null; // check that no filter is specified for non-value attributes. if (!ExtensionObject.IsNull(filter)) { DataChangeFilter datachangeFilter = filter.Body as DataChangeFilter; // validate data change filter. if (datachangeFilter != null) { error = datachangeFilter.Validate(); if (ServiceResult.IsBad(error)) { return error; } } } // passed basic validation. return null; } /// /// Validates a monitored item create request parameter. /// protected ServiceResult ValidateMonitoredItemCreateRequest(OperationContext operationContext, MonitoredItemCreateRequest item) { // check for null structure. if (item == null) { return new ServiceResult(StatusCodes.BadStructureMissing); } // validate read value id component. Validate also access rights and permissions ServiceResult error = ValidateReadRequest(operationContext, item.ItemToMonitor); if (ServiceResult.IsBad(error)) { return error; } // check for valid monitoring mode. if ((int)item.MonitoringMode < 0 || (int)item.MonitoringMode > (int)MonitoringMode.Reporting) { return new ServiceResult(StatusCodes.BadMonitoringModeInvalid); } // check for null structure. MonitoringParameters attributes = item.RequestedParameters; error = ValidateMonitoringAttributes(attributes); if (ServiceResult.IsBad(error)) { return error; } // check that no filter is specified for non-value attributes. if (item.ItemToMonitor.AttributeId != Attributes.Value && item.ItemToMonitor.AttributeId != Attributes.EventNotifier) { if (!ExtensionObject.IsNull(attributes.Filter)) { return new ServiceResult(StatusCodes.BadFilterNotAllowed); } } else { error = ValidateMonitoringFilter(attributes.Filter); if (ServiceResult.IsBad(error)) { return error; } } // passed basic validation. return null; } /// /// Validates a monitored item modify request parameter. /// protected static ServiceResult ValidateMonitoredItemModifyRequest(MonitoredItemModifyRequest item) { // check for null structure. if (item == null) { return new ServiceResult(StatusCodes.BadStructureMissing); } // check for null structure. MonitoringParameters attributes = item.RequestedParameters; ServiceResult error = ValidateMonitoringAttributes(attributes); if (ServiceResult.IsBad(error)) { return error; } // validate monitoring filter. error = ValidateMonitoringFilter(attributes.Filter); if (ServiceResult.IsBad(error)) { return error; } // passed basic validation. return null; } /// /// Validates a call request item parameter. It validates also access rights and role permissions /// /// /// /// protected ServiceResult ValidateCallRequestItem(OperationContext operationContext, CallMethodRequest callMethodRequest) { // check for null structure. if (callMethodRequest == null) { return StatusCodes.BadStructureMissing; } // check object id. if (NodeId.IsNull(callMethodRequest.ObjectId)) { return StatusCodes.BadNodeIdInvalid; } // check method id. if (NodeId.IsNull(callMethodRequest.MethodId)) { return StatusCodes.BadMethodInvalid; } // check input arguments if (callMethodRequest.InputArguments == null) { return StatusCodes.BadStructureMissing; } // passed basic validation. check also access rights and permissions return ValidatePermissions(operationContext, callMethodRequest.MethodId, PermissionType.Call); } /// /// Validates a Read or MonitoredItemCreate request. It validates also access rights and role permissions /// /// /// /// protected ServiceResult ValidateReadRequest(OperationContext operationContext, ReadValueId readValueId) { ServiceResult serviceResult = ReadValueId.Validate(readValueId); if (ServiceResult.IsGood(serviceResult)) { //any attribute other than Value or RolePermissions PermissionType requestedPermission = PermissionType.Browse; if (readValueId.AttributeId == Attributes.RolePermissions) { requestedPermission = PermissionType.ReadRolePermissions; } else if (readValueId.AttributeId == Attributes.Value) { requestedPermission = PermissionType.Read; } // check access rights and role permissions serviceResult = ValidatePermissions(operationContext, readValueId.NodeId, requestedPermission); } return serviceResult; } /// /// Validates a Write request. It validates also access rights and role permissions /// /// /// /// protected ServiceResult ValidateWriteRequest(OperationContext operationContext, WriteValue writeValue) { ServiceResult serviceResult = WriteValue.Validate(writeValue); if (ServiceResult.IsGood(serviceResult)) { PermissionType requestedPermission = PermissionType.WriteAttribute; //any attribute other than Value, RolePermissions or Historizing if (writeValue.AttributeId == Attributes.RolePermissions) { requestedPermission = PermissionType.WriteRolePermissions; } else if (writeValue.AttributeId == Attributes.Historizing) { requestedPermission = PermissionType.WriteHistorizing; } else if (writeValue.AttributeId == Attributes.Value) { requestedPermission = PermissionType.Write; } // check access rights and permissions serviceResult = ValidatePermissions(operationContext, writeValue.NodeId, requestedPermission); } return serviceResult; } /// /// Validates a HistoryRead request. It validates also access rights and role permissions /// /// /// /// protected ServiceResult ValidateHistoryReadRequest(OperationContext operationContext, HistoryReadValueId historyReadValueId) { ServiceResult serviceResult = HistoryReadValueId.Validate(historyReadValueId); if (ServiceResult.IsGood(serviceResult)) { // check access rights and permissions serviceResult = ValidatePermissions(operationContext, historyReadValueId.NodeId, PermissionType.ReadHistory); } return serviceResult; } /// /// Validates a HistoryUpdate request. It validates also access rights and role permissions /// /// /// /// protected ServiceResult ValidateHistoryUpdateRequest(OperationContext operationContext, HistoryUpdateDetails historyUpdateDetails) { ServiceResult serviceResult = HistoryUpdateDetails.Validate(historyUpdateDetails); if (ServiceResult.IsGood(serviceResult)) { // check access rights and permissions PermissionType requiredPermission = DetermineHistoryAccessPermission(historyUpdateDetails); serviceResult = ValidatePermissions(operationContext, historyUpdateDetails.NodeId, requiredPermission); } return serviceResult; } #region Validate Permissions Methods /// /// 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 /// The node whose attributes are validated /// The requested permission /// 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, NodeId nodeId, PermissionType requestedPermision) { if (context.Session != null) { INodeManager nodeManager = null; object nodeHandle = GetManagerHandle(nodeId, out nodeManager); return ValidatePermissions(context, nodeManager, nodeHandle, requestedPermision); } return StatusCodes.Good; } /// /// 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 /// The node manager handling the nodeHandle /// The node handle of the node whose attributes are validated /// The requested permission /// 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, INodeManager nodeManager, object nodeHandle, PermissionType requestedPermision) { ServiceResult serviceResult = StatusCodes.Good; // check if validation is necessary if (context.Session != null && nodeManager != null && nodeHandle != null) { NodeMetadata nodeMetadata = nodeManager.GetNodeMetadata(context, nodeHandle, BrowseResultMask.NodeClass); if (nodeMetadata != null) { // check RolePermissions serviceResult = ValidateRolePermissions(context, nodeMetadata, requestedPermision); if (ServiceResult.IsGood(serviceResult)) { // check AccessRestrictions serviceResult = ValidateAccessRestrictions(context, nodeMetadata); } } } return serviceResult; } /// /// Validate the AccessRestrictions attribute /// /// The Operation Context /// /// Good if the AccessRestrictions passes the validation protected static ServiceResult ValidateAccessRestrictions(OperationContext context, NodeMetadata nodeMetadata) { ServiceResult serviceResult = StatusCodes.Good; AccessRestrictionType restrictions = AccessRestrictionType.None; if (nodeMetadata.AccessRestrictions != AccessRestrictionType.None) { restrictions = nodeMetadata.AccessRestrictions; } else if (nodeMetadata.DefaultAccessRestrictions != AccessRestrictionType.None) { restrictions = nodeMetadata.DefaultAccessRestrictions; } if (restrictions != AccessRestrictionType.None) { bool encryptionRequired = (restrictions & AccessRestrictionType.EncryptionRequired) == AccessRestrictionType.EncryptionRequired; bool signingRequired = (restrictions & AccessRestrictionType.SigningRequired) == AccessRestrictionType.SigningRequired; bool sessionRequired = (restrictions & AccessRestrictionType.SessionRequired) == AccessRestrictionType.SessionRequired; if ((encryptionRequired && context.ChannelContext.EndpointDescription.SecurityMode != MessageSecurityMode.SignAndEncrypt && context.ChannelContext.EndpointDescription.TransportProfileUri != Profiles.HttpsBinaryTransport) || (signingRequired && context.ChannelContext.EndpointDescription.SecurityMode != MessageSecurityMode.Sign && context.ChannelContext.EndpointDescription.SecurityMode != MessageSecurityMode.SignAndEncrypt && context.ChannelContext.EndpointDescription.TransportProfileUri != Profiles.HttpsBinaryTransport) || (sessionRequired && context.Session == null)) { serviceResult = ServiceResult.Create(StatusCodes.BadSecurityModeInsufficient, "Access restricted to nodeId {0} due to insufficient security mode.", nodeMetadata.NodeId); } } return serviceResult; } /// /// Validates the role permissions /// /// /// /// /// protected internal static ServiceResult ValidateRolePermissions(OperationContext context, NodeMetadata nodeMetadata, PermissionType requestedPermission) { if (context.Session == null || nodeMetadata == null || requestedPermission == PermissionType.None) { // no permission is required hence the validation passes return StatusCodes.Good; } // get the intersection of user role permissions and role permissions RolePermissionTypeCollection userRolePermissions = null, rolePermissions = null; if (nodeMetadata.UserRolePermissions != null && nodeMetadata.UserRolePermissions.Count > 0) { userRolePermissions = nodeMetadata.UserRolePermissions; } else if (nodeMetadata.DefaultUserRolePermissions != null && nodeMetadata.DefaultUserRolePermissions.Count > 0) { userRolePermissions = nodeMetadata.DefaultUserRolePermissions; } if (nodeMetadata.RolePermissions != null && nodeMetadata.RolePermissions.Count > 0) { rolePermissions = nodeMetadata.RolePermissions; } else { rolePermissions = nodeMetadata.DefaultRolePermissions; } if ((userRolePermissions == null || userRolePermissions.Count == 0) && (rolePermissions == null || rolePermissions.Count == 0)) { // there is no restriction from role permissions return StatusCodes.Good; } // group all permissions defined in rolePermissions by RoleId Dictionary roleIdPermissions = new Dictionary(); if (rolePermissions != null && rolePermissions.Count > 0) { foreach (RolePermissionType rolePermission in rolePermissions) { if (roleIdPermissions.ContainsKey(rolePermission.RoleId)) { roleIdPermissions[rolePermission.RoleId] |= ((PermissionType)rolePermission.Permissions); } else { roleIdPermissions[rolePermission.RoleId] = ((PermissionType)rolePermission.Permissions) & requestedPermission; } } } // group all permissions defined in userRolePermissions by RoleId Dictionary roleIdPermissionsDefinedForUser = new Dictionary(); if (userRolePermissions != null && userRolePermissions.Count > 0) { foreach (RolePermissionType rolePermission in userRolePermissions) { if (roleIdPermissionsDefinedForUser.ContainsKey(rolePermission.RoleId)) { roleIdPermissionsDefinedForUser[rolePermission.RoleId] |= ((PermissionType)rolePermission.Permissions); } else { roleIdPermissionsDefinedForUser[rolePermission.RoleId] = ((PermissionType)rolePermission.Permissions) & requestedPermission; } } } Dictionary commonRoleIdPermissions = null; if (rolePermissions == null || rolePermissions.Count == 0) { // there were no role permissions defined for this node only user role permissions commonRoleIdPermissions = roleIdPermissionsDefinedForUser; } else if (userRolePermissions == null || userRolePermissions.Count == 0) { // there were no role permissions defined for this node only user role permissions commonRoleIdPermissions = roleIdPermissions; } else { commonRoleIdPermissions = new Dictionary(); // intersect role permissions from node and user foreach (NodeId roleId in roleIdPermissions.Keys) { if (roleIdPermissionsDefinedForUser.ContainsKey(roleId)) { commonRoleIdPermissions[roleId] = roleIdPermissions[roleId] & roleIdPermissionsDefinedForUser[roleId]; } } } var currentRoleIds = context.Session.Identity.GrantedRoleIds; if (currentRoleIds == null || currentRoleIds.Count == 0) { return ServiceResult.Create(StatusCodes.BadUserAccessDenied, "Current user has no granted role."); } foreach (NodeId currentRoleId in currentRoleIds) { if (commonRoleIdPermissions.ContainsKey(currentRoleId) && commonRoleIdPermissions[currentRoleId] != PermissionType.None) { // there is one role that current session has na is listed in requested role return StatusCodes.Good; } } return ServiceResult.Create(StatusCodes.BadUserAccessDenied, "The requested permission {0} is not granted for node id {1}.", requestedPermission, nodeMetadata.NodeId); } #endregion #endregion #region Private Fields private object m_lock = new object(); private IServerInternal m_server; private List m_nodeManagers; private long m_lastMonitoredItemId; private INodeManager[][] m_namespaceManagers; private uint m_maxContinuationPointsPerBrowse; #endregion } #region LocalReference Class /// /// Stores a reference between NodeManagers that is needs to be created or deleted. /// public class LocalReference { /// /// Initializes the reference. /// public LocalReference( NodeId sourceId, NodeId referenceTypeId, bool isInverse, NodeId targetId) { m_sourceId = sourceId; m_referenceTypeId = referenceTypeId; m_isInverse = isInverse; m_targetId = targetId; } /// /// The source of the reference. /// public NodeId SourceId { get { return m_sourceId; } } /// /// The type of reference. /// public NodeId ReferenceTypeId { get { return m_referenceTypeId; } } /// /// True is the reference is an inverse reference. /// public bool IsInverse { get { return m_isInverse; } } /// /// The target of the reference. /// public NodeId TargetId { get { return m_targetId; } } private NodeId m_sourceId; private NodeId m_referenceTypeId; private bool m_isInverse; private NodeId m_targetId; } #endregion }