/* ========================================================================
* 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.LogInfo(
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.LogError(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.LogError(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.LogError(e, "Unexpected error closing session for NodeManager #{0}.", ii);
}
}
}
}
}
///
/// Shuts down the node managers.
///
public virtual void Shutdown()
{
lock (m_lock)
{
Utils.LogInfo(
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.LogTrace(
(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.LogTrace(
(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, null, true);
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.LogError(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, null, true);
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);
}
///
/// Prepare a cache per NodeManager and unique NodeId that holds the attributes needed to validate the AccessRestrictions and RolePermissions.
/// This cache is then used in subsequenct calls to avoid triggering unnecessary time consuming callbacks.
/// The current services that benefit from this are the Read service
///
/// One of the following types used in the service calls:
/// ReadValueId used in the Read service
/// The collection of nodes on which the service operates uppon
/// The resulting cache that holds the values of the AccessRestrictions and RolePermissions attributes needed for Read service
private void PrepareValidationCache(List nodesCollection,
out Dictionary> uniqueNodesServiceAttributes)
{
List uniqueNodes = new List();
for (int i = 0; i < nodesCollection.Count; i++)
{
Type listType = typeof(T);
NodeId nodeId = null;
if (listType == typeof(ReadValueId))
{
nodeId = (nodesCollection[i] as ReadValueId)?.NodeId;
}
if (nodeId == null)
{
throw new ArgumentException("Provided List nodesCollection is of wrong type, T should be type BrowseDescription, ReadValueId or CallMethodRequest", nameof(nodesCollection));
}
if (!uniqueNodes.Contains(nodeId))
{
uniqueNodes.Add(nodeId);
}
}
// uniqueNodesReadAttributes is the place where the attributes for each unique nodeId are kept on the services
uniqueNodesServiceAttributes = new Dictionary>();
foreach (var uniqueNode in uniqueNodes)
{
uniqueNodesServiceAttributes.Add(uniqueNode, new List