// ------------------------------------------------------------ // Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Client { /// /// Node cache context provides /// public sealed class NodeCacheContext : INodeCacheContext { /// /// Create node cache context /// /// public NodeCacheContext(ISessionClient session) { m_session = session; } /// public NamespaceTable NamespaceUris => m_session.MessageContext.NamespaceUris; /// public StringTable ServerUris => m_session.MessageContext.ServerUris; /// public async ValueTask> FetchReferencesAsync( RequestHeader? requestHeader, NodeId nodeId, CancellationToken ct = default) { var browser = new Browser(m_session, new BrowserOptions { RequestHeader = requestHeader, BrowseDirection = BrowseDirection.Both, ReferenceTypeId = NodeId.Null, IncludeSubtypes = true, NodeClassMask = 0 }); ResultSet> results = await browser.BrowseAsync([nodeId], ct).ConfigureAwait(false); return results.Results[0]; } /// public ValueTask>> FetchReferencesAsync( RequestHeader? requestHeader, ArrayOf nodeIds, CancellationToken ct = default) { if (nodeIds.Count == 0) { return new ValueTask>>( ResultSet>.Empty); } var browser = new Browser(m_session, new BrowserOptions { RequestHeader = requestHeader, BrowseDirection = BrowseDirection.Both, ReferenceTypeId = NodeId.Null, IncludeSubtypes = true, NodeClassMask = 0 }); return browser.BrowseAsync(nodeIds, ct); } /// public async ValueTask> FetchNodesAsync( RequestHeader? requestHeader, ArrayOf nodeIds, bool skipOptionalAttributes = false, CancellationToken ct = default) { if (nodeIds.Count == 0) { return ResultSet.Empty; } var nodeCollection = new List(nodeIds.Count); // first read only nodeclasses for nodes from server. ArrayOf itemsToRead = nodeIds .ConvertAll(nodeId => new ReadValueId { NodeId = nodeId, AttributeId = Attributes.NodeClass }); ReadResponse readResponse = await m_session.ReadAsync( null, 0, TimestampsToReturn.Neither, itemsToRead, ct) .ConfigureAwait(false); ArrayOf nodeClassValues = readResponse.Results; ArrayOf diagnosticInfos = readResponse.DiagnosticInfos; ClientBase.ValidateResponse(nodeClassValues, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); // second determine attributes to read per nodeclass var attributesPerNodeId = new List?>(nodeIds.Count); var serviceResults = new List(nodeIds.Count); var attributesToRead = new List(); CreateAttributesReadNodesRequest( readResponse.ResponseHeader, itemsToRead, nodeClassValues, diagnosticInfos, attributesToRead, attributesPerNodeId, nodeCollection, serviceResults, skipOptionalAttributes); if (attributesToRead.Count > 0) { itemsToRead = attributesToRead.ToArrayOf(); readResponse = await m_session.ReadAsync( null, 0, TimestampsToReturn.Neither, itemsToRead, ct) .ConfigureAwait(false); ArrayOf values = readResponse.Results; diagnosticInfos = readResponse.DiagnosticInfos; ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); ProcessAttributesReadNodesResponse( readResponse.ResponseHeader, itemsToRead, attributesPerNodeId, values, diagnosticInfos, nodeCollection, serviceResults); } return ResultSet.From(nodeCollection, serviceResults); } /// public async ValueTask> FetchNodesAsync( RequestHeader? requestHeader, ArrayOf nodeIds, NodeClass nodeClass, bool skipOptionalAttributes = false, CancellationToken ct = default) { if (nodeIds.Count == 0) { return ResultSet.Empty; } if (nodeClass == NodeClass.Unspecified) { return await FetchNodesAsync( requestHeader, nodeIds, skipOptionalAttributes, ct).ConfigureAwait(false); } var nodeCollection = new List(nodeIds.Count); // determine attributes to read for nodeclass var attributesPerNodeId = new List?>(nodeIds.Count); var attributesToRead = new List(); CreateNodeClassAttributesReadNodesRequest( nodeIds, nodeClass, attributesToRead, attributesPerNodeId, nodeCollection, skipOptionalAttributes); var itemsToRead = attributesToRead.ToArrayOf(); ReadResponse readResponse = await m_session.ReadAsync( requestHeader, 0, TimestampsToReturn.Neither, itemsToRead, ct) .ConfigureAwait(false); ArrayOf values = readResponse.Results; ArrayOf diagnosticInfos = readResponse.DiagnosticInfos; ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); List serviceResults = new ServiceResult[nodeIds.Count].ToList(); ProcessAttributesReadNodesResponse( readResponse.ResponseHeader, itemsToRead, attributesPerNodeId, values, diagnosticInfos, nodeCollection, serviceResults); return ResultSet.From(nodeCollection, serviceResults); } /// public async ValueTask FetchNodeAsync( RequestHeader? requestHeader, NodeId nodeId, NodeClass nodeClass = NodeClass.Unspecified, bool skipOptionalAttributes = false, CancellationToken ct = default) { // build list of attributes. IDictionary attributes = CreateAttributes( nodeClass, skipOptionalAttributes); // build list of values to read. var itemsToRead = attributes.Keys .Select(attributeId => new ReadValueId { NodeId = nodeId, AttributeId = attributeId }).ToArrayOf(); // read from server. ReadResponse readResponse = await m_session.ReadAsync( null, 0, TimestampsToReturn.Neither, itemsToRead, ct) .ConfigureAwait(false); ArrayOf values = readResponse.Results; ArrayOf diagnosticInfos = readResponse.DiagnosticInfos; ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); return ProcessReadResponse( readResponse.ResponseHeader, attributes, itemsToRead, values, diagnosticInfos); } /// public async ValueTask FetchValueAsync( RequestHeader? requestHeader, NodeId nodeId, CancellationToken ct = default) { ArrayOf itemsToRead = [ new ReadValueId { NodeId = nodeId, AttributeId = Attributes.Value } ]; // read from server. ReadResponse readResponse = await m_session.ReadAsync( null, 0, TimestampsToReturn.Both, itemsToRead, ct) .ConfigureAwait(false); ArrayOf values = readResponse.Results; ArrayOf diagnosticInfos = readResponse.DiagnosticInfos; ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); if (StatusCode.IsBad(values[0].StatusCode)) { ServiceResult result = ClientBase.GetResult( values[0].StatusCode, 0, diagnosticInfos, readResponse.ResponseHeader); throw new ServiceResultException(result); } return values[0]; } /// public async ValueTask> FetchValuesAsync( RequestHeader? requestHeader, ArrayOf nodeIds, CancellationToken ct = default) { if (nodeIds.Count == 0) { return ResultSet.Empty; } // read all values from server. ArrayOf itemsToRead = nodeIds .ConvertAll(nodeId => new ReadValueId { NodeId = nodeId, AttributeId = Attributes.Value }); // read from server. var errors = new ServiceResult[itemsToRead.Count]; ReadResponse readResponse = await m_session.ReadAsync( null, 0, TimestampsToReturn.Both, itemsToRead, ct) .ConfigureAwait(false); ArrayOf values = readResponse.Results; ArrayOf diagnosticInfos = readResponse.DiagnosticInfos; ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); for (int ii = 0; ii < values.Count; ii++) { ServiceResult result = ServiceResult.Good; if (StatusCode.IsBad(values[ii].StatusCode)) { result = ClientBase.GetResult( values[ii].StatusCode, ii, diagnosticInfos, readResponse.ResponseHeader); } errors[ii] = result; } return ResultSet.From(values, errors); } /// /// Creates a read request with attributes determined by the NodeClass. /// private static void CreateAttributesReadNodesRequest( ResponseHeader responseHeader, ArrayOf itemsToRead, ArrayOf nodeClassValues, ArrayOf diagnosticInfos, List attributesToRead, List?> attributesPerNodeId, List nodeCollection, List errors, bool skipOptionalAttributes) { for (int ii = 0; ii < itemsToRead.Count; ii++) { var node = new Node { NodeId = itemsToRead[ii].NodeId }; if (!nodeClassValues[ii].IsGood) { nodeCollection.Add(node); errors.Add( new ServiceResult( nodeClassValues[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable)); attributesPerNodeId.Add(null); continue; } // check for valid node class. if (!nodeClassValues[ii].WrappedValue.TryGetValue(out NodeClass nodeClass)) { if (nodeClassValues[ii].WrappedValue.TryGetValue(out int nc)) { nodeClass = (NodeClass)nc; } else { nodeCollection.Add(node); errors.Add( ServiceResult.Create( StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", nodeClassValues[ii].WrappedValue)); attributesPerNodeId.Add(null); continue; } } node.NodeClass = nodeClass; Dictionary attributes = CreateAttributes( node.NodeClass, skipOptionalAttributes); foreach (uint attributeId in attributes.Keys) { var itemToRead = new ReadValueId { NodeId = node.NodeId, AttributeId = attributeId }; attributesToRead.Add(itemToRead); } nodeCollection.Add(node); errors.Add(ServiceResult.Good); attributesPerNodeId.Add(attributes); } } /// /// Builds the node collection results based on the attribute values of the read response. /// /// The response requestHeader of the read request. /// The collection of all attributes to read passed in the read request. /// The attributes requested per NodeId /// The attribute values returned by the read request. /// The diagnostic info returned by the read request. /// The node collection which holds the results. /// The service results for each node. private static void ProcessAttributesReadNodesResponse( ResponseHeader responseHeader, ArrayOf attributesToRead, List?> attributesPerNodeId, ArrayOf values, ArrayOf diagnosticInfos, List nodeCollection, List errors) { int readIndex = 0; for (int ii = 0; ii < nodeCollection.Count; ii++) { IDictionary? attributes = attributesPerNodeId[ii]; if (attributes == null) { continue; } int readCount = attributes.Count; ArrayOf subRangeAttributes = attributesToRead.Slice(readIndex, readCount); ArrayOf subRangeValues = values.Slice(readIndex, readCount); ArrayOf subRangeDiagnostics = diagnosticInfos.Count > 0 ? diagnosticInfos.Slice(readIndex, readCount) : diagnosticInfos; try { nodeCollection[ii] = ProcessReadResponse( responseHeader, attributes, subRangeAttributes, subRangeValues, subRangeDiagnostics); errors[ii] = ServiceResult.Good; } catch (ServiceResultException sre) { errors[ii] = sre.Result; } readIndex += readCount; } } /// /// Creates a Node based on the read response. /// /// private static Node ProcessReadResponse( ResponseHeader responseHeader, IDictionary attributes, ArrayOf itemsToRead, ArrayOf values, ArrayOf diagnosticInfos) { // process results. NodeClass nodeClass = default; for (int ii = 0; ii < itemsToRead.Count; ii++) { uint attributeId = itemsToRead[ii].AttributeId; // the node probably does not exist if the node class is not found. if (attributeId == Attributes.NodeClass) { if (!values[ii].IsGood) { throw ServiceResultException.Create( values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable); } // check for valid node class. if (!values[ii].WrappedValue.TryGetValue(out nodeClass)) { throw ServiceResultException.Unexpected( "Node does not have a valid value for NodeClass: {0}.", values[ii]); } } else if (!values[ii].IsGood) { // check for unsupported attributes. if (values[ii].StatusCode == StatusCodes.BadAttributeIdInvalid) { continue; } // ignore errors on optional attributes if (StatusCode.IsBad(values[ii].StatusCode) && attributeId is Attributes.AccessRestrictions or Attributes.Description or Attributes.RolePermissions or Attributes.UserRolePermissions or Attributes.UserWriteMask or Attributes.WriteMask or Attributes.AccessLevelEx or Attributes.ArrayDimensions or Attributes.DataTypeDefinition or Attributes.InverseName or Attributes.MinimumSamplingInterval) { continue; } // all supported attributes must be readable. if (attributeId != Attributes.Value) { throw ServiceResultException.Create( values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable); } } attributes[attributeId] = values[ii]; } Node node; DataValue value; switch (nodeClass) { case NodeClass.Object: var objectNode = new ObjectNode(); value = attributes[Attributes.EventNotifier]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Object does not support the EventNotifier attribute."); } objectNode.EventNotifier = value.WrappedValue.GetByte(); node = objectNode; break; case NodeClass.ObjectType: var objectTypeNode = new ObjectTypeNode(); value = attributes[Attributes.IsAbstract]; if (value.IsNull) { throw ServiceResultException.Unexpected( "ObjectType does not support the IsAbstract attribute."); } objectTypeNode.IsAbstract = value.WrappedValue.GetBoolean(); node = objectTypeNode; break; case NodeClass.Variable: var variableNode = new VariableNode(); // DataType Attribute value = attributes[Attributes.DataType]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Variable does not support the DataType attribute."); } variableNode.DataType = value.WrappedValue.GetNodeId(); // ValueRank Attribute value = attributes[Attributes.ValueRank]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Variable does not support the ValueRank attribute."); } variableNode.ValueRank = value.WrappedValue.GetInt32(); // ArrayDimensions Attribute value = attributes[Attributes.ArrayDimensions]; if (!value.IsNull) { if (!value.WrappedValue.TryGetValue(out ArrayOf arrayDimensions1)) { variableNode.ArrayDimensions = []; } else { variableNode.ArrayDimensions = arrayDimensions1; } } // AccessLevel Attribute value = attributes[Attributes.AccessLevel]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Variable does not support the AccessLevel attribute."); } variableNode.AccessLevel = value.WrappedValue.GetByte(); // UserAccessLevel Attribute value = attributes[Attributes.UserAccessLevel]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Variable does not support the UserAccessLevel attribute."); } variableNode.UserAccessLevel = value.WrappedValue.GetByte(); // Historizing Attribute value = attributes[Attributes.Historizing]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Variable does not support the Historizing attribute."); } variableNode.Historizing = value.WrappedValue.GetBoolean(); // MinimumSamplingInterval Attribute value = attributes[Attributes.MinimumSamplingInterval]; if (!value.IsNull) { variableNode.MinimumSamplingInterval = (double)value.WrappedValue.ConvertToDouble(); } // AccessLevelEx Attribute value = attributes[Attributes.AccessLevelEx]; if (!value.IsNull) { variableNode.AccessLevelEx = value.WrappedValue.GetUInt32(); } node = variableNode; break; case NodeClass.VariableType: var variableTypeNode = new VariableTypeNode(); // IsAbstract Attribute value = attributes[Attributes.IsAbstract]; if (value.IsNull) { throw ServiceResultException.Unexpected( "VariableType does not support the IsAbstract attribute."); } variableTypeNode.IsAbstract = value.WrappedValue.GetBoolean(); // DataType Attribute value = attributes[Attributes.DataType]; if (value.IsNull) { throw ServiceResultException.Unexpected( "VariableType does not support the DataType attribute."); } variableTypeNode.DataType = value.WrappedValue.GetNodeId(); // ValueRank Attribute value = attributes[Attributes.ValueRank]; if (value.IsNull) { throw ServiceResultException.Unexpected( "VariableType does not support the ValueRank attribute."); } variableTypeNode.ValueRank = value.WrappedValue.GetInt32(); // ArrayDimensions Attribute value = attributes[Attributes.ArrayDimensions]; if (!value.IsNull && value.WrappedValue.TryGetValue(out ArrayOf arrayDimensions2)) { variableTypeNode.ArrayDimensions = arrayDimensions2; } node = variableTypeNode; break; case NodeClass.Method: var methodNode = new MethodNode(); // Executable Attribute value = attributes[Attributes.Executable]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Method does not support the Executable attribute."); } methodNode.Executable = value.WrappedValue.GetBoolean(); // UserExecutable Attribute value = attributes[Attributes.UserExecutable]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Method does not support the UserExecutable attribute."); } methodNode.UserExecutable = value.WrappedValue.GetBoolean(); node = methodNode; break; case NodeClass.DataType: var dataTypeNode = new DataTypeNode(); // IsAbstract Attribute value = attributes[Attributes.IsAbstract]; if (value.IsNull) { throw ServiceResultException.Unexpected( "DataType does not support the IsAbstract attribute."); } dataTypeNode.IsAbstract = value.WrappedValue.GetBoolean(); // DataTypeDefinition Attribute value = attributes[Attributes.DataTypeDefinition]; if (!value.IsNull) { dataTypeNode.DataTypeDefinition = value.WrappedValue.TryGetValue(out ExtensionObject eo) ? eo : default; } node = dataTypeNode; break; case NodeClass.ReferenceType: var referenceTypeNode = new ReferenceTypeNode(); // IsAbstract Attribute value = attributes[Attributes.IsAbstract]; if (value.IsNull) { throw ServiceResultException.Unexpected( "ReferenceType does not support the IsAbstract attribute."); } referenceTypeNode.IsAbstract = value.WrappedValue.GetBoolean(); // Symmetric Attribute value = attributes[Attributes.Symmetric]; if (value.IsNull) { throw ServiceResultException.Unexpected( "ReferenceType does not support the Symmetric attribute."); } referenceTypeNode.Symmetric = value.WrappedValue.GetBoolean(); // InverseName Attribute value = attributes[Attributes.InverseName]; if (!value.IsNull && value.WrappedValue.TryGetValue(out LocalizedText inverseName)) { referenceTypeNode.InverseName = inverseName; } node = referenceTypeNode; break; case NodeClass.View: var viewNode = new ViewNode(); // EventNotifier Attribute value = attributes[Attributes.EventNotifier]; if (value.IsNull) { throw ServiceResultException.Unexpected( "View does not support the EventNotifier attribute."); } viewNode.EventNotifier = value.WrappedValue.GetByte(); // ContainsNoLoops Attribute value = attributes[Attributes.ContainsNoLoops]; if (value.IsNull) { throw ServiceResultException.Unexpected( "View does not support the ContainsNoLoops attribute."); } viewNode.ContainsNoLoops = value.WrappedValue.GetBoolean(); node = viewNode; break; case NodeClass.Unspecified: throw ServiceResultException.Unexpected( "Node does not have a valid value for NodeClass: {0}.", nodeClass); default: throw ServiceResultException.Unexpected( $"Unexpected NodeClass: {nodeClass}."); } // NodeId Attribute value = attributes[Attributes.NodeId]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Node does not support the NodeId attribute."); } node.NodeId = value.WrappedValue.GetNodeId(); node.NodeClass = nodeClass; // BrowseName Attribute value = attributes[Attributes.BrowseName]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Node does not support the BrowseName attribute."); } node.BrowseName = value.WrappedValue.GetQualifiedName(); // DisplayName Attribute value = attributes[Attributes.DisplayName]; if (value.IsNull) { throw ServiceResultException.Unexpected( "Node does not support the DisplayName attribute."); } node.DisplayName = value.WrappedValue.GetLocalizedText(); // all optional attributes follow // Description Attribute if (attributes.TryGetValue(Attributes.Description, out value) && !value.IsNull && !value.WrappedValue.IsNull) { node.Description = value.WrappedValue.GetLocalizedText(); } // WriteMask Attribute if (attributes.TryGetValue(Attributes.WriteMask, out value) && !value.IsNull) { node.WriteMask = value.WrappedValue.GetUInt32(); } // UserWriteMask Attribute if (attributes.TryGetValue(Attributes.UserWriteMask, out value) && !value.IsNull) { node.UserWriteMask = value.WrappedValue.GetUInt32(); } // RolePermissions Attribute if (attributes.TryGetValue(Attributes.RolePermissions, out value) && !value.IsNull) { if (value.WrappedValue.TryGetValue(out ArrayOf rolePermissions)) { // Preserve original behavior of inserting null entries when an // ExtensionObject cannot be decoded into a RolePermissionType. The // declared element type of node.RolePermissions is non-nullable so // the failure branch uses null! to suppress nullability metadata. var rolePermissionList = new List(); foreach (ExtensionObject rolePermission in rolePermissions) { rolePermissionList.Add(rolePermission.TryGetValue( out RolePermissionType? rolePermissionType) ? rolePermissionType! : null!); } node.RolePermissions = rolePermissionList; } } // UserRolePermissions Attribute if (attributes.TryGetValue(Attributes.UserRolePermissions, out value) && !value.IsNull) { if (value.WrappedValue.TryGetValue(out ArrayOf userRolePermissions)) { // See RolePermissions above for the rationale behind the null! casts. var userRolePermissionList = new List(); foreach (ExtensionObject rolePermission in userRolePermissions) { userRolePermissionList.Add(rolePermission.TryGetValue( out RolePermissionType? rolePermissionType) ? rolePermissionType! : null!); } node.UserRolePermissions = userRolePermissionList; } } // AccessRestrictions Attribute if (attributes.TryGetValue(Attributes.AccessRestrictions, out value) && !value.IsNull) { node.AccessRestrictions = value.WrappedValue.GetUInt16(); } return node; } /// /// Create a dictionary of attributes to read for a nodeclass. /// /// private static Dictionary CreateAttributes( NodeClass nodeClass, bool skipOptionalAttributes) { // Attributes to read for all types of nodes var attributes = new Dictionary(Attributes.MaxAttributes) { { Attributes.NodeId, default }, { Attributes.NodeClass, default }, { Attributes.BrowseName, default }, { Attributes.DisplayName, default } }; switch (nodeClass) { case NodeClass.Object: attributes.Add(Attributes.EventNotifier, default); break; case NodeClass.Variable: attributes.Add(Attributes.DataType, default); attributes.Add(Attributes.ValueRank, default); attributes.Add(Attributes.ArrayDimensions, default); attributes.Add(Attributes.AccessLevel, default); attributes.Add(Attributes.UserAccessLevel, default); attributes.Add(Attributes.Historizing, default); attributes.Add(Attributes.MinimumSamplingInterval, default); attributes.Add(Attributes.AccessLevelEx, default); break; case NodeClass.Method: attributes.Add(Attributes.Executable, default); attributes.Add(Attributes.UserExecutable, default); break; case NodeClass.ObjectType: attributes.Add(Attributes.IsAbstract, default); break; case NodeClass.VariableType: attributes.Add(Attributes.IsAbstract, default); attributes.Add(Attributes.DataType, default); attributes.Add(Attributes.ValueRank, default); attributes.Add(Attributes.ArrayDimensions, default); break; case NodeClass.ReferenceType: attributes.Add(Attributes.IsAbstract, default); attributes.Add(Attributes.Symmetric, default); attributes.Add(Attributes.InverseName, default); break; case NodeClass.DataType: attributes.Add(Attributes.IsAbstract, default); attributes.Add(Attributes.DataTypeDefinition, default); break; case NodeClass.View: attributes.Add(Attributes.EventNotifier, default); attributes.Add(Attributes.ContainsNoLoops, default); break; case NodeClass.Unspecified: // build complete list of attributes. attributes.Add(Attributes.DataType, default); attributes.Add(Attributes.ValueRank, default); attributes.Add(Attributes.ArrayDimensions, default); attributes.Add(Attributes.AccessLevel, default); attributes.Add(Attributes.UserAccessLevel, default); attributes.Add(Attributes.MinimumSamplingInterval, default); attributes.Add(Attributes.Historizing, default); attributes.Add(Attributes.EventNotifier, default); attributes.Add(Attributes.Executable, default); attributes.Add(Attributes.UserExecutable, default); attributes.Add(Attributes.IsAbstract, default); attributes.Add(Attributes.InverseName, default); attributes.Add(Attributes.Symmetric, default); attributes.Add(Attributes.ContainsNoLoops, default); attributes.Add(Attributes.DataTypeDefinition, default); attributes.Add(Attributes.AccessLevelEx, default); break; default: throw ServiceResultException.Unexpected( $"Unexpected NodeClass: {nodeClass}."); } if (!skipOptionalAttributes) { attributes.Add(Attributes.Description, default); attributes.Add(Attributes.WriteMask, default); attributes.Add(Attributes.UserWriteMask, default); attributes.Add(Attributes.RolePermissions, default); attributes.Add(Attributes.UserRolePermissions, default); attributes.Add(Attributes.AccessRestrictions, default); } return attributes; } /// /// Creates a read request with attributes determined by the NodeClass. /// private static void CreateNodeClassAttributesReadNodesRequest( ArrayOf nodeIds, NodeClass nodeClass, List attributesToRead, List?> attributesPerNodeId, List nodeCollection, bool skipOptionalAttributes) { for (int ii = 0; ii < nodeIds.Count; ii++) { var node = new Node { NodeId = nodeIds[ii], NodeClass = nodeClass }; Dictionary attributes = CreateAttributes( node.NodeClass, skipOptionalAttributes); foreach (uint attributeId in attributes.Keys) { var itemToRead = new ReadValueId { NodeId = node.NodeId, AttributeId = attributeId }; attributesToRead.Add(itemToRead); } nodeCollection.Add(node); attributesPerNodeId.Add(attributes); } } private readonly ISessionClient m_session; } }