extensions FEATURE yang-data extension implementation

Internal libyang support for yang-data extension defined in
ietf-restconf module (RFC 8040)

Fixes #1334
diff --git a/src/plugins_exts.c b/src/plugins_exts.c
index 1b74d80..17333b8 100644
--- a/src/plugins_exts.c
+++ b/src/plugins_exts.c
@@ -20,6 +20,7 @@
 #include "common.h"
 #include "plugins_exts_metadata.c"
 #include "plugins_exts_nacm.c"
+#include "plugins_exts_yangdata.c"
 
 /**
  * @brief list of all extension plugins implemented internally
@@ -30,6 +31,7 @@
     {"ietf-netconf-acm", "2012-02-22", "default-deny-all", &nacm_plugin},
     {"ietf-netconf-acm", "2018-02-14", "default-deny-all", &nacm_plugin},
     {"ietf-yang-metadata", "2016-08-05", "annotation", &metadata_plugin},
+    {"ietf-restconf", "2017-01-26", "yang-data", &yangdata_plugin},
     {NULL, NULL, NULL, NULL} /* terminating item */
 };
 
diff --git a/src/plugins_exts_internal.h b/src/plugins_exts_internal.h
index fac88f4..ab6196c 100644
--- a/src/plugins_exts_internal.h
+++ b/src/plugins_exts_internal.h
@@ -31,6 +31,11 @@
 #define LYEXT_PLUGIN_INTERNAL_ANNOTATION 4
 
 /**
+ * @brief Index of yang-data extension plugin in lyext_plugins_internal
+ */
+#define LYEXT_PLUGIN_INTERNAL_YANGDATA 5
+
+/**
  * @brief Find the extension plugin for the specified extension instance.
  *
  * @param[in] mod YANG module where the
diff --git a/src/plugins_exts_yangdata.c b/src/plugins_exts_yangdata.c
new file mode 100644
index 0000000..148360b
--- /dev/null
+++ b/src/plugins_exts_yangdata.c
@@ -0,0 +1,163 @@
+/**
+ * @file plugins_exts_yangdata.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang extension plugin - yang-data (RFC 8040)
+ *
+ * Copyright (c) 2021 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 <stdlib.h>
+
+#include "common.h"
+#include "plugins_exts.h"
+#include "schema_compile.h"
+#include "tree_schema.h"
+
+/**
+ * @brief Storage for ID used to check plugin API version compatibility.
+ * Ignored here in the internal plugin.
+LYEXT_VERSION_CHECK
+ */
+
+/**
+ * @brief Free yang-data extension instances' data.
+ *
+ * Implementation of ::lyext_clb_free callback set as lyext_plugin::free.
+ */
+void
+yangdata_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext)
+{
+    lysc_extension_instance_substatements_free(ctx, ext->substmts);
+}
+
+/**
+ * @brief Compile yang-data extension instances.
+ *
+ * Implementation of lyext_clb_compile callback set as lyext_plugin::compile.
+ */
+LY_ERR
+yangdata_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *p_ext, struct lysc_ext_instance *c_ext)
+{
+    LY_ERR ret;
+    LY_ARRAY_COUNT_TYPE u;
+    struct lysc_module *mod_c;
+    const struct lysc_node *child;
+    ly_bool valid = 1;
+    uint32_t prev_options = cctx->options;
+
+    /* yang-data can appear only at the top level of a YANG module or submodule */
+    if (c_ext->parent_type != LYEXT_PAR_MODULE) {
+        lyext_log(c_ext, LY_LLWRN, 0, cctx->path,
+                "Extension %s is ignored since it appears as a non top-level statement in \"%s\" statement.",
+                p_ext->name, lyext_parent2str(c_ext->parent_type));
+        return LY_ENOT;
+    }
+    /* check mandatory argument */
+    if (!c_ext->argument) {
+        lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                "Extension %s is instantiated without mandatory argument representing YANG data template name.",
+                p_ext->name);
+        return LY_EVALID;
+    }
+
+    mod_c = (struct lysc_module *)c_ext->parent;
+
+    /* check for duplication */
+    LY_ARRAY_FOR(mod_c->exts, u) {
+        if ((&mod_c->exts[u] != c_ext) && (mod_c->exts[u].def == c_ext->def) && !strcmp(mod_c->exts[u].argument, c_ext->argument)) {
+            /* duplication of the same yang-data extension in a single module */
+            lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path, "Extension %s is instantiated multiple times.", p_ext->name);
+            return LY_EVALID;
+        }
+    }
+
+    /* compile annotation substatements
+     * To let the compilation accept different statements possibly leading to the container top-level node, there are 3
+     * allowed substatements pointing to a single storage. But when compiled, the substaments list is compressed just to
+     * a single item providing the schema tree. */
+    LY_ARRAY_CREATE_RET(cctx->ctx, c_ext->substmts, 3, LY_EMEM);
+    LY_ARRAY_INCREMENT(c_ext->substmts);
+    c_ext->substmts[0].stmt = LY_STMT_CONTAINER;
+    c_ext->substmts[0].cardinality = LY_STMT_CARD_OPT;
+    c_ext->substmts[0].storage = &c_ext->data;
+
+    LY_ARRAY_INCREMENT(c_ext->substmts);
+    c_ext->substmts[1].stmt = LY_STMT_CHOICE;
+    c_ext->substmts[1].cardinality = LY_STMT_CARD_OPT;
+    c_ext->substmts[1].storage = &c_ext->data;
+
+    LY_ARRAY_INCREMENT(c_ext->substmts);
+    c_ext->substmts[2].stmt = LY_STMT_USES;
+    c_ext->substmts[2].cardinality = LY_STMT_CARD_OPT;
+    c_ext->substmts[2].storage = &c_ext->data;
+
+    cctx->options |= LYS_COMPILE_NO_CONFIG | LYS_COMPILE_NO_DISABLED;
+    ret = lys_compile_extension_instance(cctx, p_ext, c_ext);
+    cctx->options = prev_options;
+    LY_ARRAY_DECREMENT(c_ext->substmts);
+    LY_ARRAY_DECREMENT(c_ext->substmts);
+    LY_CHECK_RET(ret);
+
+    /* check that we have really just a single container data definition in the top */
+    child = *(struct lysc_node **)c_ext->substmts[0].storage;
+    if (!child) {
+        valid = 0;
+        lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                "Extension %s is instantiated without any top level data node, but exactly one container data node is expected.",
+                p_ext->name);
+    } else if (child->next) {
+        valid = 0;
+        lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                "Extension %s is instantiated with multiple top level data nodes, but only a single container data node is allowed.",
+                p_ext->name);
+    } else if (child->nodetype == LYS_CHOICE) {
+        /* all the choice's case are expected to result to a single container node */
+        const struct lysc_node *snode = NULL;
+
+        while ((snode = lys_getnext(snode, child, mod_c, 0))) {
+            if (snode->next) {
+                valid = 0;
+                lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                        "Extension %s is instantiated with multiple top level data nodes (inside a single choice's case), "
+                        "but only a single container data node is allowed.", p_ext->name);
+                break;
+            } else if (snode->nodetype != LYS_CONTAINER) {
+                valid = 0;
+                lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                        "Extension %s is instantiated with %s top level data node (inside a choice), "
+                        "but only a single container data node is allowed.", p_ext->name, lys_nodetype2str(snode->nodetype));
+                break;
+            }
+        }
+    } else if (child->nodetype != LYS_CONTAINER) {
+        /* via uses */
+        valid = 0;
+        lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                "Extension %s is instantiated with %s top level data node, but only a single container data node is allowed.",
+                p_ext->name, lys_nodetype2str(child->nodetype));
+    }
+
+    if (!valid) {
+        yangdata_free(cctx->ctx, c_ext);
+        c_ext->data = c_ext->substmts = NULL;
+        return LY_EVALID;
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Plugin for the yang-data extension
+ */
+struct lyext_plugin yangdata_plugin = {
+    .id = "libyang 2 - yang-data, version 1",
+    .compile = &yangdata_compile,
+    .validate = NULL,
+    .free = yangdata_free
+};
diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c
index fe87070..1b0676c 100644
--- a/src/schema_compile_node.c
+++ b/src/schema_compile_node.c
@@ -2044,33 +2044,51 @@
     }
 
     iter = NULL;
