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();
}
}