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)
This commit is contained in:
Olof hagsand 2022-09-01 10:51:06 +02:00
parent c8bf718db8
commit 2eb9c6cda1
17 changed files with 867 additions and 614 deletions

View file

@ -61,6 +61,16 @@ Expected: September 2022
* idle-timeout for periodic call-homes. * idle-timeout for periodic call-homes.
* An example util client is `clixon_restconf_callhome_client.c` used in test cases * 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 ### Corrected Bugs
* Fixed: [YANG ordering fails for nested choice and action](https://github.com/clicon/clixon/issues/356) * Fixed: [YANG ordering fails for nested choice and action](https://github.com/clicon/clixon/issues/356)

View file

@ -393,7 +393,7 @@ from_client_edit_config(clicon_handle h,
} }
/* Limited validation of incoming payload /* 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; goto done;
/* xmldb_put (difflist handling) requires list keys */ /* xmldb_put (difflist handling) requires list keys */
if (ret==1 && (ret = xml_yang_validate_list_key_only(xc, &xret)) < 0) if (ret==1 && (ret = xml_yang_validate_list_key_only(xc, &xret)) < 0)

View file

@ -96,6 +96,7 @@ extern "C" {
#include <clixon/clixon_xml_map.h> #include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_bind.h> #include <clixon/clixon_xml_bind.h>
#include <clixon/clixon_xml_io.h> #include <clixon/clixon_xml_io.h>
#include <clixon/clixon_validate_minmax.h>
#include <clixon/clixon_validate.h> #include <clixon/clixon_validate.h>
#include <clixon/clixon_datastore.h> #include <clixon/clixon_datastore.h>
#include <clixon/clixon_xpath_ctx.h> #include <clixon/clixon_xpath_ctx.h>

View file

@ -34,7 +34,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* *
* XML code * Check YANG validation
*/ */
#ifndef _CLIXON_VALIDATE_H_ #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(clicon_handle h, cxobj *xrpc, cxobj **xret);
int xml_yang_validate_rpc_reply(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_add(clicon_handle h, cxobj *xt, cxobj **xret);
int xml_yang_validate_list_key_only(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); int xml_yang_validate_all(clicon_handle h, cxobj *xt, cxobj **xret);

View file

@ -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_ */

View file

@ -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); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc);
int xml_child_order(cxobj *xn, cxobj *xc); int xml_child_order(cxobj *xn, cxobj *xc);
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); 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_child_insert_pos(cxobj *x, cxobj *xc, int i);
int xml_childvec_set(cxobj *x, int len); int xml_childvec_set(cxobj *x, int len);
cxobj **xml_childvec_get(cxobj *x); cxobj **xml_childvec_get(cxobj *x);

View file

@ -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.c clixon_yang_type.c clixon_yang_module.c \
clixon_yang_parse_lib.c clixon_yang_sub_parse.c \ clixon_yang_parse_lib.c clixon_yang_sub_parse.c \
clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.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_hash.c clixon_options.c clixon_data.c clixon_plugin.c \
clixon_proto.c clixon_proto_client.c \ clixon_proto.c clixon_proto_client.c \
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \

View file

@ -1165,7 +1165,10 @@ xmldb_get0_clear(clicon_handle h,
/* Remove global defaults and empty non-presence containers */ /* Remove global defaults and empty non-presence containers */
if (xml_defaults_nopresence(x, 1) < 0) if (xml_defaults_nopresence(x, 1) < 0)
goto done; 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, xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)(XML_FLAG_MARK|XML_FLAG_ADD|XML_FLAG_CHANGE)); (void*)(XML_FLAG_MARK|XML_FLAG_ADD|XML_FLAG_CHANGE));
ok: ok:

View file

