validation NEW if-feature validation

Some refactoring included.
diff --git a/src/common.h b/src/common.h
index 3be741d..57d1808 100644
--- a/src/common.h
+++ b/src/common.h
@@ -235,6 +235,7 @@
 #define LY_VCODE_NOUNIQ         LYVE_DATA, "Unique data leaf(s) \"%s\" not satisfied in \"%s\" and \"%s\"."
 #define LY_VCODE_DUP            LYVE_DATA, "Duplicate instance of \"%s\"."
 #define LY_VCODE_DUPCASE        LYVE_DATA, "Data for both cases \"%s\" and \"%s\" exist."
+#define LY_VCODE_NOIFF          LYVE_DATA, "Data are disabled by \"%s\" schema node if-feature."
 
 /******************************************************************************
  * Context
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 30c358c..c3f5f93 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -258,7 +258,8 @@
             ret = LY_EVALID;
             goto cleanup;
         }
-        snode = lys_find_child(parent ? parent->schema : NULL, mod, name, name_len, 0, 0);
+        /* leave if-feature check for validation */
+        snode = lys_find_child(parent ? parent->schema : NULL, mod, name, name_len, 0, LYS_GETNEXT_NOSTATECHECK);
         if (!snode) {
             LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_REFERENCE, "Element \"%.*s\" not found in the \"%s\" module.",
                    name_len, name, mod->name);
