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_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
+};