1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// OPCUA for Rust
// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2017-2022 Adam Lock
use std::convert::TryFrom;
use opcua_types::{
operand::Operand,
service_types::{
ContentFilter, ContentFilterElementResult, ContentFilterResult, EventFieldList,
EventFilter, EventFilterResult, FilterOperator, SimpleAttributeOperand,
},
status_code::StatusCode,
AttributeId, DateTimeUtc, NodeId, Variant,
};
use crate::{
address_space::{address_space::AddressSpace, node::NodeType, relative_path::*},
events::event::events_for_object,
events::operator,
};
/// This validates the event filter as best it can to make sure it doesn't contain nonsense.
pub fn validate(
event_filter: &EventFilter,
address_space: &AddressSpace,
) -> Result<EventFilterResult, StatusCode> {
let select_clause_results = event_filter.select_clauses.as_ref().map(|select_clauses| {
select_clauses
.iter()
.map(|clause| validate_select_clause(clause, address_space))
.collect()
});
let where_clause_result = validate_where_clause(&event_filter.where_clause, address_space)?;
Ok(EventFilterResult {
select_clause_results,
select_clause_diagnostic_infos: None,
where_clause_result,
})
}
/// Evaluate the event filter and see if it triggers.
pub fn evaluate(
object_id: &NodeId,
event_filter: &EventFilter,
address_space: &AddressSpace,
happened_since: &DateTimeUtc,
client_handle: u32,
) -> Option<Vec<EventFieldList>> {
if let Some(events) = events_for_object(object_id, address_space, happened_since) {
let event_fields = events
.iter()
.filter(|event_id| {
if let Ok(result) =
evaluate_where_clause(event_id, &event_filter.where_clause, address_space)
{
result == Variant::Boolean(true)
} else {
false
}
})
.map(|event_id| {
// Produce an event notification list from the select clauses.
let event_fields = event_filter.select_clauses.as_ref().map(|select_clauses| {
select_clauses
.iter()
.map(|v| operator::value_of_simple_attribute(event_id, v, address_space))
.collect()
});
EventFieldList {
client_handle,
event_fields,
}
})
.collect::<Vec<EventFieldList>>();
if event_fields.is_empty() {
None
} else {
Some(event_fields)
}
} else {
None
}
}
/// Evaluates a where clause which is a tree of conditionals
pub(crate) fn evaluate_where_clause(
object_id: &NodeId,
where_clause: &ContentFilter,
address_space: &AddressSpace,
) -> Result<Variant, StatusCode> {
// Clause is meant to have been validated before now so this code is not as stringent and makes some expectations.
if let Some(ref elements) = where_clause.elements {
if !elements.is_empty() {
use std::collections::HashSet;
let mut used_elements = HashSet::new();
used_elements.insert(0);
let result = operator::evaluate(
object_id,
&elements[0],
&mut used_elements,
elements,
address_space,
)?;
Ok(result)
} else {
Ok(true.into())
}
} else {
Ok(true.into())
}
}
fn validate_select_clause(
clause: &SimpleAttributeOperand,
address_space: &AddressSpace,
) -> StatusCode {
// The SimpleAttributeOperand structure is used in the selectClauses to select the value to return
// if an Event meets the criteria specified by the whereClause. A null value is returned in the corresponding
// event field in the publish response if the selected field is not part of the event or an
// error was returned in the selectClauseResults of the EventFilterResult.
if !clause.index_range.is_empty() {
// TODO support index ranges
error!("Select clause specifies an index range and will be rejected");
StatusCode::BadIndexRangeInvalid
} else if let Some(ref browse_path) = clause.browse_path {
// Validate that the browse paths seem okay relative to the object type definition in the clause
if let Ok(node) =
find_node_from_browse_path(address_space, &clause.type_definition_id, browse_path)
{
// Validate the attribute id. Per spec:
//
// The SimpleAttributeOperand allows the client to specify any attribute; however the server
// is only required to support the value attribute for variable nodes and the NodeId attribute
// for object nodes. That said, profiles defined in Part 7 may make support for
// additional attributes mandatory.
//
// So code will implement the bare minimum for now.
let valid_attribute_id = match node {
NodeType::Object(_) => {
// Only the node id
clause.attribute_id == AttributeId::NodeId as u32
}
NodeType::Variable(_) => {
// Only the value
clause.attribute_id == AttributeId::Value as u32
}
_ => {
// find_node_from_browse_path shouldn't have returned anything except an object
// or variable node.
panic!()
}
};
if !valid_attribute_id {
StatusCode::BadAttributeIdInvalid
} else {
StatusCode::Good
}
} else {
error!("Invalid select clause node not found {:?}", clause);
StatusCode::BadNodeIdUnknown
}
} else {
error!("Invalid select clause with no browse path supplied");
StatusCode::BadNodeIdUnknown
}
}
fn validate_where_clause(
where_clause: &ContentFilter,
address_space: &AddressSpace,
) -> Result<ContentFilterResult, StatusCode> {
// The ContentFilter structure defines a collection of elements that define filtering criteria.
// Each element in the collection describes an operator and an array of operands to be used by
// the operator. The operators that can be used in a ContentFilter are described in Table 119.
// The filter is evaluated by evaluating the first entry in the element array starting with the
// first operand in the operand array. The operands of an element may contain References to
// sub-elements resulting in the evaluation continuing to the referenced elements in the element
// array. The evaluation shall not introduce loops. For example evaluation starting from element
// “A” shall never be able to return to element “A”. However there may be more than one path
// leading to another element “B”. If an element cannot be traced back to the starting element
// it is ignored. Extra operands for any operator shall result in an error. Annex B provides
// examples using the ContentFilter structure.
if let Some(ref elements) = where_clause.elements {
let element_results = elements.iter().map(|e| {
let (status_code, operand_status_codes) = if e.filter_operands.is_none() {
// All operators need at least one operand
(StatusCode::BadFilterOperandCountMismatch, None)
} else {
let filter_operands = e.filter_operands.as_ref().unwrap();
// The right number of operators? The spec implies it is okay to pass
// more operands than the required #, but less is an error.
let operand_count_mismatch = match e.filter_operator {
FilterOperator::Equals => filter_operands.len() < 2,
FilterOperator::IsNull => filter_operands.len() < 1,
FilterOperator::GreaterThan => filter_operands.len() < 2,
FilterOperator::LessThan => filter_operands.len() < 2,
FilterOperator::GreaterThanOrEqual => filter_operands.len() < 2,
FilterOperator::LessThanOrEqual => filter_operands.len() < 2,
FilterOperator::Like => filter_operands.len() < 2,
FilterOperator::Not => filter_operands.len() < 1,
FilterOperator::Between => filter_operands.len() < 3,
FilterOperator::InList => filter_operands.len() < 2, // 2..n
FilterOperator::And => filter_operands.len() < 2,
FilterOperator::Or => filter_operands.len() < 2,
FilterOperator::Cast => filter_operands.len() < 2,
FilterOperator::BitwiseAnd => filter_operands.len() < 2,
FilterOperator::BitwiseOr => filter_operands.len() < 2,
_ => true,
};
// Check if the operands look okay
let operand_status_codes = filter_operands.iter().map(|e| {
// Look to see if any operand cannot be parsed
match <Operand>::try_from(e) {
Ok(operand) => {
match operand {
Operand::AttributeOperand(_) => {
// AttributeOperand may not be used in an EventFilter where clause
error!("AttributeOperand is not permitted in EventFilter where clause");
StatusCode::BadFilterOperandInvalid
}
Operand::ElementOperand(ref o) => {
// Check that operands have to have an index <= number of elements
if o.index as usize >= elements.len() {
error!("Invalid element operand is out of range");
StatusCode::BadFilterOperandInvalid
} else {
StatusCode::Good
}
// TODO operand should not refer to itself either directly or through circular
// references
}
Operand::SimpleAttributeOperand(ref o) => {
// The structure requires the node id of an event type supported
// by the server and a path to an InstanceDeclaration. An InstanceDeclaration
// is a Node which can be found by following forward hierarchical references from the fully
// inherited EventType where the Node is also the source of a HasModellingRuleReference. EventTypes
// InstanceDeclarations and Modelling rules are described completely in Part 3.
// In some case the same BrowsePath will apply to multiple EventTypes. If
// the Client specifies the BaseEventType in the SimpleAttributeOperand
// then the Server shall evaluate the BrowsePath without considering the Type.
// Each InstanceDeclaration in the path shall be Object or Variable Node.
// Check the element exists in the address space
if let Some(ref browse_path) = o.browse_path {
if let Ok(_node) = find_node_from_browse_path(address_space, &o.type_definition_id, browse_path) {
StatusCode::Good
} else {
StatusCode::BadFilterOperandInvalid
}
} else {
StatusCode::BadFilterOperandInvalid
}
}
_ => StatusCode::Good
}
}
Err(err) => {
error!("Operand cannot be read from extension object, err = {}", err);
StatusCode::BadFilterOperandInvalid
}
}
}).collect::<Vec<StatusCode>>();
// Check if any operands were invalid
let operator_invalid = operand_status_codes.iter().any(|e| !e.is_good());
// Check what error status to return
let status_code = if operand_count_mismatch {
error!("Where clause has invalid filter operand count");
StatusCode::BadFilterOperandCountMismatch
} else if operator_invalid {
error!("Where clause has invalid filter operator");
StatusCode::BadFilterOperatorInvalid
} else {
StatusCode::Good
};
(status_code, Some(operand_status_codes))
};
ContentFilterElementResult {
status_code,
operand_status_codes,
operand_diagnostic_infos: None,
}
}).collect::<Vec<ContentFilterElementResult>>();
Ok(ContentFilterResult {
element_results: Some(element_results),
element_diagnostic_infos: None,
})
} else {
Ok(ContentFilterResult {
element_results: None,
element_diagnostic_infos: None,
})
}
}
#[test]
fn validate_where_clause_test() {
use opcua_types::service_types::ContentFilterElement;
let address_space = AddressSpace::new();
{
let where_clause = ContentFilter { elements: None };
// check for at least one filter operand
let result = validate_where_clause(&where_clause, &address_space);
assert_eq!(
result.unwrap(),
ContentFilterResult {
element_results: None,
element_diagnostic_infos: None,
}
);
}
// Make a where clause where every single operator is included but each has the wrong number of operands.
// We should expect them all to be in error
{
let where_clause = ContentFilter {
elements: Some(vec![
ContentFilterElement::from((FilterOperator::Equals, vec![Operand::literal(10)])),
ContentFilterElement::from((FilterOperator::IsNull, vec![])),
ContentFilterElement::from((
FilterOperator::GreaterThan,
vec![Operand::literal(10)],
)),
ContentFilterElement::from((FilterOperator::LessThan, vec![Operand::literal(10)])),
ContentFilterElement::from((
FilterOperator::GreaterThanOrEqual,
vec![Operand::literal(10)],
)),
ContentFilterElement::from((
FilterOperator::LessThanOrEqual,
vec![Operand::literal(10)],
)),
ContentFilterElement::from((FilterOperator::Like, vec![Operand::literal(10)])),
ContentFilterElement::from((FilterOperator::Not, vec![])),
ContentFilterElement::from((
FilterOperator::Between,
vec![Operand::literal(10), Operand::literal(20)],
)),
ContentFilterElement::from((FilterOperator::InList, vec![Operand::literal(10)])),
ContentFilterElement::from((FilterOperator::And, vec![Operand::literal(10)])),
ContentFilterElement::from((FilterOperator::Or, vec![Operand::literal(10)])),
ContentFilterElement::from((FilterOperator::Cast, vec![Operand::literal(10)])),
ContentFilterElement::from((
FilterOperator::BitwiseAnd,
vec![Operand::literal(10)],
)),
ContentFilterElement::from((FilterOperator::BitwiseOr, vec![Operand::literal(10)])),
ContentFilterElement::from((FilterOperator::Like, vec![Operand::literal(10)])),
]),
};
// Check for less than required number of operands
let result = validate_where_clause(&where_clause, &address_space).unwrap();
result
.element_results
.unwrap()
.iter()
.for_each(|e| assert_eq!(e.status_code, StatusCode::BadFilterOperandCountMismatch));
}
// check for filter operator invalid, by giving it a bogus extension object for an element
{
use opcua_types::{service_types::ContentFilterElement, ExtensionObject};
let bad_operator = ExtensionObject::null();
let where_clause = ContentFilter {
elements: Some(vec![ContentFilterElement {
filter_operator: FilterOperator::IsNull,
filter_operands: Some(vec![bad_operator]),
}]),
};
let result = validate_where_clause(&where_clause, &address_space).unwrap();
let element_results = result.element_results.unwrap();
assert_eq!(element_results.len(), 1);
assert_eq!(
element_results[0].status_code,
StatusCode::BadFilterOperatorInvalid
);
}
// TODO check operands are compatible with operator
// TODO check for ElementOperands which are cyclical or out of range
}