Skip to main content

opcua_server/address_space/
utils.rs

1use crate::node_manager::{ParsedReadValueId, ParsedWriteValue, RequestContext, ServerContext};
2use opcua_nodes::TypeTree;
3use opcua_types::{
4    AttributeId, DataEncoding, DataTypeId, DataValue, DateTime, NumericRange, StatusCode,
5    TimestampsToReturn, Variant, WriteMask,
6};
7use tracing::debug;
8
9use super::{AccessLevel, AddressSpace, HasNodeId, NodeType, Variable};
10
11/// Validate that the user given by `context` can read the value
12/// of the given node.
13pub fn is_readable(context: &RequestContext, node: &NodeType) -> Result<(), StatusCode> {
14    if !user_access_level(context, node).contains(AccessLevel::CURRENT_READ) {
15        Err(StatusCode::BadUserAccessDenied)
16    } else {
17        Ok(())
18    }
19}
20
21/// Validate that the user given by `context` can write to the
22/// attribute given by `attribute_id`.
23pub fn is_writable(
24    context: &RequestContext,
25    node: &NodeType,
26    attribute_id: AttributeId,
27) -> Result<(), StatusCode> {
28    if let (NodeType::Variable(_), AttributeId::Value) = (node, attribute_id) {
29        if !user_access_level(context, node).contains(AccessLevel::CURRENT_WRITE) {
30            return Err(StatusCode::BadUserAccessDenied);
31        }
32
33        Ok(())
34    } else {
35        let mask_value = match attribute_id {
36            // The default address space does not support modifying node class or node id,
37            // Custom node managers are allowed to.
38            AttributeId::BrowseName => WriteMask::BROWSE_NAME,
39            AttributeId::DisplayName => WriteMask::DISPLAY_NAME,
40            AttributeId::Description => WriteMask::DESCRIPTION,
41            AttributeId::WriteMask => WriteMask::WRITE_MASK,
42            AttributeId::UserWriteMask => WriteMask::USER_WRITE_MASK,
43            AttributeId::IsAbstract => WriteMask::IS_ABSTRACT,
44            AttributeId::Symmetric => WriteMask::SYMMETRIC,
45            AttributeId::InverseName => WriteMask::INVERSE_NAME,
46            AttributeId::ContainsNoLoops => WriteMask::CONTAINS_NO_LOOPS,
47            AttributeId::EventNotifier => WriteMask::EVENT_NOTIFIER,
48            AttributeId::Value => WriteMask::VALUE_FOR_VARIABLE_TYPE,
49            AttributeId::DataType => WriteMask::DATA_TYPE,
50            AttributeId::ValueRank => WriteMask::VALUE_RANK,
51            AttributeId::ArrayDimensions => WriteMask::ARRAY_DIMENSIONS,
52            AttributeId::AccessLevel => WriteMask::ACCESS_LEVEL,
53            AttributeId::UserAccessLevel => WriteMask::USER_ACCESS_LEVEL,
54            AttributeId::MinimumSamplingInterval => WriteMask::MINIMUM_SAMPLING_INTERVAL,
55            AttributeId::Historizing => WriteMask::HISTORIZING,
56            AttributeId::Executable => WriteMask::EXECUTABLE,
57            AttributeId::UserExecutable => WriteMask::USER_EXECUTABLE,
58            AttributeId::DataTypeDefinition => WriteMask::DATA_TYPE_DEFINITION,
59            AttributeId::RolePermissions => WriteMask::ROLE_PERMISSIONS,
60            AttributeId::AccessRestrictions => WriteMask::ACCESS_RESTRICTIONS,
61            AttributeId::AccessLevelEx => WriteMask::ACCESS_LEVEL_EX,
62            _ => return Err(StatusCode::BadNotWritable),
63        };
64
65        let write_mask = node.as_node().write_mask();
66        if write_mask.is_none() || write_mask.is_some_and(|wm| !wm.contains(mask_value)) {
67            return Err(StatusCode::BadNotWritable);
68        }
69        Ok(())
70    }
71}
72
73/// Get the effective user access level for `node`.
74pub fn user_access_level(context: &RequestContext, node: &NodeType) -> AccessLevel {
75    let user_access_level = if let NodeType::Variable(ref node) = node {
76        node.user_access_level()
77    } else {
78        AccessLevel::CURRENT_READ
79    };
80    context.authenticator.effective_user_access_level(
81        &context.token,
82        user_access_level,
83        node.node_id(),
84    )
85}
86
87/// Validate that the user given by `context` is allowed to read
88/// the value of `node`.
89pub fn validate_node_read(
90    node: &NodeType,
91    context: &RequestContext,
92    node_to_read: &ParsedReadValueId,
93) -> Result<(), StatusCode> {
94    is_readable(context, node)?;
95
96    if node_to_read.attribute_id != AttributeId::Value
97        && node_to_read.index_range != NumericRange::None
98    {
99        return Err(StatusCode::BadIndexRangeDataMismatch);
100    }
101
102    if !is_supported_data_encoding(&node_to_read.data_encoding) {
103        debug!(
104            "read_node_value result for read node id {}, attribute {:?} is invalid data encoding",
105            node_to_read.node_id, node_to_read.attribute_id
106        );
107        return Err(StatusCode::BadDataEncodingInvalid);
108    }
109
110    Ok(())
111}
112
113/// Validate `value`, verifying that it can be written as the value of
114/// `variable`.
115pub fn validate_value_to_write(
116    variable: &Variable,
117    value: &Variant,
118    type_tree: &dyn TypeTree,
119) -> Result<(), StatusCode> {
120    let value_rank = variable.value_rank();
121    let node_data_type = variable.data_type();
122
123    if matches!(value, Variant::Empty) {
124        return Ok(());
125    }
126
127    if let Some(value_data_type) = value.data_type() {
128        let Some(data_type) = value_data_type.try_resolve(type_tree.namespaces()) else {
129            return Err(StatusCode::BadTypeMismatch);
130        };
131        // Value is scalar, check if the data type matches
132        let data_type_matches = type_tree.is_subtype_of(&data_type, &node_data_type);
133
134        if !data_type_matches {
135            if value.is_array() {
136                return Err(StatusCode::BadTypeMismatch);
137            }
138            // Check if the value to write is a byte string and the receiving node type a byte array.
139            // This code is a mess just for some weird edge case in the spec that a write from
140            // a byte string to a byte array should succeed
141            match value {
142                Variant::ByteString(_) => {
143                    if node_data_type == DataTypeId::Byte {
144                        match value_rank {
145                            -2 | -3 | 1 => Ok(()),
146                            _ => Err(StatusCode::BadTypeMismatch),
147                        }
148                    } else {
149                        Err(StatusCode::BadTypeMismatch)
150                    }
151                }
152                _ => Ok(()),
153            }
154        } else {
155            Ok(())
156        }
157    } else {
158        Err(StatusCode::BadTypeMismatch)
159    }
160}
161
162/// Validate that the user given by `context` can write to the attribute given
163/// by `node_to_write` on `node`.
164pub fn validate_node_write(
165    node: &NodeType,
166    context: &RequestContext,
167    node_to_write: &ParsedWriteValue,
168    type_tree: &dyn TypeTree,
169) -> Result<(), StatusCode> {
170    is_writable(context, node, node_to_write.attribute_id)?;
171
172    if node_to_write.attribute_id != AttributeId::Value && node_to_write.index_range.has_range() {
173        return Err(StatusCode::BadWriteNotSupported);
174    }
175
176    let Some(value) = node_to_write.value.value.as_ref() else {
177        return Err(StatusCode::BadTypeMismatch);
178    };
179
180    // TODO: We should do type validation for every attribute, not just value.
181    if let (NodeType::Variable(var), AttributeId::Value) = (node, node_to_write.attribute_id) {
182        validate_value_to_write(var, value, type_tree)?;
183    }
184
185    Ok(())
186}
187
188/// Return `true` if we support the given data encoding.
189///
190/// We currently only support `Binary`.
191pub fn is_supported_data_encoding(data_encoding: &DataEncoding) -> bool {
192    matches!(data_encoding, DataEncoding::Binary)
193}
194
195/// Invoke `Read` for the given `node_to_read` on `node`.
196///
197/// This can return a data value containing an error if validation failed.
198pub fn read_node_value(
199    node: &NodeType,
200    context: &RequestContext,
201    node_to_read: &ParsedReadValueId,
202    max_age: f64,
203    timestamps_to_return: TimestampsToReturn,
204) -> DataValue {
205    let mut result_value = DataValue::null();
206
207    let Some(attribute) = node.as_node().get_attribute_max_age(
208        timestamps_to_return,
209        node_to_read.attribute_id,
210        &node_to_read.index_range,
211        &node_to_read.data_encoding,
212        max_age,
213    ) else {
214        result_value.status = Some(StatusCode::BadAttributeIdInvalid);
215        return result_value;
216    };
217
218    let value = if node_to_read.attribute_id == AttributeId::UserAccessLevel {
219        match attribute.value {
220            Some(Variant::Byte(val)) => {
221                let access_level = AccessLevel::from_bits_truncate(val);
222                let access_level = context.authenticator.effective_user_access_level(
223                    &context.token,
224                    access_level,
225                    node.node_id(),
226                );
227                Some(Variant::from(access_level.bits()))
228            }
229            Some(v) => Some(v),
230            _ => None,
231        }
232    } else {
233        attribute.value
234    };
235
236    let value = if node_to_read.attribute_id == AttributeId::UserExecutable {
237        match value {
238            Some(Variant::Boolean(val)) => Some(Variant::from(
239                val && context
240                    .authenticator
241                    .is_user_executable(&context.token, node.node_id()),
242            )),
243            r => r,
244        }
245    } else {
246        value
247    };
248
249    result_value.value = value;
250    result_value.status = attribute.status;
251    if matches!(node, NodeType::Variable(_)) && node_to_read.attribute_id == AttributeId::Value {
252        match timestamps_to_return {
253            TimestampsToReturn::Source => {
254                result_value.source_timestamp = attribute.source_timestamp;
255                result_value.source_picoseconds = attribute.source_picoseconds;
256            }
257            TimestampsToReturn::Server => {
258                result_value.server_timestamp = attribute.server_timestamp;
259                result_value.server_picoseconds = attribute.server_picoseconds;
260            }
261            TimestampsToReturn::Both => {
262                result_value.source_timestamp = attribute.source_timestamp;
263                result_value.source_picoseconds = attribute.source_picoseconds;
264                result_value.server_timestamp = attribute.server_timestamp;
265                result_value.server_picoseconds = attribute.server_picoseconds;
266            }
267            TimestampsToReturn::Neither | TimestampsToReturn::Invalid => {
268                // Nothing needs to change
269            }
270        }
271    }
272    result_value
273}
274
275/// Invoke `Write` for the given `node_to_write` on `node`.
276pub fn write_node_value(
277    node: &mut NodeType,
278    node_to_write: &ParsedWriteValue,
279) -> Result<(), StatusCode> {
280    let now = DateTime::now();
281    if node_to_write.attribute_id == AttributeId::Value {
282        if let NodeType::Variable(variable) = node {
283            return variable.set_value_range(
284                node_to_write.value.value.clone().unwrap_or_default(),
285                &node_to_write.index_range,
286                node_to_write.value.status.unwrap_or_default(),
287                &now,
288                &node_to_write.value.source_timestamp.unwrap_or(now),
289            );
290        }
291    }
292    node.as_mut_node().set_attribute(
293        node_to_write.attribute_id,
294        node_to_write.value.value.clone().unwrap_or_default(),
295    )
296}
297
298/// Add the given list of namespaces to the type tree in `context` and
299/// `address_space`.
300pub fn add_namespaces(
301    context: &ServerContext,
302    address_space: &mut AddressSpace,
303    namespaces: &[&str],
304) -> Vec<u16> {
305    let mut type_tree = context.type_tree.write();
306    let mut res = Vec::new();
307    for ns in namespaces {
308        let idx = type_tree.namespaces_mut().add_namespace(ns);
309        address_space.add_namespace(ns, idx);
310        res.push(idx);
311    }
312    res
313}