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);