validation NEW when validation
Integrated only into XML parser for now.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e48cb18..8023c2b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -229,7 +229,8 @@
src/plugins_types.c
src/plugins_exts.c
src/xml.c
- src/xpath.c)
+ src/xpath.c
+ src/validation.c)
set(lintsrc
tools/lint/main.c
diff --git a/src/parser_xml.c b/src/parser_xml.c
index bf7c6ab..31fac0b 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -28,6 +28,7 @@
#include "tree_schema.h"
#include "xml.h"
#include "plugins_exts_internal.h"
+#include "validation.h"
/**
* @brief internal context for XML YANG data parser.
@@ -487,6 +488,11 @@
}
}
+ /* remember we need to evaluate this node's when */
+ if (!(snode->nodetype & (LYS_ACTION | LYS_NOTIF)) && snode->when) {
+ ly_set_add(&ctx->when_check, cur, LY_SET_OPT_USEASLIST);
+ }
+
/* calculate the hash and insert it into parent (list with keys is handled when its keys are inserted) */
lyd_hash(cur);
lyd_insert_hash(cur);
@@ -496,8 +502,6 @@
!cur->attr && !(((struct lysc_node_container*)cur->schema)->flags & LYS_PRESENCE)) {
cur->flags |= LYD_DEFAULT;
}
-
- /* TODO context validation */
}
/* TODO add missing siblings default elements */
@@ -521,6 +525,7 @@
{
LY_ERR ret = LY_SUCCESS;
struct lyd_node_inner *parent = NULL;
+ const struct lyd_node **result_trees = NULL;
struct lyd_xml_ctx xmlctx = {0};
xmlctx.options = options;
@@ -553,70 +558,45 @@
}
if (!data || !data[0]) {
- goto no_data;
+ /* no data - just check for missing mandatory nodes */
+ goto validation;
}
ret = lydxml_nodes(&xmlctx, parent, &data, *result ? &parent->child : result);
+ LY_CHECK_GOTO(ret, cleanup);
+
+ /* prepare sized array for validator */
+ if (*result) {
+ result_trees = lyd_trees_new(1, *result);
+ }
+
+ /* finish incompletely validated terminal values/attributes and when conditions */
+ ret = lyd_validate_unres(&xmlctx.incomplete_type_validation, &xmlctx.incomplete_type_validation_attrs,
+ &xmlctx.when_check, LYD_XML, lydxml_resolve_prefix, ctx, result_trees);
+ LY_CHECK_GOTO(ret, cleanup);
+
+validation:
+ if ((!(*result) || (parent && !parent->child)) && (options & (LYD_OPT_RPC | LYD_OPT_NOTIF))) {
+ /* error, missing top level node identify RPC and Notification */
+ LOGERR(ctx, LY_EINVAL, "Invalid input data of data parser - expected %s which cannot be empty.",
+ lyd_parse_options_type2str(options));
+ ret = LY_EINVAL;
+ goto cleanup;
+ }
+
+ /* context node and other validation tasks that depend on other data nodes */
+ ret = lyd_validate_modules(result_trees, NULL, 0, ctx, options);
+ LY_CHECK_GOTO(result, cleanup);
+
+cleanup:
+ ly_set_erase(&xmlctx.incomplete_type_validation, NULL);
+ ly_set_erase(&xmlctx.incomplete_type_validation_attrs, NULL);
+ ly_set_erase(&xmlctx.when_check, NULL);
+ lyxml_context_clear((struct lyxml_context*)&xmlctx);
+ lyd_trees_free(result_trees, 0);
if (ret) {
lyd_free_all(*result);
*result = NULL;
- } else {
- /* finish incompletely validated terminal values */
- for (unsigned int u = 0; u < xmlctx.incomplete_type_validation.count; u++) {
- struct lyd_node_term *node = (struct lyd_node_term*)xmlctx.incomplete_type_validation.objs[u];
- const struct lyd_node **result_trees = NULL;
-
- /* prepare sized array for validator */
- if (*result) {
- result_trees = lyd_trees_new(1, *result);
- }
- /* validate and store the value of the node */
- ret = lyd_value_parse(node, node->value.original, strlen(node->value.original), 0, 1,
- lydxml_resolve_prefix, ctx, LYD_XML, result_trees);
- lyd_trees_free(result_trees, 0);
- if (ret) {
- lyd_free_all(*result);
- *result = NULL;
- break;
- }
- }
- /* ... and attribute values */
- for (unsigned int u = 0; u < xmlctx.incomplete_type_validation_attrs.count; u++) {
- struct lyd_attr *attr = (struct lyd_attr*)xmlctx.incomplete_type_validation_attrs.objs[u];
- const struct lyd_node **result_trees = NULL;
-
- /* prepare sized array for validator */
- if (*result) {
- result_trees = lyd_trees_new(1, *result);
- }
- /* validate and store the value of the node */
- ret = lyd_value_parse_attr(attr, attr->value.original, strlen(attr->value.original), 0, 1,
- lydxml_resolve_prefix, ctx, LYD_XML, result_trees);
- lyd_trees_free(result_trees, 0);
- if (ret) {
- lyd_free_all(*result);
- *result = NULL;
- break;
- }
- }
-
- if (!(*result) || (parent && !parent->child)) {
-no_data:
- /* no data */
- if (options & (LYD_OPT_RPC | LYD_OPT_NOTIF)) {
- /* error, missing top level node identify RPC and Notification */
- LOGERR(ctx, LY_EINVAL, "Invalid input data of data parser - expected %s which cannot be empty.",
- lyd_parse_options_type2str(options));
- } else {
- /* others - no work is needed, just check for missing mandatory nodes */
- /* TODO lyd_validate(&result, options, ctx);
- * - according to the data tree type */
- }
- }
}
-
- ly_set_erase(&xmlctx.incomplete_type_validation, NULL);
- ly_set_erase(&xmlctx.incomplete_type_validation_attrs, NULL);
- lyxml_context_clear((struct lyxml_context*)&xmlctx);
return ret;
}
diff --git a/src/validation.c b/src/validation.c
new file mode 100644
index 0000000..129af23
--- /dev/null
+++ b/src/validation.c
@@ -0,0 +1,218 @@
+/**
+ * @file validation.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief Validation
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "common.h"
+#include "xpath.h"
+#include "tree_data_internal.h"
+
+/**
+ * @brief Evaluate a single "when" condition.
+ *
+ * @param[in] when When to evaluate.
+ * @param[in] node Node whose existence depends on this when.
+ * @param[in] trees Array of all data trees.
+ * @return LY_ERR value (LY_EINCOMPLETE if a referenced node does not have its when evaluated)
+ */
+static LY_ERR
+lyd_val_when(struct lysc_when *when, struct lyd_node *node, const struct lyd_node **trees)
+{
+ LY_ERR ret;
+ const struct lyd_node *ctx_node;
+ struct lyxp_set xp_set;
+
+ memset(&xp_set, 0, sizeof xp_set);
+
+ if (when->context == node->schema) {
+ ctx_node = node;
+ } else {
+ assert((!when->context && !node->parent) || (when->context == node->parent->schema));
+ ctx_node = (struct lyd_node *)node->parent;
+ }
+
+ /* evaluate when */
+ ret = lyxp_eval(when->cond, LYD_UNKNOWN, when->module, ctx_node, ctx_node ? LYXP_NODE_ELEM : LYXP_NODE_ROOT_CONFIG,
+ trees, &xp_set, LYXP_SCHEMA);
+ lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN);
+
+ /* return error or LY_EINCOMPLETE for dependant unresolved when */
+ LY_CHECK_RET(ret);
+
+ /* take action based on the result */
+ if (!xp_set.val.bool) {
+ if (node->flags & LYD_WHEN_TRUE) {
+ /* autodelete */
+ lyd_free_tree(node);
+ } else {
+ /* invalid data */
+ LOGVAL(node->schema->module->ctx, LY_VLOG_LYD, node, LY_VCODE_NOWHEN, when->cond->expr);
+ ret = LY_EVALID;
+ }
+ } else {
+ /* remember that when evaluated to true */
+ node->flags |= LYD_WHEN_TRUE;
+ }
+
+ return ret;
+}
+
+LY_ERR
+lyd_validate_unres(struct ly_set *node_types, struct ly_set *attr_types, struct ly_set *node_when, LYD_FORMAT format,
+ ly_clb_resolve_prefix get_prefix_clb, void *parser_data, const struct lyd_node **trees)
+{
+ LY_ERR ret = LY_SUCCESS;
+ uint32_t u;
+
+ /* finish incompletely validated terminal values */
+ for (u = 0; node_types && (u < node_types->count); u++) {
+ struct lyd_node_term *node = (struct lyd_node_term *)node_types->objs[u];
+
+ /* validate and store the value of the node */
+ ret = lyd_value_parse(node, node->value.original, strlen(node->value.original), 0, 1, get_prefix_clb,
+ parser_data, format, trees);
+ LY_CHECK_RET(ret);
+ }
+
+ /* ... and attribute values */
+ for (u = 0; attr_types && (u < attr_types->count); u++) {
+ struct lyd_attr *attr = (struct lyd_attr *)attr_types->objs[u];
+
+ /* validate and store the value of the node */
+ ret = lyd_value_parse_attr(attr, attr->value.original, strlen(attr->value.original), 0, 1, get_prefix_clb,
+ parser_data, format, trees);
+ LY_CHECK_RET(ret);
+ }
+
+ /* no when conditions */
+ if (!node_when || !node_when->count) {
+ return ret;
+ }
+
+ /* evaluate all when conditions */
+ uint32_t prev_count;
+ do {
+ prev_count = node_when->count;
+ u = 0;
+ while (u < node_when->count) {
+ /* evaluate all when expressions that affect this node's existence */
+ struct lyd_node *node = (struct lyd_node *)node_when->objs[u];
+ const struct lysc_node *schema = node->schema;
+ int unres_when = 0;
+
+ do {
+ uint32_t i;
+ LY_ARRAY_FOR(schema->when, i) {
+ ret = lyd_val_when(schema->when[i], node, trees);
+ if (ret) {
+ break;
+ }
+ }
+ if (ret == LY_EINCOMPLETE) {
+ /* could not evaluate this when */
+ unres_when = 1;
+ break;
+ } else if (ret) {
+ /* error */
+ return ret;
+ }
+ schema = schema->parent;
+ } while (schema && (schema->nodetype & (LYS_CASE | LYS_CHOICE)));
+
+ if (unres_when) {
+ /* keep in set and go to the next node */
+ ++u;
+ } else {
+ /* remove this node from the set */
+ ly_set_rm_index(node_when, u, NULL);
+ }
+ }
+
+ /* there must have been some when conditions resolved */
+ } while (prev_count > node_when->count);
+
+ /* there could have been no cyclic when dependencies, checked during compilation */
+ assert(!node_when->count);
+
+ return ret;
+}
+
+static const struct lys_module *
+lyd_val_next_module(const struct lys_module **modules, int mod_count, struct ly_ctx *ctx, uint32_t *i)
+{
+ if (modules && mod_count) {
+ return modules[(*i)++];
+ }
+
+ return ly_ctx_get_module_iter(ctx, i);
+}
+
+static LY_ERR
+lyd_validate_children_r(struct lyd_node *sibling, const struct lysc_node *sparent, const struct lysc_module *mod, int options)
+{
+ LY_ERR ret;
+ const struct lysc_node *snode = NULL;
+ struct lyd_node *node;
+
+ while ((snode = lys_getnext(snode, sparent, mod, 0))) {
+ /* TODO mandatory - mandatory snode must exist */
+ /* TODO min/max elem - check snode element count */
+ /* TODO unique - check snode unique */
+ /* TODO choice - case duplicites/mandatory */
+ }
+
+ for (node = sibling; node; node = node->next) {
+ /* TODO node's must */
+ /* TODO node instance duplicites */
+ /* TODO node status */
+
+ /* validate all children */
+ LY_CHECK_RET(lyd_validate_children_r((struct lyd_node *)lyd_node_children(sibling), node->schema, mod, options));
+ }
+
+ return LY_SUCCESS;
+}
+
+LY_ERR
+lyd_validate_modules(const struct lyd_node **trees, const struct lys_module **modules, int mod_count, struct ly_ctx *ctx,
+ int options)
+{
+ LY_ERR ret;
+ uint32_t i = 0, j;
+ const struct lys_module *mod;
+ struct lyd_node *tree;
+
+ while ((mod = lyd_val_next_module(modules, mod_count, ctx, &i))) {
+ if (!mod->implemented) {
+ continue;
+ }
+
+ /* find data of this module, if any */
+ tree = NULL;
+ if (trees) {
+ for (j = 0; j < LY_ARRAY_SIZE(trees); ++j) {
+ if (trees[j]->schema->module == mod) {
+ tree = (struct lyd_node *)trees[j];
+ break;
+ }
+ }
+ }
+
+ /* validate all top-level nodes and then inner nodes recursively */
+ LY_CHECK_RET(lyd_validate_children_r(tree, NULL, mod->compiled, options));
+ }
+
+ return LY_SUCCESS;
+}
diff --git a/src/validation.h b/src/validation.h
new file mode 100644
index 0000000..13608b8
--- /dev/null
+++ b/src/validation.h
@@ -0,0 +1,50 @@
+/**
+ * @file validation.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief Validation routines.
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef LY_VALIDATION_H_
+#define LY_VALIDATION_H_
+
+#include "log.h"
+#include "tree_data.h"
+
+/**
+ * @brief Finish validation of nodes and attributes. Specifically, type and when validation.
+ *
+ * @param[in] node_types Set with nodes with unresolved types, can be NULL
+ * @param[in] attr_types Set with attributes with unresolved types, can be NULL.
+ * @param[in] node_when Set with nodes with "when" conditions, can be NULL.
+ * @param[in] format Format of the unresolved data.
+ * @param[in] get_prefix_clb Format-specific getter to resolve prefixes.
+ * @param[in] parser_data Parser's data for @p get_prefix_clb.
+ * @param[in] trees Array of all data trees.
+ * @return LY_ERR value.
+ */
+LY_ERR lyd_validate_unres(struct ly_set *node_types, struct ly_set *attr_types, struct ly_set *node_when, LYD_FORMAT format,
+ ly_clb_resolve_prefix get_prefix_clb, void *parser_data, const struct lyd_node **trees);
+
+/**
+ * @brief Perform all vaidation tasks that depend on other nodes, the data tree must
+ * be complete when calling this function.
+ *
+ * @param[in] trees Array of all data trees.
+ * @param[in] modules Array of modules that should be validated, NULL for all modules.
+ * @param[in] mod_count Modules count.
+ * @param[in] ctx Context if all modules should be validated, NULL for only selected modules.
+ * @param[in] options Validation options.
+ * @return LY_ERR value.
+ */
+LY_ERR lyd_validate_modules(const struct lyd_node **trees, const struct lys_module **modules, int mod_count,
+ struct ly_ctx *ctx, int options);
+
+#endif /* LY_VALIDATION_H_ */
diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt
index 0aa4fa5..00bf835 100644
--- a/tests/src/CMakeLists.txt
+++ b/tests/src/CMakeLists.txt
@@ -13,7 +13,8 @@
src_printer_yin
src_tree_data
src_parser_xml
- src_printer_xml)
+ src_printer_xml
+ src_validation)
set(local_tests_wraps
" "
"-Wl,--wrap=realloc"
@@ -29,6 +30,7 @@
" "
" "
" "
+ " "
" ")
set(tests ${tests} ${local_tests} PARENT_SCOPE)
set(tests_wraps ${tests_wraps} ${local_tests_wraps} PARENT_SCOPE)
diff --git a/tests/src/test_validation.c b/tests/src/test_validation.c
new file mode 100644
index 0000000..eaea51d
--- /dev/null
+++ b/tests/src/test_validation.c
@@ -0,0 +1,157 @@
+/*
+ * @file test_parser_xml.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for functions from parser_xml.c
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "../../src/context.h"
+#include "../../src/tree_data_internal.h"
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+int store = -1; /* negative for infinite logging, positive for limited logging */
+
+struct ly_ctx *ctx; /* context for tests */
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+#if ENABLE_LOGGER_CHECKING
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+ (void) level; /* unused */
+ if (store) {
+ if (path && path[0]) {
+ snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+ } else {
+ strncpy(logbuf, msg, BUFSIZE - 1);
+ }
+ if (store > 0) {
+ --store;
+ }
+ }
+}
+#endif
+
+static int
+setup(void **state)
+{
+ (void) state; /* unused */
+
+ const char *schema_a =
+ "module a {"
+ "namespace urn:tests:a;"
+ "prefix a;"
+ "yang-version 1.1;"
+
+ "container cont {"
+ "leaf a {"
+ "when \"../../c = 'val_c'\";"
+ "type string;"
+ "}"
+ "leaf b {"
+ "type string;"
+ "}"
+ "}"
+ "leaf c {"
+ "when \"/cont/b = 'val_b'\";"
+ "type string;"
+ "}"
+ "}";
+
+#if ENABLE_LOGGER_CHECKING
+ ly_set_log_clb(logger, 1);
+#endif
+
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0, &ctx));
+ assert_non_null(lys_parse_mem(ctx, schema_a, LYS_IN_YANG));
+
+ return 0;
+}
+
+static int
+teardown(void **state)
+{
+#if ENABLE_LOGGER_CHECKING
+ if (*state) {
+ fprintf(stderr, "%s\n", logbuf);
+ }
+#else
+ (void) state; /* unused */
+#endif
+
+ ly_ctx_destroy(ctx, NULL);
+ ctx = NULL;
+
+ return 0;
+}
+
+void
+logbuf_clean(void)
+{
+ logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+# define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+# define logbuf_assert(str)
+#endif
+
+static void
+test_when(void **state)
+{
+ *state = test_when;
+
+ const char *data;
+ struct lyd_node *tree;
+
+ data = "<c xmlns=\"urn:tests:a\">hey</c>";
+ assert_int_equal(LY_EVALID, lyd_parse_xml(ctx, data, 0, NULL, &tree));
+ assert_null(tree);
+ logbuf_assert("When condition \"/cont/b = 'val_b'\" not satisfied.");
+
+ data = "<cont xmlns=\"urn:tests:a\"><b>val_b</b></cont><c xmlns=\"urn:tests:a\">hey</c>";
+ assert_int_equal(LY_SUCCESS, lyd_parse_xml(ctx, data, 0, NULL, &tree));
+ assert_non_null(tree);
+ assert_string_equal("c", tree->next->schema->name);
+ assert_int_equal(LYD_WHEN_TRUE, tree->next->flags);
+ lyd_free_all(tree);
+
+ data = "<cont xmlns=\"urn:tests:a\"><a>val</a><b>val_b</b></cont><c xmlns=\"urn:tests:a\">val_c</c>";
+ assert_int_equal(LY_SUCCESS, lyd_parse_xml(ctx, data, 0, NULL, &tree));
+ assert_non_null(tree);
+ assert_string_equal("a", lyd_node_children(tree)->schema->name);
+ assert_int_equal(LYD_WHEN_TRUE, lyd_node_children(tree)->flags);
+ assert_string_equal("c", tree->next->schema->name);
+ assert_int_equal(LYD_WHEN_TRUE, tree->next->flags);
+ lyd_free_all(tree);
+
+ *state = NULL;
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_when, setup, teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}