Skip to main content

opcua_server/node_manager/
view.rs

1use std::collections::{HashMap, VecDeque};
2
3use crate::{
4    address_space::ReferenceDirection,
5    session::{
6        continuation_points::{ContinuationPoint, EmptyContinuationPoint},
7        instance::Session,
8    },
9};
10use opcua_crypto::random;
11use opcua_nodes::TypeTree;
12use opcua_types::{
13    BrowseDescription, BrowseDescriptionResultMask, BrowseDirection, BrowsePath, BrowseResult,
14    BrowseResultMask, ByteString, ExpandedNodeId, LocalizedText, NodeClass, NodeClassMask, NodeId,
15    QualifiedName, ReferenceDescription, RelativePathElement, StatusCode,
16};
17use tracing::warn;
18
19use super::{NodeManager, RequestContext};
20
21#[derive(Debug, Clone)]
22/// Object describing a node with sufficient context to construct
23/// a `ReferenceDescription`.
24pub struct NodeMetadata {
25    /// Node ID of the node.
26    pub node_id: ExpandedNodeId,
27    /// Type definition of the node.
28    pub type_definition: ExpandedNodeId,
29    /// Browse name of the node.
30    pub browse_name: QualifiedName,
31    /// Display name of the node.
32    pub display_name: LocalizedText,
33    /// Node class of the node.
34    pub node_class: NodeClass,
35}
36
37impl NodeMetadata {
38    /// Convert this metadata into a ReferenceDescription with given
39    /// direction and reference type ID.
40    pub fn into_ref_desc(
41        self,
42        is_forward: bool,
43        reference_type_id: impl Into<NodeId>,
44    ) -> ReferenceDescription {
45        ReferenceDescription {
46            reference_type_id: reference_type_id.into(),
47            is_forward,
48            node_id: self.node_id,
49            browse_name: self.browse_name,
50            display_name: self.display_name,
51            node_class: self.node_class,
52            type_definition: self.type_definition,
53        }
54    }
55}
56
57#[derive(Debug)]
58/// Container for a request for the metadata of a single node.
59pub struct ExternalReferenceRequest {
60    node_id: NodeId,
61    result_mask: BrowseDescriptionResultMask,
62    item: Option<NodeMetadata>,
63}
64
65impl ExternalReferenceRequest {
66    /// Create a new external reference request from the node ID of the node being requested
67    /// and a result mask.
68    pub fn new(reference: &NodeId, result_mask: BrowseDescriptionResultMask) -> Self {
69        Self {
70            node_id: reference.clone(),
71            result_mask,
72            item: None,
73        }
74    }
75
76    /// Node ID of the node being requested.
77    pub fn node_id(&self) -> &NodeId {
78        &self.node_id
79    }
80
81    /// Set the result to a `NodeMetadata` object.
82    pub fn set(&mut self, reference: NodeMetadata) {
83        self.item = Some(reference);
84    }
85
86    /// Get the mask for fields that should be included in the returned `NodeMetadata`.
87    pub fn result_mask(&self) -> BrowseDescriptionResultMask {
88        self.result_mask
89    }
90
91    /// Consume this request and return the result.
92    pub fn into_inner(self) -> Option<NodeMetadata> {
93        self.item
94    }
95}
96
97#[derive(Debug)]
98/// A reference pointing to some node in a different node manager.
99pub struct ExternalReference {
100    target_id: ExpandedNodeId,
101    reference_type_id: NodeId,
102    direction: ReferenceDirection,
103}
104
105impl ExternalReference {
106    /// Create a new external reference.
107    pub fn new(
108        target_id: ExpandedNodeId,
109        reference_type_id: NodeId,
110        direction: ReferenceDirection,
111    ) -> Self {
112        Self {
113            target_id,
114            reference_type_id,
115            direction,
116        }
117    }
118
119    /// Create a reference description from this and a `NodeMetadata` object.
120    pub fn into_reference(self, meta: NodeMetadata) -> ReferenceDescription {
121        ReferenceDescription {
122            reference_type_id: self.reference_type_id,
123            is_forward: matches!(self.direction, ReferenceDirection::Forward),
124            node_id: self.target_id,
125            browse_name: meta.browse_name,
126            display_name: meta.display_name,
127            node_class: meta.node_class,
128            type_definition: meta.type_definition,
129        }
130    }
131}
132
133#[derive(Debug)]
134/// Result of adding a reference to a browse node.
135#[allow(clippy::large_enum_variant)]
136pub enum AddReferenceResult {
137    /// The reference was added
138    Added,
139    /// The reference does not match the filters and was rejected
140    Rejected,
141    /// The reference does match the filters, but the node is full.
142    Full(ReferenceDescription),
143}
144
145/// Container for a node being browsed and the result of the browse operation.
146pub struct BrowseNode {
147    node_id: NodeId,
148    browse_direction: BrowseDirection,
149    reference_type_id: NodeId,
150    include_subtypes: bool,
151    node_class_mask: NodeClassMask,
152    result_mask: BrowseDescriptionResultMask,
153    references: Vec<ReferenceDescription>,
154    status_code: StatusCode,
155    // It is feasible to only keep one continuation point, by using the
156    // fact that node managers are sequential. If the first node manager is done reading,
157    // we move on to the next.
158    // All we need to do is keep track of which node manager made the last continuation point.
159    input_continuation_point: Option<ContinuationPoint>,
160    next_continuation_point: Option<ContinuationPoint>,
161    max_references_per_node: usize,
162    input_index: usize,
163    pub(crate) start_node_manager: usize,
164
165    /// List of references to nodes not owned by the node manager that generated the
166    /// reference. These are resolved after the initial browse, and any excess is stored
167    /// in a continuation point.
168    external_references: Vec<ExternalReference>,
169}
170
171pub(crate) struct BrowseContinuationPoint {
172    pub node_manager_index: usize,
173    pub continuation_point: ContinuationPoint,
174    pub id: ByteString,
175
176    node_id: NodeId,
177    browse_direction: BrowseDirection,
178    reference_type_id: NodeId,
179    include_subtypes: bool,
180    node_class_mask: NodeClassMask,
181    result_mask: BrowseDescriptionResultMask,
182    pub(crate) max_references_per_node: usize,
183
184    external_references: Vec<ExternalReference>,
185}
186
187impl BrowseNode {
188    /// Create a new empty browse node
189    pub(crate) fn new(
190        description: BrowseDescription,
191        max_references_per_node: usize,
192        input_index: usize,
193    ) -> Self {
194        Self {
195            node_id: description.node_id,
196            browse_direction: description.browse_direction,
197            reference_type_id: description.reference_type_id,
198            include_subtypes: description.include_subtypes,
199            node_class_mask: NodeClassMask::from_bits_truncate(description.node_class_mask),
200            result_mask: BrowseDescriptionResultMask::from_bits_truncate(description.result_mask),
201            input_continuation_point: None,
202            next_continuation_point: None,
203            max_references_per_node,
204            references: Vec::new(),
205            status_code: StatusCode::BadNodeIdUnknown,
206            input_index,
207            start_node_manager: 0,
208            external_references: Vec::new(),
209        }
210    }
211
212    pub(crate) fn from_continuation_point(
213        point: BrowseContinuationPoint,
214        input_index: usize,
215    ) -> Self {
216        Self {
217            node_id: point.node_id,
218            browse_direction: point.browse_direction,
219            reference_type_id: point.reference_type_id,
220            include_subtypes: point.include_subtypes,
221            node_class_mask: point.node_class_mask,
222            result_mask: point.result_mask,
223            references: Vec::new(),
224            status_code: StatusCode::BadNodeIdUnknown,
225            input_continuation_point: Some(point.continuation_point),
226            next_continuation_point: None,
227            max_references_per_node: point.max_references_per_node,
228            input_index,
229            start_node_manager: point.node_manager_index,
230            external_references: point.external_references,
231        }
232    }
233
234    /// Set the response status, you should make sure to set this
235    /// if you own the node being browsed. It defaults to BadNodeIdUnknown.
236    pub fn set_status(&mut self, status: StatusCode) {
237        self.status_code = status;
238    }
239
240    /// Get the continuation point created during the last request.
241    pub fn continuation_point<T: Send + Sync + 'static>(&self) -> Option<&T> {
242        self.input_continuation_point.as_ref().and_then(|c| c.get())
243    }
244
245    /// Get the continuation point created during the last request.
246    pub fn continuation_point_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
247        self.input_continuation_point
248            .as_mut()
249            .and_then(|c| c.get_mut())
250    }
251
252    /// Consume the continuation point created during the last request.
253    pub fn take_continuation_point<T: Send + Sync + 'static>(&mut self) -> Option<Box<T>> {
254        self.input_continuation_point.take().and_then(|c| c.take())
255    }
256
257    /// Set the continuation point that will be returned to the client.
258    pub fn set_next_continuation_point<T: Send + Sync + 'static>(
259        &mut self,
260        continuation_point: Box<T>,
261    ) {
262        self.next_continuation_point = Some(ContinuationPoint::new(continuation_point));
263    }
264
265    /// Get the current number of added references.
266    pub fn result_len(&self) -> usize {
267        self.references.len()
268    }
269
270    /// Get the number of references that can be added to this result before
271    /// stopping and returning a continuation point.
272    pub fn remaining(&self) -> usize {
273        if self.result_len() >= self.max_references_per_node {
274            0
275        } else {
276            self.max_references_per_node - self.result_len()
277        }
278    }
279
280    /// Add a reference to the results list, without verifying that it is valid.
281    /// If you do this, you are responsible for validating filters,
282    /// and requested fields on each reference.
283    pub fn add_unchecked(&mut self, reference: ReferenceDescription) {
284        self.references.push(reference);
285    }
286
287    /// Return `true` if nodes with the given reference type ID should be returned.
288    pub fn allows_reference_type(&self, ty: &NodeId, type_tree: &dyn TypeTree) -> bool {
289        if self.reference_type_id.is_null() {
290            return true;
291        }
292
293        if !matches!(
294            type_tree.get(&self.reference_type_id),
295            Some(NodeClass::ReferenceType)
296        ) {
297            return false;
298        }
299        if self.include_subtypes {
300            if !type_tree.is_subtype_of(ty, &self.reference_type_id) {
301                return false;
302            }
303        } else if ty != &self.reference_type_id {
304            return false;
305        }
306        true
307    }
308
309    /// Return `true` if nodes with the given node class should be returned.
310    pub fn allows_node_class(&self, node_class: NodeClass) -> bool {
311        self.node_class_mask.is_empty()
312            || self
313                .node_class_mask
314                .contains(NodeClassMask::from_bits_truncate(node_class as u32))
315    }
316
317    /// Return `true` if references with forward direction should be returned.
318    pub fn allows_forward(&self) -> bool {
319        matches!(
320            self.browse_direction,
321            BrowseDirection::Both | BrowseDirection::Forward
322        )
323    }
324
325    /// Return `true` if references with inverse direction should be returned.
326    pub fn allows_inverse(&self) -> bool {
327        matches!(
328            self.browse_direction,
329            BrowseDirection::Both | BrowseDirection::Inverse
330        )
331    }
332
333    /// Return `true` if the given reference should be returned.
334    pub fn matches_filter(
335        &self,
336        type_tree: &dyn TypeTree,
337        reference: &ReferenceDescription,
338    ) -> bool {
339        if reference.node_id.is_null() {
340            warn!("Skipping reference with null NodeId");
341            return false;
342        }
343        if matches!(reference.node_class, NodeClass::Unspecified) {
344            warn!(
345                "Skipping reference {} with unspecified node class and NodeId",
346                reference.node_id
347            );
348            return false;
349        }
350        // Validate the reference and reference type
351        if !reference.reference_type_id.is_null()
352            && !matches!(
353                type_tree.get(&reference.reference_type_id),
354                Some(NodeClass::ReferenceType)
355            )
356        {
357            warn!(
358                "Skipping reference {} with reference type that does not exist or is not a ReferenceType",
359                reference.node_id
360            );
361            return false;
362        }
363
364        if !self.allows_node_class(reference.node_class) {
365            return false;
366        }
367
368        // Check the reference type filter.
369        self.allows_reference_type(&reference.reference_type_id, type_tree)
370    }
371
372    /// Add a reference, validating that it matches the filters, and returning `Added` if it was added.
373    /// If the browse node is full, this will return `Full` containing the given reference if
374    /// `max_references_per_node` would be exceeded. In this case you are responsible for
375    /// setting a `ContinuationPoint` to ensure all references are included.
376    /// This will clear any fields not required by ResultMask.
377    pub fn add(
378        &mut self,
379        type_tree: &dyn TypeTree,
380        mut reference: ReferenceDescription,
381    ) -> AddReferenceResult {
382        // First, validate that the reference is valid at all.
383        if !self.matches_filter(type_tree, &reference) {
384            return AddReferenceResult::Rejected;
385        }
386
387        if !self
388            .result_mask
389            .contains(BrowseDescriptionResultMask::RESULT_MASK_BROWSE_NAME)
390        {
391            reference.browse_name = QualifiedName::null();
392        }
393
394        if !self
395            .result_mask
396            .contains(BrowseDescriptionResultMask::RESULT_MASK_DISPLAY_NAME)
397        {
398            reference.display_name = LocalizedText::null();
399        }
400
401        if !self
402            .result_mask
403            .contains(BrowseDescriptionResultMask::RESULT_MASK_NODE_CLASS)
404        {
405            reference.node_class = NodeClass::Unspecified;
406        }
407
408        if !self
409            .result_mask
410            .contains(BrowseDescriptionResultMask::RESULT_MASK_REFERENCE_TYPE)
411        {
412            reference.reference_type_id = NodeId::null();
413        }
414
415        if !self
416            .result_mask
417            .contains(BrowseDescriptionResultMask::RESULT_MASK_TYPE_DEFINITION)
418        {
419            reference.type_definition = ExpandedNodeId::null();
420        }
421
422        if self.remaining() > 0 {
423            self.references.push(reference);
424            AddReferenceResult::Added
425        } else {
426            AddReferenceResult::Full(reference)
427        }
428    }
429
430    /// Whether to include subtypes of the `reference_type_id`.
431    pub fn include_subtypes(&self) -> bool {
432        self.include_subtypes
433    }
434
435    /// Node ID to browse.
436    pub fn node_id(&self) -> &NodeId {
437        &self.node_id
438    }
439
440    /// Direction to browse.
441    pub fn browse_direction(&self) -> BrowseDirection {
442        self.browse_direction
443    }
444
445    /// Mask for node classes to return. If this is empty, all node classes should be returned.
446    pub fn node_class_mask(&self) -> &NodeClassMask {
447        &self.node_class_mask
448    }
449
450    /// Mask for attributes to return.
451    pub fn result_mask(&self) -> BrowseDescriptionResultMask {
452        self.result_mask
453    }
454
455    /// Reference type ID of references to return. Subject to `include_subtypes`.
456    pub fn reference_type_id(&self) -> &NodeId {
457        &self.reference_type_id
458    }
459
460    pub(crate) fn into_result(
461        self,
462        node_manager_index: usize,
463        node_manager_count: usize,
464        session: &mut Session,
465    ) -> (BrowseResult, usize) {
466        // There may be a continuation point defined for the current node manager,
467        // in that case return that. There is also a corner case here where
468        // remaining == 0 and there is no continuation point.
469        // In this case we need to pass an empty continuation point
470        // to the next node manager.
471        let inner = self
472            .next_continuation_point
473            .map(|c| (c, node_manager_index))
474            .or_else(|| {
475                if node_manager_index < node_manager_count - 1 {
476                    Some((
477                        ContinuationPoint::new(Box::new(EmptyContinuationPoint)),
478                        node_manager_index + 1,
479                    ))
480                } else {
481                    None
482                }
483            });
484
485        let continuation_point = inner.map(|(p, node_manager_index)| BrowseContinuationPoint {
486            node_manager_index,
487            continuation_point: p,
488            id: random::byte_string(6),
489            node_id: self.node_id,
490            browse_direction: self.browse_direction,
491            reference_type_id: self.reference_type_id,
492            include_subtypes: self.include_subtypes,
493            node_class_mask: self.node_class_mask,
494            result_mask: self.result_mask,
495            max_references_per_node: self.max_references_per_node,
496            external_references: self.external_references,
497        });
498
499        let mut result = BrowseResult {
500            status_code: self.status_code,
501            continuation_point: continuation_point
502                .as_ref()
503                .map(|c| c.id.clone())
504                .unwrap_or_default(),
505            references: Some(self.references),
506        };
507
508        // If we're out of continuation points, the correct response is to not store it, and
509        // set the status code to BadNoContinuationPoints.
510        if let Some(c) = continuation_point {
511            if session.add_browse_continuation_point(c).is_err() {
512                result.status_code = StatusCode::BadNoContinuationPoints;
513                result.continuation_point = ByteString::null();
514            }
515        }
516
517        (result, self.input_index)
518    }
519
520    /// Returns whether this node is completed in this invocation of the Browse or
521    /// BrowseNext service. If this returns true, no new nodes should be added.
522    pub fn is_completed(&self) -> bool {
523        self.remaining() == 0 || self.next_continuation_point.is_some()
524    }
525
526    /// Add an external reference to the result. This will be resolved by
527    /// calling into a different node manager.
528    pub fn push_external_reference(&mut self, reference: ExternalReference) {
529        self.external_references.push(reference);
530    }
531
532    /// Get an iterator over the external references.
533    pub fn get_external_refs(&self) -> impl Iterator<Item = &NodeId> {
534        self.external_references
535            .iter()
536            .map(|n| &n.target_id.node_id)
537    }
538
539    /// Return `true` if there are any external references to evaluate.
540    pub fn any_external_refs(&self) -> bool {
541        !self.external_references.is_empty()
542    }
543
544    pub(crate) fn resolve_external_references(
545        &mut self,
546        type_tree: &dyn TypeTree,
547        resolved_nodes: &HashMap<&NodeId, &NodeMetadata>,
548    ) {
549        let mut cont_point = ExternalReferencesContPoint {
550            items: VecDeque::new(),
551        };
552
553        let refs = std::mem::take(&mut self.external_references);
554        for rf in refs {
555            if let Some(meta) = resolved_nodes.get(&rf.target_id.node_id) {
556                let rf = rf.into_reference((*meta).clone());
557                if !self.matches_filter(type_tree, &rf) {
558                    continue;
559                }
560                if self.remaining() > 0 {
561                    self.add_unchecked(rf);
562                } else {
563                    cont_point.items.push_back(rf);
564                }
565            }
566        }
567
568        if !cont_point.items.is_empty() {
569            self.set_next_continuation_point(Box::new(cont_point));
570        }
571    }
572}
573
574pub(crate) struct ExternalReferencesContPoint {
575    pub items: VecDeque<ReferenceDescription>,
576}
577
578// The node manager model works somewhat poorly with translate browse paths.
579// In theory a node manager should only need to know about references relating to its own nodes,
580// but if a browse path crosses a boundary between node managers it isn't obvious
581// how to handle that.
582// If it becomes necessary there may be ways to handle this, but it may be we just leave it up
583// to the user.
584
585#[derive(Debug, Clone)]
586pub(crate) struct BrowsePathResultElement {
587    pub(crate) node: NodeId,
588    pub(crate) depth: usize,
589    pub(crate) unmatched_browse_name: Option<QualifiedName>,
590}
591
592/// Container for a node being discovered in a browse path operation.
593#[derive(Debug, Clone)]
594pub struct BrowsePathItem<'a> {
595    pub(crate) node: NodeId,
596    input_index: usize,
597    depth: usize,
598    node_manager_index: usize,
599    iteration_number: usize,
600    path: &'a [RelativePathElement],
601    results: Vec<BrowsePathResultElement>,
602    status: StatusCode,
603    unmatched_browse_name: Option<QualifiedName>,
604}
605
606impl<'a> BrowsePathItem<'a> {
607    pub(crate) fn new(
608        elem: BrowsePathResultElement,
609        input_index: usize,
610        root: &BrowsePathItem<'a>,
611        node_manager_index: usize,
612        iteration_number: usize,
613    ) -> Self {
614        Self {
615            node: elem.node,
616            input_index,
617            depth: elem.depth,
618            node_manager_index,
619            path: if elem.depth <= root.path.len() {
620                &root.path[elem.depth..]
621            } else {
622                &[]
623            },
624            results: Vec::new(),
625            status: StatusCode::Good,
626            iteration_number,
627            unmatched_browse_name: elem.unmatched_browse_name,
628        }
629    }
630
631    pub(crate) fn new_root(path: &'a BrowsePath, input_index: usize) -> Self {
632        let mut status = StatusCode::Good;
633        let elements = path.relative_path.elements.as_ref();
634        match elements {
635            None => status = StatusCode::BadNothingToDo,
636            Some(e) if e.is_empty() => status = StatusCode::BadNothingToDo,
637            Some(e) if e.iter().any(|el| el.target_name.is_null()) => {
638                status = StatusCode::BadBrowseNameInvalid
639            }
640            _ => (),
641        }
642
643        Self {
644            node: path.starting_node.clone(),
645            input_index,
646            depth: 0,
647            node_manager_index: usize::MAX,
648            path: path.relative_path.elements.as_deref().unwrap_or(&[]),
649            results: Vec::new(),
650            status,
651            iteration_number: 0,
652            unmatched_browse_name: None,
653        }
654    }
655
656    /// Full browse path for this item.
657    pub fn path(&self) -> &'a [RelativePathElement] {
658        self.path
659    }
660
661    /// Root node ID to evaluate the path from.
662    pub fn node_id(&self) -> &NodeId {
663        &self.node
664    }
665
666    /// Add a path result element.
667    pub fn add_element(
668        &mut self,
669        node: NodeId,
670        relative_depth: usize,
671        unmatched_browse_name: Option<QualifiedName>,
672    ) {
673        self.results.push(BrowsePathResultElement {
674            node,
675            depth: self.depth + relative_depth,
676            unmatched_browse_name,
677        })
678    }
679
680    /// Set the status code for this operation.
681    pub fn set_status(&mut self, status: StatusCode) {
682        self.status = status;
683    }
684
685    pub(crate) fn results_mut(&mut self) -> &mut Vec<BrowsePathResultElement> {
686        &mut self.results
687    }
688
689    pub(crate) fn input_index(&self) -> usize {
690        self.input_index
691    }
692
693    pub(crate) fn node_manager_index(&self) -> usize {
694        self.node_manager_index
695    }
696
697    /// Get the current result status code.
698    pub fn status(&self) -> StatusCode {
699        self.status
700    }
701
702    /// Get the current interation number.
703    pub fn iteration_number(&self) -> usize {
704        self.iteration_number
705    }
706
707    /// Get the last unmatched browse name, if present.
708    pub fn unmatched_browse_name(&self) -> Option<&QualifiedName> {
709        self.unmatched_browse_name.as_ref()
710    }
711
712    /// Set the browse name as matched by the node manager given by `node_manager_index`.
713    pub fn set_browse_name_matched(&mut self, node_manager_index: usize) {
714        self.unmatched_browse_name = None;
715        self.node_manager_index = node_manager_index;
716    }
717}
718
719#[derive(Debug)]
720/// Container for a single node in a `RegisterNodes` call.
721pub struct RegisterNodeItem {
722    node_id: NodeId,
723    registered: bool,
724}
725
726impl RegisterNodeItem {
727    pub(crate) fn new(node_id: NodeId) -> Self {
728        Self {
729            node_id,
730            registered: false,
731        }
732    }
733
734    /// Node ID to register.
735    pub fn node_id(&self) -> &NodeId {
736        &self.node_id
737    }
738
739    /// Set the node registered status. This is returned to the client.
740    pub fn set_registered(&mut self, registered: bool) {
741        self.registered = registered;
742    }
743
744    pub(crate) fn into_result(self) -> Option<NodeId> {
745        if self.registered {
746            Some(self.node_id)
747        } else {
748            None
749        }
750    }
751}
752
753/// Implementation of translate_browse_path implemented by repeatedly calling browse.
754/// Note that this is always less efficient than a dedicated implementation, but
755/// for simple node managers it may be a simple solution to get translate browse paths support
756/// without a complex implementation.
757///
758/// Arguments are simply the inputs to translate_browse_paths on `NodeManager`.
759pub async fn impl_translate_browse_paths_using_browse(
760    mgr: &(impl NodeManager + Send + Sync + 'static),
761    context: &RequestContext,
762    nodes: &mut [&mut BrowsePathItem<'_>],
763) -> Result<(), StatusCode> {
764    // For unmatched browse names we first need to check if the node exists.
765    let mut to_get_metadata: Vec<_> = nodes
766        .iter_mut()
767        .filter(|n| n.unmatched_browse_name().is_some())
768        .map(|r| {
769            let id = r.node_id().clone();
770            (
771                r,
772                ExternalReferenceRequest::new(
773                    &id,
774                    BrowseDescriptionResultMask::RESULT_MASK_BROWSE_NAME,
775                ),
776            )
777        })
778        .collect();
779    let mut items_ref: Vec<_> = to_get_metadata.iter_mut().map(|r| &mut r.1).collect();
780    mgr.resolve_external_references(context, &mut items_ref)
781        .await;
782    for (node, ext) in to_get_metadata {
783        let Some(i) = ext.item else {
784            continue;
785        };
786        if &i.browse_name == node.unmatched_browse_name().unwrap() {
787            node.set_browse_name_matched(context.current_node_manager_index);
788        }
789    }
790
791    // Start with a map from the node ID to browse, to the index in the original nodes array.
792    let mut current_targets = HashMap::new();
793    for (idx, item) in nodes.iter_mut().enumerate() {
794        // If the node is still unmatched, don't use it.
795        if item.unmatched_browse_name.is_some() {
796            continue;
797        }
798        current_targets.insert(item.node_id().clone(), idx);
799    }
800    let mut next_targets = HashMap::new();
801    let mut depth = 0;
802    loop {
803        // If we are out of targets, we've reached the end.
804        if current_targets.is_empty() {
805            break;
806        }
807
808        // For each target, make a browse node.
809        let mut targets = Vec::with_capacity(current_targets.len());
810        let mut target_idx_map = HashMap::new();
811        for (id, target) in current_targets.iter() {
812            let node = &mut nodes[*target];
813            let elem = &node.path()[depth];
814            target_idx_map.insert(targets.len(), *target);
815            targets.push(BrowseNode::new(
816                BrowseDescription {
817                    node_id: id.clone(),
818                    browse_direction: if elem.is_inverse {
819                        BrowseDirection::Inverse
820                    } else {
821                        BrowseDirection::Forward
822                    },
823                    reference_type_id: elem.reference_type_id.clone(),
824                    include_subtypes: elem.include_subtypes,
825                    node_class_mask: NodeClassMask::all().bits(),
826                    result_mask: BrowseResultMask::BrowseName as u32,
827                },
828                context
829                    .info
830                    .config
831                    .limits
832                    .operational
833                    .max_references_per_browse_node,
834                *target,
835            ));
836        }
837        mgr.browse(context, &mut targets).await?;
838
839        // Call browse until the results are exhausted.
840        let mut next = targets;
841        loop {
842            let mut next_t = Vec::with_capacity(next.len());
843            for (idx, mut target) in next.into_iter().enumerate() {
844                // For each node we just browsed, drain the references and external references
845                // and produce path target elements from them.
846                let orig_idx = target_idx_map[&idx];
847                let path_target = &mut nodes[orig_idx];
848                let path_elem = &path_target.path[depth];
849                for it in target.references.drain(..) {
850                    // If we found a reference, add it as a target.
851                    if it.browse_name == path_elem.target_name {
852                        // If the path is empty, we shouldn't browse any further.
853                        if path_target.path.len() > depth + 1 {
854                            next_targets.insert(it.node_id.node_id.clone(), orig_idx);
855                        }
856                        path_target.add_element(it.node_id.node_id, depth + 1, None);
857                    }
858                }
859                // External references are added as unmatched nodes.
860                for ext in target.external_references.drain(..) {
861                    path_target.add_element(
862                        ext.target_id.node_id,
863                        depth + 1,
864                        Some(path_elem.target_name.clone()),
865                    );
866                }
867
868                if target.next_continuation_point.is_some() {
869                    target.input_continuation_point = target.next_continuation_point;
870                    target.next_continuation_point = None;
871                    next_t.push(target);
872                }
873            }
874            if next_t.is_empty() {
875                break;
876            }
877            mgr.browse(context, &mut next_t).await?;
878            next = next_t;
879        }
880        std::mem::swap(&mut current_targets, &mut next_targets);
881        next_targets.clear();
882
883        depth += 1;
884    }
885
886    Ok(())
887}