Skip to main content

opcua_nodes/events/
validation.rs

1use std::collections::HashSet;
2
3use hashbrown::HashMap;
4
5use opcua_types::{
6    AttributeId, ContentFilter, ContentFilterElementResult, ContentFilterResult, ElementOperand,
7    EventFilter, EventFilterResult, FilterOperator, LiteralOperand, NodeClass, NodeId,
8    NumericRange, ObjectTypeId, Operand, QualifiedName, RelativePath, SimpleAttributeOperand,
9    StatusCode, UAString,
10};
11
12use crate::TypeTree;
13
14#[derive(Debug, Clone)]
15/// Parsed version of the raw [opcua_types::AttributeOperand]
16pub struct ParsedAttributeOperand {
17    /// Node ID of the node to get attribute from.
18    pub node_id: NodeId,
19    /// Attribute alias.
20    pub alias: UAString,
21    /// Browse path to the property to get from.
22    pub browse_path: RelativePath,
23    /// Attribute ID to get.
24    pub attribute_id: AttributeId,
25    /// Range of attribute to get.
26    pub index_range: NumericRange,
27}
28
29#[derive(Debug, Clone)]
30/// Parsed version of the raw [SimpleAttributeOperand].
31pub struct ParsedSimpleAttributeOperand {
32    /// Node ID of the type definition to get values from.
33    pub type_definition_id: NodeId,
34    /// Path to the property to get.
35    pub browse_path: Vec<QualifiedName>,
36    /// Attribute ID to get.
37    pub attribute_id: AttributeId,
38    /// Range of attribute to get.
39    pub index_range: NumericRange,
40}
41
42#[derive(Debug, Clone)]
43/// Parsed and validated [Operand].
44pub enum ParsedOperand {
45    /// Another element in the filter.
46    ElementOperand(ElementOperand),
47    /// A literal value.
48    LiteralOperand(LiteralOperand),
49    /// An attribute in a different node.
50    AttributeOperand(ParsedAttributeOperand),
51    /// An attribute of a type.
52    SimpleAttributeOperand(ParsedSimpleAttributeOperand),
53}
54
55impl ParsedOperand {
56    pub(crate) fn parse(
57        operand: Operand,
58        num_elements: usize,
59        type_tree: &dyn TypeTree,
60        allow_attribute_operands: bool,
61    ) -> Result<Self, StatusCode> {
62        match operand {
63            Operand::ElementOperand(e) => {
64                if e.index as usize >= num_elements {
65                    Err(StatusCode::BadFilterOperandInvalid)
66                } else {
67                    Ok(Self::ElementOperand(e))
68                }
69            }
70            Operand::LiteralOperand(o) => Ok(Self::LiteralOperand(o)),
71            Operand::AttributeOperand(o) => {
72                if !allow_attribute_operands {
73                    return Err(StatusCode::BadFilterOperandInvalid);
74                }
75                let attribute_id = AttributeId::from_u32(o.attribute_id)
76                    .map_err(|_| StatusCode::BadAttributeIdInvalid)?;
77                Ok(Self::AttributeOperand(ParsedAttributeOperand {
78                    node_id: o.node_id,
79                    attribute_id,
80                    alias: o.alias,
81                    browse_path: o.browse_path,
82                    index_range: o.index_range,
83                }))
84            }
85            Operand::SimpleAttributeOperand(o) => Ok(Self::SimpleAttributeOperand(
86                validate_select_clause(o, type_tree)?,
87            )),
88        }
89    }
90}
91
92#[derive(Debug, Clone)]
93/// Parsed version of the raw [EventFilter].
94pub struct ParsedEventFilter {
95    pub(super) content_filter: ParsedContentFilter,
96    pub(super) select_clauses: Vec<ParsedSimpleAttributeOperand>,
97}
98
99impl ParsedEventFilter {
100    /// Try to build a parsed filter from a raw [EventFilter].
101    ///
102    /// This may fail, but will always return an [EventFilterResult].
103    pub fn new(
104        raw: EventFilter,
105        type_tree: &dyn TypeTree,
106    ) -> (EventFilterResult, Result<Self, StatusCode>) {
107        validate(raw, type_tree)
108    }
109}
110
111#[derive(Debug, Clone)]
112/// Parsed version of the raw [ContentFilter].
113pub struct ParsedContentFilter {
114    pub(super) elements: Vec<ParsedContentFilterElement>,
115}
116
117impl ParsedContentFilter {
118    /// Create a new empty content filter.
119    pub fn empty() -> Self {
120        Self {
121            elements: Vec::new(),
122        }
123    }
124
125    /// Create a new content filter from the raw [ContentFilter].
126    ///
127    /// If `allow_attribute_operands` is false, parsing will fail
128    /// if it encounters an attribute operand.
129    ///
130    /// `banned_operators` is a list of operators that are not allowed in the filter.
131    /// If any of these operators are present, the parsing will fail with
132    /// `StatusCode::BadFilterOperatorUnsupported`.
133    pub fn parse(
134        filter: ContentFilter,
135        type_tree: &dyn TypeTree,
136        allow_attribute_operands: bool,
137        banned_operators: &[FilterOperator],
138    ) -> (ContentFilterResult, Result<ParsedContentFilter, StatusCode>) {
139        validate_where_clause(
140            filter,
141            type_tree,
142            allow_attribute_operands,
143            banned_operators,
144        )
145    }
146}
147
148#[derive(Debug, Clone)]
149/// Element of a parsed content filter.
150pub struct ParsedContentFilterElement {
151    pub(super) operator: FilterOperator,
152    pub(super) operands: Vec<ParsedOperand>,
153}
154
155/// This validates the event filter as best it can to make sure it doesn't contain nonsense.
156fn validate(
157    event_filter: EventFilter,
158    type_tree: &dyn TypeTree,
159) -> (EventFilterResult, Result<ParsedEventFilter, StatusCode>) {
160    let num_select_clauses = event_filter
161        .select_clauses
162        .as_ref()
163        .map(|r| r.len())
164        .unwrap_or_default();
165    let mut select_clause_results = Vec::with_capacity(num_select_clauses);
166    let mut final_select_clauses = Vec::with_capacity(num_select_clauses);
167    for clause in event_filter.select_clauses.into_iter().flatten() {
168        match validate_select_clause(clause, type_tree) {
169            Ok(result) => {
170                select_clause_results.push(StatusCode::Good);
171                final_select_clauses.push(result);
172            }
173            Err(e) => select_clause_results.push(e),
174        }
175    }
176    let (where_clause_result, parsed_where_clause) = validate_where_clause(
177        event_filter.where_clause,
178        type_tree,
179        false,
180        &[FilterOperator::InView, FilterOperator::RelatedTo],
181    );
182
183    (
184        EventFilterResult {
185            select_clause_results: if select_clause_results.is_empty() {
186                None
187            } else {
188                Some(select_clause_results)
189            },
190            select_clause_diagnostic_infos: None,
191            where_clause_result,
192        },
193        parsed_where_clause.map(|f| ParsedEventFilter {
194            content_filter: f,
195            select_clauses: final_select_clauses,
196        }),
197    )
198}
199
200fn validate_select_clause(
201    clause: SimpleAttributeOperand,
202    type_tree: &dyn TypeTree,
203) -> Result<ParsedSimpleAttributeOperand, StatusCode> {
204    let Some(path) = clause.browse_path else {
205        return Err(StatusCode::BadNodeIdUnknown);
206    };
207
208    let Ok(attribute_id) = AttributeId::from_u32(clause.attribute_id) else {
209        return Err(StatusCode::BadAttributeIdInvalid);
210    };
211
212    // From the standard:  If the SimpleAttributeOperand is used in an EventFilter
213    // and the typeDefinitionId is BaseEventType the Server shall evaluate the
214    // browsePath without considering the typeDefinitionId.
215    if clause.type_definition_id == (0, ObjectTypeId::BaseEventType as u32) {
216        // Do a simpler form of the attribute ID check in this case.
217        if attribute_id != AttributeId::NodeId && attribute_id != AttributeId::Value {
218            return Err(StatusCode::BadAttributeIdInvalid);
219        }
220        // We could in theory evaluate _every_ event type here, but that would be painful
221        // and potentially expensive on servers with lots of types. It also wouldn't
222        // be all that helpful.
223        return Ok(ParsedSimpleAttributeOperand {
224            type_definition_id: clause.type_definition_id,
225            browse_path: path,
226            attribute_id,
227            index_range: clause.index_range,
228        });
229    }
230
231    let Some(node) = type_tree.find_type_prop_by_browse_path(&clause.type_definition_id, &path)
232    else {
233        return Err(StatusCode::BadNodeIdUnknown);
234    };
235
236    // Validate the attribute id. Per spec:
237    //
238    //   The SimpleAttributeOperand allows the client to specify any attribute; however the server
239    //   is only required to support the value attribute for variable nodes and the NodeId attribute
240    //   for object nodes. That said, profiles defined in Part 7 may make support for
241    //   additional attributes mandatory.
242    //
243    // So code will implement the bare minimum for now.
244    let is_valid = match node.node_class {
245        NodeClass::Object => attribute_id == AttributeId::NodeId,
246        NodeClass::Variable => attribute_id == AttributeId::Value,
247        _ => false,
248    };
249
250    if !is_valid {
251        Err(StatusCode::BadAttributeIdInvalid)
252    } else {
253        Ok(ParsedSimpleAttributeOperand {
254            type_definition_id: clause.type_definition_id,
255            browse_path: path,
256            attribute_id,
257            index_range: clause.index_range,
258        })
259    }
260}
261
262fn validate_where_clause(
263    where_clause: ContentFilter,
264    type_tree: &dyn TypeTree,
265    allow_attribute_operand: bool,
266    banned_operators: &[FilterOperator],
267) -> (ContentFilterResult, Result<ParsedContentFilter, StatusCode>) {
268    // The ContentFilter structure defines a collection of elements that define filtering criteria.
269    // Each element in the collection describes an operator and an array of operands to be used by
270    // the operator. The operators that can be used in a ContentFilter are described in Table 119.
271    // The filter is evaluated by evaluating the first entry in the element array starting with the
272    // first operand in the operand array. The operands of an element may contain References to
273    // sub-elements resulting in the evaluation continuing to the referenced elements in the element
274    // array. The evaluation shall not introduce loops. For example evaluation starting from element
275    // “A” shall never be able to return to element “A”. However there may be more than one path
276    // leading to another element “B”. If an element cannot be traced back to the starting element
277    // it is ignored. Extra operands for any operator shall result in an error. Annex B provides
278    // examples using the ContentFilter structure.
279
280    let Some(elements) = where_clause.elements else {
281        return (
282            ContentFilterResult {
283                element_results: None,
284                element_diagnostic_infos: None,
285            },
286            Ok(ParsedContentFilter::empty()),
287        );
288    };
289
290    let mut operand_refs: HashMap<usize, Vec<usize>> = HashMap::new();
291    let num_elements = elements.len();
292    let element_result_pairs: Vec<(
293        ContentFilterElementResult,
294        Option<ParsedContentFilterElement>,
295    )> = elements
296        .into_iter()
297        .enumerate()
298        .map(|(element_idx, e)| {
299            let Some(filter_operands) = e.filter_operands else {
300                return (
301                    ContentFilterElementResult {
302                        status_code: StatusCode::BadFilterOperandCountMismatch,
303                        operand_status_codes: None,
304                        operand_diagnostic_infos: None,
305                    },
306                    None,
307                );
308            };
309            let num_filter_operands = filter_operands.len();
310
311            let operand_count_mismatch = match e.filter_operator {
312                FilterOperator::Equals => filter_operands.len() != 2,
313                FilterOperator::IsNull => filter_operands.len() != 1,
314                FilterOperator::GreaterThan => filter_operands.len() != 2,
315                FilterOperator::LessThan => filter_operands.len() != 2,
316                FilterOperator::GreaterThanOrEqual => filter_operands.len() != 2,
317                FilterOperator::LessThanOrEqual => filter_operands.len() != 2,
318                FilterOperator::Like => filter_operands.len() != 2,
319                FilterOperator::Not => filter_operands.len() != 1,
320                FilterOperator::Between => filter_operands.len() != 3,
321                FilterOperator::InList => filter_operands.len() < 2, // 2..n
322                FilterOperator::And => filter_operands.len() != 2,
323                FilterOperator::Or => filter_operands.len() != 2,
324                FilterOperator::Cast => filter_operands.len() != 2,
325                FilterOperator::BitwiseAnd => filter_operands.len() != 2,
326                FilterOperator::BitwiseOr => filter_operands.len() != 2,
327                FilterOperator::InView => filter_operands.len() != 1,
328                FilterOperator::OfType => filter_operands.len() != 1,
329                FilterOperator::RelatedTo => filter_operands.len() != 6,
330            };
331
332            if banned_operators.contains(&e.filter_operator) {
333                return (
334                    ContentFilterElementResult {
335                        status_code: StatusCode::BadFilterOperatorUnsupported,
336                        operand_status_codes: None,
337                        operand_diagnostic_infos: None,
338                    },
339                    None,
340                );
341            }
342
343            let mut valid_operands = Vec::with_capacity(filter_operands.len());
344            let mut operand_status_codes = Vec::with_capacity(filter_operands.len());
345
346            let operand_results: Vec<_> = filter_operands
347                .into_iter()
348                .map(|e| {
349                    let operand = <Operand>::try_from(e.clone())?;
350                    ParsedOperand::parse(operand, num_elements, type_tree, allow_attribute_operand)
351                })
352                .collect();
353
354            for res in operand_results {
355                match res {
356                    Ok(op) => {
357                        operand_status_codes.push(StatusCode::Good);
358                        if let ParsedOperand::ElementOperand(e) = &op {
359                            operand_refs
360                                .entry(element_idx)
361                                .or_default()
362                                .push(e.index as usize);
363                        }
364                        valid_operands.push(op);
365                    }
366                    Err(e) => operand_status_codes.push(e),
367                }
368            }
369            let operator_invalid = valid_operands.len() != num_filter_operands;
370
371            // Check what error status to return
372            let status_code = if operand_count_mismatch {
373                StatusCode::BadFilterOperandCountMismatch
374            } else if operator_invalid {
375                StatusCode::BadFilterOperandInvalid
376            } else {
377                StatusCode::Good
378            };
379
380            let res = if status_code.is_good() {
381                Some(ParsedContentFilterElement {
382                    operator: e.filter_operator,
383                    operands: valid_operands,
384                })
385            } else {
386                None
387            };
388
389            (
390                ContentFilterElementResult {
391                    status_code,
392                    operand_status_codes: Some(operand_status_codes),
393                    operand_diagnostic_infos: None,
394                },
395                res,
396            )
397        })
398        .collect();
399
400    let mut is_valid = true;
401    let mut valid_elements = Vec::with_capacity(num_elements);
402    let mut element_results = Vec::with_capacity(num_elements);
403    for (result, element) in element_result_pairs {
404        if let Some(element) = element {
405            valid_elements.push(element);
406        } else {
407            is_valid = false;
408        }
409        element_results.push(result);
410    }
411
412    // Discover cycles. The operators must form a tree starting from the first
413    let mut path = HashSet::new();
414    match has_cycles(&operand_refs, 0, &mut path) {
415        Ok(()) => (),
416        Err(()) => is_valid = false,
417    }
418
419    (
420        ContentFilterResult {
421            element_results: Some(element_results),
422            element_diagnostic_infos: None,
423        },
424        if is_valid {
425            Ok(ParsedContentFilter {
426                elements: valid_elements,
427            })
428        } else {
429            Err(StatusCode::BadEventFilterInvalid)
430        },
431    )
432}
433
434fn has_cycles(
435    children: &HashMap<usize, Vec<usize>>,
436    id: usize,
437    path: &mut HashSet<usize>,
438) -> Result<(), ()> {
439    let Some(child_refs) = children.get(&id) else {
440        return Ok(());
441    };
442    if !path.insert(id) {
443        return Err(());
444    }
445
446    for child in child_refs {
447        has_cycles(children, *child, path)?;
448    }
449
450    path.remove(&id);
451
452    Ok(())
453}
454
455#[cfg(test)]
456mod tests {
457    use crate::{events::validation::validate_where_clause, DefaultTypeTree};
458    use opcua_types::{
459        AttributeId, ContentFilter, ContentFilterElement, ContentFilterResult, FilterOperator,
460        NodeClass, NodeId, ObjectTypeId, Operand, SimpleAttributeOperand, StatusCode,
461    };
462
463    #[test]
464    fn test_validate_empty_where_clause() {
465        let type_tree = DefaultTypeTree::new();
466        // check for at least one filter operand
467        let where_clause = ContentFilter { elements: None };
468        let (result, filter) = validate_where_clause(where_clause, &type_tree, false, &[]);
469        assert_eq!(
470            result,
471            ContentFilterResult {
472                element_results: None,
473                element_diagnostic_infos: None,
474            }
475        );
476        assert!(filter.is_ok());
477    }
478
479    #[test]
480    fn test_validate_operator_len() {
481        let type_tree = DefaultTypeTree::new();
482
483        // Make a where clause where every single operator is included but each has the wrong number of operands.
484        // We should expect them all to be in error
485        let where_clause = ContentFilter {
486            elements: Some(vec![
487                ContentFilterElement::from((FilterOperator::Equals, vec![Operand::literal(10)])),
488                ContentFilterElement::from((FilterOperator::IsNull, vec![])),
489                ContentFilterElement::from((
490                    FilterOperator::GreaterThan,
491                    vec![Operand::literal(10)],
492                )),
493                ContentFilterElement::from((FilterOperator::LessThan, vec![Operand::literal(10)])),
494                ContentFilterElement::from((
495                    FilterOperator::GreaterThanOrEqual,
496                    vec![Operand::literal(10)],
497                )),
498                ContentFilterElement::from((
499                    FilterOperator::LessThanOrEqual,
500                    vec![Operand::literal(10)],
501                )),
502                ContentFilterElement::from((FilterOperator::Like, vec![Operand::literal(10)])),
503                ContentFilterElement::from((FilterOperator::Not, vec![])),
504                ContentFilterElement::from((
505                    FilterOperator::Between,
506                    vec![Operand::literal(10), Operand::literal(20)],
507                )),
508                ContentFilterElement::from((FilterOperator::InList, vec![Operand::literal(10)])),
509                ContentFilterElement::from((FilterOperator::And, vec![Operand::literal(10)])),
510                ContentFilterElement::from((FilterOperator::Or, vec![Operand::literal(10)])),
511                ContentFilterElement::from((FilterOperator::Cast, vec![Operand::literal(10)])),
512                ContentFilterElement::from((
513                    FilterOperator::BitwiseAnd,
514                    vec![Operand::literal(10)],
515                )),
516                ContentFilterElement::from((FilterOperator::BitwiseOr, vec![Operand::literal(10)])),
517                ContentFilterElement::from((FilterOperator::Like, vec![Operand::literal(10)])),
518            ]),
519        };
520        // Check for less than required number of operands
521        let (result, filter) = validate_where_clause(where_clause, &type_tree, false, &[]);
522        result
523            .element_results
524            .unwrap()
525            .iter()
526            .for_each(|e| assert_eq!(e.status_code, StatusCode::BadFilterOperandCountMismatch));
527        assert_eq!(filter.unwrap_err(), StatusCode::BadEventFilterInvalid);
528    }
529
530    #[test]
531    fn test_validate_bad_filter_operand() {
532        let type_tree = DefaultTypeTree::new();
533
534        // check for filter operator invalid, by giving it a bogus extension object for an element
535        use opcua_types::{ContentFilterElement, ExtensionObject};
536        let bad_operator = ExtensionObject::null();
537        let where_clause = ContentFilter {
538            elements: Some(vec![ContentFilterElement {
539                filter_operator: FilterOperator::IsNull,
540                filter_operands: Some(vec![bad_operator]),
541            }]),
542        };
543        let (result, filter) = validate_where_clause(where_clause, &type_tree, false, &[]);
544        let element_results = result.element_results.unwrap();
545        assert_eq!(element_results.len(), 1);
546        assert_eq!(
547            element_results[0].status_code,
548            StatusCode::BadFilterOperandInvalid
549        );
550        let err = filter.unwrap_err();
551        assert_eq!(err, StatusCode::BadEventFilterInvalid);
552    }
553
554    #[test]
555    fn test_validate_select_operands() {
556        let mut type_tree = DefaultTypeTree::new();
557
558        type_tree.add_type_node(
559            &NodeId::new(1, "event"),
560            &ObjectTypeId::BaseEventType.into(),
561            NodeClass::ObjectType,
562        );
563        type_tree.add_type_property(
564            &NodeId::new(1, "prop"),
565            &NodeId::new(1, "event"),
566            &[&"Prop".into()],
567            NodeClass::Variable,
568        );
569
570        // One attribute that exists, one that doesn't.
571        let where_clause = ContentFilter {
572            elements: Some(vec![
573                ContentFilterElement::from((
574                    FilterOperator::IsNull,
575                    vec![Operand::SimpleAttributeOperand(SimpleAttributeOperand {
576                        type_definition_id: NodeId::new(1, "event"),
577                        browse_path: Some(vec!["Prop".into()]),
578                        attribute_id: AttributeId::Value as u32,
579                        index_range: Default::default(),
580                    })],
581                )),
582                ContentFilterElement::from((
583                    FilterOperator::IsNull,
584                    vec![Operand::SimpleAttributeOperand(SimpleAttributeOperand {
585                        type_definition_id: NodeId::new(1, "event"),
586                        browse_path: Some(vec!["Prop2".into()]),
587                        attribute_id: AttributeId::Value as u32,
588                        index_range: Default::default(),
589                    })],
590                )),
591            ]),
592        };
593
594        let (result, filter) = validate_where_clause(where_clause, &type_tree, false, &[]);
595        let element_results = result.element_results.unwrap();
596        assert_eq!(element_results.len(), 2);
597        assert_eq!(element_results[0].status_code, StatusCode::Good);
598        assert_eq!(
599            element_results[1].status_code,
600            StatusCode::BadFilterOperandInvalid
601        );
602        let status_codes = element_results[1].operand_status_codes.as_ref().unwrap();
603        assert_eq!(status_codes.len(), 1);
604        assert_eq!(status_codes[0], StatusCode::BadNodeIdUnknown);
605        assert_eq!(filter.unwrap_err(), StatusCode::BadEventFilterInvalid);
606    }
607
608    #[test]
609    fn test_validate_circular_filter() {
610        let type_tree = DefaultTypeTree::new();
611
612        let where_clause = ContentFilter {
613            elements: Some(vec![
614                ContentFilterElement::from((
615                    FilterOperator::And,
616                    vec![Operand::element(1), Operand::element(2)],
617                )),
618                ContentFilterElement::from((FilterOperator::IsNull, vec![Operand::literal(10)])),
619                ContentFilterElement::from((
620                    FilterOperator::Or,
621                    vec![Operand::element(1), Operand::element(3)],
622                )),
623                ContentFilterElement::from((FilterOperator::Not, vec![Operand::element(0)])),
624            ]),
625        };
626
627        let (_result, filter) = validate_where_clause(where_clause, &type_tree, false, &[]);
628        assert_eq!(filter.unwrap_err(), StatusCode::BadEventFilterInvalid);
629    }
630}