opcua_server/address_space/
method.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2022 Adam Lock
4
5//! Contains the implementation of `Method` and `MethodBuilder`.
6
7use std::sync::{Arc, RwLock};
8
9use opcua_types::service_types::{Argument, MethodAttributes};
10
11use crate::{
12    address_space::{
13        address_space::MethodCallback,
14        base::Base,
15        node::{Node, NodeBase},
16        variable::VariableBuilder,
17    },
18    session::SessionManager,
19};
20
21node_builder_impl!(MethodBuilder, Method);
22node_builder_impl_component_of!(MethodBuilder);
23node_builder_impl_generates_event!(MethodBuilder);
24
25impl MethodBuilder {
26    /// Specify output arguments from the method. This will create an OutputArguments
27    /// variable child of the method which describes the out parameters.
28    pub fn output_args(self, address_space: &mut AddressSpace, arguments: &[Argument]) -> Self {
29        self.insert_args("OutputArguments", address_space, arguments);
30        self
31    }
32
33    /// Specify input arguments to the method. This will create an InputArguments
34    /// variable child of the method which describes the in parameters.
35    pub fn input_args(self, address_space: &mut AddressSpace, arguments: &[Argument]) -> Self {
36        self.insert_args("InputArguments", address_space, arguments);
37        self
38    }
39
40    pub fn callback(mut self, callback: MethodCallback) -> Self {
41        self.node.set_callback(callback);
42        self
43    }
44
45    fn args_to_variant(arguments: &[Argument]) -> Variant {
46        let arguments = arguments
47            .iter()
48            .map(|arg| {
49                Variant::from(ExtensionObject::from_encodable(
50                    ObjectId::Argument_Encoding_DefaultBinary,
51                    arg,
52                ))
53            })
54            .collect::<Vec<Variant>>();
55        Variant::from((VariantTypeId::ExtensionObject, arguments))
56    }
57
58    fn insert_args(
59        &self,
60        args_name: &str,
61        address_space: &mut AddressSpace,
62        arguments: &[Argument],
63    ) {
64        let fn_node_id = self.node.node_id();
65        let args_id = NodeId::next_numeric(fn_node_id.namespace);
66        let args_value = Self::args_to_variant(arguments);
67        VariableBuilder::new(&args_id, args_name, args_name)
68            .property_of(fn_node_id)
69            .has_type_definition(VariableTypeId::PropertyType)
70            .data_type(DataTypeId::Argument)
71            .value_rank(1)
72            .array_dimensions(&[arguments.len() as u32])
73            .value(args_value)
74            .insert(address_space);
75    }
76}
77
78/// A `Method` is a type of node within the `AddressSpace`.
79#[derive(Derivative)]
80#[derivative(Debug)]
81pub struct Method {
82    base: Base,
83    executable: bool,
84    user_executable: bool,
85    #[derivative(Debug = "ignore")]
86    callback: Option<MethodCallback>,
87}
88
89impl Default for Method {
90    fn default() -> Self {
91        Self {
92            base: Base::new(NodeClass::Method, &NodeId::null(), "", ""),
93            executable: false,
94            user_executable: false,
95            callback: None,
96        }
97    }
98}
99
100node_base_impl!(Method);
101
102impl Node for Method {
103    fn get_attribute_max_age(
104        &self,
105        timestamps_to_return: TimestampsToReturn,
106        attribute_id: AttributeId,
107        index_range: NumericRange,
108        data_encoding: &QualifiedName,
109        max_age: f64,
110    ) -> Option<DataValue> {
111        match attribute_id {
112            AttributeId::Executable => Some(self.executable().into()),
113            AttributeId::UserExecutable => Some(self.user_executable().into()),
114            _ => self.base.get_attribute_max_age(
115                timestamps_to_return,
116                attribute_id,
117                index_range,
118                data_encoding,
119                max_age,
120            ),
121        }
122    }
123
124    fn set_attribute(
125        &mut self,
126        attribute_id: AttributeId,
127        value: Variant,
128    ) -> Result<(), StatusCode> {
129        match attribute_id {
130            AttributeId::Executable => {
131                if let Variant::Boolean(v) = value {
132                    self.set_executable(v);
133                    Ok(())
134                } else {
135                    Err(StatusCode::BadTypeMismatch)
136                }
137            }
138            AttributeId::UserExecutable => {
139                if let Variant::Boolean(v) = value {
140                    self.set_user_executable(v);
141                    Ok(())
142                } else {
143                    Err(StatusCode::BadTypeMismatch)
144                }
145            }
146            _ => self.base.set_attribute(attribute_id, value),
147        }
148    }
149}
150
151impl Method {
152    pub fn new<R, S>(
153        node_id: &NodeId,
154        browse_name: R,
155        display_name: S,
156        executable: bool,
157        user_executable: bool,
158    ) -> Method
159    where
160        R: Into<QualifiedName>,
161        S: Into<LocalizedText>,
162    {
163        Method {
164            base: Base::new(NodeClass::Method, node_id, browse_name, display_name),
165            executable,
166            user_executable,
167            callback: None,
168        }
169    }
170
171    pub fn from_attributes<S>(
172        node_id: &NodeId,
173        browse_name: S,
174        attributes: MethodAttributes,
175    ) -> Result<Self, ()>
176    where
177        S: Into<QualifiedName>,
178    {
179        let mandatory_attributes = AttributesMask::DISPLAY_NAME
180            | AttributesMask::EXECUTABLE
181            | AttributesMask::USER_EXECUTABLE;
182        let mask = AttributesMask::from_bits(attributes.specified_attributes).ok_or(())?;
183        if mask.contains(mandatory_attributes) {
184            let mut node = Self::new(
185                node_id,
186                browse_name,
187                attributes.display_name,
188                attributes.executable,
189                attributes.user_executable,
190            );
191            if mask.contains(AttributesMask::DESCRIPTION) {
192                node.set_description(attributes.description);
193            }
194            if mask.contains(AttributesMask::WRITE_MASK) {
195                node.set_write_mask(WriteMask::from_bits_truncate(attributes.write_mask));
196            }
197            if mask.contains(AttributesMask::USER_WRITE_MASK) {
198                node.set_user_write_mask(WriteMask::from_bits_truncate(attributes.user_write_mask));
199            }
200            Ok(node)
201        } else {
202            error!("Method cannot be created from attributes - missing mandatory values");
203            Err(())
204        }
205    }
206
207    pub fn is_valid(&self) -> bool {
208        self.has_callback() && self.base.is_valid()
209    }
210
211    pub fn executable(&self) -> bool {
212        self.executable
213    }
214
215    pub fn set_executable(&mut self, executable: bool) {
216        self.executable = executable;
217    }
218
219    pub fn user_executable(&self) -> bool {
220        // User executable cannot be true unless executable is true
221        self.executable && self.user_executable
222        // TODO this should check the current session state to determine if the user
223        //  has permissions to execute this method
224    }
225
226    pub fn set_user_executable(&mut self, user_executable: bool) {
227        self.user_executable = user_executable;
228    }
229
230    pub fn set_callback(&mut self, callback: MethodCallback) {
231        self.callback = Some(callback);
232    }
233
234    pub fn has_callback(&self) -> bool {
235        self.callback.is_some()
236    }
237
238    pub fn call(
239        &mut self,
240        session_id: &NodeId,
241        session_manager: Arc<RwLock<SessionManager>>,
242        request: &CallMethodRequest,
243    ) -> Result<CallMethodResult, StatusCode> {
244        if let Some(ref mut callback) = self.callback {
245            // Call the handler
246            callback.call(session_id, session_manager, request)
247        } else {
248            error!(
249                "Method call to {} has no handler, treating as invalid",
250                self.node_id()
251            );
252            Err(StatusCode::BadMethodInvalid)
253        }
254    }
255}