internal static async Task CollectInstanceDeclarationsAsync()

in src/Azure.IIoT.OpcUa.Publisher/src/Stack/Extensions/SessionEx.cs [680:839]


        internal static async Task<ServiceResultModel?> CollectInstanceDeclarationsAsync(
            this IOpcUaSession session, RequestHeader requestHeader, NodeId typeId,
            InstanceDeclarationModel? parent, List<InstanceDeclarationModel> instances,
            IDictionary<ImmutableRelativePath, InstanceDeclarationModel> map,
            NamespaceFormat namespaceFormat, Opc.Ua.NodeClass? nodeClassMask = null,
            CancellationToken ct = default)
        {
            // find the children of the type.
            var nodeToBrowse = new BrowseDescriptionCollection
            {
                new BrowseDescription
                {
                    NodeId = parent == null ? typeId : parent.NodeId.ToNodeId(session.MessageContext),
                    BrowseDirection = Opc.Ua.BrowseDirection.Forward,
                    ReferenceTypeId = ReferenceTypeIds.HasChild,
                    IncludeSubtypes = true,
                    NodeClassMask = (uint)Opc.Ua.NodeClass.Object |
                        (((uint?)nodeClassMask)
                            ?? (uint)Opc.Ua.NodeClass.Variable | (uint)Opc.Ua.NodeClass.Method),
                    ResultMask = (uint)BrowseResultMask.All
                }
            };
            await foreach (var result in session.BrowseAsync(requestHeader, null,
                nodeToBrowse, ct).ConfigureAwait(false))
            {
                if (result.ErrorInfo != null)
                {
                    return result.ErrorInfo;
                }
                if (result.References == null)
                {
                    continue;
                }
                var references = result.References
                    .Where(r => !r.NodeId.IsAbsolute)
                    .ToList();
                if (references.Count == 0)
                {
                    continue;
                }

                // find the modelling rules.
                var (targets, errorInfo2) = await session.FindAsync(requestHeader,
                    references.Select(r => (NodeId)r.NodeId),
                    ReferenceTypeIds.HasModellingRule, maxResults: 1, ct: ct).ConfigureAwait(false);
                if (errorInfo2 != null)
                {
                    return errorInfo2;
                }
                var referencesWithRules = targets
                    .Zip(references)
                    .ToList();

                // Get the children.
                var children = new Dictionary<NodeId, InstanceDeclarationModel>();
                foreach (var (modellingRule, reference) in referencesWithRules)
                {
                    var browseName = reference.BrowseName.AsString(session.MessageContext,
                        namespaceFormat);
                    var relativePath = ImmutableRelativePath.Create(parent?.BrowsePath,
                        "/" + browseName);
                    var nodeClass = reference.NodeClass.ToServiceType();
                    if (NodeId.IsNull(modellingRule.Node) || nodeClass == null)
                    {
                        // if the modelling rule is null then the instance is not part
                        // of the type declaration.
                        map.Remove(relativePath);
                        continue;
                    }
                    // create a new declaration.
                    map.TryGetValue(relativePath, out var overriden);

                    var displayName =
                        LocalizedText.IsNullOrEmpty(reference.DisplayName?.Text) ?
                            reference.BrowseName.Name : reference.DisplayName.AsString();
                    var child = new InstanceDeclarationModel
                    {
                        RootTypeId = typeId.AsString(session.MessageContext,
                            namespaceFormat),
                        NodeId = reference.NodeId.AsString(session.MessageContext,
                            namespaceFormat),
                        BrowseName = reference.BrowseName.AsString(session.MessageContext,
                            namespaceFormat),
                        NodeClass = nodeClass.Value,
                        DisplayPath = parent == null ?
                            displayName : $"{parent.DisplayPath}/{displayName}",
                        DisplayName = displayName,
                        BrowsePath = relativePath.Path,
                        ModellingRule = modellingRule.Name.AsString(session.MessageContext,
                            namespaceFormat),
                        ModellingRuleId = modellingRule.Node.AsString(session.MessageContext,
                            namespaceFormat),
                        OverriddenDeclaration = overriden
                    };

                    map[relativePath] = child;

                    // add to list.
                    children.Add((NodeId)reference.NodeId, child);
                }
                // check if nothing more to do.
                if (children.Count == 0)
                {
                    return null;
                }

                // Add variable metadata
                var variables = children
                    .Where(v => v.Value.NodeClass == NodeClass.Variable)
                    .Select(v => v.Key);
                var variableMetadata = new List<VariableMetadataModel>();
                var errorInfo = await session.CollectVariableMetadataAsync(requestHeader,
                    variables, variableMetadata, namespaceFormat, ct).ConfigureAwait(false);
                if (errorInfo != null)
                {
                    return errorInfo;
                }
                foreach (var (nodeId, variable) in variables.Zip(variableMetadata))
                {
                    children[nodeId] = children[nodeId] with { VariableMetadata = variable };
                }

                // Add method metadata
                var methods = children
                    .Where(v => v.Value.NodeClass == NodeClass.Method)
                    .Select(v => v.Key);
                var methodMetadata = new List<MethodMetadataModel>();
                errorInfo = await session.CollectMethodMetadataAsync(requestHeader,
                    variables, methodMetadata, namespaceFormat, ct).ConfigureAwait(false);
                if (errorInfo != null)
                {
                    return errorInfo;
                }
                foreach (var (nodeId, method) in methods.Zip(methodMetadata))
                {
                    children[nodeId] = children[nodeId] with { MethodMetadata = method };
                }

                // Add descriptions
                var attributes = await session.ReadAttributeAsync<LocalizedText>(requestHeader,
                    children.Keys, Attributes.Description, ct).ConfigureAwait(false);
                // TODO: Check attribute error info
                foreach (var (nodeId, description) in children.Keys.Zip(attributes))
                {
                    children[nodeId] = children[nodeId] with
                    {
                        Description = description.Item1.AsString()
                    };
                }

                // recusively collect instance declarations for the tree below.
                foreach (var child in children.Values)
                {
                    instances.Add(child);
                    await session.CollectInstanceDeclarationsAsync(requestHeader,
                        typeId, child, instances, map, namespaceFormat, ct: ct).ConfigureAwait(false);
                }
            }
            return null;
        }