/// <summary>
        /// Worker function to browse the full address space of a server.
        /// </summary>
        /// <param name="services">The service interface.</param>
        /// <param name="operationLimits">The operation limits.</param>
        public static ReferenceDescriptionCollection BrowseFullAddressSpaceWorker(
            IServerTestServices services,
            RequestHeader requestHeader,
            OperationLimits operationLimits     = null,
            BrowseDescription browseDescription = null)
        {
            operationLimits         = operationLimits ?? new OperationLimits();
            requestHeader.Timestamp = DateTime.UtcNow;

            // Browse template
            var startingNode   = Objects.RootFolder;
            var browseTemplate = browseDescription ?? new BrowseDescription {
                NodeId          = startingNode,
                BrowseDirection = BrowseDirection.Forward,
                ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
                IncludeSubtypes = true,
                NodeClassMask   = 0,
                ResultMask      = (uint)BrowseResultMask.All
            };
            var browseDescriptionCollection = ServerFixtureUtils.CreateBrowseDescriptionCollectionFromNodeId(
                new NodeIdCollection(new NodeId[] { Objects.RootFolder }),
                browseTemplate);

            // Browse
            ResponseHeader response = null;
            uint           requestedMaxReferencesPerNode = operationLimits.MaxNodesPerBrowse;
            bool           verifyMaxNodesPerBrowse       = operationLimits.MaxNodesPerBrowse > 0;
            var            referenceDescriptions         = new ReferenceDescriptionCollection();

            // Test if server responds with BadNothingToDo
            {
                var sre = Assert.Throws <ServiceResultException>(() =>
                                                                 _ = services.Browse(requestHeader, null,
                                                                                     0, browseDescriptionCollection.Take(0).ToArray(),
                                                                                     out var results, out var infos));
                Assert.AreEqual(StatusCodes.BadNothingToDo, sre.StatusCode);
            }

            while (browseDescriptionCollection.Any())
            {
                BrowseResultCollection allResults = new BrowseResultCollection();
                if (verifyMaxNodesPerBrowse &&
                    browseDescriptionCollection.Count > operationLimits.MaxNodesPerBrowse)
                {
                    verifyMaxNodesPerBrowse = false;
                    // Test if server responds with BadTooManyOperations
                    var sre = Assert.Throws <ServiceResultException>(() =>
                                                                     _ = services.Browse(requestHeader, null,
                                                                                         0, browseDescriptionCollection,
                                                                                         out var results, out var infos));
                    Assert.AreEqual(StatusCodes.BadTooManyOperations, sre.StatusCode);

                    // Test if server responds with BadTooManyOperations
                    var tempBrowsePath = browseDescriptionCollection.Take((int)operationLimits.MaxNodesPerBrowse + 1).ToArray();
                    sre = Assert.Throws <ServiceResultException>(() =>
                                                                 _ = services.Browse(requestHeader, null,
                                                                                     0, tempBrowsePath,
                                                                                     out var results, out var infos));
                    Assert.AreEqual(StatusCodes.BadTooManyOperations, sre.StatusCode);
                }

                bool repeatBrowse;
                var  maxNodesPerBrowse = operationLimits.MaxNodesPerBrowse;
                BrowseResultCollection   browseResultCollection = new BrowseResultCollection();
                DiagnosticInfoCollection diagnosticsInfoCollection;
                do
                {
                    var browseCollection = (maxNodesPerBrowse == 0) ?
                                           browseDescriptionCollection :
                                           browseDescriptionCollection.Take((int)maxNodesPerBrowse).ToArray();
                    repeatBrowse = false;
                    try
                    {
                        requestHeader.Timestamp = DateTime.UtcNow;
                        response = services.Browse(requestHeader, null,
                                                   requestedMaxReferencesPerNode, browseCollection,
                                                   out browseResultCollection, out diagnosticsInfoCollection);
                        ServerFixtureUtils.ValidateResponse(response);
                        ServerFixtureUtils.ValidateDiagnosticInfos(diagnosticsInfoCollection, browseCollection);

                        allResults.AddRange(browseResultCollection);
                    }
                    catch (ServiceResultException sre)
                    {
                        if (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;
                        }
                        else
                        {
                            throw;
                        }
                    }
                } while (repeatBrowse);

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

                // Browse next
                var continuationPoints = ServerFixtureUtils.PrepareBrowseNext(browseResultCollection);
                while (continuationPoints.Any())
                {
                    requestHeader.Timestamp = DateTime.UtcNow;
                    response = services.BrowseNext(requestHeader, false, continuationPoints,
                                                   out var browseNextResultCollection, out diagnosticsInfoCollection);
                    ServerFixtureUtils.ValidateResponse(response);
                    ServerFixtureUtils.ValidateDiagnosticInfos(diagnosticsInfoCollection, continuationPoints);
                    allResults.AddRange(browseNextResultCollection);
                    continuationPoints = ServerFixtureUtils.PrepareBrowseNext(browseNextResultCollection);
                }

                // Build browse request for next level
                var browseTable = new NodeIdCollection();
                foreach (var result in allResults)
                {
                    referenceDescriptions.AddRange(result.References);
                    foreach (var reference in result.References)
                    {
                        browseTable.Add(ExpandedNodeId.ToNodeId(reference.NodeId, null));
                    }
                }
                browseDescriptionCollection = ServerFixtureUtils.CreateBrowseDescriptionCollectionFromNodeId(browseTable, browseTemplate);
            }

            referenceDescriptions.Sort((x, y) => (x.NodeId.CompareTo(y.NodeId)));

            TestContext.Out.WriteLine("Found {0} references on server.", referenceDescriptions.Count);
            foreach (var reference in referenceDescriptions)
            {
                TestContext.Out.WriteLine("NodeId {0} {1} {2}", reference.NodeId, reference.NodeClass, reference.BrowseName);
            }
            return(referenceDescriptions);
        }
        public async Task BrowseAsync()
        {
            // Browse template
            var startingNode   = Objects.RootFolder;
            var browseTemplate = new BrowseDescription {
                NodeId          = startingNode,
                BrowseDirection = BrowseDirection.Forward,
                ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
                IncludeSubtypes = true,
                NodeClassMask   = 0,
                ResultMask      = (uint)BrowseResultMask.All
            };

            var requestHeader         = new RequestHeader();
            var referenceDescriptions = new ReferenceDescriptionCollection();

            var browseDescriptionCollection = ServerFixtureUtils.CreateBrowseDescriptionCollectionFromNodeId(
                new NodeIdCollection(new NodeId[] { Objects.RootFolder }),
                browseTemplate);

            while (browseDescriptionCollection.Any())
            {
                TestContext.Out.WriteLine("Browse {0} Nodes...", browseDescriptionCollection.Count);
                BrowseResultCollection allResults = new BrowseResultCollection();
                var response = await Session.BrowseAsync(
                    requestHeader, null, 5,
                    browseDescriptionCollection,
                    CancellationToken.None).ConfigureAwait(false);

                BrowseResultCollection   results         = response.Results;
                DiagnosticInfoCollection diagnosticInfos = response.DiagnosticInfos;

                allResults.AddRange(results);

                var continuationPoints = ServerFixtureUtils.PrepareBrowseNext(results);
                while (continuationPoints.Any())
                {
                    TestContext.Out.WriteLine("BrowseNext {0} Nodes...", continuationPoints.Count);
                    var nextResponse = await Session.BrowseNextAsync(requestHeader, false, continuationPoints, CancellationToken.None);

                    BrowseResultCollection browseNextResultCollection = nextResponse.Results;
                    diagnosticInfos = nextResponse.DiagnosticInfos;
                    ServerFixtureUtils.ValidateResponse(response.ResponseHeader);
                    ServerFixtureUtils.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);
                    allResults.AddRange(browseNextResultCollection);
                    continuationPoints = ServerFixtureUtils.PrepareBrowseNext(browseNextResultCollection);
                }

                // Build browse request for next level
                var browseTable = new NodeIdCollection();
                foreach (var result in allResults)
                {
                    referenceDescriptions.AddRange(result.References);
                    foreach (var reference in result.References)
                    {
                        browseTable.Add(ExpandedNodeId.ToNodeId(reference.NodeId, Session.NamespaceUris));
                    }
                }
                browseDescriptionCollection = ServerFixtureUtils.CreateBrowseDescriptionCollectionFromNodeId(browseTable, browseTemplate);
            }

            referenceDescriptions.Sort((x, y) => (x.NodeId.CompareTo(y.NodeId)));

            // read values
            var nodesToRead = new ReadValueIdCollection(referenceDescriptions.Select(r =>
                                                                                     new ReadValueId()
            {
                NodeId      = ExpandedNodeId.ToNodeId(r.NodeId, Session.NamespaceUris),
                AttributeId = Attributes.Value
            }));

            // test reads
            TestContext.Out.WriteLine("Test Read Nodes...");
            var readResponse = await Session.ReadAsync(requestHeader, 0, TimestampsToReturn.Neither, nodesToRead, CancellationToken.None).ConfigureAwait(false);

            // test register nodes
            TestContext.Out.WriteLine("Test Register Nodes...");
            var nodesToRegister  = new NodeIdCollection(nodesToRead.Select(n => n.NodeId));
            var registerResponse = await Session.RegisterNodesAsync(requestHeader, nodesToRegister, CancellationToken.None).ConfigureAwait(false);

            var unregisterResponse = await Session.UnregisterNodesAsync(requestHeader, registerResponse.RegisteredNodeIds, CancellationToken.None).ConfigureAwait(false);

            // test writes
            var nodesToWrite = new WriteValueCollection();
            int ii           = 0;

            foreach (var result in readResponse.Results)
            {
                if (StatusCode.IsGood(result.StatusCode))
                {
                    var writeValue = new WriteValue()
                    {
                        AttributeId = Attributes.Value,
                        NodeId      = nodesToRead[ii].NodeId,
                        Value       = new DataValue(result.WrappedValue)
                    };
                    nodesToWrite.Add(writeValue);
                }
                ii++;
            }

            TestContext.Out.WriteLine("Test Writes...");
            var writeResponse = await Session.WriteAsync(requestHeader, nodesToWrite, CancellationToken.None).ConfigureAwait(false);

            TestContext.Out.WriteLine("Found {0} references on server.", referenceDescriptions.Count);
            ii = 0;
            foreach (var reference in referenceDescriptions)
            {
                TestContext.Out.WriteLine("NodeId {0} {1} {2} {3}", reference.NodeId, reference.NodeClass, reference.BrowseName, readResponse.Results[ii++].WrappedValue);
            }
        }