From 2eb9c6cda1bce682ab249d8345b17085fc6cb604 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 1 Sep 2022 10:51:06 +0200 Subject: [PATCH] Refactored YANG min/max validation code, created new clixon_validate_minmax.[ch] Added new recursive minmax check for non-presence containers This makes validation checks stricter, including check of incoming RPCs Renamed xml_yang_check_list_unique_minmax() to xml_yang_minmax_recurse() Fixed again: [YANG min-elements within non-presence container does not work](https://github.com/clicon/clixon/issues/355) --- CHANGELOG.md | 10 + apps/backend/backend_client.c | 2 +- lib/clixon/clixon.h.in | 1 + lib/clixon/clixon_validate.h | 3 +- lib/clixon/clixon_validate_minmax.h | 48 ++ lib/clixon/clixon_xml.h | 1 - lib/src/Makefile.in | 2 +- lib/src/clixon_datastore_read.c | 5 +- lib/src/clixon_validate.c | 585 +-------------------- lib/src/clixon_validate_minmax.c | 764 ++++++++++++++++++++++++++++ lib/src/clixon_xml.c | 4 +- test/lib.sh | 1 + test/test_choice.sh | 8 +- test/test_db.sh | 2 +- test/test_helloworld.sh | 2 +- test/test_minmax.sh | 27 +- test/test_unique.sh | 16 +- 17 files changed, 867 insertions(+), 614 deletions(-) create mode 100644 lib/clixon/clixon_validate_minmax.h create mode 100644 lib/src/clixon_validate_minmax.c diff --git a/CHANGELOG.md b/CHANGELOG.md index c44fe00e..2f86dacf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,16 @@ Expected: September 2022 * idle-timeout for periodic call-homes. * An example util client is `clixon_restconf_callhome_client.c` used in test cases +### API changes on existing protocol/config features + +Users may have to change how they access the system + +* Constraints on number of elements have been made stricter (ie unique, min/max-elements) + * Usecases that passed previously may now return error + * This includes: + * Check of incoming RPCs + * Check of non-presence containers + ### Corrected Bugs * Fixed: [YANG ordering fails for nested choice and action](https://github.com/clicon/clixon/issues/356) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 652a6e88..b5fdabca 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -393,7 +393,7 @@ from_client_edit_config(clicon_handle h, } /* Limited validation of incoming payload */ - if ((ret = xml_yang_check_list_unique_minmax(xc, &xret)) < 0) + if ((ret = xml_yang_minmax_recurse(xc, &xret)) < 0) goto done; /* xmldb_put (difflist handling) requires list keys */ if (ret==1 && (ret = xml_yang_validate_list_key_only(xc, &xret)) < 0) diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index f395927c..eb5966bb 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -96,6 +96,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_validate.h b/lib/clixon/clixon_validate.h index aedc378d..3ef67280 100644 --- a/lib/clixon/clixon_validate.h +++ b/lib/clixon/clixon_validate.h @@ -34,7 +34,7 @@ ***** END LICENSE BLOCK ***** * - * XML code + * Check YANG validation */ #ifndef _CLIXON_VALIDATE_H_ @@ -45,7 +45,6 @@ */ int xml_yang_validate_rpc(clicon_handle h, cxobj *xrpc, cxobj **xret); int xml_yang_validate_rpc_reply(clicon_handle h, cxobj *xrpc, cxobj **xret); -int xml_yang_check_list_unique_minmax(cxobj *xt, cxobj **xret); int xml_yang_validate_add(clicon_handle h, cxobj *xt, cxobj **xret); int xml_yang_validate_list_key_only(cxobj *xt, cxobj **xret); int xml_yang_validate_all(clicon_handle h, cxobj *xt, cxobj **xret); diff --git a/lib/clixon/clixon_validate_minmax.h b/lib/clixon/clixon_validate_minmax.h new file mode 100644 index 00000000..86283728 --- /dev/null +++ b/lib/clixon/clixon_validate_minmax.h @@ -0,0 +1,48 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC (Netgate) + + 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 ***** + + * + * Check YANG validation for min/max-elements and unique + */ + +#ifndef _CLIXON_VALIDATE_MINMAX_H_ +#define _CLIXON_VALIDATE_MINMAX_H_ + +/* + * Prototypes + */ +int xml_yang_minmax_recurse(cxobj *xt, cxobj **xret); + +#endif /* _CLIXON_VALIDATE_MINMAX_H_ */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index d325eea1..09dcf776 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -218,7 +218,6 @@ cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc); int xml_child_order(cxobj *xn, cxobj *xc); cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); - int xml_child_insert_pos(cxobj *x, cxobj *xc, int i); int xml_childvec_set(cxobj *x, int len); cxobj **xml_childvec_get(cxobj *x); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index fd13b974..0a847475 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -85,7 +85,7 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_yang.c clixon_yang_type.c clixon_yang_module.c \ clixon_yang_parse_lib.c clixon_yang_sub_parse.c \ clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ - clixon_path.c clixon_validate.c \ + clixon_path.c clixon_validate.c clixon_validate_minmax.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_function.c \ diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 72d8bbde..2c663c91 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -1165,7 +1165,10 @@ xmldb_get0_clear(clicon_handle h, /* Remove global defaults and empty non-presence containers */ if (xml_defaults_nopresence(x, 1) < 0) goto done; - /* clear flags: mark and change */ + /* Clear XML tree of defaults */ + if (xml_tree_prune_flagged(x, XML_FLAG_TRANSIENT, 1) < 0) + goto done; + /* clear mark and change */ xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_ADD|XML_FLAG_CHANGE)); ok: diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index f8403583..12ca5879 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -34,9 +34,7 @@ ***** END LICENSE BLOCK ***** * - * XML code - * - * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * Check YANG validation */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -79,6 +77,7 @@ #include "clixon_yang_type.h" #include "clixon_xml_map.h" #include "clixon_xml_bind.h" +#include "clixon_validate_minmax.h" #include "clixon_validate.h" /*! Validate xml node of type leafref, ensure the value is one of that path's reference @@ -871,582 +870,6 @@ check_mandatory(cxobj *xt, 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 - * @param[in] sorted Sorted by system, ie sorted by key, otherwise no assumption - * @retval 0 OK, entry is unique - * @retval -1 Duplicate detected - * @note This is currently quadratic complexity. It could be improved by inserting new element sorted and binary search. - * @retval 1 Validation OK - * @retval 0 Validation failed (cbret set) - * @retval -1 Error - */ -static int -unique_search_xpath(cxobj *x, - char *xpath, - cvec *nsc, - char ***svec, - size_t *slen) -{ - int retval = -1; - cxobj **xvec = NULL; - size_t xveclen; - int i; - int s; - cxobj *xi; - char *bi; - - /* Collect tuples */ - if (xpath_vec(x, nsc, "%s", &xvec, &xveclen, xpath) < 0) - goto done; - for (i=0; i 1 */ - while ((cvi = cvec_each(cvk, cvi)) != NULL){ - /* RFC7950: Sec 7.8.3.1: entries that do not have value for all - * referenced leafs are not taken into account */ - str = cv_string_get(cvi); - if (index(str, '/') != NULL){ - clicon_err(OE_YANG, 0, "Multiple descendant nodes not allowed (w /)"); - goto done; - } - if ((xi = xml_find(x, str)) == NULL) - break; - if ((bi = xml_body(xi)) == NULL) - break; - vec[i*clen + v++] = bi; - } - if (cvi==NULL){ - /* Last element (i) is newly inserted, see if it is already there */ - if (check_insert_duplicate(vec, i, clen, sorted) < 0){ - if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) - goto done; - goto fail; - } - } - x = xml_child_each(xt, x, CX_ELMNT); - i++; - } while (x && y == xml_spec(x)); /* stop if list ends, others may follow */ - ok: - /* It would be possible to cache vec here as an optimization */ - retval = 1; - done: - if (vec) - free(vec); - return retval; - fail: - retval = 0; - goto done; -} - -/*! Given a list with unique constraint, detect duplicates - * @param[in] x The first element in the list (on return the last) - * @param[in] xt The parent of x (a list) - * @param[in] y Its yang spec (Y_LIST) - * @param[in] yu A yang unique (Y_UNIQUE) for unique schema node ids or (Y_LIST) for list keys - * @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 - * Discussion: the RFC 7950 Sec 7.8.3: "constraints on valid list entries" - * The arguments are "descendant schema node identifiers". A direct interpretation is that - * this is for "direct" descendants, but it does not rule out transient descendants. - * The implementation supports two variants: - * 1) list of direct descendants, eg "a b" - * 2) single transient schema node identifier, eg "a/b" - * The problem with combining (1) and (2) is that (2) results in a potential set of results, what - * would unique "a/b c/d" mean if both a/b and c/d returns a set? - * For (1): - * All key leafs MUST be present for all list entries. - * The combined values of all the leafs specified in the key are used to - * uniquely identify a list entry. All key leafs MUST be given values - * when a list entry is created. - */ -static int -check_unique_list(cxobj *x, - cxobj *xt, - yang_stmt *y, - yang_stmt *yu, - cxobj **xret) -{ - int retval = -1; - cg_var *cvi; /* unique node name */ - char **svec = NULL; /* vector of search results */ - size_t slen = 0; - char *xpath0 = NULL; - char *xpath1 = NULL; - int ret; - cvec *cvk; - cvec *nsc0 = NULL; - cvec *nsc1 = NULL; - - /* Check if multiple direct children */ - cvk = yang_cvec_get(yu); - if (cvec_len(cvk) > 1){ - retval = check_unique_list_direct(x, xt, y, yu, xret); - goto done; - } - cvi = cvec_i(cvk, 0); - if (cvi == NULL || (xpath0 = cv_string_get(cvi)) == NULL){ - clicon_err(OE_YANG, 0, "No descendant schemanode"); - goto done; - } - /* Check if direct schmeanode-id , ie not xpath */ - if (index(xpath0, '/') == NULL){ - retval = check_unique_list_direct(x, xt, y, yu, xret); - goto done; - } - /* Here proper xpath with at least one slash (can there be a descendant schemanodeid w/o slash?) */ - if (xml_nsctx_yang(yu, &nsc0) < 0) - goto done; - if ((ret = xpath2canonical(xpath0, nsc0, ys_spec(y), - &xpath1, &nsc1, NULL)) < 0) - goto done; - if (ret == 0) - goto fail; // XXX set xret - do { - /* Collect search results from one */ - if ((ret = unique_search_xpath(x, xpath1, nsc1, &svec, &slen)) < 0) - goto done; - if (ret == 0){ - if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) - goto done; - goto fail; - } - x = xml_child_each(xt, x, CX_ELMNT); - } while (x && y == xml_spec(x)); /* stop if list ends, others may follow */ - // ok: - /* It would be possible to cache vec here as an optimization */ - retval = 1; - done: - if (nsc0) - cvec_free(nsc0); - if (nsc1) - cvec_free(nsc1); - if (xpath1) - free(xpath1); - if (svec) - free(svec); - return retval; - fail: - retval = 0; - goto done; -} - -/*! Given a list, check if any min/max-elemants constraints apply - * @param[in] xp Parent of the xml list there are too few/many - * @param[in] y Yang spec of the failing list - * @param[in] nr Number of elements (like x) in the list - * @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 - * @see RFC7950 7.7.5 - */ -static int -check_min_max(cxobj *xp, - yang_stmt *y, - int nr, - cxobj **xret) -{ - int retval = -1; - yang_stmt *ymin; /* yang min */ - yang_stmt *ymax; /* yang max */ - cg_var *cv; - - if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){ - cv = yang_cv_get(ymin); - if (nr < cv_uint32_get(cv)){ - if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) - goto done; - goto fail; - } - } - if ((ymax = yang_find(y, Y_MAX_ELEMENTS, NULL)) != NULL){ - cv = yang_cv_get(ymax); - if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */ - nr > cv_uint32_get(cv)){ - if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) - goto done; - goto fail; - } - } - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Check if there is any empty list (no x elements) and check min-elements - * Note recurse for non-presence container - * @param[in] xt XML node - * @param[in] yt YANG node - * @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 - */ -static int -check_empty_list_minmax(cxobj *xt, - yang_stmt *ye, - cxobj **xret) -{ - int retval = -1; - int ret; - yang_stmt *yprev = NULL; - - if (yang_config(ye) == 1){ - if(yang_keyword_get(ye) == Y_CONTAINER && - yang_find(ye, Y_PRESENCE, NULL) == NULL){ - yprev = NULL; - while ((yprev = yn_each(ye, yprev)) != NULL) { - if ((ret = check_empty_list_minmax(xt, yprev, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - } - else 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; - } - } - 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. - */ -int -xml_yang_check_list_unique_minmax(cxobj *xt, - cxobj **xret) -{ - int retval = -1; - cxobj *x = NULL; - yang_stmt *y; - yang_stmt *yt; - yang_stmt *yprev = 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) */ - 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 elements */ - 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){ - if (yprev != NULL && y == yprev){ - /* Only lists and leaf-lists are allowed to be many - * This checks duplicate container and leafs - */ - if (xret && netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) - goto done; - goto fail; - } - yprev = y; /* Restart min/max count */ - continue; - } - /* Here only (leaf)lists */ - if (yprev != NULL){ /* There exists a previous (leaf)list */ - if (y == yprev){ /* 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(xt, yprev, nr, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - } - /* Here is only new list / leaf-list */ - yprev = y; /* Restart min/max count */ - nr = 1; - /* Gap analysis: Check if there is any empty list between y and yprev - * 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 ((ret = check_empty_list_minmax(xt, ye, 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 new (first element) of lists only - * First check unique keys direct children - */ - if ((ret = check_unique_list_direct(x, xt, y, y, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - /* Check if there is a unique constraint on the list - */ - 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, - * Two cases: - * 1) multiple direct children (no prefixes), eg "a b" - * 2) single xpath with canonical prefixes, eg "/ex:a/ex:b" - */ - if ((ret = check_unique_list(x, xt, y, yu, xret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - } /* while x */ - - /* yprev 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 (yprev){ - /* Check if the list length violates min/max */ - if ((ret = check_min_max(xt, yprev, 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 ((ret = check_empty_list_minmax(xt, ye, 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. @@ -1808,7 +1231,7 @@ xml_yang_validate_all(clicon_handle h, /* Check unique and min-max after choice test for example*/ if (yang_config(yt) != 0){ /* Checks if next level contains any unique list constraints */ - if ((ret = xml_yang_check_list_unique_minmax(xt, xret)) < 0) + if ((ret = xml_yang_minmax_recurse(xt, xret)) < 0) goto done; if (ret == 0) goto fail; @@ -1845,7 +1268,7 @@ xml_yang_validate_all_top(clicon_handle h, if ((ret = xml_yang_validate_all(h, x, xret)) < 1) return ret; } - if ((ret = xml_yang_check_list_unique_minmax(xt, xret)) < 1) + if ((ret = xml_yang_minmax_recurse(xt, xret)) < 1) return ret; return 1; } diff --git a/lib/src/clixon_validate_minmax.c b/lib/src/clixon_validate_minmax.c new file mode 100644 index 00000000..2fe718bf --- /dev/null +++ b/lib/src/clixon_validate_minmax.c @@ -0,0 +1,764 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * + * Check YANG validation for min/max-elements and unique + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#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_log.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_data.h" +#include "clixon_netconf_lib.h" +#include "clixon_options.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xml_io.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_yang_module.h" +#include "clixon_yang_type.h" +#include "clixon_xml_map.h" +#include "clixon_xml_bind.h" +#include "clixon_validate_minmax.h" + +/*! 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 + * @param[in] sorted Sorted by system, ie sorted by key, otherwise no assumption + * @retval 0 OK, entry is unique + * @retval -1 Duplicate detected + * @note This is currently quadratic complexity. It could be improved by inserting new element sorted and binary search. + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + */ +static int +unique_search_xpath(cxobj *x, + char *xpath, + cvec *nsc, + char ***svec, + size_t *slen) +{ + int retval = -1; + cxobj **xvec = NULL; + size_t xveclen; + int i; + int s; + cxobj *xi; + char *bi; + + /* Collect tuples */ + if (xpath_vec(x, nsc, "%s", &xvec, &xveclen, xpath) < 0) + goto done; + for (i=0; i 1 */ + while ((cvi = cvec_each(cvk, cvi)) != NULL){ + /* RFC7950: Sec 7.8.3.1: entries that do not have value for all + * referenced leafs are not taken into account */ + str = cv_string_get(cvi); + if (index(str, '/') != NULL){ + clicon_err(OE_YANG, 0, "Multiple descendant nodes not allowed (w /)"); + goto done; + } + if ((xi = xml_find(x, str)) == NULL) + break; + if ((bi = xml_body(xi)) == NULL) + break; + vec[i*clen + v++] = bi; + } + if (cvi==NULL){ + /* Last element (i) is newly inserted, see if it is already there */ + if (check_insert_duplicate(vec, i, clen, sorted) < 0){ + if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) + goto done; + goto fail; + } + } + x = xml_child_each(xt, x, CX_ELMNT); + i++; + } while (x && y == xml_spec(x)); /* stop if list ends, others may follow */ + ok: + /* It would be possible to cache vec here as an optimization */ + retval = 1; + done: + if (vec) + free(vec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Given a list with unique constraint, detect duplicates + * @param[in] x The first element in the list (on return the last) + * @param[in] xt The parent of x (a list) + * @param[in] y Its yang spec (Y_LIST) + * @param[in] yu A yang unique (Y_UNIQUE) for unique schema node ids or (Y_LIST) for list keys + * @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 + * Discussion: the RFC 7950 Sec 7.8.3: "constraints on valid list entries" + * The arguments are "descendant schema node identifiers". A direct interpretation is that + * this is for "direct" descendants, but it does not rule out transient descendants. + * The implementation supports two variants: + * 1) list of direct descendants, eg "a b" + * 2) single transient schema node identifier, eg "a/b" + * The problem with combining (1) and (2) is that (2) results in a potential set of results, what + * would unique "a/b c/d" mean if both a/b and c/d returns a set? + * For (1): + * All key leafs MUST be present for all list entries. + * The combined values of all the leafs specified in the key are used to + * uniquely identify a list entry. All key leafs MUST be given values + * when a list entry is created. + */ +static int +check_unique_list(cxobj *x, + cxobj *xt, + yang_stmt *y, + yang_stmt *yu, + cxobj **xret) +{ + int retval = -1; + cg_var *cvi; /* unique node name */ + char **svec = NULL; /* vector of search results */ + size_t slen = 0; + char *xpath0 = NULL; + char *xpath1 = NULL; + int ret; + cvec *cvk; + cvec *nsc0 = NULL; + cvec *nsc1 = NULL; + + /* Check if multiple direct children */ + cvk = yang_cvec_get(yu); + if (cvec_len(cvk) > 1){ + retval = check_unique_list_direct(x, xt, y, yu, xret); + goto done; + } + cvi = cvec_i(cvk, 0); + if (cvi == NULL || (xpath0 = cv_string_get(cvi)) == NULL){ + clicon_err(OE_YANG, 0, "No descendant schemanode"); + goto done; + } + /* Check if direct schmeanode-id , ie not xpath */ + if (index(xpath0, '/') == NULL){ + retval = check_unique_list_direct(x, xt, y, yu, xret); + goto done; + } + /* Here proper xpath with at least one slash (can there be a descendant schemanodeid w/o slash?) */ + if (xml_nsctx_yang(yu, &nsc0) < 0) + goto done; + if ((ret = xpath2canonical(xpath0, nsc0, ys_spec(y), + &xpath1, &nsc1, NULL)) < 0) + goto done; + if (ret == 0) + goto fail; // XXX set xret + do { + /* Collect search results from one */ + if ((ret = unique_search_xpath(x, xpath1, nsc1, &svec, &slen)) < 0) + goto done; + if (ret == 0){ + if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) + goto done; + goto fail; + } + x = xml_child_each(xt, x, CX_ELMNT); + } while (x && y == xml_spec(x)); /* stop if list ends, others may follow */ + // ok: + /* It would be possible to cache vec here as an optimization */ + retval = 1; + done: + if (nsc0) + cvec_free(nsc0); + if (nsc1) + cvec_free(nsc1); + if (xpath1) + free(xpath1); + if (svec) + free(svec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Given a list, check if any min/max-elemants constraints apply + * + * @param[in] xp Parent of the xml list there are too few/many (for error) + * @param[in] y Yang spec of the failing list + * @param[in] nr Number of elements (like x) in the list + * @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 + * @see RFC7950 7.7.5 + * @note No recurse for non-presence container is made, see eg xml_yang_minmax_recurse + */ +static int +check_minmax(cxobj *xp, + yang_stmt *y, + int nr, + cxobj **xret) +{ + int retval = -1; + yang_stmt *ymin; /* yang min */ + yang_stmt *ymax; /* yang max */ + cg_var *cv; + + if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){ + cv = yang_cv_get(ymin); + if (nr < cv_uint32_get(cv)){ + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) + goto done; + goto fail; + } + } + if ((ymax = yang_find(y, Y_MAX_ELEMENTS, NULL)) != NULL){ + cv = yang_cv_get(ymax); + if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */ + nr > cv_uint32_get(cv)){ + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) + goto done; + goto fail; + } + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Check if there is any empty list (no x elements) and check min-elements + * Note recurse for non-presence container + * @param[in] xt XML node + * @param[in] yt YANG node + * @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 + */ +static int +check_empty_list_minmax(cxobj *xt, + yang_stmt *ye, + cxobj **xret) +{ + int retval = -1; + int ret; + yang_stmt *yprev = NULL; + + if (yang_config(ye) == 1){ + if(yang_keyword_get(ye) == Y_CONTAINER && + yang_find(ye, Y_PRESENCE, NULL) == NULL){ + yprev = NULL; + while ((yprev = yn_each(ye, yprev)) != NULL) { + if ((ret = check_empty_list_minmax(xt, yprev, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + else 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_minmax(xt, ye, 0, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +static int +xml_yang_minmax_newlist(cxobj *x, + cxobj *xt, + yang_stmt *y, + cxobj **xret) +{ + int retval = -1; + yang_stmt *yu; + int ret; + + /* Here new (first element) of lists only + * First check unique keys direct children + */ + if ((ret = check_unique_list_direct(x, xt, y, y, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* Check if there is a unique constraint on the list + */ + 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, + * Two cases: + * 1) multiple direct children (no prefixes), eg "a b" + * 2) single xpath with canonical prefixes, eg "/ex:a/ex:b" + */ + if ((ret = check_unique_list(x, xt, y, yu, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Perform gap analysis in a child-vector interval [ye,y] + * + * Gap analysis here meaning if there is a list x with min-element constraint but there are no + * x elements in an interval of the children of xt. + * For example, assume the yang of xt is yt and is defined as: + * yt { + * list a; + * list x{ min-elements 1;}; // potential gap + * list b; + * } + * Further assume that xt is: + * + * Then a call to this function could be ye=a, y=b. + * By iterating over the children of yt in the interval [a,b] it will find "x" with a min-element + * constraint. + */ +static int +xml_yang_minmax_gap_analysis(cxobj *xt, + yang_stmt *y, + yang_stmt *yt, + yang_stmt **yep, + cxobj **xret) +{ + int retval = -1; + yang_stmt *ye; + int ret; + yang_stmt *ych = NULL; + + ye = *yep; + if (y && (ych = yang_choice(y)) == NULL) + ych = y; + /* Gap analysis: Check if there is any empty list between y and yprevlist + * 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 ((ret = check_empty_list_minmax(xt, ye, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + ye = yn_each(yt, ye); + } while(ye != NULL && /* to avoid livelock (shouldnt happen) */ + ye != ych); + } + *yep = ye; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Recursive minmax check + * + * 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. + * 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. + * + * Special handling: y / yprev + * nr yprev y eq? action + *------------------------------------- + * 1 list list eq nr++ + * 2 list list neq gap analysis; yprev: check-minmax; new: list key check + * 3 null list neq gap analysis, new: list key check + * 4 nolist list neq gap analysis, new: list key check + * 5 nolist nolist eq error + * 6 nolist nolist neq gap analysis; nopresence-check; + * 7 null nolist neq gap analysis; nopresence-check; + * 8 list nolist neq gap analysis; yprev: check-minmax; nopresence-check; + * @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 + * Note that many checks, ie all except gap analysis, may be unnecessary if this fn + * is called in a recursive environment, since the recursion being made here will + * be made in that environment anyway and thus leading to double checks. + */ +int +xml_yang_minmax_recurse(cxobj *xt, + cxobj **xret) +{ + int retval = -1; + cxobj *x = NULL; + yang_stmt *y; + yang_stmt *yprev = NULL; + yang_stmt *ye = NULL; /* yang each list to catch emtpy */ + enum rfc_6020 keyw; + int nr = 0; + int ret; + yang_stmt *yt; + + yt = xml_spec(xt); /* If yt == NULL, then no gap-analysis is done */ + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ + clicon_debug(1, "%s x:%s", __FUNCTION__, xml_name(x)); + if ((y = xml_spec(x)) == NULL) + continue; + keyw = yang_keyword_get(y); + if (keyw == Y_LIST || keyw == Y_LEAF_LIST){ + /* equal: just continue*/ + if (y == yprev){ + nr++; + continue; + } + /* gap analysis */ + if ((ret = xml_yang_minmax_gap_analysis(xt, y, yt, &ye, xret)) < 0) + goto done; + /* check-minmax of previous list */ + if (ret && + yprev && + (yang_keyword_get(yprev) == Y_LIST || yang_keyword_get(yprev) == Y_LEAF_LIST)){ + /* Check if the list length violates min/max */ + if ((ret = check_minmax(xt, yprev, nr, xret)) < 0) + goto done; + } + nr=1; + /* new list check */ + if (ret && + keyw == Y_LIST && + (ret = xml_yang_minmax_newlist(x, xt, y, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + yprev = y; + } + else{ + /* equal: error */ + if (y == yprev){ + /* Only lists and leaf-lists are allowed to be more than one */ + if (xret && netconf_minmax_elements_xml(xret, xml_parent(x), xml_name(x), 1) < 0) + goto done; + goto fail; + } + /* gap analysis */ + if ((ret = xml_yang_minmax_gap_analysis(xt, y, yt, &ye, xret)) < 0) + goto done; + /* check-minmax of previous list */ + if (ret && + yprev && + (yang_keyword_get(yprev) == Y_LIST || yang_keyword_get(yprev) == Y_LEAF_LIST)){ + /* Check if the list length violates min/max */ + if ((ret = check_minmax(xt, yprev, nr, xret)) < 0) + goto done; + nr = 0; + } + if (ret == 0) + goto fail; + if (keyw == Y_CONTAINER && + yang_find(y, Y_PRESENCE, NULL) == NULL){ + yang_stmt *yc = NULL; + while ((yc = yn_each(y, yc)) != NULL) { + if ((ret = xml_yang_minmax_recurse(x, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + yprev = y; + } + } + /* After traversal checks; + gap analysis */ +#if 1 + /* Variant of gap analysis, does not use ych + * XXX: try to unify with xml_yang_minmax_gap_analysis() + */ + if ((ye = yn_each(yt, ye)) != NULL){ + do { + if ((ret = check_empty_list_minmax(xt, ye, xret)) < 0) + goto done; + if (ret == 0) + goto fail; + } while((ye = yn_each(yt, ye)) != NULL); + } + ret = 1; +#else + if ((ret = xml_yang_minmax_gap_analysis(xt, NULL, yt, &ye, xret)) < 0) + goto done; +#endif + /* check-minmax of previous list */ + if (ret && + yprev && + (yang_keyword_get(yprev) == Y_LEAF || yang_keyword_get(yprev) == Y_LEAF_LIST)){ + /* Check if the list length violates min/max */ + if ((ret = check_minmax(xt, yprev, nr, xret)) < 0) + goto done; + } + if (ret == 0) + goto fail; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index a705b719..7fb7b087 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -881,6 +881,8 @@ xml_child_order(cxobj *xp, * @param[in] xparent xml tree node whose children should be iterated * @param[in] xprev previous child, or NULL on init * @param[in] type matching type or -1 for any + * @retval xn Next XML node + * @retval NULL End of list * @code * cxobj *x = NULL; * while ((x = xml_child_each(x_top, x, -1)) != NULL) { @@ -905,9 +907,7 @@ xml_child_order(cxobj *xp, * xprev = x; * } * @endcode -#ifdef XML_EXPLICIT_INDEX * @see xml_child_index_each -#endif XML_EXPLICIT_INDEX */ cxobj * xml_child_each(cxobj *xparent, diff --git a/test/lib.sh b/test/lib.sh index b82b8aaf..ba7ddf13 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -989,6 +989,7 @@ EOF # clixon tester read from file for large tests # Arguments: # - Command +# - Expected retval # - Filename to pipe to stdin # - expected stdout outcome function expecteof_file(){ diff --git a/test/test_choice.sh b/test/test_choice.sh index d0f30666..f3e08a81 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -373,13 +373,7 @@ new "netconf choice multiple items second" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " 0 1 -" "" "" - -new "netconf get items second" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "01" "" - -new "netconf validate items second expect fail" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "protocoloperation-failedtoo-many-elementserror/system/mb" +" "" "protocoloperation-failedtoo-many-elementserror/rpc/edit-config/config/system/mb" new "netconf choice multiple items mix" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " diff --git a/test/test_db.sh b/test/test_db.sh index 3abf4a84..f3335889 100755 --- a/test/test_db.sh +++ b/test/test_db.sh @@ -189,7 +189,7 @@ EOF new "test params: -f $cfg" for format in xml json; do - for pretty in false true json; do + for pretty in false true; do new "test db $format pretty=$pretty" testrun xml false done diff --git a/test/test_helloworld.sh b/test/test_helloworld.sh index a13db400..2aa2ffb2 100755 --- a/test/test_helloworld.sh +++ b/test/test_helloworld.sh @@ -134,7 +134,7 @@ if [ $? -ne 0 ]; then err 0 $r fi if [ "$ret" != "clixon-hello:hello world;" ]; then - err "$ret" "clixon-hello:hello world;" + err "clixon-hello:hello world;" "$ret" fi new "netconf edit-config" diff --git a/test/test_minmax.sh b/test/test_minmax.sh index 55ea8fc2..387a0f38 100755 --- a/test/test_minmax.sh +++ b/test/test_minmax.sh @@ -152,7 +152,6 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "minmax: replace with 0x a1,b1" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "replace" "" "" -#XXX echo "$DEFAULTHELLO]]>]]>" |$clixon_netconf -qf $cfg new "minmax: validate should fail" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "protocoloperation-failedtoo-few-elementserror/c/a1" @@ -204,16 +203,13 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "minmax choice: many a2 validate ok" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" -new "minmax choice: too many a1" +new "minmax choice: too many a1 should fail" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "replace 0 1 2 0 -" "" "" - -new "minmax choice: too many validate should fail" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "protocoloperation-failedtoo-many-elementserror/c2/a1" +" "" "protocoloperation-failedtoo-many-elementserror/rpc/edit-config/config/c2/a1" new "netconf discard-changes" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" @@ -245,7 +241,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " # Also need to do commit tests when running has elements and candidate has fewer new "Set maximal: 2x a1,b1" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "replace0110" "" "" +expecteof_netconf "$clixon_netconf -qf $cfg -D 1" 0 "$DEFAULTHELLO" "replace0110" "" "" new "netconf commit" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" @@ -265,18 +261,35 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "delete c" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none" "" "" +# 0 new "add empty list entry with a min-element leaf within a non-presence container" expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "0" "" "" new "minmax: validate should fail, there should be a b2ll trailing" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "protocoloperation-failedtoo-few-elementserror/c3/b2\[kb=\"0\"\]/b2ll" "" +# 042 new "add b3ll after missing b2ll" expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "042" "" "" new "minmax: validate should fail, there should be a b2ll before b3ll" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "protocoloperation-failedtoo-few-elementserror/c3/b2\[kb=\"0\"\]/b2ll" "" +# Positive tests +# 07142 +new "add b2ll to satisfy min-elements, gap" +expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "071" "" "" + +new "validate expect OK xxx" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +# 071 +new "delete b3ll, empty" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none042" "" "" + +new "validate expect OK" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill diff --git a/test/test_unique.sh b/test/test_unique.sh index ea0c6ff1..0714e77d 100755 --- a/test/test_unique.sh +++ b/test/test_unique.sh @@ -106,13 +106,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " 192.0.2.1 25 -" "" "" - -new "netconf validate (should fail)" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-faileddata-not-uniqueerroripport" - -new "netconf discard-changes" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" +" "" "applicationoperation-faileddata-not-uniqueerroripport" new "Add valid example" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "replace @@ -151,11 +145,15 @@ new "netconf discard-changes" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" # Then test single-field case -new "Add not valid example" +new "Add not valid example: 1st single" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "replace smtp 192.0.2.1 +" "" "" + +new "Add not valid example: 2nd single" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "merge http 192.0.2.1 @@ -178,7 +176,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " # Then test composite case (detect duplicates among other elements) # and also unordered -new "Add not valid example" +new "Add not valid example3" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "replace other