From 58c36decefc4c62fdcc66790c237fd91e9693c0f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 7 Apr 2020 20:57:50 +0200 Subject: [PATCH] new file: clixon_xml_bind.[ch] --- lib/clixon/clixon_xml_bind.h | 51 ++++ lib/src/clixon_xml_bind.c | 554 +++++++++++++++++++++++++++++++++++ 2 files changed, 605 insertions(+) create mode 100644 lib/clixon/clixon_xml_bind.h create mode 100644 lib/src/clixon_xml_bind.c diff --git a/lib/clixon/clixon_xml_bind.h b/lib/clixon/clixon_xml_bind.h new file mode 100644 index 00000000..7064c3ef --- /dev/null +++ b/lib/clixon/clixon_xml_bind.h @@ -0,0 +1,51 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * + * Bind YANG to XML nodes + */ + +#ifndef _CLIXON_XML_BIND_H_ +#define _CLIXON_XML_BIND_H_ + +/* + * Prototypes + */ +int xml_bind_yang_rpc(cxobj *xrpc, yang_stmt *yspec, cxobj **xerr); +int xml_bind_yang_rpc_reply(cxobj *xrpc, char *name, yang_stmt *yspec, cxobj **xerr); +int xml_bind_yang0(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr); +int xml_bind_yang(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr); + +#endif /* _CLIXON_XML_BIND_H_ */ diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c new file mode 100644 index 00000000..29d331d7 --- /dev/null +++ b/lib/src/clixon_xml_bind.c @@ -0,0 +1,554 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * + * Translation / mapping code between formats + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ + +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_string.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_data.h" +#include "clixon_yang_module.h" +#include "clixon_plugin.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_log.h" +#include "clixon_err.h" +#include "clixon_netconf_lib.h" +#include "clixon_xml_sort.h" +#include "clixon_yang_type.h" +#include "clixon_xml_bind.h" + +/*! After yang binding, bodies of containers and lists are stripped from XML bodies + * May apply to other nodes? + */ +static int +strip_whitespace(cxobj *xt) +{ + yang_stmt *yt; + enum rfc_6020 keyword; + cxobj *xc; + + if ((yt = xml_spec(xt)) != NULL){ + keyword = yang_keyword_get(yt); + if (keyword == Y_LIST || keyword == Y_CONTAINER){ + xc = NULL; + while ((xc = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL) + xml_purge(xc); + } + } + return 0; +} + +/*! Associate XML node x with x:s parents yang:s matching child + * + * @param[in] xt XML tree node + * @param[out] xerr Reason for failure, or NULL + * @retval 1 OK Yang assignment made + * @retval 2 OK Yang assignment not made because yang parent is anyxml or anydata + * @retval 0 Yang assigment not made and xerr set + * @retval -1 Error + * @note retval = 2 is special + * @see populate_self_top + */ +static int +populate_self_parent(cxobj *xt, + cxobj **xerr) +{ + int retval = -1; + yang_stmt *y = NULL; /* yang node */ + yang_stmt *yparent; /* yang parent */ + cxobj *xp = NULL; /* xml parent */ + char *name; + char *ns = NULL; /* XML namespace of xt */ + char *nsy = NULL; /* Yang namespace of xt */ + cbuf *cb = NULL; + + xp = xml_parent(xt); + name = xml_name(xt); + if (xp == NULL){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Missing parent") < 0) + goto done; + goto fail; + } + if ((yparent = xml_spec(xp)) == NULL){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Missing parent yang node") < 0) + goto done; + goto fail; + } + if (yang_keyword_get(yparent) == Y_ANYXML || yang_keyword_get(yparent) == Y_ANYDATA){ + retval = 2; + goto done; + } + if (xml2ns(xt, xml_prefix(xt), &ns) < 0) + goto done; + if ((y = yang_find_datanode(yparent, name)) == NULL){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "Failed to find YANG spec of XML node: %s", name); + cprintf(cb, " with parent: %s", xml_name(xp)); + if (ns) + cprintf(cb, " in namespace: %s", ns); + if (xerr && + netconf_bad_element_xml(xerr, "application", name, cbuf_get(cb)) < 0) + goto done; + goto fail; + } + nsy = yang_find_mynamespace(y); + if (ns == NULL || nsy == NULL){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0) + goto done; + goto fail; + } + /* Assign spec only if namespaces match */ + if (strcmp(ns, nsy) != 0){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0) + goto done; + goto fail; + } + xml_spec_set(xt, y); +#ifdef XML_EXPLICIT_INDEX + if (xml_search_index_p(xt)) + xml_search_child_insert(xp, xt); +#endif + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Associate XML node x with yang spec y by going through all top-level modules and finding match + * + * @param[in] xt XML tree node + * @param[in] yspec Yang spec + * @param[out] xerr Reason for failure, or NULL + * @retval 1 OK yang assignment made + * @retval 0 yang assigment not made and xerr set + * @retval -1 Error + * @see populate_self_parent + */ +static int +populate_self_top(cxobj *xt, + yang_stmt *yspec, + cxobj **xerr) +{ + int retval = -1; + yang_stmt *y = NULL; /* yang node */ + yang_stmt *ymod; /* yang module */ + char *name; + char *ns = NULL; /* XML namespace of xt */ + char *nsy = NULL; /* Yang namespace of xt */ + cbuf *cb = NULL; + cxobj *xp; + + name = xml_name(xt); + if (yspec == NULL){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Missing yang spec") < 0) + goto done; + goto fail; + } + if (ys_module_by_xml(yspec, xt, &ymod) < 0) + goto done; + /* ymod is "real" module, name may belong to included submodule */ + if (ymod == NULL){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "No such yang module") < 0) + goto done; + goto fail; + } + if (xml2ns(xt, xml_prefix(xt), &ns) < 0) + goto done; + if ((y = yang_find_schemanode(ymod, name)) == NULL){ /* also rpc */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "Failed to find YANG spec of XML node: %s", name); + if ((xp = xml_parent(xt)) != NULL) + cprintf(cb, " with parent: %s", xml_name(xp)); + if (ns) + cprintf(cb, " in namespace: %s", ns); + if (xerr && + netconf_bad_element_xml(xerr, "application", name, cbuf_get(cb)) < 0) + goto done; + goto fail; + } + nsy = yang_find_mynamespace(y); + if (ns == NULL || nsy == NULL){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0) + goto done; + goto fail; + } + /* Assign spec only if namespaces match */ + if (strcmp(ns, nsy) != 0){ + if (xerr && + netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0) + goto done; + goto fail; + } + xml_spec_set(xt, y); + retval = 1; + done: + if (cb) + cbuf_free(cb); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Find yang spec association of tree of XML nodes + * + * Populate xt:s children as top-level symbols + * This may be unnecessary if yspec is set on manual creation. Also note that for incoming or + * outgoing RPC need specialized function. maybe it can be built into the same function, but + * you dont know whether it is input or output rpc. + * @param[in] xt XML tree node + * @param[in] yspec Yang spec + * @param[out] xerr Reason for failure, or NULL + * @retval 1 OK yang assignment made + * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set + * @retval -1 Error + * @code + * if (xml_bind_yang(x, YB_MODULE, yspec, NULL) < 0) + * err; + * @endcode + * @note For subs to anyxml nodes will not have spec set + * There are several functions in the API family + * @see xml_bind_yang_rpc for incoming rpc + * @see xml_bind_yang0 If the calling xml object should also be populated + */ +int +xml_bind_yang(cxobj *xt, + yang_bind yb, + yang_stmt *yspec, + cxobj **xerr) +{ + int retval = -1; + cxobj *xc; /* xml child */ + int ret; + int failed = 0; /* we continue loop after failure, should we stop at fail?`*/ + + strip_whitespace(xt); + xc = NULL; /* Apply on children */ + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) { + if ((ret = xml_bind_yang0(xc, yb, yspec, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + } + if (failed) + goto fail; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Find yang spec association of tree of XML nodes + * + * @param[in] xt XML tree node + * @param[in] yb How to bind yang to XML top-level when parsing + * @param[in] yspec Yang spec + * @param[out] xerr Reason for failure, or NULL + * @retval 1 OK yang assignment made + * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set + * @retval -1 Error + * Populate xt as top-level node + */ +int +xml_bind_yang0(cxobj *xt, + yang_bind yb, + yang_stmt *yspec, + cxobj **xerr) +{ + int retval = -1; + cxobj *xc; /* xml child */ + int ret; + int failed = 0; /* we continue loop after failure, should we stop at fail?`*/ + + switch (yb){ + case YB_MODULE: + if ((ret = populate_self_top(xt, yspec, xerr)) < 0) + goto done; + break; + case YB_PARENT: + if ((ret = populate_self_parent(xt, xerr)) < 0) + goto done; + break; + default: + clicon_err(OE_XML, EINVAL, "Invalid yang binding: %d", yb); + goto done; + break; + } + if (ret == 0) + goto fail; + else if (ret == 2) /* ret=2 for anyxml from parent^ */ + goto ok; + strip_whitespace(xt); + xc = NULL; /* Apply on children */ + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) { + if ((ret = xml_bind_yang0(xc, YB_PARENT, yspec, xerr)) < 0) + goto done; + if (ret == 0) + failed++; + } + if (failed) + goto fail; + ok: + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Find yang spec association of XML node for incoming RPC starting with + * + * Incoming RPC has an "input" structure that is not taken care of by xml_bind_yang + * @param[in] xrpc XML rpc node + * @param[in] yspec Yang spec + * @param[out] xerr Reason for failure, or NULL + * @retval 1 OK yang assignment made + * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set + * @retval -1 Error + * The + * @code + * if (xml_bind_yang_rpc(h, x, NULL) < 0) + * err; + * @endcode + * @see xml_bind_yang For other generic cases + * @see xml_bind_yang_rpc_reply + */ +int +xml_bind_yang_rpc(cxobj *xrpc, + yang_stmt *yspec, + cxobj **xerr) +{ + int retval = -1; + yang_stmt *yrpc = NULL; /* yang node */ + yang_stmt *ymod=NULL; /* yang module */ + yang_stmt *yi = NULL; /* input */ + cxobj *x; + int ret; + char *opname; /* top-level netconf operation */ + char *rpcname; /* RPC name */ + char *name; + cbuf *cb = NULL; + cxobj *xc; + + opname = xml_name(xrpc); + if ((strcmp(opname, "hello")) == 0) /* Hello: dont bind, dont appear in any yang spec */ + goto ok; + else if ((strcmp(opname, "notification")) == 0) + goto ok; + else if ((strcmp(opname, "rpc")) == 0) + ; /* continue */ + else { /* Notify, rpc-reply? */ + if (xerr && + netconf_unknown_element_xml(xerr, "protocol", opname, "Unrecognized netconf operation") < 0) + goto done; + goto fail; + } + x = NULL; + while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) { + rpcname = xml_name(x); + if (ys_module_by_xml(yspec, x, &ymod) < 0) + goto done; +#if 1 + assert(ymod); +#else + if (ymod == NULL){ + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* more here */ + goto fail; + } +#endif + if ((yrpc = yang_find(ymod, Y_RPC, rpcname)) == NULL){ + if (xerr && + netconf_unknown_element_xml(xerr, "application", rpcname, "Unrecognized RPC") < 0) + goto done; + goto fail; + } + xml_spec_set(x, yrpc); /* required for validate */ + if ((yi = yang_find(yrpc, Y_INPUT, NULL)) == NULL){ + /* If no yang input spec but RPC has elements, return unknown element */ + if (xml_child_nr_type(x, CX_ELMNT) != 0){ + xc = xml_child_i_type(x, 0, CX_ELMNT); /* Pick first */ + name = xml_name(xc); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "Unrecognized parameter: %s in rpc: %s", name, rpcname); + if (xerr && + netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0) + goto done; + goto fail; + } + } + else{ + /* xml_bind_yang need to have parent with yang spec for + * recursive population to work. Therefore, assign input yang + * to rpc level although not 100% intuitive */ + xml_spec_set(x, yi); + if ((ret = xml_bind_yang(x, YB_PARENT, NULL, xerr)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + ok: + retval = 1; + done: + if (cb) + cbuf_free(cb); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Find yang spec association of XML node for outgoing RPC starting with + * + * Incoming RPC has an "input" structure that is not taken care of by xml_bind_yang + * @param[in] xrpc XML rpc node + * @param[in] name Name of RPC (not seen in output/reply) + * @param[in] yspec Yang spec + * @param[out] xerr Reason for failure, or NULL + * @retval 1 OK yang assignment made + * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set + * @retval -1 Error + * + * @code + * if (xml_bind_yang_rpc_reply(x, "get-config", yspec, name) < 0) + * err; + * @endcode + * @see xml_bind_yang For other generic cases + */ +int +xml_bind_yang_rpc_reply(cxobj *xrpc, + char *name, + yang_stmt *yspec, + cxobj **xerr) +{ + int retval = -1; + yang_stmt *yrpc = NULL; /* yang node */ + yang_stmt *ymod=NULL; /* yang module */ + yang_stmt *yo = NULL; /* output */ + cxobj *x; + int ret; + + if (strcmp(xml_name(xrpc), "rpc-reply")){ + clicon_err(OE_UNIX, EINVAL, "rpc-reply expected"); + goto done; + } + x = NULL; + while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) { + if (ys_module_by_xml(yspec, x, &ymod) < 0) + goto done; + if (ymod == NULL) + continue; + if ((yrpc = yang_find(ymod, Y_RPC, name)) == NULL) + continue; + // xml_spec_set(xrpc, yrpc); + if ((yo = yang_find(yrpc, Y_OUTPUT, NULL)) == NULL) + continue; + /* xml_bind_yang need to have parent with yang spec for + * recursive population to work. Therefore, assign input yang + * to rpc level although not 100% intuitive */ + break; + } + if (yo != NULL){ + xml_spec_set(xrpc, yo); + if ((ret = xml_bind_yang(xrpc, YB_MODULE, yspec, xerr)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + + +