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)]
22pub struct NodeMetadata {
25 pub node_id: ExpandedNodeId,
27 pub type_definition: ExpandedNodeId,
29 pub browse_name: QualifiedName,
31 pub display_name: LocalizedText,
33 pub node_class: NodeClass,
35}
36
37impl NodeMetadata {
38 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)]
58pub struct ExternalReferenceRequest {
60 node_id: NodeId,
61 result_mask: BrowseDescriptionResultMask,
62 item: Option<NodeMetadata>,
63}
64
65impl ExternalReferenceRequest {
66 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 pub fn node_id(&self) -> &NodeId {
78 &self.node_id
79 }
80
81 pub fn set(&mut self, reference: NodeMetadata) {
83 self.item = Some(reference);
84 }
85
86 pub fn result_mask(&self) -> BrowseDescriptionResultMask {
88 self.result_mask
89 }
90
91 pub fn into_inner(self) -> Option<NodeMetadata> {
93 self.item
94 }
95}
96
97#[derive(Debug)]
98pub struct ExternalReference {
100 target_id: ExpandedNodeId,
101 reference_type_id: NodeId,
102 direction: ReferenceDirection,
103}
104
105impl ExternalReference {
106 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 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#[allow(clippy::large_enum_variant)]
136pub enum AddReferenceResult {
137 Added,
139 Rejected,
141 Full(ReferenceDescription),
143}
144
145pub 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 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 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 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 pub fn set_status(&mut self, status: StatusCode) {
237 self.status_code = status;
238 }
239
240 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 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 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 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 pub fn result_len(&self) -> usize {
267 self.references.len()
268 }
269
270 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 pub fn add_unchecked(&mut self, reference: ReferenceDescription) {
284 self.references.push(reference);
285 }
286
287 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 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 pub fn allows_forward(&self) -> bool {
319 matches!(
320 self.browse_direction,
321 BrowseDirection::Both | BrowseDirection::Forward
322 )
323 }
324
325 pub fn allows_inverse(&self) -> bool {
327 matches!(
328 self.browse_direction,
329 BrowseDirection::Both | BrowseDirection::Inverse
330 )
331 }
332
333 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 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 self.allows_reference_type(&reference.reference_type_id, type_tree)
370 }
371
372 pub fn add(
378 &mut self,
379 type_tree: &dyn TypeTree,
380 mut reference: ReferenceDescription,
381 ) -> AddReferenceResult {
382 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 pub fn include_subtypes(&self) -> bool {
432 self.include_subtypes
433 }
434
435 pub fn node_id(&self) -> &NodeId {
437 &self.node_id
438 }
439
440 pub fn browse_direction(&self) -> BrowseDirection {
442 self.browse_direction
443 }
444
445 pub fn node_class_mask(&self) -> &NodeClassMask {
447 &self.node_class_mask
448 }
449
450 pub fn result_mask(&self) -> BrowseDescriptionResultMask {
452 self.result_mask
453 }
454
455 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 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 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 pub fn is_completed(&self) -> bool {
523 self.remaining() == 0 || self.next_continuation_point.is_some()
524 }
525
526 pub fn push_external_reference(&mut self, reference: ExternalReference) {
529 self.external_references.push(reference);
530 }
531
532 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 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#[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#[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 pub fn path(&self) -> &'a [RelativePathElement] {
658 self.path
659 }
660
661 pub fn node_id(&self) -> &NodeId {
663 &self.node
664 }
665
666 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 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 pub fn status(&self) -> StatusCode {
699 self.status
700 }
701
702 pub fn iteration_number(&self) -> usize {
704 self.iteration_number
705 }
706
707 pub fn unmatched_browse_name(&self) -> Option<&QualifiedName> {
709 self.unmatched_browse_name.as_ref()
710 }
711
712 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)]
720pub 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 pub fn node_id(&self) -> &NodeId {
736 &self.node_id
737 }
738
739 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
753pub 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 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 let mut current_targets = HashMap::new();
793 for (idx, item) in nodes.iter_mut().enumerate() {
794 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 current_targets.is_empty() {
805 break;
806 }
807
808 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 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 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 it.browse_name == path_elem.target_name {
852 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 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}