@ -34,9 +34,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* *
* XML code * Check YANG validation
*
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */ #include "clixon_config.h" /* generated by config & autoconf */
@ -79,6 +77,7 @@
#include "clixon_yang_type.h" #include "clixon_yang_type.h"
#include "clixon_xml_map.h" #include "clixon_xml_map.h"
#include "clixon_xml_bind.h" #include "clixon_xml_bind.h"
#include "clixon_validate_minmax.h"
#include "clixon_validate.h" #include "clixon_validate.h"
/*! Validate xml node of type leafref, ensure the value is one of that path's reference /*! 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; 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<xveclen; i++){
xi = xvec[i];
if ((bi = xml_body(xi)) == NULL)
break;
/* Check if bi is duplicate?
* XXX: sort svec?
*/
for (s=0; s<(*slen); s++){
if (strcmp(bi, (*svec)[s]) == 0){
goto fail;
}
}
(*slen) ++;
if (((*svec) = realloc((*svec), (*slen)*sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "realloc");
goto done;
}
(*svec)[(*slen)-1] = bi;
} /* i search results */
retval = 1;
done:
if (xvec)
free(xvec);
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
* @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
check_insert_duplicate(char **vec,
int i1,
int vlen,
int sorted)
{
int i;
int v;
char *b;
if (sorted){
/* Just go look at previous element to see if it is duplicate (sorted by system) */
if (i1 == 0)
return 0;
i = i1-1;
for (v=0; v<vlen; v++){
b = vec[i*vlen+v];
if (b == NULL || strcmp(b, vec[i1*vlen+v]))
return 0;
}
/* here we have passed thru all keys of previous element and they are all equal */
return -1;
}
else{
for (i=0; i<i1; i++){
for (v=0; v<vlen; v++){
b = vec[i*vlen+v];
if (b == NULL || strcmp(b, vec[i1*vlen+v]))
break;
}
if (v==vlen) /* duplicate */
break;
}
return i==i1?0:-1;
}
}
/*! 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 (cbret 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_direct(cxobj *x,
cxobj *xt,
yang_stmt *y,
yang_stmt *yu,
cxobj **xret)
{
int retval = -1;
cg_var *cvi; /* unique node name */
cxobj *xi;
char **vec = NULL; /* 2xmatrix */
int clen;
int i;
int v;
char *bi;
int sorted;
char *str;
cvec *cvk;
cvk = yang_cvec_get(yu);
/* If list and is sorted by system, then it is assumed elements are in key-order which is optimized
* Other cases are "unique" constraint or list sorted by user which is quadratic in complexity
* This second case COULD be optimized if binary insert is made on the vec vector.
*/
sorted = (yang_keyword_get(yu) == Y_LIST &&
yang_find(y, Y_ORDERED_BY, "user") == NULL);
cvk = yang_cvec_get(yu);
/* nr of unique elements to check */
if ((clen = cvec_len(cvk)) == 0){
/* No keys: no checks necessary */
goto ok;
}
if ((vec = calloc(clen*xml_child_nr(xt), sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
/* A vector is built with key-values, for each iteration check "backward" in the vector
* for duplicates
*/
i = 0; /* x element index */
do {
cvi = NULL;
v = 0; /* index in each tuple */
/* XXX Quadratic if clen > 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 /*! Validate a single XML node with yang specification for added entry
* 1. Check if mandatory leafs present as subs. * 1. Check if mandatory leafs present as subs.
* 2. Check leaf values, eg int ranges and string regexps. * 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*/ /* Check unique and min-max after choice test for example*/
if (yang_config(yt) != 0){ if (yang_config(yt) != 0){
/* Checks if next level contains any unique list constraints */ /* 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; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -1845,7 +1268,7 @@ xml_yang_validate_all_top(clicon_handle h,
if ((ret = xml_yang_validate_all(h, x, xret)) < 1) if ((ret = xml_yang_validate_all(h, x, xret)) < 1)
return ret; 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 ret;
return 1; return 1;
} }

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* 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<xveclen; i++){
xi = xvec[i];
if ((bi = xml_body(xi)) == NULL)
break;
/* Check if bi is duplicate?
* XXX: sort svec?
*/
for (s=0; s<(*slen); s++){
if (strcmp(bi, (*svec)[s]) == 0){
goto fail;
}
}
(*slen) ++;
if (((*svec) = realloc((*svec), (*slen)*sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "realloc");
goto done;
}
(*svec)[(*slen)-1] = bi;
} /* i search results */
retval = 1;
done:
if (xvec)
free(xvec);
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
* @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
check_insert_duplicate(char **vec,
int i1,
int vlen,
int sorted)
{
int i;
int v;
char *b;
if (sorted){
/* Just go look at previous element to see if it is duplicate (sorted by system) */
if (i1 == 0)
return 0;
i = i1-1;
for (v=0; v<vlen; v++){
b = vec[i*vlen+v];
if (b == NULL || strcmp(b, vec[i1*vlen+v]))
return 0;
}
/* here we have passed thru all keys of previous element and they are all equal */
return -1;
}
else{
for (i=0; i<i1; i++){
for (v=0; v<vlen; v++){
b = vec[i*vlen+v];
if (b == NULL || strcmp(b, vec[i1*vlen+v]))
break;
}
if (v==vlen) /* duplicate */
break;
}
return i==i1?0:-1;
}
}
/*! 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 (cbret 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_direct(cxobj *x,
cxobj *xt,
yang_stmt *y,
yang_stmt *yu,
cxobj **xret)
{
int retval = -1;
cg_var *cvi; /* unique node name */
cxobj *xi;
char **vec = NULL; /* 2xmatrix */
int clen;
int i;
int v;
char *bi;
int sorted;
char *str;
cvec *cvk;
cvk = yang_cvec_get(yu);
/* If list and is sorted by system, then it is assumed elements are in key-order which is optimized
* Other cases are "unique" constraint or list sorted by user which is quadratic in complexity
* This second case COULD be optimized if binary insert is made on the vec vector.
*/
sorted = (yang_keyword_get(yu) == Y_LIST &&
yang_find(y, Y_ORDERED_BY, "user") == NULL);
cvk = yang_cvec_get(yu);
/* nr of unique elements to check */
if ((clen = cvec_len(cvk)) == 0){
/* No keys: no checks necessary */
goto ok;
}
if ((vec = calloc(clen*xml_child_nr(xt), sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
/* A vector is built with key-values, for each iteration check "backward" in the vector
* for duplicates
*/
i = 0; /* x element index */
do {
cvi = NULL;
v = 0; /* index in each tuple */
/* XXX Quadratic if clen > 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:
* <xt><a><a><b><b></xt>
* 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;
}

View file

@ -881,6 +881,8 @@ xml_child_order(cxobj *xp,
* @param[in] xparent xml tree node whose children should be iterated * @param[in] xparent xml tree node whose children should be iterated
* @param[in] xprev previous child, or NULL on init * @param[in] xprev previous child, or NULL on init
* @param[in] type matching type or -1 for any * @param[in] type matching type or -1 for any
* @retval xn Next XML node
* @retval NULL End of list
* @code * @code
* cxobj *x = NULL; * cxobj *x = NULL;
* while ((x = xml_child_each(x_top, x, -1)) != NULL) { * while ((x = xml_child_each(x_top, x, -1)) != NULL) {
@ -905,9 +907,7 @@ xml_child_order(cxobj *xp,
* xprev = x; * xprev = x;
* } * }
* @endcode * @endcode
#ifdef XML_EXPLICIT_INDEX
* @see xml_child_index_each * @see xml_child_index_each
#endif XML_EXPLICIT_INDEX
*/ */
cxobj * cxobj *
xml_child_each(cxobj *xparent, xml_child_each(cxobj *xparent,

View file

@ -989,6 +989,7 @@ EOF
# clixon tester read from file for large tests # clixon tester read from file for large tests
# Arguments: # Arguments:
# - Command # - Command
# - Expected retval
# - Filename to pipe to stdin # - Filename to pipe to stdin
# - expected stdout outcome # - expected stdout outcome
function expecteof_file(){ function expecteof_file(){

View file

@ -373,13 +373,7 @@ new "netconf choice multiple items second"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><system xmlns=\"urn:example:config\" nc:operation=\"replace\" xmlns:nc=\"$BASENS\"> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><system xmlns=\"urn:example:config\" nc:operation=\"replace\" xmlns:nc=\"$BASENS\">
<mb>0</mb> <mb>0</mb>
<mb>1</mb> <mb>1</mb>
</system></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" </system></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>/rpc/edit-config/config/system/mb</error-path></rpc-error></rpc-reply>"
new "netconf get items second"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "<rpc-reply $DEFAULTNS><data><system xmlns=\"urn:example:config\"><mb>0</mb><mb>1</mb></system>" ""
new "netconf validate items second expect fail"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>/system/mb</error-path></rpc-error></rpc-reply>"
new "netconf choice multiple items mix" new "netconf choice multiple items mix"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><system xmlns=\"urn:example:config\" nc:operation=\"replace\" xmlns:nc=\"$BASENS\"> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><system xmlns=\"urn:example:config\" nc:operation=\"replace\" xmlns:nc=\"$BASENS\">

View file

@ -189,7 +189,7 @@ EOF
new "test params: -f $cfg" new "test params: -f $cfg"
for format in xml json; do 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" new "test db $format pretty=$pretty"
testrun xml false testrun xml false
done done

View file

@ -134,7 +134,7 @@ if [ $? -ne 0 ]; then
err 0 $r err 0 $r
fi fi
if [ "$ret" != "clixon-hello:hello world;" ]; then if [ "$ret" != "clixon-hello:hello world;" ]; then
err "$ret" "clixon-hello:hello world;" err "clixon-hello:hello world;" "$ret"
fi fi
new "netconf edit-config" new "netconf edit-config"

View file

@ -152,7 +152,6 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
new "minmax: replace with 0x a1,b1" new "minmax: replace with 0x a1,b1"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"/></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"/></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
#XXX echo "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" |$clixon_netconf -qf $cfg
new "minmax: validate should fail" new "minmax: validate should fail"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>/c/a1</error-path></rpc-error></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>/c/a1</error-path></rpc-error></rpc-reply>"
@ -204,16 +203,13 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
new "minmax choice: many a2 validate ok" new "minmax choice: many a2 validate ok"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "minmax choice: too many a1" new "minmax choice: too many a1 should fail"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c2 xmlns=\"urn:example:clixon\"> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c2 xmlns=\"urn:example:clixon\">
<a1><k>0</k></a1> <a1><k>0</k></a1>
<a1><k>1</k></a1> <a1><k>1</k></a1>
<a1><k>2</k></a1> <a1><k>2</k></a1>
<b1>0</b1> <b1>0</b1>
</c2></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" </c2></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>/rpc/edit-config/config/c2/a1</error-path></rpc-error></rpc-reply>"
new "minmax choice: too many validate should fail"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>/c2/a1</error-path></rpc-error></rpc-reply>"
new "netconf discard-changes" new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
@ -245,7 +241,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
# Also need to do commit tests when running has elements and candidate has fewer # Also need to do commit tests when running has elements and candidate has fewer
new "Set maximal: 2x a1,b1" new "Set maximal: 2x a1,b1"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><a1><k>0</k></a1><a1><k>1</k></a1><b1>1</b1><b1>0</b1></c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg -D 1" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><a1><k>0</k></a1><a1><k>1</k></a1><b1>1</b1><b1>0</b1></c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "netconf commit" new "netconf commit"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><commit/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><commit/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
@ -265,18 +261,35 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
new "delete c" new "delete c"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><default-operation>none</default-operation><config><c xmlns=\"urn:example:clixon\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"delete\"/></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><default-operation>none</default-operation><config><c xmlns=\"urn:example:clixon\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"delete\"/></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
# <b2><kb>0</kb></b2>
new "add empty list entry with a min-element leaf within a non-presence container" 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" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><config><c3 xmlns=\"urn:example:clixon\"><b2><kb>0</kb></b2></c3></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><config><c3 xmlns=\"urn:example:clixon\"><b2><kb>0</kb></b2></c3></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "minmax: validate should fail, there should be a b2ll trailing" new "minmax: validate should fail, there should be a b2ll trailing"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>/c3/b2\[kb=\"0\"\]/b2ll</error-path></rpc-error></rpc-reply>" "" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>/c3/b2\[kb=\"0\"\]/b2ll</error-path></rpc-error></rpc-reply>" ""
# <b2><kb>0</kb><b3ll>42</b3ll></b2>
new "add b3ll after missing b2ll" new "add b3ll after missing b2ll"
expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><config><c3 xmlns=\"urn:example:clixon\"><b2><kb>0</kb><b3ll>42</b3ll></b2></c3></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><config><c3 xmlns=\"urn:example:clixon\"><b2><kb>0</kb><b3ll>42</b3ll></b2></c3></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "minmax: validate should fail, there should be a b2ll before b3ll" new "minmax: validate should fail, there should be a b2ll before b3ll"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>/c3/b2\[kb=\"0\"\]/b2ll</error-path></rpc-error></rpc-reply>" "" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "<rpc-reply $DEFAULTNS><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>/c3/b2\[kb=\"0\"\]/b2ll</error-path></rpc-error></rpc-reply>" ""
# Positive tests
# <b2><kb>0</kb><b2c><b2ll>71</b2ll></b2c><b3ll>42</b3ll></b2>
new "add b2ll to satisfy min-elements, gap"
expecteof_netconf "$clixon_netconf -qf $cfg -D 1 -l s" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><config><c3 xmlns=\"urn:example:clixon\"><b2><kb>0</kb><b2c><b2ll>71</b2ll></b2c></b2></c3></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "validate expect OK xxx"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
# <b2><kb>0</kb><b2c><b2ll>71</b2ll></b2c></b2>
new "delete b3ll, empty"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS $DEFAULTNS><edit-config><target><candidate/></target><default-operation>none</default-operation><config><c3 xmlns=\"urn:example:clixon\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><b2><kb>0</kb><b3ll nc:operation=\"delete\">42</b3ll></b2></c3></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "validate expect OK"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "Kill backend" new "Kill backend"
# Check if premature kill # Check if premature kill

View file

@ -106,13 +106,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
<ip>192.0.2.1</ip> <ip>192.0.2.1</ip>
<port>25</port> <port>25</port>
</server> </server>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" </c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique>ip</non-unique><non-unique>port</non-unique></error-info></rpc-error></rpc-reply>"
new "netconf validate (should fail)"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique>ip</non-unique><non-unique>port</non-unique></error-info></rpc-error></rpc-reply>"
new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Add valid example" new "Add valid example"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><server> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><server>
@ -151,11 +145,15 @@ new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
# Then test single-field case # 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" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><single> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><single>
<name>smtp</name> <name>smtp</name>
<ip>192.0.2.1</ip> <ip>192.0.2.1</ip>
</single> </single>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Add not valid example: 2nd single"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>merge</default-operation><config><c xmlns=\"urn:example:clixon\">
<single> <single>
<name>http</name> <name>http</name>
<ip>192.0.2.1</ip> <ip>192.0.2.1</ip>
@ -178,7 +176,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
# Then test composite case (detect duplicates among other elements) # Then test composite case (detect duplicates among other elements)
# and also unordered # and also unordered
new "Add not valid example" new "Add not valid example3"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<b>other</b> <b>other</b>
<single> <single>