use std::collections::{HashMap, VecDeque};
use crate::{
address_space::ReferenceDirection,
session::{
continuation_points::{ContinuationPoint, EmptyContinuationPoint},
instance::Session,
},
};
use opcua_crypto::random;
use opcua_nodes::TypeTree;
use opcua_types::{
BrowseDescription, BrowseDescriptionResultMask, BrowseDirection, BrowsePath, BrowseResult,
BrowseResultMask, ByteString, ExpandedNodeId, LocalizedText, NodeClass, NodeClassMask, NodeId,
QualifiedName, ReferenceDescription, RelativePathElement, StatusCode,
};
use tracing::warn;
use super::{NodeManager, RequestContext};
#[derive(Debug, Clone)]
pub struct NodeMetadata {
pub node_id: ExpandedNodeId,
pub type_definition: ExpandedNodeId,
pub browse_name: QualifiedName,
pub display_name: LocalizedText,
pub node_class: NodeClass,
}
impl NodeMetadata {
pub fn into_ref_desc(
self,
is_forward: bool,
reference_type_id: impl Into<NodeId>,
) -> ReferenceDescription {
ReferenceDescription {
reference_type_id: reference_type_id.into(),
is_forward,
node_id: self.node_id,
browse_name: self.browse_name,
display_name: self.display_name,
node_class: self.node_class,
type_definition: self.type_definition,
}
}
}
#[derive(Debug)]
pub struct ExternalReferenceRequest {
node_id: NodeId,
result_mask: BrowseDescriptionResultMask,
item: Option<NodeMetadata>,
}
impl ExternalReferenceRequest {
pub fn new(reference: &NodeId, result_mask: BrowseDescriptionResultMask) -> Self {
Self {
node_id: reference.clone(),
result_mask,
item: None,
}
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn set(&mut self, reference: NodeMetadata) {
self.item = Some(reference);
}
pub fn result_mask(&self) -> BrowseDescriptionResultMask {
self.result_mask
}
pub fn into_inner(self) -> Option<NodeMetadata> {
self.item
}
}
#[derive(Debug)]
pub struct ExternalReference {
target_id: ExpandedNodeId,
reference_type_id: NodeId,
direction: ReferenceDirection,
}
impl ExternalReference {
pub fn new(
target_id: ExpandedNodeId,
reference_type_id: NodeId,
direction: ReferenceDirection,
) -> Self {
Self {
target_id,
reference_type_id,
direction,
}
}
pub fn into_reference(self, meta: NodeMetadata) -> ReferenceDescription {
ReferenceDescription {
reference_type_id: self.reference_type_id,
is_forward: matches!(self.direction, ReferenceDirection::Forward),
node_id: self.target_id,
browse_name: meta.browse_name,
display_name: meta.display_name,
node_class: meta.node_class,
type_definition: meta.type_definition,
}
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum AddReferenceResult {
Added,
Rejected,
Full(ReferenceDescription),
}
pub struct BrowseNode {
node_id: NodeId,
browse_direction: BrowseDirection,
reference_type_id: NodeId,
include_subtypes: bool,
node_class_mask: NodeClassMask,
result_mask: BrowseDescriptionResultMask,
references: Vec<ReferenceDescription>,
status_code: StatusCode,
input_continuation_point: Option<ContinuationPoint>,
next_continuation_point: Option<ContinuationPoint>,
max_references_per_node: usize,
input_index: usize,
pub(crate) start_node_manager: usize,
external_references: Vec<ExternalReference>,
}
pub(crate) struct BrowseContinuationPoint {
pub node_manager_index: usize,
pub continuation_point: ContinuationPoint,
pub id: ByteString,
node_id: NodeId,
browse_direction: BrowseDirection,
reference_type_id: NodeId,
include_subtypes: bool,
node_class_mask: NodeClassMask,
result_mask: BrowseDescriptionResultMask,
pub(crate) max_references_per_node: usize,
external_references: Vec<ExternalReference>,
}
impl BrowseNode {
pub(crate) fn new(
description: BrowseDescription,
max_references_per_node: usize,
input_index: usize,
) -> Self {
Self {
node_id: description.node_id,
browse_direction: description.browse_direction,
reference_type_id: description.reference_type_id,
include_subtypes: description.include_subtypes,
node_class_mask: NodeClassMask::from_bits_truncate(description.node_class_mask),
result_mask: BrowseDescriptionResultMask::from_bits_truncate(description.result_mask),
input_continuation_point: None,
next_continuation_point: None,
max_references_per_node,
references: Vec::new(),
status_code: StatusCode::BadNodeIdUnknown,
input_index,
start_node_manager: 0,
external_references: Vec::new(),
}
}
pub(crate) fn from_continuation_point(
point: BrowseContinuationPoint,
input_index: usize,
) -> Self {
Self {
node_id: point.node_id,
browse_direction: point.browse_direction,
reference_type_id: point.reference_type_id,
include_subtypes: point.include_subtypes,
node_class_mask: point.node_class_mask,
result_mask: point.result_mask,
references: Vec::new(),
status_code: StatusCode::BadNodeIdUnknown,
input_continuation_point: Some(point.continuation_point),
next_continuation_point: None,
max_references_per_node: point.max_references_per_node,
input_index,
start_node_manager: point.node_manager_index,
external_references: point.external_references,
}
}
pub fn set_status(&mut self, status: StatusCode) {
self.status_code = status;
}
pub fn continuation_point<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.input_continuation_point.as_ref().and_then(|c| c.get())
}
pub fn continuation_point_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
self.input_continuation_point
.as_mut()
.and_then(|c| c.get_mut())
}
pub fn take_continuation_point<T: Send + Sync + 'static>(&mut self) -> Option<Box<T>> {
self.input_continuation_point.take().and_then(|c| c.take())
}
pub fn set_next_continuation_point<T: Send + Sync + 'static>(
&mut self,
continuation_point: Box<T>,
) {
self.next_continuation_point = Some(ContinuationPoint::new(continuation_point));
}
pub fn result_len(&self) -> usize {
self.references.len()
}
pub fn remaining(&self) -> usize {
if self.result_len() >= self.max_references_per_node {
0
} else {
self.max_references_per_node - self.result_len()
}
}
pub fn add_unchecked(&mut self, reference: ReferenceDescription) {
self.references.push(reference);
}
pub fn allows_reference_type(&self, ty: &NodeId, type_tree: &dyn TypeTree) -> bool {
if self.reference_type_id.is_null() {
return true;
}
if !matches!(
type_tree.get(&self.reference_type_id),
Some(NodeClass::ReferenceType)
) {
return false;
}
if self.include_subtypes {
if !type_tree.is_subtype_of(ty, &self.reference_type_id) {
return false;
}
} else if ty != &self.reference_type_id {
return false;
}
true
}
pub fn allows_node_class(&self, node_class: NodeClass) -> bool {
self.node_class_mask.is_empty()
|| self
.node_class_mask
.contains(NodeClassMask::from_bits_truncate(node_class as u32))
}
pub fn allows_forward(&self) -> bool {
matches!(
self.browse_direction,
BrowseDirection::Both | BrowseDirection::Forward
)
}
pub fn allows_inverse(&self) -> bool {
matches!(
self.browse_direction,
BrowseDirection::Both | BrowseDirection::Inverse
)
}
pub fn matches_filter(
&self,
type_tree: &dyn TypeTree,
reference: &ReferenceDescription,
) -> bool {
if reference.node_id.is_null() {
warn!("Skipping reference with null NodeId");
return false;
}
if matches!(reference.node_class, NodeClass::Unspecified) {
warn!(
"Skipping reference {} with unspecified node class and NodeId",
reference.node_id
);
return false;
}
if !reference.reference_type_id.is_null()
&& !matches!(
type_tree.get(&reference.reference_type_id),
Some(NodeClass::ReferenceType)
)
{
warn!(
"Skipping reference {} with reference type that does not exist or is not a ReferenceType",
reference.node_id
);
return false;
}
if !self.allows_node_class(reference.node_class) {
return false;
}
self.allows_reference_type(&reference.reference_type_id, type_tree)
}
pub fn add(
&mut self,
type_tree: &dyn TypeTree,
mut reference: ReferenceDescription,
) -> AddReferenceResult {
if !self.matches_filter(type_tree, &reference) {
return AddReferenceResult::Rejected;
}
if !self
.result_mask
.contains(BrowseDescriptionResultMask::RESULT_MASK_BROWSE_NAME)
{
reference.browse_name = QualifiedName::null();
}
if !self
.result_mask
.contains(BrowseDescriptionResultMask::RESULT_MASK_DISPLAY_NAME)
{
reference.display_name = LocalizedText::null();
}
if !self
.result_mask
.contains(BrowseDescriptionResultMask::RESULT_MASK_NODE_CLASS)
{
reference.node_class = NodeClass::Unspecified;
}
if !self
.result_mask
.contains(BrowseDescriptionResultMask::RESULT_MASK_REFERENCE_TYPE)
{
reference.reference_type_id = NodeId::null();
}
if !self
.result_mask
.contains(BrowseDescriptionResultMask::RESULT_MASK_TYPE_DEFINITION)
{
reference.type_definition = ExpandedNodeId::null();
}
if self.remaining() > 0 {
self.references.push(reference);
AddReferenceResult::Added
} else {
AddReferenceResult::Full(reference)
}
}
pub fn include_subtypes(&self) -> bool {
self.include_subtypes
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn browse_direction(&self) -> BrowseDirection {
self.browse_direction
}
pub fn node_class_mask(&self) -> &NodeClassMask {
&self.node_class_mask
}
pub fn result_mask(&self) -> BrowseDescriptionResultMask {
self.result_mask
}
pub fn reference_type_id(&self) -> &NodeId {
&self.reference_type_id
}
pub(crate) fn into_result(
self,
node_manager_index: usize,
node_manager_count: usize,
session: &mut Session,
) -> (BrowseResult, usize) {
let inner = self
.next_continuation_point
.map(|c| (c, node_manager_index))
.or_else(|| {
if node_manager_index < node_manager_count - 1 {
Some((
ContinuationPoint::new(Box::new(EmptyContinuationPoint)),
node_manager_index + 1,
))
} else {
None
}
});
let continuation_point = inner.map(|(p, node_manager_index)| BrowseContinuationPoint {
node_manager_index,
continuation_point: p,
id: random::byte_string(6),
node_id: self.node_id,
browse_direction: self.browse_direction,
reference_type_id: self.reference_type_id,
include_subtypes: self.include_subtypes,
node_class_mask: self.node_class_mask,
result_mask: self.result_mask,
max_references_per_node: self.max_references_per_node,
external_references: self.external_references,
});
let mut result = BrowseResult {
status_code: self.status_code,
continuation_point: continuation_point
.as_ref()
.map(|c| c.id.clone())
.unwrap_or_default(),
references: Some(self.references),
};
if let Some(c) = continuation_point {
if session.add_browse_continuation_point(c).is_err() {
result.status_code = StatusCode::BadNoContinuationPoints;
result.continuation_point = ByteString::null();
}
}
(result, self.input_index)
}
pub fn is_completed(&self) -> bool {
self.remaining() == 0 || self.next_continuation_point.is_some()
}
pub fn push_external_reference(&mut self, reference: ExternalReference) {
self.external_references.push(reference);
}
pub fn get_external_refs(&self) -> impl Iterator<Item = &NodeId> {
self.external_references
.iter()
.map(|n| &n.target_id.node_id)
}
pub fn any_external_refs(&self) -> bool {
!self.external_references.is_empty()
}
pub(crate) fn resolve_external_references(
&mut self,
type_tree: &dyn TypeTree,
resolved_nodes: &HashMap<&NodeId, &NodeMetadata>,
) {
let mut cont_point = ExternalReferencesContPoint {
items: VecDeque::new(),
};
let refs = std::mem::take(&mut self.external_references);
for rf in refs {
if let Some(meta) = resolved_nodes.get(&rf.target_id.node_id) {
let rf = rf.into_reference((*meta).clone());
if !self.matches_filter(type_tree, &rf) {
continue;
}
if self.remaining() > 0 {
self.add_unchecked(rf);
} else {
cont_point.items.push_back(rf);
}
}
}
if !cont_point.items.is_empty() {
self.set_next_continuation_point(Box::new(cont_point));
}
}
}
pub(crate) struct ExternalReferencesContPoint {
pub items: VecDeque<ReferenceDescription>,
}
#[derive(Debug, Clone)]
pub(crate) struct BrowsePathResultElement {
pub(crate) node: NodeId,
pub(crate) depth: usize,
pub(crate) unmatched_browse_name: Option<QualifiedName>,
}
#[derive(Debug, Clone)]
pub struct BrowsePathItem<'a> {
pub(crate) node: NodeId,
input_index: usize,
depth: usize,
node_manager_index: usize,
iteration_number: usize,
path: &'a [RelativePathElement],
results: Vec<BrowsePathResultElement>,
status: StatusCode,
unmatched_browse_name: Option<QualifiedName>,
}
impl<'a> BrowsePathItem<'a> {
pub(crate) fn new(
elem: BrowsePathResultElement,
input_index: usize,
root: &BrowsePathItem<'a>,
node_manager_index: usize,
iteration_number: usize,
) -> Self {
Self {
node: elem.node,
input_index,
depth: elem.depth,
node_manager_index,
path: if elem.depth <= root.path.len() {
&root.path[elem.depth..]
} else {
&[]
},
results: Vec::new(),
status: StatusCode::Good,
iteration_number,
unmatched_browse_name: elem.unmatched_browse_name,
}
}
pub(crate) fn new_root(path: &'a BrowsePath, input_index: usize) -> Self {
let mut status = StatusCode::Good;
let elements = path.relative_path.elements.as_ref();
match elements {
None => status = StatusCode::BadNothingToDo,
Some(e) if e.is_empty() => status = StatusCode::BadNothingToDo,
Some(e) if e.iter().any(|el| el.target_name.is_null()) => {
status = StatusCode::BadBrowseNameInvalid
}
_ => (),
}
Self {
node: path.starting_node.clone(),
input_index,
depth: 0,
node_manager_index: usize::MAX,
path: path.relative_path.elements.as_deref().unwrap_or(&[]),
results: Vec::new(),
status,
iteration_number: 0,
unmatched_browse_name: None,
}
}
pub fn path(&self) -> &'a [RelativePathElement] {
self.path
}
pub fn node_id(&self) -> &NodeId {
&self.node
}
pub fn add_element(
&mut self,
node: NodeId,
relative_depth: usize,
unmatched_browse_name: Option<QualifiedName>,
) {
self.results.push(BrowsePathResultElement {
node,
depth: self.depth + relative_depth,
unmatched_browse_name,
})
}
pub fn set_status(&mut self, status: StatusCode) {
self.status = status;
}
pub(crate) fn results_mut(&mut self) -> &mut Vec<BrowsePathResultElement> {
&mut self.results
}
pub(crate) fn input_index(&self) -> usize {
self.input_index
}
pub(crate) fn node_manager_index(&self) -> usize {
self.node_manager_index
}
pub fn status(&self) -> StatusCode {
self.status
}
pub fn iteration_number(&self) -> usize {
self.iteration_number
}
pub fn unmatched_browse_name(&self) -> Option<&QualifiedName> {
self.unmatched_browse_name.as_ref()
}
pub fn set_browse_name_matched(&mut self, node_manager_index: usize) {
self.unmatched_browse_name = None;
self.node_manager_index = node_manager_index;
}
}
#[derive(Debug)]
pub struct RegisterNodeItem {
node_id: NodeId,
registered: bool,
}
impl RegisterNodeItem {
pub(crate) fn new(node_id: NodeId) -> Self {
Self {
node_id,
registered: false,
}
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn set_registered(&mut self, registered: bool) {
self.registered = registered;
}
pub(crate) fn into_result(self) -> Option<NodeId> {
if self.registered {
Some(self.node_id)
} else {
None
}
}
}
pub async fn impl_translate_browse_paths_using_browse(
mgr: &(impl NodeManager + Send + Sync + 'static),
context: &RequestContext,
nodes: &mut [&mut BrowsePathItem<'_>],
) -> Result<(), StatusCode> {
let mut to_get_metadata: Vec<_> = nodes
.iter_mut()
.filter(|n| n.unmatched_browse_name().is_some())
.map(|r| {
let id = r.node_id().clone();
(
r,
ExternalReferenceRequest::new(
&id,
BrowseDescriptionResultMask::RESULT_MASK_BROWSE_NAME,
),
)
})
.collect();
let mut items_ref: Vec<_> = to_get_metadata.iter_mut().map(|r| &mut r.1).collect();
mgr.resolve_external_references(context, &mut items_ref)
.await;
for (node, ext) in to_get_metadata {
let Some(i) = ext.item else {
continue;
};
if &i.browse_name == node.unmatched_browse_name().unwrap() {
node.set_browse_name_matched(context.current_node_manager_index);
}
}
let mut current_targets = HashMap::new();
for (idx, item) in nodes.iter_mut().enumerate() {
if item.unmatched_browse_name.is_some() {
continue;
}
current_targets.insert(item.node_id().clone(), idx);
}
let mut next_targets = HashMap::new();
let mut depth = 0;
loop {
if current_targets.is_empty() {
break;
}
let mut targets = Vec::with_capacity(current_targets.len());
let mut target_idx_map = HashMap::new();
for (id, target) in current_targets.iter() {
let node = &mut nodes[*target];
let elem = &node.path()[depth];
target_idx_map.insert(targets.len(), *target);
targets.push(BrowseNode::new(
BrowseDescription {
node_id: id.clone(),
browse_direction: if elem.is_inverse {
BrowseDirection::Inverse
} else {
BrowseDirection::Forward
},
reference_type_id: elem.reference_type_id.clone(),
include_subtypes: elem.include_subtypes,
node_class_mask: NodeClassMask::all().bits(),
result_mask: BrowseResultMask::BrowseName as u32,
},
context
.info
.config
.limits
.operational
.max_references_per_browse_node,
*target,
));
}
mgr.browse(context, &mut targets).await?;
let mut next = targets;
loop {
let mut next_t = Vec::with_capacity(next.len());
for (idx, mut target) in next.into_iter().enumerate() {
let orig_idx = target_idx_map[&idx];
let path_target = &mut nodes[orig_idx];
let path_elem = &path_target.path[depth];
for it in target.references.drain(..) {
if it.browse_name == path_elem.target_name {
if path_target.path.len() > depth + 1 {
next_targets.insert(it.node_id.node_id.clone(), orig_idx);
}
path_target.add_element(it.node_id.node_id, depth + 1, None);
}
}
for ext in target.external_references.drain(..) {
path_target.add_element(
ext.target_id.node_id,
depth + 1,
Some(path_elem.target_name.clone()),
);
}
if target.next_continuation_point.is_some() {
target.input_continuation_point = target.next_continuation_point;
target.next_continuation_point = None;
next_t.push(target);
}
}
if next_t.is_empty() {
break;
}
mgr.browse(context, &mut next_t).await?;
next = next_t;
}
std::mem::swap(&mut current_targets, &mut next_targets);
next_targets.clear();
depth += 1;
}
Ok(())
}