-    while ((iter = lys_getnext(iter, parent, ctx->cur_mod->compiled, getnext_flags))) {
-        if (!ly_set_contains(&parent_choices, (void*)iter, NULL) && CHECK_NODE(iter, exclude, name)) {
-            goto error;
-        }
+    if (!parent && ctx->ext) {
+        while ((iter = lys_getnext_ext(iter, parent, ctx->ext, getnext_flags))) {
+            if (!ly_set_contains(&parent_choices, (void *)iter, NULL) && CHECK_NODE(iter, exclude, name)) {
+                goto error;
+            }
 
-        /* we must compare with both the choice and all its nested data-definiition nodes (but not recursively) */
-        if (iter->nodetype == LYS_CHOICE) {
-            iter2 = NULL;
-            while ((iter2 = lys_getnext(iter2, iter, NULL, 0))) {
-                if (CHECK_NODE(iter2, exclude, name)) {
-                    goto error;
+            /* we must compare with both the choice and all its nested data-definiition nodes (but not recursively) */
+            if (iter->nodetype == LYS_CHOICE) {
+                iter2 = NULL;
+                while ((iter2 = lys_getnext_ext(iter2, iter, NULL, 0))) {
+                    if (CHECK_NODE(iter2, exclude, name)) {
+                        goto error;
+                    }
                 }
             }
         }
-    }
+    } else {
+        while ((iter = lys_getnext(iter, parent, ctx->cur_mod->compiled, getnext_flags))) {
+            if (!ly_set_contains(&parent_choices, (void *)iter, NULL) && CHECK_NODE(iter, exclude, name)) {
+                goto error;
+            }
 
-    actions = parent ? lysc_node_actions(parent) : ctx->cur_mod->compiled->rpcs;
-    LY_LIST_FOR((struct lysc_node *)actions, iter) {
-        if (CHECK_NODE(iter, exclude, name)) {
-            goto error;
+            /* we must compare with both the choice and all its nested data-definiition nodes (but not recursively) */
+            if (iter->nodetype == LYS_CHOICE) {
+                iter2 = NULL;
+                while ((iter2 = lys_getnext(iter2, iter, NULL, 0))) {
+                    if (CHECK_NODE(iter2, exclude, name)) {
+                        goto error;
+                    }
+                }
+            }
         }
-    }
 
-    notifs = parent ? lysc_node_notifs(parent) : ctx->cur_mod->compiled->notifs;
-    LY_LIST_FOR((struct lysc_node *)notifs, iter) {
-        if (CHECK_NODE(iter, exclude, name)) {
-            goto error;
+        actions = parent ? lysc_node_actions(parent) : ctx->cur_mod->compiled->rpcs;
+        LY_LIST_FOR((struct lysc_node *)actions, iter) {
+            if (CHECK_NODE(iter, exclude, name)) {
+                goto error;
+            }
+        }
+
+        notifs = parent ? lysc_node_notifs(parent) : ctx->cur_mod->compiled->notifs;
+        LY_LIST_FOR((struct lysc_node *)notifs, iter) {
+            if (CHECK_NODE(iter, exclude, name)) {
+                goto error;
+            }
         }
     }
     ly_set_erase(&parent_choices, NULL);