diff --git a/src/tree_data.c b/src/tree_data.c
index 1902f0d..976d9e2 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -953,11 +953,11 @@
     } else if (*first_sibling) {
         /* top-level siblings */
         anchor = (*first_sibling)->prev;
-        while (anchor->prev->next && (lyd_top_node_module(anchor) != lyd_top_node_module(node))) {
+        while (anchor->prev->next && (lyd_owner_module(anchor) != lyd_owner_module(node))) {
             anchor = anchor->prev;
         }
 
-        if (lyd_top_node_module(anchor) == lyd_top_node_module(node)) {
+        if (lyd_owner_module(anchor) == lyd_owner_module(node)) {
             /* insert after last sibling from this module */
             lyd_insert_after_node(anchor, node);
         } else {
@@ -1065,15 +1065,15 @@
     }
 
     if (anchor) {
-        if (anchor->next && (lyd_top_node_module(anchor) == lyd_top_node_module(anchor->next))
-                && (lyd_top_node_module(node) != lyd_top_node_module(anchor))) {
+        if (anchor->next && (lyd_owner_module(anchor) == lyd_owner_module(anchor->next))
+                && (lyd_owner_module(node) != lyd_owner_module(anchor))) {
             LOGERR(sibling->schema->module->ctx, LY_EINVAL, "Cannot insert top-level module \"%s\" data into module \"%s\" data.",
-                   lyd_top_node_module(node)->name, lyd_top_node_module(anchor)->name);
+                   lyd_owner_module(node)->name, lyd_owner_module(anchor)->name);
             return LY_EINVAL;
         }
 
-        if ((lyd_top_node_module(node) == lyd_top_node_module(anchor))
-                || (anchor->next && (lyd_top_node_module(node) == lyd_top_node_module(anchor->next)))) {
+        if ((lyd_owner_module(node) == lyd_owner_module(anchor))
+                || (anchor->next && (lyd_owner_module(node) == lyd_owner_module(anchor->next)))) {
             /* inserting before/after its module data */
             return LY_SUCCESS;
         }
@@ -1085,7 +1085,7 @@
     }
 
     if (!anchor) {
-        if (lyd_top_node_module(node) == lyd_top_node_module(sibling)) {
+        if (lyd_owner_module(node) == lyd_owner_module(sibling)) {
             /* inserting before its module data */
             return LY_SUCCESS;
         }
@@ -1093,10 +1093,10 @@
 
     /* check there are no data of this module */
     LY_LIST_FOR(sibling, sibling) {
-        if (lyd_top_node_module(node) == lyd_top_node_module(sibling)) {
+        if (lyd_owner_module(node) == lyd_owner_module(sibling)) {
             /* some data of this module found */
             LOGERR(sibling->schema->module->ctx, LY_EINVAL, "Top-level data of module \"%s\" already exist,"
-                   " they must be directly connected.", lyd_top_node_module(node)->name);
+                   " they must be directly connected.", lyd_owner_module(node)->name);
             return LY_EINVAL;
         }
     }
diff --git a/src/tree_data.h b/src/tree_data.h
index e9ef1f7..9132f30 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -44,9 +44,9 @@
  * <pre>
  *     1
  *    / \
-     *   2   4
+ *   2   4
  *  /   / \
-     * 3   5   6
+ * 3   5   6
  * </pre>
  *
  * Use the same parameters for #LYD_TREE_DFS_BEGIN and #LYD_TREE_DFS_END. While
@@ -391,7 +391,8 @@
  * - all types are fully resolved (leafref/instance-identifier targets, unions) and must be valid (lists have
  * all the keys, leaf(-lists) correct values),
  * - when statements on existing nodes are evaluated, if not satisfied, a validation error is raised,
- * - data from several cases cause a validation error,
+ * - if-feature statements are evaluated,
+ * - invalid multiple data instances/data from several cases cause a validation error,
  * - default values are added.
  * @{
  */
@@ -407,8 +408,9 @@
                                 the NETCONF \<edit-config\>'s config element. */
 
 #define LYD_OPT_PARSE_ONLY      0x0001 /**< Data will be only parsed and no validation will be performed. When statements
-                                            are kept unevaluated, union types may not be fully resolved, and default values
-                                            are not added (only the ones parsed are present). */
+                                            are kept unevaluated, union types may not be fully resolved, if-feature
+                                            statements are not checked, and default values are not added (only the ones
+                                            parsed are present). */
 #define LYD_OPT_TRUSTED         0x0002 /**< Data are considered trusted so they will be parsed as validated. If the parsed
                                             data are not valid, using this flag may lead to some unexpected behavior!
                                             This flag can be used only with #LYD_OPT_PARSE_ONLY. */
@@ -433,6 +435,7 @@
  * - when statements on existing nodes are evaluated. Depending on the previous when state (from previous validation
  * or parsing), the node is silently auto-deleted if the state changed from true to false, otherwise a validation error
  * is raised if it evaluates to false,
+ * - if-feature statements are evaluated,
  * - data from several cases behave based on their previous state (from previous validation or parsing). If there existed
  * already a case and another one was added, the previous one is silently auto-deleted. Otherwise (if data from 2 or
  * more cases were created) a validation error is raised,
@@ -461,6 +464,15 @@
 const struct lyd_node *lyd_node_children(const struct lyd_node *node);
 
 /**
+ * @brief Get the owner module of the data node. It is the module of the top-level schema node. Generally,
+ * in case of augments it is the target module, recursively, otherwise it is the module where the data node is defined.
+ *
+ * @param[in] node Data node to examine.
+ * @return Module owner of the node.
+ */
+const struct lys_module *lyd_owner_module(const struct lyd_node *node);
+
+/**
  * @brief Parse (and validate) data from memory.
  *
  * In case of LY_XML format, the data string is parsed completely. It means that when it contains
diff --git a/src/tree_data_helpers.c b/src/tree_data_helpers.c
index 4765b75..38e0d3d 100644
--- a/src/tree_data_helpers.c
+++ b/src/tree_data_helpers.c
@@ -54,12 +54,14 @@
     }
 }
 
-const struct lys_module *
-lyd_top_node_module(const struct lyd_node *node)
+API const struct lys_module *
+lyd_owner_module(const struct lyd_node *node)
 {
     const struct lysc_node *schema;
 
-    assert(node && !node->parent);
+    if (!node) {
+        return NULL;
+    }
 
     for (schema = node->schema; schema->parent; schema = schema->parent);
     return schema->module;
@@ -89,7 +91,7 @@
     *first = NULL;
     if (mod) {
         LY_LIST_FOR(tree, iter) {
-            if (lyd_top_node_module(iter) == mod) {
+            if (lyd_owner_module(iter) == mod) {
                 *first = iter;
                 break;
             }
@@ -113,9 +115,9 @@
     *first = *next;
 
     /* prepare next */
-    mod = lyd_top_node_module(*next);
+    mod = lyd_owner_module(*next);
     LY_LIST_FOR(*next, *next) {
-        if (lyd_top_node_module(*next) != mod) {
+        if (lyd_owner_module(*next) != mod) {
             break;
         }
     }
diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h
index 8825d0f..4600c3e 100644
--- a/src/tree_data_internal.h
+++ b/src/tree_data_internal.h
@@ -286,15 +286,6 @@
                               size_t val_len, struct lyd_node **match);
 
 /**
- * @brief Get the module, whose data this top-level node belongs to. Useful for augments, when the augmented
- * module is the data owner. Handles top-level choice augments.
- *
- * @param[in] node Data node to examine.
- * @return Module owner of the node.
- */
-const struct lys_module *lyd_top_node_module(const struct lyd_node *node);
-
-/**
  * @brief Iterate over implemented modules for functions that accept specific modules or the whole context.
  *
  * @param[in] tree Data tree.
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 350201a..6850e16 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -555,23 +555,19 @@
     return -1;
 }
 
-API const struct lysc_iffeature *
+API const struct lysc_node *
 lysc_node_is_disabled(const struct lysc_node *node, int recursive)
 {
     unsigned int u;
 
     LY_CHECK_ARG_RET(NULL, node, NULL);
 
-    while(node) {
-        if (node->nodetype & LYS_CHOICE) {
-            return NULL;
-        }
-
+    do {
         if (node->iffeatures) {
             /* check local if-features */
             LY_ARRAY_FOR(node->iffeatures, u) {
                 if (!lysc_iffeature_value(&node->iffeatures[u])) {
-                    return &node->iffeatures[u];
+                    return node;
                 }
             }
         }
@@ -580,9 +576,10 @@
             return NULL;
         }
 
-        /* go through parents */
+        /* go through schema-only parents */
         node = node->parent;
-    }
+    } while (node && (node->nodetype & (LYS_CASE | LYS_CHOICE)));
+
     return NULL;
 }
 
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 855a1bf..d139498 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1954,9 +1954,9 @@
  * @param[in] recursive - 0 to check if-feature only in the \p node schema node,
  * - 1 to check if-feature in all ascendant schema nodes until there is a node possibly having an instance in a data tree
  * @return NULL if enabled,
- * @return pointer to the node with the unsatisfied (disabling) if-feature expression.
+ * @return pointer to the node with the unsatisfied (disabled) if-feature expression.
  */
-const struct lysc_iffeature *lysc_node_is_disabled(const struct lysc_node *node, int recursive);
+const struct lysc_node *lysc_node_is_disabled(const struct lysc_node *node, int recursive);
 
 /**
  * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value.
diff --git a/src/validation.c b/src/validation.c
index 970401c..eb28b7c 100644
--- a/src/validation.c
+++ b/src/validation.c
@@ -80,7 +80,7 @@
  * @return LY_ERR value (LY_EINCOMPLETE if a referenced node does not have its when evaluated)
  */
 static LY_ERR
-lyd_val_when(struct lyd_node **tree, struct lyd_node *node, struct lysc_when *when)
+lyd_validate_when(struct lyd_node **tree, struct lyd_node *node, struct lysc_when *when)
 {
     LY_ERR ret = LY_SUCCESS;
     const struct lyd_node *ctx_node;
@@ -146,7 +146,7 @@
                 do {
                     uint32_t i;
                     LY_ARRAY_FOR(schema->when, i) {
-                        ret = lyd_val_when(tree, node, schema->when[i]);
+                        ret = lyd_validate_when(tree, node, schema->when[i]);
                         if (ret) {
                             break;
                         }
@@ -403,7 +403,7 @@
     }
 
     LY_LIST_FOR_SAFE(*first, next, node) {
-        if (mod && (lyd_top_node_module(node) != mod)) {
+        if (mod && (lyd_owner_module(node) != mod)) {
             /* all top-level data from this module checked */
             break;
         }
@@ -773,17 +773,23 @@
                         int val_opts)
 {
     struct lyd_node *next, *node;
+    const struct lysc_node *snode;
 
     /* validate all restrictions of nodes themselves */
     LY_LIST_FOR_SAFE(first, next, node) {
-        if (mod && (lyd_top_node_module(node) != mod)) {
+        if (mod && (lyd_owner_module(node) != mod)) {
             /* all top-level data from this module checked */
             break;
         }
 
+        /* node's schema if-features */
+        if ((snode = lysc_node_is_disabled(node->schema, 1))) {
+            LOGVAL(node->schema->module->ctx, LY_VLOG_LYD, node, LY_VCODE_NOIFF, snode->name);
+            return LY_EVALID;
+        }
+
         /* TODO node's must */
         /* TODO node status */
-        /* TODO node's if-features */
         /* TODO list all keys existence */
         /* node value including if-feature is checked by plugins */
     }
diff --git a/tests/src/test_validation.c b/tests/src/test_validation.c
index d6878c6..c6cf3c9 100644
--- a/tests/src/test_validation.c
+++ b/tests/src/test_validation.c
@@ -288,6 +288,41 @@
                 "}"
             "}"
         "}";
+    const char *schema_g =
+        "module g {"
+            "namespace urn:tests:g;"
+            "prefix g;"
+            "yang-version 1.1;"
+
+            "feature f1;"
+            "feature f2;"
+            "feature f3;"
+
+            "container cont {"
+                "if-feature \"f1\";"
+                "choice choic {"
+                    "if-feature \"f2 or f3\";"
+                    "leaf a {"
+                        "type string;"
+                    "}"
+                    "case b {"
+                        "if-feature \"f2 and f1\";"
+                        "leaf l {"
+                            "type string;"
+                        "}"
+                    "}"
+                "}"
+                "leaf d {"
+                    "type uint32;"
+                "}"
+                "container cont2 {"
+                    "if-feature \"f2\";"
+                    "leaf e {"
+                        "type string;"
+                    "}"
+                "}"
+            "}"
+        "}";
 
 #if ENABLE_LOGGER_CHECKING
     ly_set_log_clb(logger, 1);
@@ -301,6 +336,7 @@
     assert_non_null(lys_parse_mem(ctx, schema_d, LYS_IN_YANG));
     assert_non_null(lys_parse_mem(ctx, schema_e, LYS_IN_YANG));
     assert_non_null(lys_parse_mem(ctx, schema_f, LYS_IN_YANG));
+    assert_non_null(lys_parse_mem(ctx, schema_g, LYS_IN_YANG));
 
     return 0;
 }
@@ -1035,6 +1071,112 @@
     *state = NULL;
 }
 
+static void
+test_iffeature(void **state)
+{
+    *state = test_iffeature;
+
+    const char *data;
+    struct lyd_node *tree;
+    const struct lys_module *mod = ly_ctx_get_module_latest(ctx, "g");
+
+    /* get empty data */
+    tree = NULL;
+    assert_int_equal(lyd_validate_modules(&tree, &mod, 1, 0), LY_SUCCESS);
+    assert_null(tree);
+
+    /* disabled by f1 */
+    data =
+    "<cont xmlns=\"urn:tests:g\">"
+        "<d>51</d>"
+    "</cont>";
+    assert_int_equal(LY_EVALID, lyd_parse_xml(ctx, data, LYD_VALOPT_DATA_ONLY, &tree));
+    assert_null(tree);
+    logbuf_assert("Data are disabled by \"cont\" schema node if-feature. /g:cont");
+
+    /* enable f1 */
+    assert_int_equal(lys_feature_enable(mod, "f1"), LY_SUCCESS);
+
+    /* get data with default container */
+    assert_int_equal(lyd_validate_modules(&tree, &mod, 1, 0), LY_SUCCESS);
+    assert_non_null(tree);
+    lyd_free_siblings(tree);
+
+    /* disabled by f2 */
+    data =
+    "<cont xmlns=\"urn:tests:g\">"
+        "<cont2>"
+            "<e>val</e>"
+        "</cont2>"
+    "</cont>";
+    assert_int_equal(LY_EVALID, lyd_parse_xml(ctx, data, LYD_VALOPT_DATA_ONLY, &tree));
+    assert_null(tree);
+    logbuf_assert("Data are disabled by \"cont2\" schema node if-feature. /g:cont/cont2");
+
+    data =
+    "<cont xmlns=\"urn:tests:g\">"
+        "<a>val</a>"
+    "</cont>";
+    assert_int_equal(LY_EVALID, lyd_parse_xml(ctx, data, LYD_VALOPT_DATA_ONLY, &tree));
+    assert_null(tree);
+    logbuf_assert("Data are disabled by \"choic\" schema node if-feature. /g:cont/a");
+
+    /* enable f3 */
+    assert_int_equal(lys_feature_enable(mod, "f3"), LY_SUCCESS);
+
+    assert_int_equal(LY_SUCCESS, lyd_parse_xml(ctx, data, LYD_VALOPT_DATA_ONLY, &tree));
+    assert_non_null(tree);
+    lyd_free_siblings(tree);
+
+    /* disabled by f2 */
+    data =
+    "<cont xmlns=\"urn:tests:g\">"
+        "<l>val</l>"
+    "</cont>";
+    assert_int_equal(LY_EVALID, lyd_parse_xml(ctx, data, LYD_VALOPT_DATA_ONLY, &tree));
+    assert_null(tree);
+    logbuf_assert("Data are disabled by \"b\" schema node if-feature. /g:cont/l");
+
+    /* enable f2 */
+    assert_int_equal(lys_feature_enable(mod, "f2"), LY_SUCCESS);
+
+    assert_int_equal(LY_SUCCESS, lyd_parse_xml(ctx, data, LYD_VALOPT_DATA_ONLY, &tree));
+    assert_non_null(tree);
+    lyd_free_siblings(tree);
+
+    /* try separate validation */
+    assert_int_equal(lys_feature_disable(mod, "f1"), LY_SUCCESS);
+    assert_int_equal(lys_feature_disable(mod, "f2"), LY_SUCCESS);
+    assert_int_equal(lys_feature_disable(mod, "f3"), LY_SUCCESS);
+
+    data =
+    "<cont xmlns=\"urn:tests:g\">"
+        "<l>val</l>"
+        "<d>51</d>"
+        "<cont2>"
+            "<e>val</e>"
+        "</cont2>"
+    "</cont>";
+    assert_int_equal(LY_SUCCESS, lyd_parse_xml(ctx, data, LYD_OPT_PARSE_ONLY, &tree));
+    assert_non_null(tree);
+
+    assert_int_equal(LY_EVALID, lyd_validate(&tree, NULL, LYD_VALOPT_DATA_ONLY));
+    logbuf_assert("Data are disabled by \"cont\" schema node if-feature. /g:cont");
+
+    assert_int_equal(lys_feature_enable(mod, "f1"), LY_SUCCESS);
+
+    assert_int_equal(LY_EVALID, lyd_validate(&tree, NULL, LYD_VALOPT_DATA_ONLY));
+    logbuf_assert("Data are disabled by \"b\" schema node if-feature. /g:cont/l");
+
+    assert_int_equal(lys_feature_enable(mod, "f2"), LY_SUCCESS);
+
+    assert_int_equal(LY_SUCCESS, lyd_validate(&tree, NULL, LYD_VALOPT_DATA_ONLY));
+
+    lyd_free_siblings(tree);
+
+    *state = NULL;
+}
+
 int main(void)
 {
     const struct CMUnitTest tests[] = {
@@ -1045,6 +1187,7 @@
         cmocka_unit_test_teardown(test_unique_nested, teardown_s),
         cmocka_unit_test_teardown(test_dup, teardown_s),
         cmocka_unit_test_teardown(test_defaults, teardown_s),
+        cmocka_unit_test_teardown(test_iffeature, teardown_s),
     };
 
     return cmocka_run_group_tests(tests, setup, teardown);