/* ======================================================================== * 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; using System.Collections.Generic; using System.Text; using System.Xml; using System.Threading; using System.Reflection; using System.IO; using System.Diagnostics; using System.Threading.Tasks; #pragma warning disable 0618 namespace Opc.Ua.Server { /// /// The default node manager for the server. /// /// /// Every Server has one instance of this NodeManager. /// It stores objects that implement ILocalNode and indexes them by NodeId. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public partial class CoreNodeManager : INodeManager, IDisposable { #region Constructors /// /// Initializes the object with default values. /// public CoreNodeManager( IServerInternal server, ApplicationConfiguration configuration, ushort dynamicNamespaceIndex) { if (server == null) throw new ArgumentNullException(nameof(server)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); m_server = server; m_nodes = new NodeTable(server.NamespaceUris, server.ServerUris, server.TypeTree); m_monitoredItems = new Dictionary(); m_defaultMinimumSamplingInterval = 1000; m_namespaceUris = new List(); m_dynamicNamespaceIndex = dynamicNamespaceIndex; // use namespace 1 if out of range. if (m_dynamicNamespaceIndex == 0 || m_dynamicNamespaceIndex >= server.NamespaceUris.Count) { m_dynamicNamespaceIndex = 1; } m_samplingGroupManager = new SamplingGroupManager( server, this, (uint)configuration.ServerConfiguration.MaxNotificationQueueSize, configuration.ServerConfiguration.AvailableSamplingRates); } #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 nodes = null; lock(m_lock) { nodes = new List(m_nodes); m_nodes.Clear(); m_monitoredItems.Clear(); } foreach (INode node in nodes) { Utils.SilentDispose(node); } Utils.SilentDispose(m_samplingGroupManager); } } #endregion #region Public Properties /// /// Acquires the lock on the node manager. /// public object DataLock { get { return m_lock; } } #endregion /// /// Imports the nodes from a dictionary of NodeState objects. /// public void ImportNodes(ISystemContext context, IEnumerable predefinedNodes) { ImportNodes(context, predefinedNodes, false); } /// /// Imports the nodes from a dictionary of NodeState objects. /// internal void ImportNodes(ISystemContext context, IEnumerable predefinedNodes, bool isInternal) { NodeTable nodesToExport = new NodeTable(Server.NamespaceUris, Server.ServerUris, Server.TypeTree); foreach (NodeState node in predefinedNodes) { node.Export(context, nodesToExport); } lock (Server.CoreNodeManager.DataLock) { foreach (ILocalNode nodeToExport in nodesToExport) { Server.CoreNodeManager.AttachNode(nodeToExport, isInternal); } } } #region INodeManager Members /// public IEnumerable NamespaceUris { get { return m_namespaceUris; } } /// /// /// Populates the NodeManager by loading the standard nodes from an XML file stored as an embedded resource. /// public void CreateAddressSpace(IDictionary> externalReferences) { // TBD } /// /// /// Disposes all of the nodes. /// public void DeleteAddressSpace() { List nodesToDispose = new List(); lock(m_lock) { // collect nodes to dispose. foreach (INode node in m_nodes) { IDisposable disposable = node as IDisposable; if (disposable != null) { nodesToDispose.Add(disposable); } } m_nodes.Clear(); } // dispose of the nodes. foreach (IDisposable disposable in nodesToDispose) { try { disposable.Dispose(); } catch (Exception e) { Utils.Trace(e, "Unexpected error disposing a Node object."); } } } /// public object GetManagerHandle(NodeId nodeId) { lock(m_lock) { if (NodeId.IsNull(nodeId)) { return null; } return GetLocalNode(nodeId); } } /// public void TranslateBrowsePath( OperationContext context, object sourceHandle, RelativePathElement relativePath, IList targetIds, IList unresolvedTargetIds) { if (sourceHandle == null) throw new ArgumentNullException(nameof(sourceHandle)); if (relativePath == null) throw new ArgumentNullException(nameof(relativePath)); if (targetIds == null) throw new ArgumentNullException(nameof(targetIds)); if (unresolvedTargetIds == null) throw new ArgumentNullException(nameof(unresolvedTargetIds)); // check for valid handle. ILocalNode source = sourceHandle as ILocalNode; if (source == null) { return; } lock(m_lock) { // find the references that meet the filter criteria. IList references = source.References.Find( relativePath.ReferenceTypeId, relativePath.IsInverse, relativePath.IncludeSubtypes, m_server.TypeTree); // nothing more to do. if (references == null || references.Count == 0) { return; } // find targets with matching browse names. foreach (IReference reference in references) { INode target = GetLocalNode(reference.TargetId); // target is not known to the node manager. if (target == null) { // ignore unknown external references. if (reference.TargetId.IsAbsolute) { continue; } // caller must check the browse name. unresolvedTargetIds.Add((NodeId)reference.TargetId); continue; } // check browse name. if (target.BrowseName == relativePath.TargetName) { targetIds.Add(reference.TargetId); } } } } #region Browse /// public void Browse( OperationContext context, ref ContinuationPoint continuationPoint, IList references) { if (context == null) throw new ArgumentNullException(nameof(context)); if (continuationPoint == null) throw new ArgumentNullException(nameof(continuationPoint)); if (references == null) throw new ArgumentNullException(nameof(references)); // check for valid handle. ILocalNode source = continuationPoint.NodeToBrowse as ILocalNode; if (source == null) { throw new ServiceResultException(StatusCodes.BadNodeIdUnknown); } // check for view. if (!ViewDescription.IsDefault(continuationPoint.View)) { throw new ServiceResultException(StatusCodes.BadViewIdUnknown); } lock (m_lock) { // construct list of references. uint maxResultsToReturn = continuationPoint.MaxResultsToReturn; // get previous enumerator. IEnumerator enumerator = continuationPoint.Data as IEnumerator; // fetch a snapshot all references for node. if (enumerator == null) { List copy = new List(source.References); enumerator = copy.GetEnumerator(); enumerator.MoveNext(); } do { IReference reference = enumerator.Current; // silently ignore bad values. if (reference == null || NodeId.IsNull(reference.ReferenceTypeId) || NodeId.IsNull(reference.TargetId)) { continue; } // apply browse filters. bool include = ApplyBrowseFilters( reference, continuationPoint.BrowseDirection, continuationPoint.ReferenceTypeId, continuationPoint.IncludeSubtypes); if (include) { ReferenceDescription description = new ReferenceDescription(); description.NodeId = reference.TargetId; description.SetReferenceType(continuationPoint.ResultMask, reference.ReferenceTypeId, !reference.IsInverse); // only fetch the metadata if it is requested. if (continuationPoint.TargetAttributesRequired) { // get the metadata for the node. NodeMetadata metadata = GetNodeMetadata(context, GetManagerHandle(reference.TargetId), continuationPoint.ResultMask); // update description with local node metadata. if (metadata != null) { description.SetTargetAttributes( continuationPoint.ResultMask, metadata.NodeClass, metadata.BrowseName, metadata.DisplayName, metadata.TypeDefinition); // check node class mask. if (!CheckNodeClassMask(continuationPoint.NodeClassMask, description.NodeClass)) { continue; } } // any target that is not remote must be owned by another node manager. else if (!reference.TargetId.IsAbsolute) { description.Unfiltered = true; } } // add reference to list. references.Add(description); // construct continuation point if max results reached. if (maxResultsToReturn > 0 && references.Count >= maxResultsToReturn) { continuationPoint.Index = 0; continuationPoint.Data = enumerator; enumerator.MoveNext(); return; } } } while (enumerator.MoveNext()); // nothing more to browse if it exits from the loop normally. continuationPoint.Dispose(); continuationPoint = null; } } /// /// Returns true is the target meets the filter criteria. /// private bool ApplyBrowseFilters( IReference reference, BrowseDirection browseDirection, NodeId referenceTypeId, bool includeSubtypes) { // check browse direction. if (reference.IsInverse) { if (browseDirection == BrowseDirection.Forward) { return false; } } else { if (browseDirection == BrowseDirection.Inverse) { return false; } } // check reference type filter. if (!NodeId.IsNull(referenceTypeId)) { if (reference.ReferenceTypeId != referenceTypeId) { if (includeSubtypes) { if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId)) { return true; } } return false; } } // include reference for now. return true; } #endregion /// public NodeMetadata GetNodeMetadata( OperationContext context, object targetHandle, BrowseResultMask resultMask) { if (context == null) throw new ArgumentNullException(nameof(context)); // find target. ILocalNode target = targetHandle as ILocalNode; if (target == null) { return null; } lock (m_lock) { // copy the default metadata. NodeMetadata metadata = new NodeMetadata(target, target.NodeId); // copy target attributes. if ((resultMask & BrowseResultMask.NodeClass) != 0) { metadata.NodeClass = (NodeClass)target.NodeClass; } if ((resultMask & BrowseResultMask.BrowseName) != 0) { metadata.BrowseName = target.BrowseName; } if ((resultMask & BrowseResultMask.DisplayName) != 0) { metadata.DisplayName = target.DisplayName; // check if the display name can be localized. if (!String.IsNullOrEmpty(metadata.DisplayName.Key)) { metadata.DisplayName = Server.ResourceManager.Translate(context.PreferredLocales, metadata.DisplayName); } } metadata.WriteMask = target.WriteMask; if (metadata.WriteMask != AttributeWriteMask.None) { DataValue value = new DataValue((uint)(int)target.UserWriteMask); ServiceResult result = target.Read(context, Attributes.UserWriteMask, value); if (ServiceResult.IsBad(result)) { metadata.WriteMask = AttributeWriteMask.None; } else { metadata.WriteMask = (AttributeWriteMask)(int)((uint)(int)metadata.WriteMask & (uint)value.Value); } } metadata.EventNotifier = EventNotifiers.None; metadata.AccessLevel = AccessLevels.None; metadata.Executable = false; switch (target.NodeClass) { case NodeClass.Object: { metadata.EventNotifier = ((IObject)target).EventNotifier; break; } case NodeClass.View: { metadata.EventNotifier = ((IView)target).EventNotifier; break; } case NodeClass.Variable: { IVariable variable = (IVariable)target; metadata.DataType = variable.DataType; metadata.ValueRank = variable.ValueRank; metadata.ArrayDimensions = variable.ArrayDimensions; metadata.AccessLevel = variable.AccessLevel; DataValue value = new DataValue(variable.UserAccessLevel); ServiceResult result = variable.Read(context, Attributes.UserAccessLevel, value); if (ServiceResult.IsBad(result)) { metadata.AccessLevel = 0; break; } metadata.AccessLevel = (byte)(metadata.AccessLevel & (byte)value.Value); break; } case NodeClass.Method: { IMethod method = (IMethod)target; metadata.Executable = method.Executable; if (metadata.Executable) { DataValue value = new DataValue(method.UserExecutable); ServiceResult result = method.Read(context, Attributes.UserExecutable, value); if (ServiceResult.IsBad(result)) { metadata.Executable = false; break; } metadata.Executable = (bool)value.Value; } break; } } // look up type definition. if ((resultMask & BrowseResultMask.TypeDefinition) != 0) { if (target.NodeClass == NodeClass.Variable || target.NodeClass == NodeClass.Object) { metadata.TypeDefinition = target.TypeDefinitionId; } } // Set AccessRestrictions and RolePermissions Node node = (Node)target; metadata.AccessRestrictions = (AccessRestrictionType)Enum.Parse(typeof(AccessRestrictionType), node.AccessRestrictions.ToString()); metadata.RolePermissions = node.RolePermissions; metadata.UserRolePermissions = node.UserRolePermissions; // check if NamespaceMetadata is defined for NamespaceUri string namespaceUri = Server.NamespaceUris.GetString(target.NodeId.NamespaceIndex); NamespaceMetadataState namespaceMetadataState = Server.NodeManager.ConfigurationNodeManager.GetNamespaceMetadataState(namespaceUri); if (namespaceMetadataState != null) { metadata.DefaultAccessRestrictions = (AccessRestrictionType)Enum.ToObject(typeof(AccessRestrictionType), namespaceMetadataState.DefaultAccessRestrictions.Value); metadata.DefaultRolePermissions = namespaceMetadataState.DefaultRolePermissions.Value; metadata.DefaultUserRolePermissions = namespaceMetadataState.DefaultUserRolePermissions.Value; } #if LEGACY_NODEMANAGER // check if a source is defined for the node. SourceHandle handle = target.Handle as SourceHandle; if (handle != null) { // check if the metadata needs to be updated by the source. IReadMetadataSource source = handle.Source as IReadMetadataSource; if (source != null) { source.ReadMetadata( context, handle.Handle, resultMask, metadata); } } #endif // return metadata. return metadata; } } /// /// /// This method must not be called without first acquiring /// public void AddReferences(IDictionary> references) { if (references == null) throw new ArgumentNullException(nameof(references)); lock (m_lock) { IEnumerator>> enumerator = references.GetEnumerator(); while (enumerator.MoveNext()) { ILocalNode actualNode = GetLocalNode(enumerator.Current.Key) as ILocalNode; if (actualNode != null) { foreach (IReference reference in enumerator.Current.Value) { AddReference(actualNode, reference.ReferenceTypeId, reference.IsInverse, reference.TargetId); } } } } } /// public void Read( OperationContext context, double maxAge, IList nodesToRead, IList values, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (nodesToRead == null) throw new ArgumentNullException(nameof(nodesToRead)); if (values == null) throw new ArgumentNullException(nameof(values)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < nodesToRead.Count; ii++) { ReadValueId nodeToRead = nodesToRead[ii]; // skip items that have already been processed. if (nodeToRead.Processed) { continue; } // look up the node. ILocalNode node = GetLocalNode(nodeToRead.NodeId) as ILocalNode; if (node == null) { continue; } DataValue value = values[ii] = new DataValue(); value.Value = null; value.ServerTimestamp = DateTime.UtcNow; value.SourceTimestamp = DateTime.MinValue; value.StatusCode = StatusCodes.BadAttributeIdInvalid; // owned by this node manager. nodeToRead.Processed = true; // read the default value (also verifies that the attribute id is valid for the node). ServiceResult error = node.Read(context, nodeToRead.AttributeId, value); if (ServiceResult.IsBad(error)) { errors[ii] = error; continue; } // always use default value for base attributes. bool useDefault = false; switch (nodeToRead.AttributeId) { case Attributes.NodeId: case Attributes.NodeClass: case Attributes.BrowseName: { useDefault = true; break; } } if (useDefault) { errors[ii] = error; continue; } // apply index range to value attributes. if (nodeToRead.AttributeId == Attributes.Value) { object defaultValue = value.Value; error = nodeToRead.ParsedIndexRange.ApplyRange(ref defaultValue); if (ServiceResult.IsBad(error)) { value.Value = null; errors[ii] = error; continue; } // apply data encoding. if (!QualifiedName.IsNull(nodeToRead.DataEncoding)) { error = EncodeableObject.ApplyDataEncoding(Server.MessageContext, nodeToRead.DataEncoding, ref defaultValue); if (ServiceResult.IsBad(error)) { value.Value = null; errors[ii] = error; continue; } } value.Value = defaultValue; // don't replace timestamp if it was set in the NodeSource if (value.SourceTimestamp == DateTime.MinValue) { value.SourceTimestamp = DateTime.UtcNow; } } } } } /// public void HistoryRead( OperationContext context, HistoryReadDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, IList nodesToRead, IList results, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (details == null) throw new ArgumentNullException(nameof(details)); if (nodesToRead == null) throw new ArgumentNullException(nameof(nodesToRead)); if (results == null) throw new ArgumentNullException(nameof(results)); if (errors == null) throw new ArgumentNullException(nameof(errors)); ReadRawModifiedDetails readRawModifiedDetails = details as ReadRawModifiedDetails; ReadAtTimeDetails readAtTimeDetails = details as ReadAtTimeDetails; ReadProcessedDetails readProcessedDetails = details as ReadProcessedDetails; ReadEventDetails readEventDetails = details as ReadEventDetails; lock (m_lock) { for (int ii = 0; ii < nodesToRead.Count; ii++) { HistoryReadValueId nodeToRead = nodesToRead[ii]; // skip items that have already been processed. if (nodeToRead.Processed) { continue; } // look up the node. ILocalNode node = GetLocalNode(nodeToRead.NodeId) as ILocalNode; if (node == null) { continue; } // owned by this node manager. nodeToRead.Processed = true; errors[ii] = StatusCodes.BadNotReadable; } } } /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public void Write( OperationContext context, IList nodesToWrite, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (nodesToWrite == null) throw new ArgumentNullException(nameof(nodesToWrite)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < nodesToWrite.Count; ii++) { WriteValue nodeToWrite = nodesToWrite[ii]; // skip items that have already been processed. if (nodeToWrite.Processed) { continue; } // look up the node. ILocalNode node = GetLocalNode(nodeToWrite.NodeId) as ILocalNode; if (node == null) { continue; } // owned by this node manager. nodeToWrite.Processed = true; if (!node.SupportsAttribute(nodeToWrite.AttributeId)) { errors[ii] = StatusCodes.BadAttributeIdInvalid; continue; } // fetch the node metadata. NodeMetadata metadata = GetNodeMetadata(context, node, BrowseResultMask.All); // check access. bool writeable = true; ServiceResult error = null; // determine access rights. switch (nodeToWrite.AttributeId) { case Attributes.NodeId: case Attributes.NodeClass: case Attributes.AccessLevel: case Attributes.UserAccessLevel: case Attributes.Executable: case Attributes.UserExecutable: case Attributes.EventNotifier: { writeable = false; break; } case Attributes.Value: { writeable = ((metadata.AccessLevel & AccessLevels.CurrentWrite)!= 0); break; } default: { writeable = (metadata.WriteMask & Attributes.GetMask(nodeToWrite.AttributeId)) != 0; break; } } // error if not writeable. if (!writeable) { errors[ii] = StatusCodes.BadNotWritable; continue; } // determine expected datatype and value rank. NodeId expectedDatatypeId = metadata.DataType; int expectedValueRank = metadata.ValueRank; if (nodeToWrite.AttributeId != Attributes.Value) { expectedDatatypeId = Attributes.GetDataTypeId(nodeToWrite.AttributeId); DataValue value = nodeToWrite.Value; if (value.StatusCode != StatusCodes.Good || value.ServerTimestamp != DateTime.MinValue || value.SourceTimestamp != DateTime.MinValue) { errors[ii] = StatusCodes.BadWriteNotSupported; continue; } expectedValueRank = ValueRanks.Scalar; if (nodeToWrite.AttributeId == Attributes.ArrayDimensions) { expectedValueRank = ValueRanks.OneDimension; } } // check whether value being written is an instance of the expected data type. object valueToWrite = nodeToWrite.Value.Value; TypeInfo typeInfo = TypeInfo.IsInstanceOfDataType( valueToWrite, expectedDatatypeId, expectedValueRank, m_server.NamespaceUris, m_server.TypeTree); if (typeInfo == null) { errors[ii] = StatusCodes.BadTypeMismatch; continue; } // check index range. if (nodeToWrite.ParsedIndexRange.Count > 0) { // check index range for scalars. if (typeInfo.ValueRank < 0) { errors[ii] = StatusCodes.BadIndexRangeInvalid; continue; } // check index range for arrays. else { Array array = (Array)valueToWrite; if (nodeToWrite.ParsedIndexRange.Count != array.Length) { errors[ii] = StatusCodes.BadIndexRangeInvalid; continue; } } } // write the default value. error = node.Write(nodeToWrite.AttributeId, nodeToWrite.Value); if (ServiceResult.IsBad(error)) { errors[ii] = error; continue; } } } } /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public void HistoryUpdate( OperationContext context, Type detailsType, IList nodesToUpdate, IList results, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (nodesToUpdate == null) throw new ArgumentNullException(nameof(nodesToUpdate)); if (results == null) throw new ArgumentNullException(nameof(results)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < nodesToUpdate.Count; ii++) { HistoryUpdateDetails nodeToUpdate = nodesToUpdate[ii]; // skip items that have already been processed. if (nodeToUpdate.Processed) { continue; } // look up the node. ILocalNode node = GetLocalNode(nodeToUpdate.NodeId) as ILocalNode; if (node == null) { continue; } // owned by this node manager. nodeToUpdate.Processed = true; errors[ii] = StatusCodes.BadNotWritable; } } } /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public void Call( OperationContext context, IList methodsToCall, IList results, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (methodsToCall == null) throw new ArgumentNullException(nameof(methodsToCall)); if (results == null) throw new ArgumentNullException(nameof(results)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < methodsToCall.Count; ii++) { CallMethodRequest methodToCall = methodsToCall[ii]; // skip items that have already been processed. if (methodToCall.Processed) { continue; } // look up the node. ILocalNode node = GetLocalNode(methodToCall.ObjectId) as ILocalNode; if (node == null) { continue; } methodToCall.Processed = true; // look up the method. ILocalNode method = GetLocalNode(methodToCall.MethodId) as ILocalNode; if (method == null) { errors[ii] = ServiceResult.Create(StatusCodes.BadMethodInvalid, "Method is not in the address space."); continue; } // check that the method is defined for the object. if (!node.References.Exists(ReferenceTypeIds.HasComponent, false, methodToCall.MethodId, true, m_server.TypeTree)) { errors[ii] = ServiceResult.Create(StatusCodes.BadMethodInvalid, "Method is not a component of the Object."); continue; } errors[ii] = StatusCodes.BadNotImplemented; } } } /// public ServiceResult SubscribeToEvents( OperationContext context, object sourceId, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe) { if (context == null) throw new ArgumentNullException(nameof(context)); if (sourceId == null) throw new ArgumentNullException(nameof(sourceId)); if (monitoredItem == null) throw new ArgumentNullException(nameof(monitoredItem)); lock (m_lock) { // validate the node. NodeMetadata metadata = GetNodeMetadata(context, sourceId, BrowseResultMask.NodeClass); if (metadata == null) { return StatusCodes.BadNodeIdUnknown; } // validate the node class. if (((metadata.NodeClass & NodeClass.Object | NodeClass.View)) == 0) { return StatusCodes.BadNotSupported; } // check that it supports events. if ((metadata.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) { return StatusCodes.BadNotSupported; } return ServiceResult.Good; } } /// public ServiceResult SubscribeToAllEvents( OperationContext context, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe) { if (context == null) throw new ArgumentNullException(nameof(context)); if (monitoredItem == null) throw new ArgumentNullException(nameof(monitoredItem)); return ServiceResult.Good; } /// public ServiceResult ConditionRefresh( OperationContext context, IList monitoredItems) { if (context == null) throw new ArgumentNullException(nameof(context)); return ServiceResult.Good; } /// /// Creates a set of monitored items. /// public void CreateMonitoredItems( OperationContext context, uint subscriptionId, double publishingInterval, TimestampsToReturn timestampsToReturn, IList itemsToCreate, IList errors, IList filterErrors, IList monitoredItems, ref long globalIdCounter) { 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 (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); lock (m_lock) { for (int ii = 0; ii < errors.Count; ii++) { MonitoredItemCreateRequest itemToCreate = itemsToCreate[ii]; // skip items that have already been processed. if (itemToCreate.Processed) { continue; } // look up the node. ILocalNode node = this.GetLocalNode(itemToCreate.ItemToMonitor.NodeId) as ILocalNode; if (node == null) { continue; } // owned by this node manager. itemToCreate.Processed = true; if (!node.SupportsAttribute(itemToCreate.ItemToMonitor.AttributeId)) { errors[ii] = StatusCodes.BadAttributeIdInvalid; continue; } // fetch the metadata for the node. NodeMetadata metadata = GetNodeMetadata(context, node, BrowseResultMask.All); if (itemToCreate.ItemToMonitor.AttributeId == Attributes.Value) { if ((metadata.AccessLevel & AccessLevels.CurrentRead) == 0) { errors[ii] = StatusCodes.BadNotReadable; continue; } } // check value rank against index range. if (itemToCreate.ItemToMonitor.ParsedIndexRange != NumericRange.Empty) { int valueRank = metadata.ValueRank; if (itemToCreate.ItemToMonitor.AttributeId != Attributes.Value) { valueRank = Attributes.GetValueRank(itemToCreate.ItemToMonitor.AttributeId); } if (valueRank == ValueRanks.Scalar) { errors[ii] = StatusCodes.BadIndexRangeInvalid; continue; } } bool rangeRequired = false; // validate the filter against the node/attribute being monitored. errors[ii] = ValidateFilter( metadata, itemToCreate.ItemToMonitor.AttributeId, itemToCreate.RequestedParameters.Filter, out rangeRequired); if (ServiceResult.IsBad(errors[ii])) { continue; } // lookup EU range if required. Range range = null; if (rangeRequired) { errors[ii] = ReadEURange(context, node, out range); if (ServiceResult.IsBad(errors[ii])) { continue; } } // create a globally unique identifier. uint monitoredItemId = Utils.IncrementIdentifier(ref globalIdCounter); // limit the sampling rate for non-value attributes. double minimumSamplingInterval = m_defaultMinimumSamplingInterval; if (itemToCreate.ItemToMonitor.AttributeId == Attributes.Value) { // use the MinimumSamplingInterval attribute to limit the sampling rate for value attributes. IVariable variableNode = node as IVariable; if (variableNode != null) { minimumSamplingInterval = variableNode.MinimumSamplingInterval; // use the default if the node does not specify one. if (minimumSamplingInterval < 0) { minimumSamplingInterval = m_defaultMinimumSamplingInterval; } } } // create monitored item. MonitoredItem monitoredItem = m_samplingGroupManager.CreateMonitoredItem( context, subscriptionId, publishingInterval, timestampsToReturn, monitoredItemId, node, itemToCreate, range, minimumSamplingInterval); // save monitored item. m_monitoredItems.Add(monitoredItem.Id, monitoredItem); // update monitored item list. monitoredItems[ii] = monitoredItem; // read the initial value. DataValue initialValue = new DataValue(); initialValue.ServerTimestamp = DateTime.UtcNow; initialValue.StatusCode = StatusCodes.BadWaitingForInitialData; ServiceResult error = node.Read(context, itemToCreate.ItemToMonitor.AttributeId, initialValue); if (ServiceResult.IsBad(error)) { initialValue.Value = null; initialValue.StatusCode = error.StatusCode; } monitoredItem.QueueValue(initialValue, error); // errors updating the monitoring groups will be reported in notifications. errors[ii] = StatusCodes.Good; } } // update all groups with any new items. m_samplingGroupManager.ApplyChanges(); } /// /// Modifies a set of monitored items. /// public void ModifyMonitoredItems( OperationContext context, TimestampsToReturn timestampsToReturn, IList monitoredItems, IList itemsToModify, IList errors, IList filterErrors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); if (itemsToModify == null) throw new ArgumentNullException(nameof(itemsToModify)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < errors.Count; ii++) { MonitoredItemModifyRequest itemToModify = itemsToModify[ii]; // skip items that have already been processed. if (itemToModify.Processed || monitoredItems[ii] == null) { continue; } // check if the node manager created the item. if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager)) { continue; } // owned by this node manager. itemToModify.Processed = true; // validate monitored item. MonitoredItem monitoredItem = null; if (!m_monitoredItems.TryGetValue(monitoredItems[ii].Id, out monitoredItem)) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } if (!Object.ReferenceEquals(monitoredItem, monitoredItems[ii])) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } // find the node being monitored. ILocalNode node = monitoredItem.ManagerHandle as ILocalNode; if (node == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; continue; } // fetch the metadata for the node. NodeMetadata metadata = GetNodeMetadata(context, monitoredItem.ManagerHandle, BrowseResultMask.All); bool rangeRequired = false; // validate the filter against the node/attribute being monitored. errors[ii] = ValidateFilter( metadata, monitoredItem.AttributeId, itemToModify.RequestedParameters.Filter, out rangeRequired); if (ServiceResult.IsBad(errors[ii])) { continue; } // lookup EU range if required. Range range = null; if (rangeRequired) { // look up EU range. errors[ii] = ReadEURange(context, node, out range); if (ServiceResult.IsBad(errors[ii])) { continue; } } // update sampling. errors[ii] = m_samplingGroupManager.ModifyMonitoredItem( context, timestampsToReturn, monitoredItem, itemToModify, range); // state of item did not change if an error returned here. if (ServiceResult.IsBad(errors[ii])) { continue; } // item has been modified successfully. // errors updating the sampling groups will be reported in notifications. errors[ii] = StatusCodes.Good; } } // update all sampling groups. m_samplingGroupManager.ApplyChanges(); } /// /// Deletes a set of monitored items. /// public void DeleteMonitoredItems( OperationContext context, IList monitoredItems, IList processedItems, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < errors.Count; ii++) { // skip items that have already been processed. if (processedItems[ii] || monitoredItems[ii] == null) { continue; } // check if the node manager created the item. if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager)) { continue; } // owned by this node manager. processedItems[ii] = true; // validate monitored item. MonitoredItem monitoredItem = null; if (!m_monitoredItems.TryGetValue(monitoredItems[ii].Id, out monitoredItem)) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } if (!Object.ReferenceEquals(monitoredItem, monitoredItems[ii])) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } // remove item. m_samplingGroupManager.StopMonitoring(monitoredItem); // remove association with the group. m_monitoredItems.Remove(monitoredItem.Id); // delete successful. errors[ii] = StatusCodes.Good; } } // remove all items from groups. m_samplingGroupManager.ApplyChanges(); } /// /// Changes the monitoring mode for a set of monitored items. /// public void SetMonitoringMode( OperationContext context, MonitoringMode monitoringMode, IList monitoredItems, IList processedItems, IList errors) { if (context == null) throw new ArgumentNullException(nameof(context)); if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems)); if (errors == null) throw new ArgumentNullException(nameof(errors)); lock (m_lock) { for (int ii = 0; ii < errors.Count; ii++) { // skip items that have already been processed. if (processedItems[ii] || monitoredItems[ii] == null) { continue; } // check if the node manager created the item. if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager)) { continue; } // owned by this node manager. processedItems[ii] = true; // validate monitored item. MonitoredItem monitoredItem = null; if (!m_monitoredItems.TryGetValue(monitoredItems[ii].Id, out monitoredItem)) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } if (!Object.ReferenceEquals(monitoredItem, monitoredItems[ii])) { errors[ii] = StatusCodes.BadMonitoredItemIdInvalid; continue; } // update monitoring mode. MonitoringMode previousMode = monitoredItem.SetMonitoringMode(monitoringMode); // need to provide an immediate update after enabling. if (previousMode == MonitoringMode.Disabled && monitoringMode != MonitoringMode.Disabled) { DataValue initialValue = new DataValue(); initialValue.ServerTimestamp = DateTime.UtcNow; initialValue.StatusCode = StatusCodes.BadWaitingForInitialData; // read the initial value. Node node = monitoredItem.ManagerHandle as Node; if (node != null) { ServiceResult error = node.Read(context, monitoredItem.AttributeId, initialValue); if (ServiceResult.IsBad(error)) { initialValue.Value = null; initialValue.StatusCode = error.StatusCode; } } monitoredItem.QueueValue(initialValue, null); } // modify the item attributes. m_samplingGroupManager.ModifyMonitoring(context, monitoredItem); // item has been modified successfully. // errors updating the sampling groups will be reported in notifications. errors[ii] = StatusCodes.Good; } } // update all sampling groups. m_samplingGroupManager.ApplyChanges(); } #endregion #region Static Members /// /// Returns true if the node class matches the node class mask. /// public static bool CheckNodeClassMask(uint nodeClassMask, NodeClass nodeClass) { if (nodeClassMask != 0) { return ((uint)nodeClass & nodeClassMask) != 0; } return true; } #endregion #region Protected Members /// /// The server that the node manager belongs to. /// protected IServerInternal Server { get { return m_server; } } #endregion #region Browsing/Searching /// /// Returns an index for the NamespaceURI (Adds it to the server namespace table if it does not already exist). /// /// /// Returns the server's default index (1) if the namespaceUri is empty or null. /// public ushort GetNamespaceIndex(string namespaceUri) { int namespaceIndex = 1; if (!String.IsNullOrEmpty(namespaceUri)) { namespaceIndex = m_server.NamespaceUris.GetIndex(namespaceUri); if (namespaceIndex == -1) { namespaceIndex = m_server.NamespaceUris.Append(namespaceUri); } } return (ushort)namespaceIndex; } /// /// Returns all targets of the specified reference. /// public NodeIdCollection FindLocalNodes(NodeId sourceId, NodeId referenceTypeId, bool isInverse) { if (sourceId == null) throw new ArgumentNullException(nameof(sourceId)); if (referenceTypeId == null) throw new ArgumentNullException(nameof(referenceTypeId)); lock (m_lock) { ILocalNode source = GetManagerHandle(sourceId) as ILocalNode; if (source == null) { return null; } NodeIdCollection targets = new NodeIdCollection(); foreach (IReference reference in source.References) { if (reference.IsInverse != isInverse || !m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId)) { continue; } ExpandedNodeId targetId = reference.TargetId; if (targetId.IsAbsolute) { continue; } targets.Add((NodeId)targetId); } return targets; } } /// /// Returns the id the first node with the specified browse name if it exists. null otherwise /// public NodeId FindTargetId(NodeId sourceId, NodeId referenceTypeId, bool isInverse, QualifiedName browseName) { if (sourceId == null) throw new ArgumentNullException(nameof(sourceId)); if (referenceTypeId == null) throw new ArgumentNullException(nameof(referenceTypeId)); lock (m_lock) { ILocalNode source = GetManagerHandle(sourceId) as ILocalNode; if (source == null) { return null; } foreach (ReferenceNode reference in source.References) { if (reference.IsInverse != isInverse || !m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId)) { continue; } ExpandedNodeId targetId = reference.TargetId; if (targetId.IsAbsolute) { continue; } ILocalNode target = GetManagerHandle((NodeId)targetId) as ILocalNode; if (target == null) { continue; } if (QualifiedName.IsNull(browseName) || target.BrowseName == browseName) { return (NodeId)targetId; } } return null; } } /// /// Returns the first target that matches the browse path. /// public NodeId Find(NodeId sourceId, string browsePath) { IList targets = TranslateBrowsePath(sourceId, browsePath); if (targets.Count > 0) { return targets[0]; } return null; } /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( OperationContext context, NodeId sourceId, string browsePath) { return TranslateBrowsePath(context, sourceId, RelativePath.Parse(browsePath, m_server.TypeTree)); } /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( NodeId sourceId, string browsePath) { return TranslateBrowsePath(null, sourceId, RelativePath.Parse(browsePath, m_server.TypeTree)); } /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( NodeId sourceId, RelativePath relativePath) { return TranslateBrowsePath(null, sourceId, relativePath); } /// /// Returns a list of targets the match the browse path. /// public IList TranslateBrowsePath( OperationContext context, NodeId sourceId, RelativePath relativePath) { List targets = new List(); if (relativePath == null || relativePath.Elements.Count == 0) { targets.Add(sourceId); return targets; } // look up source in this node manager. ILocalNode source = null; lock (m_lock) { source = GetLocalNode(sourceId) as ILocalNode; if (source == null) { return targets; } } // return the set of matching targets. return targets; } #endregion #region Registering Data/Event Sources /// /// Registers a source for a node. /// /// /// The source could be one or more of IDataSource, IEventSource, ICallable, IHistorian or IViewManager /// public void RegisterSource(NodeId nodeId, object source, object handle, bool isEventSource) { if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); if (source == null) throw new ArgumentNullException(nameof(source)); } /// /// Called when the source is no longer used. /// /// /// When a source disappears it must either delete all of its nodes from the address space /// or unregister itself their source by calling RegisterSource with source == null. /// After doing that the source must call this method. /// public void UnregisterSource(object source) { } #endregion #region Adding/Removing Nodes #region Apply Modelling Rules /// /// Applys the modelling rules to any existing instance. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public void ApplyModellingRules( ILocalNode instance, ILocalNode typeDefinition, ILocalNode templateDeclaration, ushort namespaceIndex) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (typeDefinition == null) throw new ArgumentNullException(nameof(typeDefinition)); // check existing type definition. UpdateTypeDefinition(instance, typeDefinition.NodeId); // create list of declarations for the type definition (recursively collects definitions from supertypes). List declarations = new List(); BuildDeclarationList(typeDefinition, declarations); // add instance declaration if provided. if (templateDeclaration != null) { DeclarationNode declaration = new DeclarationNode(); declaration.Node = templateDeclaration; declaration.BrowsePath = String.Empty; declarations.Add(declaration); BuildDeclarationList(templateDeclaration, declarations); } // build list of instances to create. List typeDefinitions = new List(); SortedDictionary instanceDeclarations = new SortedDictionary(); SortedDictionary possibleTargets = new SortedDictionary(); // create instances from declarations. // subtypes appear in list last so traversing the list backwards find the overridden nodes first. for (int ii = declarations.Count-1; ii >= 0; ii--) { DeclarationNode declaration = declarations[ii]; // update type definition list. if (String.IsNullOrEmpty(declaration.BrowsePath)) { typeDefinitions.Add(declaration.Node); continue; } // skip declaration if instance already exists. // (i.e. the declaration was overridden). if (instanceDeclarations.ContainsKey(declaration.BrowsePath)) { continue; } // update instance declaration list. instanceDeclarations[declaration.BrowsePath] = declaration.Node; // save the node as a possible target of references. possibleTargets[declaration.Node.NodeId] = declaration.Node; } // build list of instances that already exist. SortedDictionary existingInstances = new SortedDictionary(); BuildInstanceList(instance, String.Empty, existingInstances); // maps the instance declaration onto an instance node. Dictionary instancesToCreate = new Dictionary(); // apply modelling rules to instance declarations. foreach (KeyValuePair current in instanceDeclarations) { string browsePath = current.Key; ILocalNode instanceDeclaration = current.Value; // check if the same instance has multiple browse paths to it. ILocalNode newInstance = null; if (instancesToCreate.TryGetValue(instanceDeclaration.NodeId, out newInstance)) { continue; } // check for an existing instance. if (existingInstances.TryGetValue(browsePath, out newInstance)) { continue; } // apply modelling rule to determine whether to create a new instance. NodeId modellingRule = instanceDeclaration.ModellingRule; // always create a new instance if one does not already exist. if (modellingRule == Objects.ModellingRule_Mandatory) { if (newInstance == null) { newInstance = instanceDeclaration.CreateCopy(CreateUniqueNodeId()); AddNode(newInstance); } } // ignore optional instances unless one has been specified in the existing tree. else if (modellingRule == Objects.ModellingRule_Optional) { if (newInstance == null) { continue; } } // ignore any unknown modelling rules. else { continue; } // save the mapping between the instance declaration and the new instance. instancesToCreate[instanceDeclaration.NodeId] = newInstance; } // add references from type definitions to top level. foreach (ILocalNode type in typeDefinitions) { foreach (IReference reference in type.References) { // ignore external references from type. if (reference.TargetId.IsAbsolute) { continue; } // ignore subtype references. if (m_nodes.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype)) { continue; } // ignore targets that are not in the instance tree. ILocalNode target = null; if (!instancesToCreate.TryGetValue((NodeId)reference.TargetId, out target)) { continue; } // add forward and backward reference. AddReference(instance, reference.ReferenceTypeId, reference.IsInverse, target, true); } } // add references between instance declarations. foreach (ILocalNode instanceDeclaration in instanceDeclarations.Values) { // find the source for the references. ILocalNode source = null; if (!instancesToCreate.TryGetValue(instanceDeclaration.NodeId, out source)) { continue; } // check if the source is a shared node. bool sharedNode = Object.ReferenceEquals(instanceDeclaration, source); foreach (IReference reference in instanceDeclaration.References) { // add external reference. if (reference.TargetId.IsAbsolute) { if (!sharedNode) { AddReference(source, reference.ReferenceTypeId, reference.IsInverse, reference.TargetId); } continue; } // check for modelling rule. if (reference.ReferenceTypeId == ReferenceTypeIds.HasModellingRule) { if (!source.References.Exists(ReferenceTypeIds.HasModellingRule, false, reference.TargetId, false, null)) { AddReference(source, reference.ReferenceTypeId, false, reference.TargetId); } continue; } // check for type definition. if (reference.ReferenceTypeId == ReferenceTypeIds.HasTypeDefinition) { if (!sharedNode) { UpdateTypeDefinition(source, instanceDeclaration.TypeDefinitionId); } continue; } // add targets that are not in the instance tree. ILocalNode target = null; if (!instancesToCreate.TryGetValue((NodeId)reference.TargetId, out target)) { // don't update shared nodes because the reference should already exist. if (sharedNode) { continue; } // top level references to the type definition node were already added. if (reference.TargetId == typeDefinition.NodeId) { continue; } // see if a reference is allowed. if (!IsExternalReferenceAllowed(reference.ReferenceTypeId)) { continue; } // add one way reference. source.References.Add(reference.ReferenceTypeId, reference.IsInverse, reference.TargetId); continue; } // add forward and backward reference. AddReference(source, reference.ReferenceTypeId, reference.IsInverse, target, true); } } } /// /// Returns true if a one-way reference to external nodes is permitted. /// private bool IsExternalReferenceAllowed(NodeId referenceTypeId) { // always exclude hierarchial references. if (m_nodes.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HierarchicalReferences)) { return false; } // allow one way reference to event. if (m_nodes.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypes.GeneratesEvent)) { return true; } // all other references not permitted. return false; } /// /// Updates the type definition for a node. /// private void UpdateTypeDefinition(ILocalNode instance, ExpandedNodeId typeDefinitionId) { // check existing type definition. ExpandedNodeId existingTypeId = instance.TypeDefinitionId; if (existingTypeId == typeDefinitionId) { return; } if (!NodeId.IsNull(existingTypeId)) { if (m_nodes.TypeTree.IsTypeOf(existingTypeId, typeDefinitionId)) { throw ServiceResultException.Create( StatusCodes.BadTypeDefinitionInvalid, "Type definition {0} is not a subtype of the existing type definition {1}.", existingTypeId, typeDefinitionId); } DeleteReference(instance, ReferenceTypeIds.HasTypeDefinition, false, existingTypeId, false); } AddReference(instance, ReferenceTypeIds.HasTypeDefinition, false, typeDefinitionId); } /// /// A node in the type system that is used to instantiate objects or variables. /// private class DeclarationNode { public ILocalNode Node; public string BrowsePath; } /// /// Builds the list of declaration nodes for a type definition. /// private void BuildDeclarationList(ILocalNode typeDefinition, List declarations) { if (typeDefinition == null) throw new ArgumentNullException(nameof(typeDefinition)); if (declarations == null) throw new ArgumentNullException(nameof(declarations)); // guard against loops (i.e. common grandparents). for (int ii = 0; ii < declarations.Count; ii++) { if (Object.ReferenceEquals(declarations[ii].Node, typeDefinition)) { return; } } // create the root declaration for the type. DeclarationNode declaration = new DeclarationNode(); declaration.Node = typeDefinition; declaration.BrowsePath = String.Empty; declarations.Add(declaration); // follow references to supertypes first. foreach (IReference reference in typeDefinition.References.Find(ReferenceTypeIds.HasSubtype, true, false, null)) { ILocalNode supertype = GetLocalNode(reference.TargetId) as ILocalNode; if (supertype == null) { continue; } BuildDeclarationList(supertype, declarations); } // add children of type. BuildDeclarationList(declaration, declarations); } /// /// Builds a list of declarations from the nodes aggregated by a parent. /// private void BuildDeclarationList(DeclarationNode parent, List declarations) { if (parent == null) throw new ArgumentNullException(nameof(parent)); if (declarations == null) throw new ArgumentNullException(nameof(declarations)); // get list of children. IList references = parent.Node.References.Find(ReferenceTypeIds.HierarchicalReferences, false, true, m_nodes.TypeTree); foreach (IReference reference in references) { // do not follow sub-type references. if (m_nodes.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype)) { continue; } // find child (ignore children that are not in the node table). ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode; if (child == null) { continue; } // create the declartion node. DeclarationNode declaration = new DeclarationNode(); declaration.Node = child; declaration.BrowsePath = Utils.Format("{0}.{1}", parent.BrowsePath, child.BrowseName); declarations.Add(declaration); // recursively include aggregated children. NodeId modellingRule = child.ModellingRule; if (modellingRule == Objects.ModellingRule_Mandatory || modellingRule == Objects.ModellingRule_Optional) { BuildDeclarationList(declaration, declarations); } } } /// /// Builds a table of instances indexed by browse path from the nodes aggregated by a parent /// private void BuildInstanceList(ILocalNode parent, string browsePath, IDictionary instances) { if (parent == null) throw new ArgumentNullException(nameof(parent)); if (instances == null) throw new ArgumentNullException(nameof(instances)); // guard against loops. if (instances.ContainsKey(browsePath)) { return; } // index parent by browse path. instances[browsePath] = parent; // get list of children. IList references = parent.References.Find(ReferenceTypeIds.HierarchicalReferences, false, true, m_nodes.TypeTree); foreach (IReference reference in references) { // find child (ignore children that are not in the node table). ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode; if (child == null) { continue; } // recursively include aggregated children. BuildInstanceList(child, Utils.Format("{0}.{1}", browsePath, child.BrowseName), instances); } } #endregion /// /// Exports a node to a nodeset. /// public void ExportNode(NodeId nodeId, NodeSet nodeSet) { lock (m_lock) { ILocalNode node = GetLocalNode(nodeId) as ILocalNode; if (node == null) { throw ServiceResultException.Create(StatusCodes.BadNodeIdUnknown, "NodeId ({0}) does not exist.", nodeId); } ExportNode(node, nodeSet, (node.NodeClass & (NodeClass.Object | NodeClass.Variable)) != 0); } } /// /// Exports a node to a nodeset. /// public void ExportNode(ILocalNode node, NodeSet nodeSet, bool instance) { lock (m_lock) { // check if the node has already been added. NodeId exportedId = nodeSet.Export(node.NodeId, m_nodes.NamespaceUris); if (nodeSet.Contains(exportedId)) { return; } // add to nodeset. Node nodeToExport = nodeSet.Add(node, m_nodes.NamespaceUris, m_nodes.ServerUris); // follow children. foreach (ReferenceNode reference in node.References) { // export all references. bool export = true; // unless it is a subtype reference. if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype)) { export = false; } if (export) { nodeSet.AddReference(nodeToExport, reference, m_nodes.NamespaceUris, m_nodes.ServerUris); } if (reference.IsInverse || m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype)) { nodeSet.AddReference(nodeToExport, reference, m_nodes.NamespaceUris, m_nodes.ServerUris); } if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.Aggregates)) { if (reference.IsInverse) { continue; } ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode; if (child != null) { if (instance) { NodeId modellingRule = child.ModellingRule; if (modellingRule != Objects.ModellingRule_Mandatory) { continue; } } ExportNode(child, nodeSet, instance); } } } } } #if XXX /// /// Changes the type definition for an instance. /// public void ChangeTypeDefinition( NodeId instanceId, NodeId typeDefinitionId) { try { m_lock.Enter(); // find the instance. ILocalNode instance = GetLocalNode(instanceId) as ILocalNode; if (instance == null) { throw ServiceResultException.Create(StatusCodes.BadNodeIdUnknown, "NodeId ({0}) does not exist.", instanceId); } // check node class. if (instance.NodeClass != NodeClass.Object && instance.NodeClass != NodeClass.Variable) { throw ServiceResultException.Create(StatusCodes.BadNodeClassInvalid, "Node (NodeClass={0}) cannot have a type definition.", instance.NodeClass); } // get current type definition. ExpandedNodeId existingTypeId = instance.TypeDefinitionId; if (existingTypeId == typeDefinitionId) { return; } // can only change to a subtype of the existing type definition. if (!m_server.TypeTree.IsTypeOf(typeDefinitionId, existingTypeId)) { throw ServiceResultException.Create(StatusCodes.BadTypeDefinitionInvalid, "Type definition ({0}) must be a must subtype of the existing type definition ({1}).", typeDefinitionId, existingTypeId); } // find the type definition node. ILocalNode typeDefinition = GetLocalNode(typeDefinitionId) as ILocalNode; if (typeDefinition == null) { throw ServiceResultException.Create(StatusCodes.BadTypeDefinitionInvalid, "TypeDefinitionId ({0}) does not exist.", typeDefinitionId); } // apply modelling rules. NodeFactory factory = new NodeFactory(m_nodes); IList nodesToAdd = factory.ApplyModellingRules(instance, typeDefinition.NodeId, ref m_lastId, 1); // add the nodes. foreach (Node nodeToAdd in nodesToAdd) { AddNode(nodeToAdd); } } finally { m_lock.Exit(); } } #endif /// /// Updates the attributes for the node. /// private static void UpdateAttributes(ILocalNode node, NodeAttributes attributes) { // DisplayName if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.DisplayName) != 0) { node.DisplayName = attributes.DisplayName; if (node.DisplayName == null) { node.DisplayName = new LocalizedText(node.BrowseName.Name); } } else { node.DisplayName = new LocalizedText(node.BrowseName.Name); } // Description if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.Description) != 0) { node.Description = attributes.Description; } // WriteMask if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.WriteMask) != 0) { node.WriteMask = (AttributeWriteMask)attributes.WriteMask; } else { node.WriteMask = AttributeWriteMask.None; } // WriteMask if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.UserWriteMask) != 0) { node.UserWriteMask = (AttributeWriteMask)attributes.UserWriteMask; } else { node.UserWriteMask = AttributeWriteMask.None; } } /// /// Deletes a node from the address sapce. /// public void DeleteNode(NodeId nodeId, bool deleteChildren, bool silent) { if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); // find the node to delete. ILocalNode node = GetManagerHandle(nodeId) as ILocalNode; if (node == null) { if (!silent) { throw ServiceResultException.Create(StatusCodes.BadSourceNodeIdInvalid, "Node '{0}' does not exist.", nodeId); } return; } bool instance = (node.NodeClass & (NodeClass.Object | NodeClass.Variable)) != 0; Dictionary> referencesToDelete = new Dictionary>(); if (silent) { try { DeleteNode(node, deleteChildren, instance, referencesToDelete); } catch (Exception e) { Utils.Trace(e, "Error deleting node: {0}", nodeId); } } else { DeleteNode(node, deleteChildren, instance, referencesToDelete); } if (referencesToDelete.Count > 0) { Task.Run(() => { OnDeleteReferences(referencesToDelete); }); } } /// /// Deletes a node from the address sapce. /// private void DeleteNode(ILocalNode node, bool deleteChildren, bool instance, Dictionary> referencesToDelete) { if (node == null) throw new ArgumentNullException(nameof(node)); List nodesToDelete = new List(); List referencesForNode = new List(); lock (m_lock) { // remove the node. m_nodes.Remove(node.NodeId); // check need to connect subtypes to the supertype if they are being deleted. ExpandedNodeId supertypeId = m_server.TypeTree.FindSuperType(node.NodeId); if (!NodeId.IsNull(supertypeId)) { m_server.TypeTree.Remove(node.NodeId); } // delete sources. #if LEGACY_NODEMANAGER DeleteRegisteredSources(node); #endif // remove any references to the node. foreach (IReference reference in node.References) { // ignore remote references. if (reference.TargetId.IsAbsolute) { continue; } // find the target. ILocalNode target = GetManagerHandle(reference.TargetId) as ILocalNode; if (target == null) { referencesForNode.Add(reference); continue; } // delete the backward reference. target.References.Remove(reference.ReferenceTypeId, !reference.IsInverse, node.NodeId); // check for children that need to be deleted. if (deleteChildren) { if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.Aggregates) && !reference.IsInverse) { nodesToDelete.Add(target); } } } if (referencesForNode.Count > 0) { referencesToDelete[node.NodeId] = referencesForNode; } } // delete the child nodes. foreach (ILocalNode nodeToDelete in nodesToDelete) { DeleteNode(nodeToDelete, deleteChildren, instance, referencesToDelete); } } /// /// Deletes the external references to a node in a background thread. /// private void OnDeleteReferences(object state) { Dictionary> referencesToDelete = state as Dictionary>; if (state == null) { return; } foreach (KeyValuePair> current in referencesToDelete) { try { m_server.NodeManager.DeleteReferences(current.Key, current.Value); } catch (Exception e) { Utils.Trace(e, "Error deleting references for node: {0}", current.Key); } } } #region Add/Remove Node Support Functions /// /// Verifies that the source and the target meet the restrictions imposed by the reference type. /// private void ValidateReference( ILocalNode source, NodeId referenceTypeId, bool isInverse, NodeClass targetNodeClass) { // find reference type. IReferenceType referenceType = GetLocalNode(referenceTypeId) as IReferenceType; if (referenceType == null) { throw ServiceResultException.Create(StatusCodes.BadReferenceTypeIdInvalid, "Reference type '{0}' does not exist.", referenceTypeId); } // swap the source and target for inverse references. NodeClass sourceNodeClass = source.NodeClass; if (isInverse) { sourceNodeClass = targetNodeClass; targetNodeClass = source.NodeClass; } // check HasComponent references. if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HasComponent)) { if ((sourceNodeClass & (NodeClass.Object | NodeClass.Variable | NodeClass.ObjectType | NodeClass.VariableType)) == 0) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Source node cannot be used with HasComponent references."); } if ((targetNodeClass & (NodeClass.Object | NodeClass.Variable | NodeClass.Method)) == 0) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Target node cannot be used with HasComponent references."); } if (targetNodeClass == NodeClass.Variable) { if ((targetNodeClass & (NodeClass.Variable | NodeClass.VariableType)) == 0) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "A Variable must be a component of an Variable or VariableType."); } } if (targetNodeClass == NodeClass.Method) { if ((sourceNodeClass & (NodeClass.Object | NodeClass.ObjectType)) == 0) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "A Method must be a component of an Object or ObjectType."); } } } // check HasProperty references. if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypes.HasProperty)) { if (targetNodeClass != NodeClass.Variable) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Targets of HasProperty references must be Variables."); } } // check HasSubtype references. if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HasSubtype)) { if ((sourceNodeClass & (NodeClass.DataType | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType)) == 0) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Source node cannot be used with HasSubtype references."); } if (targetNodeClass != sourceNodeClass) { throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "The source and target cannot be connected by a HasSubtype reference."); } } // TBD - check rules for other reference types. } #endregion #endregion #region Adding/Removing References /// /// Adds a reference between two existing nodes. /// public ServiceResult AddReference( NodeId sourceId, NodeId referenceTypeId, bool isInverse, NodeId targetId, bool bidirectional) { lock (m_lock) { // find source. ILocalNode source = GetManagerHandle(sourceId) as ILocalNode; if (source == null) { return StatusCodes.BadParentNodeIdInvalid; } // add reference from target to source. if (bidirectional) { // find target. ILocalNode target = GetManagerHandle(targetId) as ILocalNode; if (target == null) { return StatusCodes.BadNodeIdUnknown; } // ensure the reference is valid. ValidateReference(source, referenceTypeId, isInverse, target.NodeClass); // add reference from target to source. AddReferenceToLocalNode(target, referenceTypeId, !isInverse, sourceId, false); } // add reference from source to target. AddReferenceToLocalNode(source, referenceTypeId, isInverse, targetId, false); return null; } } /// /// Ensures any changes to built-in nodes are reflected in the diagnostics node manager. /// private void AddReferenceToLocalNode( ILocalNode source, NodeId referenceTypeId, bool isInverse, ExpandedNodeId targetId, bool isInternal) { source.References.Add(referenceTypeId, isInverse, targetId); if (!isInternal && source.NodeId.NamespaceIndex == 0) { lock (Server.DiagnosticsNodeManager.Lock) { NodeState state = Server.DiagnosticsNodeManager.FindPredefinedNode(source.NodeId, null); if (state != null) { INodeBrowser browser = state.CreateBrowser( m_server.DefaultSystemContext, null, referenceTypeId, true, (isInverse) ? BrowseDirection.Inverse : BrowseDirection.Forward, null, null, true); bool found = false; for (IReference reference = browser.Next(); reference != null; reference = browser.Next()) { if (reference.TargetId == targetId) { found = true; break; } } if (!found) { state.AddReference(referenceTypeId, isInverse, targetId); } } } } } /// /// Adds a reference between two existing nodes. /// public void CreateReference( NodeId sourceId, NodeId referenceTypeId, bool isInverse, NodeId targetId, bool bidirectional) { lock (m_lock) { ServiceResult result = AddReference(sourceId, referenceTypeId, isInverse, targetId, bidirectional); if (ServiceResult.IsBad(result)) { throw new ServiceResultException(result); } } } /// /// Adds a reference to the address space. /// private void AddReference( ILocalNode source, NodeId referenceTypeId, bool isInverse, ILocalNode target, bool bidirectional) { AddReferenceToLocalNode(source, referenceTypeId, isInverse, target.NodeId, false); if (bidirectional) { AddReferenceToLocalNode(target, referenceTypeId, !isInverse, source.NodeId, false); } } /// /// Adds a reference to the address space. /// private void AddReference( ILocalNode source, NodeId referenceTypeId, bool isInverse, ExpandedNodeId targetId) { AddReferenceToLocalNode(source, referenceTypeId, isInverse, targetId, false); } /// /// Deletes a reference. /// public ServiceResult DeleteReference( object sourceHandle, NodeId referenceTypeId, bool isInverse, ExpandedNodeId targetId, bool deleteBidirectional) { if (sourceHandle == null) throw new ArgumentNullException(nameof(sourceHandle)); if (referenceTypeId == null) throw new ArgumentNullException(nameof(referenceTypeId)); if (targetId == null) throw new ArgumentNullException(nameof(targetId)); lock (m_lock) { ILocalNode source = sourceHandle as ILocalNode; if (source == null) { return StatusCodes.BadSourceNodeIdInvalid; } source.References.Remove(referenceTypeId, isInverse, targetId); if (deleteBidirectional) { ILocalNode target = GetManagerHandle(targetId) as ILocalNode; if (target != null) { target.References.Remove(referenceTypeId, !isInverse, source.NodeId); } } return ServiceResult.Good; } } /// /// Deletes a reference. /// public void DeleteReference( NodeId sourceId, NodeId referenceTypeId, bool isInverse, ExpandedNodeId targetId, bool deleteBidirectional) { ServiceResult result = DeleteReference( GetManagerHandle(sourceId) as ILocalNode, referenceTypeId, isInverse, targetId, deleteBidirectional); if (ServiceResult.IsBad(result)) { throw new ServiceResultException(result); } } /// /// Adds a node to the address space. /// private void AddNode(ILocalNode node) { m_nodes.Attach(node); } #endregion /// /// Returns a node managed by the manager with the specified node id. /// public ILocalNode GetLocalNode(ExpandedNodeId nodeId) { if (nodeId == null) { return null; } // check for absolute declarations of local nodes. if (nodeId.IsAbsolute) { if (nodeId.ServerIndex != 0) { return null; } int namespaceIndex = this.Server.NamespaceUris.GetIndex(nodeId.NamespaceUri); if (namespaceIndex < 0 || nodeId.NamespaceIndex >= this.Server.NamespaceUris.Count) { return null; } return GetLocalNode(new NodeId(nodeId.Identifier, (ushort)namespaceIndex)); } return GetLocalNode((NodeId)nodeId); } /// /// Returns a node managed by the manager with the specified node id. /// public ILocalNode GetLocalNode( NodeId nodeId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes, QualifiedName browseName) { lock (m_lock) { return m_nodes.Find(nodeId, referenceTypeId, isInverse, includeSubtypes, browseName) as ILocalNode; } } /// /// Returns a node managed by the manager with the specified node id. /// public ILocalNode GetLocalNode(NodeId nodeId) { lock (m_lock) { return m_nodes.Find(nodeId) as ILocalNode; } } /// /// Returns a list of nodes which are targets of the specified references. /// public IList GetLocalNodes( NodeId sourceId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes) { lock (m_lock) { List targets = new List(); ILocalNode source = GetLocalNode(sourceId) as ILocalNode; if (source == null) { return targets; } foreach (IReference reference in source.References.Find(referenceTypeId, isInverse, true, m_nodes.TypeTree)) { ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode; if (target != null) { targets.Add(target); } } return targets; } } /// /// Returns a node managed by the manager that has the specified browse name. /// public ILocalNode GetTargetNode( NodeId sourceId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes, QualifiedName browseName) { lock (m_lock) { ILocalNode source = GetLocalNode(sourceId) as ILocalNode; if (source == null) { return null; } return GetTargetNode(source, referenceTypeId, isInverse, includeSubtypes, browseName); } } /// /// Returns a node managed by the manager that has the specified browse name. /// private ILocalNode GetTargetNode( ILocalNode source, NodeId referenceTypeId, bool isInverse, bool includeSubtypes, QualifiedName browseName) { foreach (IReference reference in source.References.Find(referenceTypeId, isInverse, includeSubtypes, m_server.TypeTree)) { ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode; if (target == null) { continue; } if (QualifiedName.IsNull(browseName) || browseName == target.BrowseName) { return target; } } return null; } /// /// Attaches a node to the address space. /// public void AttachNode(ILocalNode node) { AttachNode(node, false); } /// /// Attaches a node to the address space. /// private void AttachNode(ILocalNode node, bool isInternal) { if (node == null) throw new ArgumentNullException(nameof(node)); lock (m_lock) { // check if node exists. if (m_nodes.Exists(node.NodeId)) { throw ServiceResultException.Create( StatusCodes.BadNodeIdExists, "A node with the same node id already exists: {0}", node.NodeId); } // ensure reverse references exist. foreach (IReference reference in node.References) { // ignore references that are always one way. if (reference.ReferenceTypeId == ReferenceTypeIds.HasTypeDefinition || reference.ReferenceTypeId == ReferenceTypeIds.HasModellingRule) { continue; } // find target. ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode; if (target != null) { AddReferenceToLocalNode(target, reference.ReferenceTypeId, !reference.IsInverse, node.NodeId, isInternal); } } // must generate a model change event. AddNode(node); } } /// /// Creates a unique node identifier. /// public NodeId CreateUniqueNodeId() { return CreateUniqueNodeId(m_dynamicNamespaceIndex); } #region Private Methods /// private object GetManagerHandle(ExpandedNodeId nodeId) { lock (m_lock) { if (nodeId == null || nodeId.IsAbsolute) { return null; } return GetLocalNode(nodeId) as ILocalNode; } } #if LEGACY_CORENODEMANAGER /// /// Checks if the operation needs to be handled by an external source. /// private static bool CheckSourceHandle(ILocalNode node, Type sourceType, int index, IDictionary sources) { // check if a source is defined for the node. SourceHandle handle = node.Handle as SourceHandle; if (handle == null) { return false; } // check if the source type is valid. if (!sourceType.IsInstanceOfType(handle.Source)) { return false; } // find list of handles for the source. List handles = null; if (!sources.Contains(handle.Source)) { sources[handle.Source] = handles = new List(); } else { handles = (List)sources[handle.Source]; } // add node to list of values to process by the source. handles.Add(new RequestHandle(handle.Handle, index)); return true; } /// /// Recursively subscribes to events for the notifiers in the tree. /// private void SubscribeToEvents( OperationContext context, ILocalNode node, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe) { // find handle associated with the node. IEventSource eventSource = node as IEventSource; SourceHandle handle = node.Handle as SourceHandle; if (handle != null) { eventSource = handle.Source as IEventSource; } if (eventSource != null) { try { eventSource.SubscribeToEvents(context, (handle != null)?handle.Handle:null, subscriptionId, monitoredItem, unsubscribe); } catch (Exception e) { Utils.Trace(e, "Unexpected error calling SubscribeToEvents on an EventSource."); } } // find the child notifiers. IList references = node.References.Find(ReferenceTypes.HasNotifier, false, true, m_server.TypeTree); for (int ii = 0; ii < references.Count; ii++) { if (!references[ii].TargetId.IsAbsolute) { ILocalNode target = GetManagerHandle(references[ii].TargetId) as ILocalNode; if (target == null) { continue; } // only object or views can produce events. if ((target.NodeClass & (NodeClass.Object | NodeClass.View)) == 0) { continue; } SubscribeToEvents(context, target, subscriptionId, monitoredItem, unsubscribe); } } } #endif /// /// Reads the EU Range for a variable. /// private ServiceResult ReadEURange(OperationContext context, ILocalNode node, out Range range) { range = null; IVariable target = GetTargetNode(node, ReferenceTypes.HasProperty, false, true, BrowseNames.EURange) as IVariable; if (target == null) { return StatusCodes.BadNodeIdUnknown; } range = target.Value as Range; if (range == null) { return StatusCodes.BadTypeMismatch; } return ServiceResult.Good; } /// /// Validates a filter for a monitored item. /// private ServiceResult ValidateFilter( NodeMetadata metadata, uint attributeId, ExtensionObject filter, out bool rangeRequired) { rangeRequired = false; // check filter. DataChangeFilter datachangeFilter = null; if (filter != null) { datachangeFilter = filter.Body as DataChangeFilter; } if (datachangeFilter != null) { // get the datatype of the node. NodeId datatypeId = metadata.DataType; // check that filter is valid. ServiceResult error = datachangeFilter.Validate(); if (ServiceResult.IsBad(error)) { return error; } // check datatype of the variable. if (!m_server.TypeTree.IsTypeOf(datatypeId, DataTypes.Number)) { return StatusCodes.BadDeadbandFilterInvalid; } // percent deadbands only allowed for analog data items. if (datachangeFilter.DeadbandType == (uint)(int)DeadbandType.Percent) { ExpandedNodeId typeDefinitionId = metadata.TypeDefinition; if (typeDefinitionId == null) { return StatusCodes.BadDeadbandFilterInvalid; } // percent deadbands only allowed for analog data items. if (!m_server.TypeTree.IsTypeOf(typeDefinitionId, VariableTypes.AnalogItemType)) { return StatusCodes.BadDeadbandFilterInvalid; } // the EURange property is required to use the filter. rangeRequired = true; } } // filter is valid return ServiceResult.Good; } /// /// Creates a new unique identifier for a node. /// private NodeId CreateUniqueNodeId(ushort namespaceIndex) { return new NodeId(Utils.IncrementIdentifier(ref m_lastId), namespaceIndex); } #endregion #region Private Fields private IServerInternal m_server; private object m_lock = new object(); private NodeTable m_nodes; private long m_lastId; private SamplingGroupManager m_samplingGroupManager; private Dictionary m_monitoredItems; #if LEGACY_CORENODEMANAGER private Dictionary m_eventSources; #endif private double m_defaultMinimumSamplingInterval; private List m_namespaceUris; private ushort m_dynamicNamespaceIndex; #endregion } }