Skip to main content

opcua_server/diagnostics/
node_manager.rs

1use std::{
2    collections::{BTreeMap, VecDeque},
3    sync::Arc,
4    time::Duration,
5};
6
7use async_trait::async_trait;
8use opcua_core::trace_read_lock;
9use opcua_nodes::DefaultTypeTree;
10use serde::{Deserialize, Serialize};
11
12use crate::{
13    address_space::AccessLevel,
14    node_manager::{
15        as_opaque_node_id, from_opaque_node_id, impl_translate_browse_paths_using_browse,
16        AddReferenceResult, BrowseNode, BrowsePathItem, DynNodeManager, ExternalReferenceRequest,
17        NodeManager, NodeManagerBuilder, NodeManagersRef, NodeMetadata, ReadNode, RequestContext,
18        ServerContext, SyncSampler,
19    },
20};
21use opcua_types::{
22    AccessLevelExType, AccessRestrictionType, AttributeId, BrowseDirection, DataTypeId, DataValue,
23    DateTime, ExpandedNodeId, ExtensionObject, IdType, LocalizedText, NodeClass, NodeId,
24    NumericRange, ObjectId, ObjectTypeId, QualifiedName, ReferenceDescription, ReferenceTypeId,
25    RolePermissionType, StatusCode, TimestampsToReturn, VariableTypeId, Variant,
26};
27
28/// Node manager handling nodes in the server hierarchy that are not part of the
29/// core namespace, and that are somehow dynamic. This includes the node for each namespace,
30/// session diagnostics, etc.
31pub struct DiagnosticsNodeManager {
32    sampler: SyncSampler,
33    node_managers: NodeManagersRef,
34    namespace_index: u16,
35}
36
37/*
38The diagnostics node manager is a simple example of a node manager that
39obtains its structure from somewhere else. Specifically in this case,
40the structure is virtual, and obtained from the current server state.
41
42In order to allow this the node manager cannot be based on the in-memory node manager,
43which allows for static hierarchies only.
44
45We want to produce consistent node IDs without a cache, so we use opaque node IDs to
46make identifiers that describe where to find the data. That way we can handle Read's
47of nodes without explicitly storing each node ID.
48*/
49
50#[derive(Default, Clone, Debug)]
51/// Namespace metadata. This is visible in the namespace array under
52/// the `Server` node.
53pub struct NamespaceMetadata {
54    /// Default access restrictions on this namespace.
55    pub default_access_restrictions: AccessRestrictionType,
56    /// Default role permissions on this namespace.
57    pub default_role_permissions: Option<Vec<RolePermissionType>>,
58    /// Default user role permissions on this namespace.
59    pub default_user_role_permissions: Option<Vec<RolePermissionType>>,
60    /// Whether this namespace is a subset of the full namespace.
61    pub is_namespace_subset: Option<bool>,
62    /// Time this namespace was last updated.
63    pub namespace_publication_date: Option<DateTime>,
64    /// Namespace URI.
65    pub namespace_uri: String,
66    /// Namespace version.
67    pub namespace_version: Option<String>,
68    /// List of ID types in this namespace.
69    pub static_node_id_types: Option<Vec<IdType>>,
70    /// List of ranges for numeric node IDs on static nodes in this namespace.
71    pub static_numeric_node_id_range: Option<Vec<NumericRange>>,
72    /// Pattern that applies to string node IDs on static nodes in this namespace.
73    pub static_string_node_id_pattern: Option<String>,
74    /// Namespace index on the server.
75    pub namespace_index: u16,
76}
77
78#[derive(Default)]
79struct BrowseContinuationPoint {
80    nodes: VecDeque<ReferenceDescription>,
81}
82
83#[derive(Serialize, Deserialize, Debug)]
84struct NamespaceNode {
85    namespace: String,
86    property: Option<String>,
87}
88
89#[derive(Serialize, Deserialize, Debug)]
90enum DiagnosticsNode {
91    Namespace(NamespaceNode),
92}
93
94/// Builder for the diagnostics node manager.
95pub struct DiagnosticsNodeManagerBuilder;
96
97impl NodeManagerBuilder for DiagnosticsNodeManagerBuilder {
98    fn build(self: Box<Self>, context: ServerContext) -> Arc<DynNodeManager> {
99        Arc::new(DiagnosticsNodeManager::new(context))
100    }
101}
102
103impl DiagnosticsNodeManager {
104    pub(crate) fn new(context: ServerContext) -> Self {
105        let namespace_index = {
106            let mut type_tree = context.type_tree.write();
107            type_tree
108                .namespaces_mut()
109                .add_namespace(context.info.application_uri.as_ref())
110        };
111        Self {
112            sampler: SyncSampler::new(),
113            node_managers: context.node_managers.clone(),
114            namespace_index,
115        }
116    }
117
118    fn namespaces(&self, context: &RequestContext) -> BTreeMap<String, NamespaceMetadata> {
119        self.node_managers
120            .iter()
121            .flat_map(move |nm| nm.namespaces_for_user(context))
122            .map(|ns| (ns.namespace_uri.clone(), ns))
123            .collect()
124    }
125
126    fn namespace_node_metadata(&self, ns: &NamespaceMetadata) -> NodeMetadata {
127        NodeMetadata {
128            node_id: ExpandedNodeId::new(
129                as_opaque_node_id(
130                    &DiagnosticsNode::Namespace(NamespaceNode {
131                        namespace: ns.namespace_uri.clone(),
132                        property: None,
133                    }),
134                    self.namespace_index,
135                )
136                .unwrap(),
137            ),
138            type_definition: ExpandedNodeId::new(ObjectTypeId::NamespaceMetadataType),
139            browse_name: QualifiedName::new(ns.namespace_index, ns.namespace_uri.clone()),
140            display_name: LocalizedText::new("", &ns.namespace_uri),
141            node_class: NodeClass::Object,
142        }
143    }
144
145    fn property_node_metadata(&self, namespace: &str, name: &str) -> NodeMetadata {
146        NodeMetadata {
147            node_id: ExpandedNodeId::new(
148                as_opaque_node_id(
149                    &DiagnosticsNode::Namespace(NamespaceNode {
150                        namespace: namespace.to_owned(),
151                        property: Some(name.to_owned()),
152                    }),
153                    self.namespace_index,
154                )
155                .unwrap_or_default(),
156            ),
157            type_definition: VariableTypeId::PropertyType.into(),
158            browse_name: QualifiedName::new(0, name),
159            display_name: LocalizedText::new("", name),
160            node_class: NodeClass::Variable,
161        }
162    }
163
164    fn browse_namespaces(
165        &self,
166        node_to_browse: &mut BrowseNode,
167        type_tree: &DefaultTypeTree,
168        namespaces: &BTreeMap<String, NamespaceMetadata>,
169    ) {
170        // Only hierarchical references in this case, so we can check for that first.
171        if !matches!(
172            node_to_browse.browse_direction(),
173            BrowseDirection::Forward | BrowseDirection::Both
174        ) {
175            return;
176        }
177
178        if !node_to_browse.allows_reference_type(&ReferenceTypeId::HasComponent.into(), type_tree) {
179            return;
180        }
181
182        let mut cp = BrowseContinuationPoint::default();
183
184        for namespace in namespaces.values() {
185            // Handled by the core node manager
186            if namespace.namespace_index == 0 {
187                continue;
188            }
189            let metadata = self.namespace_node_metadata(namespace);
190            let ref_desc = ReferenceDescription {
191                reference_type_id: ReferenceTypeId::HasComponent.into(),
192                is_forward: true,
193                node_id: metadata.node_id,
194                browse_name: metadata.browse_name,
195                display_name: metadata.display_name,
196                node_class: metadata.node_class,
197                type_definition: metadata.type_definition,
198            };
199
200            if let AddReferenceResult::Full(c) = node_to_browse.add(type_tree, ref_desc) {
201                cp.nodes.push_back(c);
202            }
203        }
204
205        if !cp.nodes.is_empty() {
206            node_to_browse.set_next_continuation_point(Box::new(cp));
207        }
208    }
209
210    fn is_valid_property(prop: &str) -> bool {
211        matches!(
212            prop,
213            "DefaultAccessRestrictions"
214                | "DefaultRolePermissions"
215                | "DefaultUserRolePermissions"
216                | "IsNamespaceSubset"
217                | "NamespacePublicationDate"
218                | "NamespaceUri"
219                | "NamespaceVersion"
220                | "StaticNodeIdTypes"
221                | "StaticNumericNodeIdRange"
222                | "StaticStringNodeIdPattern"
223        )
224    }
225
226    fn browse_namespace_metadata_node(
227        &self,
228        node_to_browse: &mut BrowseNode,
229        type_tree: &DefaultTypeTree,
230        meta: &NamespaceMetadata,
231    ) {
232        let mut cp = BrowseContinuationPoint::default();
233
234        if matches!(
235            node_to_browse.browse_direction(),
236            BrowseDirection::Forward | BrowseDirection::Both
237        ) {
238            if node_to_browse.allows_reference_type(&ReferenceTypeId::HasProperty.into(), type_tree)
239                && node_to_browse.allows_node_class(NodeClass::Variable)
240            {
241                for prop in [
242                    "DefaultAccessRestrictions",
243                    "DefaultRolePermissions",
244                    "DefaultUserRolePermissions",
245                    "IsNamespaceSubset",
246                    "NamespacePublicationDate",
247                    "NamespaceUri",
248                    "NamespaceVersion",
249                    "StaticNodeIdTypes",
250                    "StaticNumericNodeIdRange",
251                    "StaticStringNodeIdPattern",
252                ] {
253                    let meta = self.property_node_metadata(&meta.namespace_uri, prop);
254                    let ref_desc = meta.into_ref_desc(true, ReferenceTypeId::HasProperty);
255
256                    if let AddReferenceResult::Full(c) = node_to_browse.add(type_tree, ref_desc) {
257                        cp.nodes.push_back(c);
258                    }
259                }
260            }
261
262            if node_to_browse
263                .allows_reference_type(&ReferenceTypeId::HasTypeDefinition.into(), type_tree)
264            {
265                let ref_desc = ReferenceDescription {
266                    reference_type_id: ReferenceTypeId::HasTypeDefinition.into(),
267                    is_forward: true,
268                    node_id: ObjectTypeId::NamespaceMetadataType.into(),
269                    browse_name: QualifiedName::new(0, "NamespaceMetadataType"),
270                    display_name: LocalizedText::new("", "NamespaceMetadataType"),
271                    node_class: NodeClass::ObjectType,
272                    type_definition: ExpandedNodeId::null(),
273                };
274                if let AddReferenceResult::Full(c) = node_to_browse.add(type_tree, ref_desc) {
275                    cp.nodes.push_back(c);
276                }
277            }
278        }
279
280        if matches!(
281            node_to_browse.browse_direction(),
282            BrowseDirection::Inverse | BrowseDirection::Both
283        ) {
284            let ref_desc = ReferenceDescription {
285                reference_type_id: ReferenceTypeId::HasComponent.into(),
286                is_forward: false,
287                node_id: ObjectId::Server_Namespaces.into(),
288                browse_name: QualifiedName::new(0, "Namespaces"),
289                display_name: LocalizedText::new("", "Namespaces"),
290                node_class: NodeClass::Object,
291                type_definition: ObjectTypeId::NamespacesType.into(),
292            };
293            if let AddReferenceResult::Full(c) = node_to_browse.add(type_tree, ref_desc) {
294                cp.nodes.push_back(c);
295            }
296        }
297
298        if !cp.nodes.is_empty() {
299            node_to_browse.set_next_continuation_point(Box::new(cp));
300        }
301    }
302
303    fn browse_namespace_property_node(
304        &self,
305        node_to_browse: &mut BrowseNode,
306        type_tree: &DefaultTypeTree,
307        meta: &NamespaceMetadata,
308    ) {
309        let mut cp = BrowseContinuationPoint::default();
310
311        if matches!(
312            node_to_browse.browse_direction(),
313            BrowseDirection::Forward | BrowseDirection::Both
314        ) {
315            let ref_desc = ReferenceDescription {
316                reference_type_id: ReferenceTypeId::HasTypeDefinition.into(),
317                is_forward: true,
318                node_id: VariableTypeId::PropertyType.into(),
319                browse_name: QualifiedName::new(0, "PropertyType"),
320                display_name: LocalizedText::new("", "PropertyType"),
321                node_class: NodeClass::VariableType,
322                type_definition: ExpandedNodeId::null(),
323            };
324            if let AddReferenceResult::Full(c) = node_to_browse.add(type_tree, ref_desc) {
325                cp.nodes.push_back(c);
326            }
327        }
328
329        if matches!(
330            node_to_browse.browse_direction(),
331            BrowseDirection::Inverse | BrowseDirection::Both
332        ) {
333            let metadata = self.namespace_node_metadata(meta);
334            let ref_desc = metadata.into_ref_desc(false, ReferenceTypeId::HasComponent);
335
336            if let AddReferenceResult::Full(c) = node_to_browse.add(type_tree, ref_desc) {
337                cp.nodes.push_back(c);
338            }
339        }
340
341        if !cp.nodes.is_empty() {
342            node_to_browse.set_next_continuation_point(Box::new(cp));
343        }
344    }
345
346    fn browse_namespace_node(
347        &self,
348        node_to_browse: &mut BrowseNode,
349        type_tree: &DefaultTypeTree,
350        namespaces: &BTreeMap<String, NamespaceMetadata>,
351        ns_node: &NamespaceNode,
352    ) {
353        let Some(namespace) = namespaces.get(&ns_node.namespace) else {
354            node_to_browse.set_status(StatusCode::BadNodeIdUnknown);
355            return;
356        };
357
358        if ns_node.property.is_some() {
359            self.browse_namespace_property_node(node_to_browse, type_tree, namespace);
360        } else {
361            self.browse_namespace_metadata_node(node_to_browse, type_tree, namespace);
362        }
363    }
364
365    fn read_namespace_metadata_node(
366        &self,
367        start_time: DateTime,
368        node_to_read: &mut ReadNode,
369        namespace: &NamespaceMetadata,
370    ) {
371        let v: Variant = match node_to_read.node().attribute_id {
372            AttributeId::NodeId => as_opaque_node_id(
373                &DiagnosticsNode::Namespace(NamespaceNode {
374                    namespace: namespace.namespace_uri.clone(),
375                    property: None,
376                }),
377                self.namespace_index,
378            )
379            .unwrap()
380            .into(),
381            AttributeId::NodeClass => (NodeClass::Object as i32).into(),
382            AttributeId::BrowseName => {
383                QualifiedName::new(namespace.namespace_index, &namespace.namespace_uri).into()
384            }
385            AttributeId::DisplayName => LocalizedText::new("", &namespace.namespace_uri).into(),
386            AttributeId::EventNotifier => 0u8.into(),
387            AttributeId::WriteMask | AttributeId::UserWriteMask => 0u32.into(),
388            _ => {
389                node_to_read.set_error(StatusCode::BadAttributeIdInvalid);
390                return;
391            }
392        };
393
394        node_to_read.set_result(DataValue {
395            value: Some(v),
396            status: Some(StatusCode::Good),
397            source_timestamp: Some(start_time),
398            source_picoseconds: None,
399            server_timestamp: Some(start_time),
400            server_picoseconds: None,
401        });
402    }
403
404    fn read_namespace_property_node(
405        &self,
406        start_time: DateTime,
407        node_to_read: &mut ReadNode,
408        namespace: &NamespaceMetadata,
409        prop: &str,
410    ) {
411        if !Self::is_valid_property(prop) {
412            node_to_read.set_error(StatusCode::BadNodeIdUnknown);
413            return;
414        }
415
416        let v: Variant = match node_to_read.node().attribute_id {
417            AttributeId::NodeId => as_opaque_node_id(
418                &DiagnosticsNode::Namespace(NamespaceNode {
419                    namespace: namespace.namespace_uri.clone(),
420                    property: Some(prop.to_owned()),
421                }),
422                self.namespace_index,
423            )
424            .unwrap()
425            .into(),
426            AttributeId::NodeClass => (NodeClass::Object as i32).into(),
427            AttributeId::BrowseName => QualifiedName::new(0, prop).into(),
428            AttributeId::DisplayName => LocalizedText::new("", prop).into(),
429            AttributeId::Value => match prop {
430                "DefaultAccessRestrictions" => namespace.default_access_restrictions.bits().into(),
431                "DefaultRolePermissions" => namespace
432                    .default_role_permissions
433                    .as_ref()
434                    .map(|r| {
435                        r.iter()
436                            .cloned()
437                            .map(ExtensionObject::from_message)
438                            .collect::<Vec<_>>()
439                    })
440                    .into(),
441                "DefaultUserRolePermissions" => namespace
442                    .default_user_role_permissions
443                    .as_ref()
444                    .map(|r| {
445                        r.iter()
446                            .cloned()
447                            .map(ExtensionObject::from_message)
448                            .collect::<Vec<_>>()
449                    })
450                    .into(),
451                "IsNamespaceSubset" => namespace.is_namespace_subset.into(),
452                "NamespacePublicationDate" => namespace.namespace_publication_date.into(),
453                "NamespaceUri" => namespace.namespace_uri.clone().into(),
454                "NamespaceVersion" => namespace.namespace_version.clone().into(),
455                "StaticNodeIdTypes" => namespace
456                    .static_node_id_types
457                    .as_ref()
458                    .map(|r| r.iter().map(|v| (*v) as u8).collect::<Vec<_>>())
459                    .into(),
460                "StaticNumericNodeIdRange" => namespace
461                    .static_numeric_node_id_range
462                    .as_ref()
463                    .map(|r| r.iter().map(|v| v.to_string()).collect::<Vec<_>>())
464                    .into(),
465                "StaticStringNodeIdPattern" => {
466                    namespace.static_string_node_id_pattern.clone().into()
467                }
468                _ => {
469                    node_to_read.set_error(StatusCode::BadNodeIdUnknown);
470                    return;
471                }
472            },
473            AttributeId::DataType => match prop {
474                "DefaultAccessRestrictions" => {
475                    Variant::NodeId(Box::new(DataTypeId::AccessRestrictionType.into()))
476                }
477                "DefaultRolePermissions" | "DefaultUserRolePermissions" => {
478                    Variant::NodeId(Box::new(DataTypeId::RolePermissionType.into()))
479                }
480                "IsNamespaceSubset" => Variant::NodeId(Box::new(DataTypeId::Boolean.into())),
481                "NamespacePublicationDate" => {
482                    Variant::NodeId(Box::new(DataTypeId::DateTime.into()))
483                }
484                "NamespaceUri" | "NamespaceVersion" | "StaticStringNodeIdPattern" => {
485                    Variant::NodeId(Box::new(DataTypeId::String.into()))
486                }
487                "StaticNodeIdTypes" => Variant::NodeId(Box::new(DataTypeId::IdType.into())),
488                "StaticNumericNodeIdRange" => {
489                    Variant::NodeId(Box::new(DataTypeId::NumericRange.into()))
490                }
491                _ => {
492                    node_to_read.set_error(StatusCode::BadNodeIdUnknown);
493                    return;
494                }
495            },
496            AttributeId::ValueRank => match prop {
497                "DefaultRolePermissions" | "DefaultUserRolePermissions" | "StaticNodeIdTypes" => {
498                    1.into()
499                }
500                _ => (-1).into(),
501            },
502            AttributeId::ArrayDimensions => match prop {
503                "DefaultRolePermissions" | "DefaultUserRolePermissions" | "StaticNodeIdTypes" => {
504                    vec![0u32].into()
505                }
506                _ => Variant::Empty,
507            },
508            AttributeId::AccessLevel | AttributeId::UserAccessLevel => {
509                AccessLevel::CURRENT_READ.bits().into()
510            }
511            AttributeId::AccessLevelEx => (AccessLevelExType::CurrentRead.bits() as u32).into(),
512            AttributeId::MinimumSamplingInterval => 0.0.into(),
513            AttributeId::Historizing => false.into(),
514            AttributeId::WriteMask | AttributeId::UserWriteMask => 0u32.into(),
515            _ => {
516                node_to_read.set_error(StatusCode::BadAttributeIdInvalid);
517                return;
518            }
519        };
520
521        node_to_read.set_result(DataValue {
522            value: Some(v),
523            status: Some(StatusCode::Good),
524            source_timestamp: Some(start_time),
525            source_picoseconds: None,
526            server_timestamp: Some(start_time),
527            server_picoseconds: None,
528        });
529    }
530
531    fn read_namespace_node(
532        &self,
533        start_time: DateTime,
534        node_to_read: &mut ReadNode,
535        namespaces: &BTreeMap<String, NamespaceMetadata>,
536        ns_node: &NamespaceNode,
537    ) {
538        let Some(namespace) = namespaces.get(&ns_node.namespace) else {
539            node_to_read.set_error(StatusCode::BadNodeIdUnknown);
540            return;
541        };
542
543        if let Some(prop) = &ns_node.property {
544            self.read_namespace_property_node(start_time, node_to_read, namespace, prop);
545        } else {
546            self.read_namespace_metadata_node(start_time, node_to_read, namespace);
547        }
548    }
549}
550
551#[async_trait]
552impl NodeManager for DiagnosticsNodeManager {
553    fn owns_node(&self, id: &NodeId) -> bool {
554        id.namespace == self.namespace_index
555    }
556
557    fn name(&self) -> &str {
558        "diagnostics"
559    }
560
561    fn namespaces_for_user(&self, context: &RequestContext) -> Vec<NamespaceMetadata> {
562        vec![NamespaceMetadata {
563            namespace_uri: context.info.application_uri.as_ref().to_owned(),
564            is_namespace_subset: Some(false),
565            static_node_id_types: Some(vec![IdType::Opaque]),
566            namespace_index: self.namespace_index,
567            ..Default::default()
568        }]
569    }
570
571    async fn init(&self, _type_tree: &mut DefaultTypeTree, context: ServerContext) {
572        let interval = context
573            .info
574            .config
575            .limits
576            .subscriptions
577            .min_sampling_interval_ms
578            .floor() as u64;
579        let sampler_interval = if interval > 0 { interval } else { 100 };
580        self.sampler.run(
581            Duration::from_millis(sampler_interval),
582            context.subscriptions.clone(),
583        );
584    }
585
586    async fn resolve_external_references(
587        &self,
588        context: &RequestContext,
589        items: &mut [&mut ExternalReferenceRequest],
590    ) {
591        let mut lazy_namespaces = None::<BTreeMap<String, NamespaceMetadata>>;
592
593        for req in items {
594            let Some(node_desc) = from_opaque_node_id::<DiagnosticsNode>(req.node_id()) else {
595                continue;
596            };
597            let meta = match node_desc {
598                DiagnosticsNode::Namespace(ns) => {
599                    let namespaces =
600                        lazy_namespaces.get_or_insert_with(|| self.namespaces(context));
601                    let Some(ns_node) = namespaces.get(&ns.namespace) else {
602                        continue;
603                    };
604
605                    if let Some(prop) = &ns.property {
606                        if !Self::is_valid_property(prop) {
607                            continue;
608                        }
609                        self.property_node_metadata(&ns.namespace, prop)
610                    } else {
611                        self.namespace_node_metadata(ns_node)
612                    }
613                }
614            };
615            req.set(meta);
616        }
617    }
618
619    async fn browse(
620        &self,
621        context: &RequestContext,
622        nodes_to_browse: &mut [BrowseNode],
623    ) -> Result<(), StatusCode> {
624        let mut lazy_namespaces = None::<BTreeMap<String, NamespaceMetadata>>;
625        let type_tree = trace_read_lock!(context.type_tree);
626
627        for node in nodes_to_browse {
628            if let Some(mut point) = node.take_continuation_point::<BrowseContinuationPoint>() {
629                if node.remaining() == 0 {
630                    break;
631                }
632                let Some(ref_desc) = point.nodes.pop_back() else {
633                    break;
634                };
635                // Node is already filtered.
636                node.add_unchecked(ref_desc);
637                continue;
638            }
639
640            if node.node_id().namespace == 0 {
641                let namespaces = lazy_namespaces.get_or_insert_with(|| self.namespaces(context));
642                let Ok(obj_id) = node.node_id().as_object_id() else {
643                    continue;
644                };
645                match obj_id {
646                    ObjectId::Server_Namespaces => {
647                        self.browse_namespaces(node, &type_tree, namespaces);
648                    }
649                    _ => continue,
650                }
651            } else if node.node_id().namespace == self.namespace_index {
652                let Some(node_desc) = from_opaque_node_id::<DiagnosticsNode>(node.node_id()) else {
653                    node.set_status(StatusCode::BadNodeIdUnknown);
654                    continue;
655                };
656                match node_desc {
657                    DiagnosticsNode::Namespace(ns) => {
658                        let namespaces =
659                            lazy_namespaces.get_or_insert_with(|| self.namespaces(context));
660                        self.browse_namespace_node(node, &type_tree, namespaces, &ns);
661                    }
662                }
663            }
664        }
665
666        Ok(())
667    }
668
669    async fn read(
670        &self,
671        context: &RequestContext,
672        _max_age: f64,
673        _timestamps_to_return: TimestampsToReturn,
674        nodes_to_read: &mut [&mut ReadNode],
675    ) -> Result<(), StatusCode> {
676        let mut lazy_namespaces = None::<BTreeMap<String, NamespaceMetadata>>;
677        let start_time = **context.info.start_time.load();
678
679        for node in nodes_to_read {
680            let Some(node_desc) = from_opaque_node_id::<DiagnosticsNode>(&node.node().node_id)
681            else {
682                node.set_error(StatusCode::BadNodeIdUnknown);
683                continue;
684            };
685            match node_desc {
686                DiagnosticsNode::Namespace(ns) => {
687                    let namespaces =
688                        lazy_namespaces.get_or_insert_with(|| self.namespaces(context));
689                    self.read_namespace_node(start_time, node, namespaces, &ns);
690                }
691            }
692        }
693        Ok(())
694    }
695
696    async fn translate_browse_paths_to_node_ids(
697        &self,
698        context: &RequestContext,
699        nodes: &mut [&mut BrowsePathItem],
700    ) -> Result<(), StatusCode> {
701        impl_translate_browse_paths_using_browse(self, context, nodes).await
702    }
703}