private async Task BrowseAddressSpaceAsync()

in src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.Browser.cs [203:437]


            private async Task BrowseAddressSpaceAsync(OpcUaSession session, CancellationToken ct)
            {
                var browseDescriptionCollection = CreateBrowseDescriptionCollection(
                    (ObjectIds.RootFolder, new RelativePath()).YieldReturn());

                // Browse
                var foundReferences = new Dictionary<ReferenceDescription, (NodeId, RelativePath)>(
                    Compare.Using<ReferenceDescription>(Utils.IsEqual));
                var foundNodes = new Dictionary<NodeId, (RelativePath, Node)>();
                try
                {
                    var searchDepth = 0;
                    var maxNodesPerBrowse = session.OperationLimits.MaxNodesPerBrowse;
                    while (browseDescriptionCollection.Count != 0 && searchDepth < kMaxSearchDepth)
                    {
                        searchDepth++;

                        bool repeatBrowse;
                        var allBrowseResults = new List<(NodeId, RelativePath, BrowseResult)>();
                        var unprocessedOperations = new BrowseDescriptionCollection();
                        BrowseResultCollection? browseResultCollection = null;
                        do
                        {
                            var browseCollection = maxNodesPerBrowse == 0
                                ? browseDescriptionCollection
                                : browseDescriptionCollection.Take((int)maxNodesPerBrowse).ToArray();
                            repeatBrowse = false;
                            try
                            {
                                var browseResponse = await session.BrowseAsync(null, null,
                                    kMaxReferencesPerNode, browseCollection, ct).ConfigureAwait(false);
                                browseResultCollection = browseResponse.Results;
                                ClientBase.ValidateResponse(browseResultCollection, browseCollection);
                                ClientBase.ValidateDiagnosticInfos(
                                    browseResponse.DiagnosticInfos, browseCollection);

                                // seperate unprocessed nodes for later
                                for (var index = 0; index < browseResultCollection.Count; index++)
                                {
                                    var browseResult = browseResultCollection[index];

                                    // check for error.
                                    var statusCode = browseResult.StatusCode;
                                    if (StatusCode.IsBad(statusCode))
                                    {
                                        //
                                        // this error indicates that the server does not have enough
                                        // simultaneously active continuation points. This request will
                                        // need to be re-sent after the other operations have been
                                        // completed and their continuation points released.
                                        //
                                        if (statusCode == StatusCodes.BadNoContinuationPoints)
                                        {
                                            unprocessedOperations.Add(browseCollection[index]);
                                            continue;
                                        }
                                    }
                                    // save results.
                                    allBrowseResults.Add((browseCollection[index].NodeId,
                                        (RelativePath)browseCollection[index].Handle, browseResult));
                                }
                            }
                            catch (ServiceResultException sre) when
                                (sre.StatusCode == StatusCodes.BadEncodingLimitsExceeded ||
                                 sre.StatusCode == StatusCodes.BadResponseTooLarge)
                            {
                                // try to address by overriding operation limit
                                maxNodesPerBrowse = maxNodesPerBrowse == 0 ?
                                    (uint)browseCollection.Count / 2 : maxNodesPerBrowse / 2;
                                repeatBrowse = true;
                            }
                        }
                        while (repeatBrowse);

                        // Browse next
                        Debug.Assert(browseResultCollection != null);
                        var (nodeIds, continuationPoints) = PrepareBrowseNext(
                            new NodeIdCollection(browseDescriptionCollection
                                .Take(browseResultCollection.Count).Select(r => r.NodeId)),
                            browseResultCollection);
                        while (continuationPoints.Count != 0)
                        {
                            var browseNextResult = await session.BrowseNextAsync(null, false,
                                continuationPoints, ct).ConfigureAwait(false);
                            var browseNextResultCollection = browseNextResult.Results;
                            ClientBase.ValidateResponse(browseNextResultCollection, continuationPoints);
                            ClientBase.ValidateDiagnosticInfos(
                                browseNextResult.DiagnosticInfos, continuationPoints);

                            allBrowseResults.AddRange(browseNextResultCollection
                                .Select((r, i) => (browseDescriptionCollection[i].NodeId,
                                    (RelativePath)browseDescriptionCollection[i].Handle, r)));
                            (nodeIds, continuationPoints) = PrepareBrowseNext(nodeIds, browseNextResultCollection);
                        }

                        if (maxNodesPerBrowse == 0)
                        {
                            browseDescriptionCollection.Clear();
                        }
                        else
                        {
                            browseDescriptionCollection = browseDescriptionCollection
                                .Skip(browseResultCollection.Count)
                                .ToArray();
                        }

                        static (NodeIdCollection, ByteStringCollection) PrepareBrowseNext(
                            NodeIdCollection browseSourceCollection, BrowseResultCollection results)
                        {
                            var continuationPoints = new ByteStringCollection();
                            var nodeIdCollection = new NodeIdCollection();
                            for (var i = 0; i < results.Count; i++)
                            {
                                var browseResult = results[i];
                                if (browseResult.ContinuationPoint != null)
                                {
                                    nodeIdCollection.Add(browseSourceCollection[i]);
                                    continuationPoints.Add(browseResult.ContinuationPoint);
                                }
                            }
                            return (nodeIdCollection, continuationPoints);
                        }

                        // Build browse request for next level
                        var browseTable = new List<(NodeId, RelativePath)>();
                        foreach (var (source, path, browseResult) in allBrowseResults)
                        {
                            var nodesToRead = new List<NodeId>();
                            foreach (var reference in browseResult.References)
                            {
                                if (foundReferences.TryAdd(reference, (source, path)))
                                {
                                    if (!_knownReferences.Remove(reference))
                                    {
                                        // Send new reference
                                        _referencesAdded++;
                                        OnReferenceChange?.Invoke(session, CreateChange(source, path, null,
                                            reference));
                                    }
                                    var targetNodeId = ExpandedNodeId.ToNodeId(reference.NodeId, session.NamespaceUris);
                                    var targetPath = new RelativePath
                                    {
                                        Elements = new RelativePathElementCollection(path.Elements
                                            .Append(new RelativePathElement
                                            {
                                                TargetName = reference.BrowseName,
                                                IsInverse = false,
                                                IncludeSubtypes = false,
                                                ReferenceTypeId = reference.ReferenceTypeId
                                            }))
                                    };
                                    browseTable.Add((targetNodeId, targetPath));
                                    await ReadNodeAsync(session, targetNodeId, targetPath,
                                        foundNodes, ct).ConfigureAwait(false);
                                }
                            }
                        }
                        browseDescriptionCollection.AddRange(CreateBrowseDescriptionCollection(browseTable));
                        // add unprocessed nodes if any
                        browseDescriptionCollection.AddRange(unprocessedOperations);
                    }

                    _referencesRemoved += _knownReferences.Count;
                    foreach (var (removedReference, (nodeId, path)) in _knownReferences)
                    {
                        OnReferenceChange?.Invoke(session, CreateChange(nodeId, path, removedReference,
                            null));
                    }
                    _knownReferences.Clear();

                    _nodesRemoved += _knownNodes.Count;
                    foreach (var (removedNodeId, (path, removedNode)) in _knownNodes)
                    {
                        OnNodeChange?.Invoke(session, CreateChange(removedNodeId, path, removedNode,
                            null));
                    }
                    _knownNodes.Clear();
                }
                catch (OperationCanceledException) { return; }
                catch (Exception ex)
                {
                    HandleException(foundReferences, foundNodes, ex);
                    throw;
                }
                finally
                {
                    _knownReferences = foundReferences;
                    _knownNodes = foundNodes;
                }

                static BrowseDescriptionCollection CreateBrowseDescriptionCollection(
                    IEnumerable<(NodeId NodeId, RelativePath Position)> items)
                {
                    return new BrowseDescriptionCollection(items.Select(
                        item => new BrowseDescription
                        {
                            Handle = item.Position,
                            BrowseDirection = Opc.Ua.BrowseDirection.Forward,
                            ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
                            IncludeSubtypes = true,
                            NodeId = item.NodeId,
                            NodeClassMask = 0,
                            ResultMask = (uint)BrowseResultMask.All
                        }));
                }

                void HandleException(Dictionary<ReferenceDescription, (NodeId, RelativePath)> foundReferences,
                    Dictionary<NodeId, (RelativePath, Node)> foundNodes, Exception ex)
                {
                    _logger.LogDebug(ex, "Stopping browse due to error.");

                    // Reset stream by resetting the sequence number to 0
                    _sequenceNumber = 0u;

                    //
                    // In case of exception we could not process the entire address space
                    // We add the remainder of the remaining existing references and nodes
                    // back to the currently known nodes and references and sort those out
                    // next time around.
                    //
                    foreach (var removedReference in _knownReferences)
                    {
                        // Re-add
                        foundReferences.AddOrUpdate(removedReference.Key, removedReference.Value);
                    }
                    _knownReferences.Clear();

                    foreach (var removedNode in _knownNodes)
                    {
                        // Re-add
                        foundNodes.AddOrUpdate(removedNode.Key, removedNode.Value);
                    }
                    _knownNodes.Clear();
                }
            }