diff --git a/CHANGELOG.md b/CHANGELOG.md index ed7c174e..cdd98395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ ### Minor changes * C-API: Added `xpath_first_localonly()` as an xpath function that skips prefix and namespace checks. -* Added experimental code for optizing XPath search using binary search. +* Added experimental code for optimizing XPath search using binary search. * Enable with XPATH_LIST_OPTIMIZE in include/clixon_custom.h * Optimizes xpaths on the form: `a[b=c]` on sorted, yangified config lists. * Removed most assert.h includes +* Created two sub-files (clixon_validate.c and clixon_api_path.c) from large lib/src/clixon_xml_map.c source file. * Added "canonical" global namespace context: `nsctx_global` * This is a normalized XML prefix:namespace pair vector computed from all loaded Yang modules. Useful when writing XML and XPATH expressions in callbacks. * Get it with `clicon_nsctx_global_get(h)` diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 2601839b..5ed5991f 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -82,7 +82,9 @@ #include #include #include +#include #include +#include #include #include #include diff --git a/lib/clixon/clixon_api_path.h b/lib/clixon/clixon_api_path.h new file mode 100644 index 00000000..b1fba9d9 --- /dev/null +++ b/lib/clixon/clixon_api_path.h @@ -0,0 +1,64 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * BNF: + * := ("/" ( | ))* + * := + * := [ ":"] + * := + * := "=" key-value *("," key-value) + * := + * := + * := ( | "_") ( | | "_" | "-" | ".") + */ + +#ifndef _CLIXON_API_PATH_H_ +#define _CLIXON_API_PATH_H_ + +/* + * Prototypes + */ +int xml_yang_root(cxobj *x, cxobj **xr); +int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt); +int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path); +int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath); +int api_path2xpath_cvv(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, cvec **nsc, cxobj **xerr); +int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, cvec **nsc); +int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop, + yang_class nodeclass, int strict, + cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr); +int xml2api_path_1(cxobj *x, cbuf *cb); + +#endif /* _CLIXON_API_PATH_H_ */ diff --git a/lib/clixon/clixon_validate.h b/lib/clixon/clixon_validate.h new file mode 100644 index 00000000..cfc7bd50 --- /dev/null +++ b/lib/clixon/clixon_validate.h @@ -0,0 +1,50 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * + * XML code + */ + +#ifndef _CLIXON_VALIDATE_H_ +#define _CLIXON_VALIDATE_H_ + +/* + * Prototypes + */ +int xml_yang_validate_rpc(clicon_handle h, cxobj *xrpc, cxobj **xret); +int xml_yang_validate_add(clicon_handle h, cxobj *xt, cxobj **xret); +int xml_yang_validate_list_key_only(clicon_handle h, cxobj *xt, cxobj **xret); +int xml_yang_validate_all(clicon_handle h, cxobj *xt, cxobj **xret); +int xml_yang_validate_all_top(clicon_handle h, cxobj *xt, cxobj **xret); + +#endif /* _CLIXON_VALIDATE_H_ */ diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 250acfe5..9a5107a2 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -32,7 +32,7 @@ ***** END LICENSE BLOCK ***** * - * XML code + * Translation / mapping code between formats */ #ifndef _CLIXON_XML_MAP_H_ @@ -47,22 +47,13 @@ typedef enum yang_class yang_class; int isxmlns(cxobj *x); int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); -int xml_yang_root(cxobj *x, cxobj **xr); int xmlns_assign(cxobj *x); -int xml_yang_validate_rpc(clicon_handle h, cxobj *xrpc, cxobj **xret); -int xml_yang_validate_list_key_only(clicon_handle h, cxobj *xt, cxobj **xret); -int xml_yang_validate_add(clicon_handle h, cxobj *xt, cxobj **xret); -int xml_yang_validate_all(clicon_handle h, cxobj *xt, cxobj **xret); -int xml_yang_validate_all_top(clicon_handle h, cxobj *xt, cxobj **xret); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1, cxobj ***first, size_t *firstlen, cxobj ***second, size_t *secondlen, cxobj ***changed_x0, cxobj ***changed_x1, size_t *changedlen); -int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt); -int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path); -int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath); int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); int xml_tree_prune_flagged(cxobj *xt, int flag, int test); int xml_default(cxobj *x, void *arg); @@ -70,14 +61,7 @@ int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_stmt *yspec); int xml_spec_populate(cxobj *x, void *arg); -int api_path2xpath_cvv(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, cvec **nsc, cxobj **xerr); -int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, cvec **nsc); -int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop, - yang_class nodeclass, int strict, - cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr); - int xml2xpath(cxobj *x, char **xpath); -int xml2api_path_1(cxobj *x, cbuf *cb); int check_namespaces(cxobj *x0, cxobj *x1, cxobj *x1p); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index ee84bee9..053addce 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -67,10 +67,11 @@ CPPFLAGS = @CPPFLAGS@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$(top_srcdir) SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ - clixon_string.c clixon_regex.c clixon_handle.c \ - clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \ + clixon_string.c clixon_regex.c clixon_handle.c clixon_file.c \ + clixon_xml.c clixon_xml_sort.c clixon_xml_map.c \ clixon_json.c clixon_yang.c clixon_yang_type.c clixon_yang_module.c \ clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ + clixon_api_path.c clixon_validate.c \ clixon_hash.c clixon_options.c clixon_data.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_optimize.c \ diff --git a/lib/src/clixon_api_path.c b/lib/src/clixon_api_path.c new file mode 100644 index 00000000..708c65cd --- /dev/null +++ b/lib/src/clixon_api_path.c @@ -0,0 +1,1064 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * BNF: + * := ("/" ( | ))* + * := + * := [ ":"] + * := + * := "=" key-value *("," key-value) + * := + * := + * := ( | "_") ( | | "_" | "-" | ".") + + */ +#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 + +/* clixon */ +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_string.h" +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_options.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_nsctx.h" +#include "clixon_netconf_lib.h" +#include "clixon_xml_map.h" +#include "clixon_api_path.h" + +/*! Given an XML node, return root node + * A root node is an ancestor xr of x with one or both of the following properties + * - its XML parent is NULL parent, + * - its associated yang specification's parent is a yang module. + * @param[in] x XML node + * @param[out] xr XML root + */ +int +xml_yang_root(cxobj *x, + cxobj **xr) +{ + int retval = -1; + cxobj *xp; + yang_stmt *y; + yang_stmt *yp; + + while ((xp = xml_parent(x)) != NULL){ + if ((y = xml_spec(x)) != NULL && + (yp = yang_parent_get(y)) != NULL) + /* Actually, maybe only the Y_MODULE clause is relevant */ + if (yp==NULL || + yang_keyword_get(yp) == Y_MODULE || + yang_keyword_get(yp) == Y_SUBMODULE) + break; /* x is the root */ + x = xp; + } + *xr = x; + retval = 0; + return retval; +} + +/*! Construct an xml key format from yang statement using wildcards for keys + * Recursively construct it to the top. + * Example: + * yang: container a -> list b -> key c -> leaf d + * xpath: /modname:a/b/%s/d + * @param[in] ys Yang statement + * @param[in] inclkey If set include key leaf (eg last leaf d in ex) + * @param[out] cb api_path_fmt, + * @retval 0 OK + * @retval -1 Error + * @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression" + */ +static int +yang2api_path_fmt_1(yang_stmt *ys, + int inclkey, + cbuf *cb) +{ + yang_stmt *yp; /* parent */ + yang_stmt *ymod; + int i; + cvec *cvk = NULL; /* vector of index keys */ + int retval = -1; + + if ((yp = yang_parent_get(ys)) == NULL){ + clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys)); + goto done; + } + if (yp != NULL && /* XXX rm */ + yang_keyword_get(yp) != Y_MODULE && + yang_keyword_get(yp) != Y_SUBMODULE){ + + if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ + goto done; + if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){ +#if 0 + /* In some cases, such as cli_show_auto, a trailing '/' should + * NOT be present if ys is a key in a list. + * But in other cases (I think most), the / should be there, + * so a patch is added in cli_show_auto instead. + */ + if (yang_keyword_get(ys) == Y_LEAF && yp && + yang_keyword_get(yp) == Y_LIST && + yang_key_match(yp, ys->ys_argument) == 1) + ; + else +#endif + cprintf(cb, "/"); + } + /* If parent namespace/module is different from child -> add child prefix */ + if (ys_real_module(yp) != (ymod = ys_real_module(ys))) + cprintf(cb, "%s:", yang_argument_get(ymod)); + } + else /* top symbol - mark with name prefix */ + cprintf(cb, "/%s:", yang_argument_get(yp)); + + if (inclkey){ + if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE) + cprintf(cb, "%s", yang_argument_get(ys)); + } + else{ + if (yang_keyword_get(ys) == Y_LEAF && yp && + yang_keyword_get(yp) == Y_LIST){ + if (yang_key_match(yp, yang_argument_get(ys)) == 0) + cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */ + } + else + if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE) + cprintf(cb, "%s", yang_argument_get(ys)); + } + + switch (yang_keyword_get(ys)){ + case Y_LIST: + cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */ + if (cvec_len(cvk)) + cprintf(cb, "="); + /* Iterate over individual keys */ + for (i=0; i list b -> key c -> leaf d + * api_path: /a/b=%s/d + * @param[in] ys Yang statement + * @param[in] inclkey If set include key leaf (eg last leaf d in ex) + * @param[out] api_path_fmt XML api path. Needs to be freed after use. + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + */ +int +yang2api_path_fmt(yang_stmt *ys, + int inclkey, + char **api_path_fmt) +{ + int retval = -1; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (yang2api_path_fmt_1(ys, inclkey, cb) < 0) + goto done; + if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + +/*! Transform an xml key format and a vector of values to an XML key + * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() + * Example: + * xmlkeyfmt: /interfaces/interface=%s/ipv4/address=%s + * cvv: 0 : set interfaces interface e ipv4 address 1.2.3.4 + * 1 : name = "e" + * 2 : ip = "1.2.3.4" + * api_path: /interfaces/interface=e/ipv4/address=1.2.3.4 + * @param[in] api_path_fmt XML key format, eg /aaa/%s/name + * @param[in] cvv cligen variable vector, one for every wildchar in + * api_path_fmt + * @param[out] api_path api_path, eg /aaa/17. Free after use + * @note first and last elements of cvv are not used,.. + * @see api_path_fmt2xpath + * @example + * api_path_fmt: /interfaces/interface=%s/name + * cvv: - + * api_path: /interfaces/interface/name + * @example + * api_path_fmt: /interfaces/interface=%s/name + * cvv: e0 + * api_path: /interfaces/interface=e0/name + * @example + * api_path_fmt: /subif-entry=%s,%s/subid + * cvv: foo + * api_path: /subif-entry=foo/subid + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + */ +int +api_path_fmt2api_path(char *api_path_fmt, + cvec *cvv, + char **api_path) +{ + int retval = -1; + char c; + int esc=0; + cbuf *cb = NULL; + int i; + int j; + char *str; + char *strenc=NULL; + cg_var *cv; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + j = 1; /* j==0 is cli string */ + for (i=0; i get module + change namespace */ + if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){ + cprintf(cberr, "No such yang module: %s", prefix); + if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + namespace = yang_find_mynamespace(ymod); /* change namespace */ + } + if (i == offset && ymod) /* root */ + y = yang_find_datanode(ymod, name); + else + y = yang_find_datanode(y, name); + if (y == NULL){ + if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0) + goto done; + goto fail; + } + + /* Get XML/xpath prefix given namespace. + * note different from api-path prefix + */ + if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){ + xprefix = yang_find_myprefix(y); + clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix); + /* not found, add it to nsc */ + if (xml_nsctx_add(nsc, xprefix, namespace) < 0) + goto done; + } + /* Check if has value, means '=' */ + if (cv2str(cv, NULL, 0) > 0){ + if ((val = cv2str_dup(cv)) == NULL) + goto done; + switch (yang_keyword_get(y)){ + case Y_LIST: + /* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3) + * Note that vnr can be < length of cvk, due to empty or unset values + */ + if (valvec){ /* loop, valvec may have been used before */ + free(valvec); + valvec = NULL; + } + if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL) + goto done; + cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ + cvi = NULL; + /* Iterate over individual yang keys */ + cprintf(xpath, "/"); + if (xprefix) + cprintf(xpath, "%s:", xprefix); + cprintf(xpath, "%s", name); + vi = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL && vi + * xtop[out]: + * foo> + * + * xbotp: + * ybotp: Y_LEAF subid + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xpath For api-path to xml xpath translation + */ +int +api_path2xml(char *api_path, + yang_stmt *yspec, + cxobj *xtop, + yang_class nodeclass, + int strict, + cxobj **xbotp, + yang_stmt **ybotp, + cxobj **xerr) +{ + int retval = -1; + char **vec = NULL; + int nvec; + cxobj *xroot; + cbuf *cberr = NULL; + + clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (*api_path!='/'){ + cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path); + if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) + goto done; + /* Remove trailing '/'. Like in /a/ -> /a */ + if (nvec > 1 && !strlen(vec[nvec-1])) + nvec--; + if (nvec < 1){ + cprintf(cberr, "Malformed api-path: %s: too short)", api_path); + if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + nvec--; /* NULL-terminated */ + if ((retval = api_path2xml_vec(vec+1, nvec, + xtop, yspec, nodeclass, strict, + xbotp, ybotp, xerr)) < 1) + goto done; + xml_yang_root(*xbotp, &xroot); + if (xmlns_assign(xroot) < 0) + goto done; + // ok: + retval = 1; + done: + if (cberr) + cbuf_free(cberr); + if (vec) + free(vec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Construct an api_path from an XML node (single level not recursive) + * @param[in] x XML node (need to be yang populated) + * @param[out] cb api_path, must be initialized + * @retval 0 OK + * @retval -1 Error + * @see yang2api_path_fmt + * @see xml2xpath + */ +int +xml2api_path_1(cxobj *x, + + cbuf *cb) +{ + int retval = -1; + yang_stmt *y = NULL; + cvec *cvk = NULL; /* vector of index keys */ + cg_var *cvi; + enum rfc_6020 keyword; + int i; + char *keyname; + cxobj *xkey; + cxobj *xb; + char *b; + char *enc; + yang_stmt *ymod; + cxobj *xp; + + if ((y = xml_spec(x)) == NULL){ + cprintf(cb, "/%s", xml_name(x)); + goto ok; + } + ymod = ys_module(y); + xp = xml_parent(x); + if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */ + cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x)); + else + cprintf(cb, "/%s", xml_name(x)); + keyword = yang_keyword_get(y); + switch (keyword){ + case Y_LEAF_LIST: + b = xml_body(x); + enc = NULL; + if (uri_percent_encode(&enc, "%s", b) < 0) + goto done; + cprintf(cb, "=%s", enc?enc:""); + if (enc) + free(enc); + break; + case Y_LIST: + cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ + if (cvec_len(cvk)) + cprintf(cb, "="); + /* Iterate over individual keys */ + cvi = NULL; + i = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + if ((xkey = xml_find(x, keyname)) == NULL) + goto done; /* No key in xml */ + if ((xb = xml_find(x, keyname)) == NULL) + goto done; + if (i++) + cprintf(cb, ","); + b = xml_body(xb); + enc = NULL; + if (uri_percent_encode(&enc, "%s", b) < 0) + goto done; + cprintf(cb, "%s", enc?enc:""); + if (enc) + free(enc); + } + break; + default: + break; + } +#if 0 + { /* Just for testing */ + cxobj *xc; + if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL) + if (xml2api_path_1(xc, cb) < 0) + goto done; + } +#endif + ok: + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 95da6fee..046df602 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -59,7 +59,7 @@ /* cligen */ #include -/* clicon */ +/* clixon */ #include "clixon_err.h" #include "clixon_string.h" #include "clixon_queue.h" @@ -74,6 +74,7 @@ #include "clixon_xpath.h" #include "clixon_netconf_lib.h" #include "clixon_xml_nsctx.h" +#include "clixon_validate.h" #include "clixon_xml_map.h" /*! Clixon configuration namespace diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c new file mode 100644 index 00000000..5d09e7bd --- /dev/null +++ b/lib/src/clixon_validate.c @@ -0,0 +1,1197 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * + * XML code + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + */ +#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_err.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_netconf_lib.h" +#include "clixon_options.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#if 0 + +#include "clixon_plugin.h" + + +#include "clixon_log.h" +#include "clixon_xml_sort.h" +#include "clixon_yang_internal.h" /* internal */ + +#endif +#include "clixon_yang_type.h" +#include "clixon_xml_map.h" +#include "clixon_validate.h" + +/*! Validate xml node of type leafref, ensure the value is one of that path's reference + * @param[in] xt XML leaf node of type leafref + * @param[in] ytype Yang type statement belonging to the XML node + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * From rfc7950 Sec 9.9.2 + * The "path" XPath expression is evaluated in the following context, + * in addition to the definition in Section 6.4.1: + * o If the "path" statement is defined within a typedef, the context + * node is the leaf or leaf-list node in the data tree that + * references the typedef. + * o Otherwise, the context node is the node in the data tree for which + * the "path" statement is defined. + */ +static int +validate_leafref(cxobj *xt, + yang_stmt *ytype, + cxobj **xret) +{ + int retval = -1; + yang_stmt *ypath; + cxobj **xvec = NULL; + cxobj *x; + int i; + size_t xlen = 0; + char *leafrefbody; + char *leafbody; + cvec *nsc = NULL; + + if ((leafrefbody = xml_body(xt)) == NULL) + goto ok; + if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ + if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + goto done; + goto fail; + } + /* XXX see comment above regarding typeref or not */ + if (xml_nsctx_yang(ytype, &nsc) < 0) + goto done; + if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, yang_argument_get(ypath)) < 0) + goto done; + for (i = 0; i < xlen; i++) { + x = xvec[i]; + if ((leafbody = xml_body(x)) == NULL) + continue; + if (strcmp(leafbody, leafrefbody) == 0) + break; + } + if (i==xlen){ + if (netconf_bad_element_xml(xret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0) + goto done; + goto fail; + } + ok: + retval = 1; + done: + if (nsc) + xml_nsctx_free(nsc); + if (xvec) + free(xvec); + return retval; + fail: + retval = 0; + goto done; +} + + +/*! Validate xml node of type identityref, ensure value is a defined identity + * Check if a given node has value derived from base identity. This is + * a run-time check necessary when validating eg netconf. + * Valid values for an identityref are any identities derived from all + * the identityref's base identities. + * Example: + * b0 --> b1 --> b2 (b1 & b2 are derived) + * identityref b2 + * base b0; + * This function does: derived_from(b2, b0); + * @param[in] xt XML leaf node of type identityref + * @param[in] ys Yang spec of leaf + * @param[in] ytype Yang type field of type identityref + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * @see ys_populate_identity where the derived types are set + * @see yang_augment_node + * @see RFC7950 Sec 9.10.2: + */ +static int +validate_identityref(cxobj *xt, + yang_stmt *ys, + yang_stmt *ytype, + cxobj **xret) + +{ + int retval = -1; + char *node = NULL; + char *idref = NULL; + yang_stmt *ybaseref; /* This is the type's base reference */ + yang_stmt *ybaseid; + char *prefix = NULL; + char *id = NULL; + cbuf *cberr = NULL; + cbuf *cb = NULL; + cvec *idrefvec; /* Derived identityref list: (module:id)**/ + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* Get idref value. Then see if this value is derived from ytype. + */ + if ((node = xml_body(xt)) == NULL){ /* It may not be empty */ + if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) + goto done; + goto fail; + } + if (nodeid_split(node, &prefix, &id) < 0) + goto done; + /* This is the type's base reference */ + if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){ + if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) + goto done; + goto fail; + } + /* This is the actual base identity */ + if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){ + if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) + goto done; + goto fail; + } + +#if 0 /* Assume proper namespace, otherwise we assume module prefixes, + * see IDENTITYREF_KLUDGE + */ + { + char *namespace; + yang_stmt *ymod; + yang_stmt *yspec; + + /* Create an idref as : which is the format of the derived + * identityref list associated with the base identities. + */ + /* Get namespace (of idref) from xml */ + if (xml2ns(xt, prefix, &namespace) < 0) + goto done; + yspec = ys_spec(ys); + /* Get module of that namespace */ + if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No module found"); + goto done; + } + cprintf(cb, "%s:%s", yang_argument_get(ymod), id); + } +#else + { + yang_stmt *ymod; + /* idref from prefix:id to module:id */ + if (prefix == NULL) + ymod = ys_module(ys); + else{ /* from prefix to name */ +#if 1 /* IDENTITYREF_KLUDGE */ + ymod = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix); +#endif + } + if (ymod == NULL){ + cprintf(cberr, "Identityref validation failed, %s not derived from %s", + node, yang_argument_get(ybaseid)); + if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + cprintf(cb, "%s:%s", yang_argument_get(ymod), id); + } +#endif + idref = cbuf_get(cb); + /* Here check if node is in the derived node list of the base identity + * The derived node list is a cvec computed XXX + */ + idrefvec = yang_cvec_get(ybaseid); + if (cvec_find(idrefvec, idref) == NULL){ + cprintf(cberr, "Identityref validation failed, %s not derived from %s", + node, yang_argument_get(ybaseid)); + if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + retval = 1; + done: + if (cberr) + cbuf_free(cberr); + if (cb) + cbuf_free(cb); + if (id) + free(id); + if (prefix) + free(prefix); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Validate an RPC node + * @param[in] h Clicon handle + * @param[in] xrpc XML node to be validated + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * rfc7950 + * 7.14.2 + * If a leaf in the input tree has a "mandatory" statement with the + * value "true", the leaf MUST be present in an RPC invocation. + * + * If a leaf in the input tree has a default value, the server MUST use + * this value in the same cases as those described in Section 7.6.1. In + * these cases, the server MUST operationally behave as if the leaf was + * present in the RPC invocation with the default value as its value. + * + * If a leaf-list in the input tree has one or more default values, the + * server MUST use these values in the same cases as those described in + * Section 7.7.2. In these cases, the server MUST operationally behave + * as if the leaf-list was present in the RPC invocation with the + * default values as its values. + * + * Since the input tree is not part of any datastore, all "config" + * statements for nodes in the input tree are ignored. + * + * If any node has a "when" statement that would evaluate to "false", + * then this node MUST NOT be present in the input tree. + * + * 7.14.4 + * Input parameters are encoded as child XML elements to the rpc node's + * XML element, in the same order as they are defined within the "input" + * statement. + * + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element defined + * in [RFC6241]. If output parameters are returned, they are encoded as + * child elements to the element defined in [RFC6241], in + * the same order as they are defined within the "output" statement. + * @see xml_yang_validate_all + * @note Should need a variant accepting cxobj **xret + */ +int +xml_yang_validate_rpc(clicon_handle h, + cxobj *xrpc, + cxobj **xret) +{ + int retval = -1; + yang_stmt *yn=NULL; /* rpc name */ + cxobj *xn; /* rpc name */ + + if (strcmp(xml_name(xrpc), "rpc")){ + clicon_err(OE_XML, EINVAL, "Expected RPC"); + goto done; + } + xn = NULL; + /* xn is name of rpc, ie */ + while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { + if ((yn = xml_spec(xn)) == NULL){ + if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) + goto done; + goto fail; + } + if ((retval = xml_yang_validate_all(h, xn, xret)) < 1) + goto done; /* error or validation fail */ + if ((retval = xml_yang_validate_add(h, xn, xret)) < 1) + goto done; /* error or validation fail */ + if (xml_apply0(xn, CX_ELMNT, xml_default, h) < 0) + goto done; + } + // ok: /* pass validation */ + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Check if an xml node is a part of a choice and have >1 siblings + * @param[in] xt XML node to be validated + * @param[in] yt xt:s yang statement + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + * Check if xt is part of valid choice + */ +static int +check_choice(cxobj *xt, + yang_stmt *yt, + cxobj **xret) +{ + int retval = -1; + yang_stmt *y; + yang_stmt *ytp; /* yt:s parent */ + yang_stmt *ytcase = NULL; /* yt:s parent case if any */ + yang_stmt *ytchoice = NULL; + yang_stmt *yp; + cxobj *x; + cxobj *xp; + + if ((ytp = yang_parent_get(yt)) == NULL) + goto ok; + /* Return OK if xt is not choice */ + switch (yang_keyword_get(ytp)){ + case Y_CASE: + ytcase = ytp; + ytchoice = yang_parent_get(ytp); + break; + case Y_CHOICE: + ytchoice = ytp; + break; + default: + goto ok; /* Not choice */ + break; + } + if ((xp = xml_parent(xt)) == NULL) + goto ok; + x = NULL; /* Find a child with same yang spec */ + while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) { + if (x == xt) + continue; + y = xml_spec(x); + if (y == yt) /* eg same list */ + continue; + yp = yang_parent_get(y); + switch (yang_keyword_get(yp)){ + case Y_CASE: + if (yang_parent_get(yp) != ytchoice) /* Not same choice (not releveant) */ + continue; + if (yp == ytcase) /* same choice but different case */ + continue; + break; + case Y_CHOICE: + if (yp != ytcase) /* Not same choice (not relevant) */ + continue; + break; + default: + continue; /* not choice */ + break; + } + if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) + goto done; + goto fail; + } /* while */ + + ok: + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Check if an xml node lacks mandatory children + * @param[in] xt XML node to be validated + * @param[in] yt xt:s yang statement + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + */ +static int +check_mandatory(cxobj *xt, + yang_stmt *yt, + cxobj **xret) + +{ + int retval = -1; + cxobj *x; + yang_stmt *y; + yang_stmt *yc; + yang_stmt *yp; + cvec *cvk = NULL; /* vector of index keys */ + cg_var *cvi; + char *keyname; + + yc = NULL; + while ((yc = yn_each(yt, yc)) != NULL) { + /* Check if a list does not have mandatory key leafs */ + if (yang_keyword_get(yt) == Y_LIST && + yang_keyword_get(yc) == Y_KEY && + yang_config(yt)){ + cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */ + cvi = NULL; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ + if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) + goto done; + goto fail; + } + } + } + if (!yang_mandatory(yc)) + continue; + switch (yang_keyword_get(yc)){ + case Y_CONTAINER: + case Y_ANYDATA: + case Y_ANYXML: + case Y_LEAF: + if (yang_config(yc)==0) + break; + /* Find a child with the mandatory yang */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) != NULL + && y==yc) + break; /* got it */ + } + if (x == NULL){ + if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), "Mandatory variable") < 0) + goto done; + goto fail; + } + break; + case Y_CHOICE: /* More complex because of choice/case structure */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) != NULL && + (yp = yang_choice(y)) != NULL && + yp == yc){ + break; /* leave loop with x set */ + } + } + if (x == NULL){ + /* @see RFC7950: 15.6 Error Message for Data That Violates + * a Mandatory "choice" Statement */ + if (netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) + goto done; + goto fail; + } + break; + default: + break; + } /* switch */ + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! + * @param[out] xret Error XML tree. Free with xml_free after use + */ +static int +check_list_key(cxobj *xt, + yang_stmt *yt, + cxobj **xret) + +{ + int retval = -1; + yang_stmt *yc; + cvec *cvk = NULL; /* vector of index keys */ + cg_var *cvi; + char *keyname; + + yc = NULL; + while ((yc = yn_each(yt, yc)) != NULL) { + /* Check if a list does not have mandatory key leafs */ + if (yang_keyword_get(yt) == Y_LIST && + yang_keyword_get(yc) == Y_KEY && + yang_config(yt)){ + cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */ + cvi = NULL; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ + if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) + goto done; + goto fail; + } + } + } + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! New element last in list, check if already exists if sp return -1 + * @param[in] vec Vector of existing entries (new is last) + * @param[in] i1 The new entry is placed at vec[i1] + * @param[in] vlen Lenght of entry + * @retval 0 OK, entry is unique + * @retval -1 Duplicate detected + * @note This is currently linear complexity. It could be improved by inserting new element sorted and binary search. + */ +static int +check_insert_duplicate(char **vec, + int i1, + int vlen) +{ + int i; + int v; + char *b; + + for (i=0; i 0 && /* 0 means unbounded */ + nr > cv_uint32_get(cv)){ + if (netconf_minmax_elements_xml(xret, x, 1) < 0) + goto done; + goto fail; + } + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Detect unique constraint for duplicates from parent node and minmax + * @param[in] xt XML parent (may have lists w unique constraints as child) + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed (xret set) + * @retval -1 Error + * Assume xt:s children are sorted and yang populated. + * The function does two different things of the children of an XML node: + * (1) Check min/max element constraints + * (2) Check unique constraints + * + * The routine uses a node traversing mechanism as the following example, where + * two lists [x1,..] and [x2,..] are embedded: + * xt: {a, b, [x1, x1, x1], d, e, f, [x2, x2, x2], g} + * The function does this using a single iteration and uses the fact that the + * xml symbols share yang symbols: ie [x1..] has yang y1 and d has yd. + * + * Unique constraints: + * Lists are identified, then check_unique_list is called on each list. + * Example, x has an associated yang list node with list of unique constraints + * y-list->y-unique - "a" + * xt->x -> ab + * x -> bc + * x -> ab + * + * Min-max constraints: + * Find upper and lower bound of existing lists and report violations + * Somewhat tricky to find violation of min-elements of empty + * lists, but this is done by a "gap-detection" mechanism, which detects + * gaps in the xml nodes given the ancestor Yang structure. + * But no gap analysis is done if the yang spec of the top-level xml is unknown + * Example: + * Yang structure:y1, y2, y3, + * XML structure: [x1, x1], [x3, x3] where [x2] list is missing + * @note min-element constraints on empty lists are not detected on top-level. + * Or more specifically, if no yang spec if associated with the top-level + * XML node. This may not be a large problem since it would mean empty configs + * are not allowed. + */ +static int +check_list_unique_minmax(cxobj *xt, + cxobj **xret) +{ + int retval = -1; + cxobj *x = NULL; + yang_stmt *y; + yang_stmt *yt; + yang_stmt *yp = NULL; /* previous in list */ + yang_stmt *ye = NULL; /* yang each list to catch emtpy */ + yang_stmt *ych; /* y:s parent node (if choice that ye can compare to) */ + cxobj *xp = NULL; /* previous in list */ + yang_stmt *yu; /* yang unique */ + int ret; + int nr=0; /* Nr of list elements for min/max check */ + enum rfc_6020 keyw; + + /* RFC 7950 7.7.5: regarding min-max elements check + * The behavior of the constraint depends on the type of the + * leaf-list's or list's closest ancestor node in the schema tree + * that is not a non-presence container (see Section 7.5.1): + * o If no such ancestor exists in the schema tree, the constraint + * is enforced. + * o Otherwise, if this ancestor is a case node, the constraint is + * enforced if any other node from the case exists. + * o Otherwise, it is enforced if the ancestor node exists. + */ + yt = xml_spec(xt); /* If yt == NULL, then no gap-analysis is done */ + /* Traverse all elemenents */ + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) == NULL) + continue; + if ((ych=yang_choice(y)) == NULL) + ych = y; + keyw = yang_keyword_get(y); + if (keyw != Y_LIST && keyw != Y_LEAF_LIST) + continue; + if (yp != NULL){ /* There exists a previous (leaf)list */ + if (y == yp){ /* If same yang as previous x, then skip (eg same list) */ + nr++; + continue; + } + else { + /* Check if the list length violates min/max */ + if ((ret = check_min_max(xp, yp, nr, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + yp = y; /* Restart min/max count */ + xp = x; /* Need a reference to the XML as well */ + nr = 1; + /* Gap analysis: Check if there is any empty list between y and yp + * Note, does not detect empty choice list (too complicated) + */ + if (yt != NULL && ych != ye){ + /* Skip analysis if Yang spec is unknown OR + * if we are still iterating the same Y_CASE w multiple lists + */ + ye = yn_each(yt, ye); + if (ye && ych != ye) + do { + if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){ + /* Check if the list length violates min/max */ + if ((ret = check_min_max(xt, ye, 0, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + ye = yn_each(yt, ye); + } while(ye != NULL && /* to avoid livelock (shouldnt happen) */ + ye != ych); + } + if (keyw != Y_LIST) + continue; + /* Here only lists. test unique constraints */ + yu = NULL; + while ((yu = yn_each(y, yu)) != NULL) { + if (yang_keyword_get(yu) != Y_UNIQUE) + continue; + /* Here is a list w unique constraints identified by: + * its first element x, its yang spec y, its parent xt, and + * a unique yang spec yu, + */ + if ((ret = check_unique_list(x, xt, y, yu, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + /* yp if set, is a list that has been traversed + * This check is made in the loop as well - this is for the last list + */ + if (yp){ + /* Check if the list length violates min/max */ + if ((ret = check_min_max(xp, yp, nr, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + /* Check if there is any empty list between after last non-empty list + * Note, does not detect empty lists within choice/case (too complicated) + */ + if ((ye = yn_each(yt, ye)) != NULL) + do { + if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){ + /* Check if the list length violates min/max */ + if ((ret = check_min_max(xt, ye, 0, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } while((ye = yn_each(yt, ye)) != NULL); + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Validate a single XML node with yang specification for added entry + * 1. Check if mandatory leafs present as subs. + * 2. Check leaf values, eg int ranges and string regexps. + * @param[in] xt XML node to be validated + * @param[out] xret Error XML tree. Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + * @code + * cxobj *x; + * cbuf *xret = NULL; + * if ((ret = xml_yang_validate_add(h, x, &xret)) < 0) + * err; + * if (ret == 0) + * fail; + * @endcode + * @see xml_yang_validate_all + * @see xml_yang_validate_rpc + * @note Should need a variant accepting cxobj **xret + */ +int +xml_yang_validate_add(clicon_handle h, + cxobj *xt, + cxobj **xret) +{ + int retval = -1; + cg_var *cv = NULL; + char *reason = NULL; + yang_stmt *yt; /* yang spec of xt going in */ + char *body; + int ret; + cxobj *x; + enum cv_type cvtype; + + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + if ((ret = check_choice(xt, yt, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + if ((ret = check_mandatory(xt, yt, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* Check leaf values */ + switch (yang_keyword_get(yt)){ + case Y_LEAF: + /* fall thru */ + case Y_LEAF_LIST: + /* validate value against ranges, etc */ + if ((cv = cv_dup(yang_cv_get(yt))) == NULL){ + clicon_err(OE_UNIX, errno, "cv_dup"); + goto done; + } + /* In the union case, value is parsed as generic REST type, + * needs to be reparsed when concrete type is selected + */ + if ((body = xml_body(xt)) == NULL){ + /* We do not allow ints to be empty. Otherwise NULL strings + * are considered as "" */ + cvtype = cv_type_get(cv); + if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){ + if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) + goto done; + goto fail; + } + } + else{ + if (cv_parse1(body, cv, &reason) != 1){ + if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + goto done; + goto fail; + } + } + + if ((ys_cv_validate(h, cv, yt, &reason)) != 1){ + if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + goto done; + goto fail; + } + break; + default: + break; + } + } + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_add(h, x, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; + done: + if (cv) + cv_free(cv); + if (reason) + free(reason); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Some checks done only at edit_config, eg keys in lists + * @param[out] xret Error XML tree. Free with xml_free after use + */ +int +xml_yang_validate_list_key_only(clicon_handle h, + cxobj *xt, + cxobj **xret) +{ + int retval = -1; + yang_stmt *yt; /* yang spec of xt going in */ + int ret; + cxobj *x; + + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + if ((ret = check_list_key(xt, yt, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_list_key_only(h, x, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Validate a single XML node with yang specification for all (not only added) entries + * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. + * @param[in] xt XML node to be validated + * @param[out] xret Error XML tree (if retval=0). Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + * @code + * cxobj *x; + * cbuf *xret = NULL; + * if ((ret = xml_yang_validate_all(h, x, &xret)) < 0) + * err; + * if (ret == 0) + * fail; + * xml_free(xret); + * @endcode + * @see xml_yang_validate_add + * @see xml_yang_validate_rpc + * @note Should need a variant accepting cxobj **xret + */ +int +xml_yang_validate_all(clicon_handle h, + cxobj *xt, + cxobj **xret) +{ + int retval = -1; + yang_stmt *ys; /* yang node */ + yang_stmt *yc; /* yang child */ + yang_stmt *ye; /* yang must error-message */ + char *xpath; + int nr; + int ret; + cxobj *x; + char *namespace = NULL; + cbuf *cb = NULL; + + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + ys=xml_spec(xt); + if (ys==NULL){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (xml2ns(xt, xml_prefix(xt), &namespace) < 0) + goto done; + if (namespace){ + cprintf(cb, "namespace is: %s", namespace); + if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) + goto done; + } + else + if (netconf_unknown_element_xml(xret, "application", xml_name(xt), NULL) < 0) + goto done; + goto fail; + } + if (yang_config(ys) != 0){ + /* Node-specific validation */ + switch (yang_keyword_get(ys)){ + case Y_ANYXML: + case Y_ANYDATA: + goto ok; + break; + case Y_LEAF: + /* fall thru */ + case Y_LEAF_LIST: + /* Special case if leaf is leafref, then first check against + current xml tree + */ + /* Get base type yc */ + if (yang_type_get(ys, NULL, &yc, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + if (strcmp(yang_argument_get(yc), "leafref") == 0){ + if ((ret = validate_leafref(xt, yc, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + else if (strcmp(yang_argument_get(yc), "identityref") == 0){ + if ((ret = validate_identityref(xt, ys, yc, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + break; + default: + break; + } + /* must sub-node RFC 7950 Sec 7.5.3. Can be several. + * XXX. use yang path instead? */ + yc = NULL; + while ((yc = yn_each(ys, yc)) != NULL) { + if (yang_keyword_get(yc) != Y_MUST) + continue; + xpath = yang_argument_get(yc); /* "must" has xpath argument */ + if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) + goto done; + if (!nr){ + ye = yang_find(yc, Y_ERROR_MESSAGE, NULL); + if (netconf_operation_failed_xml(xret, "application", + ye?yang_argument_get(ye):"must xpath validation failed") < 0) + goto done; + goto fail; + } + } + /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ + if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ + xpath = yang_argument_get(yc); /* "when" has xpath argument */ + if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) + goto done; + if (!nr){ + if (netconf_operation_failed_xml(xret, "application", + "when xpath validation failed") < 0) + goto done; + goto fail; + } + } + } + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_all(h, x, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + /* Check unique and min-max after choice test for example*/ + if (yang_config(ys) != 0){ + /* Checks if next level contains any unique list constraints */ + if ((ret = check_list_unique_minmax(xt, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + ok: + retval = 1; + done: + if (cb) + cbuf_free(cb); + return retval; + fail: + retval = 0; + goto done; +} +/*! Translate a single xml node to a cligen variable vector. Note not recursive + * @param[out] xret Error XML tree (if ret == 0). Free with xml_free after use + * @retval 1 Validation OK + * @retval 0 Validation failed (xret set) + * @retval -1 Error + */ +int +xml_yang_validate_all_top(clicon_handle h, + cxobj *xt, + cxobj **xret) +{ + int ret; + cxobj *x; + + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_all(h, x, xret)) < 1) + return ret; + } + if ((ret = check_list_unique_minmax(xt, xret)) < 1) + return ret; + return 1; +} diff --git a/lib/src/clixon_xml_changelog.c b/lib/src/clixon_xml_changelog.c index 4c228a90..89085cd1 100644 --- a/lib/src/clixon_xml_changelog.c +++ b/lib/src/clixon_xml_changelog.c @@ -69,6 +69,7 @@ #include "clixon_netconf_lib.h" #include "clixon_xml_nsctx.h" #include "clixon_xml_map.h" +#include "clixon_validate.h" #include "clixon_xml_changelog.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 4ac40c18..0be80b45 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -32,25 +32,7 @@ ***** END LICENSE BLOCK ***** * - * XML code - * - * file - * +---------+ db2xml_key-> save_db_to_xml-> - * +-------------> | database| <------------+------------------+ - * | +---------+ <-xml2db | <-load_xml_to_db | - * | | | - * | | | - * v v v - * +---------+ <-xml2cvec_key +-----------+ +---------+ - * | cvec | <---------------------> | xml cxobj |<--------->| xmlfile | - * +---------+ cvec2xml-> +-----------+ +---------+ - * cvec2xml_1(yang)-> xml2json->| - * xml2txt-> | - * xml2cli-> v - * +---------+ - * | file | - * +---------+ - * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * Translation / mapping code between formats */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -264,1151 +246,6 @@ xml2cli(FILE *f, return retval; } -/*! Validate xml node of type leafref, ensure the value is one of that path's reference - * @param[in] xt XML leaf node of type leafref - * @param[in] ytype Yang type statement belonging to the XML node - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed - * @retval -1 Error - * From rfc7950 Sec 9.9.2 - * The "path" XPath expression is evaluated in the following context, - * in addition to the definition in Section 6.4.1: - * o If the "path" statement is defined within a typedef, the context - * node is the leaf or leaf-list node in the data tree that - * references the typedef. - * o Otherwise, the context node is the node in the data tree for which - * the "path" statement is defined. - */ -static int -validate_leafref(cxobj *xt, - yang_stmt *ytype, - cxobj **xret) -{ - int retval = -1; - yang_stmt *ypath; - cxobj **xvec = NULL; - cxobj *x; - int i; - size_t xlen = 0; - char *leafrefbody; - char *leafbody; - cvec *nsc = NULL; - - if ((leafrefbody = xml_body(xt)) == NULL) - goto ok; - if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) - goto done; - goto fail; - } - /* XXX see comment above regarding typeref or not */ - if (xml_nsctx_yang(ytype, &nsc) < 0) - goto done; - if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, yang_argument_get(ypath)) < 0) - goto done; - for (i = 0; i < xlen; i++) { - x = xvec[i]; - if ((leafbody = xml_body(x)) == NULL) - continue; - if (strcmp(leafbody, leafrefbody) == 0) - break; - } - if (i==xlen){ - if (netconf_bad_element_xml(xret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0) - goto done; - goto fail; - } - ok: - retval = 1; - done: - if (nsc) - xml_nsctx_free(nsc); - if (xvec) - free(xvec); - return retval; - fail: - retval = 0; - goto done; -} - - -/*! Validate xml node of type identityref, ensure value is a defined identity - * Check if a given node has value derived from base identity. This is - * a run-time check necessary when validating eg netconf. - * Valid values for an identityref are any identities derived from all - * the identityref's base identities. - * Example: - * b0 --> b1 --> b2 (b1 & b2 are derived) - * identityref b2 - * base b0; - * This function does: derived_from(b2, b0); - * @param[in] xt XML leaf node of type identityref - * @param[in] ys Yang spec of leaf - * @param[in] ytype Yang type field of type identityref - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed - * @retval -1 Error - * @see ys_populate_identity where the derived types are set - * @see yang_augment_node - * @see RFC7950 Sec 9.10.2: - */ -static int -validate_identityref(cxobj *xt, - yang_stmt *ys, - yang_stmt *ytype, - cxobj **xret) - -{ - int retval = -1; - char *node = NULL; - char *idref = NULL; - yang_stmt *ybaseref; /* This is the type's base reference */ - yang_stmt *ybaseid; - char *prefix = NULL; - char *id = NULL; - cbuf *cberr = NULL; - cbuf *cb = NULL; - cvec *idrefvec; /* Derived identityref list: (module:id)**/ - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if ((cberr = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - /* Get idref value. Then see if this value is derived from ytype. - */ - if ((node = xml_body(xt)) == NULL){ /* It may not be empty */ - if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) - goto done; - goto fail; - } - if (nodeid_split(node, &prefix, &id) < 0) - goto done; - /* This is the type's base reference */ - if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) - goto done; - goto fail; - } - /* This is the actual base identity */ - if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) - goto done; - goto fail; - } - -#if 0 /* Assume proper namespace, otherwise we assume module prefixes, - * see IDENTITYREF_KLUDGE - */ - { - char *namespace; - yang_stmt *ymod; - yang_stmt *yspec; - - /* Create an idref as : which is the format of the derived - * identityref list associated with the base identities. - */ - /* Get namespace (of idref) from xml */ - if (xml2ns(xt, prefix, &namespace) < 0) - goto done; - yspec = ys_spec(ys); - /* Get module of that namespace */ - if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No module found"); - goto done; - } - cprintf(cb, "%s:%s", yang_argument_get(ymod), id); - } -#else - { - yang_stmt *ymod; - /* idref from prefix:id to module:id */ - if (prefix == NULL) - ymod = ys_module(ys); - else{ /* from prefix to name */ -#if 1 /* IDENTITYREF_KLUDGE */ - ymod = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix); -#endif - } - if (ymod == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - cprintf(cb, "%s:%s", yang_argument_get(ymod), id); - } -#endif - idref = cbuf_get(cb); - /* Here check if node is in the derived node list of the base identity - * The derived node list is a cvec computed XXX - */ - idrefvec = yang_cvec_get(ybaseid); - if (cvec_find(idrefvec, idref) == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - retval = 1; - done: - if (cberr) - cbuf_free(cberr); - if (cb) - cbuf_free(cb); - if (id) - free(id); - if (prefix) - free(prefix); - return retval; - fail: - retval = 0; - goto done; -} - -/*! Given an XML node, return root node - * A root node is an ancestor xr of x with one or both of the following properties - * - its XML parent is NULL parent, - * - its associated yang specification's parent is a yang module. - * @param[in] x XML node - * @param[out] xr XML root - */ -int -xml_yang_root(cxobj *x, - cxobj **xr) -{ - int retval = -1; - cxobj *xp; - yang_stmt *y; - yang_stmt *yp; - - while ((xp = xml_parent(x)) != NULL){ - if ((y = xml_spec(x)) != NULL && - (yp = yang_parent_get(y)) != NULL) - /* Actually, maybe only the Y_MODULE clause is relevant */ - if (yp==NULL || - yang_keyword_get(yp) == Y_MODULE || - yang_keyword_get(yp) == Y_SUBMODULE) - break; /* x is the root */ - x = xp; - } - *xr = x; - retval = 0; - return retval; -} - -/*! Validate an RPC node - * @param[in] h Clicon handle - * @param[in] xrpc XML node to be validated - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed - * @retval -1 Error - * rfc7950 - * 7.14.2 - * If a leaf in the input tree has a "mandatory" statement with the - * value "true", the leaf MUST be present in an RPC invocation. - * - * If a leaf in the input tree has a default value, the server MUST use - * this value in the same cases as those described in Section 7.6.1. In - * these cases, the server MUST operationally behave as if the leaf was - * present in the RPC invocation with the default value as its value. - * - * If a leaf-list in the input tree has one or more default values, the - * server MUST use these values in the same cases as those described in - * Section 7.7.2. In these cases, the server MUST operationally behave - * as if the leaf-list was present in the RPC invocation with the - * default values as its values. - * - * Since the input tree is not part of any datastore, all "config" - * statements for nodes in the input tree are ignored. - * - * If any node has a "when" statement that would evaluate to "false", - * then this node MUST NOT be present in the input tree. - * - * 7.14.4 - * Input parameters are encoded as child XML elements to the rpc node's - * XML element, in the same order as they are defined within the "input" - * statement. - * - * If the RPC operation invocation succeeded and no output parameters - * are returned, the contains a single element defined - * in [RFC6241]. If output parameters are returned, they are encoded as - * child elements to the element defined in [RFC6241], in - * the same order as they are defined within the "output" statement. - * @see xml_yang_validate_all - * @note Should need a variant accepting cxobj **xret - */ -int -xml_yang_validate_rpc(clicon_handle h, - cxobj *xrpc, - cxobj **xret) -{ - int retval = -1; - yang_stmt *yn=NULL; /* rpc name */ - cxobj *xn; /* rpc name */ - - if (strcmp(xml_name(xrpc), "rpc")){ - clicon_err(OE_XML, EINVAL, "Expected RPC"); - goto done; - } - xn = NULL; - /* xn is name of rpc, ie */ - while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { - if ((yn = xml_spec(xn)) == NULL){ - if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) - goto done; - goto fail; - } - if ((retval = xml_yang_validate_all(h, xn, xret)) < 1) - goto done; /* error or validation fail */ - if ((retval = xml_yang_validate_add(h, xn, xret)) < 1) - goto done; /* error or validation fail */ - if (xml_apply0(xn, CX_ELMNT, xml_default, h) < 0) - goto done; - } - // ok: /* pass validation */ - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Check if an xml node is a part of a choice and have >1 siblings - * @param[in] xt XML node to be validated - * @param[in] yt xt:s yang statement - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed (cbret set) - * @retval -1 Error - * Check if xt is part of valid choice - */ -static int -check_choice(cxobj *xt, - yang_stmt *yt, - cxobj **xret) -{ - int retval = -1; - yang_stmt *y; - yang_stmt *ytp; /* yt:s parent */ - yang_stmt *ytcase = NULL; /* yt:s parent case if any */ - yang_stmt *ytchoice = NULL; - yang_stmt *yp; - cxobj *x; - cxobj *xp; - - if ((ytp = yang_parent_get(yt)) == NULL) - goto ok; - /* Return OK if xt is not choice */ - switch (yang_keyword_get(ytp)){ - case Y_CASE: - ytcase = ytp; - ytchoice = yang_parent_get(ytp); - break; - case Y_CHOICE: - ytchoice = ytp; - break; - default: - goto ok; /* Not choice */ - break; - } - if ((xp = xml_parent(xt)) == NULL) - goto ok; - x = NULL; /* Find a child with same yang spec */ - while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) { - if (x == xt) - continue; - y = xml_spec(x); - if (y == yt) /* eg same list */ - continue; - yp = yang_parent_get(y); - switch (yang_keyword_get(yp)){ - case Y_CASE: - if (yang_parent_get(yp) != ytchoice) /* Not same choice (not releveant) */ - continue; - if (yp == ytcase) /* same choice but different case */ - continue; - break; - case Y_CHOICE: - if (yp != ytcase) /* Not same choice (not relevant) */ - continue; - break; - default: - continue; /* not choice */ - break; - } - if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) - goto done; - goto fail; - } /* while */ - - ok: - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Check if an xml node lacks mandatory children - * @param[in] xt XML node to be validated - * @param[in] yt xt:s yang statement - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed (cbret set) - * @retval -1 Error - */ -static int -check_mandatory(cxobj *xt, - yang_stmt *yt, - cxobj **xret) - -{ - int retval = -1; - int i; - cxobj *x; - yang_stmt *y; - yang_stmt *yc; - yang_stmt *yp; - cvec *cvk = NULL; /* vector of index keys */ - cg_var *cvi; - char *keyname; - - for (i=0; iys_len; i++){ - yc = yt->ys_stmt[i]; - /* Check if a list does not have mandatory key leafs */ - if (yt->ys_keyword == Y_LIST && - yc->ys_keyword == Y_KEY && - yang_config(yt)){ - cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */ - cvi = NULL; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ - if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) - goto done; - goto fail; - } - } - } - if (!yang_mandatory(yc)) - continue; - switch (yc->ys_keyword){ - case Y_CONTAINER: - case Y_ANYDATA: - case Y_ANYXML: - case Y_LEAF: - if (yang_config(yc)==0) - break; - /* Find a child with the mandatory yang */ - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((y = xml_spec(x)) != NULL - && y==yc) - break; /* got it */ - } - if (x == NULL){ - if (netconf_missing_element_xml(xret, "application", yc->ys_argument, "Mandatory variable") < 0) - goto done; - goto fail; - } - break; - case Y_CHOICE: /* More complex because of choice/case structure */ - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((y = xml_spec(x)) != NULL && - (yp = yang_choice(y)) != NULL && - yp == yc){ - break; /* leave loop with x set */ - } - } - if (x == NULL){ - /* @see RFC7950: 15.6 Error Message for Data That Violates - * a Mandatory "choice" Statement */ - if (netconf_data_missing_xml(xret, yc->ys_argument, NULL) < 0) - goto done; - goto fail; - } - break; - default: - break; - } /* switch */ - } - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! - * @param[out] xret Error XML tree. Free with xml_free after use - */ -static int -check_list_key(cxobj *xt, - yang_stmt *yt, - cxobj **xret) - -{ - int retval = -1; - int i; - yang_stmt *yc; - cvec *cvk = NULL; /* vector of index keys */ - cg_var *cvi; - char *keyname; - - for (i=0; iys_len; i++){ - yc = yt->ys_stmt[i]; - /* Check if a list does not have mandatory key leafs */ - if (yt->ys_keyword == Y_LIST && - yc->ys_keyword == Y_KEY && - yang_config(yt)){ - cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */ - cvi = NULL; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ - if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) - goto done; - goto fail; - } - } - } - } - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! New element last in list, check if already exists if sp return -1 - * @param[in] vec Vector of existing entries (new is last) - * @param[in] i1 The new entry is placed at vec[i1] - * @param[in] vlen Lenght of entry - * @retval 0 OK, entry is unique - * @retval -1 Duplicate detected - * @note This is currently linear complexity. It could be improved by inserting new element sorted and binary search. - */ -static int -check_insert_duplicate(char **vec, - int i1, - int vlen) -{ - int i; - int v; - char *b; - - for (i=0; i 0 && /* 0 means unbounded */ - nr > cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, x, 1) < 0) - goto done; - goto fail; - } - } - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Detect unique constraint for duplicates from parent node and minmax - * @param[in] xt XML parent (may have lists w unique constraints as child) - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed (xret set) - * @retval -1 Error - * Assume xt:s children are sorted and yang populated. - * The function does two different things of the children of an XML node: - * (1) Check min/max element constraints - * (2) Check unique constraints - * - * The routine uses a node traversing mechanism as the following example, where - * two lists [x1,..] and [x2,..] are embedded: - * xt: {a, b, [x1, x1, x1], d, e, f, [x2, x2, x2], g} - * The function does this using a single iteration and uses the fact that the - * xml symbols share yang symbols: ie [x1..] has yang y1 and d has yd. - * - * Unique constraints: - * Lists are identified, then check_unique_list is called on each list. - * Example, x has an associated yang list node with list of unique constraints - * y-list->y-unique - "a" - * xt->x -> ab - * x -> bc - * x -> ab - * - * Min-max constraints: - * Find upper and lower bound of existing lists and report violations - * Somewhat tricky to find violation of min-elements of empty - * lists, but this is done by a "gap-detection" mechanism, which detects - * gaps in the xml nodes given the ancestor Yang structure. - * But no gap analysis is done if the yang spec of the top-level xml is unknown - * Example: - * Yang structure:y1, y2, y3, - * XML structure: [x1, x1], [x3, x3] where [x2] list is missing - * @note min-element constraints on empty lists are not detected on top-level. - * Or more specifically, if no yang spec if associated with the top-level - * XML node. This may not be a large problem since it would mean empty configs - * are not allowed. - */ -static int -check_list_unique_minmax(cxobj *xt, - cxobj **xret) -{ - int retval = -1; - cxobj *x = NULL; - yang_stmt *y; - yang_stmt *yt; - yang_stmt *yp = NULL; /* previous in list */ - yang_stmt *ye = NULL; /* yang each list to catch emtpy */ - yang_stmt *ych; /* y:s parent node (if choice that ye can compare to) */ - cxobj *xp = NULL; /* previous in list */ - yang_stmt *yu; /* yang unique */ - int ret; - int nr=0; /* Nr of list elements for min/max check */ - enum rfc_6020 keyw; - - /* RFC 7950 7.7.5: regarding min-max elements check - * The behavior of the constraint depends on the type of the - * leaf-list's or list's closest ancestor node in the schema tree - * that is not a non-presence container (see Section 7.5.1): - * o If no such ancestor exists in the schema tree, the constraint - * is enforced. - * o Otherwise, if this ancestor is a case node, the constraint is - * enforced if any other node from the case exists. - * o Otherwise, it is enforced if the ancestor node exists. - */ - yt = xml_spec(xt); /* If yt == NULL, then no gap-analysis is done */ - /* Traverse all elemenents */ - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((y = xml_spec(x)) == NULL) - continue; - if ((ych=yang_choice(y)) == NULL) - ych = y; - keyw = yang_keyword_get(y); - if (keyw != Y_LIST && keyw != Y_LEAF_LIST) - continue; - if (yp != NULL){ /* There exists a previous (leaf)list */ - if (y == yp){ /* If same yang as previous x, then skip (eg same list) */ - nr++; - continue; - } - else { - /* Check if the list length violates min/max */ - if ((ret = check_min_max(xp, yp, nr, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - } - yp = y; /* Restart min/max count */ - xp = x; /* Need a reference to the XML as well */ - nr = 1; - /* Gap analysis: Check if there is any empty list between y and yp - * Note, does not detect empty choice list (too complicated) - */ - if (yt != NULL && ych != ye){ - /* Skip analysis if Yang spec is unknown OR - * if we are still iterating the same Y_CASE w multiple lists - */ - ye = yn_each(yt, ye); - if (ye && ych != ye) - do { - if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){ - /* Check if the list length violates min/max */ - if ((ret = check_min_max(xt, ye, 0, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - ye = yn_each(yt, ye); - } while(ye != NULL && /* to avoid livelock (shouldnt happen) */ - ye != ych); - } - if (keyw != Y_LIST) - continue; - /* Here only lists. test unique constraints */ - yu = NULL; - while ((yu = yn_each(y, yu)) != NULL) { - if (yang_keyword_get(yu) != Y_UNIQUE) - continue; - /* Here is a list w unique constraints identified by: - * its first element x, its yang spec y, its parent xt, and - * a unique yang spec yu, - */ - if ((ret = check_unique_list(x, xt, y, yu, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - } - /* yp if set, is a list that has been traversed - * This check is made in the loop as well - this is for the last list - */ - if (yp){ - /* Check if the list length violates min/max */ - if ((ret = check_min_max(xp, yp, nr, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - /* Check if there is any empty list between after last non-empty list - * Note, does not detect empty lists within choice/case (too complicated) - */ - if ((ye = yn_each(yt, ye)) != NULL) - do { - if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){ - /* Check if the list length violates min/max */ - if ((ret = check_min_max(xt, ye, 0, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - } while((ye = yn_each(yt, ye)) != NULL); - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Validate a single XML node with yang specification for added entry - * 1. Check if mandatory leafs present as subs. - * 2. Check leaf values, eg int ranges and string regexps. - * @param[in] xt XML node to be validated - * @param[out] xret Error XML tree. Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed (cbret set) - * @retval -1 Error - * @code - * cxobj *x; - * cbuf *xret = NULL; - * if ((ret = xml_yang_validate_add(h, x, &xret)) < 0) - * err; - * if (ret == 0) - * fail; - * @endcode - * @see xml_yang_validate_all - * @see xml_yang_validate_rpc - * @note Should need a variant accepting cxobj **xret - */ -int -xml_yang_validate_add(clicon_handle h, - cxobj *xt, - cxobj **xret) -{ - int retval = -1; - cg_var *cv = NULL; - char *reason = NULL; - yang_stmt *yt; /* yang spec of xt going in */ - char *body; - int ret; - cxobj *x; - enum cv_type cvtype; - - /* if not given by argument (overide) use default link - and !Node has a config sub-statement and it is false */ - if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ - if ((ret = check_choice(xt, yt, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - if ((ret = check_mandatory(xt, yt, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - /* Check leaf values */ - switch (yt->ys_keyword){ - case Y_LEAF: - /* fall thru */ - case Y_LEAF_LIST: - /* validate value against ranges, etc */ - if ((cv = cv_dup(yang_cv_get(yt))) == NULL){ - clicon_err(OE_UNIX, errno, "cv_dup"); - goto done; - } - /* In the union case, value is parsed as generic REST type, - * needs to be reparsed when concrete type is selected - */ - if ((body = xml_body(xt)) == NULL){ - /* We do not allow ints to be empty. Otherwise NULL strings - * are considered as "" */ - cvtype = cv_type_get(cv); - if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){ - if (netconf_bad_element_xml(xret, "application", yt->ys_argument, "Invalid NULL value") < 0) - goto done; - goto fail; - } - } - else{ - if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element_xml(xret, "application", yt->ys_argument, reason) < 0) - goto done; - goto fail; - } - } - - if ((ys_cv_validate(h, cv, yt, &reason)) != 1){ - if (netconf_bad_element_xml(xret, "application", yt->ys_argument, reason) < 0) - goto done; - goto fail; - } - break; - default: - break; - } - } - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((ret = xml_yang_validate_add(h, x, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - retval = 1; - done: - if (cv) - cv_free(cv); - if (reason) - free(reason); - return retval; - fail: - retval = 0; - goto done; -} - -/*! Some checks done only at edit_config, eg keys in lists - * @param[out] xret Error XML tree. Free with xml_free after use - */ -int -xml_yang_validate_list_key_only(clicon_handle h, - cxobj *xt, - cxobj **xret) -{ - int retval = -1; - yang_stmt *yt; /* yang spec of xt going in */ - int ret; - cxobj *x; - - /* if not given by argument (overide) use default link - and !Node has a config sub-statement and it is false */ - if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ - if ((ret = check_list_key(xt, yt, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((ret = xml_yang_validate_list_key_only(h, x, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Validate a single XML node with yang specification for all (not only added) entries - * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. - * @param[in] xt XML node to be validated - * @param[out] xret Error XML tree (if retval=0). Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed (cbret set) - * @retval -1 Error - * @code - * cxobj *x; - * cbuf *xret = NULL; - * if ((ret = xml_yang_validate_all(h, x, &xret)) < 0) - * err; - * if (ret == 0) - * fail; - * xml_free(xret); - * @endcode - * @see xml_yang_validate_add - * @see xml_yang_validate_rpc - * @note Should need a variant accepting cxobj **xret - */ -int -xml_yang_validate_all(clicon_handle h, - cxobj *xt, - cxobj **xret) -{ - int retval = -1; - yang_stmt *ys; /* yang node */ - yang_stmt *yc; /* yang child */ - yang_stmt *ye; /* yang must error-message */ - char *xpath; - int nr; - int ret; - cxobj *x; - char *namespace = NULL; - cbuf *cb = NULL; - - /* if not given by argument (overide) use default link - and !Node has a config sub-statement and it is false */ - ys=xml_spec(xt); - if (ys==NULL){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if (xml2ns(xt, xml_prefix(xt), &namespace) < 0) - goto done; - if (namespace){ - cprintf(cb, "namespace is: %s", namespace); - if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) - goto done; - } - else - if (netconf_unknown_element_xml(xret, "application", xml_name(xt), NULL) < 0) - goto done; - goto fail; - } - if (yang_config(ys) != 0){ - /* Node-specific validation */ - switch (yang_keyword_get(ys)){ - case Y_ANYXML: - case Y_ANYDATA: - goto ok; - break; - case Y_LEAF: - /* fall thru */ - case Y_LEAF_LIST: - /* Special case if leaf is leafref, then first check against - current xml tree - */ - /* Get base type yc */ - if (yang_type_get(ys, NULL, &yc, NULL, NULL, NULL, NULL, NULL) < 0) - goto done; - if (strcmp(yang_argument_get(yc), "leafref") == 0){ - if ((ret = validate_leafref(xt, yc, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - else if (strcmp(yang_argument_get(yc), "identityref") == 0){ - if ((ret = validate_identityref(xt, ys, yc, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - break; - default: - break; - } - /* must sub-node RFC 7950 Sec 7.5.3. Can be several. - * XXX. use yang path instead? */ - yc = NULL; - while ((yc = yn_each(ys, yc)) != NULL) { - if (yc->ys_keyword != Y_MUST) - continue; - xpath = yc->ys_argument; /* "must" has xpath argument */ - if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) - goto done; - if (!nr){ - ye = yang_find(yc, Y_ERROR_MESSAGE, NULL); - if (netconf_operation_failed_xml(xret, "application", - ye?ye->ys_argument:"must xpath validation failed") < 0) - goto done; - goto fail; - } - } - /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ - if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ - xpath = yc->ys_argument; /* "when" has xpath argument */ - if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) - goto done; - if (!nr){ - if (netconf_operation_failed_xml(xret, "application", - "when xpath validation failed") < 0) - goto done; - goto fail; - } - } - } - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((ret = xml_yang_validate_all(h, x, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - /* Check unique and min-max after choice test for example*/ - if (yang_config(ys) != 0){ - /* Checks if next level contains any unique list constraints */ - if ((ret = check_list_unique_minmax(xt, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - ok: - retval = 1; - done: - if (cb) - cbuf_free(cb); - return retval; - fail: - retval = 0; - goto done; -} - -/*! Translate a single xml node to a cligen variable vector. Note not recursive - * @param[out] xret Error XML tree (if ret == 0). Free with xml_free after use - * @retval 1 Validation OK - * @retval 0 Validation failed (xret set) - * @retval -1 Error - */ -int -xml_yang_validate_all_top(clicon_handle h, - cxobj *xt, - cxobj **xret) -{ - int ret; - cxobj *x; - - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((ret = xml_yang_validate_all(h, x, xret)) < 1) - return ret; - } - if ((ret = check_list_unique_minmax(xt, xret)) < 1) - return ret; - return 1; -} - /*! Translate a single xml node to a cligen variable vector. Note not recursive * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt @@ -1747,306 +584,6 @@ xml_diff(yang_stmt *yspec, return retval; } -/*! Construct an xml key format from yang statement using wildcards for keys - * Recursively construct it to the top. - * Example: - * yang: container a -> list b -> key c -> leaf d - * xpath: /modname:a/b/%s/d - * @param[in] ys Yang statement - * @param[in] inclkey If set include key leaf (eg last leaf d in ex) - * @param[out] cb api_path_fmt, - * @retval 0 OK - * @retval -1 Error - * @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression" - */ -static int -yang2api_path_fmt_1(yang_stmt *ys, - int inclkey, - cbuf *cb) -{ - yang_stmt *yp; /* parent */ - yang_stmt *ymod; - int i; - cvec *cvk = NULL; /* vector of index keys */ - int retval = -1; - - if ((yp = yang_parent_get(ys)) == NULL){ - clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys)); - goto done; - } - if (yp != NULL && /* XXX rm */ - yang_keyword_get(yp) != Y_MODULE && - yang_keyword_get(yp) != Y_SUBMODULE){ - - if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ - goto done; - if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){ -#if 0 - /* In some cases, such as cli_show_auto, a trailing '/' should - * NOT be present if ys is a key in a list. - * But in other cases (I think most), the / should be there, - * so a patch is added in cli_show_auto instead. - */ - if (yang_keyword_get(ys) == Y_LEAF && yp && - yang_keyword_get(yp) == Y_LIST && - yang_key_match(yp, ys->ys_argument) == 1) - ; - else -#endif - cprintf(cb, "/"); - } - /* If parent namespace/module is different from child -> add child prefix */ - if (ys_real_module(yp) != (ymod = ys_real_module(ys))) - cprintf(cb, "%s:", yang_argument_get(ymod)); - } - else /* top symbol - mark with name prefix */ - cprintf(cb, "/%s:", yang_argument_get(yp)); - - if (inclkey){ - if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE) - cprintf(cb, "%s", yang_argument_get(ys)); - } - else{ - if (yang_keyword_get(ys) == Y_LEAF && yp && - yang_keyword_get(yp) == Y_LIST){ - if (yang_key_match(yp, yang_argument_get(ys)) == 0) - cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */ - } - else - if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE) - cprintf(cb, "%s", yang_argument_get(ys)); - } - - switch (yang_keyword_get(ys)){ - case Y_LIST: - cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */ - if (cvec_len(cvk)) - cprintf(cb, "="); - /* Iterate over individual keys */ - for (i=0; i list b -> key c -> leaf d - * api_path: /a/b=%s/d - * @param[in] ys Yang statement - * @param[in] inclkey If set include key leaf (eg last leaf d in ex) - * @param[out] api_path_fmt XML api path. Needs to be freed after use. - * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 - */ -int -yang2api_path_fmt(yang_stmt *ys, - int inclkey, - char **api_path_fmt) -{ - int retval = -1; - cbuf *cb = NULL; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if (yang2api_path_fmt_1(ys, inclkey, cb) < 0) - goto done; - if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - -/*! Transform an xml key format and a vector of values to an XML key - * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() - * Example: - * xmlkeyfmt: /interfaces/interface=%s/ipv4/address=%s - * cvv: 0 : set interfaces interface e ipv4 address 1.2.3.4 - * 1 : name = "e" - * 2 : ip = "1.2.3.4" - * api_path: /interfaces/interface=e/ipv4/address=1.2.3.4 - * @param[in] api_path_fmt XML key format, eg /aaa/%s/name - * @param[in] cvv cligen variable vector, one for every wildchar in - * api_path_fmt - * @param[out] api_path api_path, eg /aaa/17. Free after use - * @note first and last elements of cvv are not used,.. - * @see api_path_fmt2xpath - * @example - * api_path_fmt: /interfaces/interface=%s/name - * cvv: - - * api_path: /interfaces/interface/name - * @example - * api_path_fmt: /interfaces/interface=%s/name - * cvv: e0 - * api_path: /interfaces/interface=e0/name - * @example - * api_path_fmt: /subif-entry=%s,%s/subid - * cvv: foo - * api_path: /subif-entry=foo/subid - * - * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 - */ -int -api_path_fmt2api_path(char *api_path_fmt, - cvec *cvv, - char **api_path) -{ - int retval = -1; - char c; - int esc=0; - cbuf *cb = NULL; - int i; - int j; - char *str; - char *strenc=NULL; - cg_var *cv; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - j = 1; /* j==0 is cli string */ - for (i=0; i get module + change namespace */ - if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){ - cprintf(cberr, "No such yang module: %s", prefix); - if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - namespace = yang_find_mynamespace(ymod); /* change namespace */ - } - if (i == offset && ymod) /* root */ - y = yang_find_datanode(ymod, name); - else - y = yang_find_datanode(y, name); - if (y == NULL){ - if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0) - goto done; - goto fail; - } - - /* Get XML/xpath prefix given namespace. - * note different from api-path prefix - */ - if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){ - xprefix = yang_find_myprefix(y); - clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix); - /* not found, add it to nsc */ - if (xml_nsctx_add(nsc, xprefix, namespace) < 0) - goto done; - } - /* Check if has value, means '=' */ - if (cv2str(cv, NULL, 0) > 0){ - if ((val = cv2str_dup(cv)) == NULL) - goto done; - switch (yang_keyword_get(y)){ - case Y_LIST: - /* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3) - * Note that vnr can be < length of cvk, due to empty or unset values - */ - if (valvec){ /* loop, valvec may have been used before */ - free(valvec); - valvec = NULL; - } - if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL) - goto done; - cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ - cvi = NULL; - /* Iterate over individual yang keys */ - cprintf(xpath, "/"); - if (xprefix) - cprintf(xpath, "%s:", xprefix); - cprintf(xpath, "%s", name); - vi = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL && viys_keyword == Y_SPEC){ /* top-node */ - if (prefix == NULL){ - cprintf(cberr, "api-path element '%s', expected prefix:name", nodeid); - if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){ - cprintf(cberr, "No such yang module prefix"); - if (netconf_unknown_element_xml(xerr, "application", prefix, cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - namespace = yang_find_mynamespace(ymod); - y0 = ymod; - } - y = (nodeclass==YC_SCHEMANODE)? - yang_find_schemanode(y0, name): - yang_find_datanode(y0, name); - if (y == NULL){ - if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0) - goto done; - goto fail; - } - if (prefix && namespace == NULL){ - if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){ - cprintf(cberr, "api-path element prefix: '%s', no such yang module", prefix); - if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - namespace = yang_find_mynamespace(ymod); - } - switch (y->ys_keyword){ - case Y_LEAF_LIST: - if (0 && restval==NULL){ - clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); - goto done; - } - if ((x = xml_new(y->ys_argument, x0, y)) == NULL) - goto done; - xml_type_set(x, CX_ELMNT); - if ((xb = xml_new("body", x, NULL)) == NULL) - goto done; - xml_type_set(xb, CX_BODY); - if (restval && xml_value_set(xb, restval) < 0) - goto done; - break; - case Y_LIST: - cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ - if (valvec){ /* loop, valvec may have been used before */ - free(valvec); - valvec = NULL; - } - if (restval==NULL){ - if (strict){ - cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid); - if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - } - else{ - /* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3) - * Note that vnr can be < length of cvk, due to empty or unset values - */ - if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) - goto done; - if ((nvalvec != cvec_len(cvk)) && strict){ - cprintf(cberr, "List key %s length mismatch", name); - if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - } - cvi = NULL; - /* create list object */ - if ((x = xml_new(name, x0, y)) == NULL) - goto done; - xml_type_set(x, CX_ELMNT); - vi = 0; - /* Create keys */ - while ((cvi = cvec_each(cvk, cvi)) != NULL){ - keyname = cv_string_get(cvi); - if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){ - cprintf(cberr, "List statement \"%s\" has no key leaf \"%s\"", - yang_argument_get(y), keyname); - if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - if ((xn = xml_new(keyname, x, ykey)) == NULL) - goto done; - xml_type_set(xn, CX_ELMNT); - if ((xb = xml_new("body", xn, NULL)) == NULL) - goto done; - xml_type_set(xb, CX_BODY); - if (vi++ < nvalvec){ - if (xml_value_set(xb, valvec[vi-1]) < 0) - goto done; - } - } - break; - default: /* eg Y_CONTAINER, Y_LEAF */ - if ((x = xml_find_type(x0, NULL, name, CX_ELMNT)) == NULL){ /* eg key of list */ - if ((x = xml_new(name, x0, y)) == NULL) - goto done; - xml_type_set(x, CX_ELMNT); - } - break; - } - if (x && namespace){ - if (xmlns_set(x, NULL, namespace) < 0) - goto done; - } - if ((retval = api_path2xml_vec(vec+1, nvec-1, - x, y, - nodeclass, strict, - xpathp, ypathp, xerr)) < 1) - goto done; - ok: - retval = 1; /* OK */ - done: - clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - if (cberr) - cbuf_free(cberr); - if (prefix) - free(prefix); - if (name) - free(name); - if (restval) - free(restval); - if (valvec) - free(valvec); - return retval; - fail: - retval = 0; /* invalid api-path or XML */ - goto done; -} - -/*! Create xml tree from api-path - * @param[in] api_path (Absolute) API-path as defined in RFC 8040 - * @param[in] yspec Yang spec - * @param[in,out] xtop Incoming XML tree - * @param[in] nodeclass Set to schema nodes, data nodes, etc - * @param[out] xbotp Resulting xml tree (end of xpath) - * @param[out] ybotp Yang spec matching xbotp - * @param[out] xerr Netconf error message (if retval=0) - * @retval 1 OK - * @retval 0 Invalid api_path or associated XML, netconf error - * @retval -1 Fatal error, clicon_err called - * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml - * @example - * api_path: /subif-entry=foo/subid - * xtop[in] - * xtop[out]: - * foo> - * - * xbotp: - * ybotp: Y_LEAF subid - * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 - * @see api_path2xpath For api-path to xml xpath translation - */ -int -api_path2xml(char *api_path, - yang_stmt *yspec, - cxobj *xtop, - yang_class nodeclass, - int strict, - cxobj **xbotp, - yang_stmt **ybotp, - cxobj **xerr) -{ - int retval = -1; - char **vec = NULL; - int nvec; - cxobj *xroot; - cbuf *cberr = NULL; - - clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); - if ((cberr = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - if (*api_path!='/'){ - cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path); - if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) - goto done; - /* Remove trailing '/'. Like in /a/ -> /a */ - if (nvec > 1 && !strlen(vec[nvec-1])) - nvec--; - if (nvec < 1){ - cprintf(cberr, "Malformed api-path: %s: too short)", api_path); - if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - nvec--; /* NULL-terminated */ - if ((retval = api_path2xml_vec(vec+1, nvec, - xtop, yspec, nodeclass, strict, - xbotp, ybotp, xerr)) < 1) - goto done; - xml_yang_root(*xbotp, &xroot); - if (xmlns_assign(xroot) < 0) - goto done; - // ok: - retval = 1; - done: - if (cberr) - cbuf_free(cberr); - if (vec) - free(vec); - return retval; - fail: - retval = 0; - goto done; -} /*! Given an XML node, build an xpath to root, internal function * @retval 0 OK @@ -3124,93 +1098,6 @@ xml2xpath(cxobj *x, return retval; } -/*! Construct an api_path from an XML node (single level not recursive) - * @param[in] x XML node (need to be yang populated) - * @param[out] cb api_path, must be initialized - * @retval 0 OK - * @retval -1 Error - * @see yang2api_path_fmt - * @see xml2xpath - */ -int -xml2api_path_1(cxobj *x, - cbuf *cb) -{ - int retval = -1; - yang_stmt *y = NULL; - cvec *cvk = NULL; /* vector of index keys */ - cg_var *cvi; - enum rfc_6020 keyword; - int i; - char *keyname; - cxobj *xkey; - cxobj *xb; - char *b; - char *enc; - yang_stmt *ymod; - cxobj *xp; - - if ((y = xml_spec(x)) == NULL){ - cprintf(cb, "/%s", xml_name(x)); - goto ok; - } - ymod = ys_module(y); - xp = xml_parent(x); - if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */ - cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x)); - else - cprintf(cb, "/%s", xml_name(x)); - keyword = yang_keyword_get(y); - switch (keyword){ - case Y_LEAF_LIST: - b = xml_body(x); - enc = NULL; - if (uri_percent_encode(&enc, "%s", b) < 0) - goto done; - cprintf(cb, "=%s", enc?enc:""); - if (enc) - free(enc); - break; - case Y_LIST: - cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ - if (cvec_len(cvk)) - cprintf(cb, "="); - /* Iterate over individual keys */ - cvi = NULL; - i = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if ((xkey = xml_find(x, keyname)) == NULL) - goto done; /* No key in xml */ - if ((xb = xml_find(x, keyname)) == NULL) - goto done; - if (i++) - cprintf(cb, ","); - b = xml_body(xb); - enc = NULL; - if (uri_percent_encode(&enc, "%s", b) < 0) - goto done; - cprintf(cb, "%s", enc?enc:""); - if (enc) - free(enc); - } - break; - default: - break; - } -#if 0 - { /* Just for testing */ - cxobj *xc; - if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL) - if (xml2api_path_1(xc, cb) < 0) - goto done; - } -#endif - ok: - retval = 0; - done: - return retval; -} /*! Check if the module tree x is in is assigned right XML namespace, assign if not * @param[in] x XML node diff --git a/lib/src/clixon_xpath_optimize.c b/lib/src/clixon_xpath_optimize.c index cdfceafd..e0ede3ca 100644 --- a/lib/src/clixon_xpath_optimize.c +++ b/lib/src/clixon_xpath_optimize.c @@ -64,7 +64,6 @@ #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_sort.h" -#include "clixon_xml_nsctx.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_xpath_optimize.h" diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index a1410dda..ffa6cc53 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -39,7 +39,6 @@ #ifndef _CLIXON_YANG_INTERNAL_H_ #define _CLIXON_YANG_INTERNAL_H_ - /* * Clixon-specific cligen variable (cv) flags * CLIgen flags defined are in the range 0x01 -0x0f @@ -48,7 +47,6 @@ */ #define V_UNSET 0x10 /* Used by XML code to denote a value is not default */ - #define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */ /*! Yang type cache. Yang type statements can cache all typedef info here diff --git a/test/test_cli_apipath.sh b/test/test_cli_apipath.sh new file mode 100755 index 00000000..2f36c310 --- /dev/null +++ b/test/test_cli_apipath.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# Tests for manually adding keys to cli set/merge/del callbacks including error handling +# Note only completed commands, not interactive expand/completion + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +# include err() and new() functions and creates $dir + +cfg=$dir/conf_yang.xml +fyang=$dir/$APPNAME.yang +clidir=$dir/cli +if [ -d $clidir ]; then + rm -rf $clidir/* +else + mkdir $clidir +fi + +# Use yang in example + +cat < $cfg + + $cfg + /usr/local/share/clixon + $dir + $fyang + $clidir + /usr/local/lib/$APPNAME/cli + $APPNAME + + ALL + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + +EOF + +cat < $fyang +module $APPNAME { + namespace "urn:example:m"; + prefix m; + container x { + list m1 { + key "a b"; + leaf a { + type string; + } + leaf b { + type string; + } + leaf c { + type string; + } + } + } +} +EOF + +cat < $clidir/ex.cli +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H> "; +CLICON_PLUGIN="example_cli"; + +# Positive +set x,cli_merge("/example:x");{ + a b ,cli_merge("/example:x/m1=%s,%s/");{ + c ,cli_merge("/example:x/m1=%s,%s/c"); + } +} +# Negative +err x,cli_set("/example2:x");{ + a ,cli_merge("/example:x/m1=%s"); +} + +show config @datamodel, cli_show_auto("candidate", "cli"); + +EOF + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + + new "waiting" + wait_backend +fi + +# Positive tests +new "set x" +expectfn "$clixon_cli -1 -f $cfg set x" 0 "" + +new "set x a b" +expectfn "$clixon_cli -1 -f $cfg set x a 99 b 22" 0 "" + +new "set x a b c" +expectfn "$clixon_cli -1 -f $cfg set x a 22 b 33 c 55" 0 "" + +new "show conf x" +expectfn "$clixon_cli -1 -f $cfg show conf x" 0 "x m1 a 22 b 33" + +# Negative tests +new "err x" +expectfn "$clixon_cli -1 -f $cfg err x" 255 "Config error: api-path syntax error \"/example2:x\": application unknown-element No such yang module prefix example2: Invalid argument" + +new "err x a" +expectfn "$clixon_cli -1 -f $cfg err x a 99" 255 "Config error: api-path syntax error \"/example:x/m1=%s\": rpc malformed-message List key m1 length mismatch : Invalid argument" + +new "Kill backend" +# Check if premature kill +pid=$(pgrep -u root -f clixon_backend) +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +stop_backend -f $cfg + +rm -rf $dir