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:
parent
c8bf718db8
commit
2eb9c6cda1
17 changed files with 867 additions and 614 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
48
lib/clixon/clixon_validate_minmax.h
Normal file
48
lib/clixon/clixon_validate_minmax.h
Normal 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_ */
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
764
lib/src/clixon_validate_minmax.c
Normal file
764
lib/src/clixon_validate_minmax.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(){
|
||||||
|
|
|
||||||
|
|
@ -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\">
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue