Skip to content

Commit

Permalink
Avoid depending on the type tree outside of node managers (#11)
Browse files Browse the repository at this point in the history
This makes it possible to write servers with a dynamic type tree, which
is potentially very powerful.
  • Loading branch information
einarmo committed Dec 15, 2024
1 parent 50e3a1a commit 00c70b1
Show file tree
Hide file tree
Showing 26 changed files with 232 additions and 130 deletions.
8 changes: 4 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ The following is a list of tasks, with progress indicated where relevant.
- The web server is removed. Likely forever, a better solution is to use the `metrics` library to hook into the rust metrics ecosystem.
- A smattering of TODO's, most are somehow blocked by other tasks.
- Merge most recent PRs on the main repo, especially the one migrating away from OpenSSL.
- Split the library into parts again.
- **100%** Split the library into parts again.
- Initially into types, client, core, and server.
- This is needed for other features.
- Write a codegen/macro library. Initially this should just replace all the JS codegen, later on it will do _more_.
- **70%** Write a codegen/macro library. Initially this should just replace all the JS codegen, later on it will do _more_.
- It would be best if this could be written in such a way that it can either be used as a core for a macro library, or as a standalone build.rs codegen module.
- Implement sophisticated event support, using a macro to create event types.
- Investigate decoding. There are several things that would be interesting to do here.
- **70%** Implement sophisticated event support, using a macro to create event types.
- **100%?** Investigate decoding. There are several things that would be interesting to do here.
- Capture request-id/request-handle for error reporting during decoding. This will allow us to fatally fail much less often, but will require major changes to codegen.
- See if there is a way to avoid needing to pass the ID when decoding ExtensionObjects. This info should be available, either in the object itself or as part of the type being decoded.
- Flesh out the server and client SDK with tooling for ease if use.
Expand Down
1 change: 1 addition & 0 deletions lib/tests/integration/browse.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::utils::setup;
use opcua::{
server::address_space::{ObjectBuilder, ReferenceDirection, VariableBuilder},
server::node_manager::TypeTree,
types::{
BrowseDescription, BrowseDirection, BrowsePath, BrowseResultMask, ByteString, DataTypeId,
NodeClass, NodeClassMask, NodeId, ObjectId, ObjectTypeId, ReferenceTypeId, RelativePath,
Expand Down
2 changes: 1 addition & 1 deletion lib/tests/integration/node_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async fn add_delete_reference() {
sp.find_references(
&id1,
None::<(NodeId, bool)>,
&type_tree,
&*type_tree,
opcua::types::BrowseDirection::Forward,
)
.find(|r| {
Expand Down
11 changes: 6 additions & 5 deletions lib/tests/utils/node_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use opcua::{
InMemoryNodeManager, InMemoryNodeManagerBuilder, InMemoryNodeManagerImpl,
NamespaceMetadata,
},
AddNodeItem, AddReferenceItem, DeleteNodeItem, DeleteReferenceItem, HistoryNode,
HistoryUpdateNode, MethodCall, MonitoredItemRef, MonitoredItemUpdateRef,
AddNodeItem, AddReferenceItem, DefaultTypeTree, DeleteNodeItem, DeleteReferenceItem,
HistoryNode, HistoryUpdateNode, MethodCall, MonitoredItemRef, MonitoredItemUpdateRef,
NodeManagerBuilder, NodeManagersRef, ParsedReadValueId, RequestContext, ServerContext,
TypeTree, TypeTreeNode, WriteNode,
},
Expand Down Expand Up @@ -300,7 +300,8 @@ impl InMemoryNodeManagerImpl for TestNodeManagerImpl {
let type_tree = trace_read_lock!(context.type_tree);

for write in nodes_to_write {
let node = match address_space.validate_node_write(context, write.value(), &type_tree) {
let node = match address_space.validate_node_write(context, write.value(), &*type_tree)
{
Ok(v) => v,
Err(e) => {
write.set_status(e);
Expand Down Expand Up @@ -927,7 +928,7 @@ impl TestNodeManagerImpl {
pub fn add_node<'a>(
&self,
address_space: &RwLock<AddressSpace>,
type_tree: &RwLock<TypeTree>,
type_tree: &RwLock<DefaultTypeTree>,
node: NodeType,
parent_id: &'a NodeId,
reference_type_id: &'a NodeId,
Expand Down Expand Up @@ -971,7 +972,7 @@ impl TestNodeManagerImpl {
fn insert_node_inner(
&self,
address_space: &mut AddressSpace,
type_tree: &mut TypeTree,
type_tree: &mut DefaultTypeTree,
node: NodeType,
parent_id: &NodeId,
refs: Vec<(&NodeId, NodeId, ReferenceDirection)>,
Expand Down
40 changes: 21 additions & 19 deletions opcua-server/src/address_space/address_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use hashbrown::{Equivalent, HashMap, HashSet};
use log::{debug, error, info, warn};
use opcua_nodes::{NamespaceMap, NodeInsertTarget, ReferenceDirection};

use crate::node_manager::{ParsedReadValueId, ParsedWriteValue, RequestContext, TypeTree};
use crate::node_manager::{
DefaultTypeTree, ParsedReadValueId, ParsedWriteValue, RequestContext, TypeTree,
};
use opcua_types::{
BrowseDirection, DataValue, LocalizedText, NodeClass, NodeId, QualifiedName, ReferenceTypeId,
StatusCode, TimestampsToReturn,
Expand Down Expand Up @@ -260,7 +262,7 @@ impl References {
&'a self,
source_node: &'b NodeId,
filter: Option<(impl Into<NodeId>, bool)>,
type_tree: &'b TypeTree,
type_tree: &'b dyn TypeTree,
direction: BrowseDirection,
) -> impl Iterator<Item = ReferenceRef<'a>> + 'b {
ReferenceIterator::new(
Expand All @@ -276,7 +278,7 @@ impl References {
// Handy feature to let us easily return a concrete type from `find_references`.
struct ReferenceIterator<'a, 'b> {
filter: Option<(NodeId, bool)>,
type_tree: &'b TypeTree,
type_tree: &'b dyn TypeTree,
iter_s: Option<hashbrown::hash_set::Iter<'a, Reference>>,
iter_t: Option<hashbrown::hash_set::Iter<'a, Reference>>,
}
Expand Down Expand Up @@ -333,7 +335,7 @@ impl<'a, 'b> ReferenceIterator<'a, 'b> {
direction: BrowseDirection,
references: &'a References,
filter: Option<(NodeId, bool)>,
type_tree: &'b TypeTree,
type_tree: &'b dyn TypeTree,
) -> Self {
Self {
filter,
Expand Down Expand Up @@ -415,7 +417,7 @@ impl AddressSpace {
info!("Imported {count} nodes");
}

pub fn load_into_type_tree(&self, type_tree: &mut TypeTree) {
pub fn load_into_type_tree(&self, type_tree: &mut DefaultTypeTree) {
let mut found_ids = VecDeque::new();
println!("Begin loading into type tree");
// Populate types first so that we have reference types to browse in the next stage.
Expand Down Expand Up @@ -613,7 +615,7 @@ impl AddressSpace {
&'a self,
source_node: &'b NodeId,
filter: Option<(impl Into<NodeId>, bool)>,
type_tree: &'b TypeTree,
type_tree: &'b dyn TypeTree,
direction: BrowseDirection,
) -> impl Iterator<Item = ReferenceRef<'a>> + 'b {
self.references
Expand All @@ -624,7 +626,7 @@ impl AddressSpace {
&'a self,
source_node: &'b NodeId,
filter: Option<(impl Into<NodeId>, bool)>,
type_tree: &'b TypeTree,
type_tree: &'b dyn TypeTree,
direction: BrowseDirection,
browse_name: impl Into<QualifiedName>,
) -> Option<&'a NodeType> {
Expand All @@ -644,7 +646,7 @@ impl AddressSpace {
&'a self,
source_node: &'b NodeId,
filter: Option<(impl Into<NodeId>, bool)>,
type_tree: &'b TypeTree,
type_tree: &'b dyn TypeTree,
direction: BrowseDirection,
browse_path: &[QualifiedName],
) -> Option<&'a NodeType> {
Expand Down Expand Up @@ -743,7 +745,7 @@ impl AddressSpace {
&'a mut self,
context: &RequestContext,
node_to_write: &ParsedWriteValue,
type_tree: &TypeTree,
type_tree: &dyn TypeTree,
) -> Result<&'a mut NodeType, StatusCode> {
let Some(node) = self.find_mut(&node_to_write.node_id) else {
debug!(
Expand Down Expand Up @@ -858,7 +860,7 @@ mod tests {
CoreNamespace, EventNotifier, MethodBuilder, NodeBase, NodeType, Object, ObjectBuilder,
ObjectTypeBuilder, Variable, VariableBuilder,
},
node_manager::TypeTree,
node_manager::{DefaultTypeTree, TypeTree},
};
use opcua_nodes::NamespaceMap;
use opcua_types::{
Expand Down Expand Up @@ -1024,7 +1026,7 @@ mod tests {
.find_references(
&NodeId::root_folder_id(),
Some((ReferenceTypeId::Organizes, false)),
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Forward,
)
.collect();
Expand All @@ -1034,7 +1036,7 @@ mod tests {
.find_references(
&NodeId::root_folder_id(),
None::<(NodeId, bool)>,
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Forward,
)
.collect();
Expand All @@ -1044,7 +1046,7 @@ mod tests {
.find_references(
&NodeId::objects_folder_id(),
Some((ReferenceTypeId::Organizes, false)),
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Forward,
)
.collect();
Expand All @@ -1067,7 +1069,7 @@ mod tests {
.find_references(
&NodeId::root_folder_id(),
Some((ReferenceTypeId::Organizes, false)),
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Inverse,
)
.collect();
Expand All @@ -1077,7 +1079,7 @@ mod tests {
.find_references(
&NodeId::objects_folder_id(),
Some((ReferenceTypeId::Organizes, false)),
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Inverse,
)
.collect();
Expand All @@ -1087,7 +1089,7 @@ mod tests {
#[test]
fn find_reference_subtypes() {
let address_space = make_sample_address_space();
let mut type_tree = TypeTree::new();
let mut type_tree = DefaultTypeTree::new();
address_space.load_into_type_tree(&mut type_tree);

let reference_types = vec![
Expand Down Expand Up @@ -1285,7 +1287,7 @@ mod tests {
let result = address_space.find_node_by_browse_path(
&object_id,
None::<(NodeId, bool)>,
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Forward,
&["Objects".into(), "Sample".into(), "v1".into()],
);
Expand All @@ -1296,7 +1298,7 @@ mod tests {
let result = address_space.find_node_by_browse_path(
&object_id,
None::<(NodeId, bool)>,
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Forward,
&["Objects".into(), "Sample".into(), "vxxx".into()],
);
Expand Down Expand Up @@ -1460,7 +1462,7 @@ mod tests {
.find_references(
&fn_node_id,
Some((ReferenceTypeId::HasProperty, false)),
&TypeTree::new(),
&DefaultTypeTree::new(),
BrowseDirection::Forward,
)
.collect();
Expand Down
4 changes: 2 additions & 2 deletions opcua-server/src/address_space/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub fn validate_node_read(
pub fn validate_value_to_write(
variable: &Variable,
value: &Variant,
type_tree: &TypeTree,
type_tree: &dyn TypeTree,
) -> Result<(), StatusCode> {
let value_rank = variable.value_rank();
let node_data_type = variable.data_type();
Expand Down Expand Up @@ -154,7 +154,7 @@ pub fn validate_node_write(
node: &NodeType,
context: &RequestContext,
node_to_write: &ParsedWriteValue,
type_tree: &TypeTree,
type_tree: &dyn TypeTree,
) -> Result<(), StatusCode> {
is_writable(context, node, node_to_write.attribute_id)?;

Expand Down
20 changes: 19 additions & 1 deletion opcua-server/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc};
use log::warn;
use tokio_util::sync::CancellationToken;

use crate::constants;
use crate::{constants, node_manager::TypeTreeForUser};
use opcua_core::config::Config;
use opcua_crypto::SecurityPolicy;
use opcua_types::MessageSecurityMode;
Expand All @@ -24,6 +24,7 @@ pub struct ServerBuilder {
pub(crate) config: ServerConfig,
pub(crate) node_managers: Vec<Box<dyn NodeManagerBuilder>>,
pub(crate) authenticator: Option<Arc<dyn AuthManager>>,
pub(crate) type_tree_getter: Option<Arc<dyn TypeTreeForUser>>,
pub(crate) token: CancellationToken,
}

Expand All @@ -34,6 +35,7 @@ impl Default for ServerBuilder {
node_managers: Default::default(),
authenticator: None,
token: CancellationToken::new(),
type_tree_getter: None,
};
builder
.with_node_manager(InMemoryNodeManagerBuilder::new(CoreNodeManagerBuilder))
Expand Down Expand Up @@ -256,6 +258,22 @@ impl ServerBuilder {
self
}

/// Set a custom type tree getter. Most servers do not need to touch this.
///
/// The type tree getter gets a type tree for a specific user, letting you have different type trees
/// for different users, which is relevant for some servers.
///
/// This is currently used for constructing event filters, and when building external references.
/// You only need to set this if you intend to have types that are not in the global `DefaultTypeTree`.
///
/// Note that built in node managers do not use the type tree getter, if you want to have
/// per-user types you need to implement a node manager that can correctly filter
/// during browse, etc. This only lets you use the custom type tree for each individual user.
pub fn with_type_tree_getter(mut self, type_tree_getter: Arc<dyn TypeTreeForUser>) -> Self {
self.type_tree_getter = Some(type_tree_getter);
self
}

/// Server application name.
pub fn application_name(mut self, application_name: impl Into<String>) -> Self {
self.config.application_name = application_name.into();
Expand Down
11 changes: 7 additions & 4 deletions opcua-server/src/events/evaluate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ mod tests {
use crate::{
address_space::{AddressSpace, CoreNamespace, ObjectTypeBuilder, VariableBuilder},
events::evaluate::like_to_regex,
node_manager::TypeTree,
node_manager::DefaultTypeTree,
BaseEventType, Event, ParsedContentFilter,
};
use opcua_types::{
Expand Down Expand Up @@ -513,9 +513,9 @@ mod tests {
}
}

fn type_tree() -> TypeTree {
fn type_tree() -> DefaultTypeTree {
let mut address_space = AddressSpace::new();
let mut type_tree = TypeTree::new();
let mut type_tree = DefaultTypeTree::new();
address_space.import_node_set::<CoreNamespace>(type_tree.namespaces_mut());
address_space.add_namespace(
"my:namespace:uri",
Expand All @@ -540,7 +540,10 @@ mod tests {
type_tree
}

fn filter(elements: Vec<ContentFilterElement>, type_tree: &TypeTree) -> ParsedContentFilter {
fn filter(
elements: Vec<ContentFilterElement>,
type_tree: &DefaultTypeTree,
) -> ParsedContentFilter {
let (_, f) = ParsedContentFilter::parse(
ContentFilter {
elements: Some(elements),
Expand Down
Loading

0 comments on commit 00c70b1

Please sign in to comment.